##// END OF EJS Templates
svn: fix usage of http exception from webob to pyramid one....
marcink -
r2771:82cb455f default
parent child Browse files
Show More
@@ -1,204 +1,204 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2018 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 base64
22 22 import logging
23 23 import urllib
24 24 from urlparse import urljoin
25 25
26 26 import requests
27 from webob.exc import HTTPNotAcceptable
27 from pyramid.httpexceptions import HTTPNotAcceptable
28 28
29 29 from rhodecode.lib import caches
30 30 from rhodecode.lib.middleware import simplevcs
31 31 from rhodecode.lib.utils import is_valid_repo
32 32 from rhodecode.lib.utils2 import str2bool, safe_int
33 33 from rhodecode.lib.ext_json import json
34 34 from rhodecode.lib.hooks_daemon import store_txn_id_data
35 35
36 36
37 37 log = logging.getLogger(__name__)
38 38
39 39
40 40 class SimpleSvnApp(object):
41 41 IGNORED_HEADERS = [
42 42 'connection', 'keep-alive', 'content-encoding',
43 43 'transfer-encoding', 'content-length']
44 44 rc_extras = {}
45 45
46 46 def __init__(self, config):
47 47 self.config = config
48 48
49 49 def __call__(self, environ, start_response):
50 50 request_headers = self._get_request_headers(environ)
51 51
52 52 data = environ['wsgi.input']
53 53 # johbo: Avoid that we end up with sending the request in chunked
54 54 # transfer encoding (mainly on Gunicorn). If we know the content
55 55 # length, then we should transfer the payload in one request.
56 56 if environ['REQUEST_METHOD'] == 'MKCOL' or 'CONTENT_LENGTH' in environ:
57 57 data = data.read()
58 58 if data.startswith('(create-txn-with-props'):
59 59 # store on-the-fly our rc_extra using svn revision properties
60 60 # those can be read later on in hooks executed so we have a way
61 61 # to pass in the data into svn hooks
62 62 rc_data = base64.urlsafe_b64encode(json.dumps(self.rc_extras))
63 63 rc_data_len = len(rc_data)
64 64 # header defines data lenght, and serialized data
65 65 skel = ' rc-scm-extras {} {}'.format(rc_data_len, rc_data)
66 66 data = data[:-2] + skel + '))'
67 67
68 68 log.debug('Calling: %s method via `%s`', environ['REQUEST_METHOD'],
69 69 self._get_url(environ['PATH_INFO']))
70 70
71 71 response = requests.request(
72 72 environ['REQUEST_METHOD'], self._get_url(environ['PATH_INFO']),
73 73 data=data, headers=request_headers)
74 74
75 75 if response.status_code not in [200, 401]:
76 76 if response.status_code >= 500:
77 77 log.error('Got SVN response:%s with text:`%s`',
78 78 response, response.text)
79 79 else:
80 80 log.debug('Got SVN response:%s with text:`%s`',
81 81 response, response.text)
82 82 else:
83 83 log.debug('got response code: %s', response.status_code)
84 84
85 85 response_headers = self._get_response_headers(response.headers)
86 86
87 87 if response.headers.get('SVN-Txn-name'):
88 88 svn_tx_id = response.headers.get('SVN-Txn-name')
89 89 txn_id = caches.compute_key_from_params(
90 90 self.config['repository'], svn_tx_id)
91 91 port = safe_int(self.rc_extras['hooks_uri'].split(':')[-1])
92 92 store_txn_id_data(txn_id, {'port': port})
93 93
94 94 start_response(
95 95 '{} {}'.format(response.status_code, response.reason),
96 96 response_headers)
97 97 return response.iter_content(chunk_size=1024)
98 98
99 99 def _get_url(self, path):
100 100 url_path = urljoin(
101 101 self.config.get('subversion_http_server_url', ''), path)
102 102 url_path = urllib.quote(url_path, safe="/:=~+!$,;'")
103 103 return url_path
104 104
105 105 def _get_request_headers(self, environ):
106 106 headers = {}
107 107
108 108 for key in environ:
109 109 if not key.startswith('HTTP_'):
110 110 continue
111 111 new_key = key.split('_')
112 112 new_key = [k.capitalize() for k in new_key[1:]]
113 113 new_key = '-'.join(new_key)
114 114 headers[new_key] = environ[key]
115 115
116 116 if 'CONTENT_TYPE' in environ:
117 117 headers['Content-Type'] = environ['CONTENT_TYPE']
118 118
119 119 if 'CONTENT_LENGTH' in environ:
120 120 headers['Content-Length'] = environ['CONTENT_LENGTH']
121 121
122 122 return headers
123 123
124 124 def _get_response_headers(self, headers):
125 125 headers = [
126 126 (h, headers[h])
127 127 for h in headers
128 128 if h.lower() not in self.IGNORED_HEADERS
129 129 ]
130 130
131 131 return headers
132 132
133 133
134 134 class DisabledSimpleSvnApp(object):
135 135 def __init__(self, config):
136 136 self.config = config
137 137
138 138 def __call__(self, environ, start_response):
139 139 reason = 'Cannot handle SVN call because: SVN HTTP Proxy is not enabled'
140 140 log.warning(reason)
141 141 return HTTPNotAcceptable(reason)(environ, start_response)
142 142
143 143
144 144 class SimpleSvn(simplevcs.SimpleVCS):
145 145
146 146 SCM = 'svn'
147 147 READ_ONLY_COMMANDS = ('OPTIONS', 'PROPFIND', 'GET', 'REPORT')
148 148 DEFAULT_HTTP_SERVER = 'http://localhost:8090'
149 149
150 150 def _get_repository_name(self, environ):
151 151 """
152 152 Gets repository name out of PATH_INFO header
153 153
154 154 :param environ: environ where PATH_INFO is stored
155 155 """
156 156 path = environ['PATH_INFO'].split('!')
157 157 repo_name = path[0].strip('/')
158 158
159 159 # SVN includes the whole path in it's requests, including
160 160 # subdirectories inside the repo. Therefore we have to search for
161 161 # the repo root directory.
162 162 if not is_valid_repo(
163 163 repo_name, self.base_path, explicit_scm=self.SCM):
164 164 current_path = ''
165 165 for component in repo_name.split('/'):
166 166 current_path += component
167 167 if is_valid_repo(
168 168 current_path, self.base_path, explicit_scm=self.SCM):
169 169 return current_path
170 170 current_path += '/'
171 171
172 172 return repo_name
173 173
174 174 def _get_action(self, environ):
175 175 return (
176 176 'pull'
177 177 if environ['REQUEST_METHOD'] in self.READ_ONLY_COMMANDS
178 178 else 'push')
179 179
180 180 def _should_use_callback_daemon(self, extras, environ, action):
181 181 # only MERGE command triggers hooks, so we don't want to start
182 182 # hooks server too many times. POST however starts the svn transaction
183 183 # so we also need to run the init of callback daemon of POST
184 184 if environ['REQUEST_METHOD'] in ['MERGE', 'POST']:
185 185 return True
186 186 return False
187 187
188 188 def _create_wsgi_app(self, repo_path, repo_name, config):
189 189 if self._is_svn_enabled():
190 190 return SimpleSvnApp(config)
191 191 # we don't have http proxy enabled return dummy request handler
192 192 return DisabledSimpleSvnApp(config)
193 193
194 194 def _is_svn_enabled(self):
195 195 conf = self.repo_vcs_config
196 196 return str2bool(conf.get('vcs_svn_proxy', 'http_requests_enabled'))
197 197
198 198 def _create_config(self, extras, repo_name):
199 199 conf = self.repo_vcs_config
200 200 server_url = conf.get('vcs_svn_proxy', 'http_server_url')
201 201 server_url = server_url or self.DEFAULT_HTTP_SERVER
202 202
203 203 extras['subversion_http_server_url'] = server_url
204 204 return extras
General Comments 0
You need to be logged in to leave comments. Login now