~ubuntu-branches/ubuntu/quantal/enigmail/quantal-security

« back to all changes in this revision

Viewing changes to testing/mozbase/mozhttpd/mozhttpd/mozhttpd.py

  • Committer: Package Import Robot
  • Author(s): Chris Coulson
  • Date: 2013-09-13 16:02:15 UTC
  • mfrom: (0.12.16)
  • Revision ID: package-import@ubuntu.com-20130913160215-u3g8nmwa0pdwagwc
Tags: 2:1.5.2-0ubuntu0.12.10.1
* New upstream release v1.5.2 for Thunderbird 24

* Build enigmail using a stripped down Thunderbird 17 build system, as it's
  now quite difficult to build the way we were doing previously, with the
  latest Firefox build system
* Add debian/patches/no_libxpcom.patch - Don't link against libxpcom, as it
  doesn't exist anymore (but exists in the build system)
* Add debian/patches/use_sdk.patch - Use the SDK version of xpt.py and
  friends
* Drop debian/patches/ipc-pipe_rename.diff (not needed anymore)
* Drop debian/patches/makefile_depth.diff (not needed anymore)

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
#!/usr/bin/env python
2
 
 
3
 
# This Source Code Form is subject to the terms of the Mozilla Public
4
 
# License, v. 2.0. If a copy of the MPL was not distributed with this file,
5
 
# You can obtain one at http://mozilla.org/MPL/2.0/.
6
 
 
7
 
import BaseHTTPServer
8
 
import SimpleHTTPServer
9
 
import errno
10
 
import logging
11
 
import threading
12
 
import posixpath
13
 
import socket
14
 
import sys
15
 
import os
16
 
import urllib
17
 
import urlparse
18
 
import re
19
 
import iface
20
 
import time
21
 
from SocketServer import ThreadingMixIn
22
 
 
23
 
class EasyServer(ThreadingMixIn, BaseHTTPServer.HTTPServer):
24
 
    allow_reuse_address = True
25
 
    acceptable_errors = (errno.EPIPE, errno.ECONNABORTED)
26
 
 
27
 
    def handle_error(self, request, client_address):
28
 
        error = sys.exc_value
29
 
 
30
 
        if ((isinstance(error, socket.error) and
31
 
             isinstance(error.args, tuple) and
32
 
             error.args[0] in self.acceptable_errors)
33
 
            or
34
 
            (isinstance(error, IOError) and
35
 
             error.errno in self.acceptable_errors)):
36
 
            pass  # remote hang up before the result is sent
37
 
        else:
38
 
            logging.error(error)
39
 
 
40
 
 
41
 
class Request(object):
42
 
    """Details of a request."""
43
 
 
44
 
    # attributes from urlsplit that this class also sets
45
 
    uri_attrs = ('scheme', 'netloc', 'path', 'query', 'fragment')
46
 
 
47
 
    def __init__(self, uri, headers, rfile=None):
48
 
        self.uri = uri
49
 
        self.headers = headers
50
 
        parsed = urlparse.urlsplit(uri)
51
 
        for i, attr in enumerate(self.uri_attrs):
52
 
            setattr(self, attr, parsed[i])
53
 
        try:
54
 
            body_len = int(self.headers.get('Content-length', 0))
55
 
        except ValueError:
56
 
            body_len = 0
57
 
        if body_len and rfile:
58
 
            self.body = rfile.read(body_len)
59
 
        else:
60
 
            self.body = None
61
 
 
62
 
 
63
 
class RequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
64
 
 
65
 
    docroot = os.getcwd() # current working directory at time of import
66
 
    proxy_host_dirs = False
67
 
    request_log = []
68
 
    log_requests = False
69
 
    request = None
70
 
 
71
 
    def _try_handler(self, method):
72
 
        if self.log_requests:
73
 
            self.request_log.append({ 'method': method,
74
 
                                      'path': self.request.path,
75
 
                                      'time': time.time() })
76
 
 
77
 
        handlers = [handler for handler in self.urlhandlers
78
 
                    if handler['method'] == method]
79
 
        for handler in handlers:
80
 
            m = re.match(handler['path'], self.request.path)
81
 
            if m:
82
 
                (response_code, headerdict, data) = \
83
 
                    handler['function'](self.request, *m.groups())
84
 
                self.send_response(response_code)
85
 
                for (keyword, value) in headerdict.iteritems():
86
 
                    self.send_header(keyword, value)
87
 
                self.end_headers()
88
 
                self.wfile.write(data)
89
 
 
90
 
                return True
91
 
 
92
 
        return False
93
 
 
94
 
    def parse_request(self):
95
 
        retval = SimpleHTTPServer.SimpleHTTPRequestHandler.parse_request(self)
96
 
        self.request = Request(self.path, self.headers, self.rfile)
97
 
        return retval
98
 
 
99
 
    def do_GET(self):
100
 
        if not self._try_handler('GET'):
101
 
            if self.docroot:
102
 
                # don't include query string and fragment, and prepend
103
 
                # host directory if required.
104
 
                if self.request.netloc and self.proxy_host_dirs:
105
 
                    self.path = '/' + self.request.netloc + \
106
 
                        self.request.path
107
 
                else:
108
 
                    self.path = self.request.path
109
 
                SimpleHTTPServer.SimpleHTTPRequestHandler.do_GET(self)
110
 
            else:
111
 
                self.send_response(404)
112
 
                self.end_headers()
113
 
                self.wfile.write('')
114
 
 
115
 
    def do_POST(self):
116
 
        # if we don't have a match, we always fall through to 404 (this may
117
 
        # not be "technically" correct if we have a local file at the same
118
 
        # path as the resource but... meh)
119
 
        if not self._try_handler('POST'):
120
 
            self.send_response(404)
121
 
            self.end_headers()
122
 
            self.wfile.write('')
123
 
 
124
 
    def do_DEL(self):
125
 
        # if we don't have a match, we always fall through to 404 (this may
126
 
        # not be "technically" correct if we have a local file at the same
127
 
        # path as the resource but... meh)
128
 
        if not self._try_handler('DEL'):
129
 
            self.send_response(404)
130
 
            self.end_headers()
131
 
            self.wfile.write('')
132
 
 
133
 
    def translate_path(self, path):
134
 
        # this is taken from SimpleHTTPRequestHandler.translate_path(),
135
 
        # except we serve from self.docroot instead of os.getcwd(), and
136
 
        # parse_request()/do_GET() have already stripped the query string and
137
 
        # fragment and mangled the path for proxying, if required.
138
 
        path = posixpath.normpath(urllib.unquote(self.path))
139
 
        words = path.split('/')
140
 
        words = filter(None, words)
141
 
        path = self.docroot
142
 
        for word in words:
143
 
            drive, word = os.path.splitdrive(word)
144
 
            head, word = os.path.split(word)
145
 
            if word in (os.curdir, os.pardir): continue
146
 
            path = os.path.join(path, word)
147
 
        return path
148
 
 
149
 
 
150
 
    # I found on my local network that calls to this were timing out
151
 
    # I believe all of these calls are from log_message
152
 
    def address_string(self):
153
 
        return "a.b.c.d"
154
 
 
155
 
    # This produces a LOT of noise
156
 
    def log_message(self, format, *args):
157
 
        pass
158
 
 
159
 
 
160
 
class MozHttpd(object):
161
 
    """
162
 
    Very basic HTTP server class. Takes a docroot (path on the filesystem)
163
 
    and a set of urlhandler dictionaries of the form:
164
 
 
165
 
    {
166
 
      'method': HTTP method (string): GET, POST, or DEL,
167
 
      'path': PATH_INFO (regular expression string),
168
 
      'function': function of form fn(arg1, arg2, arg3, ..., request)
169
 
    }
170
 
 
171
 
    and serves HTTP. For each request, MozHttpd will either return a file
172
 
    off the docroot, or dispatch to a handler function (if both path and
173
 
    method match).
174
 
 
175
 
    Note that one of docroot or urlhandlers may be None (in which case no
176
 
    local files or handlers, respectively, will be used). If both docroot or
177
 
    urlhandlers are None then MozHttpd will default to serving just the local
178
 
    directory.
179
 
 
180
 
    MozHttpd also handles proxy requests (i.e. with a full URI on the request
181
 
    line).  By default files are served from docroot according to the request
182
 
    URI's path component, but if proxy_host_dirs is True, files are served
183
 
    from <self.docroot>/<host>/.
184
 
 
185
 
    For example, the request "GET http://foo.bar/dir/file.html" would
186
 
    (assuming no handlers match) serve <docroot>/dir/file.html if
187
 
    proxy_host_dirs is False, or <docroot>/foo.bar/dir/file.html if it is
188
 
    True.
189
 
    """
190
 
 
191
 
    def __init__(self, host="127.0.0.1", port=8888, docroot=None,
192
 
                 urlhandlers=None, proxy_host_dirs=False, log_requests=False):
193
 
        self.host = host
194
 
        self.port = int(port)
195
 
        self.docroot = docroot
196
 
        if not urlhandlers and not docroot:
197
 
            self.docroot = os.getcwd()
198
 
        self.proxy_host_dirs = proxy_host_dirs
199
 
        self.httpd = None
200
 
        self.urlhandlers = urlhandlers or []
201
 
        self.log_requests = log_requests
202
 
        self.request_log = []
203
 
 
204
 
        class RequestHandlerInstance(RequestHandler):
205
 
            docroot = self.docroot
206
 
            urlhandlers = self.urlhandlers
207
 
            proxy_host_dirs = self.proxy_host_dirs
208
 
            request_log = self.request_log
209
 
            log_requests = self.log_requests
210
 
 
211
 
        self.handler_class = RequestHandlerInstance
212
 
 
213
 
    def start(self, block=False):
214
 
        """
215
 
        Start the server.  If block is True, the call will not return.
216
 
        If block is False, the server will be started on a separate thread that
217
 
        can be terminated by a call to .stop()
218
 
        """
219
 
        self.httpd = EasyServer((self.host, self.port), self.handler_class)
220
 
        if block:
221
 
            self.httpd.serve_forever()
222
 
        else:
223
 
            self.server = threading.Thread(target=self.httpd.serve_forever)
224
 
            self.server.setDaemon(True) # don't hang on exit
225
 
            self.server.start()
226
 
 
227
 
    def stop(self):
228
 
        if self.httpd:
229
 
            ### FIXME: There is no shutdown() method in Python 2.4...
230
 
            try:
231
 
                self.httpd.shutdown()
232
 
            except AttributeError:
233
 
                pass
234
 
        self.httpd = None
235
 
 
236
 
    __del__ = stop
237
 
 
238
 
 
239
 
def main(args=sys.argv[1:]):
240
 
 
241
 
    # parse command line options
242
 
    from optparse import OptionParser
243
 
    parser = OptionParser()
244
 
    parser.add_option('-p', '--port', dest='port',
245
 
                      type="int", default=8888,
246
 
                      help="port to run the server on [DEFAULT: %default]")
247
 
    parser.add_option('-H', '--host', dest='host',
248
 
                      default='127.0.0.1',
249
 
                      help="host [DEFAULT: %default]")
250
 
    parser.add_option('-i', '--external-ip', action="store_true",
251
 
                      dest='external_ip', default=False,
252
 
                      help="find and use external ip for host")
253
 
    parser.add_option('-d', '--docroot', dest='docroot',
254
 
                      default=os.getcwd(),
255
 
                      help="directory to serve files from [DEFAULT: %default]")
256
 
    options, args = parser.parse_args(args)
257
 
    if args:
258
 
        parser.error("mozhttpd does not take any arguments")
259
 
 
260
 
    if options.external_ip:
261
 
        host = iface.get_lan_ip()
262
 
    else:
263
 
        host = options.host
264
 
 
265
 
    # create the server
266
 
    server = MozHttpd(host=host, port=options.port, docroot=options.docroot)
267
 
 
268
 
    print "Serving '%s' at %s:%s" % (server.docroot, server.host, server.port)
269
 
    server.start(block=True)
270
 
 
271
 
if __name__ == '__main__':
272
 
    main()