/LMG/Tools/webframework.py
Python | 310 lines | 304 code | 6 blank | 0 comment | 0 complexity | 9b69879e63d44da43b70be9a3384e733 MD5 | raw file
- import re
- import StringIO
- import logging
- import socket
-
- from hashlib import md5
- import time
- import random
-
- import wsgiref
- import wsgiref.handlers
- import wsgiref.headers
- from wsgiref import simple_server
-
- from traceback import format_exc
- from httplib import responses as HTTP_STATUS_MAP
-
- from BitTornado.__init__ import product_name
-
- DEBUG = True
-
- class Request(object):
- """Request holder"""
- def __init__(self, environ):
- #http://ip:port/dir/file?param=1&another=2
- self.environ = environ
- self.host = environ.get('HTTP_HOST', '') # ip:port
- self.scheme = environ.get('wsgi.url_scheme', '') # http
- self.path = environ.get('PATH_INFO', '') # /dir/file
- self.query = environ.get('QUERY_STRING', '') # param=1&another=2
- self.method = environ.get('REQUEST_METHOD', '') # GET
- self.authorization = environ.get('HTTP_AUTHORIZATION')
- self.fullpath = environ.get('SCRIPT_NAME', '') + self.path
-
- def parameters(self):
- parameters = {}
- for param in self.query.split('&'):
- try:
- key, value = param.split("=")
- except ValueError:
- continue # bad parameter
- if key in parameters:
- if isinstance(parameters[key], list):
- parameters[key].append(value)
- else:
- parameters[key] = [parameters[key], value]
- else:
- parameters[key] = value
- return parameters
-
- class Response(object):
- """Response holder"""
- def __init__(self):
- self.out = StringIO.StringIO()
- self.set_status(200)
- self.headers = wsgiref.headers.Headers([])
- self.headers['Content-Type'] = "text/html; charset=utf-8"
- self.headers['Cache-Control'] = "no-cache"
-
- def set_status(self, code, message = None):
- if not message:
- message = HTTP_STATUS_MAP[code]
- self.status = (code, message)
-
- def clear(self):
- self.out.seek(0)
- self.out.truncate(0)
-
- def wsgi_write(self, start_response):
- body = self.out.getvalue()
- if isinstance(body, unicode):
- body = body.encode('utf-8')
- self.headers['Content-Length'] = str(len(body))
- start_response('%d %s' % self.status, self.headers.items())
- self.out.close()
- return body
-
- class RequestHandler(object):
- """Abstract Request Handler"""
- def initialize(self, request, response):
- self.request = request
- self.response = response
-
- def get(self, *args):
- """Handler method for GET requests."""
- self.error(405)
-
- def post(self, *args):
- """Handler method for POST requests."""
- self.error(405)
-
- def head(self, *args):
- """Handler method for HEAD requests."""
- self.error(405)
-
- def options(self, *args):
- """Handler method for OPTIONS requests."""
- self.error(405)
-
- def trace(self, *args):
- """Handler method for TRACE requests."""
- self.error(405)
-
- def error(self, code):
- """Clears the response output stream and sets the given HTTP error code."""
- self.response.set_status(code)
- self.response.clear()
-
- def handle_exception(self, exception):
- """Called if this handler throws an exception during execution."""
- self.error(500)
- logging.error(exception)
- if DEBUG:
- self.response.clear()
- self.response.headers['Content-Type'] = 'text/plain'
- self.response.out.write(exception)
-
- class WSGIApplication(object):
- """Web Application"""
- def __init__(self, mapping):
- self.mapping = []
- for regex, handler in mapping:
- self.mapping.append((re.compile(self.fix_regex(regex)), handler))
-
- # Authorization
- self.nonce = {}
- self.realm = product_name
-
- def fix_regex(self, regex):
- """ fix regular expressions """
- if not regex.startswith('^'):
- regex = '^' + regex
- if not regex.endswith('$'):
- regex += '$'
- return regex
-
- def bad_auth(self, response, start_response):
- """ Bad authorization """
- if utility.webconfig.Read("authorization") == "basic":
- response.headers['WWW-Authenticate'] = 'Basic realm=%s' % (self.realm)
- response.set_status(401)
- else:
- nonce = md5('%s:%s' % (time.time(), random.random())).hexdigest()
- opaque = md5('%s:%s' % (time.time(), random.random())).hexdigest()
- self.nonce[nonce] = None
- parts = {'realm':self.realm, 'qop':'auth', 'nonce':nonce, 'opaque':opaque}
- head = ', '.join(['%s="%s"' % (k, v) for (k, v) in parts.items()])
- response.headers['WWW-Authenticate'] = 'Digest %s' % head
- response.set_status(401)
- return response.wsgi_write(start_response)
-
- def bad_ip(self, response, start_response):
- """ Bad IP address """
- response.set_status(403)
- return response.wsgi_write(start_response)
-
- def auth(self, request):
- """ Check authorization """
- # Check if needed
- authorization = request.authorization
- conf_username = utility.webconfig.Read("username")
- conf_password = utility.webconfig.Read("password")
- if not conf_username:
- return True
- if authorization is None:
- return False
-
- if utility.webconfig.Read("authorization") == "basic":
- # Verify scheme
- authmeth, auth = authorization.split(' ', 1)
- if authmeth.lower() != 'basic':
- return False
- # Verify username and password
- auth = auth.strip().decode('base64')
- username, password = auth.split(':', 1)
- if username != conf_username or password != conf_password:
- return False
- return True
- else:
- # Verify scheme
- if not ' ' in authorization:
- return False
- authmeth, auth = authorization.split(' ', 1)
- if 'digest' != authmeth.lower():
- return False
- # Verify username and password
- amap = {}
- for itm in auth.split(','):
- k, v = [s.strip() for s in itm.split('=', 1)]
- amap[k] = v.replace('"', '')
- try:
- username = amap['username']
- authpath = amap['uri']
- nonce = amap['nonce']
- realm = amap['realm']
- response = amap['response']
- if not authpath.split('?', 1)[0] in request.fullpath:
- return False
- if realm != self.realm:
- return False
- qop = amap.get('qop', '')
- cnonce = amap.get('cnonce', '')
- nc = amap.get('nc', '00000000')
- if qop:
- if 'auth' != qop:
- return False
- if not (nonce and nc):
- return False
- except:
- return False
- ha1 = md5('%s:%s:%s' % (conf_username, self.realm, conf_password)).hexdigest()
- ha2 = md5('%s:%s' % (request.method, authpath)).hexdigest()
- if qop:
- chk = '%s:%s:%s:%s:%s:%s' % (ha1, nonce, nc, cnonce, qop, ha2)
- else:
- chk = '%s:%s:%s' % (ha1, nonce, ha2)
- if response != md5(chk).hexdigest():
- if nonce in self.nonce:
- del self.nonce[nonce]
- return False
- pnc = self.nonce.get(nonce, '00000000')
- if nc <= pnc:
- if nonce in self.nonce:
- del self.nonce[nonce]
- return False
- self.nonce[nonce] = nc
- return True
-
- def __call__(self, environ, start_response):
- """Handle incoming request"""
- # Create request and response objects
- request = Request(environ)
- response = Response()
-
- # Check scheme
- if request.scheme != 'http':
- response.set_status(404)
- return response.wsgi_write(start_response)
-
- # Check ip
- allowed = utility.webconfig.Read("allowed", "bencode-list")
- if allowed and environ.get('REMOTE_ADDR') not in allowed:
- return self.bad_ip(response, start_response)
-
- # Check authorization
- if not self.auth(request):
- return self.bad_auth(response, start_response)
-
- # Find the right handler
- handler = None
- groups = ()
- for regex, handler_class in self.mapping:
- match = regex.match(request.path)
- if match:
- handler = handler_class()
- handler.initialize(request, response)
- groups = match.groups()
- break
-
- # Handle the request
- if handler:
- methods = {'GET': handler.get, 'POST': handler.post, 'HEAD': handler.head,
- 'OPTIONS': handler.options, 'TRACE': handler.trace}
- try:
- func = methods.get(request.method)
- if func:
- func(*groups)
- else:
- handler.error(501)
- except Exception, e:
- handler.handle_exception(format_exc())
- else:
- response.set_status(404)
-
- return response.wsgi_write(start_response)
-
- class SimpleServer(simple_server.WSGIServer):
- def __init__(self, *args, **kwargs):
- simple_server.WSGIServer.__init__(self, *args, **kwargs)
-
- def DoStop(self):
- self.server_close()
-
- def DoStart(self):
- self.socket = socket.socket(self.address_family,
- self.socket_type)
- self.OnBind()
- self.OnActivate()
-
- def ChangeAddress(self, addr):
- self.server_address = addr
-
- def OnBind(self):
- simple_server.WSGIServer.server_bind(self)
-
- def server_bind(self):
- pass
-
- def OnActivate(self):
- simple_server.WSGIServer.server_activate(self)
-
- def server_activate(self):
- pass
-
- def app_get(mapping, host = '', port = 8080):
- return simple_server.make_server(host, port, WSGIApplication(mapping), SimpleServer)
-
- if __name__ == "__main__":
- app = app_get([('/', RequestHandler), ('/test', RequestHandler)])
- app.serve_forever()