##// END OF EJS Templates
fix: updated command list mapping for mercurial 6.5, after client upgrade a new command can be given which is clonebundles_manifest, we marked it explicit as pull command. Also added few more commands, fixes RCCE-111
super-admin -
r5484:aa514264 default
parent child Browse files
Show More
@@ -1,161 +1,167 b''
1
1
2 # Copyright (C) 2010-2023 RhodeCode GmbH
2 # Copyright (C) 2010-2023 RhodeCode GmbH
3 #
3 #
4 # This program is free software: you can redistribute it and/or modify
4 # This program is free software: you can redistribute it and/or modify
5 # it under the terms of the GNU Affero General Public License, version 3
5 # it under the terms of the GNU Affero General Public License, version 3
6 # (only), as published by the Free Software Foundation.
6 # (only), as published by the Free Software Foundation.
7 #
7 #
8 # This program is distributed in the hope that it will be useful,
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
11 # GNU General Public License for more details.
12 #
12 #
13 # You should have received a copy of the GNU Affero General Public License
13 # You should have received a copy of the GNU Affero General Public License
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 #
15 #
16 # This program is dual-licensed. If you wish to learn more about the
16 # This program is dual-licensed. If you wish to learn more about the
17 # RhodeCode Enterprise Edition, including its added features, Support services,
17 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
18 # and proprietary license terms, please see https://rhodecode.com/licenses/
19
19
20 """
20 """
21 SimpleHG middleware for handling mercurial protocol request
21 SimpleHG middleware for handling mercurial protocol request
22 (push/clone etc.). It's implemented with basic auth function
22 (push/clone etc.). It's implemented with basic auth function
23 """
23 """
24
24
25 import logging
25 import logging
26 import urllib.parse
26 import urllib.parse
27 import urllib.request
27 import urllib.request
28 import urllib.parse
28 import urllib.parse
29 import urllib.error
29 import urllib.error
30
30
31 from rhodecode.lib import utils
31 from rhodecode.lib import utils
32 from rhodecode.lib.ext_json import json
32 from rhodecode.lib.ext_json import json
33 from rhodecode.lib.middleware import simplevcs
33 from rhodecode.lib.middleware import simplevcs
34 from rhodecode.lib.middleware.utils import get_path_info
34 from rhodecode.lib.middleware.utils import get_path_info
35
35
36 log = logging.getLogger(__name__)
36 log = logging.getLogger(__name__)
37
37
38
38
39 class SimpleHg(simplevcs.SimpleVCS):
39 class SimpleHg(simplevcs.SimpleVCS):
40
40
41 SCM = 'hg'
41 SCM = 'hg'
42
42
43 def _get_repository_name(self, environ):
43 def _get_repository_name(self, environ):
44 """
44 """
45 Gets repository name out of PATH_INFO header
45 Gets repository name out of PATH_INFO header
46
46
47 :param environ: environ where PATH_INFO is stored
47 :param environ: environ where PATH_INFO is stored
48 """
48 """
49 repo_name = get_path_info(environ)
49 repo_name = get_path_info(environ)
50 if repo_name and repo_name.startswith('/'):
50 if repo_name and repo_name.startswith('/'):
51 # remove only the first leading /
51 # remove only the first leading /
52 repo_name = repo_name[1:]
52 repo_name = repo_name[1:]
53 return repo_name.rstrip('/')
53 return repo_name.rstrip('/')
54
54
55 _ACTION_MAPPING = {
55 _ACTION_MAPPING = {
56 'between': 'pull',
57 'branches': 'pull',
58 'branchmap': 'pull',
59 'capabilities': 'pull',
56 'changegroup': 'pull',
60 'changegroup': 'pull',
57 'changegroupsubset': 'pull',
61 'changegroupsubset': 'pull',
62 'changesetdata': 'pull',
63 'clonebundles': 'pull',
64 'clonebundles_manifest': 'pull',
65 'debugwireargs': 'pull',
66 'filedata': 'pull',
58 'getbundle': 'pull',
67 'getbundle': 'pull',
59 'stream_out': 'pull',
60 'listkeys': 'pull',
61 'between': 'pull',
62 'branchmap': 'pull',
63 'branches': 'pull',
64 'clonebundles': 'pull',
65 'capabilities': 'pull',
66 'debugwireargs': 'pull',
67 'heads': 'pull',
68 'heads': 'pull',
68 'lookup': 'pull',
69 'hello': 'pull',
69 'hello': 'pull',
70 'known': 'pull',
70 'known': 'pull',
71 'listkeys': 'pull',
72 'lookup': 'pull',
73 'manifestdata': 'pull',
74 'narrow_widen': 'pull',
75 'protocaps': 'pull',
76 'stream_out': 'pull',
71
77
72 # largefiles
78 # largefiles
79 'getlfile': 'pull',
73 'putlfile': 'push',
80 'putlfile': 'push',
74 'getlfile': 'pull',
75 'statlfile': 'pull',
81 'statlfile': 'pull',
76 'lheads': 'pull',
82 'lheads': 'pull',
77
83
78 # evolve
84 # evolve
79 'evoext_obshashrange_v1': 'pull',
85 'evoext_obshashrange_v1': 'pull',
80 'evoext_obshash': 'pull',
86 'evoext_obshash': 'pull',
81 'evoext_obshash1': 'pull',
87 'evoext_obshash1': 'pull',
82
88
83 'unbundle': 'push',
89 'unbundle': 'push',
84 'pushkey': 'push',
90 'pushkey': 'push',
85 }
91 }
86
92
87 @classmethod
93 @classmethod
88 def _get_xarg_headers(cls, environ):
94 def _get_xarg_headers(cls, environ):
89 i = 1
95 i = 1
90 chunks = [] # gather chunks stored in multiple 'hgarg_N'
96 chunks = [] # gather chunks stored in multiple 'hgarg_N'
91 while True:
97 while True:
92 head = environ.get('HTTP_X_HGARG_{}'.format(i))
98 head = environ.get('HTTP_X_HGARG_{}'.format(i))
93 if not head:
99 if not head:
94 break
100 break
95 i += 1
101 i += 1
96 chunks.append(urllib.parse.unquote_plus(head))
102 chunks.append(urllib.parse.unquote_plus(head))
97 full_arg = ''.join(chunks)
103 full_arg = ''.join(chunks)
98 pref = 'cmds='
104 pref = 'cmds='
99 if full_arg.startswith(pref):
105 if full_arg.startswith(pref):
100 # strip the cmds= header defining our batch commands
106 # strip the cmds= header defining our batch commands
101 full_arg = full_arg[len(pref):]
107 full_arg = full_arg[len(pref):]
102 cmds = full_arg.split(';')
108 cmds = full_arg.split(';')
103 return cmds
109 return cmds
104
110
105 @classmethod
111 @classmethod
106 def _get_batch_cmd(cls, environ):
112 def _get_batch_cmd(cls, environ):
107 """
113 """
108 Handle batch command send commands. Those are ';' separated commands
114 Handle batch command send commands. Those are ';' separated commands
109 sent by batch command that server needs to execute. We need to extract
115 sent by batch command that server needs to execute. We need to extract
110 those, and map them to our ACTION_MAPPING to get all push/pull commands
116 those, and map them to our ACTION_MAPPING to get all push/pull commands
111 specified in the batch
117 specified in the batch
112 """
118 """
113 default = 'push'
119 default = 'push'
114 batch_cmds = []
120 batch_cmds = []
115 try:
121 try:
116 cmds = cls._get_xarg_headers(environ)
122 cmds = cls._get_xarg_headers(environ)
117 for pair in cmds:
123 for pair in cmds:
118 parts = pair.split(' ', 1)
124 parts = pair.split(' ', 1)
119 if len(parts) != 2:
125 if len(parts) != 2:
120 continue
126 continue
121 # entry should be in a format `key ARGS`
127 # entry should be in a format `key ARGS`
122 cmd, args = parts
128 cmd, args = parts
123 action = cls._ACTION_MAPPING.get(cmd, default)
129 action = cls._ACTION_MAPPING.get(cmd, default)
124 batch_cmds.append(action)
130 batch_cmds.append(action)
125 except Exception:
131 except Exception:
126 log.exception('Failed to extract batch commands operations')
132 log.exception('Failed to extract batch commands operations')
127
133
128 # in case we failed, (e.g malformed data) assume it's PUSH sub-command
134 # in case we failed, (e.g malformed data) assume it's PUSH sub-command
129 # for safety
135 # for safety
130 return batch_cmds or [default]
136 return batch_cmds or [default]
131
137
132 def _get_action(self, environ):
138 def _get_action(self, environ):
133 """
139 """
134 Maps mercurial request commands into a pull or push command.
140 Maps mercurial request commands into a pull or push command.
135 In case of unknown/unexpected data, it returns 'push' to be safe.
141 In case of unknown/unexpected data, it returns 'push' to be safe.
136
142
137 :param environ:
143 :param environ:
138 """
144 """
139 default = 'push'
145 default = 'push'
140 query = urllib.parse.parse_qs(environ['QUERY_STRING'], keep_blank_values=True)
146 query = urllib.parse.parse_qs(environ['QUERY_STRING'], keep_blank_values=True)
141
147
142 if 'cmd' in query:
148 if 'cmd' in query:
143 cmd = query['cmd'][0]
149 cmd = query['cmd'][0]
144 if cmd == 'batch':
150 if cmd == 'batch':
145 cmds = self._get_batch_cmd(environ)
151 cmds = self._get_batch_cmd(environ)
146 if 'push' in cmds:
152 if 'push' in cmds:
147 return 'push'
153 return 'push'
148 else:
154 else:
149 return 'pull'
155 return 'pull'
150 return self._ACTION_MAPPING.get(cmd, default)
156 return self._ACTION_MAPPING.get(cmd, default)
151
157
152 return default
158 return default
153
159
154 def _create_wsgi_app(self, repo_path, repo_name, config):
160 def _create_wsgi_app(self, repo_path, repo_name, config):
155 return self.scm_app.create_hg_wsgi_app(repo_path, repo_name, config)
161 return self.scm_app.create_hg_wsgi_app(repo_path, repo_name, config)
156
162
157 def _create_config(self, extras, repo_name, scheme='http'):
163 def _create_config(self, extras, repo_name, scheme='http'):
158 config = utils.make_db_config(repo=repo_name)
164 config = utils.make_db_config(repo=repo_name)
159 config.set('rhodecode', 'RC_SCM_DATA', json.dumps(extras))
165 config.set('rhodecode', 'RC_SCM_DATA', json.dumps(extras))
160
166
161 return config.serialize()
167 return config.serialize()
General Comments 0
You need to be logged in to leave comments. Login now