fixed rocket (removed handling of static files, faster parse headers, pathoc fault-tolerant)
This commit is contained in:
@@ -1 +1 @@
|
||||
Version 2.0.9 (2012-09-30 16:50:47) dev
|
||||
Version 2.0.9 (2012-10-01 11:57:39) dev
|
||||
|
||||
+24
-226
@@ -2,6 +2,7 @@
|
||||
|
||||
# This file is part of the Rocket Web Server
|
||||
# Copyright (c) 2011 Timothy Farrell
|
||||
# Modified by Massimo Di Pierro
|
||||
|
||||
# Import System Modules
|
||||
import sys
|
||||
@@ -12,7 +13,7 @@ import platform
|
||||
import traceback
|
||||
|
||||
# Define Constants
|
||||
VERSION = '1.2.4'
|
||||
VERSION = '1.2.5'
|
||||
SERVER_NAME = socket.gethostname()
|
||||
SERVER_SOFTWARE = 'Rocket %s' % VERSION
|
||||
HTTP_SERVER_SOFTWARE = '%s Python/%s' % (SERVER_SOFTWARE, sys.version.split(' ')[0])
|
||||
@@ -1454,37 +1455,34 @@ class Worker(Thread):
|
||||
return req
|
||||
|
||||
|
||||
def read_headers(self, sock_file):
|
||||
def read_headers(self, sock_file, environ):
|
||||
try:
|
||||
headers = dict()
|
||||
lname = lval = ''
|
||||
lname = None
|
||||
while True:
|
||||
line = sock_file.readline()
|
||||
l = sock_file.readline()
|
||||
if PY3K:
|
||||
try:
|
||||
line = str(line, 'ISO-8859-1')
|
||||
l = str(l, 'ISO-8859-1')
|
||||
except UnicodeDecodeError:
|
||||
self.err_log.warning('Client sent invalid header: ' + repr(line))
|
||||
if line == '\r\n':
|
||||
if lname: headers[str(lname)] = str(lval)
|
||||
self.err_log.warning('Invalid request header: '+repr(l))
|
||||
|
||||
if l.strip() == '':
|
||||
break
|
||||
elif line.strip() == '' or '\0' in line:
|
||||
raise BadRequest("Empty line in hader")
|
||||
elif line[0] in ' \t' and lname:
|
||||
elif l[0] in ' \t' and lname:
|
||||
# Some headers take more than one line
|
||||
lval += ' ' + line.strip()
|
||||
elif ':' in line:
|
||||
if lname: headers[str(lname)] = str(lval)
|
||||
lname, lval = line.split(':', 1)
|
||||
# HTTP header names are us-ascii encoded
|
||||
lname = lname.strip().upper().replace('-', '_')
|
||||
environ[lname] += ',' + l.strip()
|
||||
else:
|
||||
# HTTP header values are latin-1 encoded
|
||||
lval = lval.strip()
|
||||
l = l.split(':', 1)
|
||||
# HTTP header names are us-ascii encoded
|
||||
|
||||
lname = str('HTTP_'+l[0].strip().upper().replace('-', '_'))
|
||||
lval = str(l[-1].strip())
|
||||
environ[lname] = lval
|
||||
|
||||
except socket.timeout:
|
||||
raise SocketTimeout("Socket timed out before request.")
|
||||
|
||||
return headers
|
||||
|
||||
class SocketTimeout(Exception):
|
||||
"Exception for when a socket times out between requests."
|
||||
pass
|
||||
@@ -1545,209 +1543,10 @@ class ChunkedReader(object):
|
||||
yield self.readline()
|
||||
|
||||
def get_method(method):
|
||||
|
||||
|
||||
methods = dict(wsgi=WSGIWorker,
|
||||
fs=FileSystemWorker)
|
||||
methods = dict(wsgi=WSGIWorker)
|
||||
return methods[method.lower()]
|
||||
|
||||
# Monolithic build...end of module: rocket\worker.py
|
||||
# Monolithic build...start of module: rocket\methods\__init__.py
|
||||
|
||||
# Monolithic build...end of module: rocket\methods\__init__.py
|
||||
# Monolithic build...start of module: rocket\methods\fs.py
|
||||
|
||||
# Import System Modules
|
||||
import os
|
||||
import time
|
||||
import mimetypes
|
||||
from email.utils import formatdate
|
||||
from wsgiref.headers import Headers
|
||||
from wsgiref.util import FileWrapper
|
||||
# Import Package Modules
|
||||
# package imports removed in monolithic build
|
||||
|
||||
|
||||
# Define Constants
|
||||
CHUNK_SIZE = 2**16 # 64 Kilobyte chunks
|
||||
HEADER_RESPONSE = '''HTTP/1.1 %s\r\n%s'''
|
||||
INDEX_HEADER = '''\
|
||||
<html>
|
||||
<head><title>Directory Index: %(path)s</title>
|
||||
<style> .parent { margin-bottom: 1em; }</style>
|
||||
</head>
|
||||
<body><h1>Directory Index: %(path)s</h1>
|
||||
<table>
|
||||
<tr><th>Directories</th></tr>
|
||||
'''
|
||||
INDEX_ROW = '''<tr><td><div class="%(cls)s"><a href="/%(link)s">%(name)s</a></div></td></tr>'''
|
||||
INDEX_FOOTER = '''</table></body></html>\r\n'''
|
||||
|
||||
class LimitingFileWrapper(FileWrapper):
|
||||
def __init__(self, limit=None, *args, **kwargs):
|
||||
self.limit = limit
|
||||
FileWrapper.__init__(self, *args, **kwargs)
|
||||
|
||||
def read(self, amt):
|
||||
if amt > self.limit:
|
||||
amt = self.limit
|
||||
self.limit -= amt
|
||||
return FileWrapper.read(self, amt)
|
||||
|
||||
class FileSystemWorker(Worker):
|
||||
def __init__(self, *args, **kwargs):
|
||||
"""Builds some instance variables that will last the life of the
|
||||
thread."""
|
||||
|
||||
Worker.__init__(self, *args, **kwargs)
|
||||
|
||||
self.root = os.path.abspath(self.app_info['document_root'])
|
||||
self.display_index = self.app_info['display_index']
|
||||
|
||||
def serve_file(self, filepath, headers):
|
||||
filestat = os.stat(filepath)
|
||||
self.size = filestat.st_size
|
||||
modtime = time.strftime("%a, %d %b %Y %H:%M:%S GMT",
|
||||
time.gmtime(filestat.st_mtime))
|
||||
self.headers.add_header('Last-Modified', modtime)
|
||||
if headers.get('if_modified_since') == modtime:
|
||||
# The browser cache is up-to-date, send a 304.
|
||||
self.status = "304 Not Modified"
|
||||
self.data = []
|
||||
return
|
||||
|
||||
ct = mimetypes.guess_type(filepath)[0]
|
||||
self.content_type = ct if ct else 'text/plain'
|
||||
try:
|
||||
f = open(filepath, 'rb')
|
||||
self.headers['Pragma'] = 'cache'
|
||||
self.headers['Cache-Control'] = 'private'
|
||||
self.headers['Content-Length'] = str(self.size)
|
||||
if self.etag:
|
||||
self.headers.add_header('Etag', self.etag)
|
||||
if self.expires:
|
||||
self.headers.add_header('Expires', self.expires)
|
||||
|
||||
try:
|
||||
# Implement 206 partial file support.
|
||||
start, end = headers['range'].split('-')
|
||||
start = 0 if not start.isdigit() else int(start)
|
||||
end = self.size if not end.isdigit() else int(end)
|
||||
if self.size < end or start < 0:
|
||||
self.status = "214 Unsatisfiable Range Requested"
|
||||
self.data = FileWrapper(f, CHUNK_SIZE)
|
||||
else:
|
||||
f.seek(start)
|
||||
self.data = LimitingFileWrapper(f, CHUNK_SIZE, limit=end)
|
||||
self.status = "206 Partial Content"
|
||||
except:
|
||||
self.data = FileWrapper(f, CHUNK_SIZE)
|
||||
except IOError:
|
||||
self.status = "403 Forbidden"
|
||||
|
||||
def serve_dir(self, pth, rpth):
|
||||
def rel_path(path):
|
||||
return os.path.normpath(path[len(self.root):] if path.startswith(self.root) else path)
|
||||
|
||||
if not self.display_index:
|
||||
self.status = '404 File Not Found'
|
||||
return b('')
|
||||
else:
|
||||
self.content_type = 'text/html'
|
||||
|
||||
dir_contents = [os.path.join(pth, x) for x in os.listdir(os.path.normpath(pth))]
|
||||
dir_contents.sort()
|
||||
|
||||
dirs = [rel_path(x)+'/' for x in dir_contents if os.path.isdir(x)]
|
||||
files = [rel_path(x) for x in dir_contents if os.path.isfile(x)]
|
||||
|
||||
self.data = [INDEX_HEADER % dict(path='/'+rpth)]
|
||||
if rpth:
|
||||
self.data += [INDEX_ROW % dict(name='(parent directory)', cls='dir parent', link='/'.join(rpth[:-1].split('/')[:-1]))]
|
||||
self.data += [INDEX_ROW % dict(name=os.path.basename(x[:-1]), link=os.path.join(rpth, os.path.basename(x[:-1])).replace('\\', '/'), cls='dir') for x in dirs]
|
||||
self.data += ['<tr><th>Files</th></tr>']
|
||||
self.data += [INDEX_ROW % dict(name=os.path.basename(x), link=os.path.join(rpth, os.path.basename(x)).replace('\\', '/'), cls='file') for x in files]
|
||||
self.data += [INDEX_FOOTER]
|
||||
self.headers['Content-Length'] = self.size = str(sum([len(x) for x in self.data]))
|
||||
self.status = '200 OK'
|
||||
|
||||
def run_app(self, conn):
|
||||
self.status = "200 OK"
|
||||
self.size = 0
|
||||
self.expires = None
|
||||
self.etag = None
|
||||
self.content_type = 'text/plain'
|
||||
self.content_length = None
|
||||
|
||||
if __debug__:
|
||||
self.err_log.debug('Getting sock_file')
|
||||
|
||||
# Build our file-like object
|
||||
sock_file = conn.makefile('rb',BUF_SIZE)
|
||||
request = self.read_request_line(sock_file)
|
||||
if request['method'].upper() not in ('GET', ):
|
||||
self.status = "501 Not Implemented"
|
||||
|
||||
try:
|
||||
# Get our file path
|
||||
reader = self.read_headers(sock_file)
|
||||
headers = dict((k.lower(),v) for k,v in reader.iteritems())
|
||||
rpath = request.get('path', '').lstrip('/')
|
||||
filepath = os.path.join(self.root, rpath)
|
||||
filepath = os.path.abspath(filepath)
|
||||
if __debug__:
|
||||
self.err_log.debug('Request for path: %s' % filepath)
|
||||
|
||||
self.closeConnection = headers.get('connection', 'close').lower() == 'close'
|
||||
self.headers = Headers([('Date', formatdate(usegmt=True)),
|
||||
('Server', HTTP_SERVER_SOFTWARE),
|
||||
('Connection', headers.get('connection', 'close')),
|
||||
])
|
||||
|
||||
if not filepath.lower().startswith(self.root.lower()):
|
||||
# File must be within our root directory
|
||||
self.status = "400 Bad Request"
|
||||
self.closeConnection = True
|
||||
elif not os.path.exists(filepath):
|
||||
self.status = "404 File Not Found"
|
||||
self.closeConnection = True
|
||||
elif os.path.isdir(filepath):
|
||||
self.serve_dir(filepath, rpath)
|
||||
elif os.path.isfile(filepath):
|
||||
self.serve_file(filepath, headers)
|
||||
else:
|
||||
# It exists but it's not a file or a directory????
|
||||
# What is it then?
|
||||
self.status = "501 Not Implemented"
|
||||
self.closeConnection = True
|
||||
|
||||
h = self.headers
|
||||
statcode, statstr = self.status.split(' ', 1)
|
||||
statcode = int(statcode)
|
||||
if statcode >= 400:
|
||||
h.add_header('Content-Type', self.content_type)
|
||||
self.data = [statstr]
|
||||
|
||||
# Build our output headers
|
||||
header_data = HEADER_RESPONSE % (self.status, str(h))
|
||||
|
||||
# Send the headers
|
||||
if __debug__:
|
||||
self.err_log.debug('Sending Headers: %s' % repr(header_data))
|
||||
self.conn.sendall(b(header_data))
|
||||
|
||||
for data in self.data:
|
||||
self.conn.sendall(b(data))
|
||||
|
||||
if hasattr(self.data, 'close'):
|
||||
self.data.close()
|
||||
|
||||
finally:
|
||||
if __debug__:
|
||||
self.err_log.debug('Finally closing sock_file')
|
||||
sock_file.close()
|
||||
|
||||
# Monolithic build...end of module: rocket\methods\fs.py
|
||||
# Monolithic build...start of module: rocket\methods\wsgi.py
|
||||
|
||||
# Import System Modules
|
||||
@@ -1815,16 +1614,15 @@ class WSGIWorker(Worker):
|
||||
environ = self.base_environ.copy()
|
||||
|
||||
# Grab the headers
|
||||
for k, v in self.read_headers(sock_file).items():
|
||||
environ[str('HTTP_'+k)] = v
|
||||
self.read_headers(sock_file,environ)
|
||||
|
||||
# Add CGI Variables
|
||||
environ['REQUEST_METHOD'] = request['method']
|
||||
environ['PATH_INFO'] = request['path']
|
||||
environ['SERVER_PROTOCOL'] = request['protocol']
|
||||
environ['SERVER_PORT'] = str(conn.server_port)
|
||||
environ['REMOTE_PORT'] = str(conn.client_port)
|
||||
environ['REMOTE_ADDR'] = str(conn.client_addr)
|
||||
environ['REQUEST_METHOD'] = request['method']
|
||||
environ['PATH_INFO'] = request['path']
|
||||
environ['SERVER_PROTOCOL'] = request['protocol']
|
||||
environ['QUERY_STRING'] = request['query_string']
|
||||
if 'HTTP_CONTENT_LENGTH' in environ:
|
||||
environ['CONTENT_LENGTH'] = environ['HTTP_CONTENT_LENGTH']
|
||||
|
||||
Reference in New Issue
Block a user