##// END OF EJS Templates
configitems: register the 'web.prefix' config
Boris Feld -
r34240:d24816df default
parent child Browse files
Show More
@@ -1,650 +1,653 b''
1 # configitems.py - centralized declaration of configuration option
1 # configitems.py - centralized declaration of configuration option
2 #
2 #
3 # Copyright 2017 Pierre-Yves David <pierre-yves.david@octobus.net>
3 # Copyright 2017 Pierre-Yves David <pierre-yves.david@octobus.net>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import functools
10 import functools
11
11
12 from . import (
12 from . import (
13 encoding,
13 encoding,
14 error,
14 error,
15 )
15 )
16
16
17 def loadconfigtable(ui, extname, configtable):
17 def loadconfigtable(ui, extname, configtable):
18 """update config item known to the ui with the extension ones"""
18 """update config item known to the ui with the extension ones"""
19 for section, items in configtable.items():
19 for section, items in configtable.items():
20 knownitems = ui._knownconfig.setdefault(section, {})
20 knownitems = ui._knownconfig.setdefault(section, {})
21 knownkeys = set(knownitems)
21 knownkeys = set(knownitems)
22 newkeys = set(items)
22 newkeys = set(items)
23 for key in sorted(knownkeys & newkeys):
23 for key in sorted(knownkeys & newkeys):
24 msg = "extension '%s' overwrite config item '%s.%s'"
24 msg = "extension '%s' overwrite config item '%s.%s'"
25 msg %= (extname, section, key)
25 msg %= (extname, section, key)
26 ui.develwarn(msg, config='warn-config')
26 ui.develwarn(msg, config='warn-config')
27
27
28 knownitems.update(items)
28 knownitems.update(items)
29
29
30 class configitem(object):
30 class configitem(object):
31 """represent a known config item
31 """represent a known config item
32
32
33 :section: the official config section where to find this item,
33 :section: the official config section where to find this item,
34 :name: the official name within the section,
34 :name: the official name within the section,
35 :default: default value for this item,
35 :default: default value for this item,
36 :alias: optional list of tuples as alternatives.
36 :alias: optional list of tuples as alternatives.
37 """
37 """
38
38
39 def __init__(self, section, name, default=None, alias=()):
39 def __init__(self, section, name, default=None, alias=()):
40 self.section = section
40 self.section = section
41 self.name = name
41 self.name = name
42 self.default = default
42 self.default = default
43 self.alias = list(alias)
43 self.alias = list(alias)
44
44
45 coreitems = {}
45 coreitems = {}
46
46
47 def _register(configtable, *args, **kwargs):
47 def _register(configtable, *args, **kwargs):
48 item = configitem(*args, **kwargs)
48 item = configitem(*args, **kwargs)
49 section = configtable.setdefault(item.section, {})
49 section = configtable.setdefault(item.section, {})
50 if item.name in section:
50 if item.name in section:
51 msg = "duplicated config item registration for '%s.%s'"
51 msg = "duplicated config item registration for '%s.%s'"
52 raise error.ProgrammingError(msg % (item.section, item.name))
52 raise error.ProgrammingError(msg % (item.section, item.name))
53 section[item.name] = item
53 section[item.name] = item
54
54
55 # special value for case where the default is derived from other values
55 # special value for case where the default is derived from other values
56 dynamicdefault = object()
56 dynamicdefault = object()
57
57
58 # Registering actual config items
58 # Registering actual config items
59
59
60 def getitemregister(configtable):
60 def getitemregister(configtable):
61 return functools.partial(_register, configtable)
61 return functools.partial(_register, configtable)
62
62
63 coreconfigitem = getitemregister(coreitems)
63 coreconfigitem = getitemregister(coreitems)
64
64
65 coreconfigitem('auth', 'cookiefile',
65 coreconfigitem('auth', 'cookiefile',
66 default=None,
66 default=None,
67 )
67 )
68 # bookmarks.pushing: internal hack for discovery
68 # bookmarks.pushing: internal hack for discovery
69 coreconfigitem('bookmarks', 'pushing',
69 coreconfigitem('bookmarks', 'pushing',
70 default=list,
70 default=list,
71 )
71 )
72 # bundle.mainreporoot: internal hack for bundlerepo
72 # bundle.mainreporoot: internal hack for bundlerepo
73 coreconfigitem('bundle', 'mainreporoot',
73 coreconfigitem('bundle', 'mainreporoot',
74 default='',
74 default='',
75 )
75 )
76 # bundle.reorder: experimental config
76 # bundle.reorder: experimental config
77 coreconfigitem('bundle', 'reorder',
77 coreconfigitem('bundle', 'reorder',
78 default='auto',
78 default='auto',
79 )
79 )
80 coreconfigitem('censor', 'policy',
80 coreconfigitem('censor', 'policy',
81 default='abort',
81 default='abort',
82 )
82 )
83 coreconfigitem('chgserver', 'idletimeout',
83 coreconfigitem('chgserver', 'idletimeout',
84 default=3600,
84 default=3600,
85 )
85 )
86 coreconfigitem('chgserver', 'skiphash',
86 coreconfigitem('chgserver', 'skiphash',
87 default=False,
87 default=False,
88 )
88 )
89 coreconfigitem('cmdserver', 'log',
89 coreconfigitem('cmdserver', 'log',
90 default=None,
90 default=None,
91 )
91 )
92 coreconfigitem('color', 'mode',
92 coreconfigitem('color', 'mode',
93 default='auto',
93 default='auto',
94 )
94 )
95 coreconfigitem('color', 'pagermode',
95 coreconfigitem('color', 'pagermode',
96 default=dynamicdefault,
96 default=dynamicdefault,
97 )
97 )
98 coreconfigitem('commands', 'status.relative',
98 coreconfigitem('commands', 'status.relative',
99 default=False,
99 default=False,
100 )
100 )
101 coreconfigitem('commands', 'status.skipstates',
101 coreconfigitem('commands', 'status.skipstates',
102 default=[],
102 default=[],
103 )
103 )
104 coreconfigitem('commands', 'status.verbose',
104 coreconfigitem('commands', 'status.verbose',
105 default=False,
105 default=False,
106 )
106 )
107 coreconfigitem('commands', 'update.requiredest',
107 coreconfigitem('commands', 'update.requiredest',
108 default=False,
108 default=False,
109 )
109 )
110 coreconfigitem('devel', 'all-warnings',
110 coreconfigitem('devel', 'all-warnings',
111 default=False,
111 default=False,
112 )
112 )
113 coreconfigitem('devel', 'bundle2.debug',
113 coreconfigitem('devel', 'bundle2.debug',
114 default=False,
114 default=False,
115 )
115 )
116 coreconfigitem('devel', 'check-locks',
116 coreconfigitem('devel', 'check-locks',
117 default=False,
117 default=False,
118 )
118 )
119 coreconfigitem('devel', 'check-relroot',
119 coreconfigitem('devel', 'check-relroot',
120 default=False,
120 default=False,
121 )
121 )
122 coreconfigitem('devel', 'default-date',
122 coreconfigitem('devel', 'default-date',
123 default=None,
123 default=None,
124 )
124 )
125 coreconfigitem('devel', 'deprec-warn',
125 coreconfigitem('devel', 'deprec-warn',
126 default=False,
126 default=False,
127 )
127 )
128 coreconfigitem('devel', 'disableloaddefaultcerts',
128 coreconfigitem('devel', 'disableloaddefaultcerts',
129 default=False,
129 default=False,
130 )
130 )
131 coreconfigitem('devel', 'legacy.exchange',
131 coreconfigitem('devel', 'legacy.exchange',
132 default=list,
132 default=list,
133 )
133 )
134 coreconfigitem('devel', 'servercafile',
134 coreconfigitem('devel', 'servercafile',
135 default='',
135 default='',
136 )
136 )
137 coreconfigitem('devel', 'serverexactprotocol',
137 coreconfigitem('devel', 'serverexactprotocol',
138 default='',
138 default='',
139 )
139 )
140 coreconfigitem('devel', 'serverrequirecert',
140 coreconfigitem('devel', 'serverrequirecert',
141 default=False,
141 default=False,
142 )
142 )
143 coreconfigitem('devel', 'strip-obsmarkers',
143 coreconfigitem('devel', 'strip-obsmarkers',
144 default=True,
144 default=True,
145 )
145 )
146 coreconfigitem('email', 'charsets',
146 coreconfigitem('email', 'charsets',
147 default=list,
147 default=list,
148 )
148 )
149 coreconfigitem('email', 'method',
149 coreconfigitem('email', 'method',
150 default='smtp',
150 default='smtp',
151 )
151 )
152 coreconfigitem('experimental', 'bundle-phases',
152 coreconfigitem('experimental', 'bundle-phases',
153 default=False,
153 default=False,
154 )
154 )
155 coreconfigitem('experimental', 'bundle2-advertise',
155 coreconfigitem('experimental', 'bundle2-advertise',
156 default=True,
156 default=True,
157 )
157 )
158 coreconfigitem('experimental', 'bundle2-output-capture',
158 coreconfigitem('experimental', 'bundle2-output-capture',
159 default=False,
159 default=False,
160 )
160 )
161 coreconfigitem('experimental', 'bundle2.pushback',
161 coreconfigitem('experimental', 'bundle2.pushback',
162 default=False,
162 default=False,
163 )
163 )
164 coreconfigitem('experimental', 'bundle2lazylocking',
164 coreconfigitem('experimental', 'bundle2lazylocking',
165 default=False,
165 default=False,
166 )
166 )
167 coreconfigitem('experimental', 'bundlecomplevel',
167 coreconfigitem('experimental', 'bundlecomplevel',
168 default=None,
168 default=None,
169 )
169 )
170 coreconfigitem('experimental', 'changegroup3',
170 coreconfigitem('experimental', 'changegroup3',
171 default=False,
171 default=False,
172 )
172 )
173 coreconfigitem('experimental', 'clientcompressionengines',
173 coreconfigitem('experimental', 'clientcompressionengines',
174 default=list,
174 default=list,
175 )
175 )
176 coreconfigitem('experimental', 'copytrace',
176 coreconfigitem('experimental', 'copytrace',
177 default='on',
177 default='on',
178 )
178 )
179 coreconfigitem('experimental', 'crecordtest',
179 coreconfigitem('experimental', 'crecordtest',
180 default=None,
180 default=None,
181 )
181 )
182 coreconfigitem('experimental', 'editortmpinhg',
182 coreconfigitem('experimental', 'editortmpinhg',
183 default=False,
183 default=False,
184 )
184 )
185 coreconfigitem('experimental', 'stabilization',
185 coreconfigitem('experimental', 'stabilization',
186 default=list,
186 default=list,
187 alias=[('experimental', 'evolution')],
187 alias=[('experimental', 'evolution')],
188 )
188 )
189 coreconfigitem('experimental', 'stabilization.bundle-obsmarker',
189 coreconfigitem('experimental', 'stabilization.bundle-obsmarker',
190 default=False,
190 default=False,
191 alias=[('experimental', 'evolution.bundle-obsmarker')],
191 alias=[('experimental', 'evolution.bundle-obsmarker')],
192 )
192 )
193 coreconfigitem('experimental', 'stabilization.track-operation',
193 coreconfigitem('experimental', 'stabilization.track-operation',
194 default=False,
194 default=False,
195 alias=[('experimental', 'evolution.track-operation')]
195 alias=[('experimental', 'evolution.track-operation')]
196 )
196 )
197 coreconfigitem('experimental', 'exportableenviron',
197 coreconfigitem('experimental', 'exportableenviron',
198 default=list,
198 default=list,
199 )
199 )
200 coreconfigitem('experimental', 'extendedheader.index',
200 coreconfigitem('experimental', 'extendedheader.index',
201 default=None,
201 default=None,
202 )
202 )
203 coreconfigitem('experimental', 'extendedheader.similarity',
203 coreconfigitem('experimental', 'extendedheader.similarity',
204 default=False,
204 default=False,
205 )
205 )
206 coreconfigitem('experimental', 'format.compression',
206 coreconfigitem('experimental', 'format.compression',
207 default='zlib',
207 default='zlib',
208 )
208 )
209 coreconfigitem('experimental', 'graphshorten',
209 coreconfigitem('experimental', 'graphshorten',
210 default=False,
210 default=False,
211 )
211 )
212 coreconfigitem('experimental', 'hook-track-tags',
212 coreconfigitem('experimental', 'hook-track-tags',
213 default=False,
213 default=False,
214 )
214 )
215 coreconfigitem('experimental', 'httppostargs',
215 coreconfigitem('experimental', 'httppostargs',
216 default=False,
216 default=False,
217 )
217 )
218 coreconfigitem('experimental', 'manifestv2',
218 coreconfigitem('experimental', 'manifestv2',
219 default=False,
219 default=False,
220 )
220 )
221 coreconfigitem('experimental', 'mergedriver',
221 coreconfigitem('experimental', 'mergedriver',
222 default=None,
222 default=None,
223 )
223 )
224 coreconfigitem('experimental', 'obsmarkers-exchange-debug',
224 coreconfigitem('experimental', 'obsmarkers-exchange-debug',
225 default=False,
225 default=False,
226 )
226 )
227 coreconfigitem('experimental', 'rebase.multidest',
227 coreconfigitem('experimental', 'rebase.multidest',
228 default=False,
228 default=False,
229 )
229 )
230 coreconfigitem('experimental', 'revertalternateinteractivemode',
230 coreconfigitem('experimental', 'revertalternateinteractivemode',
231 default=True,
231 default=True,
232 )
232 )
233 coreconfigitem('experimental', 'revlogv2',
233 coreconfigitem('experimental', 'revlogv2',
234 default=None,
234 default=None,
235 )
235 )
236 coreconfigitem('experimental', 'spacemovesdown',
236 coreconfigitem('experimental', 'spacemovesdown',
237 default=False,
237 default=False,
238 )
238 )
239 coreconfigitem('experimental', 'treemanifest',
239 coreconfigitem('experimental', 'treemanifest',
240 default=False,
240 default=False,
241 )
241 )
242 coreconfigitem('experimental', 'updatecheck',
242 coreconfigitem('experimental', 'updatecheck',
243 default=None,
243 default=None,
244 )
244 )
245 coreconfigitem('format', 'aggressivemergedeltas',
245 coreconfigitem('format', 'aggressivemergedeltas',
246 default=False,
246 default=False,
247 )
247 )
248 coreconfigitem('format', 'chunkcachesize',
248 coreconfigitem('format', 'chunkcachesize',
249 default=None,
249 default=None,
250 )
250 )
251 coreconfigitem('format', 'dotencode',
251 coreconfigitem('format', 'dotencode',
252 default=True,
252 default=True,
253 )
253 )
254 coreconfigitem('format', 'generaldelta',
254 coreconfigitem('format', 'generaldelta',
255 default=False,
255 default=False,
256 )
256 )
257 coreconfigitem('format', 'manifestcachesize',
257 coreconfigitem('format', 'manifestcachesize',
258 default=None,
258 default=None,
259 )
259 )
260 coreconfigitem('format', 'maxchainlen',
260 coreconfigitem('format', 'maxchainlen',
261 default=None,
261 default=None,
262 )
262 )
263 coreconfigitem('format', 'obsstore-version',
263 coreconfigitem('format', 'obsstore-version',
264 default=None,
264 default=None,
265 )
265 )
266 coreconfigitem('format', 'usefncache',
266 coreconfigitem('format', 'usefncache',
267 default=True,
267 default=True,
268 )
268 )
269 coreconfigitem('format', 'usegeneraldelta',
269 coreconfigitem('format', 'usegeneraldelta',
270 default=True,
270 default=True,
271 )
271 )
272 coreconfigitem('format', 'usestore',
272 coreconfigitem('format', 'usestore',
273 default=True,
273 default=True,
274 )
274 )
275 coreconfigitem('hostsecurity', 'ciphers',
275 coreconfigitem('hostsecurity', 'ciphers',
276 default=None,
276 default=None,
277 )
277 )
278 coreconfigitem('hostsecurity', 'disabletls10warning',
278 coreconfigitem('hostsecurity', 'disabletls10warning',
279 default=False,
279 default=False,
280 )
280 )
281 coreconfigitem('http_proxy', 'always',
281 coreconfigitem('http_proxy', 'always',
282 default=False,
282 default=False,
283 )
283 )
284 coreconfigitem('http_proxy', 'host',
284 coreconfigitem('http_proxy', 'host',
285 default=None,
285 default=None,
286 )
286 )
287 coreconfigitem('http_proxy', 'no',
287 coreconfigitem('http_proxy', 'no',
288 default=list,
288 default=list,
289 )
289 )
290 coreconfigitem('http_proxy', 'passwd',
290 coreconfigitem('http_proxy', 'passwd',
291 default=None,
291 default=None,
292 )
292 )
293 coreconfigitem('http_proxy', 'user',
293 coreconfigitem('http_proxy', 'user',
294 default=None,
294 default=None,
295 )
295 )
296 coreconfigitem('merge', 'followcopies',
296 coreconfigitem('merge', 'followcopies',
297 default=True,
297 default=True,
298 )
298 )
299 coreconfigitem('pager', 'ignore',
299 coreconfigitem('pager', 'ignore',
300 default=list,
300 default=list,
301 )
301 )
302 coreconfigitem('patch', 'eol',
302 coreconfigitem('patch', 'eol',
303 default='strict',
303 default='strict',
304 )
304 )
305 coreconfigitem('patch', 'fuzz',
305 coreconfigitem('patch', 'fuzz',
306 default=2,
306 default=2,
307 )
307 )
308 coreconfigitem('paths', 'default',
308 coreconfigitem('paths', 'default',
309 default=None,
309 default=None,
310 )
310 )
311 coreconfigitem('paths', 'default-push',
311 coreconfigitem('paths', 'default-push',
312 default=None,
312 default=None,
313 )
313 )
314 coreconfigitem('phases', 'checksubrepos',
314 coreconfigitem('phases', 'checksubrepos',
315 default='follow',
315 default='follow',
316 )
316 )
317 coreconfigitem('phases', 'publish',
317 coreconfigitem('phases', 'publish',
318 default=True,
318 default=True,
319 )
319 )
320 coreconfigitem('profiling', 'enabled',
320 coreconfigitem('profiling', 'enabled',
321 default=False,
321 default=False,
322 )
322 )
323 coreconfigitem('profiling', 'format',
323 coreconfigitem('profiling', 'format',
324 default='text',
324 default='text',
325 )
325 )
326 coreconfigitem('profiling', 'freq',
326 coreconfigitem('profiling', 'freq',
327 default=1000,
327 default=1000,
328 )
328 )
329 coreconfigitem('profiling', 'limit',
329 coreconfigitem('profiling', 'limit',
330 default=30,
330 default=30,
331 )
331 )
332 coreconfigitem('profiling', 'nested',
332 coreconfigitem('profiling', 'nested',
333 default=0,
333 default=0,
334 )
334 )
335 coreconfigitem('profiling', 'sort',
335 coreconfigitem('profiling', 'sort',
336 default='inlinetime',
336 default='inlinetime',
337 )
337 )
338 coreconfigitem('profiling', 'statformat',
338 coreconfigitem('profiling', 'statformat',
339 default='hotpath',
339 default='hotpath',
340 )
340 )
341 coreconfigitem('progress', 'assume-tty',
341 coreconfigitem('progress', 'assume-tty',
342 default=False,
342 default=False,
343 )
343 )
344 coreconfigitem('progress', 'changedelay',
344 coreconfigitem('progress', 'changedelay',
345 default=1,
345 default=1,
346 )
346 )
347 coreconfigitem('progress', 'clear-complete',
347 coreconfigitem('progress', 'clear-complete',
348 default=True,
348 default=True,
349 )
349 )
350 coreconfigitem('progress', 'debug',
350 coreconfigitem('progress', 'debug',
351 default=False,
351 default=False,
352 )
352 )
353 coreconfigitem('progress', 'delay',
353 coreconfigitem('progress', 'delay',
354 default=3,
354 default=3,
355 )
355 )
356 coreconfigitem('progress', 'disable',
356 coreconfigitem('progress', 'disable',
357 default=False,
357 default=False,
358 )
358 )
359 coreconfigitem('progress', 'estimate',
359 coreconfigitem('progress', 'estimate',
360 default=2,
360 default=2,
361 )
361 )
362 coreconfigitem('progress', 'refresh',
362 coreconfigitem('progress', 'refresh',
363 default=0.1,
363 default=0.1,
364 )
364 )
365 coreconfigitem('progress', 'width',
365 coreconfigitem('progress', 'width',
366 default=dynamicdefault,
366 default=dynamicdefault,
367 )
367 )
368 coreconfigitem('push', 'pushvars.server',
368 coreconfigitem('push', 'pushvars.server',
369 default=False,
369 default=False,
370 )
370 )
371 coreconfigitem('server', 'bundle1',
371 coreconfigitem('server', 'bundle1',
372 default=True,
372 default=True,
373 )
373 )
374 coreconfigitem('server', 'bundle1gd',
374 coreconfigitem('server', 'bundle1gd',
375 default=None,
375 default=None,
376 )
376 )
377 coreconfigitem('server', 'compressionengines',
377 coreconfigitem('server', 'compressionengines',
378 default=list,
378 default=list,
379 )
379 )
380 coreconfigitem('server', 'concurrent-push-mode',
380 coreconfigitem('server', 'concurrent-push-mode',
381 default='strict',
381 default='strict',
382 )
382 )
383 coreconfigitem('server', 'disablefullbundle',
383 coreconfigitem('server', 'disablefullbundle',
384 default=False,
384 default=False,
385 )
385 )
386 coreconfigitem('server', 'maxhttpheaderlen',
386 coreconfigitem('server', 'maxhttpheaderlen',
387 default=1024,
387 default=1024,
388 )
388 )
389 coreconfigitem('server', 'preferuncompressed',
389 coreconfigitem('server', 'preferuncompressed',
390 default=False,
390 default=False,
391 )
391 )
392 coreconfigitem('server', 'uncompressed',
392 coreconfigitem('server', 'uncompressed',
393 default=True,
393 default=True,
394 )
394 )
395 coreconfigitem('server', 'uncompressedallowsecret',
395 coreconfigitem('server', 'uncompressedallowsecret',
396 default=False,
396 default=False,
397 )
397 )
398 coreconfigitem('server', 'validate',
398 coreconfigitem('server', 'validate',
399 default=False,
399 default=False,
400 )
400 )
401 coreconfigitem('server', 'zliblevel',
401 coreconfigitem('server', 'zliblevel',
402 default=-1,
402 default=-1,
403 )
403 )
404 coreconfigitem('smtp', 'host',
404 coreconfigitem('smtp', 'host',
405 default=None,
405 default=None,
406 )
406 )
407 coreconfigitem('smtp', 'local_hostname',
407 coreconfigitem('smtp', 'local_hostname',
408 default=None,
408 default=None,
409 )
409 )
410 coreconfigitem('smtp', 'password',
410 coreconfigitem('smtp', 'password',
411 default=None,
411 default=None,
412 )
412 )
413 coreconfigitem('smtp', 'tls',
413 coreconfigitem('smtp', 'tls',
414 default='none',
414 default='none',
415 )
415 )
416 coreconfigitem('smtp', 'username',
416 coreconfigitem('smtp', 'username',
417 default=None,
417 default=None,
418 )
418 )
419 coreconfigitem('sparse', 'missingwarning',
419 coreconfigitem('sparse', 'missingwarning',
420 default=True,
420 default=True,
421 )
421 )
422 coreconfigitem('trusted', 'groups',
422 coreconfigitem('trusted', 'groups',
423 default=list,
423 default=list,
424 )
424 )
425 coreconfigitem('trusted', 'users',
425 coreconfigitem('trusted', 'users',
426 default=list,
426 default=list,
427 )
427 )
428 coreconfigitem('ui', '_usedassubrepo',
428 coreconfigitem('ui', '_usedassubrepo',
429 default=False,
429 default=False,
430 )
430 )
431 coreconfigitem('ui', 'allowemptycommit',
431 coreconfigitem('ui', 'allowemptycommit',
432 default=False,
432 default=False,
433 )
433 )
434 coreconfigitem('ui', 'archivemeta',
434 coreconfigitem('ui', 'archivemeta',
435 default=True,
435 default=True,
436 )
436 )
437 coreconfigitem('ui', 'askusername',
437 coreconfigitem('ui', 'askusername',
438 default=False,
438 default=False,
439 )
439 )
440 coreconfigitem('ui', 'clonebundlefallback',
440 coreconfigitem('ui', 'clonebundlefallback',
441 default=False,
441 default=False,
442 )
442 )
443 coreconfigitem('ui', 'clonebundleprefers',
443 coreconfigitem('ui', 'clonebundleprefers',
444 default=list,
444 default=list,
445 )
445 )
446 coreconfigitem('ui', 'clonebundles',
446 coreconfigitem('ui', 'clonebundles',
447 default=True,
447 default=True,
448 )
448 )
449 coreconfigitem('ui', 'color',
449 coreconfigitem('ui', 'color',
450 default='auto',
450 default='auto',
451 )
451 )
452 coreconfigitem('ui', 'commitsubrepos',
452 coreconfigitem('ui', 'commitsubrepos',
453 default=False,
453 default=False,
454 )
454 )
455 coreconfigitem('ui', 'debug',
455 coreconfigitem('ui', 'debug',
456 default=False,
456 default=False,
457 )
457 )
458 coreconfigitem('ui', 'debugger',
458 coreconfigitem('ui', 'debugger',
459 default=None,
459 default=None,
460 )
460 )
461 coreconfigitem('ui', 'fallbackencoding',
461 coreconfigitem('ui', 'fallbackencoding',
462 default=None,
462 default=None,
463 )
463 )
464 coreconfigitem('ui', 'forcecwd',
464 coreconfigitem('ui', 'forcecwd',
465 default=None,
465 default=None,
466 )
466 )
467 coreconfigitem('ui', 'forcemerge',
467 coreconfigitem('ui', 'forcemerge',
468 default=None,
468 default=None,
469 )
469 )
470 coreconfigitem('ui', 'formatdebug',
470 coreconfigitem('ui', 'formatdebug',
471 default=False,
471 default=False,
472 )
472 )
473 coreconfigitem('ui', 'formatjson',
473 coreconfigitem('ui', 'formatjson',
474 default=False,
474 default=False,
475 )
475 )
476 coreconfigitem('ui', 'formatted',
476 coreconfigitem('ui', 'formatted',
477 default=None,
477 default=None,
478 )
478 )
479 coreconfigitem('ui', 'graphnodetemplate',
479 coreconfigitem('ui', 'graphnodetemplate',
480 default=None,
480 default=None,
481 )
481 )
482 coreconfigitem('ui', 'http2debuglevel',
482 coreconfigitem('ui', 'http2debuglevel',
483 default=None,
483 default=None,
484 )
484 )
485 coreconfigitem('ui', 'interactive',
485 coreconfigitem('ui', 'interactive',
486 default=None,
486 default=None,
487 )
487 )
488 coreconfigitem('ui', 'interface',
488 coreconfigitem('ui', 'interface',
489 default=None,
489 default=None,
490 )
490 )
491 coreconfigitem('ui', 'logblockedtimes',
491 coreconfigitem('ui', 'logblockedtimes',
492 default=False,
492 default=False,
493 )
493 )
494 coreconfigitem('ui', 'logtemplate',
494 coreconfigitem('ui', 'logtemplate',
495 default=None,
495 default=None,
496 )
496 )
497 coreconfigitem('ui', 'merge',
497 coreconfigitem('ui', 'merge',
498 default=None,
498 default=None,
499 )
499 )
500 coreconfigitem('ui', 'mergemarkers',
500 coreconfigitem('ui', 'mergemarkers',
501 default='basic',
501 default='basic',
502 )
502 )
503 coreconfigitem('ui', 'mergemarkertemplate',
503 coreconfigitem('ui', 'mergemarkertemplate',
504 default=('{node|short} '
504 default=('{node|short} '
505 '{ifeq(tags, "tip", "", '
505 '{ifeq(tags, "tip", "", '
506 'ifeq(tags, "", "", "{tags} "))}'
506 'ifeq(tags, "", "", "{tags} "))}'
507 '{if(bookmarks, "{bookmarks} ")}'
507 '{if(bookmarks, "{bookmarks} ")}'
508 '{ifeq(branch, "default", "", "{branch} ")}'
508 '{ifeq(branch, "default", "", "{branch} ")}'
509 '- {author|user}: {desc|firstline}')
509 '- {author|user}: {desc|firstline}')
510 )
510 )
511 coreconfigitem('ui', 'nontty',
511 coreconfigitem('ui', 'nontty',
512 default=False,
512 default=False,
513 )
513 )
514 coreconfigitem('ui', 'origbackuppath',
514 coreconfigitem('ui', 'origbackuppath',
515 default=None,
515 default=None,
516 )
516 )
517 coreconfigitem('ui', 'paginate',
517 coreconfigitem('ui', 'paginate',
518 default=True,
518 default=True,
519 )
519 )
520 coreconfigitem('ui', 'patch',
520 coreconfigitem('ui', 'patch',
521 default=None,
521 default=None,
522 )
522 )
523 coreconfigitem('ui', 'portablefilenames',
523 coreconfigitem('ui', 'portablefilenames',
524 default='warn',
524 default='warn',
525 )
525 )
526 coreconfigitem('ui', 'promptecho',
526 coreconfigitem('ui', 'promptecho',
527 default=False,
527 default=False,
528 )
528 )
529 coreconfigitem('ui', 'quiet',
529 coreconfigitem('ui', 'quiet',
530 default=False,
530 default=False,
531 )
531 )
532 coreconfigitem('ui', 'quietbookmarkmove',
532 coreconfigitem('ui', 'quietbookmarkmove',
533 default=False,
533 default=False,
534 )
534 )
535 coreconfigitem('ui', 'remotecmd',
535 coreconfigitem('ui', 'remotecmd',
536 default='hg',
536 default='hg',
537 )
537 )
538 coreconfigitem('ui', 'report_untrusted',
538 coreconfigitem('ui', 'report_untrusted',
539 default=True,
539 default=True,
540 )
540 )
541 coreconfigitem('ui', 'rollback',
541 coreconfigitem('ui', 'rollback',
542 default=True,
542 default=True,
543 )
543 )
544 coreconfigitem('ui', 'slash',
544 coreconfigitem('ui', 'slash',
545 default=False,
545 default=False,
546 )
546 )
547 coreconfigitem('ui', 'ssh',
547 coreconfigitem('ui', 'ssh',
548 default='ssh',
548 default='ssh',
549 )
549 )
550 coreconfigitem('ui', 'statuscopies',
550 coreconfigitem('ui', 'statuscopies',
551 default=False,
551 default=False,
552 )
552 )
553 coreconfigitem('ui', 'strict',
553 coreconfigitem('ui', 'strict',
554 default=False,
554 default=False,
555 )
555 )
556 coreconfigitem('ui', 'style',
556 coreconfigitem('ui', 'style',
557 default='',
557 default='',
558 )
558 )
559 coreconfigitem('ui', 'supportcontact',
559 coreconfigitem('ui', 'supportcontact',
560 default=None,
560 default=None,
561 )
561 )
562 coreconfigitem('ui', 'textwidth',
562 coreconfigitem('ui', 'textwidth',
563 default=78,
563 default=78,
564 )
564 )
565 coreconfigitem('ui', 'timeout',
565 coreconfigitem('ui', 'timeout',
566 default='600',
566 default='600',
567 )
567 )
568 coreconfigitem('ui', 'traceback',
568 coreconfigitem('ui', 'traceback',
569 default=False,
569 default=False,
570 )
570 )
571 coreconfigitem('ui', 'tweakdefaults',
571 coreconfigitem('ui', 'tweakdefaults',
572 default=False,
572 default=False,
573 )
573 )
574 coreconfigitem('ui', 'usehttp2',
574 coreconfigitem('ui', 'usehttp2',
575 default=False,
575 default=False,
576 )
576 )
577 coreconfigitem('ui', 'username',
577 coreconfigitem('ui', 'username',
578 alias=[('ui', 'user')]
578 alias=[('ui', 'user')]
579 )
579 )
580 coreconfigitem('ui', 'verbose',
580 coreconfigitem('ui', 'verbose',
581 default=False,
581 default=False,
582 )
582 )
583 coreconfigitem('verify', 'skipflags',
583 coreconfigitem('verify', 'skipflags',
584 default=None,
584 default=None,
585 )
585 )
586 coreconfigitem('web', 'accesslog',
586 coreconfigitem('web', 'accesslog',
587 default='-',
587 default='-',
588 )
588 )
589 coreconfigitem('web', 'address',
589 coreconfigitem('web', 'address',
590 default='',
590 default='',
591 )
591 )
592 coreconfigitem('web', 'allow_archive',
592 coreconfigitem('web', 'allow_archive',
593 default=list,
593 default=list,
594 )
594 )
595 coreconfigitem('web', 'allow_read',
595 coreconfigitem('web', 'allow_read',
596 default=list,
596 default=list,
597 )
597 )
598 coreconfigitem('web', 'baseurl',
598 coreconfigitem('web', 'baseurl',
599 default=None,
599 default=None,
600 )
600 )
601 coreconfigitem('web', 'cacerts',
601 coreconfigitem('web', 'cacerts',
602 default=None,
602 default=None,
603 )
603 )
604 coreconfigitem('web', 'certificate',
604 coreconfigitem('web', 'certificate',
605 default=None,
605 default=None,
606 )
606 )
607 coreconfigitem('web', 'collapse',
607 coreconfigitem('web', 'collapse',
608 default=False,
608 default=False,
609 )
609 )
610 coreconfigitem('web', 'csp',
610 coreconfigitem('web', 'csp',
611 default=None,
611 default=None,
612 )
612 )
613 coreconfigitem('web', 'deny_read',
613 coreconfigitem('web', 'deny_read',
614 default=list,
614 default=list,
615 )
615 )
616 coreconfigitem('web', 'descend',
616 coreconfigitem('web', 'descend',
617 default=True,
617 default=True,
618 )
618 )
619 coreconfigitem('web', 'description',
619 coreconfigitem('web', 'description',
620 default="",
620 default="",
621 )
621 )
622 coreconfigitem('web', 'encoding',
622 coreconfigitem('web', 'encoding',
623 default=lambda: encoding.encoding,
623 default=lambda: encoding.encoding,
624 )
624 )
625 coreconfigitem('web', 'errorlog',
625 coreconfigitem('web', 'errorlog',
626 default='-',
626 default='-',
627 )
627 )
628 coreconfigitem('web', 'ipv6',
628 coreconfigitem('web', 'ipv6',
629 default=False,
629 default=False,
630 )
630 )
631 coreconfigitem('web', 'port',
631 coreconfigitem('web', 'port',
632 default=8000,
632 default=8000,
633 )
633 )
634 coreconfigitem('web', 'prefix',
635 default='',
636 )
634 coreconfigitem('worker', 'backgroundclose',
637 coreconfigitem('worker', 'backgroundclose',
635 default=dynamicdefault,
638 default=dynamicdefault,
636 )
639 )
637 # Windows defaults to a limit of 512 open files. A buffer of 128
640 # Windows defaults to a limit of 512 open files. A buffer of 128
638 # should give us enough headway.
641 # should give us enough headway.
639 coreconfigitem('worker', 'backgroundclosemaxqueue',
642 coreconfigitem('worker', 'backgroundclosemaxqueue',
640 default=384,
643 default=384,
641 )
644 )
642 coreconfigitem('worker', 'backgroundcloseminfilecount',
645 coreconfigitem('worker', 'backgroundcloseminfilecount',
643 default=2048,
646 default=2048,
644 )
647 )
645 coreconfigitem('worker', 'backgroundclosethreadcount',
648 coreconfigitem('worker', 'backgroundclosethreadcount',
646 default=4,
649 default=4,
647 )
650 )
648 coreconfigitem('worker', 'numcpus',
651 coreconfigitem('worker', 'numcpus',
649 default=None,
652 default=None,
650 )
653 )
@@ -1,539 +1,539 b''
1 # hgweb/hgwebdir_mod.py - Web interface for a directory of repositories.
1 # hgweb/hgwebdir_mod.py - Web interface for a directory of repositories.
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, 2006 Matt Mackall <mpm@selenic.com>
4 # Copyright 2005, 2006 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 os
11 import os
12 import re
12 import re
13 import time
13 import time
14
14
15 from ..i18n import _
15 from ..i18n import _
16
16
17 from .common import (
17 from .common import (
18 ErrorResponse,
18 ErrorResponse,
19 HTTP_NOT_FOUND,
19 HTTP_NOT_FOUND,
20 HTTP_OK,
20 HTTP_OK,
21 HTTP_SERVER_ERROR,
21 HTTP_SERVER_ERROR,
22 cspvalues,
22 cspvalues,
23 get_contact,
23 get_contact,
24 get_mtime,
24 get_mtime,
25 ismember,
25 ismember,
26 paritygen,
26 paritygen,
27 staticfile,
27 staticfile,
28 )
28 )
29 from .request import wsgirequest
29 from .request import wsgirequest
30
30
31 from .. import (
31 from .. import (
32 encoding,
32 encoding,
33 error,
33 error,
34 hg,
34 hg,
35 profiling,
35 profiling,
36 scmutil,
36 scmutil,
37 templater,
37 templater,
38 ui as uimod,
38 ui as uimod,
39 util,
39 util,
40 )
40 )
41
41
42 from . import (
42 from . import (
43 hgweb_mod,
43 hgweb_mod,
44 webutil,
44 webutil,
45 wsgicgi,
45 wsgicgi,
46 )
46 )
47
47
48 def cleannames(items):
48 def cleannames(items):
49 return [(util.pconvert(name).strip('/'), path) for name, path in items]
49 return [(util.pconvert(name).strip('/'), path) for name, path in items]
50
50
51 def findrepos(paths):
51 def findrepos(paths):
52 repos = []
52 repos = []
53 for prefix, root in cleannames(paths):
53 for prefix, root in cleannames(paths):
54 roothead, roottail = os.path.split(root)
54 roothead, roottail = os.path.split(root)
55 # "foo = /bar/*" or "foo = /bar/**" lets every repo /bar/N in or below
55 # "foo = /bar/*" or "foo = /bar/**" lets every repo /bar/N in or below
56 # /bar/ be served as as foo/N .
56 # /bar/ be served as as foo/N .
57 # '*' will not search inside dirs with .hg (except .hg/patches),
57 # '*' will not search inside dirs with .hg (except .hg/patches),
58 # '**' will search inside dirs with .hg (and thus also find subrepos).
58 # '**' will search inside dirs with .hg (and thus also find subrepos).
59 try:
59 try:
60 recurse = {'*': False, '**': True}[roottail]
60 recurse = {'*': False, '**': True}[roottail]
61 except KeyError:
61 except KeyError:
62 repos.append((prefix, root))
62 repos.append((prefix, root))
63 continue
63 continue
64 roothead = os.path.normpath(os.path.abspath(roothead))
64 roothead = os.path.normpath(os.path.abspath(roothead))
65 paths = scmutil.walkrepos(roothead, followsym=True, recurse=recurse)
65 paths = scmutil.walkrepos(roothead, followsym=True, recurse=recurse)
66 repos.extend(urlrepos(prefix, roothead, paths))
66 repos.extend(urlrepos(prefix, roothead, paths))
67 return repos
67 return repos
68
68
69 def urlrepos(prefix, roothead, paths):
69 def urlrepos(prefix, roothead, paths):
70 """yield url paths and filesystem paths from a list of repo paths
70 """yield url paths and filesystem paths from a list of repo paths
71
71
72 >>> conv = lambda seq: [(v, util.pconvert(p)) for v,p in seq]
72 >>> conv = lambda seq: [(v, util.pconvert(p)) for v,p in seq]
73 >>> conv(urlrepos(b'hg', b'/opt', [b'/opt/r', b'/opt/r/r', b'/opt']))
73 >>> conv(urlrepos(b'hg', b'/opt', [b'/opt/r', b'/opt/r/r', b'/opt']))
74 [('hg/r', '/opt/r'), ('hg/r/r', '/opt/r/r'), ('hg', '/opt')]
74 [('hg/r', '/opt/r'), ('hg/r/r', '/opt/r/r'), ('hg', '/opt')]
75 >>> conv(urlrepos(b'', b'/opt', [b'/opt/r', b'/opt/r/r', b'/opt']))
75 >>> conv(urlrepos(b'', b'/opt', [b'/opt/r', b'/opt/r/r', b'/opt']))
76 [('r', '/opt/r'), ('r/r', '/opt/r/r'), ('', '/opt')]
76 [('r', '/opt/r'), ('r/r', '/opt/r/r'), ('', '/opt')]
77 """
77 """
78 for path in paths:
78 for path in paths:
79 path = os.path.normpath(path)
79 path = os.path.normpath(path)
80 yield (prefix + '/' +
80 yield (prefix + '/' +
81 util.pconvert(path[len(roothead):]).lstrip('/')).strip('/'), path
81 util.pconvert(path[len(roothead):]).lstrip('/')).strip('/'), path
82
82
83 def geturlcgivars(baseurl, port):
83 def geturlcgivars(baseurl, port):
84 """
84 """
85 Extract CGI variables from baseurl
85 Extract CGI variables from baseurl
86
86
87 >>> geturlcgivars(b"http://host.org/base", b"80")
87 >>> geturlcgivars(b"http://host.org/base", b"80")
88 ('host.org', '80', '/base')
88 ('host.org', '80', '/base')
89 >>> geturlcgivars(b"http://host.org:8000/base", b"80")
89 >>> geturlcgivars(b"http://host.org:8000/base", b"80")
90 ('host.org', '8000', '/base')
90 ('host.org', '8000', '/base')
91 >>> geturlcgivars(b'/base', 8000)
91 >>> geturlcgivars(b'/base', 8000)
92 ('', '8000', '/base')
92 ('', '8000', '/base')
93 >>> geturlcgivars(b"base", b'8000')
93 >>> geturlcgivars(b"base", b'8000')
94 ('', '8000', '/base')
94 ('', '8000', '/base')
95 >>> geturlcgivars(b"http://host", b'8000')
95 >>> geturlcgivars(b"http://host", b'8000')
96 ('host', '8000', '/')
96 ('host', '8000', '/')
97 >>> geturlcgivars(b"http://host/", b'8000')
97 >>> geturlcgivars(b"http://host/", b'8000')
98 ('host', '8000', '/')
98 ('host', '8000', '/')
99 """
99 """
100 u = util.url(baseurl)
100 u = util.url(baseurl)
101 name = u.host or ''
101 name = u.host or ''
102 if u.port:
102 if u.port:
103 port = u.port
103 port = u.port
104 path = u.path or ""
104 path = u.path or ""
105 if not path.startswith('/'):
105 if not path.startswith('/'):
106 path = '/' + path
106 path = '/' + path
107
107
108 return name, str(port), path
108 return name, str(port), path
109
109
110 class hgwebdir(object):
110 class hgwebdir(object):
111 """HTTP server for multiple repositories.
111 """HTTP server for multiple repositories.
112
112
113 Given a configuration, different repositories will be served depending
113 Given a configuration, different repositories will be served depending
114 on the request path.
114 on the request path.
115
115
116 Instances are typically used as WSGI applications.
116 Instances are typically used as WSGI applications.
117 """
117 """
118 def __init__(self, conf, baseui=None):
118 def __init__(self, conf, baseui=None):
119 self.conf = conf
119 self.conf = conf
120 self.baseui = baseui
120 self.baseui = baseui
121 self.ui = None
121 self.ui = None
122 self.lastrefresh = 0
122 self.lastrefresh = 0
123 self.motd = None
123 self.motd = None
124 self.refresh()
124 self.refresh()
125
125
126 def refresh(self):
126 def refresh(self):
127 refreshinterval = 20
127 refreshinterval = 20
128 if self.ui:
128 if self.ui:
129 refreshinterval = self.ui.configint('web', 'refreshinterval',
129 refreshinterval = self.ui.configint('web', 'refreshinterval',
130 refreshinterval)
130 refreshinterval)
131
131
132 # refreshinterval <= 0 means to always refresh.
132 # refreshinterval <= 0 means to always refresh.
133 if (refreshinterval > 0 and
133 if (refreshinterval > 0 and
134 self.lastrefresh + refreshinterval > time.time()):
134 self.lastrefresh + refreshinterval > time.time()):
135 return
135 return
136
136
137 if self.baseui:
137 if self.baseui:
138 u = self.baseui.copy()
138 u = self.baseui.copy()
139 else:
139 else:
140 u = uimod.ui.load()
140 u = uimod.ui.load()
141 u.setconfig('ui', 'report_untrusted', 'off', 'hgwebdir')
141 u.setconfig('ui', 'report_untrusted', 'off', 'hgwebdir')
142 u.setconfig('ui', 'nontty', 'true', 'hgwebdir')
142 u.setconfig('ui', 'nontty', 'true', 'hgwebdir')
143 # displaying bundling progress bar while serving feels wrong and may
143 # displaying bundling progress bar while serving feels wrong and may
144 # break some wsgi implementations.
144 # break some wsgi implementations.
145 u.setconfig('progress', 'disable', 'true', 'hgweb')
145 u.setconfig('progress', 'disable', 'true', 'hgweb')
146
146
147 if not isinstance(self.conf, (dict, list, tuple)):
147 if not isinstance(self.conf, (dict, list, tuple)):
148 map = {'paths': 'hgweb-paths'}
148 map = {'paths': 'hgweb-paths'}
149 if not os.path.exists(self.conf):
149 if not os.path.exists(self.conf):
150 raise error.Abort(_('config file %s not found!') % self.conf)
150 raise error.Abort(_('config file %s not found!') % self.conf)
151 u.readconfig(self.conf, remap=map, trust=True)
151 u.readconfig(self.conf, remap=map, trust=True)
152 paths = []
152 paths = []
153 for name, ignored in u.configitems('hgweb-paths'):
153 for name, ignored in u.configitems('hgweb-paths'):
154 for path in u.configlist('hgweb-paths', name):
154 for path in u.configlist('hgweb-paths', name):
155 paths.append((name, path))
155 paths.append((name, path))
156 elif isinstance(self.conf, (list, tuple)):
156 elif isinstance(self.conf, (list, tuple)):
157 paths = self.conf
157 paths = self.conf
158 elif isinstance(self.conf, dict):
158 elif isinstance(self.conf, dict):
159 paths = self.conf.items()
159 paths = self.conf.items()
160
160
161 repos = findrepos(paths)
161 repos = findrepos(paths)
162 for prefix, root in u.configitems('collections'):
162 for prefix, root in u.configitems('collections'):
163 prefix = util.pconvert(prefix)
163 prefix = util.pconvert(prefix)
164 for path in scmutil.walkrepos(root, followsym=True):
164 for path in scmutil.walkrepos(root, followsym=True):
165 repo = os.path.normpath(path)
165 repo = os.path.normpath(path)
166 name = util.pconvert(repo)
166 name = util.pconvert(repo)
167 if name.startswith(prefix):
167 if name.startswith(prefix):
168 name = name[len(prefix):]
168 name = name[len(prefix):]
169 repos.append((name.lstrip('/'), repo))
169 repos.append((name.lstrip('/'), repo))
170
170
171 self.repos = repos
171 self.repos = repos
172 self.ui = u
172 self.ui = u
173 encoding.encoding = self.ui.config('web', 'encoding')
173 encoding.encoding = self.ui.config('web', 'encoding')
174 self.style = self.ui.config('web', 'style', 'paper')
174 self.style = self.ui.config('web', 'style', 'paper')
175 self.templatepath = self.ui.config('web', 'templates', None)
175 self.templatepath = self.ui.config('web', 'templates', None)
176 self.stripecount = self.ui.config('web', 'stripes', 1)
176 self.stripecount = self.ui.config('web', 'stripes', 1)
177 if self.stripecount:
177 if self.stripecount:
178 self.stripecount = int(self.stripecount)
178 self.stripecount = int(self.stripecount)
179 self._baseurl = self.ui.config('web', 'baseurl')
179 self._baseurl = self.ui.config('web', 'baseurl')
180 prefix = self.ui.config('web', 'prefix', '')
180 prefix = self.ui.config('web', 'prefix')
181 if prefix.startswith('/'):
181 if prefix.startswith('/'):
182 prefix = prefix[1:]
182 prefix = prefix[1:]
183 if prefix.endswith('/'):
183 if prefix.endswith('/'):
184 prefix = prefix[:-1]
184 prefix = prefix[:-1]
185 self.prefix = prefix
185 self.prefix = prefix
186 self.lastrefresh = time.time()
186 self.lastrefresh = time.time()
187
187
188 def run(self):
188 def run(self):
189 if not encoding.environ.get('GATEWAY_INTERFACE',
189 if not encoding.environ.get('GATEWAY_INTERFACE',
190 '').startswith("CGI/1."):
190 '').startswith("CGI/1."):
191 raise RuntimeError("This function is only intended to be "
191 raise RuntimeError("This function is only intended to be "
192 "called while running as a CGI script.")
192 "called while running as a CGI script.")
193 wsgicgi.launch(self)
193 wsgicgi.launch(self)
194
194
195 def __call__(self, env, respond):
195 def __call__(self, env, respond):
196 req = wsgirequest(env, respond)
196 req = wsgirequest(env, respond)
197 return self.run_wsgi(req)
197 return self.run_wsgi(req)
198
198
199 def read_allowed(self, ui, req):
199 def read_allowed(self, ui, req):
200 """Check allow_read and deny_read config options of a repo's ui object
200 """Check allow_read and deny_read config options of a repo's ui object
201 to determine user permissions. By default, with neither option set (or
201 to determine user permissions. By default, with neither option set (or
202 both empty), allow all users to read the repo. There are two ways a
202 both empty), allow all users to read the repo. There are two ways a
203 user can be denied read access: (1) deny_read is not empty, and the
203 user can be denied read access: (1) deny_read is not empty, and the
204 user is unauthenticated or deny_read contains user (or *), and (2)
204 user is unauthenticated or deny_read contains user (or *), and (2)
205 allow_read is not empty and the user is not in allow_read. Return True
205 allow_read is not empty and the user is not in allow_read. Return True
206 if user is allowed to read the repo, else return False."""
206 if user is allowed to read the repo, else return False."""
207
207
208 user = req.env.get('REMOTE_USER')
208 user = req.env.get('REMOTE_USER')
209
209
210 deny_read = ui.configlist('web', 'deny_read', untrusted=True)
210 deny_read = ui.configlist('web', 'deny_read', untrusted=True)
211 if deny_read and (not user or ismember(ui, user, deny_read)):
211 if deny_read and (not user or ismember(ui, user, deny_read)):
212 return False
212 return False
213
213
214 allow_read = ui.configlist('web', 'allow_read', untrusted=True)
214 allow_read = ui.configlist('web', 'allow_read', untrusted=True)
215 # by default, allow reading if no allow_read option has been set
215 # by default, allow reading if no allow_read option has been set
216 if (not allow_read) or ismember(ui, user, allow_read):
216 if (not allow_read) or ismember(ui, user, allow_read):
217 return True
217 return True
218
218
219 return False
219 return False
220
220
221 def run_wsgi(self, req):
221 def run_wsgi(self, req):
222 profile = self.ui.configbool('profiling', 'enabled')
222 profile = self.ui.configbool('profiling', 'enabled')
223 with profiling.profile(self.ui, enabled=profile):
223 with profiling.profile(self.ui, enabled=profile):
224 for r in self._runwsgi(req):
224 for r in self._runwsgi(req):
225 yield r
225 yield r
226
226
227 def _runwsgi(self, req):
227 def _runwsgi(self, req):
228 try:
228 try:
229 self.refresh()
229 self.refresh()
230
230
231 csp, nonce = cspvalues(self.ui)
231 csp, nonce = cspvalues(self.ui)
232 if csp:
232 if csp:
233 req.headers.append(('Content-Security-Policy', csp))
233 req.headers.append(('Content-Security-Policy', csp))
234
234
235 virtual = req.env.get("PATH_INFO", "").strip('/')
235 virtual = req.env.get("PATH_INFO", "").strip('/')
236 tmpl = self.templater(req, nonce)
236 tmpl = self.templater(req, nonce)
237 ctype = tmpl('mimetype', encoding=encoding.encoding)
237 ctype = tmpl('mimetype', encoding=encoding.encoding)
238 ctype = templater.stringify(ctype)
238 ctype = templater.stringify(ctype)
239
239
240 # a static file
240 # a static file
241 if virtual.startswith('static/') or 'static' in req.form:
241 if virtual.startswith('static/') or 'static' in req.form:
242 if virtual.startswith('static/'):
242 if virtual.startswith('static/'):
243 fname = virtual[7:]
243 fname = virtual[7:]
244 else:
244 else:
245 fname = req.form['static'][0]
245 fname = req.form['static'][0]
246 static = self.ui.config("web", "static", None,
246 static = self.ui.config("web", "static", None,
247 untrusted=False)
247 untrusted=False)
248 if not static:
248 if not static:
249 tp = self.templatepath or templater.templatepaths()
249 tp = self.templatepath or templater.templatepaths()
250 if isinstance(tp, str):
250 if isinstance(tp, str):
251 tp = [tp]
251 tp = [tp]
252 static = [os.path.join(p, 'static') for p in tp]
252 static = [os.path.join(p, 'static') for p in tp]
253 staticfile(static, fname, req)
253 staticfile(static, fname, req)
254 return []
254 return []
255
255
256 # top-level index
256 # top-level index
257
257
258 repos = dict(self.repos)
258 repos = dict(self.repos)
259
259
260 if (not virtual or virtual == 'index') and virtual not in repos:
260 if (not virtual or virtual == 'index') and virtual not in repos:
261 req.respond(HTTP_OK, ctype)
261 req.respond(HTTP_OK, ctype)
262 return self.makeindex(req, tmpl)
262 return self.makeindex(req, tmpl)
263
263
264 # nested indexes and hgwebs
264 # nested indexes and hgwebs
265
265
266 if virtual.endswith('/index') and virtual not in repos:
266 if virtual.endswith('/index') and virtual not in repos:
267 subdir = virtual[:-len('index')]
267 subdir = virtual[:-len('index')]
268 if any(r.startswith(subdir) for r in repos):
268 if any(r.startswith(subdir) for r in repos):
269 req.respond(HTTP_OK, ctype)
269 req.respond(HTTP_OK, ctype)
270 return self.makeindex(req, tmpl, subdir)
270 return self.makeindex(req, tmpl, subdir)
271
271
272 def _virtualdirs():
272 def _virtualdirs():
273 # Check the full virtual path, each parent, and the root ('')
273 # Check the full virtual path, each parent, and the root ('')
274 if virtual != '':
274 if virtual != '':
275 yield virtual
275 yield virtual
276
276
277 for p in util.finddirs(virtual):
277 for p in util.finddirs(virtual):
278 yield p
278 yield p
279
279
280 yield ''
280 yield ''
281
281
282 for virtualrepo in _virtualdirs():
282 for virtualrepo in _virtualdirs():
283 real = repos.get(virtualrepo)
283 real = repos.get(virtualrepo)
284 if real:
284 if real:
285 req.env['REPO_NAME'] = virtualrepo
285 req.env['REPO_NAME'] = virtualrepo
286 try:
286 try:
287 # ensure caller gets private copy of ui
287 # ensure caller gets private copy of ui
288 repo = hg.repository(self.ui.copy(), real)
288 repo = hg.repository(self.ui.copy(), real)
289 return hgweb_mod.hgweb(repo).run_wsgi(req)
289 return hgweb_mod.hgweb(repo).run_wsgi(req)
290 except IOError as inst:
290 except IOError as inst:
291 msg = encoding.strtolocal(inst.strerror)
291 msg = encoding.strtolocal(inst.strerror)
292 raise ErrorResponse(HTTP_SERVER_ERROR, msg)
292 raise ErrorResponse(HTTP_SERVER_ERROR, msg)
293 except error.RepoError as inst:
293 except error.RepoError as inst:
294 raise ErrorResponse(HTTP_SERVER_ERROR, str(inst))
294 raise ErrorResponse(HTTP_SERVER_ERROR, str(inst))
295
295
296 # browse subdirectories
296 # browse subdirectories
297 subdir = virtual + '/'
297 subdir = virtual + '/'
298 if [r for r in repos if r.startswith(subdir)]:
298 if [r for r in repos if r.startswith(subdir)]:
299 req.respond(HTTP_OK, ctype)
299 req.respond(HTTP_OK, ctype)
300 return self.makeindex(req, tmpl, subdir)
300 return self.makeindex(req, tmpl, subdir)
301
301
302 # prefixes not found
302 # prefixes not found
303 req.respond(HTTP_NOT_FOUND, ctype)
303 req.respond(HTTP_NOT_FOUND, ctype)
304 return tmpl("notfound", repo=virtual)
304 return tmpl("notfound", repo=virtual)
305
305
306 except ErrorResponse as err:
306 except ErrorResponse as err:
307 req.respond(err, ctype)
307 req.respond(err, ctype)
308 return tmpl('error', error=err.message or '')
308 return tmpl('error', error=err.message or '')
309 finally:
309 finally:
310 tmpl = None
310 tmpl = None
311
311
312 def makeindex(self, req, tmpl, subdir=""):
312 def makeindex(self, req, tmpl, subdir=""):
313
313
314 def archivelist(ui, nodeid, url):
314 def archivelist(ui, nodeid, url):
315 allowed = ui.configlist("web", "allow_archive", untrusted=True)
315 allowed = ui.configlist("web", "allow_archive", untrusted=True)
316 archives = []
316 archives = []
317 for typ, spec in hgweb_mod.archivespecs.iteritems():
317 for typ, spec in hgweb_mod.archivespecs.iteritems():
318 if typ in allowed or ui.configbool("web", "allow" + typ,
318 if typ in allowed or ui.configbool("web", "allow" + typ,
319 untrusted=True):
319 untrusted=True):
320 archives.append({"type" : typ, "extension": spec[2],
320 archives.append({"type" : typ, "extension": spec[2],
321 "node": nodeid, "url": url})
321 "node": nodeid, "url": url})
322 return archives
322 return archives
323
323
324 def rawentries(subdir="", **map):
324 def rawentries(subdir="", **map):
325
325
326 descend = self.ui.configbool('web', 'descend')
326 descend = self.ui.configbool('web', 'descend')
327 collapse = self.ui.configbool('web', 'collapse')
327 collapse = self.ui.configbool('web', 'collapse')
328 seenrepos = set()
328 seenrepos = set()
329 seendirs = set()
329 seendirs = set()
330 for name, path in self.repos:
330 for name, path in self.repos:
331
331
332 if not name.startswith(subdir):
332 if not name.startswith(subdir):
333 continue
333 continue
334 name = name[len(subdir):]
334 name = name[len(subdir):]
335 directory = False
335 directory = False
336
336
337 if '/' in name:
337 if '/' in name:
338 if not descend:
338 if not descend:
339 continue
339 continue
340
340
341 nameparts = name.split('/')
341 nameparts = name.split('/')
342 rootname = nameparts[0]
342 rootname = nameparts[0]
343
343
344 if not collapse:
344 if not collapse:
345 pass
345 pass
346 elif rootname in seendirs:
346 elif rootname in seendirs:
347 continue
347 continue
348 elif rootname in seenrepos:
348 elif rootname in seenrepos:
349 pass
349 pass
350 else:
350 else:
351 directory = True
351 directory = True
352 name = rootname
352 name = rootname
353
353
354 # redefine the path to refer to the directory
354 # redefine the path to refer to the directory
355 discarded = '/'.join(nameparts[1:])
355 discarded = '/'.join(nameparts[1:])
356
356
357 # remove name parts plus accompanying slash
357 # remove name parts plus accompanying slash
358 path = path[:-len(discarded) - 1]
358 path = path[:-len(discarded) - 1]
359
359
360 try:
360 try:
361 r = hg.repository(self.ui, path)
361 r = hg.repository(self.ui, path)
362 directory = False
362 directory = False
363 except (IOError, error.RepoError):
363 except (IOError, error.RepoError):
364 pass
364 pass
365
365
366 parts = [name]
366 parts = [name]
367 parts.insert(0, '/' + subdir.rstrip('/'))
367 parts.insert(0, '/' + subdir.rstrip('/'))
368 if req.env['SCRIPT_NAME']:
368 if req.env['SCRIPT_NAME']:
369 parts.insert(0, req.env['SCRIPT_NAME'])
369 parts.insert(0, req.env['SCRIPT_NAME'])
370 url = re.sub(r'/+', '/', '/'.join(parts) + '/')
370 url = re.sub(r'/+', '/', '/'.join(parts) + '/')
371
371
372 # show either a directory entry or a repository
372 # show either a directory entry or a repository
373 if directory:
373 if directory:
374 # get the directory's time information
374 # get the directory's time information
375 try:
375 try:
376 d = (get_mtime(path), util.makedate()[1])
376 d = (get_mtime(path), util.makedate()[1])
377 except OSError:
377 except OSError:
378 continue
378 continue
379
379
380 # add '/' to the name to make it obvious that
380 # add '/' to the name to make it obvious that
381 # the entry is a directory, not a regular repository
381 # the entry is a directory, not a regular repository
382 row = {'contact': "",
382 row = {'contact': "",
383 'contact_sort': "",
383 'contact_sort': "",
384 'name': name + '/',
384 'name': name + '/',
385 'name_sort': name,
385 'name_sort': name,
386 'url': url,
386 'url': url,
387 'description': "",
387 'description': "",
388 'description_sort': "",
388 'description_sort': "",
389 'lastchange': d,
389 'lastchange': d,
390 'lastchange_sort': d[1]-d[0],
390 'lastchange_sort': d[1]-d[0],
391 'archives': [],
391 'archives': [],
392 'isdirectory': True,
392 'isdirectory': True,
393 'labels': [],
393 'labels': [],
394 }
394 }
395
395
396 seendirs.add(name)
396 seendirs.add(name)
397 yield row
397 yield row
398 continue
398 continue
399
399
400 u = self.ui.copy()
400 u = self.ui.copy()
401 try:
401 try:
402 u.readconfig(os.path.join(path, '.hg', 'hgrc'))
402 u.readconfig(os.path.join(path, '.hg', 'hgrc'))
403 except Exception as e:
403 except Exception as e:
404 u.warn(_('error reading %s/.hg/hgrc: %s\n') % (path, e))
404 u.warn(_('error reading %s/.hg/hgrc: %s\n') % (path, e))
405 continue
405 continue
406 def get(section, name, default=uimod._unset):
406 def get(section, name, default=uimod._unset):
407 return u.config(section, name, default, untrusted=True)
407 return u.config(section, name, default, untrusted=True)
408
408
409 if u.configbool("web", "hidden", untrusted=True):
409 if u.configbool("web", "hidden", untrusted=True):
410 continue
410 continue
411
411
412 if not self.read_allowed(u, req):
412 if not self.read_allowed(u, req):
413 continue
413 continue
414
414
415 # update time with local timezone
415 # update time with local timezone
416 try:
416 try:
417 r = hg.repository(self.ui, path)
417 r = hg.repository(self.ui, path)
418 except IOError:
418 except IOError:
419 u.warn(_('error accessing repository at %s\n') % path)
419 u.warn(_('error accessing repository at %s\n') % path)
420 continue
420 continue
421 except error.RepoError:
421 except error.RepoError:
422 u.warn(_('error accessing repository at %s\n') % path)
422 u.warn(_('error accessing repository at %s\n') % path)
423 continue
423 continue
424 try:
424 try:
425 d = (get_mtime(r.spath), util.makedate()[1])
425 d = (get_mtime(r.spath), util.makedate()[1])
426 except OSError:
426 except OSError:
427 continue
427 continue
428
428
429 contact = get_contact(get)
429 contact = get_contact(get)
430 description = get("web", "description")
430 description = get("web", "description")
431 seenrepos.add(name)
431 seenrepos.add(name)
432 name = get("web", "name", name)
432 name = get("web", "name", name)
433 row = {'contact': contact or "unknown",
433 row = {'contact': contact or "unknown",
434 'contact_sort': contact.upper() or "unknown",
434 'contact_sort': contact.upper() or "unknown",
435 'name': name,
435 'name': name,
436 'name_sort': name,
436 'name_sort': name,
437 'url': url,
437 'url': url,
438 'description': description or "unknown",
438 'description': description or "unknown",
439 'description_sort': description.upper() or "unknown",
439 'description_sort': description.upper() or "unknown",
440 'lastchange': d,
440 'lastchange': d,
441 'lastchange_sort': d[1]-d[0],
441 'lastchange_sort': d[1]-d[0],
442 'archives': archivelist(u, "tip", url),
442 'archives': archivelist(u, "tip", url),
443 'isdirectory': None,
443 'isdirectory': None,
444 'labels': u.configlist('web', 'labels', untrusted=True),
444 'labels': u.configlist('web', 'labels', untrusted=True),
445 }
445 }
446
446
447 yield row
447 yield row
448
448
449 sortdefault = None, False
449 sortdefault = None, False
450 def entries(sortcolumn="", descending=False, subdir="", **map):
450 def entries(sortcolumn="", descending=False, subdir="", **map):
451 rows = rawentries(subdir=subdir, **map)
451 rows = rawentries(subdir=subdir, **map)
452
452
453 if sortcolumn and sortdefault != (sortcolumn, descending):
453 if sortcolumn and sortdefault != (sortcolumn, descending):
454 sortkey = '%s_sort' % sortcolumn
454 sortkey = '%s_sort' % sortcolumn
455 rows = sorted(rows, key=lambda x: x[sortkey],
455 rows = sorted(rows, key=lambda x: x[sortkey],
456 reverse=descending)
456 reverse=descending)
457 for row, parity in zip(rows, paritygen(self.stripecount)):
457 for row, parity in zip(rows, paritygen(self.stripecount)):
458 row['parity'] = parity
458 row['parity'] = parity
459 yield row
459 yield row
460
460
461 self.refresh()
461 self.refresh()
462 sortable = ["name", "description", "contact", "lastchange"]
462 sortable = ["name", "description", "contact", "lastchange"]
463 sortcolumn, descending = sortdefault
463 sortcolumn, descending = sortdefault
464 if 'sort' in req.form:
464 if 'sort' in req.form:
465 sortcolumn = req.form['sort'][0]
465 sortcolumn = req.form['sort'][0]
466 descending = sortcolumn.startswith('-')
466 descending = sortcolumn.startswith('-')
467 if descending:
467 if descending:
468 sortcolumn = sortcolumn[1:]
468 sortcolumn = sortcolumn[1:]
469 if sortcolumn not in sortable:
469 if sortcolumn not in sortable:
470 sortcolumn = ""
470 sortcolumn = ""
471
471
472 sort = [("sort_%s" % column,
472 sort = [("sort_%s" % column,
473 "%s%s" % ((not descending and column == sortcolumn)
473 "%s%s" % ((not descending and column == sortcolumn)
474 and "-" or "", column))
474 and "-" or "", column))
475 for column in sortable]
475 for column in sortable]
476
476
477 self.refresh()
477 self.refresh()
478 self.updatereqenv(req.env)
478 self.updatereqenv(req.env)
479
479
480 return tmpl("index", entries=entries, subdir=subdir,
480 return tmpl("index", entries=entries, subdir=subdir,
481 pathdef=hgweb_mod.makebreadcrumb('/' + subdir, self.prefix),
481 pathdef=hgweb_mod.makebreadcrumb('/' + subdir, self.prefix),
482 sortcolumn=sortcolumn, descending=descending,
482 sortcolumn=sortcolumn, descending=descending,
483 **dict(sort))
483 **dict(sort))
484
484
485 def templater(self, req, nonce):
485 def templater(self, req, nonce):
486
486
487 def motd(**map):
487 def motd(**map):
488 if self.motd is not None:
488 if self.motd is not None:
489 yield self.motd
489 yield self.motd
490 else:
490 else:
491 yield config('web', 'motd', '')
491 yield config('web', 'motd', '')
492
492
493 def config(section, name, default=uimod._unset, untrusted=True):
493 def config(section, name, default=uimod._unset, untrusted=True):
494 return self.ui.config(section, name, default, untrusted)
494 return self.ui.config(section, name, default, untrusted)
495
495
496 self.updatereqenv(req.env)
496 self.updatereqenv(req.env)
497
497
498 url = req.env.get('SCRIPT_NAME', '')
498 url = req.env.get('SCRIPT_NAME', '')
499 if not url.endswith('/'):
499 if not url.endswith('/'):
500 url += '/'
500 url += '/'
501
501
502 vars = {}
502 vars = {}
503 styles = (
503 styles = (
504 req.form.get('style', [None])[0],
504 req.form.get('style', [None])[0],
505 config('web', 'style'),
505 config('web', 'style'),
506 'paper'
506 'paper'
507 )
507 )
508 style, mapfile = templater.stylemap(styles, self.templatepath)
508 style, mapfile = templater.stylemap(styles, self.templatepath)
509 if style == styles[0]:
509 if style == styles[0]:
510 vars['style'] = style
510 vars['style'] = style
511
511
512 start = url[-1] == '?' and '&' or '?'
512 start = url[-1] == '?' and '&' or '?'
513 sessionvars = webutil.sessionvars(vars, start)
513 sessionvars = webutil.sessionvars(vars, start)
514 logourl = config('web', 'logourl', 'https://mercurial-scm.org/')
514 logourl = config('web', 'logourl', 'https://mercurial-scm.org/')
515 logoimg = config('web', 'logoimg', 'hglogo.png')
515 logoimg = config('web', 'logoimg', 'hglogo.png')
516 staticurl = config('web', 'staticurl') or url + 'static/'
516 staticurl = config('web', 'staticurl') or url + 'static/'
517 if not staticurl.endswith('/'):
517 if not staticurl.endswith('/'):
518 staticurl += '/'
518 staticurl += '/'
519
519
520 defaults = {
520 defaults = {
521 "encoding": encoding.encoding,
521 "encoding": encoding.encoding,
522 "motd": motd,
522 "motd": motd,
523 "url": url,
523 "url": url,
524 "logourl": logourl,
524 "logourl": logourl,
525 "logoimg": logoimg,
525 "logoimg": logoimg,
526 "staticurl": staticurl,
526 "staticurl": staticurl,
527 "sessionvars": sessionvars,
527 "sessionvars": sessionvars,
528 "style": style,
528 "style": style,
529 "nonce": nonce,
529 "nonce": nonce,
530 }
530 }
531 tmpl = templater.templater.frommapfile(mapfile, defaults=defaults)
531 tmpl = templater.templater.frommapfile(mapfile, defaults=defaults)
532 return tmpl
532 return tmpl
533
533
534 def updatereqenv(self, env):
534 def updatereqenv(self, env):
535 if self._baseurl is not None:
535 if self._baseurl is not None:
536 name, port, path = geturlcgivars(self._baseurl, env['SERVER_PORT'])
536 name, port, path = geturlcgivars(self._baseurl, env['SERVER_PORT'])
537 env['SERVER_NAME'] = name
537 env['SERVER_NAME'] = name
538 env['SERVER_PORT'] = port
538 env['SERVER_PORT'] = port
539 env['SCRIPT_NAME'] = path
539 env['SCRIPT_NAME'] = path
@@ -1,335 +1,335 b''
1 # hgweb/server.py - The standalone hg web server.
1 # hgweb/server.py - The standalone hg web server.
2 #
2 #
3 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
3 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
4 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
5 #
5 #
6 # This software may be used and distributed according to the terms of the
6 # This software may be used and distributed according to the terms of the
7 # GNU General Public License version 2 or any later version.
7 # GNU General Public License version 2 or any later version.
8
8
9 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 error,
20 error,
21 pycompat,
21 pycompat,
22 util,
22 util,
23 )
23 )
24
24
25 httpservermod = util.httpserver
25 httpservermod = util.httpserver
26 socketserver = util.socketserver
26 socketserver = util.socketserver
27 urlerr = util.urlerr
27 urlerr = util.urlerr
28 urlreq = util.urlreq
28 urlreq = util.urlreq
29
29
30 from . import (
30 from . import (
31 common,
31 common,
32 )
32 )
33
33
34 def _splitURI(uri):
34 def _splitURI(uri):
35 """Return path and query that has been split from uri
35 """Return path and query that has been split from uri
36
36
37 Just like CGI environment, the path is unquoted, the query is
37 Just like CGI environment, the path is unquoted, the query is
38 not.
38 not.
39 """
39 """
40 if '?' in uri:
40 if '?' in uri:
41 path, query = uri.split('?', 1)
41 path, query = uri.split('?', 1)
42 else:
42 else:
43 path, query = uri, ''
43 path, query = uri, ''
44 return urlreq.unquote(path), query
44 return urlreq.unquote(path), query
45
45
46 class _error_logger(object):
46 class _error_logger(object):
47 def __init__(self, handler):
47 def __init__(self, handler):
48 self.handler = handler
48 self.handler = handler
49 def flush(self):
49 def flush(self):
50 pass
50 pass
51 def write(self, str):
51 def write(self, str):
52 self.writelines(str.split('\n'))
52 self.writelines(str.split('\n'))
53 def writelines(self, seq):
53 def writelines(self, seq):
54 for msg in seq:
54 for msg in seq:
55 self.handler.log_error("HG error: %s", msg)
55 self.handler.log_error("HG error: %s", msg)
56
56
57 class _httprequesthandler(httpservermod.basehttprequesthandler):
57 class _httprequesthandler(httpservermod.basehttprequesthandler):
58
58
59 url_scheme = 'http'
59 url_scheme = 'http'
60
60
61 @staticmethod
61 @staticmethod
62 def preparehttpserver(httpserver, ui):
62 def preparehttpserver(httpserver, ui):
63 """Prepare .socket of new HTTPServer instance"""
63 """Prepare .socket of new HTTPServer instance"""
64 pass
64 pass
65
65
66 def __init__(self, *args, **kargs):
66 def __init__(self, *args, **kargs):
67 self.protocol_version = 'HTTP/1.1'
67 self.protocol_version = '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("%s - - [%s] %s\n" % (self.client_address[0],
71 fp.write("%s - - [%s] %s\n" % (self.client_address[0],
72 self.log_date_time_string(),
72 self.log_date_time_string(),
73 format % args))
73 format % args))
74 fp.flush()
74 fp.flush()
75
75
76 def log_error(self, format, *args):
76 def log_error(self, format, *args):
77 self._log_any(self.server.errorlog, format, *args)
77 self._log_any(self.server.errorlog, format, *args)
78
78
79 def log_message(self, format, *args):
79 def log_message(self, format, *args):
80 self._log_any(self.server.accesslog, format, *args)
80 self._log_any(self.server.accesslog, format, *args)
81
81
82 def log_request(self, code='-', size='-'):
82 def log_request(self, code='-', size='-'):
83 xheaders = []
83 xheaders = []
84 if util.safehasattr(self, 'headers'):
84 if util.safehasattr(self, 'headers'):
85 xheaders = [h for h in self.headers.items()
85 xheaders = [h for h in self.headers.items()
86 if h[0].startswith('x-')]
86 if h[0].startswith('x-')]
87 self.log_message('"%s" %s %s%s',
87 self.log_message('"%s" %s %s%s',
88 self.requestline, str(code), str(size),
88 self.requestline, str(code), str(size),
89 ''.join([' %s:%s' % h for h in sorted(xheaders)]))
89 ''.join([' %s:%s' % h for h in sorted(xheaders)]))
90
90
91 def do_write(self):
91 def do_write(self):
92 try:
92 try:
93 self.do_hgweb()
93 self.do_hgweb()
94 except socket.error as inst:
94 except socket.error as inst:
95 if inst[0] != errno.EPIPE:
95 if inst[0] != errno.EPIPE:
96 raise
96 raise
97
97
98 def do_POST(self):
98 def do_POST(self):
99 try:
99 try:
100 self.do_write()
100 self.do_write()
101 except Exception:
101 except Exception:
102 self._start_response("500 Internal Server Error", [])
102 self._start_response("500 Internal Server Error", [])
103 self._write("Internal Server Error")
103 self._write("Internal Server Error")
104 self._done()
104 self._done()
105 tb = "".join(traceback.format_exception(*sys.exc_info()))
105 tb = "".join(traceback.format_exception(*sys.exc_info()))
106 self.log_error("Exception happened during processing "
106 self.log_error("Exception happened during processing "
107 "request '%s':\n%s", self.path, tb)
107 "request '%s':\n%s", self.path, tb)
108
108
109 def do_GET(self):
109 def do_GET(self):
110 self.do_POST()
110 self.do_POST()
111
111
112 def do_hgweb(self):
112 def do_hgweb(self):
113 path, query = _splitURI(self.path)
113 path, query = _splitURI(self.path)
114
114
115 env = {}
115 env = {}
116 env['GATEWAY_INTERFACE'] = 'CGI/1.1'
116 env['GATEWAY_INTERFACE'] = 'CGI/1.1'
117 env['REQUEST_METHOD'] = self.command
117 env['REQUEST_METHOD'] = self.command
118 env['SERVER_NAME'] = self.server.server_name
118 env['SERVER_NAME'] = self.server.server_name
119 env['SERVER_PORT'] = str(self.server.server_port)
119 env['SERVER_PORT'] = str(self.server.server_port)
120 env['REQUEST_URI'] = self.path
120 env['REQUEST_URI'] = self.path
121 env['SCRIPT_NAME'] = self.server.prefix
121 env['SCRIPT_NAME'] = self.server.prefix
122 env['PATH_INFO'] = path[len(self.server.prefix):]
122 env['PATH_INFO'] = path[len(self.server.prefix):]
123 env['REMOTE_HOST'] = self.client_address[0]
123 env['REMOTE_HOST'] = self.client_address[0]
124 env['REMOTE_ADDR'] = self.client_address[0]
124 env['REMOTE_ADDR'] = self.client_address[0]
125 if query:
125 if query:
126 env['QUERY_STRING'] = query
126 env['QUERY_STRING'] = query
127
127
128 if self.headers.typeheader is None:
128 if self.headers.typeheader is None:
129 env['CONTENT_TYPE'] = self.headers.type
129 env['CONTENT_TYPE'] = self.headers.type
130 else:
130 else:
131 env['CONTENT_TYPE'] = self.headers.typeheader
131 env['CONTENT_TYPE'] = self.headers.typeheader
132 length = self.headers.getheader('content-length')
132 length = self.headers.getheader('content-length')
133 if length:
133 if length:
134 env['CONTENT_LENGTH'] = length
134 env['CONTENT_LENGTH'] = length
135 for header in [h for h in self.headers.keys()
135 for header in [h for h in self.headers.keys()
136 if h not in ('content-type', 'content-length')]:
136 if h not in ('content-type', 'content-length')]:
137 hkey = 'HTTP_' + header.replace('-', '_').upper()
137 hkey = 'HTTP_' + header.replace('-', '_').upper()
138 hval = self.headers.getheader(header)
138 hval = self.headers.getheader(header)
139 hval = hval.replace('\n', '').strip()
139 hval = hval.replace('\n', '').strip()
140 if hval:
140 if hval:
141 env[hkey] = hval
141 env[hkey] = hval
142 env['SERVER_PROTOCOL'] = self.request_version
142 env['SERVER_PROTOCOL'] = self.request_version
143 env['wsgi.version'] = (1, 0)
143 env['wsgi.version'] = (1, 0)
144 env['wsgi.url_scheme'] = self.url_scheme
144 env['wsgi.url_scheme'] = self.url_scheme
145 if env.get('HTTP_EXPECT', '').lower() == '100-continue':
145 if env.get('HTTP_EXPECT', '').lower() == '100-continue':
146 self.rfile = common.continuereader(self.rfile, self.wfile.write)
146 self.rfile = common.continuereader(self.rfile, self.wfile.write)
147
147
148 env['wsgi.input'] = self.rfile
148 env['wsgi.input'] = self.rfile
149 env['wsgi.errors'] = _error_logger(self)
149 env['wsgi.errors'] = _error_logger(self)
150 env['wsgi.multithread'] = isinstance(self.server,
150 env['wsgi.multithread'] = isinstance(self.server,
151 socketserver.ThreadingMixIn)
151 socketserver.ThreadingMixIn)
152 env['wsgi.multiprocess'] = isinstance(self.server,
152 env['wsgi.multiprocess'] = isinstance(self.server,
153 socketserver.ForkingMixIn)
153 socketserver.ForkingMixIn)
154 env['wsgi.run_once'] = 0
154 env['wsgi.run_once'] = 0
155
155
156 self.saved_status = None
156 self.saved_status = None
157 self.saved_headers = []
157 self.saved_headers = []
158 self.sent_headers = False
158 self.sent_headers = False
159 self.length = None
159 self.length = None
160 self._chunked = None
160 self._chunked = None
161 for chunk in self.server.application(env, self._start_response):
161 for chunk in self.server.application(env, self._start_response):
162 self._write(chunk)
162 self._write(chunk)
163 if not self.sent_headers:
163 if not self.sent_headers:
164 self.send_headers()
164 self.send_headers()
165 self._done()
165 self._done()
166
166
167 def send_headers(self):
167 def send_headers(self):
168 if not self.saved_status:
168 if not self.saved_status:
169 raise AssertionError("Sending headers before "
169 raise AssertionError("Sending headers before "
170 "start_response() called")
170 "start_response() called")
171 saved_status = self.saved_status.split(None, 1)
171 saved_status = self.saved_status.split(None, 1)
172 saved_status[0] = int(saved_status[0])
172 saved_status[0] = int(saved_status[0])
173 self.send_response(*saved_status)
173 self.send_response(*saved_status)
174 self.length = None
174 self.length = None
175 self._chunked = False
175 self._chunked = False
176 for h in self.saved_headers:
176 for h in self.saved_headers:
177 self.send_header(*h)
177 self.send_header(*h)
178 if h[0].lower() == 'content-length':
178 if h[0].lower() == 'content-length':
179 self.length = int(h[1])
179 self.length = int(h[1])
180 if (self.length is None and
180 if (self.length is None and
181 saved_status[0] != common.HTTP_NOT_MODIFIED):
181 saved_status[0] != common.HTTP_NOT_MODIFIED):
182 self._chunked = (not self.close_connection and
182 self._chunked = (not self.close_connection and
183 self.request_version == "HTTP/1.1")
183 self.request_version == "HTTP/1.1")
184 if self._chunked:
184 if self._chunked:
185 self.send_header('Transfer-Encoding', 'chunked')
185 self.send_header('Transfer-Encoding', 'chunked')
186 else:
186 else:
187 self.send_header('Connection', 'close')
187 self.send_header('Connection', 'close')
188 self.end_headers()
188 self.end_headers()
189 self.sent_headers = True
189 self.sent_headers = True
190
190
191 def _start_response(self, http_status, headers, exc_info=None):
191 def _start_response(self, http_status, headers, exc_info=None):
192 code, msg = http_status.split(None, 1)
192 code, msg = http_status.split(None, 1)
193 code = int(code)
193 code = int(code)
194 self.saved_status = http_status
194 self.saved_status = http_status
195 bad_headers = ('connection', 'transfer-encoding')
195 bad_headers = ('connection', 'transfer-encoding')
196 self.saved_headers = [h for h in headers
196 self.saved_headers = [h for h in headers
197 if h[0].lower() not in bad_headers]
197 if h[0].lower() not in bad_headers]
198 return self._write
198 return self._write
199
199
200 def _write(self, data):
200 def _write(self, data):
201 if not self.saved_status:
201 if not self.saved_status:
202 raise AssertionError("data written before start_response() called")
202 raise AssertionError("data written before start_response() called")
203 elif not self.sent_headers:
203 elif not self.sent_headers:
204 self.send_headers()
204 self.send_headers()
205 if self.length is not None:
205 if self.length is not None:
206 if len(data) > self.length:
206 if len(data) > self.length:
207 raise AssertionError("Content-length header sent, but more "
207 raise AssertionError("Content-length header sent, but more "
208 "bytes than specified are being written.")
208 "bytes than specified are being written.")
209 self.length = self.length - len(data)
209 self.length = self.length - len(data)
210 elif self._chunked and data:
210 elif self._chunked and data:
211 data = '%x\r\n%s\r\n' % (len(data), data)
211 data = '%x\r\n%s\r\n' % (len(data), data)
212 self.wfile.write(data)
212 self.wfile.write(data)
213 self.wfile.flush()
213 self.wfile.flush()
214
214
215 def _done(self):
215 def _done(self):
216 if self._chunked:
216 if self._chunked:
217 self.wfile.write('0\r\n\r\n')
217 self.wfile.write('0\r\n\r\n')
218 self.wfile.flush()
218 self.wfile.flush()
219
219
220 class _httprequesthandlerssl(_httprequesthandler):
220 class _httprequesthandlerssl(_httprequesthandler):
221 """HTTPS handler based on Python's ssl module"""
221 """HTTPS handler based on Python's ssl module"""
222
222
223 url_scheme = 'https'
223 url_scheme = 'https'
224
224
225 @staticmethod
225 @staticmethod
226 def preparehttpserver(httpserver, ui):
226 def preparehttpserver(httpserver, ui):
227 try:
227 try:
228 from .. import sslutil
228 from .. import sslutil
229 sslutil.modernssl
229 sslutil.modernssl
230 except ImportError:
230 except ImportError:
231 raise error.Abort(_("SSL support is unavailable"))
231 raise error.Abort(_("SSL support is unavailable"))
232
232
233 certfile = ui.config('web', 'certificate')
233 certfile = ui.config('web', 'certificate')
234
234
235 # These config options are currently only meant for testing. Use
235 # These config options are currently only meant for testing. Use
236 # at your own risk.
236 # at your own risk.
237 cafile = ui.config('devel', 'servercafile')
237 cafile = ui.config('devel', 'servercafile')
238 reqcert = ui.configbool('devel', 'serverrequirecert')
238 reqcert = ui.configbool('devel', 'serverrequirecert')
239
239
240 httpserver.socket = sslutil.wrapserversocket(httpserver.socket,
240 httpserver.socket = sslutil.wrapserversocket(httpserver.socket,
241 ui,
241 ui,
242 certfile=certfile,
242 certfile=certfile,
243 cafile=cafile,
243 cafile=cafile,
244 requireclientcert=reqcert)
244 requireclientcert=reqcert)
245
245
246 def setup(self):
246 def setup(self):
247 self.connection = self.request
247 self.connection = self.request
248 self.rfile = socket._fileobject(self.request, "rb", self.rbufsize)
248 self.rfile = socket._fileobject(self.request, "rb", self.rbufsize)
249 self.wfile = socket._fileobject(self.request, "wb", self.wbufsize)
249 self.wfile = socket._fileobject(self.request, "wb", self.wbufsize)
250
250
251 try:
251 try:
252 import threading
252 import threading
253 threading.activeCount() # silence pyflakes and bypass demandimport
253 threading.activeCount() # silence pyflakes and bypass demandimport
254 _mixin = socketserver.ThreadingMixIn
254 _mixin = socketserver.ThreadingMixIn
255 except ImportError:
255 except ImportError:
256 if util.safehasattr(os, "fork"):
256 if util.safehasattr(os, "fork"):
257 _mixin = socketserver.ForkingMixIn
257 _mixin = socketserver.ForkingMixIn
258 else:
258 else:
259 class _mixin(object):
259 class _mixin(object):
260 pass
260 pass
261
261
262 def openlog(opt, default):
262 def openlog(opt, default):
263 if opt and opt != '-':
263 if opt and opt != '-':
264 return open(opt, 'a')
264 return open(opt, 'a')
265 return default
265 return default
266
266
267 class MercurialHTTPServer(_mixin, httpservermod.httpserver, object):
267 class MercurialHTTPServer(_mixin, httpservermod.httpserver, object):
268
268
269 # SO_REUSEADDR has broken semantics on windows
269 # SO_REUSEADDR has broken semantics on windows
270 if pycompat.osname == 'nt':
270 if pycompat.osname == 'nt':
271 allow_reuse_address = 0
271 allow_reuse_address = 0
272
272
273 def __init__(self, ui, app, addr, handler, **kwargs):
273 def __init__(self, ui, app, addr, handler, **kwargs):
274 httpservermod.httpserver.__init__(self, addr, handler, **kwargs)
274 httpservermod.httpserver.__init__(self, addr, handler, **kwargs)
275 self.daemon_threads = True
275 self.daemon_threads = True
276 self.application = app
276 self.application = app
277
277
278 handler.preparehttpserver(self, ui)
278 handler.preparehttpserver(self, ui)
279
279
280 prefix = ui.config('web', 'prefix', '')
280 prefix = ui.config('web', 'prefix')
281 if prefix:
281 if prefix:
282 prefix = '/' + prefix.strip('/')
282 prefix = '/' + prefix.strip('/')
283 self.prefix = prefix
283 self.prefix = prefix
284
284
285 alog = openlog(ui.config('web', 'accesslog'), ui.fout)
285 alog = openlog(ui.config('web', 'accesslog'), ui.fout)
286 elog = openlog(ui.config('web', 'errorlog'), ui.ferr)
286 elog = openlog(ui.config('web', 'errorlog'), ui.ferr)
287 self.accesslog = alog
287 self.accesslog = alog
288 self.errorlog = elog
288 self.errorlog = elog
289
289
290 self.addr, self.port = self.socket.getsockname()[0:2]
290 self.addr, self.port = self.socket.getsockname()[0:2]
291 self.fqaddr = socket.getfqdn(addr[0])
291 self.fqaddr = socket.getfqdn(addr[0])
292
292
293 class IPv6HTTPServer(MercurialHTTPServer):
293 class IPv6HTTPServer(MercurialHTTPServer):
294 address_family = getattr(socket, 'AF_INET6', None)
294 address_family = getattr(socket, 'AF_INET6', None)
295 def __init__(self, *args, **kwargs):
295 def __init__(self, *args, **kwargs):
296 if self.address_family is None:
296 if self.address_family is None:
297 raise error.RepoError(_('IPv6 is not available on this system'))
297 raise error.RepoError(_('IPv6 is not available on this system'))
298 super(IPv6HTTPServer, self).__init__(*args, **kwargs)
298 super(IPv6HTTPServer, self).__init__(*args, **kwargs)
299
299
300 def create_server(ui, app):
300 def create_server(ui, app):
301
301
302 if ui.config('web', 'certificate'):
302 if ui.config('web', 'certificate'):
303 handler = _httprequesthandlerssl
303 handler = _httprequesthandlerssl
304 else:
304 else:
305 handler = _httprequesthandler
305 handler = _httprequesthandler
306
306
307 if ui.configbool('web', 'ipv6'):
307 if ui.configbool('web', 'ipv6'):
308 cls = IPv6HTTPServer
308 cls = IPv6HTTPServer
309 else:
309 else:
310 cls = MercurialHTTPServer
310 cls = MercurialHTTPServer
311
311
312 # ugly hack due to python issue5853 (for threaded use)
312 # ugly hack due to python issue5853 (for threaded use)
313 try:
313 try:
314 import mimetypes
314 import mimetypes
315 mimetypes.init()
315 mimetypes.init()
316 except UnicodeDecodeError:
316 except UnicodeDecodeError:
317 # Python 2.x's mimetypes module attempts to decode strings
317 # Python 2.x's mimetypes module attempts to decode strings
318 # from Windows' ANSI APIs as ascii (fail), then re-encode them
318 # from Windows' ANSI APIs as ascii (fail), then re-encode them
319 # as ascii (clown fail), because the default Python Unicode
319 # as ascii (clown fail), because the default Python Unicode
320 # codec is hardcoded as ascii.
320 # codec is hardcoded as ascii.
321
321
322 sys.argv # unwrap demand-loader so that reload() works
322 sys.argv # unwrap demand-loader so that reload() works
323 reload(sys) # resurrect sys.setdefaultencoding()
323 reload(sys) # resurrect sys.setdefaultencoding()
324 oldenc = sys.getdefaultencoding()
324 oldenc = sys.getdefaultencoding()
325 sys.setdefaultencoding("latin1") # or any full 8-bit encoding
325 sys.setdefaultencoding("latin1") # or any full 8-bit encoding
326 mimetypes.init()
326 mimetypes.init()
327 sys.setdefaultencoding(oldenc)
327 sys.setdefaultencoding(oldenc)
328
328
329 address = ui.config('web', 'address')
329 address = ui.config('web', 'address')
330 port = util.getport(ui.config('web', 'port'))
330 port = util.getport(ui.config('web', 'port'))
331 try:
331 try:
332 return cls(ui, app, (address, port), handler)
332 return cls(ui, app, (address, port), handler)
333 except socket.error as inst:
333 except socket.error as inst:
334 raise error.Abort(_("cannot start server at '%s:%d': %s")
334 raise error.Abort(_("cannot start server at '%s:%d': %s")
335 % (address, port, inst.args[1]))
335 % (address, port, inst.args[1]))
General Comments 0
You need to be logged in to leave comments. Login now