##// END OF EJS Templates
hgweb: transition permissions hooks to modern request type (API)...
Gregory Szorc -
r36893:02bea04b default
parent child Browse files
Show More
@@ -1,253 +1,252 b''
1 1 # hgweb/common.py - Utility functions needed by hgweb_mod and hgwebdir_mod
2 2 #
3 3 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
4 4 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
5 5 #
6 6 # This software may be used and distributed according to the terms of the
7 7 # GNU General Public License version 2 or any later version.
8 8
9 9 from __future__ import absolute_import
10 10
11 11 import base64
12 12 import errno
13 13 import mimetypes
14 14 import os
15 15 import stat
16 16
17 17 from .. import (
18 18 encoding,
19 19 pycompat,
20 20 util,
21 21 )
22 22
23 23 httpserver = util.httpserver
24 24
25 25 HTTP_OK = 200
26 26 HTTP_NOT_MODIFIED = 304
27 27 HTTP_BAD_REQUEST = 400
28 28 HTTP_UNAUTHORIZED = 401
29 29 HTTP_FORBIDDEN = 403
30 30 HTTP_NOT_FOUND = 404
31 31 HTTP_METHOD_NOT_ALLOWED = 405
32 32 HTTP_SERVER_ERROR = 500
33 33
34 34
35 35 def ismember(ui, username, userlist):
36 36 """Check if username is a member of userlist.
37 37
38 38 If userlist has a single '*' member, all users are considered members.
39 39 Can be overridden by extensions to provide more complex authorization
40 40 schemes.
41 41 """
42 42 return userlist == ['*'] or username in userlist
43 43
44 44 def checkauthz(hgweb, req, op):
45 45 '''Check permission for operation based on request data (including
46 46 authentication info). Return if op allowed, else raise an ErrorResponse
47 47 exception.'''
48 48
49 user = req.env.get(r'REMOTE_USER')
49 user = req.remoteuser
50 50
51 51 deny_read = hgweb.configlist('web', 'deny_read')
52 52 if deny_read and (not user or ismember(hgweb.repo.ui, user, deny_read)):
53 53 raise ErrorResponse(HTTP_UNAUTHORIZED, 'read not authorized')
54 54
55 55 allow_read = hgweb.configlist('web', 'allow_read')
56 56 if allow_read and (not ismember(hgweb.repo.ui, user, allow_read)):
57 57 raise ErrorResponse(HTTP_UNAUTHORIZED, 'read not authorized')
58 58
59 59 if op == 'pull' and not hgweb.allowpull:
60 60 raise ErrorResponse(HTTP_UNAUTHORIZED, 'pull not authorized')
61 61 elif op == 'pull' or op is None: # op is None for interface requests
62 62 return
63 63
64 64 # enforce that you can only push using POST requests
65 if req.env[r'REQUEST_METHOD'] != r'POST':
65 if req.method != 'POST':
66 66 msg = 'push requires POST request'
67 67 raise ErrorResponse(HTTP_METHOD_NOT_ALLOWED, msg)
68 68
69 69 # require ssl by default for pushing, auth info cannot be sniffed
70 70 # and replayed
71 scheme = req.env.get('wsgi.url_scheme')
72 if hgweb.configbool('web', 'push_ssl') and scheme != 'https':
71 if hgweb.configbool('web', 'push_ssl') and req.urlscheme != 'https':
73 72 raise ErrorResponse(HTTP_FORBIDDEN, 'ssl required')
74 73
75 74 deny = hgweb.configlist('web', 'deny_push')
76 75 if deny and (not user or ismember(hgweb.repo.ui, user, deny)):
77 76 raise ErrorResponse(HTTP_UNAUTHORIZED, 'push not authorized')
78 77
79 78 allow = hgweb.configlist('web', 'allow-push')
80 79 if not (allow and ismember(hgweb.repo.ui, user, allow)):
81 80 raise ErrorResponse(HTTP_UNAUTHORIZED, 'push not authorized')
82 81
83 82 # Hooks for hgweb permission checks; extensions can add hooks here.
84 83 # Each hook is invoked like this: hook(hgweb, request, operation),
85 84 # where operation is either read, pull or push. Hooks should either
86 85 # raise an ErrorResponse exception, or just return.
87 86 #
88 87 # It is possible to do both authentication and authorization through
89 88 # this.
90 89 permhooks = [checkauthz]
91 90
92 91
93 92 class ErrorResponse(Exception):
94 93 def __init__(self, code, message=None, headers=None):
95 94 if message is None:
96 95 message = _statusmessage(code)
97 96 Exception.__init__(self, pycompat.sysstr(message))
98 97 self.code = code
99 98 if headers is None:
100 99 headers = []
101 100 self.headers = headers
102 101
103 102 class continuereader(object):
104 103 """File object wrapper to handle HTTP 100-continue.
105 104
106 105 This is used by servers so they automatically handle Expect: 100-continue
107 106 request headers. On first read of the request body, the 100 Continue
108 107 response is sent. This should trigger the client into actually sending
109 108 the request body.
110 109 """
111 110 def __init__(self, f, write):
112 111 self.f = f
113 112 self._write = write
114 113 self.continued = False
115 114
116 115 def read(self, amt=-1):
117 116 if not self.continued:
118 117 self.continued = True
119 118 self._write('HTTP/1.1 100 Continue\r\n\r\n')
120 119 return self.f.read(amt)
121 120
122 121 def __getattr__(self, attr):
123 122 if attr in ('close', 'readline', 'readlines', '__iter__'):
124 123 return getattr(self.f, attr)
125 124 raise AttributeError
126 125
127 126 def _statusmessage(code):
128 127 responses = httpserver.basehttprequesthandler.responses
129 128 return responses.get(code, ('Error', 'Unknown error'))[0]
130 129
131 130 def statusmessage(code, message=None):
132 131 return '%d %s' % (code, message or _statusmessage(code))
133 132
134 133 def get_stat(spath, fn):
135 134 """stat fn if it exists, spath otherwise"""
136 135 cl_path = os.path.join(spath, fn)
137 136 if os.path.exists(cl_path):
138 137 return os.stat(cl_path)
139 138 else:
140 139 return os.stat(spath)
141 140
142 141 def get_mtime(spath):
143 142 return get_stat(spath, "00changelog.i")[stat.ST_MTIME]
144 143
145 144 def ispathsafe(path):
146 145 """Determine if a path is safe to use for filesystem access."""
147 146 parts = path.split('/')
148 147 for part in parts:
149 148 if (part in ('', pycompat.oscurdir, pycompat.ospardir) or
150 149 pycompat.ossep in part or
151 150 pycompat.osaltsep is not None and pycompat.osaltsep in part):
152 151 return False
153 152
154 153 return True
155 154
156 155 def staticfile(directory, fname, res):
157 156 """return a file inside directory with guessed Content-Type header
158 157
159 158 fname always uses '/' as directory separator and isn't allowed to
160 159 contain unusual path components.
161 160 Content-Type is guessed using the mimetypes module.
162 161 Return an empty string if fname is illegal or file not found.
163 162
164 163 """
165 164 if not ispathsafe(fname):
166 165 return
167 166
168 167 fpath = os.path.join(*fname.split('/'))
169 168 if isinstance(directory, str):
170 169 directory = [directory]
171 170 for d in directory:
172 171 path = os.path.join(d, fpath)
173 172 if os.path.exists(path):
174 173 break
175 174 try:
176 175 os.stat(path)
177 176 ct = mimetypes.guess_type(pycompat.fsdecode(path))[0] or "text/plain"
178 177 with open(path, 'rb') as fh:
179 178 data = fh.read()
180 179
181 180 res.headers['Content-Type'] = ct
182 181 res.setbodybytes(data)
183 182 return res
184 183 except TypeError:
185 184 raise ErrorResponse(HTTP_SERVER_ERROR, 'illegal filename')
186 185 except OSError as err:
187 186 if err.errno == errno.ENOENT:
188 187 raise ErrorResponse(HTTP_NOT_FOUND)
189 188 else:
190 189 raise ErrorResponse(HTTP_SERVER_ERROR,
191 190 encoding.strtolocal(err.strerror))
192 191
193 192 def paritygen(stripecount, offset=0):
194 193 """count parity of horizontal stripes for easier reading"""
195 194 if stripecount and offset:
196 195 # account for offset, e.g. due to building the list in reverse
197 196 count = (stripecount + offset) % stripecount
198 197 parity = (stripecount + offset) // stripecount & 1
199 198 else:
200 199 count = 0
201 200 parity = 0
202 201 while True:
203 202 yield parity
204 203 count += 1
205 204 if stripecount and count >= stripecount:
206 205 parity = 1 - parity
207 206 count = 0
208 207
209 208 def get_contact(config):
210 209 """Return repo contact information or empty string.
211 210
212 211 web.contact is the primary source, but if that is not set, try
213 212 ui.username or $EMAIL as a fallback to display something useful.
214 213 """
215 214 return (config("web", "contact") or
216 215 config("ui", "username") or
217 216 encoding.environ.get("EMAIL") or "")
218 217
219 218 def cspvalues(ui):
220 219 """Obtain the Content-Security-Policy header and nonce value.
221 220
222 221 Returns a 2-tuple of the CSP header value and the nonce value.
223 222
224 223 First value is ``None`` if CSP isn't enabled. Second value is ``None``
225 224 if CSP isn't enabled or if the CSP header doesn't need a nonce.
226 225 """
227 226 # Without demandimport, "import uuid" could have an immediate side-effect
228 227 # running "ldconfig" on Linux trying to find libuuid.
229 228 # With Python <= 2.7.12, that "ldconfig" is run via a shell and the shell
230 229 # may pollute the terminal with:
231 230 #
232 231 # shell-init: error retrieving current directory: getcwd: cannot access
233 232 # parent directories: No such file or directory
234 233 #
235 234 # Python >= 2.7.13 has fixed it by running "ldconfig" directly without a
236 235 # shell (hg changeset a09ae70f3489).
237 236 #
238 237 # Moved "import uuid" from here so it's executed after we know we have
239 238 # a sane cwd (i.e. after dispatch.py cwd check).
240 239 #
241 240 # We can move it back once we no longer need Python <= 2.7.12 support.
242 241 import uuid
243 242
244 243 # Don't allow untrusted CSP setting since it be disable protections
245 244 # from a trusted/global source.
246 245 csp = ui.config('web', 'csp', untrusted=False)
247 246 nonce = None
248 247
249 248 if csp and '%nonce%' in csp:
250 249 nonce = base64.urlsafe_b64encode(uuid.uuid4().bytes).rstrip('=')
251 250 csp = csp.replace('%nonce%', nonce)
252 251
253 252 return csp, nonce
@@ -1,456 +1,456 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 HTTP_NOT_FOUND,
18 18 HTTP_NOT_MODIFIED,
19 19 HTTP_OK,
20 20 HTTP_SERVER_ERROR,
21 21 cspvalues,
22 22 permhooks,
23 23 )
24 24
25 25 from .. import (
26 26 encoding,
27 27 error,
28 28 formatter,
29 29 hg,
30 30 hook,
31 31 profiling,
32 32 pycompat,
33 33 repoview,
34 34 templatefilters,
35 35 templater,
36 36 ui as uimod,
37 37 util,
38 38 wireprotoserver,
39 39 )
40 40
41 41 from . import (
42 42 request as requestmod,
43 43 webcommands,
44 44 webutil,
45 45 wsgicgi,
46 46 )
47 47
48 48 archivespecs = util.sortdict((
49 49 ('zip', ('application/zip', 'zip', '.zip', None)),
50 50 ('gz', ('application/x-gzip', 'tgz', '.tar.gz', None)),
51 51 ('bz2', ('application/x-bzip2', 'tbz2', '.tar.bz2', None)),
52 52 ))
53 53
54 54 def getstyle(req, configfn, templatepath):
55 55 styles = (
56 56 req.qsparams.get('style', None),
57 57 configfn('web', 'style'),
58 58 'paper',
59 59 )
60 60 return styles, templater.stylemap(styles, templatepath)
61 61
62 62 def makebreadcrumb(url, prefix=''):
63 63 '''Return a 'URL breadcrumb' list
64 64
65 65 A 'URL breadcrumb' is a list of URL-name pairs,
66 66 corresponding to each of the path items on a URL.
67 67 This can be used to create path navigation entries.
68 68 '''
69 69 if url.endswith('/'):
70 70 url = url[:-1]
71 71 if prefix:
72 72 url = '/' + prefix + url
73 73 relpath = url
74 74 if relpath.startswith('/'):
75 75 relpath = relpath[1:]
76 76
77 77 breadcrumb = []
78 78 urlel = url
79 79 pathitems = [''] + relpath.split('/')
80 80 for pathel in reversed(pathitems):
81 81 if not pathel or not urlel:
82 82 break
83 83 breadcrumb.append({'url': urlel, 'name': pathel})
84 84 urlel = os.path.dirname(urlel)
85 85 return reversed(breadcrumb)
86 86
87 87 class requestcontext(object):
88 88 """Holds state/context for an individual request.
89 89
90 90 Servers can be multi-threaded. Holding state on the WSGI application
91 91 is prone to race conditions. Instances of this class exist to hold
92 92 mutable and race-free state for requests.
93 93 """
94 94 def __init__(self, app, repo, req, res):
95 95 self.repo = repo
96 96 self.reponame = app.reponame
97 97 self.req = req
98 98 self.res = res
99 99
100 100 self.archivespecs = archivespecs
101 101
102 102 self.maxchanges = self.configint('web', 'maxchanges')
103 103 self.stripecount = self.configint('web', 'stripes')
104 104 self.maxshortchanges = self.configint('web', 'maxshortchanges')
105 105 self.maxfiles = self.configint('web', 'maxfiles')
106 106 self.allowpull = self.configbool('web', 'allow-pull')
107 107
108 108 # we use untrusted=False to prevent a repo owner from using
109 109 # web.templates in .hg/hgrc to get access to any file readable
110 110 # by the user running the CGI script
111 111 self.templatepath = self.config('web', 'templates', untrusted=False)
112 112
113 113 # This object is more expensive to build than simple config values.
114 114 # It is shared across requests. The app will replace the object
115 115 # if it is updated. Since this is a reference and nothing should
116 116 # modify the underlying object, it should be constant for the lifetime
117 117 # of the request.
118 118 self.websubtable = app.websubtable
119 119
120 120 self.csp, self.nonce = cspvalues(self.repo.ui)
121 121
122 122 # Trust the settings from the .hg/hgrc files by default.
123 123 def config(self, section, name, default=uimod._unset, untrusted=True):
124 124 return self.repo.ui.config(section, name, default,
125 125 untrusted=untrusted)
126 126
127 127 def configbool(self, section, name, default=uimod._unset, untrusted=True):
128 128 return self.repo.ui.configbool(section, name, default,
129 129 untrusted=untrusted)
130 130
131 131 def configint(self, section, name, default=uimod._unset, untrusted=True):
132 132 return self.repo.ui.configint(section, name, default,
133 133 untrusted=untrusted)
134 134
135 135 def configlist(self, section, name, default=uimod._unset, untrusted=True):
136 136 return self.repo.ui.configlist(section, name, default,
137 137 untrusted=untrusted)
138 138
139 139 def archivelist(self, nodeid):
140 140 allowed = self.configlist('web', 'allow_archive')
141 141 for typ, spec in self.archivespecs.iteritems():
142 142 if typ in allowed or self.configbool('web', 'allow%s' % typ):
143 143 yield {'type': typ, 'extension': spec[2], 'node': nodeid}
144 144
145 145 def templater(self, req):
146 146 # determine scheme, port and server name
147 147 # this is needed to create absolute urls
148 148 logourl = self.config('web', 'logourl')
149 149 logoimg = self.config('web', 'logoimg')
150 150 staticurl = (self.config('web', 'staticurl')
151 151 or req.apppath + '/static/')
152 152 if not staticurl.endswith('/'):
153 153 staticurl += '/'
154 154
155 155 # some functions for the templater
156 156
157 157 def motd(**map):
158 158 yield self.config('web', 'motd')
159 159
160 160 # figure out which style to use
161 161
162 162 vars = {}
163 163 styles, (style, mapfile) = getstyle(req, self.config,
164 164 self.templatepath)
165 165 if style == styles[0]:
166 166 vars['style'] = style
167 167
168 168 sessionvars = webutil.sessionvars(vars, '?')
169 169
170 170 if not self.reponame:
171 171 self.reponame = (self.config('web', 'name', '')
172 172 or req.reponame
173 173 or req.apppath
174 174 or self.repo.root)
175 175
176 176 def websubfilter(text):
177 177 return templatefilters.websub(text, self.websubtable)
178 178
179 179 # create the templater
180 180 # TODO: export all keywords: defaults = templatekw.keywords.copy()
181 181 defaults = {
182 182 'url': req.apppath + '/',
183 183 'logourl': logourl,
184 184 'logoimg': logoimg,
185 185 'staticurl': staticurl,
186 186 'urlbase': req.advertisedbaseurl,
187 187 'repo': self.reponame,
188 188 'encoding': encoding.encoding,
189 189 'motd': motd,
190 190 'sessionvars': sessionvars,
191 191 'pathdef': makebreadcrumb(req.apppath),
192 192 'style': style,
193 193 'nonce': self.nonce,
194 194 }
195 195 tres = formatter.templateresources(self.repo.ui, self.repo)
196 196 tmpl = templater.templater.frommapfile(mapfile,
197 197 filters={'websub': websubfilter},
198 198 defaults=defaults,
199 199 resources=tres)
200 200 return tmpl
201 201
202 202
203 203 class hgweb(object):
204 204 """HTTP server for individual repositories.
205 205
206 206 Instances of this class serve HTTP responses for a particular
207 207 repository.
208 208
209 209 Instances are typically used as WSGI applications.
210 210
211 211 Some servers are multi-threaded. On these servers, there may
212 212 be multiple active threads inside __call__.
213 213 """
214 214 def __init__(self, repo, name=None, baseui=None):
215 215 if isinstance(repo, str):
216 216 if baseui:
217 217 u = baseui.copy()
218 218 else:
219 219 u = uimod.ui.load()
220 220 r = hg.repository(u, repo)
221 221 else:
222 222 # we trust caller to give us a private copy
223 223 r = repo
224 224
225 225 r.ui.setconfig('ui', 'report_untrusted', 'off', 'hgweb')
226 226 r.baseui.setconfig('ui', 'report_untrusted', 'off', 'hgweb')
227 227 r.ui.setconfig('ui', 'nontty', 'true', 'hgweb')
228 228 r.baseui.setconfig('ui', 'nontty', 'true', 'hgweb')
229 229 # resolve file patterns relative to repo root
230 230 r.ui.setconfig('ui', 'forcecwd', r.root, 'hgweb')
231 231 r.baseui.setconfig('ui', 'forcecwd', r.root, 'hgweb')
232 232 # displaying bundling progress bar while serving feel wrong and may
233 233 # break some wsgi implementation.
234 234 r.ui.setconfig('progress', 'disable', 'true', 'hgweb')
235 235 r.baseui.setconfig('progress', 'disable', 'true', 'hgweb')
236 236 self._repos = [hg.cachedlocalrepo(self._webifyrepo(r))]
237 237 self._lastrepo = self._repos[0]
238 238 hook.redirect(True)
239 239 self.reponame = name
240 240
241 241 def _webifyrepo(self, repo):
242 242 repo = getwebview(repo)
243 243 self.websubtable = webutil.getwebsubs(repo)
244 244 return repo
245 245
246 246 @contextlib.contextmanager
247 247 def _obtainrepo(self):
248 248 """Obtain a repo unique to the caller.
249 249
250 250 Internally we maintain a stack of cachedlocalrepo instances
251 251 to be handed out. If one is available, we pop it and return it,
252 252 ensuring it is up to date in the process. If one is not available,
253 253 we clone the most recently used repo instance and return it.
254 254
255 255 It is currently possible for the stack to grow without bounds
256 256 if the server allows infinite threads. However, servers should
257 257 have a thread limit, thus establishing our limit.
258 258 """
259 259 if self._repos:
260 260 cached = self._repos.pop()
261 261 r, created = cached.fetch()
262 262 else:
263 263 cached = self._lastrepo.copy()
264 264 r, created = cached.fetch()
265 265 if created:
266 266 r = self._webifyrepo(r)
267 267
268 268 self._lastrepo = cached
269 269 self.mtime = cached.mtime
270 270 try:
271 271 yield r
272 272 finally:
273 273 self._repos.append(cached)
274 274
275 275 def run(self):
276 276 """Start a server from CGI environment.
277 277
278 278 Modern servers should be using WSGI and should avoid this
279 279 method, if possible.
280 280 """
281 281 if not encoding.environ.get('GATEWAY_INTERFACE',
282 282 '').startswith("CGI/1."):
283 283 raise RuntimeError("This function is only intended to be "
284 284 "called while running as a CGI script.")
285 285 wsgicgi.launch(self)
286 286
287 287 def __call__(self, env, respond):
288 288 """Run the WSGI application.
289 289
290 290 This may be called by multiple threads.
291 291 """
292 292 req = requestmod.wsgirequest(env, respond)
293 293 return self.run_wsgi(req)
294 294
295 295 def run_wsgi(self, wsgireq):
296 296 """Internal method to run the WSGI application.
297 297
298 298 This is typically only called by Mercurial. External consumers
299 299 should be using instances of this class as the WSGI application.
300 300 """
301 301 with self._obtainrepo() as repo:
302 302 profile = repo.ui.configbool('profiling', 'enabled')
303 303 with profiling.profile(repo.ui, enabled=profile):
304 304 for r in self._runwsgi(wsgireq, repo):
305 305 yield r
306 306
307 307 def _runwsgi(self, wsgireq, repo):
308 308 req = wsgireq.req
309 309 res = wsgireq.res
310 310 rctx = requestcontext(self, repo, req, res)
311 311
312 312 # This state is global across all threads.
313 313 encoding.encoding = rctx.config('web', 'encoding')
314 314 rctx.repo.ui.environ = wsgireq.env
315 315
316 316 if rctx.csp:
317 317 # hgwebdir may have added CSP header. Since we generate our own,
318 318 # replace it.
319 319 wsgireq.headers = [h for h in wsgireq.headers
320 320 if h[0] != 'Content-Security-Policy']
321 321 wsgireq.headers.append(('Content-Security-Policy', rctx.csp))
322 322 res.headers['Content-Security-Policy'] = rctx.csp
323 323
324 324 handled = wireprotoserver.handlewsgirequest(
325 rctx, wsgireq, req, res, self.check_perm)
325 rctx, req, res, self.check_perm)
326 326 if handled:
327 327 return res.sendresponse()
328 328
329 329 if req.havepathinfo:
330 330 query = req.dispatchpath
331 331 else:
332 332 query = req.querystring.partition('&')[0].partition(';')[0]
333 333
334 334 # translate user-visible url structure to internal structure
335 335
336 336 args = query.split('/', 2)
337 337 if 'cmd' not in req.qsparams and args and args[0]:
338 338 cmd = args.pop(0)
339 339 style = cmd.rfind('-')
340 340 if style != -1:
341 341 req.qsparams['style'] = cmd[:style]
342 342 cmd = cmd[style + 1:]
343 343
344 344 # avoid accepting e.g. style parameter as command
345 345 if util.safehasattr(webcommands, cmd):
346 346 req.qsparams['cmd'] = cmd
347 347
348 348 if cmd == 'static':
349 349 req.qsparams['file'] = '/'.join(args)
350 350 else:
351 351 if args and args[0]:
352 352 node = args.pop(0).replace('%2F', '/')
353 353 req.qsparams['node'] = node
354 354 if args:
355 355 if 'file' in req.qsparams:
356 356 del req.qsparams['file']
357 357 for a in args:
358 358 req.qsparams.add('file', a)
359 359
360 360 ua = req.headers.get('User-Agent', '')
361 361 if cmd == 'rev' and 'mercurial' in ua:
362 362 req.qsparams['style'] = 'raw'
363 363
364 364 if cmd == 'archive':
365 365 fn = req.qsparams['node']
366 366 for type_, spec in rctx.archivespecs.iteritems():
367 367 ext = spec[2]
368 368 if fn.endswith(ext):
369 369 req.qsparams['node'] = fn[:-len(ext)]
370 370 req.qsparams['type'] = type_
371 371 else:
372 372 cmd = req.qsparams.get('cmd', '')
373 373
374 374 # process the web interface request
375 375
376 376 try:
377 377 tmpl = rctx.templater(req)
378 378 ctype = tmpl('mimetype', encoding=encoding.encoding)
379 379 ctype = templater.stringify(ctype)
380 380
381 381 # check read permissions non-static content
382 382 if cmd != 'static':
383 self.check_perm(rctx, wsgireq, None)
383 self.check_perm(rctx, req, None)
384 384
385 385 if cmd == '':
386 386 req.qsparams['cmd'] = tmpl.cache['default']
387 387 cmd = req.qsparams['cmd']
388 388
389 389 # Don't enable caching if using a CSP nonce because then it wouldn't
390 390 # be a nonce.
391 391 if rctx.configbool('web', 'cache') and not rctx.nonce:
392 392 tag = 'W/"%d"' % self.mtime
393 393 if req.headers.get('If-None-Match') == tag:
394 394 raise ErrorResponse(HTTP_NOT_MODIFIED)
395 395
396 396 wsgireq.headers.append((r'ETag', pycompat.sysstr(tag)))
397 397 res.headers['ETag'] = tag
398 398
399 399 if cmd not in webcommands.__all__:
400 400 msg = 'no such method: %s' % cmd
401 401 raise ErrorResponse(HTTP_BAD_REQUEST, msg)
402 402 else:
403 403 # Set some globals appropriate for web handlers. Commands can
404 404 # override easily enough.
405 405 res.status = '200 Script output follows'
406 406 res.headers['Content-Type'] = ctype
407 407 content = getattr(webcommands, cmd)(rctx, wsgireq, tmpl)
408 408
409 409 if content is res:
410 410 return res.sendresponse()
411 411 elif content is True:
412 412 return []
413 413 else:
414 414 wsgireq.respond(HTTP_OK, ctype)
415 415 return content
416 416
417 417 except (error.LookupError, error.RepoLookupError) as err:
418 418 wsgireq.respond(HTTP_NOT_FOUND, ctype)
419 419 msg = pycompat.bytestr(err)
420 420 if (util.safehasattr(err, 'name') and
421 421 not isinstance(err, error.ManifestLookupError)):
422 422 msg = 'revision not found: %s' % err.name
423 423 return tmpl('error', error=msg)
424 424 except (error.RepoError, error.RevlogError) as inst:
425 425 wsgireq.respond(HTTP_SERVER_ERROR, ctype)
426 426 return tmpl('error', error=pycompat.bytestr(inst))
427 427 except ErrorResponse as inst:
428 428 wsgireq.respond(inst, ctype)
429 429 if inst.code == HTTP_NOT_MODIFIED:
430 430 # Not allowed to return a body on a 304
431 431 return ['']
432 432 return tmpl('error', error=pycompat.bytestr(inst))
433 433
434 434 def check_perm(self, rctx, req, op):
435 435 for permhook in permhooks:
436 436 permhook(rctx, req, op)
437 437
438 438 def getwebview(repo):
439 439 """The 'web.view' config controls changeset filter to hgweb. Possible
440 440 values are ``served``, ``visible`` and ``all``. Default is ``served``.
441 441 The ``served`` filter only shows changesets that can be pulled from the
442 442 hgweb instance. The``visible`` filter includes secret changesets but
443 443 still excludes "hidden" one.
444 444
445 445 See the repoview module for details.
446 446
447 447 The option has been around undocumented since Mercurial 2.5, but no
448 448 user ever asked about it. So we better keep it undocumented for now."""
449 449 # experimental config: web.view
450 450 viewconfig = repo.ui.config('web', 'view', untrusted=True)
451 451 if viewconfig == 'all':
452 452 return repo.unfiltered()
453 453 elif viewconfig in repoview.filtertable:
454 454 return repo.filtered(viewconfig)
455 455 else:
456 456 return repo.filtered('served')
@@ -1,654 +1,653 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 36 SSHV1 = wireprototypes.SSHV1
37 37 SSHV2 = wireprototypes.SSHV2
38 38
39 39 def decodevaluefromheaders(req, headerprefix):
40 40 """Decode a long value from multiple HTTP request headers.
41 41
42 42 Returns the value as a bytes, not a str.
43 43 """
44 44 chunks = []
45 45 i = 1
46 46 while True:
47 47 v = req.headers.get(b'%s-%d' % (headerprefix, i))
48 48 if v is None:
49 49 break
50 50 chunks.append(pycompat.bytesurl(v))
51 51 i += 1
52 52
53 53 return ''.join(chunks)
54 54
55 55 class httpv1protocolhandler(wireprototypes.baseprotocolhandler):
56 56 def __init__(self, req, ui, checkperm):
57 57 self._req = req
58 58 self._ui = ui
59 59 self._checkperm = checkperm
60 60
61 61 @property
62 62 def name(self):
63 63 return 'http-v1'
64 64
65 65 def getargs(self, args):
66 66 knownargs = self._args()
67 67 data = {}
68 68 keys = args.split()
69 69 for k in keys:
70 70 if k == '*':
71 71 star = {}
72 72 for key in knownargs.keys():
73 73 if key != 'cmd' and key not in keys:
74 74 star[key] = knownargs[key][0]
75 75 data['*'] = star
76 76 else:
77 77 data[k] = knownargs[k][0]
78 78 return [data[k] for k in keys]
79 79
80 80 def _args(self):
81 81 args = self._req.qsparams.asdictoflists()
82 82 postlen = int(self._req.headers.get(b'X-HgArgs-Post', 0))
83 83 if postlen:
84 84 args.update(urlreq.parseqs(
85 85 self._req.bodyfh.read(postlen), keep_blank_values=True))
86 86 return args
87 87
88 88 argvalue = decodevaluefromheaders(self._req, b'X-HgArg')
89 89 args.update(urlreq.parseqs(argvalue, keep_blank_values=True))
90 90 return args
91 91
92 92 def forwardpayload(self, fp):
93 93 # Existing clients *always* send Content-Length.
94 94 length = int(self._req.headers[b'Content-Length'])
95 95
96 96 # If httppostargs is used, we need to read Content-Length
97 97 # minus the amount that was consumed by args.
98 98 length -= int(self._req.headers.get(b'X-HgArgs-Post', 0))
99 99 for s in util.filechunkiter(self._req.bodyfh, limit=length):
100 100 fp.write(s)
101 101
102 102 @contextlib.contextmanager
103 103 def mayberedirectstdio(self):
104 104 oldout = self._ui.fout
105 105 olderr = self._ui.ferr
106 106
107 107 out = util.stringio()
108 108
109 109 try:
110 110 self._ui.fout = out
111 111 self._ui.ferr = out
112 112 yield out
113 113 finally:
114 114 self._ui.fout = oldout
115 115 self._ui.ferr = olderr
116 116
117 117 def client(self):
118 118 return 'remote:%s:%s:%s' % (
119 119 self._req.urlscheme,
120 120 urlreq.quote(self._req.remotehost or ''),
121 121 urlreq.quote(self._req.remoteuser or ''))
122 122
123 123 def addcapabilities(self, repo, caps):
124 124 caps.append('httpheader=%d' %
125 125 repo.ui.configint('server', 'maxhttpheaderlen'))
126 126 if repo.ui.configbool('experimental', 'httppostargs'):
127 127 caps.append('httppostargs')
128 128
129 129 # FUTURE advertise 0.2rx once support is implemented
130 130 # FUTURE advertise minrx and mintx after consulting config option
131 131 caps.append('httpmediatype=0.1rx,0.1tx,0.2tx')
132 132
133 133 compengines = wireproto.supportedcompengines(repo.ui, util.SERVERROLE)
134 134 if compengines:
135 135 comptypes = ','.join(urlreq.quote(e.wireprotosupport().name)
136 136 for e in compengines)
137 137 caps.append('compression=%s' % comptypes)
138 138
139 139 return caps
140 140
141 141 def checkperm(self, perm):
142 142 return self._checkperm(perm)
143 143
144 144 # This method exists mostly so that extensions like remotefilelog can
145 145 # disable a kludgey legacy method only over http. As of early 2018,
146 146 # there are no other known users, so with any luck we can discard this
147 147 # hook if remotefilelog becomes a first-party extension.
148 148 def iscmd(cmd):
149 149 return cmd in wireproto.commands
150 150
151 def handlewsgirequest(rctx, wsgireq, req, res, checkperm):
151 def handlewsgirequest(rctx, req, res, checkperm):
152 152 """Possibly process a wire protocol request.
153 153
154 154 If the current request is a wire protocol request, the request is
155 155 processed by this function.
156 156
157 ``wsgireq`` is a ``wsgirequest`` instance.
158 157 ``req`` is a ``parsedrequest`` instance.
159 158 ``res`` is a ``wsgiresponse`` instance.
160 159
161 160 Returns a bool indicating if the request was serviced. If set, the caller
162 161 should stop processing the request, as a response has already been issued.
163 162 """
164 163 # Avoid cycle involving hg module.
165 164 from .hgweb import common as hgwebcommon
166 165
167 166 repo = rctx.repo
168 167
169 168 # HTTP version 1 wire protocol requests are denoted by a "cmd" query
170 169 # string parameter. If it isn't present, this isn't a wire protocol
171 170 # request.
172 171 if 'cmd' not in req.qsparams:
173 172 return False
174 173
175 174 cmd = req.qsparams['cmd']
176 175
177 176 # The "cmd" request parameter is used by both the wire protocol and hgweb.
178 177 # While not all wire protocol commands are available for all transports,
179 178 # if we see a "cmd" value that resembles a known wire protocol command, we
180 179 # route it to a protocol handler. This is better than routing possible
181 180 # wire protocol requests to hgweb because it prevents hgweb from using
182 181 # known wire protocol commands and it is less confusing for machine
183 182 # clients.
184 183 if not iscmd(cmd):
185 184 return False
186 185
187 186 # The "cmd" query string argument is only valid on the root path of the
188 187 # repo. e.g. ``/?cmd=foo``, ``/repo?cmd=foo``. URL paths within the repo
189 188 # like ``/blah?cmd=foo`` are not allowed. So don't recognize the request
190 189 # in this case. We send an HTTP 404 for backwards compatibility reasons.
191 190 if req.dispatchpath:
192 191 res.status = hgwebcommon.statusmessage(404)
193 192 res.headers['Content-Type'] = HGTYPE
194 193 # TODO This is not a good response to issue for this request. This
195 194 # is mostly for BC for now.
196 195 res.setbodybytes('0\n%s\n' % b'Not Found')
197 196 return True
198 197
199 198 proto = httpv1protocolhandler(req, repo.ui,
200 lambda perm: checkperm(rctx, wsgireq, perm))
199 lambda perm: checkperm(rctx, req, perm))
201 200
202 201 # The permissions checker should be the only thing that can raise an
203 202 # ErrorResponse. It is kind of a layer violation to catch an hgweb
204 203 # exception here. So consider refactoring into a exception type that
205 204 # is associated with the wire protocol.
206 205 try:
207 206 _callhttp(repo, req, res, proto, cmd)
208 207 except hgwebcommon.ErrorResponse as e:
209 208 for k, v in e.headers:
210 209 res.headers[k] = v
211 210 res.status = hgwebcommon.statusmessage(e.code, pycompat.bytestr(e))
212 211 # TODO This response body assumes the failed command was
213 212 # "unbundle." That assumption is not always valid.
214 213 res.setbodybytes('0\n%s\n' % pycompat.bytestr(e))
215 214
216 215 return True
217 216
218 217 def _httpresponsetype(ui, req, prefer_uncompressed):
219 218 """Determine the appropriate response type and compression settings.
220 219
221 220 Returns a tuple of (mediatype, compengine, engineopts).
222 221 """
223 222 # Determine the response media type and compression engine based
224 223 # on the request parameters.
225 224 protocaps = decodevaluefromheaders(req, 'X-HgProto').split(' ')
226 225
227 226 if '0.2' in protocaps:
228 227 # All clients are expected to support uncompressed data.
229 228 if prefer_uncompressed:
230 229 return HGTYPE2, util._noopengine(), {}
231 230
232 231 # Default as defined by wire protocol spec.
233 232 compformats = ['zlib', 'none']
234 233 for cap in protocaps:
235 234 if cap.startswith('comp='):
236 235 compformats = cap[5:].split(',')
237 236 break
238 237
239 238 # Now find an agreed upon compression format.
240 239 for engine in wireproto.supportedcompengines(ui, util.SERVERROLE):
241 240 if engine.wireprotosupport().name in compformats:
242 241 opts = {}
243 242 level = ui.configint('server', '%slevel' % engine.name())
244 243 if level is not None:
245 244 opts['level'] = level
246 245
247 246 return HGTYPE2, engine, opts
248 247
249 248 # No mutually supported compression format. Fall back to the
250 249 # legacy protocol.
251 250
252 251 # Don't allow untrusted settings because disabling compression or
253 252 # setting a very high compression level could lead to flooding
254 253 # the server's network or CPU.
255 254 opts = {'level': ui.configint('server', 'zliblevel')}
256 255 return HGTYPE, util.compengines['zlib'], opts
257 256
258 257 def _callhttp(repo, req, res, proto, cmd):
259 258 # Avoid cycle involving hg module.
260 259 from .hgweb import common as hgwebcommon
261 260
262 261 def genversion2(gen, engine, engineopts):
263 262 # application/mercurial-0.2 always sends a payload header
264 263 # identifying the compression engine.
265 264 name = engine.wireprotosupport().name
266 265 assert 0 < len(name) < 256
267 266 yield struct.pack('B', len(name))
268 267 yield name
269 268
270 269 for chunk in gen:
271 270 yield chunk
272 271
273 272 def setresponse(code, contenttype, bodybytes=None, bodygen=None):
274 273 if code == HTTP_OK:
275 274 res.status = '200 Script output follows'
276 275 else:
277 276 res.status = hgwebcommon.statusmessage(code)
278 277
279 278 res.headers['Content-Type'] = contenttype
280 279
281 280 if bodybytes is not None:
282 281 res.setbodybytes(bodybytes)
283 282 if bodygen is not None:
284 283 res.setbodygen(bodygen)
285 284
286 285 if not wireproto.commands.commandavailable(cmd, proto):
287 286 setresponse(HTTP_OK, HGERRTYPE,
288 287 _('requested wire protocol command is not available over '
289 288 'HTTP'))
290 289 return
291 290
292 291 proto.checkperm(wireproto.commands[cmd].permission)
293 292
294 293 rsp = wireproto.dispatch(repo, proto, cmd)
295 294
296 295 if isinstance(rsp, bytes):
297 296 setresponse(HTTP_OK, HGTYPE, bodybytes=rsp)
298 297 elif isinstance(rsp, wireprototypes.bytesresponse):
299 298 setresponse(HTTP_OK, HGTYPE, bodybytes=rsp.data)
300 299 elif isinstance(rsp, wireprototypes.streamreslegacy):
301 300 setresponse(HTTP_OK, HGTYPE, bodygen=rsp.gen)
302 301 elif isinstance(rsp, wireprototypes.streamres):
303 302 gen = rsp.gen
304 303
305 304 # This code for compression should not be streamres specific. It
306 305 # is here because we only compress streamres at the moment.
307 306 mediatype, engine, engineopts = _httpresponsetype(
308 307 repo.ui, req, rsp.prefer_uncompressed)
309 308 gen = engine.compressstream(gen, engineopts)
310 309
311 310 if mediatype == HGTYPE2:
312 311 gen = genversion2(gen, engine, engineopts)
313 312
314 313 setresponse(HTTP_OK, mediatype, bodygen=gen)
315 314 elif isinstance(rsp, wireprototypes.pushres):
316 315 rsp = '%d\n%s' % (rsp.res, rsp.output)
317 316 setresponse(HTTP_OK, HGTYPE, bodybytes=rsp)
318 317 elif isinstance(rsp, wireprototypes.pusherr):
319 318 rsp = '0\n%s\n' % rsp.res
320 319 res.drain = True
321 320 setresponse(HTTP_OK, HGTYPE, bodybytes=rsp)
322 321 elif isinstance(rsp, wireprototypes.ooberror):
323 322 setresponse(HTTP_OK, HGERRTYPE, bodybytes=rsp.message)
324 323 else:
325 324 raise error.ProgrammingError('hgweb.protocol internal failure', rsp)
326 325
327 326 def _sshv1respondbytes(fout, value):
328 327 """Send a bytes response for protocol version 1."""
329 328 fout.write('%d\n' % len(value))
330 329 fout.write(value)
331 330 fout.flush()
332 331
333 332 def _sshv1respondstream(fout, source):
334 333 write = fout.write
335 334 for chunk in source.gen:
336 335 write(chunk)
337 336 fout.flush()
338 337
339 338 def _sshv1respondooberror(fout, ferr, rsp):
340 339 ferr.write(b'%s\n-\n' % rsp)
341 340 ferr.flush()
342 341 fout.write(b'\n')
343 342 fout.flush()
344 343
345 344 class sshv1protocolhandler(wireprototypes.baseprotocolhandler):
346 345 """Handler for requests services via version 1 of SSH protocol."""
347 346 def __init__(self, ui, fin, fout):
348 347 self._ui = ui
349 348 self._fin = fin
350 349 self._fout = fout
351 350
352 351 @property
353 352 def name(self):
354 353 return wireprototypes.SSHV1
355 354
356 355 def getargs(self, args):
357 356 data = {}
358 357 keys = args.split()
359 358 for n in xrange(len(keys)):
360 359 argline = self._fin.readline()[:-1]
361 360 arg, l = argline.split()
362 361 if arg not in keys:
363 362 raise error.Abort(_("unexpected parameter %r") % arg)
364 363 if arg == '*':
365 364 star = {}
366 365 for k in xrange(int(l)):
367 366 argline = self._fin.readline()[:-1]
368 367 arg, l = argline.split()
369 368 val = self._fin.read(int(l))
370 369 star[arg] = val
371 370 data['*'] = star
372 371 else:
373 372 val = self._fin.read(int(l))
374 373 data[arg] = val
375 374 return [data[k] for k in keys]
376 375
377 376 def forwardpayload(self, fpout):
378 377 # We initially send an empty response. This tells the client it is
379 378 # OK to start sending data. If a client sees any other response, it
380 379 # interprets it as an error.
381 380 _sshv1respondbytes(self._fout, b'')
382 381
383 382 # The file is in the form:
384 383 #
385 384 # <chunk size>\n<chunk>
386 385 # ...
387 386 # 0\n
388 387 count = int(self._fin.readline())
389 388 while count:
390 389 fpout.write(self._fin.read(count))
391 390 count = int(self._fin.readline())
392 391
393 392 @contextlib.contextmanager
394 393 def mayberedirectstdio(self):
395 394 yield None
396 395
397 396 def client(self):
398 397 client = encoding.environ.get('SSH_CLIENT', '').split(' ', 1)[0]
399 398 return 'remote:ssh:' + client
400 399
401 400 def addcapabilities(self, repo, caps):
402 401 return caps
403 402
404 403 def checkperm(self, perm):
405 404 pass
406 405
407 406 class sshv2protocolhandler(sshv1protocolhandler):
408 407 """Protocol handler for version 2 of the SSH protocol."""
409 408
410 409 @property
411 410 def name(self):
412 411 return wireprototypes.SSHV2
413 412
414 413 def _runsshserver(ui, repo, fin, fout, ev):
415 414 # This function operates like a state machine of sorts. The following
416 415 # states are defined:
417 416 #
418 417 # protov1-serving
419 418 # Server is in protocol version 1 serving mode. Commands arrive on
420 419 # new lines. These commands are processed in this state, one command
421 420 # after the other.
422 421 #
423 422 # protov2-serving
424 423 # Server is in protocol version 2 serving mode.
425 424 #
426 425 # upgrade-initial
427 426 # The server is going to process an upgrade request.
428 427 #
429 428 # upgrade-v2-filter-legacy-handshake
430 429 # The protocol is being upgraded to version 2. The server is expecting
431 430 # the legacy handshake from version 1.
432 431 #
433 432 # upgrade-v2-finish
434 433 # The upgrade to version 2 of the protocol is imminent.
435 434 #
436 435 # shutdown
437 436 # The server is shutting down, possibly in reaction to a client event.
438 437 #
439 438 # And here are their transitions:
440 439 #
441 440 # protov1-serving -> shutdown
442 441 # When server receives an empty request or encounters another
443 442 # error.
444 443 #
445 444 # protov1-serving -> upgrade-initial
446 445 # An upgrade request line was seen.
447 446 #
448 447 # upgrade-initial -> upgrade-v2-filter-legacy-handshake
449 448 # Upgrade to version 2 in progress. Server is expecting to
450 449 # process a legacy handshake.
451 450 #
452 451 # upgrade-v2-filter-legacy-handshake -> shutdown
453 452 # Client did not fulfill upgrade handshake requirements.
454 453 #
455 454 # upgrade-v2-filter-legacy-handshake -> upgrade-v2-finish
456 455 # Client fulfilled version 2 upgrade requirements. Finishing that
457 456 # upgrade.
458 457 #
459 458 # upgrade-v2-finish -> protov2-serving
460 459 # Protocol upgrade to version 2 complete. Server can now speak protocol
461 460 # version 2.
462 461 #
463 462 # protov2-serving -> protov1-serving
464 463 # Ths happens by default since protocol version 2 is the same as
465 464 # version 1 except for the handshake.
466 465
467 466 state = 'protov1-serving'
468 467 proto = sshv1protocolhandler(ui, fin, fout)
469 468 protoswitched = False
470 469
471 470 while not ev.is_set():
472 471 if state == 'protov1-serving':
473 472 # Commands are issued on new lines.
474 473 request = fin.readline()[:-1]
475 474
476 475 # Empty lines signal to terminate the connection.
477 476 if not request:
478 477 state = 'shutdown'
479 478 continue
480 479
481 480 # It looks like a protocol upgrade request. Transition state to
482 481 # handle it.
483 482 if request.startswith(b'upgrade '):
484 483 if protoswitched:
485 484 _sshv1respondooberror(fout, ui.ferr,
486 485 b'cannot upgrade protocols multiple '
487 486 b'times')
488 487 state = 'shutdown'
489 488 continue
490 489
491 490 state = 'upgrade-initial'
492 491 continue
493 492
494 493 available = wireproto.commands.commandavailable(request, proto)
495 494
496 495 # This command isn't available. Send an empty response and go
497 496 # back to waiting for a new command.
498 497 if not available:
499 498 _sshv1respondbytes(fout, b'')
500 499 continue
501 500
502 501 rsp = wireproto.dispatch(repo, proto, request)
503 502
504 503 if isinstance(rsp, bytes):
505 504 _sshv1respondbytes(fout, rsp)
506 505 elif isinstance(rsp, wireprototypes.bytesresponse):
507 506 _sshv1respondbytes(fout, rsp.data)
508 507 elif isinstance(rsp, wireprototypes.streamres):
509 508 _sshv1respondstream(fout, rsp)
510 509 elif isinstance(rsp, wireprototypes.streamreslegacy):
511 510 _sshv1respondstream(fout, rsp)
512 511 elif isinstance(rsp, wireprototypes.pushres):
513 512 _sshv1respondbytes(fout, b'')
514 513 _sshv1respondbytes(fout, b'%d' % rsp.res)
515 514 elif isinstance(rsp, wireprototypes.pusherr):
516 515 _sshv1respondbytes(fout, rsp.res)
517 516 elif isinstance(rsp, wireprototypes.ooberror):
518 517 _sshv1respondooberror(fout, ui.ferr, rsp.message)
519 518 else:
520 519 raise error.ProgrammingError('unhandled response type from '
521 520 'wire protocol command: %s' % rsp)
522 521
523 522 # For now, protocol version 2 serving just goes back to version 1.
524 523 elif state == 'protov2-serving':
525 524 state = 'protov1-serving'
526 525 continue
527 526
528 527 elif state == 'upgrade-initial':
529 528 # We should never transition into this state if we've switched
530 529 # protocols.
531 530 assert not protoswitched
532 531 assert proto.name == wireprototypes.SSHV1
533 532
534 533 # Expected: upgrade <token> <capabilities>
535 534 # If we get something else, the request is malformed. It could be
536 535 # from a future client that has altered the upgrade line content.
537 536 # We treat this as an unknown command.
538 537 try:
539 538 token, caps = request.split(b' ')[1:]
540 539 except ValueError:
541 540 _sshv1respondbytes(fout, b'')
542 541 state = 'protov1-serving'
543 542 continue
544 543
545 544 # Send empty response if we don't support upgrading protocols.
546 545 if not ui.configbool('experimental', 'sshserver.support-v2'):
547 546 _sshv1respondbytes(fout, b'')
548 547 state = 'protov1-serving'
549 548 continue
550 549
551 550 try:
552 551 caps = urlreq.parseqs(caps)
553 552 except ValueError:
554 553 _sshv1respondbytes(fout, b'')
555 554 state = 'protov1-serving'
556 555 continue
557 556
558 557 # We don't see an upgrade request to protocol version 2. Ignore
559 558 # the upgrade request.
560 559 wantedprotos = caps.get(b'proto', [b''])[0]
561 560 if SSHV2 not in wantedprotos:
562 561 _sshv1respondbytes(fout, b'')
563 562 state = 'protov1-serving'
564 563 continue
565 564
566 565 # It looks like we can honor this upgrade request to protocol 2.
567 566 # Filter the rest of the handshake protocol request lines.
568 567 state = 'upgrade-v2-filter-legacy-handshake'
569 568 continue
570 569
571 570 elif state == 'upgrade-v2-filter-legacy-handshake':
572 571 # Client should have sent legacy handshake after an ``upgrade``
573 572 # request. Expected lines:
574 573 #
575 574 # hello
576 575 # between
577 576 # pairs 81
578 577 # 0000...-0000...
579 578
580 579 ok = True
581 580 for line in (b'hello', b'between', b'pairs 81'):
582 581 request = fin.readline()[:-1]
583 582
584 583 if request != line:
585 584 _sshv1respondooberror(fout, ui.ferr,
586 585 b'malformed handshake protocol: '
587 586 b'missing %s' % line)
588 587 ok = False
589 588 state = 'shutdown'
590 589 break
591 590
592 591 if not ok:
593 592 continue
594 593
595 594 request = fin.read(81)
596 595 if request != b'%s-%s' % (b'0' * 40, b'0' * 40):
597 596 _sshv1respondooberror(fout, ui.ferr,
598 597 b'malformed handshake protocol: '
599 598 b'missing between argument value')
600 599 state = 'shutdown'
601 600 continue
602 601
603 602 state = 'upgrade-v2-finish'
604 603 continue
605 604
606 605 elif state == 'upgrade-v2-finish':
607 606 # Send the upgrade response.
608 607 fout.write(b'upgraded %s %s\n' % (token, SSHV2))
609 608 servercaps = wireproto.capabilities(repo, proto)
610 609 rsp = b'capabilities: %s' % servercaps.data
611 610 fout.write(b'%d\n%s\n' % (len(rsp), rsp))
612 611 fout.flush()
613 612
614 613 proto = sshv2protocolhandler(ui, fin, fout)
615 614 protoswitched = True
616 615
617 616 state = 'protov2-serving'
618 617 continue
619 618
620 619 elif state == 'shutdown':
621 620 break
622 621
623 622 else:
624 623 raise error.ProgrammingError('unhandled ssh server state: %s' %
625 624 state)
626 625
627 626 class sshserver(object):
628 627 def __init__(self, ui, repo, logfh=None):
629 628 self._ui = ui
630 629 self._repo = repo
631 630 self._fin = ui.fin
632 631 self._fout = ui.fout
633 632
634 633 # Log write I/O to stdout and stderr if configured.
635 634 if logfh:
636 635 self._fout = util.makeloggingfileobject(
637 636 logfh, self._fout, 'o', logdata=True)
638 637 ui.ferr = util.makeloggingfileobject(
639 638 logfh, ui.ferr, 'e', logdata=True)
640 639
641 640 hook.redirect(True)
642 641 ui.fout = repo.ui.fout = ui.ferr
643 642
644 643 # Prevent insertion/deletion of CRs
645 644 util.setbinary(self._fin)
646 645 util.setbinary(self._fout)
647 646
648 647 def serve_forever(self):
649 648 self.serveuntil(threading.Event())
650 649 sys.exit(0)
651 650
652 651 def serveuntil(self, ev):
653 652 """Serve until a threading.Event is set."""
654 653 _runsshserver(self._ui, self._repo, self._fin, self._fout, ev)
@@ -1,404 +1,404 b''
1 1 #require serve
2 2
3 3 This test is a duplicate of 'test-http.t', feel free to factor out
4 4 parts that are not bundle1/bundle2 specific.
5 5
6 6 $ cat << EOF >> $HGRCPATH
7 7 > [devel]
8 8 > # This test is dedicated to interaction through old bundle
9 9 > legacy.exchange = bundle1
10 10 > EOF
11 11
12 12 $ hg init test
13 13 $ cd test
14 14 $ echo foo>foo
15 15 $ mkdir foo.d foo.d/bAr.hg.d foo.d/baR.d.hg
16 16 $ echo foo>foo.d/foo
17 17 $ echo bar>foo.d/bAr.hg.d/BaR
18 18 $ echo bar>foo.d/baR.d.hg/bAR
19 19 $ hg commit -A -m 1
20 20 adding foo
21 21 adding foo.d/bAr.hg.d/BaR
22 22 adding foo.d/baR.d.hg/bAR
23 23 adding foo.d/foo
24 24 $ hg serve -p $HGPORT -d --pid-file=../hg1.pid -E ../error.log
25 25 $ hg serve --config server.uncompressed=False -p $HGPORT1 -d --pid-file=../hg2.pid
26 26
27 27 Test server address cannot be reused
28 28
29 29 $ hg serve -p $HGPORT1 2>&1
30 30 abort: cannot start server at 'localhost:$HGPORT1': $EADDRINUSE$
31 31 [255]
32 32
33 33 $ cd ..
34 34 $ cat hg1.pid hg2.pid >> $DAEMON_PIDS
35 35
36 36 clone via stream
37 37
38 38 $ hg clone --stream http://localhost:$HGPORT/ copy 2>&1
39 39 streaming all changes
40 40 6 files to transfer, 606 bytes of data
41 41 transferred * bytes in * seconds (*/sec) (glob)
42 42 searching for changes
43 43 no changes found
44 44 updating to branch default
45 45 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
46 46 $ hg verify -R copy
47 47 checking changesets
48 48 checking manifests
49 49 crosschecking files in changesets and manifests
50 50 checking files
51 51 4 files, 1 changesets, 4 total revisions
52 52
53 53 try to clone via stream, should use pull instead
54 54
55 55 $ hg clone --stream http://localhost:$HGPORT1/ copy2
56 56 warning: stream clone requested but server has them disabled
57 57 requesting all changes
58 58 adding changesets
59 59 adding manifests
60 60 adding file changes
61 61 added 1 changesets with 4 changes to 4 files
62 62 new changesets 8b6053c928fe
63 63 updating to branch default
64 64 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
65 65
66 66 try to clone via stream but missing requirements, so should use pull instead
67 67
68 68 $ cat > $TESTTMP/removesupportedformat.py << EOF
69 69 > from mercurial import localrepo
70 70 > def extsetup(ui):
71 71 > localrepo.localrepository.supportedformats.remove(b'generaldelta')
72 72 > EOF
73 73
74 74 $ hg clone --config extensions.rsf=$TESTTMP/removesupportedformat.py --stream http://localhost:$HGPORT/ copy3
75 75 warning: stream clone requested but client is missing requirements: generaldelta
76 76 (see https://www.mercurial-scm.org/wiki/MissingRequirement for more information)
77 77 requesting all changes
78 78 adding changesets
79 79 adding manifests
80 80 adding file changes
81 81 added 1 changesets with 4 changes to 4 files
82 82 new changesets 8b6053c928fe
83 83 updating to branch default
84 84 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
85 85
86 86 clone via pull
87 87
88 88 $ hg clone http://localhost:$HGPORT1/ copy-pull
89 89 requesting all changes
90 90 adding changesets
91 91 adding manifests
92 92 adding file changes
93 93 added 1 changesets with 4 changes to 4 files
94 94 new changesets 8b6053c928fe
95 95 updating to branch default
96 96 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
97 97 $ hg verify -R copy-pull
98 98 checking changesets
99 99 checking manifests
100 100 crosschecking files in changesets and manifests
101 101 checking files
102 102 4 files, 1 changesets, 4 total revisions
103 103 $ cd test
104 104 $ echo bar > bar
105 105 $ hg commit -A -d '1 0' -m 2
106 106 adding bar
107 107 $ cd ..
108 108
109 109 clone over http with --update
110 110
111 111 $ hg clone http://localhost:$HGPORT1/ updated --update 0
112 112 requesting all changes
113 113 adding changesets
114 114 adding manifests
115 115 adding file changes
116 116 added 2 changesets with 5 changes to 5 files
117 117 new changesets 8b6053c928fe:5fed3813f7f5
118 118 updating to branch default
119 119 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
120 120 $ hg log -r . -R updated
121 121 changeset: 0:8b6053c928fe
122 122 user: test
123 123 date: Thu Jan 01 00:00:00 1970 +0000
124 124 summary: 1
125 125
126 126 $ rm -rf updated
127 127
128 128 incoming via HTTP
129 129
130 130 $ hg clone http://localhost:$HGPORT1/ --rev 0 partial
131 131 adding changesets
132 132 adding manifests
133 133 adding file changes
134 134 added 1 changesets with 4 changes to 4 files
135 135 new changesets 8b6053c928fe
136 136 updating to branch default
137 137 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
138 138 $ cd partial
139 139 $ touch LOCAL
140 140 $ hg ci -qAm LOCAL
141 141 $ hg incoming http://localhost:$HGPORT1/ --template '{desc}\n'
142 142 comparing with http://localhost:$HGPORT1/
143 143 searching for changes
144 144 2
145 145 $ cd ..
146 146
147 147 pull
148 148
149 149 $ cd copy-pull
150 150 $ cat >> .hg/hgrc <<EOF
151 151 > [hooks]
152 152 > changegroup = sh -c "printenv.py changegroup"
153 153 > EOF
154 154 $ hg pull
155 155 pulling from http://localhost:$HGPORT1/
156 156 searching for changes
157 157 adding changesets
158 158 adding manifests
159 159 adding file changes
160 160 added 1 changesets with 1 changes to 1 files
161 161 new changesets 5fed3813f7f5
162 162 changegroup hook: HG_HOOKNAME=changegroup HG_HOOKTYPE=changegroup HG_NODE=5fed3813f7f5e1824344fdc9cf8f63bb662c292d HG_NODE_LAST=5fed3813f7f5e1824344fdc9cf8f63bb662c292d HG_SOURCE=pull HG_TXNID=TXN:$ID$ HG_URL=http://localhost:$HGPORT1/
163 163 (run 'hg update' to get a working copy)
164 164 $ cd ..
165 165
166 166 clone from invalid URL
167 167
168 168 $ hg clone http://localhost:$HGPORT/bad
169 169 abort: HTTP Error 404: Not Found
170 170 [255]
171 171
172 172 test http authentication
173 173 + use the same server to test server side streaming preference
174 174
175 175 $ cd test
176 176 $ cat << EOT > userpass.py
177 177 > import base64
178 178 > from mercurial.hgweb import common
179 179 > def perform_authentication(hgweb, req, op):
180 > auth = req.env.get('HTTP_AUTHORIZATION')
180 > auth = req.headers.get('Authorization')
181 181 > if not auth:
182 182 > raise common.ErrorResponse(common.HTTP_UNAUTHORIZED, 'who',
183 183 > [('WWW-Authenticate', 'Basic Realm="mercurial"')])
184 184 > if base64.b64decode(auth.split()[1]).split(b':', 1) != [b'user',
185 185 > b'pass']:
186 186 > raise common.ErrorResponse(common.HTTP_FORBIDDEN, 'no')
187 187 > def extsetup():
188 188 > common.permhooks.insert(0, perform_authentication)
189 189 > EOT
190 190 $ hg serve --config extensions.x=userpass.py -p $HGPORT2 -d --pid-file=pid \
191 191 > --config server.preferuncompressed=True \
192 192 > --config web.push_ssl=False --config web.allow_push=* -A ../access.log
193 193 $ cat pid >> $DAEMON_PIDS
194 194
195 195 $ cat << EOF > get_pass.py
196 196 > import getpass
197 197 > def newgetpass(arg):
198 198 > return "pass"
199 199 > getpass.getpass = newgetpass
200 200 > EOF
201 201
202 202 $ hg id http://localhost:$HGPORT2/
203 203 abort: http authorization required for http://localhost:$HGPORT2/
204 204 [255]
205 205 $ hg id http://localhost:$HGPORT2/
206 206 abort: http authorization required for http://localhost:$HGPORT2/
207 207 [255]
208 208 $ hg id --config ui.interactive=true --config extensions.getpass=get_pass.py http://user@localhost:$HGPORT2/
209 209 http authorization required for http://localhost:$HGPORT2/
210 210 realm: mercurial
211 211 user: user
212 212 password: 5fed3813f7f5
213 213 $ hg id http://user:pass@localhost:$HGPORT2/
214 214 5fed3813f7f5
215 215 $ echo '[auth]' >> .hg/hgrc
216 216 $ echo 'l.schemes=http' >> .hg/hgrc
217 217 $ echo 'l.prefix=lo' >> .hg/hgrc
218 218 $ echo 'l.username=user' >> .hg/hgrc
219 219 $ echo 'l.password=pass' >> .hg/hgrc
220 220 $ hg id http://localhost:$HGPORT2/
221 221 5fed3813f7f5
222 222 $ hg id http://localhost:$HGPORT2/
223 223 5fed3813f7f5
224 224 $ hg id http://user@localhost:$HGPORT2/
225 225 5fed3813f7f5
226 226 $ hg clone http://user:pass@localhost:$HGPORT2/ dest 2>&1
227 227 streaming all changes
228 228 7 files to transfer, 916 bytes of data
229 229 transferred * bytes in * seconds (*/sec) (glob)
230 230 searching for changes
231 231 no changes found
232 232 updating to branch default
233 233 5 files updated, 0 files merged, 0 files removed, 0 files unresolved
234 234 --pull should override server's preferuncompressed
235 235 $ hg clone --pull http://user:pass@localhost:$HGPORT2/ dest-pull 2>&1
236 236 requesting all changes
237 237 adding changesets
238 238 adding manifests
239 239 adding file changes
240 240 added 2 changesets with 5 changes to 5 files
241 241 new changesets 8b6053c928fe:5fed3813f7f5
242 242 updating to branch default
243 243 5 files updated, 0 files merged, 0 files removed, 0 files unresolved
244 244
245 245 $ hg id http://user2@localhost:$HGPORT2/
246 246 abort: http authorization required for http://localhost:$HGPORT2/
247 247 [255]
248 248 $ hg id http://user:pass2@localhost:$HGPORT2/
249 249 abort: HTTP Error 403: no
250 250 [255]
251 251
252 252 $ hg -R dest tag -r tip top
253 253 $ hg -R dest push http://user:pass@localhost:$HGPORT2/
254 254 pushing to http://user:***@localhost:$HGPORT2/
255 255 searching for changes
256 256 remote: adding changesets
257 257 remote: adding manifests
258 258 remote: adding file changes
259 259 remote: added 1 changesets with 1 changes to 1 files
260 260 $ hg rollback -q
261 261
262 262 $ sed 's/.*] "/"/' < ../access.log
263 263 "GET /?cmd=capabilities HTTP/1.1" 401 -
264 264 "GET /?cmd=capabilities HTTP/1.1" 401 -
265 265 "GET /?cmd=capabilities HTTP/1.1" 401 -
266 266 "GET /?cmd=capabilities HTTP/1.1" 200 -
267 267 "GET /?cmd=lookup HTTP/1.1" 200 - x-hgarg-1:key=tip x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
268 268 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=namespaces x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
269 269 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
270 270 "GET /?cmd=capabilities HTTP/1.1" 401 -
271 271 "GET /?cmd=capabilities HTTP/1.1" 200 -
272 272 "GET /?cmd=lookup HTTP/1.1" 200 - x-hgarg-1:key=tip x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
273 273 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=namespaces x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
274 274 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
275 275 "GET /?cmd=capabilities HTTP/1.1" 401 -
276 276 "GET /?cmd=capabilities HTTP/1.1" 200 -
277 277 "GET /?cmd=lookup HTTP/1.1" 200 - x-hgarg-1:key=tip x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
278 278 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=namespaces x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
279 279 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
280 280 "GET /?cmd=capabilities HTTP/1.1" 401 -
281 281 "GET /?cmd=capabilities HTTP/1.1" 200 -
282 282 "GET /?cmd=lookup HTTP/1.1" 200 - x-hgarg-1:key=tip x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
283 283 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=namespaces x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
284 284 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
285 285 "GET /?cmd=capabilities HTTP/1.1" 401 -
286 286 "GET /?cmd=capabilities HTTP/1.1" 200 -
287 287 "GET /?cmd=lookup HTTP/1.1" 200 - x-hgarg-1:key=tip x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
288 288 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=namespaces x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
289 289 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
290 290 "GET /?cmd=capabilities HTTP/1.1" 401 -
291 291 "GET /?cmd=capabilities HTTP/1.1" 200 -
292 292 "GET /?cmd=branchmap HTTP/1.1" 200 - x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
293 293 "GET /?cmd=stream_out HTTP/1.1" 200 - x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
294 294 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
295 295 "GET /?cmd=batch HTTP/1.1" 200 - x-hgarg-1:cmds=heads+%3Bknown+nodes%3D5fed3813f7f5e1824344fdc9cf8f63bb662c292d x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
296 296 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=phases x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
297 297 "GET /?cmd=capabilities HTTP/1.1" 401 -
298 298 "GET /?cmd=capabilities HTTP/1.1" 200 -
299 299 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
300 300 "GET /?cmd=batch HTTP/1.1" 200 - x-hgarg-1:cmds=heads+%3Bknown+nodes%3D x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
301 301 "GET /?cmd=getbundle HTTP/1.1" 200 - x-hgarg-1:common=0000000000000000000000000000000000000000&heads=5fed3813f7f5e1824344fdc9cf8f63bb662c292d x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
302 302 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=phases x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
303 303 "GET /?cmd=capabilities HTTP/1.1" 401 -
304 304 "GET /?cmd=capabilities HTTP/1.1" 401 -
305 305 "GET /?cmd=capabilities HTTP/1.1" 403 -
306 306 "GET /?cmd=capabilities HTTP/1.1" 401 -
307 307 "GET /?cmd=capabilities HTTP/1.1" 200 -
308 308 "GET /?cmd=batch HTTP/1.1" 200 - x-hgarg-1:cmds=heads+%3Bknown+nodes%3D7f4e523d01f2cc3765ac8934da3d14db775ff872 x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
309 309 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=phases x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
310 310 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
311 311 "GET /?cmd=branchmap HTTP/1.1" 200 - x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
312 312 "GET /?cmd=branchmap HTTP/1.1" 200 - x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
313 313 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
314 314 "POST /?cmd=unbundle HTTP/1.1" 200 - x-hgarg-1:heads=686173686564+5eb5abfefeea63c80dd7553bcc3783f37e0c5524* (glob)
315 315 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=phases x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
316 316
317 317 $ cd ..
318 318
319 319 clone of serve with repo in root and unserved subrepo (issue2970)
320 320
321 321 $ hg --cwd test init sub
322 322 $ echo empty > test/sub/empty
323 323 $ hg --cwd test/sub add empty
324 324 $ hg --cwd test/sub commit -qm 'add empty'
325 325 $ hg --cwd test/sub tag -r 0 something
326 326 $ echo sub = sub > test/.hgsub
327 327 $ hg --cwd test add .hgsub
328 328 $ hg --cwd test commit -qm 'add subrepo'
329 329 $ hg clone http://localhost:$HGPORT noslash-clone
330 330 requesting all changes
331 331 adding changesets
332 332 adding manifests
333 333 adding file changes
334 334 added 3 changesets with 7 changes to 7 files
335 335 new changesets 8b6053c928fe:56f9bc90cce6
336 336 updating to branch default
337 337 abort: HTTP Error 404: Not Found
338 338 [255]
339 339 $ hg clone http://localhost:$HGPORT/ slash-clone
340 340 requesting all changes
341 341 adding changesets
342 342 adding manifests
343 343 adding file changes
344 344 added 3 changesets with 7 changes to 7 files
345 345 new changesets 8b6053c928fe:56f9bc90cce6
346 346 updating to branch default
347 347 abort: HTTP Error 404: Not Found
348 348 [255]
349 349
350 350 check error log
351 351
352 352 $ cat error.log
353 353
354 354 Check error reporting while pulling/cloning
355 355
356 356 $ $RUNTESTDIR/killdaemons.py
357 357 $ hg serve -R test -p $HGPORT -d --pid-file=hg3.pid -E error.log --config extensions.crash=${TESTDIR}/crashgetbundler.py
358 358 $ cat hg3.pid >> $DAEMON_PIDS
359 359 $ hg clone http://localhost:$HGPORT/ abort-clone
360 360 requesting all changes
361 361 abort: remote error:
362 362 this is an exercise
363 363 [255]
364 364 $ cat error.log
365 365
366 366 disable pull-based clones
367 367
368 368 $ hg serve -R test -p $HGPORT1 -d --pid-file=hg4.pid -E error.log --config server.disablefullbundle=True
369 369 $ cat hg4.pid >> $DAEMON_PIDS
370 370 $ hg clone http://localhost:$HGPORT1/ disable-pull-clone
371 371 requesting all changes
372 372 abort: remote error:
373 373 server has pull-based clones disabled
374 374 [255]
375 375
376 376 ... but keep stream clones working
377 377
378 378 $ hg clone --stream --noupdate http://localhost:$HGPORT1/ test-stream-clone
379 379 streaming all changes
380 380 * files to transfer, * of data (glob)
381 381 transferred * in * seconds (* KB/sec) (glob)
382 382 searching for changes
383 383 no changes found
384 384
385 385 ... and also keep partial clones and pulls working
386 386 $ hg clone http://localhost:$HGPORT1 --rev 0 test-partial-clone
387 387 adding changesets
388 388 adding manifests
389 389 adding file changes
390 390 added 1 changesets with 4 changes to 4 files
391 391 new changesets 8b6053c928fe
392 392 updating to branch default
393 393 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
394 394 $ hg pull -R test-partial-clone
395 395 pulling from http://localhost:$HGPORT1/
396 396 searching for changes
397 397 adding changesets
398 398 adding manifests
399 399 adding file changes
400 400 added 2 changesets with 3 changes to 3 files
401 401 new changesets 5fed3813f7f5:56f9bc90cce6
402 402 (run 'hg update' to get a working copy)
403 403
404 404 $ cat error.log
@@ -1,552 +1,552 b''
1 1 #require killdaemons serve
2 2
3 3 $ hg init test
4 4 $ cd test
5 5 $ echo foo>foo
6 6 $ mkdir foo.d foo.d/bAr.hg.d foo.d/baR.d.hg
7 7 $ echo foo>foo.d/foo
8 8 $ echo bar>foo.d/bAr.hg.d/BaR
9 9 $ echo bar>foo.d/baR.d.hg/bAR
10 10 $ hg commit -A -m 1
11 11 adding foo
12 12 adding foo.d/bAr.hg.d/BaR
13 13 adding foo.d/baR.d.hg/bAR
14 14 adding foo.d/foo
15 15 $ hg serve -p $HGPORT -d --pid-file=../hg1.pid -E ../error.log
16 16 $ hg serve --config server.uncompressed=False -p $HGPORT1 -d --pid-file=../hg2.pid
17 17
18 18 Test server address cannot be reused
19 19
20 20 $ hg serve -p $HGPORT1 2>&1
21 21 abort: cannot start server at 'localhost:$HGPORT1': $EADDRINUSE$
22 22 [255]
23 23
24 24 $ cd ..
25 25 $ cat hg1.pid hg2.pid >> $DAEMON_PIDS
26 26
27 27 clone via stream
28 28
29 29 $ hg clone --stream http://localhost:$HGPORT/ copy 2>&1
30 30 streaming all changes
31 31 6 files to transfer, 606 bytes of data
32 32 transferred * bytes in * seconds (*/sec) (glob)
33 33 searching for changes
34 34 no changes found
35 35 updating to branch default
36 36 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
37 37 $ hg verify -R copy
38 38 checking changesets
39 39 checking manifests
40 40 crosschecking files in changesets and manifests
41 41 checking files
42 42 4 files, 1 changesets, 4 total revisions
43 43
44 44 try to clone via stream, should use pull instead
45 45
46 46 $ hg clone --stream http://localhost:$HGPORT1/ copy2
47 47 warning: stream clone requested but server has them disabled
48 48 requesting all changes
49 49 adding changesets
50 50 adding manifests
51 51 adding file changes
52 52 added 1 changesets with 4 changes to 4 files
53 53 new changesets 8b6053c928fe
54 54 updating to branch default
55 55 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
56 56
57 57 try to clone via stream but missing requirements, so should use pull instead
58 58
59 59 $ cat > $TESTTMP/removesupportedformat.py << EOF
60 60 > from mercurial import localrepo
61 61 > def extsetup(ui):
62 62 > localrepo.localrepository.supportedformats.remove('generaldelta')
63 63 > EOF
64 64
65 65 $ hg clone --config extensions.rsf=$TESTTMP/removesupportedformat.py --stream http://localhost:$HGPORT/ copy3
66 66 warning: stream clone requested but client is missing requirements: generaldelta
67 67 (see https://www.mercurial-scm.org/wiki/MissingRequirement for more information)
68 68 requesting all changes
69 69 adding changesets
70 70 adding manifests
71 71 adding file changes
72 72 added 1 changesets with 4 changes to 4 files
73 73 new changesets 8b6053c928fe
74 74 updating to branch default
75 75 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
76 76
77 77 clone via pull
78 78
79 79 $ hg clone http://localhost:$HGPORT1/ copy-pull
80 80 requesting all changes
81 81 adding changesets
82 82 adding manifests
83 83 adding file changes
84 84 added 1 changesets with 4 changes to 4 files
85 85 new changesets 8b6053c928fe
86 86 updating to branch default
87 87 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
88 88 $ hg verify -R copy-pull
89 89 checking changesets
90 90 checking manifests
91 91 crosschecking files in changesets and manifests
92 92 checking files
93 93 4 files, 1 changesets, 4 total revisions
94 94 $ cd test
95 95 $ echo bar > bar
96 96 $ hg commit -A -d '1 0' -m 2
97 97 adding bar
98 98 $ cd ..
99 99
100 100 clone over http with --update
101 101
102 102 $ hg clone http://localhost:$HGPORT1/ updated --update 0
103 103 requesting all changes
104 104 adding changesets
105 105 adding manifests
106 106 adding file changes
107 107 added 2 changesets with 5 changes to 5 files
108 108 new changesets 8b6053c928fe:5fed3813f7f5
109 109 updating to branch default
110 110 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
111 111 $ hg log -r . -R updated
112 112 changeset: 0:8b6053c928fe
113 113 user: test
114 114 date: Thu Jan 01 00:00:00 1970 +0000
115 115 summary: 1
116 116
117 117 $ rm -rf updated
118 118
119 119 incoming via HTTP
120 120
121 121 $ hg clone http://localhost:$HGPORT1/ --rev 0 partial
122 122 adding changesets
123 123 adding manifests
124 124 adding file changes
125 125 added 1 changesets with 4 changes to 4 files
126 126 new changesets 8b6053c928fe
127 127 updating to branch default
128 128 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
129 129 $ cd partial
130 130 $ touch LOCAL
131 131 $ hg ci -qAm LOCAL
132 132 $ hg incoming http://localhost:$HGPORT1/ --template '{desc}\n'
133 133 comparing with http://localhost:$HGPORT1/
134 134 searching for changes
135 135 2
136 136 $ cd ..
137 137
138 138 pull
139 139
140 140 $ cd copy-pull
141 141 $ cat >> .hg/hgrc <<EOF
142 142 > [hooks]
143 143 > changegroup = sh -c "printenv.py changegroup"
144 144 > EOF
145 145 $ hg pull
146 146 pulling from http://localhost:$HGPORT1/
147 147 searching for changes
148 148 adding changesets
149 149 adding manifests
150 150 adding file changes
151 151 added 1 changesets with 1 changes to 1 files
152 152 new changesets 5fed3813f7f5
153 153 changegroup hook: HG_HOOKNAME=changegroup HG_HOOKTYPE=changegroup HG_NODE=5fed3813f7f5e1824344fdc9cf8f63bb662c292d HG_NODE_LAST=5fed3813f7f5e1824344fdc9cf8f63bb662c292d HG_SOURCE=pull HG_TXNID=TXN:$ID$ HG_URL=http://localhost:$HGPORT1/
154 154 (run 'hg update' to get a working copy)
155 155 $ cd ..
156 156
157 157 clone from invalid URL
158 158
159 159 $ hg clone http://localhost:$HGPORT/bad
160 160 abort: HTTP Error 404: Not Found
161 161 [255]
162 162
163 163 test http authentication
164 164 + use the same server to test server side streaming preference
165 165
166 166 $ cd test
167 167 $ cat << EOT > userpass.py
168 168 > import base64
169 169 > from mercurial.hgweb import common
170 170 > def perform_authentication(hgweb, req, op):
171 > auth = req.env.get('HTTP_AUTHORIZATION')
171 > auth = req.headers.get('Authorization')
172 172 > if not auth:
173 173 > raise common.ErrorResponse(common.HTTP_UNAUTHORIZED, 'who',
174 174 > [('WWW-Authenticate', 'Basic Realm="mercurial"')])
175 175 > if base64.b64decode(auth.split()[1]).split(':', 1) != ['user', 'pass']:
176 176 > raise common.ErrorResponse(common.HTTP_FORBIDDEN, 'no')
177 177 > def extsetup():
178 178 > common.permhooks.insert(0, perform_authentication)
179 179 > EOT
180 180 $ hg serve --config extensions.x=userpass.py -p $HGPORT2 -d --pid-file=pid \
181 181 > --config server.preferuncompressed=True \
182 182 > --config web.push_ssl=False --config web.allow_push=* -A ../access.log
183 183 $ cat pid >> $DAEMON_PIDS
184 184
185 185 $ cat << EOF > get_pass.py
186 186 > import getpass
187 187 > def newgetpass(arg):
188 188 > return "pass"
189 189 > getpass.getpass = newgetpass
190 190 > EOF
191 191
192 192 $ hg id http://localhost:$HGPORT2/
193 193 abort: http authorization required for http://localhost:$HGPORT2/
194 194 [255]
195 195 $ hg id http://localhost:$HGPORT2/
196 196 abort: http authorization required for http://localhost:$HGPORT2/
197 197 [255]
198 198 $ hg id --config ui.interactive=true --config extensions.getpass=get_pass.py http://user@localhost:$HGPORT2/
199 199 http authorization required for http://localhost:$HGPORT2/
200 200 realm: mercurial
201 201 user: user
202 202 password: 5fed3813f7f5
203 203 $ hg id http://user:pass@localhost:$HGPORT2/
204 204 5fed3813f7f5
205 205 $ echo '[auth]' >> .hg/hgrc
206 206 $ echo 'l.schemes=http' >> .hg/hgrc
207 207 $ echo 'l.prefix=lo' >> .hg/hgrc
208 208 $ echo 'l.username=user' >> .hg/hgrc
209 209 $ echo 'l.password=pass' >> .hg/hgrc
210 210 $ hg id http://localhost:$HGPORT2/
211 211 5fed3813f7f5
212 212 $ hg id http://localhost:$HGPORT2/
213 213 5fed3813f7f5
214 214 $ hg id http://user@localhost:$HGPORT2/
215 215 5fed3813f7f5
216 216 $ hg clone http://user:pass@localhost:$HGPORT2/ dest 2>&1
217 217 streaming all changes
218 218 7 files to transfer, 916 bytes of data
219 219 transferred * bytes in * seconds (*/sec) (glob)
220 220 searching for changes
221 221 no changes found
222 222 updating to branch default
223 223 5 files updated, 0 files merged, 0 files removed, 0 files unresolved
224 224 --pull should override server's preferuncompressed
225 225 $ hg clone --pull http://user:pass@localhost:$HGPORT2/ dest-pull 2>&1
226 226 requesting all changes
227 227 adding changesets
228 228 adding manifests
229 229 adding file changes
230 230 added 2 changesets with 5 changes to 5 files
231 231 new changesets 8b6053c928fe:5fed3813f7f5
232 232 updating to branch default
233 233 5 files updated, 0 files merged, 0 files removed, 0 files unresolved
234 234
235 235 $ hg id http://user2@localhost:$HGPORT2/
236 236 abort: http authorization required for http://localhost:$HGPORT2/
237 237 [255]
238 238 $ hg id http://user:pass2@localhost:$HGPORT2/
239 239 abort: HTTP Error 403: no
240 240 [255]
241 241
242 242 $ hg -R dest tag -r tip top
243 243 $ hg -R dest push http://user:pass@localhost:$HGPORT2/
244 244 pushing to http://user:***@localhost:$HGPORT2/
245 245 searching for changes
246 246 remote: adding changesets
247 247 remote: adding manifests
248 248 remote: adding file changes
249 249 remote: added 1 changesets with 1 changes to 1 files
250 250 $ hg rollback -q
251 251 $ hg -R dest push http://user:pass@localhost:$HGPORT2/ --debug --config devel.debug.peer-request=yes
252 252 pushing to http://user:***@localhost:$HGPORT2/
253 253 using http://localhost:$HGPORT2/
254 254 http auth: user user, password ****
255 255 sending capabilities command
256 256 devel-peer-request: GET http://localhost:$HGPORT2/?cmd=capabilities
257 257 http auth: user user, password ****
258 258 devel-peer-request: finished in *.???? seconds (200) (glob)
259 259 query 1; heads
260 260 sending batch command
261 261 devel-peer-request: GET http://localhost:$HGPORT2/?cmd=batch
262 262 devel-peer-request: Vary X-HgArg-1,X-HgProto-1
263 263 devel-peer-request: X-hgproto-1 0.1 0.2 comp=$USUAL_COMPRESSIONS$
264 264 devel-peer-request: 68 bytes of commands arguments in headers
265 265 devel-peer-request: finished in *.???? seconds (200) (glob)
266 266 searching for changes
267 267 all remote heads known locally
268 268 preparing listkeys for "phases"
269 269 sending listkeys command
270 270 devel-peer-request: GET http://localhost:$HGPORT2/?cmd=listkeys
271 271 devel-peer-request: Vary X-HgArg-1,X-HgProto-1
272 272 devel-peer-request: X-hgproto-1 0.1 0.2 comp=$USUAL_COMPRESSIONS$
273 273 devel-peer-request: 16 bytes of commands arguments in headers
274 274 devel-peer-request: finished in *.???? seconds (200) (glob)
275 275 received listkey for "phases": 58 bytes
276 276 checking for updated bookmarks
277 277 preparing listkeys for "bookmarks"
278 278 sending listkeys command
279 279 devel-peer-request: GET http://localhost:$HGPORT2/?cmd=listkeys
280 280 devel-peer-request: Vary X-HgArg-1,X-HgProto-1
281 281 devel-peer-request: X-hgproto-1 0.1 0.2 comp=$USUAL_COMPRESSIONS$
282 282 devel-peer-request: 19 bytes of commands arguments in headers
283 283 devel-peer-request: finished in *.???? seconds (200) (glob)
284 284 received listkey for "bookmarks": 0 bytes
285 285 sending branchmap command
286 286 devel-peer-request: GET http://localhost:$HGPORT2/?cmd=branchmap
287 287 devel-peer-request: Vary X-HgProto-1
288 288 devel-peer-request: X-hgproto-1 0.1 0.2 comp=$USUAL_COMPRESSIONS$
289 289 devel-peer-request: finished in *.???? seconds (200) (glob)
290 290 sending branchmap command
291 291 devel-peer-request: GET http://localhost:$HGPORT2/?cmd=branchmap
292 292 devel-peer-request: Vary X-HgProto-1
293 293 devel-peer-request: X-hgproto-1 0.1 0.2 comp=$USUAL_COMPRESSIONS$
294 294 devel-peer-request: finished in *.???? seconds (200) (glob)
295 295 preparing listkeys for "bookmarks"
296 296 sending listkeys command
297 297 devel-peer-request: GET http://localhost:$HGPORT2/?cmd=listkeys
298 298 devel-peer-request: Vary X-HgArg-1,X-HgProto-1
299 299 devel-peer-request: X-hgproto-1 0.1 0.2 comp=$USUAL_COMPRESSIONS$
300 300 devel-peer-request: 19 bytes of commands arguments in headers
301 301 devel-peer-request: finished in *.???? seconds (200) (glob)
302 302 received listkey for "bookmarks": 0 bytes
303 303 1 changesets found
304 304 list of changesets:
305 305 7f4e523d01f2cc3765ac8934da3d14db775ff872
306 306 bundle2-output-bundle: "HG20", 5 parts total
307 307 bundle2-output-part: "replycaps" 188 bytes payload
308 308 bundle2-output-part: "check:phases" 24 bytes payload
309 309 bundle2-output-part: "check:heads" streamed payload
310 310 bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
311 311 bundle2-output-part: "phase-heads" 24 bytes payload
312 312 sending unbundle command
313 313 sending 996 bytes
314 314 devel-peer-request: POST http://localhost:$HGPORT2/?cmd=unbundle
315 315 devel-peer-request: Content-length 996
316 316 devel-peer-request: Content-type application/mercurial-0.1
317 317 devel-peer-request: Vary X-HgArg-1,X-HgProto-1
318 318 devel-peer-request: X-hgproto-1 0.1 0.2 comp=$USUAL_COMPRESSIONS$
319 319 devel-peer-request: 16 bytes of commands arguments in headers
320 320 devel-peer-request: 996 bytes of data
321 321 devel-peer-request: finished in *.???? seconds (200) (glob)
322 322 bundle2-input-bundle: no-transaction
323 323 bundle2-input-part: "reply:changegroup" (advisory) (params: 0 advisory) supported
324 324 bundle2-input-part: "output" (advisory) (params: 0 advisory) supported
325 325 bundle2-input-part: total payload size 100
326 326 remote: adding changesets
327 327 remote: adding manifests
328 328 remote: adding file changes
329 329 remote: added 1 changesets with 1 changes to 1 files
330 330 bundle2-input-part: "output" (advisory) supported
331 331 bundle2-input-bundle: 2 parts total
332 332 preparing listkeys for "phases"
333 333 sending listkeys command
334 334 devel-peer-request: GET http://localhost:$HGPORT2/?cmd=listkeys
335 335 devel-peer-request: Vary X-HgArg-1,X-HgProto-1
336 336 devel-peer-request: X-hgproto-1 0.1 0.2 comp=$USUAL_COMPRESSIONS$
337 337 devel-peer-request: 16 bytes of commands arguments in headers
338 338 devel-peer-request: finished in *.???? seconds (200) (glob)
339 339 received listkey for "phases": 15 bytes
340 340 $ hg rollback -q
341 341
342 342 $ sed 's/.*] "/"/' < ../access.log
343 343 "GET /?cmd=capabilities HTTP/1.1" 401 -
344 344 "GET /?cmd=capabilities HTTP/1.1" 401 -
345 345 "GET /?cmd=capabilities HTTP/1.1" 401 -
346 346 "GET /?cmd=capabilities HTTP/1.1" 200 -
347 347 "GET /?cmd=lookup HTTP/1.1" 200 - x-hgarg-1:key=tip x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
348 348 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=namespaces x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
349 349 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
350 350 "GET /?cmd=capabilities HTTP/1.1" 401 -
351 351 "GET /?cmd=capabilities HTTP/1.1" 200 -
352 352 "GET /?cmd=lookup HTTP/1.1" 200 - x-hgarg-1:key=tip x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
353 353 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=namespaces x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
354 354 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
355 355 "GET /?cmd=capabilities HTTP/1.1" 401 -
356 356 "GET /?cmd=capabilities HTTP/1.1" 200 -
357 357 "GET /?cmd=lookup HTTP/1.1" 200 - x-hgarg-1:key=tip x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
358 358 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=namespaces x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
359 359 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
360 360 "GET /?cmd=capabilities HTTP/1.1" 401 -
361 361 "GET /?cmd=capabilities HTTP/1.1" 200 -
362 362 "GET /?cmd=lookup HTTP/1.1" 200 - x-hgarg-1:key=tip x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
363 363 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=namespaces x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
364 364 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
365 365 "GET /?cmd=capabilities HTTP/1.1" 401 -
366 366 "GET /?cmd=capabilities HTTP/1.1" 200 -
367 367 "GET /?cmd=lookup HTTP/1.1" 200 - x-hgarg-1:key=tip x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
368 368 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=namespaces x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
369 369 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
370 370 "GET /?cmd=capabilities HTTP/1.1" 401 -
371 371 "GET /?cmd=capabilities HTTP/1.1" 200 -
372 372 "GET /?cmd=branchmap HTTP/1.1" 200 - x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
373 373 "GET /?cmd=stream_out HTTP/1.1" 200 - x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
374 374 "GET /?cmd=batch HTTP/1.1" 200 - x-hgarg-1:cmds=heads+%3Bknown+nodes%3D5fed3813f7f5e1824344fdc9cf8f63bb662c292d x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
375 375 "GET /?cmd=getbundle HTTP/1.1" 200 - x-hgarg-1:bookmarks=1&$USUAL_BUNDLE_CAPS$&cg=0&common=5fed3813f7f5e1824344fdc9cf8f63bb662c292d&heads=5fed3813f7f5e1824344fdc9cf8f63bb662c292d&listkeys=bookmarks&phases=1 x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
376 376 "GET /?cmd=capabilities HTTP/1.1" 401 -
377 377 "GET /?cmd=capabilities HTTP/1.1" 200 -
378 378 "GET /?cmd=batch HTTP/1.1" 200 - x-hgarg-1:cmds=heads+%3Bknown+nodes%3D x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
379 379 "GET /?cmd=getbundle HTTP/1.1" 200 - x-hgarg-1:bookmarks=1&$USUAL_BUNDLE_CAPS$&cg=1&common=0000000000000000000000000000000000000000&heads=5fed3813f7f5e1824344fdc9cf8f63bb662c292d&listkeys=bookmarks&phases=1 x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
380 380 "GET /?cmd=capabilities HTTP/1.1" 401 -
381 381 "GET /?cmd=capabilities HTTP/1.1" 401 -
382 382 "GET /?cmd=capabilities HTTP/1.1" 403 -
383 383 "GET /?cmd=capabilities HTTP/1.1" 401 -
384 384 "GET /?cmd=capabilities HTTP/1.1" 200 -
385 385 "GET /?cmd=batch HTTP/1.1" 200 - x-hgarg-1:cmds=heads+%3Bknown+nodes%3D7f4e523d01f2cc3765ac8934da3d14db775ff872 x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
386 386 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=phases x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
387 387 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
388 388 "GET /?cmd=branchmap HTTP/1.1" 200 - x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
389 389 "GET /?cmd=branchmap HTTP/1.1" 200 - x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
390 390 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
391 391 "POST /?cmd=unbundle HTTP/1.1" 200 - x-hgarg-1:heads=666f726365* (glob)
392 392 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=phases x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
393 393 "GET /?cmd=capabilities HTTP/1.1" 401 -
394 394 "GET /?cmd=capabilities HTTP/1.1" 200 -
395 395 "GET /?cmd=batch HTTP/1.1" 200 - x-hgarg-1:cmds=heads+%3Bknown+nodes%3D7f4e523d01f2cc3765ac8934da3d14db775ff872 x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
396 396 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=phases x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
397 397 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
398 398 "GET /?cmd=branchmap HTTP/1.1" 200 - x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
399 399 "GET /?cmd=branchmap HTTP/1.1" 200 - x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
400 400 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
401 401 "POST /?cmd=unbundle HTTP/1.1" 200 - x-hgarg-1:heads=666f726365 x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
402 402 "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=phases x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
403 403
404 404 $ cd ..
405 405
406 406 clone of serve with repo in root and unserved subrepo (issue2970)
407 407
408 408 $ hg --cwd test init sub
409 409 $ echo empty > test/sub/empty
410 410 $ hg --cwd test/sub add empty
411 411 $ hg --cwd test/sub commit -qm 'add empty'
412 412 $ hg --cwd test/sub tag -r 0 something
413 413 $ echo sub = sub > test/.hgsub
414 414 $ hg --cwd test add .hgsub
415 415 $ hg --cwd test commit -qm 'add subrepo'
416 416 $ hg clone http://localhost:$HGPORT noslash-clone
417 417 requesting all changes
418 418 adding changesets
419 419 adding manifests
420 420 adding file changes
421 421 added 3 changesets with 7 changes to 7 files
422 422 new changesets 8b6053c928fe:56f9bc90cce6
423 423 updating to branch default
424 424 abort: HTTP Error 404: Not Found
425 425 [255]
426 426 $ hg clone http://localhost:$HGPORT/ slash-clone
427 427 requesting all changes
428 428 adding changesets
429 429 adding manifests
430 430 adding file changes
431 431 added 3 changesets with 7 changes to 7 files
432 432 new changesets 8b6053c928fe:56f9bc90cce6
433 433 updating to branch default
434 434 abort: HTTP Error 404: Not Found
435 435 [255]
436 436
437 437 check error log
438 438
439 439 $ cat error.log
440 440
441 441 check abort error reporting while pulling/cloning
442 442
443 443 $ $RUNTESTDIR/killdaemons.py
444 444 $ hg serve -R test -p $HGPORT -d --pid-file=hg3.pid -E error.log --config extensions.crash=${TESTDIR}/crashgetbundler.py
445 445 $ cat hg3.pid >> $DAEMON_PIDS
446 446 $ hg clone http://localhost:$HGPORT/ abort-clone
447 447 requesting all changes
448 448 remote: abort: this is an exercise
449 449 abort: pull failed on remote
450 450 [255]
451 451 $ cat error.log
452 452
453 453 disable pull-based clones
454 454
455 455 $ hg serve -R test -p $HGPORT1 -d --pid-file=hg4.pid -E error.log --config server.disablefullbundle=True
456 456 $ cat hg4.pid >> $DAEMON_PIDS
457 457 $ hg clone http://localhost:$HGPORT1/ disable-pull-clone
458 458 requesting all changes
459 459 remote: abort: server has pull-based clones disabled
460 460 abort: pull failed on remote
461 461 (remove --pull if specified or upgrade Mercurial)
462 462 [255]
463 463
464 464 ... but keep stream clones working
465 465
466 466 $ hg clone --stream --noupdate http://localhost:$HGPORT1/ test-stream-clone
467 467 streaming all changes
468 468 * files to transfer, * of data (glob)
469 469 transferred * in * seconds (*/sec) (glob)
470 470 searching for changes
471 471 no changes found
472 472 $ cat error.log
473 473
474 474 ... and also keep partial clones and pulls working
475 475 $ hg clone http://localhost:$HGPORT1 --rev 0 test-partial-clone
476 476 adding changesets
477 477 adding manifests
478 478 adding file changes
479 479 added 1 changesets with 4 changes to 4 files
480 480 new changesets 8b6053c928fe
481 481 updating to branch default
482 482 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
483 483 $ hg pull -R test-partial-clone
484 484 pulling from http://localhost:$HGPORT1/
485 485 searching for changes
486 486 adding changesets
487 487 adding manifests
488 488 adding file changes
489 489 added 2 changesets with 3 changes to 3 files
490 490 new changesets 5fed3813f7f5:56f9bc90cce6
491 491 (run 'hg update' to get a working copy)
492 492
493 493 corrupt cookies file should yield a warning
494 494
495 495 $ cat > $TESTTMP/cookies.txt << EOF
496 496 > bad format
497 497 > EOF
498 498
499 499 $ hg --config auth.cookiefile=$TESTTMP/cookies.txt id http://localhost:$HGPORT/
500 500 (error loading cookie file $TESTTMP/cookies.txt: '*/cookies.txt' does not look like a Netscape format cookies file; continuing without cookies) (glob)
501 501 56f9bc90cce6
502 502
503 503 $ killdaemons.py
504 504
505 505 Create dummy authentication handler that looks for cookies. It doesn't do anything
506 506 useful. It just raises an HTTP 500 with details about the Cookie request header.
507 507 We raise HTTP 500 because its message is printed in the abort message.
508 508
509 509 $ cat > cookieauth.py << EOF
510 510 > from mercurial import util
511 511 > from mercurial.hgweb import common
512 512 > def perform_authentication(hgweb, req, op):
513 > cookie = req.env.get('HTTP_COOKIE')
513 > cookie = req.headers.get('Cookie')
514 514 > if not cookie:
515 515 > raise common.ErrorResponse(common.HTTP_SERVER_ERROR, 'no-cookie')
516 516 > raise common.ErrorResponse(common.HTTP_SERVER_ERROR, 'Cookie: %s' % cookie)
517 517 > def extsetup():
518 518 > common.permhooks.insert(0, perform_authentication)
519 519 > EOF
520 520
521 521 $ hg serve --config extensions.cookieauth=cookieauth.py -R test -p $HGPORT -d --pid-file=pid
522 522 $ cat pid > $DAEMON_PIDS
523 523
524 524 Request without cookie sent should fail due to lack of cookie
525 525
526 526 $ hg id http://localhost:$HGPORT
527 527 abort: HTTP Error 500: no-cookie
528 528 [255]
529 529
530 530 Populate a cookies file
531 531
532 532 $ cat > cookies.txt << EOF
533 533 > # HTTP Cookie File
534 534 > # Expiration is 2030-01-01 at midnight
535 535 > .example.com TRUE / FALSE 1893456000 hgkey examplevalue
536 536 > EOF
537 537
538 538 Should not send a cookie for another domain
539 539
540 540 $ hg --config auth.cookiefile=cookies.txt id http://localhost:$HGPORT/
541 541 abort: HTTP Error 500: no-cookie
542 542 [255]
543 543
544 544 Add a cookie entry for our test server and verify it is sent
545 545
546 546 $ cat >> cookies.txt << EOF
547 547 > localhost.local FALSE / FALSE 1893456000 hgkey localhostvalue
548 548 > EOF
549 549
550 550 $ hg --config auth.cookiefile=cookies.txt id http://localhost:$HGPORT/
551 551 abort: HTTP Error 500: Cookie: hgkey=localhostvalue
552 552 [255]
@@ -1,464 +1,464 b''
1 1 #testcases sshv1 sshv2
2 2
3 3 #if sshv2
4 4 $ cat >> $HGRCPATH << EOF
5 5 > [experimental]
6 6 > sshpeer.advertise-v2 = true
7 7 > sshserver.support-v2 = true
8 8 > EOF
9 9 #endif
10 10
11 11 This file contains testcases that tend to be related to the wire protocol part
12 12 of largefiles.
13 13
14 14 $ USERCACHE="$TESTTMP/cache"; export USERCACHE
15 15 $ mkdir "${USERCACHE}"
16 16 $ cat >> $HGRCPATH <<EOF
17 17 > [extensions]
18 18 > largefiles=
19 19 > purge=
20 20 > rebase=
21 21 > transplant=
22 22 > [phases]
23 23 > publish=False
24 24 > [largefiles]
25 25 > minsize=2
26 26 > patterns=glob:**.dat
27 27 > usercache=${USERCACHE}
28 28 > [web]
29 29 > allow_archive = zip
30 30 > [hooks]
31 31 > precommit=sh -c "echo \\"Invoking status precommit hook\\"; hg status"
32 32 > EOF
33 33
34 34
35 35 #if serve
36 36 vanilla clients not locked out from largefiles servers on vanilla repos
37 37 $ mkdir r1
38 38 $ cd r1
39 39 $ hg init
40 40 $ echo c1 > f1
41 41 $ hg add f1
42 42 $ hg commit -m "m1"
43 43 Invoking status precommit hook
44 44 A f1
45 45 $ cd ..
46 46 $ hg serve -R r1 -d -p $HGPORT --pid-file hg.pid
47 47 $ cat hg.pid >> $DAEMON_PIDS
48 48 $ hg --config extensions.largefiles=! clone http://localhost:$HGPORT r2
49 49 requesting all changes
50 50 adding changesets
51 51 adding manifests
52 52 adding file changes
53 53 added 1 changesets with 1 changes to 1 files
54 54 new changesets b6eb3a2e2efe
55 55 updating to branch default
56 56 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
57 57
58 58 largefiles clients still work with vanilla servers
59 59 $ hg serve --config extensions.largefiles=! -R r1 -d -p $HGPORT1 --pid-file hg.pid
60 60 $ cat hg.pid >> $DAEMON_PIDS
61 61 $ hg clone http://localhost:$HGPORT1 r3
62 62 requesting all changes
63 63 adding changesets
64 64 adding manifests
65 65 adding file changes
66 66 added 1 changesets with 1 changes to 1 files
67 67 new changesets b6eb3a2e2efe
68 68 updating to branch default
69 69 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
70 70 #endif
71 71
72 72 vanilla clients locked out from largefiles http repos
73 73 $ mkdir r4
74 74 $ cd r4
75 75 $ hg init
76 76 $ echo c1 > f1
77 77 $ hg add --large f1
78 78 $ hg commit -m "m1"
79 79 Invoking status precommit hook
80 80 A f1
81 81 $ cd ..
82 82
83 83 largefiles can be pushed locally (issue3583)
84 84 $ hg init dest
85 85 $ cd r4
86 86 $ hg outgoing ../dest
87 87 comparing with ../dest
88 88 searching for changes
89 89 changeset: 0:639881c12b4c
90 90 tag: tip
91 91 user: test
92 92 date: Thu Jan 01 00:00:00 1970 +0000
93 93 summary: m1
94 94
95 95 $ hg push ../dest
96 96 pushing to ../dest
97 97 searching for changes
98 98 adding changesets
99 99 adding manifests
100 100 adding file changes
101 101 added 1 changesets with 1 changes to 1 files
102 102
103 103 exit code with nothing outgoing (issue3611)
104 104 $ hg outgoing ../dest
105 105 comparing with ../dest
106 106 searching for changes
107 107 no changes found
108 108 [1]
109 109 $ cd ..
110 110
111 111 #if serve
112 112 $ hg serve -R r4 -d -p $HGPORT2 --pid-file hg.pid
113 113 $ cat hg.pid >> $DAEMON_PIDS
114 114 $ hg --config extensions.largefiles=! clone http://localhost:$HGPORT2 r5
115 115 abort: remote error:
116 116
117 117 This repository uses the largefiles extension.
118 118
119 119 Please enable it in your Mercurial config file.
120 120 [255]
121 121
122 122 used all HGPORTs, kill all daemons
123 123 $ killdaemons.py
124 124 #endif
125 125
126 126 vanilla clients locked out from largefiles ssh repos
127 127 $ hg --config extensions.largefiles=! clone -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" ssh://user@dummy/r4 r5
128 128 remote:
129 129 remote: This repository uses the largefiles extension.
130 130 remote:
131 131 remote: Please enable it in your Mercurial config file.
132 132 remote:
133 133 remote: -
134 134 abort: remote error
135 135 (check previous remote output)
136 136 [255]
137 137
138 138 #if serve
139 139
140 140 largefiles clients refuse to push largefiles repos to vanilla servers
141 141 $ mkdir r6
142 142 $ cd r6
143 143 $ hg init
144 144 $ echo c1 > f1
145 145 $ hg add f1
146 146 $ hg commit -m "m1"
147 147 Invoking status precommit hook
148 148 A f1
149 149 $ cat >> .hg/hgrc <<!
150 150 > [web]
151 151 > push_ssl = false
152 152 > allow_push = *
153 153 > !
154 154 $ cd ..
155 155 $ hg clone r6 r7
156 156 updating to branch default
157 157 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
158 158 $ cd r7
159 159 $ echo c2 > f2
160 160 $ hg add --large f2
161 161 $ hg commit -m "m2"
162 162 Invoking status precommit hook
163 163 A f2
164 164 $ hg verify --large
165 165 checking changesets
166 166 checking manifests
167 167 crosschecking files in changesets and manifests
168 168 checking files
169 169 2 files, 2 changesets, 2 total revisions
170 170 searching 1 changesets for largefiles
171 171 verified existence of 1 revisions of 1 largefiles
172 172 $ hg serve --config extensions.largefiles=! -R ../r6 -d -p $HGPORT --pid-file ../hg.pid
173 173 $ cat ../hg.pid >> $DAEMON_PIDS
174 174 $ hg push http://localhost:$HGPORT
175 175 pushing to http://localhost:$HGPORT/
176 176 searching for changes
177 177 abort: http://localhost:$HGPORT/ does not appear to be a largefile store
178 178 [255]
179 179 $ cd ..
180 180
181 181 putlfile errors are shown (issue3123)
182 182 Corrupt the cached largefile in r7 and move it out of the servers usercache
183 183 $ mv r7/.hg/largefiles/4cdac4d8b084d0b599525cf732437fb337d422a8 .
184 184 $ echo 'client side corruption' > r7/.hg/largefiles/4cdac4d8b084d0b599525cf732437fb337d422a8
185 185 $ rm "$USERCACHE/4cdac4d8b084d0b599525cf732437fb337d422a8"
186 186 $ hg init empty
187 187 $ hg serve -R empty -d -p $HGPORT1 --pid-file hg.pid \
188 188 > --config 'web.allow_push=*' --config web.push_ssl=False
189 189 $ cat hg.pid >> $DAEMON_PIDS
190 190 $ hg push -R r7 http://localhost:$HGPORT1
191 191 pushing to http://localhost:$HGPORT1/
192 192 searching for changes
193 193 remote: largefiles: failed to put 4cdac4d8b084d0b599525cf732437fb337d422a8 into store: largefile contents do not match hash
194 194 abort: remotestore: could not put $TESTTMP/r7/.hg/largefiles/4cdac4d8b084d0b599525cf732437fb337d422a8 to remote store http://localhost:$HGPORT1/
195 195 [255]
196 196 $ mv 4cdac4d8b084d0b599525cf732437fb337d422a8 r7/.hg/largefiles/4cdac4d8b084d0b599525cf732437fb337d422a8
197 197 Push of file that exists on server but is corrupted - magic healing would be nice ... but too magic
198 198 $ echo "server side corruption" > empty/.hg/largefiles/4cdac4d8b084d0b599525cf732437fb337d422a8
199 199 $ hg push -R r7 http://localhost:$HGPORT1
200 200 pushing to http://localhost:$HGPORT1/
201 201 searching for changes
202 202 remote: adding changesets
203 203 remote: adding manifests
204 204 remote: adding file changes
205 205 remote: added 2 changesets with 2 changes to 2 files
206 206 $ cat empty/.hg/largefiles/4cdac4d8b084d0b599525cf732437fb337d422a8
207 207 server side corruption
208 208 $ rm -rf empty
209 209
210 210 Push a largefiles repository to a served empty repository
211 211 $ hg init r8
212 212 $ echo c3 > r8/f1
213 213 $ hg add --large r8/f1 -R r8
214 214 $ hg commit -m "m1" -R r8
215 215 Invoking status precommit hook
216 216 A f1
217 217 $ hg init empty
218 218 $ hg serve -R empty -d -p $HGPORT2 --pid-file hg.pid \
219 219 > --config 'web.allow_push=*' --config web.push_ssl=False
220 220 $ cat hg.pid >> $DAEMON_PIDS
221 221 $ rm "${USERCACHE}"/*
222 222 $ hg push -R r8 http://localhost:$HGPORT2/#default
223 223 pushing to http://localhost:$HGPORT2/
224 224 searching for changes
225 225 remote: adding changesets
226 226 remote: adding manifests
227 227 remote: adding file changes
228 228 remote: added 1 changesets with 1 changes to 1 files
229 229 $ [ -f "${USERCACHE}"/02a439e5c31c526465ab1a0ca1f431f76b827b90 ]
230 230 $ [ -f empty/.hg/largefiles/02a439e5c31c526465ab1a0ca1f431f76b827b90 ]
231 231
232 232 Clone over http, no largefiles pulled on clone.
233 233
234 234 $ hg clone http://localhost:$HGPORT2/#default http-clone -U
235 235 adding changesets
236 236 adding manifests
237 237 adding file changes
238 238 added 1 changesets with 1 changes to 1 files
239 239 new changesets cf03e5bb9936
240 240
241 241 Archive contains largefiles
242 242 >>> import os
243 243 >>> import urllib2
244 244 >>> u = 'http://localhost:%s/archive/default.zip' % os.environ['HGPORT2']
245 245 >>> with open('archive.zip', 'w') as f:
246 246 ... f.write(urllib2.urlopen(u).read())
247 247 $ unzip -t archive.zip
248 248 Archive: archive.zip
249 249 testing: empty-default/.hg_archival.txt*OK (glob)
250 250 testing: empty-default/f1*OK (glob)
251 251 No errors detected in compressed data of archive.zip.
252 252
253 253 test 'verify' with remotestore:
254 254
255 255 $ rm "${USERCACHE}"/02a439e5c31c526465ab1a0ca1f431f76b827b90
256 256 $ mv empty/.hg/largefiles/02a439e5c31c526465ab1a0ca1f431f76b827b90 .
257 257 $ hg -R http-clone verify --large --lfa
258 258 checking changesets
259 259 checking manifests
260 260 crosschecking files in changesets and manifests
261 261 checking files
262 262 1 files, 1 changesets, 1 total revisions
263 263 searching 1 changesets for largefiles
264 264 changeset 0:cf03e5bb9936: f1 missing
265 265 verified existence of 1 revisions of 1 largefiles
266 266 [1]
267 267 $ mv 02a439e5c31c526465ab1a0ca1f431f76b827b90 empty/.hg/largefiles/
268 268 $ hg -R http-clone -q verify --large --lfa
269 269
270 270 largefiles pulled on update - a largefile missing on the server:
271 271 $ mv empty/.hg/largefiles/02a439e5c31c526465ab1a0ca1f431f76b827b90 .
272 272 $ hg -R http-clone up --config largefiles.usercache=http-clone-usercache
273 273 getting changed largefiles
274 274 f1: largefile 02a439e5c31c526465ab1a0ca1f431f76b827b90 not available from http://localhost:$HGPORT2/
275 275 0 largefiles updated, 0 removed
276 276 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
277 277 $ hg -R http-clone st
278 278 ! f1
279 279 $ hg -R http-clone up -Cqr null
280 280
281 281 largefiles pulled on update - a largefile corrupted on the server:
282 282 $ echo corruption > empty/.hg/largefiles/02a439e5c31c526465ab1a0ca1f431f76b827b90
283 283 $ hg -R http-clone up --config largefiles.usercache=http-clone-usercache
284 284 getting changed largefiles
285 285 f1: data corruption (expected 02a439e5c31c526465ab1a0ca1f431f76b827b90, got 6a7bb2556144babe3899b25e5428123735bb1e27)
286 286 0 largefiles updated, 0 removed
287 287 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
288 288 $ hg -R http-clone st
289 289 ! f1
290 290 $ [ ! -f http-clone/.hg/largefiles/02a439e5c31c526465ab1a0ca1f431f76b827b90 ]
291 291 $ [ ! -f http-clone/f1 ]
292 292 $ [ ! -f http-clone-usercache ]
293 293 $ hg -R http-clone verify --large --lfc
294 294 checking changesets
295 295 checking manifests
296 296 crosschecking files in changesets and manifests
297 297 checking files
298 298 1 files, 1 changesets, 1 total revisions
299 299 searching 1 changesets for largefiles
300 300 verified contents of 1 revisions of 1 largefiles
301 301 $ hg -R http-clone up -Cqr null
302 302
303 303 largefiles pulled on update - no server side problems:
304 304 $ mv 02a439e5c31c526465ab1a0ca1f431f76b827b90 empty/.hg/largefiles/
305 305 $ hg -R http-clone --debug up --config largefiles.usercache=http-clone-usercache --config progress.debug=true
306 306 resolving manifests
307 307 branchmerge: False, force: False, partial: False
308 308 ancestor: 000000000000, local: 000000000000+, remote: cf03e5bb9936
309 309 .hglf/f1: remote created -> g
310 310 getting .hglf/f1
311 311 updating: .hglf/f1 1/1 files (100.00%)
312 312 getting changed largefiles
313 313 using http://localhost:$HGPORT2/
314 314 sending capabilities command
315 315 sending batch command
316 316 getting largefiles: 0/1 files (0.00%)
317 317 getting f1:02a439e5c31c526465ab1a0ca1f431f76b827b90
318 318 sending getlfile command
319 319 found 02a439e5c31c526465ab1a0ca1f431f76b827b90 in store
320 320 1 largefiles updated, 0 removed
321 321 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
322 322
323 323 $ ls http-clone-usercache/*
324 324 http-clone-usercache/02a439e5c31c526465ab1a0ca1f431f76b827b90
325 325
326 326 $ rm -rf empty http-clone*
327 327
328 328 used all HGPORTs, kill all daemons
329 329 $ killdaemons.py
330 330
331 331 largefiles should batch verify remote calls
332 332
333 333 $ hg init batchverifymain
334 334 $ cd batchverifymain
335 335 $ echo "aaa" >> a
336 336 $ hg add --large a
337 337 $ hg commit -m "a"
338 338 Invoking status precommit hook
339 339 A a
340 340 $ echo "bbb" >> b
341 341 $ hg add --large b
342 342 $ hg commit -m "b"
343 343 Invoking status precommit hook
344 344 A b
345 345 $ cd ..
346 346 $ hg serve -R batchverifymain -d -p $HGPORT --pid-file hg.pid \
347 347 > -A access.log
348 348 $ cat hg.pid >> $DAEMON_PIDS
349 349 $ hg clone --noupdate http://localhost:$HGPORT batchverifyclone
350 350 requesting all changes
351 351 adding changesets
352 352 adding manifests
353 353 adding file changes
354 354 added 2 changesets with 2 changes to 2 files
355 355 new changesets 567253b0f523:04d19c27a332
356 356 $ hg -R batchverifyclone verify --large --lfa
357 357 checking changesets
358 358 checking manifests
359 359 crosschecking files in changesets and manifests
360 360 checking files
361 361 2 files, 2 changesets, 2 total revisions
362 362 searching 2 changesets for largefiles
363 363 verified existence of 2 revisions of 2 largefiles
364 364 $ tail -1 access.log
365 365 $LOCALIP - - [$LOGDATE$] "GET /?cmd=batch HTTP/1.1" 200 - x-hgarg-1:cmds=statlfile+sha%3D972a1a11f19934401291cc99117ec614933374ce%3Bstatlfile+sha%3Dc801c9cfe94400963fcb683246217d5db77f9a9a x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ (glob)
366 366 $ hg -R batchverifyclone update
367 367 getting changed largefiles
368 368 2 largefiles updated, 0 removed
369 369 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
370 370
371 371 Clear log file before next test
372 372
373 373 $ printf "" > access.log
374 374
375 375 Verify should check file on remote server only when file is not
376 376 available locally.
377 377
378 378 $ echo "ccc" >> batchverifymain/c
379 379 $ hg -R batchverifymain status
380 380 ? c
381 381 $ hg -R batchverifymain add --large batchverifymain/c
382 382 $ hg -R batchverifymain commit -m "c"
383 383 Invoking status precommit hook
384 384 A c
385 385 $ hg -R batchverifyclone pull
386 386 pulling from http://localhost:$HGPORT/
387 387 searching for changes
388 388 adding changesets
389 389 adding manifests
390 390 adding file changes
391 391 added 1 changesets with 1 changes to 1 files
392 392 new changesets 6bba8cb6935d
393 393 (run 'hg update' to get a working copy)
394 394 $ hg -R batchverifyclone verify --lfa
395 395 checking changesets
396 396 checking manifests
397 397 crosschecking files in changesets and manifests
398 398 checking files
399 399 3 files, 3 changesets, 3 total revisions
400 400 searching 3 changesets for largefiles
401 401 verified existence of 3 revisions of 3 largefiles
402 402 $ tail -1 access.log
403 403 $LOCALIP - - [$LOGDATE$] "GET /?cmd=batch HTTP/1.1" 200 - x-hgarg-1:cmds=statlfile+sha%3Dc8559c3c9cfb42131794b7d8009230403b9b454c x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$ (glob)
404 404
405 405 $ killdaemons.py
406 406
407 407 largefiles should not ask for password again after successful authorization
408 408
409 409 $ hg init credentialmain
410 410 $ cd credentialmain
411 411 $ echo "aaa" >> a
412 412 $ hg add --large a
413 413 $ hg commit -m "a"
414 414 Invoking status precommit hook
415 415 A a
416 416
417 417 Before running server clear the user cache to force clone to download
418 418 a large file from the server rather than to get it from the cache
419 419
420 420 $ rm "${USERCACHE}"/*
421 421
422 422 $ cd ..
423 423 $ cat << EOT > userpass.py
424 424 > import base64
425 425 > from mercurial.hgweb import common
426 426 > def perform_authentication(hgweb, req, op):
427 > auth = req.env.get('HTTP_AUTHORIZATION')
427 > auth = req.headers.get('Authorization')
428 428 > if not auth:
429 429 > raise common.ErrorResponse(common.HTTP_UNAUTHORIZED, 'who',
430 430 > [('WWW-Authenticate', 'Basic Realm="mercurial"')])
431 431 > if base64.b64decode(auth.split()[1]).split(':', 1) != ['user', 'pass']:
432 432 > raise common.ErrorResponse(common.HTTP_FORBIDDEN, 'no')
433 433 > def extsetup():
434 434 > common.permhooks.insert(0, perform_authentication)
435 435 > EOT
436 436 $ hg serve --config extensions.x=userpass.py -R credentialmain \
437 437 > -d -p $HGPORT --pid-file hg.pid -A access.log
438 438 $ cat hg.pid >> $DAEMON_PIDS
439 439 $ cat << EOF > get_pass.py
440 440 > import getpass
441 441 > def newgetpass(arg):
442 442 > return "pass"
443 443 > getpass.getpass = newgetpass
444 444 > EOF
445 445 $ hg clone --config ui.interactive=true --config extensions.getpass=get_pass.py \
446 446 > http://user@localhost:$HGPORT credentialclone
447 447 http authorization required for http://localhost:$HGPORT/
448 448 realm: mercurial
449 449 user: user
450 450 password: requesting all changes
451 451 adding changesets
452 452 adding manifests
453 453 adding file changes
454 454 added 1 changesets with 1 changes to 1 files
455 455 new changesets 567253b0f523
456 456 updating to branch default
457 457 getting changed largefiles
458 458 1 largefiles updated, 0 removed
459 459 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
460 460
461 461 $ killdaemons.py
462 462 $ rm hg.pid access.log
463 463
464 464 #endif
General Comments 0
You need to be logged in to leave comments. Login now