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