From f64efedd96ffe3c844f1fa3b3093b32c52bfe932 Mon Sep 17 00:00:00 2001 From: Evgeny Ezhov Date: Tue, 18 Feb 2020 16:47:02 -0800 Subject: [PATCH 1/8] Fixed issue #40 - error during coping and moving files with cyrillic names --- README.md | 3 +++ tests/base_client_it.py | 1 + tests/test_client_it.py | 6 +++--- tests/test_cyrilic_client_it.py | 23 +++++++++++++++++++++++ tests/тестовый.txt | 1 + webdav3/client.py | 4 ++-- 6 files changed, 33 insertions(+), 5 deletions(-) create mode 100644 tests/test_cyrilic_client_it.py create mode 100644 tests/тестовый.txt diff --git a/README.md b/README.md index 8db0bda..0445a5a 100644 --- a/README.md +++ b/README.md @@ -298,6 +298,9 @@ res1.write_async(local_path="~/Downloads/file1", callback) Release Notes ------------- +**Version 3.14.1** + * Fixed issue during coping and moving files with cyrillic names + **Version 3.14** * Override methods for customizing communication with WebDAV servers * Support multiple clients simultaneously diff --git a/tests/base_client_it.py b/tests/base_client_it.py index 9fa988c..5b4290f 100644 --- a/tests/base_client_it.py +++ b/tests/base_client_it.py @@ -13,6 +13,7 @@ class BaseClientTestCase(unittest.TestCase): remote_path_dir = 'test_dir' remote_path_dir2 = 'test_dir2' remote_inner_path_dir = 'test_dir/inner' + inner_dir_name = 'inner' local_base_dir = 'tests/' local_file = 'test.txt' local_file_path = local_base_dir + 'test.txt' diff --git a/tests/test_client_it.py b/tests/test_client_it.py index 2d6c14f..515cca6 100644 --- a/tests/test_client_it.py +++ b/tests/test_client_it.py @@ -208,10 +208,10 @@ class ClientTestCase(BaseClientTestCase): self._prepare_for_downloading(True) self.client.pull(self.remote_path_dir, self.local_path_dir) self.assertTrue(path.exists(self.local_path_dir), 'Expected the directory is downloaded.') - self.assertTrue(path.exists(self.local_path_dir + os.path.sep + 'inner'), 'Expected the directory is downloaded.') - self.assertTrue(path.exists(self.local_path_dir + os.path.sep + 'inner'), 'Expected the directory is downloaded.') + self.assertTrue(path.exists(self.local_path_dir + os.path.sep + self.inner_dir_name), 'Expected the directory is downloaded.') + self.assertTrue(path.exists(self.local_path_dir + os.path.sep + self.inner_dir_name), 'Expected the directory is downloaded.') self.assertTrue(path.isdir(self.local_path_dir), 'Expected this is a directory.') - self.assertTrue(path.isdir(self.local_path_dir + os.path.sep + 'inner'), 'Expected this is a directory.') + self.assertTrue(path.isdir(self.local_path_dir + os.path.sep + self.inner_dir_name), 'Expected this is a directory.') self.assertTrue(path.exists(self.local_path_dir + os.path.sep + self.local_file), 'Expected the file is downloaded') self.assertTrue(path.isfile(self.local_path_dir + os.path.sep + self.local_file), diff --git a/tests/test_cyrilic_client_it.py b/tests/test_cyrilic_client_it.py new file mode 100644 index 0000000..87add07 --- /dev/null +++ b/tests/test_cyrilic_client_it.py @@ -0,0 +1,23 @@ +import os +import unittest + +from tests.test_client_it import ClientTestCase + + +class MultiClientTestCase(ClientTestCase): + remote_path_file = 'директория/тестовый.txt' + remote_path_file2 = 'директория/тестовый2.txt' + remote_inner_path_file = 'директория/вложенная/тестовый.txt' + remote_path_dir = 'директория' + remote_path_dir2 = 'директория2' + remote_inner_path_dir = 'директория/вложенная' + inner_dir_name = 'вложенная' + local_base_dir = 'tests/' + local_file = 'тестовый.txt' + local_file_path = local_base_dir + 'тестовый.txt' + local_path_dir = local_base_dir + 'res/директория' + pulled_file = local_path_dir + os.sep + local_file + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/тестовый.txt b/tests/тестовый.txt new file mode 100644 index 0000000..1cc4810 --- /dev/null +++ b/tests/тестовый.txt @@ -0,0 +1 @@ +test content for testing of webdav client \ No newline at end of file diff --git a/webdav3/client.py b/webdav3/client.py index a5da364..0768b41 100644 --- a/webdav3/client.py +++ b/webdav3/client.py @@ -535,7 +535,7 @@ class Client(object): raise RemoteParentNotFound(urn_to.path()) headers = [ - "Destination: {url}".format(url=self.get_url(urn_to)) + "Destination: {url}".format(url=self.get_url(urn_to.quote())) ] if self.is_dir(urn_from.path()): headers.append("Depth: {depth}".format(depth=depth)) @@ -558,7 +558,7 @@ class Client(object): if not self.check(urn_to.parent()): raise RemoteParentNotFound(urn_to.path()) - header_destination = "Destination: {path}".format(path=self.get_url(urn_to)) + header_destination = "Destination: {path}".format(path=self.get_url(urn_to.quote())) header_overwrite = "Overwrite: {flag}".format(flag="T" if overwrite else "F") self.execute_request(action='move', path=urn_from.quote(), headers_ext=[header_destination, header_overwrite]) From 3c5ba516af8ae198f00632a72dbd28f8b0343656 Mon Sep 17 00:00:00 2001 From: Daniel Loader Date: Fri, 14 Feb 2020 09:34:38 +0000 Subject: [PATCH 2/8] Add Oauth2 Bearer Token support Basic implementation of authenticating with token instead of basic auth, as the auth=(login,password) flag on requests sends junk authentication as it overrides the bearer authentication header with a base64 encoded blank string. --- webdav3/client.py | 33 ++++++++++++++++++++++----------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/webdav3/client.py b/webdav3/client.py index 0768b41..94e212c 100644 --- a/webdav3/client.py +++ b/webdav3/client.py @@ -177,7 +177,7 @@ class Client(object): headers.extend(headers_ext) if self.webdav.token: - webdav_token = "Authorization: OAuth {token}".format(token=self.webdav.token) + webdav_token = "Authorization: Bearer {token}".format(token=self.webdav.token) headers.append(webdav_token) return dict([map(lambda s: s.strip(), i.split(':', 1)) for i in headers]) @@ -211,16 +211,27 @@ class Client(object): """ if self.session.auth: self.session.request(method="GET", url=self.webdav.hostname, verify=self.verify) # (Re)Authenticates against the proxy - response = self.session.request( - method=self.requests[action], - url=self.get_url(path), - auth=(self.webdav.login, self.webdav.password), - headers=self.get_headers(action, headers_ext), - timeout=self.timeout, - data=data, - stream=True, - verify=self.verify - ) + if all([self.webdav.login, self.webdav.password]) is False: + response = self.session.request( + method=self.requests[action], + url=self.get_url(path), + headers=self.get_headers(action, headers_ext), + timeout=self.timeout, + data=data, + stream=True, + verify=self.verify + ) + else: + response = self.session.request( + method=self.requests[action], + url=self.get_url(path), + auth=(self.webdav.login, self.webdav.password), + headers=self.get_headers(action, headers_ext), + timeout=self.timeout, + data=data, + stream=True, + verify=self.verify + ) if response.status_code == 507: raise NotEnoughSpace() if response.status_code == 404: From 0ac3c5e55faafb986c4f0c7ec4c1da39ba545780 Mon Sep 17 00:00:00 2001 From: Evgeny Ezhov Date: Tue, 18 Feb 2020 17:19:57 -0800 Subject: [PATCH 3/8] Small refactoring --- README.md | 1 + webdav3/client.py | 31 ++++++++++--------------------- 2 files changed, 11 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index 0445a5a..a6eef1a 100644 --- a/README.md +++ b/README.md @@ -300,6 +300,7 @@ Release Notes ------------- **Version 3.14.1** * Fixed issue during coping and moving files with cyrillic names + * Support OAuth2 bearer tokens by https://github.com/danielloader **Version 3.14** * Override methods for customizing communication with WebDAV servers diff --git a/webdav3/client.py b/webdav3/client.py index 94e212c..ca29f80 100644 --- a/webdav3/client.py +++ b/webdav3/client.py @@ -211,27 +211,16 @@ class Client(object): """ if self.session.auth: self.session.request(method="GET", url=self.webdav.hostname, verify=self.verify) # (Re)Authenticates against the proxy - if all([self.webdav.login, self.webdav.password]) is False: - response = self.session.request( - method=self.requests[action], - url=self.get_url(path), - headers=self.get_headers(action, headers_ext), - timeout=self.timeout, - data=data, - stream=True, - verify=self.verify - ) - else: - response = self.session.request( - method=self.requests[action], - url=self.get_url(path), - auth=(self.webdav.login, self.webdav.password), - headers=self.get_headers(action, headers_ext), - timeout=self.timeout, - data=data, - stream=True, - verify=self.verify - ) + response = self.session.request( + method=self.requests[action], + url=self.get_url(path), + auth=(self.webdav.login, self.webdav.password) if not self.webdav.token else None, + headers=self.get_headers(action, headers_ext), + timeout=self.timeout, + data=data, + stream=True, + verify=self.verify + ) if response.status_code == 507: raise NotEnoughSpace() if response.status_code == 404: From 3aab20b02a00213bb6265f467a7e0527d9979f68 Mon Sep 17 00:00:00 2001 From: Evgeny Ezhov Date: Sun, 23 Feb 2020 17:43:12 -0800 Subject: [PATCH 4/8] Bump version to 3.14.1 before publish --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index fcbf7b2..abd81c1 100644 --- a/setup.py +++ b/setup.py @@ -6,7 +6,7 @@ from setuptools import setup, find_packages from setuptools.command.install import install as InstallCommand from setuptools.command.test import test as TestCommand -version = "3.14" +version = "3.14.1" requirements = "libxml2-dev libxslt-dev python-dev" From af64110364af2253e3afd5e7f9f62982fa5a8be1 Mon Sep 17 00:00:00 2001 From: Martin Date: Mon, 23 Mar 2020 10:34:34 +0100 Subject: [PATCH 5/8] Fix request calling to use session's auth. --- webdav3/client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webdav3/client.py b/webdav3/client.py index ca29f80..c88a172 100644 --- a/webdav3/client.py +++ b/webdav3/client.py @@ -214,7 +214,7 @@ class Client(object): response = self.session.request( method=self.requests[action], url=self.get_url(path), - auth=(self.webdav.login, self.webdav.password) if not self.webdav.token else None, + auth=(self.webdav.login, self.webdav.password) if (not self.webdav.token and not self.session.auth) else None, headers=self.get_headers(action, headers_ext), timeout=self.timeout, data=data, From eeaf66a2788ca42ec93ed5b0410c391e28badfde Mon Sep 17 00:00:00 2001 From: Evgeny Ezhov Date: Sun, 5 Apr 2020 17:10:49 -0700 Subject: [PATCH 6/8] Update Travis CI config for SonarQube --- .travis.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 1bbd775..0873913 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,8 +4,7 @@ dist: xenial addons: sonarcloud: organization: "ezhov-evgeny" - token: - secure: f0f714f3bea6bd103e3eb82724ef3bb0d3b54d1d + token: f0f714f3bea6bd103e3eb82724ef3bb0d3b54d1d services: - docker From 39afefffc5e7b5613786dcaa2d719c50d847e703 Mon Sep 17 00:00:00 2001 From: Evgeny Ezhov Date: Sun, 5 Apr 2020 17:43:10 -0700 Subject: [PATCH 7/8] Fixed #49: Remove tailing slashes in web_hostname --- tests/test_tailing_slash_client_it.py | 26 ++++++++++++++++++++++++++ webdav3/connection.py | 1 + webdav3/urn.py | 2 -- 3 files changed, 27 insertions(+), 2 deletions(-) create mode 100644 tests/test_tailing_slash_client_it.py diff --git a/tests/test_tailing_slash_client_it.py b/tests/test_tailing_slash_client_it.py new file mode 100644 index 0000000..6a8744c --- /dev/null +++ b/tests/test_tailing_slash_client_it.py @@ -0,0 +1,26 @@ +import unittest + +from tests.test_client_it import ClientTestCase + + +class TailingSlashClientTestCase(ClientTestCase): + options = { + 'webdav_hostname': 'http://localhost:8585/', + 'webdav_login': 'alice', + 'webdav_password': 'secret1234', + 'webdav_override_methods': { + 'check': 'GET' + } + } + + def test_list_inner(self): + self._prepare_for_downloading(True) + file_list = self.client.list(self.remote_inner_path_dir) + self.assertIsNotNone(file_list, 'List of files should not be None') + + def test_hostname_no_tailing_slash(self): + self.assertEqual('5', self.client.webdav.hostname[-1]) + + +if __name__ == '__main__': + unittest.main() diff --git a/webdav3/connection.py b/webdav3/connection.py index 302212b..499ba23 100644 --- a/webdav3/connection.py +++ b/webdav3/connection.py @@ -50,6 +50,7 @@ class WebDAVSettings(ConnectionSettings): self.root = Urn(self.root).quote() if self.root else '' self.root = self.root.rstrip(Urn.separate) + self.hostname = self.hostname.rstrip(Urn.separate) def is_valid(self): if not self.hostname: diff --git a/webdav3/urn.py b/webdav3/urn.py index 6279de2..e78fa24 100644 --- a/webdav3/urn.py +++ b/webdav3/urn.py @@ -34,13 +34,11 @@ class Urn(object): return self._path def filename(self): - path_split = self._path.split(Urn.separate) name = path_split[-2] + Urn.separate if path_split[-1] == '' else path_split[-1] return unquote(name) def parent(self): - path_split = self._path.split(Urn.separate) nesting_level = self.nesting_level() parent_path_split = path_split[:nesting_level] From a3c75b7155662cbc0c63e202e20dfea2e8d76480 Mon Sep 17 00:00:00 2001 From: Alex Jordan Date: Mon, 6 Apr 2020 08:39:51 -0400 Subject: [PATCH 8/8] Fixes ezhov-evgeny#43 - local certificate options ignored If the webdav_cert_path and webdav_key_path options are set, they should be passed to the call to requests in Client.execute_request as the cert parameter. --- webdav3/client.py | 1 + 1 file changed, 1 insertion(+) diff --git a/webdav3/client.py b/webdav3/client.py index c88a172..9e495d1 100644 --- a/webdav3/client.py +++ b/webdav3/client.py @@ -217,6 +217,7 @@ class Client(object): auth=(self.webdav.login, self.webdav.password) if (not self.webdav.token and not self.session.auth) else None, headers=self.get_headers(action, headers_ext), timeout=self.timeout, + cert=(self.webdav.cert_path, self.webdav.key_path) if (self.webdav.cert_path and self.webdav.key_path) else None, data=data, stream=True, verify=self.verify