diff --git a/owncloud/owncloud.py b/owncloud/owncloud.py index 2933503..1e6579e 100644 --- a/owncloud/owncloud.py +++ b/owncloud/owncloud.py @@ -396,10 +396,25 @@ def anon_login(self, folder_token, folder_password=''): @classmethod def from_public_link(cls, public_link, folder_password='', **kwargs): public_link_components = parse.urlparse(public_link) + + # Extract the webroot by stripping the share path. + # Handles both pretty URLs (/s/TOKEN) and non-pretty (/index.php/s/TOKEN), + # as well as subdirectory installs (e.g. /owncloud/s/TOKEN). + path = public_link_components.path + idx = path.find('/index.php/') + if idx != -1: + base_path = path[:idx] + elif '/s/' in path: + base_path = path[:path.find('/s/')] + else: + base_path = '' + url = public_link_components.scheme + '://' + public_link_components.hostname if public_link_components.port: - url += ":" + public_link_components.port - folder_token = public_link_components.path.split('/')[-1] + url += ':' + str(public_link_components.port) + url += base_path + + folder_token = path.split('/')[-1] anon_session = cls(url, **kwargs) anon_session.anon_login(folder_token, folder_password=folder_password) return anon_session diff --git a/owncloud/test/test_from_public_link.py b/owncloud/test/test_from_public_link.py new file mode 100644 index 0000000..27104f3 --- /dev/null +++ b/owncloud/test/test_from_public_link.py @@ -0,0 +1,88 @@ +# -*- coding: utf-8 -*- +"""Unit tests for Client.from_public_link URL parsing. + +No live server required — tests only verify that the base URL +and token are extracted correctly from various public link formats. +""" +import unittest +from unittest.mock import patch, MagicMock +import owncloud + + +class TestFromPublicLink(unittest.TestCase): + """Verify from_public_link correctly extracts base URL and token + across all real-world public link formats.""" + + def _call(self, url): + """Call from_public_link with mocked anon_login, return (base_url, token).""" + captured = {} + + with patch.object(owncloud.Client, 'anon_login') as mock_login: + client = owncloud.Client.from_public_link(url) + # Client.__init__ appends '/' to url + captured['base_url'] = client.url + captured['token'] = mock_login.call_args[0][0] + + return captured['base_url'], captured['token'] + + def test_root_pretty_url(self): + url, token = self._call('https://cloud.example.com/s/aBcDeFgH') + self.assertEqual(url, 'https://cloud.example.com/') + self.assertEqual(token, 'aBcDeFgH') + + def test_root_non_pretty_url(self): + url, token = self._call('https://cloud.example.com/index.php/s/aBcDeFgH') + self.assertEqual(url, 'https://cloud.example.com/') + self.assertEqual(token, 'aBcDeFgH') + + def test_subpath_pretty_url(self): + url, token = self._call('https://example.com/owncloud/s/aBcDeFgH') + self.assertEqual(url, 'https://example.com/owncloud/') + self.assertEqual(token, 'aBcDeFgH') + + def test_subpath_non_pretty_url(self): + url, token = self._call('https://example.com/owncloud/index.php/s/aBcDeFgH') + self.assertEqual(url, 'https://example.com/owncloud/') + self.assertEqual(token, 'aBcDeFgH') + + def test_port_handling(self): + url, token = self._call('https://cloud.example.com:8443/s/aBcDeFgH') + self.assertEqual(url, 'https://cloud.example.com:8443/') + self.assertEqual(token, 'aBcDeFgH') + + def test_port_with_subpath(self): + url, token = self._call('https://example.com:8443/owncloud/s/aBcDeFgH') + self.assertEqual(url, 'https://example.com:8443/owncloud/') + self.assertEqual(token, 'aBcDeFgH') + + def test_port_with_subpath_non_pretty(self): + url, token = self._call('https://example.com:8443/owncloud/index.php/s/aBcDeFgH') + self.assertEqual(url, 'https://example.com:8443/owncloud/') + self.assertEqual(token, 'aBcDeFgH') + + def test_deep_subpath(self): + url, token = self._call('https://example.com/apps/owncloud/s/aBcDeFgH') + self.assertEqual(url, 'https://example.com/apps/owncloud/') + self.assertEqual(token, 'aBcDeFgH') + + def test_http_scheme(self): + url, token = self._call('http://localhost/s/aBcDeFgH') + self.assertEqual(url, 'http://localhost/') + self.assertEqual(token, 'aBcDeFgH') + + def test_http_localhost_with_port(self): + url, token = self._call('http://localhost:8080/s/aBcDeFgH') + self.assertEqual(url, 'http://localhost:8080/') + self.assertEqual(token, 'aBcDeFgH') + + def test_folder_password_passed_through(self): + with patch.object(owncloud.Client, 'anon_login') as mock_login: + owncloud.Client.from_public_link( + 'https://cloud.example.com/s/aBcDeFgH', + folder_password='secret' + ) + mock_login.assert_called_once_with('aBcDeFgH', folder_password='secret') + + +if __name__ == '__main__': + unittest.main()