##// END OF EJS Templates
wireproto: support /api/* URL space for exposing APIs...
Gregory Szorc -
r37064:1cfef569 default
parent child Browse files
Show More
@@ -0,0 +1,65 b''
1 $ send() {
2 > hg --verbose debugwireproto --peer raw http://$LOCALIP:$HGPORT/
3 > }
4
5 $ hg init server
6 $ cat > server/.hg/hgrc << EOF
7 > [experimental]
8 > web.apiserver = true
9 > EOF
10 $ hg -R server serve -p $HGPORT -d --pid-file hg.pid
11 $ cat hg.pid > $DAEMON_PIDS
12
13 HTTP v2 protocol not enabled by default
14
15 $ send << EOF
16 > httprequest GET api/exp-http-v2-0001
17 > user-agent: test
18 > EOF
19 using raw connection to peer
20 s> GET /api/exp-http-v2-0001 HTTP/1.1\r\n
21 s> Accept-Encoding: identity\r\n
22 s> user-agent: test\r\n
23 s> host: $LOCALIP:$HGPORT\r\n (glob)
24 s> \r\n
25 s> makefile('rb', None)
26 s> HTTP/1.1 404 Not Found\r\n
27 s> Server: testing stub value\r\n
28 s> Date: $HTTP_DATE$\r\n
29 s> Content-Type: text/plain\r\n
30 s> Content-Length: 33\r\n
31 s> \r\n
32 s> API exp-http-v2-0001 not enabled\n
33
34 Restart server with support for HTTP v2 API
35
36 $ killdaemons.py
37 $ cat > server/.hg/hgrc << EOF
38 > [experimental]
39 > web.apiserver = true
40 > web.api.http-v2 = true
41 > EOF
42
43 $ hg -R server serve -p $HGPORT -d --pid-file hg.pid
44 $ cat hg.pid > $DAEMON_PIDS
45
46 Requests simply echo their path (for now)
47
48 $ send << EOF
49 > httprequest GET api/exp-http-v2-0001/path1/path2
50 > user-agent: test
51 > EOF
52 using raw connection to peer
53 s> GET /api/exp-http-v2-0001/path1/path2 HTTP/1.1\r\n
54 s> Accept-Encoding: identity\r\n
55 s> user-agent: test\r\n
56 s> host: $LOCALIP:$HGPORT\r\n (glob)
57 s> \r\n
58 s> makefile('rb', None)
59 s> HTTP/1.1 200 OK\r\n
60 s> Server: testing stub value\r\n
61 s> Date: $HTTP_DATE$\r\n
62 s> Content-Type: text/plain\r\n
63 s> Content-Length: 12\r\n
64 s> \r\n
65 s> path1/path2\n
@@ -0,0 +1,201 b''
1 $ send() {
2 > hg --verbose debugwireproto --peer raw http://$LOCALIP:$HGPORT/
3 > }
4
5 $ hg init server
6 $ hg -R server serve -p $HGPORT -d --pid-file hg.pid
7 $ cat hg.pid > $DAEMON_PIDS
8
9 Request to /api fails unless web.apiserver is enabled
10
11 $ send << EOF
12 > httprequest GET api
13 > user-agent: test
14 > EOF
15 using raw connection to peer
16 s> GET /api HTTP/1.1\r\n
17 s> Accept-Encoding: identity\r\n
18 s> user-agent: test\r\n
19 s> host: $LOCALIP:$HGPORT\r\n (glob)
20 s> \r\n
21 s> makefile('rb', None)
22 s> HTTP/1.1 404 Not Found\r\n
23 s> Server: testing stub value\r\n
24 s> Date: $HTTP_DATE$\r\n
25 s> Content-Type: text/plain\r\n
26 s> Content-Length: 44\r\n
27 s> \r\n
28 s> Experimental API server endpoint not enabled
29
30 $ send << EOF
31 > httprequest GET api/
32 > user-agent: test
33 > EOF
34 using raw connection to peer
35 s> GET /api/ HTTP/1.1\r\n
36 s> Accept-Encoding: identity\r\n
37 s> user-agent: test\r\n
38 s> host: $LOCALIP:$HGPORT\r\n (glob)
39 s> \r\n
40 s> makefile('rb', None)
41 s> HTTP/1.1 404 Not Found\r\n
42 s> Server: testing stub value\r\n
43 s> Date: $HTTP_DATE$\r\n
44 s> Content-Type: text/plain\r\n
45 s> Content-Length: 44\r\n
46 s> \r\n
47 s> Experimental API server endpoint not enabled
48
49 Restart server with support for API server
50
51 $ killdaemons.py
52 $ cat > server/.hg/hgrc << EOF
53 > [experimental]
54 > web.apiserver = true
55 > EOF
56
57 $ hg -R server serve -p $HGPORT -d --pid-file hg.pid
58 $ cat hg.pid > $DAEMON_PIDS
59
60 /api lists available APIs (empty since none are available by default)
61
62 $ send << EOF
63 > httprequest GET api
64 > user-agent: test
65 > EOF
66 using raw connection to peer
67 s> GET /api HTTP/1.1\r\n
68 s> Accept-Encoding: identity\r\n
69 s> user-agent: test\r\n
70 s> host: $LOCALIP:$HGPORT\r\n (glob)
71 s> \r\n
72 s> makefile('rb', None)
73 s> HTTP/1.1 200 OK\r\n
74 s> Server: testing stub value\r\n
75 s> Date: $HTTP_DATE$\r\n
76 s> Content-Type: text/plain\r\n
77 s> Content-Length: 100\r\n
78 s> \r\n
79 s> APIs can be accessed at /api/<name>, where <name> can be one of the following:\n
80 s> \n
81 s> (no available APIs)\n
82
83 $ send << EOF
84 > httprequest GET api/
85 > user-agent: test
86 > EOF
87 using raw connection to peer
88 s> GET /api/ HTTP/1.1\r\n
89 s> Accept-Encoding: identity\r\n
90 s> user-agent: test\r\n
91 s> host: $LOCALIP:$HGPORT\r\n (glob)
92 s> \r\n
93 s> makefile('rb', None)
94 s> HTTP/1.1 200 OK\r\n
95 s> Server: testing stub value\r\n
96 s> Date: $HTTP_DATE$\r\n
97 s> Content-Type: text/plain\r\n
98 s> Content-Length: 100\r\n
99 s> \r\n
100 s> APIs can be accessed at /api/<name>, where <name> can be one of the following:\n
101 s> \n
102 s> (no available APIs)\n
103
104 Accessing an unknown API yields a 404
105
106 $ send << EOF
107 > httprequest GET api/unknown
108 > user-agent: test
109 > EOF
110 using raw connection to peer
111 s> GET /api/unknown HTTP/1.1\r\n
112 s> Accept-Encoding: identity\r\n
113 s> user-agent: test\r\n
114 s> host: $LOCALIP:$HGPORT\r\n (glob)
115 s> \r\n
116 s> makefile('rb', None)
117 s> HTTP/1.1 404 Not Found\r\n
118 s> Server: testing stub value\r\n
119 s> Date: $HTTP_DATE$\r\n
120 s> Content-Type: text/plain\r\n
121 s> Content-Length: 33\r\n
122 s> \r\n
123 s> Unknown API: unknown\n
124 s> Known APIs:
125
126 Accessing a known but not enabled API yields a different error
127
128 $ send << EOF
129 > httprequest GET api/exp-http-v2-0001
130 > user-agent: test
131 > EOF
132 using raw connection to peer
133 s> GET /api/exp-http-v2-0001 HTTP/1.1\r\n
134 s> Accept-Encoding: identity\r\n
135 s> user-agent: test\r\n
136 s> host: $LOCALIP:$HGPORT\r\n (glob)
137 s> \r\n
138 s> makefile('rb', None)
139 s> HTTP/1.1 404 Not Found\r\n
140 s> Server: testing stub value\r\n
141 s> Date: $HTTP_DATE$\r\n
142 s> Content-Type: text/plain\r\n
143 s> Content-Length: 33\r\n
144 s> \r\n
145 s> API exp-http-v2-0001 not enabled\n
146
147 Restart server with support for HTTP v2 API
148
149 $ killdaemons.py
150 $ cat > server/.hg/hgrc << EOF
151 > [experimental]
152 > web.apiserver = true
153 > web.api.http-v2 = true
154 > EOF
155
156 $ hg -R server serve -p $HGPORT -d --pid-file hg.pid
157 $ cat hg.pid > $DAEMON_PIDS
158
159 /api lists the HTTP v2 protocol as available
160
161 $ send << EOF
162 > httprequest GET api
163 > user-agent: test
164 > EOF
165 using raw connection to peer
166 s> GET /api HTTP/1.1\r\n
167 s> Accept-Encoding: identity\r\n
168 s> user-agent: test\r\n
169 s> host: $LOCALIP:$HGPORT\r\n (glob)
170 s> \r\n
171 s> makefile('rb', None)
172 s> HTTP/1.1 200 OK\r\n
173 s> Server: testing stub value\r\n
174 s> Date: $HTTP_DATE$\r\n
175 s> Content-Type: text/plain\r\n
176 s> Content-Length: 96\r\n
177 s> \r\n
178 s> APIs can be accessed at /api/<name>, where <name> can be one of the following:\n
179 s> \n
180 s> exp-http-v2-0001
181
182 $ send << EOF
183 > httprequest GET api/
184 > user-agent: test
185 > EOF
186 using raw connection to peer
187 s> GET /api/ HTTP/1.1\r\n
188 s> Accept-Encoding: identity\r\n
189 s> user-agent: test\r\n
190 s> host: $LOCALIP:$HGPORT\r\n (glob)
191 s> \r\n
192 s> makefile('rb', None)
193 s> HTTP/1.1 200 OK\r\n
194 s> Server: testing stub value\r\n
195 s> Date: $HTTP_DATE$\r\n
196 s> Content-Type: text/plain\r\n
197 s> Content-Length: 96\r\n
198 s> \r\n
199 s> APIs can be accessed at /api/<name>, where <name> can be one of the following:\n
200 s> \n
201 s> exp-http-v2-0001
@@ -1,1311 +1,1317 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 import re
12 12
13 13 from . import (
14 14 encoding,
15 15 error,
16 16 )
17 17
18 18 def loadconfigtable(ui, extname, configtable):
19 19 """update config item known to the ui with the extension ones"""
20 20 for section, items in sorted(configtable.items()):
21 21 knownitems = ui._knownconfig.setdefault(section, itemregister())
22 22 knownkeys = set(knownitems)
23 23 newkeys = set(items)
24 24 for key in sorted(knownkeys & newkeys):
25 25 msg = "extension '%s' overwrite config item '%s.%s'"
26 26 msg %= (extname, section, key)
27 27 ui.develwarn(msg, config='warn-config')
28 28
29 29 knownitems.update(items)
30 30
31 31 class configitem(object):
32 32 """represent a known config item
33 33
34 34 :section: the official config section where to find this item,
35 35 :name: the official name within the section,
36 36 :default: default value for this item,
37 37 :alias: optional list of tuples as alternatives,
38 38 :generic: this is a generic definition, match name using regular expression.
39 39 """
40 40
41 41 def __init__(self, section, name, default=None, alias=(),
42 42 generic=False, priority=0):
43 43 self.section = section
44 44 self.name = name
45 45 self.default = default
46 46 self.alias = list(alias)
47 47 self.generic = generic
48 48 self.priority = priority
49 49 self._re = None
50 50 if generic:
51 51 self._re = re.compile(self.name)
52 52
53 53 class itemregister(dict):
54 54 """A specialized dictionary that can handle wild-card selection"""
55 55
56 56 def __init__(self):
57 57 super(itemregister, self).__init__()
58 58 self._generics = set()
59 59
60 60 def update(self, other):
61 61 super(itemregister, self).update(other)
62 62 self._generics.update(other._generics)
63 63
64 64 def __setitem__(self, key, item):
65 65 super(itemregister, self).__setitem__(key, item)
66 66 if item.generic:
67 67 self._generics.add(item)
68 68
69 69 def get(self, key):
70 70 baseitem = super(itemregister, self).get(key)
71 71 if baseitem is not None and not baseitem.generic:
72 72 return baseitem
73 73
74 74 # search for a matching generic item
75 75 generics = sorted(self._generics, key=(lambda x: (x.priority, x.name)))
76 76 for item in generics:
77 77 # we use 'match' instead of 'search' to make the matching simpler
78 78 # for people unfamiliar with regular expression. Having the match
79 79 # rooted to the start of the string will produce less surprising
80 80 # result for user writing simple regex for sub-attribute.
81 81 #
82 82 # For example using "color\..*" match produces an unsurprising
83 83 # result, while using search could suddenly match apparently
84 84 # unrelated configuration that happens to contains "color."
85 85 # anywhere. This is a tradeoff where we favor requiring ".*" on
86 86 # some match to avoid the need to prefix most pattern with "^".
87 87 # The "^" seems more error prone.
88 88 if item._re.match(key):
89 89 return item
90 90
91 91 return None
92 92
93 93 coreitems = {}
94 94
95 95 def _register(configtable, *args, **kwargs):
96 96 item = configitem(*args, **kwargs)
97 97 section = configtable.setdefault(item.section, itemregister())
98 98 if item.name in section:
99 99 msg = "duplicated config item registration for '%s.%s'"
100 100 raise error.ProgrammingError(msg % (item.section, item.name))
101 101 section[item.name] = item
102 102
103 103 # special value for case where the default is derived from other values
104 104 dynamicdefault = object()
105 105
106 106 # Registering actual config items
107 107
108 108 def getitemregister(configtable):
109 109 f = functools.partial(_register, configtable)
110 110 # export pseudo enum as configitem.*
111 111 f.dynamicdefault = dynamicdefault
112 112 return f
113 113
114 114 coreconfigitem = getitemregister(coreitems)
115 115
116 116 coreconfigitem('alias', '.*',
117 117 default=None,
118 118 generic=True,
119 119 )
120 120 coreconfigitem('annotate', 'nodates',
121 121 default=False,
122 122 )
123 123 coreconfigitem('annotate', 'showfunc',
124 124 default=False,
125 125 )
126 126 coreconfigitem('annotate', 'unified',
127 127 default=None,
128 128 )
129 129 coreconfigitem('annotate', 'git',
130 130 default=False,
131 131 )
132 132 coreconfigitem('annotate', 'ignorews',
133 133 default=False,
134 134 )
135 135 coreconfigitem('annotate', 'ignorewsamount',
136 136 default=False,
137 137 )
138 138 coreconfigitem('annotate', 'ignoreblanklines',
139 139 default=False,
140 140 )
141 141 coreconfigitem('annotate', 'ignorewseol',
142 142 default=False,
143 143 )
144 144 coreconfigitem('annotate', 'nobinary',
145 145 default=False,
146 146 )
147 147 coreconfigitem('annotate', 'noprefix',
148 148 default=False,
149 149 )
150 150 coreconfigitem('auth', 'cookiefile',
151 151 default=None,
152 152 )
153 153 # bookmarks.pushing: internal hack for discovery
154 154 coreconfigitem('bookmarks', 'pushing',
155 155 default=list,
156 156 )
157 157 # bundle.mainreporoot: internal hack for bundlerepo
158 158 coreconfigitem('bundle', 'mainreporoot',
159 159 default='',
160 160 )
161 161 # bundle.reorder: experimental config
162 162 coreconfigitem('bundle', 'reorder',
163 163 default='auto',
164 164 )
165 165 coreconfigitem('censor', 'policy',
166 166 default='abort',
167 167 )
168 168 coreconfigitem('chgserver', 'idletimeout',
169 169 default=3600,
170 170 )
171 171 coreconfigitem('chgserver', 'skiphash',
172 172 default=False,
173 173 )
174 174 coreconfigitem('cmdserver', 'log',
175 175 default=None,
176 176 )
177 177 coreconfigitem('color', '.*',
178 178 default=None,
179 179 generic=True,
180 180 )
181 181 coreconfigitem('color', 'mode',
182 182 default='auto',
183 183 )
184 184 coreconfigitem('color', 'pagermode',
185 185 default=dynamicdefault,
186 186 )
187 187 coreconfigitem('commands', 'show.aliasprefix',
188 188 default=list,
189 189 )
190 190 coreconfigitem('commands', 'status.relative',
191 191 default=False,
192 192 )
193 193 coreconfigitem('commands', 'status.skipstates',
194 194 default=[],
195 195 )
196 196 coreconfigitem('commands', 'status.verbose',
197 197 default=False,
198 198 )
199 199 coreconfigitem('commands', 'update.check',
200 200 default=None,
201 201 # Deprecated, remove after 4.4 release
202 202 alias=[('experimental', 'updatecheck')]
203 203 )
204 204 coreconfigitem('commands', 'update.requiredest',
205 205 default=False,
206 206 )
207 207 coreconfigitem('committemplate', '.*',
208 208 default=None,
209 209 generic=True,
210 210 )
211 211 coreconfigitem('convert', 'cvsps.cache',
212 212 default=True,
213 213 )
214 214 coreconfigitem('convert', 'cvsps.fuzz',
215 215 default=60,
216 216 )
217 217 coreconfigitem('convert', 'cvsps.logencoding',
218 218 default=None,
219 219 )
220 220 coreconfigitem('convert', 'cvsps.mergefrom',
221 221 default=None,
222 222 )
223 223 coreconfigitem('convert', 'cvsps.mergeto',
224 224 default=None,
225 225 )
226 226 coreconfigitem('convert', 'git.committeractions',
227 227 default=lambda: ['messagedifferent'],
228 228 )
229 229 coreconfigitem('convert', 'git.extrakeys',
230 230 default=list,
231 231 )
232 232 coreconfigitem('convert', 'git.findcopiesharder',
233 233 default=False,
234 234 )
235 235 coreconfigitem('convert', 'git.remoteprefix',
236 236 default='remote',
237 237 )
238 238 coreconfigitem('convert', 'git.renamelimit',
239 239 default=400,
240 240 )
241 241 coreconfigitem('convert', 'git.saverev',
242 242 default=True,
243 243 )
244 244 coreconfigitem('convert', 'git.similarity',
245 245 default=50,
246 246 )
247 247 coreconfigitem('convert', 'git.skipsubmodules',
248 248 default=False,
249 249 )
250 250 coreconfigitem('convert', 'hg.clonebranches',
251 251 default=False,
252 252 )
253 253 coreconfigitem('convert', 'hg.ignoreerrors',
254 254 default=False,
255 255 )
256 256 coreconfigitem('convert', 'hg.revs',
257 257 default=None,
258 258 )
259 259 coreconfigitem('convert', 'hg.saverev',
260 260 default=False,
261 261 )
262 262 coreconfigitem('convert', 'hg.sourcename',
263 263 default=None,
264 264 )
265 265 coreconfigitem('convert', 'hg.startrev',
266 266 default=None,
267 267 )
268 268 coreconfigitem('convert', 'hg.tagsbranch',
269 269 default='default',
270 270 )
271 271 coreconfigitem('convert', 'hg.usebranchnames',
272 272 default=True,
273 273 )
274 274 coreconfigitem('convert', 'ignoreancestorcheck',
275 275 default=False,
276 276 )
277 277 coreconfigitem('convert', 'localtimezone',
278 278 default=False,
279 279 )
280 280 coreconfigitem('convert', 'p4.encoding',
281 281 default=dynamicdefault,
282 282 )
283 283 coreconfigitem('convert', 'p4.startrev',
284 284 default=0,
285 285 )
286 286 coreconfigitem('convert', 'skiptags',
287 287 default=False,
288 288 )
289 289 coreconfigitem('convert', 'svn.debugsvnlog',
290 290 default=True,
291 291 )
292 292 coreconfigitem('convert', 'svn.trunk',
293 293 default=None,
294 294 )
295 295 coreconfigitem('convert', 'svn.tags',
296 296 default=None,
297 297 )
298 298 coreconfigitem('convert', 'svn.branches',
299 299 default=None,
300 300 )
301 301 coreconfigitem('convert', 'svn.startrev',
302 302 default=0,
303 303 )
304 304 coreconfigitem('debug', 'dirstate.delaywrite',
305 305 default=0,
306 306 )
307 307 coreconfigitem('defaults', '.*',
308 308 default=None,
309 309 generic=True,
310 310 )
311 311 coreconfigitem('devel', 'all-warnings',
312 312 default=False,
313 313 )
314 314 coreconfigitem('devel', 'bundle2.debug',
315 315 default=False,
316 316 )
317 317 coreconfigitem('devel', 'cache-vfs',
318 318 default=None,
319 319 )
320 320 coreconfigitem('devel', 'check-locks',
321 321 default=False,
322 322 )
323 323 coreconfigitem('devel', 'check-relroot',
324 324 default=False,
325 325 )
326 326 coreconfigitem('devel', 'default-date',
327 327 default=None,
328 328 )
329 329 coreconfigitem('devel', 'deprec-warn',
330 330 default=False,
331 331 )
332 332 coreconfigitem('devel', 'disableloaddefaultcerts',
333 333 default=False,
334 334 )
335 335 coreconfigitem('devel', 'warn-empty-changegroup',
336 336 default=False,
337 337 )
338 338 coreconfigitem('devel', 'legacy.exchange',
339 339 default=list,
340 340 )
341 341 coreconfigitem('devel', 'servercafile',
342 342 default='',
343 343 )
344 344 coreconfigitem('devel', 'serverexactprotocol',
345 345 default='',
346 346 )
347 347 coreconfigitem('devel', 'serverrequirecert',
348 348 default=False,
349 349 )
350 350 coreconfigitem('devel', 'strip-obsmarkers',
351 351 default=True,
352 352 )
353 353 coreconfigitem('devel', 'warn-config',
354 354 default=None,
355 355 )
356 356 coreconfigitem('devel', 'warn-config-default',
357 357 default=None,
358 358 )
359 359 coreconfigitem('devel', 'user.obsmarker',
360 360 default=None,
361 361 )
362 362 coreconfigitem('devel', 'warn-config-unknown',
363 363 default=None,
364 364 )
365 365 coreconfigitem('devel', 'debug.peer-request',
366 366 default=False,
367 367 )
368 368 coreconfigitem('diff', 'nodates',
369 369 default=False,
370 370 )
371 371 coreconfigitem('diff', 'showfunc',
372 372 default=False,
373 373 )
374 374 coreconfigitem('diff', 'unified',
375 375 default=None,
376 376 )
377 377 coreconfigitem('diff', 'git',
378 378 default=False,
379 379 )
380 380 coreconfigitem('diff', 'ignorews',
381 381 default=False,
382 382 )
383 383 coreconfigitem('diff', 'ignorewsamount',
384 384 default=False,
385 385 )
386 386 coreconfigitem('diff', 'ignoreblanklines',
387 387 default=False,
388 388 )
389 389 coreconfigitem('diff', 'ignorewseol',
390 390 default=False,
391 391 )
392 392 coreconfigitem('diff', 'nobinary',
393 393 default=False,
394 394 )
395 395 coreconfigitem('diff', 'noprefix',
396 396 default=False,
397 397 )
398 398 coreconfigitem('email', 'bcc',
399 399 default=None,
400 400 )
401 401 coreconfigitem('email', 'cc',
402 402 default=None,
403 403 )
404 404 coreconfigitem('email', 'charsets',
405 405 default=list,
406 406 )
407 407 coreconfigitem('email', 'from',
408 408 default=None,
409 409 )
410 410 coreconfigitem('email', 'method',
411 411 default='smtp',
412 412 )
413 413 coreconfigitem('email', 'reply-to',
414 414 default=None,
415 415 )
416 416 coreconfigitem('email', 'to',
417 417 default=None,
418 418 )
419 419 coreconfigitem('experimental', 'archivemetatemplate',
420 420 default=dynamicdefault,
421 421 )
422 422 coreconfigitem('experimental', 'bundle-phases',
423 423 default=False,
424 424 )
425 425 coreconfigitem('experimental', 'bundle2-advertise',
426 426 default=True,
427 427 )
428 428 coreconfigitem('experimental', 'bundle2-output-capture',
429 429 default=False,
430 430 )
431 431 coreconfigitem('experimental', 'bundle2.pushback',
432 432 default=False,
433 433 )
434 434 coreconfigitem('experimental', 'bundle2.stream',
435 435 default=False,
436 436 )
437 437 coreconfigitem('experimental', 'bundle2lazylocking',
438 438 default=False,
439 439 )
440 440 coreconfigitem('experimental', 'bundlecomplevel',
441 441 default=None,
442 442 )
443 443 coreconfigitem('experimental', 'changegroup3',
444 444 default=False,
445 445 )
446 446 coreconfigitem('experimental', 'clientcompressionengines',
447 447 default=list,
448 448 )
449 449 coreconfigitem('experimental', 'copytrace',
450 450 default='on',
451 451 )
452 452 coreconfigitem('experimental', 'copytrace.movecandidateslimit',
453 453 default=100,
454 454 )
455 455 coreconfigitem('experimental', 'copytrace.sourcecommitlimit',
456 456 default=100,
457 457 )
458 458 coreconfigitem('experimental', 'crecordtest',
459 459 default=None,
460 460 )
461 461 coreconfigitem('experimental', 'directaccess',
462 462 default=False,
463 463 )
464 464 coreconfigitem('experimental', 'directaccess.revnums',
465 465 default=False,
466 466 )
467 467 coreconfigitem('experimental', 'editortmpinhg',
468 468 default=False,
469 469 )
470 470 coreconfigitem('experimental', 'evolution',
471 471 default=list,
472 472 )
473 473 coreconfigitem('experimental', 'evolution.allowdivergence',
474 474 default=False,
475 475 alias=[('experimental', 'allowdivergence')]
476 476 )
477 477 coreconfigitem('experimental', 'evolution.allowunstable',
478 478 default=None,
479 479 )
480 480 coreconfigitem('experimental', 'evolution.createmarkers',
481 481 default=None,
482 482 )
483 483 coreconfigitem('experimental', 'evolution.effect-flags',
484 484 default=True,
485 485 alias=[('experimental', 'effect-flags')]
486 486 )
487 487 coreconfigitem('experimental', 'evolution.exchange',
488 488 default=None,
489 489 )
490 490 coreconfigitem('experimental', 'evolution.bundle-obsmarker',
491 491 default=False,
492 492 )
493 493 coreconfigitem('experimental', 'evolution.report-instabilities',
494 494 default=True,
495 495 )
496 496 coreconfigitem('experimental', 'evolution.track-operation',
497 497 default=True,
498 498 )
499 499 coreconfigitem('experimental', 'worddiff',
500 500 default=False,
501 501 )
502 502 coreconfigitem('experimental', 'maxdeltachainspan',
503 503 default=-1,
504 504 )
505 505 coreconfigitem('experimental', 'mergetempdirprefix',
506 506 default=None,
507 507 )
508 508 coreconfigitem('experimental', 'mmapindexthreshold',
509 509 default=None,
510 510 )
511 511 coreconfigitem('experimental', 'nonnormalparanoidcheck',
512 512 default=False,
513 513 )
514 514 coreconfigitem('experimental', 'exportableenviron',
515 515 default=list,
516 516 )
517 517 coreconfigitem('experimental', 'extendedheader.index',
518 518 default=None,
519 519 )
520 520 coreconfigitem('experimental', 'extendedheader.similarity',
521 521 default=False,
522 522 )
523 523 coreconfigitem('experimental', 'format.compression',
524 524 default='zlib',
525 525 )
526 526 coreconfigitem('experimental', 'graphshorten',
527 527 default=False,
528 528 )
529 529 coreconfigitem('experimental', 'graphstyle.parent',
530 530 default=dynamicdefault,
531 531 )
532 532 coreconfigitem('experimental', 'graphstyle.missing',
533 533 default=dynamicdefault,
534 534 )
535 535 coreconfigitem('experimental', 'graphstyle.grandparent',
536 536 default=dynamicdefault,
537 537 )
538 538 coreconfigitem('experimental', 'hook-track-tags',
539 539 default=False,
540 540 )
541 541 coreconfigitem('experimental', 'httppostargs',
542 542 default=False,
543 543 )
544 544 coreconfigitem('experimental', 'mergedriver',
545 545 default=None,
546 546 )
547 547 coreconfigitem('experimental', 'obsmarkers-exchange-debug',
548 548 default=False,
549 549 )
550 550 coreconfigitem('experimental', 'remotenames',
551 551 default=False,
552 552 )
553 553 coreconfigitem('experimental', 'revlogv2',
554 554 default=None,
555 555 )
556 556 coreconfigitem('experimental', 'single-head-per-branch',
557 557 default=False,
558 558 )
559 559 coreconfigitem('experimental', 'sshserver.support-v2',
560 560 default=False,
561 561 )
562 562 coreconfigitem('experimental', 'spacemovesdown',
563 563 default=False,
564 564 )
565 565 coreconfigitem('experimental', 'sparse-read',
566 566 default=False,
567 567 )
568 568 coreconfigitem('experimental', 'sparse-read.density-threshold',
569 569 default=0.25,
570 570 )
571 571 coreconfigitem('experimental', 'sparse-read.min-gap-size',
572 572 default='256K',
573 573 )
574 574 coreconfigitem('experimental', 'treemanifest',
575 575 default=False,
576 576 )
577 577 coreconfigitem('experimental', 'update.atomic-file',
578 578 default=False,
579 579 )
580 580 coreconfigitem('experimental', 'sshpeer.advertise-v2',
581 581 default=False,
582 582 )
583 coreconfigitem('experimental', 'web.apiserver',
584 default=False,
585 )
586 coreconfigitem('experimental', 'web.api.http-v2',
587 default=False,
588 )
583 589 coreconfigitem('experimental', 'xdiff',
584 590 default=False,
585 591 )
586 592 coreconfigitem('extensions', '.*',
587 593 default=None,
588 594 generic=True,
589 595 )
590 596 coreconfigitem('extdata', '.*',
591 597 default=None,
592 598 generic=True,
593 599 )
594 600 coreconfigitem('format', 'aggressivemergedeltas',
595 601 default=False,
596 602 )
597 603 coreconfigitem('format', 'chunkcachesize',
598 604 default=None,
599 605 )
600 606 coreconfigitem('format', 'dotencode',
601 607 default=True,
602 608 )
603 609 coreconfigitem('format', 'generaldelta',
604 610 default=False,
605 611 )
606 612 coreconfigitem('format', 'manifestcachesize',
607 613 default=None,
608 614 )
609 615 coreconfigitem('format', 'maxchainlen',
610 616 default=None,
611 617 )
612 618 coreconfigitem('format', 'obsstore-version',
613 619 default=None,
614 620 )
615 621 coreconfigitem('format', 'usefncache',
616 622 default=True,
617 623 )
618 624 coreconfigitem('format', 'usegeneraldelta',
619 625 default=True,
620 626 )
621 627 coreconfigitem('format', 'usestore',
622 628 default=True,
623 629 )
624 630 coreconfigitem('fsmonitor', 'warn_when_unused',
625 631 default=True,
626 632 )
627 633 coreconfigitem('fsmonitor', 'warn_update_file_count',
628 634 default=50000,
629 635 )
630 636 coreconfigitem('hooks', '.*',
631 637 default=dynamicdefault,
632 638 generic=True,
633 639 )
634 640 coreconfigitem('hgweb-paths', '.*',
635 641 default=list,
636 642 generic=True,
637 643 )
638 644 coreconfigitem('hostfingerprints', '.*',
639 645 default=list,
640 646 generic=True,
641 647 )
642 648 coreconfigitem('hostsecurity', 'ciphers',
643 649 default=None,
644 650 )
645 651 coreconfigitem('hostsecurity', 'disabletls10warning',
646 652 default=False,
647 653 )
648 654 coreconfigitem('hostsecurity', 'minimumprotocol',
649 655 default=dynamicdefault,
650 656 )
651 657 coreconfigitem('hostsecurity', '.*:minimumprotocol$',
652 658 default=dynamicdefault,
653 659 generic=True,
654 660 )
655 661 coreconfigitem('hostsecurity', '.*:ciphers$',
656 662 default=dynamicdefault,
657 663 generic=True,
658 664 )
659 665 coreconfigitem('hostsecurity', '.*:fingerprints$',
660 666 default=list,
661 667 generic=True,
662 668 )
663 669 coreconfigitem('hostsecurity', '.*:verifycertsfile$',
664 670 default=None,
665 671 generic=True,
666 672 )
667 673
668 674 coreconfigitem('http_proxy', 'always',
669 675 default=False,
670 676 )
671 677 coreconfigitem('http_proxy', 'host',
672 678 default=None,
673 679 )
674 680 coreconfigitem('http_proxy', 'no',
675 681 default=list,
676 682 )
677 683 coreconfigitem('http_proxy', 'passwd',
678 684 default=None,
679 685 )
680 686 coreconfigitem('http_proxy', 'user',
681 687 default=None,
682 688 )
683 689 coreconfigitem('logtoprocess', 'commandexception',
684 690 default=None,
685 691 )
686 692 coreconfigitem('logtoprocess', 'commandfinish',
687 693 default=None,
688 694 )
689 695 coreconfigitem('logtoprocess', 'command',
690 696 default=None,
691 697 )
692 698 coreconfigitem('logtoprocess', 'develwarn',
693 699 default=None,
694 700 )
695 701 coreconfigitem('logtoprocess', 'uiblocked',
696 702 default=None,
697 703 )
698 704 coreconfigitem('merge', 'checkunknown',
699 705 default='abort',
700 706 )
701 707 coreconfigitem('merge', 'checkignored',
702 708 default='abort',
703 709 )
704 710 coreconfigitem('experimental', 'merge.checkpathconflicts',
705 711 default=False,
706 712 )
707 713 coreconfigitem('merge', 'followcopies',
708 714 default=True,
709 715 )
710 716 coreconfigitem('merge', 'on-failure',
711 717 default='continue',
712 718 )
713 719 coreconfigitem('merge', 'preferancestor',
714 720 default=lambda: ['*'],
715 721 )
716 722 coreconfigitem('merge-tools', '.*',
717 723 default=None,
718 724 generic=True,
719 725 )
720 726 coreconfigitem('merge-tools', br'.*\.args$',
721 727 default="$local $base $other",
722 728 generic=True,
723 729 priority=-1,
724 730 )
725 731 coreconfigitem('merge-tools', br'.*\.binary$',
726 732 default=False,
727 733 generic=True,
728 734 priority=-1,
729 735 )
730 736 coreconfigitem('merge-tools', br'.*\.check$',
731 737 default=list,
732 738 generic=True,
733 739 priority=-1,
734 740 )
735 741 coreconfigitem('merge-tools', br'.*\.checkchanged$',
736 742 default=False,
737 743 generic=True,
738 744 priority=-1,
739 745 )
740 746 coreconfigitem('merge-tools', br'.*\.executable$',
741 747 default=dynamicdefault,
742 748 generic=True,
743 749 priority=-1,
744 750 )
745 751 coreconfigitem('merge-tools', br'.*\.fixeol$',
746 752 default=False,
747 753 generic=True,
748 754 priority=-1,
749 755 )
750 756 coreconfigitem('merge-tools', br'.*\.gui$',
751 757 default=False,
752 758 generic=True,
753 759 priority=-1,
754 760 )
755 761 coreconfigitem('merge-tools', br'.*\.mergemarkers$',
756 762 default='basic',
757 763 generic=True,
758 764 priority=-1,
759 765 )
760 766 coreconfigitem('merge-tools', br'.*\.mergemarkertemplate$',
761 767 default=dynamicdefault, # take from ui.mergemarkertemplate
762 768 generic=True,
763 769 priority=-1,
764 770 )
765 771 coreconfigitem('merge-tools', br'.*\.priority$',
766 772 default=0,
767 773 generic=True,
768 774 priority=-1,
769 775 )
770 776 coreconfigitem('merge-tools', br'.*\.premerge$',
771 777 default=dynamicdefault,
772 778 generic=True,
773 779 priority=-1,
774 780 )
775 781 coreconfigitem('merge-tools', br'.*\.symlink$',
776 782 default=False,
777 783 generic=True,
778 784 priority=-1,
779 785 )
780 786 coreconfigitem('pager', 'attend-.*',
781 787 default=dynamicdefault,
782 788 generic=True,
783 789 )
784 790 coreconfigitem('pager', 'ignore',
785 791 default=list,
786 792 )
787 793 coreconfigitem('pager', 'pager',
788 794 default=dynamicdefault,
789 795 )
790 796 coreconfigitem('patch', 'eol',
791 797 default='strict',
792 798 )
793 799 coreconfigitem('patch', 'fuzz',
794 800 default=2,
795 801 )
796 802 coreconfigitem('paths', 'default',
797 803 default=None,
798 804 )
799 805 coreconfigitem('paths', 'default-push',
800 806 default=None,
801 807 )
802 808 coreconfigitem('paths', '.*',
803 809 default=None,
804 810 generic=True,
805 811 )
806 812 coreconfigitem('phases', 'checksubrepos',
807 813 default='follow',
808 814 )
809 815 coreconfigitem('phases', 'new-commit',
810 816 default='draft',
811 817 )
812 818 coreconfigitem('phases', 'publish',
813 819 default=True,
814 820 )
815 821 coreconfigitem('profiling', 'enabled',
816 822 default=False,
817 823 )
818 824 coreconfigitem('profiling', 'format',
819 825 default='text',
820 826 )
821 827 coreconfigitem('profiling', 'freq',
822 828 default=1000,
823 829 )
824 830 coreconfigitem('profiling', 'limit',
825 831 default=30,
826 832 )
827 833 coreconfigitem('profiling', 'nested',
828 834 default=0,
829 835 )
830 836 coreconfigitem('profiling', 'output',
831 837 default=None,
832 838 )
833 839 coreconfigitem('profiling', 'showmax',
834 840 default=0.999,
835 841 )
836 842 coreconfigitem('profiling', 'showmin',
837 843 default=dynamicdefault,
838 844 )
839 845 coreconfigitem('profiling', 'sort',
840 846 default='inlinetime',
841 847 )
842 848 coreconfigitem('profiling', 'statformat',
843 849 default='hotpath',
844 850 )
845 851 coreconfigitem('profiling', 'type',
846 852 default='stat',
847 853 )
848 854 coreconfigitem('progress', 'assume-tty',
849 855 default=False,
850 856 )
851 857 coreconfigitem('progress', 'changedelay',
852 858 default=1,
853 859 )
854 860 coreconfigitem('progress', 'clear-complete',
855 861 default=True,
856 862 )
857 863 coreconfigitem('progress', 'debug',
858 864 default=False,
859 865 )
860 866 coreconfigitem('progress', 'delay',
861 867 default=3,
862 868 )
863 869 coreconfigitem('progress', 'disable',
864 870 default=False,
865 871 )
866 872 coreconfigitem('progress', 'estimateinterval',
867 873 default=60.0,
868 874 )
869 875 coreconfigitem('progress', 'format',
870 876 default=lambda: ['topic', 'bar', 'number', 'estimate'],
871 877 )
872 878 coreconfigitem('progress', 'refresh',
873 879 default=0.1,
874 880 )
875 881 coreconfigitem('progress', 'width',
876 882 default=dynamicdefault,
877 883 )
878 884 coreconfigitem('push', 'pushvars.server',
879 885 default=False,
880 886 )
881 887 coreconfigitem('server', 'bookmarks-pushkey-compat',
882 888 default=True,
883 889 )
884 890 coreconfigitem('server', 'bundle1',
885 891 default=True,
886 892 )
887 893 coreconfigitem('server', 'bundle1gd',
888 894 default=None,
889 895 )
890 896 coreconfigitem('server', 'bundle1.pull',
891 897 default=None,
892 898 )
893 899 coreconfigitem('server', 'bundle1gd.pull',
894 900 default=None,
895 901 )
896 902 coreconfigitem('server', 'bundle1.push',
897 903 default=None,
898 904 )
899 905 coreconfigitem('server', 'bundle1gd.push',
900 906 default=None,
901 907 )
902 908 coreconfigitem('server', 'compressionengines',
903 909 default=list,
904 910 )
905 911 coreconfigitem('server', 'concurrent-push-mode',
906 912 default='strict',
907 913 )
908 914 coreconfigitem('server', 'disablefullbundle',
909 915 default=False,
910 916 )
911 917 coreconfigitem('server', 'maxhttpheaderlen',
912 918 default=1024,
913 919 )
914 920 coreconfigitem('server', 'preferuncompressed',
915 921 default=False,
916 922 )
917 923 coreconfigitem('server', 'uncompressed',
918 924 default=True,
919 925 )
920 926 coreconfigitem('server', 'uncompressedallowsecret',
921 927 default=False,
922 928 )
923 929 coreconfigitem('server', 'validate',
924 930 default=False,
925 931 )
926 932 coreconfigitem('server', 'zliblevel',
927 933 default=-1,
928 934 )
929 935 coreconfigitem('share', 'pool',
930 936 default=None,
931 937 )
932 938 coreconfigitem('share', 'poolnaming',
933 939 default='identity',
934 940 )
935 941 coreconfigitem('smtp', 'host',
936 942 default=None,
937 943 )
938 944 coreconfigitem('smtp', 'local_hostname',
939 945 default=None,
940 946 )
941 947 coreconfigitem('smtp', 'password',
942 948 default=None,
943 949 )
944 950 coreconfigitem('smtp', 'port',
945 951 default=dynamicdefault,
946 952 )
947 953 coreconfigitem('smtp', 'tls',
948 954 default='none',
949 955 )
950 956 coreconfigitem('smtp', 'username',
951 957 default=None,
952 958 )
953 959 coreconfigitem('sparse', 'missingwarning',
954 960 default=True,
955 961 )
956 962 coreconfigitem('subrepos', 'allowed',
957 963 default=dynamicdefault, # to make backporting simpler
958 964 )
959 965 coreconfigitem('subrepos', 'hg:allowed',
960 966 default=dynamicdefault,
961 967 )
962 968 coreconfigitem('subrepos', 'git:allowed',
963 969 default=dynamicdefault,
964 970 )
965 971 coreconfigitem('subrepos', 'svn:allowed',
966 972 default=dynamicdefault,
967 973 )
968 974 coreconfigitem('templates', '.*',
969 975 default=None,
970 976 generic=True,
971 977 )
972 978 coreconfigitem('trusted', 'groups',
973 979 default=list,
974 980 )
975 981 coreconfigitem('trusted', 'users',
976 982 default=list,
977 983 )
978 984 coreconfigitem('ui', '_usedassubrepo',
979 985 default=False,
980 986 )
981 987 coreconfigitem('ui', 'allowemptycommit',
982 988 default=False,
983 989 )
984 990 coreconfigitem('ui', 'archivemeta',
985 991 default=True,
986 992 )
987 993 coreconfigitem('ui', 'askusername',
988 994 default=False,
989 995 )
990 996 coreconfigitem('ui', 'clonebundlefallback',
991 997 default=False,
992 998 )
993 999 coreconfigitem('ui', 'clonebundleprefers',
994 1000 default=list,
995 1001 )
996 1002 coreconfigitem('ui', 'clonebundles',
997 1003 default=True,
998 1004 )
999 1005 coreconfigitem('ui', 'color',
1000 1006 default='auto',
1001 1007 )
1002 1008 coreconfigitem('ui', 'commitsubrepos',
1003 1009 default=False,
1004 1010 )
1005 1011 coreconfigitem('ui', 'debug',
1006 1012 default=False,
1007 1013 )
1008 1014 coreconfigitem('ui', 'debugger',
1009 1015 default=None,
1010 1016 )
1011 1017 coreconfigitem('ui', 'editor',
1012 1018 default=dynamicdefault,
1013 1019 )
1014 1020 coreconfigitem('ui', 'fallbackencoding',
1015 1021 default=None,
1016 1022 )
1017 1023 coreconfigitem('ui', 'forcecwd',
1018 1024 default=None,
1019 1025 )
1020 1026 coreconfigitem('ui', 'forcemerge',
1021 1027 default=None,
1022 1028 )
1023 1029 coreconfigitem('ui', 'formatdebug',
1024 1030 default=False,
1025 1031 )
1026 1032 coreconfigitem('ui', 'formatjson',
1027 1033 default=False,
1028 1034 )
1029 1035 coreconfigitem('ui', 'formatted',
1030 1036 default=None,
1031 1037 )
1032 1038 coreconfigitem('ui', 'graphnodetemplate',
1033 1039 default=None,
1034 1040 )
1035 1041 coreconfigitem('ui', 'interactive',
1036 1042 default=None,
1037 1043 )
1038 1044 coreconfigitem('ui', 'interface',
1039 1045 default=None,
1040 1046 )
1041 1047 coreconfigitem('ui', 'interface.chunkselector',
1042 1048 default=None,
1043 1049 )
1044 1050 coreconfigitem('ui', 'logblockedtimes',
1045 1051 default=False,
1046 1052 )
1047 1053 coreconfigitem('ui', 'logtemplate',
1048 1054 default=None,
1049 1055 )
1050 1056 coreconfigitem('ui', 'merge',
1051 1057 default=None,
1052 1058 )
1053 1059 coreconfigitem('ui', 'mergemarkers',
1054 1060 default='basic',
1055 1061 )
1056 1062 coreconfigitem('ui', 'mergemarkertemplate',
1057 1063 default=('{node|short} '
1058 1064 '{ifeq(tags, "tip", "", '
1059 1065 'ifeq(tags, "", "", "{tags} "))}'
1060 1066 '{if(bookmarks, "{bookmarks} ")}'
1061 1067 '{ifeq(branch, "default", "", "{branch} ")}'
1062 1068 '- {author|user}: {desc|firstline}')
1063 1069 )
1064 1070 coreconfigitem('ui', 'nontty',
1065 1071 default=False,
1066 1072 )
1067 1073 coreconfigitem('ui', 'origbackuppath',
1068 1074 default=None,
1069 1075 )
1070 1076 coreconfigitem('ui', 'paginate',
1071 1077 default=True,
1072 1078 )
1073 1079 coreconfigitem('ui', 'patch',
1074 1080 default=None,
1075 1081 )
1076 1082 coreconfigitem('ui', 'portablefilenames',
1077 1083 default='warn',
1078 1084 )
1079 1085 coreconfigitem('ui', 'promptecho',
1080 1086 default=False,
1081 1087 )
1082 1088 coreconfigitem('ui', 'quiet',
1083 1089 default=False,
1084 1090 )
1085 1091 coreconfigitem('ui', 'quietbookmarkmove',
1086 1092 default=False,
1087 1093 )
1088 1094 coreconfigitem('ui', 'remotecmd',
1089 1095 default='hg',
1090 1096 )
1091 1097 coreconfigitem('ui', 'report_untrusted',
1092 1098 default=True,
1093 1099 )
1094 1100 coreconfigitem('ui', 'rollback',
1095 1101 default=True,
1096 1102 )
1097 1103 coreconfigitem('ui', 'slash',
1098 1104 default=False,
1099 1105 )
1100 1106 coreconfigitem('ui', 'ssh',
1101 1107 default='ssh',
1102 1108 )
1103 1109 coreconfigitem('ui', 'ssherrorhint',
1104 1110 default=None,
1105 1111 )
1106 1112 coreconfigitem('ui', 'statuscopies',
1107 1113 default=False,
1108 1114 )
1109 1115 coreconfigitem('ui', 'strict',
1110 1116 default=False,
1111 1117 )
1112 1118 coreconfigitem('ui', 'style',
1113 1119 default='',
1114 1120 )
1115 1121 coreconfigitem('ui', 'supportcontact',
1116 1122 default=None,
1117 1123 )
1118 1124 coreconfigitem('ui', 'textwidth',
1119 1125 default=78,
1120 1126 )
1121 1127 coreconfigitem('ui', 'timeout',
1122 1128 default='600',
1123 1129 )
1124 1130 coreconfigitem('ui', 'timeout.warn',
1125 1131 default=0,
1126 1132 )
1127 1133 coreconfigitem('ui', 'traceback',
1128 1134 default=False,
1129 1135 )
1130 1136 coreconfigitem('ui', 'tweakdefaults',
1131 1137 default=False,
1132 1138 )
1133 1139 coreconfigitem('ui', 'username',
1134 1140 alias=[('ui', 'user')]
1135 1141 )
1136 1142 coreconfigitem('ui', 'verbose',
1137 1143 default=False,
1138 1144 )
1139 1145 coreconfigitem('verify', 'skipflags',
1140 1146 default=None,
1141 1147 )
1142 1148 coreconfigitem('web', 'allowbz2',
1143 1149 default=False,
1144 1150 )
1145 1151 coreconfigitem('web', 'allowgz',
1146 1152 default=False,
1147 1153 )
1148 1154 coreconfigitem('web', 'allow-pull',
1149 1155 alias=[('web', 'allowpull')],
1150 1156 default=True,
1151 1157 )
1152 1158 coreconfigitem('web', 'allow-push',
1153 1159 alias=[('web', 'allow_push')],
1154 1160 default=list,
1155 1161 )
1156 1162 coreconfigitem('web', 'allowzip',
1157 1163 default=False,
1158 1164 )
1159 1165 coreconfigitem('web', 'archivesubrepos',
1160 1166 default=False,
1161 1167 )
1162 1168 coreconfigitem('web', 'cache',
1163 1169 default=True,
1164 1170 )
1165 1171 coreconfigitem('web', 'contact',
1166 1172 default=None,
1167 1173 )
1168 1174 coreconfigitem('web', 'deny_push',
1169 1175 default=list,
1170 1176 )
1171 1177 coreconfigitem('web', 'guessmime',
1172 1178 default=False,
1173 1179 )
1174 1180 coreconfigitem('web', 'hidden',
1175 1181 default=False,
1176 1182 )
1177 1183 coreconfigitem('web', 'labels',
1178 1184 default=list,
1179 1185 )
1180 1186 coreconfigitem('web', 'logoimg',
1181 1187 default='hglogo.png',
1182 1188 )
1183 1189 coreconfigitem('web', 'logourl',
1184 1190 default='https://mercurial-scm.org/',
1185 1191 )
1186 1192 coreconfigitem('web', 'accesslog',
1187 1193 default='-',
1188 1194 )
1189 1195 coreconfigitem('web', 'address',
1190 1196 default='',
1191 1197 )
1192 1198 coreconfigitem('web', 'allow_archive',
1193 1199 default=list,
1194 1200 )
1195 1201 coreconfigitem('web', 'allow_read',
1196 1202 default=list,
1197 1203 )
1198 1204 coreconfigitem('web', 'baseurl',
1199 1205 default=None,
1200 1206 )
1201 1207 coreconfigitem('web', 'cacerts',
1202 1208 default=None,
1203 1209 )
1204 1210 coreconfigitem('web', 'certificate',
1205 1211 default=None,
1206 1212 )
1207 1213 coreconfigitem('web', 'collapse',
1208 1214 default=False,
1209 1215 )
1210 1216 coreconfigitem('web', 'csp',
1211 1217 default=None,
1212 1218 )
1213 1219 coreconfigitem('web', 'deny_read',
1214 1220 default=list,
1215 1221 )
1216 1222 coreconfigitem('web', 'descend',
1217 1223 default=True,
1218 1224 )
1219 1225 coreconfigitem('web', 'description',
1220 1226 default="",
1221 1227 )
1222 1228 coreconfigitem('web', 'encoding',
1223 1229 default=lambda: encoding.encoding,
1224 1230 )
1225 1231 coreconfigitem('web', 'errorlog',
1226 1232 default='-',
1227 1233 )
1228 1234 coreconfigitem('web', 'ipv6',
1229 1235 default=False,
1230 1236 )
1231 1237 coreconfigitem('web', 'maxchanges',
1232 1238 default=10,
1233 1239 )
1234 1240 coreconfigitem('web', 'maxfiles',
1235 1241 default=10,
1236 1242 )
1237 1243 coreconfigitem('web', 'maxshortchanges',
1238 1244 default=60,
1239 1245 )
1240 1246 coreconfigitem('web', 'motd',
1241 1247 default='',
1242 1248 )
1243 1249 coreconfigitem('web', 'name',
1244 1250 default=dynamicdefault,
1245 1251 )
1246 1252 coreconfigitem('web', 'port',
1247 1253 default=8000,
1248 1254 )
1249 1255 coreconfigitem('web', 'prefix',
1250 1256 default='',
1251 1257 )
1252 1258 coreconfigitem('web', 'push_ssl',
1253 1259 default=True,
1254 1260 )
1255 1261 coreconfigitem('web', 'refreshinterval',
1256 1262 default=20,
1257 1263 )
1258 1264 coreconfigitem('web', 'server-header',
1259 1265 default=None,
1260 1266 )
1261 1267 coreconfigitem('web', 'staticurl',
1262 1268 default=None,
1263 1269 )
1264 1270 coreconfigitem('web', 'stripes',
1265 1271 default=1,
1266 1272 )
1267 1273 coreconfigitem('web', 'style',
1268 1274 default='paper',
1269 1275 )
1270 1276 coreconfigitem('web', 'templates',
1271 1277 default=None,
1272 1278 )
1273 1279 coreconfigitem('web', 'view',
1274 1280 default='served',
1275 1281 )
1276 1282 coreconfigitem('worker', 'backgroundclose',
1277 1283 default=dynamicdefault,
1278 1284 )
1279 1285 # Windows defaults to a limit of 512 open files. A buffer of 128
1280 1286 # should give us enough headway.
1281 1287 coreconfigitem('worker', 'backgroundclosemaxqueue',
1282 1288 default=384,
1283 1289 )
1284 1290 coreconfigitem('worker', 'backgroundcloseminfilecount',
1285 1291 default=2048,
1286 1292 )
1287 1293 coreconfigitem('worker', 'backgroundclosethreadcount',
1288 1294 default=4,
1289 1295 )
1290 1296 coreconfigitem('worker', 'enabled',
1291 1297 default=True,
1292 1298 )
1293 1299 coreconfigitem('worker', 'numcpus',
1294 1300 default=None,
1295 1301 )
1296 1302
1297 1303 # Rebase related configuration moved to core because other extension are doing
1298 1304 # strange things. For example, shelve import the extensions to reuse some bit
1299 1305 # without formally loading it.
1300 1306 coreconfigitem('commands', 'rebase.requiredest',
1301 1307 default=False,
1302 1308 )
1303 1309 coreconfigitem('experimental', 'rebaseskipobsolete',
1304 1310 default=True,
1305 1311 )
1306 1312 coreconfigitem('rebase', 'singletransaction',
1307 1313 default=False,
1308 1314 )
1309 1315 coreconfigitem('rebase', 'experimental.inmemory',
1310 1316 default=False,
1311 1317 )
@@ -1,454 +1,461 b''
1 1 # hgweb/hgweb_mod.py - Web interface for a repository.
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 contextlib
12 12 import os
13 13
14 14 from .common import (
15 15 ErrorResponse,
16 16 HTTP_BAD_REQUEST,
17 17 cspvalues,
18 18 permhooks,
19 19 statusmessage,
20 20 )
21 21
22 22 from .. import (
23 23 encoding,
24 24 error,
25 25 formatter,
26 26 hg,
27 27 hook,
28 28 profiling,
29 29 pycompat,
30 30 repoview,
31 31 templatefilters,
32 32 templater,
33 33 ui as uimod,
34 34 util,
35 35 wireprotoserver,
36 36 )
37 37
38 38 from . import (
39 39 request as requestmod,
40 40 webcommands,
41 41 webutil,
42 42 wsgicgi,
43 43 )
44 44
45 45 archivespecs = util.sortdict((
46 46 ('zip', ('application/zip', 'zip', '.zip', None)),
47 47 ('gz', ('application/x-gzip', 'tgz', '.tar.gz', None)),
48 48 ('bz2', ('application/x-bzip2', 'tbz2', '.tar.bz2', None)),
49 49 ))
50 50
51 51 def getstyle(req, configfn, templatepath):
52 52 styles = (
53 53 req.qsparams.get('style', None),
54 54 configfn('web', 'style'),
55 55 'paper',
56 56 )
57 57 return styles, templater.stylemap(styles, templatepath)
58 58
59 59 def makebreadcrumb(url, prefix=''):
60 60 '''Return a 'URL breadcrumb' list
61 61
62 62 A 'URL breadcrumb' is a list of URL-name pairs,
63 63 corresponding to each of the path items on a URL.
64 64 This can be used to create path navigation entries.
65 65 '''
66 66 if url.endswith('/'):
67 67 url = url[:-1]
68 68 if prefix:
69 69 url = '/' + prefix + url
70 70 relpath = url
71 71 if relpath.startswith('/'):
72 72 relpath = relpath[1:]
73 73
74 74 breadcrumb = []
75 75 urlel = url
76 76 pathitems = [''] + relpath.split('/')
77 77 for pathel in reversed(pathitems):
78 78 if not pathel or not urlel:
79 79 break
80 80 breadcrumb.append({'url': urlel, 'name': pathel})
81 81 urlel = os.path.dirname(urlel)
82 82 return reversed(breadcrumb)
83 83
84 84 class requestcontext(object):
85 85 """Holds state/context for an individual request.
86 86
87 87 Servers can be multi-threaded. Holding state on the WSGI application
88 88 is prone to race conditions. Instances of this class exist to hold
89 89 mutable and race-free state for requests.
90 90 """
91 91 def __init__(self, app, repo, req, res):
92 92 self.repo = repo
93 93 self.reponame = app.reponame
94 94 self.req = req
95 95 self.res = res
96 96
97 97 self.archivespecs = archivespecs
98 98
99 99 self.maxchanges = self.configint('web', 'maxchanges')
100 100 self.stripecount = self.configint('web', 'stripes')
101 101 self.maxshortchanges = self.configint('web', 'maxshortchanges')
102 102 self.maxfiles = self.configint('web', 'maxfiles')
103 103 self.allowpull = self.configbool('web', 'allow-pull')
104 104
105 105 # we use untrusted=False to prevent a repo owner from using
106 106 # web.templates in .hg/hgrc to get access to any file readable
107 107 # by the user running the CGI script
108 108 self.templatepath = self.config('web', 'templates', untrusted=False)
109 109
110 110 # This object is more expensive to build than simple config values.
111 111 # It is shared across requests. The app will replace the object
112 112 # if it is updated. Since this is a reference and nothing should
113 113 # modify the underlying object, it should be constant for the lifetime
114 114 # of the request.
115 115 self.websubtable = app.websubtable
116 116
117 117 self.csp, self.nonce = cspvalues(self.repo.ui)
118 118
119 119 # Trust the settings from the .hg/hgrc files by default.
120 120 def config(self, section, name, default=uimod._unset, untrusted=True):
121 121 return self.repo.ui.config(section, name, default,
122 122 untrusted=untrusted)
123 123
124 124 def configbool(self, section, name, default=uimod._unset, untrusted=True):
125 125 return self.repo.ui.configbool(section, name, default,
126 126 untrusted=untrusted)
127 127
128 128 def configint(self, section, name, default=uimod._unset, untrusted=True):
129 129 return self.repo.ui.configint(section, name, default,
130 130 untrusted=untrusted)
131 131
132 132 def configlist(self, section, name, default=uimod._unset, untrusted=True):
133 133 return self.repo.ui.configlist(section, name, default,
134 134 untrusted=untrusted)
135 135
136 136 def archivelist(self, nodeid):
137 137 allowed = self.configlist('web', 'allow_archive')
138 138 for typ, spec in self.archivespecs.iteritems():
139 139 if typ in allowed or self.configbool('web', 'allow%s' % typ):
140 140 yield {'type': typ, 'extension': spec[2], 'node': nodeid}
141 141
142 142 def templater(self, req):
143 143 # determine scheme, port and server name
144 144 # this is needed to create absolute urls
145 145 logourl = self.config('web', 'logourl')
146 146 logoimg = self.config('web', 'logoimg')
147 147 staticurl = (self.config('web', 'staticurl')
148 148 or req.apppath + '/static/')
149 149 if not staticurl.endswith('/'):
150 150 staticurl += '/'
151 151
152 152 # some functions for the templater
153 153
154 154 def motd(**map):
155 155 yield self.config('web', 'motd')
156 156
157 157 # figure out which style to use
158 158
159 159 vars = {}
160 160 styles, (style, mapfile) = getstyle(req, self.config,
161 161 self.templatepath)
162 162 if style == styles[0]:
163 163 vars['style'] = style
164 164
165 165 sessionvars = webutil.sessionvars(vars, '?')
166 166
167 167 if not self.reponame:
168 168 self.reponame = (self.config('web', 'name', '')
169 169 or req.reponame
170 170 or req.apppath
171 171 or self.repo.root)
172 172
173 173 def websubfilter(text):
174 174 return templatefilters.websub(text, self.websubtable)
175 175
176 176 # create the templater
177 177 # TODO: export all keywords: defaults = templatekw.keywords.copy()
178 178 defaults = {
179 179 'url': req.apppath + '/',
180 180 'logourl': logourl,
181 181 'logoimg': logoimg,
182 182 'staticurl': staticurl,
183 183 'urlbase': req.advertisedbaseurl,
184 184 'repo': self.reponame,
185 185 'encoding': encoding.encoding,
186 186 'motd': motd,
187 187 'sessionvars': sessionvars,
188 188 'pathdef': makebreadcrumb(req.apppath),
189 189 'style': style,
190 190 'nonce': self.nonce,
191 191 }
192 192 tres = formatter.templateresources(self.repo.ui, self.repo)
193 193 tmpl = templater.templater.frommapfile(mapfile,
194 194 filters={'websub': websubfilter},
195 195 defaults=defaults,
196 196 resources=tres)
197 197 return tmpl
198 198
199 199 def sendtemplate(self, name, **kwargs):
200 200 """Helper function to send a response generated from a template."""
201 201 kwargs = pycompat.byteskwargs(kwargs)
202 202 self.res.setbodygen(self.tmpl.generate(name, kwargs))
203 203 return self.res.sendresponse()
204 204
205 205 class hgweb(object):
206 206 """HTTP server for individual repositories.
207 207
208 208 Instances of this class serve HTTP responses for a particular
209 209 repository.
210 210
211 211 Instances are typically used as WSGI applications.
212 212
213 213 Some servers are multi-threaded. On these servers, there may
214 214 be multiple active threads inside __call__.
215 215 """
216 216 def __init__(self, repo, name=None, baseui=None):
217 217 if isinstance(repo, str):
218 218 if baseui:
219 219 u = baseui.copy()
220 220 else:
221 221 u = uimod.ui.load()
222 222 r = hg.repository(u, repo)
223 223 else:
224 224 # we trust caller to give us a private copy
225 225 r = repo
226 226
227 227 r.ui.setconfig('ui', 'report_untrusted', 'off', 'hgweb')
228 228 r.baseui.setconfig('ui', 'report_untrusted', 'off', 'hgweb')
229 229 r.ui.setconfig('ui', 'nontty', 'true', 'hgweb')
230 230 r.baseui.setconfig('ui', 'nontty', 'true', 'hgweb')
231 231 # resolve file patterns relative to repo root
232 232 r.ui.setconfig('ui', 'forcecwd', r.root, 'hgweb')
233 233 r.baseui.setconfig('ui', 'forcecwd', r.root, 'hgweb')
234 234 # displaying bundling progress bar while serving feel wrong and may
235 235 # break some wsgi implementation.
236 236 r.ui.setconfig('progress', 'disable', 'true', 'hgweb')
237 237 r.baseui.setconfig('progress', 'disable', 'true', 'hgweb')
238 238 self._repos = [hg.cachedlocalrepo(self._webifyrepo(r))]
239 239 self._lastrepo = self._repos[0]
240 240 hook.redirect(True)
241 241 self.reponame = name
242 242
243 243 def _webifyrepo(self, repo):
244 244 repo = getwebview(repo)
245 245 self.websubtable = webutil.getwebsubs(repo)
246 246 return repo
247 247
248 248 @contextlib.contextmanager
249 249 def _obtainrepo(self):
250 250 """Obtain a repo unique to the caller.
251 251
252 252 Internally we maintain a stack of cachedlocalrepo instances
253 253 to be handed out. If one is available, we pop it and return it,
254 254 ensuring it is up to date in the process. If one is not available,
255 255 we clone the most recently used repo instance and return it.
256 256
257 257 It is currently possible for the stack to grow without bounds
258 258 if the server allows infinite threads. However, servers should
259 259 have a thread limit, thus establishing our limit.
260 260 """
261 261 if self._repos:
262 262 cached = self._repos.pop()
263 263 r, created = cached.fetch()
264 264 else:
265 265 cached = self._lastrepo.copy()
266 266 r, created = cached.fetch()
267 267 if created:
268 268 r = self._webifyrepo(r)
269 269
270 270 self._lastrepo = cached
271 271 self.mtime = cached.mtime
272 272 try:
273 273 yield r
274 274 finally:
275 275 self._repos.append(cached)
276 276
277 277 def run(self):
278 278 """Start a server from CGI environment.
279 279
280 280 Modern servers should be using WSGI and should avoid this
281 281 method, if possible.
282 282 """
283 283 if not encoding.environ.get('GATEWAY_INTERFACE',
284 284 '').startswith("CGI/1."):
285 285 raise RuntimeError("This function is only intended to be "
286 286 "called while running as a CGI script.")
287 287 wsgicgi.launch(self)
288 288
289 289 def __call__(self, env, respond):
290 290 """Run the WSGI application.
291 291
292 292 This may be called by multiple threads.
293 293 """
294 294 req = requestmod.parserequestfromenv(env)
295 295 res = requestmod.wsgiresponse(req, respond)
296 296
297 297 return self.run_wsgi(req, res)
298 298
299 299 def run_wsgi(self, req, res):
300 300 """Internal method to run the WSGI application.
301 301
302 302 This is typically only called by Mercurial. External consumers
303 303 should be using instances of this class as the WSGI application.
304 304 """
305 305 with self._obtainrepo() as repo:
306 306 profile = repo.ui.configbool('profiling', 'enabled')
307 307 with profiling.profile(repo.ui, enabled=profile):
308 308 for r in self._runwsgi(req, res, repo):
309 309 yield r
310 310
311 311 def _runwsgi(self, req, res, repo):
312 312 rctx = requestcontext(self, repo, req, res)
313 313
314 314 # This state is global across all threads.
315 315 encoding.encoding = rctx.config('web', 'encoding')
316 316 rctx.repo.ui.environ = req.rawenv
317 317
318 318 if rctx.csp:
319 319 # hgwebdir may have added CSP header. Since we generate our own,
320 320 # replace it.
321 321 res.headers['Content-Security-Policy'] = rctx.csp
322 322
323 # /api/* is reserved for various API implementations. Dispatch
324 # accordingly.
325 if req.dispatchparts and req.dispatchparts[0] == b'api':
326 wireprotoserver.handlewsgiapirequest(rctx, req, res,
327 self.check_perm)
328 return res.sendresponse()
329
323 330 handled = wireprotoserver.handlewsgirequest(
324 331 rctx, req, res, self.check_perm)
325 332 if handled:
326 333 return res.sendresponse()
327 334
328 335 # Old implementations of hgweb supported dispatching the request via
329 336 # the initial query string parameter instead of using PATH_INFO.
330 337 # If PATH_INFO is present (signaled by ``req.dispatchpath`` having
331 338 # a value), we use it. Otherwise fall back to the query string.
332 339 if req.dispatchpath is not None:
333 340 query = req.dispatchpath
334 341 else:
335 342 query = req.querystring.partition('&')[0].partition(';')[0]
336 343
337 344 # translate user-visible url structure to internal structure
338 345
339 346 args = query.split('/', 2)
340 347 if 'cmd' not in req.qsparams and args and args[0]:
341 348 cmd = args.pop(0)
342 349 style = cmd.rfind('-')
343 350 if style != -1:
344 351 req.qsparams['style'] = cmd[:style]
345 352 cmd = cmd[style + 1:]
346 353
347 354 # avoid accepting e.g. style parameter as command
348 355 if util.safehasattr(webcommands, cmd):
349 356 req.qsparams['cmd'] = cmd
350 357
351 358 if cmd == 'static':
352 359 req.qsparams['file'] = '/'.join(args)
353 360 else:
354 361 if args and args[0]:
355 362 node = args.pop(0).replace('%2F', '/')
356 363 req.qsparams['node'] = node
357 364 if args:
358 365 if 'file' in req.qsparams:
359 366 del req.qsparams['file']
360 367 for a in args:
361 368 req.qsparams.add('file', a)
362 369
363 370 ua = req.headers.get('User-Agent', '')
364 371 if cmd == 'rev' and 'mercurial' in ua:
365 372 req.qsparams['style'] = 'raw'
366 373
367 374 if cmd == 'archive':
368 375 fn = req.qsparams['node']
369 376 for type_, spec in rctx.archivespecs.iteritems():
370 377 ext = spec[2]
371 378 if fn.endswith(ext):
372 379 req.qsparams['node'] = fn[:-len(ext)]
373 380 req.qsparams['type'] = type_
374 381 else:
375 382 cmd = req.qsparams.get('cmd', '')
376 383
377 384 # process the web interface request
378 385
379 386 try:
380 387 rctx.tmpl = rctx.templater(req)
381 388 ctype = rctx.tmpl.render('mimetype',
382 389 {'encoding': encoding.encoding})
383 390
384 391 # check read permissions non-static content
385 392 if cmd != 'static':
386 393 self.check_perm(rctx, req, None)
387 394
388 395 if cmd == '':
389 396 req.qsparams['cmd'] = rctx.tmpl.render('default', {})
390 397 cmd = req.qsparams['cmd']
391 398
392 399 # Don't enable caching if using a CSP nonce because then it wouldn't
393 400 # be a nonce.
394 401 if rctx.configbool('web', 'cache') and not rctx.nonce:
395 402 tag = 'W/"%d"' % self.mtime
396 403 if req.headers.get('If-None-Match') == tag:
397 404 res.status = '304 Not Modified'
398 405 # Response body not allowed on 304.
399 406 res.setbodybytes('')
400 407 return res.sendresponse()
401 408
402 409 res.headers['ETag'] = tag
403 410
404 411 if cmd not in webcommands.__all__:
405 412 msg = 'no such method: %s' % cmd
406 413 raise ErrorResponse(HTTP_BAD_REQUEST, msg)
407 414 else:
408 415 # Set some globals appropriate for web handlers. Commands can
409 416 # override easily enough.
410 417 res.status = '200 Script output follows'
411 418 res.headers['Content-Type'] = ctype
412 419 return getattr(webcommands, cmd)(rctx)
413 420
414 421 except (error.LookupError, error.RepoLookupError) as err:
415 422 msg = pycompat.bytestr(err)
416 423 if (util.safehasattr(err, 'name') and
417 424 not isinstance(err, error.ManifestLookupError)):
418 425 msg = 'revision not found: %s' % err.name
419 426
420 427 res.status = '404 Not Found'
421 428 res.headers['Content-Type'] = ctype
422 429 return rctx.sendtemplate('error', error=msg)
423 430 except (error.RepoError, error.RevlogError) as e:
424 431 res.status = '500 Internal Server Error'
425 432 res.headers['Content-Type'] = ctype
426 433 return rctx.sendtemplate('error', error=pycompat.bytestr(e))
427 434 except ErrorResponse as e:
428 435 res.status = statusmessage(e.code, pycompat.bytestr(e))
429 436 res.headers['Content-Type'] = ctype
430 437 return rctx.sendtemplate('error', error=pycompat.bytestr(e))
431 438
432 439 def check_perm(self, rctx, req, op):
433 440 for permhook in permhooks:
434 441 permhook(rctx, req, op)
435 442
436 443 def getwebview(repo):
437 444 """The 'web.view' config controls changeset filter to hgweb. Possible
438 445 values are ``served``, ``visible`` and ``all``. Default is ``served``.
439 446 The ``served`` filter only shows changesets that can be pulled from the
440 447 hgweb instance. The``visible`` filter includes secret changesets but
441 448 still excludes "hidden" one.
442 449
443 450 See the repoview module for details.
444 451
445 452 The option has been around undocumented since Mercurial 2.5, but no
446 453 user ever asked about it. So we better keep it undocumented for now."""
447 454 # experimental config: web.view
448 455 viewconfig = repo.ui.config('web', 'view', untrusted=True)
449 456 if viewconfig == 'all':
450 457 return repo.unfiltered()
451 458 elif viewconfig in repoview.filtertable:
452 459 return repo.filtered(viewconfig)
453 460 else:
454 461 return repo.filtered('served')
@@ -1,653 +1,723 b''
1 1 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
2 2 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 3 #
4 4 # This software may be used and distributed according to the terms of the
5 5 # GNU General Public License version 2 or any later version.
6 6
7 7 from __future__ import absolute_import
8 8
9 9 import contextlib
10 10 import struct
11 11 import sys
12 12 import threading
13 13
14 14 from .i18n import _
15 15 from . import (
16 16 encoding,
17 17 error,
18 18 hook,
19 19 pycompat,
20 20 util,
21 21 wireproto,
22 22 wireprototypes,
23 23 )
24 24
25 25 stringio = util.stringio
26 26
27 27 urlerr = util.urlerr
28 28 urlreq = util.urlreq
29 29
30 30 HTTP_OK = 200
31 31
32 32 HGTYPE = 'application/mercurial-0.1'
33 33 HGTYPE2 = 'application/mercurial-0.2'
34 34 HGERRTYPE = 'application/hg-error'
35 35
36 HTTPV2 = wireprototypes.HTTPV2
36 37 SSHV1 = wireprototypes.SSHV1
37 38 SSHV2 = wireprototypes.SSHV2
38 39
39 40 def decodevaluefromheaders(req, headerprefix):
40 41 """Decode a long value from multiple HTTP request headers.
41 42
42 43 Returns the value as a bytes, not a str.
43 44 """
44 45 chunks = []
45 46 i = 1
46 47 while True:
47 48 v = req.headers.get(b'%s-%d' % (headerprefix, i))
48 49 if v is None:
49 50 break
50 51 chunks.append(pycompat.bytesurl(v))
51 52 i += 1
52 53
53 54 return ''.join(chunks)
54 55
55 56 class httpv1protocolhandler(wireprototypes.baseprotocolhandler):
56 57 def __init__(self, req, ui, checkperm):
57 58 self._req = req
58 59 self._ui = ui
59 60 self._checkperm = checkperm
60 61
61 62 @property
62 63 def name(self):
63 64 return 'http-v1'
64 65
65 66 def getargs(self, args):
66 67 knownargs = self._args()
67 68 data = {}
68 69 keys = args.split()
69 70 for k in keys:
70 71 if k == '*':
71 72 star = {}
72 73 for key in knownargs.keys():
73 74 if key != 'cmd' and key not in keys:
74 75 star[key] = knownargs[key][0]
75 76 data['*'] = star
76 77 else:
77 78 data[k] = knownargs[k][0]
78 79 return [data[k] for k in keys]
79 80
80 81 def _args(self):
81 82 args = self._req.qsparams.asdictoflists()
82 83 postlen = int(self._req.headers.get(b'X-HgArgs-Post', 0))
83 84 if postlen:
84 85 args.update(urlreq.parseqs(
85 86 self._req.bodyfh.read(postlen), keep_blank_values=True))
86 87 return args
87 88
88 89 argvalue = decodevaluefromheaders(self._req, b'X-HgArg')
89 90 args.update(urlreq.parseqs(argvalue, keep_blank_values=True))
90 91 return args
91 92
92 93 def forwardpayload(self, fp):
93 94 # Existing clients *always* send Content-Length.
94 95 length = int(self._req.headers[b'Content-Length'])
95 96
96 97 # If httppostargs is used, we need to read Content-Length
97 98 # minus the amount that was consumed by args.
98 99 length -= int(self._req.headers.get(b'X-HgArgs-Post', 0))
99 100 for s in util.filechunkiter(self._req.bodyfh, limit=length):
100 101 fp.write(s)
101 102
102 103 @contextlib.contextmanager
103 104 def mayberedirectstdio(self):
104 105 oldout = self._ui.fout
105 106 olderr = self._ui.ferr
106 107
107 108 out = util.stringio()
108 109
109 110 try:
110 111 self._ui.fout = out
111 112 self._ui.ferr = out
112 113 yield out
113 114 finally:
114 115 self._ui.fout = oldout
115 116 self._ui.ferr = olderr
116 117
117 118 def client(self):
118 119 return 'remote:%s:%s:%s' % (
119 120 self._req.urlscheme,
120 121 urlreq.quote(self._req.remotehost or ''),
121 122 urlreq.quote(self._req.remoteuser or ''))
122 123
123 124 def addcapabilities(self, repo, caps):
124 125 caps.append('httpheader=%d' %
125 126 repo.ui.configint('server', 'maxhttpheaderlen'))
126 127 if repo.ui.configbool('experimental', 'httppostargs'):
127 128 caps.append('httppostargs')
128 129
129 130 # FUTURE advertise 0.2rx once support is implemented
130 131 # FUTURE advertise minrx and mintx after consulting config option
131 132 caps.append('httpmediatype=0.1rx,0.1tx,0.2tx')
132 133
133 134 compengines = wireproto.supportedcompengines(repo.ui, util.SERVERROLE)
134 135 if compengines:
135 136 comptypes = ','.join(urlreq.quote(e.wireprotosupport().name)
136 137 for e in compengines)
137 138 caps.append('compression=%s' % comptypes)
138 139
139 140 return caps
140 141
141 142 def checkperm(self, perm):
142 143 return self._checkperm(perm)
143 144
144 145 # This method exists mostly so that extensions like remotefilelog can
145 146 # disable a kludgey legacy method only over http. As of early 2018,
146 147 # there are no other known users, so with any luck we can discard this
147 148 # hook if remotefilelog becomes a first-party extension.
148 149 def iscmd(cmd):
149 150 return cmd in wireproto.commands
150 151
151 152 def handlewsgirequest(rctx, req, res, checkperm):
152 153 """Possibly process a wire protocol request.
153 154
154 155 If the current request is a wire protocol request, the request is
155 156 processed by this function.
156 157
157 158 ``req`` is a ``parsedrequest`` instance.
158 159 ``res`` is a ``wsgiresponse`` instance.
159 160
160 161 Returns a bool indicating if the request was serviced. If set, the caller
161 162 should stop processing the request, as a response has already been issued.
162 163 """
163 164 # Avoid cycle involving hg module.
164 165 from .hgweb import common as hgwebcommon
165 166
166 167 repo = rctx.repo
167 168
168 169 # HTTP version 1 wire protocol requests are denoted by a "cmd" query
169 170 # string parameter. If it isn't present, this isn't a wire protocol
170 171 # request.
171 172 if 'cmd' not in req.qsparams:
172 173 return False
173 174
174 175 cmd = req.qsparams['cmd']
175 176
176 177 # The "cmd" request parameter is used by both the wire protocol and hgweb.
177 178 # While not all wire protocol commands are available for all transports,
178 179 # if we see a "cmd" value that resembles a known wire protocol command, we
179 180 # route it to a protocol handler. This is better than routing possible
180 181 # wire protocol requests to hgweb because it prevents hgweb from using
181 182 # known wire protocol commands and it is less confusing for machine
182 183 # clients.
183 184 if not iscmd(cmd):
184 185 return False
185 186
186 187 # The "cmd" query string argument is only valid on the root path of the
187 188 # repo. e.g. ``/?cmd=foo``, ``/repo?cmd=foo``. URL paths within the repo
188 189 # like ``/blah?cmd=foo`` are not allowed. So don't recognize the request
189 190 # in this case. We send an HTTP 404 for backwards compatibility reasons.
190 191 if req.dispatchpath:
191 192 res.status = hgwebcommon.statusmessage(404)
192 193 res.headers['Content-Type'] = HGTYPE
193 194 # TODO This is not a good response to issue for this request. This
194 195 # is mostly for BC for now.
195 196 res.setbodybytes('0\n%s\n' % b'Not Found')
196 197 return True
197 198
198 199 proto = httpv1protocolhandler(req, repo.ui,
199 200 lambda perm: checkperm(rctx, req, perm))
200 201
201 202 # The permissions checker should be the only thing that can raise an
202 203 # ErrorResponse. It is kind of a layer violation to catch an hgweb
203 204 # exception here. So consider refactoring into a exception type that
204 205 # is associated with the wire protocol.
205 206 try:
206 207 _callhttp(repo, req, res, proto, cmd)
207 208 except hgwebcommon.ErrorResponse as e:
208 209 for k, v in e.headers:
209 210 res.headers[k] = v
210 211 res.status = hgwebcommon.statusmessage(e.code, pycompat.bytestr(e))
211 212 # TODO This response body assumes the failed command was
212 213 # "unbundle." That assumption is not always valid.
213 214 res.setbodybytes('0\n%s\n' % pycompat.bytestr(e))
214 215
215 216 return True
216 217
218 def handlewsgiapirequest(rctx, req, res, checkperm):
219 """Handle requests to /api/*."""
220 assert req.dispatchparts[0] == b'api'
221
222 repo = rctx.repo
223
224 # This whole URL space is experimental for now. But we want to
225 # reserve the URL space. So, 404 all URLs if the feature isn't enabled.
226 if not repo.ui.configbool('experimental', 'web.apiserver'):
227 res.status = b'404 Not Found'
228 res.headers[b'Content-Type'] = b'text/plain'
229 res.setbodybytes(_('Experimental API server endpoint not enabled'))
230 return
231
232 # The URL space is /api/<protocol>/*. The structure of URLs under varies
233 # by <protocol>.
234
235 # Registered APIs are made available via config options of the name of
236 # the protocol.
237 availableapis = set()
238 for k, v in API_HANDLERS.items():
239 section, option = v['config']
240 if repo.ui.configbool(section, option):
241 availableapis.add(k)
242
243 # Requests to /api/ list available APIs.
244 if req.dispatchparts == [b'api']:
245 res.status = b'200 OK'
246 res.headers[b'Content-Type'] = b'text/plain'
247 lines = [_('APIs can be accessed at /api/<name>, where <name> can be '
248 'one of the following:\n')]
249 if availableapis:
250 lines.extend(sorted(availableapis))
251 else:
252 lines.append(_('(no available APIs)\n'))
253 res.setbodybytes(b'\n'.join(lines))
254 return
255
256 proto = req.dispatchparts[1]
257
258 if proto not in API_HANDLERS:
259 res.status = b'404 Not Found'
260 res.headers[b'Content-Type'] = b'text/plain'
261 res.setbodybytes(_('Unknown API: %s\nKnown APIs: %s') % (
262 proto, b', '.join(sorted(availableapis))))
263 return
264
265 if proto not in availableapis:
266 res.status = b'404 Not Found'
267 res.headers[b'Content-Type'] = b'text/plain'
268 res.setbodybytes(_('API %s not enabled\n') % proto)
269 return
270
271 API_HANDLERS[proto]['handler'](rctx, req, res, checkperm,
272 req.dispatchparts[2:])
273
274 def _handlehttpv2request(rctx, req, res, checkperm, urlparts):
275 res.status = b'200 OK'
276 res.headers[b'Content-Type'] = b'text/plain'
277 res.setbodybytes(b'/'.join(urlparts) + b'\n')
278
279 # Maps API name to metadata so custom API can be registered.
280 API_HANDLERS = {
281 HTTPV2: {
282 'config': ('experimental', 'web.api.http-v2'),
283 'handler': _handlehttpv2request,
284 },
285 }
286
217 287 def _httpresponsetype(ui, req, prefer_uncompressed):
218 288 """Determine the appropriate response type and compression settings.
219 289
220 290 Returns a tuple of (mediatype, compengine, engineopts).
221 291 """
222 292 # Determine the response media type and compression engine based
223 293 # on the request parameters.
224 294 protocaps = decodevaluefromheaders(req, 'X-HgProto').split(' ')
225 295
226 296 if '0.2' in protocaps:
227 297 # All clients are expected to support uncompressed data.
228 298 if prefer_uncompressed:
229 299 return HGTYPE2, util._noopengine(), {}
230 300
231 301 # Default as defined by wire protocol spec.
232 302 compformats = ['zlib', 'none']
233 303 for cap in protocaps:
234 304 if cap.startswith('comp='):
235 305 compformats = cap[5:].split(',')
236 306 break
237 307
238 308 # Now find an agreed upon compression format.
239 309 for engine in wireproto.supportedcompengines(ui, util.SERVERROLE):
240 310 if engine.wireprotosupport().name in compformats:
241 311 opts = {}
242 312 level = ui.configint('server', '%slevel' % engine.name())
243 313 if level is not None:
244 314 opts['level'] = level
245 315
246 316 return HGTYPE2, engine, opts
247 317
248 318 # No mutually supported compression format. Fall back to the
249 319 # legacy protocol.
250 320
251 321 # Don't allow untrusted settings because disabling compression or
252 322 # setting a very high compression level could lead to flooding
253 323 # the server's network or CPU.
254 324 opts = {'level': ui.configint('server', 'zliblevel')}
255 325 return HGTYPE, util.compengines['zlib'], opts
256 326
257 327 def _callhttp(repo, req, res, proto, cmd):
258 328 # Avoid cycle involving hg module.
259 329 from .hgweb import common as hgwebcommon
260 330
261 331 def genversion2(gen, engine, engineopts):
262 332 # application/mercurial-0.2 always sends a payload header
263 333 # identifying the compression engine.
264 334 name = engine.wireprotosupport().name
265 335 assert 0 < len(name) < 256
266 336 yield struct.pack('B', len(name))
267 337 yield name
268 338
269 339 for chunk in gen:
270 340 yield chunk
271 341
272 342 def setresponse(code, contenttype, bodybytes=None, bodygen=None):
273 343 if code == HTTP_OK:
274 344 res.status = '200 Script output follows'
275 345 else:
276 346 res.status = hgwebcommon.statusmessage(code)
277 347
278 348 res.headers['Content-Type'] = contenttype
279 349
280 350 if bodybytes is not None:
281 351 res.setbodybytes(bodybytes)
282 352 if bodygen is not None:
283 353 res.setbodygen(bodygen)
284 354
285 355 if not wireproto.commands.commandavailable(cmd, proto):
286 356 setresponse(HTTP_OK, HGERRTYPE,
287 357 _('requested wire protocol command is not available over '
288 358 'HTTP'))
289 359 return
290 360
291 361 proto.checkperm(wireproto.commands[cmd].permission)
292 362
293 363 rsp = wireproto.dispatch(repo, proto, cmd)
294 364
295 365 if isinstance(rsp, bytes):
296 366 setresponse(HTTP_OK, HGTYPE, bodybytes=rsp)
297 367 elif isinstance(rsp, wireprototypes.bytesresponse):
298 368 setresponse(HTTP_OK, HGTYPE, bodybytes=rsp.data)
299 369 elif isinstance(rsp, wireprototypes.streamreslegacy):
300 370 setresponse(HTTP_OK, HGTYPE, bodygen=rsp.gen)
301 371 elif isinstance(rsp, wireprototypes.streamres):
302 372 gen = rsp.gen
303 373
304 374 # This code for compression should not be streamres specific. It
305 375 # is here because we only compress streamres at the moment.
306 376 mediatype, engine, engineopts = _httpresponsetype(
307 377 repo.ui, req, rsp.prefer_uncompressed)
308 378 gen = engine.compressstream(gen, engineopts)
309 379
310 380 if mediatype == HGTYPE2:
311 381 gen = genversion2(gen, engine, engineopts)
312 382
313 383 setresponse(HTTP_OK, mediatype, bodygen=gen)
314 384 elif isinstance(rsp, wireprototypes.pushres):
315 385 rsp = '%d\n%s' % (rsp.res, rsp.output)
316 386 setresponse(HTTP_OK, HGTYPE, bodybytes=rsp)
317 387 elif isinstance(rsp, wireprototypes.pusherr):
318 388 rsp = '0\n%s\n' % rsp.res
319 389 res.drain = True
320 390 setresponse(HTTP_OK, HGTYPE, bodybytes=rsp)
321 391 elif isinstance(rsp, wireprototypes.ooberror):
322 392 setresponse(HTTP_OK, HGERRTYPE, bodybytes=rsp.message)
323 393 else:
324 394 raise error.ProgrammingError('hgweb.protocol internal failure', rsp)
325 395
326 396 def _sshv1respondbytes(fout, value):
327 397 """Send a bytes response for protocol version 1."""
328 398 fout.write('%d\n' % len(value))
329 399 fout.write(value)
330 400 fout.flush()
331 401
332 402 def _sshv1respondstream(fout, source):
333 403 write = fout.write
334 404 for chunk in source.gen:
335 405 write(chunk)
336 406 fout.flush()
337 407
338 408 def _sshv1respondooberror(fout, ferr, rsp):
339 409 ferr.write(b'%s\n-\n' % rsp)
340 410 ferr.flush()
341 411 fout.write(b'\n')
342 412 fout.flush()
343 413
344 414 class sshv1protocolhandler(wireprototypes.baseprotocolhandler):
345 415 """Handler for requests services via version 1 of SSH protocol."""
346 416 def __init__(self, ui, fin, fout):
347 417 self._ui = ui
348 418 self._fin = fin
349 419 self._fout = fout
350 420
351 421 @property
352 422 def name(self):
353 423 return wireprototypes.SSHV1
354 424
355 425 def getargs(self, args):
356 426 data = {}
357 427 keys = args.split()
358 428 for n in xrange(len(keys)):
359 429 argline = self._fin.readline()[:-1]
360 430 arg, l = argline.split()
361 431 if arg not in keys:
362 432 raise error.Abort(_("unexpected parameter %r") % arg)
363 433 if arg == '*':
364 434 star = {}
365 435 for k in xrange(int(l)):
366 436 argline = self._fin.readline()[:-1]
367 437 arg, l = argline.split()
368 438 val = self._fin.read(int(l))
369 439 star[arg] = val
370 440 data['*'] = star
371 441 else:
372 442 val = self._fin.read(int(l))
373 443 data[arg] = val
374 444 return [data[k] for k in keys]
375 445
376 446 def forwardpayload(self, fpout):
377 447 # We initially send an empty response. This tells the client it is
378 448 # OK to start sending data. If a client sees any other response, it
379 449 # interprets it as an error.
380 450 _sshv1respondbytes(self._fout, b'')
381 451
382 452 # The file is in the form:
383 453 #
384 454 # <chunk size>\n<chunk>
385 455 # ...
386 456 # 0\n
387 457 count = int(self._fin.readline())
388 458 while count:
389 459 fpout.write(self._fin.read(count))
390 460 count = int(self._fin.readline())
391 461
392 462 @contextlib.contextmanager
393 463 def mayberedirectstdio(self):
394 464 yield None
395 465
396 466 def client(self):
397 467 client = encoding.environ.get('SSH_CLIENT', '').split(' ', 1)[0]
398 468 return 'remote:ssh:' + client
399 469
400 470 def addcapabilities(self, repo, caps):
401 471 return caps
402 472
403 473 def checkperm(self, perm):
404 474 pass
405 475
406 476 class sshv2protocolhandler(sshv1protocolhandler):
407 477 """Protocol handler for version 2 of the SSH protocol."""
408 478
409 479 @property
410 480 def name(self):
411 481 return wireprototypes.SSHV2
412 482
413 483 def _runsshserver(ui, repo, fin, fout, ev):
414 484 # This function operates like a state machine of sorts. The following
415 485 # states are defined:
416 486 #
417 487 # protov1-serving
418 488 # Server is in protocol version 1 serving mode. Commands arrive on
419 489 # new lines. These commands are processed in this state, one command
420 490 # after the other.
421 491 #
422 492 # protov2-serving
423 493 # Server is in protocol version 2 serving mode.
424 494 #
425 495 # upgrade-initial
426 496 # The server is going to process an upgrade request.
427 497 #
428 498 # upgrade-v2-filter-legacy-handshake
429 499 # The protocol is being upgraded to version 2. The server is expecting
430 500 # the legacy handshake from version 1.
431 501 #
432 502 # upgrade-v2-finish
433 503 # The upgrade to version 2 of the protocol is imminent.
434 504 #
435 505 # shutdown
436 506 # The server is shutting down, possibly in reaction to a client event.
437 507 #
438 508 # And here are their transitions:
439 509 #
440 510 # protov1-serving -> shutdown
441 511 # When server receives an empty request or encounters another
442 512 # error.
443 513 #
444 514 # protov1-serving -> upgrade-initial
445 515 # An upgrade request line was seen.
446 516 #
447 517 # upgrade-initial -> upgrade-v2-filter-legacy-handshake
448 518 # Upgrade to version 2 in progress. Server is expecting to
449 519 # process a legacy handshake.
450 520 #
451 521 # upgrade-v2-filter-legacy-handshake -> shutdown
452 522 # Client did not fulfill upgrade handshake requirements.
453 523 #
454 524 # upgrade-v2-filter-legacy-handshake -> upgrade-v2-finish
455 525 # Client fulfilled version 2 upgrade requirements. Finishing that
456 526 # upgrade.
457 527 #
458 528 # upgrade-v2-finish -> protov2-serving
459 529 # Protocol upgrade to version 2 complete. Server can now speak protocol
460 530 # version 2.
461 531 #
462 532 # protov2-serving -> protov1-serving
463 533 # Ths happens by default since protocol version 2 is the same as
464 534 # version 1 except for the handshake.
465 535
466 536 state = 'protov1-serving'
467 537 proto = sshv1protocolhandler(ui, fin, fout)
468 538 protoswitched = False
469 539
470 540 while not ev.is_set():
471 541 if state == 'protov1-serving':
472 542 # Commands are issued on new lines.
473 543 request = fin.readline()[:-1]
474 544
475 545 # Empty lines signal to terminate the connection.
476 546 if not request:
477 547 state = 'shutdown'
478 548 continue
479 549
480 550 # It looks like a protocol upgrade request. Transition state to
481 551 # handle it.
482 552 if request.startswith(b'upgrade '):
483 553 if protoswitched:
484 554 _sshv1respondooberror(fout, ui.ferr,
485 555 b'cannot upgrade protocols multiple '
486 556 b'times')
487 557 state = 'shutdown'
488 558 continue
489 559
490 560 state = 'upgrade-initial'
491 561 continue
492 562
493 563 available = wireproto.commands.commandavailable(request, proto)
494 564
495 565 # This command isn't available. Send an empty response and go
496 566 # back to waiting for a new command.
497 567 if not available:
498 568 _sshv1respondbytes(fout, b'')
499 569 continue
500 570
501 571 rsp = wireproto.dispatch(repo, proto, request)
502 572
503 573 if isinstance(rsp, bytes):
504 574 _sshv1respondbytes(fout, rsp)
505 575 elif isinstance(rsp, wireprototypes.bytesresponse):
506 576 _sshv1respondbytes(fout, rsp.data)
507 577 elif isinstance(rsp, wireprototypes.streamres):
508 578 _sshv1respondstream(fout, rsp)
509 579 elif isinstance(rsp, wireprototypes.streamreslegacy):
510 580 _sshv1respondstream(fout, rsp)
511 581 elif isinstance(rsp, wireprototypes.pushres):
512 582 _sshv1respondbytes(fout, b'')
513 583 _sshv1respondbytes(fout, b'%d' % rsp.res)
514 584 elif isinstance(rsp, wireprototypes.pusherr):
515 585 _sshv1respondbytes(fout, rsp.res)
516 586 elif isinstance(rsp, wireprototypes.ooberror):
517 587 _sshv1respondooberror(fout, ui.ferr, rsp.message)
518 588 else:
519 589 raise error.ProgrammingError('unhandled response type from '
520 590 'wire protocol command: %s' % rsp)
521 591
522 592 # For now, protocol version 2 serving just goes back to version 1.
523 593 elif state == 'protov2-serving':
524 594 state = 'protov1-serving'
525 595 continue
526 596
527 597 elif state == 'upgrade-initial':
528 598 # We should never transition into this state if we've switched
529 599 # protocols.
530 600 assert not protoswitched
531 601 assert proto.name == wireprototypes.SSHV1
532 602
533 603 # Expected: upgrade <token> <capabilities>
534 604 # If we get something else, the request is malformed. It could be
535 605 # from a future client that has altered the upgrade line content.
536 606 # We treat this as an unknown command.
537 607 try:
538 608 token, caps = request.split(b' ')[1:]
539 609 except ValueError:
540 610 _sshv1respondbytes(fout, b'')
541 611 state = 'protov1-serving'
542 612 continue
543 613
544 614 # Send empty response if we don't support upgrading protocols.
545 615 if not ui.configbool('experimental', 'sshserver.support-v2'):
546 616 _sshv1respondbytes(fout, b'')
547 617 state = 'protov1-serving'
548 618 continue
549 619
550 620 try:
551 621 caps = urlreq.parseqs(caps)
552 622 except ValueError:
553 623 _sshv1respondbytes(fout, b'')
554 624 state = 'protov1-serving'
555 625 continue
556 626
557 627 # We don't see an upgrade request to protocol version 2. Ignore
558 628 # the upgrade request.
559 629 wantedprotos = caps.get(b'proto', [b''])[0]
560 630 if SSHV2 not in wantedprotos:
561 631 _sshv1respondbytes(fout, b'')
562 632 state = 'protov1-serving'
563 633 continue
564 634
565 635 # It looks like we can honor this upgrade request to protocol 2.
566 636 # Filter the rest of the handshake protocol request lines.
567 637 state = 'upgrade-v2-filter-legacy-handshake'
568 638 continue
569 639
570 640 elif state == 'upgrade-v2-filter-legacy-handshake':
571 641 # Client should have sent legacy handshake after an ``upgrade``
572 642 # request. Expected lines:
573 643 #
574 644 # hello
575 645 # between
576 646 # pairs 81
577 647 # 0000...-0000...
578 648
579 649 ok = True
580 650 for line in (b'hello', b'between', b'pairs 81'):
581 651 request = fin.readline()[:-1]
582 652
583 653 if request != line:
584 654 _sshv1respondooberror(fout, ui.ferr,
585 655 b'malformed handshake protocol: '
586 656 b'missing %s' % line)
587 657 ok = False
588 658 state = 'shutdown'
589 659 break
590 660
591 661 if not ok:
592 662 continue
593 663
594 664 request = fin.read(81)
595 665 if request != b'%s-%s' % (b'0' * 40, b'0' * 40):
596 666 _sshv1respondooberror(fout, ui.ferr,
597 667 b'malformed handshake protocol: '
598 668 b'missing between argument value')
599 669 state = 'shutdown'
600 670 continue
601 671
602 672 state = 'upgrade-v2-finish'
603 673 continue
604 674
605 675 elif state == 'upgrade-v2-finish':
606 676 # Send the upgrade response.
607 677 fout.write(b'upgraded %s %s\n' % (token, SSHV2))
608 678 servercaps = wireproto.capabilities(repo, proto)
609 679 rsp = b'capabilities: %s' % servercaps.data
610 680 fout.write(b'%d\n%s\n' % (len(rsp), rsp))
611 681 fout.flush()
612 682
613 683 proto = sshv2protocolhandler(ui, fin, fout)
614 684 protoswitched = True
615 685
616 686 state = 'protov2-serving'
617 687 continue
618 688
619 689 elif state == 'shutdown':
620 690 break
621 691
622 692 else:
623 693 raise error.ProgrammingError('unhandled ssh server state: %s' %
624 694 state)
625 695
626 696 class sshserver(object):
627 697 def __init__(self, ui, repo, logfh=None):
628 698 self._ui = ui
629 699 self._repo = repo
630 700 self._fin = ui.fin
631 701 self._fout = ui.fout
632 702
633 703 # Log write I/O to stdout and stderr if configured.
634 704 if logfh:
635 705 self._fout = util.makeloggingfileobject(
636 706 logfh, self._fout, 'o', logdata=True)
637 707 ui.ferr = util.makeloggingfileobject(
638 708 logfh, ui.ferr, 'e', logdata=True)
639 709
640 710 hook.redirect(True)
641 711 ui.fout = repo.ui.fout = ui.ferr
642 712
643 713 # Prevent insertion/deletion of CRs
644 714 util.setbinary(self._fin)
645 715 util.setbinary(self._fout)
646 716
647 717 def serve_forever(self):
648 718 self.serveuntil(threading.Event())
649 719 sys.exit(0)
650 720
651 721 def serveuntil(self, ev):
652 722 """Serve until a threading.Event is set."""
653 723 _runsshserver(self._ui, self._repo, self._fin, self._fout, ev)
@@ -1,157 +1,162 b''
1 1 # Copyright 2018 Gregory Szorc <gregory.szorc@gmail.com>
2 2 #
3 3 # This software may be used and distributed according to the terms of the
4 4 # GNU General Public License version 2 or any later version.
5 5
6 6 from __future__ import absolute_import
7 7
8 8 import abc
9 9
10 10 # Names of the SSH protocol implementations.
11 11 SSHV1 = 'ssh-v1'
12 # This is advertised over the wire. Incremental the counter at the end
12 # These are advertised over the wire. Increment the counters at the end
13 13 # to reflect BC breakages.
14 14 SSHV2 = 'exp-ssh-v2-0001'
15 HTTPV2 = 'exp-http-v2-0001'
15 16
16 17 # All available wire protocol transports.
17 18 TRANSPORTS = {
18 19 SSHV1: {
19 20 'transport': 'ssh',
20 21 'version': 1,
21 22 },
22 23 SSHV2: {
23 24 'transport': 'ssh',
24 25 'version': 2,
25 26 },
26 27 'http-v1': {
27 28 'transport': 'http',
28 29 'version': 1,
30 },
31 HTTPV2: {
32 'transport': 'http',
33 'version': 2,
29 34 }
30 35 }
31 36
32 37 class bytesresponse(object):
33 38 """A wire protocol response consisting of raw bytes."""
34 39 def __init__(self, data):
35 40 self.data = data
36 41
37 42 class ooberror(object):
38 43 """wireproto reply: failure of a batch of operation
39 44
40 45 Something failed during a batch call. The error message is stored in
41 46 `self.message`.
42 47 """
43 48 def __init__(self, message):
44 49 self.message = message
45 50
46 51 class pushres(object):
47 52 """wireproto reply: success with simple integer return
48 53
49 54 The call was successful and returned an integer contained in `self.res`.
50 55 """
51 56 def __init__(self, res, output):
52 57 self.res = res
53 58 self.output = output
54 59
55 60 class pusherr(object):
56 61 """wireproto reply: failure
57 62
58 63 The call failed. The `self.res` attribute contains the error message.
59 64 """
60 65 def __init__(self, res, output):
61 66 self.res = res
62 67 self.output = output
63 68
64 69 class streamres(object):
65 70 """wireproto reply: binary stream
66 71
67 72 The call was successful and the result is a stream.
68 73
69 74 Accepts a generator containing chunks of data to be sent to the client.
70 75
71 76 ``prefer_uncompressed`` indicates that the data is expected to be
72 77 uncompressable and that the stream should therefore use the ``none``
73 78 engine.
74 79 """
75 80 def __init__(self, gen=None, prefer_uncompressed=False):
76 81 self.gen = gen
77 82 self.prefer_uncompressed = prefer_uncompressed
78 83
79 84 class streamreslegacy(object):
80 85 """wireproto reply: uncompressed binary stream
81 86
82 87 The call was successful and the result is a stream.
83 88
84 89 Accepts a generator containing chunks of data to be sent to the client.
85 90
86 91 Like ``streamres``, but sends an uncompressed data for "version 1" clients
87 92 using the application/mercurial-0.1 media type.
88 93 """
89 94 def __init__(self, gen=None):
90 95 self.gen = gen
91 96
92 97 class baseprotocolhandler(object):
93 98 """Abstract base class for wire protocol handlers.
94 99
95 100 A wire protocol handler serves as an interface between protocol command
96 101 handlers and the wire protocol transport layer. Protocol handlers provide
97 102 methods to read command arguments, redirect stdio for the duration of
98 103 the request, handle response types, etc.
99 104 """
100 105
101 106 __metaclass__ = abc.ABCMeta
102 107
103 108 @abc.abstractproperty
104 109 def name(self):
105 110 """The name of the protocol implementation.
106 111
107 112 Used for uniquely identifying the transport type.
108 113 """
109 114
110 115 @abc.abstractmethod
111 116 def getargs(self, args):
112 117 """return the value for arguments in <args>
113 118
114 119 returns a list of values (same order as <args>)"""
115 120
116 121 @abc.abstractmethod
117 122 def forwardpayload(self, fp):
118 123 """Read the raw payload and forward to a file.
119 124
120 125 The payload is read in full before the function returns.
121 126 """
122 127
123 128 @abc.abstractmethod
124 129 def mayberedirectstdio(self):
125 130 """Context manager to possibly redirect stdio.
126 131
127 132 The context manager yields a file-object like object that receives
128 133 stdout and stderr output when the context manager is active. Or it
129 134 yields ``None`` if no I/O redirection occurs.
130 135
131 136 The intent of this context manager is to capture stdio output
132 137 so it may be sent in the response. Some transports support streaming
133 138 stdio to the client in real time. For these transports, stdio output
134 139 won't be captured.
135 140 """
136 141
137 142 @abc.abstractmethod
138 143 def client(self):
139 144 """Returns a string representation of this client (as bytes)."""
140 145
141 146 @abc.abstractmethod
142 147 def addcapabilities(self, repo, caps):
143 148 """Adds advertised capabilities specific to this protocol.
144 149
145 150 Receives the list of capabilities collected so far.
146 151
147 152 Returns a list of capabilities. The passed in argument can be returned.
148 153 """
149 154
150 155 @abc.abstractmethod
151 156 def checkperm(self, perm):
152 157 """Validate that the client has permissions to perform a request.
153 158
154 159 The argument is the permission required to proceed. If the client
155 160 doesn't have that permission, the exception should raise or abort
156 161 in a protocol specific manner.
157 162 """
General Comments 0
You need to be logged in to leave comments. Login now