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.19 2001-11-12 22:51:04 jhermann 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\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 try:
207 optlist, args = getopt.getopt(sys.argv[1:], 'n:p:u:')
208 except getopt.GetoptError, e:
209 usage(str(e))
211 user = ROUNDUP_USER
212 for (opt, arg) in optlist:
213 if opt == '-n': hostname = arg
214 elif opt == '-p': port = int(arg)
215 elif opt == '-u': user = arg
216 elif opt == '-h': usage()
218 if hasattr(os, 'getuid'):
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 SystemExit:
248 raise
249 except:
250 type, value = sys.exc_info()[:2]
251 usage('%s: %s'%(type, value))
253 # we don't want the cgi module interpreting the command-line args ;)
254 sys.argv = sys.argv[:1]
255 address = (hostname, port)
256 httpd = BaseHTTPServer.HTTPServer(address, RoundupRequestHandler)
257 print 'Roundup server started on', address
258 httpd.serve_forever()
260 if __name__ == '__main__':
261 main()
263 #
264 # $Log: not supported by cvs2svn $
265 # Revision 1.18 2001/11/01 22:04:37 richard
266 # Started work on supporting a pop3-fetching server
267 # Fixed bugs:
268 # . bug #477104 ] HTML tag error in roundup-server
269 # . bug #477107 ] HTTP header problem
270 #
271 # Revision 1.17 2001/10/29 23:55:44 richard
272 # Fix to CGI top-level index (thanks Juergen Hermann)
273 #
274 # Revision 1.16 2001/10/27 00:12:21 richard
275 # Fixed roundup-server for windows, thanks Juergen Hermann.
276 #
277 # Revision 1.15 2001/10/12 02:23:26 richard
278 # Didn't clean up after myself :)
279 #
280 # Revision 1.14 2001/10/12 02:20:32 richard
281 # server now handles setuid'ing much better
282 #
283 # Revision 1.13 2001/10/05 02:23:24 richard
284 # . roundup-admin create now prompts for property info if none is supplied
285 # on the command-line.
286 # . hyperdb Class getprops() method may now return only the mutable
287 # properties.
288 # . Login now uses cookies, which makes it a whole lot more flexible. We can
289 # now support anonymous user access (read-only, unless there's an
290 # "anonymous" user, in which case write access is permitted). Login
291 # handling has been moved into cgi_client.Client.main()
292 # . The "extended" schema is now the default in roundup init.
293 # . The schemas have had their page headings modified to cope with the new
294 # login handling. Existing installations should copy the interfaces.py
295 # file from the roundup lib directory to their instance home.
296 # . Incorrectly had a Bizar Software copyright on the cgitb.py module from
297 # Ping - has been removed.
298 # . Fixed a whole bunch of places in the CGI interface where we should have
299 # been returning Not Found instead of throwing an exception.
300 # . Fixed a deviation from the spec: trying to modify the 'id' property of
301 # an item now throws an exception.
302 #
303 # Revision 1.12 2001/09/29 13:27:00 richard
304 # CGI interfaces now spit up a top-level index of all the instances they can
305 # serve.
306 #
307 # Revision 1.11 2001/08/07 00:24:42 richard
308 # stupid typo
309 #
310 # Revision 1.10 2001/08/07 00:15:51 richard
311 # Added the copyright/license notice to (nearly) all files at request of
312 # Bizar Software.
313 #
314 # Revision 1.9 2001/08/05 07:44:36 richard
315 # Instances are now opened by a special function that generates a unique
316 # module name for the instances on import time.
317 #
318 # Revision 1.8 2001/08/03 01:28:33 richard
319 # Used the much nicer load_package, pointed out by Steve Majewski.
320 #
321 # Revision 1.7 2001/08/03 00:59:34 richard
322 # Instance import now imports the instance using imp.load_module so that
323 # we can have instance homes of "roundup" or other existing python package
324 # names.
325 #
326 # Revision 1.6 2001/07/29 07:01:39 richard
327 # Added vim command to all source so that we don't get no steenkin' tabs :)
328 #
329 # Revision 1.5 2001/07/24 01:07:59 richard
330 # Added command-line arg handling to roundup-server so it's more useful
331 # out-of-the-box.
332 #
333 # Revision 1.4 2001/07/23 10:31:45 richard
334 # disabled the reloading until it can be done properly
335 #
336 # Revision 1.3 2001/07/23 08:53:44 richard
337 # Fixed the ROUNDUPS decl in roundup-server
338 # Move the installation notes to INSTALL
339 #
340 # Revision 1.2 2001/07/23 04:05:05 anthonybaxter
341 # actually quit if python version wrong
342 #
343 # Revision 1.1 2001/07/23 03:46:48 richard
344 # moving the bin files to facilitate out-of-the-boxness
345 #
346 # Revision 1.1 2001/07/22 11:15:45 richard
347 # More Grande Splite stuff
348 #
349 #
350 # vim: set filetype=python ts=4 sw=4 et si