Mercurial > p > roundup > code
annotate server.py @ 4:b10f8834cce4
modified to use localconfig.py (if it exists)
and to make the various options (e.g. paths) based on ROUNDUP_HOME &c.
| author | Anthony Baxter <anthonybaxter@users.sourceforge.net> |
|---|---|
| date | Thu, 19 Jul 2001 05:46:47 +0000 |
| parents | 5e92642cd1f8 |
| children | 72a0ba086b3e |
| rev | line source |
|---|---|
|
0
5e92642cd1f8
Initial import of code
Richard Jones <richard@users.sourceforge.net>
parents:
diff
changeset
|
1 #!/usr/bin/python |
|
5e92642cd1f8
Initial import of code
Richard Jones <richard@users.sourceforge.net>
parents:
diff
changeset
|
2 """ HTTP Server that serves roundup. |
|
5e92642cd1f8
Initial import of code
Richard Jones <richard@users.sourceforge.net>
parents:
diff
changeset
|
3 |
|
5e92642cd1f8
Initial import of code
Richard Jones <richard@users.sourceforge.net>
parents:
diff
changeset
|
4 Stolen from CGIHTTPServer |
|
5e92642cd1f8
Initial import of code
Richard Jones <richard@users.sourceforge.net>
parents:
diff
changeset
|
5 |
|
5e92642cd1f8
Initial import of code
Richard Jones <richard@users.sourceforge.net>
parents:
diff
changeset
|
6 """ |
|
5e92642cd1f8
Initial import of code
Richard Jones <richard@users.sourceforge.net>
parents:
diff
changeset
|
7 import sys |
|
5e92642cd1f8
Initial import of code
Richard Jones <richard@users.sourceforge.net>
parents:
diff
changeset
|
8 if int(sys.version[0]) < 2: |
|
5e92642cd1f8
Initial import of code
Richard Jones <richard@users.sourceforge.net>
parents:
diff
changeset
|
9 print "Content-Type: text/plain\n" |
|
5e92642cd1f8
Initial import of code
Richard Jones <richard@users.sourceforge.net>
parents:
diff
changeset
|
10 print "Roundup requires Python 2.0 or newer." |
|
5e92642cd1f8
Initial import of code
Richard Jones <richard@users.sourceforge.net>
parents:
diff
changeset
|
11 |
|
5e92642cd1f8
Initial import of code
Richard Jones <richard@users.sourceforge.net>
parents:
diff
changeset
|
12 __version__ = "0.1" |
|
5e92642cd1f8
Initial import of code
Richard Jones <richard@users.sourceforge.net>
parents:
diff
changeset
|
13 |
|
5e92642cd1f8
Initial import of code
Richard Jones <richard@users.sourceforge.net>
parents:
diff
changeset
|
14 __all__ = ["CGIHTTPRequestHandler"] |
|
5e92642cd1f8
Initial import of code
Richard Jones <richard@users.sourceforge.net>
parents:
diff
changeset
|
15 |
|
5e92642cd1f8
Initial import of code
Richard Jones <richard@users.sourceforge.net>
parents:
diff
changeset
|
16 import os, urllib, StringIO, traceback, cgi, binascii |
|
5e92642cd1f8
Initial import of code
Richard Jones <richard@users.sourceforge.net>
parents:
diff
changeset
|
17 import BaseHTTPServer |
|
5e92642cd1f8
Initial import of code
Richard Jones <richard@users.sourceforge.net>
parents:
diff
changeset
|
18 import SimpleHTTPServer |
|
5e92642cd1f8
Initial import of code
Richard Jones <richard@users.sourceforge.net>
parents:
diff
changeset
|
19 import date, hyperdb, template, roundupdb, roundup_cgi |
|
5e92642cd1f8
Initial import of code
Richard Jones <richard@users.sourceforge.net>
parents:
diff
changeset
|
20 import cgitb |
|
5e92642cd1f8
Initial import of code
Richard Jones <richard@users.sourceforge.net>
parents:
diff
changeset
|
21 |
|
5e92642cd1f8
Initial import of code
Richard Jones <richard@users.sourceforge.net>
parents:
diff
changeset
|
22 class RoundupRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler): |
|
5e92642cd1f8
Initial import of code
Richard Jones <richard@users.sourceforge.net>
parents:
diff
changeset
|
23 def send_head(self): |
|
5e92642cd1f8
Initial import of code
Richard Jones <richard@users.sourceforge.net>
parents:
diff
changeset
|
24 """Version of send_head that support CGI scripts""" |
|
5e92642cd1f8
Initial import of code
Richard Jones <richard@users.sourceforge.net>
parents:
diff
changeset
|
25 return self.run_cgi() |
|
5e92642cd1f8
Initial import of code
Richard Jones <richard@users.sourceforge.net>
parents:
diff
changeset
|
26 |
|
5e92642cd1f8
Initial import of code
Richard Jones <richard@users.sourceforge.net>
parents:
diff
changeset
|
27 def run_cgi(self): |
|
5e92642cd1f8
Initial import of code
Richard Jones <richard@users.sourceforge.net>
parents:
diff
changeset
|
28 """Execute a CGI script.""" |
|
5e92642cd1f8
Initial import of code
Richard Jones <richard@users.sourceforge.net>
parents:
diff
changeset
|
29 rest = self.path |
|
5e92642cd1f8
Initial import of code
Richard Jones <richard@users.sourceforge.net>
parents:
diff
changeset
|
30 i = rest.rfind('?') |
|
5e92642cd1f8
Initial import of code
Richard Jones <richard@users.sourceforge.net>
parents:
diff
changeset
|
31 if i >= 0: |
|
5e92642cd1f8
Initial import of code
Richard Jones <richard@users.sourceforge.net>
parents:
diff
changeset
|
32 rest, query = rest[:i], rest[i+1:] |
|
5e92642cd1f8
Initial import of code
Richard Jones <richard@users.sourceforge.net>
parents:
diff
changeset
|
33 else: |
|
5e92642cd1f8
Initial import of code
Richard Jones <richard@users.sourceforge.net>
parents:
diff
changeset
|
34 query = '' |
|
5e92642cd1f8
Initial import of code
Richard Jones <richard@users.sourceforge.net>
parents:
diff
changeset
|
35 |
|
5e92642cd1f8
Initial import of code
Richard Jones <richard@users.sourceforge.net>
parents:
diff
changeset
|
36 # Set up the CGI environment |
|
5e92642cd1f8
Initial import of code
Richard Jones <richard@users.sourceforge.net>
parents:
diff
changeset
|
37 env = {} |
|
5e92642cd1f8
Initial import of code
Richard Jones <richard@users.sourceforge.net>
parents:
diff
changeset
|
38 env['REQUEST_METHOD'] = self.command |
|
5e92642cd1f8
Initial import of code
Richard Jones <richard@users.sourceforge.net>
parents:
diff
changeset
|
39 env['PATH_INFO'] = urllib.unquote(rest) |
|
5e92642cd1f8
Initial import of code
Richard Jones <richard@users.sourceforge.net>
parents:
diff
changeset
|
40 if query: |
|
5e92642cd1f8
Initial import of code
Richard Jones <richard@users.sourceforge.net>
parents:
diff
changeset
|
41 env['QUERY_STRING'] = query |
|
5e92642cd1f8
Initial import of code
Richard Jones <richard@users.sourceforge.net>
parents:
diff
changeset
|
42 host = self.address_string() |
|
5e92642cd1f8
Initial import of code
Richard Jones <richard@users.sourceforge.net>
parents:
diff
changeset
|
43 if self.headers.typeheader is None: |
|
5e92642cd1f8
Initial import of code
Richard Jones <richard@users.sourceforge.net>
parents:
diff
changeset
|
44 env['CONTENT_TYPE'] = self.headers.type |
|
5e92642cd1f8
Initial import of code
Richard Jones <richard@users.sourceforge.net>
parents:
diff
changeset
|
45 else: |
|
5e92642cd1f8
Initial import of code
Richard Jones <richard@users.sourceforge.net>
parents:
diff
changeset
|
46 env['CONTENT_TYPE'] = self.headers.typeheader |
|
5e92642cd1f8
Initial import of code
Richard Jones <richard@users.sourceforge.net>
parents:
diff
changeset
|
47 length = self.headers.getheader('content-length') |
|
5e92642cd1f8
Initial import of code
Richard Jones <richard@users.sourceforge.net>
parents:
diff
changeset
|
48 if length: |
|
5e92642cd1f8
Initial import of code
Richard Jones <richard@users.sourceforge.net>
parents:
diff
changeset
|
49 env['CONTENT_LENGTH'] = length |
|
5e92642cd1f8
Initial import of code
Richard Jones <richard@users.sourceforge.net>
parents:
diff
changeset
|
50 co = filter(None, self.headers.getheaders('cookie')) |
|
5e92642cd1f8
Initial import of code
Richard Jones <richard@users.sourceforge.net>
parents:
diff
changeset
|
51 if co: |
|
5e92642cd1f8
Initial import of code
Richard Jones <richard@users.sourceforge.net>
parents:
diff
changeset
|
52 env['HTTP_COOKIE'] = ', '.join(co) |
|
5e92642cd1f8
Initial import of code
Richard Jones <richard@users.sourceforge.net>
parents:
diff
changeset
|
53 env['SCRIPT_NAME'] = '' |
|
5e92642cd1f8
Initial import of code
Richard Jones <richard@users.sourceforge.net>
parents:
diff
changeset
|
54 env['SERVER_NAME'] = self.server.server_name |
|
5e92642cd1f8
Initial import of code
Richard Jones <richard@users.sourceforge.net>
parents:
diff
changeset
|
55 env['SERVER_PORT'] = str(self.server.server_port) |
|
5e92642cd1f8
Initial import of code
Richard Jones <richard@users.sourceforge.net>
parents:
diff
changeset
|
56 |
|
5e92642cd1f8
Initial import of code
Richard Jones <richard@users.sourceforge.net>
parents:
diff
changeset
|
57 decoded_query = query.replace('+', ' ') |
|
5e92642cd1f8
Initial import of code
Richard Jones <richard@users.sourceforge.net>
parents:
diff
changeset
|
58 |
|
5e92642cd1f8
Initial import of code
Richard Jones <richard@users.sourceforge.net>
parents:
diff
changeset
|
59 # if root, setuid to nobody |
|
5e92642cd1f8
Initial import of code
Richard Jones <richard@users.sourceforge.net>
parents:
diff
changeset
|
60 if not os.getuid(): |
|
5e92642cd1f8
Initial import of code
Richard Jones <richard@users.sourceforge.net>
parents:
diff
changeset
|
61 nobody = nobody_uid() |
|
5e92642cd1f8
Initial import of code
Richard Jones <richard@users.sourceforge.net>
parents:
diff
changeset
|
62 os.setuid(nobody) |
|
5e92642cd1f8
Initial import of code
Richard Jones <richard@users.sourceforge.net>
parents:
diff
changeset
|
63 |
|
5e92642cd1f8
Initial import of code
Richard Jones <richard@users.sourceforge.net>
parents:
diff
changeset
|
64 # TODO check for file timestamp changes |
|
5e92642cd1f8
Initial import of code
Richard Jones <richard@users.sourceforge.net>
parents:
diff
changeset
|
65 reload(date) |
|
5e92642cd1f8
Initial import of code
Richard Jones <richard@users.sourceforge.net>
parents:
diff
changeset
|
66 reload(hyperdb) |
|
5e92642cd1f8
Initial import of code
Richard Jones <richard@users.sourceforge.net>
parents:
diff
changeset
|
67 reload(roundupdb) |
|
5e92642cd1f8
Initial import of code
Richard Jones <richard@users.sourceforge.net>
parents:
diff
changeset
|
68 reload(template) |
|
5e92642cd1f8
Initial import of code
Richard Jones <richard@users.sourceforge.net>
parents:
diff
changeset
|
69 reload(roundup_cgi) |
|
5e92642cd1f8
Initial import of code
Richard Jones <richard@users.sourceforge.net>
parents:
diff
changeset
|
70 |
|
5e92642cd1f8
Initial import of code
Richard Jones <richard@users.sourceforge.net>
parents:
diff
changeset
|
71 # initialise the roundupdb, check for auth |
|
5e92642cd1f8
Initial import of code
Richard Jones <richard@users.sourceforge.net>
parents:
diff
changeset
|
72 db = roundupdb.openDB('db', 'admin') |
|
5e92642cd1f8
Initial import of code
Richard Jones <richard@users.sourceforge.net>
parents:
diff
changeset
|
73 message = 'Unauthorised' |
|
5e92642cd1f8
Initial import of code
Richard Jones <richard@users.sourceforge.net>
parents:
diff
changeset
|
74 auth = self.headers.getheader('authorization') |
|
5e92642cd1f8
Initial import of code
Richard Jones <richard@users.sourceforge.net>
parents:
diff
changeset
|
75 if auth: |
|
5e92642cd1f8
Initial import of code
Richard Jones <richard@users.sourceforge.net>
parents:
diff
changeset
|
76 l = binascii.a2b_base64(auth.split(' ')[1]).split(':') |
|
5e92642cd1f8
Initial import of code
Richard Jones <richard@users.sourceforge.net>
parents:
diff
changeset
|
77 user = l[0] |
|
5e92642cd1f8
Initial import of code
Richard Jones <richard@users.sourceforge.net>
parents:
diff
changeset
|
78 password = None |
|
5e92642cd1f8
Initial import of code
Richard Jones <richard@users.sourceforge.net>
parents:
diff
changeset
|
79 if len(l) > 1: |
|
5e92642cd1f8
Initial import of code
Richard Jones <richard@users.sourceforge.net>
parents:
diff
changeset
|
80 password = l[1] |
|
5e92642cd1f8
Initial import of code
Richard Jones <richard@users.sourceforge.net>
parents:
diff
changeset
|
81 try: |
|
5e92642cd1f8
Initial import of code
Richard Jones <richard@users.sourceforge.net>
parents:
diff
changeset
|
82 uid = db.user.lookup(user) |
|
5e92642cd1f8
Initial import of code
Richard Jones <richard@users.sourceforge.net>
parents:
diff
changeset
|
83 except KeyError: |
|
5e92642cd1f8
Initial import of code
Richard Jones <richard@users.sourceforge.net>
parents:
diff
changeset
|
84 auth = None |
|
5e92642cd1f8
Initial import of code
Richard Jones <richard@users.sourceforge.net>
parents:
diff
changeset
|
85 message = 'Username not recognised' |
|
5e92642cd1f8
Initial import of code
Richard Jones <richard@users.sourceforge.net>
parents:
diff
changeset
|
86 else: |
|
5e92642cd1f8
Initial import of code
Richard Jones <richard@users.sourceforge.net>
parents:
diff
changeset
|
87 if password != db.user.get(uid, 'password'): |
|
5e92642cd1f8
Initial import of code
Richard Jones <richard@users.sourceforge.net>
parents:
diff
changeset
|
88 message = 'Incorrect password' |
|
5e92642cd1f8
Initial import of code
Richard Jones <richard@users.sourceforge.net>
parents:
diff
changeset
|
89 auth = None |
|
5e92642cd1f8
Initial import of code
Richard Jones <richard@users.sourceforge.net>
parents:
diff
changeset
|
90 db.close() |
|
5e92642cd1f8
Initial import of code
Richard Jones <richard@users.sourceforge.net>
parents:
diff
changeset
|
91 del db |
|
5e92642cd1f8
Initial import of code
Richard Jones <richard@users.sourceforge.net>
parents:
diff
changeset
|
92 if not auth: |
|
5e92642cd1f8
Initial import of code
Richard Jones <richard@users.sourceforge.net>
parents:
diff
changeset
|
93 self.send_response(401) |
|
5e92642cd1f8
Initial import of code
Richard Jones <richard@users.sourceforge.net>
parents:
diff
changeset
|
94 self.send_header('Content-Type', 'text/html') |
|
5e92642cd1f8
Initial import of code
Richard Jones <richard@users.sourceforge.net>
parents:
diff
changeset
|
95 self.send_header('WWW-Authenticate', 'basic realm="Roundup"') |
|
5e92642cd1f8
Initial import of code
Richard Jones <richard@users.sourceforge.net>
parents:
diff
changeset
|
96 self.end_headers() |
|
5e92642cd1f8
Initial import of code
Richard Jones <richard@users.sourceforge.net>
parents:
diff
changeset
|
97 self.wfile.write(message) |
|
5e92642cd1f8
Initial import of code
Richard Jones <richard@users.sourceforge.net>
parents:
diff
changeset
|
98 return |
|
5e92642cd1f8
Initial import of code
Richard Jones <richard@users.sourceforge.net>
parents:
diff
changeset
|
99 |
|
5e92642cd1f8
Initial import of code
Richard Jones <richard@users.sourceforge.net>
parents:
diff
changeset
|
100 self.send_response(200, "Script output follows") |
|
5e92642cd1f8
Initial import of code
Richard Jones <richard@users.sourceforge.net>
parents:
diff
changeset
|
101 |
|
5e92642cd1f8
Initial import of code
Richard Jones <richard@users.sourceforge.net>
parents:
diff
changeset
|
102 # do the roundup thang |
|
5e92642cd1f8
Initial import of code
Richard Jones <richard@users.sourceforge.net>
parents:
diff
changeset
|
103 save_stdin = sys.stdin |
|
5e92642cd1f8
Initial import of code
Richard Jones <richard@users.sourceforge.net>
parents:
diff
changeset
|
104 try: |
|
5e92642cd1f8
Initial import of code
Richard Jones <richard@users.sourceforge.net>
parents:
diff
changeset
|
105 sys.stdin = self.rfile |
|
5e92642cd1f8
Initial import of code
Richard Jones <richard@users.sourceforge.net>
parents:
diff
changeset
|
106 client = roundup_cgi.Client(self.wfile, env, user) |
|
5e92642cd1f8
Initial import of code
Richard Jones <richard@users.sourceforge.net>
parents:
diff
changeset
|
107 client.main() |
|
5e92642cd1f8
Initial import of code
Richard Jones <richard@users.sourceforge.net>
parents:
diff
changeset
|
108 except roundup_cgi.Unauthorised: |
|
5e92642cd1f8
Initial import of code
Richard Jones <richard@users.sourceforge.net>
parents:
diff
changeset
|
109 self.wfile.write('Content-Type: text/html\n') |
|
5e92642cd1f8
Initial import of code
Richard Jones <richard@users.sourceforge.net>
parents:
diff
changeset
|
110 self.wfile.write('Status: 403\n') |
|
5e92642cd1f8
Initial import of code
Richard Jones <richard@users.sourceforge.net>
parents:
diff
changeset
|
111 self.wfile.write('Unauthorised') |
|
5e92642cd1f8
Initial import of code
Richard Jones <richard@users.sourceforge.net>
parents:
diff
changeset
|
112 except: |
|
5e92642cd1f8
Initial import of code
Richard Jones <richard@users.sourceforge.net>
parents:
diff
changeset
|
113 try: |
|
5e92642cd1f8
Initial import of code
Richard Jones <richard@users.sourceforge.net>
parents:
diff
changeset
|
114 reload(cgitb) |
|
5e92642cd1f8
Initial import of code
Richard Jones <richard@users.sourceforge.net>
parents:
diff
changeset
|
115 self.wfile.write(cgitb.breaker()) |
|
5e92642cd1f8
Initial import of code
Richard Jones <richard@users.sourceforge.net>
parents:
diff
changeset
|
116 self.wfile.write(cgitb.html()) |
|
5e92642cd1f8
Initial import of code
Richard Jones <richard@users.sourceforge.net>
parents:
diff
changeset
|
117 except: |
|
5e92642cd1f8
Initial import of code
Richard Jones <richard@users.sourceforge.net>
parents:
diff
changeset
|
118 self.wfile.write("Content-Type: text/html\n\n") |
|
5e92642cd1f8
Initial import of code
Richard Jones <richard@users.sourceforge.net>
parents:
diff
changeset
|
119 self.wfile.write("<pre>") |
|
5e92642cd1f8
Initial import of code
Richard Jones <richard@users.sourceforge.net>
parents:
diff
changeset
|
120 s = StringIO.StringIO() |
|
5e92642cd1f8
Initial import of code
Richard Jones <richard@users.sourceforge.net>
parents:
diff
changeset
|
121 traceback.print_exc(None, s) |
|
5e92642cd1f8
Initial import of code
Richard Jones <richard@users.sourceforge.net>
parents:
diff
changeset
|
122 self.wfile.write(cgi.escape(s.getvalue())) |
|
5e92642cd1f8
Initial import of code
Richard Jones <richard@users.sourceforge.net>
parents:
diff
changeset
|
123 self.wfile.write("</pre>\n") |
|
5e92642cd1f8
Initial import of code
Richard Jones <richard@users.sourceforge.net>
parents:
diff
changeset
|
124 sys.stdin = save_stdin |
|
5e92642cd1f8
Initial import of code
Richard Jones <richard@users.sourceforge.net>
parents:
diff
changeset
|
125 do_POST = run_cgi |
|
5e92642cd1f8
Initial import of code
Richard Jones <richard@users.sourceforge.net>
parents:
diff
changeset
|
126 |
|
5e92642cd1f8
Initial import of code
Richard Jones <richard@users.sourceforge.net>
parents:
diff
changeset
|
127 |
|
5e92642cd1f8
Initial import of code
Richard Jones <richard@users.sourceforge.net>
parents:
diff
changeset
|
128 nobody = None |
|
5e92642cd1f8
Initial import of code
Richard Jones <richard@users.sourceforge.net>
parents:
diff
changeset
|
129 |
|
5e92642cd1f8
Initial import of code
Richard Jones <richard@users.sourceforge.net>
parents:
diff
changeset
|
130 def nobody_uid(): |
|
5e92642cd1f8
Initial import of code
Richard Jones <richard@users.sourceforge.net>
parents:
diff
changeset
|
131 """Internal routine to get nobody's uid""" |
|
5e92642cd1f8
Initial import of code
Richard Jones <richard@users.sourceforge.net>
parents:
diff
changeset
|
132 global nobody |
|
5e92642cd1f8
Initial import of code
Richard Jones <richard@users.sourceforge.net>
parents:
diff
changeset
|
133 if nobody: |
|
5e92642cd1f8
Initial import of code
Richard Jones <richard@users.sourceforge.net>
parents:
diff
changeset
|
134 return nobody |
|
5e92642cd1f8
Initial import of code
Richard Jones <richard@users.sourceforge.net>
parents:
diff
changeset
|
135 try: |
|
5e92642cd1f8
Initial import of code
Richard Jones <richard@users.sourceforge.net>
parents:
diff
changeset
|
136 import pwd |
|
5e92642cd1f8
Initial import of code
Richard Jones <richard@users.sourceforge.net>
parents:
diff
changeset
|
137 except ImportError: |
|
5e92642cd1f8
Initial import of code
Richard Jones <richard@users.sourceforge.net>
parents:
diff
changeset
|
138 return -1 |
|
5e92642cd1f8
Initial import of code
Richard Jones <richard@users.sourceforge.net>
parents:
diff
changeset
|
139 try: |
|
5e92642cd1f8
Initial import of code
Richard Jones <richard@users.sourceforge.net>
parents:
diff
changeset
|
140 nobody = pwd.getpwnam('nobody')[2] |
|
5e92642cd1f8
Initial import of code
Richard Jones <richard@users.sourceforge.net>
parents:
diff
changeset
|
141 except KeyError: |
|
5e92642cd1f8
Initial import of code
Richard Jones <richard@users.sourceforge.net>
parents:
diff
changeset
|
142 nobody = 1 + max(map(lambda x: x[2], pwd.getpwall())) |
|
5e92642cd1f8
Initial import of code
Richard Jones <richard@users.sourceforge.net>
parents:
diff
changeset
|
143 return nobody |
|
5e92642cd1f8
Initial import of code
Richard Jones <richard@users.sourceforge.net>
parents:
diff
changeset
|
144 |
|
5e92642cd1f8
Initial import of code
Richard Jones <richard@users.sourceforge.net>
parents:
diff
changeset
|
145 if __name__ == '__main__': |
|
5e92642cd1f8
Initial import of code
Richard Jones <richard@users.sourceforge.net>
parents:
diff
changeset
|
146 address = ('dirk.adroit', 9080) |
|
5e92642cd1f8
Initial import of code
Richard Jones <richard@users.sourceforge.net>
parents:
diff
changeset
|
147 httpd = BaseHTTPServer.HTTPServer(address, RoundupRequestHandler) |
|
5e92642cd1f8
Initial import of code
Richard Jones <richard@users.sourceforge.net>
parents:
diff
changeset
|
148 print 'Roundup server started on', address |
|
5e92642cd1f8
Initial import of code
Richard Jones <richard@users.sourceforge.net>
parents:
diff
changeset
|
149 httpd.serve_forever() |
|
5e92642cd1f8
Initial import of code
Richard Jones <richard@users.sourceforge.net>
parents:
diff
changeset
|
150 |
