Code

More IE fixes: it does not like quotes around cookie values; in the
[roundup.git] / roundup-server
1 #!/usr/bin/python
2 #
3 # Copyright (c) 2001 Bizar Software Pty Ltd (http://www.bizarsoftware.com.au/)
4 # This module is free software, and you may redistribute it and/or modify
5 # under the same terms as Python, so long as this copyright message and
6 # disclaimer are retained in their original form.
7 #
8 # IN NO EVENT SHALL BIZAR SOFTWARE PTY LTD BE LIABLE TO ANY PARTY FOR
9 # DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING
10 # OUT OF THE USE OF THIS CODE, EVEN IF THE AUTHOR HAS BEEN ADVISED OF THE
11 # POSSIBILITY OF SUCH DAMAGE.
12 #
13 # BIZAR SOFTWARE PTY LTD SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING,
14 # BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
15 # FOR A PARTICULAR PURPOSE.  THE CODE PROVIDED HEREUNDER IS ON AN "AS IS"
16 # BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
17 # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
18
19 """ HTTP Server that serves roundup.
21 Based on CGIHTTPServer in the Python library.
23 $Id: roundup-server,v 1.18 2001-11-01 22:04:37 richard Exp $
25 """
26 import sys
27 if int(sys.version[0]) < 2:
28     print "Content-Type: text/plain\n"
29     print "Roundup requires Python 2.0 or newer."
30     sys.exit(0)
32 import os, urllib, StringIO, traceback, cgi, binascii, string, getopt, imp
33 import BaseHTTPServer
35 # Roundup modules of use here
36 from roundup import cgitb, cgi_client
37 import roundup.instance
39 #
40 ##  Configuration
41 #
43 # This indicates where the Roundup instance lives
44 ROUNDUP_INSTANCE_HOMES = {
45     'bar': '/tmp/bar',
46 }
48 ROUNDUP_USER = None
51 # Where to log debugging information to. Use an instance of DevNull if you
52 # don't want to log anywhere.
53 # TODO: actually use this stuff
54 #class DevNull:
55 #    def write(self, info):
56 #        pass
57 #LOG = open('/var/log/roundup.cgi.log', 'a')
58 #LOG = DevNull()
60 #
61 ##  end configuration
62 #
65 class RoundupRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
66     ROUNDUP_INSTANCE_HOMES = ROUNDUP_INSTANCE_HOMES
67     ROUNDUP_USER = ROUNDUP_USER
69     def run_cgi(self):
70         """ Execute the CGI command. Wrap an innner call in an error
71             handler so all errors can be caught.
72         """
73         save_stdin = sys.stdin
74         sys.stdin = self.rfile
75         try:
76             self.inner_run_cgi()
77         except cgi_client.NotFound:
78             self.send_error(404, self.path)
79         except cgi_client.Unauthorised:
80             self.wfile.write('Content-Type: text/html\n')
81             self.wfile.write('Status: 403\n\n')
82             self.wfile.write('You are not authorised to access this URL.')
83         except:
84             try:
85                 reload(cgitb)
86                 self.wfile.write("Content-Type: text/html\n\n")
87                 self.wfile.write(cgitb.breaker())
88                 self.wfile.write(cgitb.html())
89             except:
90                 self.wfile.write("Content-Type: text/html\n\n")
91                 self.wfile.write("<pre>")
92                 s = StringIO.StringIO()
93                 traceback.print_exc(None, s)
94                 self.wfile.write(cgi.escape(s.getvalue()))
95                 self.wfile.write("</pre>\n")
96         sys.stdin = save_stdin
98     do_GET = do_POST = do_HEAD = send_head = run_cgi
100     def index(self):
101         ''' Print up an index of the available instances
102         '''
103         w = self.wfile.write
104         w("Content-Type: text/html\n\n")
105         w('<html><head><title>Roundup instances index</title></head>\n')
106         w('<body><h1>Roundup instances index</h1><ol>\n')
107         for instance in self.ROUNDUP_INSTANCE_HOMES.keys():
108             w('<li><a href="%s/index">%s</a>\n'%(urllib.quote(instance),
109                 cgi.escape(instance)))
110         w('</ol></body></html>')
112     def inner_run_cgi(self):
113         ''' This is the inner part of the CGI handling
114         '''
116         rest = self.path
117         i = rest.rfind('?')
118         if i >= 0:
119             rest, query = rest[:i], rest[i+1:]
120         else:
121             query = ''
123         # figure the instance
124         if rest == '/':
125             return self.index()
126         l_path = string.split(rest, '/')
127         instance_name = urllib.unquote(l_path[1])
128         if self.ROUNDUP_INSTANCE_HOMES.has_key(instance_name):
129             instance_home = self.ROUNDUP_INSTANCE_HOMES[instance_name]
130             instance = roundup.instance.open(instance_home)
131         else:
132             raise cgi_client.NotFound
134         # figure out what the rest of the path is
135         if len(l_path) > 2:
136             rest = '/'.join(l_path[2:])
137         else:
138             rest = '/'
140         # Set up the CGI environment
141         env = {}
142         env['INSTANCE_NAME'] = instance_name
143         env['REQUEST_METHOD'] = self.command
144         env['PATH_INFO'] = urllib.unquote(rest)
145         if query:
146             env['QUERY_STRING'] = query
147         host = self.address_string()
148         if self.headers.typeheader is None:
149             env['CONTENT_TYPE'] = self.headers.type
150         else:
151             env['CONTENT_TYPE'] = self.headers.typeheader
152         length = self.headers.getheader('content-length')
153         if length:
154             env['CONTENT_LENGTH'] = length
155         co = filter(None, self.headers.getheaders('cookie'))
156         if co:
157             env['HTTP_COOKIE'] = ', '.join(co)
158         env['SCRIPT_NAME'] = ''
159         env['SERVER_NAME'] = self.server.server_name
160         env['SERVER_PORT'] = str(self.server.server_port)
162         decoded_query = query.replace('+', ' ')
164         # reload all modules
165         # TODO check for file timestamp changes and dependencies
166         #reload(date)
167         #reload(hyperdb)
168         #reload(roundupdb)
169         #reload(htmltemplate)
170         #reload(cgi_client)
171         #sys.path.insert(0, module_path)
172         #try:
173         #    reload(instance)
174         #finally:
175         #    del sys.path[0]
177         self.send_response(200, "Script output follows")
179         # do the roundup thang
180         client = instance.Client(instance, self, env)
181         client.main()
183 def usage(message=''):
184     if message: message = 'Error: %s\n'%message
185     print '''%sUsage:
186 roundup-server [-n hostname] [-p port] [name=instance home]*
188  -n: sets the host name
189  -p: sets the port to listen on
191  name=instance home
192    Sets the instance home(s) to use. The name is how the instance is
193    identified in the URL (it's the first part of the URL path). The
194    instance home is the directory that was identified when you did
195    "roundup-admin init". You may specify any number of these name=home
196    pairs on the command-line. For convenience, you may edit the
197    ROUNDUP_INSTANCE_HOMES variable in the roundup-server file instead.
198 '''%message
199     sys.exit(0)
201 def main():
202     hostname = ''
203     port = 8080
204     try:
205         # handle the command-line args
206         optlist, args = getopt.getopt(sys.argv[1:], 'n:p:u:')
207         user = ROUNDUP_USER
208         for (opt, arg) in optlist:
209             if opt == '-n': hostname = arg
210             elif opt == '-p': port = int(arg)
211             elif opt == '-u': user = arg
212             elif opt == '-h': usage()
214         if hasattr(os, 'getuid'):
215             # if root, setuid to the running user
216             if not os.getuid() and user is not None:
217                 try:
218                     import pwd
219                 except ImportError:
220                     raise ValueError, "Can't change users - no pwd module"
221                 try:
222                     uid = pwd.getpwnam(user)[2]
223                 except KeyError:
224                     raise ValueError, "User %s doesn't exist"%user
225                 os.setuid(uid)
226             elif os.getuid() and user is not None:
227                 print 'WARNING: ignoring "-u" argument, not root'
229             # People can remove this check if they're really determined
230             if not os.getuid() and user is None:
231                 raise ValueError, "Can't run as root!"
233         # handle instance specs
234         if args:
235             d = {}
236             for arg in args:
237                 try:
238                     name, home = string.split(arg, '=')
239                 except ValueError:
240                     raise ValueError, "Instances must be name=home"
241                 d[name] = home
242             RoundupRequestHandler.ROUNDUP_INSTANCE_HOMES = d
243     except:
244         type, value = sys.exc_info()[:2]
245         usage('%s: %s'%(type, value))
247     # we don't want the cgi module interpreting the command-line args ;)
248     sys.argv = sys.argv[:1]
249     address = (hostname, port)
250     httpd = BaseHTTPServer.HTTPServer(address, RoundupRequestHandler)
251     print 'Roundup server started on', address
252     httpd.serve_forever()
254 if __name__ == '__main__':
255     main()
258 # $Log: not supported by cvs2svn $
259 # Revision 1.17  2001/10/29 23:55:44  richard
260 # Fix to CGI top-level index (thanks Juergen Hermann)
262 # Revision 1.16  2001/10/27 00:12:21  richard
263 # Fixed roundup-server for windows, thanks Juergen Hermann.
265 # Revision 1.15  2001/10/12 02:23:26  richard
266 # Didn't clean up after myself :)
268 # Revision 1.14  2001/10/12 02:20:32  richard
269 # server now handles setuid'ing much better
271 # Revision 1.13  2001/10/05 02:23:24  richard
272 #  . roundup-admin create now prompts for property info if none is supplied
273 #    on the command-line.
274 #  . hyperdb Class getprops() method may now return only the mutable
275 #    properties.
276 #  . Login now uses cookies, which makes it a whole lot more flexible. We can
277 #    now support anonymous user access (read-only, unless there's an
278 #    "anonymous" user, in which case write access is permitted). Login
279 #    handling has been moved into cgi_client.Client.main()
280 #  . The "extended" schema is now the default in roundup init.
281 #  . The schemas have had their page headings modified to cope with the new
282 #    login handling. Existing installations should copy the interfaces.py
283 #    file from the roundup lib directory to their instance home.
284 #  . Incorrectly had a Bizar Software copyright on the cgitb.py module from
285 #    Ping - has been removed.
286 #  . Fixed a whole bunch of places in the CGI interface where we should have
287 #    been returning Not Found instead of throwing an exception.
288 #  . Fixed a deviation from the spec: trying to modify the 'id' property of
289 #    an item now throws an exception.
291 # Revision 1.12  2001/09/29 13:27:00  richard
292 # CGI interfaces now spit up a top-level index of all the instances they can
293 # serve.
295 # Revision 1.11  2001/08/07 00:24:42  richard
296 # stupid typo
298 # Revision 1.10  2001/08/07 00:15:51  richard
299 # Added the copyright/license notice to (nearly) all files at request of
300 # Bizar Software.
302 # Revision 1.9  2001/08/05 07:44:36  richard
303 # Instances are now opened by a special function that generates a unique
304 # module name for the instances on import time.
306 # Revision 1.8  2001/08/03 01:28:33  richard
307 # Used the much nicer load_package, pointed out by Steve Majewski.
309 # Revision 1.7  2001/08/03 00:59:34  richard
310 # Instance import now imports the instance using imp.load_module so that
311 # we can have instance homes of "roundup" or other existing python package
312 # names.
314 # Revision 1.6  2001/07/29 07:01:39  richard
315 # Added vim command to all source so that we don't get no steenkin' tabs :)
317 # Revision 1.5  2001/07/24 01:07:59  richard
318 # Added command-line arg handling to roundup-server so it's more useful
319 # out-of-the-box.
321 # Revision 1.4  2001/07/23 10:31:45  richard
322 # disabled the reloading until it can be done properly
324 # Revision 1.3  2001/07/23 08:53:44  richard
325 # Fixed the ROUNDUPS decl in roundup-server
326 # Move the installation notes to INSTALL
328 # Revision 1.2  2001/07/23 04:05:05  anthonybaxter
329 # actually quit if python version wrong
331 # Revision 1.1  2001/07/23 03:46:48  richard
332 # moving the bin files to facilitate out-of-the-boxness
334 # Revision 1.1  2001/07/22 11:15:45  richard
335 # More Grande Splite stuff
338 # vim: set filetype=python ts=4 sw=4 et si