eggs/mercurial-1.7.3-py2.6-linux-x86_64.egg/mercurial/hgweb/server.py
changeset 69 c6bca38c1cbf
equal deleted inserted replaced
68:5ff1fc726848 69:c6bca38c1cbf
       
     1 # hgweb/server.py - The standalone hg web server.
       
     2 #
       
     3 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
       
     4 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
       
     5 #
       
     6 # This software may be used and distributed according to the terms of the
       
     7 # GNU General Public License version 2 or any later version.
       
     8 
       
     9 import os, sys, errno, urllib, BaseHTTPServer, socket, SocketServer, traceback
       
    10 from mercurial import util, error
       
    11 from mercurial.i18n import _
       
    12 
       
    13 def _splitURI(uri):
       
    14     """ Return path and query splited from uri
       
    15 
       
    16     Just like CGI environment, the path is unquoted, the query is
       
    17     not.
       
    18     """
       
    19     if '?' in uri:
       
    20         path, query = uri.split('?', 1)
       
    21     else:
       
    22         path, query = uri, ''
       
    23     return urllib.unquote(path), query
       
    24 
       
    25 class _error_logger(object):
       
    26     def __init__(self, handler):
       
    27         self.handler = handler
       
    28     def flush(self):
       
    29         pass
       
    30     def write(self, str):
       
    31         self.writelines(str.split('\n'))
       
    32     def writelines(self, seq):
       
    33         for msg in seq:
       
    34             self.handler.log_error("HG error:  %s", msg)
       
    35 
       
    36 class _httprequesthandler(BaseHTTPServer.BaseHTTPRequestHandler):
       
    37 
       
    38     url_scheme = 'http'
       
    39 
       
    40     @staticmethod
       
    41     def preparehttpserver(httpserver, ssl_cert):
       
    42         """Prepare .socket of new HTTPServer instance"""
       
    43         pass
       
    44 
       
    45     def __init__(self, *args, **kargs):
       
    46         self.protocol_version = 'HTTP/1.1'
       
    47         BaseHTTPServer.BaseHTTPRequestHandler.__init__(self, *args, **kargs)
       
    48 
       
    49     def _log_any(self, fp, format, *args):
       
    50         fp.write("%s - - [%s] %s\n" % (self.client_address[0],
       
    51                                        self.log_date_time_string(),
       
    52                                        format % args))
       
    53         fp.flush()
       
    54 
       
    55     def log_error(self, format, *args):
       
    56         self._log_any(self.server.errorlog, format, *args)
       
    57 
       
    58     def log_message(self, format, *args):
       
    59         self._log_any(self.server.accesslog, format, *args)
       
    60 
       
    61     def do_write(self):
       
    62         try:
       
    63             self.do_hgweb()
       
    64         except socket.error, inst:
       
    65             if inst[0] != errno.EPIPE:
       
    66                 raise
       
    67 
       
    68     def do_POST(self):
       
    69         try:
       
    70             self.do_write()
       
    71         except StandardError:
       
    72             self._start_response("500 Internal Server Error", [])
       
    73             self._write("Internal Server Error")
       
    74             tb = "".join(traceback.format_exception(*sys.exc_info()))
       
    75             self.log_error("Exception happened during processing "
       
    76                            "request '%s':\n%s", self.path, tb)
       
    77 
       
    78     def do_GET(self):
       
    79         self.do_POST()
       
    80 
       
    81     def do_hgweb(self):
       
    82         path, query = _splitURI(self.path)
       
    83 
       
    84         env = {}
       
    85         env['GATEWAY_INTERFACE'] = 'CGI/1.1'
       
    86         env['REQUEST_METHOD'] = self.command
       
    87         env['SERVER_NAME'] = self.server.server_name
       
    88         env['SERVER_PORT'] = str(self.server.server_port)
       
    89         env['REQUEST_URI'] = self.path
       
    90         env['SCRIPT_NAME'] = self.server.prefix
       
    91         env['PATH_INFO'] = path[len(self.server.prefix):]
       
    92         env['REMOTE_HOST'] = self.client_address[0]
       
    93         env['REMOTE_ADDR'] = self.client_address[0]
       
    94         if query:
       
    95             env['QUERY_STRING'] = query
       
    96 
       
    97         if self.headers.typeheader is None:
       
    98             env['CONTENT_TYPE'] = self.headers.type
       
    99         else:
       
   100             env['CONTENT_TYPE'] = self.headers.typeheader
       
   101         length = self.headers.getheader('content-length')
       
   102         if length:
       
   103             env['CONTENT_LENGTH'] = length
       
   104         for header in [h for h in self.headers.keys()
       
   105                        if h not in ('content-type', 'content-length')]:
       
   106             hkey = 'HTTP_' + header.replace('-', '_').upper()
       
   107             hval = self.headers.getheader(header)
       
   108             hval = hval.replace('\n', '').strip()
       
   109             if hval:
       
   110                 env[hkey] = hval
       
   111         env['SERVER_PROTOCOL'] = self.request_version
       
   112         env['wsgi.version'] = (1, 0)
       
   113         env['wsgi.url_scheme'] = self.url_scheme
       
   114         env['wsgi.input'] = self.rfile
       
   115         env['wsgi.errors'] = _error_logger(self)
       
   116         env['wsgi.multithread'] = isinstance(self.server,
       
   117                                              SocketServer.ThreadingMixIn)
       
   118         env['wsgi.multiprocess'] = isinstance(self.server,
       
   119                                               SocketServer.ForkingMixIn)
       
   120         env['wsgi.run_once'] = 0
       
   121 
       
   122         self.close_connection = True
       
   123         self.saved_status = None
       
   124         self.saved_headers = []
       
   125         self.sent_headers = False
       
   126         self.length = None
       
   127         for chunk in self.server.application(env, self._start_response):
       
   128             self._write(chunk)
       
   129 
       
   130     def send_headers(self):
       
   131         if not self.saved_status:
       
   132             raise AssertionError("Sending headers before "
       
   133                                  "start_response() called")
       
   134         saved_status = self.saved_status.split(None, 1)
       
   135         saved_status[0] = int(saved_status[0])
       
   136         self.send_response(*saved_status)
       
   137         should_close = True
       
   138         for h in self.saved_headers:
       
   139             self.send_header(*h)
       
   140             if h[0].lower() == 'content-length':
       
   141                 should_close = False
       
   142                 self.length = int(h[1])
       
   143         # The value of the Connection header is a list of case-insensitive
       
   144         # tokens separated by commas and optional whitespace.
       
   145         if 'close' in [token.strip().lower() for token in
       
   146                        self.headers.get('connection', '').split(',')]:
       
   147             should_close = True
       
   148         if should_close:
       
   149             self.send_header('Connection', 'close')
       
   150         self.close_connection = should_close
       
   151         self.end_headers()
       
   152         self.sent_headers = True
       
   153 
       
   154     def _start_response(self, http_status, headers, exc_info=None):
       
   155         code, msg = http_status.split(None, 1)
       
   156         code = int(code)
       
   157         self.saved_status = http_status
       
   158         bad_headers = ('connection', 'transfer-encoding')
       
   159         self.saved_headers = [h for h in headers
       
   160                               if h[0].lower() not in bad_headers]
       
   161         return self._write
       
   162 
       
   163     def _write(self, data):
       
   164         if not self.saved_status:
       
   165             raise AssertionError("data written before start_response() called")
       
   166         elif not self.sent_headers:
       
   167             self.send_headers()
       
   168         if self.length is not None:
       
   169             if len(data) > self.length:
       
   170                 raise AssertionError("Content-length header sent, but more "
       
   171                                      "bytes than specified are being written.")
       
   172             self.length = self.length - len(data)
       
   173         self.wfile.write(data)
       
   174         self.wfile.flush()
       
   175 
       
   176 class _httprequesthandleropenssl(_httprequesthandler):
       
   177     """HTTPS handler based on pyOpenSSL"""
       
   178 
       
   179     url_scheme = 'https'
       
   180 
       
   181     @staticmethod
       
   182     def preparehttpserver(httpserver, ssl_cert):
       
   183         try:
       
   184             import OpenSSL
       
   185             OpenSSL.SSL.Context
       
   186         except ImportError:
       
   187             raise util.Abort(_("SSL support is unavailable"))
       
   188         ctx = OpenSSL.SSL.Context(OpenSSL.SSL.SSLv23_METHOD)
       
   189         ctx.use_privatekey_file(ssl_cert)
       
   190         ctx.use_certificate_file(ssl_cert)
       
   191         sock = socket.socket(httpserver.address_family, httpserver.socket_type)
       
   192         httpserver.socket = OpenSSL.SSL.Connection(ctx, sock)
       
   193         httpserver.server_bind()
       
   194         httpserver.server_activate()
       
   195 
       
   196     def setup(self):
       
   197         self.connection = self.request
       
   198         self.rfile = socket._fileobject(self.request, "rb", self.rbufsize)
       
   199         self.wfile = socket._fileobject(self.request, "wb", self.wbufsize)
       
   200 
       
   201     def do_write(self):
       
   202         import OpenSSL
       
   203         try:
       
   204             _httprequesthandler.do_write(self)
       
   205         except OpenSSL.SSL.SysCallError, inst:
       
   206             if inst.args[0] != errno.EPIPE:
       
   207                 raise
       
   208 
       
   209     def handle_one_request(self):
       
   210         import OpenSSL
       
   211         try:
       
   212             _httprequesthandler.handle_one_request(self)
       
   213         except (OpenSSL.SSL.SysCallError, OpenSSL.SSL.ZeroReturnError):
       
   214             self.close_connection = True
       
   215             pass
       
   216 
       
   217 class _httprequesthandlerssl(_httprequesthandler):
       
   218     """HTTPS handler based on Pythons ssl module (introduced in 2.6)"""
       
   219 
       
   220     url_scheme = 'https'
       
   221 
       
   222     @staticmethod
       
   223     def preparehttpserver(httpserver, ssl_cert):
       
   224         try:
       
   225             import ssl
       
   226             ssl.wrap_socket
       
   227         except ImportError:
       
   228             raise util.Abort(_("SSL support is unavailable"))
       
   229         httpserver.socket = ssl.wrap_socket(httpserver.socket, server_side=True,
       
   230             certfile=ssl_cert, ssl_version=ssl.PROTOCOL_SSLv23)
       
   231 
       
   232     def setup(self):
       
   233         self.connection = self.request
       
   234         self.rfile = socket._fileobject(self.request, "rb", self.rbufsize)
       
   235         self.wfile = socket._fileobject(self.request, "wb", self.wbufsize)
       
   236 
       
   237 try:
       
   238     from threading import activeCount
       
   239     _mixin = SocketServer.ThreadingMixIn
       
   240 except ImportError:
       
   241     if hasattr(os, "fork"):
       
   242         _mixin = SocketServer.ForkingMixIn
       
   243     else:
       
   244         class _mixin:
       
   245             pass
       
   246 
       
   247 def openlog(opt, default):
       
   248     if opt and opt != '-':
       
   249         return open(opt, 'a')
       
   250     return default
       
   251 
       
   252 class MercurialHTTPServer(object, _mixin, BaseHTTPServer.HTTPServer):
       
   253 
       
   254     # SO_REUSEADDR has broken semantics on windows
       
   255     if os.name == 'nt':
       
   256         allow_reuse_address = 0
       
   257 
       
   258     def __init__(self, ui, app, addr, handler, **kwargs):
       
   259         BaseHTTPServer.HTTPServer.__init__(self, addr, handler, **kwargs)
       
   260         self.daemon_threads = True
       
   261         self.application = app
       
   262 
       
   263         handler.preparehttpserver(self, ui.config('web', 'certificate'))
       
   264 
       
   265         prefix = ui.config('web', 'prefix', '')
       
   266         if prefix:
       
   267             prefix = '/' + prefix.strip('/')
       
   268         self.prefix = prefix
       
   269 
       
   270         alog = openlog(ui.config('web', 'accesslog', '-'), sys.stdout)
       
   271         elog = openlog(ui.config('web', 'errorlog', '-'), sys.stderr)
       
   272         self.accesslog = alog
       
   273         self.errorlog = elog
       
   274 
       
   275         self.addr, self.port = self.socket.getsockname()[0:2]
       
   276         self.fqaddr = socket.getfqdn(addr[0])
       
   277 
       
   278 class IPv6HTTPServer(MercurialHTTPServer):
       
   279     address_family = getattr(socket, 'AF_INET6', None)
       
   280     def __init__(self, *args, **kwargs):
       
   281         if self.address_family is None:
       
   282             raise error.RepoError(_('IPv6 is not available on this system'))
       
   283         super(IPv6HTTPServer, self).__init__(*args, **kwargs)
       
   284 
       
   285 def create_server(ui, app):
       
   286 
       
   287     if ui.config('web', 'certificate'):
       
   288         if sys.version_info >= (2, 6):
       
   289             handler = _httprequesthandlerssl
       
   290         else:
       
   291             handler = _httprequesthandleropenssl
       
   292     else:
       
   293         handler = _httprequesthandler
       
   294 
       
   295     if ui.configbool('web', 'ipv6'):
       
   296         cls = IPv6HTTPServer
       
   297     else:
       
   298         cls = MercurialHTTPServer
       
   299 
       
   300     # ugly hack due to python issue5853 (for threaded use)
       
   301     import mimetypes; mimetypes.init()
       
   302 
       
   303     address = ui.config('web', 'address', '')
       
   304     port = util.getport(ui.config('web', 'port', 8000))
       
   305     try:
       
   306         return cls(ui, app, (address, port), handler)
       
   307     except socket.error, inst:
       
   308         raise util.Abort(_("cannot start server at '%s:%d': %s")
       
   309                          % (address, port, inst.args[1]))