1 import os
2 import sys
3 import shutil
4 import socket
5 import SocketServer
6 from SocketServer import BaseServer, ThreadingTCPServer, TCPServer
7 import mimetypes
8 mimetypes.init()
9 from BaseHTTPServer import HTTPServer as httpbase
10 from CGIHTTPServer import CGIHTTPRequestHandler, nobody_uid
11 try:
12 from OpenSSL import SSL
13 except ImportError:
14 import warn
15 warn.debug("Unable to use the HTTPS protocol")
16 SSL = None
17 try:
18 from cStringIO import StringIO
19 except ImportError:
20 from StringIO import StringIO
21 import warn
22 import urllib
23 import select
24 import urlparse
25 import posixpath
26 import re
27 import cgi
28 import types
29 import thread
30 import gzip
31 __thread_data__ = {}
32
34 """ Returns the spyro's request dictionary that
35 rules the current thread or shttp.http request. If this thread is not
36 ruled by an http request, it will raise a KeyError exception"""
37 return __thread_data__[thread.get_ident()][0]
38
41
44
48
51
52 warn = warn.Warn("shttp.http")
53
55 - def __init__(self, data, headers={}, content_type='text/plain', status=200, reason="Response Follows", options={}, content_type_from_path=None):
56 """ This class is used as an exception to send Custom Responses from a
57 SPyRO method to the HTTP web server. The web server must known how this
58 must be handled.
59 |data| The data to be send in the response (an str object)
60 |headers| A dictionary with pair valued headers
61 |content_type| The content type of the data
62 |status| The number of status to present in the response (if it's posible).
63 The default value is 200 (OK).
64 """
65 self.reason = reason
66 self.options = options
67 self.status = status
68 self.data = data
69 self.headers = headers
70 self.content_type_from_path = content_type_from_path
71 self.content_type = content_type
72
76
79
81 if environment['REQUEST_METHOD'].lower() == 'post':
82 environment['REQUEST_METHOD'] = 'GET'
83 kwargs = getkwargs(httpreq, environment)
84 environment['REQUEST_METHOD'] = 'POST'
85 else:
86 kwargs = {}
87 fields = cgi.FieldStorage(fp=httpreq.rfile,environ=environment)
88 for x in fields.keys():
89 if fields[x].filename is None:
90 kwargs[x] = fields.getvalue(x)
91 else:
92 kwargs[x] = fields[x]
93 return kwargs
94
96 """ |headers| can be one of the following:
97 - A dictionary
98 - A tuple/list of tuple/list of two elements
99 - A tuple/list of a string per entry
100 - A mixin of the previous two
101 """
102 wfile = httpreq.wfile
103 wfile.write("Content-Type: " + returntype + "\r\n")
104 env,_headers = getthreaddata()
105
106 try:
107 res.read
108 isfile = True
109 except AttributeError,e:
110 isfile = False
111 res = str(res)
112
113 try:
114 if isfile:
115 raise KeyError("Refuse to compress a direct file")
116 for enc in env['HTTP_ACCEPT_ENCODING'].split(","):
117 enc = enc.strip()
118 if not enc.endswith("gzip"):
119 continue
120 string = StringIO()
121 gzfile = gzip.GzipFile(filename=None, mode="wb", fileobj = string)
122 gzfile.write(res)
123 gzfile.flush()
124 gzfile.close()
125 res = string.getvalue()
126 wfile.write("Content-Encoding: %s\r\n"%enc)
127 break
128 wfile.write("Content-Length: %d\r\n"%len(res))
129 except (ValueError, KeyError, TypeError):
130 pass
131 try:
132 items = headers.items()
133 except AttributeError:
134 items = headers
135 _headers.extend(items)
136 _headers.append(("Connection","close"))
137 for item in _headers:
138 try:
139 name,value = item
140 wfile.write(name)
141 wfile.write(": ")
142 wfile.write(str(value))
143 except ValueError:
144 wfile.write(item)
145 wfile.write("\r\n")
146 wfile.write("\r\n")
147 if isfile:
148 shutil.copyfileobj(res, wfile)
149 try:
150 res.close()
151 except Exception, e:
152 warn.warn("Error closing webmethod response: ", e)
153 else:
154 wfile.write(res)
155 wfile.flush()
156
157 -def webmethod(httpreq, environment, method, returntype="text/plain"):
158 kwargs = getkwargs(httpreq, environment)
159 res = str(method(**kwargs))
160 webmethodwrite(httpreq, res, returntype)
161
162 import Cookie, urllib
164 try:
165 c = environment['HTTP_COOKIE']
166 cookies = Cookie.SimpleCookie()
167 cookies.load(c)
168 values = urllib.unquote(cookies[authcookiename].value)
169 values = values.split(',')
170 x = [ urllib.unquote(cookies[v].value) for v in values ]
171 return x
172 except KeyError:
173 return None
174
175 -def webspyro(spyroserver, httpreq, environment, returntype="text/plain", authcookiename='SPyRO_AuthMethod'):
176 try:
177 path = environment['PATH_INFO'].lstrip("/")
178 if not path: raise KeyError
179 except KeyError:
180 path = 'index'
181 parts = [ environment['SCRIPT_NAME'].split('/')[-1] ]
182 parts.extend( path.split("/") )
183 objname = ".".join(parts[:-1])
184 attrname = parts[-1]
185 kwargs = getkwargs(httpreq, environment)
186 auth = getcookiespyroauth(authcookiename,environment)
187
188 res = spyroserver.handle_request(objname, attrname, (), kwargs, auth, reqtype='CALL')
189 if res['errcode'] != 0:
190 raise HttpMethodError(res)
191 webmethodwrite(httpreq, res['retvalue'], returntype)
192
193
194 -def httpcall(realhandler,returntype='text/plain'):
195 """ Creates a lexical closure to execute a function with
196 query_string and post requests as function arguments
197 |realhandler| The handler of the call
198 |returntype| The return type of the function to be placed as
199 Content-Type header
200
201 Note: If httpcall is used as decorator the declared function
202 will be replaced, use regcall of http object as decorator.
203
204 """
205 def directcall(httpreq, environment):
206 webmethod(httpreq, environment, realhandler, returntype)
207 return directcall
208
209 -def httpcallspyro(spyroserver, returntype='text/plain', authcookiename='SPyRO_AuthMethod'):
210 """ Creates a lexical closure to execute a function with
211 query_string and post requests as function arguments
212 |spyroserver| The SPyRO server to the call
213
214 If the proper authorization policies aren't disabled (policy 'can_follow_objname'):
215 /SPyRO/Chart/Bar/Draw
216 /SPyRO -> Registered URL base to spyroserver
217 /Chart -> Object
218 /Bar -> A property of /Chart
219 /Draw -> A method of /Bar
220
221 |returntype| The return type of the function to be placed as
222 Content-Type header
223
224 Note: If httpcall is used as decorator the declared function
225 will be replaced, use regcall of http object as decorator.
226
227 """
228 def directcall(httpreq, environment):
229 webspyro(spyroserver, httpreq, environment, returntype, authcookiename)
230 return directcall
231
234 self.http = http
235 self.wfile = http.wfile
236 self.response = None
237 self.closed = False
238
240 if self.closed: return
241 self.http.wfile = self.wfile
242 self.write = self.wfile.write
243 self.flush = self.wfile.flush
244 self.close = self.wfile.close
245 self.closed = True
246 if not self.response.startswith("HTTP/"):
247 self.http.send_response(200,"CGI output follows")
248 self.wfile.write(self.response)
249
251 if self.response is None:
252 self.response = data
253 else:
254 self.response = self.response + data
255 if len(self.response) >= 5:
256 self.change()
257 else:
258 if self.response in ("H","HT","HTTP"):
259 return
260 else:
261 self.change()
262
265
268
271
274 """
275 Translate a /-separated PATH to the local filename syntax.
276
277 Components that mean special things to the local file system
278 (e.g. drive or directory names) are ignored.
279 (XXX They should probably be diagnosed.)
280
281 """
282
283
284
285 words = path.split('?')[0].split('/')
286 words = filter(None, words)
287 final = []
288 for word in words:
289 drive, word = os.path.splitdrive(word)
290 head, word = os.path.split(word)
291 if word == os.curdir: continue
292 if word == os.pardir:
293 try:
294 del final[-1]
295 except IndexError:
296 pass
297 final.append(word)
298 sep = os.sep
299 return self.virtual_host.document_root + sep + sep.join(final)
300
302 res = CGIHTTPRequestHandler.parse_request(self)
303 parsed = urlparse.urlparse(self.path)
304 self.path = urllib.unquote(posixpath.normpath(parsed[2]))
305 if parsed[2][-1] == '/':
306 if parsed[2] != '/':
307 self.path = self.path + "/"
308 if parsed[-2] != '':
309 self.path = "%s?%s"%(self.path, urllib.unquote(parsed[-2]))
310 self.virtual_host = self.server.get_virtual_host(self.headers.get('Host'))
311 self.cgi_directories = self.virtual_host.cgi_directories
312 if self.virtual_host.rewrite_function:
313 self.virtual_host.rewrite_function(self)
314 return res
315
317 self.connection = self.request
318 self.rfile = socket._fileobject(self.request, "rb", self.rbufsize)
319 self.wfile = socket._fileobject(self.request, "wb", self.wbufsize)
320
322 CGIHTTPRequestHandler.handle(self)
323
324
326 """ Checks if the URI corresponds to a CGI or a Python Handler """
327 cgi_handlers = self.virtual_host.cgi_handlers
328 path = self.path.split('?',1)[0]
329
330 handler = None
331 keys = cgi_handlers.keys()
332 keys.sort()
333
334 for x in keys:
335 i = len(x)
336 prefix = path[:i]
337
338
339 if prefix == x and (len(path) == i or path[i] == '/'):
340 self.cgi_info = path[:i], re.sub(r"^/+","",self.path[i:]), cgi_handlers[x]
341 return True
342 return CGIHTTPRequestHandler.is_cgi(self)
343
345 """ Returns True if running the script is OK, False otherwise """
346 if not os.path.exists(scriptfile):
347 self.send_error(404, "No such CGI script (%r)" % scriptname)
348 return False
349 if not os.path.isfile(scriptfile):
350 self.send_error(403, "CGI script is not a plain file (%r)" %
351 scriptname)
352 return False
353 ispy = self.is_python(scriptname)
354 if not ispy:
355 if not (self.have_fork or self.have_popen2 or self.have_popen3):
356 self.send_error(403, "CGI script is not a Python script (%r)" %
357 scriptname)
358 return False
359 if not self.is_executable(scriptfile):
360 self.send_error(403, "CGI script is not executable (%r)" %
361 scriptname)
362 return False
363 return True
364
366 return self.client_address[0]
367
369 """Execute a CGI script."""
370 if len(self.cgi_info) == 3:
371 dir, rest, handler = self.cgi_info
372 else:
373 dir, rest = self.cgi_info
374 handler = None
375 i = rest.rfind('?')
376
377 if i >= 0:
378 rest, query = rest[:i], rest[i+1:]
379 else:
380 query = ''
381
382 self.query = query
383 i = rest.find('/')
384 if i >= 0:
385 script, rest = rest[:i], rest[i:]
386 else:
387 script, rest = rest, ''
388
389 self.script = script
390 self.scriptname = scriptname = dir + '/' + script
391 self.scriptfile = scriptfile = self.translate_path(scriptname)
392
393
394 if handler is None:
395 if not self.check_script_sanity(scriptname, scriptfile):
396 raise HttpCgiError("Unable to run %s => %s as CGI"%(scriptname, scriptfile))
397
398
399 env = {}
400 for k,v in self.headers.items():
401 env["HTTP_%s"%(k.replace("-","_").upper())] = v
402 length = self.headers.getheader('content-length')
403 if length:
404 env['CONTENT_LENGTH'] = length
405 self.length = length
406 if query:
407 env['QUERY_STRING'] = query
408 env['REQUEST_METHOD'] = self.command
409
410
411 env['SERVER_SOFTWARE'] = self.version_string()
412 env['SERVER_NAME'] = self.request.getsockname()[0]
413 env['GATEWAY_INTERFACE'] = 'CGI/1.1'
414 env['SERVER_PROTOCOL'] = self.protocol_version
415 env['SERVER_PORT'] = str(self.server.server_address[1])
416 uqrest = urllib.unquote(rest)
417 env['PATH_INFO'] = uqrest
418
419 env['SCRIPT_NAME'] = scriptname
420
421
422 env['REMOTE_HOST'] = env['REMOTE_ADDR'] = self.client_address[0]
423 authorization = self.headers.getheader("authorization")
424 if authorization:
425 authorization = authorization.split()
426 if len(authorization) == 2:
427 import base64, binascii
428 env['AUTH_TYPE'] = authorization[0]
429 if authorization[0].lower() == "basic":
430 try:
431 authorization = base64.decodestring(authorization[1])
432 except binascii.Error:
433 pass
434 else:
435 authorization = authorization.split(':')
436 if len(authorization) == 2:
437 env['REMOTE_USER'] = authorization[0]
438
439 if self.headers.typeheader is None:
440 env['CONTENT_TYPE'] = self.headers.type
441 else:
442 env['CONTENT_TYPE'] = self.headers.typeheader
443 accept = []
444 for line in self.headers.getallmatchingheaders('accept'):
445 if line[:1] in "\t\n\r ":
446 accept.append(line.strip())
447 else:
448 accept = accept + line[7:].split(',')
449 env['HTTP_ACCEPT'] = ','.join(accept)
450 co = filter(None, self.headers.getheaders('cookie'))
451 if co:
452 env['HTTP_COOKIE'] = ', '.join(co)
453
454
455
456 for k in ('QUERY_STRING', 'REMOTE_HOST', 'CONTENT_LENGTH',
457 'HTTP_USER_AGENT', 'HTTP_COOKIE'):
458 env.setdefault(k, "")
459 return env
460
462 """Execute a CGI script."""
463 self.scriptname = self.scriptfile = self.query = self.length = None
464 env = self.get_environ()
465 query = self.query
466 scriptname = self.scriptname
467 scriptfile = self.scriptfile
468 length = self.length
469 decoded_query = query.replace('+', ' ')
470 self.wfile = HTTPWriteWrapper(self)
471 _setrequest(env)
472 if len(self.cgi_info) == 3:
473 try:
474 handler = self.cgi_info[2]
475 handler(self, env)
476 except CustomResponse, resp:
477 self.send_response(resp.status, resp.reason)
478 if resp.content_type_from_path:
479 resp.content_type = self.guess_type(resp.content_type_from_path)
480 webmethodwrite(self, resp.data, resp.content_type, resp.headers, options=resp.options)
481 except:
482 type, value, traceback = sys.exc_info()
483 if isinstance(value, HttpMethodError):
484 error = repr(value.args)
485 elif isinstance(value, Exception):
486 try:
487 error = repr(value.message)
488 except AttributeError:
489 error = repr(value)
490 else:
491 error = repr(value)
492 if self.virtual_host.show_errors:
493 weberror = error
494 else:
495 weberror = "Private error, please contact with your system administrator"
496 self.send_response(500, "Internal Server Error: %s"%error)
497 warn.warn("Internal Error", self.cgi_info, ". Exception: ", error)
498 webmethodwrite(self, 'Internal Server Error: %s'%error, 'text/plain')
499 _delrequest()
500 return
501 if self.have_fork and not self.server.usingssl:
502
503 args = [self.script]
504 if '=' not in decoded_query:
505 args.append(decoded_query)
506 nobody = nobody_uid()
507 self.wfile.flush()
508 pid = os.fork()
509 if pid != 0:
510
511 pid, sts = os.waitpid(pid, 0)
512
513 while select.select([self.rfile], [], [], 0)[0]:
514 if not self.rfile.read(1):
515 break
516 if sts:
517 self.log_error("CGI script exit status %#x", sts)
518 _delrequest()
519 return
520
521 try:
522 try:
523 os.setuid(nobody)
524 except os.error:
525 pass
526 os.dup2(self.rfile.fileno(), 0)
527 os.dup2(self.wfile.fileno(), 1)
528 os.execve(scriptfile, args, env)
529 except:
530 self.server.handle_error(self.request, self.client_address)
531 os._exit(127)
532
533 elif self.have_popen2 or self.have_popen3:
534
535 if self.have_popen3:
536 popenx = os.popen3
537 else:
538 popenx = os.popen2
539 cmdline = scriptfile
540 if self.is_python(scriptfile):
541 interp = sys.executable
542 if interp.lower().endswith("w.exe"):
543
544 interp = interp[:-5] + interp[-4:]
545 cmdline = "%s -u %s" % (interp, cmdline)
546 if '=' not in query and '"' not in query:
547 cmdline = '%s "%s"' % (cmdline, query)
548 self.log_message("command: %s", cmdline)
549 try:
550 nbytes = int(length)
551 except (TypeError, ValueError):
552 nbytes = 0
553 files = popenx(cmdline, 'b')
554 fi = files[0]
555 fo = files[1]
556 if self.have_popen3:
557 fe = files[2]
558 if self.command.lower() == "post" and nbytes > 0:
559 data = self.rfile.read(nbytes)
560 fi.write(data)
561
562 while select.select([self.rfile._sock], [], [], 0)[0]:
563 if not self.rfile._sock.recv(1):
564 break
565 fi.close()
566 shutil.copyfileobj(fo, self.wfile)
567 if self.have_popen3:
568 errors = fe.read()
569 fe.close()
570 if errors:
571 self.log_error('%s', errors)
572 sts = fo.close()
573 if sts:
574 self.log_error("CGI script exit status %#x", sts)
575 else:
576 self.log_message("CGI script exited OK")
577
578 else:
579
580 save_argv = sys.argv
581 save_stdin = sys.stdin
582 save_stdout = sys.stdout
583 save_stderr = sys.stderr
584 try:
585 save_cwd = os.getcwd()
586 try:
587 sys.argv = [scriptfile]
588 if '=' not in decoded_query:
589 sys.argv.append(decoded_query)
590 sys.stdout = self.wfile
591 sys.stdin = self.rfile
592 execfile(scriptfile, {"__name__": "__main__"})
593 finally:
594 sys.argv = save_argv
595 sys.stdin = save_stdin
596 sys.stdout = save_stdout
597 sys.stderr = save_stderr
598 os.chdir(save_cwd)
599 except SystemExit, sts:
600 self.log_error("CGI script exit status %s", str(sts))
601 else:
602 self.log_message("CGI script exited OK")
603 _delrequest()
604
607
609 - def __init__(self, name, document_root, cgi_handlers, cgi_directories, show_errors, rewrite_function=None):
610 self.name = name
611 self.document_root = document_root
612 self.cgi_handlers = cgi_handlers
613 self.cgi_directories = cgi_directories
614 self.show_errors = show_errors
615 self.rewrite_function = rewrite_function
616
618 - def __init__(self,
619 server_address,
620 HandlerClass = HTTPRequestHandler,
621 ssl_privatekey_file=None,
622 ssl_certificate_file=None,
623 ssl_method=None,
624 cgi_handlers=None,
625 cgi_directories=None,
626 document_root=".",
627 request_queue_size=1024,
628 show_errors=True,
629 virtual_hosts=None
630 ):
631
632 ThreadingTCPServer.request_queue_size = request_queue_size
633 try:
634 warn.debug("reading /etc/mime.types...")
635 f = file("/etc/mime.types")
636 for l in f.readlines():
637 if l.startswith("#"): continue
638 mimes = l.rstrip().split()
639 for ext in mimes[1:]:
640 HandlerClass.extensions_map[ext] = mimes[0]
641 except Exception,e:
642 warn.warn(e)
643 ThreadingTCPServer.__init__(self, server_address, HandlerClass)
644 if cgi_handlers is None:
645 cgi_handlers = {}
646 self.cgi_handlers = cgi_handlers
647 self.show_errors = show_errors
648 self.document_root = document_root
649
650
651 if cgi_directories is None: cgi_directories = []
652 self.cgi_directories = cgi_directories
653 self.usingssl = False
654 self.process_next_request = True
655 if virtual_hosts is None:
656 virtual_hosts = { }
657 self.virtual_hosts = virtual_hosts
658 try:
659 virtual_hosts['']
660 except KeyError:
661 virtual_hosts[''] = VirtualHost(server_address[0],document_root,cgi_handlers,cgi_directories,show_errors)
662
663 if ssl_privatekey_file:
664 if ssl_method is None: ssl_method = SSL.SSLv23_METHOD
665 self.usingssl = True
666 context = SSL.Context(ssl_method)
667
668
669 context.use_privatekey_file(ssl_privatekey_file)
670 context.use_certificate_file(ssl_certificate_file)
671 self.socket = SSL.Connection(context, socket.socket(self.address_family,
672 self.socket_type))
673 self.reuse_address()
674 self.server_bind()
675 httpbase.server_activate(self)
676 allow_reuse_address = True
677
679 try:
680 return self.virtual_hosts[virtual_host]
681 except KeyError:
682 return self.virtual_hosts['']
683
685 if not self.allow_reuse_address:
686 return
687 try:
688 self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
689 except socket.error, err:
690 warn.debug("SO_REUSEADDR is not supported: ",err)
691
693 """Called by constructor to bind the socket.
694
695 May be overridden.
696
697 """
698 self.reuse_address()
699 SocketServer.TCPServer.server_bind(self)
700 host, port = self.socket.getsockname()[:2]
701 self.server_name = socket.getfqdn(host)
702 self.server_port = port
703
704 self.server_address = self.socket.getsockname()
705
707 """ Set an urihandler
708 |uri| The URI to be handled
709 |handler| a function that handles the event
710 |virtual_host| The name of the virtual host, default to '' (default virtual host)
711 Prototype:
712 function(httpreq, environment)
713 httpreq is an HTTPRequestHandler Object
714 """
715 self.virtual_hosts[virtual_host].cgi_handlers[uri] = handler
716
718 """Deletes the uri-handler associated to |uri|
719 |virtual_host| The name of the virtual host, default to '' (default virtual host)
720 """
721 del self.virtual_hosts[virtual_host].cgi_handlers[uri]
722
724 print environment
725 print httpreq.cgi_info
726 print httpreq.path
727 print "*" * 30
728 wfile = httpreq.wfile
729 wfile.write("""Content-Type: text/plain
730
731 Hola mundo
732
733 Primer programa con script handlers
734 """)
735
736 - def regobj(self, uri, object, returntype="text/plain", authority=None):
737 def handler(httpreq, environment):
738 res = httpreq.cgi_info[1]
739 method = object
740 for x in res.split('?')[0].split('/'):
741 if x == '': continue
742 method = getattr(method,x)
743 webmethod(httpreq, environment, method, returntype)
744 self.register_urihandler(uri, handler)
745 return object
746
747 - def regcall(self, uri, returntype='text/plain'):
748 """ Register uri |uri| to be handled by a function
749 This method can be used safelly as decorator
750
751 Example
752 @httpobj.regcall('/selected-uri','text/html')
753 def square(x):
754 int(x)
755 return x*x
756 """
757 def decorator(function):
758 self.register_urihandler(uri, httpcall(function, returntype))
759 return function
760 return decorator
761
763 """ Handles requestgs while self.process_next_request is True """
764 while self.process_next_request:
765 self.handle_request()
766
767
769 """ Process one request, overloaded to support implement trusted
770 SSL under heavy load """
771 if self.usingssl:
772 TCPServer.process_request(self, request, client_address)
773 else:
774 ThreadingTCPServer.process_request(self, request, client_address)
775
777 self.process_next_request = False
778 self.socket.settimeout(0)
779 self.socket.shutdown(socket.SHUT_RDWR)
780