Show More
@@ -0,0 +1,160 b'' | |||
|
1 | # RhodeCode VCSServer provides access to different vcs backends via network. | |
|
2 | # Copyright (C) 2014-2020 RhodeCode GmbH | |
|
3 | # | |
|
4 | # This program is free software; you can redistribute it and/or modify | |
|
5 | # it under the terms of the GNU General Public License as published by | |
|
6 | # the Free Software Foundation; either version 3 of the License, or | |
|
7 | # (at your option) any later version. | |
|
8 | # | |
|
9 | # This program is distributed in the hope that it will be useful, | |
|
10 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
|
11 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
|
12 | # GNU General Public License for more details. | |
|
13 | # | |
|
14 | # You should have received a copy of the GNU General Public License | |
|
15 | # along with this program; if not, write to the Free Software Foundation, | |
|
16 | # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA | |
|
17 | ||
|
18 | import os | |
|
19 | import tempfile | |
|
20 | ||
|
21 | from svn import client | |
|
22 | from svn import core | |
|
23 | from svn import ra | |
|
24 | ||
|
25 | from mercurial import error | |
|
26 | ||
|
27 | from vcsserver.utils import safe_bytes | |
|
28 | ||
|
29 | core.svn_config_ensure(None) | |
|
30 | svn_config = core.svn_config_get_config(None) | |
|
31 | ||
|
32 | ||
|
33 | class RaCallbacks(ra.Callbacks): | |
|
34 | @staticmethod | |
|
35 | def open_tmp_file(pool): # pragma: no cover | |
|
36 | (fd, fn) = tempfile.mkstemp() | |
|
37 | os.close(fd) | |
|
38 | return fn | |
|
39 | ||
|
40 | @staticmethod | |
|
41 | def get_client_string(pool): | |
|
42 | return b'RhodeCode-subversion-url-checker' | |
|
43 | ||
|
44 | ||
|
45 | class SubversionException(Exception): | |
|
46 | pass | |
|
47 | ||
|
48 | ||
|
49 | class SubversionConnectionException(SubversionException): | |
|
50 | """Exception raised when a generic error occurs when connecting to a repository.""" | |
|
51 | ||
|
52 | ||
|
53 | def normalize_url(url): | |
|
54 | if not url: | |
|
55 | return url | |
|
56 | if url.startswith(b'svn+http://') or url.startswith(b'svn+https://'): | |
|
57 | url = url[4:] | |
|
58 | url = url.rstrip(b'/') | |
|
59 | return url | |
|
60 | ||
|
61 | ||
|
62 | def _create_auth_baton(pool): | |
|
63 | """Create a Subversion authentication baton. """ | |
|
64 | # Give the client context baton a suite of authentication | |
|
65 | # providers.h | |
|
66 | platform_specific = [ | |
|
67 | 'svn_auth_get_gnome_keyring_simple_provider', | |
|
68 | 'svn_auth_get_gnome_keyring_ssl_client_cert_pw_provider', | |
|
69 | 'svn_auth_get_keychain_simple_provider', | |
|
70 | 'svn_auth_get_keychain_ssl_client_cert_pw_provider', | |
|
71 | 'svn_auth_get_kwallet_simple_provider', | |
|
72 | 'svn_auth_get_kwallet_ssl_client_cert_pw_provider', | |
|
73 | 'svn_auth_get_ssl_client_cert_file_provider', | |
|
74 | 'svn_auth_get_windows_simple_provider', | |
|
75 | 'svn_auth_get_windows_ssl_server_trust_provider', | |
|
76 | ] | |
|
77 | ||
|
78 | providers = [] | |
|
79 | ||
|
80 | for p in platform_specific: | |
|
81 | if getattr(core, p, None) is not None: | |
|
82 | try: | |
|
83 | providers.append(getattr(core, p)()) | |
|
84 | except RuntimeError: | |
|
85 | pass | |
|
86 | ||
|
87 | providers += [ | |
|
88 | client.get_simple_provider(), | |
|
89 | client.get_username_provider(), | |
|
90 | client.get_ssl_client_cert_file_provider(), | |
|
91 | client.get_ssl_client_cert_pw_file_provider(), | |
|
92 | client.get_ssl_server_trust_file_provider(), | |
|
93 | ] | |
|
94 | ||
|
95 | return core.svn_auth_open(providers, pool) | |
|
96 | ||
|
97 | ||
|
98 | class SubversionRepo(object): | |
|
99 | """Wrapper for a Subversion repository. | |
|
100 | ||
|
101 | It uses the SWIG Python bindings, see above for requirements. | |
|
102 | """ | |
|
103 | def __init__(self, svn_url: bytes = b'', username: bytes = b'', password: bytes = b''): | |
|
104 | ||
|
105 | self.username = username | |
|
106 | self.password = password | |
|
107 | self.svn_url = core.svn_path_canonicalize(svn_url) | |
|
108 | ||
|
109 | self.auth_baton_pool = core.Pool() | |
|
110 | self.auth_baton = _create_auth_baton(self.auth_baton_pool) | |
|
111 | # self.init_ra_and_client() assumes that a pool already exists | |
|
112 | self.pool = core.Pool() | |
|
113 | ||
|
114 | self.ra = self.init_ra_and_client() | |
|
115 | self.uuid = ra.get_uuid(self.ra, self.pool) | |
|
116 | ||
|
117 | def init_ra_and_client(self): | |
|
118 | """Initializes the RA and client layers, because sometimes getting | |
|
119 | unified diffs runs the remote server out of open files. | |
|
120 | """ | |
|
121 | ||
|
122 | if self.username: | |
|
123 | core.svn_auth_set_parameter(self.auth_baton, | |
|
124 | core.SVN_AUTH_PARAM_DEFAULT_USERNAME, | |
|
125 | self.username) | |
|
126 | if self.password: | |
|
127 | core.svn_auth_set_parameter(self.auth_baton, | |
|
128 | core.SVN_AUTH_PARAM_DEFAULT_PASSWORD, | |
|
129 | self.password) | |
|
130 | ||
|
131 | callbacks = RaCallbacks() | |
|
132 | callbacks.auth_baton = self.auth_baton | |
|
133 | ||
|
134 | try: | |
|
135 | return ra.open2(self.svn_url, callbacks, svn_config, self.pool) | |
|
136 | except SubversionException as e: | |
|
137 | # e.child contains a detailed error messages | |
|
138 | msglist = [] | |
|
139 | svn_exc = e | |
|
140 | while svn_exc: | |
|
141 | if svn_exc.args[0]: | |
|
142 | msglist.append(svn_exc.args[0]) | |
|
143 | svn_exc = svn_exc.child | |
|
144 | msg = '\n'.join(msglist) | |
|
145 | raise SubversionConnectionException(msg) | |
|
146 | ||
|
147 | ||
|
148 | class svnremoterepo(object): | |
|
149 | """ the dumb wrapper for actual Subversion repositories """ | |
|
150 | ||
|
151 | def __init__(self, username: bytes = b'', password: bytes = b'', svn_url: bytes = b''): | |
|
152 | self.username = username or b'' | |
|
153 | self.password = password or b'' | |
|
154 | self.path = normalize_url(svn_url) | |
|
155 | ||
|
156 | def svn(self): | |
|
157 | try: | |
|
158 | return SubversionRepo(self.path, self.username, self.password) | |
|
159 | except SubversionConnectionException as e: | |
|
160 | raise error.Abort(safe_bytes(e)) |
@@ -7,7 +7,6 b' dogpile.cache==1.1.8' | |||
|
7 | 7 | |
|
8 | 8 | decorator==5.1.1 |
|
9 | 9 | dulwich==0.21.3 |
|
10 | hgsubversion==1.9.3 | |
|
11 | 10 | hg-evolve==11.0.0 |
|
12 | 11 | |
|
13 | 12 | mercurial==6.3.2 |
@@ -23,7 +23,9 b' import urllib.parse' | |||
|
23 | 23 | import logging |
|
24 | 24 | import posixpath as vcspath |
|
25 | 25 | import io |
|
26 |
import urllib.request |
|
|
26 | import urllib.request | |
|
27 | import urllib.parse | |
|
28 | import urllib.error | |
|
27 | 29 | import traceback |
|
28 | 30 | |
|
29 | 31 | import svn.client |
@@ -38,7 +40,7 b' from vcsserver.base import RepoFactory, ' | |||
|
38 | 40 | from vcsserver.exceptions import NoContentException |
|
39 | 41 | from vcsserver.utils import safe_str |
|
40 | 42 | from vcsserver.vcs_base import RemoteBase |
|
41 | ||
|
43 | from vcsserver.lib.svnremoterepo import svnremoterepo | |
|
42 | 44 | log = logging.getLogger(__name__) |
|
43 | 45 | |
|
44 | 46 | |
@@ -50,7 +52,7 b' svn_compatible_versions_map = {' | |||
|
50 | 52 | 'pre-1.9-compatible': '1.8', |
|
51 | 53 | } |
|
52 | 54 | |
|
53 |
current_compatible_version = '1.1 |
|
|
55 | current_compatible_version = '1.14' | |
|
54 | 56 | |
|
55 | 57 | |
|
56 | 58 | def reraise_safe_exceptions(func): |
@@ -105,9 +107,6 b' class SvnRemote(RemoteBase):' | |||
|
105 | 107 | |
|
106 | 108 | def __init__(self, factory, hg_factory=None): |
|
107 | 109 | self._factory = factory |
|
108 | # TODO: Remove once we do not use internal Mercurial objects anymore | |
|
109 | # for subversion | |
|
110 | self._hg_factory = hg_factory | |
|
111 | 110 | |
|
112 | 111 | @reraise_safe_exceptions |
|
113 | 112 | def discover_svn_version(self): |
@@ -127,15 +126,13 b' class SvnRemote(RemoteBase):' | |||
|
127 | 126 | log.exception("failed to read object_store") |
|
128 | 127 | return False |
|
129 | 128 | |
|
130 |
def check_url(self, url |
|
|
131 | # this can throw exception if not installed, but we detect this | |
|
132 | from hgsubversion import svnrepo | |
|
129 | def check_url(self, url): | |
|
133 | 130 | |
|
134 | baseui = self._hg_factory._create_config(config_items) | |
|
135 | 131 | # uuid function get's only valid UUID from proper repo, else |
|
136 | 132 | # throws exception |
|
133 | username, password, src_url = self.get_url_and_credentials(url) | |
|
137 | 134 | try: |
|
138 |
|
|
|
135 | svnremoterepo(username, password, src_url).svn().uuid | |
|
139 | 136 | except Exception: |
|
140 | 137 | tb = traceback.format_exc() |
|
141 | 138 | log.debug("Invalid Subversion url: `%s`, tb: %s", url, tb) |
@@ -359,6 +356,7 b' class SvnRemote(RemoteBase):' | |||
|
359 | 356 | |
|
360 | 357 | cache_on, context_uid, repo_id = self._cache_on(wire) |
|
361 | 358 | region = self._region(wire) |
|
359 | ||
|
362 | 360 | @region.conditional_cache_on_arguments(condition=cache_on) |
|
363 | 361 | def _get_file_size(_repo_id, _path, _revision): |
|
364 | 362 | repo = self._factory.repo(wire) |
@@ -37,60 +37,57 b' def safe_int(val, default=None):' | |||
|
37 | 37 | return val |
|
38 | 38 | |
|
39 | 39 | |
|
40 |
def safe_str( |
|
|
40 | def safe_str(str_, to_encoding=None) -> str: | |
|
41 | 41 | """ |
|
42 | 42 | safe str function. Does few trick to turn unicode_ into string |
|
43 | 43 | |
|
44 |
:param |
|
|
44 | :param str_: str to encode | |
|
45 | 45 | :param to_encoding: encode to this type UTF8 default |
|
46 | 46 | :rtype: str |
|
47 | 47 | :returns: str object |
|
48 | 48 | """ |
|
49 | if isinstance(str_, str): | |
|
50 | return str_ | |
|
51 | ||
|
52 | # if it's bytes cast to str | |
|
53 | if not isinstance(str_, bytes): | |
|
54 | return str(str_) | |
|
55 | ||
|
49 | 56 | to_encoding = to_encoding or ['utf8'] |
|
50 | # if it's not basestr cast to str | |
|
51 | if not isinstance(unicode_, basestring): | |
|
52 | return str(unicode_) | |
|
53 | ||
|
54 | if isinstance(unicode_, str): | |
|
55 | return unicode_ | |
|
56 | ||
|
57 | 57 | if not isinstance(to_encoding, (list, tuple)): |
|
58 | 58 | to_encoding = [to_encoding] |
|
59 | 59 | |
|
60 | 60 | for enc in to_encoding: |
|
61 | 61 | try: |
|
62 |
return |
|
|
63 |
except Unicode |
|
|
62 | return str(str_, enc) | |
|
63 | except UnicodeDecodeError: | |
|
64 | 64 | pass |
|
65 | 65 | |
|
66 |
return |
|
|
66 | return str(str_, to_encoding[0], 'replace') | |
|
67 | 67 | |
|
68 | 68 | |
|
69 |
def safe_ |
|
|
69 | def safe_bytes(str_, from_encoding=None) -> bytes: | |
|
70 | 70 | """ |
|
71 |
safe |
|
|
71 | safe bytes function. Does few trick to turn str_ into bytes string: | |
|
72 | 72 | |
|
73 | 73 | :param str_: string to decode |
|
74 | 74 | :param from_encoding: encode from this type UTF8 default |
|
75 | 75 | :rtype: unicode |
|
76 | 76 | :returns: unicode object |
|
77 | 77 | """ |
|
78 | from_encoding = from_encoding or ['utf8'] | |
|
79 | ||
|
80 | if isinstance(str_, unicode): | |
|
78 | if isinstance(str_, bytes): | |
|
81 | 79 | return str_ |
|
82 | 80 | |
|
81 | if not isinstance(str_, str): | |
|
82 | raise ValueError('safe_bytes cannot convert other types than str: got: {}'.format(type(str_))) | |
|
83 | ||
|
84 | from_encoding = from_encoding or ['utf8'] | |
|
83 | 85 | if not isinstance(from_encoding, (list, tuple)): |
|
84 | 86 | from_encoding = [from_encoding] |
|
85 | 87 | |
|
86 | try: | |
|
87 | return unicode(str_) | |
|
88 | except UnicodeDecodeError: | |
|
89 | pass | |
|
90 | ||
|
91 | 88 | for enc in from_encoding: |
|
92 | 89 | try: |
|
93 |
return |
|
|
90 | return str_.encode(enc) | |
|
94 | 91 | except UnicodeDecodeError: |
|
95 | 92 | pass |
|
96 | 93 |
General Comments 0
You need to be logged in to leave comments.
Login now