[IMP/FIX] base, res_company: changed the default headers in order to add the default...
[odoo/odoo.git] / openerp / service / websrv_lib.py
1 # -*- coding: utf-8 -*-
2 #
3 # Copyright P. Christeas <p_christ@hol.gr> 2008-2010
4 #
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
10 # Service Company
11 #
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.
16 #
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.
21 #
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 ###############################################################################
26
27 #.apidoc title: HTTP Layer library (websrv_lib)
28
29 """ Framework for generic http servers
30
31     This library contains *no* OpenERP-specific functionality. It should be
32     usable in other projects, too.
33 """
34
35 import logging
36 import SocketServer
37 from BaseHTTPServer import *
38 from SimpleHTTPServer import SimpleHTTPRequestHandler
39
40 _logger = logging.getLogger(__name__)
41
42 class AuthRequiredExc(Exception):
43     def __init__(self,atype,realm):
44         Exception.__init__(self)
45         self.atype = atype
46         self.realm = realm
47
48 class AuthRejectedExc(Exception):
49     pass
50
51 class AuthProvider:
52     def __init__(self,realm):
53         self.realm = realm
54
55     def authenticate(self, user, passwd, client_address):
56         return False
57
58     def log(self, msg):
59         print msg
60
61     def checkRequest(self,handler,path = '/'):
62         """ Check if we are allowed to process that request
63         """
64         pass
65
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()
72
73     def handle(self):
74         """ Classes here should NOT handle inside their constructor
75         """
76         pass
77
78     def finish(self):
79         pass
80
81     def setup(self):
82         pass
83
84 # A list of HTTPDir.
85 handlers = []
86
87 class HTTPDir:
88     """ A dispatcher class, like a virtual folder in httpd
89     """
90     def __init__(self, path, handler, auth_provider=None, secure_only=False):
91         self.path = path
92         self.handler = handler
93         self.auth_provider = auth_provider
94         self.secure_only = secure_only
95
96     def matches(self, request):
97         """ Test if some request matches us. If so, return
98             the matched path. """
99         if request.startswith(self.path):
100             return self.path
101         return False
102
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()
107         return handler
108
109 def reg_http_service(path, handler, auth_provider=None, secure_only=False):
110     """ Register a HTTP handler at a given path.
111
112     The auth_provider will be instanciated and set on the handler instances.
113     """
114     global handlers
115     service = HTTPDir(path, handler, auth_provider, secure_only)
116     pos = len(handlers)
117     lastpos = pos
118     while pos > 0:
119         pos -= 1
120         if handlers[pos].matches(service.path):
121             lastpos = pos
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
124         # we are inserting.
125     handlers.insert(lastpos, service)
126
127 def list_http_services(protocol=None):
128     global handlers
129     ret = []
130     for svc in handlers:
131         if protocol is None or protocol == 'http' or svc.secure_only:
132             ret.append((svc.path, str(svc.handler)))
133     
134     return ret
135
136 def find_http_service(path, secure=False):
137     global handlers
138     for vdir in handlers:
139         p = vdir.matches(path)
140         if p == False or (vdir.secure_only and not secure):
141             continue
142         return vdir
143     return None
144
145 class noconnection(object):
146     """ a class to use instead of the real connection
147     """
148     def __init__(self, realsocket=None):
149         self.__hidden_socket = realsocket
150
151     def makefile(self, mode, bufsize):
152         return None
153
154     def close(self):
155         pass
156
157     def getsockname(self):
158         """ We need to return info about the real socket that is used for the request
159         """
160         if not self.__hidden_socket:
161             raise AttributeError("No-connection class cannot tell real socket")
162         return self.__hidden_socket.getsockname()
163
164 class dummyconn:
165     def shutdown(self, tru):
166         pass
167
168 def _quote_html(html):
169     return html.replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;")
170
171 class FixSendError:
172     #error_message_format = """ """
173     def send_error(self, code, message=None):
174         #overriden from BaseHTTPRequestHandler, we also send the content-length
175         try:
176             short, long = self.responses[code]
177         except KeyError:
178             short, long = '???', '???'
179         if message is None:
180             message = short
181         explain = 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)
190         self.end_headers()
191         if hasattr(self, '_flush'):
192             self._flush()
193         
194         if self.command != 'HEAD' and code >= 200 and code not in (204, 304):
195             self.wfile.write(content)
196
197 class HttpOptions:
198     _HTTP_OPTIONS = {'Allow': ['OPTIONS' ] }
199
200     def do_OPTIONS(self):
201         """return the list of capabilities """
202
203         opts = self._HTTP_OPTIONS
204         nopts = self._prep_OPTIONS(opts)
205         if nopts:
206             opts = nopts
207
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
216
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))
222         self.end_headers()
223
224     def _prep_OPTIONS(self, opts):
225         """Prepare the OPTIONS response, if needed
226         
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.
231
232         """
233         return opts
234
235
236 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: