def upload_source(self, input, file): """ POST /upload_source """ alerts = [] if get_exeflags(file["content"]): alerts.append({ "severity": "warning", "message": "You have submitted an executable! Please send the " "source code." }) Logger.info("UPLOAD", "User %s has uploaded an executable" % input["token"]) if not alerts: alerts.append({ "severity": "success", "message": "Source file uploaded correctly." }) source_id = Database.gen_id() try: path = StorageManager.new_source_file(source_id, file["name"]) except ValueError: BaseHandler.raise_exc(BadRequest, "INVALID_FILENAME", "The provided file has an invalid name") StorageManager.save_file(path, file["content"]) file_size = StorageManager.get_file_size(path) Database.add_source(source_id, input["id"], path, file_size) Logger.info("UPLOAD", "User %s has uploaded the source %s" % ( input["token"], source_id)) output = BaseHandler.format_dates(Database.get_source(source_id)) output["validation"] = {"alerts": alerts} return output
def evaluate_output(task_name, input_path, output_path): """ Given an input of a task, evaluate the correctness of the output :param task_name: Name of the task :param input_path: Path to the user's input file :param output_path: Path to the user's output file :return: The stdout of the checker """ try: # call the checker and store the output start_time = time.monotonic() output = gevent.subprocess.check_output([ ContestManager.tasks[task_name]["checker"], StorageManager.get_absolute_path(input_path), StorageManager.get_absolute_path(output_path) ]) if time.monotonic() > start_time + 1: Logger.warning( "TASK", "Evaluation of output %s " "for task %s, with input %s, took %f " "seconds" % (output_path, task_name, input_path, time.monotonic() - start_time)) except: # TODO log the stdout and stderr of the checker Logger.error( "TASK", "Error while evaluating output %s " "for task %s, with input %s: %s" % (output_path, task_name, input_path, traceback.format_exc())) raise Logger.info( "TASK", "Evaluated output %s for task %s, with input %s" % (output_path, task_name, input_path)) return output
def upload_output(self, input, file): """ POST /upload_output """ output_id = Database.gen_id() try: path = StorageManager.new_output_file(output_id, file["name"]) except ValueError: BaseHandler.raise_exc(BadRequest, "INVALID_FILENAME", "The provided file has an invalid name") StorageManager.save_file(path, file["content"]) file_size = StorageManager.get_file_size(path) try: result = ContestManager.evaluate_output(input["task"], input["path"], path) except: BaseHandler.raise_exc(InternalServerError, "INTERNAL_ERROR", "Failed to evaluate the output") Database.add_output(output_id, input["id"], path, file_size, result) Logger.info( "UPLOAD", "User %s has uploaded the output %s" % (input["token"], output_id)) return InfoHandler.patch_output(Database.get_output(output_id))
def test_rename_file(self): backup = Config.storedir Config.storedir = Utils.new_tmp_dir() relative_path = 'baz/file.txt' new_path = 'baz/txt.elif' StorageManager.save_file(relative_path, 'foobar'.encode()) StorageManager.rename_file(relative_path, new_path) with open(StorageManager.get_absolute_path(new_path), 'r') as file: lines = file.readlines() self.assertEqual('foobar', lines[0]) Config.storedir = backup
def test_get_file_size(self): filename = Utils.new_tmp_file() with open(filename, 'w') as file: file.write('This string is 28 chars long') self.assertEqual(28, StorageManager.get_file_size(filename))
def test_new_output_file(self): output_id = 'output_id' filename = 'filename.foo' path = StorageManager.new_output_file(output_id, filename) self.assertIn("output", path) self.assertTrue(path.find(output_id) >= 0) self.assertTrue(path.find(filename) >= 0)
def test_sanitize_file_too_long(self): filename = "file_" + ("a" * 5 * StorageManager.MAX_LENGTH) + ".txt" sanitized = StorageManager._sanitize(filename) self.assertEqual( "file_" + ("a" * (StorageManager.MAX_LENGTH - 9)) + ".txt", sanitized) self.assertEqual(StorageManager.MAX_LENGTH, len(sanitized))
def test_new_source_file(self): source_id = 'source_id' filename = 'filename.foo' path = StorageManager.new_source_file(source_id, filename) self.assertIn("source", path) self.assertTrue(path.find(source_id) >= 0) self.assertTrue(path.find(filename) >= 0)
def generate_input(self, task, user): """ POST /generate_input """ token = user["token"] if Database.get_user_task(token, task["name"])["current_attempt"]: self.raise_exc(Forbidden, "FORBIDDEN", "You already have a ready input!") attempt = Database.get_next_attempt(token, task["name"]) id, path = ContestManager.get_input(task["name"], attempt) size = StorageManager.get_file_size(path) Database.begin() try: Database.add_input(id, token, task["name"], attempt, path, size, autocommit=False) Database.set_user_attempt(token, task["name"], attempt, autocommit=False) Database.commit() except: Database.rollback() raise Logger.info( "CONTEST", "Generated input %s for user %s on task %s" % (id, token, task["name"])) return BaseHandler.format_dates(Database.get_input(id=id))
def upload_pack(self, file): """ POST /admin/upload_pack """ if Database.get_meta("admin_token"): BaseHandler.raise_exc(Forbidden, "FORBIDDEN", "The pack has already been extracted") elif os.path.exists(Config.encrypted_file): BaseHandler.raise_exc(Forbidden, "FORBIDDEN", "The pack has already been uploaded") if not crypto.validate(file["content"]): self.raise_exc(Forbidden, "BAD_FILE", "The uploaded file is " "not valid") StorageManager.save_file(os.path.realpath(Config.encrypted_file), file["content"]) return {}
def get_input(task_name, attempt): """ Fetch an input from the queue and properly rename it :param task_name: Name of the task :param attempt: Number of the attempt for the user :return: A pair, the first element is the id of the input file, the second the path """ if ContestManager.input_queue[task_name].empty(): Logger.warning("TASK", "Empty queue for task %s!" % task_name) input = ContestManager.input_queue[task_name].get() path = StorageManager.new_input_file(input["id"], task_name, attempt) StorageManager.rename_file(input["path"], path) return input["id"], path
def test_new_input_file(self): input_id = 'input_id' task = 'simple_task' attempt = 42 path = StorageManager.new_input_file(input_id, task, attempt) self.assertIn("input", path) self.assertTrue(path.find(input_id) >= 0) self.assertTrue(path.find(task) >= 0) self.assertTrue(path.find(str(attempt)) >= 0)
def test_save_file(self): backup = Config.storedir Config.storedir = Utils.new_tmp_dir("new_path") relative_path = os.path.join("baz", "file.txt") content = 'This is the content of the file' try: os.remove(Config.storedir) except: pass StorageManager.save_file(relative_path, content.encode()) with open(StorageManager.get_absolute_path(relative_path), 'r') as file: file_content = file.readlines() self.assertEqual(1, len(file_content)) self.assertEqual(content, file_content[0]) Config.storedir = backup
def test_get_absolute_path(self): backup = Config.storedir Config.storedir = Utils.new_tmp_dir() relative_path = 'path/to/file' abs_path = StorageManager.get_absolute_path(relative_path) self.assertTrue(abs_path.find(Config.storedir) >= 0) self.assertTrue(abs_path.find(relative_path) >= 0) Config.storedir = backup
def test_sanitize_no_extension(self): filename = "x" * StorageManager.MAX_LENGTH * 5 sanitized = StorageManager._sanitize(filename) self.assertEqual("x" * StorageManager.MAX_LENGTH, sanitized) self.assertEqual(StorageManager.MAX_LENGTH, len(sanitized))
def test_sanitize_invalid_filename_3(self): filename = ".." with self.assertRaises(ValueError): StorageManager._sanitize(filename)
def worker(task_name): """ Method that stays in the background and generates inputs """ task = ContestManager.tasks[task_name] queue = ContestManager.input_queue[task_name] while True: try: id = Database.gen_id() path = StorageManager.new_input_file(id, task_name, "invalid") seed = int(sha256(id.encode()).hexdigest(), 16) % (2**31) stdout = os.open( StorageManager.get_absolute_path(path), os.O_WRONLY | os.O_CREAT, 0o644, ) try: start_time = time.monotonic() # generate the input and store the stdout into a file retcode = gevent.subprocess.call( [task["generator"], str(seed), "0"], stdout=stdout) if time.monotonic() > start_time + 1: Logger.warning( "TASK", "Generation of input %s for task %s took %f seconds" % (seed, task_name, time.monotonic() - start_time), ) finally: os.close(stdout) if retcode != 0: Logger.error( "TASK", "Error %d generating input %s (%d) for task %s" % (retcode, id, seed, task_name), ) # skip the input continue # if there is a validator in the task use it to check if the # generated input is valid if "validator" in task: stdin = os.open(StorageManager.get_absolute_path(path), os.O_RDONLY) try: start_time = time.monotonic() # execute the validator piping the input file to stdin retcode = gevent.subprocess.call( [task["validator"], "0"], stdin=stdin) if time.monotonic() > start_time + 1: Logger.warning( "TASK", "Validation of input %s for task %s took %f " "seconds" % (seed, task_name, time.monotonic() - start_time), ) finally: os.close(stdin) if retcode != 0: Logger.error( "TASK", "Error %d validating input %s (%d) for task %s" % (retcode, id, seed, task_name), ) # skip the input continue Logger.debug( "TASK", "Generated input %s (%d) for task %s" % (id, seed, task_name), ) # this method is blocking if the queue is full queue.put({"id": id, "path": path}) except: Logger.error( "TASK", "Exception while creating an input file: " + traceback.format_exc(), )
def test_sanitize_extension_too_long(self): filename = "file." + ("x" * 5 * StorageManager.MAX_LENGTH) sanitized = StorageManager._sanitize(filename) self.assertEqual("file." + ("x" * (StorageManager.MAX_LENGTH - 5)), sanitized) self.assertEqual(StorageManager.MAX_LENGTH, len(sanitized))
def test_sanitize(self): filename = " fi!@le n²amжe'.txt " sanitized = StorageManager._sanitize(filename) self.assertEqual("file_n²amжe.txt", sanitized)
def test_sanitize_no_name(self): filename = ".hidden" sanitized = StorageManager._sanitize(filename) self.assertEqual(".hidden", sanitized)