##// END OF EJS Templates
artifacts: skip vcs detection for artifact downloads.
marcink -
r4475:e9be5c19 default
parent child Browse files
Show More
@@ -1,244 +1,267 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2020 RhodeCode GmbH
3 # Copyright (C) 2010-2020 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import gzip
21 import gzip
22 import shutil
22 import shutil
23 import logging
23 import logging
24 import tempfile
24 import tempfile
25 import urlparse
25 import urlparse
26
26
27 from webob.exc import HTTPNotFound
27 from webob.exc import HTTPNotFound
28
28
29 import rhodecode
29 import rhodecode
30 from rhodecode.lib.middleware.appenlight import wrap_in_appenlight_if_enabled
30 from rhodecode.lib.middleware.appenlight import wrap_in_appenlight_if_enabled
31 from rhodecode.lib.middleware.simplegit import SimpleGit, GIT_PROTO_PAT
31 from rhodecode.lib.middleware.simplegit import SimpleGit, GIT_PROTO_PAT
32 from rhodecode.lib.middleware.simplehg import SimpleHg
32 from rhodecode.lib.middleware.simplehg import SimpleHg
33 from rhodecode.lib.middleware.simplesvn import SimpleSvn
33 from rhodecode.lib.middleware.simplesvn import SimpleSvn
34 from rhodecode.model.settings import VcsSettingsModel
34 from rhodecode.model.settings import VcsSettingsModel
35
35
36 log = logging.getLogger(__name__)
36 log = logging.getLogger(__name__)
37
37
38 VCS_TYPE_KEY = '_rc_vcs_type'
38 VCS_TYPE_KEY = '_rc_vcs_type'
39 VCS_TYPE_SKIP = '_rc_vcs_skip'
39 VCS_TYPE_SKIP = '_rc_vcs_skip'
40
40
41
41
42 def is_git(environ):
42 def is_git(environ):
43 """
43 """
44 Returns True if requests should be handled by GIT wsgi middleware
44 Returns True if requests should be handled by GIT wsgi middleware
45 """
45 """
46 is_git_path = GIT_PROTO_PAT.match(environ['PATH_INFO'])
46 is_git_path = GIT_PROTO_PAT.match(environ['PATH_INFO'])
47 log.debug(
47 log.debug(
48 'request path: `%s` detected as GIT PROTOCOL %s', environ['PATH_INFO'],
48 'request path: `%s` detected as GIT PROTOCOL %s', environ['PATH_INFO'],
49 is_git_path is not None)
49 is_git_path is not None)
50
50
51 return is_git_path
51 return is_git_path
52
52
53
53
54 def is_hg(environ):
54 def is_hg(environ):
55 """
55 """
56 Returns True if requests target is mercurial server - header
56 Returns True if requests target is mercurial server - header
57 ``HTTP_ACCEPT`` of such request would start with ``application/mercurial``.
57 ``HTTP_ACCEPT`` of such request would start with ``application/mercurial``.
58 """
58 """
59 is_hg_path = False
59 is_hg_path = False
60
60
61 http_accept = environ.get('HTTP_ACCEPT')
61 http_accept = environ.get('HTTP_ACCEPT')
62
62
63 if http_accept and http_accept.startswith('application/mercurial'):
63 if http_accept and http_accept.startswith('application/mercurial'):
64 query = urlparse.parse_qs(environ['QUERY_STRING'])
64 query = urlparse.parse_qs(environ['QUERY_STRING'])
65 if 'cmd' in query:
65 if 'cmd' in query:
66 is_hg_path = True
66 is_hg_path = True
67
67
68 log.debug(
68 log.debug(
69 'request path: `%s` detected as HG PROTOCOL %s', environ['PATH_INFO'],
69 'request path: `%s` detected as HG PROTOCOL %s', environ['PATH_INFO'],
70 is_hg_path)
70 is_hg_path)
71
71
72 return is_hg_path
72 return is_hg_path
73
73
74
74
75 def is_svn(environ):
75 def is_svn(environ):
76 """
76 """
77 Returns True if requests target is Subversion server
77 Returns True if requests target is Subversion server
78 """
78 """
79
79
80 http_dav = environ.get('HTTP_DAV', '')
80 http_dav = environ.get('HTTP_DAV', '')
81 magic_path_segment = rhodecode.CONFIG.get(
81 magic_path_segment = rhodecode.CONFIG.get(
82 'rhodecode_subversion_magic_path', '/!svn')
82 'rhodecode_subversion_magic_path', '/!svn')
83 is_svn_path = (
83 is_svn_path = (
84 'subversion' in http_dav or
84 'subversion' in http_dav or
85 magic_path_segment in environ['PATH_INFO']
85 magic_path_segment in environ['PATH_INFO']
86 or environ['REQUEST_METHOD'] in ['PROPFIND', 'PROPPATCH']
86 or environ['REQUEST_METHOD'] in ['PROPFIND', 'PROPPATCH']
87 )
87 )
88 log.debug(
88 log.debug(
89 'request path: `%s` detected as SVN PROTOCOL %s', environ['PATH_INFO'],
89 'request path: `%s` detected as SVN PROTOCOL %s', environ['PATH_INFO'],
90 is_svn_path)
90 is_svn_path)
91
91
92 return is_svn_path
92 return is_svn_path
93
93
94
94
95 class GunzipMiddleware(object):
95 class GunzipMiddleware(object):
96 """
96 """
97 WSGI middleware that unzips gzip-encoded requests before
97 WSGI middleware that unzips gzip-encoded requests before
98 passing on to the underlying application.
98 passing on to the underlying application.
99 """
99 """
100
100
101 def __init__(self, application):
101 def __init__(self, application):
102 self.app = application
102 self.app = application
103
103
104 def __call__(self, environ, start_response):
104 def __call__(self, environ, start_response):
105 accepts_encoding_header = environ.get('HTTP_CONTENT_ENCODING', b'')
105 accepts_encoding_header = environ.get('HTTP_CONTENT_ENCODING', b'')
106
106
107 if b'gzip' in accepts_encoding_header:
107 if b'gzip' in accepts_encoding_header:
108 log.debug('gzip detected, now running gunzip wrapper')
108 log.debug('gzip detected, now running gunzip wrapper')
109 wsgi_input = environ['wsgi.input']
109 wsgi_input = environ['wsgi.input']
110
110
111 if not hasattr(environ['wsgi.input'], 'seek'):
111 if not hasattr(environ['wsgi.input'], 'seek'):
112 # The gzip implementation in the standard library of Python 2.x
112 # The gzip implementation in the standard library of Python 2.x
113 # requires the '.seek()' and '.tell()' methods to be available
113 # requires the '.seek()' and '.tell()' methods to be available
114 # on the input stream. Read the data into a temporary file to
114 # on the input stream. Read the data into a temporary file to
115 # work around this limitation.
115 # work around this limitation.
116
116
117 wsgi_input = tempfile.SpooledTemporaryFile(64 * 1024 * 1024)
117 wsgi_input = tempfile.SpooledTemporaryFile(64 * 1024 * 1024)
118 shutil.copyfileobj(environ['wsgi.input'], wsgi_input)
118 shutil.copyfileobj(environ['wsgi.input'], wsgi_input)
119 wsgi_input.seek(0)
119 wsgi_input.seek(0)
120
120
121 environ['wsgi.input'] = gzip.GzipFile(fileobj=wsgi_input, mode='r')
121 environ['wsgi.input'] = gzip.GzipFile(fileobj=wsgi_input, mode='r')
122 # since we "Ungzipped" the content we say now it's no longer gzip
122 # since we "Ungzipped" the content we say now it's no longer gzip
123 # content encoding
123 # content encoding
124 del environ['HTTP_CONTENT_ENCODING']
124 del environ['HTTP_CONTENT_ENCODING']
125
125
126 # content length has changes ? or i'm not sure
126 # content length has changes ? or i'm not sure
127 if 'CONTENT_LENGTH' in environ:
127 if 'CONTENT_LENGTH' in environ:
128 del environ['CONTENT_LENGTH']
128 del environ['CONTENT_LENGTH']
129 else:
129 else:
130 log.debug('content not gzipped, gzipMiddleware passing '
130 log.debug('content not gzipped, gzipMiddleware passing '
131 'request further')
131 'request further')
132 return self.app(environ, start_response)
132 return self.app(environ, start_response)
133
133
134
134
135 def is_vcs_call(environ):
135 def is_vcs_call(environ):
136 if VCS_TYPE_KEY in environ:
136 if VCS_TYPE_KEY in environ:
137 raw_type = environ[VCS_TYPE_KEY]
137 raw_type = environ[VCS_TYPE_KEY]
138 return raw_type and raw_type != VCS_TYPE_SKIP
138 return raw_type and raw_type != VCS_TYPE_SKIP
139 return False
139 return False
140
140
141
141
142 def get_path_elem(route_path):
143 if not route_path:
144 return None
145
146 cleaned_route_path = route_path.lstrip('/')
147 if cleaned_route_path:
148 cleaned_route_path_elems = cleaned_route_path.split('/')
149 if cleaned_route_path_elems:
150 return cleaned_route_path_elems[0]
151 return None
152
153
142 def detect_vcs_request(environ, backends):
154 def detect_vcs_request(environ, backends):
143 checks = {
155 checks = {
144 'hg': (is_hg, SimpleHg),
156 'hg': (is_hg, SimpleHg),
145 'git': (is_git, SimpleGit),
157 'git': (is_git, SimpleGit),
146 'svn': (is_svn, SimpleSvn),
158 'svn': (is_svn, SimpleSvn),
147 }
159 }
148 handler = None
160 handler = None
161 # List of path views first chunk we don't do any checks
162 white_list = [
163 # e.g /_file_store/download
164 '_file_store'
165 ]
166
167 path_info = environ['PATH_INFO']
168
169 if get_path_elem(path_info) in white_list:
170 log.debug('path `%s` in whitelist, skipping...', path_info)
171 return handler
149
172
150 if VCS_TYPE_KEY in environ:
173 if VCS_TYPE_KEY in environ:
151 raw_type = environ[VCS_TYPE_KEY]
174 raw_type = environ[VCS_TYPE_KEY]
152 if raw_type == VCS_TYPE_SKIP:
175 if raw_type == VCS_TYPE_SKIP:
153 log.debug('got `skip` marker for vcs detection, skipping...')
176 log.debug('got `skip` marker for vcs detection, skipping...')
154 return handler
177 return handler
155
178
156 _check, handler = checks.get(raw_type) or [None, None]
179 _check, handler = checks.get(raw_type) or [None, None]
157 if handler:
180 if handler:
158 log.debug('got handler:%s from environ', handler)
181 log.debug('got handler:%s from environ', handler)
159
182
160 if not handler:
183 if not handler:
161 log.debug('request start: checking if request is of VCS type in order: %s', backends)
184 log.debug('request start: checking if request is of VCS type in order: %s', backends)
162 for vcs_type in backends:
185 for vcs_type in backends:
163 vcs_check, _handler = checks[vcs_type]
186 vcs_check, _handler = checks[vcs_type]
164 if vcs_check(environ):
187 if vcs_check(environ):
165 log.debug('vcs handler found %s', _handler)
188 log.debug('vcs handler found %s', _handler)
166 handler = _handler
189 handler = _handler
167 break
190 break
168
191
169 return handler
192 return handler
170
193
171
194
172 class VCSMiddleware(object):
195 class VCSMiddleware(object):
173
196
174 def __init__(self, app, registry, config, appenlight_client):
197 def __init__(self, app, registry, config, appenlight_client):
175 self.application = app
198 self.application = app
176 self.registry = registry
199 self.registry = registry
177 self.config = config
200 self.config = config
178 self.appenlight_client = appenlight_client
201 self.appenlight_client = appenlight_client
179 self.use_gzip = True
202 self.use_gzip = True
180 # order in which we check the middlewares, based on vcs.backends config
203 # order in which we check the middlewares, based on vcs.backends config
181 self.check_middlewares = config['vcs.backends']
204 self.check_middlewares = config['vcs.backends']
182
205
183 def vcs_config(self, repo_name=None):
206 def vcs_config(self, repo_name=None):
184 """
207 """
185 returns serialized VcsSettings
208 returns serialized VcsSettings
186 """
209 """
187 try:
210 try:
188 return VcsSettingsModel(
211 return VcsSettingsModel(
189 repo=repo_name).get_ui_settings_as_config_obj()
212 repo=repo_name).get_ui_settings_as_config_obj()
190 except Exception:
213 except Exception:
191 pass
214 pass
192
215
193 def wrap_in_gzip_if_enabled(self, app, config):
216 def wrap_in_gzip_if_enabled(self, app, config):
194 if self.use_gzip:
217 if self.use_gzip:
195 app = GunzipMiddleware(app)
218 app = GunzipMiddleware(app)
196 return app
219 return app
197
220
198 def _get_handler_app(self, environ):
221 def _get_handler_app(self, environ):
199 app = None
222 app = None
200 log.debug('VCSMiddleware: detecting vcs type.')
223 log.debug('VCSMiddleware: detecting vcs type.')
201 handler = detect_vcs_request(environ, self.check_middlewares)
224 handler = detect_vcs_request(environ, self.check_middlewares)
202 if handler:
225 if handler:
203 app = handler(self.config, self.registry)
226 app = handler(self.config, self.registry)
204
227
205 return app
228 return app
206
229
207 def __call__(self, environ, start_response):
230 def __call__(self, environ, start_response):
208 # check if we handle one of interesting protocols, optionally extract
231 # check if we handle one of interesting protocols, optionally extract
209 # specific vcsSettings and allow changes of how things are wrapped
232 # specific vcsSettings and allow changes of how things are wrapped
210 vcs_handler = self._get_handler_app(environ)
233 vcs_handler = self._get_handler_app(environ)
211 if vcs_handler:
234 if vcs_handler:
212 # translate the _REPO_ID into real repo NAME for usage
235 # translate the _REPO_ID into real repo NAME for usage
213 # in middleware
236 # in middleware
214 environ['PATH_INFO'] = vcs_handler._get_by_id(environ['PATH_INFO'])
237 environ['PATH_INFO'] = vcs_handler._get_by_id(environ['PATH_INFO'])
215
238
216 # Set acl, url and vcs repo names.
239 # Set acl, url and vcs repo names.
217 vcs_handler.set_repo_names(environ)
240 vcs_handler.set_repo_names(environ)
218
241
219 # register repo config back to the handler
242 # register repo config back to the handler
220 vcs_conf = self.vcs_config(vcs_handler.acl_repo_name)
243 vcs_conf = self.vcs_config(vcs_handler.acl_repo_name)
221 # maybe damaged/non existent settings. We still want to
244 # maybe damaged/non existent settings. We still want to
222 # pass that point to validate on is_valid_and_existing_repo
245 # pass that point to validate on is_valid_and_existing_repo
223 # and return proper HTTP Code back to client
246 # and return proper HTTP Code back to client
224 if vcs_conf:
247 if vcs_conf:
225 vcs_handler.repo_vcs_config = vcs_conf
248 vcs_handler.repo_vcs_config = vcs_conf
226
249
227 # check for type, presence in database and on filesystem
250 # check for type, presence in database and on filesystem
228 if not vcs_handler.is_valid_and_existing_repo(
251 if not vcs_handler.is_valid_and_existing_repo(
229 vcs_handler.acl_repo_name,
252 vcs_handler.acl_repo_name,
230 vcs_handler.base_path,
253 vcs_handler.base_path,
231 vcs_handler.SCM):
254 vcs_handler.SCM):
232 return HTTPNotFound()(environ, start_response)
255 return HTTPNotFound()(environ, start_response)
233
256
234 environ['REPO_NAME'] = vcs_handler.url_repo_name
257 environ['REPO_NAME'] = vcs_handler.url_repo_name
235
258
236 # Wrap handler in middlewares if they are enabled.
259 # Wrap handler in middlewares if they are enabled.
237 vcs_handler = self.wrap_in_gzip_if_enabled(
260 vcs_handler = self.wrap_in_gzip_if_enabled(
238 vcs_handler, self.config)
261 vcs_handler, self.config)
239 vcs_handler, _ = wrap_in_appenlight_if_enabled(
262 vcs_handler, _ = wrap_in_appenlight_if_enabled(
240 vcs_handler, self.config, self.appenlight_client)
263 vcs_handler, self.config, self.appenlight_client)
241
264
242 return vcs_handler(environ, start_response)
265 return vcs_handler(environ, start_response)
243
266
244 return self.application(environ, start_response)
267 return self.application(environ, start_response)
General Comments 0
You need to be logged in to leave comments. Login now