##// END OF EJS Templates
vcs: skip vcs detection on repo creating page
super-admin -
r5131:8d42472a default
parent child Browse files
Show More
@@ -1,290 +1,295 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 import gzip
21 21 import shutil
22 22 import logging
23 23 import tempfile
24 24 import urllib.parse
25 25
26 26 from webob.exc import HTTPNotFound
27 27
28 28 import rhodecode
29 29 from rhodecode.lib.middleware.utils import get_path_info
30 30 from rhodecode.lib.middleware.appenlight import wrap_in_appenlight_if_enabled
31 31 from rhodecode.lib.middleware.simplegit import SimpleGit, GIT_PROTO_PAT
32 32 from rhodecode.lib.middleware.simplehg import SimpleHg
33 33 from rhodecode.lib.middleware.simplesvn import SimpleSvn
34 34 from rhodecode.model.settings import VcsSettingsModel
35 35
36 36
37 37 log = logging.getLogger(__name__)
38 38
39 39 VCS_TYPE_KEY = '_rc_vcs_type'
40 40 VCS_TYPE_SKIP = '_rc_vcs_skip'
41 41
42 42
43 43 def is_git(environ):
44 44 """
45 45 Returns True if requests should be handled by GIT wsgi middleware
46 46 """
47 47 path_info = get_path_info(environ)
48 48 is_git_path = GIT_PROTO_PAT.match(path_info)
49 49 log.debug(
50 50 'request path: `%s` detected as GIT PROTOCOL %s', path_info,
51 51 is_git_path is not None)
52 52
53 53 return is_git_path
54 54
55 55
56 56 def is_hg(environ):
57 57 """
58 58 Returns True if requests target is mercurial server - header
59 59 ``HTTP_ACCEPT`` of such request would start with ``application/mercurial``.
60 60 """
61 61 is_hg_path = False
62 62
63 63 http_accept = environ.get('HTTP_ACCEPT')
64 64
65 65 if http_accept and http_accept.startswith('application/mercurial'):
66 66 query = urllib.parse.parse_qs(environ['QUERY_STRING'])
67 67 if 'cmd' in query:
68 68 is_hg_path = True
69 69
70 70 path_info = get_path_info(environ)
71 71 log.debug(
72 72 'request path: `%s` detected as HG PROTOCOL %s', path_info,
73 73 is_hg_path)
74 74
75 75 return is_hg_path
76 76
77 77
78 78 def is_svn(environ):
79 79 """
80 80 Returns True if requests target is Subversion server
81 81 """
82 82
83 83 http_dav = environ.get('HTTP_DAV', '')
84 84 magic_path_segment = rhodecode.CONFIG.get(
85 85 'rhodecode_subversion_magic_path', '/!svn')
86 86 path_info = get_path_info(environ)
87 87 is_svn_path = (
88 88 'subversion' in http_dav or
89 89 magic_path_segment in path_info
90 90 or environ['REQUEST_METHOD'] in ['PROPFIND', 'PROPPATCH']
91 91 )
92 92 log.debug(
93 93 'request path: `%s` detected as SVN PROTOCOL %s', path_info,
94 94 is_svn_path)
95 95
96 96 return is_svn_path
97 97
98 98
99 99 class GunzipMiddleware(object):
100 100 """
101 101 WSGI middleware that unzips gzip-encoded requests before
102 102 passing on to the underlying application.
103 103 """
104 104
105 105 def __init__(self, application):
106 106 self.app = application
107 107
108 108 def __call__(self, environ, start_response):
109 109 accepts_encoding_header = environ.get('HTTP_CONTENT_ENCODING', b'')
110 110
111 111 if b'gzip' in accepts_encoding_header:
112 112 log.debug('gzip detected, now running gunzip wrapper')
113 113 wsgi_input = environ['wsgi.input']
114 114
115 115 if not hasattr(environ['wsgi.input'], 'seek'):
116 116 # The gzip implementation in the standard library of Python 2.x
117 117 # requires the '.seek()' and '.tell()' methods to be available
118 118 # on the input stream. Read the data into a temporary file to
119 119 # work around this limitation.
120 120
121 121 wsgi_input = tempfile.SpooledTemporaryFile(64 * 1024 * 1024)
122 122 shutil.copyfileobj(environ['wsgi.input'], wsgi_input)
123 123 wsgi_input.seek(0)
124 124
125 125 environ['wsgi.input'] = gzip.GzipFile(fileobj=wsgi_input, mode='r')
126 126 # since we "Ungzipped" the content we say now it's no longer gzip
127 127 # content encoding
128 128 del environ['HTTP_CONTENT_ENCODING']
129 129
130 130 # content length has changes ? or i'm not sure
131 131 if 'CONTENT_LENGTH' in environ:
132 132 del environ['CONTENT_LENGTH']
133 133 else:
134 134 log.debug('content not gzipped, gzipMiddleware passing '
135 135 'request further')
136 136 return self.app(environ, start_response)
137 137
138 138
139 139 def is_vcs_call(environ):
140 140 if VCS_TYPE_KEY in environ:
141 141 raw_type = environ[VCS_TYPE_KEY]
142 142 return raw_type and raw_type != VCS_TYPE_SKIP
143 143 return False
144 144
145 145
146 146 def detect_vcs_request(environ, backends):
147 147 checks = {
148 148 'hg': (is_hg, SimpleHg),
149 149 'git': (is_git, SimpleGit),
150 150 'svn': (is_svn, SimpleSvn),
151 151 }
152 152 handler = None
153 153 # List of path views first chunk we don't do any checks
154 154 white_list = [
155 155 # favicon often requested by browsers
156 156 'favicon.ico',
157 157
158 158 # e.g /_file_store/download
159 159 '_file_store++',
160 160
161 161 # _admin/api is safe too
162 162 '_admin/api',
163 163
164 164 # _admin/gist is safe too
165 165 '_admin/gists++',
166 166
167 167 # _admin/my_account is safe too
168 168 '_admin/my_account++',
169 169
170 170 # static files no detection
171 171 '_static++',
172 172
173 173 # debug-toolbar
174 174 '_debug_toolbar++',
175 175
176 176 # skip ops ping, status
177 177 '_admin/ops/ping',
178 178 '_admin/ops/status',
179 179
180 180 # full channelstream connect should be VCS skipped
181 181 '_admin/channelstream/connect',
182
183 '++/repo_creating_check'
182 184 ]
183 185 path_info = get_path_info(environ)
184 186 path_url = path_info.lstrip('/')
185 187
186 188 for item in white_list:
187 189 if item.endswith('++') and path_url.startswith(item[:-2]):
188 190 log.debug('path `%s` in whitelist (match:%s), skipping...', path_url, item)
189 191 return handler
192 if item.startswith('++') and path_url.endswith(item[2:]):
193 log.debug('path `%s` in whitelist (match:%s), skipping...', path_url, item)
194 return handler
190 195 if item == path_url:
191 196 log.debug('path `%s` in whitelist (match:%s), skipping...', path_url, item)
192 197 return handler
193 198
194 199 if VCS_TYPE_KEY in environ:
195 200 raw_type = environ[VCS_TYPE_KEY]
196 201 if raw_type == VCS_TYPE_SKIP:
197 202 log.debug('got `skip` marker for vcs detection, skipping...')
198 203 return handler
199 204
200 205 _check, handler = checks.get(raw_type) or [None, None]
201 206 if handler:
202 207 log.debug('got handler:%s from environ', handler)
203 208
204 209 if not handler:
205 210 log.debug('request start: checking if request for `%s` is of VCS type in order: %s', path_url, backends)
206 211 for vcs_type in backends:
207 212 vcs_check, _handler = checks[vcs_type]
208 213 if vcs_check(environ):
209 214 log.debug('vcs handler found %s', _handler)
210 215 handler = _handler
211 216 break
212 217
213 218 return handler
214 219
215 220
216 221 class VCSMiddleware(object):
217 222
218 223 def __init__(self, app, registry, config, appenlight_client):
219 224 self.application = app
220 225 self.registry = registry
221 226 self.config = config
222 227 self.appenlight_client = appenlight_client
223 228 self.use_gzip = True
224 229 # order in which we check the middlewares, based on vcs.backends config
225 230 self.check_middlewares = config['vcs.backends']
226 231
227 232 def vcs_config(self, repo_name=None):
228 233 """
229 234 returns serialized VcsSettings
230 235 """
231 236 try:
232 237 return VcsSettingsModel(
233 238 repo=repo_name).get_ui_settings_as_config_obj()
234 239 except Exception:
235 240 pass
236 241
237 242 def wrap_in_gzip_if_enabled(self, app, config):
238 243 if self.use_gzip:
239 244 app = GunzipMiddleware(app)
240 245 return app
241 246
242 247 def _get_handler_app(self, environ):
243 248 app = None
244 249 log.debug('VCSMiddleware: detecting vcs type.')
245 250 handler = detect_vcs_request(environ, self.check_middlewares)
246 251 if handler:
247 252 app = handler(self.config, self.registry)
248 253
249 254 return app
250 255
251 256 def __call__(self, environ, start_response):
252 257 # check if we handle one of interesting protocols, optionally extract
253 258 # specific vcsSettings and allow changes of how things are wrapped
254 259 vcs_handler = self._get_handler_app(environ)
255 260 if vcs_handler:
256 261 # translate the _REPO_ID into real repo NAME for usage
257 262 # in middleware
258 263
259 264 path_info = get_path_info(environ)
260 265 environ['PATH_INFO'] = vcs_handler._get_by_id(path_info)
261 266
262 267 # Set acl, url and vcs repo names.
263 268 vcs_handler.set_repo_names(environ)
264 269
265 270 # register repo config back to the handler
266 271 vcs_conf = self.vcs_config(vcs_handler.acl_repo_name)
267 272 # maybe damaged/non existent settings. We still want to
268 273 # pass that point to validate on is_valid_and_existing_repo
269 274 # and return proper HTTP Code back to client
270 275 if vcs_conf:
271 276 vcs_handler.repo_vcs_config = vcs_conf
272 277
273 278 # check for type, presence in database and on filesystem
274 279 if not vcs_handler.is_valid_and_existing_repo(
275 280 vcs_handler.acl_repo_name,
276 281 vcs_handler.base_path,
277 282 vcs_handler.SCM):
278 283 return HTTPNotFound()(environ, start_response)
279 284
280 285 environ['REPO_NAME'] = vcs_handler.url_repo_name
281 286
282 287 # Wrap handler in middlewares if they are enabled.
283 288 vcs_handler = self.wrap_in_gzip_if_enabled(
284 289 vcs_handler, self.config)
285 290 vcs_handler, _ = wrap_in_appenlight_if_enabled(
286 291 vcs_handler, self.config, self.appenlight_client)
287 292
288 293 return vcs_handler(environ, start_response)
289 294
290 295 return self.application(environ, start_response)
General Comments 0
You need to be logged in to leave comments. Login now