~azzar1/unity/add-show-desktop-key

1099 by matt.giuca
dispatch/__init__.py: Updated header comments to new policy, updated copyright.
1
# IVLE - Informatics Virtual Learning Environment
2
# Copyright (C) 2007-2009 The University of Melbourne
1079 by William Grant
Merge setup-refactor branch. This completely breaks existing installations;
3
#
4
# This program is free software; you can redistribute it and/or modify
5
# it under the terms of the GNU General Public License as published by
6
# the Free Software Foundation; either version 2 of the License, or
7
# (at your option) any later version.
8
#
9
# This program is distributed in the hope that it will be useful,
10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
# GNU General Public License for more details.
13
#
14
# You should have received a copy of the GNU General Public License
15
# along with this program; if not, write to the Free Software
16
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
17
1099.1.1 by Matt Giuca
Began implementing new dispatch framework (with Will Grant and Nick Chadwick).
18
# Author: Matt Giuca, Will Grant
1099 by matt.giuca
dispatch/__init__.py: Updated header comments to new policy, updated copyright.
19
20
"""
21
This is a mod_python handler program. The correct way to call it is to have
22
Apache send all requests to be handled by the module 'dispatch'.
23
24
Top-level handler. Handles all requests to all pages in IVLE.
25
Handles authentication (not authorization).
26
Then passes the request along to the appropriate ivle app.
27
"""
1079 by William Grant
Merge setup-refactor branch. This completely breaks existing installations;
28
29
import sys
30
import os
31
import os.path
32
import urllib
33
import cgi
34
import traceback
35
import logging
36
import socket
37
import time
1099.1.72 by Nick Chadwick
Dispatch now generates an index for each plugin type, allowing plugins to
38
import inspect
1079 by William Grant
Merge setup-refactor branch. This completely breaks existing installations;
39
40
import mod_python
1099.1.1 by Matt Giuca
Began implementing new dispatch framework (with Will Grant and Nick Chadwick).
41
import routes
1079 by William Grant
Merge setup-refactor branch. This completely breaks existing installations;
42
43
from ivle import util
44
import ivle.conf
45
import ivle.conf.apps
1099.1.5 by William Grant
ivle.dispatch{,.{login,request}}: Fix mod_python imports to ensure that we can
46
from ivle.dispatch.request import Request
47
from ivle.dispatch import login
1099.1.95 by William Grant
Error views are now retrieved from a class method of the view, not the plugin
48
from ivle.webapp.base.plugins import ViewPlugin
1099.1.110 by William Grant
Implement an authorization system in the new framework. This breaks the REST
49
from ivle.webapp.errors import HTTPError, Unauthorized
1079 by William Grant
Merge setup-refactor branch. This completely breaks existing installations;
50
import apps
51
import html
52
1099.1.1 by Matt Giuca
Began implementing new dispatch framework (with Will Grant and Nick Chadwick).
53
# XXX List of plugins, which will eventually be read in from conf
54
plugins_HACK = [
1099.1.68 by William Grant
Move the remaining images to the new framework, in the new ivle.webapp.core
55
    'ivle.webapp.core#Plugin',
1099.1.11 by William Grant
ivle.dispatch: Change plugins_HACK spec such that it takes a sequence of
56
    'ivle.webapp.admin.user#Plugin',
1099.1.22 by root
ivle.dispatch: Reorder things in plugins_HACK to fix wildcards clobbering
57
    'ivle.webapp.tutorial#Plugin',
1099.1.20 by William Grant
ivle.webapp.admin.subject: Port www/apps/subjects to new framework.
58
    'ivle.webapp.admin.subject#Plugin',
1099.1.43 by William Grant
ivle.webapp.browser: Move to ivle.webapp.filesystem.browser.
59
    'ivle.webapp.filesystem.browser#Plugin',
1099.1.48 by William Grant
ivle.webapp.filesystem.diff: Port www/apps/diff to new framework. This moves
60
    'ivle.webapp.filesystem.diff#Plugin',
1099.1.54 by William Grant
ivle.webapp.filesystem.svnlog: Port www/apps/svnlog to new framework. As with
61
    'ivle.webapp.filesystem.svnlog#Plugin',
1099.1.17 by Nick Chadwick
Moved groups over to the new class-based xhtml templating way of being
62
    'ivle.webapp.groups#Plugin',
1099.1.29 by William Grant
ivle.webapp.console.service: Port www/apps/consoleservice to new framework.
63
    'ivle.webapp.console#Plugin',
1099.1.41 by William Grant
Port www/apps/logout to new framework (in ivle.webapp.security).
64
    'ivle.webapp.security#Plugin',
1099.1.59 by William Grant
Provide a media file framework in ivle.webapp.media.
65
    'ivle.webapp.media#Plugin',
1099.1.80 by William Grant
Port the forum app to the new framework. With it also comes new cookie
66
    'ivle.webapp.forum#Plugin',
1099.2.1 by Nick Chadwick
Added help to the plugins hack in dispatch.
67
    'ivle.webapp.help#Plugin',
1099.1.108 by William Grant
Port the tos app to the new framework, and fix ivle.webapp.help's reference
68
    'ivle.webapp.tos#Plugin',
1099.1.72 by Nick Chadwick
Dispatch now generates an index for each plugin type, allowing plugins to
69
] 
1099.1.1 by Matt Giuca
Began implementing new dispatch framework (with Will Grant and Nick Chadwick).
70
1099.1.72 by Nick Chadwick
Dispatch now generates an index for each plugin type, allowing plugins to
71
def generate_route_mapper(view_plugins):
1099.1.1 by Matt Giuca
Began implementing new dispatch framework (with Will Grant and Nick Chadwick).
72
    """
73
    Build a Mapper object for doing URL matching using 'routes', based on the
1099.1.59 by William Grant
Provide a media file framework in ivle.webapp.media.
74
    given plugin registry.
1099.1.1 by Matt Giuca
Began implementing new dispatch framework (with Will Grant and Nick Chadwick).
75
    """
1099.1.13 by William Grant
ivle.dispatch: Throw the routes mapper into req.mapper, and make it explicit.
76
    m = routes.Mapper(explicit=True)
1099.1.72 by Nick Chadwick
Dispatch now generates an index for each plugin type, allowing plugins to
77
    for plugin in view_plugins:
1099.1.1 by Matt Giuca
Began implementing new dispatch framework (with Will Grant and Nick Chadwick).
78
        # Establish a URL pattern for each element of plugin.urls
1099.1.72 by Nick Chadwick
Dispatch now generates an index for each plugin type, allowing plugins to
79
        assert hasattr(plugin, 'urls'), "%r does not have any urls" % plugin 
80
        for url in plugin.urls:
1099.1.1 by Matt Giuca
Began implementing new dispatch framework (with Will Grant and Nick Chadwick).
81
            routex = url[0]
82
            view_class = url[1]
83
            kwargs_dict = url[2] if len(url) >= 3 else {}
84
            m.connect(routex, view=view_class, **kwargs_dict)
85
    return m
86
1099.1.59 by William Grant
Provide a media file framework in ivle.webapp.media.
87
def get_plugin(pluginstr):
88
    plugin_path, classname = pluginstr.split('#')
89
    # Load the plugin module from somewhere in the Python path
90
    # (Note that plugin_path is a fully-qualified Python module name).
91
    return (plugin_path,
92
            getattr(__import__(plugin_path, fromlist=[classname]), classname))
93
1079 by William Grant
Merge setup-refactor branch. This completely breaks existing installations;
94
def handler(req):
95
    """Handles a request which may be to anywhere in the site except media.
96
    Intended to be called by mod_python, as a handler.
97
98
    req: An Apache request object.
99
    """
100
    # Make the request object into an IVLE request which can be passed to apps
101
    apachereq = req
102
    try:
103
        req = Request(req, html.write_html_head)
104
    except Exception:
105
        # Pass the apachereq to error reporter, since ivle req isn't created
106
        # yet.
107
        handle_unknown_exception(apachereq, *sys.exc_info())
108
        # Tell Apache not to generate its own errors as well
1099.1.4 by William Grant
ivle.dispatch: Move some imports around so that it can be imported from
109
        return mod_python.apache.OK
1079 by William Grant
Merge setup-refactor branch. This completely breaks existing installations;
110
111
    # Run the main handler, and catch all exceptions
112
    try:
113
        return handler_(req, apachereq)
114
    except mod_python.apache.SERVER_RETURN:
115
        # An apache error. We discourage these, but they might still happen.
116
        # Just raise up.
117
        raise
118
    except Exception:
119
        handle_unknown_exception(req, *sys.exc_info())
120
        # Tell Apache not to generate its own errors as well
1099.1.4 by William Grant
ivle.dispatch: Move some imports around so that it can be imported from
121
        return mod_python.apache.OK
1079 by William Grant
Merge setup-refactor branch. This completely breaks existing installations;
122
123
def handler_(req, apachereq):
124
    """
125
    Nested handler function. May raise exceptions. The top-level handler is
126
    just used to catch exceptions.
127
    Takes both an IVLE request and an Apache req.
128
    """
129
    # Hack? Try and get the user login early just in case we throw an error
130
    # (most likely 404) to stop us seeing not logged in even when we are.
131
    if not req.publicmode:
132
        req.user = login.get_user_details(req)
133
1099.1.1 by Matt Giuca
Began implementing new dispatch framework (with Will Grant and Nick Chadwick).
134
    ### BEGIN New plugins framework ###
135
    # XXX This should be done ONCE per Python process, not per request.
136
    # (Wait till WSGI)
137
    # XXX No authentication is done here
1099.1.59 by William Grant
Provide a media file framework in ivle.webapp.media.
138
    req.plugins = dict([get_plugin(pluginstr) for pluginstr in plugins_HACK])
1099.1.72 by Nick Chadwick
Dispatch now generates an index for each plugin type, allowing plugins to
139
    # Index the plugins by base class
140
    req.plugin_index = {}
141
    for plugin in req.plugins.values():
142
        # Getmro returns a tuple of all the super-classes of the plugin
143
        for base in inspect.getmro(plugin):
144
            if base not in req.plugin_index:
145
                req.plugin_index[base] = []
146
            req.plugin_index[base].append(plugin)
1099.1.59 by William Grant
Provide a media file framework in ivle.webapp.media.
147
    req.reverse_plugins = dict([(v, k) for (k, v) in req.plugins.items()])
1099.1.72 by Nick Chadwick
Dispatch now generates an index for each plugin type, allowing plugins to
148
    req.mapper = generate_route_mapper(req.plugin_index[ViewPlugin])
1099.1.59 by William Grant
Provide a media file framework in ivle.webapp.media.
149
1099.1.13 by William Grant
ivle.dispatch: Throw the routes mapper into req.mapper, and make it explicit.
150
    matchdict = req.mapper.match(req.uri)
1099.1.1 by Matt Giuca
Began implementing new dispatch framework (with Will Grant and Nick Chadwick).
151
    if matchdict is not None:
152
        viewcls = matchdict['view']
153
        # Get the remaining arguments, less 'view', 'action' and 'controller'
154
        # (The latter two seem to be built-in, and we don't want them).
155
        kwargs = matchdict.copy()
156
        del kwargs['view']
1099.1.84 by William Grant
Handle HTTPErrors nicely in the new framework. Currently there is no facility
157
        try:
1099.1.86 by William Grant
Also capture the view object creation in the new framework's exception handler.
158
            # Instantiate the view, which should be a BaseView class
159
            view = viewcls(req, **kwargs)
1099.1.110 by William Grant
Implement an authorization system in the new framework. This breaks the REST
160
161
            # Check that the request (mainly the user) is permitted to access
162
            # the view.
163
            if not view.authorize(req):
164
                raise Unauthorized()
1099.1.86 by William Grant
Also capture the view object creation in the new framework's exception handler.
165
            # Render the output
1099.1.84 by William Grant
Handle HTTPErrors nicely in the new framework. Currently there is no facility
166
            view.render(req)
167
        except HTTPError, e:
168
            # A view explicitly raised an HTTP error. Respect it.
169
            req.status = e.code
1099.1.91 by William Grant
Add support for custom error views. Plugins can now declare that errors
170
171
            # Try to find a custom error view.
1099.1.95 by William Grant
Error views are now retrieved from a class method of the view, not the plugin
172
            if hasattr(viewcls, 'get_error_view'):
173
                errviewcls = viewcls.get_error_view(e)
174
            else:
175
                errviewcls = None
176
1099.1.91 by William Grant
Add support for custom error views. Plugins can now declare that errors
177
            if errviewcls:
178
                errview = errviewcls(req, e)
179
                errview.render(req)
180
                return req.OK
181
            else:
182
                req.write(e.message)
183
                return e.code
1099.1.84 by William Grant
Handle HTTPErrors nicely in the new framework. Currently there is no facility
184
        except Exception, e:
185
            # A non-HTTPError appeared. We have an unknown exception. Panic.
186
            handle_unknown_exception(req, *sys.exc_info())
1099.1.88 by William Grant
Make sure that we don't use the old framework if the new framework crashes.
187
            return req.OK
1099.1.84 by William Grant
Handle HTTPErrors nicely in the new framework. Currently there is no facility
188
        else:
189
            req.store.commit()
190
            return req.OK
1099.1.1 by Matt Giuca
Began implementing new dispatch framework (with Will Grant and Nick Chadwick).
191
    ### END New plugins framework ###
192
1079 by William Grant
Merge setup-refactor branch. This completely breaks existing installations;
193
    # Check req.app to see if it is valid. 404 if not.
194
    if req.app is not None and req.app not in ivle.conf.apps.app_url:
1081 by me at id
ivle.conf, ivle.dispatch: Redo some of Nick's changes made between the
195
        req.throw_error(Request.HTTP_NOT_FOUND,
196
            "There is no application called %s." % repr(req.app))
1079 by William Grant
Merge setup-refactor branch. This completely breaks existing installations;
197
198
    # Special handling for public mode - only allow the public app, call it
199
    # and get out.
200
    # NOTE: This will not behave correctly if the public app uses
201
    # write_html_head_foot, but "serve" does not.
202
    if req.publicmode:
203
        if req.app != ivle.conf.apps.public_app:
204
            req.throw_error(Request.HTTP_FORBIDDEN,
205
                "This application is not available on the public site.")
206
        app = ivle.conf.apps.app_url[ivle.conf.apps.public_app]
207
        apps.call_app(app.dir, req)
208
        return req.OK
209
210
    # app is the App object for the chosen app
211
    if req.app is None:
212
        app = ivle.conf.apps.app_url[ivle.conf.apps.default_app]
213
    else:
214
        app = ivle.conf.apps.app_url[req.app]
215
216
    # Check if app requires auth. If so, perform authentication and login.
217
    # This will either return a User object, None, or perform a redirect
218
    # which we will not catch here.
219
    if app.requireauth:
220
        req.user = login.login(req)
221
        logged_in = req.user is not None
222
    else:
223
        req.user = login.get_user_details(req)
224
        logged_in = True
225
226
    if logged_in:
227
        # Keep the user's session alive by writing to the session object.
228
        # req.get_session().save()
229
        # Well, it's a fine idea, but it creates considerable grief in the
230
        # concurrent update department, so instead, we'll just make the
231
        # sessions not time out.
232
        req.get_session().unlock()
233
234
        # If user did not specify an app, HTTP redirect to default app and
235
        # exit.
236
        if req.app is None:
237
            req.throw_redirect(util.make_path(ivle.conf.apps.default_app))
238
239
        # Set the default title to the app's tab name, if any. Otherwise URL
240
        # name.
241
        if app.name is not None:
242
            req.title = app.name
243
        else:
244
            req.title = req.app
245
246
        # Call the specified app with the request object
247
        apps.call_app(app.dir, req)
248
249
    # if not logged in, login.login will have written the login box.
250
    # Just clean up and exit.
251
252
    # MAKE SURE we write the HTTP (and possibly HTML) header. This
253
    # wouldn't happen if nothing else ever got written, so we have to make
254
    # sure.
255
    req.ensure_headers_written()
256
257
    # When done, write out the HTML footer if the app has requested it
258
    if req.write_html_head_foot:
259
        html.write_html_foot(req)
260
261
    # Note: Apache will not write custom HTML error messages here.
262
    # Use req.throw_error to do that.
263
    return req.OK
264
265
def handle_unknown_exception(req, exc_type, exc_value, exc_traceback):
266
    """
267
    Given an exception that has just been thrown from IVLE, print its details
268
    to the request.
269
    This is a full handler. It assumes nothing has been written, and writes a
270
    complete HTML page.
271
    req: May be EITHER an IVLE req or an Apache req.
272
    IVLE reqs may have the HTML head/foot written (on a 400 error), but
273
    the handler code may pass an apache req if an exception occurs before
274
    the IVLE request is created.
275
    """
276
    req.content_type = "text/html"
277
    logfile = os.path.join(ivle.conf.log_path, 'ivle_error.log')
278
    logfail = False
279
    # For some reason, some versions of mod_python have "_server" instead of
280
    # "main_server". So we check for both.
281
    try:
1099.1.4 by William Grant
ivle.dispatch: Move some imports around so that it can be imported from
282
        admin_email = mod_python.apache.main_server.server_admin
1079 by William Grant
Merge setup-refactor branch. This completely breaks existing installations;
283
    except AttributeError:
284
        try:
1099.1.4 by William Grant
ivle.dispatch: Move some imports around so that it can be imported from
285
            admin_email = mod_python.apache._server.server_admin
1079 by William Grant
Merge setup-refactor branch. This completely breaks existing installations;
286
        except AttributeError:
287
            admin_email = ""
288
    try:
289
        httpcode = exc_value.httpcode
290
        req.status = httpcode
291
    except AttributeError:
292
        httpcode = None
1099.1.4 by William Grant
ivle.dispatch: Move some imports around so that it can be imported from
293
        req.status = mod_python.apache.HTTP_INTERNAL_SERVER_ERROR
1079 by William Grant
Merge setup-refactor branch. This completely breaks existing installations;
294
    try:
1087 by chadnickbok
Fixes Issue #3
295
        publicmode = req.publicmode
296
    except AttributeError:
1088 by chadnickbok
Fixed an issue with my previous patch to this file. Now, if
297
        publicmode = True
1087 by chadnickbok
Fixes Issue #3
298
    try:
1079 by William Grant
Merge setup-refactor branch. This completely breaks existing installations;
299
        login = req.user.login
300
    except AttributeError:
301
        login = None
1087 by chadnickbok
Fixes Issue #3
302
    try:
303
        role = req.user.role
304
    except AttributeError:
305
        role = None
1079 by William Grant
Merge setup-refactor branch. This completely breaks existing installations;
306
307
    # Log File
308
    try:
309
        for h in logging.getLogger().handlers:
310
            logging.getLogger().removeHandler(h)
311
        logging.basicConfig(level=logging.INFO,
312
            format='%(asctime)s %(levelname)s: ' +
313
                '(HTTP: ' + str(req.status) +
314
                ', Ref: ' + str(login) + '@' +
315
                str(socket.gethostname()) + str(req.uri) +
316
                ') %(message)s',
317
            filename=logfile,
318
            filemode='a')
319
    except IOError:
320
        logfail = True
321
    logging.debug('Logging Unhandled Exception')
322
323
    # We handle 3 types of error.
324
    # IVLEErrors with 4xx response codes (client error).
325
    # IVLEErrors with 5xx response codes (handled server error).
326
    # Other exceptions (unhandled server error).
327
    # IVLEErrors should not have other response codes than 4xx or 5xx
328
    # (eg. throw_redirect should have been used for 3xx codes).
329
    # Therefore, that is treated as an unhandled error.
330
331
    if (exc_type == util.IVLEError and httpcode >= 400
332
        and httpcode <= 499):
333
        # IVLEErrors with 4xx response codes are client errors.
334
        # Therefore, these have a "nice" response (we even coat it in the IVLE
335
        # HTML wrappers).
336
        
337
        req.write_html_head_foot = True
338
        req.write_javascript_settings = False
339
        req.write('<div id="ivle_padding">\n')
340
        try:
341
            codename, msg = req.get_http_codename(httpcode)
342
        except AttributeError:
343
            codename, msg = None, None
344
        # Override the default message with the supplied one,
345
        # if available.
346
        if exc_value.message is not None:
347
            msg = exc_value.message
348
        if codename is not None:
349
            req.write("<h1>Error: %s</h1>\n" % cgi.escape(codename))
350
        else:
351
            req.write("<h1>Error</h1>\n")
352
        if msg is not None:
353
            req.write("<p>%s</p>\n" % cgi.escape(msg))
354
        else:
355
            req.write("<p>An unknown error occured.</p>\n")
356
        
357
        # Logging
358
        logging.info(str(msg))
359
        
360
        req.write("<p>(HTTP error code %d)</p>\n" % httpcode)
361
        if logfail:
362
            req.write("<p>Warning: Could not open Error Log: '%s'</p>\n"
363
                %cgi.escape(logfile))
364
        req.write('</div>\n')
365
        html.write_html_foot(req)
366
    else:
367
        # A "bad" error message. We shouldn't get here unless IVLE
368
        # misbehaves (which is currently very easy, if things aren't set up
369
        # correctly).
370
        # Write the traceback.
371
        # If this is a non-4xx IVLEError, get the message and httpcode and
372
        # make the error message a bit nicer (but still include the
373
        # traceback).
374
        # We also need to special-case IVLEJailError, as we can get another
375
        # almost-exception out of it.
376
377
        codename, msg = None, None
378
379
        if exc_type is util.IVLEJailError:
380
            msg = exc_value.type_str + ": " + exc_value.message
381
            tb = 'Exception information extracted from IVLEJailError:\n'
382
            tb += urllib.unquote(exc_value.info)
383
        else:
384
            try:
385
                codename, msg = req.get_http_codename(httpcode)
386
            except AttributeError:
387
                pass
388
            # Override the default message with the supplied one,
389
            # if available.
390
            if hasattr(exc_value, 'message') and exc_value.message is not None:
391
                msg = exc_value.message
392
                # Prepend the exception type
393
                if exc_type != util.IVLEError:
394
                    msg = exc_type.__name__ + ": " + msg
395
396
            tb = ''.join(traceback.format_exception(exc_type, exc_value,
397
                                                    exc_traceback))
398
399
        # Logging
400
        logging.error('%s\n%s'%(str(msg), tb))
1087 by chadnickbok
Fixes Issue #3
401
        # Error messages are only displayed is the user is NOT a student,
402
        # or if there has been a problem logging the error message
1096 by me at id
ivle.dispatch: Only show tracebacks if we are logged in and not a student, or
403
        show_errors = (not publicmode) and ((login and \
404
                            str(role) != "student") or logfail)
1079 by William Grant
Merge setup-refactor branch. This completely breaks existing installations;
405
        req.write("""<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"                 
406
	"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">                                      
407
<html xmlns="http://www.w3.org/1999/xhtml">
408
<head><title>IVLE Internal Server Error</title></head>
409
<body>
410
<h1>IVLE Internal Server Error""")
1087 by chadnickbok
Fixes Issue #3
411
        if (show_errors):
412
            if (codename is not None
1099.1.4 by William Grant
ivle.dispatch: Move some imports around so that it can be imported from
413
                        and httpcode != mod_python.apache.HTTP_INTERNAL_SERVER_ERROR):
1087 by chadnickbok
Fixes Issue #3
414
                req.write(": %s" % cgi.escape(codename))
415
        
1079 by William Grant
Merge setup-refactor branch. This completely breaks existing installations;
416
        req.write("""</h1>
417
<p>An error has occured which is the fault of the IVLE developers or
1087 by chadnickbok
Fixes Issue #3
418
administration. The developers have been notified.</p>
1079 by William Grant
Merge setup-refactor branch. This completely breaks existing installations;
419
""")
1087 by chadnickbok
Fixes Issue #3
420
        if (show_errors):
421
            if msg is not None:
422
                req.write("<p>%s</p>\n" % cgi.escape(msg))
423
            if httpcode is not None:
424
                req.write("<p>(HTTP error code %d)</p>\n" % httpcode)
425
            req.write("""
426
    <p>Please report this to <a href="mailto:%s">%s</a> (the system
427
    administrator). Include the following information:</p>
428
    """ % (cgi.escape(admin_email), cgi.escape(admin_email)))
1079 by William Grant
Merge setup-refactor branch. This completely breaks existing installations;
429
1087 by chadnickbok
Fixes Issue #3
430
            req.write("<pre>\n%s\n</pre>\n"%cgi.escape(tb))
431
            if logfail:
432
                req.write("<p>Warning: Could not open Error Log: '%s'</p>\n"
433
                    %cgi.escape(logfile))
1079 by William Grant
Merge setup-refactor branch. This completely breaks existing installations;
434
        req.write("</body></html>")