#!/usr/bin/python3 # coding=utf-8 import falcon import json import client from errors.session import NoSuchSession, AuthenticationFailed MAX_REQUEST_LENGTH = 1024 * 1024 # 1 MB api = application = falcon.API() def get_json_payload(req, session_is_optional=False): content_type_parts = req.content_type.split(';') content_type = content_type_parts[0].strip() encoding = 'utf-8' if len(content_type_parts) > 1: for part in content_type_parts[1:]: subparts = part.strip().split('=') if (len(subparts) == 2) and subparts[0] == 'charset': encoding = subparts[1] if not ((content_type == 'application/json') or (content_type == 'text/json')): raise falcon.HTTPUnsupportedMediaType('Unsupported content-type: {0}'.format(req.content_type)) length = req.get_header('Content-Length') if length is None: raise falcon.HTTPLengthRequired('Requests without content-length are not accepted') try: l = int(length) except: raise falcon.HTTPBadRequest('Invalid Content-Length', 'The given Content-Length is not a number: {0}'.format(length)) if l > MAX_REQUEST_LENGTH: raise falcon.HTTPError(falcon.HTTP_413, 'Request Entity Too Large', 'Cannot accept the request of length {0}: maximum allowed content-length is {1}'.format(length, MAX_REQUEST_LENGTH)) try: txt = req.stream.read() except Exception as e: raise falcon.HTTPBadRequest('Error reading request', 'Error while reading the request body: {0}'.format(e.message)) try: js = json.loads(txt.decode(encoding=encoding, errors='replace')) except ValueError as e: raise falcon.HTTPBadRequest('Error parsing JSON payload', 'Error while parsing the request as JSON: {0}'.format(e.message)) sid = js.get('sid') if sid is None: if session_is_optional: return js, None raise falcon.HTTPBadRequest('No Session', 'Request is missing a session-ID') del js['sid'] try: session = client.get_session_by_id(sid) except NoSuchSession: if session_is_optional: return js, None raise falcon.HTTPBadRequest('Session Expired', 'This user session has expired. Please log-in again.') return js, session class CodeqService(object): """Base class for all CodeQ services. It only support the POST method and JSON data. Handles JSON decoding and encoding, and session retrieval which can be optional. """ session_is_optional = False def on_post(self, req, resp): js, session = get_json_payload(req, self.session_is_optional) resp.body = json.dumps(self.process(js, session)) resp.cache_control = 'private, no-cache, no-store' resp.content_type = 'application/json' resp.status = falcon.HTTP_200 def process(self, js, session): raise falcon.HTTPNotFound() class ProblemList(CodeqService): session_is_optional = True def process(self, js, session): language = js.get('language') if language is None: return {'code': 1, 'message': 'Language was not provided'} else: return {'code': 0, 'message': 'ok', 'problems': client.list_problems_in_groups(language)} class Login(CodeqService): session_is_optional = True def process(self, js, old_session): username = js.get('username') password = js.get('password') if username is None: return {'code': 1, 'message': 'Username was not provided'} elif password is None: return {'code': 2, 'message': 'Password was not provided'} else: try: session = client.authenticate_and_create_session(username, password) except AuthenticationFailed: return {'code': 3, 'message': 'Username or password do not match'} else: if old_session: old_session.destroy() return {'code': 0, 'message': 'OK', 'sid':session.get_sid()} class Activity(CodeqService): def process(self, js, session): trace = js.get('trace') solution = js.get('solution') problem_id = js.get('problem_id') if (trace is not None) or (solution is not None): # we have something to do if problem_id is None: return {'code': 1, 'message': 'Problem ID is missing'} else: session.update_solution(problem_id, trace, solution) class Query(CodeqService): def process(self, js, session): step = js.get('step') if step is None: return {'code': 1, 'message': '"step" is not set'} else: problem_id = js.get('problem_id') if problem_id is None: return {'code': 4, 'message': 'Problem ID not given'} trace = js.get('trace') prolog = session.get_prolog() program = None if step == 'run': program = js.get('program') query = js.get('query') if program is None: result = {'code': 2, 'message': 'No program specified'} elif query is None: result = {'code': 3, 'message': 'No query specified'} else: messages, status, have_more = prolog.run_for_user(session.get_uid(), problem_id, program, query) result = {'code': 0, 'message': 'ok', 'terminal': {'messages': messages, 'status': status, 'have_more': have_more}} elif step == 'next': messages, status, have_more = prolog.step() result = {'code': 0, 'message': 'ok', 'terminal': {'messages': messages, 'status': status, 'have_more': have_more}} elif step == 'end': messages, status, have_more = prolog.end() result = {'code': 0, 'message': 'ok', 'terminal': {'messages': messages, 'status': status, 'have_more': have_more}} else: result = {'code': 5, 'message': 'Unknown prolog step: {0}'.format(step)} if program or trace: session.update_solution(problem_id, trace, program) return result class GetProblem(CodeqService): def process(self, js, session): language = js.get('language') problem_group = js.get('problem_group') problem = js.get('problem') if language is None: return {'code': 1, 'message': 'Language identifier not given'} elif problem_group is None: return {'code': 2, 'message': 'Problem group identifier not given'} elif problem is None: return {'code': 3, 'message': 'Problem identifier not given'} else: return {'code': 0, 'message': 'ok', 'data': session.get_problem_data(language, problem_group, problem)} problem_list = ProblemList() api.add_route('/list_problems', problem_list) login = Login() api.add_route('/login', login) activity = Activity() api.add_route('/activity', activity) query = Query() api.add_route('/query', query) get_problem = GetProblem() api.add_route('/get_problem', get_problem) if __name__ == '__main__': import logging from waitress import serve logger = logging.getLogger('waitress') logger.setLevel(logging.DEBUG) serve(application, host='0.0.0.0', port=8082)