Example #1
0
    def parse_line(self):
        """Parse and validate the Request-Line.
        """

        # Tokenize the first line as a Request-Line.
        # ==========================================

        tokens = self.raw_line.strip().split()
        if len(tokens) == 3:
            method, uri, version = tokens
        elif len(tokens) == 2:
            method, uri = tokens
            version = 'HTTP/0.9'
        else:
            raise Response(
                400, "The Request-Line `%s' " % self.raw_line +
                "appears to be malformed because it has " +
                "neither two nor three tokens.")

        # Validate the URI.
        # =================
        # We build a mapping using the urlparse naming convention for the keys.
        # Then we do a little validation and cleanup.

        uri = urlparse.urlparse(uri)
        keys = ('scheme', 'netloc', 'path', 'parameters', 'query', 'fragment')
        _uri = {}
        for i in range(len(uri)):
            k = keys[i]
            v = uri[i]
            _uri[k] = v
        uri = _uri

        if not uri['path']:
            # this catches, e.g., '//foo'
            raise Response(400)
        if '%' in uri['path']:
            uri['path'] = urllib.unquote(uri['path'])

        # Validate the version.
        # =====================
        # Consider raising Response(505) here ... for 0.9? 2.0?

        m = HTTP_VERSION.match(version)
        if not m:
            raise Response(
                400, "The HTTP-Version `%s' appears to " % version +
                "be malformed because it does not match the " +
                "pattern `^HTTP/\d+\.\d+$'.")

        # Save a few absolutely basic things for application use.
        # =======================================================

        self.method = method
        self.uri = uri
        self.path = uri['path']
Example #2
0
 def __on_exception(website, request):
     response = Response(500)
     if mode.IS_DEBUGGING or mode.IS_DEVELOPMENT:
         response.body = traceback.format_exc()
     for plugin in website.__hooks['exception']:
         try:
             exec plugin.hooks['exception']
         except SystemExit:
             pass
     return response
Example #3
0
 def __on_exception(website, request):
     response = Response(500)
     if mode.IS_DEBUGGING or mode.IS_DEVELOPMENT:
         response.body = traceback.format_exc()
     if website.__hooks['exception'] is not None:
         for func in website.__hooks['exception']:
             response = func(website, request, response)
             if response is None:
                 raise HookError, "An exception hook returned None."
     return response
Example #4
0
 def validate(self, uri_path, fs_path):
     """Given a URI and a filesystem translation, return a path or raise 301.
     """
     if not os.path.exists(fs_path):
         raise Response(404)
     elif os.path.isdir(fs_path) and not uri_path.endswith('/'):
         new_location = '%s/' % uri_path
         response = Response(301)
         response.headers['Location'] = new_location
         raise response
     return fs_path
Example #5
0
    def serve_xmlrpc(self, request):
        """Serve an XMLRPC request.
        """
        if request.method != 'POST':
            raise Response(501)

        response = Response(200)
        response.headers['Content-Type'] = 'text/xml'
        try:
            params, method = xmlrpclib.loads(request.raw_body)


            # Find a callable.
            # ================

            try:
                protected = (  method.startswith('_')
                            or method in self.__protected
                            or method in self.protected
                              )
                if protected:
                    raise NotImplementedError(method)
                else:
                    func = getattr(self, method, None)
                    if func is None:
                        raise NotImplementedError(method)

            except NotImplementedError:
                fault = xmlrpclib.Fault( 404
                                       , "method '%s' " % method +
                                         "not found"
                                        )
                body = xmlrpclib.dumps(fault)


            else:
                # Call it.
                # ========

                try:
                    body = xmlrpclib.dumps( (func(*params),)
                                          , methodresponse=1
                                          , allow_none=True
                                           )
                except xmlrpclib.Fault, fault:
                    body = xmlrpclib.dumps(fault)
                except:
                    err = (sys.exc_type, sys.exc_value)
                    body = xmlrpclib.dumps(xmlrpclib.Fault(1, "%s:%s" % err))
Example #6
0
    def __respond_unsafely(self, request):
        """Given a Request, return a response (w/o error handling).
        """

        # Translate the request to the filesystem.
        # ========================================

        fspath = self.static.translate(request.path)
        if self.paths.__ is not None:
            if fspath.startswith(self.paths.__): # protect magic directory
                raise Response(404)


        # See if any known application claims this request.
        # =================================================

        app = self.__get_app(request, fspath) # may redirect
        if app is not None:
            response = app.respond(request)
        else:

            # No app wants it. Get a resource and a handler.
            # ==============================================

            fspath = self.static.validate(request.path, fspath) # 404 or 301
            fspath = self.static.find_default(fspath) # may raise 403
            fp = open(fspath)
            handler = self.__get_handler(fp)
            fp.seek(0)


            # Set up the context and then call the handler.
            # =============================================
            # Session and page/conversation contexts (SEAM) should be added by
            # hooks.

            context = dict(website=self)                # eternal
            context['fp'] = fp                          # ephemeral
            context['request'] = request
            context['response'] = Response()
            #context['cookie'] = Cookie(request)
            #context['form'] = Form(request)
            #context['query'] = Query(request)

            response = handler.handle(**context)

        return response
Example #7
0
 def __execute(website, __file__, request):
     """Execute a script.
     """
     response = Response()
     try:
         exec open(__file__)
     except SystemExit:
         pass
     return response
Example #8
0
    def __respond_unsafely(self, request):
        """Given a Request, return a response (w/o error handling).
        """

        if request.path.startswith('/__'):
            raise Response(404)

        fspath = self.__static.translate(request.path)

        if exists(fspath):

            # Found a resource; serve it.
            # ===========================

            fspath = request.fspath = self.__static.find_default(fspath)
            if is_script(fspath):
                response = self.__execute(fspath, request)
            else:
                response = self.__static.respond(request)
                del response.headers['Content-Length']

        else:

            # No resource; look for a script to handle the request.
            # =====================================================

            script = None
            parts = request.path.split('/')
            while len(parts) > 0:
                del parts[-1]
                path = self.__static.translate('/'.join(parts))
                if exists(path):
                    path = self.__static.find_default(path)
                    if is_script(path):
                        script = path
                        break

            if script is not None:
                request.fspath = script
                response = self.__execute(script, request)
            else:
                raise Response(404)

        return response
Example #9
0
 def find_default(self, fs_path):
     """Given a path, return a filepath or raise 403.
     """
     if os.path.isdir(fs_path):
         default = None
         for name in self.defaults:
             _path = os.path.join(fs_path, name)
             if os.path.isfile(_path):
                 default = _path
                 break
         if default is None:
             raise Response(403)
         fs_path = default
     return fs_path
Example #10
0
 def __get_app(self, request, fspath):
     """Given a request, return the first matching app.
     """
     app = None
     for urlpath, _app in self.__apps:
         if request.path.startswith(urlpath):
             dirpath = self.static.translate(urlpath)
             if not isdir(dirpath):       # always check for existence
                 raise Response(404)
             if urlpath.endswith('/'):   # sometimes check for trailing slash
                 self.static.validate(request.path, dirpath) # may raise 301
             app = _app
             break
     if app is None:
         log.debug("No app found for '%s'" % request.path)
     return app
Example #11
0
    def get_responder(self, request):
        """Given a request, return a responder.
        """

        # Match a responder based on the URI path.
        # ========================================
        # We also update request.path.

        responder = None
        for _responder in self.responders:
            if request.path.startswith(_responder.path):
                responder = _responder
                request.path = request.path[len(responder.path)-1:]
                if not request.path:
                    request.path = '/'
                break
        if responder is None:
            # This catches, e.g., ../../../../../../../../../etc/master.passwd
            raise Response(400)

        logger.debug("Using %s for this request." % responder)


        # Make sure our paths still exist, but aren't being accessed.
        # ===========================================================

        fs_path = utils.translate(request.path, responder.root, raw=True)

        if not os.path.isdir(responder.root):
            logger.critical( "Filesystem root (%s) has " % responder.root +
                             "disappeared for responder '%s'" % responder
                            )
            raise Response(500)

        if responder.__ is not None:
            if not os.path.isdir(responder.__):
                logger.critical( "Magic directory (%s) has " % responder.__ +
                                 "disappeared for responder '%s'" % responder
                                )
                raise Response(500)
            if fs_path.startswith(responder.__):
                raise Response(403)

        if responder.pkg is not None:
            if not os.path.isdir(responder.pkg):
                logger.critical( "Package directory (%s) has " % responder.pkg +
                                 "disappeared for responder '%s'" % responder
                                )
                raise Response(500)
            if fs_path.startswith(responder.pkg):
                raise Response(403)
        return responder
Example #12
0
    def respond(self, request):
        """Serve a static file off of the filesystem.

        In staging and deployment modes, we honor any 'If-Modified-Since'
        header, an HTTP header used for caching.

        """

        fs_path = self.translate(request.path)
        fs_path = self.validate(request.path, fs_path)
        fs_path = self.find_default(fs_path)

        ims = request.headers.get('If-Modified-Since', '')

        # Get basic info from the filesystem and start building a response.
        # =================================================================

        stats = os.stat(fs_path)
        mtime = stats[stat.ST_MTIME]
        size = stats[stat.ST_SIZE]
        content_type = mimetypes.guess_type(fs_path)[0] or 'text/plain'
        response = Response(200)

        # Support 304s, but only in deployment mode.
        # ==========================================

        if mode.IS_DEPLOYMENT or mode.IS_STAGING:
            if ims:
                mod_since = rfc822.parsedate(ims)
                last_modified = time.gmtime(mtime)
                if last_modified[:6] <= mod_since[:6]:
                    response.code = 304

        # Finish building the response and return it.
        # ===========================================

        response.headers['Last-Modified'] = rfc822.formatdate(mtime)
        response.headers['Content-Type'] = content_type
        response.headers['Content-Length'] = size
        if response.code != 304:
            response.body = open(fs_path).read()
        return response
Example #13
0
def translate(uri_path, fs_root, defaults=[], raw=False):
    """Translate a requested URI to the filesystem.

    Takes a URI path, a filesystem path, and a list of filenames which should be
    considered default resources. The URI path is taken to be rooted in the
    filesystem path. If raw is True, then we perform no validation and look for
    no defaults.

    If raw is False, and the requested path points to a directory, we ensure
    that the URI ends with a slash, and we look for a default resource if any
    are named. If the URI points to a file, we make sure the file exists.

    This method can raise the following Responses:

        301 Moved Permanently
        403 Forbidden
        404 Not Found

    If successful, we return the filesystem path to the particular resource.

    """

    from httpy import Response

    logger = logging.getLogger('httpy.utils.translate')

    # Knit the requested URI onto the filesystem path.
    # ================================================

    _parts = [fs_root] + uri_path.lstrip('/').split('/')
    fs_path = os.sep.join(_parts)
    fs_path = os.path.realpath(fs_path)

    if raw:
        return fs_path

    # Interpret it.
    # =============

    if os.path.isdir(fs_path):

        # Process the request as a directory.
        # ===================================

        if not uri_path.endswith('/'):
            # redirect directory requests to trailing slash
            new_location = '%s/' % uri_path
            response = Response(301)
            response.headers['Location'] = new_location
            logger.debug("redirecting to trailing slash: %s" % new_location)
            raise response

        logger.debug("looking for these defaults: %s" % str(defaults))
        default = None
        for name in defaults:
            _path = os.path.join(fs_path, name)
            if os.path.isfile(_path):
                default = _path
                break
        if default is None:
            logger.debug("no default resource in: %s" % fs_path)
            raise Response(403)
        fs_path = default

    else:

        # Process the request as a file.
        # ==============================

        if not os.path.exists(fs_path):
            logger.debug("did not find %s at %s" % (uri_path, fs_path))
            raise Response(404)

    return fs_path
Example #14
0
 def respond(self, request):
     response = Response(200, "Greetings, program!")
     delattr(response, "headers")
     raise response
Example #15
0
def HTTP404(**foo):
    return Response(404)
Example #16
0

            else:
                # Call it.
                # ========

                try:
                    body = xmlrpclib.dumps( (func(*params),)
                                          , methodresponse=1
                                          , allow_none=True
                                           )
                except xmlrpclib.Fault, fault:
                    body = xmlrpclib.dumps(fault)
                except:
                    err = (sys.exc_type, sys.exc_value)
                    body = xmlrpclib.dumps(xmlrpclib.Fault(1, "%s:%s" % err))

            response.body = body


        except: # Bug in the module.
            logger.error("Error serving request.")
            logger.debug(traceback.format_exc())
            raise Response(500)

        else:   # Valid XMLRPC response.
            return response


XMLRPC = Responder
Example #17
0
 def respond(self, request):
     response = Response(200, "Greetings, program!")
     response.body = Response(200, "Greetings, program!")
     raise response
Example #18
0
 def respond(self, request):
     response = Response(200, "Greetings, program!")
     response.headers = "wheee!"
     raise response
Example #19
0
 def respond(self, request):
     raise Response(200, "Greetings, program!")