##// END OF EJS Templates
svn: escape special chars to allow interactions with non-standard svn paths....
marcink -
r1586:23640ce6 default
parent child Browse files
Show More
@@ -1,157 +1,169 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2017 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 logging
22 import urllib
22 23 from urlparse import urljoin
23 24
25
24 26 import requests
25 27 from webob.exc import HTTPNotAcceptable
26 28
27 29 from rhodecode.lib.middleware import simplevcs
28 30 from rhodecode.lib.utils import is_valid_repo
29 31 from rhodecode.lib.utils2 import str2bool
30 32
31 33 log = logging.getLogger(__name__)
32 34
33 35
34 36 class SimpleSvnApp(object):
35 37 IGNORED_HEADERS = [
36 38 'connection', 'keep-alive', 'content-encoding',
37 39 'transfer-encoding', 'content-length']
38 40
39 41 def __init__(self, config):
40 42 self.config = config
41 43
42 44 def __call__(self, environ, start_response):
43 45 request_headers = self._get_request_headers(environ)
44 46
45 47 data = environ['wsgi.input']
46 48 # johbo: Avoid that we end up with sending the request in chunked
47 49 # transfer encoding (mainly on Gunicorn). If we know the content
48 50 # length, then we should transfer the payload in one request.
49 51 if environ['REQUEST_METHOD'] == 'MKCOL' or 'CONTENT_LENGTH' in environ:
50 52 data = data.read()
51 53
52 54 log.debug('Calling: %s method via `%s`', environ['REQUEST_METHOD'],
53 55 self._get_url(environ['PATH_INFO']))
54 56 response = requests.request(
55 57 environ['REQUEST_METHOD'], self._get_url(environ['PATH_INFO']),
56 58 data=data, headers=request_headers)
57 59
60 if response.status_code not in [200, 401]:
61 if response.status_code >= 500:
62 log.error('Got SVN response:%s with text:`%s`',
63 response, response.text)
64 else:
65 log.debug('Got SVN response:%s with text:`%s`',
66 response, response.text)
67
58 68 response_headers = self._get_response_headers(response.headers)
59 69 start_response(
60 70 '{} {}'.format(response.status_code, response.reason),
61 71 response_headers)
62 72 return response.iter_content(chunk_size=1024)
63 73
64 74 def _get_url(self, path):
65 return urljoin(
75 url_path = urljoin(
66 76 self.config.get('subversion_http_server_url', ''), path)
77 url_path = urllib.quote(url_path, safe="/:=~+!$,;'")
78 return url_path
67 79
68 80 def _get_request_headers(self, environ):
69 81 headers = {}
70 82
71 83 for key in environ:
72 84 if not key.startswith('HTTP_'):
73 85 continue
74 86 new_key = key.split('_')
75 87 new_key = [k.capitalize() for k in new_key[1:]]
76 88 new_key = '-'.join(new_key)
77 89 headers[new_key] = environ[key]
78 90
79 91 if 'CONTENT_TYPE' in environ:
80 92 headers['Content-Type'] = environ['CONTENT_TYPE']
81 93
82 94 if 'CONTENT_LENGTH' in environ:
83 95 headers['Content-Length'] = environ['CONTENT_LENGTH']
84 96
85 97 return headers
86 98
87 99 def _get_response_headers(self, headers):
88 100 headers = [
89 101 (h, headers[h])
90 102 for h in headers
91 103 if h.lower() not in self.IGNORED_HEADERS
92 104 ]
93 105
94 106 return headers
95 107
96 108
97 109 class DisabledSimpleSvnApp(object):
98 110 def __init__(self, config):
99 111 self.config = config
100 112
101 113 def __call__(self, environ, start_response):
102 114 reason = 'Cannot handle SVN call because: SVN HTTP Proxy is not enabled'
103 115 log.warning(reason)
104 116 return HTTPNotAcceptable(reason)(environ, start_response)
105 117
106 118
107 119 class SimpleSvn(simplevcs.SimpleVCS):
108 120
109 121 SCM = 'svn'
110 122 READ_ONLY_COMMANDS = ('OPTIONS', 'PROPFIND', 'GET', 'REPORT')
111 123 DEFAULT_HTTP_SERVER = 'http://localhost:8090'
112 124
113 125 def _get_repository_name(self, environ):
114 126 """
115 127 Gets repository name out of PATH_INFO header
116 128
117 129 :param environ: environ where PATH_INFO is stored
118 130 """
119 131 path = environ['PATH_INFO'].split('!')
120 132 repo_name = path[0].strip('/')
121 133
122 134 # SVN includes the whole path in it's requests, including
123 135 # subdirectories inside the repo. Therefore we have to search for
124 136 # the repo root directory.
125 137 if not is_valid_repo(repo_name, self.basepath, self.SCM):
126 138 current_path = ''
127 139 for component in repo_name.split('/'):
128 140 current_path += component
129 141 if is_valid_repo(current_path, self.basepath, self.SCM):
130 142 return current_path
131 143 current_path += '/'
132 144
133 145 return repo_name
134 146
135 147 def _get_action(self, environ):
136 148 return (
137 149 'pull'
138 150 if environ['REQUEST_METHOD'] in self.READ_ONLY_COMMANDS
139 151 else 'push')
140 152
141 153 def _create_wsgi_app(self, repo_path, repo_name, config):
142 154 if self._is_svn_enabled():
143 155 return SimpleSvnApp(config)
144 156 # we don't have http proxy enabled return dummy request handler
145 157 return DisabledSimpleSvnApp(config)
146 158
147 159 def _is_svn_enabled(self):
148 160 conf = self.repo_vcs_config
149 161 return str2bool(conf.get('vcs_svn_proxy', 'http_requests_enabled'))
150 162
151 163 def _create_config(self, extras, repo_name):
152 164 conf = self.repo_vcs_config
153 165 server_url = conf.get('vcs_svn_proxy', 'http_server_url')
154 166 server_url = server_url or self.DEFAULT_HTTP_SERVER
155 167
156 168 extras['subversion_http_server_url'] = server_url
157 169 return extras
General Comments 0
You need to be logged in to leave comments. Login now