X-Git-Url: http://git.inspyration.org/?a=blobdiff_plain;f=bin%2Fservice%2Fwebsrv_lib.py;h=c89bcee7fa2ebc9c68018f0146a3669480e48536;hb=5d53dcc70079ca3041cc731bac44e9baf9a289f4;hp=e091ceb3a4489243c8239a3cdb1519b9264e0078;hpb=041f949bccbe7f06d402aa73b7a4301f000ca46d;p=odoo%2Fodoo.git diff --git a/bin/service/websrv_lib.py b/bin/service/websrv_lib.py index e091ceb..c89bcee 100644 --- a/bin/service/websrv_lib.py +++ b/bin/service/websrv_lib.py @@ -1,7 +1,6 @@ # -*- coding: utf-8 -*- # -# Copyright P. Christeas 2008,2009 -# A part of the code comes from the ganeti project: http://www.mail-archive.com/ganeti-devel@googlegroups.com/msg00713.html# +# Copyright P. Christeas 2008-2010 # # WARNING: This program as such is intended to be used by professional # programmers who take the whole responsability of assessing all potential @@ -136,12 +135,25 @@ class HTTPDir: return self.path return False -class noconnection: +class noconnection(object): """ a class to use instead of the real connection """ + def __init__(self, realsocket=None): + self.__hidden_socket = realsocket + def makefile(self, mode, bufsize): return None + def close(self): + pass + + def getsockname(self): + """ We need to return info about the real socket that is used for the request + """ + if not self.__hidden_socket: + raise AttributeError("No-connection class cannot tell real socket") + return self.__hidden_socket.getsockname() + class dummyconn: def shutdown(self, tru): pass @@ -169,10 +181,51 @@ class FixSendError: self.send_header('Connection', 'close') self.send_header('Content-Length', len(content) or 0) self.end_headers() + if hasattr(self, '_flush'): + self._flush() + if self.command != 'HEAD' and code >= 200 and code not in (204, 304): self.wfile.write(content) -class MultiHTTPHandler(FixSendError,BaseHTTPRequestHandler): +class HttpOptions: + _HTTP_OPTIONS = {'Allow': ['OPTIONS' ] } + + def do_OPTIONS(self): + """return the list of capabilities """ + + opts = self._HTTP_OPTIONS + nopts = self._prep_OPTIONS(opts) + if nopts: + opts = nopts + + self.send_response(200) + self.send_header("Content-Length", 0) + if 'Microsoft' in self.headers.get('User-Agent', ''): + self.send_header('MS-Author-Via', 'DAV') + # Microsoft's webdav lib ass-umes that the server would + # be a FrontPage(tm) one, unless we send a non-standard + # header that we are not an elephant. + # http://www.ibm.com/developerworks/rational/library/2089.html + + for key, value in opts.items(): + if isinstance(value, basestring): + self.send_header(key, value) + elif isinstance(value, (tuple, list)): + self.send_header(key, ', '.join(value)) + self.end_headers() + + def _prep_OPTIONS(self, opts): + """Prepare the OPTIONS response, if needed + + Sometimes, like in special DAV folders, the OPTIONS may contain + extra keywords, perhaps also dependant on the request url. + @param the options already. MUST be copied before being altered + @return the updated options. + + """ + return opts + +class MultiHTTPHandler(FixSendError, HttpOptions, BaseHTTPRequestHandler): """ this is a multiple handler, that will dispatch each request to a nested handler, iff it matches @@ -201,13 +254,19 @@ class MultiHTTPHandler(FixSendError,BaseHTTPRequestHandler): fore.raw_requestline = "%s %s %s\n" % (self.command, path, self.version) if not fore.parse_request(): # An error code has been sent, just exit return + if fore.headers.status: + self.log_error("Parse error at headers: %s", fore.headers.status) + self.close_connection = 1 + self.send_error(400,"Parse error at HTTP headers") + return + self.request_version = fore.request_version if auth_provider and auth_provider.realm: try: self.sec_realms[auth_provider.realm].checkRequest(fore,path) except AuthRequiredExc,ae: - if self.request_version != 'HTTP/1.1': - self.log_error("Cannot require auth at %s",self.request_version) + if self.request_version != 'HTTP/1.1' and ('Darwin/9.' not in fore.headers.get('User-Agent', '')): + self.log_error("Cannot require auth at %s", self.request_version) self.send_error(403) return self._get_ignore_body(fore) # consume any body that came, not loose sync with input @@ -226,11 +285,30 @@ class MultiHTTPHandler(FixSendError,BaseHTTPRequestHandler): return mname = 'do_' + fore.command if not hasattr(fore, mname): - fore.send_error(501, "Unsupported method (%r)" % fore.command) + if fore.command == 'OPTIONS': + self.do_OPTIONS() + return + self.send_error(501, "Unsupported method (%r)" % fore.command) return fore.close_connection = 0 method = getattr(fore, mname) - method() + try: + method() + except (AuthRejectedExc, AuthRequiredExc): + raise + except Exception, e: + if hasattr(self, 'log_exception'): + self.log_exception("Could not run %s", mname) + else: + self.log_error("Could not run %s: %s", mname, e) + self.send_error(500, "Internal error") + # may not work if method has already sent data + fore.close_connection = 1 + self.close_connection = 1 + if hasattr(fore, '_flush'): + fore._flush() + return + if fore.close_connection: # print "Closing connection because of handler" self.close_connection = fore.close_connection @@ -289,6 +367,7 @@ class MultiHTTPHandler(FixSendError,BaseHTTPRequestHandler): [command, path] = words self.close_connection = 1 if command != 'GET': + self.log_error("Junk http request: %s", self.raw_requestline) self.send_error(400, "Bad HTTP/0.9 request type (%r)" % command) return False @@ -315,6 +394,14 @@ class MultiHTTPHandler(FixSendError,BaseHTTPRequestHandler): self.log_message("Could not parse rawline.") return # self.parse_request(): # Do NOT parse here. the first line should be the only + + if self.path == '*' and self.command == 'OPTIONS': + # special handling of path='*', must not use any vdir at all. + if not self.parse_request(): + return + self.do_OPTIONS() + return + for vdir in self.server.vdirs: p = vdir.matches(self.path) if p == False: @@ -324,15 +411,21 @@ class MultiHTTPHandler(FixSendError,BaseHTTPRequestHandler): npath = '/' + npath if not self.in_handlers.has_key(p): - self.in_handlers[p] = vdir.handler(noconnection(),self.client_address,self.server) + self.in_handlers[p] = vdir.handler(noconnection(self.request),self.client_address,self.server) if vdir.auth_provider: vdir.auth_provider.setupAuth(self, self.in_handlers[p]) hnd = self.in_handlers[p] hnd.rfile = self.rfile hnd.wfile = self.wfile self.rlpath = self.raw_requestline - self._handle_one_foreign(hnd,npath, vdir.auth_provider) - # print "Handled, closing = ", self.close_connection + try: + self._handle_one_foreign(hnd,npath, vdir.auth_provider) + except IOError, e: + if e.errno == errno.EPIPE: + self.log_message("Could not complete request %s," \ + "client closed connection", self.rlpath.rstrip()) + else: + raise return # if no match: self.send_error(404, "Path not found: %s" % self.path) @@ -397,15 +490,26 @@ class ConnThreadingMixIn: # main process daemon_threads = False + def _get_next_name(self): + return None + def _handle_request_noblock(self): """Start a new thread to process the request.""" if not threading: # happens while quitting python return - t = threading.Thread(target = self._handle_request2) + t = threading.Thread(name=self._get_next_name(), target=self._handle_request2) if self.daemon_threads: t.setDaemon (1) t.start() + def _mark_start(self, thread): + """ Mark the start of a request thread """ + pass + + def _mark_end(self, thread): + """ Mark the end of a request thread """ + pass + def _handle_request2(self): """Handle one request, without blocking. @@ -414,14 +518,17 @@ class ConnThreadingMixIn: no risk of blocking in get_request(). """ try: + self._mark_start(threading.currentThread()) request, client_address = self.get_request() + if self.verify_request(request, client_address): + try: + self.process_request(request, client_address) + except Exception: + self.handle_error(request, client_address) + self.close_request(request) except socket.error: return - if self.verify_request(request, client_address): - try: - self.process_request(request, client_address) - except Exception: - self.handle_error(request, client_address) - self.close_request(request) + finally: + self._mark_end(threading.currentThread()) #eof