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