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']
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
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
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
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))
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
def __execute(website, __file__, request): """Execute a script. """ response = Response() try: exec open(__file__) except SystemExit: pass return response
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
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
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
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
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
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
def respond(self, request): response = Response(200, "Greetings, program!") delattr(response, "headers") raise response
def HTTP404(**foo): return Response(404)
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
def respond(self, request): response = Response(200, "Greetings, program!") response.body = Response(200, "Greetings, program!") raise response
def respond(self, request): response = Response(200, "Greetings, program!") response.headers = "wheee!" raise response
def respond(self, request): raise Response(200, "Greetings, program!")