changeset 36757:8bba684efde7 stable 4.5.2

merge with security patches
author Kevin Bullock <kbullock+mercurial@ringworld.org>
date Tue, 06 Mar 2018 13:17:07 -0600
parents 2c49b2e7da86 (current diff) 2ecb0fc535b1 (diff)
children 2034cf3bfc70
files
diffstat 10 files changed, 1586 insertions(+), 89 deletions(-) [+]
line wrap: on
line diff
--- a/hgext/largefiles/uisetup.py	Tue Mar 06 13:08:00 2018 -0600
+++ b/hgext/largefiles/uisetup.py	Tue Mar 06 13:17:07 2018 -0600
@@ -12,7 +12,6 @@
 from mercurial.i18n import _
 
 from mercurial.hgweb import (
-    hgweb_mod,
     webcommands,
 )
 
@@ -175,9 +174,10 @@
 
     # make putlfile behave the same as push and {get,stat}lfile behave
     # the same as pull w.r.t. permissions checks
-    hgweb_mod.perms['putlfile'] = 'push'
-    hgweb_mod.perms['getlfile'] = 'pull'
-    hgweb_mod.perms['statlfile'] = 'pull'
+    wireproto.permissions['putlfile'] = 'push'
+    wireproto.permissions['getlfile'] = 'pull'
+    wireproto.permissions['statlfile'] = 'pull'
+    wireproto.permissions['lheads'] = 'pull'
 
     extensions.wrapfunction(webcommands, 'decodepath', overrides.decodepath)
 
--- a/mercurial/hgweb/hgweb_mod.py	Tue Mar 06 13:08:00 2018 -0600
+++ b/mercurial/hgweb/hgweb_mod.py	Tue Mar 06 13:17:07 2018 -0600
@@ -36,6 +36,7 @@
     templater,
     ui as uimod,
     util,
+    wireproto,
 )
 
 from . import (
@@ -45,15 +46,8 @@
     wsgicgi,
 )
 
-perms = {
-    'changegroup': 'pull',
-    'changegroupsubset': 'pull',
-    'getbundle': 'pull',
-    'stream_out': 'pull',
-    'listkeys': 'pull',
-    'unbundle': 'push',
-    'pushkey': 'push',
-}
+# Aliased for API compatibility.
+perms = wireproto.permissions
 
 archivespecs = util.sortdict((
     ('zip', ('application/zip', 'zip', '.zip', None)),
@@ -366,8 +360,13 @@
             try:
                 if query:
                     raise ErrorResponse(HTTP_NOT_FOUND)
-                if cmd in perms:
-                    self.check_perm(rctx, req, perms[cmd])
+
+                req.checkperm = lambda op: self.check_perm(rctx, req, op)
+                # Assume commands with no defined permissions are writes /
+                # for pushes. This is the safest from a security perspective
+                # because it doesn't allow commands with undefined semantics
+                # from bypassing permissions checks.
+                req.checkperm(perms.get(cmd, 'push'))
                 return protocol.call(rctx.repo, req, cmd)
             except ErrorResponse as inst:
                 # A client that sends unbundle without 100-continue will
--- a/mercurial/hgweb/protocol.py	Tue Mar 06 13:08:00 2018 -0600
+++ b/mercurial/hgweb/protocol.py	Tue Mar 06 13:17:07 2018 -0600
@@ -52,6 +52,7 @@
         self.response = ''
         self.ui = ui
         self.name = 'http'
+        self.checkperm = req.checkperm
 
     def getargs(self, args):
         knownargs = self._args()
--- a/mercurial/wireproto.py	Tue Mar 06 13:08:00 2018 -0600
+++ b/mercurial/wireproto.py	Tue Mar 06 13:17:07 2018 -0600
@@ -677,6 +677,11 @@
 # list of commands
 commands = {}
 
+# Maps wire protocol name to operation type. This is used for permissions
+# checking. All defined @wireiprotocommand should have an entry in this
+# dict.
+permissions = {}
+
 def wireprotocommand(name, args=''):
     """decorator for wire protocol command"""
     def register(func):
@@ -684,6 +689,8 @@
         return func
     return register
 
+# TODO define a more appropriate permissions type to use for this.
+permissions['batch'] = 'pull'
 @wireprotocommand('batch', 'cmds *')
 def batch(repo, proto, cmds, others):
     repo = repo.filtered("served")
@@ -696,6 +703,17 @@
                 n, v = a.split('=')
                 vals[unescapearg(n)] = unescapearg(v)
         func, spec = commands[op]
+
+        # If the protocol supports permissions checking, perform that
+        # checking on each batched command.
+        # TODO formalize permission checking as part of protocol interface.
+        if util.safehasattr(proto, 'checkperm'):
+            # Assume commands with no defined permissions are writes / for
+            # pushes. This is the safest from a security perspective because
+            # it doesn't allow commands with undefined semantics from
+            # bypassing permissions checks.
+            proto.checkperm(permissions.get(op, 'push'))
+
         if spec:
             keys = spec.split()
             data = {}
@@ -716,6 +734,7 @@
         res.append(escapearg(result))
     return ';'.join(res)
 
+permissions['between'] = 'pull'
 @wireprotocommand('between', 'pairs')
 def between(repo, proto, pairs):
     pairs = [decodelist(p, '-') for p in pairs.split(" ")]
@@ -724,6 +743,7 @@
         r.append(encodelist(b) + "\n")
     return "".join(r)
 
+permissions['branchmap'] = 'pull'
 @wireprotocommand('branchmap')
 def branchmap(repo, proto):
     branchmap = repo.branchmap()
@@ -734,6 +754,7 @@
         heads.append('%s %s' % (branchname, branchnodes))
     return '\n'.join(heads)
 
+permissions['branches'] = 'pull'
 @wireprotocommand('branches', 'nodes')
 def branches(repo, proto, nodes):
     nodes = decodelist(nodes)
@@ -742,6 +763,7 @@
         r.append(encodelist(b) + "\n")
     return "".join(r)
 
+permissions['clonebundles'] = 'pull'
 @wireprotocommand('clonebundles', '')
 def clonebundles(repo, proto):
     """Server command for returning info for available bundles to seed clones.
@@ -804,10 +826,12 @@
 
 # If you are writing an extension and consider wrapping this function. Wrap
 # `_capabilities` instead.
+permissions['capabilities'] = 'pull'
 @wireprotocommand('capabilities')
 def capabilities(repo, proto):
     return ' '.join(_capabilities(repo, proto))
 
+permissions['changegroup'] = 'pull'
 @wireprotocommand('changegroup', 'roots')
 def changegroup(repo, proto, roots):
     nodes = decodelist(roots)
@@ -817,6 +841,7 @@
     gen = iter(lambda: cg.read(32768), '')
     return streamres(gen=gen)
 
+permissions['changegroupsubset'] = 'pull'
 @wireprotocommand('changegroupsubset', 'bases heads')
 def changegroupsubset(repo, proto, bases, heads):
     bases = decodelist(bases)
@@ -827,12 +852,14 @@
     gen = iter(lambda: cg.read(32768), '')
     return streamres(gen=gen)
 
+permissions['debugwireargs'] = 'pull'
 @wireprotocommand('debugwireargs', 'one two *')
 def debugwireargs(repo, proto, one, two, others):
     # only accept optional args from the known set
     opts = options('debugwireargs', ['three', 'four'], others)
     return repo.debugwireargs(one, two, **pycompat.strkwargs(opts))
 
+permissions['getbundle'] = 'pull'
 @wireprotocommand('getbundle', '*')
 def getbundle(repo, proto, others):
     opts = options('getbundle', gboptsmap.keys(), others)
@@ -899,11 +926,13 @@
 
     return streamres(gen=chunks, prefer_uncompressed=not prefercompressed)
 
+permissions['heads'] = 'pull'
 @wireprotocommand('heads')
 def heads(repo, proto):
     h = repo.heads()
     return encodelist(h) + "\n"
 
+permissions['hello'] = 'pull'
 @wireprotocommand('hello')
 def hello(repo, proto):
     '''the hello command returns a set of lines describing various
@@ -915,11 +944,13 @@
     '''
     return "capabilities: %s\n" % (capabilities(repo, proto))
 
+permissions['listkeys'] = 'pull'
 @wireprotocommand('listkeys', 'namespace')
 def listkeys(repo, proto, namespace):
     d = repo.listkeys(encoding.tolocal(namespace)).items()
     return pushkeymod.encodekeys(d)
 
+permissions['lookup'] = 'pull'
 @wireprotocommand('lookup', 'key')
 def lookup(repo, proto, key):
     try:
@@ -932,10 +963,12 @@
         success = 0
     return "%d %s\n" % (success, r)
 
+permissions['known'] = 'pull'
 @wireprotocommand('known', 'nodes *')
 def known(repo, proto, nodes, others):
     return ''.join(b and "1" or "0" for b in repo.known(decodelist(nodes)))
 
+permissions['pushkey'] = 'push'
 @wireprotocommand('pushkey', 'namespace key old new')
 def pushkey(repo, proto, namespace, key, old, new):
     # compatibility with pre-1.8 clients which were accidentally
@@ -968,6 +1001,7 @@
                      encoding.tolocal(old), new)
     return '%s\n' % int(r)
 
+permissions['stream_out'] = 'pull'
 @wireprotocommand('stream_out')
 def stream(repo, proto):
     '''If the server supports streaming clone, it advertises the "stream"
@@ -976,6 +1010,7 @@
     '''
     return streamres_legacy(streamclone.generatev1wireproto(repo))
 
+permissions['unbundle'] = 'push'
 @wireprotocommand('unbundle', 'heads')
 def unbundle(repo, proto, heads):
     their_heads = decodelist(heads)
--- a/tests/test-http-bundle1.t	Tue Mar 06 13:08:00 2018 -0600
+++ b/tests/test-http-bundle1.t	Tue Mar 06 13:17:07 2018 -0600
@@ -259,60 +259,52 @@
   $ hg rollback -q
 
   $ sed 's/.*] "/"/' < ../access.log
-  "GET /?cmd=capabilities HTTP/1.1" 200 -
-  "GET /?cmd=lookup HTTP/1.1" 200 - x-hgarg-1:key=tip x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
-  "GET /?cmd=listkeys HTTP/1.1" 401 - x-hgarg-1:namespace=namespaces x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
+  "GET /?cmd=capabilities HTTP/1.1" 401 -
+  "GET /?cmd=capabilities HTTP/1.1" 401 -
+  "GET /?cmd=capabilities HTTP/1.1" 401 -
   "GET /?cmd=capabilities HTTP/1.1" 200 -
   "GET /?cmd=lookup HTTP/1.1" 200 - x-hgarg-1:key=tip x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
-  "GET /?cmd=listkeys HTTP/1.1" 401 - x-hgarg-1:namespace=namespaces x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
+  "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=namespaces x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
+  "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
+  "GET /?cmd=capabilities HTTP/1.1" 401 -
   "GET /?cmd=capabilities HTTP/1.1" 200 -
   "GET /?cmd=lookup HTTP/1.1" 200 - x-hgarg-1:key=tip x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
-  "GET /?cmd=listkeys HTTP/1.1" 401 - x-hgarg-1:namespace=namespaces x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
-  "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=namespaces x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
-  "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
-  "GET /?cmd=capabilities HTTP/1.1" 200 -
-  "GET /?cmd=lookup HTTP/1.1" 200 - x-hgarg-1:key=tip x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
-  "GET /?cmd=listkeys HTTP/1.1" 401 - x-hgarg-1:namespace=namespaces x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
   "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=namespaces x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
   "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
+  "GET /?cmd=capabilities HTTP/1.1" 401 -
   "GET /?cmd=capabilities HTTP/1.1" 200 -
   "GET /?cmd=lookup HTTP/1.1" 200 - x-hgarg-1:key=tip x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
-  "GET /?cmd=listkeys HTTP/1.1" 401 - x-hgarg-1:namespace=namespaces x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
   "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=namespaces x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
   "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
+  "GET /?cmd=capabilities HTTP/1.1" 401 -
   "GET /?cmd=capabilities HTTP/1.1" 200 -
   "GET /?cmd=lookup HTTP/1.1" 200 - x-hgarg-1:key=tip x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
-  "GET /?cmd=listkeys HTTP/1.1" 401 - x-hgarg-1:namespace=namespaces x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
   "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=namespaces x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
   "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
+  "GET /?cmd=capabilities HTTP/1.1" 401 -
   "GET /?cmd=capabilities HTTP/1.1" 200 -
   "GET /?cmd=lookup HTTP/1.1" 200 - x-hgarg-1:key=tip x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
-  "GET /?cmd=listkeys HTTP/1.1" 401 - x-hgarg-1:namespace=namespaces x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
   "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=namespaces x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
   "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
+  "GET /?cmd=capabilities HTTP/1.1" 401 -
   "GET /?cmd=capabilities HTTP/1.1" 200 -
   "GET /?cmd=branchmap HTTP/1.1" 200 - x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
-  "GET /?cmd=stream_out HTTP/1.1" 401 - x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
   "GET /?cmd=stream_out HTTP/1.1" 200 - x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
   "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
   "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$
   "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=phases x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
+  "GET /?cmd=capabilities HTTP/1.1" 401 -
   "GET /?cmd=capabilities HTTP/1.1" 200 -
-  "GET /?cmd=listkeys HTTP/1.1" 401 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
   "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
   "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$
   "GET /?cmd=getbundle HTTP/1.1" 200 - x-hgarg-1:common=0000000000000000000000000000000000000000&heads=5fed3813f7f5e1824344fdc9cf8f63bb662c292d x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
   "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=phases x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
-  "GET /?cmd=capabilities HTTP/1.1" 200 -
-  "GET /?cmd=lookup HTTP/1.1" 200 - x-hgarg-1:key=tip x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
-  "GET /?cmd=listkeys HTTP/1.1" 401 - x-hgarg-1:namespace=namespaces x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
-  "GET /?cmd=capabilities HTTP/1.1" 200 -
-  "GET /?cmd=lookup HTTP/1.1" 200 - x-hgarg-1:key=tip x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
-  "GET /?cmd=listkeys HTTP/1.1" 401 - x-hgarg-1:namespace=namespaces x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
-  "GET /?cmd=listkeys HTTP/1.1" 403 - x-hgarg-1:namespace=namespaces x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
+  "GET /?cmd=capabilities HTTP/1.1" 401 -
+  "GET /?cmd=capabilities HTTP/1.1" 401 -
+  "GET /?cmd=capabilities HTTP/1.1" 403 -
+  "GET /?cmd=capabilities HTTP/1.1" 401 -
   "GET /?cmd=capabilities HTTP/1.1" 200 -
   "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$
-  "GET /?cmd=listkeys HTTP/1.1" 401 - x-hgarg-1:namespace=phases x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
   "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=phases x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
   "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
   "GET /?cmd=branchmap HTTP/1.1" 200 - x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-http-permissions.t	Tue Mar 06 13:17:07 2018 -0600
@@ -0,0 +1,1502 @@
+#require killdaemons
+
+  $ cat > fakeremoteuser.py << EOF
+  > import os
+  > from mercurial.hgweb import hgweb_mod
+  > from mercurial import wireproto
+  > class testenvhgweb(hgweb_mod.hgweb):
+  >     def __call__(self, env, respond):
+  >         # Allow REMOTE_USER to define authenticated user.
+  >         if r'REMOTE_USER' in os.environ:
+  >             env[r'REMOTE_USER'] = os.environ[r'REMOTE_USER']
+  >         # Allow REQUEST_METHOD to override HTTP method
+  >         if r'REQUEST_METHOD' in os.environ:
+  >             env[r'REQUEST_METHOD'] = os.environ[r'REQUEST_METHOD']
+  >         return super(testenvhgweb, self).__call__(env, respond)
+  > hgweb_mod.hgweb = testenvhgweb
+  > 
+  > @wireproto.wireprotocommand('customreadnoperm')
+  > def customread(repo, proto):
+  >     return b'read-only command no defined permissions\n'
+  > @wireproto.wireprotocommand('customwritenoperm')
+  > def customwritenoperm(repo, proto):
+  >     return b'write command no defined permissions\n'
+  > wireproto.permissions['customreadwithperm'] = 'pull'
+  > @wireproto.wireprotocommand('customreadwithperm')
+  > def customreadwithperm(repo, proto):
+  >     return b'read-only command w/ defined permissions\n'
+  > wireproto.permissions['customwritewithperm'] = 'push'
+  > @wireproto.wireprotocommand('customwritewithperm')
+  > def customwritewithperm(repo, proto):
+  >     return b'write command w/ defined permissions\n'
+  > EOF
+
+  $ cat >> $HGRCPATH << EOF
+  > [extensions]
+  > fakeremoteuser = $TESTTMP/fakeremoteuser.py
+  > strip =
+  > EOF
+
+  $ hg init test
+  $ cd test
+  $ echo a > a
+  $ hg ci -Ama
+  adding a
+  $ cd ..
+  $ hg clone test test2
+  updating to branch default
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ cd test2
+  $ echo a >> a
+  $ hg ci -mb
+  $ hg book bm -r 0
+  $ cd ../test
+
+web.deny_read=* prevents access to wire protocol for all users
+
+  $ cat > .hg/hgrc <<EOF
+  > [web]
+  > deny_read = *
+  > EOF
+
+  $ hg serve -p $HGPORT -d --pid-file hg.pid
+  $ cat hg.pid > $DAEMON_PIDS
+
+  $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=capabilities'
+  401 read not authorized
+  
+  0
+  read not authorized
+  [1]
+
+  $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=stream_out'
+  401 read not authorized
+  
+  0
+  read not authorized
+  [1]
+
+  $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=listkeys' --requestheader 'x-hgarg-1=namespace=phases'
+  401 read not authorized
+  
+  0
+  read not authorized
+  [1]
+
+  $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=batch' --requestheader 'x-hgarg-1=cmds=listkeys+namespace%3Dphases'
+  401 read not authorized
+  
+  0
+  read not authorized
+  [1]
+
+  $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=customreadnoperm'
+  401 read not authorized
+  
+  0
+  read not authorized
+  [1]
+
+  $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=customreadwithperm'
+  401 read not authorized
+  
+  0
+  read not authorized
+  [1]
+
+  $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=customwritenoperm'
+  401 read not authorized
+  
+  0
+  read not authorized
+  [1]
+
+  $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=customwritewithperm'
+  401 read not authorized
+  
+  0
+  read not authorized
+  [1]
+
+  $ hg --cwd ../test2 pull http://localhost:$HGPORT/
+  pulling from http://localhost:$HGPORT/
+  abort: authorization failed
+  [255]
+
+  $ killdaemons.py
+
+web.deny_read=* with REMOTE_USER set still locks out clients
+
+  $ REMOTE_USER=authed_user hg serve -p $HGPORT -d --pid-file hg.pid
+  $ cat hg.pid > $DAEMON_PIDS
+
+  $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=capabilities'
+  401 read not authorized
+  
+  0
+  read not authorized
+  [1]
+
+  $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=stream_out'
+  401 read not authorized
+  
+  0
+  read not authorized
+  [1]
+
+  $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=batch' --requestheader 'x-hgarg-1=cmds=listkeys+namespace%3Dphases'
+  401 read not authorized
+  
+  0
+  read not authorized
+  [1]
+
+  $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=customreadnoperm'
+  401 read not authorized
+  
+  0
+  read not authorized
+  [1]
+
+  $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=customreadwithperm'
+  401 read not authorized
+  
+  0
+  read not authorized
+  [1]
+
+  $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=customwritenoperm'
+  401 read not authorized
+  
+  0
+  read not authorized
+  [1]
+
+  $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=customwritewithperm'
+  401 read not authorized
+  
+  0
+  read not authorized
+  [1]
+
+  $ hg --cwd ../test2 pull http://localhost:$HGPORT/
+  pulling from http://localhost:$HGPORT/
+  abort: authorization failed
+  [255]
+
+  $ killdaemons.py
+
+web.deny_read=<user> denies access to unauthenticated user
+
+  $ cat > .hg/hgrc <<EOF
+  > [web]
+  > deny_read = baduser1,baduser2
+  > EOF
+
+  $ hg serve -p $HGPORT -d --pid-file hg.pid
+  $ cat hg.pid > $DAEMON_PIDS
+
+  $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=listkeys' --requestheader 'x-hgarg-1=namespace=phases'
+  401 read not authorized
+  
+  0
+  read not authorized
+  [1]
+
+  $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=batch' --requestheader 'x-hgarg-1=cmds=listkeys+namespace%3Dphases'
+  401 read not authorized
+  
+  0
+  read not authorized
+  [1]
+
+  $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=customreadnoperm'
+  401 read not authorized
+  
+  0
+  read not authorized
+  [1]
+
+  $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=customreadwithperm'
+  401 read not authorized
+  
+  0
+  read not authorized
+  [1]
+
+  $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=customwritenoperm'
+  401 read not authorized
+  
+  0
+  read not authorized
+  [1]
+
+  $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=customwritewithperm'
+  401 read not authorized
+  
+  0
+  read not authorized
+  [1]
+
+  $ hg --cwd ../test2 pull http://localhost:$HGPORT/
+  pulling from http://localhost:$HGPORT/
+  abort: authorization failed
+  [255]
+
+  $ killdaemons.py
+
+web.deny_read=<user> denies access to users in deny list
+
+  $ REMOTE_USER=baduser2 hg serve -p $HGPORT -d --pid-file hg.pid
+  $ cat hg.pid > $DAEMON_PIDS
+
+  $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=listkeys' --requestheader 'x-hgarg-1=namespace=phases'
+  401 read not authorized
+  
+  0
+  read not authorized
+  [1]
+
+  $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=batch' --requestheader 'x-hgarg-1=cmds=listkeys+namespace%3Dphases'
+  401 read not authorized
+  
+  0
+  read not authorized
+  [1]
+
+  $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=customreadnoperm'
+  401 read not authorized
+  
+  0
+  read not authorized
+  [1]
+
+  $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=customreadwithperm'
+  401 read not authorized
+  
+  0
+  read not authorized
+  [1]
+
+  $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=customwritenoperm'
+  401 read not authorized
+  
+  0
+  read not authorized
+  [1]
+
+  $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=customwritewithperm'
+  401 read not authorized
+  
+  0
+  read not authorized
+  [1]
+
+  $ hg --cwd ../test2 pull http://localhost:$HGPORT/
+  pulling from http://localhost:$HGPORT/
+  abort: authorization failed
+  [255]
+
+  $ killdaemons.py
+
+web.deny_read=<user> allows access to authenticated users not in list
+
+  $ REMOTE_USER=gooduser hg serve -p $HGPORT -d --pid-file hg.pid
+  $ cat hg.pid > $DAEMON_PIDS
+
+  $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=listkeys' --requestheader 'x-hgarg-1=namespace=phases'
+  200 Script output follows
+  
+  cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b	1
+  publishing	True (no-eol)
+
+  $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=batch' --requestheader 'x-hgarg-1=cmds=listkeys+namespace%3Dphases'
+  200 Script output follows
+  
+  cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b	1
+  publishing	True (no-eol)
+
+  $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=customreadnoperm'
+  405 push requires POST request
+  
+  0
+  push requires POST request
+  [1]
+
+  $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=customreadwithperm'
+  200 Script output follows
+  
+  read-only command w/ defined permissions
+
+  $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=customwritenoperm'
+  405 push requires POST request
+  
+  0
+  push requires POST request
+  [1]
+
+  $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=customwritewithperm'
+  405 push requires POST request
+  
+  0
+  push requires POST request
+  [1]
+
+  $ hg --cwd ../test2 pull http://localhost:$HGPORT/
+  pulling from http://localhost:$HGPORT/
+  searching for changes
+  no changes found
+
+  $ killdaemons.py
+
+web.allow_read=* allows reads for unauthenticated users
+
+  $ cat > .hg/hgrc <<EOF
+  > [web]
+  > allow_read = *
+  > EOF
+
+  $ hg serve -p $HGPORT -d --pid-file hg.pid
+  $ cat hg.pid > $DAEMON_PIDS
+
+  $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=listkeys' --requestheader 'x-hgarg-1=namespace=phases'
+  200 Script output follows
+  
+  cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b	1
+  publishing	True (no-eol)
+
+  $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=batch' --requestheader 'x-hgarg-1=cmds=listkeys+namespace%3Dphases'
+  200 Script output follows
+  
+  cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b	1
+  publishing	True (no-eol)
+
+  $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=customreadnoperm'
+  405 push requires POST request
+  
+  0
+  push requires POST request
+  [1]
+
+  $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=customreadwithperm'
+  200 Script output follows
+  
+  read-only command w/ defined permissions
+
+  $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=customwritenoperm'
+  405 push requires POST request
+  
+  0
+  push requires POST request
+  [1]
+
+  $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=customwritewithperm'
+  405 push requires POST request
+  
+  0
+  push requires POST request
+  [1]
+
+  $ hg --cwd ../test2 pull http://localhost:$HGPORT/
+  pulling from http://localhost:$HGPORT/
+  searching for changes
+  no changes found
+
+  $ killdaemons.py
+
+web.allow_read=* allows read for authenticated user
+
+  $ REMOTE_USER=authed_user hg serve -p $HGPORT -d --pid-file hg.pid
+  $ cat hg.pid > $DAEMON_PIDS
+
+  $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=listkeys' --requestheader 'x-hgarg-1=namespace=phases'
+  200 Script output follows
+  
+  cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b	1
+  publishing	True (no-eol)
+
+  $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=batch' --requestheader 'x-hgarg-1=cmds=listkeys+namespace%3Dphases'
+  200 Script output follows
+  
+  cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b	1
+  publishing	True (no-eol)
+
+  $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=customreadnoperm'
+  405 push requires POST request
+  
+  0
+  push requires POST request
+  [1]
+
+  $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=customreadwithperm'
+  200 Script output follows
+  
+  read-only command w/ defined permissions
+
+  $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=customwritenoperm'
+  405 push requires POST request
+  
+  0
+  push requires POST request
+  [1]
+
+  $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=customwritewithperm'
+  405 push requires POST request
+  
+  0
+  push requires POST request
+  [1]
+
+  $ hg --cwd ../test2 pull http://localhost:$HGPORT/
+  pulling from http://localhost:$HGPORT/
+  searching for changes
+  no changes found
+
+  $ killdaemons.py
+
+web.allow_read=<user> does not allow unauthenticated users to read
+
+  $ cat > .hg/hgrc <<EOF
+  > [web]
+  > allow_read = gooduser
+  > EOF
+
+  $ hg serve -p $HGPORT -d --pid-file hg.pid
+  $ cat hg.pid > $DAEMON_PIDS
+
+  $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=listkeys' --requestheader 'x-hgarg-1=namespace=phases'
+  401 read not authorized
+  
+  0
+  read not authorized
+  [1]
+
+  $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=batch' --requestheader 'x-hgarg-1=cmds=listkeys+namespace%3Dphases'
+  401 read not authorized
+  
+  0
+  read not authorized
+  [1]
+
+  $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=customreadnoperm'
+  401 read not authorized
+  
+  0
+  read not authorized
+  [1]
+
+  $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=customreadwithperm'
+  401 read not authorized
+  
+  0
+  read not authorized
+  [1]
+
+  $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=customwritenoperm'
+  401 read not authorized
+  
+  0
+  read not authorized
+  [1]
+
+  $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=customwritewithperm'
+  401 read not authorized
+  
+  0
+  read not authorized
+  [1]
+
+  $ hg --cwd ../test2 pull http://localhost:$HGPORT/
+  pulling from http://localhost:$HGPORT/
+  abort: authorization failed
+  [255]
+
+  $ killdaemons.py
+
+web.allow_read=<user> does not allow user not in list to read
+
+  $ REMOTE_USER=baduser hg serve -p $HGPORT -d --pid-file hg.pid
+  $ cat hg.pid > $DAEMON_PIDS
+
+  $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=listkeys' --requestheader 'x-hgarg-1=namespace=phases'
+  401 read not authorized
+  
+  0
+  read not authorized
+  [1]
+
+  $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=batch' --requestheader 'x-hgarg-1=cmds=listkeys+namespace%3Dphases'
+  401 read not authorized
+  
+  0
+  read not authorized
+  [1]
+
+  $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=customreadnoperm'
+  401 read not authorized
+  
+  0
+  read not authorized
+  [1]
+
+  $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=customreadwithperm'
+  401 read not authorized
+  
+  0
+  read not authorized
+  [1]
+
+  $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=customwritenoperm'
+  401 read not authorized
+  
+  0
+  read not authorized
+  [1]
+
+  $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=customwritewithperm'
+  401 read not authorized
+  
+  0
+  read not authorized
+  [1]
+
+  $ hg --cwd ../test2 pull http://localhost:$HGPORT/
+  pulling from http://localhost:$HGPORT/
+  abort: authorization failed
+  [255]
+
+  $ killdaemons.py
+
+web.allow_read=<user> allows read from user in list
+
+  $ REMOTE_USER=gooduser hg serve -p $HGPORT -d --pid-file hg.pid
+  $ cat hg.pid > $DAEMON_PIDS
+
+  $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=listkeys' --requestheader 'x-hgarg-1=namespace=phases'
+  200 Script output follows
+  
+  cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b	1
+  publishing	True (no-eol)
+
+  $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=batch' --requestheader 'x-hgarg-1=cmds=listkeys+namespace%3Dphases'
+  200 Script output follows
+  
+  cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b	1
+  publishing	True (no-eol)
+
+  $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=customreadnoperm'
+  405 push requires POST request
+  
+  0
+  push requires POST request
+  [1]
+
+  $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=customreadwithperm'
+  200 Script output follows
+  
+  read-only command w/ defined permissions
+
+  $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=customwritenoperm'
+  405 push requires POST request
+  
+  0
+  push requires POST request
+  [1]
+
+  $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=customwritewithperm'
+  405 push requires POST request
+  
+  0
+  push requires POST request
+  [1]
+
+  $ hg --cwd ../test2 pull http://localhost:$HGPORT/
+  pulling from http://localhost:$HGPORT/
+  searching for changes
+  no changes found
+
+  $ killdaemons.py
+
+web.deny_read takes precedence over web.allow_read
+
+  $ cat > .hg/hgrc <<EOF
+  > [web]
+  > allow_read = baduser
+  > deny_read = baduser
+  > EOF
+
+  $ REMOTE_USER=baduser hg serve -p $HGPORT -d --pid-file hg.pid
+  $ cat hg.pid > $DAEMON_PIDS
+
+  $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=listkeys' --requestheader 'x-hgarg-1=namespace=phases'
+  401 read not authorized
+  
+  0
+  read not authorized
+  [1]
+
+  $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=batch' --requestheader 'x-hgarg-1=cmds=listkeys+namespace%3Dphases'
+  401 read not authorized
+  
+  0
+  read not authorized
+  [1]
+
+  $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=customreadnoperm'
+  401 read not authorized
+  
+  0
+  read not authorized
+  [1]
+
+  $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=customreadwithperm'
+  401 read not authorized
+  
+  0
+  read not authorized
+  [1]
+
+  $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=customwritenoperm'
+  401 read not authorized
+  
+  0
+  read not authorized
+  [1]
+
+  $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=customwritewithperm'
+  401 read not authorized
+  
+  0
+  read not authorized
+  [1]
+
+  $ hg --cwd ../test2 pull http://localhost:$HGPORT/
+  pulling from http://localhost:$HGPORT/
+  abort: authorization failed
+  [255]
+
+  $ killdaemons.py
+
+web.allow-pull=false denies read access to repo
+
+  $ cat > .hg/hgrc <<EOF
+  > [web]
+  > allow-pull = false
+  > EOF
+
+  $ hg serve -p $HGPORT -d --pid-file hg.pid
+  $ cat hg.pid > $DAEMON_PIDS
+
+  $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=capabilities'
+  401 pull not authorized
+  
+  0
+  pull not authorized
+  [1]
+
+  $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=listkeys' --requestheader 'x-hgarg-1=namespace=phases'
+  401 pull not authorized
+  
+  0
+  pull not authorized
+  [1]
+
+  $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=batch' --requestheader 'x-hgarg-1=cmds=listkeys+namespace%3Dphases'
+  401 pull not authorized
+  
+  0
+  pull not authorized
+  [1]
+
+  $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=customreadnoperm'
+  405 push requires POST request
+  
+  0
+  push requires POST request
+  [1]
+
+  $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=customreadwithperm'
+  401 pull not authorized
+  
+  0
+  pull not authorized
+  [1]
+
+  $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=customwritenoperm'
+  405 push requires POST request
+  
+  0
+  push requires POST request
+  [1]
+
+  $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=customwritewithperm'
+  405 push requires POST request
+  
+  0
+  push requires POST request
+  [1]
+
+  $ hg --cwd ../test2 pull http://localhost:$HGPORT/
+  pulling from http://localhost:$HGPORT/
+  abort: authorization failed
+  [255]
+
+  $ killdaemons.py
+
+Attempting a write command with HTTP GET fails
+
+  $ cat > .hg/hgrc <<EOF
+  > EOF
+
+  $ REQUEST_METHOD=GET hg serve -p $HGPORT -d --pid-file hg.pid
+  $ cat hg.pid > $DAEMON_PIDS
+
+  $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=pushkey' --requestheader 'x-hgarg-1=namespace=bookmarks&key=bm&old=&new=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b'
+  405 push requires POST request
+  
+  0
+  push requires POST request
+  [1]
+
+  $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=batch' --requestheader 'x-hgarg-1=cmds=pushkey+namespace%3Dbookmarks%2Ckey%3Dbm%2Cold%3D%2Cnew%3Dcb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b'
+  405 push requires POST request
+  
+  0
+  push requires POST request
+  [1]
+
+  $ hg bookmarks
+  no bookmarks set
+  $ hg bookmark -d bm
+  abort: bookmark 'bm' does not exist
+  [255]
+
+  $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=customwritenoperm'
+  405 push requires POST request
+  
+  0
+  push requires POST request
+  [1]
+
+  $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=customwritewithperm'
+  405 push requires POST request
+  
+  0
+  push requires POST request
+  [1]
+
+  $ killdaemons.py
+
+Attempting a write command with an unknown HTTP verb fails
+
+  $ REQUEST_METHOD=someverb hg serve -p $HGPORT -d --pid-file hg.pid
+  $ cat hg.pid > $DAEMON_PIDS
+
+  $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=pushkey' --requestheader 'x-hgarg-1=namespace=bookmarks&key=bm&old=&new=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b'
+  405 push requires POST request
+  
+  0
+  push requires POST request
+  [1]
+
+  $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=batch' --requestheader 'x-hgarg-1=cmds=pushkey+namespace%3Dbookmarks%2Ckey%3Dbm%2Cold%3D%2Cnew%3Dcb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b'
+  405 push requires POST request
+  
+  0
+  push requires POST request
+  [1]
+
+  $ hg bookmarks
+  no bookmarks set
+  $ hg bookmark -d bm
+  abort: bookmark 'bm' does not exist
+  [255]
+
+  $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=customwritenoperm'
+  405 push requires POST request
+  
+  0
+  push requires POST request
+  [1]
+
+  $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=customwritewithperm'
+  405 push requires POST request
+  
+  0
+  push requires POST request
+  [1]
+
+  $ killdaemons.py
+
+Pushing on a plaintext channel is disabled by default
+
+  $ cat > .hg/hgrc <<EOF
+  > EOF
+
+  $ REQUEST_METHOD=POST hg serve -p $HGPORT -d --pid-file hg.pid
+  $ cat hg.pid > $DAEMON_PIDS
+
+  $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=pushkey' --requestheader 'x-hgarg-1=namespace=bookmarks&key=bm&old=&new=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b'
+  403 ssl required
+  
+  0
+  ssl required
+  [1]
+
+  $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=batch' --requestheader 'x-hgarg-1=cmds=pushkey+namespace%3Dbookmarks%2Ckey%3Dbm%2Cold%3D%2Cnew%3Dcb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b'
+  403 ssl required
+  
+  0
+  ssl required
+  [1]
+
+  $ hg bookmarks
+  no bookmarks set
+
+  $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=customwritenoperm'
+  403 ssl required
+  
+  0
+  ssl required
+  [1]
+
+  $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=customwritewithperm'
+  403 ssl required
+  
+  0
+  ssl required
+  [1]
+
+Reset server to remove REQUEST_METHOD hack to test hg client
+
+  $ killdaemons.py
+  $ hg serve -p $HGPORT -d --pid-file hg.pid
+  $ cat hg.pid > $DAEMON_PIDS
+
+  $ hg --cwd ../test2 push -B bm http://localhost:$HGPORT/
+  pushing to http://localhost:$HGPORT/
+  searching for changes
+  no changes found
+  abort: HTTP Error 403: ssl required
+  [255]
+
+  $ hg --cwd ../test2 push http://localhost:$HGPORT/
+  pushing to http://localhost:$HGPORT/
+  searching for changes
+  abort: HTTP Error 403: ssl required
+  [255]
+
+  $ killdaemons.py
+
+web.deny_push=* denies pushing to unauthenticated users
+
+  $ cat > .hg/hgrc <<EOF
+  > [web]
+  > push_ssl = false
+  > deny_push = *
+  > EOF
+
+  $ REQUEST_METHOD=POST hg serve -p $HGPORT -d --pid-file hg.pid
+  $ cat hg.pid > $DAEMON_PIDS
+
+  $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=pushkey' --requestheader 'x-hgarg-1=namespace=bookmarks&key=bm&old=&new=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b'
+  401 push not authorized
+  
+  0
+  push not authorized
+  [1]
+
+  $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=batch' --requestheader 'x-hgarg-1=cmds=pushkey+namespace%3Dbookmarks%2Ckey%3Dbm%2Cold%3D%2Cnew%3Dcb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b'
+  401 push not authorized
+  
+  0
+  push not authorized
+  [1]
+
+  $ hg bookmarks
+  no bookmarks set
+
+  $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=customwritenoperm'
+  401 push not authorized
+  
+  0
+  push not authorized
+  [1]
+
+  $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=customwritewithperm'
+  401 push not authorized
+  
+  0
+  push not authorized
+  [1]
+
+Reset server to remove REQUEST_METHOD hack to test hg client
+
+  $ killdaemons.py
+  $ hg serve -p $HGPORT -d --pid-file hg.pid
+  $ cat hg.pid > $DAEMON_PIDS
+
+  $ hg --cwd ../test2 push -B bm http://localhost:$HGPORT/
+  pushing to http://localhost:$HGPORT/
+  searching for changes
+  no changes found
+  abort: authorization failed
+  [255]
+
+  $ hg --cwd ../test2 push http://localhost:$HGPORT/
+  pushing to http://localhost:$HGPORT/
+  searching for changes
+  abort: authorization failed
+  [255]
+
+  $ killdaemons.py
+
+web.deny_push=* denies pushing to authenticated users
+
+  $ REMOTE_USER=someuser REQUEST_METHOD=POST hg serve -p $HGPORT -d --pid-file hg.pid
+  $ cat hg.pid > $DAEMON_PIDS
+
+  $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=pushkey' --requestheader 'x-hgarg-1=namespace=bookmarks&key=bm&old=&new=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b'
+  401 push not authorized
+  
+  0
+  push not authorized
+  [1]
+
+  $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=batch' --requestheader 'x-hgarg-1=cmds=pushkey+namespace%3Dbookmarks%2Ckey%3Dbm%2Cold%3D%2Cnew%3Dcb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b'
+  401 push not authorized
+  
+  0
+  push not authorized
+  [1]
+
+  $ hg bookmarks
+  no bookmarks set
+
+  $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=customwritenoperm'
+  401 push not authorized
+  
+  0
+  push not authorized
+  [1]
+
+  $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=customwritewithperm'
+  401 push not authorized
+  
+  0
+  push not authorized
+  [1]
+
+Reset server to remove REQUEST_METHOD hack to test hg client
+
+  $ killdaemons.py
+  $ REMOTE_USER=someuser hg serve -p $HGPORT -d --pid-file hg.pid
+  $ cat hg.pid > $DAEMON_PIDS
+
+  $ hg --cwd ../test2 push -B bm http://localhost:$HGPORT/
+  pushing to http://localhost:$HGPORT/
+  searching for changes
+  no changes found
+  abort: authorization failed
+  [255]
+
+  $ hg --cwd ../test2 push http://localhost:$HGPORT/
+  pushing to http://localhost:$HGPORT/
+  searching for changes
+  abort: authorization failed
+  [255]
+
+  $ killdaemons.py
+
+web.deny_push=<user> denies pushing to user in list
+
+  $ cat > .hg/hgrc <<EOF
+  > [web]
+  > push_ssl = false
+  > deny_push = baduser
+  > EOF
+
+  $ REMOTE_USER=baduser REQUEST_METHOD=POST hg serve -p $HGPORT -d --pid-file hg.pid
+  $ cat hg.pid > $DAEMON_PIDS
+
+  $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=pushkey' --requestheader 'x-hgarg-1=namespace=bookmarks&key=bm&old=&new=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b'
+  401 push not authorized
+  
+  0
+  push not authorized
+  [1]
+
+  $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=batch' --requestheader 'x-hgarg-1=cmds=pushkey+namespace%3Dbookmarks%2Ckey%3Dbm%2Cold%3D%2Cnew%3Dcb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b'
+  401 push not authorized
+  
+  0
+  push not authorized
+  [1]
+
+  $ hg bookmarks
+  no bookmarks set
+
+  $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=customwritenoperm'
+  401 push not authorized
+  
+  0
+  push not authorized
+  [1]
+
+  $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=customwritewithperm'
+  401 push not authorized
+  
+  0
+  push not authorized
+  [1]
+
+Reset server to remove REQUEST_METHOD hack to test hg client
+
+  $ killdaemons.py
+  $ REMOTE_USER=baduser hg serve -p $HGPORT -d --pid-file hg.pid
+  $ cat hg.pid > $DAEMON_PIDS
+
+  $ hg --cwd ../test2 push -B bm http://localhost:$HGPORT/
+  pushing to http://localhost:$HGPORT/
+  searching for changes
+  no changes found
+  abort: authorization failed
+  [255]
+
+  $ hg --cwd ../test2 push http://localhost:$HGPORT/
+  pushing to http://localhost:$HGPORT/
+  searching for changes
+  abort: authorization failed
+  [255]
+
+  $ killdaemons.py
+
+web.deny_push=<user> denies pushing to user not in list because allow-push isn't set
+
+  $ REMOTE_USER=gooduser REQUEST_METHOD=POST hg serve -p $HGPORT -d --pid-file hg.pid
+  $ cat hg.pid > $DAEMON_PIDS
+
+  $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=pushkey' --requestheader 'x-hgarg-1=namespace=bookmarks&key=bm&old=&new=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b'
+  401 push not authorized
+  
+  0
+  push not authorized
+  [1]
+
+  $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=batch' --requestheader 'x-hgarg-1=cmds=pushkey+namespace%3Dbookmarks%2Ckey%3Dbm%2Cold%3D%2Cnew%3Dcb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b'
+  401 push not authorized
+  
+  0
+  push not authorized
+  [1]
+
+  $ hg bookmarks
+  no bookmarks set
+
+  $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=customwritenoperm'
+  401 push not authorized
+  
+  0
+  push not authorized
+  [1]
+
+  $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=customwritewithperm'
+  401 push not authorized
+  
+  0
+  push not authorized
+  [1]
+
+Reset server to remove REQUEST_METHOD hack to test hg client
+
+  $ killdaemons.py
+  $ REMOTE_USER=gooduser hg serve -p $HGPORT -d --pid-file hg.pid
+  $ cat hg.pid > $DAEMON_PIDS
+
+  $ hg --cwd ../test2 push -B bm http://localhost:$HGPORT/
+  pushing to http://localhost:$HGPORT/
+  searching for changes
+  no changes found
+  abort: authorization failed
+  [255]
+
+  $ hg --cwd ../test2 push http://localhost:$HGPORT/
+  pushing to http://localhost:$HGPORT/
+  searching for changes
+  abort: authorization failed
+  [255]
+
+  $ killdaemons.py
+
+web.allow-push=* allows pushes from unauthenticated users
+
+  $ cat > .hg/hgrc <<EOF
+  > [web]
+  > push_ssl = false
+  > allow-push = *
+  > EOF
+
+  $ REQUEST_METHOD=POST hg serve -p $HGPORT -d --pid-file hg.pid
+  $ cat hg.pid > $DAEMON_PIDS
+
+  $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=pushkey' --requestheader 'x-hgarg-1=namespace=bookmarks&key=bm&old=&new=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b'
+  200 Script output follows
+  
+  1
+
+  $ hg bookmarks
+     bm                        0:cb9a9f314b8b
+  $ hg book -d bm
+
+  $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=customwritenoperm'
+  200 Script output follows
+  
+  write command no defined permissions
+
+  $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=customwritewithperm'
+  200 Script output follows
+  
+  write command w/ defined permissions
+
+Reset server to remove REQUEST_METHOD hack to test hg client
+
+  $ killdaemons.py
+  $ hg serve -p $HGPORT -d --pid-file hg.pid
+  $ cat hg.pid > $DAEMON_PIDS
+
+  $ hg --cwd ../test2 push -B bm http://localhost:$HGPORT/
+  pushing to http://localhost:$HGPORT/
+  searching for changes
+  no changes found
+  exporting bookmark bm
+  [1]
+
+  $ hg book -d bm
+
+  $ hg --cwd ../test2 push http://localhost:$HGPORT/
+  pushing to http://localhost:$HGPORT/
+  searching for changes
+  remote: adding changesets
+  remote: adding manifests
+  remote: adding file changes
+  remote: added 1 changesets with 1 changes to 1 files
+
+  $ hg strip -r 1:
+  saved backup bundle to $TESTTMP/test/.hg/strip-backup/ba677d0156c1-eea704d7-backup.hg
+
+  $ killdaemons.py
+
+web.allow-push=* allows pushes from authenticated users
+
+  $ REMOTE_USER=someuser REQUEST_METHOD=POST hg serve -p $HGPORT -d --pid-file hg.pid
+  $ cat hg.pid > $DAEMON_PIDS
+
+  $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=pushkey' --requestheader 'x-hgarg-1=namespace=bookmarks&key=bm&old=&new=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b'
+  200 Script output follows
+  
+  1
+
+  $ hg bookmarks
+     bm                        0:cb9a9f314b8b
+  $ hg book -d bm
+
+  $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=customwritenoperm'
+  200 Script output follows
+  
+  write command no defined permissions
+
+  $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=customwritewithperm'
+  200 Script output follows
+  
+  write command w/ defined permissions
+
+Reset server to remove REQUEST_METHOD hack to test hg client
+
+  $ killdaemons.py
+  $ REMOTE_USER=someuser hg serve -p $HGPORT -d --pid-file hg.pid
+  $ cat hg.pid > $DAEMON_PIDS
+
+  $ hg --cwd ../test2 push -B bm http://localhost:$HGPORT/
+  pushing to http://localhost:$HGPORT/
+  searching for changes
+  no changes found
+  exporting bookmark bm
+  [1]
+
+  $ hg book -d bm
+
+  $ hg --cwd ../test2 push http://localhost:$HGPORT/
+  pushing to http://localhost:$HGPORT/
+  searching for changes
+  remote: adding changesets
+  remote: adding manifests
+  remote: adding file changes
+  remote: added 1 changesets with 1 changes to 1 files
+
+  $ hg strip -r 1:
+  saved backup bundle to $TESTTMP/test/.hg/strip-backup/ba677d0156c1-eea704d7-backup.hg
+
+  $ killdaemons.py
+
+web.allow-push=<user> denies push to user not in list
+
+  $ cat > .hg/hgrc <<EOF
+  > [web]
+  > push_ssl = false
+  > allow-push = gooduser
+  > EOF
+
+  $ REMOTE_USER=baduser REQUEST_METHOD=POST hg serve -p $HGPORT -d --pid-file hg.pid
+  $ cat hg.pid > $DAEMON_PIDS
+
+  $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=pushkey' --requestheader 'x-hgarg-1=namespace=bookmarks&key=bm&old=&new=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b'
+  401 push not authorized
+  
+  0
+  push not authorized
+  [1]
+
+  $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=batch' --requestheader 'x-hgarg-1=cmds=pushkey+namespace%3Dbookmarks%2Ckey%3Dbm%2Cold%3D%2Cnew%3Dcb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b'
+  401 push not authorized
+  
+  0
+  push not authorized
+  [1]
+
+  $ hg bookmarks
+  no bookmarks set
+
+  $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=customwritenoperm'
+  401 push not authorized
+  
+  0
+  push not authorized
+  [1]
+
+  $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=customwritewithperm'
+  401 push not authorized
+  
+  0
+  push not authorized
+  [1]
+
+Reset server to remove REQUEST_METHOD hack to test hg client
+
+  $ killdaemons.py
+  $ REMOTE_USER=baduser hg serve -p $HGPORT -d --pid-file hg.pid
+  $ cat hg.pid > $DAEMON_PIDS
+
+  $ hg --cwd ../test2 push -B bm http://localhost:$HGPORT/
+  pushing to http://localhost:$HGPORT/
+  searching for changes
+  no changes found
+  abort: authorization failed
+  [255]
+
+  $ hg --cwd ../test2 push http://localhost:$HGPORT/
+  pushing to http://localhost:$HGPORT/
+  searching for changes
+  abort: authorization failed
+  [255]
+
+  $ killdaemons.py
+
+web.allow-push=<user> allows push from user in list
+
+  $ REMOTE_USER=gooduser REQUEST_METHOD=POST hg serve -p $HGPORT -d --pid-file hg.pid
+  $ cat hg.pid > $DAEMON_PIDS
+
+  $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=pushkey' --requestheader 'x-hgarg-1=namespace=bookmarks&key=bm&old=&new=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b'
+  200 Script output follows
+  
+  1
+
+  $ hg bookmarks
+     bm                        0:cb9a9f314b8b
+  $ hg book -d bm
+
+  $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=batch' --requestheader 'x-hgarg-1=cmds=pushkey+namespace%3Dbookmarks%2Ckey%3Dbm%2Cold%3D%2Cnew%3Dcb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b'
+  200 Script output follows
+  
+  1
+
+  $ hg bookmarks
+     bm                        0:cb9a9f314b8b
+  $ hg book -d bm
+
+  $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=customwritenoperm'
+  200 Script output follows
+  
+  write command no defined permissions
+
+  $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=customwritewithperm'
+  200 Script output follows
+  
+  write command w/ defined permissions
+
+Reset server to remove REQUEST_METHOD hack to test hg client
+
+  $ killdaemons.py
+  $ REMOTE_USER=gooduser hg serve -p $HGPORT -d --pid-file hg.pid
+  $ cat hg.pid > $DAEMON_PIDS
+
+  $ hg --cwd ../test2 push -B bm http://localhost:$HGPORT/
+  pushing to http://localhost:$HGPORT/
+  searching for changes
+  no changes found
+  exporting bookmark bm
+  [1]
+
+  $ hg book -d bm
+
+  $ hg --cwd ../test2 push http://localhost:$HGPORT/
+  pushing to http://localhost:$HGPORT/
+  searching for changes
+  remote: adding changesets
+  remote: adding manifests
+  remote: adding file changes
+  remote: added 1 changesets with 1 changes to 1 files
+
+  $ hg strip -r 1:
+  saved backup bundle to $TESTTMP/test/.hg/strip-backup/ba677d0156c1-eea704d7-backup.hg
+
+  $ killdaemons.py
+
+web.deny_push takes precedence over web.allow_push
+
+  $ cat > .hg/hgrc <<EOF
+  > [web]
+  > push_ssl = false
+  > allow-push = someuser
+  > deny_push = someuser
+  > EOF
+
+  $ REMOTE_USER=someuser REQUEST_METHOD=POST hg serve -p $HGPORT -d --pid-file hg.pid
+  $ cat hg.pid > $DAEMON_PIDS
+
+  $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=pushkey' --requestheader 'x-hgarg-1=namespace=bookmarks&key=bm&old=&new=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b'
+  401 push not authorized
+  
+  0
+  push not authorized
+  [1]
+
+  $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=batch' --requestheader 'x-hgarg-1=cmds=pushkey+namespace%3Dbookmarks%2Ckey%3Dbm%2Cold%3D%2Cnew%3Dcb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b'
+  401 push not authorized
+  
+  0
+  push not authorized
+  [1]
+
+  $ hg bookmarks
+  no bookmarks set
+
+  $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=customwritenoperm'
+  401 push not authorized
+  
+  0
+  push not authorized
+  [1]
+
+  $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=customwritewithperm'
+  401 push not authorized
+  
+  0
+  push not authorized
+  [1]
+
+Reset server to remove REQUEST_METHOD hack to test hg client
+
+  $ killdaemons.py
+  $ REMOTE_USER=someuser hg serve -p $HGPORT -d --pid-file hg.pid
+  $ cat hg.pid > $DAEMON_PIDS
+
+  $ hg --cwd ../test2 push -B bm http://localhost:$HGPORT/
+  pushing to http://localhost:$HGPORT/
+  searching for changes
+  no changes found
+  abort: authorization failed
+  [255]
+
+  $ hg --cwd ../test2 push http://localhost:$HGPORT/
+  pushing to http://localhost:$HGPORT/
+  searching for changes
+  abort: authorization failed
+  [255]
+
+  $ killdaemons.py
+
+web.allow-push has no effect if web.deny_read is set
+
+  $ cat > .hg/hgrc <<EOF
+  > [web]
+  > push_ssl = false
+  > allow-push = *
+  > deny_read = *
+  > EOF
+
+  $ REQUEST_METHOD=POST REMOTE_USER=someuser hg serve -p $HGPORT -d --pid-file hg.pid
+  $ cat hg.pid > $DAEMON_PIDS
+
+  $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=pushkey' --requestheader 'x-hgarg-1=namespace=bookmarks&key=bm&old=&new=cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b'
+  401 read not authorized
+  
+  0
+  read not authorized
+  [1]
+
+  $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=batch' --requestheader 'x-hgarg-1=cmds=pushkey+namespace%3Dbookmarks%2Ckey%3Dbm%2Cold%3D%2Cnew%3Dcb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b'
+  401 read not authorized
+  
+  0
+  read not authorized
+  [1]
+
+  $ hg bookmarks
+  no bookmarks set
+
+  $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=customreadnoperm'
+  401 read not authorized
+  
+  0
+  read not authorized
+  [1]
+
+  $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=customreadwithperm'
+  401 read not authorized
+  
+  0
+  read not authorized
+  [1]
+
+  $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=customwritenoperm'
+  401 read not authorized
+  
+  0
+  read not authorized
+  [1]
+
+  $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=customwritewithperm'
+  401 read not authorized
+  
+  0
+  read not authorized
+  [1]
+
+Reset server to remove REQUEST_METHOD hack to test hg client
+
+  $ killdaemons.py
+  $ REMOTE_USER=someuser hg serve -p $HGPORT -d --pid-file hg.pid
+  $ cat hg.pid > $DAEMON_PIDS
+
+  $ hg --cwd ../test2 push -B bm http://localhost:$HGPORT/
+  pushing to http://localhost:$HGPORT/
+  abort: authorization failed
+  [255]
+
+  $ hg --cwd ../test2 push http://localhost:$HGPORT/
+  pushing to http://localhost:$HGPORT/
+  abort: authorization failed
+  [255]
+
+  $ killdaemons.py
--- a/tests/test-http.t	Tue Mar 06 13:08:00 2018 -0600
+++ b/tests/test-http.t	Tue Mar 06 13:17:07 2018 -0600
@@ -254,6 +254,7 @@
   http auth: user user, password ****
   sending capabilities command
   devel-peer-request: GET http://localhost:$HGPORT2/?cmd=capabilities
+  http auth: user user, password ****
   devel-peer-request:   finished in *.???? seconds (200) (glob)
   query 1; heads
   sending batch command
@@ -270,7 +271,6 @@
   devel-peer-request:   Vary X-HgArg-1,X-HgProto-1
   devel-peer-request:   X-hgproto-1 0.1 0.2 comp=$USUAL_COMPRESSIONS$
   devel-peer-request:   16 bytes of commands arguments in headers
-  http auth: user user, password ****
   devel-peer-request:   finished in *.???? seconds (200) (glob)
   received listkey for "phases": 58 bytes
   checking for updated bookmarks
@@ -340,57 +340,49 @@
   $ hg rollback -q
 
   $ sed 's/.*] "/"/' < ../access.log
-  "GET /?cmd=capabilities HTTP/1.1" 200 -
-  "GET /?cmd=lookup HTTP/1.1" 200 - x-hgarg-1:key=tip x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
-  "GET /?cmd=listkeys HTTP/1.1" 401 - x-hgarg-1:namespace=namespaces x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
+  "GET /?cmd=capabilities HTTP/1.1" 401 -
+  "GET /?cmd=capabilities HTTP/1.1" 401 -
+  "GET /?cmd=capabilities HTTP/1.1" 401 -
   "GET /?cmd=capabilities HTTP/1.1" 200 -
   "GET /?cmd=lookup HTTP/1.1" 200 - x-hgarg-1:key=tip x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
-  "GET /?cmd=listkeys HTTP/1.1" 401 - x-hgarg-1:namespace=namespaces x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
+  "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=namespaces x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
+  "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
+  "GET /?cmd=capabilities HTTP/1.1" 401 -
   "GET /?cmd=capabilities HTTP/1.1" 200 -
   "GET /?cmd=lookup HTTP/1.1" 200 - x-hgarg-1:key=tip x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
-  "GET /?cmd=listkeys HTTP/1.1" 401 - x-hgarg-1:namespace=namespaces x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
-  "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=namespaces x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
-  "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
-  "GET /?cmd=capabilities HTTP/1.1" 200 -
-  "GET /?cmd=lookup HTTP/1.1" 200 - x-hgarg-1:key=tip x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
-  "GET /?cmd=listkeys HTTP/1.1" 401 - x-hgarg-1:namespace=namespaces x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
   "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=namespaces x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
   "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
+  "GET /?cmd=capabilities HTTP/1.1" 401 -
   "GET /?cmd=capabilities HTTP/1.1" 200 -
   "GET /?cmd=lookup HTTP/1.1" 200 - x-hgarg-1:key=tip x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
-  "GET /?cmd=listkeys HTTP/1.1" 401 - x-hgarg-1:namespace=namespaces x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
   "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=namespaces x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
   "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
+  "GET /?cmd=capabilities HTTP/1.1" 401 -
   "GET /?cmd=capabilities HTTP/1.1" 200 -
   "GET /?cmd=lookup HTTP/1.1" 200 - x-hgarg-1:key=tip x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
-  "GET /?cmd=listkeys HTTP/1.1" 401 - x-hgarg-1:namespace=namespaces x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
   "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=namespaces x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
   "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
+  "GET /?cmd=capabilities HTTP/1.1" 401 -
   "GET /?cmd=capabilities HTTP/1.1" 200 -
   "GET /?cmd=lookup HTTP/1.1" 200 - x-hgarg-1:key=tip x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
-  "GET /?cmd=listkeys HTTP/1.1" 401 - x-hgarg-1:namespace=namespaces x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
   "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=namespaces x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
   "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
+  "GET /?cmd=capabilities HTTP/1.1" 401 -
   "GET /?cmd=capabilities HTTP/1.1" 200 -
   "GET /?cmd=branchmap HTTP/1.1" 200 - x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
-  "GET /?cmd=stream_out HTTP/1.1" 401 - x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
   "GET /?cmd=stream_out HTTP/1.1" 200 - x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
   "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$
   "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$
+  "GET /?cmd=capabilities HTTP/1.1" 401 -
   "GET /?cmd=capabilities HTTP/1.1" 200 -
   "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$
-  "GET /?cmd=getbundle HTTP/1.1" 401 - 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$
   "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$
-  "GET /?cmd=capabilities HTTP/1.1" 200 -
-  "GET /?cmd=lookup HTTP/1.1" 200 - x-hgarg-1:key=tip x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
-  "GET /?cmd=listkeys HTTP/1.1" 401 - x-hgarg-1:namespace=namespaces x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
-  "GET /?cmd=capabilities HTTP/1.1" 200 -
-  "GET /?cmd=lookup HTTP/1.1" 200 - x-hgarg-1:key=tip x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
-  "GET /?cmd=listkeys HTTP/1.1" 401 - x-hgarg-1:namespace=namespaces x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
-  "GET /?cmd=listkeys HTTP/1.1" 403 - x-hgarg-1:namespace=namespaces x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
+  "GET /?cmd=capabilities HTTP/1.1" 401 -
+  "GET /?cmd=capabilities HTTP/1.1" 401 -
+  "GET /?cmd=capabilities HTTP/1.1" 403 -
+  "GET /?cmd=capabilities HTTP/1.1" 401 -
   "GET /?cmd=capabilities HTTP/1.1" 200 -
   "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$
-  "GET /?cmd=listkeys HTTP/1.1" 401 - x-hgarg-1:namespace=phases x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
   "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=phases x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
   "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
   "GET /?cmd=branchmap HTTP/1.1" 200 - x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
@@ -398,9 +390,9 @@
   "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
   "POST /?cmd=unbundle HTTP/1.1" 200 - x-hgarg-1:heads=666f726365* (glob)
   "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=phases x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
+  "GET /?cmd=capabilities HTTP/1.1" 401 -
   "GET /?cmd=capabilities HTTP/1.1" 200 -
   "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$
-  "GET /?cmd=listkeys HTTP/1.1" 401 - x-hgarg-1:namespace=phases x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
   "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=phases x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
   "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
   "GET /?cmd=branchmap HTTP/1.1" 200 - x-hgproto-1:0.1 0.2 comp=$USUAL_COMPRESSIONS$
--- a/tests/test-largefiles-wireproto.t	Tue Mar 06 13:08:00 2018 -0600
+++ b/tests/test-largefiles-wireproto.t	Tue Mar 06 13:17:07 2018 -0600
@@ -434,11 +434,11 @@
   > EOF
   $ hg clone --config ui.interactive=true --config extensions.getpass=get_pass.py \
   >          http://user@localhost:$HGPORT credentialclone
-  requesting all changes
   http authorization required for http://localhost:$HGPORT/
   realm: mercurial
   user: user
-  password: adding changesets
+  password: requesting all changes
+  adding changesets
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
--- a/tests/test-pull-http.t	Tue Mar 06 13:08:00 2018 -0600
+++ b/tests/test-pull-http.t	Tue Mar 06 13:17:07 2018 -0600
@@ -50,7 +50,6 @@
   $ hg serve -p $HGPORT -d --pid-file=hg.pid -E errors.log
   $ cat hg.pid >> $DAEMON_PIDS
   $ hg clone http://localhost:$HGPORT/ test4 # bundle2+
-  requesting all changes
   abort: authorization failed
   [255]
   $ hg clone http://localhost:$HGPORT/ test4 --config devel.legacy.exchange=bundle1
@@ -74,7 +73,6 @@
 
   $ req
   pulling from http://localhost:$HGPORT/
-  searching for changes
   abort: authorization failed
   % serve errors
 
--- a/tests/test-push-http.t	Tue Mar 06 13:08:00 2018 -0600
+++ b/tests/test-push-http.t	Tue Mar 06 13:17:07 2018 -0600
@@ -307,28 +307,6 @@
   $ hg --config extensions.strip= strip -r 1:
   saved backup bundle to $TESTTMP/test/.hg/strip-backup/ba677d0156c1-eea704d7-backup.hg
 
-expect authorization error: all users denied
-
-  $ echo '[web]' > .hg/hgrc
-  $ echo 'push_ssl = false' >> .hg/hgrc
-  $ echo 'deny_push = *' >> .hg/hgrc
-  $ req
-  pushing to http://localhost:$HGPORT/
-  searching for changes
-  abort: authorization failed
-  % serve errors
-  [255]
-
-expect authorization error: some users denied, users must be authenticated
-
-  $ echo 'deny_push = unperson' >> .hg/hgrc
-  $ req
-  pushing to http://localhost:$HGPORT/
-  searching for changes
-  abort: authorization failed
-  % serve errors
-  [255]
-
 #if bundle2
 
   $ cat > .hg/hgrc <<EOF