189 lines
6.5 KiB
Python
Executable File
189 lines
6.5 KiB
Python
Executable File
# The contents of this file are subject to the BitTorrent Open Source License
|
|
# Version 1.1 (the License). You may not copy or use this file, in either
|
|
# source code or executable form, except in compliance with the License. You
|
|
# may obtain a copy of the License at http://www.bittorrent.com/license/.
|
|
#
|
|
# Software distributed under the License is distributed on an AS IS basis,
|
|
# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
|
# for the specific language governing rights and limitations under the
|
|
# License.
|
|
|
|
# Written by Bram Cohen
|
|
|
|
from RawServer_magic import Handler
|
|
from cStringIO import StringIO
|
|
from sys import stdout
|
|
import time
|
|
from gzip import GzipFile
|
|
|
|
DEBUG = False
|
|
|
|
weekdays = [_("Mon"), _("Tue"), _("Wed"), _("Thu"), _("Fri"), _("Sat"), _("Sun")]
|
|
|
|
months = [None, _("Jan"), _("Feb"), _("Mar"), _("Apr"), _("May"), _("Jun"),
|
|
_("Jul"), _("Aug"), _("Sep"), _("Oct"), _("Nov"), _("Dec")]
|
|
|
|
|
|
class HTTPConnection(object):
|
|
|
|
def __init__(self, handler, connection):
|
|
self.handler = handler
|
|
self.connection = connection
|
|
self.buf = ''
|
|
self.closed = False
|
|
self.done = False
|
|
self.donereading = False
|
|
self.next_func = self.read_type
|
|
|
|
def get_ip(self):
|
|
return self.connection.ip
|
|
|
|
def data_came_in(self, data):
|
|
if self.donereading or self.next_func is None:
|
|
return True
|
|
self.buf += data
|
|
while True:
|
|
try:
|
|
i = self.buf.index('\n')
|
|
except ValueError:
|
|
return True
|
|
val = self.buf[:i]
|
|
self.buf = self.buf[i+1:]
|
|
self.next_func = self.next_func(val)
|
|
if self.donereading:
|
|
return True
|
|
if self.next_func is None or self.closed:
|
|
return False
|
|
|
|
def read_type(self, data):
|
|
self.header = data.strip()
|
|
words = data.split()
|
|
if len(words) == 3:
|
|
self.command, self.path, garbage = words
|
|
self.pre1 = False
|
|
elif len(words) == 2:
|
|
self.command, self.path = words
|
|
self.pre1 = True
|
|
if self.command != 'GET':
|
|
return None
|
|
else:
|
|
return None
|
|
if self.command not in ('HEAD', 'GET'):
|
|
return None
|
|
self.headers = {}
|
|
return self.read_header
|
|
|
|
def read_header(self, data):
|
|
data = data.strip()
|
|
if data == '':
|
|
self.donereading = True
|
|
# check for Accept-Encoding: header, pick a
|
|
if self.headers.has_key('accept-encoding'):
|
|
ae = self.headers['accept-encoding']
|
|
if DEBUG:
|
|
print "Got Accept-Encoding: " + ae + "\n"
|
|
else:
|
|
#identity assumed if no header
|
|
ae = 'identity'
|
|
# this eventually needs to support multple acceptable types
|
|
# q-values and all that fancy HTTP crap
|
|
# for now assume we're only communicating with our own client
|
|
if ae.find('gzip') != -1:
|
|
self.encoding = 'gzip'
|
|
else:
|
|
#default to identity.
|
|
self.encoding = 'identity'
|
|
r = self.handler.getfunc(self, self.path, self.headers)
|
|
if r is not None:
|
|
self.answer(r)
|
|
return None
|
|
try:
|
|
i = data.index(':')
|
|
except ValueError:
|
|
return None
|
|
self.headers[data[:i].strip().lower()] = data[i+1:].strip()
|
|
if DEBUG:
|
|
print data[:i].strip() + ": " + data[i+1:].strip()
|
|
return self.read_header
|
|
|
|
def answer(self, (responsecode, responsestring, headers, data)):
|
|
if self.closed:
|
|
return
|
|
if self.encoding == 'gzip':
|
|
#transform data using gzip compression
|
|
#this is nasty but i'm unsure of a better way at the moment
|
|
compressed = StringIO()
|
|
gz = GzipFile(fileobj = compressed, mode = 'wb', compresslevel = 9)
|
|
gz.write(data)
|
|
gz.close()
|
|
compressed.seek(0,0)
|
|
cdata = compressed.read()
|
|
compressed.close()
|
|
if len(cdata) >= len(data):
|
|
self.encoding = 'identity'
|
|
else:
|
|
if DEBUG:
|
|
print _("Compressed: %i Uncompressed: %i\n") % (len(cdata),len(data))
|
|
data = cdata
|
|
headers['Content-Encoding'] = 'gzip'
|
|
|
|
# i'm abusing the identd field here, but this should be ok
|
|
if self.encoding == 'identity':
|
|
ident = '-'
|
|
else:
|
|
ident = self.encoding
|
|
username = '-'
|
|
referer = self.headers.get('referer','-')
|
|
useragent = self.headers.get('user-agent','-')
|
|
year, month, day, hour, minute, second, a, b, c = time.localtime(time.time())
|
|
print '%s %s %s [%02d/%3s/%04d:%02d:%02d:%02d] "%s" %i %i "%s" "%s"' % (
|
|
self.connection.ip, ident, username, day, months[month], year, hour,
|
|
minute, second, self.header, responsecode, len(data), referer, useragent)
|
|
t = time.time()
|
|
if t - self.handler.lastflush > self.handler.minflush:
|
|
self.handler.lastflush = t
|
|
stdout.flush()
|
|
|
|
self.done = True
|
|
r = StringIO()
|
|
r.write('HTTP/1.0 ' + str(responsecode) + ' ' +
|
|
responsestring + '\r\n')
|
|
if not self.pre1:
|
|
headers['Content-Length'] = len(data)
|
|
for key, value in headers.items():
|
|
r.write(key + ': ' + str(value) + '\r\n')
|
|
r.write('\r\n')
|
|
if self.command != 'HEAD':
|
|
r.write(data)
|
|
self.connection.write(r.getvalue())
|
|
if self.connection.is_flushed():
|
|
self.connection.shutdown(1)
|
|
|
|
|
|
class HTTPHandler(Handler):
|
|
|
|
def __init__(self, getfunc, minflush):
|
|
self.connections = {}
|
|
self.getfunc = getfunc
|
|
self.minflush = minflush
|
|
self.lastflush = time.time()
|
|
|
|
def connection_made(self, connection):
|
|
self.connections[connection] = HTTPConnection(self, connection)
|
|
|
|
def connection_flushed(self, connection):
|
|
if self.connections[connection].done:
|
|
connection.shutdown(1)
|
|
|
|
def connection_lost(self, connection):
|
|
ec = self.connections[connection]
|
|
ec.closed = True
|
|
del ec.connection
|
|
del ec.next_func
|
|
del self.connections[connection]
|
|
|
|
def data_came_in(self, connection, data):
|
|
c = self.connections[connection]
|
|
if not c.data_came_in(data) and not c.closed:
|
|
c.connection.shutdown(1)
|