##// END OF EJS Templates
dispatch: protect against malicious 'hg serve --stdio' invocations (sec)...
dispatch: protect against malicious 'hg serve --stdio' invocations (sec) Some shared-ssh installations assume that 'hg serve --stdio' is a safe command to run for minimally trusted users. Unfortunately, the messy implementation of argument parsing here meant that trying to access a repo named '--debugger' would give the user a pdb prompt, thereby sidestepping any hoped-for sandboxing. Serving repositories over HTTP(S) is unaffected. We're not currently hardening any subcommands other than 'serve'. If your service exposes other commands to users with arbitrary repository names, it is imperative that you defend against repository names of '--debugger' and anything starting with '--config'. The read-only mode of hg-ssh stopped working because it provided its hook configuration to "hg serve --stdio" via --config parameter. This is banned for security reasons now. This patch switches it to directly call ui.setconfig(). If your custom hosting infrastructure relies on passing --config to "hg serve --stdio", you'll need to find a different way to get that configuration into Mercurial, either by using ui.setconfig() as hg-ssh does in this patch, or by placing an hgrc file someplace where Mercurial will read it. mitrandir@fb.com provided some extra fixes for the dispatch code and for hg-ssh in places that I overlooked.

File last commit:

r30639:d524c885 default
r32050:77eaf953 4.1.3 stable
Show More
server.py
335 lines | 11.3 KiB | text/x-python | PythonLexer
Eric Hopper
Fixing up comment headers for split up code.
r2391 # hgweb/server.py - The standalone hg web server.
Eric Hopper
Splitting up hgweb so it's easier to change.
r2355 #
# Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
Thomas Arendsen Hein
Updated copyright notices and add "and others" to "hg version"
r4635 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
Eric Hopper
Splitting up hgweb so it's easier to change.
r2355 #
Martin Geisler
updated license to be explicit about GPL version 2
r8225 # This software may be used and distributed according to the terms of the
Matt Mackall
Update license to GPLv2+
r10263 # GNU General Public License version 2 or any later version.
Eric Hopper
Splitting up hgweb so it's easier to change.
r2355
Yuya Nishihara
hgweb: use absolute_import
r27046 from __future__ import absolute_import
import errno
import os
import socket
import sys
import traceback
from ..i18n import _
from .. import (
error,
Pulkit Goyal
py3: replace os.name with pycompat.osname (part 1 of 2)...
r30639 pycompat,
Yuya Nishihara
hgweb: use absolute_import
r27046 util,
)
Pulkit Goyal
py3: conditionalize BaseHTTPServer, SimpleHTTPServer and CGIHTTPServer import...
r29566 httpservermod = util.httpserver
Pulkit Goyal
py3: conditionalize SocketServer import...
r29433 socketserver = util.socketserver
timeless
pycompat: switch to util.urlreq/util.urlerr for py3 compat
r28883 urlerr = util.urlerr
urlreq = util.urlreq
Yuya Nishihara
hgweb: use absolute_import
r27046 from . import (
common,
)
Eric Hopper
Splitting up hgweb so it's easier to change.
r2355
def _splitURI(uri):
Mads Kiilerich
improve some comments and docstrings, fixing issues found when spell checking
r17427 """Return path and query that has been split from uri
Eric Hopper
Splitting up hgweb so it's easier to change.
r2355
Just like CGI environment, the path is unquoted, the query is
not.
"""
if '?' in uri:
path, query = uri.split('?', 1)
else:
path, query = uri, ''
timeless
pycompat: switch to util.urlreq/util.urlerr for py3 compat
r28883 return urlreq.unquote(path), query
Eric Hopper
Splitting up hgweb so it's easier to change.
r2355
Eric Hopper
This patch make several WSGI related alterations....
r2506 class _error_logger(object):
def __init__(self, handler):
self.handler = handler
def flush(self):
pass
Benoit Boissinot
hgweb: fix errors spotted by pychecker
r3130 def write(self, str):
Eric Hopper
This patch make several WSGI related alterations....
r2506 self.writelines(str.split('\n'))
Benoit Boissinot
hgweb: fix errors spotted by pychecker
r3130 def writelines(self, seq):
Eric Hopper
This patch make several WSGI related alterations....
r2506 for msg in seq:
self.handler.log_error("HG error: %s", msg)
Pulkit Goyal
py3: conditionalize BaseHTTPServer, SimpleHTTPServer and CGIHTTPServer import...
r29566 class _httprequesthandler(httpservermod.basehttprequesthandler):
Wesley J. Landaker
Make hg serve set the wsgi.url_scheme property correctly....
r4870
url_scheme = 'http'
Thomas Arendsen Hein
removed trailing whitespace
r4957
Mads Kiilerich
hgweb: refactor all pyOpenSSL references into one class
r12783 @staticmethod
Gregory Szorc
hgweb: pass ui into preparehttpserver...
r29553 def preparehttpserver(httpserver, ui):
Mads Kiilerich
hgweb: refactor all pyOpenSSL references into one class
r12783 """Prepare .socket of new HTTPServer instance"""
pass
Eric Hopper
Splitting up hgweb so it's easier to change.
r2355 def __init__(self, *args, **kargs):
Vadim Gelfer
http server: support persistent connections....
r2434 self.protocol_version = 'HTTP/1.1'
Pulkit Goyal
py3: conditionalize BaseHTTPServer, SimpleHTTPServer and CGIHTTPServer import...
r29566 httpservermod.basehttprequesthandler.__init__(self, *args, **kargs)
Eric Hopper
Splitting up hgweb so it's easier to change.
r2355
Patrick Mezard
hgweb.server: flush log files after every access
r5549 def _log_any(self, fp, format, *args):
fp.write("%s - - [%s] %s\n" % (self.client_address[0],
self.log_date_time_string(),
format % args))
fp.flush()
Eric Hopper
Splitting up hgweb so it's easier to change.
r2355 def log_error(self, format, *args):
Patrick Mezard
hgweb.server: flush log files after every access
r5549 self._log_any(self.server.errorlog, format, *args)
Eric Hopper
Splitting up hgweb so it's easier to change.
r2355
def log_message(self, format, *args):
Patrick Mezard
hgweb.server: flush log files after every access
r5549 self._log_any(self.server.accesslog, format, *args)
Eric Hopper
Splitting up hgweb so it's easier to change.
r2355
Steven Brown
httprepo: long arguments support (issue2126)...
r14093 def log_request(self, code='-', size='-'):
David Soria Parra
hgweb: log headers only if headers were successfully parsed...
r19877 xheaders = []
if util.safehasattr(self, 'headers'):
xheaders = [h for h in self.headers.items()
if h[0].startswith('x-')]
Steven Brown
httprepo: long arguments support (issue2126)...
r14093 self.log_message('"%s" %s %s%s',
self.requestline, str(code), str(size),
''.join([' %s:%s' % h for h in sorted(xheaders)]))
Brendan Cully
Add SSL support to hg serve, activated via --certificate option
r4860 def do_write(self):
try:
self.do_hgweb()
Gregory Szorc
global: mass rewrite to use modern exception syntax...
r25660 except socket.error as inst:
Brendan Cully
Add SSL support to hg serve, activated via --certificate option
r4860 if inst[0] != errno.EPIPE:
raise
Eric Hopper
Splitting up hgweb so it's easier to change.
r2355 def do_POST(self):
try:
Brendan Cully
Add SSL support to hg serve, activated via --certificate option
r4860 self.do_write()
Mads Kiilerich
serve: catch and log all Exceptions, not only StandardException...
r13443 except Exception:
Thomas Arendsen Hein
Handle exceptions in do_hgweb: Send "Internal Server Error", log traceback
r4015 self._start_response("500 Internal Server Error", [])
self._write("Internal Server Error")
Gregory Szorc
hgweb: send proper HTTP response after uncaught exception...
r23409 self._done()
Thomas Arendsen Hein
Handle exceptions in do_hgweb: Send "Internal Server Error", log traceback
r4015 tb = "".join(traceback.format_exception(*sys.exc_info()))
Martin Geisler
wrap string literals in error messages
r8663 self.log_error("Exception happened during processing "
"request '%s':\n%s", self.path, tb)
Eric Hopper
Splitting up hgweb so it's easier to change.
r2355
def do_GET(self):
self.do_POST()
def do_hgweb(self):
Michele Cella
adding "prefix" option to "hg serve" (command line and [web] section)...
r5835 path, query = _splitURI(self.path)
Eric Hopper
Splitting up hgweb so it's easier to change.
r2355
env = {}
env['GATEWAY_INTERFACE'] = 'CGI/1.1'
env['REQUEST_METHOD'] = self.command
env['SERVER_NAME'] = self.server.server_name
env['SERVER_PORT'] = str(self.server.server_port)
Brendan Cully
hgweb: support for generating and parsing NWI URLs
r3263 env['REQUEST_URI'] = self.path
Michele Cella
adding "prefix" option to "hg serve" (command line and [web] section)...
r5835 env['SCRIPT_NAME'] = self.server.prefix
env['PATH_INFO'] = path[len(self.server.prefix):]
Matt Mackall
hg serve: don't do DNS lookups
r4359 env['REMOTE_HOST'] = self.client_address[0]
env['REMOTE_ADDR'] = self.client_address[0]
Eric Hopper
Splitting up hgweb so it's easier to change.
r2355 if query:
env['QUERY_STRING'] = query
if self.headers.typeheader is None:
env['CONTENT_TYPE'] = self.headers.type
else:
env['CONTENT_TYPE'] = self.headers.typeheader
length = self.headers.getheader('content-length')
if length:
env['CONTENT_LENGTH'] = length
Thomas Arendsen Hein
Cleanup of whitespace, indentation and line continuation.
r4633 for header in [h for h in self.headers.keys()
Eric Hopper
Fix server to set up a more WSGI compliant environment.
r2505 if h not in ('content-type', 'content-length')]:
hkey = 'HTTP_' + header.replace('-', '_').upper()
hval = self.headers.getheader(header)
hval = hval.replace('\n', '').strip()
if hval:
env[hkey] = hval
env['SERVER_PROTOCOL'] = self.request_version
Eric Hopper
This patch make several WSGI related alterations....
r2506 env['wsgi.version'] = (1, 0)
Brendan Cully
cosmetics
r4871 env['wsgi.url_scheme'] = self.url_scheme
Augie Fackler
hgweb: add support for 100-continue as recommended by PEP 333.
r13570 if env.get('HTTP_EXPECT', '').lower() == '100-continue':
self.rfile = common.continuereader(self.rfile, self.wfile.write)
Eric Hopper
This patch make several WSGI related alterations....
r2506 env['wsgi.input'] = self.rfile
env['wsgi.errors'] = _error_logger(self)
env['wsgi.multithread'] = isinstance(self.server,
Pulkit Goyal
py3: conditionalize SocketServer import...
r29433 socketserver.ThreadingMixIn)
Eric Hopper
This patch make several WSGI related alterations....
r2506 env['wsgi.multiprocess'] = isinstance(self.server,
Pulkit Goyal
py3: conditionalize SocketServer import...
r29433 socketserver.ForkingMixIn)
Eric Hopper
This patch make several WSGI related alterations....
r2506 env['wsgi.run_once'] = 0
Eric Hopper
Splitting up hgweb so it's easier to change.
r2355
Eric Hopper
This patch make several WSGI related alterations....
r2506 self.saved_status = None
self.saved_headers = []
self.sent_headers = False
Eric Hopper
Put support for persistent connections back in.
r2508 self.length = None
Mads Kiilerich
serve: use chunked encoding in hgweb responses...
r18354 self._chunked = None
Dirkjan Ochtman
hgweb: all protocol functions have become generators...
r6784 for chunk in self.server.application(env, self._start_response):
self._write(chunk)
Mads Kiilerich
serve: send response headers even if response has no body...
r18349 if not self.sent_headers:
self.send_headers()
Mads Kiilerich
serve: use chunked encoding in hgweb responses...
r18354 self._done()
Eric Hopper
This patch make several WSGI related alterations....
r2506
def send_headers(self):
if not self.saved_status:
Martin Geisler
wrap string literals in error messages
r8663 raise AssertionError("Sending headers before "
"start_response() called")
Eric Hopper
This patch make several WSGI related alterations....
r2506 saved_status = self.saved_status.split(None, 1)
saved_status[0] = int(saved_status[0])
self.send_response(*saved_status)
Mads Kiilerich
serve: use chunked encoding in hgweb responses...
r18354 self.length = None
self._chunked = False
Eric Hopper
This patch make several WSGI related alterations....
r2506 for h in self.saved_headers:
self.send_header(*h)
Eric Hopper
Put support for persistent connections back in.
r2508 if h[0].lower() == 'content-length':
self.length = int(h[1])
Mads Kiilerich
serve: don't send any content headers with 304 responses...
r18380 if (self.length is None and
saved_status[0] != common.HTTP_NOT_MODIFIED):
Mads Kiilerich
serve: use chunked encoding in hgweb responses...
r18354 self._chunked = (not self.close_connection and
self.request_version == "HTTP/1.1")
if self._chunked:
self.send_header('Transfer-Encoding', 'chunked')
else:
self.send_header('Connection', 'close')
Eric Hopper
This patch make several WSGI related alterations....
r2506 self.end_headers()
self.sent_headers = True
def _start_response(self, http_status, headers, exc_info=None):
code, msg = http_status.split(None, 1)
code = int(code)
self.saved_status = http_status
Eric Hopper
Put support for persistent connections back in.
r2508 bad_headers = ('connection', 'transfer-encoding')
Thomas Arendsen Hein
Cleanup of whitespace, indentation and line continuation.
r4633 self.saved_headers = [h for h in headers
if h[0].lower() not in bad_headers]
Eric Hopper
This patch make several WSGI related alterations....
r2506 return self._write
def _write(self, data):
if not self.saved_status:
raise AssertionError("data written before start_response() called")
elif not self.sent_headers:
self.send_headers()
Eric Hopper
Put support for persistent connections back in.
r2508 if self.length is not None:
if len(data) > self.length:
Martin Geisler
wrap string literals in error messages
r8663 raise AssertionError("Content-length header sent, but more "
"bytes than specified are being written.")
Eric Hopper
Put support for persistent connections back in.
r2508 self.length = self.length - len(data)
Mads Kiilerich
serve: use chunked encoding in hgweb responses...
r18354 elif self._chunked and data:
data = '%x\r\n%s\r\n' % (len(data), data)
Eric Hopper
This patch make several WSGI related alterations....
r2506 self.wfile.write(data)
self.wfile.flush()
Eric Hopper
Splitting up hgweb so it's easier to change.
r2355
Mads Kiilerich
serve: use chunked encoding in hgweb responses...
r18354 def _done(self):
if self._chunked:
self.wfile.write('0\r\n\r\n')
self.wfile.flush()
Mads Kiilerich
hgweb: use Pythons ssl module for HTTPS serve when using Python 2.6 or later...
r12784 class _httprequesthandlerssl(_httprequesthandler):
timeless@mozdev.org
hgweb.server: fix _httprequesthandlerssl help text
r26202 """HTTPS handler based on Python's ssl module"""
Mads Kiilerich
hgweb: use Pythons ssl module for HTTPS serve when using Python 2.6 or later...
r12784
url_scheme = 'https'
@staticmethod
Gregory Szorc
hgweb: pass ui into preparehttpserver...
r29553 def preparehttpserver(httpserver, ui):
Mads Kiilerich
hgweb: use Pythons ssl module for HTTPS serve when using Python 2.6 or later...
r12784 try:
Gregory Szorc
hgweb: use sslutil.wrapserversocket()...
r29555 from .. import sslutil
sslutil.modernssl
Mads Kiilerich
hgweb: use Pythons ssl module for HTTPS serve when using Python 2.6 or later...
r12784 except ImportError:
Pierre-Yves David
error: get Abort from 'error' instead of 'util'...
r26587 raise error.Abort(_("SSL support is unavailable"))
Gregory Szorc
hgweb: pass ui into preparehttpserver...
r29553
certfile = ui.config('web', 'certificate')
Gregory Szorc
hgweb: use sslutil.wrapserversocket()...
r29555
# These config options are currently only meant for testing. Use
# at your own risk.
cafile = ui.config('devel', 'servercafile')
reqcert = ui.configbool('devel', 'serverrequirecert')
httpserver.socket = sslutil.wrapserversocket(httpserver.socket,
ui,
certfile=certfile,
cafile=cafile,
requireclientcert=reqcert)
Mads Kiilerich
hgweb: use Pythons ssl module for HTTPS serve when using Python 2.6 or later...
r12784
def setup(self):
self.connection = self.request
self.rfile = socket._fileobject(self.request, "rb", self.rbufsize)
self.wfile = socket._fileobject(self.request, "wb", self.wbufsize)
Dirkjan Ochtman
server: externalize and streamline mixin setup
r10639 try:
Yuya Nishihara
hgweb: use absolute_import
r27046 import threading
threading.activeCount() # silence pyflakes and bypass demandimport
Pulkit Goyal
py3: conditionalize SocketServer import...
r29433 _mixin = socketserver.ThreadingMixIn
Dirkjan Ochtman
server: externalize and streamline mixin setup
r10639 except ImportError:
Augie Fackler
hgweb: move remaining hasattr calls to safehasattr
r14957 if util.safehasattr(os, "fork"):
Pulkit Goyal
py3: conditionalize SocketServer import...
r29433 _mixin = socketserver.ForkingMixIn
Dirkjan Ochtman
server: externalize and streamline mixin setup
r10639 else:
Thomas Arendsen Hein
classes: fix class style problems found by b071cd58af50...
r14764 class _mixin(object):
Dirkjan Ochtman
server: externalize and streamline mixin setup
r10639 pass
Dirkjan Ochtman
server: unnest server classes into module namespace
r10643 def openlog(opt, default):
if opt and opt != '-':
return open(opt, 'a')
return default
Martijn Pieters
hgweb: fix the MRO in Python 3...
r30082 class MercurialHTTPServer(_mixin, httpservermod.httpserver, object):
Dirkjan Ochtman
server: unnest server classes into module namespace
r10643
# SO_REUSEADDR has broken semantics on windows
Pulkit Goyal
py3: replace os.name with pycompat.osname (part 1 of 2)...
r30639 if pycompat.osname == 'nt':
Dirkjan Ochtman
server: unnest server classes into module namespace
r10643 allow_reuse_address = 0
def __init__(self, ui, app, addr, handler, **kwargs):
Pulkit Goyal
py3: conditionalize BaseHTTPServer, SimpleHTTPServer and CGIHTTPServer import...
r29566 httpservermod.httpserver.__init__(self, addr, handler, **kwargs)
Dirkjan Ochtman
server: unnest server classes into module namespace
r10643 self.daemon_threads = True
self.application = app
Eric Hopper
Splitting up hgweb so it's easier to change.
r2355
Gregory Szorc
hgweb: pass ui into preparehttpserver...
r29553 handler.preparehttpserver(self, ui)
Dirkjan Ochtman
server: unnest server classes into module namespace
r10643
prefix = ui.config('web', 'prefix', '')
if prefix:
prefix = '/' + prefix.strip('/')
self.prefix = prefix
Yuya Nishihara
hgweb: make log streams compatible with command server...
r30264 alog = openlog(ui.config('web', 'accesslog', '-'), ui.fout)
elog = openlog(ui.config('web', 'errorlog', '-'), ui.ferr)
Dirkjan Ochtman
server: unnest server classes into module namespace
r10643 self.accesslog = alog
self.errorlog = elog
self.addr, self.port = self.socket.getsockname()[0:2]
self.fqaddr = socket.getfqdn(addr[0])
class IPv6HTTPServer(MercurialHTTPServer):
address_family = getattr(socket, 'AF_INET6', None)
def __init__(self, *args, **kwargs):
if self.address_family is None:
raise error.RepoError(_('IPv6 is not available on this system'))
super(IPv6HTTPServer, self).__init__(*args, **kwargs)
Dirkjan Ochtman
server: initialize wsgi app in command, then wrap server around it
r10644 def create_server(ui, app):
Eric Hopper
Splitting up hgweb so it's easier to change.
r2355
Dirkjan Ochtman
server: initialize wsgi app in command, then wrap server around it
r10644 if ui.config('web', 'certificate'):
Siddharth Agarwal
hgweb.server: drop support for Python 2.4
r26848 handler = _httprequesthandlerssl
Brendan Cully
Add SSL support to hg serve, activated via --certificate option
r4860 else:
Mads Kiilerich
hgweb: refactor all pyOpenSSL references into one class
r12783 handler = _httprequesthandler
Brendan Cully
Add SSL support to hg serve, activated via --certificate option
r4860
Dirkjan Ochtman
server: initialize wsgi app in command, then wrap server around it
r10644 if ui.configbool('web', 'ipv6'):
Dirkjan Ochtman
server: abstract setup of ipv6 vs. normal server
r10641 cls = IPv6HTTPServer
else:
cls = MercurialHTTPServer
Dirkjan Ochtman
hgweb: pre-init mimetypes module (fixes ugly bug in python-2.6.2 mimetypes)...
r8224 # ugly hack due to python issue5853 (for threaded use)
Matt Mackall
hgweb: hack around mimetypes encoding thinko (issue4160)...
r20357 try:
import mimetypes
mimetypes.init()
except UnicodeDecodeError:
# Python 2.x's mimetypes module attempts to decode strings
# from Windows' ANSI APIs as ascii (fail), then re-encode them
# as ascii (clown fail), because the default Python Unicode
# codec is hardcoded as ascii.
Yuya Nishihara
hgweb: make sure sys module is loaded prior to reload hack...
r20529 sys.argv # unwrap demand-loader so that reload() works
Matt Mackall
hgweb: hack around mimetypes encoding thinko (issue4160)...
r20357 reload(sys) # resurrect sys.setdefaultencoding()
oldenc = sys.getdefaultencoding()
sys.setdefaultencoding("latin1") # or any full 8-bit encoding
mimetypes.init()
sys.setdefaultencoding(oldenc)
Dirkjan Ochtman
hgweb: pre-init mimetypes module (fixes ugly bug in python-2.6.2 mimetypes)...
r8224
Dirkjan Ochtman
server: initialize wsgi app in command, then wrap server around it
r10644 address = ui.config('web', 'address', '')
Brodie Rao
mail/hgweb: support service names for ports (issue2350)...
r12076 port = util.getport(ui.config('web', 'port', 8000))
Matt Mackall
hgweb: internalize some socket details
r3628 try:
Dirkjan Ochtman
server: initialize wsgi app in command, then wrap server around it
r10644 return cls(ui, app, (address, port), handler)
Gregory Szorc
global: mass rewrite to use modern exception syntax...
r25660 except socket.error as inst:
Pierre-Yves David
error: get Abort from 'error' instead of 'util'...
r26587 raise error.Abort(_("cannot start server at '%s:%d': %s")
Stephen Deasey
hgweb: clarify which address and port can/cannot be bound at startup (bug 769)...
r6262 % (address, port, inst.args[1]))