##// END OF EJS Templates
hgweb: remove dead code handling UnicodeDecodeError...
Manuel Jacob -
r50178:44b26349 default
parent child Browse files
Show More
@@ -1,436 +1,418 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 Olivia Mackall <olivia@selenic.com>
4 # Copyright 2005-2007 Olivia Mackall <olivia@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
9
10 import errno
10 import errno
11 import importlib
12 import os
11 import os
13 import socket
12 import socket
14 import sys
13 import sys
15 import traceback
14 import traceback
16 import wsgiref.validate
15 import wsgiref.validate
17
16
18 from ..i18n import _
17 from ..i18n import _
19 from ..pycompat import (
18 from ..pycompat import (
20 getattr,
19 getattr,
21 open,
20 open,
22 )
21 )
23
22
24 from .. import (
23 from .. import (
25 encoding,
24 encoding,
26 error,
25 error,
27 pycompat,
26 pycompat,
28 util,
27 util,
29 )
28 )
30 from ..utils import (
29 from ..utils import (
31 urlutil,
30 urlutil,
32 )
31 )
33
32
34 httpservermod = util.httpserver
33 httpservermod = util.httpserver
35 socketserver = util.socketserver
34 socketserver = util.socketserver
36 urlerr = util.urlerr
35 urlerr = util.urlerr
37 urlreq = util.urlreq
36 urlreq = util.urlreq
38
37
39 from . import common
38 from . import common
40
39
41
40
42 def _splitURI(uri):
41 def _splitURI(uri):
43 """Return path and query that has been split from uri
42 """Return path and query that has been split from uri
44
43
45 Just like CGI environment, the path is unquoted, the query is
44 Just like CGI environment, the path is unquoted, the query is
46 not.
45 not.
47 """
46 """
48 if '?' in uri:
47 if '?' in uri:
49 path, query = uri.split('?', 1)
48 path, query = uri.split('?', 1)
50 else:
49 else:
51 path, query = uri, r''
50 path, query = uri, r''
52 return urlreq.unquote(path), query
51 return urlreq.unquote(path), query
53
52
54
53
55 class _error_logger:
54 class _error_logger:
56 def __init__(self, handler):
55 def __init__(self, handler):
57 self.handler = handler
56 self.handler = handler
58
57
59 def flush(self):
58 def flush(self):
60 pass
59 pass
61
60
62 def write(self, str):
61 def write(self, str):
63 self.writelines(str.split(b'\n'))
62 self.writelines(str.split(b'\n'))
64
63
65 def writelines(self, seq):
64 def writelines(self, seq):
66 for msg in seq:
65 for msg in seq:
67 self.handler.log_error("HG error: %s", encoding.strfromlocal(msg))
66 self.handler.log_error("HG error: %s", encoding.strfromlocal(msg))
68
67
69
68
70 class _httprequesthandler(httpservermod.basehttprequesthandler):
69 class _httprequesthandler(httpservermod.basehttprequesthandler):
71
70
72 url_scheme = b'http'
71 url_scheme = b'http'
73
72
74 @staticmethod
73 @staticmethod
75 def preparehttpserver(httpserver, ui):
74 def preparehttpserver(httpserver, ui):
76 """Prepare .socket of new HTTPServer instance"""
75 """Prepare .socket of new HTTPServer instance"""
77
76
78 def __init__(self, *args, **kargs):
77 def __init__(self, *args, **kargs):
79 self.protocol_version = r'HTTP/1.1'
78 self.protocol_version = r'HTTP/1.1'
80 httpservermod.basehttprequesthandler.__init__(self, *args, **kargs)
79 httpservermod.basehttprequesthandler.__init__(self, *args, **kargs)
81
80
82 def _log_any(self, fp, format, *args):
81 def _log_any(self, fp, format, *args):
83 fp.write(
82 fp.write(
84 pycompat.sysbytes(
83 pycompat.sysbytes(
85 r"%s - - [%s] %s"
84 r"%s - - [%s] %s"
86 % (
85 % (
87 self.client_address[0],
86 self.client_address[0],
88 self.log_date_time_string(),
87 self.log_date_time_string(),
89 format % args,
88 format % args,
90 )
89 )
91 )
90 )
92 + b'\n'
91 + b'\n'
93 )
92 )
94 fp.flush()
93 fp.flush()
95
94
96 def log_error(self, format, *args):
95 def log_error(self, format, *args):
97 self._log_any(self.server.errorlog, format, *args)
96 self._log_any(self.server.errorlog, format, *args)
98
97
99 def log_message(self, format, *args):
98 def log_message(self, format, *args):
100 self._log_any(self.server.accesslog, format, *args)
99 self._log_any(self.server.accesslog, format, *args)
101
100
102 def log_request(self, code='-', size='-'):
101 def log_request(self, code='-', size='-'):
103 xheaders = []
102 xheaders = []
104 if util.safehasattr(self, b'headers'):
103 if util.safehasattr(self, b'headers'):
105 xheaders = [
104 xheaders = [
106 h for h in self.headers.items() if h[0].startswith('x-')
105 h for h in self.headers.items() if h[0].startswith('x-')
107 ]
106 ]
108 self.log_message(
107 self.log_message(
109 '"%s" %s %s%s',
108 '"%s" %s %s%s',
110 self.requestline,
109 self.requestline,
111 str(code),
110 str(code),
112 str(size),
111 str(size),
113 ''.join([' %s:%s' % h for h in sorted(xheaders)]),
112 ''.join([' %s:%s' % h for h in sorted(xheaders)]),
114 )
113 )
115
114
116 def do_write(self):
115 def do_write(self):
117 try:
116 try:
118 self.do_hgweb()
117 self.do_hgweb()
119 except socket.error as inst:
118 except socket.error as inst:
120 if inst.errno != errno.EPIPE:
119 if inst.errno != errno.EPIPE:
121 raise
120 raise
122
121
123 def do_POST(self):
122 def do_POST(self):
124 try:
123 try:
125 self.do_write()
124 self.do_write()
126 except Exception as e:
125 except Exception as e:
127 # I/O below could raise another exception. So log the original
126 # I/O below could raise another exception. So log the original
128 # exception first to ensure it is recorded.
127 # exception first to ensure it is recorded.
129 if not (
128 if not (
130 isinstance(e, (OSError, socket.error))
129 isinstance(e, (OSError, socket.error))
131 and e.errno == errno.ECONNRESET
130 and e.errno == errno.ECONNRESET
132 ):
131 ):
133 tb = "".join(traceback.format_exception(*sys.exc_info()))
132 tb = "".join(traceback.format_exception(*sys.exc_info()))
134 # We need a native-string newline to poke in the log
133 # We need a native-string newline to poke in the log
135 # message, because we won't get a newline when using an
134 # message, because we won't get a newline when using an
136 # r-string. This is the easy way out.
135 # r-string. This is the easy way out.
137 newline = chr(10)
136 newline = chr(10)
138 self.log_error(
137 self.log_error(
139 r"Exception happened during processing "
138 r"Exception happened during processing "
140 "request '%s':%s%s",
139 "request '%s':%s%s",
141 self.path,
140 self.path,
142 newline,
141 newline,
143 tb,
142 tb,
144 )
143 )
145
144
146 self._start_response("500 Internal Server Error", [])
145 self._start_response("500 Internal Server Error", [])
147 self._write(b"Internal Server Error")
146 self._write(b"Internal Server Error")
148 self._done()
147 self._done()
149
148
150 def do_PUT(self):
149 def do_PUT(self):
151 self.do_POST()
150 self.do_POST()
152
151
153 def do_GET(self):
152 def do_GET(self):
154 self.do_POST()
153 self.do_POST()
155
154
156 def do_hgweb(self):
155 def do_hgweb(self):
157 self.sent_headers = False
156 self.sent_headers = False
158 path, query = _splitURI(self.path)
157 path, query = _splitURI(self.path)
159
158
160 # Ensure the slicing of path below is valid
159 # Ensure the slicing of path below is valid
161 if path != self.server.prefix and not path.startswith(
160 if path != self.server.prefix and not path.startswith(
162 self.server.prefix + b'/'
161 self.server.prefix + b'/'
163 ):
162 ):
164 self._start_response(pycompat.strurl(common.statusmessage(404)), [])
163 self._start_response(pycompat.strurl(common.statusmessage(404)), [])
165 if self.command == 'POST':
164 if self.command == 'POST':
166 # Paranoia: tell the client we're going to close the
165 # Paranoia: tell the client we're going to close the
167 # socket so they don't try and reuse a socket that
166 # socket so they don't try and reuse a socket that
168 # might have a POST body waiting to confuse us. We do
167 # might have a POST body waiting to confuse us. We do
169 # this by directly munging self.saved_headers because
168 # this by directly munging self.saved_headers because
170 # self._start_response ignores Connection headers.
169 # self._start_response ignores Connection headers.
171 self.saved_headers = [('Connection', 'Close')]
170 self.saved_headers = [('Connection', 'Close')]
172 self._write(b"Not Found")
171 self._write(b"Not Found")
173 self._done()
172 self._done()
174 return
173 return
175
174
176 env = {}
175 env = {}
177 env['GATEWAY_INTERFACE'] = 'CGI/1.1'
176 env['GATEWAY_INTERFACE'] = 'CGI/1.1'
178 env['REQUEST_METHOD'] = self.command
177 env['REQUEST_METHOD'] = self.command
179 env['SERVER_NAME'] = self.server.server_name
178 env['SERVER_NAME'] = self.server.server_name
180 env['SERVER_PORT'] = str(self.server.server_port)
179 env['SERVER_PORT'] = str(self.server.server_port)
181 env['REQUEST_URI'] = self.path
180 env['REQUEST_URI'] = self.path
182 env['SCRIPT_NAME'] = pycompat.sysstr(self.server.prefix)
181 env['SCRIPT_NAME'] = pycompat.sysstr(self.server.prefix)
183 env['PATH_INFO'] = pycompat.sysstr(path[len(self.server.prefix) :])
182 env['PATH_INFO'] = pycompat.sysstr(path[len(self.server.prefix) :])
184 env['REMOTE_HOST'] = self.client_address[0]
183 env['REMOTE_HOST'] = self.client_address[0]
185 env['REMOTE_ADDR'] = self.client_address[0]
184 env['REMOTE_ADDR'] = self.client_address[0]
186 env['QUERY_STRING'] = query or ''
185 env['QUERY_STRING'] = query or ''
187
186
188 if self.headers.get_content_type() is None:
187 if self.headers.get_content_type() is None:
189 env['CONTENT_TYPE'] = self.headers.get_default_type()
188 env['CONTENT_TYPE'] = self.headers.get_default_type()
190 else:
189 else:
191 env['CONTENT_TYPE'] = self.headers.get_content_type()
190 env['CONTENT_TYPE'] = self.headers.get_content_type()
192 length = self.headers.get('content-length')
191 length = self.headers.get('content-length')
193 if length:
192 if length:
194 env['CONTENT_LENGTH'] = length
193 env['CONTENT_LENGTH'] = length
195 for header in [
194 for header in [
196 h
195 h
197 for h in self.headers.keys()
196 for h in self.headers.keys()
198 if h.lower() not in ('content-type', 'content-length')
197 if h.lower() not in ('content-type', 'content-length')
199 ]:
198 ]:
200 hkey = 'HTTP_' + header.replace('-', '_').upper()
199 hkey = 'HTTP_' + header.replace('-', '_').upper()
201 hval = self.headers.get(header)
200 hval = self.headers.get(header)
202 hval = hval.replace('\n', '').strip()
201 hval = hval.replace('\n', '').strip()
203 if hval:
202 if hval:
204 env[hkey] = hval
203 env[hkey] = hval
205 env['SERVER_PROTOCOL'] = self.request_version
204 env['SERVER_PROTOCOL'] = self.request_version
206 env['wsgi.version'] = (1, 0)
205 env['wsgi.version'] = (1, 0)
207 env['wsgi.url_scheme'] = pycompat.sysstr(self.url_scheme)
206 env['wsgi.url_scheme'] = pycompat.sysstr(self.url_scheme)
208 if env.get('HTTP_EXPECT', b'').lower() == b'100-continue':
207 if env.get('HTTP_EXPECT', b'').lower() == b'100-continue':
209 self.rfile = common.continuereader(self.rfile, self.wfile.write)
208 self.rfile = common.continuereader(self.rfile, self.wfile.write)
210
209
211 env['wsgi.input'] = self.rfile
210 env['wsgi.input'] = self.rfile
212 env['wsgi.errors'] = _error_logger(self)
211 env['wsgi.errors'] = _error_logger(self)
213 env['wsgi.multithread'] = isinstance(
212 env['wsgi.multithread'] = isinstance(
214 self.server, socketserver.ThreadingMixIn
213 self.server, socketserver.ThreadingMixIn
215 )
214 )
216 if util.safehasattr(socketserver, b'ForkingMixIn'):
215 if util.safehasattr(socketserver, b'ForkingMixIn'):
217 env['wsgi.multiprocess'] = isinstance(
216 env['wsgi.multiprocess'] = isinstance(
218 self.server, socketserver.ForkingMixIn
217 self.server, socketserver.ForkingMixIn
219 )
218 )
220 else:
219 else:
221 env['wsgi.multiprocess'] = False
220 env['wsgi.multiprocess'] = False
222
221
223 env['wsgi.run_once'] = 0
222 env['wsgi.run_once'] = 0
224
223
225 wsgiref.validate.check_environ(env)
224 wsgiref.validate.check_environ(env)
226
225
227 self.saved_status = None
226 self.saved_status = None
228 self.saved_headers = []
227 self.saved_headers = []
229 self.length = None
228 self.length = None
230 self._chunked = None
229 self._chunked = None
231 for chunk in self.server.application(env, self._start_response):
230 for chunk in self.server.application(env, self._start_response):
232 self._write(chunk)
231 self._write(chunk)
233 if not self.sent_headers:
232 if not self.sent_headers:
234 self.send_headers()
233 self.send_headers()
235 self._done()
234 self._done()
236
235
237 def send_headers(self):
236 def send_headers(self):
238 if not self.saved_status:
237 if not self.saved_status:
239 raise AssertionError(
238 raise AssertionError(
240 b"Sending headers before start_response() called"
239 b"Sending headers before start_response() called"
241 )
240 )
242 saved_status = self.saved_status.split(None, 1)
241 saved_status = self.saved_status.split(None, 1)
243 saved_status[0] = int(saved_status[0])
242 saved_status[0] = int(saved_status[0])
244 self.send_response(*saved_status)
243 self.send_response(*saved_status)
245 self.length = None
244 self.length = None
246 self._chunked = False
245 self._chunked = False
247 for h in self.saved_headers:
246 for h in self.saved_headers:
248 self.send_header(*h)
247 self.send_header(*h)
249 if h[0].lower() == 'content-length':
248 if h[0].lower() == 'content-length':
250 self.length = int(h[1])
249 self.length = int(h[1])
251 if self.length is None and saved_status[0] != common.HTTP_NOT_MODIFIED:
250 if self.length is None and saved_status[0] != common.HTTP_NOT_MODIFIED:
252 self._chunked = (
251 self._chunked = (
253 not self.close_connection and self.request_version == 'HTTP/1.1'
252 not self.close_connection and self.request_version == 'HTTP/1.1'
254 )
253 )
255 if self._chunked:
254 if self._chunked:
256 self.send_header('Transfer-Encoding', 'chunked')
255 self.send_header('Transfer-Encoding', 'chunked')
257 else:
256 else:
258 self.send_header('Connection', 'close')
257 self.send_header('Connection', 'close')
259 self.end_headers()
258 self.end_headers()
260 self.sent_headers = True
259 self.sent_headers = True
261
260
262 def _start_response(self, http_status, headers, exc_info=None):
261 def _start_response(self, http_status, headers, exc_info=None):
263 assert isinstance(http_status, str)
262 assert isinstance(http_status, str)
264 code, msg = http_status.split(None, 1)
263 code, msg = http_status.split(None, 1)
265 code = int(code)
264 code = int(code)
266 self.saved_status = http_status
265 self.saved_status = http_status
267 bad_headers = ('connection', 'transfer-encoding')
266 bad_headers = ('connection', 'transfer-encoding')
268 self.saved_headers = [
267 self.saved_headers = [
269 h for h in headers if h[0].lower() not in bad_headers
268 h for h in headers if h[0].lower() not in bad_headers
270 ]
269 ]
271 return self._write
270 return self._write
272
271
273 def _write(self, data):
272 def _write(self, data):
274 if not self.saved_status:
273 if not self.saved_status:
275 raise AssertionError(b"data written before start_response() called")
274 raise AssertionError(b"data written before start_response() called")
276 elif not self.sent_headers:
275 elif not self.sent_headers:
277 self.send_headers()
276 self.send_headers()
278 if self.length is not None:
277 if self.length is not None:
279 if len(data) > self.length:
278 if len(data) > self.length:
280 raise AssertionError(
279 raise AssertionError(
281 b"Content-length header sent, but more "
280 b"Content-length header sent, but more "
282 b"bytes than specified are being written."
281 b"bytes than specified are being written."
283 )
282 )
284 self.length = self.length - len(data)
283 self.length = self.length - len(data)
285 elif self._chunked and data:
284 elif self._chunked and data:
286 data = b'%x\r\n%s\r\n' % (len(data), data)
285 data = b'%x\r\n%s\r\n' % (len(data), data)
287 self.wfile.write(data)
286 self.wfile.write(data)
288 self.wfile.flush()
287 self.wfile.flush()
289
288
290 def _done(self):
289 def _done(self):
291 if self._chunked:
290 if self._chunked:
292 self.wfile.write(b'0\r\n\r\n')
291 self.wfile.write(b'0\r\n\r\n')
293 self.wfile.flush()
292 self.wfile.flush()
294
293
295 def version_string(self):
294 def version_string(self):
296 if self.server.serverheader:
295 if self.server.serverheader:
297 return encoding.strfromlocal(self.server.serverheader)
296 return encoding.strfromlocal(self.server.serverheader)
298 return httpservermod.basehttprequesthandler.version_string(self)
297 return httpservermod.basehttprequesthandler.version_string(self)
299
298
300
299
301 class _httprequesthandlerssl(_httprequesthandler):
300 class _httprequesthandlerssl(_httprequesthandler):
302 """HTTPS handler based on Python's ssl module"""
301 """HTTPS handler based on Python's ssl module"""
303
302
304 url_scheme = b'https'
303 url_scheme = b'https'
305
304
306 @staticmethod
305 @staticmethod
307 def preparehttpserver(httpserver, ui):
306 def preparehttpserver(httpserver, ui):
308 try:
307 try:
309 from .. import sslutil
308 from .. import sslutil
310
309
311 sslutil.wrapserversocket
310 sslutil.wrapserversocket
312 except ImportError:
311 except ImportError:
313 raise error.Abort(_(b"SSL support is unavailable"))
312 raise error.Abort(_(b"SSL support is unavailable"))
314
313
315 certfile = ui.config(b'web', b'certificate')
314 certfile = ui.config(b'web', b'certificate')
316
315
317 # These config options are currently only meant for testing. Use
316 # These config options are currently only meant for testing. Use
318 # at your own risk.
317 # at your own risk.
319 cafile = ui.config(b'devel', b'servercafile')
318 cafile = ui.config(b'devel', b'servercafile')
320 reqcert = ui.configbool(b'devel', b'serverrequirecert')
319 reqcert = ui.configbool(b'devel', b'serverrequirecert')
321
320
322 httpserver.socket = sslutil.wrapserversocket(
321 httpserver.socket = sslutil.wrapserversocket(
323 httpserver.socket,
322 httpserver.socket,
324 ui,
323 ui,
325 certfile=certfile,
324 certfile=certfile,
326 cafile=cafile,
325 cafile=cafile,
327 requireclientcert=reqcert,
326 requireclientcert=reqcert,
328 )
327 )
329
328
330 def setup(self):
329 def setup(self):
331 self.connection = self.request
330 self.connection = self.request
332 self.rfile = self.request.makefile("rb", self.rbufsize)
331 self.rfile = self.request.makefile("rb", self.rbufsize)
333 self.wfile = self.request.makefile("wb", self.wbufsize)
332 self.wfile = self.request.makefile("wb", self.wbufsize)
334
333
335
334
336 try:
335 try:
337 import threading
336 import threading
338
337
339 threading.active_count() # silence pyflakes and bypass demandimport
338 threading.active_count() # silence pyflakes and bypass demandimport
340 _mixin = socketserver.ThreadingMixIn
339 _mixin = socketserver.ThreadingMixIn
341 except ImportError:
340 except ImportError:
342 if util.safehasattr(os, b"fork"):
341 if util.safehasattr(os, b"fork"):
343 _mixin = socketserver.ForkingMixIn
342 _mixin = socketserver.ForkingMixIn
344 else:
343 else:
345
344
346 class _mixin:
345 class _mixin:
347 pass
346 pass
348
347
349
348
350 def openlog(opt, default):
349 def openlog(opt, default):
351 if opt and opt != b'-':
350 if opt and opt != b'-':
352 return open(opt, b'ab')
351 return open(opt, b'ab')
353 return default
352 return default
354
353
355
354
356 class MercurialHTTPServer(_mixin, httpservermod.httpserver, object):
355 class MercurialHTTPServer(_mixin, httpservermod.httpserver, object):
357
356
358 # SO_REUSEADDR has broken semantics on windows
357 # SO_REUSEADDR has broken semantics on windows
359 if pycompat.iswindows:
358 if pycompat.iswindows:
360 allow_reuse_address = 0
359 allow_reuse_address = 0
361
360
362 def __init__(self, ui, app, addr, handler, **kwargs):
361 def __init__(self, ui, app, addr, handler, **kwargs):
363 httpservermod.httpserver.__init__(self, addr, handler, **kwargs)
362 httpservermod.httpserver.__init__(self, addr, handler, **kwargs)
364 self.daemon_threads = True
363 self.daemon_threads = True
365 self.application = app
364 self.application = app
366
365
367 handler.preparehttpserver(self, ui)
366 handler.preparehttpserver(self, ui)
368
367
369 prefix = ui.config(b'web', b'prefix')
368 prefix = ui.config(b'web', b'prefix')
370 if prefix:
369 if prefix:
371 prefix = b'/' + prefix.strip(b'/')
370 prefix = b'/' + prefix.strip(b'/')
372 self.prefix = prefix
371 self.prefix = prefix
373
372
374 alog = openlog(ui.config(b'web', b'accesslog'), ui.fout)
373 alog = openlog(ui.config(b'web', b'accesslog'), ui.fout)
375 elog = openlog(ui.config(b'web', b'errorlog'), ui.ferr)
374 elog = openlog(ui.config(b'web', b'errorlog'), ui.ferr)
376 self.accesslog = alog
375 self.accesslog = alog
377 self.errorlog = elog
376 self.errorlog = elog
378
377
379 self.addr, self.port = self.socket.getsockname()[0:2]
378 self.addr, self.port = self.socket.getsockname()[0:2]
380 self.fqaddr = self.server_name
379 self.fqaddr = self.server_name
381
380
382 self.serverheader = ui.config(b'web', b'server-header')
381 self.serverheader = ui.config(b'web', b'server-header')
383
382
384
383
385 class IPv6HTTPServer(MercurialHTTPServer):
384 class IPv6HTTPServer(MercurialHTTPServer):
386 address_family = getattr(socket, 'AF_INET6', None)
385 address_family = getattr(socket, 'AF_INET6', None)
387
386
388 def __init__(self, *args, **kwargs):
387 def __init__(self, *args, **kwargs):
389 if self.address_family is None:
388 if self.address_family is None:
390 raise error.RepoError(_(b'IPv6 is not available on this system'))
389 raise error.RepoError(_(b'IPv6 is not available on this system'))
391 super(IPv6HTTPServer, self).__init__(*args, **kwargs)
390 super(IPv6HTTPServer, self).__init__(*args, **kwargs)
392
391
393
392
394 def create_server(ui, app):
393 def create_server(ui, app):
395
394
396 if ui.config(b'web', b'certificate'):
395 if ui.config(b'web', b'certificate'):
397 handler = _httprequesthandlerssl
396 handler = _httprequesthandlerssl
398 else:
397 else:
399 handler = _httprequesthandler
398 handler = _httprequesthandler
400
399
401 if ui.configbool(b'web', b'ipv6'):
400 if ui.configbool(b'web', b'ipv6'):
402 cls = IPv6HTTPServer
401 cls = IPv6HTTPServer
403 else:
402 else:
404 cls = MercurialHTTPServer
403 cls = MercurialHTTPServer
405
404
406 # ugly hack due to python issue5853 (for threaded use)
405 # ugly hack due to python issue5853 (for threaded use)
407 try:
408 import mimetypes
406 import mimetypes
409
407
410 mimetypes.init()
408 mimetypes.init()
411 except UnicodeDecodeError:
412 # Python 2.x's mimetypes module attempts to decode strings
413 # from Windows' ANSI APIs as ascii (fail), then re-encode them
414 # as ascii (clown fail), because the default Python Unicode
415 # codec is hardcoded as ascii.
416
417 sys.argv # unwrap demand-loader so that reload() works
418 # resurrect sys.setdefaultencoding()
419 try:
420 importlib.reload(sys)
421 except AttributeError:
422 reload(sys)
423 oldenc = sys.getdefaultencoding()
424 sys.setdefaultencoding(b"latin1") # or any full 8-bit encoding
425 mimetypes.init()
426 sys.setdefaultencoding(oldenc)
427
409
428 address = ui.config(b'web', b'address')
410 address = ui.config(b'web', b'address')
429 port = urlutil.getport(ui.config(b'web', b'port'))
411 port = urlutil.getport(ui.config(b'web', b'port'))
430 try:
412 try:
431 return cls(ui, app, (address, port), handler)
413 return cls(ui, app, (address, port), handler)
432 except socket.error as inst:
414 except socket.error as inst:
433 raise error.Abort(
415 raise error.Abort(
434 _(b"cannot start server at '%s:%d': %s")
416 _(b"cannot start server at '%s:%d': %s")
435 % (address, port, encoding.strtolocal(inst.args[1]))
417 % (address, port, encoding.strtolocal(inst.args[1]))
436 )
418 )
@@ -1,33 +1,32 b''
1 #require test-repo pyflakes hg10
1 #require test-repo pyflakes hg10
2
2
3 $ . "$TESTDIR/helpers-testrepo.sh"
3 $ . "$TESTDIR/helpers-testrepo.sh"
4
4
5 run pyflakes on all tracked files ending in .py or without a file ending
5 run pyflakes on all tracked files ending in .py or without a file ending
6 (skipping binary file random-seed)
6 (skipping binary file random-seed)
7
7
8 $ cat > test.py <<EOF
8 $ cat > test.py <<EOF
9 > print(undefinedname)
9 > print(undefinedname)
10 > EOF
10 > EOF
11 $ "$PYTHON" -m pyflakes test.py 2>/dev/null | "$TESTDIR/filterpyflakes.py"
11 $ "$PYTHON" -m pyflakes test.py 2>/dev/null | "$TESTDIR/filterpyflakes.py"
12 test.py:1:* undefined name 'undefinedname' (glob)
12 test.py:1:* undefined name 'undefinedname' (glob)
13
13
14 $ cd "`dirname "$TESTDIR"`"
14 $ cd "`dirname "$TESTDIR"`"
15
15
16 $ testrepohg locate 'set:**.py or grep("^#!.*python")' \
16 $ testrepohg locate 'set:**.py or grep("^#!.*python")' \
17 > -X hgext/fsmonitor/pywatchman \
17 > -X hgext/fsmonitor/pywatchman \
18 > -X contrib/python-zstandard \
18 > -X contrib/python-zstandard \
19 > -X mercurial/thirdparty \
19 > -X mercurial/thirdparty \
20 > 2>/dev/null \
20 > 2>/dev/null \
21 > | xargs "$PYTHON" -m pyflakes 2>/dev/null | "$TESTDIR/filterpyflakes.py"
21 > | xargs "$PYTHON" -m pyflakes 2>/dev/null | "$TESTDIR/filterpyflakes.py"
22 contrib/perf.py:*:* undefined name 'xrange' (glob) (?)
22 contrib/perf.py:*:* undefined name 'xrange' (glob) (?)
23 mercurial/hgweb/server.py:*:* undefined name 'reload' (glob) (?)
24 mercurial/pycompat.py:*:* 'codecs' imported but unused (glob)
23 mercurial/pycompat.py:*:* 'codecs' imported but unused (glob)
25 mercurial/pycompat.py:*:* 'concurrent.futures' imported but unused (glob)
24 mercurial/pycompat.py:*:* 'concurrent.futures' imported but unused (glob)
26 mercurial/pycompat.py:*:* 'http.client as httplib' imported but unused (glob)
25 mercurial/pycompat.py:*:* 'http.client as httplib' imported but unused (glob)
27 mercurial/pycompat.py:*:* 'http.cookiejar as cookielib' imported but unused (glob)
26 mercurial/pycompat.py:*:* 'http.cookiejar as cookielib' imported but unused (glob)
28 mercurial/pycompat.py:*:* 'io' imported but unused (glob)
27 mercurial/pycompat.py:*:* 'io' imported but unused (glob)
29 mercurial/pycompat.py:*:* 'queue' imported but unused (glob)
28 mercurial/pycompat.py:*:* 'queue' imported but unused (glob)
30 mercurial/pycompat.py:*:* 'socketserver' imported but unused (glob)
29 mercurial/pycompat.py:*:* 'socketserver' imported but unused (glob)
31 mercurial/pycompat.py:*:* 'xmlrpc.client as xmlrpclib' imported but unused (glob)
30 mercurial/pycompat.py:*:* 'xmlrpc.client as xmlrpclib' imported but unused (glob)
32 mercurial/util.py:*:* 'pickle' imported but unused (glob)
31 mercurial/util.py:*:* 'pickle' imported but unused (glob)
33
32
General Comments 0
You need to be logged in to leave comments. Login now