##// END OF EJS Templates
fix(mercurial): fixed httppostargs logic
super-admin -
r5606:faa7b2f4 default
parent child Browse files
Show More
@@ -1,167 +1,179 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 copy
25 import logging
26 import logging
26 import urllib.parse
27 import urllib.parse
27 import urllib.request
28 import urllib.request
28 import urllib.parse
29 import urllib.parse
29 import urllib.error
30 import urllib.error
30
31
31 from rhodecode.lib import utils
32 from rhodecode.lib import utils
32 from rhodecode.lib.ext_json import json
33 from rhodecode.lib.ext_json import json
33 from rhodecode.lib.middleware import simplevcs
34 from rhodecode.lib.middleware import simplevcs
34 from rhodecode.lib.middleware.utils import get_path_info
35 from rhodecode.lib.middleware.utils import get_path_info
36 from rhodecode.lib.str_utils import safe_str
35
37
36 log = logging.getLogger(__name__)
38 log = logging.getLogger(__name__)
37
39
38
40
39 class SimpleHg(simplevcs.SimpleVCS):
41 class SimpleHg(simplevcs.SimpleVCS):
40
42
41 SCM = 'hg'
43 SCM = 'hg'
42
44
43 def _get_repository_name(self, environ):
45 def _get_repository_name(self, environ):
44 """
46 """
45 Gets repository name out of PATH_INFO header
47 Gets repository name out of PATH_INFO header
46
48
47 :param environ: environ where PATH_INFO is stored
49 :param environ: environ where PATH_INFO is stored
48 """
50 """
49 repo_name = get_path_info(environ)
51 repo_name = get_path_info(environ)
50 if repo_name and repo_name.startswith('/'):
52 if repo_name and repo_name.startswith('/'):
51 # remove only the first leading /
53 # remove only the first leading /
52 repo_name = repo_name[1:]
54 repo_name = repo_name[1:]
53 return repo_name.rstrip('/')
55 return repo_name.rstrip('/')
54
56
55 _ACTION_MAPPING = {
57 _ACTION_MAPPING = {
56 'between': 'pull',
58 'between': 'pull',
57 'branches': 'pull',
59 'branches': 'pull',
58 'branchmap': 'pull',
60 'branchmap': 'pull',
59 'capabilities': 'pull',
61 'capabilities': 'pull',
60 'changegroup': 'pull',
62 'changegroup': 'pull',
61 'changegroupsubset': 'pull',
63 'changegroupsubset': 'pull',
62 'changesetdata': 'pull',
64 'changesetdata': 'pull',
63 'clonebundles': 'pull',
65 'clonebundles': 'pull',
64 'clonebundles_manifest': 'pull',
66 'clonebundles_manifest': 'pull',
65 'debugwireargs': 'pull',
67 'debugwireargs': 'pull',
66 'filedata': 'pull',
68 'filedata': 'pull',
67 'getbundle': 'pull',
69 'getbundle': 'pull',
68 'heads': 'pull',
70 'heads': 'pull',
69 'hello': 'pull',
71 'hello': 'pull',
70 'known': 'pull',
72 'known': 'pull',
71 'listkeys': 'pull',
73 'listkeys': 'pull',
72 'lookup': 'pull',
74 'lookup': 'pull',
73 'manifestdata': 'pull',
75 'manifestdata': 'pull',
74 'narrow_widen': 'pull',
76 'narrow_widen': 'pull',
75 'protocaps': 'pull',
77 'protocaps': 'pull',
76 'stream_out': 'pull',
78 'stream_out': 'pull',
77
79
78 # largefiles
80 # largefiles
79 'getlfile': 'pull',
81 'getlfile': 'pull',
80 'putlfile': 'push',
82 'putlfile': 'push',
81 'statlfile': 'pull',
83 'statlfile': 'pull',
82 'lheads': 'pull',
84 'lheads': 'pull',
83
85
84 # evolve
86 # evolve
85 'evoext_obshashrange_v1': 'pull',
87 'evoext_obshashrange_v1': 'pull',
86 'evoext_obshash': 'pull',
88 'evoext_obshash': 'pull',
87 'evoext_obshash1': 'pull',
89 'evoext_obshash1': 'pull',
88
90
89 'unbundle': 'push',
91 'unbundle': 'push',
90 'pushkey': 'push',
92 'pushkey': 'push',
91 }
93 }
92
94
93 @classmethod
95 @classmethod
94 def _get_xarg_headers(cls, environ):
96 def _get_xarg_headers(cls, environ):
95 i = 1
97 i = 1
96 chunks = [] # gather chunks stored in multiple 'hgarg_N'
98 chunks = [] # gather chunks stored in multiple 'hgarg_N'
97 while True:
99 while True:
98 head = environ.get('HTTP_X_HGARG_{}'.format(i))
100 head = environ.get(f'HTTP_X_HGARG_{i}')
99 if not head:
101 if not head:
100 break
102 break
101 i += 1
103 i += 1
102 chunks.append(urllib.parse.unquote_plus(head))
104 chunks.append(urllib.parse.unquote_plus(head))
103 full_arg = ''.join(chunks)
105 full_arg = ''.join(chunks)
104 pref = 'cmds='
106 pref = 'cmds='
105 if full_arg.startswith(pref):
107 if full_arg.startswith(pref):
106 # strip the cmds= header defining our batch commands
108 # strip the cmds= header defining our batch commands
107 full_arg = full_arg[len(pref):]
109 full_arg = full_arg[len(pref):]
108 cmds = full_arg.split(';')
110 cmds = full_arg.split(';')
109 return cmds
111 return cmds
110
112
111 @classmethod
113 @classmethod
112 def _get_batch_cmd(cls, environ):
114 def _get_batch_cmd(cls, environ):
113 """
115 """
114 Handle batch command send commands. Those are ';' separated commands
116 Handle batch command send commands. Those are ';' separated commands
115 sent by batch command that server needs to execute. We need to extract
117 sent by batch command that server needs to execute. We need to extract
116 those, and map them to our ACTION_MAPPING to get all push/pull commands
118 those, and map them to our ACTION_MAPPING to get all push/pull commands
117 specified in the batch
119 specified in the batch
118 """
120 """
119 default = 'push'
121 default = 'push'
120 batch_cmds = []
122 batch_cmds = []
123
121 try:
124 try:
122 cmds = cls._get_xarg_headers(environ)
125 httppostargs_enabled = True
126 post_args_size = environ.get('HTTP_X_HGARGS_POST')
127 if post_args_size and httppostargs_enabled:
128 # a new proto when httppostargs is enabled
129 response_data = copy.copy(environ['wsgi.input'])
130 cmds = [safe_str(response_data.read(post_args_size))]
131 else:
132 # old way... from headers
133 cmds = cls._get_xarg_headers(environ)
134
123 for pair in cmds:
135 for pair in cmds:
124 parts = pair.split(' ', 1)
136 parts = pair.split(' ', 1)
125 if len(parts) != 2:
137 if len(parts) != 2:
126 continue
138 continue
127 # entry should be in a format `key ARGS`
139 # entry should be in a format `key ARGS`
128 cmd, args = parts
140 cmd, args = parts
129 action = cls._ACTION_MAPPING.get(cmd, default)
141 action = cls._ACTION_MAPPING.get(cmd, default)
130 batch_cmds.append(action)
142 batch_cmds.append(action)
131 except Exception:
143 except Exception:
132 log.exception('Failed to extract batch commands operations')
144 log.exception('Failed to extract batch commands operations')
133
145
134 # in case we failed, (e.g malformed data) assume it's PUSH sub-command
146 # in case we failed, (e.g malformed data) assume it's PUSH sub-command
135 # for safety
147 # for safety
136 return batch_cmds or [default]
148 return batch_cmds or [default]
137
149
138 def _get_action(self, environ):
150 def _get_action(self, environ):
139 """
151 """
140 Maps mercurial request commands into a pull or push command.
152 Maps mercurial request commands into a pull or push command.
141 In case of unknown/unexpected data, it returns 'push' to be safe.
153 In case of unknown/unexpected data, it returns 'push' to be safe.
142
154
143 :param environ:
155 :param environ:
144 """
156 """
145 default = 'push'
157 default = 'push'
146 query = urllib.parse.parse_qs(environ['QUERY_STRING'], keep_blank_values=True)
158 query = urllib.parse.parse_qs(environ['QUERY_STRING'], keep_blank_values=True)
147
159
148 if 'cmd' in query:
160 if 'cmd' in query:
149 cmd = query['cmd'][0]
161 cmd = query['cmd'][0]
150 if cmd == 'batch':
162 if cmd == 'batch':
151 cmds = self._get_batch_cmd(environ)
163 cmds = self._get_batch_cmd(environ)
152 if 'push' in cmds:
164 if 'push' in cmds:
153 return 'push'
165 return 'push'
154 else:
166 else:
155 return 'pull'
167 return 'pull'
156 return self._ACTION_MAPPING.get(cmd, default)
168 return self._ACTION_MAPPING.get(cmd, default)
157
169
158 return default
170 return default
159
171
160 def _create_wsgi_app(self, repo_path, repo_name, config):
172 def _create_wsgi_app(self, repo_path, repo_name, config):
161 return self.scm_app.create_hg_wsgi_app(repo_path, repo_name, config)
173 return self.scm_app.create_hg_wsgi_app(repo_path, repo_name, config)
162
174
163 def _create_config(self, extras, repo_name, scheme='http'):
175 def _create_config(self, extras, repo_name, scheme='http'):
164 config = utils.make_db_config(repo=repo_name)
176 config = utils.make_db_config(repo=repo_name)
165 config.set('rhodecode', 'RC_SCM_DATA', json.dumps(extras))
177 config.set('rhodecode', 'RC_SCM_DATA', json.dumps(extras))
166
178
167 return config.serialize()
179 return config.serialize()
General Comments 0
You need to be logged in to leave comments. Login now