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