##// END OF EJS Templates
vcs: Use regular expression to recognize requests to shadow repositories.
Martin Bornhold -
r888:6690eec6 default
parent child Browse files
Show More
@@ -1,224 +1,226 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2016 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
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 Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import gzip
22 import re
22 23 import shutil
23 24 import logging
24 25 import tempfile
25 26 import urlparse
26 27
27 28 from webob.exc import HTTPNotFound
28 29
29 30 import rhodecode
30 31 from rhodecode.lib.middleware.appenlight import wrap_in_appenlight_if_enabled
31 32 from rhodecode.lib.middleware.simplegit import SimpleGit, GIT_PROTO_PAT
32 33 from rhodecode.lib.middleware.simplehg import SimpleHg
33 34 from rhodecode.lib.middleware.simplesvn import SimpleSvn
34 35 from rhodecode.model.settings import VcsSettingsModel
35 36
36 37 log = logging.getLogger(__name__)
37 38
38 39
39 40 def is_git(environ):
40 41 """
41 42 Returns True if requests should be handled by GIT wsgi middleware
42 43 """
43 44 is_git_path = GIT_PROTO_PAT.match(environ['PATH_INFO'])
44 45 log.debug(
45 46 'request path: `%s` detected as GIT PROTOCOL %s', environ['PATH_INFO'],
46 47 is_git_path is not None)
47 48
48 49 return is_git_path
49 50
50 51
51 52 def is_hg(environ):
52 53 """
53 54 Returns True if requests target is mercurial server - header
54 55 ``HTTP_ACCEPT`` of such request would start with ``application/mercurial``.
55 56 """
56 57 is_hg_path = False
57 58
58 59 http_accept = environ.get('HTTP_ACCEPT')
59 60
60 61 if http_accept and http_accept.startswith('application/mercurial'):
61 62 query = urlparse.parse_qs(environ['QUERY_STRING'])
62 63 if 'cmd' in query:
63 64 is_hg_path = True
64 65
65 66 log.debug(
66 67 'request path: `%s` detected as HG PROTOCOL %s', environ['PATH_INFO'],
67 68 is_hg_path)
68 69
69 70 return is_hg_path
70 71
71 72
72 73 def is_svn(environ):
73 74 """
74 75 Returns True if requests target is Subversion server
75 76 """
76 77 http_dav = environ.get('HTTP_DAV', '')
77 78 magic_path_segment = rhodecode.CONFIG.get(
78 79 'rhodecode_subversion_magic_path', '/!svn')
79 80 is_svn_path = (
80 81 'subversion' in http_dav or
81 82 magic_path_segment in environ['PATH_INFO'])
82 83 log.debug(
83 84 'request path: `%s` detected as SVN PROTOCOL %s', environ['PATH_INFO'],
84 85 is_svn_path)
85 86
86 87 return is_svn_path
87 88
88 89
89 90 class GunzipMiddleware(object):
90 91 """
91 92 WSGI middleware that unzips gzip-encoded requests before
92 93 passing on to the underlying application.
93 94 """
94 95
95 96 def __init__(self, application):
96 97 self.app = application
97 98
98 99 def __call__(self, environ, start_response):
99 100 accepts_encoding_header = environ.get('HTTP_CONTENT_ENCODING', b'')
100 101
101 102 if b'gzip' in accepts_encoding_header:
102 103 log.debug('gzip detected, now running gunzip wrapper')
103 104 wsgi_input = environ['wsgi.input']
104 105
105 106 if not hasattr(environ['wsgi.input'], 'seek'):
106 107 # The gzip implementation in the standard library of Python 2.x
107 108 # requires the '.seek()' and '.tell()' methods to be available
108 109 # on the input stream. Read the data into a temporary file to
109 110 # work around this limitation.
110 111
111 112 wsgi_input = tempfile.SpooledTemporaryFile(64 * 1024 * 1024)
112 113 shutil.copyfileobj(environ['wsgi.input'], wsgi_input)
113 114 wsgi_input.seek(0)
114 115
115 116 environ['wsgi.input'] = gzip.GzipFile(fileobj=wsgi_input, mode='r')
116 117 # since we "Ungzipped" the content we say now it's no longer gzip
117 118 # content encoding
118 119 del environ['HTTP_CONTENT_ENCODING']
119 120
120 121 # content length has changes ? or i'm not sure
121 122 if 'CONTENT_LENGTH' in environ:
122 123 del environ['CONTENT_LENGTH']
123 124 else:
124 125 log.debug('content not gzipped, gzipMiddleware passing '
125 126 'request further')
126 127 return self.app(environ, start_response)
127 128
128 129
129 130 class VCSMiddleware(object):
130 131
131 132 def __init__(self, app, config, appenlight_client, registry):
132 133 self.application = app
133 134 self.config = config
134 135 self.appenlight_client = appenlight_client
135 136 self.registry = registry
136 137 self.use_gzip = True
137 138 # order in which we check the middlewares, based on vcs.backends config
138 139 self.check_middlewares = config['vcs.backends']
139 140 self.checks = {
140 141 'hg': (is_hg, SimpleHg),
141 142 'git': (is_git, SimpleGit),
142 143 'svn': (is_svn, SimpleSvn),
143 144 }
144 145
145 146 def vcs_config(self, repo_name=None):
146 147 """
147 148 returns serialized VcsSettings
148 149 """
149 150 return VcsSettingsModel(repo=repo_name).get_ui_settings_as_config_obj()
150 151
151 152 def wrap_in_gzip_if_enabled(self, app, config):
152 153 if self.use_gzip:
153 154 app = GunzipMiddleware(app)
154 155 return app
155 156
156 157 def _get_handler_app(self, environ):
157 158 app = None
158 159 log.debug('Checking vcs types in order: %r', self.check_middlewares)
159 160 for vcs_type in self.check_middlewares:
160 161 vcs_check, handler = self.checks[vcs_type]
161 162 if vcs_check(environ):
162 163 log.debug(
163 164 'Found VCS Middleware to handle the request %s', handler)
164 165 app = handler(self.application, self.config, self.registry)
165 166 break
166 167
167 168 return app
168 169
169 170 def __call__(self, environ, start_response):
170 171 # check if we handle one of interesting protocols, optionally extract
171 172 # specific vcsSettings and allow changes of how things are wrapped
172 173 vcs_handler = self._get_handler_app(environ)
173 174 if vcs_handler:
174 175 # translate the _REPO_ID into real repo NAME for usage
175 176 # in middleware
176 177 environ['PATH_INFO'] = vcs_handler._get_by_id(environ['PATH_INFO'])
177 178 repo_name = vcs_handler._get_repository_name(environ)
178 179
179 180 acl_repo_name = repo_name
180 181 vcs_repo_name = repo_name
181 182 url_repo_name = repo_name
182 183 pr_id = None
183 184
184 # TODO: johbo: recognize a pull request based on pattern matching
185 if '/pull-request/' in repo_name:
186 acl_repo_name, other = repo_name.split('/pull-request/')
187 # TODO: johbo: Set shadow repo path
188 basename, repo_segment = acl_repo_name.rsplit('/', 1)
189 pr_id = int(other[0:-len('/repository')])
190 vcs_repo_name = '{basename}/.__shadow_{repo_segment}_pr-{pr_id}'.format(
191 basename=basename,
192 repo_segment=repo_segment,
193 pr_id=pr_id)
185 pr_regex = re.compile(
186 '(?P<base_name>(?:[\w-]+)(?:/[\w-]+)*)/'
187 '(?P<repo_name>[\w-]+)'
188 '/pull-request/(?P<pr_id>\d+)/repository')
189 match = pr_regex.match(repo_name)
190 if match:
191 match_dict = match.groupdict()
192 pr_id = match_dict.get('pr_id')
193 acl_repo_name = '{base_name}/{repo_name}'.format(**match_dict)
194 vcs_repo_name = '{base_name}/.__shadow_{repo_name}_pr-{pr_id}'.format(
195 **match_dict)
194 196
195 197 log.debug('repo_names %s', {
196 198 'acl_repo_name': acl_repo_name,
197 199 'vcs_repo_name': vcs_repo_name,
198 200 'url_repo_name': url_repo_name,
199 201 })
200 202 log.debug('pull_request %s', pr_id)
201 203
202 204 # check for type, presence in database and on filesystem
203 205 if not vcs_handler.is_valid_and_existing_repo(
204 206 acl_repo_name, vcs_handler.basepath, vcs_handler.SCM):
205 207 return HTTPNotFound()(environ, start_response)
206 208
207 209 # TODO: johbo: Needed for the Pyro4 backend and Mercurial only.
208 210 # Remove once we fully switched to the HTTP backend.
209 211 environ['REPO_NAME'] = url_repo_name
210 212
211 213 # register repo_name and it's config back to the handler
212 214 vcs_handler.acl_repo_name = acl_repo_name
213 215 vcs_handler.url_repo_name = url_repo_name
214 216 vcs_handler.vcs_repo_name = vcs_repo_name
215 217 vcs_handler.pr_id = pr_id
216 218 vcs_handler.repo_vcs_config = self.vcs_config(acl_repo_name)
217 219
218 220 vcs_handler = self.wrap_in_gzip_if_enabled(
219 221 vcs_handler, self.config)
220 222 vcs_handler, _ = wrap_in_appenlight_if_enabled(
221 223 vcs_handler, self.config, self.appenlight_client)
222 224 return vcs_handler(environ, start_response)
223 225
224 226 return self.application(environ, start_response)
General Comments 0
You need to be logged in to leave comments. Login now