update_41.py 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165
  1. #!/usr/bin/env python3
  2. # Contest Management System - http://cms-dev.github.io/
  3. # Copyright © 2018 Luca Wehrstedt <luca.wehrstedt@gmail.com>
  4. # Copyright © 2019 Stefano Maggiolo <s.maggiolo@gmail.com>
  5. #
  6. # This program is free software: you can redistribute it and/or modify
  7. # it under the terms of the GNU Affero General Public License as
  8. # published by the Free Software Foundation, either version 3 of the
  9. # License, or (at your option) any later version.
  10. #
  11. # This program is distributed in the hope that it will be useful,
  12. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. # GNU Affero General Public License for more details.
  15. #
  16. # You should have received a copy of the GNU Affero General Public License
  17. # along with this program. If not, see <http://www.gnu.org/licenses/>.
  18. """A class to update a dump created by CMS.
  19. Used by DumpImporter and DumpUpdater.
  20. This updater makes sure that the constraints on codenames, filenames,
  21. filename schemas and digests hold.
  22. """
  23. import logging
  24. import re
  25. logger = logging.getLogger(__name__)
  26. # Fields that contain codenames.
  27. CODENAME_FIELDS = {
  28. "Contest": ["name"],
  29. "Task": ["name"],
  30. "Testcase": ["codename"],
  31. "Admin": ["username"],
  32. "User": ["username"],
  33. "Team": ["code"]
  34. }
  35. # Fields that contain filenames.
  36. FILENAME_FIELDS = {
  37. "Executable": ["filename"],
  38. "UserTestManager": ["filename"],
  39. "UserTestExecutable": ["filename"],
  40. "PrintJob": ["filename"],
  41. "Attachment": ["filename"],
  42. "Manager": ["filename"]
  43. }
  44. # Fields that contain filename schemas.
  45. FILENAME_SCHEMA_FIELDS = {
  46. "File": ["filename"],
  47. "UserTestFile": ["filename"]
  48. }
  49. # Fields that contain arrays of filename schemas.
  50. FILENAME_SCHEMA_ARRAY_FIELDS = {
  51. "Task": ["submission_format"]
  52. }
  53. # Fields that contain digests.
  54. DIGEST_FIELDS = {
  55. "Statement": ["digest"],
  56. "Attachment": ["digest"],
  57. "Manager": ["digest"],
  58. "Testcase": ["input", "output"],
  59. "UserTest": ["input"],
  60. "UserTestFile": ["digest"],
  61. "UserTestManager": ["digest"],
  62. "UserTestResult": ["output"],
  63. "UserTestExecutable": ["digest"],
  64. "File": ["digest"],
  65. "Executable": ["digest"],
  66. "PrintJob": ["digest"]
  67. }
  68. class Updater:
  69. def __init__(self, data):
  70. assert data["_version"] == 40
  71. self.objs = data
  72. self.bad_codenames = []
  73. self.bad_filenames = []
  74. self.bad_filename_schemas = []
  75. self.bad_digests = []
  76. def check_codename(self, class_, attr, codename):
  77. if not re.match("^[A-Za-z0-9_-]+$", codename):
  78. self.bad_codenames.append("%s.%s" % (class_, attr))
  79. def check_filename(self, class_, attr, filename):
  80. if not re.match('^[A-Za-z0-9_.-]+$', filename) \
  81. or filename in {',', '..'}:
  82. self.bad_filenames.append("%s.%s" % (class_, attr))
  83. def check_filename_schema(self, class_, attr, schema):
  84. if not re.match('^[A-Za-z0-9_.-]+(\\.%l)?$', schema) \
  85. or schema in {'.', '..'}:
  86. self.bad_filename_schemas.append("%s.%s" % (class_, attr))
  87. def check_digest(self, class_, attr, digest):
  88. if digest is not None and not re.match('^([0-9a-f]{40}|x)$', digest):
  89. self.bad_digests.append("%s.%s" % (class_, attr))
  90. def run(self):
  91. for k, v in self.objs.items():
  92. if k.startswith("_"):
  93. continue
  94. if v["_class"] in CODENAME_FIELDS:
  95. for attr in CODENAME_FIELDS[v["_class"]]:
  96. self.check_codename(v["_class"], attr, v[attr])
  97. if v["_class"] in FILENAME_FIELDS:
  98. for attr in FILENAME_FIELDS[v["_class"]]:
  99. self.check_filename(v["_class"], attr, v[attr])
  100. if v["_class"] in FILENAME_SCHEMA_FIELDS:
  101. for attr in FILENAME_SCHEMA_FIELDS[v["_class"]]:
  102. self.check_filename_schema(v["_class"], attr, v[attr])
  103. if v["_class"] in FILENAME_SCHEMA_ARRAY_FIELDS:
  104. for attr in FILENAME_SCHEMA_ARRAY_FIELDS[v["_class"]]:
  105. for schema in v[attr]:
  106. self.check_filename_schema(v["_class"], attr, schema)
  107. if v["_class"] in DIGEST_FIELDS:
  108. for attr in DIGEST_FIELDS[v["_class"]]:
  109. self.check_digest(v["_class"], attr, v[attr])
  110. bad = False
  111. if self.bad_codenames:
  112. logger.error(
  113. "The following fields contained invalid codenames: %s. "
  114. "They can only contain letters, digits, underscores and "
  115. "dashes."
  116. % ", ".join(self.bad_codenames))
  117. bad = True
  118. if self.bad_filenames:
  119. logger.error(
  120. "The following fields contained invalid filenames: %s. "
  121. "They can only contain letters, digits, underscores, dashes "
  122. "and periods and cannot be '.' or '..'."
  123. % ", ".join(self.bad_filenames))
  124. bad = True
  125. if self.bad_filename_schemas:
  126. logger.error(
  127. "The following fields contained invalid filename schemas: %s. "
  128. "They can only contain letters, digits, underscores, dashes "
  129. "and periods, end with '.%%l' and cannot be '.' or '..'."
  130. % ", ".join(self.bad_filename_schemas))
  131. bad = True
  132. if self.bad_digests:
  133. logger.error(
  134. "The following fields contained invalid digests: %s. "
  135. "They must be 40-character long lowercase hex values, or 'x'."
  136. % ", ".join(self.bad_digests))
  137. bad = True
  138. if bad:
  139. raise ValueError("Some data was invalid.")
  140. return self.objs