def parse_http_headers(environ):
    h = Headers([])
    for k, v in environ.items():
        if k.startswith('HTTP_'):
            name = k[5:]
            h.add_header(name, v)
    return h
Exemplo n.º 2
0
class Response(object):
    def __init__(self,
                 response=None,
                 status=200,
                 charset='utf-8',
                 content_type='text/html'):
        self.response = [] if response is None else response
        self._status = status
        self.charset = charset
        self.headers = Headers()
        content_type = '{content_type}; charset={charset}'.format(
            content_type=content_type, charset=charset)
        self.headers.add_header('content_type', content_type)

    @property
    def status(self):
        status_string = http.client.responses.get(
            self._status, 'UNKNOWN')  # 如果没有status, 就回复UNKNOWN
        return '{status} {status_string}'.format(status=self._status,
                                                 status_string=status_string)

    def __iter__(self):  # 如果response里面的值是bytes的话,就返回, 否则转换成设定的字符编码再返回。
        for val in self.response:
            if isinstance(val, bytes):
                yield val
            else:
                yield val.encode(self.charset)
Exemplo n.º 3
0
class Response:
    default_status = 200
    default_charset = 'utf-8'
    default_content_type = 'text/html; charset=UTF-8'

    def __init__(self, body='', status=None, headers=None, charset=None):
        self._body = body
        self.status = status or self.default_status
        self.headers = Headers()
        self.charset = charset or self.default_charset

        if headers:
            for name, value in headers.items():
                self.headers.add_header(name, value)

    @property
    def status_code(self):
        return "%d %s" % (self.status, http_responses[self.status])

    @property
    def header_list(self):
        if 'Content-Type' not in self.headers:
            self.headers.add_header('Content-Type', self.default_content_type)
            return self.headers.items()

    @property
    def body(self):
        if isinstance(self._body, str):
            return [self._body.encode(self.charset)]
        return [self._body]
Exemplo n.º 4
0
def parse_http_headers(environ):
    h = Headers([])
    for k, v in environ.items():
        if k.startswith('HTTP_'):
            name = k[5:]
            h.add_header(name, v)
    return h
Exemplo n.º 5
0
class BaseResponse(object):
    def __init__(self,
                 status_code=None,
                 reason_phrase=None,
                 content_type=None,
                 charset=None):
        try:
            if not status_code:
                status_code = 200
                reason_phrase = "OK"

            self.status_code = int(status_code)
            self.reason_phrase = reason_phrase

        except (TypeError, ValueError):
            # TODO: add traceback
            self.status_code = 500
            self.reason_phrase = "Internal Server Error"

        self.cookies = []
        self.charset = charset or "utf-8"
        self._handler_class = None
        self._content = []
        self.__set_default_headers()

        content_type = "text/html" if not content_type else content_type.strip(
            " \r\n")
        if (content_type.split("/", 1)[0] in ("text", "application")
                and "charset" not in content_type.lower()):
            content_type = "%s; charset=%s" % (content_type.rstrip("; \r\n"),
                                               self.charset)
        self.headers.add_header("Content-Type", content_type)

    def __set_default_headers(self):
        self.headers = Headers([
            ("Server", "MadLiar/%s" % __version__),
            ("X-Frame-Options", "SAMEORIGIN"),
        ])

    @property
    def content(self):
        return b''.join(self._content)

    @content.setter
    def content(self, value):
        # Consume iterators upon assignment to allow repeated iteration.
        if not hasattr(value, '__iter__'):
            value = [value]

        content = b''.join(
            map(
                lambda x: bytes(x)
                if isinstance(x, bytes) else bytes(x.encode(self.charset)),
                value))
        self.headers.add_header("Content-length", str(len(content)))
        self._content = [content]

    def __iter__(self):
        return iter(self._content)
Exemplo n.º 6
0
    def testExtras(self):
        h = Headers([])
        self.assertEqual(str(h), "\r\n")

        h.add_header("foo", "bar", baz="spam")
        self.assertEqual(h["foo"], 'bar; baz="spam"')
        self.assertEqual(str(h), 'foo: bar; baz="spam"\r\n\r\n')

        h.add_header("Foo", "bar", cheese=None)
        self.assertEqual(h.get_all("foo"), ['bar; baz="spam"', "bar; cheese"])

        self.assertEqual(str(h), 'foo: bar; baz="spam"\r\n' "Foo: bar; cheese\r\n" "\r\n")
Exemplo n.º 7
0
class Response(threading.local):
    """ Represents a single response using thread-local namespace. """

    def bind(self, app):
        """ Clears old data and creates a brand new Response object """
        self._COOKIES = None
        self.status = 200
        self.header_list = []
        self.header = HeaderWrapper(self.header_list)
        self.charset = 'UTF-8'
        self.content_type = 'text/html; charset=UTF-8'
        self.error = None
        self.app = app

    def add_header(self, key, value):
        self.header.add_header(key.title(), str(value))

    def wsgiheaders(self):
        ''' Returns a wsgi conform list of header/value pairs '''
        for c in self.COOKIES.values():
            self.add_header('Set-Cookie', c.OutputString())
        return self.header_list

    @property
    def COOKIES(self):
        if not self._COOKIES:
            self._COOKIES = SimpleCookie()
        return self._COOKIES

    def set_cookie(self, key, value, **kargs):
        """
        Sets a Cookie. Optional settings:
        expires, path, comment, domain, max-age, secure, version, httponly
        """
        if not isinstance(value, basestring):
            sec = self.app.config['securecookie.key']
            value = cookie_encode(value, sec)
        self.COOKIES[key] = value
        for k, v in kargs.iteritems():
            self.COOKIES[key][k] = v

    def get_content_type(self):
        """ Get the current 'Content-Type' header. """
        return self.header['Content-Type']
        
    def set_content_type(self, value):
        if 'charset=' in value:
            self.charset = value.split('charset=')[-1].split(';')[0].strip()
        self.header['Content-Type'] = value

    content_type = property(get_content_type, set_content_type, None,
                            get_content_type.__doc__)
Exemplo n.º 8
0
class Request:
    id: str
    environ: dict
    wsgi_input: BufferedReader
    path: str
    method: str
    port: str
    host: str
    protocol: str
    server_name: str
    query: dict
    headers: Headers
    body: Dict
    raw_body: bytes
    params: Dict[str, Any]

    def __init__(self, environ: dict):
        self.id = ''.join(random.choice(chars) for i in range(30))
        self.body = dict()
        self.environ = environ
        self.wsgi_input: BufferedReader = cast(BufferedReader,
                                               environ.get('wsgi.input'))
        self.path = environ.get('PATH_INFO', '')
        self.method = environ.get('REQUEST_METHOD', '')
        self.port = environ.get('PORT', '')
        self.host = environ.get('HTTP_HOST', '')
        self.protocol = environ.get('HTTP_PROTOCOL', '')
        self.server_name = environ.get('SERVER_NAME', '')
        self.query = parse_qs(environ.get('QUERY_STRING', ''))
        self.params = dict()
        self.headers = Headers()
        self._parse_http_headers()
        self._read_request_body()

    def _parse_http_headers(self: 'Request'):
        for key in self.environ.keys():
            replaced_key: str = key.replace('HTTP_', '')
            value = self.environ.get(key)

            if replaced_key in HTTP_HEADERS:
                final_key: str = capwords(replaced_key, '_').replace('_', '-')
                self.headers.add_header(final_key, value)

    def _read_request_body(self: 'Request'):
        try:
            request_body_size = int(self.headers.get(CONTENT_LENGTH, '0'))
        except ValueError:
            request_body_size = 0
        self.raw_body = self.wsgi_input.read(request_body_size)
Exemplo n.º 9
0
class Static(Route):

    def __init__(self, status, content_type):
        self.__status = status
        self.__ct = content_type
        self.__header = Headers()

    @property
    def status_code(self):
        return '{} {}'.format(self.__status, responses[self.__status])

    @property
    def headers(self):
        self.__header.add_header('Content-type', self.__ct)
        return self.__header.items()
    def testExtras(self):
        h = Headers()
        self.assertEqual(str(h), '\r\n')

        h.add_header('foo', 'bar', baz="spam")
        self.assertEqual(h['foo'], 'bar; baz="spam"')
        self.assertEqual(str(h), 'foo: bar; baz="spam"\r\n\r\n')

        h.add_header('Foo', 'bar', cheese=None)
        self.assertEqual(h.get_all('foo'), ['bar; baz="spam"', 'bar; cheese'])

        self.assertEqual(
            str(h), 'foo: bar; baz="spam"\r\n'
            'Foo: bar; cheese\r\n'
            '\r\n')
Exemplo n.º 11
0
    def __call__(self, environ, start_response):
        key_morsel = Cookie(environ.get("HTTP_COOKIE", "")).get(self.toggle_key)
        # useful vars
        query = query_str2dict(environ.get("QUERY_STRING"))
        enable_by_cookie = key_morsel.value == self.enable_value if key_morsel else False
        enable_by_query = query.get(self.toggle_key) == self.enable_value
        # pop toggle_key from query dic to avoid case: '?_profile=on&_profile='
        disable = query.pop(self.toggle_key, None) == ""  # only can be disabled by query
        enable = not disable and (enable_by_query or enable_by_cookie)

        run_app, resp_body, saved_ss_args = self._intercept_call()

        # processing cookies and queries
        so = query.pop(self.SIMPLE_OUTPUT_TOGGLE_KEY, None)
        if so is not None:
            self.simple_output = so == "True"
        cookie_to_set = None
        if enable_by_query and not enable_by_cookie:
            cookie_to_set = "%s=%s; Path=/; HttpOnly" % (self.toggle_key, self.enable_value)
        elif disable:
            cookie_to_set = "%s=; Path=/; Max-Age=1; HttpOnly" % self.toggle_key

        if enable:
            start = time.time()
            profile = Profile()
            profile.runcall(run_app, environ)  # here we call the WSGI app
            elapsed = time.time() - start
        else:
            profile = elapsed = None  # for annoying IDE
            run_app(environ)

        status, headers = saved_ss_args[:2]
        headers_dic = Headers(headers)
        if cookie_to_set:
            headers_dic.add_header("Set-Cookie", cookie_to_set)

        # insert result into response
        content_type = headers_dic.get("Content-Type", "")
        if enable and status.startswith("200") and content_type.startswith("text/html"):
            environ["QUERY_STRING"] = dict2query_str(query)

            matched = _find_charset.match(content_type)
            encoding = matched.group(1) if matched else "ascii"
            rendered = self.render_result(profile, elapsed, environ).encode(encoding, "replace")
            resp_body = [insert_into_body(rendered, b"".join(resp_body))]
            headers_dic["Content-Length"] = str(len(resp_body[0]))
        start_response(status, headers, saved_ss_args[2] if len(saved_ss_args) == 3 else None)
        return resp_body
Exemplo n.º 12
0
def _parse_headers(environ):
    """
    Parse the environmental variables, looking for HTTP request headers.
    :param environ: environmental variables
    :type environ: dict
    :return: request headers
    :rtype: dict
    """
    headers = Headers([])
    for key, value in environ.items():
        match = _HTTP_HEADER_REGEX.match(key)
        if match is None:
            continue
        name = _normalize_header_name(match.group(0))
        headers.add_header(name, value)
    return headers
Exemplo n.º 13
0
def static_file_view(env, start_response, filename, block_size, charset):
    method = env['REQUEST_METHOD'].upper()
    if method not in ('HEAD', 'GET'):
        start_response('405 METHOD NOT ALLOWED',
                       [('Content-Type', 'text/plain; UTF-8')])
        return [b'']

    mimetype, encoding = mimetypes.guess_type(filename)
    headers = Headers([])
    headers.add_header('Content-Encodings', encoding)
    headers.add_header('Content-Type', get_content_type(mimetype, charset))
    headers.add_header('Content-Length', get_content_length(filename))
    headers.add_header('Last-Modified', generate_last_modified())
    headers.add_header("Accept-Ranges", "bytes")

    start_response('200 OK', headers.items())
    return _get_body(filename, method, block_size, charset)
    def testBytes(self):
        h = Headers([(b"Content-Type", b"text/plain; charset=utf-8")])
        self.assertEqual("text/plain; charset=utf-8", h.get("Content-Type"))

        h[b"Foo"] = bytes(b"bar")
        self.assertEqual("bar", h.get("Foo"))
        self.assertEqual("bar", h.get(b"Foo"))

        h.setdefault(b"Bar", b"foo")
        self.assertEqual("foo", h.get("Bar"))
        self.assertEqual("foo", h.get(b"Bar"))

        h.add_header(b"content-disposition", b"attachment", filename=b"bud.gif")
        self.assertEqual('attachment; filename="bud.gif"', h.get("content-disposition"))

        del h["content-disposition"]
        self.assertTrue(b"content-disposition" not in h)
Exemplo n.º 15
0
    def __call__(self):
        """Set the headers and return the body.

        It is not necessary to supply Content-length, because this is added by
        the caller.
        """
        headers = Headers([])
        content_type_params = {}
        if self.charset is not None:
            content_type_params['charset'] = self.charset
        headers.add_header(
            'Content-Type', self.content_type, **content_type_params)
        headers.add_header(
            'Content-Disposition', 'attachment', filename=self.filename)
        for key, value in headers.items():
            self.request.response.setHeader(key, value)
        return self.getBody()
Exemplo n.º 16
0
    def testExtras(self):
        h = Headers([])
        self.assertEqual(str(h),'\r\n')

        h.add_header('foo','bar',baz="spam")
        self.assertEqual(h['foo'], 'bar; baz="spam"')
        self.assertEqual(str(h),'foo: bar; baz="spam"\r\n\r\n')

        h.add_header('Foo','bar',cheese=None)
        self.assertEqual(h.get_all('foo'),
            ['bar; baz="spam"', 'bar; cheese'])

        self.assertEqual(str(h),
            'foo: bar; baz="spam"\r\n'
            'Foo: bar; cheese\r\n'
            '\r\n'
        )
Exemplo n.º 17
0
    def http_request_header(self, range):
        scheme, netloc, path, params, query, fragment = list(self._urlparse)

        query = query + ''.join(
            [i + '=' + j for i, j in self._rangef.getquery(range).items()])
        query = query.lstrip('&')
        if query:
            path = path + '?' + query
        headers = Headers(self.headers.items())
        for i, j in self._rangef.getheader(range).items():
            headers.add_header(i, j)

        if self.cookie:
            headers.add_header('Cookie', self.cookie)

        path = path.strip().replace(' ', '%20')
        return path, headers
Exemplo n.º 18
0
class Response(threading.local):
    """ Represents a single response using thread-local namespace. """

    def bind(self):
        """ Clears old data and creates a brand new Response object """
        self._COOKIES = None
        self.status = 200
        self.header_list = []
        self.header = HeaderWrapper(self.header_list)
        self.content_type = 'text/html'
        self.error = None
        self.charset = 'utf8'

    def wsgiheaders(self):
        ''' Returns a wsgi conform list of header/value pairs '''
        for c in self.COOKIES.itervalues():
            self.header.add_header('Set-Cookie', c.OutputString())
        return [(h.title(), str(v)) for h, v in self.header.items()]

    @property
    def COOKIES(self):
        if not self._COOKIES:
            self._COOKIES = SimpleCookie()
        return self._COOKIES

    def set_cookie(self, key, value, **kargs):
        """
        Sets a Cookie. Optional settings:
        expires, path, comment, domain, max-age, secure, version, httponly
        """
        self.COOKIES[key] = value
        for k, v in kargs.iteritems():
            self.COOKIES[key][k] = v

    def get_content_type(self):
        """ Get the current 'Content-Type' header. """
        return self.header['Content-Type']
        
    def set_content_type(self, value):
        if 'charset=' in value:
            self.charset = value.split('charset=')[-1].split(';')[0].strip()
        self.header['Content-Type'] = value

    content_type = property(get_content_type, set_content_type, None,
                            get_content_type.__doc__)
Exemplo n.º 19
0
class Response(threading.local):
    """ Represents a single response using thread-local namespace. """

    def bind(self):
        """ Clears old data and creates a brand new Response object """
        self._COOKIES = None
        self.status = 200
        self.header_list = []
        self.header = HeaderWrapper(self.header_list)
        self.content_type = "text/html"
        self.error = None
        self.charset = "utf8"

    def wsgiheaders(self):
        """ Returns a wsgi conform list of header/value pairs """
        for c in self.COOKIES.itervalues():
            self.header.add_header("Set-Cookie", c.OutputString())
        return [(h.title(), str(v)) for h, v in self.header.items()]

    @property
    def COOKIES(self):
        if not self._COOKIES:
            self._COOKIES = SimpleCookie()
        return self._COOKIES

    def set_cookie(self, key, value, **kargs):
        """
        Sets a Cookie. Optional settings:
        expires, path, comment, domain, max-age, secure, version, httponly
        """
        self.COOKIES[key] = value
        for k, v in kargs.iteritems():
            self.COOKIES[key][k] = v

    def get_content_type(self):
        """ Get the current 'Content-Type' header. """
        return self.header["Content-Type"]

    def set_content_type(self, value):
        if "charset=" in value:
            self.charset = value.split("charset=")[-1].split(";")[0].strip()
        self.header["Content-Type"] = value

    content_type = property(get_content_type, set_content_type, None, get_content_type.__doc__)
class Response:

    def __init__(self, response=None, status=200, charset='utf-8', content_type='text/html'):
        self.response = [] if response is None else response
        self.charset = charset
        self.headers = Headers()
        ctype = f'{content_type}; charset={charset}'
        self.headers.add_header('content-type', ctype)
        self._status = status

    @property
    def status(self):
        status_string = http.client.responses[self._status] or 'UNKNOWN'
        return f'{self._status} {status_string}'

    def __iter__(self):
        for k in self.response:
            if isinstance(k, bytes):
                yield k
            else:
                yield k.encode(self.charset)
Exemplo n.º 21
0
    def testBytes(self):
        h = Headers([
            (b"Content-Type", b"text/plain; charset=utf-8"),
            ])
        self.assertEqual("text/plain; charset=utf-8", h.get("Content-Type"))

        h[b"Foo"] = bytes(b"bar")
        self.assertEqual("bar", h.get("Foo"))
        self.assertEqual("bar", h.get(b"Foo"))

        h.setdefault(b"Bar", b"foo")
        self.assertEqual("foo", h.get("Bar"))
        self.assertEqual("foo", h.get(b"Bar"))

        h.add_header(b'content-disposition', b'attachment',
            filename=b'bud.gif')
        self.assertEqual('attachment; filename="bud.gif"',
            h.get("content-disposition"))

        del h['content-disposition']
        self.assertTrue(b'content-disposition' not in h)
Exemplo n.º 22
0
class Response(threading.local):
    """ Represents a single response using thread-local namespace. """

    def bind(self):
        """ Clears old data and creates a brand new Response object """
        self._COOKIES = None
        self.status = 200
        self.header_list = []
        self.header = HeaderWrapper(self.header_list)
        self.content_type = 'text/html'
        self.error = None

    def wsgiheaders(self):
        ''' Returns a wsgi conform list of header/value pairs '''
        for c in self.COOKIES.itervalues():
            self.header.add_header('Set-Cookie', c.OutputString())
        return [(h.title(), str(v)) for h, v in self.header_list]

    @property
    def COOKIES(self):
        if not self._COOKIES:
            self._COOKIES = SimpleCookie()
        return self._COOKIES

    def set_cookie(self, key, value, **kargs):
        """ Sets a Cookie. Optional settings: expires, path, comment, domain, max-age, secure, version, httponly """
        self.COOKIES[key] = value
        for k in kargs:
            self.COOKIES[key][k] = kargs[k]

    def get_content_type(self):
        '''Gives access to the 'Content-Type' header and defaults to 'text/html'.'''
        return self.header['Content-Type']
        
    def set_content_type(self, value):
        self.header['Content-Type'] = value
        
    content_type = property(get_content_type, set_content_type, None, get_content_type.__doc__)
Exemplo n.º 23
0
class Response(object):
    """
    Wrapper class around the start_response and return iterable of the WSGI
    protocol.

    :ivar status: HTTP status of the response
    :type status: int
    :ivar headers: response headers
    :type headers: dict
    :ivar cookies: cookies to set
    :type cookies: dict
    """

    def __init__(self):
        self.status = None
        self.__header_list = []
        self.headers = Headers(self.__header_list)
        self.headers['date'] = rfc1123_date()
        self.cookies = SimpleCookie()

    def start_response_args(self, body):
        """
        Return a status string and a list of headers for this response,
        appropriate as arguments to the start_response function.
        :param body: response body
        :type body: str
        :return: tuple of status string and list of header tuples
        :rtype: (str, list)
        """
        if self.status is None:
            raise RuntimeError('response status was not set')
        self.headers['content-length'] = str(len(body))
        for cookie in self.cookies:
            self.headers.add_header('set-cookie', cookie.output(header=''))
        status_str = '%d %s' % (self.status, httplib.responses[self.status])
        return (status_str, self.__header_list)
Exemplo n.º 24
0
    def do_GET(self, environ, start_response):
        headers = Headers()
        video_url = "{PATH_INFO}?{QUERY_STRING}".format(**environ).strip("/")
        if video_url == "?":
            raise self.HTTPError(400, more="no URL provided")
        try:
            videos = self.downloader.get_videos(video_url)
        except CannotDownload as cad:
            raise self.HTTPError(400, "Cannot download", more=str(cad))

        if len(videos) != 1:
            raise self.HTTPError(400, more="playlists not supported yet")
        video = videos[0]
        audio_file = self.downloader.cache_dir / video.path
        assert audio_file.exists()
        filesize = audio_file.stat().st_size
        headers.add_header("Content-Disposition",
                           "attachment",
                           filename=video.title)
        headers.add_header("Content-Type", "audio/mpeg")
        headers.add_header("Content-Length", str(filesize))
        start_response("200 OK", headers.items())
        return FileWrapper(audio_file.open("rb"))
Exemplo n.º 25
0
    def __call__(self, environ, start_response):
        '''Main function for handling a single request. Follows the
		WSGI API.

		@param environ: dictionary with environment variables for the
		request and some special variables. See the PEP for expected
		variables.

		@param start_response: a function that can be called to set the
		http response and headers. For example::

			start_response(200, [('Content-Type', 'text/plain')])

		@returns: the html page content as a list of lines
		'''
        headerlist = []
        headers = Headers(headerlist)
        path = environ.get('PATH_INFO', '/')
        try:
            methods = ('GET', 'HEAD')
            if not environ['REQUEST_METHOD'] in methods:
                raise WWWError('405', headers=[('Allow', ', '.join(methods))])

            # cleanup path
            #~ print 'INPUT', path
            path = path.replace('\\', '/')  # make it windows save
            isdir = path.endswith('/')
            parts = [p for p in path.split('/') if p and not p == '.']
            if [p for p in parts if p.startswith('.')]:
                # exclude .. and all hidden files from possible paths
                raise WebPathNotValidError()
            path = '/' + '/'.join(parts)
            if isdir and not path == '/':
                path += '/'
            #~ print 'PATH', path

            if not path:
                path = '/'
            elif path == '/favicon.ico':
                path = '/+resources/favicon.ico'
            else:
                path = urllib.unquote(path)

            if path == '/':
                headers.add_header('Content-Type',
                                   'text/html',
                                   charset='utf-8')
                content = self.render_index()
            elif path.startswith('/+docs/'):
                dir = self.notebook.document_root
                if not dir:
                    raise WebPageNotFoundError(path)
                file = dir.file(path[7:])
                content = [file.raw()]
                # Will raise FileNotFound when file does not exist
                headers['Content-Type'] = file.get_mimetype()
            elif path.startswith('/+file/'):
                file = self.notebook.dir.file(path[7:])
                # TODO: need abstraction for getting file from top level dir ?
                content = [file.raw()]
                # Will raise FileNotFound when file does not exist
                headers['Content-Type'] = file.get_mimetype()
            elif path.startswith('/+resources/'):
                if self.template.resources_dir:
                    file = self.template.resources_dir.file(path[12:])
                    if not file.exists():
                        file = data_file('pixmaps/%s' % path[12:])
                else:
                    file = data_file('pixmaps/%s' % path[12:])

                if file:
                    content = [file.raw()]
                    # Will raise FileNotFound when file does not exist
                    headers['Content-Type'] = file.get_mimetype()
                else:
                    raise WebPageNotFoundError(path)
            else:
                # Must be a page or a namespace (html file or directory path)
                headers.add_header('Content-Type',
                                   'text/html',
                                   charset='utf-8')
                if path.endswith('.html'):
                    pagename = path[:-5].replace('/', ':')
                elif path.endswith('/'):
                    pagename = path[:-1].replace('/', ':')
                else:
                    raise WebPageNotFoundError(path)

                path = self.notebook.pages.lookup_from_user_input(pagename)
                try:
                    page = self.notebook.get_page(path)
                    if page.hascontent:
                        content = self.render_page(page)
                    elif page.haschildren:
                        content = self.render_index(page)
                    else:
                        raise WebPageNotFoundError(path)
                except PageNotFoundError:
                    raise WebPageNotFoundError(path)
        except Exception as error:
            headerlist = []
            headers = Headers(headerlist)
            headers.add_header('Content-Type', 'text/plain', charset='utf-8')
            if isinstance(error, (WWWError, FileNotFoundError)):
                logger.error(error.msg)
                if isinstance(error, FileNotFoundError):
                    error = WebPageNotFoundError(path)
                    # show url path instead of file path
                if error.headers:
                    for key, value in error.headers:
                        headers.add_header(key, value)
                start_response(error.status, headerlist)
                content = unicode(error).splitlines(True)
            # TODO also handle template errors as special here
            else:
                # Unexpected error - maybe a bug, do not expose output on bugs
                # to the outside world
                logger.exception('Unexpected error:')
                start_response('500 Internal Server Error', headerlist)
                content = ['Internal Server Error']
            if environ['REQUEST_METHOD'] == 'HEAD':
                return []
            else:
                return [string.encode('utf-8') for string in content]
        else:
            start_response('200 OK', headerlist)
            if environ['REQUEST_METHOD'] == 'HEAD':
                return []
            elif 'utf-8' in headers['Content-Type']:
                return [string.encode('utf-8') for string in content]
            else:
                return content
Exemplo n.º 26
0
class BeanServer(object):
    "A really, really simple application server."

    default_headers = [('Content-Type', 'text/html')]

    def __init__(self, ledger, opts):
        self.ledger = ledger

        self.data = []
        self.load()

        # Map of session to dict.
        self.cookiejar = {}

        # Prototype for context object.
        ctx = self.ctx = Context()
        self.opts = ctx.opts = opts
        ctx.debug = opts.debug

    def setHeader(self, name, value):
        self.headers[name] = value

    def write(self, data):
        assert isinstance(data, str), data
        self.data.append(data)

    def load(self):
        "Load the application pages."
        import app
        reload(app)
        self.mapper = app.mapper

    def __call__(self, environ, start_response):
        if self.ctx.debug:
            self.load()

        self.environ = environ
        self.response = start_response
        del self.data[:]
        self.headers = Headers(self.default_headers)

        ctx = copy(self.ctx) # shallow
        ctx.ledger = self.ledger

        path = environ['PATH_INFO']

        ishtml = '.' not in basename(path) or path.endswith('.html')
        if ishtml:
            # Load cookie (session is only in memory).
            cookie = Cookie.SimpleCookie(environ.get('HTTP_COOKIE', ''))
            has_cookie = (bool(cookie) and
                          'session' in cookie and
                          cookie["session"].value in self.cookiejar)
            if has_cookie:
                session_id = cookie["session"].value
                session = self.cookiejar[session_id]
            else:
                session_id = '%x' % randint(0, 16**16)
                cookie["session"] = session_id
                session = self.cookiejar[session_id] = {}
            ctx.session = session

        try:
            # Linear search in the regexp to match the request path.
            page, vardict = self.mapper.match(path)
            if page is None:
                raise HttpNotFound(path)
            else:
                # Update the context object with components of the request and
                # with the query parameters.
                ctx.environ = environ

                form = cgi.parse(environ=environ)
## FIXME: make this wsgi compatible.
                ## conlen = int(self.environ['CONTENT_LENGTH'])
                ## s = self.environ['wsgi.input'].read(conlen)
                ## form = cgi.parse_qs(s)

                ctx.__dict__.update(form)
                ctx.__dict__.update(vardict)

                page(self, ctx)

                # Add session cookie to headers, if necessary.
                if ishtml and not has_cookie:
                    for k, v in sorted(cookie.items()):
                        self.headers.add_header('Set-Cookie', v.OutputString())

                start_response('200 OK', self.headers.items())
                return self.data

        except HttpRedirect, e:
            location = e.message
            start_response(e.status, [('Location', location)])
            return [str(e)]

        except HttpError, e:
            status = getattr(e, 'status', '500 Internal Server Error')
            start_response(status, [('Content-Type', 'text/html')])
            return [str(e)]
Exemplo n.º 27
0
class Handler(object):

    badidre = re.compile(r"[<>\s]")

    def __init__(self, service, filemap, wsgienv, start_resp):
        self._svc = service
        self._fmap = filemap
        self._env = wsgienv
        self._start = start_resp
        self._meth = wsgienv.get('REQUEST_METHOD', 'GET')
        self._hdr = Headers([])
        self._code = 0
        self._msg = "unknown status"

    def send_error(self, code, message):
        status = "{0} {1}".format(str(code), message)
        self._start(status, [], sys.exc_info())

    def add_header(self, name, value):
        # Caution: HTTP does not support Unicode characters (see
        # https://www.python.org/dev/peps/pep-0333/#unicode-issues);
        # thus, this will raise a UnicodeEncodeError if the input strings
        # include Unicode (char code > 255).
        e = "ISO-8859-1"
        self._hdr.add_header(name.encode(e), value.encode(e))

    def set_response(self, code, message):
        self._code = code
        self._msg = message

    def end_headers(self):
        status = "{0} {1}".format(str(self._code), self._msg)
        ###DEBUG:
        log.debug("sending header: %s", str(self._hdr.items()))
        ###DEBUG:
        self._start(status, self._hdr.items())

    def handle(self):
        meth_handler = 'do_' + self._meth

        path = self._env.get('PATH_INFO', '/')[1:]

        if hasattr(self, meth_handler):
            return getattr(self, meth_handler)(path)
        else:
            return self.send_error(
                403, self._meth + " not supported on this resource")

    def do_GET(self, path):

        if not path:
            self.code = 403
            self.send_error(self.code, "No identifier given")
            return ["Server ready\n"]

        if path.startswith('/'):
            path = path[1:]
        parts = path.split('/')

        if parts[0] == "ark:":
            # support full ark identifiers
            if len(parts) > 2 and parts[1] == NIST_ARK_NAAN:
                dsid = parts[2]
            else:
                dsid = '/'.join(parts[:3])
            filepath = "/".join(parts[3:])
        else:
            dsid = parts[0]
            filepath = "/".join(parts[1:])

        if self.badidre.search(dsid):
            self.send_error(400, "Unsupported SIP identifier: " + dsid)
            return []

        if filepath:
            return self.get_datafile(dsid, filepath)
        return self.get_metadata(dsid)

    def get_metadata(self, dsid):

        try:
            mdata = self._svc.resolve_id(dsid)
        except IDNotFound as ex:
            self.send_error(404,
                            "Dataset with ID={0} not available".format(dsid))
            return []
        except SIPDirectoryNotFound as ex:
            # shouldn't happen
            self.send_error(404,
                            "Dataset with ID={0} not available".format(dsid))
            return []
        except Exception as ex:
            log.exception("Internal error: " + str(ex))
            self.send_error(500, "Internal error")
            return []

        self.set_response(200, "Identifier found")
        self.add_header('Content-Type', 'application/json')
        self.end_headers()

        return [json.dumps(mdata, indent=4, separators=(',', ': '))]

    def get_datafile(self, id, filepath):

        try:
            loc, mtype = self._svc.locate_data_file(id, filepath)
        except IDNotFound as ex:
            self.send_error(404,
                            "Dataset with ID={0} not available".format(id))
            return []
        except SIPDirectoryNotFound as ex:
            # shouldn't happen
            self.send_error(404,
                            "Dataset with ID={0} not available".format(id))
            return []
        except Exception as ex:
            log.exception("Internal error: " + str(ex))
            self.send_error(500, "Internal error")
            return []
        if not loc:
            self.send_error(
                404, "Dataset (ID={0}) does not contain file={1}".format(
                    id, filepath))

        xsend = None
        prfx = [p for p in self._fmap.keys() if loc.startswith(p + '/')]
        if len(prfx) > 0:
            xsend = self._fmap[prfx[0]] + loc[len(prfx[0]):]
            log.debug("Sending file via X-Accel-Redirect: %s", xsend)

        self.set_response(200, "Data file found")
        self.add_header('Content-Type', mtype)
        if xsend:
            self.add_header('X-Accel-Redirect', xsend)
        self.end_headers()

        if xsend:
            return []
        return self.iter_file(loc)

    def iter_file(self, loc):
        # this is the backup, inefficient way to send a file
        with open(loc, 'rb') as fd:
            buf = fd.read(5000000)
            yield buf

    def do_HEAD(self, path):

        self.do_GET(path)
        return []
Exemplo n.º 28
0
Arquivo: www.py Projeto: gdw2/zim
	def __call__(self, environ, start_response):
		'''Main function for handling a single request. Follows the
		WSGI API.

		@param environ: dictionary with environment variables for the
		request and some special variables. See the PEP for expected
		variables.

		@param start_response: a function that can be called to set the
		http response and headers. For example::

			start_response(200, [('Content-Type', 'text/plain')])

		@returns: the html page content as a list of lines
		'''
		headerlist = []
		headers = Headers(headerlist)
		path = environ.get('PATH_INFO', '/')
		try:
			methods = ('GET', 'HEAD')
			if not environ['REQUEST_METHOD'] in methods:
				raise WWWError('405', headers=[('Allow', ', '.join(methods))])

			# cleanup path
			#~ print 'INPUT', path
			path = path.replace('\\', '/') # make it windows save
			isdir = path.endswith('/')
			parts = [p for p in path.split('/') if p and not p == '.']
			if [p for p in parts if p.startswith('.')]:
				# exclude .. and all hidden files from possible paths
				raise PathNotValidError()
			path = '/' + '/'.join(parts)
			if isdir and not path == '/': path += '/'
			#~ print 'PATH', path

			if not path:
				path = '/'
			elif path == '/favicon.ico':
				path = '/+resources/favicon.ico'
			else:
				path = urllib.unquote(path)

			if path == '/':
				headers.add_header('Content-Type', 'text/html', charset='utf-8')
				content = self.render_index()
			elif path.startswith('/+docs/'):
				dir = self.notebook.document_root
				if not dir:
					raise PageNotFoundError(path)
				file = dir.file(path[7:])
				content = [file.raw()]
					# Will raise FileNotFound when file does not exist
				headers['Content-Type'] = file.get_mimetype()
			elif path.startswith('/+file/'):
				file = self.notebook.dir.file(path[7:])
					# TODO: need abstraction for getting file from top level dir ?
				content = [file.raw()]
					# Will raise FileNotFound when file does not exist
				headers['Content-Type'] = file.get_mimetype()
 			elif path.startswith('/+resources/'):
				if self.template.resources_dir:
					file = self.template.resources_dir.file(path[12:])
					if not file.exists():
						file = data_file('pixmaps/%s' % path[12:])
				else:
					file = data_file('pixmaps/%s' % path[12:])

				if file:
					content = [file.raw()]
						# Will raise FileNotFound when file does not exist
					headers['Content-Type'] = file.get_mimetype()
	 			else:
					raise PageNotFoundError(path)
			else:
				# Must be a page or a namespace (html file or directory path)
				headers.add_header('Content-Type', 'text/html', charset='utf-8')
				if path.endswith('.html'):
					pagename = path[:-5].replace('/', ':')
				elif path.endswith('/'):
					pagename = path[:-1].replace('/', ':')
				else:
					raise PageNotFoundError(path)

				path = self.notebook.resolve_path(pagename)
				page = self.notebook.get_page(path)
				if page.hascontent:
					content = self.render_page(page)
				elif page.haschildren:
					content = self.render_index(page)
				else:
					raise PageNotFoundError(page)
		except Exception, error:
			headerlist = []
			headers = Headers(headerlist)
			headers.add_header('Content-Type', 'text/plain', charset='utf-8')
			if isinstance(error, (WWWError, FileNotFoundError)):
				logger.error(error.msg)
				if isinstance(error, FileNotFoundError):
					error = PageNotFoundError(path)
					# show url path instead of file path
				if error.headers:
					for key, value in error.headers:
						headers.add_header(key, value)
				start_response(error.status, headerlist)
				content = unicode(error).splitlines(True)
			# TODO also handle template errors as special here
			else:
				# Unexpected error - maybe a bug, do not expose output on bugs
				# to the outside world
				logger.exception('Unexpected error:')
				start_response('500 Internal Server Error', headerlist)
				content = ['Internal Server Error']
			if environ['REQUEST_METHOD'] == 'HEAD':
				return []
			else:
				return [string.encode('utf-8') for string in content]
Exemplo n.º 29
0
class SimIngestHandler(object):
    def __init__(self, wsgienv, start_resp):
        self._env = wsgienv
        self._start = start_resp
        self._meth = wsgienv.get('REQUEST_METHOD', 'GET')
        self._hdr = Headers([])
        self._code = 0
        self._msg = "unknown status"
        self._auth = (authmeth, authkey)

    def send_error(self, code, message):
        status = "{0} {1}".format(str(code), message)
        self._start(status, [], sys.exc_info())
        return []

    def add_header(self, name, value):
        self._hdr.add_header(name, value)

    def set_response(self, code, message):
        self._code = code
        self._msg = message

    def end_headers(self):
        status = "{0} {1}".format(str(self._code), self._msg)
        self._start(status, self._hdr.items())

    def handle(self, env, start_resp):
        meth_handler = 'do_' + self._meth

        path = self._env.get('PATH_INFO', '/')[1:]
        params = cgi.parse_qs(self._env.get('QUERY_STRING', ''))
        print("AUTH METHOD: %s" % self._auth[0], file=sys.stderr)
        if not self.authorize():
            return self.send_unauthorized()

        if hasattr(self, meth_handler):
            return getattr(self, meth_handler)(path, params)
        else:
            return self.send_error(
                403, self._meth + " not supported on this resource")

    def authorize(self):
        if self._auth[0] == 'header':
            return self.authorize_via_headertoken()
        else:
            return self.authorize_via_queryparam()

    def authorize_via_queryparam(self):
        params = cgi.parse_qs(self._env.get('QUERY_STRING', ''))
        auths = params.get('auth', [])
        if self._auth[1]:
            # match the last value provided
            return len(auths) > 0 and self._auth[1] == auths[-1]
        if len(auths) > 0:
            log.warn(
                "Authorization key provided, but none has been configured")
        return len(auths) == 0

    def authorize_via_headertoken(self):
        authhdr = self._env.get('HTTP_AUTHORIZATION', "")
        print("Request HTTP_AUTHORIZATION: %s" % authhdr, file=sys.stderr)
        parts = authhdr.split()
        if self._auth[1]:
            return len(parts) > 1 and parts[0] == "Bearer" and \
                self._auth[1] == parts[1]
        if authhdr:
            log.warn(
                "Authorization key provided, but none has been configured")
        return authhdr == ""

    def send_unauthorized(self):
        self.set_response(401, "Not authorized")
        if self._auth[0] == 'header':
            self.add_header('WWW-Authenticate', 'Bearer')
        self.end_headers()
        return []

    def do_GET(self, path, params=None):
        path = path.strip('/')
        if not path:
            try:
                out = json.dumps(["nerdm", "invalid"]) + '\n'
            except Exception, ex:
                return self.send_error(500, "Internal error")

            self.set_response(200, "Supported Record Types")
            self.add_header('Content-Type', 'application/json')
            self.end_headers()
            return [out]
        elif path in "nerdm invalid".split():
            self.set_response(200, "Service is ready")
            self.add_header('Content-Type', 'application/json')
            self.end_headers()
            return ["Service ready\n"]
Exemplo n.º 30
0
class Handler(object):

    def __init__(self, loaders, wsgienv, start_resp, archdir, auth=None, postexec=None):
        self._env = wsgienv
        self._start = start_resp
        self._meth = wsgienv.get('REQUEST_METHOD', 'GET')
        self._hdr = Headers([])
        self._code = 0
        self._msg = "unknown status"
        self._auth = auth
        self._archdir = archdir
        self._postexec = postexec

        self._loaders = loaders

    def send_error(self, code, message):
        status = "{0} {1}".format(str(code), message)
        self._start(status, [], sys.exc_info())
        return []

    def add_header(self, name, value):
        self._hdr.add_header(name, value)

    def set_response(self, code, message):
        self._code = code
        self._msg = message

    def end_headers(self):
        status = "{0} {1}".format(str(self._code), self._msg)
        self._start(status, list(self._hdr.items()))

    def handle(self):
        meth_handler = 'do_'+self._meth

        path = self._env.get('PATH_INFO', '/')[1:]
        if not self.authorize():
            return self.send_unauthorized()

        if hasattr(self, meth_handler):
            return getattr(self, meth_handler)(path)
        else:
            return self.send_error(403, self._meth +
                                   " not supported on this resource")

    def authorize(self):
        if self._auth[0] == 'header':
            return self.authorize_via_headertoken()
        else:
            return self.authorize_via_queryparam()

    def authorize_via_queryparam(self):
        params = parse_qs(self._env.get('QUERY_STRING', ''))
        auths = params.get('auth',[])
        if self._auth[1]:
            # match the last value provided
            return len(auths) > 0 and self._auth[1] == auths[-1]  
        if len(auths) > 0:
            log.warning("Authorization key provided, but none has been configured")
        return len(auths) == 0

    def authorize_via_headertoken(self):
        authhdr = self._env.get('HTTP_AUTHORIZATION', "")
        log.debug("Request HTTP_AUTHORIZATION: %s", authhdr)
        parts = authhdr.split()
        if self._auth[1]:
            return len(parts) > 1 and parts[0] == "Bearer" and \
                self._auth[1] == parts[1]
        if authhdr:
            log.warning("Authorization key provided, but none has been configured")
        return authhdr == ""

    def send_unauthorized(self):
        self.set_response(401, "Not authorized")
        if self._auth[0] == 'header':
            self.add_header('WWW-Authenticate', 'Bearer')
        self.end_headers()
        return []

    def do_GET(self, path):
        path = path.strip('/')
        if not path:
            try:
                out = json.dumps(list(self._loaders.keys())) + '\n'
                out = out.encode()
            except Exception as ex:
                log.exception("Internal error: "+str(ex))
                return self.send_error(500, "Internal error")

            self.set_response(200, "Supported Record Types")
            self.add_header('Content-Type', 'application/json')
            self.add_header('Content-Length', str(len(out)))
            self.end_headers()
            return [out]
        elif path in self._loaders:
            self.set_response(200, "Service is ready")
            self.add_header('Content-Type', 'application/json')
            self.end_headers()
            return [b"Service ready\n"]
        else:
            return self.send_error(404, "resource does not exist")
            
    def do_POST(self, path):
        path = path.strip('/')
        steps = path.split('/')
        if len(steps) == 0:
            return self.send_error(405, "POST not supported on this resource")
        elif len(steps) == 1:
            if steps[0] == 'nerdm':
                return self.ingest_nerdm_record()
            else:
                return self.send_error(403, "new records are not allowed for " +
                                       "submission to this resource")
        else:
            return self.send_error(404, "resource does not exist")

    def nerdm_archive_cache(self, rec):
        """
        cache a NERDm record into a local disk archive.  The cache is for 
        records that have been accepted but not ingested.  
        """
        try:
            arkid = re.sub(r'/.*$', '', re.sub(r'ark:/\d+/', '', rec['@id']))
            ver = rec.get('version', '1.0.0').replace('.', '_')
            recid = "%s-v%s" % (os.path.basename(arkid), ver)
            outfile = os.path.join(self._archdir, '_cache', recid+".json")
            with open(outfile, 'w') as fd:
                json.dump(rec, fd, indent=2)

            return recid
        
        except KeyError as ex:
            # this shouldn't happen if the record was already validated
            raise RecordIngestError("submitted record is missing the @id "+
                                    "property")
        except ValueError as ex:
            # this shouldn't happen if the record was already validated
            raise RecordIngestError("submitted record is apparently invalid; "+
                                    "unable to submit")
        except OSError as ex:
            raise RuntimeError("Failed to cache record ({0}): {1}"
                               .format(arkid, str(ex)))

    def nerdm_archive_commit(self, recid):
        """
        commit a previously cached record to the local disk archive.  This
        method is called after the record has been successfully ingested to
        the RMM's database.
        """
        outfile = os.path.join(self._archdir, '_cache', recid+".json")
        if not os.path.exists(outfile):
            raise RuntimeError("record to commit ({0}) not found in cache: {1}"
                               .format(recid, outfile))
        try:
            os.rename(outfile,
                      os.path.join(self._archdir, os.path.basename(outfile)))
        except OSError as ex:
            raise RuntimeError("Failed to archvie record ({0}): {1}"
                               .format(recid, str(ex)))
        

    def ingest_nerdm_record(self):
        """
        Accept a NERDm record for ingest into the RMM
        """
        loader = self._loaders['nerdm']

        try:
            clen = int(self._env['CONTENT_LENGTH'])
        except KeyError as ex:
            log.exception("Content-Length not provided for input record")
            return self.send_error(411, "Content-Length is required")
        except ValueError as ex:
            log.exception("Failed to parse input JSON record: "+str(e))
            return self.send_error(400, "Content-Length is not an integer")

        try:
            bodyin = self._env['wsgi.input']
            doc = bodyin.read(clen)
            rec = json.loads(doc)
        except Exception as ex:
            log.exception("Failed to parse input JSON record: "+str(ex))
            log.warning("Input document starts...\n{0}...\n...{1} ({2}/{3} chars)"
                        .format(doc[:75], doc[-20:], len(doc), clen))
            return self.send_error(400,
                                   "Failed to load input record (bad format?): "+
                                   str(ex))

        try:
            recid = self.nerdm_archive_cache(rec)
            
            res = loader.load(rec, validate=True)
            if res.failure_count > 0:
                res = res.failures()[0]
                logmsg = "Failed to load record with "+str(res.key)
                for e in res.errs:
                    logmsg += "\n  "+str(e)
                log.error(logmsg)
                self.set_response(400, "Input record is not valid")
                self.add_header('Content-Type', 'application/json')
                self.end_headers()
                out = json.dumps([str(e) for e in res.errs]) + '\n'
                return [ out.encode() ]

        except RecordIngestError as ex:
            log.exception("Failed to load posted record: "+str(ex))
            self.set_response(400, "Input record is not valid (missing @id)")
            self.add_header('Content-Type', 'application/json')
            self.end_headers()
            out = json.dumps([ "Record is missing @id property" ]) + '\n'
            return [ out.encode() ]

        except Exception as ex:
            log.exception("Loading error: "+str(ex))
            return self.send_error(500, "Load failure due to internal error")

        try:
            self.nerdm_archive_commit(recid)
        except Exception as ex:
            log.exception("Commit error: "+str(ex))

        if self._postexec:
            # run post-commit script
            try:
                self.nerdm_post_commit(recid)
            except Exception as ex:
                log.exception("Post-commit error: "+str(ex))

        log.info("Accepted record %s with @id=%s",
                 rec.get('ediid','?'), rec.get('@id','?'))
        self.set_response(200, "Record accepted")
        self.end_headers()
        return []

    def nerdm_post_commit(self, recid):
        """
        run an external executable for further processing after the record is commited to 
        the database (e.g. update an external index)
        """
        cmd = _mkpostcomm(self._postexec, recid)

        try:
            log.debug("Executing post-commit script:\n  %s", " ".join(cmd))
            p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
            (out, err) = p.communicate()
            if p.returncode != 0:
                log.error("Error occurred while running post-commit script:\n"+(err or out))
        except OSError as ex:
            log.error("Failed to execute post-commit script:\n  %s\n%s", " ".join(cmd), str(ex))
        except Exception as ex:
            log.error("Unexpected failure executing post-commit script:\n  %s\n%s", " ".join(cmd), str(ex))
Exemplo n.º 31
0
class application(object):
    # don't serve static by default
    static_serve = False
    static_alias = {'': 'ketcher.html'}
    static_root = None

    indigo = None
    indigo_inchi = None

    def __init__(self, environ, start_response):
        self.path = environ['PATH_INFO'].strip('/')
        self.method = environ['REQUEST_METHOD']
        self.content_type = environ.get('CONTENT_TYPE', '')
        self.fields = FieldStorage(fp=environ['wsgi.input'],
                                   environ=environ,
                                   keep_blank_values=True)
        self.FileWrapper = environ.get('wsgi.file_wrapper', FileWrapper)
        self.headers = Headers([])

        if self.static_serve:
            self.headers['Access-Control-Allow-Origin'] = '*'

        route = getattr(self, 'on_' + self.path, None)
        if route is None:
            route = self.serve_static if self.method == 'GET' and \
                                         self.static_serve else self.notsupported

        status = "200 OK"
        try:
            self.response = route()
        except self.HttpException as e:
            status = e.args[0]
            self.response = [e.args[1]]

        self.headers.setdefault('Content-Type', 'text/plain')
        start_response(status, self.headers.items())

    def __iter__(self):
        for chunk in self.response:
            yield chunk if sys.version_info.major < 3 or \
                           not hasattr(chunk, 'encode') else chunk.encode()

    def notsupported(self):
        raise self.HttpException("405 Method Not Allowed",
                                 "Request not supported")

    def indigo_required(method):
        def wrapper(self, **args):
            if not self.indigo:
                raise self.HttpException("501 Not Implemented",
                                         "Indigo libraries are not found")
            try:
                return method(self, **args)
            except indigo.IndigoException as e:
                message = str(sys.exc_info()[1])
                if 'indigoLoad' in message:  # error on load
                    message = "Cannot load the specified " + \
                              "structure: %s " % str(e)
                raise self.HttpException("400 Bad Request", message)

        return wrapper

    @indigo_required
    def on_knocknock(self):
        return ["You are welcome!"]

    @indigo_required
    def on_layout(self):
        moldata = None
        if self.method == 'GET' and 'smiles' in self.fields:
            moldata = self.fields.getfirst('smiles')
        elif self.is_form_request() and 'moldata' in self.fields:
            moldata = self.fields.getfirst('moldata')
        selective = 'selective' in self.fields
        if moldata:
            if '>>' in moldata or moldata.startswith('$RXN'):
                rxn = self.indigo.loadQueryReaction(moldata)
                if selective:
                    for mol in rxn.iterateMolecules():
                        self.selective_layout(mol)
                else:
                    rxn.layout()
                return ["Ok.\n", rxn.rxnfile()]
            elif moldata.startswith('InChI'):
                mol = self.indigo_inchi.loadMolecule(moldata)
                mol.layout()
                return ["Ok.\n", mol.molfile()]
            else:
                mol = self.indigo.loadQueryMolecule(moldata)
                if selective:
                    for rg in mol.iterateRGroups():
                        for frag in rg.iterateRGroupFragments():
                            self.selective_layout(frag)
                    self.selective_layout(mol)
                else:
                    mol.layout()
                return ["Ok.\n", mol.molfile()]
        self.notsupported()

    @indigo_required
    def on_smiles(self):
        moldata = self.fields.getfirst('moldata')
        if moldata:
            if '>>' in moldata or moldata.startswith('$RXN'):
                rxn = self.indigo.loadQueryReaction(moldata)
                return ["Ok.\n", rxn.smiles()]
            elif moldata.startswith('InChI'):
                mol = self.indigo_inchi.loadMolecule(moldata)
                return ["Ok.\n", mol.smiles()]
            else:
                mol = self.indigo.loadQueryMolecule(moldata)
                return ["Ok.\n", mol.smiles()]
        self.notsupported()

    @indigo_required
    def on_getinchi(self):
        md, is_rxn = self.load_moldata()
        inchi = self.indigo_inchi.getInchi(md)
        return ["Ok.\n", inchi]

    @indigo_required
    def on_getmolfile(self):
        md, is_rxn = self.load_moldata()
        return ["Ok.\n", md.molfile()]

    @indigo_required
    def on_automap(self):
        moldata = None
        if self.method == 'GET' and 'smiles' in self.fields:
            moldata = self.fields.getfirst('smiles')
        elif self.is_form_request() and 'moldata' in self.fields:
            moldata = self.fields.getfirst('moldata')

        if moldata:
            mode = self.fields.getfirst('mode', 'discard')
            rxn = self.indigo.loadQueryReaction(moldata)
            if not moldata.startswith('$RXN'):
                rxn.layout()
            rxn.automap(mode)
            return ["Ok.\n", rxn.rxnfile()]
        self.notsupported()

    @indigo_required
    def on_aromatize(self):
        try:
            md, is_rxn = self.load_moldata()
        except:
            message = str(sys.exc_info()[1])
            if message.startswith("\"molfile loader:") and \
               message.endswith("queries\""): # hack to avoid user confusion
                md, is_rxn = self.load_moldata(True)
            else:
                raise
        md.aromatize()
        return ["Ok.\n", md.rxnfile() if is_rxn else md.molfile()]

    @indigo_required
    def on_dearomatize(self):
        try:
            md, is_rxn = self.load_moldata()
        except:  # TODO: test for query features presence
            raise self.HttpException("400 Bad Request",
                                     "Molecules and reactions " + \
                                     "containing query features " + \
                                     "cannot be dearomatized yet.")
        md.dearomatize()
        return ["Ok.\n", md.rxnfile() if is_rxn else md.molfile()]

    @indigo_required
    def on_calculate_cip(self):
        application.indigo.setOption('molfile-saving-add-stereo-desc', True)
        try:
            md, is_rxn = self.load_moldata()
        except:
            message = str(sys.exc_info()[1])
            if message.startswith("\"molfile loader:") and \
               message.endswith("queries\""): # hack to avoid user confusion
                md, is_rxn = self.load_moldata(True)
            else:
                raise
        result = md.rxnfile() if is_rxn else md.molfile()
        application.indigo.setOption('molfile-saving-add-stereo-desc', False)
        return ["Ok.\n", result]

    def on_open(self):
        if self.is_form_request():
            self.headers.add_header('Content-Type', 'text/html')
            return [
                '<html><body onload="parent.ui.loadMoleculeFromFile()" title="',
                b64encode("Ok.\n"),
                b64encode(self.fields.getfirst('filedata')), '"></body></html>'
            ]
        self.notsupported()

    def on_save(self):
        if self.is_form_request():
            type, data = self.fields.getfirst('filedata').split('\n', 1)
            type = type.strip()
            if type == 'smi':
                self.headers.add_header('Content-Type',
                                        'chemical/x-daylight-smiles')
            elif type == 'mol':
                if data.startswith('$RXN'):
                    type = 'rxn'
                self.headers.add_header('Content-Type',
                                        'chemical/x-mdl-%sfile' % type)

            self.headers.add_header('Content-Length', str(len(data)))
            self.headers.add_header('Content-Disposition',
                                    'attachment',
                                    filename='ketcher.%s' % type)
            return [data]
        self.notsupported()

    class HttpException(Exception):
        pass

    def load_moldata(self, is_query=False):
        moldata = self.fields.getfirst('moldata')
        is_rxn = False
        if moldata.startswith('$RXN'):
            if is_query:
                md = self.indigo.loadQueryReaction(moldata)
            else:
                md = self.indigo.loadReaction(moldata)
            is_rxn = True
        elif moldata.startswith('InChI'):
            md = self.indigo_inchi.loadMolecule(moldata)
            md.layout()
        else:
            if is_query:
                md = self.indigo.loadQueryMolecule(moldata)
            else:
                md = self.indigo.loadMolecule(moldata)
        return md, is_rxn

    def selective_layout(self, mol):
        dsgs = [dsg for dsg in mol.iterateDataSGroups() \
                if dsg.description() == '_ketcher_selective_layout' and \
                dsg.data() == '1']
        atoms = sorted([atom.index() for dsg in dsgs \
                        for atom in dsg.iterateAtoms()])
        for dsg in dsgs:
            dsg.remove()
        mol.getSubmolecule(atoms).layout()
        return mol

    def serve_static(self):
        root = path.realpath(self.static_root or getcwd())
        fpath = self.static_alias.get(self.path, self.path)
        fpath = path.realpath(path.join(root, fpath))

        if not fpath.startswith(root + path.sep) or not path.isfile(fpath) \
           or fpath == path.realpath(__file__):
            raise self.HttpException("404 Not Found",
                                     "Requested file isn't accessible")

        self.headers['Content-Type'] = guess_type(fpath)[0] or 'text/plain'
        try:
            fd = open(fpath, 'rb')
            return self.FileWrapper(fd) if self.method == 'GET' else ['']
        except (IOError, OSError):
            raise self.HttpException(
                "402 Payment Required",  # or 403, hmm..
                "Must get more money for overtime")

    def is_form_request(self):
        return self.method == 'POST' and \
               (self.content_type.startswith('application/x-www-form-urlencoded')
                or self.content_type.startswith('multipart/form-data'))
Exemplo n.º 32
0
class SimDistribHandler(object):
    def __init__(self, archive, wsgienv, start_resp):
        self.arch = archive
        self._env = wsgienv
        self._start = start_resp
        self._meth = wsgienv.get('REQUEST_METHOD', 'GET')
        self._hdr = Headers([])
        self._code = 0
        self._msg = "unknown status"

    def send_error(self, code, message):
        status = "{0} {1}".format(str(code), message)
        self._start(status, [], sys.exc_info())
        return []

    def add_header(self, name, value):
        self._hdr.add_header(name, value)

    def set_response(self, code, message):
        self._code = code
        self._msg = message

    def end_headers(self):
        status = "{0} {1}".format(str(self._code), self._msg)
        self._start(status, self._hdr.items())

    def handle(self, env, start_resp):
        meth_handler = 'do_' + self._meth

        path = self._env.get('PATH_INFO', '/')[1:]
        params = cgi.parse_qs(self._env.get('QUERY_STRING', ''))

        if hasattr(self, meth_handler):
            return getattr(self, meth_handler)(path, params)
        else:
            return self.send_error(
                403, self._meth + " not supported on this resource")

    def do_HEAD(self, path, params=None, forhead=False):
        return self.do_GET(path, params, True)

    def do_GET(self, path, params=None, forhead=False):
        aid = None
        vers = None
        path = path.strip('/')
        if path.startswith("od/ds/"):
            path = path[len("od/ds/"):]
        print("processing " + path)

        # refresh the archive
        self.arch.loadinfo()

        if not path:
            try:
                out = json.dumps(self.arch.aipids) + '\n'
            except Exception as ex:
                return self.send_error(500, "Internal error")

            self.set_response(200, "AIP Identifiers")
            self.add_header('Content-Type', 'application/json')
            self.add_header('Content-Length', str(len(out)))
            self.end_headers()
            if forhead:
                return []
            return [out]

        elif path.startswith("_aip/"):
            # requesting a bag file
            path = path[len("_aip/"):].strip('/')
            filepath = os.path.join(self.arch.dir, path)
            if os.path.isfile(filepath):
                self.set_response(200, "Bag file found")
                self.add_header('Content-Type', "application/zip")
                self.end_headers()
                if forhead:
                    return []
                return self.iter_file(filepath)
            else:
                return self.send_error(404, "bag file does not exist")

        elif '/' in path:
            parts = path.split('/', 1)
            aid = parts[0]
            path = (len(parts) > 1 and parts[1]) or ''
            print("accessing " + aid)

        elif path:
            aid = path
            path = ''

        else:
            return self.send_error(404, "resource does not exist")

        # path-info is now captured as aid and path
        if aid not in self.arch._aips:
            return self.send_error(404, "resource does not exist")

        if not path:
            self.set_response(200, "AIP Identifier exists")
            self.add_header('Content-Type', 'application/json')
            self.add_header('Content-Length', str(len(aid) + 4))
            self.end_headers()
            if forhead:
                return []
            return ['["' + aid + '"]']

        elif path == "_aip":
            try:
                out = json.dumps(self.arch.list_bags(aid)) + '\n'
            except Exception, ex:
                return self.send_error(500, "Internal error")

            self.set_response(200, "All bags for ID")
            self.add_header('Content-Type', 'application/json')
            self.add_header('Content-Length', str(len(out)))
            self.end_headers()
            if forhead:
                return []
            return [out]

        elif path == "_aip/_head":
            try:
                out = self.arch.head_for(aid)
                if out:
                    out = json.dumps(out) + '\n'
            except Exception, ex:
                print(
                    "Failed to create JSON output for head bag, aid={0}: {2}".
                    format(aid, vers, str(ex)))
                return self.send_error(500, "Internal error")
Exemplo n.º 33
0
class Response:
    default_status = 200
    default_content_type = 'text/html; charset=UTF-8'

    def __init__(self, body: str='', status: int=None, headers: Dict=None,
                 **more_headers) -> None:
        self.headers = Headers()
        self.body = body
        self._status_code = status or self.default_status
        self._cookies = SimpleCookie()  # type: ignore

        if headers:
            for name, value in headers.items():
                self.headers.add_header(name, value)
        if more_headers:
            for name, value in more_headers.items():
                self.headers.add_header(name, value)

    @property
    def status_code(self):
        """ The HTTP status code as an integer (e.g. 404)."""
        return self._status_code

    @property
    def status(self):
        """ The HTTP status line as a string (e.g. ``404 Not Found``)."""
        if not 100 <= self._status_code <= 999:
            raise ValueError('Status code out of range.')
        status = _HTTP_STATUS_LINES.get(self._status_code)
        return str(status or ('{} Unknown'.format(self._status_code)))

    @status.setter
    def status(self, status_code: int):
        if not 100 <= status_code <= 999:
            raise ValueError('Status code out of range.')
        self._status_code = status_code

    @property
    def headerlist(self) -> List[Tuple[str, str]]:
        """ WSGI conform list of (header, value) tuples. """
        out = []  # type: List[Tuple[str, str]]
        if 'Content-Type' not in self.headers:
            self.headers.add_header('Content-Type', self.default_content_type)
        out += [(key, value)
                for key in self.headers.keys()
                for value in self.headers.get_all(key)]
        if self._cookies:
            for c in self._cookies.values():
                out.append(('Set-Cookie', c.OutputString()))
        return [(k, v.encode('utf8').decode('latin1')) for (k, v) in out]

    def set_cookie(self, key: str, value: Any, expires: str=None, path: str=None, **options: Dict[str, Any]) -> None:
        from datetime import timedelta, datetime, date
        import time
        self._cookies[key] = value
        if expires:
            self._cookies[key]['expires'] = expires
        if path:
            self._cookies[key]['path'] = path

        for k, v in options.items():
            if k == 'max_age':
                if isinstance(v, timedelta):
                    v = v.seconds + v.days * 24 * 3600  # type: ignore
            if k == 'expires':
                if isinstance(v, (date, datetime)):
                    v = v.timetuple()  # type: ignore
                elif isinstance(v, (int, float)):
                    v = v.gmtime(value)  # type: ignore
                v = time.strftime("%a, %d %b %Y %H:%M:%S GMT", v)  # type: ignore
            self._cookies[key][k.replace('_', '-')] = v  # type: ignore

    def delete_cookie(self, key, **kwargs) -> None:
        kwargs['max_age'] = -1
        kwargs['expires'] = 0
        self.set_cookie(key, '', **kwargs)

    def apply(self, other):
        self.status = other._status_code
        self._cookies = other._cookies
        self.headers = other.headers
        self.body = other.body
Exemplo n.º 34
0
	def __call__(self, environ, start_response):
		'''Main function for handling a single request. Arguments are the file
		handle to write the output to and the path to serve. Any exceptions
		will result in a error response being written.

		First argument is a dictionary with environment variables and some special
		variables. See the PEP for expected variables. The second argument is a
		function that can be called for example like:

			start_response(200, [('Content-Type', 'text/plain')])

		This method is supposed to take care of sending the response line and
		the headers.

		The return value of this call is a list of lines with the content to
		be served.
		'''
		headerlist = []
		headers = Headers(headerlist)
		path = environ.get('PATH_INFO', '/')
		try:
			methods = ('GET', 'HEAD')
			if not environ['REQUEST_METHOD'] in methods:
				raise WWWError('405', headers=[('Allow', ', '.join(methods))])

			# TODO clean up path from any '../' (and ..\)

			if not path:
				path = '/'
			elif path == '/favicon.ico':
				path = '/+icons/favicon.ico'
			elif path in icons:
				# TODO FIXME HACK - this translation needs to be done when exporting
				path = '/+icons/' + icons[path]

			if self.notebook is None:
				raise NoConfigError
			elif path == '/':
				headers.add_header('Content-Type', 'text/html', charset='utf-8')
				content = self.render_index()
			elif path.startswith('/+docs/'):
				pass # TODO document root
			elif path.startswith('/+file/'):
				pass # TODO attachment or raw source
			elif path.startswith('/+icons/'):
				# TODO check if favicon is overridden or something
				file = data_file('pixmaps/%s' % path[8:])
				if path.endswith('.png'):
					headers['Content-Type'] = 'image/png'
				elif path.endswith('.ico'):
					headers['Content-Type'] = 'image/vnd.microsoft.icon'
				content = [file.read(encoding=None)]
			else:
				# Must be a page or a namespace (html file or directory path)
				headers.add_header('Content-Type', 'text/html', charset='utf-8')
				if path.endswith('.html'):
					pagename = path[:-5].replace('/', ':')
				elif path.endswith('/'):
					pagename = path[:-1].replace('/', ':')
				else:
					raise PageNotFoundError(path)

				pagename = urllib.unquote(pagename)
				path = self.notebook.resolve_path(pagename)
				page = self.notebook.get_page(path)
				if page.hascontent:
					content = self.render_page(page)
				elif page.haschildren:
					content = self.render_index(page)
				else:
					raise PageNotFoundError(page)
		except Exception, error:
			headerlist = []
			headers = Headers(headerlist)
			headers.add_header('Content-Type', 'text/plain', charset='utf-8')
			if isinstance(error, WWWError):
				logger.error(error.msg)
				if error.headers:
					header.extend(error.headers)
				start_response(error.status, headerlist)
				content = unicode(error).splitlines(True)
			# TODO also handle template errors as special here
			else:
				# Unexpected error - maybe a bug, do not expose output on bugs
				# to the outside world
				logger.exception('Unexpected error:')
				start_response('500 Internal Server Error', headerlist)
				content = ['Internal Server Error']
			if environ['REQUEST_METHOD'] == 'HEAD':
				return []
			else:
				return [string.encode('utf-8') for string in content]
Exemplo n.º 35
0
 def make_headers(self, headers):
     h = Headers([("Allow", "GET, HEAD")])
     for item in headers:
         h.add_header(item[0], item[1])
     return h.items()
Exemplo n.º 36
0
    def __call__(self, environ, start_response):
        '''Main function for handling a single request. Follows the
		WSGI API.

		@param environ: dictionary with environment variables for the
		request and some special variables. See the PEP for expected
		variables.

		@param start_response: a function that can be called to set the
		http response and headers. For example::

			start_response(200, [('Content-Type', 'text/plain')])

		@returns: the html page content as a list of lines
		'''
        if self.auth_creds:
            import base64

            def bad_auth():
                body = 'Please authenticate'
                realm = 'zimAuth'
                logger.info('Requesting Basic HTTP-Authentication')
                headers = [('Content-Type', 'text/plain'),
                           ('Content-Length', str(len(body))),
                           ('WWW-Authenticate', 'Basic realm="%s"' % realm)]
                start_response('401 Unauthorized', headers)
                return [body.encode()]

            auth = environ.get('HTTP_AUTHORIZATION')
            if auth:
                scheme, data = auth.split(None, 1)
                assert scheme.lower() == 'basic'
                username, password = base64.b64decode(data).decode(
                    'UTF-8').split(':')
                if username != self.auth_creds[
                        0] or password != self.auth_creds[1]:
                    return bad_auth()
                environ['REMOTE_USER'] = username
                del environ['HTTP_AUTHORIZATION']
            else:
                return bad_auth()

        headerlist = []
        headers = Headers(headerlist)
        path = environ.get('PATH_INFO', '/')
        path = path.encode('iso-8859-1').decode('UTF-8')
        # The WSGI standard mandates iso-8859-1, but we want UTF-8. See:
        # - https://www.python.org/dev/peps/pep-3333/#unicode-issues
        # - https://code.djangoproject.com/ticket/19468
        try:
            methods = ('GET', 'HEAD')
            if not environ['REQUEST_METHOD'] in methods:
                raise WWWError('405', headers=[('Allow', ', '.join(methods))])

            # cleanup path
            path = path.replace('\\', '/')  # make it windows save
            isdir = path.endswith('/')
            parts = [p for p in path.split('/') if p and not p == '.']
            if [p for p in parts if p.startswith('.')]:
                # exclude .. and all hidden files from possible paths
                raise WebPathNotValidError()
            path = '/' + '/'.join(parts)
            if isdir and not path == '/':
                path += '/'

            if not path:
                path = '/'
            elif path == '/favicon.ico':
                path = '/+resources/favicon.ico'
            else:
                path = urllib.parse.unquote(path)

            if path == '/':
                headers.add_header('Content-Type',
                                   'text/html',
                                   charset='utf-8')
                content = self.render_index()
            elif path.startswith('/+docs/'):
                dir = self.notebook.document_root
                if not dir:
                    raise WebPageNotFoundError(path)
                file = dir.file(path[7:])
                content = [file.raw()]
                # Will raise FileNotFound when file does not exist
                headers['Content-Type'] = file.get_mimetype()
            elif path.startswith('/+file/'):
                file = self.notebook.folder.file(path[7:])
                # TODO: need abstraction for getting file from top level dir ?
                content = [file.read_binary()]
                # Will raise FileNotFound when file does not exist
                headers['Content-Type'] = file.mimetype()
            elif path.startswith('/+resources/'):
                if self.template.resources_dir:
                    file = self.template.resources_dir.file(path[12:])
                    if not file.exists():
                        file = data_file('pixmaps/%s' % path[12:])
                else:
                    file = data_file('pixmaps/%s' % path[12:])

                if file:
                    content = [file.raw()]
                    # Will raise FileNotFound when file does not exist
                    headers['Content-Type'] = file.get_mimetype()
                else:
                    raise WebPageNotFoundError(path)
            else:
                # Must be a page or a namespace (html file or directory path)
                headers.add_header('Content-Type',
                                   'text/html',
                                   charset='utf-8')
                if path.endswith('.html'):
                    pagename = path[:-5].replace('/', ':')
                elif path.endswith('/'):
                    pagename = path[:-1].replace('/', ':')
                else:
                    raise WebPageNotFoundError(path)

                path = self.notebook.pages.lookup_from_user_input(pagename)
                try:
                    page = self.notebook.get_page(path)
                    if page.hascontent:
                        content = self.render_page(page)
                    elif page.haschildren:
                        content = self.render_index(page)
                    else:
                        raise WebPageNotFoundError(path)
                except PageNotFoundError:
                    raise WebPageNotFoundError(path)
        except Exception as error:
            headerlist = []
            headers = Headers(headerlist)
            headers.add_header('Content-Type', 'text/plain', charset='utf-8')
            if isinstance(error, (WWWError, FileNotFoundError)):
                logger.error(error.msg)
                if isinstance(error, FileNotFoundError):
                    error = WebPageNotFoundError(path)
                    # show url path instead of file path
                if error.headers:
                    for key, value in error.headers:
                        headers.add_header(key, value)
                start_response(error.status, headerlist)
                content = str(error).splitlines(True)
            # TODO also handle template errors as special here
            else:
                # Unexpected error - maybe a bug, do not expose output on bugs
                # to the outside world
                logger.exception('Unexpected error:')
                start_response('500 Internal Server Error', headerlist)
                content = ['Internal Server Error']

            if environ['REQUEST_METHOD'] == 'HEAD':
                return []
            else:
                return [c.encode('UTF-8') for c in content]
        else:
            start_response('200 OK', headerlist)
            if environ['REQUEST_METHOD'] == 'HEAD':
                return []
            elif content and isinstance(content[0], str):
                return [c.encode('UTF-8') for c in content]
            else:
                return content
Exemplo n.º 37
0
def static_file_view(env, start_response, filename, block_size, charset, CACHE_DURATION):
    method = env['REQUEST_METHOD'].upper()
    if method not in ('HEAD', 'GET'):
        start_response('405 METHOD NOT ALLOWED',
                       [('Content-Type', 'text/plain; UTF-8')])
        return [b'']
    mimetype, encoding = mimetypes.guess_type(filename)
    headers = Headers([])

    cache_days = CACHE_DURATION.get(mimetype, 0)
    expires = datetime.datetime.utcnow() + datetime.timedelta(cache_days)
    headers.add_header('Cache-control', f'public, max-age={expires.strftime(RFC_1123_DATE)}')
    headers.add_header('Expires', expires.strftime(RFC_1123_DATE))
    if env.get('HTTP_IF_MODIFIED_SINCE'):
      if env.get('HTTP_IF_MODIFIED_SINCE') >= generate_last_modified(filename):
        start_response('304 ok', headers.items())
        return [b'304']
    headers.add_header('Content-Encodings', encoding)
    if mimetype:
        headers.add_header('Content-Type', get_content_type(mimetype, charset))
    headers.add_header('Content-Length', get_content_length(filename))
    headers.add_header('Last-Modified', generate_last_modified(filename))
    headers.add_header("Accept-Ranges", "bytes")
    start_response('200 OK', headers.items())
    return _get_body(filename, method, block_size, charset)
Exemplo n.º 38
0
class SimRMMHandler(object):

    def __init__(self, archive, wsgienv, start_resp):
        self.arch = archive
        self._env = wsgienv
        self._start = start_resp
        self._meth = wsgienv.get('REQUEST_METHOD', 'GET')
        self._hdr = Headers([])
        self._code = 0
        self._msg = "unknown status"

    def send_error(self, code, message):
        status = "{0} {1}".format(str(code), message)
        self._start(status, [], sys.exc_info())
        return []

    def add_header(self, name, value):
        self._hdr.add_header(name, value)

    def set_response(self, code, message):
        self._code = code
        self._msg = message

    def end_headers(self):
        status = "{0} {1}".format(str(self._code), self._msg)
        self._start(status, self._hdr.items())

    def handle(self, env, start_resp):
        meth_handler = 'do_'+self._meth

        path = self._env.get('PATH_INFO', '/')[1:]
        params = cgi.parse_qs(self._env.get('QUERY_STRING', ''))

        if hasattr(self, meth_handler):
            return getattr(self, meth_handler)(path, params)
        else:
            return self.send_error(403, self._meth +
                                   " not supported on this resource")

    def do_GET(self, path, params=None):
        if path:
            path = path.rstrip('/')
        if path.startswith("records"):
            path = path[len("records"):].lstrip('/')
        id = None
        print("path="+str(path)+"; params="+str(params))
        if not path and params and "@id" in params:
            path = params["@id"]
            path = (len(path) > 0 and path[0]) or ''
        if path:
            if path.startswith("ark:/88434/"):
                id = path[len("ark:/88434/"):]
            else:
                self.arch.loadlu()
                id = self.arch.ediid_to_id(path)

        if id:
            mdfile = os.path.join(self.arch.dir, id+".json")
        if not id or not os.path.exists(mdfile):
            if not id:
                id = "resource"
            return self.send_error(404, id + " does not exist")

        try:
            with open(mdfile) as fd:
                data = json.load(fd, object_pairs_hook=OrderedDict)
                data["_id"] ={"timestamp":1521220572,"machineIdentifier":3325465}
                if params and "@id" in params:
                    data = { "ResultCount": 1, "PageSize": 0,
                             "ResultData": [ data ] }
        except Exception as ex:
            print(str(ex))
            return self.send_error(500, "Internal error")

        self.set_response(200, "Identifier exists")
        self.add_header('Content-Type', 'application/json')
        self.end_headers()
        return [ json.dumps(data, indent=2) + "\n" ]
Exemplo n.º 39
0
class BaseResponse:
    """Base class for Response."""
    default_status = 200
    default_content_type = 'text/plain;'

    def __init__(self, body=None, status=None, headers=None):
        self._body = body if body else [b'']
        self._status_code = status or self.default_status
        self.headers = Headers()
        self._cookies = SimpleCookie()

        if headers:
            for name, value in headers.items():
                self.headers.add_header(name, value)

    @property
    def body(self):
        return self._body

    @property
    def status_code(self):
        """ The HTTP status code as an integer (e.g. 404)."""
        return self._status_code

    @property
    def status(self):
        """ The HTTP status line as a string (e.g. ``404 Not Found``)."""
        status = _HTTP_STATUS_LINES.get(self._status_code)
        return str(status or ('{} Unknown'.format(self._status_code)))

    @status.setter
    def status(self, status_code):
        if not 100 <= status_code <= 999:
            raise ValueError('Status code out of range.')
        self._status_code = status_code

    @property
    def headerlist(self):
        """ WSGI conform list of (header, value) tuples. """
        if 'Content-Type' not in self.headers:
            self.headers.add_header('Content-Type', self.default_content_type)
        if self._cookies:
            for c in self._cookies.values():
                self.headers.add_header('Set-Cookie', c.OutputString())
        return self.headers.items()

    def set_cookie(self,
                   key,
                   value,
                   expires=None,
                   max_age=None,
                   path='/',
                   secret=None,
                   digestmod=hashlib.sha256):
        from kobin.app import current_config
        if secret is None:
            secret = current_config('SECRET_KEY')
        if secret:
            if isinstance(secret, str):
                secret = secret.encode('utf-8')
            encoded = base64.b64encode(
                pickle.dumps((key, value), pickle.HIGHEST_PROTOCOL))
            sig = base64.b64encode(
                hmac.new(secret, encoded, digestmod=digestmod).digest())
            value_bytes = b'!' + sig + b'?' + encoded
            value = value_bytes.decode('utf-8')

        self._cookies[key] = value
        if len(key) + len(value) > 3800:
            raise ValueError('Content does not fit into a cookie.')

        if max_age is not None:
            if isinstance(max_age, int):
                max_age_value = max_age
            else:
                max_age_value = max_age.seconds + max_age.days * 24 * 3600
            self._cookies[key]['max-age'] = max_age_value
        if expires is not None:
            if isinstance(expires, int):
                expires_value = expires
            else:
                expires_value = time.strftime("%a, %d %b %Y %H:%M:%S GMT",
                                              expires.timetuple())
            self._cookies[key]['expires'] = expires_value
        if path:
            self._cookies[key]['path'] = path

    def delete_cookie(self, key, **kwargs):
        kwargs['max_age'] = -1
        kwargs['expires'] = 0
        self.set_cookie(key, '', **kwargs)
Exemplo n.º 40
0
Arquivo: neo.py Projeto: vtphan/neo
class Response(threading.local):
    def setup(self):
        self.status = http_status['200']
        self.headers = Headers([('Content-type','text/html; charset=UTF-8')])
        self.out = b''

    def download(self, dir, args, cd=False):
        ims = request.env.get('HTTP_IF_MODIFIED_SINCE', '')
        file = os.path.join(dir, *args)
        if not os.access(file, os.R_OK):
            self.error(mesg='File not found',raise_exc=True)
        mimetype, encoding = mimetypes.guess_type(file)
        if mimetype: self.headers.add_header('Content-Type',mimetype)
        if encoding: self.headers.add_header('Content-Encoding',encoding)
        if cd:
            self.headers.add_header('Content-Disposition','attachment',filename=args[-1])
        stats = os.stat(file)
        self.headers.add_header('Content-Length', str(stats.st_size))
        time_fmt = "%a, %d %b %Y %H:%M:%S GMT"
        last_modified = strftime(time_fmt, gmtime(stats.st_mtime))
        self.headers.add_header('Last-Modified', last_modified)

        if ims: ims = strptime(ims.split(";")[0].strip(), time_fmt)
        else: ims = None
        if ims is not None and ims >= gmtime(stats.st_mtime-2):
            date = strftime(time_fmt, gmtime())
            self.headers.add_header('Date', date)
            self.status = http_status['304']
        elif request.method == 'HEAD': self.out = ''
        else: self.out = open(file,'rb').read()

    def error(self, mesg='', status='404', raise_exc=True):
        self.status = http_status[status]
        self.out = mesg
        self.headers = Headers([('Content-type','text/plain')])
        if raise_exc: raise Exception('ResponseError NotFound')

    def output(self):
        self.headers.add_header('Content-Length', str(len(self.out)))
        if type(self.out)==str: return self.out.encode('utf-8')
        return self.out
Exemplo n.º 41
0
class Handler(object):

    badidre = re.compile(r"[<>\s]")

    def __init__(self, service, siptype, wsgienv, start_resp, auth=None):
        self._svc = service
        self._env = wsgienv
        self._start = start_resp
        self._meth = wsgienv.get('REQUEST_METHOD', 'GET')
        self._hdr = Headers([])
        self._code = 0
        self._msg = "unknown status"
        self._auth = auth

    def send_error(self, code, message):
        stat = "{0} {1}".format(str(code), message)
        self._start(stat, [], sys.exc_info())

    def add_header(self, name, value):
        # Caution: HTTP does not support Unicode characters (see
        # https://www.python.org/dev/peps/pep-0333/#unicode-issues);
        # thus, this will raise a UnicodeEncodeError if the input strings
        # include Unicode (char code > 255).
        e = "ISO-8859-1"
        self._hdr.add_header(name.encode(e), value.encode(e))

    def set_response(self, code, message):
        self._code = code
        self._msg = message

    def end_headers(self):
        stat = "{0} {1}".format(str(self._code), self._msg)
        self._start(stat, self._hdr.items())

    def handle(self):
        meth_handler = 'do_' + self._meth

        path = self._env.get('PATH_INFO', '/').strip('/')
        if not self.authorize():
            return self.send_unauthorized()

        if hasattr(self, meth_handler):
            out = getattr(self, meth_handler)(path)
            if isinstance(out, list) and len(out) > 0:
                out.append('\n')
            return out
        else:
            return self.send_error(
                403, self._meth + " not supported on this resource")

    def authorize(self):
        if self._auth[0] == 'header':
            return self.authorize_via_headertoken()
        else:
            return self.authorize_via_queryparam()

    def authorize_via_queryparam(self):
        params = cgi.parse_qs(self._env.get('QUERY_STRING', ''))
        auths = params.get('auth', [])
        if self._auth[1]:
            # match the last value provided
            return len(auths) > 0 and self._auth[1] == auths[-1]
        if len(auths) > 0:
            log.warn(
                "Authorization key provided, but none has been configured")
        return len(auths) == 0

    def authorize_via_headertoken(self):
        authhdr = self._env.get('HTTP_AUTHORIZATION', "")
        parts = authhdr.split()
        if self._auth[1]:
            return len(parts) > 1 and parts[0] == "Bearer" and \
                self._auth[1] == parts[1]
        if authhdr:
            log.warn(
                "Authorization key provided, but none has been configured")
        return authhdr == ""

    def send_unauthorized(self):
        self.set_response(401, "Not authorized")
        if self._auth[0] == 'header':
            self.add_header('WWW-Authenticate', 'Bearer')
        self.end_headers()
        return []

    def do_GET(self, path):
        # return the status on request or a list of previous requests
        steps = path.split('/')
        if steps[0] == '':
            try:
                out = json.dumps(['midas'])
            except Exception, ex:
                log.exception("Internal error: " + str(ex))
                self.send_error(500, "Internal error")
                return ["[]"]

            self.set_response(200, "Supported SIP Types")
            self.add_header('Content-Type', 'application/json')
            self.end_headers()
            return [out]

        elif steps[0] == 'midas':
            if len(steps) > 2:
                path = '/'.join(steps[1:])
                self.send_error(400, "Unsupported SIP identifier: " + path)
                return []
            elif len(steps) > 1:
                if steps[1].startswith("_") or steps[1].startswith(".") or \
                   self.badidre.search(steps[1]):

                    self.send_error(400, "Unsupported SIP identifier: " + path)
                    return []

                return self.request_status(steps[1])

            else:
                return self.requests()
Exemplo n.º 42
0
class SimHandler(object):
    def __init__(self, repo, basepath, prefixes, wsgienv, start_resp):
        self.repo = repo
        self.basepath = basepath
        self.prefs = prefixes
        self._env = wsgienv
        self._start = start_resp
        self._meth = wsgienv.get("REQUEST_METHOD", "GET")

        self._hdr = Headers([])
        self._code = 0
        self._msg = "unknown state"

    def send_error(self,
                   code,
                   message,
                   errtitle=None,
                   errdesc={},
                   tellexc=False):
        edata = None
        if errdesc and not errtitle:
            errtitle = message
        if errtitle:
            edata = {"errors": [{"title": errtitle, "status": code}]}
            if errdesc:
                edata['errors'][0].update(errdesc)

        if edata:
            edata = json.dumps(edata)
            self.add_header("Content-type", JSONAPI_MT)
            self.add_header("Content-length", len(edata))
        status = "{0} {1}".format(str(code), message)
        excinfo = None
        if tellexc:
            excinfo = sys.exc_info()
            if excinfo == (None, None, None):
                excinfo = None
        self._start(status, self._hdr.items(), excinfo)

        if edata:
            return [edata.encode()]
        return []

    def add_header(self, name, value):
        self._hdr.add_header(name, str(value))

    def set_response(self, code, message):
        self._code = code
        self._msg = message

    def end_headers(self):
        status = "{0} {1}".format(str(self._code), self._msg)
        self._start(status, self._hdr.items())

    def handle(self):
        meth_handler = 'do_' + self._meth

        path = self._env.get('PATH_INFO', '/')
        params = parse_qs(self._env.get('QUERY_STRING', ''))

        if path.startswith(self.basepath):
            path = path[len(self.basepath):]
        else:
            return self.send_error(404, "Unsupported service")

        if hasattr(self, meth_handler):
            return getattr(self, meth_handler)(path, params)
        else:
            return self.send_error(
                405, self._meth + " not supported on this resource")

    _envelope = OrderedDict([("data", OrderedDict([("type", "dois")]))])

    def _new_resp(self, id=None, attrs=None):
        out = deepcopy(self._envelope)
        if id:
            out['data']['id'] = id
        if attrs is not None:
            out['data']['attributes'] = attrs
        return out

    def do_GET(self, path, params=None):
        if path:
            path = path.strip('/')
        else:
            return self.send_error(200, "Ready")

        if 'HTTP_ACCEPT' in self._env and self._env[
                'HTTP_ACCEPT'] != JSONAPI_MT:
            return self.send_error(406, "Not Acceptable",
                                   "Unsupported Accept value",
                                   {"detail": self._env['HTTP_ACCEPT']})

        try:
            out = self._new_resp(path, self.repo.describe(path))
        except ValueError as ex:
            return self.send_error(404,
                                   str(ex),
                                   "ID not found",
                                   errdesc={"detail": "path"})

        try:
            out = json.dumps(out)
            self.set_response(200, "Found")
            self.add_header("Content-type", JSONAPI_MT)
            self.add_header("Content-length", len(out))
            self.end_headers()
            return [out.encode()]
        except (ValueError, TypeError) as ex:
            return self.send_error(500,
                                   "JSON encoding error",
                                   errdesc={"detail": str(ex)},
                                   tellexc=True)

    def do_HEAD(self, path, params=None):
        if path:
            path = path.strip('/')
        else:
            return self.send_error(200, "Ready")

        if path in self.repo.ids:
            return self.send_error(200, "ID Found")
        else:
            return self.send_error(404, "ID Not Found")

    def do_POST(self, path, params=None):
        if path:
            path = path.strip('/')

        if path:
            return self.send_error(405,
                                   "Cannot POST to ID",
                                   errdesc={"detail": path})

        if 'HTTP_ACCEPT' in self._env and self._env[
                'HTTP_ACCEPT'] != JSONAPI_MT:
            return self.send_error(406, "Not Acceptable",
                                   "Unsupported Accept value",
                                   {"detail": "self._env['HTTP_ACCEPT']"})
        if 'CONTENT_TYPE' in self._env and self._env[
                'CONTENT_TYPE'] != JSONAPI_MT:
            return self.send_error(415, "Wrong Input Type",
                                   "Unsupported input content type",
                                   {"detail": self._env['CONTENT_TYPE']})

        try:
            bodyin = self._env['wsgi.input'].read().decode('utf-8')
            doc = json.loads(bodyin, object_pairs_hook=OrderedDict)
        except (ValueError, TypeError) as ex:
            return self.send_error(400, "Not JSON",
                                   "Failed to parse input as JSON",
                                   {"detail": str(ex)})

        doi = None
        event = None
        try:
            if doc['data']['type'] != "dois":
                return self.send_error(
                    400, "Wrong input data type", {
                        "detail": doc['data']['type'],
                        "source": {
                            "pointer": "/data/type"
                        }
                    })

            prefix = None
            if 'doi' in doc['data']['attributes']:
                doi = doc['data']['attributes']['doi']
                parts = doi.split('/', 1)
                if len(parts) < 2:
                    return self.send_error(400,
                                           "Bad doi syntax",
                                           errdesc={"detail": doi})

                prefix = parts[0]
            elif 'prefix' in doc['data']['attributes']:
                prefix = doc['data']['attributes']['prefix']
            if prefix and not doi:
                if 'suffix' in doc['data']['attributes']:
                    suffix = doc['data']['attributes']['suffix']
                else:
                    suffix = "rand" + str(random.randrange(10000, 99999))
                doi = "%s/%s" % (prefix, suffix)

            if not doi:
                return self.send_error(400, "No prefix specified")

            if self.prefs and prefix not in self.prefs:
                return self.send_error(403, "Not Authorized for Prefix")

            event = doc['data']['attributes'].get('event')

        except KeyError as ex:
            return self.send_error(400, "Bad Input: Missing property",
                                   {"detail": str(ex)})
        state = "draft"
        errors = None
        if doi in self.repo.ids:
            state = self.repo.ids[doi]['state']
            out = self.repo.update_id(doi, doc['data']['attributes'])
            resp = {"code": 200, "message": "Updated"}
        else:
            out = self.repo.add_id(doi, doc['data']['attributes'])
            resp = {"code": 201, "message": "Created"}

        if (event == "publish" and state != "findable") or \
           (event == "register" and state != "registered"):
            missing = []
            for prop in "url titles publisher publicationYear creators types".split(
            ):
                if prop not in out or not out[prop]:
                    missing.append(prop)
                elif prop.endswith('s') and len(out[prop]) < 1:
                    missing.append(prop)
            if missing:
                self.repo.ids[doi]['state'] = state
                out['state'] = state
                errors = [{
                    "title": "Cannot publish due to missing metadata",
                    "detail": "Missing properties: " + str(missing)
                }]
                resp = {"code": 422, "message": "Unprocessable Entity"}

        try:
            out = self._new_resp(doi, out)
            if errors:
                out['errors'] = errors
            out = json.dumps(out)
            self.set_response(**resp)
            self.add_header("Content-type", JSONAPI_MT)
            self.add_header("Content-length", str(len(out)))
            self.end_headers()
            return [out.encode()]
        except (ValueError, TypeError) as ex:
            return self.send_error(500,
                                   "JSON encoding error",
                                   errdesc={"detail": str(ex)},
                                   tellexc=True)

    def do_PUT(self, path, params=None):
        if path:
            path = path.strip('/')

        if not path:
            return self.send_error(405, "Cannot PUT without ID")

        if 'HTTP_ACCEPT' in self._env and self._env[
                'HTTP_ACCEPT'] != JSONAPI_MT:
            return self.send_error(406, "Not Acceptable",
                                   "Unsupported Accept value",
                                   {"detail": self._env['HTTP_ACCEPT']})
        if 'CONTENT_TYPE' in self._env and self._env[
                'CONTENT_TYPE'] != JSONAPI_MT:
            return self.send_error(415, "Wrong Input Type",
                                   "Unsupported input content type",
                                   {"detail": self._env['HTTP_ACCEPT']})

        parts = path.split('/', 1)
        if len(parts) < 2:
            return self.send_error(405,
                                   "Incomplete DOI",
                                   errdesc={"detail": path})
        if self.prefs and parts[0] not in self.prefs:
            return self.send_error(401,
                                   "Not authorized for prefix",
                                   errdesc={"detail": parts[0]})

        if path not in self.repo.ids:
            return self.send_error(404,
                                   "ID Not Found",
                                   errdesc={"detail": path})

        try:
            bodyin = self._env['wsgi.input'].read().decode('utf-8')
            doc = json.loads(bodyin, object_pairs_hook=OrderedDict)
        except (ValueError, TypeError) as ex:
            return self.send_error(400, "Not JSON",
                                   "Failed to parse input as JSON",
                                   {"detail": str(ex)})

        doi = path
        errors = None
        try:
            state = self.repo.ids[doi].get('state', 'draft')
            attrs = doc['data']['attributes']
            event = attrs.get('event')
            out = self.repo.update_id(path, attrs)
        except KeyError as ex:
            return self.send_error(400, "Bad Input: missing property",
                                   {"detail": str(ex)})
        except ValueError as ex:
            return self.send_error(404,
                                   "ID not found",
                                   errdesc={"detail": path})

        resp = {"code": 201, "message": "Updated"}
        if event == "publish" and state != "findable":
            missing = []
            for prop in "url titles publisher publicationYear creators types".split(
            ):
                if prop not in out or not out[prop]:
                    missing.append(prop)
                elif prop.endswith('s') and len(out[prop]) < 1:
                    missing.append(prop)
            if missing:
                self.repo.ids[doi]['state'] = state
                out['state'] = state
                errors = [{
                    "title": "Cannot publish due to missing metadata",
                    "detail": "Missing properties: " + str(missing)
                }]
                resp = {"code": 422, "message": "Unprocessable Entity"}

        try:
            out = self._new_resp(doi, out)
            if errors:
                out['errors'] = errors
            out = json.dumps(out)
            self.set_response(**resp)
            self.add_header("Content-type", JSONAPI_MT)
            self.add_header("Content-length", len(out))
            self.end_headers()
            return [out.encode()]
        except (ValueError, TypeError) as ex:
            return self.send_error(500, "JSON encoding error", tellexc=True)

    def do_DELETE(self, path, params=None):
        if path:
            path = path.strip('/')
        else:
            return self.send_error(405,
                                   "ID not deletable",
                                   errdesc={"detail": path})

        if path not in self.repo.ids:
            return self.send_error(404,
                                   "ID Not Found",
                                   errdesc={"detail": path})

        try:
            self.repo.delete(path)
        except ValueError as ex:
            return self.send_error(403, str(ex), errdesc={"detail": path})

        return self.send_error(204, "Deleted")
Exemplo n.º 43
0
class BaseResponse:
    """Base class for Response"""
    default_status = 200
    default_content_type = 'text/plain;'

    def __init__(self, body=b'', status=None, headers=None):
        self.headers = Headers()
        self._body = body
        self._status_code = status or self.default_status
        self._cookies = SimpleCookie()

        if headers:
            for name, value in headers.items():
                self.headers.add_header(name, value)

    @property
    def body(self):
        return [self._body]

    @property
    def status_code(self):
        """ The HTTP status code as an integer (e.g. 404)."""
        return self._status_code

    @property
    def status(self):
        """ The HTTP status line as a string (e.g. ``404 Not Found``)."""
        if not 100 <= self._status_code <= 999:
            raise ValueError('Status code out of range.')
        status = _HTTP_STATUS_LINES.get(self._status_code)
        return str(status or ('{} Unknown'.format(self._status_code)))

    @status.setter
    def status(self, status_code):
        if not 100 <= status_code <= 999:
            raise ValueError('Status code out of range.')
        self._status_code = status_code

    @property
    def headerlist(self):
        """ WSGI conform list of (header, value) tuples. """
        if 'Content-Type' not in self.headers:
            self.headers.add_header('Content-Type', self.default_content_type)
        if self._cookies:
            for c in self._cookies.values():
                self.headers.add_header('Set-Cookie', c.OutputString())
        return self.headers.items()

    def set_cookie(self, key, value, expires=None, max_age=None, path=None,
                   secret=None, digestmod=hashlib.sha256):
        if secret:
            if isinstance(secret, str):
                secret = secret.encode('utf-8')
            encoded = base64.b64encode(pickle.dumps((key, value), pickle.HIGHEST_PROTOCOL))
            sig = base64.b64encode(hmac.new(secret, encoded, digestmod=digestmod).digest())
            value_bytes = b'!' + sig + b'?' + encoded
            value = value_bytes.decode('utf-8')

        self._cookies[key] = value
        if len(key) + len(value) > 3800:
            raise ValueError('Content does not fit into a cookie.')

        if max_age is not None:
            if isinstance(max_age, int):
                max_age_value = max_age
            else:
                max_age_value = max_age.seconds + max_age.days * 24 * 3600
            self._cookies[key]['max-age'] = max_age_value
        if expires is not None:
            if isinstance(expires, int):
                expires_value = expires
            else:
                expires_value = time.strftime("%a, %d %b %Y %H:%M:%S GMT", expires.timetuple())
            self._cookies[key]['expires'] = expires_value
        if path:
            self._cookies[key]['path'] = path

    def delete_cookie(self, key, **kwargs):
        kwargs['max_age'] = -1
        kwargs['expires'] = 0
        self.set_cookie(key, '', **kwargs)
Exemplo n.º 44
0
class Shortly(object):
    def __init__(self):
        self.url_map = {}
        self.view_functions = {}
        self.headers = Headers()
        self.status = None

    def add_url_rule(self, rule, endpoint=None, view_func=None, **options):
        if endpoint is None:
            endpoint = _endpoint_from_view_func(view_func)
        options["endpoint"] = endpoint
        methods = options.pop("methods", None)

        # if the methods are not given and the view_func object knows its
        # methods we can use that instead.  If neither exists, we go with
        # a tuple of only ``GET`` as default.
        if methods is None:
            methods = getattr(view_func, "methods", None) or ("GET", )
        if isinstance(methods, str):
            raise TypeError("Allowed methods have to be iterables of strings, "
                            'for example: @app.route(..., methods=["POST"])')
        methods = set(item.upper() for item in methods)

        rule = Rule(rule, methods=methods, **options)

        self.url_map.add(rule)
        if view_func is not None:
            self.view_functions[endpoint] = view_func

    def route(self, rule, **options):
        def decorator(f):
            endpoint = options.pop("endpoint", None)
            self.add_url_rule(rule, endpoint, f, **options)
            return f

        return decorator

    def request_context(self, environ):
        """
        :param environ: a WSGI environment
        """
        request = DotDict()
        request.scheme = util.guess_scheme(environ)
        request.uri = util.request_uri(environ)
        request.address = util.application_uri(environ)
        request.path = util.shift_path_info(environ)
        if environ.get('REQUEST_METHOD', None):
            request.method = environ['REQUEST_METHOD']

        if environ.get('CONTENT_TYPE', None):
            self.headers.add_header('CONTENT_TYPE', environ['CONTENT_TYPE'])

        try:
            length = int(environ.get('CONTENT_LENGTH', '0'))
            request.body = environ['wsgi.input'].read(length)
        except ValueError:
            request.body = b''
        return request

    def dispatch_request(self, request):

        if request.path.startswith('/static'):
            fn = os.path.join(path, request.path[1:])
            if '.' not in fn.split(os.path.sep)[-1]:
                fn = os.path.join(fn, 'index.html')
            type = mimetypes.guess_type(fn)[0]

            if os.path.exists(fn):
                self.status = '200 OK'
                self.headers.add_header('Content-type', type)
                return util.FileWrapper(open(fn, "rb"))
            else:
                self.status = '404 Not Found'
                self.headers.add_header('Content-type', 'text/plain')
                return [b'not found']

        try:

            self.status = '200 OK'
            body = json.loads(request.body.decode('utf-8'))
            #rule = request.url_rule
            #return self.view_functions[rule.endpoint](**req.view_args)
            return body
        except Exception as e:
            self.status = '500 server error'
            return str(e)

    def wsgi_app(self, environ, start_response):
        ctx = self.request_context(environ)
        try:
            try:
                response = self.dispatch_request(ctx)
                headers = [(k, v) for k, v in self.headers.items()]
                start_response(self.status, headers)
                return response
            except Exception as e:
                start_response('500 server error',
                               [('Content-type', 'text/plain')])
                return [str(e)]
        finally:
            pass

    def __call__(self, environ, start_response):
        return self.wsgi_app(environ, start_response)
Exemplo n.º 45
0
class Route():

    def __init__(self, path, method, callback, status=200, content_type=None):
        self.path = path
        self.method = method
        self.callback = callback

        self.__status = status

        if content_type is None: content_type = 'text/html; charset=UTF-8'
        self.__ct = content_type

        self.__header = Headers()

    @property
    def status_code(self):
        return '{} {}'.format(self.__status, responses[self.__status])

    @property
    def headers(self):
        self.__header.add_header('Strict-Transport-Security', 'max-age=31536000; includeSubDomains')
        self.__header.add_header('Content-Security-Policy', "default-src 'self'")
        self.__header.add_header('X-Content-Type-Options', 'nosniff')
        self.__header.add_header('X-Frame-Options', 'SAMEORIGIN')
        self.__header.add_header('X-XSS-Protection', '1; mode=block')
        self.__header.add_header('Content-type', self.__ct)
        return self.__header.items()
Exemplo n.º 46
0
class application(object):
    # don't serve static by default
    static_serve = False
    static_alias = { '' : 'ketcher.html' }
    static_root = None

    indigo = None
    indigo_inchi = None

    def __init__(self, environ, start_response):
        self.path = environ['PATH_INFO'].strip('/')
        self.method = environ['REQUEST_METHOD']
        self.content_type = environ.get('CONTENT_TYPE', '')
        self.fields = FieldStorage(fp=environ['wsgi.input'],
                                   environ=environ, keep_blank_values=True)
        self.FileWrapper = environ.get('wsgi.file_wrapper', FileWrapper)
        self.headers = Headers([])

        route = getattr(self, 'on_' + self.path, None)
        if route is None:
            route = self.serve_static if self.method == 'GET' and \
                                         self.static_serve else self.notsupported

        status = "200 OK"
        try:
            self.response = route()
        except self.HttpException as e:
            status = e.args[0]
            self.response = [e.args[1]]

        self.headers.setdefault('Content-Type', 'text/plain')
        start_response(status, self.headers.items())

    def __iter__(self):
        for chunk in self.response:
            yield chunk if sys.version_info[0] < 3 or \
                           not hasattr(chunk, 'encode') else chunk.encode()

    def notsupported(self):
        raise self.HttpException("405 Method Not Allowed",
                                 "Request not supported")

    def indigo_required(method):
        def wrapper(self, **args):
            if not self.indigo:
                raise self.HttpException("501 Not Implemented",
                                         "Indigo libraries are not found")
            try:
                return method(self, **args)
            except indigo.IndigoException as e:
                message = str(sys.exc_info()[1])
                if 'indigoLoad' in message:    # error on load
                    message = "Cannot load the specified " + \
                              "structure: %s " % str(e)
                raise self.HttpException("400 Bad Request",
                                         message)
        return wrapper

    @indigo_required
    def on_knocknock(self):
        return ["You are welcome!"]

    @indigo_required
    def on_layout(self):
        moldata = None
        if self.method == 'GET' and 'smiles' in self.fields:
            moldata = self.fields.getfirst('smiles')
        elif self.is_form_request() and 'moldata' in self.fields:
            moldata = self.fields.getfirst('moldata')
        selective = 'selective' in self.fields
        if moldata:
            if '>>' in moldata or moldata.startswith('$RXN'):
                rxn = self.indigo.loadQueryReaction(moldata)
                if selective:
                    for mol in rxn.iterateMolecules():
                        self.selective_layout(mol)
                else:
                    rxn.layout()
                return ["Ok.\n",
                        rxn.rxnfile()]
            elif moldata.startswith('InChI'):
                mol = self.indigo_inchi.loadMolecule(moldata)
                mol.layout()
                return ["Ok.\n",
                        mol.molfile()]
            else:
                mol = self.indigo.loadQueryMolecule(moldata)
                if selective:
                    for rg in mol.iterateRGroups():
                        for frag in rg.iterateRGroupFragments():
                            self.selective_layout(frag)
                    self.selective_layout(mol)
                else:
                    mol.layout()
                return ["Ok.\n",
                        mol.molfile()]
        self.notsupported()

    @indigo_required
    def on_automap(self):
        moldata = None
        if self.method == 'GET' and 'smiles' in self.fields:
            moldata = self.fields.getfirst('smiles')
        elif self.is_form_request() and 'moldata' in self.fields:
            moldata = self.fields.getfirst('moldata')

        if moldata:
            mode = self.fields.getfirst('mode', 'discard')
            rxn = self.indigo.loadQueryReaction(moldata)
            if not moldata.startswith('$RXN'):
                rxn.layout()
            rxn.automap(mode)
            return ["Ok.\n",
                    rxn.rxnfile()]
        self.notsupported()

    @indigo_required
    def on_aromatize(self):
        try:
            md, is_rxn = self.load_moldata()
        except:
            message = str(sys.exc_info()[1])
            if message.startswith("\"molfile loader:") and \
               message.endswith("queries\""): # hack to avoid user confusion
                md, is_rxn = self.load_moldata(True)
            else:
                raise
        md.aromatize()
        return ["Ok.\n",
                md.rxnfile() if is_rxn else md.molfile()]

    @indigo_required
    def on_getinchi(self):
        md, is_rxn = self.load_moldata()
        inchi = self.indigo_inchi.getInchi(md)
        return ["Ok.\n", inchi]

    @indigo_required
    def on_dearomatize(self):
        try:
            md, is_rxn = self.load_moldata()
        except:                 # TODO: test for query features presence
            raise self.HttpException("400 Bad Request",
                                     "Molecules and reactions " + \
                                     "containing query features " + \
                                     "cannot be dearomatized yet.")
        md.dearomatize()
        return ["Ok.\n",
                md.rxnfile() if is_rxn else md.molfile()]

    def on_open(self):
        if self.is_form_request():
            self.headers.add_header('Content-Type', 'text/html')
            return ['<html><body onload="parent.ui.loadMoleculeFromFile()" title="',
                    b64encode("Ok.\n"),
                    b64encode(self.fields.getfirst('filedata')),
                    '"></body></html>']
        self.notsupported()

    def on_save(self):
        if self.is_form_request():
            type, data = self.fields.getfirst('filedata').split('\n', 1)
            type = type.strip()
            if type == 'smi':
                self.headers.add_header('Content-Type',
                                        'chemical/x-daylight-smiles')
            elif type == 'mol':
                if data.startswith('$RXN'):
                    type = 'rxn'
                self.headers.add_header('Content-Type',
                                        'chemical/x-mdl-%sfile' % type)

            self.headers.add_header('Content-Length', str(len(data)))
            self.headers.add_header('Content-Disposition', 'attachment',
                                    filename='ketcher.%s' % type)
            return [data]
        self.notsupported()

    class HttpException(Exception): pass
    def load_moldata(self, is_query=False):
        moldata = self.fields.getfirst('moldata')
        if moldata.startswith('$RXN'):
            if is_query:
                md = self.indigo.loadQueryReaction(moldata)
            else:
                md = self.indigo.loadReaction(moldata)
            is_rxn = True
        else:
            if is_query:
                md = self.indigo.loadQueryMolecule(moldata)
            else:
                md = self.indigo.loadMolecule(moldata)
            is_rxn = False
        return md, is_rxn

    def selective_layout(self, mol):
        dsgs = [dsg for dsg in mol.iterateDataSGroups() \
                if dsg.description() == '_ketcher_selective_layout' and \
                dsg.data() == '1']
        atoms = sorted([atom.index() for dsg in dsgs \
                        for atom in dsg.iterateAtoms()])
        for dsg in dsgs:
            dsg.remove()
        mol.getSubmolecule(atoms).layout()
        return mol

    def serve_static(self):
        root = self.static_root or getcwd()
        fpath = self.static_alias.get(self.path, self.path)
        fpath = path.abspath(path.join(root, fpath))

        if not fpath.startswith(root + path.sep) or not path.isfile(fpath) \
           or fpath == path.abspath(__file__):
            raise self.HttpException("404 Not Found",
                                     "Requested file isn't accessible")

        self.headers['Content-Type'] = guess_type(fpath)[0] or 'text/plain'
        try:
            fd = open(fpath, 'rb')
            return self.FileWrapper(fd) if self.method == 'GET' else ['']
        except (IOError, OSError):
            raise self.HttpException("402 Payment Required",  # or 403, hmm..
                                     "Must get more money for overtime")

    def is_form_request(self):
        return self.method == 'POST' and \
               (self.content_type.startswith('application/x-www-form-urlencoded')
                or self.content_type.startswith('multipart/form-data'))
Exemplo n.º 47
0
class StaticFile(object):
    ACCEPT_GZIP_RE = re.compile(r'\bgzip\b')
    BLOCK_SIZE = 16 * 4096
    # All mimetypes starting 'text/' take a charset parameter, plus the
    # additions in this set
    MIMETYPES_WITH_CHARSET = {'application/javascript', 'application/xml'}
    CHARSET = 'utf-8'
    # Ten years is what nginx sets a max age if you use 'expires max;'
    # so we'll follow its lead
    FOREVER = 10*365*24*60*60

    GZIP_SUFFIX = '.gz'

    def __init__(self, path, is_immutable, guess_type=mimetypes.guess_type, **config):
        self.path = path
        stat = os.stat(path)
        self.mtime_tuple = gmtime(stat.st_mtime)
        mimetype, encoding = guess_type(path)
        mimetype = mimetype or 'application/octet-stream'
        charset = self.get_charset(mimetype)
        params = {'charset': charset} if charset else {}
        self.headers = Headers([
            ('Last-Modified', formatdate(stat.st_mtime, usegmt=True)),
            ('Content-Length', str(stat.st_size)),
        ])
        self.headers.add_header('Content-Type', str(mimetype), **params)
        if encoding:
            self.headers['Content-Encoding'] = encoding

        max_age = self.FOREVER if is_immutable else config['max_age']
        if max_age is not None:
            self.headers['Cache-Control'] = 'public, max-age=%s' % max_age

        if config['allow_all_origins']:
            self.headers['Access-Control-Allow-Origin'] = '*'

        gzip_path = path + self.GZIP_SUFFIX
        if os.path.isfile(gzip_path):
            self.gzip_path = gzip_path
            self.headers['Vary'] = 'Accept-Encoding'
            # Copy the headers and add the appropriate encoding and length
            self.gzip_headers = Headers(self.headers.items())
            self.gzip_headers['Content-Encoding'] = 'gzip'
            self.gzip_headers['Content-Length'] = str(os.stat(gzip_path).st_size)
        else:
            self.gzip_path = self.gzip_headers = None

    def get_charset(self, mimetype):
        if mimetype.startswith('text/') or mimetype in self.MIMETYPES_WITH_CHARSET:
            return self.CHARSET

    def serve(self, environ, start_response):
        method = environ['REQUEST_METHOD']
        if method != 'GET' and method != 'HEAD':
            start_response('405 Method Not Allowed', [('Allow', 'GET, HEAD')])
            return []
        if self.file_not_modified(environ):
            start_response('304 Not Modified', [])
            return []
        path, headers = self.get_path_and_headers(environ)
        start_response('200 OK', headers.items())
        if method == 'HEAD':
            return []
        file_wrapper = environ.get('wsgi.file_wrapper', self.yield_file)
        fileobj = open(path, 'rb')
        return file_wrapper(fileobj)

    def file_not_modified(self, environ):
        try:
            last_requested = environ['HTTP_IF_MODIFIED_SINCE']
        except KeyError:
            return False
        # Exact match, no need to parse
        if last_requested == self.headers['Last-Modified']:
            return True
        return parsedate(last_requested) >= self.mtime_tuple

    def get_path_and_headers(self, environ):
        if self.gzip_path:
            if self.ACCEPT_GZIP_RE.search(environ.get('HTTP_ACCEPT_ENCODING', '')):
                return self.gzip_path, self.gzip_headers
        return self.path, self.headers

    def yield_file(self, fileobj):
        # Only used as a fallback in case environ doesn't supply a
        # wsgi.file_wrapper
        try:
            while True:
                block = fileobj.read(self.BLOCK_SIZE)
                if block:
                    yield block
                else:
                    break
        finally:
            fileobj.close()