[FIX] gamification: call _send_badge on right object
[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
28 """ Framework for generic http servers
29
30     This library contains *no* OpenERP-specific functionality. It should be
31     usable in other projects, too.
32 """
33
34 import logging
35 import SocketServer
36 from BaseHTTPServer import *
37 from SimpleHTTPServer import SimpleHTTPRequestHandler
38
39 _logger = logging.getLogger(__name__)
40
41 class AuthRequiredExc(Exception):
42     def __init__(self,atype,realm):
43         Exception.__init__(self)
44         self.atype = atype
45         self.realm = realm
46
47 class AuthRejectedExc(Exception):
48     pass
49
50 class AuthProvider:
51     def __init__(self,realm):
52         self.realm = realm
53
54     def authenticate(self, user, passwd, client_address):
55         return False
56
57     def log(self, msg):
58         print msg
59
60     def checkRequest(self,handler,path = '/'):
61         """ Check if we are allowed to process that request
62         """
63         pass
64
65 class HTTPHandler(SimpleHTTPRequestHandler):
66     def __init__(self,request, client_address, server):
67         SimpleHTTPRequestHandler.__init__(self,request,client_address,server)
68         # print "Handler for %s inited" % str(client_address)
69         self.protocol_version = 'HTTP/1.1'
70         self.connection = dummyconn()
71
72     def handle(self):
73         """ Classes here should NOT handle inside their constructor
74         """
75         pass
76
77     def finish(self):
78         pass
79
80     def setup(self):
81         pass
82
83 # A list of HTTPDir.
84 handlers = []
85
86 class HTTPDir:
87     """ A dispatcher class, like a virtual folder in httpd
88     """
89     def __init__(self, path, handler, auth_provider=None, secure_only=False):
90         self.path = path
91         self.handler = handler
92         self.auth_provider = auth_provider
93         self.secure_only = secure_only
94
95     def matches(self, request):
96         """ Test if some request matches us. If so, return
97             the matched path. """
98         if request.startswith(self.path):
99             return self.path
100         return False
101
102     def instanciate_handler(self, request, client_address, server):
103         handler = self.handler(noconnection(request), client_address, server)
104         if self.auth_provider:
105             handler.auth_provider = self.auth_provider()
106         return handler
107
108 def reg_http_service(path, handler, auth_provider=None, secure_only=False):
109     """ Register a HTTP handler at a given path.
110
111     The auth_provider will be instanciated and set on the handler instances.
112     """
113     global handlers
114     service = HTTPDir(path, handler, auth_provider, secure_only)
115     pos = len(handlers)
116     lastpos = pos
117     while pos > 0:
118         pos -= 1
119         if handlers[pos].matches(service.path):
120             lastpos = pos
121         # we won't break here, but search all way to the top, to
122         # ensure there is no lesser entry that will shadow the one
123         # we are inserting.
124     handlers.insert(lastpos, service)
125
126 def list_http_services(protocol=None):
127     global handlers
128     ret = []
129     for svc in handlers:
130         if protocol is None or protocol == 'http' or svc.secure_only:
131             ret.append((svc.path, str(svc.handler)))
132     
133     return ret
134
135 def find_http_service(path, secure=False):
136     global handlers
137     for vdir in handlers:
138         p = vdir.matches(path)
139         if p == False or (vdir.secure_only and not secure):
140             continue
141         return vdir
142     return None
143
144 class noconnection(object):
145     """ a class to use instead of the real connection
146     """
147     def __init__(self, realsocket=None):
148         self.__hidden_socket = realsocket
149
150     def makefile(self, mode, bufsize):
151         return None
152
153     def close(self):
154         pass
155
156     def getsockname(self):
157         """ We need to return info about the real socket that is used for the request
158         """
159         if not self.__hidden_socket:
160             raise AttributeError("No-connection class cannot tell real socket")
161         return self.__hidden_socket.getsockname()
162
163 class dummyconn:
164     def shutdown(self, tru):
165         pass
166
167 def _quote_html(html):
168     return html.replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;")
169
170 class FixSendError:
171     #error_message_format = """ """
172     def send_error(self, code, message=None):
173         #overriden from BaseHTTPRequestHandler, we also send the content-length
174         try:
175             short, long = self.responses[code]
176         except KeyError:
177             short, long = '???', '???'
178         if message is None:
179             message = short
180         explain = long
181         _logger.error("code %d, message %s", code, message)
182         # using _quote_html to prevent Cross Site Scripting attacks (see bug #1100201)
183         content = (self.error_message_format %
184                    {'code': code, 'message': _quote_html(message), 'explain': explain})
185         self.send_response(code, message)
186         self.send_header("Content-Type", self.error_content_type)
187         self.send_header('Connection', 'close')
188         self.send_header('Content-Length', len(content) or 0)
189         self.end_headers()
190         if hasattr(self, '_flush'):
191             self._flush()
192         
193         if self.command != 'HEAD' and code >= 200 and code not in (204, 304):
194             self.wfile.write(content)
195
196 class HttpOptions:
197     _HTTP_OPTIONS = {'Allow': ['OPTIONS' ] }
198
199     def do_OPTIONS(self):
200         """return the list of capabilities """
201
202         opts = self._HTTP_OPTIONS
203         nopts = self._prep_OPTIONS(opts)
204         if nopts:
205             opts = nopts
206
207         self.send_response(200)
208         self.send_header("Content-Length", 0)
209         if 'Microsoft' in self.headers.get('User-Agent', ''):
210             self.send_header('MS-Author-Via', 'DAV') 
211             # Microsoft's webdav lib ass-umes that the server would
212             # be a FrontPage(tm) one, unless we send a non-standard
213             # header that we are not an elephant.
214             # http://www.ibm.com/developerworks/rational/library/2089.html
215
216         for key, value in opts.items():
217             if isinstance(value, basestring):
218                 self.send_header(key, value)
219             elif isinstance(value, (tuple, list)):
220                 self.send_header(key, ', '.join(value))
221         self.end_headers()
222
223     def _prep_OPTIONS(self, opts):
224         """Prepare the OPTIONS response, if needed
225         
226         Sometimes, like in special DAV folders, the OPTIONS may contain
227         extra keywords, perhaps also dependant on the request url. 
228         :param opts: MUST be copied before being altered
229         :returns: the updated options.
230
231         """
232         return opts
233
234
235 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: