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.15 2001-10-12 02:23:26 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
34 import SimpleHTTPServer
36 # Roundup modules of use here
37 from roundup import cgitb, cgi_client
38 import roundup.instance
40 #
41 ## Configuration
42 #
44 # This indicates where the Roundup instance lives
45 ROUNDUP_INSTANCE_HOMES = {
46 'bar': '/tmp/bar',
47 }
49 ROUNDUP_USER = None
52 # Where to log debugging information to. Use an instance of DevNull if you
53 # don't want to log anywhere.
54 # TODO: actually use this stuff
55 #class DevNull:
56 # def write(self, info):
57 # pass
58 #LOG = open('/var/log/roundup.cgi.log', 'a')
59 #LOG = DevNull()
61 #
62 ## end configuration
63 #
66 class RoundupRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
67 ROUNDUP_INSTANCE_HOMES = ROUNDUP_INSTANCE_HOMES
68 ROUNDUP_USER = ROUNDUP_USER
69 def send_head(self):
70 """Version of send_head that support CGI scripts"""
71 # TODO: actually do the HEAD ...
72 return self.run_cgi()
74 def run_cgi(self):
75 """ Execute the CGI command. Wrap an innner call in an error
76 handler so all errors can be caught.
77 """
78 save_stdin = sys.stdin
79 sys.stdin = self.rfile
80 try:
81 self.inner_run_cgi()
82 except cgi_client.NotFound:
83 self.send_error(404, self.path)
84 except cgi_client.Unauthorised:
85 self.wfile.write('Content-Type: text/html\n')
86 self.wfile.write('Status: 403\n\n')
87 self.wfile.write('You are not authorised to access this URL.')
88 except:
89 try:
90 reload(cgitb)
91 self.wfile.write("Content-Type: text/html\n\n")
92 self.wfile.write(cgitb.breaker())
93 self.wfile.write(cgitb.html())
94 except:
95 self.wfile.write("Content-Type: text/html\n\n")
96 self.wfile.write("<pre>")
97 s = StringIO.StringIO()
98 traceback.print_exc(None, s)
99 self.wfile.write(cgi.escape(s.getvalue()))
100 self.wfile.write("</pre>\n")
101 sys.stdin = save_stdin
103 def index(self):
104 ''' Print up an index of the available instances
105 '''
106 w = self.wfile.write
107 w("Content-Type: text/html\n\n")
108 w('<html><head><title>Roundup instances index</title><head>\n')
109 w('<body><h1>Roundup instances index</h1><ol>\n')
110 for instance in self.ROUNDUP_INSTANCE_HOMES.keys():
111 w('<li><a href="%s/index">%s</a>\n'%(urllib.quote(instance),
112 instance))
113 w('</ol></body></html>')
115 def inner_run_cgi(self):
116 ''' This is the inner part of the CGI handling
117 '''
119 rest = self.path
120 i = rest.rfind('?')
121 if i >= 0:
122 rest, query = rest[:i], rest[i+1:]
123 else:
124 query = ''
126 # figure the instance
127 if rest == '/':
128 return self.index()
129 l_path = string.split(rest, '/')
130 instance_name = urllib.unquote(l_path[1])
131 if self.ROUNDUP_INSTANCE_HOMES.has_key(instance_name):
132 instance_home = self.ROUNDUP_INSTANCE_HOMES[instance_name]
133 instance = roundup.instance.open(instance_home)
134 else:
135 raise cgi_client.NotFound
137 # figure out what the rest of the path is
138 if len(l_path) > 2:
139 rest = '/'.join(l_path[2:])
140 else:
141 rest = '/'
143 # Set up the CGI environment
144 env = {}
145 env['INSTANCE_NAME'] = instance_name
146 env['REQUEST_METHOD'] = self.command
147 env['PATH_INFO'] = urllib.unquote(rest)
148 if query:
149 env['QUERY_STRING'] = query
150 host = self.address_string()
151 if self.headers.typeheader is None:
152 env['CONTENT_TYPE'] = self.headers.type
153 else:
154 env['CONTENT_TYPE'] = self.headers.typeheader
155 length = self.headers.getheader('content-length')
156 if length:
157 env['CONTENT_LENGTH'] = length
158 co = filter(None, self.headers.getheaders('cookie'))
159 if co:
160 env['HTTP_COOKIE'] = ', '.join(co)
161 env['SCRIPT_NAME'] = ''
162 env['SERVER_NAME'] = self.server.server_name
163 env['SERVER_PORT'] = str(self.server.server_port)
165 decoded_query = query.replace('+', ' ')
167 # reload all modules
168 # TODO check for file timestamp changes and dependencies
169 #reload(date)
170 #reload(hyperdb)
171 #reload(roundupdb)
172 #reload(htmltemplate)
173 #reload(cgi_client)
174 #sys.path.insert(0, module_path)
175 #try:
176 # reload(instance)
177 #finally:
178 # del sys.path[0]
180 self.send_response(200, "Script output follows")
182 # do the roundup thang
183 client = instance.Client(instance, self.wfile, env)
184 client.main()
186 do_POST = run_cgi
188 def usage(message=''):
189 if message: message = 'Error: %s\n'%message
190 print '''%sUsage:
191 roundup-server [-n hostname] [-p port] [name=instance home]*
193 -n: sets the host name
194 -p: sets the port to listen on
196 name=instance home
197 Sets the instance home(s) to use. The name is how the instance is
198 identified in the URL (it's the first part of the URL path). The
199 instance home is the directory that was identified when you did
200 "roundup-admin init". You may specify any number of these name=home
201 pairs on the command-line. For convenience, you may edit the
202 ROUNDUP_INSTANCE_HOMES variable in the roundup-server file instead.
203 '''%message
204 sys.exit(0)
206 def main():
207 hostname = ''
208 port = 8080
209 try:
210 # handle the command-line args
211 optlist, args = getopt.getopt(sys.argv[1:], 'n:p:u:')
212 user = ROUNDUP_USER
213 for (opt, arg) in optlist:
214 if opt == '-n': hostname = arg
215 elif opt == '-p': port = int(arg)
216 elif opt == '-u': user = arg
217 elif opt == '-h': usage()
219 # if root, setuid to the running user
220 if not os.getuid() and user is not None:
221 try:
222 import pwd
223 except ImportError:
224 raise ValueError, "Can't change users - no pwd module"
225 try:
226 uid = pwd.getpwnam(user)[2]
227 except KeyError:
228 raise ValueError, "User %s doesn't exist"%user
229 os.setuid(uid)
230 elif os.getuid() and user is not None:
231 print 'WARNING: ignoring "-u" argument, not root'
233 # People can remove this check if they're really determined
234 if not os.getuid() and user is None:
235 raise ValueError, "Can't run as root!"
237 # handle instance specs
238 if args:
239 d = {}
240 for arg in args:
241 try:
242 name, home = string.split(arg, '=')
243 except ValueError:
244 raise ValueError, "Instances must be name=home"
245 d[name] = home
246 RoundupRequestHandler.ROUNDUP_INSTANCE_HOMES = d
247 except:
248 type, value = sys.exc_info()[:2]
249 usage('%s: %s'%(type, value))
251 # we don't want the cgi module interpreting the command-line args ;)
252 sys.argv = sys.argv[:1]
253 address = (hostname, port)
254 httpd = BaseHTTPServer.HTTPServer(address, RoundupRequestHandler)
255 print 'Roundup server started on', address
256 httpd.serve_forever()
258 if __name__ == '__main__':
259 main()
261 #
262 # $Log: not supported by cvs2svn $
263 # Revision 1.14 2001/10/12 02:20:32 richard
264 # server now handles setuid'ing much better
265 #
266 # Revision 1.13 2001/10/05 02:23:24 richard
267 # . roundup-admin create now prompts for property info if none is supplied
268 # on the command-line.
269 # . hyperdb Class getprops() method may now return only the mutable
270 # properties.
271 # . Login now uses cookies, which makes it a whole lot more flexible. We can
272 # now support anonymous user access (read-only, unless there's an
273 # "anonymous" user, in which case write access is permitted). Login
274 # handling has been moved into cgi_client.Client.main()
275 # . The "extended" schema is now the default in roundup init.
276 # . The schemas have had their page headings modified to cope with the new
277 # login handling. Existing installations should copy the interfaces.py
278 # file from the roundup lib directory to their instance home.
279 # . Incorrectly had a Bizar Software copyright on the cgitb.py module from
280 # Ping - has been removed.
281 # . Fixed a whole bunch of places in the CGI interface where we should have
282 # been returning Not Found instead of throwing an exception.
283 # . Fixed a deviation from the spec: trying to modify the 'id' property of
284 # an item now throws an exception.
285 #
286 # Revision 1.12 2001/09/29 13:27:00 richard
287 # CGI interfaces now spit up a top-level index of all the instances they can
288 # serve.
289 #
290 # Revision 1.11 2001/08/07 00:24:42 richard
291 # stupid typo
292 #
293 # Revision 1.10 2001/08/07 00:15:51 richard
294 # Added the copyright/license notice to (nearly) all files at request of
295 # Bizar Software.
296 #
297 # Revision 1.9 2001/08/05 07:44:36 richard
298 # Instances are now opened by a special function that generates a unique
299 # module name for the instances on import time.
300 #
301 # Revision 1.8 2001/08/03 01:28:33 richard
302 # Used the much nicer load_package, pointed out by Steve Majewski.
303 #
304 # Revision 1.7 2001/08/03 00:59:34 richard
305 # Instance import now imports the instance using imp.load_module so that
306 # we can have instance homes of "roundup" or other existing python package
307 # names.
308 #
309 # Revision 1.6 2001/07/29 07:01:39 richard
310 # Added vim command to all source so that we don't get no steenkin' tabs :)
311 #
312 # Revision 1.5 2001/07/24 01:07:59 richard
313 # Added command-line arg handling to roundup-server so it's more useful
314 # out-of-the-box.
315 #
316 # Revision 1.4 2001/07/23 10:31:45 richard
317 # disabled the reloading until it can be done properly
318 #
319 # Revision 1.3 2001/07/23 08:53:44 richard
320 # Fixed the ROUNDUPS decl in roundup-server
321 # Move the installation notes to INSTALL
322 #
323 # Revision 1.2 2001/07/23 04:05:05 anthonybaxter
324 # actually quit if python version wrong
325 #
326 # Revision 1.1 2001/07/23 03:46:48 richard
327 # moving the bin files to facilitate out-of-the-boxness
328 #
329 # Revision 1.1 2001/07/22 11:15:45 richard
330 # More Grande Splite stuff
331 #
332 #
333 # vim: set filetype=python ts=4 sw=4 et si