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