##// END OF EJS Templates
hgweb: send proper HTTP response after uncaught exception...
Gregory Szorc -
r23409:dc4d2cd3 stable
parent child Browse files
Show More
@@ -0,0 +1,17 b''
1 # A dummy extension that installs an hgweb command that throws an Exception.
2
3 from mercurial.hgweb import webcommands
4
5 def raiseerror(web, req, tmpl):
6 '''Dummy web command that raises an uncaught Exception.'''
7
8 # Simulate an error after partial response.
9 if 'partialresponse' in req.form:
10 req.respond(200, 'text/plain')
11 req.write('partial content\n')
12
13 raise AttributeError('I am an uncaught error!')
14
15 def extsetup(ui):
16 setattr(webcommands, 'raiseerror', raiseerror)
17 webcommands.__all__.append('raiseerror')
@@ -1,348 +1,349 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 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 self._done()
84 tb = "".join(traceback.format_exception(*sys.exc_info()))
85 tb = "".join(traceback.format_exception(*sys.exc_info()))
85 self.log_error("Exception happened during processing "
86 self.log_error("Exception happened during processing "
86 "request '%s':\n%s", self.path, tb)
87 "request '%s':\n%s", self.path, tb)
87
88
88 def do_GET(self):
89 def do_GET(self):
89 self.do_POST()
90 self.do_POST()
90
91
91 def do_hgweb(self):
92 def do_hgweb(self):
92 path, query = _splitURI(self.path)
93 path, query = _splitURI(self.path)
93
94
94 env = {}
95 env = {}
95 env['GATEWAY_INTERFACE'] = 'CGI/1.1'
96 env['GATEWAY_INTERFACE'] = 'CGI/1.1'
96 env['REQUEST_METHOD'] = self.command
97 env['REQUEST_METHOD'] = self.command
97 env['SERVER_NAME'] = self.server.server_name
98 env['SERVER_NAME'] = self.server.server_name
98 env['SERVER_PORT'] = str(self.server.server_port)
99 env['SERVER_PORT'] = str(self.server.server_port)
99 env['REQUEST_URI'] = self.path
100 env['REQUEST_URI'] = self.path
100 env['SCRIPT_NAME'] = self.server.prefix
101 env['SCRIPT_NAME'] = self.server.prefix
101 env['PATH_INFO'] = path[len(self.server.prefix):]
102 env['PATH_INFO'] = path[len(self.server.prefix):]
102 env['REMOTE_HOST'] = self.client_address[0]
103 env['REMOTE_HOST'] = self.client_address[0]
103 env['REMOTE_ADDR'] = self.client_address[0]
104 env['REMOTE_ADDR'] = self.client_address[0]
104 if query:
105 if query:
105 env['QUERY_STRING'] = query
106 env['QUERY_STRING'] = query
106
107
107 if self.headers.typeheader is None:
108 if self.headers.typeheader is None:
108 env['CONTENT_TYPE'] = self.headers.type
109 env['CONTENT_TYPE'] = self.headers.type
109 else:
110 else:
110 env['CONTENT_TYPE'] = self.headers.typeheader
111 env['CONTENT_TYPE'] = self.headers.typeheader
111 length = self.headers.getheader('content-length')
112 length = self.headers.getheader('content-length')
112 if length:
113 if length:
113 env['CONTENT_LENGTH'] = length
114 env['CONTENT_LENGTH'] = length
114 for header in [h for h in self.headers.keys()
115 for header in [h for h in self.headers.keys()
115 if h not in ('content-type', 'content-length')]:
116 if h not in ('content-type', 'content-length')]:
116 hkey = 'HTTP_' + header.replace('-', '_').upper()
117 hkey = 'HTTP_' + header.replace('-', '_').upper()
117 hval = self.headers.getheader(header)
118 hval = self.headers.getheader(header)
118 hval = hval.replace('\n', '').strip()
119 hval = hval.replace('\n', '').strip()
119 if hval:
120 if hval:
120 env[hkey] = hval
121 env[hkey] = hval
121 env['SERVER_PROTOCOL'] = self.request_version
122 env['SERVER_PROTOCOL'] = self.request_version
122 env['wsgi.version'] = (1, 0)
123 env['wsgi.version'] = (1, 0)
123 env['wsgi.url_scheme'] = self.url_scheme
124 env['wsgi.url_scheme'] = self.url_scheme
124 if env.get('HTTP_EXPECT', '').lower() == '100-continue':
125 if env.get('HTTP_EXPECT', '').lower() == '100-continue':
125 self.rfile = common.continuereader(self.rfile, self.wfile.write)
126 self.rfile = common.continuereader(self.rfile, self.wfile.write)
126
127
127 env['wsgi.input'] = self.rfile
128 env['wsgi.input'] = self.rfile
128 env['wsgi.errors'] = _error_logger(self)
129 env['wsgi.errors'] = _error_logger(self)
129 env['wsgi.multithread'] = isinstance(self.server,
130 env['wsgi.multithread'] = isinstance(self.server,
130 SocketServer.ThreadingMixIn)
131 SocketServer.ThreadingMixIn)
131 env['wsgi.multiprocess'] = isinstance(self.server,
132 env['wsgi.multiprocess'] = isinstance(self.server,
132 SocketServer.ForkingMixIn)
133 SocketServer.ForkingMixIn)
133 env['wsgi.run_once'] = 0
134 env['wsgi.run_once'] = 0
134
135
135 self.saved_status = None
136 self.saved_status = None
136 self.saved_headers = []
137 self.saved_headers = []
137 self.sent_headers = False
138 self.sent_headers = False
138 self.length = None
139 self.length = None
139 self._chunked = None
140 self._chunked = None
140 for chunk in self.server.application(env, self._start_response):
141 for chunk in self.server.application(env, self._start_response):
141 self._write(chunk)
142 self._write(chunk)
142 if not self.sent_headers:
143 if not self.sent_headers:
143 self.send_headers()
144 self.send_headers()
144 self._done()
145 self._done()
145
146
146 def send_headers(self):
147 def send_headers(self):
147 if not self.saved_status:
148 if not self.saved_status:
148 raise AssertionError("Sending headers before "
149 raise AssertionError("Sending headers before "
149 "start_response() called")
150 "start_response() called")
150 saved_status = self.saved_status.split(None, 1)
151 saved_status = self.saved_status.split(None, 1)
151 saved_status[0] = int(saved_status[0])
152 saved_status[0] = int(saved_status[0])
152 self.send_response(*saved_status)
153 self.send_response(*saved_status)
153 self.length = None
154 self.length = None
154 self._chunked = False
155 self._chunked = False
155 for h in self.saved_headers:
156 for h in self.saved_headers:
156 self.send_header(*h)
157 self.send_header(*h)
157 if h[0].lower() == 'content-length':
158 if h[0].lower() == 'content-length':
158 self.length = int(h[1])
159 self.length = int(h[1])
159 if (self.length is None and
160 if (self.length is None and
160 saved_status[0] != common.HTTP_NOT_MODIFIED):
161 saved_status[0] != common.HTTP_NOT_MODIFIED):
161 self._chunked = (not self.close_connection and
162 self._chunked = (not self.close_connection and
162 self.request_version == "HTTP/1.1")
163 self.request_version == "HTTP/1.1")
163 if self._chunked:
164 if self._chunked:
164 self.send_header('Transfer-Encoding', 'chunked')
165 self.send_header('Transfer-Encoding', 'chunked')
165 else:
166 else:
166 self.send_header('Connection', 'close')
167 self.send_header('Connection', 'close')
167 self.end_headers()
168 self.end_headers()
168 self.sent_headers = True
169 self.sent_headers = True
169
170
170 def _start_response(self, http_status, headers, exc_info=None):
171 def _start_response(self, http_status, headers, exc_info=None):
171 code, msg = http_status.split(None, 1)
172 code, msg = http_status.split(None, 1)
172 code = int(code)
173 code = int(code)
173 self.saved_status = http_status
174 self.saved_status = http_status
174 bad_headers = ('connection', 'transfer-encoding')
175 bad_headers = ('connection', 'transfer-encoding')
175 self.saved_headers = [h for h in headers
176 self.saved_headers = [h for h in headers
176 if h[0].lower() not in bad_headers]
177 if h[0].lower() not in bad_headers]
177 return self._write
178 return self._write
178
179
179 def _write(self, data):
180 def _write(self, data):
180 if not self.saved_status:
181 if not self.saved_status:
181 raise AssertionError("data written before start_response() called")
182 raise AssertionError("data written before start_response() called")
182 elif not self.sent_headers:
183 elif not self.sent_headers:
183 self.send_headers()
184 self.send_headers()
184 if self.length is not None:
185 if self.length is not None:
185 if len(data) > self.length:
186 if len(data) > self.length:
186 raise AssertionError("Content-length header sent, but more "
187 raise AssertionError("Content-length header sent, but more "
187 "bytes than specified are being written.")
188 "bytes than specified are being written.")
188 self.length = self.length - len(data)
189 self.length = self.length - len(data)
189 elif self._chunked and data:
190 elif self._chunked and data:
190 data = '%x\r\n%s\r\n' % (len(data), data)
191 data = '%x\r\n%s\r\n' % (len(data), data)
191 self.wfile.write(data)
192 self.wfile.write(data)
192 self.wfile.flush()
193 self.wfile.flush()
193
194
194 def _done(self):
195 def _done(self):
195 if self._chunked:
196 if self._chunked:
196 self.wfile.write('0\r\n\r\n')
197 self.wfile.write('0\r\n\r\n')
197 self.wfile.flush()
198 self.wfile.flush()
198
199
199 class _httprequesthandleropenssl(_httprequesthandler):
200 class _httprequesthandleropenssl(_httprequesthandler):
200 """HTTPS handler based on pyOpenSSL"""
201 """HTTPS handler based on pyOpenSSL"""
201
202
202 url_scheme = 'https'
203 url_scheme = 'https'
203
204
204 @staticmethod
205 @staticmethod
205 def preparehttpserver(httpserver, ssl_cert):
206 def preparehttpserver(httpserver, ssl_cert):
206 try:
207 try:
207 import OpenSSL
208 import OpenSSL
208 OpenSSL.SSL.Context
209 OpenSSL.SSL.Context
209 except ImportError:
210 except ImportError:
210 raise util.Abort(_("SSL support is unavailable"))
211 raise util.Abort(_("SSL support is unavailable"))
211 ctx = OpenSSL.SSL.Context(OpenSSL.SSL.TLSv1_METHOD)
212 ctx = OpenSSL.SSL.Context(OpenSSL.SSL.TLSv1_METHOD)
212 ctx.use_privatekey_file(ssl_cert)
213 ctx.use_privatekey_file(ssl_cert)
213 ctx.use_certificate_file(ssl_cert)
214 ctx.use_certificate_file(ssl_cert)
214 sock = socket.socket(httpserver.address_family, httpserver.socket_type)
215 sock = socket.socket(httpserver.address_family, httpserver.socket_type)
215 httpserver.socket = OpenSSL.SSL.Connection(ctx, sock)
216 httpserver.socket = OpenSSL.SSL.Connection(ctx, sock)
216 httpserver.server_bind()
217 httpserver.server_bind()
217 httpserver.server_activate()
218 httpserver.server_activate()
218
219
219 def setup(self):
220 def setup(self):
220 self.connection = self.request
221 self.connection = self.request
221 self.rfile = socket._fileobject(self.request, "rb", self.rbufsize)
222 self.rfile = socket._fileobject(self.request, "rb", self.rbufsize)
222 self.wfile = socket._fileobject(self.request, "wb", self.wbufsize)
223 self.wfile = socket._fileobject(self.request, "wb", self.wbufsize)
223
224
224 def do_write(self):
225 def do_write(self):
225 import OpenSSL
226 import OpenSSL
226 try:
227 try:
227 _httprequesthandler.do_write(self)
228 _httprequesthandler.do_write(self)
228 except OpenSSL.SSL.SysCallError, inst:
229 except OpenSSL.SSL.SysCallError, inst:
229 if inst.args[0] != errno.EPIPE:
230 if inst.args[0] != errno.EPIPE:
230 raise
231 raise
231
232
232 def handle_one_request(self):
233 def handle_one_request(self):
233 import OpenSSL
234 import OpenSSL
234 try:
235 try:
235 _httprequesthandler.handle_one_request(self)
236 _httprequesthandler.handle_one_request(self)
236 except (OpenSSL.SSL.SysCallError, OpenSSL.SSL.ZeroReturnError):
237 except (OpenSSL.SSL.SysCallError, OpenSSL.SSL.ZeroReturnError):
237 self.close_connection = True
238 self.close_connection = True
238 pass
239 pass
239
240
240 class _httprequesthandlerssl(_httprequesthandler):
241 class _httprequesthandlerssl(_httprequesthandler):
241 """HTTPS handler based on Pythons ssl module (introduced in 2.6)"""
242 """HTTPS handler based on Pythons ssl module (introduced in 2.6)"""
242
243
243 url_scheme = 'https'
244 url_scheme = 'https'
244
245
245 @staticmethod
246 @staticmethod
246 def preparehttpserver(httpserver, ssl_cert):
247 def preparehttpserver(httpserver, ssl_cert):
247 try:
248 try:
248 import ssl
249 import ssl
249 ssl.wrap_socket
250 ssl.wrap_socket
250 except ImportError:
251 except ImportError:
251 raise util.Abort(_("SSL support is unavailable"))
252 raise util.Abort(_("SSL support is unavailable"))
252 httpserver.socket = ssl.wrap_socket(
253 httpserver.socket = ssl.wrap_socket(
253 httpserver.socket, server_side=True,
254 httpserver.socket, server_side=True,
254 certfile=ssl_cert, ssl_version=ssl.PROTOCOL_TLSv1)
255 certfile=ssl_cert, ssl_version=ssl.PROTOCOL_TLSv1)
255
256
256 def setup(self):
257 def setup(self):
257 self.connection = self.request
258 self.connection = self.request
258 self.rfile = socket._fileobject(self.request, "rb", self.rbufsize)
259 self.rfile = socket._fileobject(self.request, "rb", self.rbufsize)
259 self.wfile = socket._fileobject(self.request, "wb", self.wbufsize)
260 self.wfile = socket._fileobject(self.request, "wb", self.wbufsize)
260
261
261 try:
262 try:
262 from threading import activeCount
263 from threading import activeCount
263 activeCount() # silence pyflakes
264 activeCount() # silence pyflakes
264 _mixin = SocketServer.ThreadingMixIn
265 _mixin = SocketServer.ThreadingMixIn
265 except ImportError:
266 except ImportError:
266 if util.safehasattr(os, "fork"):
267 if util.safehasattr(os, "fork"):
267 _mixin = SocketServer.ForkingMixIn
268 _mixin = SocketServer.ForkingMixIn
268 else:
269 else:
269 class _mixin(object):
270 class _mixin(object):
270 pass
271 pass
271
272
272 def openlog(opt, default):
273 def openlog(opt, default):
273 if opt and opt != '-':
274 if opt and opt != '-':
274 return open(opt, 'a')
275 return open(opt, 'a')
275 return default
276 return default
276
277
277 class MercurialHTTPServer(object, _mixin, BaseHTTPServer.HTTPServer):
278 class MercurialHTTPServer(object, _mixin, BaseHTTPServer.HTTPServer):
278
279
279 # SO_REUSEADDR has broken semantics on windows
280 # SO_REUSEADDR has broken semantics on windows
280 if os.name == 'nt':
281 if os.name == 'nt':
281 allow_reuse_address = 0
282 allow_reuse_address = 0
282
283
283 def __init__(self, ui, app, addr, handler, **kwargs):
284 def __init__(self, ui, app, addr, handler, **kwargs):
284 BaseHTTPServer.HTTPServer.__init__(self, addr, handler, **kwargs)
285 BaseHTTPServer.HTTPServer.__init__(self, addr, handler, **kwargs)
285 self.daemon_threads = True
286 self.daemon_threads = True
286 self.application = app
287 self.application = app
287
288
288 handler.preparehttpserver(self, ui.config('web', 'certificate'))
289 handler.preparehttpserver(self, ui.config('web', 'certificate'))
289
290
290 prefix = ui.config('web', 'prefix', '')
291 prefix = ui.config('web', 'prefix', '')
291 if prefix:
292 if prefix:
292 prefix = '/' + prefix.strip('/')
293 prefix = '/' + prefix.strip('/')
293 self.prefix = prefix
294 self.prefix = prefix
294
295
295 alog = openlog(ui.config('web', 'accesslog', '-'), sys.stdout)
296 alog = openlog(ui.config('web', 'accesslog', '-'), sys.stdout)
296 elog = openlog(ui.config('web', 'errorlog', '-'), sys.stderr)
297 elog = openlog(ui.config('web', 'errorlog', '-'), sys.stderr)
297 self.accesslog = alog
298 self.accesslog = alog
298 self.errorlog = elog
299 self.errorlog = elog
299
300
300 self.addr, self.port = self.socket.getsockname()[0:2]
301 self.addr, self.port = self.socket.getsockname()[0:2]
301 self.fqaddr = socket.getfqdn(addr[0])
302 self.fqaddr = socket.getfqdn(addr[0])
302
303
303 class IPv6HTTPServer(MercurialHTTPServer):
304 class IPv6HTTPServer(MercurialHTTPServer):
304 address_family = getattr(socket, 'AF_INET6', None)
305 address_family = getattr(socket, 'AF_INET6', None)
305 def __init__(self, *args, **kwargs):
306 def __init__(self, *args, **kwargs):
306 if self.address_family is None:
307 if self.address_family is None:
307 raise error.RepoError(_('IPv6 is not available on this system'))
308 raise error.RepoError(_('IPv6 is not available on this system'))
308 super(IPv6HTTPServer, self).__init__(*args, **kwargs)
309 super(IPv6HTTPServer, self).__init__(*args, **kwargs)
309
310
310 def create_server(ui, app):
311 def create_server(ui, app):
311
312
312 if ui.config('web', 'certificate'):
313 if ui.config('web', 'certificate'):
313 if sys.version_info >= (2, 6):
314 if sys.version_info >= (2, 6):
314 handler = _httprequesthandlerssl
315 handler = _httprequesthandlerssl
315 else:
316 else:
316 handler = _httprequesthandleropenssl
317 handler = _httprequesthandleropenssl
317 else:
318 else:
318 handler = _httprequesthandler
319 handler = _httprequesthandler
319
320
320 if ui.configbool('web', 'ipv6'):
321 if ui.configbool('web', 'ipv6'):
321 cls = IPv6HTTPServer
322 cls = IPv6HTTPServer
322 else:
323 else:
323 cls = MercurialHTTPServer
324 cls = MercurialHTTPServer
324
325
325 # ugly hack due to python issue5853 (for threaded use)
326 # ugly hack due to python issue5853 (for threaded use)
326 try:
327 try:
327 import mimetypes
328 import mimetypes
328 mimetypes.init()
329 mimetypes.init()
329 except UnicodeDecodeError:
330 except UnicodeDecodeError:
330 # Python 2.x's mimetypes module attempts to decode strings
331 # Python 2.x's mimetypes module attempts to decode strings
331 # from Windows' ANSI APIs as ascii (fail), then re-encode them
332 # from Windows' ANSI APIs as ascii (fail), then re-encode them
332 # as ascii (clown fail), because the default Python Unicode
333 # as ascii (clown fail), because the default Python Unicode
333 # codec is hardcoded as ascii.
334 # codec is hardcoded as ascii.
334
335
335 sys.argv # unwrap demand-loader so that reload() works
336 sys.argv # unwrap demand-loader so that reload() works
336 reload(sys) # resurrect sys.setdefaultencoding()
337 reload(sys) # resurrect sys.setdefaultencoding()
337 oldenc = sys.getdefaultencoding()
338 oldenc = sys.getdefaultencoding()
338 sys.setdefaultencoding("latin1") # or any full 8-bit encoding
339 sys.setdefaultencoding("latin1") # or any full 8-bit encoding
339 mimetypes.init()
340 mimetypes.init()
340 sys.setdefaultencoding(oldenc)
341 sys.setdefaultencoding(oldenc)
341
342
342 address = ui.config('web', 'address', '')
343 address = ui.config('web', 'address', '')
343 port = util.getport(ui.config('web', 'port', 8000))
344 port = util.getport(ui.config('web', 'port', 8000))
344 try:
345 try:
345 return cls(ui, app, (address, port), handler)
346 return cls(ui, app, (address, port), handler)
346 except socket.error, inst:
347 except socket.error, inst:
347 raise util.Abort(_("cannot start server at '%s:%d': %s")
348 raise util.Abort(_("cannot start server at '%s:%d': %s")
348 % (address, port, inst.args[1]))
349 % (address, port, inst.args[1]))
@@ -1,61 +1,60 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2
2
3 """This does HTTP GET requests given a host:port and path and returns
3 """This does HTTP GET requests given a host:port and path and returns
4 a subset of the headers plus the body of the result."""
4 a subset of the headers plus the body of the result."""
5
5
6 import httplib, sys
6 import httplib, sys
7
7
8 try:
8 try:
9 import msvcrt, os
9 import msvcrt, os
10 msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
10 msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
11 msvcrt.setmode(sys.stderr.fileno(), os.O_BINARY)
11 msvcrt.setmode(sys.stderr.fileno(), os.O_BINARY)
12 except ImportError:
12 except ImportError:
13 pass
13 pass
14
14
15 twice = False
15 twice = False
16 if '--twice' in sys.argv:
16 if '--twice' in sys.argv:
17 sys.argv.remove('--twice')
17 sys.argv.remove('--twice')
18 twice = True
18 twice = True
19 headeronly = False
19 headeronly = False
20 if '--headeronly' in sys.argv:
20 if '--headeronly' in sys.argv:
21 sys.argv.remove('--headeronly')
21 sys.argv.remove('--headeronly')
22 headeronly = True
22 headeronly = True
23
23
24 reasons = {'Not modified': 'Not Modified'} # python 2.4
24 reasons = {'Not modified': 'Not Modified'} # python 2.4
25
25
26 tag = None
26 tag = None
27 def request(host, path, show):
27 def request(host, path, show):
28 assert not path.startswith('/'), path
28 assert not path.startswith('/'), path
29 global tag
29 global tag
30 headers = {}
30 headers = {}
31 if tag:
31 if tag:
32 headers['If-None-Match'] = tag
32 headers['If-None-Match'] = tag
33
33
34 conn = httplib.HTTPConnection(host)
34 conn = httplib.HTTPConnection(host)
35 conn.request("GET", '/' + path, None, headers)
35 conn.request("GET", '/' + path, None, headers)
36 response = conn.getresponse()
36 response = conn.getresponse()
37 print response.status, reasons.get(response.reason, response.reason)
37 print response.status, reasons.get(response.reason, response.reason)
38 if show[:1] == ['-']:
38 if show[:1] == ['-']:
39 show = sorted(h for h, v in response.getheaders()
39 show = sorted(h for h, v in response.getheaders()
40 if h.lower() not in show)
40 if h.lower() not in show)
41 for h in [h.lower() for h in show]:
41 for h in [h.lower() for h in show]:
42 if response.getheader(h, None) is not None:
42 if response.getheader(h, None) is not None:
43 print "%s: %s" % (h, response.getheader(h))
43 print "%s: %s" % (h, response.getheader(h))
44 if not headeronly:
44 if not headeronly:
45 print
45 print
46 if response.status != 500:
46 data = response.read()
47 data = response.read()
47 sys.stdout.write(data)
48 sys.stdout.write(data)
49
48
50 if twice and response.getheader('ETag', None):
49 if twice and response.getheader('ETag', None):
51 tag = response.getheader('ETag')
50 tag = response.getheader('ETag')
52
51
53 return response.status
52 return response.status
54
53
55 status = request(sys.argv[1], sys.argv[2], sys.argv[3:])
54 status = request(sys.argv[1], sys.argv[2], sys.argv[3:])
56 if twice:
55 if twice:
57 status = request(sys.argv[1], sys.argv[2], sys.argv[3:])
56 status = request(sys.argv[1], sys.argv[2], sys.argv[3:])
58
57
59 if 200 <= status <= 305:
58 if 200 <= status <= 305:
60 sys.exit(0)
59 sys.exit(0)
61 sys.exit(1)
60 sys.exit(1)
@@ -1,582 +1,608 b''
1 #require serve
1 #require serve
2
2
3 Some tests for hgweb. Tests static files, plain files and different 404's.
3 Some tests for hgweb. Tests static files, plain files and different 404's.
4
4
5 $ hg init test
5 $ hg init test
6 $ cd test
6 $ cd test
7 $ mkdir da
7 $ mkdir da
8 $ echo foo > da/foo
8 $ echo foo > da/foo
9 $ echo foo > foo
9 $ echo foo > foo
10 $ hg ci -Ambase
10 $ hg ci -Ambase
11 adding da/foo
11 adding da/foo
12 adding foo
12 adding foo
13 $ hg serve -n test -p $HGPORT -d --pid-file=hg.pid -A access.log -E errors.log
13 $ hg serve -n test -p $HGPORT -d --pid-file=hg.pid -A access.log -E errors.log
14 $ cat hg.pid >> $DAEMON_PIDS
14 $ cat hg.pid >> $DAEMON_PIDS
15
15
16 manifest
16 manifest
17
17
18 $ ("$TESTDIR/get-with-headers.py" localhost:$HGPORT 'file/tip/?style=raw')
18 $ ("$TESTDIR/get-with-headers.py" localhost:$HGPORT 'file/tip/?style=raw')
19 200 Script output follows
19 200 Script output follows
20
20
21
21
22 drwxr-xr-x da
22 drwxr-xr-x da
23 -rw-r--r-- 4 foo
23 -rw-r--r-- 4 foo
24
24
25
25
26 $ ("$TESTDIR/get-with-headers.py" localhost:$HGPORT 'file/tip/da?style=raw')
26 $ ("$TESTDIR/get-with-headers.py" localhost:$HGPORT 'file/tip/da?style=raw')
27 200 Script output follows
27 200 Script output follows
28
28
29
29
30 -rw-r--r-- 4 foo
30 -rw-r--r-- 4 foo
31
31
32
32
33
33
34 plain file
34 plain file
35
35
36 $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT 'file/tip/foo?style=raw'
36 $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT 'file/tip/foo?style=raw'
37 200 Script output follows
37 200 Script output follows
38
38
39 foo
39 foo
40
40
41 should give a 404 - static file that does not exist
41 should give a 404 - static file that does not exist
42
42
43 $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT 'static/bogus'
43 $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT 'static/bogus'
44 404 Not Found
44 404 Not Found
45
45
46 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
46 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
47 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US">
47 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US">
48 <head>
48 <head>
49 <link rel="icon" href="/static/hgicon.png" type="image/png" />
49 <link rel="icon" href="/static/hgicon.png" type="image/png" />
50 <meta name="robots" content="index, nofollow" />
50 <meta name="robots" content="index, nofollow" />
51 <link rel="stylesheet" href="/static/style-paper.css" type="text/css" />
51 <link rel="stylesheet" href="/static/style-paper.css" type="text/css" />
52 <script type="text/javascript" src="/static/mercurial.js"></script>
52 <script type="text/javascript" src="/static/mercurial.js"></script>
53
53
54 <title>test: error</title>
54 <title>test: error</title>
55 </head>
55 </head>
56 <body>
56 <body>
57
57
58 <div class="container">
58 <div class="container">
59 <div class="menu">
59 <div class="menu">
60 <div class="logo">
60 <div class="logo">
61 <a href="http://mercurial.selenic.com/">
61 <a href="http://mercurial.selenic.com/">
62 <img src="/static/hglogo.png" width=75 height=90 border=0 alt="mercurial" /></a>
62 <img src="/static/hglogo.png" width=75 height=90 border=0 alt="mercurial" /></a>
63 </div>
63 </div>
64 <ul>
64 <ul>
65 <li><a href="/shortlog">log</a></li>
65 <li><a href="/shortlog">log</a></li>
66 <li><a href="/graph">graph</a></li>
66 <li><a href="/graph">graph</a></li>
67 <li><a href="/tags">tags</a></li>
67 <li><a href="/tags">tags</a></li>
68 <li><a href="/bookmarks">bookmarks</a></li>
68 <li><a href="/bookmarks">bookmarks</a></li>
69 <li><a href="/branches">branches</a></li>
69 <li><a href="/branches">branches</a></li>
70 </ul>
70 </ul>
71 <ul>
71 <ul>
72 <li><a href="/help">help</a></li>
72 <li><a href="/help">help</a></li>
73 </ul>
73 </ul>
74 </div>
74 </div>
75
75
76 <div class="main">
76 <div class="main">
77
77
78 <h2 class="breadcrumb"><a href="/">Mercurial</a> </h2>
78 <h2 class="breadcrumb"><a href="/">Mercurial</a> </h2>
79 <h3>error</h3>
79 <h3>error</h3>
80
80
81 <form class="search" action="/log">
81 <form class="search" action="/log">
82
82
83 <p><input name="rev" id="search1" type="text" size="30"></p>
83 <p><input name="rev" id="search1" type="text" size="30"></p>
84 <div id="hint">Find changesets by keywords (author, files, the commit message), revision
84 <div id="hint">Find changesets by keywords (author, files, the commit message), revision
85 number or hash, or <a href="/help/revsets">revset expression</a>.</div>
85 number or hash, or <a href="/help/revsets">revset expression</a>.</div>
86 </form>
86 </form>
87
87
88 <div class="description">
88 <div class="description">
89 <p>
89 <p>
90 An error occurred while processing your request:
90 An error occurred while processing your request:
91 </p>
91 </p>
92 <p>
92 <p>
93 Not Found
93 Not Found
94 </p>
94 </p>
95 </div>
95 </div>
96 </div>
96 </div>
97 </div>
97 </div>
98
98
99 <script type="text/javascript">process_dates()</script>
99 <script type="text/javascript">process_dates()</script>
100
100
101
101
102 </body>
102 </body>
103 </html>
103 </html>
104
104
105 [1]
105 [1]
106
106
107 should give a 404 - bad revision
107 should give a 404 - bad revision
108
108
109 $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT 'file/spam/foo?style=raw'
109 $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT 'file/spam/foo?style=raw'
110 404 Not Found
110 404 Not Found
111
111
112
112
113 error: revision not found: spam
113 error: revision not found: spam
114 [1]
114 [1]
115
115
116 should give a 400 - bad command
116 should give a 400 - bad command
117
117
118 $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT 'file/tip/foo?cmd=spam&style=raw'
118 $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT 'file/tip/foo?cmd=spam&style=raw'
119 400* (glob)
119 400* (glob)
120
120
121
121
122 error: no such method: spam
122 error: no such method: spam
123 [1]
123 [1]
124
124
125 $ "$TESTDIR/get-with-headers.py" --headeronly localhost:$HGPORT '?cmd=spam'
125 $ "$TESTDIR/get-with-headers.py" --headeronly localhost:$HGPORT '?cmd=spam'
126 400 no such method: spam
126 400 no such method: spam
127 [1]
127 [1]
128
128
129 should give a 400 - bad command as a part of url path (issue4071)
129 should give a 400 - bad command as a part of url path (issue4071)
130
130
131 $ "$TESTDIR/get-with-headers.py" --headeronly localhost:$HGPORT 'spam'
131 $ "$TESTDIR/get-with-headers.py" --headeronly localhost:$HGPORT 'spam'
132 400 no such method: spam
132 400 no such method: spam
133 [1]
133 [1]
134
134
135 $ "$TESTDIR/get-with-headers.py" --headeronly localhost:$HGPORT 'raw-spam'
135 $ "$TESTDIR/get-with-headers.py" --headeronly localhost:$HGPORT 'raw-spam'
136 400 no such method: spam
136 400 no such method: spam
137 [1]
137 [1]
138
138
139 $ "$TESTDIR/get-with-headers.py" --headeronly localhost:$HGPORT 'spam/tip/foo'
139 $ "$TESTDIR/get-with-headers.py" --headeronly localhost:$HGPORT 'spam/tip/foo'
140 400 no such method: spam
140 400 no such method: spam
141 [1]
141 [1]
142
142
143 should give a 404 - file does not exist
143 should give a 404 - file does not exist
144
144
145 $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT 'file/tip/bork?style=raw'
145 $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT 'file/tip/bork?style=raw'
146 404 Not Found
146 404 Not Found
147
147
148
148
149 error: bork@2ef0ac749a14: not found in manifest
149 error: bork@2ef0ac749a14: not found in manifest
150 [1]
150 [1]
151 $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT 'file/tip/bork'
151 $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT 'file/tip/bork'
152 404 Not Found
152 404 Not Found
153
153
154 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
154 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
155 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US">
155 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US">
156 <head>
156 <head>
157 <link rel="icon" href="/static/hgicon.png" type="image/png" />
157 <link rel="icon" href="/static/hgicon.png" type="image/png" />
158 <meta name="robots" content="index, nofollow" />
158 <meta name="robots" content="index, nofollow" />
159 <link rel="stylesheet" href="/static/style-paper.css" type="text/css" />
159 <link rel="stylesheet" href="/static/style-paper.css" type="text/css" />
160 <script type="text/javascript" src="/static/mercurial.js"></script>
160 <script type="text/javascript" src="/static/mercurial.js"></script>
161
161
162 <title>test: error</title>
162 <title>test: error</title>
163 </head>
163 </head>
164 <body>
164 <body>
165
165
166 <div class="container">
166 <div class="container">
167 <div class="menu">
167 <div class="menu">
168 <div class="logo">
168 <div class="logo">
169 <a href="http://mercurial.selenic.com/">
169 <a href="http://mercurial.selenic.com/">
170 <img src="/static/hglogo.png" width=75 height=90 border=0 alt="mercurial" /></a>
170 <img src="/static/hglogo.png" width=75 height=90 border=0 alt="mercurial" /></a>
171 </div>
171 </div>
172 <ul>
172 <ul>
173 <li><a href="/shortlog">log</a></li>
173 <li><a href="/shortlog">log</a></li>
174 <li><a href="/graph">graph</a></li>
174 <li><a href="/graph">graph</a></li>
175 <li><a href="/tags">tags</a></li>
175 <li><a href="/tags">tags</a></li>
176 <li><a href="/bookmarks">bookmarks</a></li>
176 <li><a href="/bookmarks">bookmarks</a></li>
177 <li><a href="/branches">branches</a></li>
177 <li><a href="/branches">branches</a></li>
178 </ul>
178 </ul>
179 <ul>
179 <ul>
180 <li><a href="/help">help</a></li>
180 <li><a href="/help">help</a></li>
181 </ul>
181 </ul>
182 </div>
182 </div>
183
183
184 <div class="main">
184 <div class="main">
185
185
186 <h2 class="breadcrumb"><a href="/">Mercurial</a> </h2>
186 <h2 class="breadcrumb"><a href="/">Mercurial</a> </h2>
187 <h3>error</h3>
187 <h3>error</h3>
188
188
189 <form class="search" action="/log">
189 <form class="search" action="/log">
190
190
191 <p><input name="rev" id="search1" type="text" size="30"></p>
191 <p><input name="rev" id="search1" type="text" size="30"></p>
192 <div id="hint">Find changesets by keywords (author, files, the commit message), revision
192 <div id="hint">Find changesets by keywords (author, files, the commit message), revision
193 number or hash, or <a href="/help/revsets">revset expression</a>.</div>
193 number or hash, or <a href="/help/revsets">revset expression</a>.</div>
194 </form>
194 </form>
195
195
196 <div class="description">
196 <div class="description">
197 <p>
197 <p>
198 An error occurred while processing your request:
198 An error occurred while processing your request:
199 </p>
199 </p>
200 <p>
200 <p>
201 bork@2ef0ac749a14: not found in manifest
201 bork@2ef0ac749a14: not found in manifest
202 </p>
202 </p>
203 </div>
203 </div>
204 </div>
204 </div>
205 </div>
205 </div>
206
206
207 <script type="text/javascript">process_dates()</script>
207 <script type="text/javascript">process_dates()</script>
208
208
209
209
210 </body>
210 </body>
211 </html>
211 </html>
212
212
213 [1]
213 [1]
214 $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT 'diff/tip/bork?style=raw'
214 $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT 'diff/tip/bork?style=raw'
215 404 Not Found
215 404 Not Found
216
216
217
217
218 error: bork@2ef0ac749a14: not found in manifest
218 error: bork@2ef0ac749a14: not found in manifest
219 [1]
219 [1]
220
220
221 try bad style
221 try bad style
222
222
223 $ ("$TESTDIR/get-with-headers.py" localhost:$HGPORT 'file/tip/?style=foobar')
223 $ ("$TESTDIR/get-with-headers.py" localhost:$HGPORT 'file/tip/?style=foobar')
224 200 Script output follows
224 200 Script output follows
225
225
226 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
226 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
227 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US">
227 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en-US">
228 <head>
228 <head>
229 <link rel="icon" href="/static/hgicon.png" type="image/png" />
229 <link rel="icon" href="/static/hgicon.png" type="image/png" />
230 <meta name="robots" content="index, nofollow" />
230 <meta name="robots" content="index, nofollow" />
231 <link rel="stylesheet" href="/static/style-paper.css" type="text/css" />
231 <link rel="stylesheet" href="/static/style-paper.css" type="text/css" />
232 <script type="text/javascript" src="/static/mercurial.js"></script>
232 <script type="text/javascript" src="/static/mercurial.js"></script>
233
233
234 <title>test: 2ef0ac749a14 /</title>
234 <title>test: 2ef0ac749a14 /</title>
235 </head>
235 </head>
236 <body>
236 <body>
237
237
238 <div class="container">
238 <div class="container">
239 <div class="menu">
239 <div class="menu">
240 <div class="logo">
240 <div class="logo">
241 <a href="http://mercurial.selenic.com/">
241 <a href="http://mercurial.selenic.com/">
242 <img src="/static/hglogo.png" alt="mercurial" /></a>
242 <img src="/static/hglogo.png" alt="mercurial" /></a>
243 </div>
243 </div>
244 <ul>
244 <ul>
245 <li><a href="/shortlog/2ef0ac749a14">log</a></li>
245 <li><a href="/shortlog/2ef0ac749a14">log</a></li>
246 <li><a href="/graph/2ef0ac749a14">graph</a></li>
246 <li><a href="/graph/2ef0ac749a14">graph</a></li>
247 <li><a href="/tags">tags</a></li>
247 <li><a href="/tags">tags</a></li>
248 <li><a href="/bookmarks">bookmarks</a></li>
248 <li><a href="/bookmarks">bookmarks</a></li>
249 <li><a href="/branches">branches</a></li>
249 <li><a href="/branches">branches</a></li>
250 </ul>
250 </ul>
251 <ul>
251 <ul>
252 <li><a href="/rev/2ef0ac749a14">changeset</a></li>
252 <li><a href="/rev/2ef0ac749a14">changeset</a></li>
253 <li class="active">browse</li>
253 <li class="active">browse</li>
254 </ul>
254 </ul>
255 <ul>
255 <ul>
256
256
257 </ul>
257 </ul>
258 <ul>
258 <ul>
259 <li><a href="/help">help</a></li>
259 <li><a href="/help">help</a></li>
260 </ul>
260 </ul>
261 </div>
261 </div>
262
262
263 <div class="main">
263 <div class="main">
264 <h2 class="breadcrumb"><a href="/">Mercurial</a> </h2>
264 <h2 class="breadcrumb"><a href="/">Mercurial</a> </h2>
265 <h3>directory / @ 0:2ef0ac749a14 <span class="tag">tip</span> </h3>
265 <h3>directory / @ 0:2ef0ac749a14 <span class="tag">tip</span> </h3>
266
266
267 <form class="search" action="/log">
267 <form class="search" action="/log">
268
268
269 <p><input name="rev" id="search1" type="text" size="30" /></p>
269 <p><input name="rev" id="search1" type="text" size="30" /></p>
270 <div id="hint">Find changesets by keywords (author, files, the commit message), revision
270 <div id="hint">Find changesets by keywords (author, files, the commit message), revision
271 number or hash, or <a href="/help/revsets">revset expression</a>.</div>
271 number or hash, or <a href="/help/revsets">revset expression</a>.</div>
272 </form>
272 </form>
273
273
274 <table class="bigtable">
274 <table class="bigtable">
275 <tr>
275 <tr>
276 <th class="name">name</th>
276 <th class="name">name</th>
277 <th class="size">size</th>
277 <th class="size">size</th>
278 <th class="permissions">permissions</th>
278 <th class="permissions">permissions</th>
279 </tr>
279 </tr>
280 <tbody class="stripes2">
280 <tbody class="stripes2">
281 <tr class="fileline">
281 <tr class="fileline">
282 <td class="name"><a href="/file/2ef0ac749a14/">[up]</a></td>
282 <td class="name"><a href="/file/2ef0ac749a14/">[up]</a></td>
283 <td class="size"></td>
283 <td class="size"></td>
284 <td class="permissions">drwxr-xr-x</td>
284 <td class="permissions">drwxr-xr-x</td>
285 </tr>
285 </tr>
286
286
287 <tr class="fileline">
287 <tr class="fileline">
288 <td class="name">
288 <td class="name">
289 <a href="/file/2ef0ac749a14/da">
289 <a href="/file/2ef0ac749a14/da">
290 <img src="/static/coal-folder.png" alt="dir."/> da/
290 <img src="/static/coal-folder.png" alt="dir."/> da/
291 </a>
291 </a>
292 <a href="/file/2ef0ac749a14/da/">
292 <a href="/file/2ef0ac749a14/da/">
293
293
294 </a>
294 </a>
295 </td>
295 </td>
296 <td class="size"></td>
296 <td class="size"></td>
297 <td class="permissions">drwxr-xr-x</td>
297 <td class="permissions">drwxr-xr-x</td>
298 </tr>
298 </tr>
299
299
300 <tr class="fileline">
300 <tr class="fileline">
301 <td class="filename">
301 <td class="filename">
302 <a href="/file/2ef0ac749a14/foo">
302 <a href="/file/2ef0ac749a14/foo">
303 <img src="/static/coal-file.png" alt="file"/> foo
303 <img src="/static/coal-file.png" alt="file"/> foo
304 </a>
304 </a>
305 </td>
305 </td>
306 <td class="size">4</td>
306 <td class="size">4</td>
307 <td class="permissions">-rw-r--r--</td>
307 <td class="permissions">-rw-r--r--</td>
308 </tr>
308 </tr>
309 </tbody>
309 </tbody>
310 </table>
310 </table>
311 </div>
311 </div>
312 </div>
312 </div>
313 <script type="text/javascript">process_dates()</script>
313 <script type="text/javascript">process_dates()</script>
314
314
315
315
316 </body>
316 </body>
317 </html>
317 </html>
318
318
319
319
320 stop and restart
320 stop and restart
321
321
322 $ "$TESTDIR/killdaemons.py" $DAEMON_PIDS
322 $ "$TESTDIR/killdaemons.py" $DAEMON_PIDS
323 $ hg serve -p $HGPORT -d --pid-file=hg.pid -A access.log
323 $ hg serve -p $HGPORT -d --pid-file=hg.pid -A access.log
324 $ cat hg.pid >> $DAEMON_PIDS
324 $ cat hg.pid >> $DAEMON_PIDS
325
325
326 Test the access/error files are opened in append mode
326 Test the access/error files are opened in append mode
327
327
328 $ $PYTHON -c "print len(file('access.log').readlines()), 'log lines written'"
328 $ $PYTHON -c "print len(file('access.log').readlines()), 'log lines written'"
329 14 log lines written
329 14 log lines written
330
330
331 static file
331 static file
332
332
333 $ "$TESTDIR/get-with-headers.py" --twice localhost:$HGPORT 'static/style-gitweb.css' - date etag server
333 $ "$TESTDIR/get-with-headers.py" --twice localhost:$HGPORT 'static/style-gitweb.css' - date etag server
334 200 Script output follows
334 200 Script output follows
335 content-length: 5262
335 content-length: 5262
336 content-type: text/css
336 content-type: text/css
337
337
338 body { font-family: sans-serif; font-size: 12px; border:solid #d9d8d1; border-width:1px; margin:10px; }
338 body { font-family: sans-serif; font-size: 12px; border:solid #d9d8d1; border-width:1px; margin:10px; }
339 a { color:#0000cc; }
339 a { color:#0000cc; }
340 a:hover, a:visited, a:active { color:#880000; }
340 a:hover, a:visited, a:active { color:#880000; }
341 div.page_header { height:25px; padding:8px; font-size:18px; font-weight:bold; background-color:#d9d8d1; }
341 div.page_header { height:25px; padding:8px; font-size:18px; font-weight:bold; background-color:#d9d8d1; }
342 div.page_header a:visited { color:#0000cc; }
342 div.page_header a:visited { color:#0000cc; }
343 div.page_header a:hover { color:#880000; }
343 div.page_header a:hover { color:#880000; }
344 div.page_nav { padding:8px; }
344 div.page_nav { padding:8px; }
345 div.page_nav a:visited { color:#0000cc; }
345 div.page_nav a:visited { color:#0000cc; }
346 div.page_path { padding:8px; border:solid #d9d8d1; border-width:0px 0px 1px}
346 div.page_path { padding:8px; border:solid #d9d8d1; border-width:0px 0px 1px}
347 div.page_footer { padding:4px 8px; background-color: #d9d8d1; }
347 div.page_footer { padding:4px 8px; background-color: #d9d8d1; }
348 div.page_footer_text { float:left; color:#555555; font-style:italic; }
348 div.page_footer_text { float:left; color:#555555; font-style:italic; }
349 div.page_body { padding:8px; }
349 div.page_body { padding:8px; }
350 div.title, a.title {
350 div.title, a.title {
351 display:block; padding:6px 8px;
351 display:block; padding:6px 8px;
352 font-weight:bold; background-color:#edece6; text-decoration:none; color:#000000;
352 font-weight:bold; background-color:#edece6; text-decoration:none; color:#000000;
353 }
353 }
354 a.title:hover { background-color: #d9d8d1; }
354 a.title:hover { background-color: #d9d8d1; }
355 div.title_text { padding:6px 0px; border: solid #d9d8d1; border-width:0px 0px 1px; }
355 div.title_text { padding:6px 0px; border: solid #d9d8d1; border-width:0px 0px 1px; }
356 div.log_body { padding:8px 8px 8px 150px; }
356 div.log_body { padding:8px 8px 8px 150px; }
357 .age { white-space:nowrap; }
357 .age { white-space:nowrap; }
358 span.age { position:relative; float:left; width:142px; font-style:italic; }
358 span.age { position:relative; float:left; width:142px; font-style:italic; }
359 div.log_link {
359 div.log_link {
360 padding:0px 8px;
360 padding:0px 8px;
361 font-size:10px; font-family:sans-serif; font-style:normal;
361 font-size:10px; font-family:sans-serif; font-style:normal;
362 position:relative; float:left; width:136px;
362 position:relative; float:left; width:136px;
363 }
363 }
364 div.list_head { padding:6px 8px 4px; border:solid #d9d8d1; border-width:1px 0px 0px; font-style:italic; }
364 div.list_head { padding:6px 8px 4px; border:solid #d9d8d1; border-width:1px 0px 0px; font-style:italic; }
365 a.list { text-decoration:none; color:#000000; }
365 a.list { text-decoration:none; color:#000000; }
366 a.list:hover { text-decoration:underline; color:#880000; }
366 a.list:hover { text-decoration:underline; color:#880000; }
367 table { padding:8px 4px; }
367 table { padding:8px 4px; }
368 th { padding:2px 5px; font-size:12px; text-align:left; }
368 th { padding:2px 5px; font-size:12px; text-align:left; }
369 tr.light:hover, .parity0:hover { background-color:#edece6; }
369 tr.light:hover, .parity0:hover { background-color:#edece6; }
370 tr.dark, .parity1 { background-color:#f6f6f0; }
370 tr.dark, .parity1 { background-color:#f6f6f0; }
371 tr.dark:hover, .parity1:hover { background-color:#edece6; }
371 tr.dark:hover, .parity1:hover { background-color:#edece6; }
372 td { padding:2px 5px; font-size:12px; vertical-align:top; }
372 td { padding:2px 5px; font-size:12px; vertical-align:top; }
373 td.closed { background-color: #99f; }
373 td.closed { background-color: #99f; }
374 td.link { padding:2px 5px; font-family:sans-serif; font-size:10px; }
374 td.link { padding:2px 5px; font-family:sans-serif; font-size:10px; }
375 td.indexlinks { white-space: nowrap; }
375 td.indexlinks { white-space: nowrap; }
376 td.indexlinks a {
376 td.indexlinks a {
377 padding: 2px 5px; line-height: 10px;
377 padding: 2px 5px; line-height: 10px;
378 border: 1px solid;
378 border: 1px solid;
379 color: #ffffff; background-color: #7777bb;
379 color: #ffffff; background-color: #7777bb;
380 border-color: #aaaadd #333366 #333366 #aaaadd;
380 border-color: #aaaadd #333366 #333366 #aaaadd;
381 font-weight: bold; text-align: center; text-decoration: none;
381 font-weight: bold; text-align: center; text-decoration: none;
382 font-size: 10px;
382 font-size: 10px;
383 }
383 }
384 td.indexlinks a:hover { background-color: #6666aa; }
384 td.indexlinks a:hover { background-color: #6666aa; }
385 div.pre { font-family:monospace; font-size:12px; white-space:pre; }
385 div.pre { font-family:monospace; font-size:12px; white-space:pre; }
386 div.diff_info { font-family:monospace; color:#000099; background-color:#edece6; font-style:italic; }
386 div.diff_info { font-family:monospace; color:#000099; background-color:#edece6; font-style:italic; }
387 div.index_include { border:solid #d9d8d1; border-width:0px 0px 1px; padding:12px 8px; }
387 div.index_include { border:solid #d9d8d1; border-width:0px 0px 1px; padding:12px 8px; }
388 div.search { margin:4px 8px; position:absolute; top:56px; right:12px }
388 div.search { margin:4px 8px; position:absolute; top:56px; right:12px }
389 .linenr { color:#999999; text-decoration:none }
389 .linenr { color:#999999; text-decoration:none }
390 div.rss_logo { float: right; white-space: nowrap; }
390 div.rss_logo { float: right; white-space: nowrap; }
391 div.rss_logo a {
391 div.rss_logo a {
392 padding:3px 6px; line-height:10px;
392 padding:3px 6px; line-height:10px;
393 border:1px solid; border-color:#fcc7a5 #7d3302 #3e1a01 #ff954e;
393 border:1px solid; border-color:#fcc7a5 #7d3302 #3e1a01 #ff954e;
394 color:#ffffff; background-color:#ff6600;
394 color:#ffffff; background-color:#ff6600;
395 font-weight:bold; font-family:sans-serif; font-size:10px;
395 font-weight:bold; font-family:sans-serif; font-size:10px;
396 text-align:center; text-decoration:none;
396 text-align:center; text-decoration:none;
397 }
397 }
398 div.rss_logo a:hover { background-color:#ee5500; }
398 div.rss_logo a:hover { background-color:#ee5500; }
399 pre { margin: 0; }
399 pre { margin: 0; }
400 span.logtags span {
400 span.logtags span {
401 padding: 0px 4px;
401 padding: 0px 4px;
402 font-size: 10px;
402 font-size: 10px;
403 font-weight: normal;
403 font-weight: normal;
404 border: 1px solid;
404 border: 1px solid;
405 background-color: #ffaaff;
405 background-color: #ffaaff;
406 border-color: #ffccff #ff00ee #ff00ee #ffccff;
406 border-color: #ffccff #ff00ee #ff00ee #ffccff;
407 }
407 }
408 span.logtags span.tagtag {
408 span.logtags span.tagtag {
409 background-color: #ffffaa;
409 background-color: #ffffaa;
410 border-color: #ffffcc #ffee00 #ffee00 #ffffcc;
410 border-color: #ffffcc #ffee00 #ffee00 #ffffcc;
411 }
411 }
412 span.logtags span.branchtag {
412 span.logtags span.branchtag {
413 background-color: #aaffaa;
413 background-color: #aaffaa;
414 border-color: #ccffcc #00cc33 #00cc33 #ccffcc;
414 border-color: #ccffcc #00cc33 #00cc33 #ccffcc;
415 }
415 }
416 span.logtags span.inbranchtag {
416 span.logtags span.inbranchtag {
417 background-color: #d5dde6;
417 background-color: #d5dde6;
418 border-color: #e3ecf4 #9398f4 #9398f4 #e3ecf4;
418 border-color: #e3ecf4 #9398f4 #9398f4 #e3ecf4;
419 }
419 }
420 span.logtags span.bookmarktag {
420 span.logtags span.bookmarktag {
421 background-color: #afdffa;
421 background-color: #afdffa;
422 border-color: #ccecff #46ace6 #46ace6 #ccecff;
422 border-color: #ccecff #46ace6 #46ace6 #ccecff;
423 }
423 }
424
424
425 /* Graph */
425 /* Graph */
426 div#wrapper {
426 div#wrapper {
427 position: relative;
427 position: relative;
428 margin: 0;
428 margin: 0;
429 padding: 0;
429 padding: 0;
430 margin-top: 3px;
430 margin-top: 3px;
431 }
431 }
432
432
433 canvas {
433 canvas {
434 position: absolute;
434 position: absolute;
435 z-index: 5;
435 z-index: 5;
436 top: -0.9em;
436 top: -0.9em;
437 margin: 0;
437 margin: 0;
438 }
438 }
439
439
440 ul#nodebgs {
440 ul#nodebgs {
441 list-style: none inside none;
441 list-style: none inside none;
442 padding: 0;
442 padding: 0;
443 margin: 0;
443 margin: 0;
444 top: -0.7em;
444 top: -0.7em;
445 }
445 }
446
446
447 ul#graphnodes li, ul#nodebgs li {
447 ul#graphnodes li, ul#nodebgs li {
448 height: 39px;
448 height: 39px;
449 }
449 }
450
450
451 ul#graphnodes {
451 ul#graphnodes {
452 position: absolute;
452 position: absolute;
453 z-index: 10;
453 z-index: 10;
454 top: -0.8em;
454 top: -0.8em;
455 list-style: none inside none;
455 list-style: none inside none;
456 padding: 0;
456 padding: 0;
457 }
457 }
458
458
459 ul#graphnodes li .info {
459 ul#graphnodes li .info {
460 display: block;
460 display: block;
461 font-size: 100%;
461 font-size: 100%;
462 position: relative;
462 position: relative;
463 top: -3px;
463 top: -3px;
464 font-style: italic;
464 font-style: italic;
465 }
465 }
466
466
467 /* Comparison */
467 /* Comparison */
468 .legend {
468 .legend {
469 padding: 1.5% 0 1.5% 0;
469 padding: 1.5% 0 1.5% 0;
470 }
470 }
471
471
472 .legendinfo {
472 .legendinfo {
473 border: 1px solid #d9d8d1;
473 border: 1px solid #d9d8d1;
474 font-size: 80%;
474 font-size: 80%;
475 text-align: center;
475 text-align: center;
476 padding: 0.5%;
476 padding: 0.5%;
477 }
477 }
478
478
479 .equal {
479 .equal {
480 background-color: #ffffff;
480 background-color: #ffffff;
481 }
481 }
482
482
483 .delete {
483 .delete {
484 background-color: #faa;
484 background-color: #faa;
485 color: #333;
485 color: #333;
486 }
486 }
487
487
488 .insert {
488 .insert {
489 background-color: #ffa;
489 background-color: #ffa;
490 }
490 }
491
491
492 .replace {
492 .replace {
493 background-color: #e8e8e8;
493 background-color: #e8e8e8;
494 }
494 }
495
495
496 .comparison {
496 .comparison {
497 overflow-x: auto;
497 overflow-x: auto;
498 }
498 }
499
499
500 .header th {
500 .header th {
501 text-align: center;
501 text-align: center;
502 }
502 }
503
503
504 .block {
504 .block {
505 border-top: 1px solid #d9d8d1;
505 border-top: 1px solid #d9d8d1;
506 }
506 }
507
507
508 .scroll-loading {
508 .scroll-loading {
509 -webkit-animation: change_color 1s linear 0s infinite alternate;
509 -webkit-animation: change_color 1s linear 0s infinite alternate;
510 -moz-animation: change_color 1s linear 0s infinite alternate;
510 -moz-animation: change_color 1s linear 0s infinite alternate;
511 -o-animation: change_color 1s linear 0s infinite alternate;
511 -o-animation: change_color 1s linear 0s infinite alternate;
512 animation: change_color 1s linear 0s infinite alternate;
512 animation: change_color 1s linear 0s infinite alternate;
513 }
513 }
514
514
515 @-webkit-keyframes change_color {
515 @-webkit-keyframes change_color {
516 from { background-color: #A0CEFF; } to { }
516 from { background-color: #A0CEFF; } to { }
517 }
517 }
518 @-moz-keyframes change_color {
518 @-moz-keyframes change_color {
519 from { background-color: #A0CEFF; } to { }
519 from { background-color: #A0CEFF; } to { }
520 }
520 }
521 @-o-keyframes change_color {
521 @-o-keyframes change_color {
522 from { background-color: #A0CEFF; } to { }
522 from { background-color: #A0CEFF; } to { }
523 }
523 }
524 @keyframes change_color {
524 @keyframes change_color {
525 from { background-color: #A0CEFF; } to { }
525 from { background-color: #A0CEFF; } to { }
526 }
526 }
527
527
528 .scroll-loading-error {
528 .scroll-loading-error {
529 background-color: #FFCCCC !important;
529 background-color: #FFCCCC !important;
530 }
530 }
531 304 Not Modified
531 304 Not Modified
532
532
533
533
534 phase changes are refreshed (issue4061)
534 phase changes are refreshed (issue4061)
535
535
536 $ echo bar >> foo
536 $ echo bar >> foo
537 $ hg ci -msecret --secret
537 $ hg ci -msecret --secret
538 $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT 'log?style=raw'
538 $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT 'log?style=raw'
539 200 Script output follows
539 200 Script output follows
540
540
541
541
542 # HG changelog
542 # HG changelog
543 # Node ID 2ef0ac749a14e4f57a5a822464a0902c6f7f448f
543 # Node ID 2ef0ac749a14e4f57a5a822464a0902c6f7f448f
544
544
545 changeset: 2ef0ac749a14e4f57a5a822464a0902c6f7f448f
545 changeset: 2ef0ac749a14e4f57a5a822464a0902c6f7f448f
546 revision: 0
546 revision: 0
547 user: test
547 user: test
548 date: Thu, 01 Jan 1970 00:00:00 +0000
548 date: Thu, 01 Jan 1970 00:00:00 +0000
549 summary: base
549 summary: base
550 branch: default
550 branch: default
551 tag: tip
551 tag: tip
552
552
553
553
554 $ hg phase --draft tip
554 $ hg phase --draft tip
555 $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT 'log?style=raw'
555 $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT 'log?style=raw'
556 200 Script output follows
556 200 Script output follows
557
557
558
558
559 # HG changelog
559 # HG changelog
560 # Node ID a084749e708a9c4c0a5b652a2a446322ce290e04
560 # Node ID a084749e708a9c4c0a5b652a2a446322ce290e04
561
561
562 changeset: a084749e708a9c4c0a5b652a2a446322ce290e04
562 changeset: a084749e708a9c4c0a5b652a2a446322ce290e04
563 revision: 1
563 revision: 1
564 user: test
564 user: test
565 date: Thu, 01 Jan 1970 00:00:00 +0000
565 date: Thu, 01 Jan 1970 00:00:00 +0000
566 summary: secret
566 summary: secret
567 branch: default
567 branch: default
568 tag: tip
568 tag: tip
569
569
570 changeset: 2ef0ac749a14e4f57a5a822464a0902c6f7f448f
570 changeset: 2ef0ac749a14e4f57a5a822464a0902c6f7f448f
571 revision: 0
571 revision: 0
572 user: test
572 user: test
573 date: Thu, 01 Jan 1970 00:00:00 +0000
573 date: Thu, 01 Jan 1970 00:00:00 +0000
574 summary: base
574 summary: base
575
575
576
576
577
577
578 errors
578 errors
579
579
580 $ cat errors.log
580 $ cat errors.log
581
581
582 Uncaught exceptions result in a logged error and canned HTTP response
583
584 $ "$TESTDIR/killdaemons.py" $DAEMON_PIDS
585 $ hg --config extensions.hgweberror=$TESTDIR/hgweberror.py serve -p $HGPORT -d --pid-file=hg.pid -A access.log -E errors.log
586 $ cat hg.pid >> $DAEMON_PIDS
587
588 $ $TESTDIR/get-with-headers.py localhost:$HGPORT 'raiseerror' transfer-encoding content-type
589 500 Internal Server Error
590 transfer-encoding: chunked
591
592 Internal Server Error (no-eol)
593 [1]
594
595 $ head -1 errors.log
596 .* Exception happened during processing request '/raiseerror': (re)
597
598 Uncaught exception after partial content sent
599
600 $ $TESTDIR/get-with-headers.py localhost:$HGPORT 'raiseerror?partialresponse=1' transfer-encoding content-type
601 200 Script output follows
602 transfer-encoding: chunked
603 content-type: text/plain
604
605 partial content
606 Internal Server Error (no-eol)
607
582 $ cd ..
608 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now