Add timestamp offset for block header
[p2pool.git] / SOAPpy / Server.py
1 from __future__ import nested_scopes
2
3 """
4 ################################################################################
5 #
6 # SOAPpy - Cayce Ullman       (cayce@actzero.com)
7 #          Brian Matthews     (blm@actzero.com)
8 #          Gregory Warnes     (Gregory.R.Warnes@Pfizer.com)
9 #          Christopher Blunck (blunck@gst.com)
10 #
11 ################################################################################
12 # Copyright (c) 2003, Pfizer
13 # Copyright (c) 2001, Cayce Ullman.
14 # Copyright (c) 2001, Brian Matthews.
15 #
16 # All rights reserved.
17 #
18 # Redistribution and use in source and binary forms, with or without
19 # modification, are permitted provided that the following conditions are met:
20 # Redistributions of source code must retain the above copyright notice, this
21 # list of conditions and the following disclaimer.
22 #
23 # Redistributions in binary form must reproduce the above copyright notice,
24 # this list of conditions and the following disclaimer in the documentation
25 # and/or other materials provided with the distribution.
26 #
27 # Neither the name of actzero, inc. nor the names of its contributors may
28 # be used to endorse or promote products derived from this software without
29 # specific prior written permission.
30 #
31 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
32 # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
33 # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
34 # ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR
35 # ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
36 # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
37 # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
38 # ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
39 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
40 # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
41 #
42 ################################################################################
43 """
44
45 ident = '$Id: Server.py 1468 2008-05-24 01:55:33Z warnes $'
46 from version import __version__
47
48 #import xml.sax
49 import socket
50 import sys
51 import SocketServer
52 from types import *
53 import BaseHTTPServer
54 import thread
55
56 # SOAPpy modules
57 from Parser      import parseSOAPRPC
58 from Config      import Config
59 from Types       import faultType, voidType, simplify
60 from NS          import NS
61 from SOAPBuilder import buildSOAP
62 from Utilities   import debugHeader, debugFooter
63
64 try: from M2Crypto import SSL
65 except: pass
66
67 ident = '$Id: Server.py 1468 2008-05-24 01:55:33Z warnes $'
68
69 from version import __version__
70
71 ################################################################################
72 # Call context dictionary
73 ################################################################################
74
75 _contexts = dict()
76
77 def GetSOAPContext():
78     global _contexts
79     return _contexts[thread.get_ident()]
80
81 ################################################################################
82 # Server
83 ################################################################################
84
85 # Method Signature class for adding extra info to registered funcs, right now
86 # used just to indicate it should be called with keywords, instead of ordered
87 # params.
88 class MethodSig:
89     def __init__(self, func, keywords=0, context=0):
90         self.func     = func
91         self.keywords = keywords
92         self.context  = context
93         self.__name__ = func.__name__
94
95     def __call__(self, *args, **kw):
96         return apply(self.func,args,kw)
97
98 class SOAPContext:
99     def __init__(self, header, body, attrs, xmldata, connection, httpheaders,
100         soapaction):
101
102         self.header     = header
103         self.body       = body
104         self.attrs      = attrs
105         self.xmldata    = xmldata
106         self.connection = connection
107         self.httpheaders= httpheaders
108         self.soapaction = soapaction
109
110 # A class to describe how header messages are handled
111 class HeaderHandler:
112     # Initially fail out if there are any problems.
113     def __init__(self, header, attrs):
114         for i in header.__dict__.keys():
115             if i[0] == "_":
116                 continue
117
118             d = getattr(header, i)
119
120             try:
121                 fault = int(attrs[id(d)][(NS.ENV, 'mustUnderstand')])
122             except:
123                 fault = 0
124
125             if fault:
126                 raise faultType, ("%s:MustUnderstand" % NS.ENV_T,
127                                   "Required Header Misunderstood",
128                                   "%s" % i)
129
130 ################################################################################
131 # SOAP Server
132 ################################################################################
133 class SOAPServerBase:
134
135     def get_request(self):
136         sock, addr = SocketServer.TCPServer.get_request(self)
137
138         if self.ssl_context:
139             sock = SSL.Connection(self.ssl_context, sock)
140             sock._setup_ssl(addr)
141             if sock.accept_ssl() != 1:
142                 raise socket.error, "Couldn't accept SSL connection"
143
144         return sock, addr
145
146     def registerObject(self, object, namespace = '', path = ''):
147         if namespace == '' and path == '': namespace = self.namespace
148         if namespace == '' and path != '':
149             namespace = path.replace("/", ":")
150             if namespace[0] == ":": namespace = namespace[1:]
151         self.objmap[namespace] = object
152
153     def registerFunction(self, function, namespace = '', funcName = None,
154                          path = ''):
155         if not funcName : funcName = function.__name__
156         if namespace == '' and path == '': namespace = self.namespace
157         if namespace == '' and path != '':
158             namespace = path.replace("/", ":")
159             if namespace[0] == ":": namespace = namespace[1:]
160         if self.funcmap.has_key(namespace):
161             self.funcmap[namespace][funcName] = function
162         else:
163             self.funcmap[namespace] = {funcName : function}
164
165     def registerKWObject(self, object, namespace = '', path = ''):
166         if namespace == '' and path == '': namespace = self.namespace
167         if namespace == '' and path != '':
168             namespace = path.replace("/", ":")
169             if namespace[0] == ":": namespace = namespace[1:]
170         for i in dir(object.__class__):
171             if i[0] != "_" and callable(getattr(object, i)):
172                 self.registerKWFunction(getattr(object,i), namespace)
173
174     # convenience  - wraps your func for you.
175     def registerKWFunction(self, function, namespace = '', funcName = None,
176                            path = ''):
177         if namespace == '' and path == '': namespace = self.namespace
178         if namespace == '' and path != '':
179             namespace = path.replace("/", ":")
180             if namespace[0] == ":": namespace = namespace[1:]
181         self.registerFunction(MethodSig(function,keywords=1), namespace,
182         funcName)
183
184     def unregisterObject(self, object, namespace = '', path = ''):
185         if namespace == '' and path == '': namespace = self.namespace
186         if namespace == '' and path != '':
187             namespace = path.replace("/", ":")
188             if namespace[0] == ":": namespace = namespace[1:]
189
190         del self.objmap[namespace]
191         
192 class SOAPRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
193     def version_string(self):
194         return '<a href="http://pywebsvcs.sf.net">' + \
195             'SOAPpy ' + __version__ + '</a> (Python ' + \
196             sys.version.split()[0] + ')'
197
198     def date_time_string(self):
199         self.__last_date_time_string = \
200             BaseHTTPServer.BaseHTTPRequestHandler.\
201             date_time_string(self)
202
203         return self.__last_date_time_string
204
205     def do_POST(self):
206         global _contexts
207         
208         status = 500
209         try:
210             if self.server.config.dumpHeadersIn:
211                 s = 'Incoming HTTP headers'
212                 debugHeader(s)
213                 print self.raw_requestline.strip()
214                 print "\n".join(map (lambda x: x.strip(),
215                     self.headers.headers))
216                 debugFooter(s)
217
218             data = self.rfile.read(int(self.headers["Content-length"]))
219
220             if self.server.config.dumpSOAPIn:
221                 s = 'Incoming SOAP'
222                 debugHeader(s)
223                 print data,
224                 if data[-1] != '\n':
225                     print
226                 debugFooter(s)
227
228             (r, header, body, attrs) = \
229                 parseSOAPRPC(data, header = 1, body = 1, attrs = 1)
230
231             method = r._name
232             args   = r._aslist()
233             kw     = r._asdict()
234
235             if Config.simplify_objects:
236                 args = simplify(args)
237                 kw = simplify(kw)
238
239             # Handle mixed named and unnamed arguments by assuming
240             # that all arguments with names of the form "v[0-9]+"
241             # are unnamed and should be passed in numeric order,
242             # other arguments are named and should be passed using
243             # this name.
244
245             # This is a non-standard exension to the SOAP protocol,
246             # but is supported by Apache AXIS.
247
248             # It is enabled by default.  To disable, set
249             # Config.specialArgs to False.
250
251
252             ordered_args = {}
253             named_args   = {}
254
255             if Config.specialArgs: 
256                 
257                 for (k,v) in  kw.items():
258
259                     if k[0]=="v":
260                         try:
261                             i = int(k[1:])
262                             ordered_args[i] = v
263                         except ValueError:
264                             named_args[str(k)] = v
265
266                     else:
267                         named_args[str(k)] = v
268
269             # We have to decide namespace precedence
270             # I'm happy with the following scenario
271             # if r._ns is specified use it, if not check for
272             # a path, if it's specified convert it and use it as the
273             # namespace. If both are specified, use r._ns.
274             
275             ns = r._ns
276
277             if len(self.path) > 1 and not ns:
278                 ns = self.path.replace("/", ":")
279                 if ns[0] == ":": ns = ns[1:]
280             
281             # authorization method
282             a = None
283
284             keylist = ordered_args.keys()
285             keylist.sort()
286
287             # create list in proper order w/o names
288             tmp = map( lambda x: ordered_args[x], keylist)
289             ordered_args = tmp
290
291             #print '<-> Argument Matching Yielded:'
292             #print '<-> Ordered Arguments:' + str(ordered_args)
293             #print '<-> Named Arguments  :' + str(named_args)
294              
295             resp = ""
296             
297             # For fault messages
298             if ns:
299                 nsmethod = "%s:%s" % (ns, method)
300             else:
301                 nsmethod = method
302
303             try:
304                 # First look for registered functions
305                 if self.server.funcmap.has_key(ns) and \
306                     self.server.funcmap[ns].has_key(method):
307                     f = self.server.funcmap[ns][method]
308
309                     # look for the authorization method
310                     if self.server.config.authMethod != None:
311                         authmethod = self.server.config.authMethod
312                         if self.server.funcmap.has_key(ns) and \
313                                self.server.funcmap[ns].has_key(authmethod):
314                             a = self.server.funcmap[ns][authmethod]
315                 else:
316                     # Now look at registered objects
317                     # Check for nested attributes. This works even if
318                     # there are none, because the split will return
319                     # [method]
320                     f = self.server.objmap[ns]
321                     
322                     # Look for the authorization method
323                     if self.server.config.authMethod != None:
324                         authmethod = self.server.config.authMethod
325                         if hasattr(f, authmethod):
326                             a = getattr(f, authmethod)
327
328                     # then continue looking for the method
329                     l = method.split(".")
330                     for i in l:
331                         f = getattr(f, i)
332             except:
333                 info = sys.exc_info()
334                 try:
335                     resp = buildSOAP(faultType("%s:Client" % NS.ENV_T,
336                                                "Method Not Found",
337                                                "%s : %s %s %s" % (nsmethod,
338                                                                   info[0],
339                                                                   info[1],
340                                                                   info[2])),
341                                      encoding = self.server.encoding,
342                                      config = self.server.config)
343                 finally:
344                     del info
345                 status = 500
346             else:
347                 try:
348                     if header:
349                         x = HeaderHandler(header, attrs)
350
351                     fr = 1
352
353                     # call context book keeping
354                     # We're stuffing the method into the soapaction if there
355                     # isn't one, someday, we'll set that on the client
356                     # and it won't be necessary here
357                     # for now we're doing both
358
359                     if "SOAPAction".lower() not in self.headers.keys() or \
360                        self.headers["SOAPAction"] == "\"\"":
361                         self.headers["SOAPAction"] = method
362                         
363                     thread_id = thread.get_ident()
364                     _contexts[thread_id] = SOAPContext(header, body,
365                                                        attrs, data,
366                                                        self.connection,
367                                                        self.headers,
368                                                        self.headers["SOAPAction"])
369
370                     # Do an authorization check
371                     if a != None:
372                         if not apply(a, (), {"_SOAPContext" :
373                                              _contexts[thread_id] }):
374                             raise faultType("%s:Server" % NS.ENV_T,
375                                             "Authorization failed.",
376                                             "%s" % nsmethod)
377                     
378                     # If it's wrapped, some special action may be needed
379                     if isinstance(f, MethodSig):
380                         c = None
381                     
382                         if f.context:  # retrieve context object
383                             c = _contexts[thread_id]
384
385                         if Config.specialArgs:
386                             if c:
387                                 named_args["_SOAPContext"] = c
388                             fr = apply(f, ordered_args, named_args)
389                         elif f.keywords:
390                             # This is lame, but have to de-unicode
391                             # keywords
392                             
393                             strkw = {}
394                             
395                             for (k, v) in kw.items():
396                                 strkw[str(k)] = v
397                             if c:
398                                 strkw["_SOAPContext"] = c
399                             fr = apply(f, (), strkw)
400                         elif c:
401                             fr = apply(f, args, {'_SOAPContext':c})
402                         else:
403                             fr = apply(f, args, {})
404
405                     else:
406                         if Config.specialArgs:
407                             fr = apply(f, ordered_args, named_args)
408                         else:
409                             fr = apply(f, args, {})
410
411                     
412                     if type(fr) == type(self) and \
413                         isinstance(fr, voidType):
414                         resp = buildSOAP(kw = {'%sResponse' % method: fr},
415                             encoding = self.server.encoding,
416                             config = self.server.config)
417                     else:
418                         resp = buildSOAP(kw =
419                             {'%sResponse' % method: {'Result': fr}},
420                             encoding = self.server.encoding,
421                             config = self.server.config)
422
423                     # Clean up _contexts
424                     if _contexts.has_key(thread_id):
425                         del _contexts[thread_id]
426                         
427                 except Exception, e:
428                     import traceback
429                     info = sys.exc_info()
430
431                     try:
432                         if self.server.config.dumpFaultInfo:
433                             s = 'Method %s exception' % nsmethod
434                             debugHeader(s)
435                             traceback.print_exception(info[0], info[1],
436                                                       info[2])
437                             debugFooter(s)
438
439                         if isinstance(e, faultType):
440                             f = e
441                         else:
442                             f = faultType("%s:Server" % NS.ENV_T,
443                                           "Method Failed",
444                                           "%s" % nsmethod)
445
446                         if self.server.config.returnFaultInfo:
447                             f._setDetail("".join(traceback.format_exception(
448                                 info[0], info[1], info[2])))
449                         elif not hasattr(f, 'detail'):
450                             f._setDetail("%s %s" % (info[0], info[1]))
451                     finally:
452                         del info
453
454                     resp = buildSOAP(f, encoding = self.server.encoding,
455                        config = self.server.config)
456                     status = 500
457                 else:
458                     status = 200
459         except faultType, e:
460             import traceback
461             info = sys.exc_info()
462             try:
463                 if self.server.config.dumpFaultInfo:
464                     s = 'Received fault exception'
465                     debugHeader(s)
466                     traceback.print_exception(info[0], info[1],
467                         info[2])
468                     debugFooter(s)
469
470                 if self.server.config.returnFaultInfo:
471                     e._setDetail("".join(traceback.format_exception(
472                             info[0], info[1], info[2])))
473                 elif not hasattr(e, 'detail'):
474                     e._setDetail("%s %s" % (info[0], info[1]))
475             finally:
476                 del info
477
478             resp = buildSOAP(e, encoding = self.server.encoding,
479                 config = self.server.config)
480             status = 500
481         except Exception, e:
482             # internal error, report as HTTP server error
483
484             if self.server.config.dumpFaultInfo:
485                 s = 'Internal exception %s' % e
486                 import traceback
487                 debugHeader(s)
488                 info = sys.exc_info()
489                 try:
490                     traceback.print_exception(info[0], info[1], info[2])
491                 finally:
492                     del info
493
494                 debugFooter(s)
495
496             self.send_response(500)
497             self.end_headers()
498
499             if self.server.config.dumpHeadersOut and \
500                 self.request_version != 'HTTP/0.9':
501                 s = 'Outgoing HTTP headers'
502                 debugHeader(s)
503                 if self.responses.has_key(status):
504                     s = ' ' + self.responses[status][0]
505                 else:
506                     s = ''
507                 print "%s %d%s" % (self.protocol_version, 500, s)
508                 print "Server:", self.version_string()
509                 print "Date:", self.__last_date_time_string
510                 debugFooter(s)
511         else:
512             # got a valid SOAP response
513             self.send_response(status)
514
515             t = 'text/xml';
516             if self.server.encoding != None:
517                 t += '; charset=%s' % self.server.encoding
518             self.send_header("Content-type", t)
519             self.send_header("Content-length", str(len(resp)))
520             self.end_headers()
521
522             if self.server.config.dumpHeadersOut and \
523                 self.request_version != 'HTTP/0.9':
524                 s = 'Outgoing HTTP headers'
525                 debugHeader(s)
526                 if self.responses.has_key(status):
527                     s = ' ' + self.responses[status][0]
528                 else:
529                     s = ''
530                 print "%s %d%s" % (self.protocol_version, status, s)
531                 print "Server:", self.version_string()
532                 print "Date:", self.__last_date_time_string
533                 print "Content-type:", t
534                 print "Content-length:", len(resp)
535                 debugFooter(s)
536
537             if self.server.config.dumpSOAPOut:
538                 s = 'Outgoing SOAP'
539                 debugHeader(s)
540                 print resp,
541                 if resp[-1] != '\n':
542                     print
543                 debugFooter(s)
544
545             self.wfile.write(resp)
546             self.wfile.flush()
547
548             # We should be able to shut down both a regular and an SSL
549             # connection, but under Python 2.1, calling shutdown on an
550             # SSL connections drops the output, so this work-around.
551             # This should be investigated more someday.
552
553             if self.server.config.SSLserver and \
554                 isinstance(self.connection, SSL.Connection):
555                 self.connection.set_shutdown(SSL.SSL_SENT_SHUTDOWN |
556                     SSL.SSL_RECEIVED_SHUTDOWN)
557             else:
558                 self.connection.shutdown(1)
559
560         def do_GET(self):
561             
562             #print 'command        ', self.command
563             #print 'path           ', self.path
564             #print 'request_version', self.request_version
565             #print 'headers'
566             #print '   type    ', self.headers.type
567             #print '   maintype', self.headers.maintype
568             #print '   subtype ', self.headers.subtype
569             #print '   params  ', self.headers.plist
570             
571             path = self.path.lower()
572             if path.endswith('wsdl'):
573                 method = 'wsdl'
574                 function = namespace = None
575                 if self.server.funcmap.has_key(namespace) \
576                         and self.server.funcmap[namespace].has_key(method):
577                     function = self.server.funcmap[namespace][method]
578                 else: 
579                     if namespace in self.server.objmap.keys():
580                         function = self.server.objmap[namespace]
581                         l = method.split(".")
582                         for i in l:
583                             function = getattr(function, i)
584             
585                 if function:
586                     self.send_response(200)
587                     self.send_header("Content-type", 'text/plain')
588                     self.end_headers()
589                     response = apply(function, ())
590                     self.wfile.write(str(response))
591                     return
592             
593             # return error
594             self.send_response(200)
595             self.send_header("Content-type", 'text/html')
596             self.end_headers()
597             self.wfile.write('''\
598 <title>
599 <head>Error!</head>
600 </title>
601
602 <body>
603 <h1>Oops!</h1>
604
605 <p>
606   This server supports HTTP GET requests only for the the purpose of
607   obtaining Web Services Description Language (WSDL) for a specific
608   service.
609
610   Either you requested an URL that does not end in "wsdl" or this
611   server does not implement a wsdl method.
612 </p>
613
614
615 </body>''')
616
617             
618     def log_message(self, format, *args):
619         if self.server.log:
620             BaseHTTPServer.BaseHTTPRequestHandler.\
621                 log_message (self, format, *args)
622
623
624
625 class SOAPServer(SOAPServerBase, SocketServer.TCPServer):
626
627     def __init__(self, addr = ('localhost', 8000),
628         RequestHandler = SOAPRequestHandler, log = 0, encoding = 'UTF-8',
629         config = Config, namespace = None, ssl_context = None):
630
631         # Test the encoding, raising an exception if it's not known
632         if encoding != None:
633             ''.encode(encoding)
634
635         if ssl_context != None and not config.SSLserver:
636             raise AttributeError, \
637                 "SSL server not supported by this Python installation"
638
639         self.namespace          = namespace
640         self.objmap             = {}
641         self.funcmap            = {}
642         self.ssl_context        = ssl_context
643         self.encoding           = encoding
644         self.config             = config
645         self.log                = log
646
647         self.allow_reuse_address= 1
648
649         SocketServer.TCPServer.__init__(self, addr, RequestHandler)
650
651
652 class ThreadingSOAPServer(SOAPServerBase, SocketServer.ThreadingTCPServer):
653
654     def __init__(self, addr = ('localhost', 8000),
655         RequestHandler = SOAPRequestHandler, log = 0, encoding = 'UTF-8',
656         config = Config, namespace = None, ssl_context = None):
657
658         # Test the encoding, raising an exception if it's not known
659         if encoding != None:
660             ''.encode(encoding)
661
662         if ssl_context != None and not config.SSLserver:
663             raise AttributeError, \
664                 "SSL server not supported by this Python installation"
665
666         self.namespace          = namespace
667         self.objmap             = {}
668         self.funcmap            = {}
669         self.ssl_context        = ssl_context
670         self.encoding           = encoding
671         self.config             = config
672         self.log                = log
673
674         self.allow_reuse_address= 1
675
676         SocketServer.ThreadingTCPServer.__init__(self, addr, RequestHandler)
677
678 # only define class if Unix domain sockets are available
679 if hasattr(socket, "AF_UNIX"):
680
681     class SOAPUnixSocketServer(SOAPServerBase, SocketServer.UnixStreamServer):
682     
683         def __init__(self, addr = 8000,
684             RequestHandler = SOAPRequestHandler, log = 0, encoding = 'UTF-8',
685             config = Config, namespace = None, ssl_context = None):
686     
687             # Test the encoding, raising an exception if it's not known
688             if encoding != None:
689                 ''.encode(encoding)
690     
691             if ssl_context != None and not config.SSLserver:
692                 raise AttributeError, \
693                     "SSL server not supported by this Python installation"
694     
695             self.namespace          = namespace
696             self.objmap             = {}
697             self.funcmap            = {}
698             self.ssl_context        = ssl_context
699             self.encoding           = encoding
700             self.config             = config
701             self.log                = log
702     
703             self.allow_reuse_address= 1
704     
705             SocketServer.UnixStreamServer.__init__(self, str(addr), RequestHandler)
706