##// END OF EJS Templates
subversion: Detect requests also based on magic path.
johbo -
r437:28802ace default
parent child Browse files
Show More
@@ -1,156 +1,160 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 22 import shutil
23 23 import logging
24 24 import tempfile
25 25 import urlparse
26 26
27 27 import rhodecode
28 28 from rhodecode.lib.middleware.appenlight import wrap_in_appenlight_if_enabled
29 29 from rhodecode.lib.middleware.simplegit import SimpleGit, GIT_PROTO_PAT
30 30 from rhodecode.lib.middleware.simplehg import SimpleHg
31 31 from rhodecode.lib.middleware.simplesvn import SimpleSvn
32 32
33 33
34 34 log = logging.getLogger(__name__)
35 35
36 36
37 37 def is_git(environ):
38 38 """
39 39 Returns True if requests should be handled by GIT wsgi middleware
40 40 """
41 41 is_git_path = GIT_PROTO_PAT.match(environ['PATH_INFO'])
42 42 log.debug(
43 43 'request path: `%s` detected as GIT PROTOCOL %s', environ['PATH_INFO'],
44 44 is_git_path is not None)
45 45
46 46 return is_git_path
47 47
48 48
49 49 def is_hg(environ):
50 50 """
51 51 Returns True if requests target is mercurial server - header
52 52 ``HTTP_ACCEPT`` of such request would start with ``application/mercurial``.
53 53 """
54 54 is_hg_path = False
55 55
56 56 http_accept = environ.get('HTTP_ACCEPT')
57 57
58 58 if http_accept and http_accept.startswith('application/mercurial'):
59 59 query = urlparse.parse_qs(environ['QUERY_STRING'])
60 60 if 'cmd' in query:
61 61 is_hg_path = True
62 62
63 63 log.debug(
64 64 'request path: `%s` detected as HG PROTOCOL %s', environ['PATH_INFO'],
65 65 is_hg_path)
66 66
67 67 return is_hg_path
68 68
69 69
70 70 def is_svn(environ):
71 71 """
72 72 Returns True if requests target is Subversion server
73 73 """
74 74 http_dav = environ.get('HTTP_DAV', '')
75 is_svn_path = 'subversion' in http_dav
75 magic_path_segment = rhodecode.CONFIG.get(
76 'rhodecode_subversion_magic_path', '/!svn')
77 is_svn_path = (
78 'subversion' in http_dav or
79 magic_path_segment in environ['PATH_INFO'])
76 80 log.debug(
77 81 'request path: `%s` detected as SVN PROTOCOL %s', environ['PATH_INFO'],
78 82 is_svn_path)
79 83
80 84 return is_svn_path
81 85
82 86
83 87 class GunzipMiddleware(object):
84 88 """
85 89 WSGI middleware that unzips gzip-encoded requests before
86 90 passing on to the underlying application.
87 91 """
88 92
89 93 def __init__(self, application):
90 94 self.app = application
91 95
92 96 def __call__(self, environ, start_response):
93 97 accepts_encoding_header = environ.get('HTTP_CONTENT_ENCODING', b'')
94 98
95 99 if b'gzip' in accepts_encoding_header:
96 100 log.debug('gzip detected, now running gunzip wrapper')
97 101 wsgi_input = environ['wsgi.input']
98 102
99 103 if not hasattr(environ['wsgi.input'], 'seek'):
100 104 # The gzip implementation in the standard library of Python 2.x
101 105 # requires the '.seek()' and '.tell()' methods to be available
102 106 # on the input stream. Read the data into a temporary file to
103 107 # work around this limitation.
104 108
105 109 wsgi_input = tempfile.SpooledTemporaryFile(64 * 1024 * 1024)
106 110 shutil.copyfileobj(environ['wsgi.input'], wsgi_input)
107 111 wsgi_input.seek(0)
108 112
109 113 environ['wsgi.input'] = gzip.GzipFile(fileobj=wsgi_input, mode='r')
110 114 # since we "Ungzipped" the content we say now it's no longer gzip
111 115 # content encoding
112 116 del environ['HTTP_CONTENT_ENCODING']
113 117
114 118 # content length has changes ? or i'm not sure
115 119 if 'CONTENT_LENGTH' in environ:
116 120 del environ['CONTENT_LENGTH']
117 121 else:
118 122 log.debug('content not gzipped, gzipMiddleware passing '
119 123 'request further')
120 124 return self.app(environ, start_response)
121 125
122 126
123 127 class VCSMiddleware(object):
124 128
125 129 def __init__(self, app, config, appenlight_client):
126 130 self.application = app
127 131 self.config = config
128 132 self.appenlight_client = appenlight_client
129 133
130 134 def _get_handler_app(self, environ):
131 135 app = None
132 136 if is_hg(environ):
133 137 app = SimpleHg(self.application, self.config)
134 138
135 139 if is_git(environ):
136 140 app = SimpleGit(self.application, self.config)
137 141
138 142 proxy_svn = rhodecode.CONFIG.get(
139 143 'rhodecode_proxy_subversion_http_requests', False)
140 144 if proxy_svn and is_svn(environ):
141 145 app = SimpleSvn(self.application, self.config)
142 146
143 147 if app:
144 148 app = GunzipMiddleware(app)
145 149 app, _ = wrap_in_appenlight_if_enabled(
146 150 app, self.config, self.appenlight_client)
147 151
148 152 return app
149 153
150 154 def __call__(self, environ, start_response):
151 155 # check if we handle one of interesting protocols ?
152 156 vcs_handler = self._get_handler_app(environ)
153 157 if vcs_handler:
154 158 return vcs_handler(environ, start_response)
155 159
156 160 return self.application(environ, start_response)
@@ -1,114 +1,134 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 from mock import patch, Mock
22 22
23 23 import rhodecode
24 24 from rhodecode.lib.middleware import vcs
25 25
26 26
27 27 def test_is_hg():
28 28 environ = {
29 29 'PATH_INFO': 'rhodecode-dev',
30 30 'QUERY_STRING': 'cmd=changegroup',
31 31 'HTTP_ACCEPT': 'application/mercurial'
32 32 }
33 33 assert vcs.is_hg(environ)
34 34
35 35
36 36 def test_is_hg_no_cmd():
37 37 environ = {
38 38 'PATH_INFO': 'rhodecode-dev',
39 39 'QUERY_STRING': '',
40 40 'HTTP_ACCEPT': 'application/mercurial'
41 41 }
42 42 assert not vcs.is_hg(environ)
43 43
44 44
45 45 def test_is_hg_empty_cmd():
46 46 environ = {
47 47 'PATH_INFO': 'rhodecode-dev',
48 48 'QUERY_STRING': 'cmd=',
49 49 'HTTP_ACCEPT': 'application/mercurial'
50 50 }
51 51 assert not vcs.is_hg(environ)
52 52
53 53
54 54 def test_is_svn_returns_true_if_subversion_is_in_a_dav_header():
55 55 environ = {
56 56 'PATH_INFO': 'rhodecode-dev',
57 57 'HTTP_DAV': 'http://subversion.tigris.org/xmlns/dav/svn/log-revprops'
58 58 }
59 59 assert vcs.is_svn(environ) is True
60 60
61 61
62 62 def test_is_svn_returns_false_if_subversion_is_not_in_a_dav_header():
63 63 environ = {
64 64 'PATH_INFO': 'rhodecode-dev',
65 65 'HTTP_DAV': 'http://stuff.tigris.org/xmlns/dav/svn/log-revprops'
66 66 }
67 67 assert vcs.is_svn(environ) is False
68 68
69 69
70 70 def test_is_svn_returns_false_if_no_dav_header():
71 71 environ = {
72 72 'PATH_INFO': 'rhodecode-dev',
73 73 }
74 74 assert vcs.is_svn(environ) is False
75 75
76 76
77 def test_is_svn_returns_true_if_magic_path_segment():
78 environ = {
79 'PATH_INFO': '/stub-repository/!svn/rev/4',
80 }
81 assert vcs.is_svn(environ)
82
83
84 def test_is_svn_allows_to_configure_the_magic_path(monkeypatch):
85 """
86 This is intended as a fallback in case someone has configured his
87 Subversion server with a different magic path segment.
88 """
89 monkeypatch.setitem(
90 rhodecode.CONFIG, 'rhodecode_subversion_magic_path', '/!my-magic')
91 environ = {
92 'PATH_INFO': '/stub-repository/!my-magic/rev/4',
93 }
94 assert vcs.is_svn(environ)
95
96
77 97 class TestVCSMiddleware(object):
78 98 def test_get_handler_app_retuns_svn_app_when_proxy_enabled(self):
79 99 environ = {
80 100 'PATH_INFO': 'rhodecode-dev',
81 101 'HTTP_DAV': 'http://subversion.tigris.org/xmlns/dav/svn/log'
82 102 }
83 103 app = Mock()
84 104 config = Mock()
85 105 middleware = vcs.VCSMiddleware(
86 106 app, config=config, appenlight_client=None)
87 107 snv_patch = patch('rhodecode.lib.middleware.vcs.SimpleSvn')
88 108 settings_patch = patch.dict(
89 109 rhodecode.CONFIG,
90 110 {'rhodecode_proxy_subversion_http_requests': True})
91 111 with snv_patch as svn_mock, settings_patch:
92 112 svn_mock.return_value = None
93 113 middleware._get_handler_app(environ)
94 114
95 115 svn_mock.assert_called_once_with(app, config)
96 116
97 117 def test_get_handler_app_retuns_no_svn_app_when_proxy_disabled(self):
98 118 environ = {
99 119 'PATH_INFO': 'rhodecode-dev',
100 120 'HTTP_DAV': 'http://subversion.tigris.org/xmlns/dav/svn/log'
101 121 }
102 122 app = Mock()
103 123 config = Mock()
104 124 middleware = vcs.VCSMiddleware(
105 125 app, config=config, appenlight_client=None)
106 126 snv_patch = patch('rhodecode.lib.middleware.vcs.SimpleSvn')
107 127 settings_patch = patch.dict(
108 128 rhodecode.CONFIG,
109 129 {'rhodecode_proxy_subversion_http_requests': False})
110 130 with snv_patch as svn_mock, settings_patch:
111 131 app = middleware._get_handler_app(environ)
112 132
113 133 assert svn_mock.call_count == 0
114 134 assert app is None
General Comments 0
You need to be logged in to leave comments. Login now