def post(self): submission_id = self.get_argument("submission_id", "") # Decrypt submission_id. try: submission_id = decrypt_number(submission_id) except ValueError: # We reply with Forbidden if the given ID cannot be # decrypted. logger.warning( "User %s tried to play a token " "on an undecryptable submission_id." % self.current_user.username ) raise tornado.web.HTTPError(403) # Find submission and check it is of the current user. submission = Submission.get_from_id(submission_id, self.sql_session) if submission is None or submission.user != self.current_user: logger.warning( "User %s tried to play a token " "on an unexisting submission_id." % self.current_user.username ) raise tornado.web.HTTPError(404) # Don't trust the user, check again if (s)he can really play # the token. timestamp = int(time.time()) if self.contest.tokens_available(self.current_user.username, submission.task.name, timestamp)[0] <= 0: logger.warning("User %s tried to play a token " "when it shouldn't." % self.current_user.username) # Add "no luck" notification self.application.service.add_notification( self.current_user.username, timestamp, self._("Token request discarded"), self._("Your request has been discarded because you have no " "tokens available."), ) self.redirect("/tasks/%s" % encrypt_number(submission.task.id)) return token = Token(timestamp, submission) self.sql_session.add(token) self.sql_session.commit() # Inform ScoringService and eventually the ranking that the # token has been played. self.application.service.scoring_service.submission_tokened(submission_id=submission_id, timestamp=timestamp) logger.info("Token played by user %s on task %s." % (self.current_user.username, submission.task.name)) # Add "All ok" notification self.application.service.add_notification( self.current_user.username, timestamp, self._("Token request received"), self._("Your request has been received " "and applied to the submission."), ) self.redirect("/tasks/%s" % encrypt_number(submission.task.id))
def __init__(self, browser, task, filename, base_url=None): GenericRequest.__init__(self, browser, base_url) self.url = "%ssubmit/%s" % (self.base_url, encrypt_number(task[0])) self.task = task self.filename = filename self.data = {}
def __init__(self, browser, task, base_url=None, submissions_path=None): GenericRequest.__init__(self, browser, base_url) self.url = "%ssubmit/%s" % (self.base_url, encrypt_number(task[0])) self.task = task self.submissions_path = submissions_path self.data = {}
def post(self, task_id): self.timestamp = self.r_params["timestamp"] self.task_id = task_id self.task = Task.get_from_id(task_id, self.sql_session) if self.current_user is None or self.task is None or self.task.contest != self.contest: raise tornado.web.HTTPError(404) # Enforce minimum time between submissions for the same task. last_submission = ( self.sql_session.query(Submission) .filter_by(task_id=self.task.id) .filter_by(user_id=self.current_user.id) .order_by(Submission.timestamp.desc()) .first() ) if last_submission is not None and self.timestamp - last_submission.timestamp < config.min_submission_interval: self.application.service.add_notification( self.current_user.username, int(time.time()), self._("Submissions too frequent!"), self._("For each task, you can submit " "again after %s seconds from last submission.") % config.min_submission_interval, ) self.redirect("/tasks/%s" % encrypt_number(self.task.id)) return # Ensure that the user did not submit multiple files with the # same name. if any(len(x) != 1 for x in self.request.files.values()): self.application.service.add_notification( self.current_user.username, int(time.time()), self._("Invalid submission format!"), self._("Please select the correct files."), ) self.redirect("/tasks/%s" % encrypt_number(self.task.id)) return # If the user submitted an archive, extract it and use content # as request.files. if len(self.request.files) == 1 and self.request.files.keys()[0] == "submission": archive_data = self.request.files["submission"][0] del self.request.files["submission"] # Extract the files from the archive. temp_archive_file, temp_archive_filename = tempfile.mkstemp(config.temp_dir) with os.fdopen(temp_archive_file, "w") as temp_archive_file: temp_archive_file.write(archive_data["body"]) archive_contents = extract_archive(temp_archive_filename, archive_data["filename"]) if archive_contents is None: self.application.service.add_notification( self.current_user.username, int(time.time()), self._("Invalid archive format!"), self._("The submitted archive could not be opened."), ) self.redirect("/tasks/%s" % encrypt_number(self.task.id)) return for item in archive_contents: self.request.files[item["filename"]] = [item] # This ensure that the user sent one file for every name in # submission format and no more. Less is acceptable if task # type says so. task_type = get_task_type(task=self.task) required = set([x.filename for x in self.task.submission_format]) provided = set(self.request.files.keys()) if not (required == provided or (task_type.ALLOW_PARTIAL_SUBMISSION and required.issuperset(provided))): self.application.service.add_notification( self.current_user.username, int(time.time()), self._("Invalid submission format!"), self._("Please select the correct files."), ) self.redirect("/tasks/%s" % encrypt_number(self.task.id)) return # Add submitted files. After this, self.files is a dictionary # indexed by *our* filenames (something like "output01.txt" or # "taskname.%l", and whose value is a couple # (user_assigned_filename, content). self.files = {} for uploaded, data in self.request.files.iteritems(): self.files[uploaded] = (data[0]["filename"], data[0]["body"]) # If we allow partial submissions, implicitly we recover the # non-submitted files from the previous submission. And put # them in self.file_digests (i.e., like they have already been # sent to FS). self.submission_lang = None self.file_digests = {} self.retrieved = 0 if task_type.ALLOW_PARTIAL_SUBMISSION and last_submission is not None: for filename in required.difference(provided): if filename in last_submission.files: # If we retrieve a language-dependent file from # last submission, we take not that language must # be the same. if "%l" in filename: self.submission_lang = last_submission.language self.file_digests[filename] = last_submission.files[filename].digest self.retrieved += 1 # We need to ensure that everytime we have a .%l in our # filenames, the user has one amongst ".cpp", ".c", or ".pas, # and that all these are the same (i.e., no mixed-language # submissions). def which_language(user_filename): """Determine the language of user_filename from its extension. user_filename (string): the file to test. return (string): the extension of user_filename, or None if it is not a recognized language. """ extension = os.path.splitext(user_filename)[1] try: return Submission.LANGUAGES_MAP[extension] except KeyError: return None error = None for our_filename in self.files: user_filename = self.files[our_filename][0] if our_filename.find(".%l") != -1: lang = which_language(user_filename) if lang is None: error = self._("Cannot recognize submission's language.") break elif self.submission_lang is not None and self.submission_lang != lang: error = self._("All sources must be in the same language.") break else: self.submission_lang = lang if error is not None: self.application.service.add_notification( self.current_user.username, int(time.time()), self._("Invalid submission!"), error ) self.redirect("/tasks/%s" % encrypt_number(self.task.id)) return # Check if submitted files are small enough. if any([len(f[1]) > config.max_submission_length for f in self.files.values()]): self.application.service.add_notification( self.current_user.username, int(time.time()), self._("Submission too big!"), self._("Each files must be at most %d bytes long.") % config.max_submission_length, ) self.redirect("/tasks/%s" % encrypt_number(self.task.id)) return # All checks done, submission accepted. # Attempt to store the submission locally to be able to # recover a failure. self.local_copy_saved = False if config.submit_local_copy: try: path = os.path.join( config.submit_local_copy_path.replace("%s", config.data_dir), self.current_user.username ) if not os.path.exists(path): os.makedirs(path) with codecs.open(os.path.join(path, str(self.timestamp)), "w", "utf-8") as file_: pickle.dump((self.contest.id, self.current_user.id, self.task, self.files), file_) self.local_copy_saved = True except Exception as error: logger.error("Submission local copy failed - %s" % traceback.format_exc()) self.username = self.current_user.username self.sql_session.close() # We now have to send all the files to the destination... try: for filename in self.files: digest = self.application.service.file_cacher.put_file( description="Submission file %s sent by %s at %d." % (filename, self.username, self.timestamp), binary_data=self.files[filename][1], ) self.file_digests[filename] = digest # In case of error, the server aborts the submission except Exception as error: logger.error("Storage failed! %s" % error) if self.local_copy_saved: message = "In case of emergency, this server has a local copy." else: message = "No local copy stored! Your submission was ignored." self.application.service.add_notification( self.username, int(time.time()), self._("Submission storage failed!"), self._(message) ) self.redirect("/tasks/%s" % encrypt_number(self.task_id)) # All the files are stored, ready to submit! self.sql_session = Session() current_user = self.get_current_user() self.task = Task.get_from_id(self.task_id, self.sql_session) logger.info("All files stored for submission sent by %s" % self.username) submission = Submission( user=current_user, task=self.task, timestamp=self.timestamp, files={}, language=self.submission_lang ) for filename, digest in self.file_digests.items(): self.sql_session.add(File(digest, filename, submission)) self.sql_session.add(submission) self.sql_session.commit() self.r_params["submission"] = submission self.r_params["warned"] = False self.application.service.evaluation_service.new_submission(submission_id=submission.id) self.application.service.add_notification( self.username, int(time.time()), self._("Submission received"), self._("Your submission has been received " "and is currently being evaluated."), ) # The argument (encripted submission id) is not used by CWS # (nor it discloses information to the user), but it is useful # for automatic testing to obtain the submission id). self.redirect("/tasks/%s?%s" % (encrypt_number(self.task.id), encrypt_number(submission.id)))
def __init__(self, browser, task_id, base_url=None): GenericRequest.__init__(self, browser, base_url) self.url = "%stasks/%s" % (self.base_url, encrypt_number(task_id)) self.task_id = task_id