##// END OF EJS Templates
svn: extend detection of SVN to PROPFIND/PROPATCH methods....
marcink -
r2405:2f080035 default
parent child Browse files
Show More
@@ -1,241 +1,244
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2017 RhodeCode GmbH
3 # Copyright (C) 2010-2017 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 http_dav = environ.get('HTTP_DAV', '')
80 http_dav = environ.get('HTTP_DAV', '')
80 magic_path_segment = rhodecode.CONFIG.get(
81 magic_path_segment = rhodecode.CONFIG.get(
81 'rhodecode_subversion_magic_path', '/!svn')
82 'rhodecode_subversion_magic_path', '/!svn')
82 is_svn_path = (
83 is_svn_path = (
83 'subversion' in http_dav or
84 'subversion' in http_dav or
84 magic_path_segment in environ['PATH_INFO'])
85 magic_path_segment in environ['PATH_INFO']
86 or environ['REQUEST_METHOD'] in ['PROPFIND', 'PROPPATCH']
87 )
85 log.debug(
88 log.debug(
86 'request path: `%s` detected as SVN PROTOCOL %s', environ['PATH_INFO'],
89 'request path: `%s` detected as SVN PROTOCOL %s', environ['PATH_INFO'],
87 is_svn_path)
90 is_svn_path)
88
91
89 return is_svn_path
92 return is_svn_path
90
93
91
94
92 class GunzipMiddleware(object):
95 class GunzipMiddleware(object):
93 """
96 """
94 WSGI middleware that unzips gzip-encoded requests before
97 WSGI middleware that unzips gzip-encoded requests before
95 passing on to the underlying application.
98 passing on to the underlying application.
96 """
99 """
97
100
98 def __init__(self, application):
101 def __init__(self, application):
99 self.app = application
102 self.app = application
100
103
101 def __call__(self, environ, start_response):
104 def __call__(self, environ, start_response):
102 accepts_encoding_header = environ.get('HTTP_CONTENT_ENCODING', b'')
105 accepts_encoding_header = environ.get('HTTP_CONTENT_ENCODING', b'')
103
106
104 if b'gzip' in accepts_encoding_header:
107 if b'gzip' in accepts_encoding_header:
105 log.debug('gzip detected, now running gunzip wrapper')
108 log.debug('gzip detected, now running gunzip wrapper')
106 wsgi_input = environ['wsgi.input']
109 wsgi_input = environ['wsgi.input']
107
110
108 if not hasattr(environ['wsgi.input'], 'seek'):
111 if not hasattr(environ['wsgi.input'], 'seek'):
109 # The gzip implementation in the standard library of Python 2.x
112 # The gzip implementation in the standard library of Python 2.x
110 # requires the '.seek()' and '.tell()' methods to be available
113 # requires the '.seek()' and '.tell()' methods to be available
111 # 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
112 # work around this limitation.
115 # work around this limitation.
113
116
114 wsgi_input = tempfile.SpooledTemporaryFile(64 * 1024 * 1024)
117 wsgi_input = tempfile.SpooledTemporaryFile(64 * 1024 * 1024)
115 shutil.copyfileobj(environ['wsgi.input'], wsgi_input)
118 shutil.copyfileobj(environ['wsgi.input'], wsgi_input)
116 wsgi_input.seek(0)
119 wsgi_input.seek(0)
117
120
118 environ['wsgi.input'] = gzip.GzipFile(fileobj=wsgi_input, mode='r')
121 environ['wsgi.input'] = gzip.GzipFile(fileobj=wsgi_input, mode='r')
119 # 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
120 # content encoding
123 # content encoding
121 del environ['HTTP_CONTENT_ENCODING']
124 del environ['HTTP_CONTENT_ENCODING']
122
125
123 # content length has changes ? or i'm not sure
126 # content length has changes ? or i'm not sure
124 if 'CONTENT_LENGTH' in environ:
127 if 'CONTENT_LENGTH' in environ:
125 del environ['CONTENT_LENGTH']
128 del environ['CONTENT_LENGTH']
126 else:
129 else:
127 log.debug('content not gzipped, gzipMiddleware passing '
130 log.debug('content not gzipped, gzipMiddleware passing '
128 'request further')
131 'request further')
129 return self.app(environ, start_response)
132 return self.app(environ, start_response)
130
133
131
134
132 def is_vcs_call(environ):
135 def is_vcs_call(environ):
133 if VCS_TYPE_KEY in environ:
136 if VCS_TYPE_KEY in environ:
134 raw_type = environ[VCS_TYPE_KEY]
137 raw_type = environ[VCS_TYPE_KEY]
135 return raw_type and raw_type != VCS_TYPE_SKIP
138 return raw_type and raw_type != VCS_TYPE_SKIP
136 return False
139 return False
137
140
138
141
139 def detect_vcs_request(environ, backends):
142 def detect_vcs_request(environ, backends):
140 checks = {
143 checks = {
141 'hg': (is_hg, SimpleHg),
144 'hg': (is_hg, SimpleHg),
142 'git': (is_git, SimpleGit),
145 'git': (is_git, SimpleGit),
143 'svn': (is_svn, SimpleSvn),
146 'svn': (is_svn, SimpleSvn),
144 }
147 }
145 handler = None
148 handler = None
146
149
147 if VCS_TYPE_KEY in environ:
150 if VCS_TYPE_KEY in environ:
148 raw_type = environ[VCS_TYPE_KEY]
151 raw_type = environ[VCS_TYPE_KEY]
149 if raw_type == VCS_TYPE_SKIP:
152 if raw_type == VCS_TYPE_SKIP:
150 log.debug('got `skip` marker for vcs detection, skipping...')
153 log.debug('got `skip` marker for vcs detection, skipping...')
151 return handler
154 return handler
152
155
153 _check, handler = checks.get(raw_type) or [None, None]
156 _check, handler = checks.get(raw_type) or [None, None]
154 if handler:
157 if handler:
155 log.debug('got handler:%s from environ', handler)
158 log.debug('got handler:%s from environ', handler)
156
159
157 if not handler:
160 if not handler:
158 log.debug('checking if request is of VCS type in order: %s', backends)
161 log.debug('checking if request is of VCS type in order: %s', backends)
159 for vcs_type in backends:
162 for vcs_type in backends:
160 vcs_check, _handler = checks[vcs_type]
163 vcs_check, _handler = checks[vcs_type]
161 if vcs_check(environ):
164 if vcs_check(environ):
162 log.debug('vcs handler found %s', _handler)
165 log.debug('vcs handler found %s', _handler)
163 handler = _handler
166 handler = _handler
164 break
167 break
165
168
166 return handler
169 return handler
167
170
168
171
169 class VCSMiddleware(object):
172 class VCSMiddleware(object):
170
173
171 def __init__(self, app, registry, config, appenlight_client):
174 def __init__(self, app, registry, config, appenlight_client):
172 self.application = app
175 self.application = app
173 self.registry = registry
176 self.registry = registry
174 self.config = config
177 self.config = config
175 self.appenlight_client = appenlight_client
178 self.appenlight_client = appenlight_client
176 self.use_gzip = True
179 self.use_gzip = True
177 # order in which we check the middlewares, based on vcs.backends config
180 # order in which we check the middlewares, based on vcs.backends config
178 self.check_middlewares = config['vcs.backends']
181 self.check_middlewares = config['vcs.backends']
179
182
180 def vcs_config(self, repo_name=None):
183 def vcs_config(self, repo_name=None):
181 """
184 """
182 returns serialized VcsSettings
185 returns serialized VcsSettings
183 """
186 """
184 try:
187 try:
185 return VcsSettingsModel(
188 return VcsSettingsModel(
186 repo=repo_name).get_ui_settings_as_config_obj()
189 repo=repo_name).get_ui_settings_as_config_obj()
187 except Exception:
190 except Exception:
188 pass
191 pass
189
192
190 def wrap_in_gzip_if_enabled(self, app, config):
193 def wrap_in_gzip_if_enabled(self, app, config):
191 if self.use_gzip:
194 if self.use_gzip:
192 app = GunzipMiddleware(app)
195 app = GunzipMiddleware(app)
193 return app
196 return app
194
197
195 def _get_handler_app(self, environ):
198 def _get_handler_app(self, environ):
196 app = None
199 app = None
197 log.debug('VCSMiddleware: detecting vcs type.')
200 log.debug('VCSMiddleware: detecting vcs type.')
198 handler = detect_vcs_request(environ, self.check_middlewares)
201 handler = detect_vcs_request(environ, self.check_middlewares)
199 if handler:
202 if handler:
200 app = handler(self.config, self.registry)
203 app = handler(self.config, self.registry)
201
204
202 return app
205 return app
203
206
204 def __call__(self, environ, start_response):
207 def __call__(self, environ, start_response):
205 # check if we handle one of interesting protocols, optionally extract
208 # check if we handle one of interesting protocols, optionally extract
206 # specific vcsSettings and allow changes of how things are wrapped
209 # specific vcsSettings and allow changes of how things are wrapped
207 vcs_handler = self._get_handler_app(environ)
210 vcs_handler = self._get_handler_app(environ)
208 if vcs_handler:
211 if vcs_handler:
209 # translate the _REPO_ID into real repo NAME for usage
212 # translate the _REPO_ID into real repo NAME for usage
210 # in middleware
213 # in middleware
211 environ['PATH_INFO'] = vcs_handler._get_by_id(environ['PATH_INFO'])
214 environ['PATH_INFO'] = vcs_handler._get_by_id(environ['PATH_INFO'])
212
215
213 # Set acl, url and vcs repo names.
216 # Set acl, url and vcs repo names.
214 vcs_handler.set_repo_names(environ)
217 vcs_handler.set_repo_names(environ)
215
218
216 # register repo config back to the handler
219 # register repo config back to the handler
217 vcs_conf = self.vcs_config(vcs_handler.acl_repo_name)
220 vcs_conf = self.vcs_config(vcs_handler.acl_repo_name)
218 # maybe damaged/non existent settings. We still want to
221 # maybe damaged/non existent settings. We still want to
219 # pass that point to validate on is_valid_and_existing_repo
222 # pass that point to validate on is_valid_and_existing_repo
220 # and return proper HTTP Code back to client
223 # and return proper HTTP Code back to client
221 if vcs_conf:
224 if vcs_conf:
222 vcs_handler.repo_vcs_config = vcs_conf
225 vcs_handler.repo_vcs_config = vcs_conf
223
226
224 # check for type, presence in database and on filesystem
227 # check for type, presence in database and on filesystem
225 if not vcs_handler.is_valid_and_existing_repo(
228 if not vcs_handler.is_valid_and_existing_repo(
226 vcs_handler.acl_repo_name,
229 vcs_handler.acl_repo_name,
227 vcs_handler.base_path,
230 vcs_handler.base_path,
228 vcs_handler.SCM):
231 vcs_handler.SCM):
229 return HTTPNotFound()(environ, start_response)
232 return HTTPNotFound()(environ, start_response)
230
233
231 environ['REPO_NAME'] = vcs_handler.url_repo_name
234 environ['REPO_NAME'] = vcs_handler.url_repo_name
232
235
233 # Wrap handler in middlewares if they are enabled.
236 # Wrap handler in middlewares if they are enabled.
234 vcs_handler = self.wrap_in_gzip_if_enabled(
237 vcs_handler = self.wrap_in_gzip_if_enabled(
235 vcs_handler, self.config)
238 vcs_handler, self.config)
236 vcs_handler, _ = wrap_in_appenlight_if_enabled(
239 vcs_handler, _ = wrap_in_appenlight_if_enabled(
237 vcs_handler, self.config, self.appenlight_client)
240 vcs_handler, self.config, self.appenlight_client)
238
241
239 return vcs_handler(environ, start_response)
242 return vcs_handler(environ, start_response)
240
243
241 return self.application(environ, start_response)
244 return self.application(environ, start_response)
General Comments 0
You need to be logged in to leave comments. Login now