##// END OF EJS Templates
hgweb: fail if an invalid command was supplied in url path (issue4071)...
Anton Shestakov -
r22506:6e1fbcb1 stable
parent child Browse files
Show More
@@ -1,396 +1,394 b''
1 # hgweb/hgweb_mod.py - Web interface for a repository.
1 # hgweb/hgweb_mod.py - Web interface for a repository.
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, re
9 import os, re
10 from mercurial import ui, hg, hook, error, encoding, templater, util, repoview
10 from mercurial import ui, hg, hook, error, encoding, templater, util, repoview
11 from mercurial.templatefilters import websub
11 from mercurial.templatefilters import websub
12 from mercurial.i18n import _
12 from mercurial.i18n import _
13 from common import get_stat, ErrorResponse, permhooks, caching
13 from common import get_stat, ErrorResponse, permhooks, caching
14 from common import HTTP_OK, HTTP_NOT_MODIFIED, HTTP_BAD_REQUEST
14 from common import HTTP_OK, HTTP_NOT_MODIFIED, HTTP_BAD_REQUEST
15 from common import HTTP_NOT_FOUND, HTTP_SERVER_ERROR
15 from common import HTTP_NOT_FOUND, HTTP_SERVER_ERROR
16 from request import wsgirequest
16 from request import wsgirequest
17 import webcommands, protocol, webutil
17 import webcommands, protocol, webutil
18
18
19 perms = {
19 perms = {
20 'changegroup': 'pull',
20 'changegroup': 'pull',
21 'changegroupsubset': 'pull',
21 'changegroupsubset': 'pull',
22 'getbundle': 'pull',
22 'getbundle': 'pull',
23 'stream_out': 'pull',
23 'stream_out': 'pull',
24 'listkeys': 'pull',
24 'listkeys': 'pull',
25 'unbundle': 'push',
25 'unbundle': 'push',
26 'pushkey': 'push',
26 'pushkey': 'push',
27 }
27 }
28
28
29 def makebreadcrumb(url, prefix=''):
29 def makebreadcrumb(url, prefix=''):
30 '''Return a 'URL breadcrumb' list
30 '''Return a 'URL breadcrumb' list
31
31
32 A 'URL breadcrumb' is a list of URL-name pairs,
32 A 'URL breadcrumb' is a list of URL-name pairs,
33 corresponding to each of the path items on a URL.
33 corresponding to each of the path items on a URL.
34 This can be used to create path navigation entries.
34 This can be used to create path navigation entries.
35 '''
35 '''
36 if url.endswith('/'):
36 if url.endswith('/'):
37 url = url[:-1]
37 url = url[:-1]
38 if prefix:
38 if prefix:
39 url = '/' + prefix + url
39 url = '/' + prefix + url
40 relpath = url
40 relpath = url
41 if relpath.startswith('/'):
41 if relpath.startswith('/'):
42 relpath = relpath[1:]
42 relpath = relpath[1:]
43
43
44 breadcrumb = []
44 breadcrumb = []
45 urlel = url
45 urlel = url
46 pathitems = [''] + relpath.split('/')
46 pathitems = [''] + relpath.split('/')
47 for pathel in reversed(pathitems):
47 for pathel in reversed(pathitems):
48 if not pathel or not urlel:
48 if not pathel or not urlel:
49 break
49 break
50 breadcrumb.append({'url': urlel, 'name': pathel})
50 breadcrumb.append({'url': urlel, 'name': pathel})
51 urlel = os.path.dirname(urlel)
51 urlel = os.path.dirname(urlel)
52 return reversed(breadcrumb)
52 return reversed(breadcrumb)
53
53
54
54
55 class hgweb(object):
55 class hgweb(object):
56 def __init__(self, repo, name=None, baseui=None):
56 def __init__(self, repo, name=None, baseui=None):
57 if isinstance(repo, str):
57 if isinstance(repo, str):
58 if baseui:
58 if baseui:
59 u = baseui.copy()
59 u = baseui.copy()
60 else:
60 else:
61 u = ui.ui()
61 u = ui.ui()
62 r = hg.repository(u, repo)
62 r = hg.repository(u, repo)
63 else:
63 else:
64 # we trust caller to give us a private copy
64 # we trust caller to give us a private copy
65 r = repo
65 r = repo
66
66
67 r = self._getview(r)
67 r = self._getview(r)
68 r.ui.setconfig('ui', 'report_untrusted', 'off', 'hgweb')
68 r.ui.setconfig('ui', 'report_untrusted', 'off', 'hgweb')
69 r.baseui.setconfig('ui', 'report_untrusted', 'off', 'hgweb')
69 r.baseui.setconfig('ui', 'report_untrusted', 'off', 'hgweb')
70 r.ui.setconfig('ui', 'nontty', 'true', 'hgweb')
70 r.ui.setconfig('ui', 'nontty', 'true', 'hgweb')
71 r.baseui.setconfig('ui', 'nontty', 'true', 'hgweb')
71 r.baseui.setconfig('ui', 'nontty', 'true', 'hgweb')
72 self.repo = r
72 self.repo = r
73 hook.redirect(True)
73 hook.redirect(True)
74 self.mtime = -1
74 self.mtime = -1
75 self.size = -1
75 self.size = -1
76 self.reponame = name
76 self.reponame = name
77 self.archives = 'zip', 'gz', 'bz2'
77 self.archives = 'zip', 'gz', 'bz2'
78 self.stripecount = 1
78 self.stripecount = 1
79 # a repo owner may set web.templates in .hg/hgrc to get any file
79 # a repo owner may set web.templates in .hg/hgrc to get any file
80 # readable by the user running the CGI script
80 # readable by the user running the CGI script
81 self.templatepath = self.config('web', 'templates')
81 self.templatepath = self.config('web', 'templates')
82 self.websubtable = self.loadwebsub()
82 self.websubtable = self.loadwebsub()
83
83
84 # The CGI scripts are often run by a user different from the repo owner.
84 # The CGI scripts are often run by a user different from the repo owner.
85 # Trust the settings from the .hg/hgrc files by default.
85 # Trust the settings from the .hg/hgrc files by default.
86 def config(self, section, name, default=None, untrusted=True):
86 def config(self, section, name, default=None, untrusted=True):
87 return self.repo.ui.config(section, name, default,
87 return self.repo.ui.config(section, name, default,
88 untrusted=untrusted)
88 untrusted=untrusted)
89
89
90 def configbool(self, section, name, default=False, untrusted=True):
90 def configbool(self, section, name, default=False, untrusted=True):
91 return self.repo.ui.configbool(section, name, default,
91 return self.repo.ui.configbool(section, name, default,
92 untrusted=untrusted)
92 untrusted=untrusted)
93
93
94 def configlist(self, section, name, default=None, untrusted=True):
94 def configlist(self, section, name, default=None, untrusted=True):
95 return self.repo.ui.configlist(section, name, default,
95 return self.repo.ui.configlist(section, name, default,
96 untrusted=untrusted)
96 untrusted=untrusted)
97
97
98 def _getview(self, repo):
98 def _getview(self, repo):
99 viewconfig = repo.ui.config('web', 'view', 'served',
99 viewconfig = repo.ui.config('web', 'view', 'served',
100 untrusted=True)
100 untrusted=True)
101 if viewconfig == 'all':
101 if viewconfig == 'all':
102 return repo.unfiltered()
102 return repo.unfiltered()
103 elif viewconfig in repoview.filtertable:
103 elif viewconfig in repoview.filtertable:
104 return repo.filtered(viewconfig)
104 return repo.filtered(viewconfig)
105 else:
105 else:
106 return repo.filtered('served')
106 return repo.filtered('served')
107
107
108 def refresh(self, request=None):
108 def refresh(self, request=None):
109 st = get_stat(self.repo.spath)
109 st = get_stat(self.repo.spath)
110 # compare changelog size in addition to mtime to catch
110 # compare changelog size in addition to mtime to catch
111 # rollbacks made less than a second ago
111 # rollbacks made less than a second ago
112 if st.st_mtime != self.mtime or st.st_size != self.size:
112 if st.st_mtime != self.mtime or st.st_size != self.size:
113 r = hg.repository(self.repo.baseui, self.repo.root)
113 r = hg.repository(self.repo.baseui, self.repo.root)
114 self.repo = self._getview(r)
114 self.repo = self._getview(r)
115 self.maxchanges = int(self.config("web", "maxchanges", 10))
115 self.maxchanges = int(self.config("web", "maxchanges", 10))
116 self.stripecount = int(self.config("web", "stripes", 1))
116 self.stripecount = int(self.config("web", "stripes", 1))
117 self.maxshortchanges = int(self.config("web", "maxshortchanges",
117 self.maxshortchanges = int(self.config("web", "maxshortchanges",
118 60))
118 60))
119 self.maxfiles = int(self.config("web", "maxfiles", 10))
119 self.maxfiles = int(self.config("web", "maxfiles", 10))
120 self.allowpull = self.configbool("web", "allowpull", True)
120 self.allowpull = self.configbool("web", "allowpull", True)
121 encoding.encoding = self.config("web", "encoding",
121 encoding.encoding = self.config("web", "encoding",
122 encoding.encoding)
122 encoding.encoding)
123 # update these last to avoid threads seeing empty settings
123 # update these last to avoid threads seeing empty settings
124 self.mtime = st.st_mtime
124 self.mtime = st.st_mtime
125 self.size = st.st_size
125 self.size = st.st_size
126 if request:
126 if request:
127 self.repo.ui.environ = request.env
127 self.repo.ui.environ = request.env
128
128
129 def run(self):
129 def run(self):
130 if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."):
130 if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."):
131 raise RuntimeError("This function is only intended to be "
131 raise RuntimeError("This function is only intended to be "
132 "called while running as a CGI script.")
132 "called while running as a CGI script.")
133 import mercurial.hgweb.wsgicgi as wsgicgi
133 import mercurial.hgweb.wsgicgi as wsgicgi
134 wsgicgi.launch(self)
134 wsgicgi.launch(self)
135
135
136 def __call__(self, env, respond):
136 def __call__(self, env, respond):
137 req = wsgirequest(env, respond)
137 req = wsgirequest(env, respond)
138 return self.run_wsgi(req)
138 return self.run_wsgi(req)
139
139
140 def run_wsgi(self, req):
140 def run_wsgi(self, req):
141
141
142 self.refresh(req)
142 self.refresh(req)
143
143
144 # work with CGI variables to create coherent structure
144 # work with CGI variables to create coherent structure
145 # use SCRIPT_NAME, PATH_INFO and QUERY_STRING as well as our REPO_NAME
145 # use SCRIPT_NAME, PATH_INFO and QUERY_STRING as well as our REPO_NAME
146
146
147 req.url = req.env['SCRIPT_NAME']
147 req.url = req.env['SCRIPT_NAME']
148 if not req.url.endswith('/'):
148 if not req.url.endswith('/'):
149 req.url += '/'
149 req.url += '/'
150 if 'REPO_NAME' in req.env:
150 if 'REPO_NAME' in req.env:
151 req.url += req.env['REPO_NAME'] + '/'
151 req.url += req.env['REPO_NAME'] + '/'
152
152
153 if 'PATH_INFO' in req.env:
153 if 'PATH_INFO' in req.env:
154 parts = req.env['PATH_INFO'].strip('/').split('/')
154 parts = req.env['PATH_INFO'].strip('/').split('/')
155 repo_parts = req.env.get('REPO_NAME', '').split('/')
155 repo_parts = req.env.get('REPO_NAME', '').split('/')
156 if parts[:len(repo_parts)] == repo_parts:
156 if parts[:len(repo_parts)] == repo_parts:
157 parts = parts[len(repo_parts):]
157 parts = parts[len(repo_parts):]
158 query = '/'.join(parts)
158 query = '/'.join(parts)
159 else:
159 else:
160 query = req.env['QUERY_STRING'].split('&', 1)[0]
160 query = req.env['QUERY_STRING'].split('&', 1)[0]
161 query = query.split(';', 1)[0]
161 query = query.split(';', 1)[0]
162
162
163 # process this if it's a protocol request
163 # process this if it's a protocol request
164 # protocol bits don't need to create any URLs
164 # protocol bits don't need to create any URLs
165 # and the clients always use the old URL structure
165 # and the clients always use the old URL structure
166
166
167 cmd = req.form.get('cmd', [''])[0]
167 cmd = req.form.get('cmd', [''])[0]
168 if protocol.iscmd(cmd):
168 if protocol.iscmd(cmd):
169 try:
169 try:
170 if query:
170 if query:
171 raise ErrorResponse(HTTP_NOT_FOUND)
171 raise ErrorResponse(HTTP_NOT_FOUND)
172 if cmd in perms:
172 if cmd in perms:
173 self.check_perm(req, perms[cmd])
173 self.check_perm(req, perms[cmd])
174 return protocol.call(self.repo, req, cmd)
174 return protocol.call(self.repo, req, cmd)
175 except ErrorResponse, inst:
175 except ErrorResponse, inst:
176 # A client that sends unbundle without 100-continue will
176 # A client that sends unbundle without 100-continue will
177 # break if we respond early.
177 # break if we respond early.
178 if (cmd == 'unbundle' and
178 if (cmd == 'unbundle' and
179 (req.env.get('HTTP_EXPECT',
179 (req.env.get('HTTP_EXPECT',
180 '').lower() != '100-continue') or
180 '').lower() != '100-continue') or
181 req.env.get('X-HgHttp2', '')):
181 req.env.get('X-HgHttp2', '')):
182 req.drain()
182 req.drain()
183 else:
183 else:
184 req.headers.append(('Connection', 'Close'))
184 req.headers.append(('Connection', 'Close'))
185 req.respond(inst, protocol.HGTYPE,
185 req.respond(inst, protocol.HGTYPE,
186 body='0\n%s\n' % inst.message)
186 body='0\n%s\n' % inst.message)
187 return ''
187 return ''
188
188
189 # translate user-visible url structure to internal structure
189 # translate user-visible url structure to internal structure
190
190
191 args = query.split('/', 2)
191 args = query.split('/', 2)
192 if 'cmd' not in req.form and args and args[0]:
192 if 'cmd' not in req.form and args and args[0]:
193
193
194 cmd = args.pop(0)
194 cmd = args.pop(0)
195 style = cmd.rfind('-')
195 style = cmd.rfind('-')
196 if style != -1:
196 if style != -1:
197 req.form['style'] = [cmd[:style]]
197 req.form['style'] = [cmd[:style]]
198 cmd = cmd[style + 1:]
198 cmd = cmd[style + 1:]
199
199
200 # avoid accepting e.g. style parameter as command
200 # avoid accepting e.g. style parameter as command
201 if util.safehasattr(webcommands, cmd):
201 if util.safehasattr(webcommands, cmd):
202 req.form['cmd'] = [cmd]
202 req.form['cmd'] = [cmd]
203 else:
204 cmd = ''
205
203
206 if cmd == 'static':
204 if cmd == 'static':
207 req.form['file'] = ['/'.join(args)]
205 req.form['file'] = ['/'.join(args)]
208 else:
206 else:
209 if args and args[0]:
207 if args and args[0]:
210 node = args.pop(0)
208 node = args.pop(0)
211 req.form['node'] = [node]
209 req.form['node'] = [node]
212 if args:
210 if args:
213 req.form['file'] = args
211 req.form['file'] = args
214
212
215 ua = req.env.get('HTTP_USER_AGENT', '')
213 ua = req.env.get('HTTP_USER_AGENT', '')
216 if cmd == 'rev' and 'mercurial' in ua:
214 if cmd == 'rev' and 'mercurial' in ua:
217 req.form['style'] = ['raw']
215 req.form['style'] = ['raw']
218
216
219 if cmd == 'archive':
217 if cmd == 'archive':
220 fn = req.form['node'][0]
218 fn = req.form['node'][0]
221 for type_, spec in self.archive_specs.iteritems():
219 for type_, spec in self.archive_specs.iteritems():
222 ext = spec[2]
220 ext = spec[2]
223 if fn.endswith(ext):
221 if fn.endswith(ext):
224 req.form['node'] = [fn[:-len(ext)]]
222 req.form['node'] = [fn[:-len(ext)]]
225 req.form['type'] = [type_]
223 req.form['type'] = [type_]
226
224
227 # process the web interface request
225 # process the web interface request
228
226
229 try:
227 try:
230 tmpl = self.templater(req)
228 tmpl = self.templater(req)
231 ctype = tmpl('mimetype', encoding=encoding.encoding)
229 ctype = tmpl('mimetype', encoding=encoding.encoding)
232 ctype = templater.stringify(ctype)
230 ctype = templater.stringify(ctype)
233
231
234 # check read permissions non-static content
232 # check read permissions non-static content
235 if cmd != 'static':
233 if cmd != 'static':
236 self.check_perm(req, None)
234 self.check_perm(req, None)
237
235
238 if cmd == '':
236 if cmd == '':
239 req.form['cmd'] = [tmpl.cache['default']]
237 req.form['cmd'] = [tmpl.cache['default']]
240 cmd = req.form['cmd'][0]
238 cmd = req.form['cmd'][0]
241
239
242 if self.configbool('web', 'cache', True):
240 if self.configbool('web', 'cache', True):
243 caching(self, req) # sets ETag header or raises NOT_MODIFIED
241 caching(self, req) # sets ETag header or raises NOT_MODIFIED
244 if cmd not in webcommands.__all__:
242 if cmd not in webcommands.__all__:
245 msg = 'no such method: %s' % cmd
243 msg = 'no such method: %s' % cmd
246 raise ErrorResponse(HTTP_BAD_REQUEST, msg)
244 raise ErrorResponse(HTTP_BAD_REQUEST, msg)
247 elif cmd == 'file' and 'raw' in req.form.get('style', []):
245 elif cmd == 'file' and 'raw' in req.form.get('style', []):
248 self.ctype = ctype
246 self.ctype = ctype
249 content = webcommands.rawfile(self, req, tmpl)
247 content = webcommands.rawfile(self, req, tmpl)
250 else:
248 else:
251 content = getattr(webcommands, cmd)(self, req, tmpl)
249 content = getattr(webcommands, cmd)(self, req, tmpl)
252 req.respond(HTTP_OK, ctype)
250 req.respond(HTTP_OK, ctype)
253
251
254 return content
252 return content
255
253
256 except (error.LookupError, error.RepoLookupError), err:
254 except (error.LookupError, error.RepoLookupError), err:
257 req.respond(HTTP_NOT_FOUND, ctype)
255 req.respond(HTTP_NOT_FOUND, ctype)
258 msg = str(err)
256 msg = str(err)
259 if (util.safehasattr(err, 'name') and
257 if (util.safehasattr(err, 'name') and
260 not isinstance(err, error.ManifestLookupError)):
258 not isinstance(err, error.ManifestLookupError)):
261 msg = 'revision not found: %s' % err.name
259 msg = 'revision not found: %s' % err.name
262 return tmpl('error', error=msg)
260 return tmpl('error', error=msg)
263 except (error.RepoError, error.RevlogError), inst:
261 except (error.RepoError, error.RevlogError), inst:
264 req.respond(HTTP_SERVER_ERROR, ctype)
262 req.respond(HTTP_SERVER_ERROR, ctype)
265 return tmpl('error', error=str(inst))
263 return tmpl('error', error=str(inst))
266 except ErrorResponse, inst:
264 except ErrorResponse, inst:
267 req.respond(inst, ctype)
265 req.respond(inst, ctype)
268 if inst.code == HTTP_NOT_MODIFIED:
266 if inst.code == HTTP_NOT_MODIFIED:
269 # Not allowed to return a body on a 304
267 # Not allowed to return a body on a 304
270 return ['']
268 return ['']
271 return tmpl('error', error=inst.message)
269 return tmpl('error', error=inst.message)
272
270
273 def loadwebsub(self):
271 def loadwebsub(self):
274 websubtable = []
272 websubtable = []
275 websubdefs = self.repo.ui.configitems('websub')
273 websubdefs = self.repo.ui.configitems('websub')
276 # we must maintain interhg backwards compatibility
274 # we must maintain interhg backwards compatibility
277 websubdefs += self.repo.ui.configitems('interhg')
275 websubdefs += self.repo.ui.configitems('interhg')
278 for key, pattern in websubdefs:
276 for key, pattern in websubdefs:
279 # grab the delimiter from the character after the "s"
277 # grab the delimiter from the character after the "s"
280 unesc = pattern[1]
278 unesc = pattern[1]
281 delim = re.escape(unesc)
279 delim = re.escape(unesc)
282
280
283 # identify portions of the pattern, taking care to avoid escaped
281 # identify portions of the pattern, taking care to avoid escaped
284 # delimiters. the replace format and flags are optional, but
282 # delimiters. the replace format and flags are optional, but
285 # delimiters are required.
283 # delimiters are required.
286 match = re.match(
284 match = re.match(
287 r'^s%s(.+)(?:(?<=\\\\)|(?<!\\))%s(.*)%s([ilmsux])*$'
285 r'^s%s(.+)(?:(?<=\\\\)|(?<!\\))%s(.*)%s([ilmsux])*$'
288 % (delim, delim, delim), pattern)
286 % (delim, delim, delim), pattern)
289 if not match:
287 if not match:
290 self.repo.ui.warn(_("websub: invalid pattern for %s: %s\n")
288 self.repo.ui.warn(_("websub: invalid pattern for %s: %s\n")
291 % (key, pattern))
289 % (key, pattern))
292 continue
290 continue
293
291
294 # we need to unescape the delimiter for regexp and format
292 # we need to unescape the delimiter for regexp and format
295 delim_re = re.compile(r'(?<!\\)\\%s' % delim)
293 delim_re = re.compile(r'(?<!\\)\\%s' % delim)
296 regexp = delim_re.sub(unesc, match.group(1))
294 regexp = delim_re.sub(unesc, match.group(1))
297 format = delim_re.sub(unesc, match.group(2))
295 format = delim_re.sub(unesc, match.group(2))
298
296
299 # the pattern allows for 6 regexp flags, so set them if necessary
297 # the pattern allows for 6 regexp flags, so set them if necessary
300 flagin = match.group(3)
298 flagin = match.group(3)
301 flags = 0
299 flags = 0
302 if flagin:
300 if flagin:
303 for flag in flagin.upper():
301 for flag in flagin.upper():
304 flags |= re.__dict__[flag]
302 flags |= re.__dict__[flag]
305
303
306 try:
304 try:
307 regexp = re.compile(regexp, flags)
305 regexp = re.compile(regexp, flags)
308 websubtable.append((regexp, format))
306 websubtable.append((regexp, format))
309 except re.error:
307 except re.error:
310 self.repo.ui.warn(_("websub: invalid regexp for %s: %s\n")
308 self.repo.ui.warn(_("websub: invalid regexp for %s: %s\n")
311 % (key, regexp))
309 % (key, regexp))
312 return websubtable
310 return websubtable
313
311
314 def templater(self, req):
312 def templater(self, req):
315
313
316 # determine scheme, port and server name
314 # determine scheme, port and server name
317 # this is needed to create absolute urls
315 # this is needed to create absolute urls
318
316
319 proto = req.env.get('wsgi.url_scheme')
317 proto = req.env.get('wsgi.url_scheme')
320 if proto == 'https':
318 if proto == 'https':
321 proto = 'https'
319 proto = 'https'
322 default_port = "443"
320 default_port = "443"
323 else:
321 else:
324 proto = 'http'
322 proto = 'http'
325 default_port = "80"
323 default_port = "80"
326
324
327 port = req.env["SERVER_PORT"]
325 port = req.env["SERVER_PORT"]
328 port = port != default_port and (":" + port) or ""
326 port = port != default_port and (":" + port) or ""
329 urlbase = '%s://%s%s' % (proto, req.env['SERVER_NAME'], port)
327 urlbase = '%s://%s%s' % (proto, req.env['SERVER_NAME'], port)
330 logourl = self.config("web", "logourl", "http://mercurial.selenic.com/")
328 logourl = self.config("web", "logourl", "http://mercurial.selenic.com/")
331 logoimg = self.config("web", "logoimg", "hglogo.png")
329 logoimg = self.config("web", "logoimg", "hglogo.png")
332 staticurl = self.config("web", "staticurl") or req.url + 'static/'
330 staticurl = self.config("web", "staticurl") or req.url + 'static/'
333 if not staticurl.endswith('/'):
331 if not staticurl.endswith('/'):
334 staticurl += '/'
332 staticurl += '/'
335
333
336 # some functions for the templater
334 # some functions for the templater
337
335
338 def motd(**map):
336 def motd(**map):
339 yield self.config("web", "motd", "")
337 yield self.config("web", "motd", "")
340
338
341 # figure out which style to use
339 # figure out which style to use
342
340
343 vars = {}
341 vars = {}
344 styles = (
342 styles = (
345 req.form.get('style', [None])[0],
343 req.form.get('style', [None])[0],
346 self.config('web', 'style'),
344 self.config('web', 'style'),
347 'paper',
345 'paper',
348 )
346 )
349 style, mapfile = templater.stylemap(styles, self.templatepath)
347 style, mapfile = templater.stylemap(styles, self.templatepath)
350 if style == styles[0]:
348 if style == styles[0]:
351 vars['style'] = style
349 vars['style'] = style
352
350
353 start = req.url[-1] == '?' and '&' or '?'
351 start = req.url[-1] == '?' and '&' or '?'
354 sessionvars = webutil.sessionvars(vars, start)
352 sessionvars = webutil.sessionvars(vars, start)
355
353
356 if not self.reponame:
354 if not self.reponame:
357 self.reponame = (self.config("web", "name")
355 self.reponame = (self.config("web", "name")
358 or req.env.get('REPO_NAME')
356 or req.env.get('REPO_NAME')
359 or req.url.strip('/') or self.repo.root)
357 or req.url.strip('/') or self.repo.root)
360
358
361 def websubfilter(text):
359 def websubfilter(text):
362 return websub(text, self.websubtable)
360 return websub(text, self.websubtable)
363
361
364 # create the templater
362 # create the templater
365
363
366 tmpl = templater.templater(mapfile,
364 tmpl = templater.templater(mapfile,
367 filters={"websub": websubfilter},
365 filters={"websub": websubfilter},
368 defaults={"url": req.url,
366 defaults={"url": req.url,
369 "logourl": logourl,
367 "logourl": logourl,
370 "logoimg": logoimg,
368 "logoimg": logoimg,
371 "staticurl": staticurl,
369 "staticurl": staticurl,
372 "urlbase": urlbase,
370 "urlbase": urlbase,
373 "repo": self.reponame,
371 "repo": self.reponame,
374 "encoding": encoding.encoding,
372 "encoding": encoding.encoding,
375 "motd": motd,
373 "motd": motd,
376 "sessionvars": sessionvars,
374 "sessionvars": sessionvars,
377 "pathdef": makebreadcrumb(req.url),
375 "pathdef": makebreadcrumb(req.url),
378 "style": style,
376 "style": style,
379 })
377 })
380 return tmpl
378 return tmpl
381
379
382 def archivelist(self, nodeid):
380 def archivelist(self, nodeid):
383 allowed = self.configlist("web", "allow_archive")
381 allowed = self.configlist("web", "allow_archive")
384 for i, spec in self.archive_specs.iteritems():
382 for i, spec in self.archive_specs.iteritems():
385 if i in allowed or self.configbool("web", "allow" + i):
383 if i in allowed or self.configbool("web", "allow" + i):
386 yield {"type" : i, "extension" : spec[2], "node" : nodeid}
384 yield {"type" : i, "extension" : spec[2], "node" : nodeid}
387
385
388 archive_specs = {
386 archive_specs = {
389 'bz2': ('application/x-bzip2', 'tbz2', '.tar.bz2', None),
387 'bz2': ('application/x-bzip2', 'tbz2', '.tar.bz2', None),
390 'gz': ('application/x-gzip', 'tgz', '.tar.gz', None),
388 'gz': ('application/x-gzip', 'tgz', '.tar.gz', None),
391 'zip': ('application/zip', 'zip', '.zip', None),
389 'zip': ('application/zip', 'zip', '.zip', None),
392 }
390 }
393
391
394 def check_perm(self, req, op):
392 def check_perm(self, req, op):
395 for hook in permhooks:
393 for hook in permhooks:
396 hook(self, req, op)
394 hook(self, req, op)
@@ -1,520 +1,538 b''
1 $ "$TESTDIR/hghave" serve || exit 80
1 $ "$TESTDIR/hghave" serve || exit 80
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'
126 400 no such method: spam
127 [1]
128
129 should give a 400 - bad command as a part of url path (issue4071)
130
131 $ "$TESTDIR/get-with-headers.py" --headeronly localhost:$HGPORT 'spam'
132 400 no such method: spam
133 [1]
134
135 $ "$TESTDIR/get-with-headers.py" --headeronly localhost:$HGPORT 'raw-spam'
136 400 no such method: spam
137 [1]
138
139 $ "$TESTDIR/get-with-headers.py" --headeronly localhost:$HGPORT 'spam/tip/foo'
140 400 no such method: spam
141 [1]
142
125 should give a 404 - file does not exist
143 should give a 404 - file does not exist
126
144
127 $ "$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'
128 404 Not Found
146 404 Not Found
129
147
130
148
131 error: bork@2ef0ac749a14: not found in manifest
149 error: bork@2ef0ac749a14: not found in manifest
132 [1]
150 [1]
133 $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT 'file/tip/bork'
151 $ "$TESTDIR/get-with-headers.py" localhost:$HGPORT 'file/tip/bork'
134 404 Not Found
152 404 Not Found
135
153
136 <!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">
137 <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">
138 <head>
156 <head>
139 <link rel="icon" href="/static/hgicon.png" type="image/png" />
157 <link rel="icon" href="/static/hgicon.png" type="image/png" />
140 <meta name="robots" content="index, nofollow" />
158 <meta name="robots" content="index, nofollow" />
141 <link rel="stylesheet" href="/static/style-paper.css" type="text/css" />
159 <link rel="stylesheet" href="/static/style-paper.css" type="text/css" />
142 <script type="text/javascript" src="/static/mercurial.js"></script>
160 <script type="text/javascript" src="/static/mercurial.js"></script>
143
161
144 <title>test: error</title>
162 <title>test: error</title>
145 </head>
163 </head>
146 <body>
164 <body>
147
165
148 <div class="container">
166 <div class="container">
149 <div class="menu">
167 <div class="menu">
150 <div class="logo">
168 <div class="logo">
151 <a href="http://mercurial.selenic.com/">
169 <a href="http://mercurial.selenic.com/">
152 <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>
153 </div>
171 </div>
154 <ul>
172 <ul>
155 <li><a href="/shortlog">log</a></li>
173 <li><a href="/shortlog">log</a></li>
156 <li><a href="/graph">graph</a></li>
174 <li><a href="/graph">graph</a></li>
157 <li><a href="/tags">tags</a></li>
175 <li><a href="/tags">tags</a></li>
158 <li><a href="/bookmarks">bookmarks</a></li>
176 <li><a href="/bookmarks">bookmarks</a></li>
159 <li><a href="/branches">branches</a></li>
177 <li><a href="/branches">branches</a></li>
160 </ul>
178 </ul>
161 <ul>
179 <ul>
162 <li><a href="/help">help</a></li>
180 <li><a href="/help">help</a></li>
163 </ul>
181 </ul>
164 </div>
182 </div>
165
183
166 <div class="main">
184 <div class="main">
167
185
168 <h2 class="breadcrumb"><a href="/">Mercurial</a> </h2>
186 <h2 class="breadcrumb"><a href="/">Mercurial</a> </h2>
169 <h3>error</h3>
187 <h3>error</h3>
170
188
171 <form class="search" action="/log">
189 <form class="search" action="/log">
172
190
173 <p><input name="rev" id="search1" type="text" size="30"></p>
191 <p><input name="rev" id="search1" type="text" size="30"></p>
174 <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
175 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>
176 </form>
194 </form>
177
195
178 <div class="description">
196 <div class="description">
179 <p>
197 <p>
180 An error occurred while processing your request:
198 An error occurred while processing your request:
181 </p>
199 </p>
182 <p>
200 <p>
183 bork@2ef0ac749a14: not found in manifest
201 bork@2ef0ac749a14: not found in manifest
184 </p>
202 </p>
185 </div>
203 </div>
186 </div>
204 </div>
187 </div>
205 </div>
188
206
189 <script type="text/javascript">process_dates()</script>
207 <script type="text/javascript">process_dates()</script>
190
208
191
209
192 </body>
210 </body>
193 </html>
211 </html>
194
212
195 [1]
213 [1]
196 $ "$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'
197 404 Not Found
215 404 Not Found
198
216
199
217
200 error: bork@2ef0ac749a14: not found in manifest
218 error: bork@2ef0ac749a14: not found in manifest
201 [1]
219 [1]
202
220
203 try bad style
221 try bad style
204
222
205 $ ("$TESTDIR/get-with-headers.py" localhost:$HGPORT 'file/tip/?style=foobar')
223 $ ("$TESTDIR/get-with-headers.py" localhost:$HGPORT 'file/tip/?style=foobar')
206 200 Script output follows
224 200 Script output follows
207
225
208 <!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">
209 <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">
210 <head>
228 <head>
211 <link rel="icon" href="/static/hgicon.png" type="image/png" />
229 <link rel="icon" href="/static/hgicon.png" type="image/png" />
212 <meta name="robots" content="index, nofollow" />
230 <meta name="robots" content="index, nofollow" />
213 <link rel="stylesheet" href="/static/style-paper.css" type="text/css" />
231 <link rel="stylesheet" href="/static/style-paper.css" type="text/css" />
214 <script type="text/javascript" src="/static/mercurial.js"></script>
232 <script type="text/javascript" src="/static/mercurial.js"></script>
215
233
216 <title>test: 2ef0ac749a14 /</title>
234 <title>test: 2ef0ac749a14 /</title>
217 </head>
235 </head>
218 <body>
236 <body>
219
237
220 <div class="container">
238 <div class="container">
221 <div class="menu">
239 <div class="menu">
222 <div class="logo">
240 <div class="logo">
223 <a href="http://mercurial.selenic.com/">
241 <a href="http://mercurial.selenic.com/">
224 <img src="/static/hglogo.png" alt="mercurial" /></a>
242 <img src="/static/hglogo.png" alt="mercurial" /></a>
225 </div>
243 </div>
226 <ul>
244 <ul>
227 <li><a href="/shortlog/2ef0ac749a14">log</a></li>
245 <li><a href="/shortlog/2ef0ac749a14">log</a></li>
228 <li><a href="/graph/2ef0ac749a14">graph</a></li>
246 <li><a href="/graph/2ef0ac749a14">graph</a></li>
229 <li><a href="/tags">tags</a></li>
247 <li><a href="/tags">tags</a></li>
230 <li><a href="/bookmarks">bookmarks</a></li>
248 <li><a href="/bookmarks">bookmarks</a></li>
231 <li><a href="/branches">branches</a></li>
249 <li><a href="/branches">branches</a></li>
232 </ul>
250 </ul>
233 <ul>
251 <ul>
234 <li><a href="/rev/2ef0ac749a14">changeset</a></li>
252 <li><a href="/rev/2ef0ac749a14">changeset</a></li>
235 <li class="active">browse</li>
253 <li class="active">browse</li>
236 </ul>
254 </ul>
237 <ul>
255 <ul>
238
256
239 </ul>
257 </ul>
240 <ul>
258 <ul>
241 <li><a href="/help">help</a></li>
259 <li><a href="/help">help</a></li>
242 </ul>
260 </ul>
243 </div>
261 </div>
244
262
245 <div class="main">
263 <div class="main">
246 <h2 class="breadcrumb"><a href="/">Mercurial</a> </h2>
264 <h2 class="breadcrumb"><a href="/">Mercurial</a> </h2>
247 <h3>directory / @ 0:2ef0ac749a14 <span class="tag">tip</span> </h3>
265 <h3>directory / @ 0:2ef0ac749a14 <span class="tag">tip</span> </h3>
248
266
249 <form class="search" action="/log">
267 <form class="search" action="/log">
250
268
251 <p><input name="rev" id="search1" type="text" size="30" /></p>
269 <p><input name="rev" id="search1" type="text" size="30" /></p>
252 <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
253 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>
254 </form>
272 </form>
255
273
256 <table class="bigtable">
274 <table class="bigtable">
257 <tr>
275 <tr>
258 <th class="name">name</th>
276 <th class="name">name</th>
259 <th class="size">size</th>
277 <th class="size">size</th>
260 <th class="permissions">permissions</th>
278 <th class="permissions">permissions</th>
261 </tr>
279 </tr>
262 <tbody class="stripes2">
280 <tbody class="stripes2">
263 <tr class="fileline">
281 <tr class="fileline">
264 <td class="name"><a href="/file/2ef0ac749a14/">[up]</a></td>
282 <td class="name"><a href="/file/2ef0ac749a14/">[up]</a></td>
265 <td class="size"></td>
283 <td class="size"></td>
266 <td class="permissions">drwxr-xr-x</td>
284 <td class="permissions">drwxr-xr-x</td>
267 </tr>
285 </tr>
268
286
269 <tr class="fileline">
287 <tr class="fileline">
270 <td class="name">
288 <td class="name">
271 <a href="/file/2ef0ac749a14/da">
289 <a href="/file/2ef0ac749a14/da">
272 <img src="/static/coal-folder.png" alt="dir."/> da/
290 <img src="/static/coal-folder.png" alt="dir."/> da/
273 </a>
291 </a>
274 <a href="/file/2ef0ac749a14/da/">
292 <a href="/file/2ef0ac749a14/da/">
275
293
276 </a>
294 </a>
277 </td>
295 </td>
278 <td class="size"></td>
296 <td class="size"></td>
279 <td class="permissions">drwxr-xr-x</td>
297 <td class="permissions">drwxr-xr-x</td>
280 </tr>
298 </tr>
281
299
282 <tr class="fileline">
300 <tr class="fileline">
283 <td class="filename">
301 <td class="filename">
284 <a href="/file/2ef0ac749a14/foo">
302 <a href="/file/2ef0ac749a14/foo">
285 <img src="/static/coal-file.png" alt="file"/> foo
303 <img src="/static/coal-file.png" alt="file"/> foo
286 </a>
304 </a>
287 </td>
305 </td>
288 <td class="size">4</td>
306 <td class="size">4</td>
289 <td class="permissions">-rw-r--r--</td>
307 <td class="permissions">-rw-r--r--</td>
290 </tr>
308 </tr>
291 </tbody>
309 </tbody>
292 </table>
310 </table>
293 </div>
311 </div>
294 </div>
312 </div>
295 <script type="text/javascript">process_dates()</script>
313 <script type="text/javascript">process_dates()</script>
296
314
297
315
298 </body>
316 </body>
299 </html>
317 </html>
300
318
301
319
302 stop and restart
320 stop and restart
303
321
304 $ "$TESTDIR/killdaemons.py" $DAEMON_PIDS
322 $ "$TESTDIR/killdaemons.py" $DAEMON_PIDS
305 $ 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
306 $ cat hg.pid >> $DAEMON_PIDS
324 $ cat hg.pid >> $DAEMON_PIDS
307
325
308 Test the access/error files are opened in append mode
326 Test the access/error files are opened in append mode
309
327
310 $ python -c "print len(file('access.log').readlines()), 'log lines written'"
328 $ python -c "print len(file('access.log').readlines()), 'log lines written'"
311 10 log lines written
329 14 log lines written
312
330
313 static file
331 static file
314
332
315 $ "$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
316 200 Script output follows
334 200 Script output follows
317 content-length: 5262
335 content-length: 5262
318 content-type: text/css
336 content-type: text/css
319
337
320 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; }
321 a { color:#0000cc; }
339 a { color:#0000cc; }
322 a:hover, a:visited, a:active { color:#880000; }
340 a:hover, a:visited, a:active { color:#880000; }
323 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; }
324 div.page_header a:visited { color:#0000cc; }
342 div.page_header a:visited { color:#0000cc; }
325 div.page_header a:hover { color:#880000; }
343 div.page_header a:hover { color:#880000; }
326 div.page_nav { padding:8px; }
344 div.page_nav { padding:8px; }
327 div.page_nav a:visited { color:#0000cc; }
345 div.page_nav a:visited { color:#0000cc; }
328 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}
329 div.page_footer { padding:4px 8px; background-color: #d9d8d1; }
347 div.page_footer { padding:4px 8px; background-color: #d9d8d1; }
330 div.page_footer_text { float:left; color:#555555; font-style:italic; }
348 div.page_footer_text { float:left; color:#555555; font-style:italic; }
331 div.page_body { padding:8px; }
349 div.page_body { padding:8px; }
332 div.title, a.title {
350 div.title, a.title {
333 display:block; padding:6px 8px;
351 display:block; padding:6px 8px;
334 font-weight:bold; background-color:#edece6; text-decoration:none; color:#000000;
352 font-weight:bold; background-color:#edece6; text-decoration:none; color:#000000;
335 }
353 }
336 a.title:hover { background-color: #d9d8d1; }
354 a.title:hover { background-color: #d9d8d1; }
337 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; }
338 div.log_body { padding:8px 8px 8px 150px; }
356 div.log_body { padding:8px 8px 8px 150px; }
339 .age { white-space:nowrap; }
357 .age { white-space:nowrap; }
340 span.age { position:relative; float:left; width:142px; font-style:italic; }
358 span.age { position:relative; float:left; width:142px; font-style:italic; }
341 div.log_link {
359 div.log_link {
342 padding:0px 8px;
360 padding:0px 8px;
343 font-size:10px; font-family:sans-serif; font-style:normal;
361 font-size:10px; font-family:sans-serif; font-style:normal;
344 position:relative; float:left; width:136px;
362 position:relative; float:left; width:136px;
345 }
363 }
346 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; }
347 a.list { text-decoration:none; color:#000000; }
365 a.list { text-decoration:none; color:#000000; }
348 a.list:hover { text-decoration:underline; color:#880000; }
366 a.list:hover { text-decoration:underline; color:#880000; }
349 table { padding:8px 4px; }
367 table { padding:8px 4px; }
350 th { padding:2px 5px; font-size:12px; text-align:left; }
368 th { padding:2px 5px; font-size:12px; text-align:left; }
351 tr.light:hover, .parity0:hover { background-color:#edece6; }
369 tr.light:hover, .parity0:hover { background-color:#edece6; }
352 tr.dark, .parity1 { background-color:#f6f6f0; }
370 tr.dark, .parity1 { background-color:#f6f6f0; }
353 tr.dark:hover, .parity1:hover { background-color:#edece6; }
371 tr.dark:hover, .parity1:hover { background-color:#edece6; }
354 td { padding:2px 5px; font-size:12px; vertical-align:top; }
372 td { padding:2px 5px; font-size:12px; vertical-align:top; }
355 td.closed { background-color: #99f; }
373 td.closed { background-color: #99f; }
356 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; }
357 td.indexlinks { white-space: nowrap; }
375 td.indexlinks { white-space: nowrap; }
358 td.indexlinks a {
376 td.indexlinks a {
359 padding: 2px 5px; line-height: 10px;
377 padding: 2px 5px; line-height: 10px;
360 border: 1px solid;
378 border: 1px solid;
361 color: #ffffff; background-color: #7777bb;
379 color: #ffffff; background-color: #7777bb;
362 border-color: #aaaadd #333366 #333366 #aaaadd;
380 border-color: #aaaadd #333366 #333366 #aaaadd;
363 font-weight: bold; text-align: center; text-decoration: none;
381 font-weight: bold; text-align: center; text-decoration: none;
364 font-size: 10px;
382 font-size: 10px;
365 }
383 }
366 td.indexlinks a:hover { background-color: #6666aa; }
384 td.indexlinks a:hover { background-color: #6666aa; }
367 div.pre { font-family:monospace; font-size:12px; white-space:pre; }
385 div.pre { font-family:monospace; font-size:12px; white-space:pre; }
368 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; }
369 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; }
370 div.search { margin:4px 8px; position:absolute; top:56px; right:12px }
388 div.search { margin:4px 8px; position:absolute; top:56px; right:12px }
371 .linenr { color:#999999; text-decoration:none }
389 .linenr { color:#999999; text-decoration:none }
372 div.rss_logo { float: right; white-space: nowrap; }
390 div.rss_logo { float: right; white-space: nowrap; }
373 div.rss_logo a {
391 div.rss_logo a {
374 padding:3px 6px; line-height:10px;
392 padding:3px 6px; line-height:10px;
375 border:1px solid; border-color:#fcc7a5 #7d3302 #3e1a01 #ff954e;
393 border:1px solid; border-color:#fcc7a5 #7d3302 #3e1a01 #ff954e;
376 color:#ffffff; background-color:#ff6600;
394 color:#ffffff; background-color:#ff6600;
377 font-weight:bold; font-family:sans-serif; font-size:10px;
395 font-weight:bold; font-family:sans-serif; font-size:10px;
378 text-align:center; text-decoration:none;
396 text-align:center; text-decoration:none;
379 }
397 }
380 div.rss_logo a:hover { background-color:#ee5500; }
398 div.rss_logo a:hover { background-color:#ee5500; }
381 pre { margin: 0; }
399 pre { margin: 0; }
382 span.logtags span {
400 span.logtags span {
383 padding: 0px 4px;
401 padding: 0px 4px;
384 font-size: 10px;
402 font-size: 10px;
385 font-weight: normal;
403 font-weight: normal;
386 border: 1px solid;
404 border: 1px solid;
387 background-color: #ffaaff;
405 background-color: #ffaaff;
388 border-color: #ffccff #ff00ee #ff00ee #ffccff;
406 border-color: #ffccff #ff00ee #ff00ee #ffccff;
389 }
407 }
390 span.logtags span.tagtag {
408 span.logtags span.tagtag {
391 background-color: #ffffaa;
409 background-color: #ffffaa;
392 border-color: #ffffcc #ffee00 #ffee00 #ffffcc;
410 border-color: #ffffcc #ffee00 #ffee00 #ffffcc;
393 }
411 }
394 span.logtags span.branchtag {
412 span.logtags span.branchtag {
395 background-color: #aaffaa;
413 background-color: #aaffaa;
396 border-color: #ccffcc #00cc33 #00cc33 #ccffcc;
414 border-color: #ccffcc #00cc33 #00cc33 #ccffcc;
397 }
415 }
398 span.logtags span.inbranchtag {
416 span.logtags span.inbranchtag {
399 background-color: #d5dde6;
417 background-color: #d5dde6;
400 border-color: #e3ecf4 #9398f4 #9398f4 #e3ecf4;
418 border-color: #e3ecf4 #9398f4 #9398f4 #e3ecf4;
401 }
419 }
402 span.logtags span.bookmarktag {
420 span.logtags span.bookmarktag {
403 background-color: #afdffa;
421 background-color: #afdffa;
404 border-color: #ccecff #46ace6 #46ace6 #ccecff;
422 border-color: #ccecff #46ace6 #46ace6 #ccecff;
405 }
423 }
406
424
407 /* Graph */
425 /* Graph */
408 div#wrapper {
426 div#wrapper {
409 position: relative;
427 position: relative;
410 margin: 0;
428 margin: 0;
411 padding: 0;
429 padding: 0;
412 margin-top: 3px;
430 margin-top: 3px;
413 }
431 }
414
432
415 canvas {
433 canvas {
416 position: absolute;
434 position: absolute;
417 z-index: 5;
435 z-index: 5;
418 top: -0.9em;
436 top: -0.9em;
419 margin: 0;
437 margin: 0;
420 }
438 }
421
439
422 ul#nodebgs {
440 ul#nodebgs {
423 list-style: none inside none;
441 list-style: none inside none;
424 padding: 0;
442 padding: 0;
425 margin: 0;
443 margin: 0;
426 top: -0.7em;
444 top: -0.7em;
427 }
445 }
428
446
429 ul#graphnodes li, ul#nodebgs li {
447 ul#graphnodes li, ul#nodebgs li {
430 height: 39px;
448 height: 39px;
431 }
449 }
432
450
433 ul#graphnodes {
451 ul#graphnodes {
434 position: absolute;
452 position: absolute;
435 z-index: 10;
453 z-index: 10;
436 top: -0.8em;
454 top: -0.8em;
437 list-style: none inside none;
455 list-style: none inside none;
438 padding: 0;
456 padding: 0;
439 }
457 }
440
458
441 ul#graphnodes li .info {
459 ul#graphnodes li .info {
442 display: block;
460 display: block;
443 font-size: 100%;
461 font-size: 100%;
444 position: relative;
462 position: relative;
445 top: -3px;
463 top: -3px;
446 font-style: italic;
464 font-style: italic;
447 }
465 }
448
466
449 /* Comparison */
467 /* Comparison */
450 .legend {
468 .legend {
451 padding: 1.5% 0 1.5% 0;
469 padding: 1.5% 0 1.5% 0;
452 }
470 }
453
471
454 .legendinfo {
472 .legendinfo {
455 border: 1px solid #d9d8d1;
473 border: 1px solid #d9d8d1;
456 font-size: 80%;
474 font-size: 80%;
457 text-align: center;
475 text-align: center;
458 padding: 0.5%;
476 padding: 0.5%;
459 }
477 }
460
478
461 .equal {
479 .equal {
462 background-color: #ffffff;
480 background-color: #ffffff;
463 }
481 }
464
482
465 .delete {
483 .delete {
466 background-color: #faa;
484 background-color: #faa;
467 color: #333;
485 color: #333;
468 }
486 }
469
487
470 .insert {
488 .insert {
471 background-color: #ffa;
489 background-color: #ffa;
472 }
490 }
473
491
474 .replace {
492 .replace {
475 background-color: #e8e8e8;
493 background-color: #e8e8e8;
476 }
494 }
477
495
478 .comparison {
496 .comparison {
479 overflow-x: auto;
497 overflow-x: auto;
480 }
498 }
481
499
482 .header th {
500 .header th {
483 text-align: center;
501 text-align: center;
484 }
502 }
485
503
486 .block {
504 .block {
487 border-top: 1px solid #d9d8d1;
505 border-top: 1px solid #d9d8d1;
488 }
506 }
489
507
490 .scroll-loading {
508 .scroll-loading {
491 -webkit-animation: change_color 1s linear 0s infinite alternate;
509 -webkit-animation: change_color 1s linear 0s infinite alternate;
492 -moz-animation: change_color 1s linear 0s infinite alternate;
510 -moz-animation: change_color 1s linear 0s infinite alternate;
493 -o-animation: change_color 1s linear 0s infinite alternate;
511 -o-animation: change_color 1s linear 0s infinite alternate;
494 animation: change_color 1s linear 0s infinite alternate;
512 animation: change_color 1s linear 0s infinite alternate;
495 }
513 }
496
514
497 @-webkit-keyframes change_color {
515 @-webkit-keyframes change_color {
498 from { background-color: #A0CEFF; } to { }
516 from { background-color: #A0CEFF; } to { }
499 }
517 }
500 @-moz-keyframes change_color {
518 @-moz-keyframes change_color {
501 from { background-color: #A0CEFF; } to { }
519 from { background-color: #A0CEFF; } to { }
502 }
520 }
503 @-o-keyframes change_color {
521 @-o-keyframes change_color {
504 from { background-color: #A0CEFF; } to { }
522 from { background-color: #A0CEFF; } to { }
505 }
523 }
506 @keyframes change_color {
524 @keyframes change_color {
507 from { background-color: #A0CEFF; } to { }
525 from { background-color: #A0CEFF; } to { }
508 }
526 }
509
527
510 .scroll-loading-error {
528 .scroll-loading-error {
511 background-color: #FFCCCC !important;
529 background-color: #FFCCCC !important;
512 }
530 }
513 304 Not Modified
531 304 Not Modified
514
532
515
533
516 errors
534 errors
517
535
518 $ cat errors.log
536 $ cat errors.log
519
537
520 $ cd ..
538 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now