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: |