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