sync with linuxport @ 18013
[xbmc:xbmc-antiquated.git] / system / python / spyce / spyceWWW.py
1 ##################################################
2 # SPYCE - Python-based HTML Scripting
3 # Copyright (c) 2002 Rimon Barr.
4 #
5 # Refer to spyce.py
6 # CVS: $Id$
7 ##################################################
8
9 import sys, os, string, socket, BaseHTTPServer, SocketServer, cgi, stat, time
10 import spyce, spyceConfig, spyceException, spyceCmd, spyceUtil
11
12 __doc__ = '''Self-standing Spyce web server.'''
13
14 LOG = 1
15
16 def formatBytes(bytes):
17   bytes = float(bytes)
18   if bytes<=9999: return "%6.0f" % bytes
19   bytes = bytes / float(1024)
20   if bytes<=999: return "%5.1fK" % bytes
21   bytes = bytes / float(1024)
22   return "%5.1fM" % bytes
23
24 ##################################################
25 # Request / response handlers
26 #
27
28 class spyceHTTPRequest(spyce.spyceRequest):
29   'HTTP Spyce request object. (see spyce.spyceRequest)'
30   def __init__(self, httpdHandler, documentRoot):
31     spyce.spyceRequest.__init__(self)
32     self._in = httpdHandler.rfile
33     self._headers = httpdHandler.headers
34     self._httpdHandler = httpdHandler
35     self._documentRoot = documentRoot
36     self._env = None
37   def env(self, name=None):
38     if not self._env:
39       self._env = {
40         'REMOTE_ADDR': self._httpdHandler.client_address[0],
41         'REMOTE_PORT': self._httpdHandler.client_address[1],
42         'GATEWAY_INTERFACE': "CGI/1.1",
43         'REQUEST_METHOD': self._httpdHandler.command,
44         'REQUEST_URI': self._httpdHandler.path,
45         'PATH_INFO': self._httpdHandler.pathinfo,
46         'SERVER_SOFTWARE': 'spyce/%s' % spyce.__version__,
47         'SERVER_PROTOCOL': self._httpdHandler.request_version,
48         # 'SERVER_ADDR' ... '127.0.0.1'
49         # 'SERVER_PORT' ... '80'
50         # 'SERVER_NAME' ... 'mymachine.mydomain.com'
51         # 'SERVER_SIGNATURE' ... ' Apache/1.3.22 Server at mymachine.mydomain.com Port 80'
52         # 'SERVER_ADMIN'] ... 'rimon@acm.org'
53         'DOCUMENT_ROOT': self._documentRoot,
54         'QUERY_STRING': 
55           string.join(string.split(self._httpdHandler.path, '?')[1:]) or '',
56         'CONTENT_LENGTH': self.getHeader('Content-Length'),
57         'CONTENT_TYPE': self.getHeader('Content-type'),
58         'HTTP_USER_AGENT': self.getHeader('User-Agent'),
59         'HTTP_ACCEPT': self.getHeader('Accept'),
60         'HTTP_ACCEPT_ENCODING': self.getHeader('Accept-Encoding'),
61         'HTTP_ACCEPT_LANGUAGE': self.getHeader('Accept-Language'),
62         'HTTP_ACCEPT_CHARSET': self.getHeader('Accept-Charset'),
63         'HTTP_COOKIE': self.getHeader('Cookie'),
64         'HTTP_REFERER': self.getHeader('Referer'),
65         'HTTP_HOST': self.getHeader('Host'),
66         'HTTP_CONNECTION': self.getHeader('Connection'),
67         'HTTP_KEEP_ALIVE': self.getHeader('Keep-alive'),
68         # From ASP
69         # AUTH_TYPE, 
70         # APPL_PHYSICAL_PATH, 
71         # REMOTE_HOST,
72         # SERVER_PROTOCOL, 
73         # SERVER_SOFWARE
74       }
75     return spyceUtil.extractValue(self._env, name)
76   def getHeader(self, type=None):
77     if type: type=string.lower(type)
78     return spyceUtil.extractValue(self._headers.dict, type)
79   def getServerID(self):
80     return os.getpid()
81
82 class spyceHTTPResponse(spyceCmd.spyceCmdlineResponse):
83   'HTTP Spyce response object. (see spyce.spyceResponse)'
84   def __init__(self, httpdHandler):
85     self._httpheader = httpdHandler.request_version!='HTTP/0.9'
86     spyceCmd.spyceCmdlineResponse.__init__(self, spyceUtil.NoCloseOut(httpdHandler.wfile), sys.stdout, self._httpheader)
87     self._httpdHandler = httpdHandler
88     # incidentally, this a rather expensive operation!
89     if LOG:
90       httpdHandler.log_request()
91   def sendHeaders(self):
92     if self._httpheader and not self.headersSent:
93       resultText = spyceUtil.extractValue(self.RETURN_CODE, self.returncode)
94       self.origout.write('%s %s %s\n' % (self._httpdHandler.request_version, self.returncode, resultText))
95       spyceCmd.spyceCmdlineResponse.sendHeaders(self)
96   def close(self):
97     spyceCmd.spyceCmdlineResponse.close(self)
98     self._httpdHandler.request.close()
99
100
101 ##################################################
102 # Spyce web server
103 #
104
105 class myHTTPhandler(BaseHTTPServer.BaseHTTPRequestHandler):
106   def do_GET(self):
107     try:
108       # parse pathinfo
109       pathinfo = os.path.normpath(string.split(self.path, '?')[0])
110       while pathinfo and (pathinfo[0]==os.sep or pathinfo[0:2]==os.pardir):
111         if pathinfo[0:len(os.sep)]==os.sep: pathinfo=pathinfo[len(os.sep):]
112         if pathinfo[0:len(os.pardir)]==os.pardir: pathinfo=pathinfo[len(os.pardir):]
113       self.pathinfo = "/"+pathinfo
114       # convert to path
115       path = os.path.join(self.server.documentRoot, pathinfo)
116       # directory listing
117       if os.path.isdir(path):
118         return self.handler_dir(path)
119       # search up path (path_info)
120       while len(path)>len(self.server.documentRoot) and not os.path.exists(path):
121         path, _ = os.path.split(path)
122       # for files (or links), find appropriate handler
123       if os.path.isfile(path) or os.path.islink(path):
124         _, ext = os.path.splitext(path)
125         if ext: ext = ext[1:]
126         try:
127           handler = self.server.handler[ext]
128         except:
129           handler = self.server.handler[None]
130         # process request
131         return handler(self, path)
132       # invalid path
133       self.send_error(404, "Invalid path")
134       return None
135     except IOError: 
136       self.send_error(404, "Unexpected IOError")
137       return None
138   do_POST=do_GET
139   def handler_spyce(self, path):
140     # process spyce
141     request = spyceHTTPRequest(self, self.server.documentRoot)
142     response = spyceHTTPResponse(self)
143     result = spyce.spyceFileHandler(request, response, path)
144     response.close()
145   def handler_dump(self, path):
146     # process content to dump (with correct mime type)
147     f = None
148     try:
149       try:
150         f = open(path, 'rb')
151       except IOError:
152         self.send_error(404, "No permission to open file")
153         return None
154       try:
155         _, ext = os.path.splitext(path)
156         if ext: ext=ext[1:]
157         mimetype = self.server.mimeTable[ext]
158       except:
159         mimetype = "application/octet-stream"
160       self.send_response(200)
161       self.send_header("Content-type", mimetype)
162       self.end_headers()
163       self.wfile.write(f.read())
164       self.request.close()
165     finally:
166       try:
167         if f: f.close()
168       except: pass
169   def handler_dir(self, path):
170     # process directory
171     if(self.path[-1:]!='/'):
172       self.send_response(301)
173       self.send_header('Location', self.path+'/')
174       self.end_headers()
175       return
176     try:
177       list = os.listdir(path)
178     except os.error:
179       self.send_error(404, "Path does not exist")
180       return None
181     list.sort(lambda a, b: cmp(a.lower(), b.lower()))
182     def info(name, path=path):
183       fullname = os.path.join(path, name)
184       displayname = linkname = name = cgi.escape(name)
185       # Append / for directories or @ for symbolic links
186       if os.path.isdir(fullname):
187         displayname = name + "/"
188         linkname = name + "/"
189       elif os.path.islink(fullname):
190         displayname = name + "@"
191       statinfo = os.stat(fullname)
192       mtime = statinfo[stat.ST_MTIME]
193       size = statinfo[stat.ST_SIZE]
194       return linkname, displayname, mtime, size
195     list = map(info, list)
196
197     NAME_WIDTH = 30
198     output = '''
199 <html><head>
200   <title>Index of %(title)s</title>
201 </head>
202 <body>
203 <h1>Index of /%(title)s</h1>
204 <pre> Name%(filler)s  Date%(filler_date)s  Size<hr/>''' % {
205     'title' : self.pathinfo,
206     'filler': ' '*(NAME_WIDTH-len('Name')),
207     'filler_date': ' '*(len(time.asctime(time.localtime(0)))-len('Date')),
208     }
209
210     if list:
211       for link, display, mtime, size in list:
212         output = output + ' <a href="%(link)s">%(display)s</a>%(filler)s  %(mtime)s  %(size)s\n' % {
213           'link': link,
214           'display': display[:NAME_WIDTH],
215           'link': link,
216           'filler': ' '*(NAME_WIDTH-len(display)),
217           'mtime': time.asctime(time.localtime(mtime)),
218           'size': formatBytes(size),
219         }
220     else:
221       output = output + 'No files\n'
222
223     output = output[:-1] + '''<hr/></pre>
224 <address>Spyce-WWW/%(version)s server</address>
225 </body></html>
226 ''' % {
227     'version' : spyce.__version__,
228     }
229     self.send_response(200)
230     self.send_header("Content-type", "text/html")
231     self.end_headers()
232     self.wfile.write(output)
233
234 def buildMimeTable(files):
235   mimetable = {}
236   for file in files:
237     try:
238       f = None
239       try:
240         f = open(file, 'r')
241         print "#   MIME file: "+file
242         line = f.readline()
243         while line:
244           if line[0]=='#': 
245             line = f.readline(); continue
246           line = string.strip(line)
247           if not line:
248             line = f.readline(); continue
249           line = string.replace(line, '\t', ' ')
250           items = filter(None, map(string.strip, string.split(line, ' ')))
251           mimetype, extensions = items[0], items[1:]
252           for ext in extensions:
253             mimetable[ext] = mimetype
254           line = f.readline()
255       except IOError: pass
256     finally:
257       try:
258         if f: f.close()
259       except: pass
260   return mimetable
261
262 def buildHandlerTable(handler, server):
263   for ext in handler.keys():
264     handler[ext] = eval('server.handler_'+handler[ext])
265   return handler
266
267 def spyceHTTPserver(port, root, config_file=None, daemon=None):
268   os.environ[spyce.SPYCE_ENTRY] = 'www'
269   spyceCmd.showVersion()
270   print '# Starting web server'
271   # test for threading support, if needed
272   try:
273     server = spyce.getServer(
274       config_file=config_file, 
275       overide_www_port=port, 
276       overide_www_root=root)
277   except (spyceException.spyceForbidden, spyceException.spyceNotFound), e:
278     print e
279     return
280   if server.concurrency==spyceConfig.SPYCE_CONCURRENCY_THREAD:
281     spyceUtil.ThreadedWriter()  # will raise exception if 'import thread' fails
282   # determine type of server concurrency
283   serverSuperClass = {
284     spyceConfig.SPYCE_CONCURRENCY_SINGLE: SocketServer.TCPServer,
285     spyceConfig.SPYCE_CONCURRENCY_FORK:   SocketServer.ForkingTCPServer,
286     spyceConfig.SPYCE_CONCURRENCY_THREAD: SocketServer.ThreadingTCPServer,
287   } [server.concurrency]
288   class sharedSocketServer(serverSuperClass):
289     def server_bind(self):
290       self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
291       SocketServer.TCPServer.server_bind(self)
292   try:
293     # initialize server
294     try:
295       httpd = sharedSocketServer(('',server.config.getSpyceWWWPort()), myHTTPhandler)
296       print '# Server Port: %d' % server.config.getSpyceWWWPort()
297       httpd.documentRoot = os.path.abspath(server.config.getSpyceWWWRoot())
298       print '# Server Root: '+httpd.documentRoot
299       httpd.mimeTable = buildMimeTable(server.config.getSpyceWWWMime())
300       httpd.handler = buildHandlerTable(server.config.getSpyceWWWHandler(), myHTTPhandler)
301     except:
302       print 'Unable to start server on port %s' % server.config.getSpyceWWWPort()
303       return -1
304     # daemonize
305     if daemon:
306       print '# Daemonizing process.'
307       try:
308         spyceCmd.daemonize(pidfile=daemon)
309       except SystemExit: # expected
310         return 0
311       global LOG
312       LOG = 0
313     # process requests
314     print '# Ready.'
315     while 1:
316       try:
317         httpd.handle_request()
318       except KeyboardInterrupt: raise
319       except:
320         print 'Error: %s' % spyceUtil.exceptionString()
321   except KeyboardInterrupt:
322     print 'Break!'
323   return 0
324