mercurial/wireproto.py
changeset 36760 7bf80d9d9543
parent 36719 390d16ea7c76
parent 36755 ff4bc0ab6740
child 36800 0b18604db95e
equal deleted inserted replaced
36747:4c71a26a4009 36760:7bf80d9d9543
   670 POLICY_V1_ONLY = 'v1-only'
   670 POLICY_V1_ONLY = 'v1-only'
   671 POLICY_V2_ONLY = 'v2-only'
   671 POLICY_V2_ONLY = 'v2-only'
   672 
   672 
   673 commands = commanddict()
   673 commands = commanddict()
   674 
   674 
       
   675 # Maps wire protocol name to operation type. This is used for permissions
       
   676 # checking. All defined @wireiprotocommand should have an entry in this
       
   677 # dict.
       
   678 permissions = {}
       
   679 
   675 def wireprotocommand(name, args='', transportpolicy=POLICY_ALL):
   680 def wireprotocommand(name, args='', transportpolicy=POLICY_ALL):
   676     """Decorator to declare a wire protocol command.
   681     """Decorator to declare a wire protocol command.
   677 
   682 
   678     ``name`` is the name of the wire protocol command being provided.
   683     ``name`` is the name of the wire protocol command being provided.
   679 
   684 
   699     def register(func):
   704     def register(func):
   700         commands[name] = commandentry(func, args=args, transports=transports)
   705         commands[name] = commandentry(func, args=args, transports=transports)
   701         return func
   706         return func
   702     return register
   707     return register
   703 
   708 
       
   709 # TODO define a more appropriate permissions type to use for this.
       
   710 permissions['batch'] = 'pull'
   704 @wireprotocommand('batch', 'cmds *')
   711 @wireprotocommand('batch', 'cmds *')
   705 def batch(repo, proto, cmds, others):
   712 def batch(repo, proto, cmds, others):
   706     repo = repo.filtered("served")
   713     repo = repo.filtered("served")
   707     res = []
   714     res = []
   708     for pair in cmds.split(';'):
   715     for pair in cmds.split(';'):
   711         for a in args.split(','):
   718         for a in args.split(','):
   712             if a:
   719             if a:
   713                 n, v = a.split('=')
   720                 n, v = a.split('=')
   714                 vals[unescapearg(n)] = unescapearg(v)
   721                 vals[unescapearg(n)] = unescapearg(v)
   715         func, spec = commands[op]
   722         func, spec = commands[op]
       
   723 
       
   724         # If the protocol supports permissions checking, perform that
       
   725         # checking on each batched command.
       
   726         # TODO formalize permission checking as part of protocol interface.
       
   727         if util.safehasattr(proto, 'checkperm'):
       
   728             # Assume commands with no defined permissions are writes / for
       
   729             # pushes. This is the safest from a security perspective because
       
   730             # it doesn't allow commands with undefined semantics from
       
   731             # bypassing permissions checks.
       
   732             proto.checkperm(permissions.get(op, 'push'))
       
   733 
   716         if spec:
   734         if spec:
   717             keys = spec.split()
   735             keys = spec.split()
   718             data = {}
   736             data = {}
   719             for k in keys:
   737             for k in keys:
   720                 if k == '*':
   738                 if k == '*':
   738             result = result.data
   756             result = result.data
   739         res.append(escapearg(result))
   757         res.append(escapearg(result))
   740 
   758 
   741     return bytesresponse(';'.join(res))
   759     return bytesresponse(';'.join(res))
   742 
   760 
       
   761 permissions['between'] = 'pull'
   743 @wireprotocommand('between', 'pairs', transportpolicy=POLICY_V1_ONLY)
   762 @wireprotocommand('between', 'pairs', transportpolicy=POLICY_V1_ONLY)
   744 def between(repo, proto, pairs):
   763 def between(repo, proto, pairs):
   745     pairs = [decodelist(p, '-') for p in pairs.split(" ")]
   764     pairs = [decodelist(p, '-') for p in pairs.split(" ")]
   746     r = []
   765     r = []
   747     for b in repo.between(pairs):
   766     for b in repo.between(pairs):
   748         r.append(encodelist(b) + "\n")
   767         r.append(encodelist(b) + "\n")
   749 
   768 
   750     return bytesresponse(''.join(r))
   769     return bytesresponse(''.join(r))
   751 
   770 
       
   771 permissions['branchmap'] = 'pull'
   752 @wireprotocommand('branchmap')
   772 @wireprotocommand('branchmap')
   753 def branchmap(repo, proto):
   773 def branchmap(repo, proto):
   754     branchmap = repo.branchmap()
   774     branchmap = repo.branchmap()
   755     heads = []
   775     heads = []
   756     for branch, nodes in branchmap.iteritems():
   776     for branch, nodes in branchmap.iteritems():
   758         branchnodes = encodelist(nodes)
   778         branchnodes = encodelist(nodes)
   759         heads.append('%s %s' % (branchname, branchnodes))
   779         heads.append('%s %s' % (branchname, branchnodes))
   760 
   780 
   761     return bytesresponse('\n'.join(heads))
   781     return bytesresponse('\n'.join(heads))
   762 
   782 
       
   783 permissions['branches'] = 'pull'
   763 @wireprotocommand('branches', 'nodes', transportpolicy=POLICY_V1_ONLY)
   784 @wireprotocommand('branches', 'nodes', transportpolicy=POLICY_V1_ONLY)
   764 def branches(repo, proto, nodes):
   785 def branches(repo, proto, nodes):
   765     nodes = decodelist(nodes)
   786     nodes = decodelist(nodes)
   766     r = []
   787     r = []
   767     for b in repo.branches(nodes):
   788     for b in repo.branches(nodes):
   768         r.append(encodelist(b) + "\n")
   789         r.append(encodelist(b) + "\n")
   769 
   790 
   770     return bytesresponse(''.join(r))
   791     return bytesresponse(''.join(r))
   771 
   792 
       
   793 permissions['clonebundles'] = 'pull'
   772 @wireprotocommand('clonebundles', '')
   794 @wireprotocommand('clonebundles', '')
   773 def clonebundles(repo, proto):
   795 def clonebundles(repo, proto):
   774     """Server command for returning info for available bundles to seed clones.
   796     """Server command for returning info for available bundles to seed clones.
   775 
   797 
   776     Clients will parse this response and determine what bundle to fetch.
   798     Clients will parse this response and determine what bundle to fetch.
   819 
   841 
   820     return proto.addcapabilities(repo, caps)
   842     return proto.addcapabilities(repo, caps)
   821 
   843 
   822 # If you are writing an extension and consider wrapping this function. Wrap
   844 # If you are writing an extension and consider wrapping this function. Wrap
   823 # `_capabilities` instead.
   845 # `_capabilities` instead.
       
   846 permissions['capabilities'] = 'pull'
   824 @wireprotocommand('capabilities')
   847 @wireprotocommand('capabilities')
   825 def capabilities(repo, proto):
   848 def capabilities(repo, proto):
   826     return bytesresponse(' '.join(_capabilities(repo, proto)))
   849     return bytesresponse(' '.join(_capabilities(repo, proto)))
   827 
   850 
       
   851 permissions['changegroup'] = 'pull'
   828 @wireprotocommand('changegroup', 'roots', transportpolicy=POLICY_V1_ONLY)
   852 @wireprotocommand('changegroup', 'roots', transportpolicy=POLICY_V1_ONLY)
   829 def changegroup(repo, proto, roots):
   853 def changegroup(repo, proto, roots):
   830     nodes = decodelist(roots)
   854     nodes = decodelist(roots)
   831     outgoing = discovery.outgoing(repo, missingroots=nodes,
   855     outgoing = discovery.outgoing(repo, missingroots=nodes,
   832                                   missingheads=repo.heads())
   856                                   missingheads=repo.heads())
   833     cg = changegroupmod.makechangegroup(repo, outgoing, '01', 'serve')
   857     cg = changegroupmod.makechangegroup(repo, outgoing, '01', 'serve')
   834     gen = iter(lambda: cg.read(32768), '')
   858     gen = iter(lambda: cg.read(32768), '')
   835     return streamres(gen=gen)
   859     return streamres(gen=gen)
   836 
   860 
       
   861 permissions['changegroupsubset'] = 'pull'
   837 @wireprotocommand('changegroupsubset', 'bases heads',
   862 @wireprotocommand('changegroupsubset', 'bases heads',
   838                   transportpolicy=POLICY_V1_ONLY)
   863                   transportpolicy=POLICY_V1_ONLY)
   839 def changegroupsubset(repo, proto, bases, heads):
   864 def changegroupsubset(repo, proto, bases, heads):
   840     bases = decodelist(bases)
   865     bases = decodelist(bases)
   841     heads = decodelist(heads)
   866     heads = decodelist(heads)
   843                                   missingheads=heads)
   868                                   missingheads=heads)
   844     cg = changegroupmod.makechangegroup(repo, outgoing, '01', 'serve')
   869     cg = changegroupmod.makechangegroup(repo, outgoing, '01', 'serve')
   845     gen = iter(lambda: cg.read(32768), '')
   870     gen = iter(lambda: cg.read(32768), '')
   846     return streamres(gen=gen)
   871     return streamres(gen=gen)
   847 
   872 
       
   873 permissions['debugwireargs'] = 'pull'
   848 @wireprotocommand('debugwireargs', 'one two *')
   874 @wireprotocommand('debugwireargs', 'one two *')
   849 def debugwireargs(repo, proto, one, two, others):
   875 def debugwireargs(repo, proto, one, two, others):
   850     # only accept optional args from the known set
   876     # only accept optional args from the known set
   851     opts = options('debugwireargs', ['three', 'four'], others)
   877     opts = options('debugwireargs', ['three', 'four'], others)
   852     return bytesresponse(repo.debugwireargs(one, two,
   878     return bytesresponse(repo.debugwireargs(one, two,
   853                                             **pycompat.strkwargs(opts)))
   879                                             **pycompat.strkwargs(opts)))
   854 
   880 
       
   881 permissions['getbundle'] = 'pull'
   855 @wireprotocommand('getbundle', '*')
   882 @wireprotocommand('getbundle', '*')
   856 def getbundle(repo, proto, others):
   883 def getbundle(repo, proto, others):
   857     opts = options('getbundle', gboptsmap.keys(), others)
   884     opts = options('getbundle', gboptsmap.keys(), others)
   858     for k, v in opts.iteritems():
   885     for k, v in opts.iteritems():
   859         keytype = gboptsmap[k]
   886         keytype = gboptsmap[k]
   916         chunks = bundler.getchunks()
   943         chunks = bundler.getchunks()
   917         prefercompressed = False
   944         prefercompressed = False
   918 
   945 
   919     return streamres(gen=chunks, prefer_uncompressed=not prefercompressed)
   946     return streamres(gen=chunks, prefer_uncompressed=not prefercompressed)
   920 
   947 
       
   948 permissions['heads'] = 'pull'
   921 @wireprotocommand('heads')
   949 @wireprotocommand('heads')
   922 def heads(repo, proto):
   950 def heads(repo, proto):
   923     h = repo.heads()
   951     h = repo.heads()
   924     return bytesresponse(encodelist(h) + '\n')
   952     return bytesresponse(encodelist(h) + '\n')
   925 
   953 
       
   954 permissions['hello'] = 'pull'
   926 @wireprotocommand('hello')
   955 @wireprotocommand('hello')
   927 def hello(repo, proto):
   956 def hello(repo, proto):
   928     """Called as part of SSH handshake to obtain server info.
   957     """Called as part of SSH handshake to obtain server info.
   929 
   958 
   930     Returns a list of lines describing interesting things about the
   959     Returns a list of lines describing interesting things about the
   936         capabilities: <token0> <token1> <token2>
   965         capabilities: <token0> <token1> <token2>
   937     """
   966     """
   938     caps = capabilities(repo, proto).data
   967     caps = capabilities(repo, proto).data
   939     return bytesresponse('capabilities: %s\n' % caps)
   968     return bytesresponse('capabilities: %s\n' % caps)
   940 
   969 
       
   970 permissions['listkeys'] = 'pull'
   941 @wireprotocommand('listkeys', 'namespace')
   971 @wireprotocommand('listkeys', 'namespace')
   942 def listkeys(repo, proto, namespace):
   972 def listkeys(repo, proto, namespace):
   943     d = sorted(repo.listkeys(encoding.tolocal(namespace)).items())
   973     d = sorted(repo.listkeys(encoding.tolocal(namespace)).items())
   944     return bytesresponse(pushkeymod.encodekeys(d))
   974     return bytesresponse(pushkeymod.encodekeys(d))
   945 
   975 
       
   976 permissions['lookup'] = 'pull'
   946 @wireprotocommand('lookup', 'key')
   977 @wireprotocommand('lookup', 'key')
   947 def lookup(repo, proto, key):
   978 def lookup(repo, proto, key):
   948     try:
   979     try:
   949         k = encoding.tolocal(key)
   980         k = encoding.tolocal(key)
   950         c = repo[k]
   981         c = repo[k]
   953     except Exception as inst:
   984     except Exception as inst:
   954         r = util.forcebytestr(inst)
   985         r = util.forcebytestr(inst)
   955         success = 0
   986         success = 0
   956     return bytesresponse('%d %s\n' % (success, r))
   987     return bytesresponse('%d %s\n' % (success, r))
   957 
   988 
       
   989 permissions['known'] = 'pull'
   958 @wireprotocommand('known', 'nodes *')
   990 @wireprotocommand('known', 'nodes *')
   959 def known(repo, proto, nodes, others):
   991 def known(repo, proto, nodes, others):
   960     v = ''.join(b and '1' or '0' for b in repo.known(decodelist(nodes)))
   992     v = ''.join(b and '1' or '0' for b in repo.known(decodelist(nodes)))
   961     return bytesresponse(v)
   993     return bytesresponse(v)
   962 
   994 
       
   995 permissions['pushkey'] = 'push'
   963 @wireprotocommand('pushkey', 'namespace key old new')
   996 @wireprotocommand('pushkey', 'namespace key old new')
   964 def pushkey(repo, proto, namespace, key, old, new):
   997 def pushkey(repo, proto, namespace, key, old, new):
   965     # compatibility with pre-1.8 clients which were accidentally
   998     # compatibility with pre-1.8 clients which were accidentally
   966     # sending raw binary nodes rather than utf-8-encoded hex
   999     # sending raw binary nodes rather than utf-8-encoded hex
   967     if len(new) == 20 and util.escapestr(new) != new:
  1000     if len(new) == 20 and util.escapestr(new) != new:
   979                          encoding.tolocal(old), new) or False
  1012                          encoding.tolocal(old), new) or False
   980 
  1013 
   981     output = output.getvalue() if output else ''
  1014     output = output.getvalue() if output else ''
   982     return bytesresponse('%d\n%s' % (int(r), output))
  1015     return bytesresponse('%d\n%s' % (int(r), output))
   983 
  1016 
       
  1017 permissions['stream_out'] = 'pull'
   984 @wireprotocommand('stream_out')
  1018 @wireprotocommand('stream_out')
   985 def stream(repo, proto):
  1019 def stream(repo, proto):
   986     '''If the server supports streaming clone, it advertises the "stream"
  1020     '''If the server supports streaming clone, it advertises the "stream"
   987     capability with a value representing the version and flags of the repo
  1021     capability with a value representing the version and flags of the repo
   988     it is serving. Client checks to see if it understands the format.
  1022     it is serving. Client checks to see if it understands the format.
   989     '''
  1023     '''
   990     return streamres_legacy(streamclone.generatev1wireproto(repo))
  1024     return streamres_legacy(streamclone.generatev1wireproto(repo))
   991 
  1025 
       
  1026 permissions['unbundle'] = 'push'
   992 @wireprotocommand('unbundle', 'heads')
  1027 @wireprotocommand('unbundle', 'heads')
   993 def unbundle(repo, proto, heads):
  1028 def unbundle(repo, proto, heads):
   994     their_heads = decodelist(heads)
  1029     their_heads = decodelist(heads)
   995 
  1030 
   996     with proto.mayberedirectstdio() as output:
  1031     with proto.mayberedirectstdio() as output: