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