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