##// END OF EJS Templates
Correct Content-Type header values for archive downloads....
Ry4an Brase -
r12570:a72c5ff1 stable
parent child Browse files
Show More
@@ -1,291 +1,291 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
9 import os
10 from mercurial import ui, hg, hook, error, encoding, templater
10 from mercurial import ui, hg, hook, error, encoding, templater
11 from common import get_mtime, ErrorResponse, permhooks
11 from common import get_mtime, ErrorResponse, permhooks
12 from common import HTTP_OK, HTTP_BAD_REQUEST, HTTP_NOT_FOUND, HTTP_SERVER_ERROR
12 from common import HTTP_OK, HTTP_BAD_REQUEST, HTTP_NOT_FOUND, HTTP_SERVER_ERROR
13 from request import wsgirequest
13 from request import wsgirequest
14 import webcommands, protocol, webutil
14 import webcommands, protocol, webutil
15
15
16 perms = {
16 perms = {
17 'changegroup': 'pull',
17 'changegroup': 'pull',
18 'changegroupsubset': 'pull',
18 'changegroupsubset': 'pull',
19 'stream_out': 'pull',
19 'stream_out': 'pull',
20 'listkeys': 'pull',
20 'listkeys': 'pull',
21 'unbundle': 'push',
21 'unbundle': 'push',
22 'pushkey': 'push',
22 'pushkey': 'push',
23 }
23 }
24
24
25 class hgweb(object):
25 class hgweb(object):
26 def __init__(self, repo, name=None, baseui=None):
26 def __init__(self, repo, name=None, baseui=None):
27 if isinstance(repo, str):
27 if isinstance(repo, str):
28 if baseui:
28 if baseui:
29 u = baseui.copy()
29 u = baseui.copy()
30 else:
30 else:
31 u = ui.ui()
31 u = ui.ui()
32 self.repo = hg.repository(u, repo)
32 self.repo = hg.repository(u, repo)
33 else:
33 else:
34 self.repo = repo
34 self.repo = repo
35
35
36 self.repo.ui.setconfig('ui', 'report_untrusted', 'off')
36 self.repo.ui.setconfig('ui', 'report_untrusted', 'off')
37 self.repo.ui.setconfig('ui', 'interactive', 'off')
37 self.repo.ui.setconfig('ui', 'interactive', 'off')
38 hook.redirect(True)
38 hook.redirect(True)
39 self.mtime = -1
39 self.mtime = -1
40 self.reponame = name
40 self.reponame = name
41 self.archives = 'zip', 'gz', 'bz2'
41 self.archives = 'zip', 'gz', 'bz2'
42 self.stripecount = 1
42 self.stripecount = 1
43 # a repo owner may set web.templates in .hg/hgrc to get any file
43 # a repo owner may set web.templates in .hg/hgrc to get any file
44 # readable by the user running the CGI script
44 # readable by the user running the CGI script
45 self.templatepath = self.config('web', 'templates')
45 self.templatepath = self.config('web', 'templates')
46
46
47 # The CGI scripts are often run by a user different from the repo owner.
47 # The CGI scripts are often run by a user different from the repo owner.
48 # Trust the settings from the .hg/hgrc files by default.
48 # Trust the settings from the .hg/hgrc files by default.
49 def config(self, section, name, default=None, untrusted=True):
49 def config(self, section, name, default=None, untrusted=True):
50 return self.repo.ui.config(section, name, default,
50 return self.repo.ui.config(section, name, default,
51 untrusted=untrusted)
51 untrusted=untrusted)
52
52
53 def configbool(self, section, name, default=False, untrusted=True):
53 def configbool(self, section, name, default=False, untrusted=True):
54 return self.repo.ui.configbool(section, name, default,
54 return self.repo.ui.configbool(section, name, default,
55 untrusted=untrusted)
55 untrusted=untrusted)
56
56
57 def configlist(self, section, name, default=None, untrusted=True):
57 def configlist(self, section, name, default=None, untrusted=True):
58 return self.repo.ui.configlist(section, name, default,
58 return self.repo.ui.configlist(section, name, default,
59 untrusted=untrusted)
59 untrusted=untrusted)
60
60
61 def refresh(self, request=None):
61 def refresh(self, request=None):
62 if request:
62 if request:
63 self.repo.ui.environ = request.env
63 self.repo.ui.environ = request.env
64 mtime = get_mtime(self.repo.spath)
64 mtime = get_mtime(self.repo.spath)
65 if mtime != self.mtime:
65 if mtime != self.mtime:
66 self.mtime = mtime
66 self.mtime = mtime
67 self.repo = hg.repository(self.repo.ui, self.repo.root)
67 self.repo = hg.repository(self.repo.ui, self.repo.root)
68 self.maxchanges = int(self.config("web", "maxchanges", 10))
68 self.maxchanges = int(self.config("web", "maxchanges", 10))
69 self.stripecount = int(self.config("web", "stripes", 1))
69 self.stripecount = int(self.config("web", "stripes", 1))
70 self.maxshortchanges = int(self.config("web", "maxshortchanges", 60))
70 self.maxshortchanges = int(self.config("web", "maxshortchanges", 60))
71 self.maxfiles = int(self.config("web", "maxfiles", 10))
71 self.maxfiles = int(self.config("web", "maxfiles", 10))
72 self.allowpull = self.configbool("web", "allowpull", True)
72 self.allowpull = self.configbool("web", "allowpull", True)
73 encoding.encoding = self.config("web", "encoding",
73 encoding.encoding = self.config("web", "encoding",
74 encoding.encoding)
74 encoding.encoding)
75
75
76 def run(self):
76 def run(self):
77 if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."):
77 if not os.environ.get('GATEWAY_INTERFACE', '').startswith("CGI/1."):
78 raise RuntimeError("This function is only intended to be "
78 raise RuntimeError("This function is only intended to be "
79 "called while running as a CGI script.")
79 "called while running as a CGI script.")
80 import mercurial.hgweb.wsgicgi as wsgicgi
80 import mercurial.hgweb.wsgicgi as wsgicgi
81 wsgicgi.launch(self)
81 wsgicgi.launch(self)
82
82
83 def __call__(self, env, respond):
83 def __call__(self, env, respond):
84 req = wsgirequest(env, respond)
84 req = wsgirequest(env, respond)
85 return self.run_wsgi(req)
85 return self.run_wsgi(req)
86
86
87 def run_wsgi(self, req):
87 def run_wsgi(self, req):
88
88
89 self.refresh(req)
89 self.refresh(req)
90
90
91 # work with CGI variables to create coherent structure
91 # work with CGI variables to create coherent structure
92 # use SCRIPT_NAME, PATH_INFO and QUERY_STRING as well as our REPO_NAME
92 # use SCRIPT_NAME, PATH_INFO and QUERY_STRING as well as our REPO_NAME
93
93
94 req.url = req.env['SCRIPT_NAME']
94 req.url = req.env['SCRIPT_NAME']
95 if not req.url.endswith('/'):
95 if not req.url.endswith('/'):
96 req.url += '/'
96 req.url += '/'
97 if 'REPO_NAME' in req.env:
97 if 'REPO_NAME' in req.env:
98 req.url += req.env['REPO_NAME'] + '/'
98 req.url += req.env['REPO_NAME'] + '/'
99
99
100 if 'PATH_INFO' in req.env:
100 if 'PATH_INFO' in req.env:
101 parts = req.env['PATH_INFO'].strip('/').split('/')
101 parts = req.env['PATH_INFO'].strip('/').split('/')
102 repo_parts = req.env.get('REPO_NAME', '').split('/')
102 repo_parts = req.env.get('REPO_NAME', '').split('/')
103 if parts[:len(repo_parts)] == repo_parts:
103 if parts[:len(repo_parts)] == repo_parts:
104 parts = parts[len(repo_parts):]
104 parts = parts[len(repo_parts):]
105 query = '/'.join(parts)
105 query = '/'.join(parts)
106 else:
106 else:
107 query = req.env['QUERY_STRING'].split('&', 1)[0]
107 query = req.env['QUERY_STRING'].split('&', 1)[0]
108 query = query.split(';', 1)[0]
108 query = query.split(';', 1)[0]
109
109
110 # process this if it's a protocol request
110 # process this if it's a protocol request
111 # protocol bits don't need to create any URLs
111 # protocol bits don't need to create any URLs
112 # and the clients always use the old URL structure
112 # and the clients always use the old URL structure
113
113
114 cmd = req.form.get('cmd', [''])[0]
114 cmd = req.form.get('cmd', [''])[0]
115 if cmd and cmd in protocol.__all__:
115 if cmd and cmd in protocol.__all__:
116 if query:
116 if query:
117 raise ErrorResponse(HTTP_NOT_FOUND)
117 raise ErrorResponse(HTTP_NOT_FOUND)
118 try:
118 try:
119 if cmd in perms:
119 if cmd in perms:
120 try:
120 try:
121 self.check_perm(req, perms[cmd])
121 self.check_perm(req, perms[cmd])
122 except ErrorResponse, inst:
122 except ErrorResponse, inst:
123 if cmd == 'unbundle':
123 if cmd == 'unbundle':
124 req.drain()
124 req.drain()
125 raise
125 raise
126 method = getattr(protocol, cmd)
126 method = getattr(protocol, cmd)
127 return method(self.repo, req)
127 return method(self.repo, req)
128 except ErrorResponse, inst:
128 except ErrorResponse, inst:
129 req.respond(inst, protocol.HGTYPE)
129 req.respond(inst, protocol.HGTYPE)
130 if not inst.message:
130 if not inst.message:
131 return []
131 return []
132 return '0\n%s\n' % inst.message,
132 return '0\n%s\n' % inst.message,
133
133
134 # translate user-visible url structure to internal structure
134 # translate user-visible url structure to internal structure
135
135
136 args = query.split('/', 2)
136 args = query.split('/', 2)
137 if 'cmd' not in req.form and args and args[0]:
137 if 'cmd' not in req.form and args and args[0]:
138
138
139 cmd = args.pop(0)
139 cmd = args.pop(0)
140 style = cmd.rfind('-')
140 style = cmd.rfind('-')
141 if style != -1:
141 if style != -1:
142 req.form['style'] = [cmd[:style]]
142 req.form['style'] = [cmd[:style]]
143 cmd = cmd[style + 1:]
143 cmd = cmd[style + 1:]
144
144
145 # avoid accepting e.g. style parameter as command
145 # avoid accepting e.g. style parameter as command
146 if hasattr(webcommands, cmd):
146 if hasattr(webcommands, cmd):
147 req.form['cmd'] = [cmd]
147 req.form['cmd'] = [cmd]
148 else:
148 else:
149 cmd = ''
149 cmd = ''
150
150
151 if cmd == 'static':
151 if cmd == 'static':
152 req.form['file'] = ['/'.join(args)]
152 req.form['file'] = ['/'.join(args)]
153 else:
153 else:
154 if args and args[0]:
154 if args and args[0]:
155 node = args.pop(0)
155 node = args.pop(0)
156 req.form['node'] = [node]
156 req.form['node'] = [node]
157 if args:
157 if args:
158 req.form['file'] = args
158 req.form['file'] = args
159
159
160 ua = req.env.get('HTTP_USER_AGENT', '')
160 ua = req.env.get('HTTP_USER_AGENT', '')
161 if cmd == 'rev' and 'mercurial' in ua:
161 if cmd == 'rev' and 'mercurial' in ua:
162 req.form['style'] = ['raw']
162 req.form['style'] = ['raw']
163
163
164 if cmd == 'archive':
164 if cmd == 'archive':
165 fn = req.form['node'][0]
165 fn = req.form['node'][0]
166 for type_, spec in self.archive_specs.iteritems():
166 for type_, spec in self.archive_specs.iteritems():
167 ext = spec[2]
167 ext = spec[2]
168 if fn.endswith(ext):
168 if fn.endswith(ext):
169 req.form['node'] = [fn[:-len(ext)]]
169 req.form['node'] = [fn[:-len(ext)]]
170 req.form['type'] = [type_]
170 req.form['type'] = [type_]
171
171
172 # process the web interface request
172 # process the web interface request
173
173
174 try:
174 try:
175 tmpl = self.templater(req)
175 tmpl = self.templater(req)
176 ctype = tmpl('mimetype', encoding=encoding.encoding)
176 ctype = tmpl('mimetype', encoding=encoding.encoding)
177 ctype = templater.stringify(ctype)
177 ctype = templater.stringify(ctype)
178
178
179 # check read permissions non-static content
179 # check read permissions non-static content
180 if cmd != 'static':
180 if cmd != 'static':
181 self.check_perm(req, None)
181 self.check_perm(req, None)
182
182
183 if cmd == '':
183 if cmd == '':
184 req.form['cmd'] = [tmpl.cache['default']]
184 req.form['cmd'] = [tmpl.cache['default']]
185 cmd = req.form['cmd'][0]
185 cmd = req.form['cmd'][0]
186
186
187 if cmd not in webcommands.__all__:
187 if cmd not in webcommands.__all__:
188 msg = 'no such method: %s' % cmd
188 msg = 'no such method: %s' % cmd
189 raise ErrorResponse(HTTP_BAD_REQUEST, msg)
189 raise ErrorResponse(HTTP_BAD_REQUEST, msg)
190 elif cmd == 'file' and 'raw' in req.form.get('style', []):
190 elif cmd == 'file' and 'raw' in req.form.get('style', []):
191 self.ctype = ctype
191 self.ctype = ctype
192 content = webcommands.rawfile(self, req, tmpl)
192 content = webcommands.rawfile(self, req, tmpl)
193 else:
193 else:
194 content = getattr(webcommands, cmd)(self, req, tmpl)
194 content = getattr(webcommands, cmd)(self, req, tmpl)
195 req.respond(HTTP_OK, ctype)
195 req.respond(HTTP_OK, ctype)
196
196
197 return content
197 return content
198
198
199 except error.LookupError, err:
199 except error.LookupError, err:
200 req.respond(HTTP_NOT_FOUND, ctype)
200 req.respond(HTTP_NOT_FOUND, ctype)
201 msg = str(err)
201 msg = str(err)
202 if 'manifest' not in msg:
202 if 'manifest' not in msg:
203 msg = 'revision not found: %s' % err.name
203 msg = 'revision not found: %s' % err.name
204 return tmpl('error', error=msg)
204 return tmpl('error', error=msg)
205 except (error.RepoError, error.RevlogError), inst:
205 except (error.RepoError, error.RevlogError), inst:
206 req.respond(HTTP_SERVER_ERROR, ctype)
206 req.respond(HTTP_SERVER_ERROR, ctype)
207 return tmpl('error', error=str(inst))
207 return tmpl('error', error=str(inst))
208 except ErrorResponse, inst:
208 except ErrorResponse, inst:
209 req.respond(inst, ctype)
209 req.respond(inst, ctype)
210 return tmpl('error', error=inst.message)
210 return tmpl('error', error=inst.message)
211
211
212 def templater(self, req):
212 def templater(self, req):
213
213
214 # determine scheme, port and server name
214 # determine scheme, port and server name
215 # this is needed to create absolute urls
215 # this is needed to create absolute urls
216
216
217 proto = req.env.get('wsgi.url_scheme')
217 proto = req.env.get('wsgi.url_scheme')
218 if proto == 'https':
218 if proto == 'https':
219 proto = 'https'
219 proto = 'https'
220 default_port = "443"
220 default_port = "443"
221 else:
221 else:
222 proto = 'http'
222 proto = 'http'
223 default_port = "80"
223 default_port = "80"
224
224
225 port = req.env["SERVER_PORT"]
225 port = req.env["SERVER_PORT"]
226 port = port != default_port and (":" + port) or ""
226 port = port != default_port and (":" + port) or ""
227 urlbase = '%s://%s%s' % (proto, req.env['SERVER_NAME'], port)
227 urlbase = '%s://%s%s' % (proto, req.env['SERVER_NAME'], port)
228 staticurl = self.config("web", "staticurl") or req.url + 'static/'
228 staticurl = self.config("web", "staticurl") or req.url + 'static/'
229 if not staticurl.endswith('/'):
229 if not staticurl.endswith('/'):
230 staticurl += '/'
230 staticurl += '/'
231
231
232 # some functions for the templater
232 # some functions for the templater
233
233
234 def header(**map):
234 def header(**map):
235 yield tmpl('header', encoding=encoding.encoding, **map)
235 yield tmpl('header', encoding=encoding.encoding, **map)
236
236
237 def footer(**map):
237 def footer(**map):
238 yield tmpl("footer", **map)
238 yield tmpl("footer", **map)
239
239
240 def motd(**map):
240 def motd(**map):
241 yield self.config("web", "motd", "")
241 yield self.config("web", "motd", "")
242
242
243 # figure out which style to use
243 # figure out which style to use
244
244
245 vars = {}
245 vars = {}
246 styles = (
246 styles = (
247 req.form.get('style', [None])[0],
247 req.form.get('style', [None])[0],
248 self.config('web', 'style'),
248 self.config('web', 'style'),
249 'paper',
249 'paper',
250 )
250 )
251 style, mapfile = templater.stylemap(styles, self.templatepath)
251 style, mapfile = templater.stylemap(styles, self.templatepath)
252 if style == styles[0]:
252 if style == styles[0]:
253 vars['style'] = style
253 vars['style'] = style
254
254
255 start = req.url[-1] == '?' and '&' or '?'
255 start = req.url[-1] == '?' and '&' or '?'
256 sessionvars = webutil.sessionvars(vars, start)
256 sessionvars = webutil.sessionvars(vars, start)
257
257
258 if not self.reponame:
258 if not self.reponame:
259 self.reponame = (self.config("web", "name")
259 self.reponame = (self.config("web", "name")
260 or req.env.get('REPO_NAME')
260 or req.env.get('REPO_NAME')
261 or req.url.strip('/') or self.repo.root)
261 or req.url.strip('/') or self.repo.root)
262
262
263 # create the templater
263 # create the templater
264
264
265 tmpl = templater.templater(mapfile,
265 tmpl = templater.templater(mapfile,
266 defaults={"url": req.url,
266 defaults={"url": req.url,
267 "staticurl": staticurl,
267 "staticurl": staticurl,
268 "urlbase": urlbase,
268 "urlbase": urlbase,
269 "repo": self.reponame,
269 "repo": self.reponame,
270 "header": header,
270 "header": header,
271 "footer": footer,
271 "footer": footer,
272 "motd": motd,
272 "motd": motd,
273 "sessionvars": sessionvars
273 "sessionvars": sessionvars
274 })
274 })
275 return tmpl
275 return tmpl
276
276
277 def archivelist(self, nodeid):
277 def archivelist(self, nodeid):
278 allowed = self.configlist("web", "allow_archive")
278 allowed = self.configlist("web", "allow_archive")
279 for i, spec in self.archive_specs.iteritems():
279 for i, spec in self.archive_specs.iteritems():
280 if i in allowed or self.configbool("web", "allow" + i):
280 if i in allowed or self.configbool("web", "allow" + i):
281 yield {"type" : i, "extension" : spec[2], "node" : nodeid}
281 yield {"type" : i, "extension" : spec[2], "node" : nodeid}
282
282
283 archive_specs = {
283 archive_specs = {
284 'bz2': ('application/x-tar', 'tbz2', '.tar.bz2', None),
284 'bz2': ('application/x-bzip2', 'tbz2', '.tar.bz2', None),
285 'gz': ('application/x-tar', 'tgz', '.tar.gz', None),
285 'gz': ('application/x-gzip', 'tgz', '.tar.gz', None),
286 'zip': ('application/zip', 'zip', '.zip', None),
286 'zip': ('application/zip', 'zip', '.zip', None),
287 }
287 }
288
288
289 def check_perm(self, req, op):
289 def check_perm(self, req, op):
290 for hook in permhooks:
290 for hook in permhooks:
291 hook(self, req, op)
291 hook(self, req, op)
General Comments 0
You need to be logged in to leave comments. Login now