##// END OF EJS Templates
fix(mercurial): dropped support for depreacted hgsubversion fixes RCCE-12
super-admin -
r1190:cb9d8354 default
parent child Browse files
Show More
@@ -1,242 +1,255 b''
1 1 # RhodeCode VCSServer provides access to different vcs backends via network.
2 2 # Copyright (C) 2014-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 General Public License as published by
6 6 # the Free Software Foundation; either version 3 of the License, or
7 7 # (at your option) any later version.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU General Public License
15 15 # along with this program; if not, write to the Free Software Foundation,
16 16 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17 17
18 18 import os
19 19 import logging
20 20 import itertools
21 21
22 22 import mercurial
23 23 import mercurial.error
24 24 import mercurial.wireprotoserver
25 25 import mercurial.hgweb.common
26 26 import mercurial.hgweb.hgweb_mod
27 27 import webob.exc
28 28
29 29 from vcsserver import pygrack, exceptions, settings, git_lfs
30 30 from vcsserver.str_utils import ascii_bytes, safe_bytes
31 31
32 32 log = logging.getLogger(__name__)
33 33
34 34
35 35 # propagated from mercurial documentation
36 36 HG_UI_SECTIONS = [
37 37 'alias', 'auth', 'decode/encode', 'defaults', 'diff', 'email', 'extensions',
38 38 'format', 'merge-patterns', 'merge-tools', 'hooks', 'http_proxy', 'smtp',
39 39 'patch', 'paths', 'profiling', 'server', 'trusted', 'ui', 'web',
40 40 ]
41 41
42 42
43 43 class HgWeb(mercurial.hgweb.hgweb_mod.hgweb):
44 44 """Extension of hgweb that simplifies some functions."""
45 45
46 46 def _get_view(self, repo):
47 47 """Views are not supported."""
48 48 return repo
49 49
50 50 def loadsubweb(self):
51 51 """The result is only used in the templater method which is not used."""
52 52 return None
53 53
54 54 def run(self):
55 55 """Unused function so raise an exception if accidentally called."""
56 56 raise NotImplementedError
57 57
58 58 def templater(self, req):
59 59 """Function used in an unreachable code path.
60 60
61 61 This code is unreachable because we guarantee that the HTTP request,
62 62 corresponds to a Mercurial command. See the is_hg method. So, we are
63 63 never going to get a user-visible url.
64 64 """
65 65 raise NotImplementedError
66 66
67 67 def archivelist(self, nodeid):
68 68 """Unused function so raise an exception if accidentally called."""
69 69 raise NotImplementedError
70 70
71 71 def __call__(self, environ, start_response):
72 72 """Run the WSGI application.
73 73
74 74 This may be called by multiple threads.
75 75 """
76 76 from mercurial.hgweb import request as requestmod
77 77 req = requestmod.parserequestfromenv(environ)
78 78 res = requestmod.wsgiresponse(req, start_response)
79 79 gen = self.run_wsgi(req, res)
80 80
81 81 first_chunk = None
82 82
83 83 try:
84 84 data = next(gen)
85 85
86 86 def first_chunk():
87 87 yield data
88 88 except StopIteration:
89 89 pass
90 90
91 91 if first_chunk:
92 92 return itertools.chain(first_chunk(), gen)
93 93 return gen
94 94
95 95 def _runwsgi(self, req, res, repo):
96 96
97 97 cmd = req.qsparams.get(b'cmd', '')
98 98 if not mercurial.wireprotoserver.iscmd(cmd):
99 99 # NOTE(marcink): for unsupported commands, we return bad request
100 100 # internally from HG
101 101 log.warning('cmd: `%s` is not supported by the mercurial wireprotocol v1', cmd)
102 102 from mercurial.hgweb.common import statusmessage
103 103 res.status = statusmessage(mercurial.hgweb.common.HTTP_BAD_REQUEST)
104 104 res.setbodybytes(b'')
105 105 return res.sendresponse()
106 106
107 107 return super()._runwsgi(req, res, repo)
108 108
109 109
110 def sanitize_hg_ui(baseui):
111 # NOTE(marcink): since python3 hgsubversion is deprecated.
112 # From old installations we might still have this set enabled
113 # we explicitly remove this now here to make sure it wont propagate further
114
115 if baseui.config(b'extensions', b'hgsubversion') is not None:
116 for cfg in (baseui._ocfg, baseui._tcfg, baseui._ucfg):
117 if b'extensions' in cfg:
118 if b'hgsubversion' in cfg[b'extensions']:
119 del cfg[b'extensions'][b'hgsubversion']
120
121
110 122 def make_hg_ui_from_config(repo_config):
111 123 baseui = mercurial.ui.ui()
112 124
113 125 # clean the baseui object
114 126 baseui._ocfg = mercurial.config.config()
115 127 baseui._ucfg = mercurial.config.config()
116 128 baseui._tcfg = mercurial.config.config()
117 129
118 130 for section, option, value in repo_config:
119 131 baseui.setconfig(
120 132 ascii_bytes(section, allow_bytes=True),
121 133 ascii_bytes(option, allow_bytes=True),
122 134 ascii_bytes(value, allow_bytes=True))
123 135
124 136 # make our hgweb quiet so it doesn't print output
125 137 baseui.setconfig(b'ui', b'quiet', b'true')
126 138
127 139 return baseui
128 140
129 141
130 142 def update_hg_ui_from_hgrc(baseui, repo_path):
131 143 path = os.path.join(repo_path, '.hg', 'hgrc')
132 144
133 145 if not os.path.isfile(path):
134 146 log.debug('hgrc file is not present at %s, skipping...', path)
135 147 return
136 148 log.debug('reading hgrc from %s', path)
137 149 cfg = mercurial.config.config()
138 150 cfg.read(ascii_bytes(path))
139 151 for section in HG_UI_SECTIONS:
140 152 for k, v in cfg.items(section):
141 153 log.debug('settings ui from file: [%s] %s=%s', section, k, v)
142 154 baseui.setconfig(
143 155 ascii_bytes(section, allow_bytes=True),
144 156 ascii_bytes(k, allow_bytes=True),
145 157 ascii_bytes(v, allow_bytes=True))
146 158
147 159
148 160 def create_hg_wsgi_app(repo_path, repo_name, config):
149 161 """
150 162 Prepares a WSGI application to handle Mercurial requests.
151 163
152 164 :param config: is a list of 3-item tuples representing a ConfigObject
153 165 (it is the serialized version of the config object).
154 166 """
155 167 log.debug("Creating Mercurial WSGI application")
156 168
157 169 baseui = make_hg_ui_from_config(config)
158 170 update_hg_ui_from_hgrc(baseui, repo_path)
171 sanitize_hg_ui(baseui)
159 172
160 173 try:
161 174 return HgWeb(safe_bytes(repo_path), name=safe_bytes(repo_name), baseui=baseui)
162 175 except mercurial.error.RequirementError as e:
163 176 raise exceptions.RequirementException(e)(e)
164 177
165 178
166 179 class GitHandler:
167 180 """
168 181 Handler for Git operations like push/pull etc
169 182 """
170 183 def __init__(self, repo_location, repo_name, git_path, update_server_info,
171 184 extras):
172 185 if not os.path.isdir(repo_location):
173 186 raise OSError(repo_location)
174 187 self.content_path = repo_location
175 188 self.repo_name = repo_name
176 189 self.repo_location = repo_location
177 190 self.extras = extras
178 191 self.git_path = git_path
179 192 self.update_server_info = update_server_info
180 193
181 194 def __call__(self, environ, start_response):
182 195 app = webob.exc.HTTPNotFound()
183 196 candidate_paths = (
184 197 self.content_path, os.path.join(self.content_path, '.git'))
185 198
186 199 for content_path in candidate_paths:
187 200 try:
188 201 app = pygrack.GitRepository(
189 202 self.repo_name, content_path, self.git_path,
190 203 self.update_server_info, self.extras)
191 204 break
192 205 except OSError:
193 206 continue
194 207
195 208 return app(environ, start_response)
196 209
197 210
198 211 def create_git_wsgi_app(repo_path, repo_name, config):
199 212 """
200 213 Creates a WSGI application to handle Git requests.
201 214
202 215 :param config: is a dictionary holding the extras.
203 216 """
204 217 git_path = settings.GIT_EXECUTABLE
205 218 update_server_info = config.pop('git_update_server_info')
206 219 app = GitHandler(
207 220 repo_path, repo_name, git_path, update_server_info, config)
208 221
209 222 return app
210 223
211 224
212 225 class GitLFSHandler:
213 226 """
214 227 Handler for Git LFS operations
215 228 """
216 229
217 230 def __init__(self, repo_location, repo_name, git_path, update_server_info,
218 231 extras):
219 232 if not os.path.isdir(repo_location):
220 233 raise OSError(repo_location)
221 234 self.content_path = repo_location
222 235 self.repo_name = repo_name
223 236 self.repo_location = repo_location
224 237 self.extras = extras
225 238 self.git_path = git_path
226 239 self.update_server_info = update_server_info
227 240
228 241 def get_app(self, git_lfs_enabled, git_lfs_store_path, git_lfs_http_scheme):
229 242 app = git_lfs.create_app(git_lfs_enabled, git_lfs_store_path, git_lfs_http_scheme)
230 243 return app
231 244
232 245
233 246 def create_git_lfs_wsgi_app(repo_path, repo_name, config):
234 247 git_path = settings.GIT_EXECUTABLE
235 248 update_server_info = config.pop(b'git_update_server_info')
236 249 git_lfs_enabled = config.pop(b'git_lfs_enabled')
237 250 git_lfs_store_path = config.pop(b'git_lfs_store_path')
238 251 git_lfs_http_scheme = config.pop(b'git_lfs_http_scheme', 'http')
239 252 app = GitLFSHandler(
240 253 repo_path, repo_name, git_path, update_server_info, config)
241 254
242 255 return app.get_app(git_lfs_enabled, git_lfs_store_path, git_lfs_http_scheme)
General Comments 0
You need to be logged in to leave comments. Login now