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