websrv_lib: update copyright and remove bogus note
[odoo/odoo.git] / bin / service / websrv_lib.py
index 82f3e4f..c89bcee 100644 (file)
@@ -1,7 +1,6 @@
 # -*- coding: utf-8 -*-
 #
-# Copyright P. Christeas <p_christ@hol.gr> 2008,2009
-#
+# Copyright P. Christeas <p_christ@hol.gr> 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,14 +254,20 @@ 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)
-                    self.send_error(401)
+                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
                 self.send_response(401,'Authorization required')
@@ -221,16 +280,35 @@ class MultiHTTPHandler(FixSendError,BaseHTTPRequestHandler):
                 return
             except AuthRejectedExc,e:
                 self.log_error("Rejected auth: %s" % e.args[0])
-                self.send_error(401,e.args[0])
+                self.send_error(403,e.args[0])
                 self.close_connection = 1
                 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)
@@ -370,7 +463,7 @@ class SecureMultiHTTPHandler(MultiHTTPHandler):
             self.rfile = self.connection.makefile('rb', self.rbufsize)
             self.wfile = self.connection.makefile('wb', self.wbufsize)
             self.log_message("Secure %s connection from %s",self.connection.cipher(),self.client_address)
-        except:
+        except Exception:
             self.request.shutdown(socket.SHUT_RDWR)
             raise
 
@@ -381,7 +474,7 @@ class SecureMultiHTTPHandler(MultiHTTPHandler):
         MultiHTTPHandler.finish(self)
         try:
             self.connection.shutdown(socket.SHUT_RDWR)
-        except:
+        except Exception:
             pass
 
 import threading
@@ -397,13 +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."""
-        t = threading.Thread(target = self._handle_request2)
+        if not threading: # happens while quitting python
+            return
+        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.
 
@@ -412,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:
-                self.handle_error(request, client_address)
-                self.close_request(request)
+        finally:
+            self._mark_end(threading.currentThread())
 
 #eof