1 # -*- coding: utf-8 -*-
3 # Copyright P. Christeas <p_christ@hol.gr> 2008-2010
5 # WARNING: This program as such is intended to be used by professional
6 # programmers who take the whole responsibility of assessing all potential
7 # consequences resulting from its eventual inadequacies and bugs
8 # End users who are looking for a ready-to-use solution with commercial
9 # guarantees and support are strongly advised to contract a Free Software
12 # This program is Free Software; you can redistribute it and/or
13 # modify it under the terms of the GNU General Public License
14 # as published by the Free Software Foundation; either version 2
15 # of the License, or (at your option) any later version.
17 # This program is distributed in the hope that it will be useful,
18 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # GNU General Public License for more details.
22 # You should have received a copy of the GNU General Public License
23 # along with this program; if not, write to the Free Software
24 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
25 ###############################################################################
27 #.apidoc title: HTTP Layer library (websrv_lib)
29 """ Framework for generic http servers
31 This library contains *no* OpenERP-specific functionality. It should be
32 usable in other projects, too.
37 from BaseHTTPServer import *
38 from SimpleHTTPServer import SimpleHTTPRequestHandler
40 _logger = logging.getLogger(__name__)
42 class AuthRequiredExc(Exception):
43 def __init__(self,atype,realm):
44 Exception.__init__(self)
48 class AuthRejectedExc(Exception):
52 def __init__(self,realm):
55 def authenticate(self, user, passwd, client_address):
61 def checkRequest(self,handler,path = '/'):
62 """ Check if we are allowed to process that request
66 class HTTPHandler(SimpleHTTPRequestHandler):
67 def __init__(self,request, client_address, server):
68 SimpleHTTPRequestHandler.__init__(self,request,client_address,server)
69 # print "Handler for %s inited" % str(client_address)
70 self.protocol_version = 'HTTP/1.1'
71 self.connection = dummyconn()
74 """ Classes here should NOT handle inside their constructor
88 """ A dispatcher class, like a virtual folder in httpd
90 def __init__(self, path, handler, auth_provider=None, secure_only=False):
92 self.handler = handler
93 self.auth_provider = auth_provider
94 self.secure_only = secure_only
96 def matches(self, request):
97 """ Test if some request matches us. If so, return
99 if request.startswith(self.path):
103 def instanciate_handler(self, request, client_address, server):
104 handler = self.handler(noconnection(request), client_address, server)
105 if self.auth_provider:
106 handler.auth_provider = self.auth_provider()
109 def reg_http_service(path, handler, auth_provider=None, secure_only=False):
110 """ Register a HTTP handler at a given path.
112 The auth_provider will be instanciated and set on the handler instances.
115 service = HTTPDir(path, handler, auth_provider, secure_only)
120 if handlers[pos].matches(service.path):
122 # we won't break here, but search all way to the top, to
123 # ensure there is no lesser entry that will shadow the one
125 handlers.insert(lastpos, service)
127 def list_http_services(protocol=None):
131 if protocol is None or protocol == 'http' or svc.secure_only:
132 ret.append((svc.path, str(svc.handler)))
136 def find_http_service(path, secure=False):
138 for vdir in handlers:
139 p = vdir.matches(path)
140 if p == False or (vdir.secure_only and not secure):
145 class noconnection(object):
146 """ a class to use instead of the real connection
148 def __init__(self, realsocket=None):
149 self.__hidden_socket = realsocket
151 def makefile(self, mode, bufsize):
157 def getsockname(self):
158 """ We need to return info about the real socket that is used for the request
160 if not self.__hidden_socket:
161 raise AttributeError("No-connection class cannot tell real socket")
162 return self.__hidden_socket.getsockname()
165 def shutdown(self, tru):
168 def _quote_html(html):
169 return html.replace("&", "&").replace("<", "<").replace(">", ">")
172 #error_message_format = """ """
173 def send_error(self, code, message=None):
174 #overriden from BaseHTTPRequestHandler, we also send the content-length
176 short, long = self.responses[code]
178 short, long = '???', '???'
182 _logger.error("code %d, message %s", code, message)
183 # using _quote_html to prevent Cross Site Scripting attacks (see bug #1100201)
184 content = (self.error_message_format %
185 {'code': code, 'message': _quote_html(message), 'explain': explain})
186 self.send_response(code, message)
187 self.send_header("Content-Type", self.error_content_type)
188 self.send_header('Connection', 'close')
189 self.send_header('Content-Length', len(content) or 0)
191 if hasattr(self, '_flush'):
194 if self.command != 'HEAD' and code >= 200 and code not in (204, 304):
195 self.wfile.write(content)
198 _HTTP_OPTIONS = {'Allow': ['OPTIONS' ] }
200 def do_OPTIONS(self):
201 """return the list of capabilities """
203 opts = self._HTTP_OPTIONS
204 nopts = self._prep_OPTIONS(opts)
208 self.send_response(200)
209 self.send_header("Content-Length", 0)
210 if 'Microsoft' in self.headers.get('User-Agent', ''):
211 self.send_header('MS-Author-Via', 'DAV')
212 # Microsoft's webdav lib ass-umes that the server would
213 # be a FrontPage(tm) one, unless we send a non-standard
214 # header that we are not an elephant.
215 # http://www.ibm.com/developerworks/rational/library/2089.html
217 for key, value in opts.items():
218 if isinstance(value, basestring):
219 self.send_header(key, value)
220 elif isinstance(value, (tuple, list)):
221 self.send_header(key, ', '.join(value))
224 def _prep_OPTIONS(self, opts):
225 """Prepare the OPTIONS response, if needed
227 Sometimes, like in special DAV folders, the OPTIONS may contain
228 extra keywords, perhaps also dependant on the request url.
229 :param opts: MUST be copied before being altered
230 :returns: the updated options.
236 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: