1<?php
2
3declare(strict_types=1);
4
5/**
6 * @copyright Copyright (c) 2017, Robin Appelman <robin@icewind.nl>
7 *
8 * @author Arthur Schiwon <blizzz@arthur-schiwon.de>
9 * @author Christoph Wurst <christoph@winzerhof-wurst.at>
10 * @author Guillaume Virlet <github@virlet.org>
11 * @author Joas Schilling <coding@schilljs.com>
12 * @author Robin Appelman <robin@icewind.nl>
13 * @author Roeland Jago Douma <roeland@famdouma.nl>
14 *
15 * @license GNU AGPL version 3 or any later version
16 *
17 * This program is free software: you can redistribute it and/or modify
18 * it under the terms of the GNU Affero General Public License as
19 * published by the Free Software Foundation, either version 3 of the
20 * License, or (at your option) any later version.
21 *
22 * This program is distributed in the hope that it will be useful,
23 * but WITHOUT ANY WARRANTY; without even the implied warranty of
24 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
25 * GNU Affero General Public License for more details.
26 *
27 * You should have received a copy of the GNU Affero General Public License
28 * along with this program. If not, see <http://www.gnu.org/licenses/>.
29 *
30 */
31namespace OC\Federation;
32
33use OCP\Contacts\IManager;
34use OCP\Federation\ICloudId;
35use OCP\Federation\ICloudIdManager;
36use OCP\IURLGenerator;
37use OCP\IUserManager;
38
39class CloudIdManager implements ICloudIdManager {
40	/** @var IManager */
41	private $contactsManager;
42	/** @var IURLGenerator */
43	private $urlGenerator;
44	/** @var IUserManager */
45	private $userManager;
46
47	public function __construct(IManager $contactsManager, IURLGenerator $urlGenerator, IUserManager $userManager) {
48		$this->contactsManager = $contactsManager;
49		$this->urlGenerator = $urlGenerator;
50		$this->userManager = $userManager;
51	}
52
53	/**
54	 * @param string $cloudId
55	 * @return ICloudId
56	 * @throws \InvalidArgumentException
57	 */
58	public function resolveCloudId(string $cloudId): ICloudId {
59		// TODO magic here to get the url and user instead of just splitting on @
60
61		if (!$this->isValidCloudId($cloudId)) {
62			throw new \InvalidArgumentException('Invalid cloud id');
63		}
64
65		// Find the first character that is not allowed in user names
66		$id = $this->fixRemoteURL($cloudId);
67		$posSlash = strpos($id, '/');
68		$posColon = strpos($id, ':');
69
70		if ($posSlash === false && $posColon === false) {
71			$invalidPos = \strlen($id);
72		} elseif ($posSlash === false) {
73			$invalidPos = $posColon;
74		} elseif ($posColon === false) {
75			$invalidPos = $posSlash;
76		} else {
77			$invalidPos = min($posSlash, $posColon);
78		}
79
80		$lastValidAtPos = strrpos($id, '@', $invalidPos - strlen($id));
81
82		if ($lastValidAtPos !== false) {
83			$user = substr($id, 0, $lastValidAtPos);
84			$remote = substr($id, $lastValidAtPos + 1);
85			if (!empty($user) && !empty($remote)) {
86				return new CloudId($id, $user, $remote, $this->getDisplayNameFromContact($id));
87			}
88		}
89		throw new \InvalidArgumentException('Invalid cloud id');
90	}
91
92	protected function getDisplayNameFromContact(string $cloudId): ?string {
93		$addressBookEntries = $this->contactsManager->search($cloudId, ['CLOUD']);
94		foreach ($addressBookEntries as $entry) {
95			if (isset($entry['CLOUD'])) {
96				foreach ($entry['CLOUD'] as $cloudID) {
97					if ($cloudID === $cloudId) {
98						// Warning, if user decides to make his full name local only,
99						// no FN is found on federated servers
100						if (isset($entry['FN'])) {
101							return $entry['FN'];
102						} else {
103							return $cloudID;
104						}
105					}
106				}
107			}
108		}
109		return null;
110	}
111
112	/**
113	 * @param string $user
114	 * @param string|null $remote
115	 * @return CloudId
116	 */
117	public function getCloudId(string $user, ?string $remote): ICloudId {
118		if ($remote === null) {
119			$remote = rtrim($this->removeProtocolFromUrl($this->urlGenerator->getAbsoluteURL('/')), '/');
120			$fixedRemote = $this->fixRemoteURL($remote);
121			$localUser = $this->userManager->get($user);
122			$displayName = !is_null($localUser) ? $localUser->getDisplayName() : '';
123		} else {
124			// TODO check what the correct url is for remote (asking the remote)
125			$fixedRemote = $this->fixRemoteURL($remote);
126			$host = $this->removeProtocolFromUrl($fixedRemote);
127			$displayName = $this->getDisplayNameFromContact($user . '@' . $host);
128		}
129		$id = $user . '@' . $remote;
130		return new CloudId($id, $user, $fixedRemote, $displayName);
131	}
132
133	/**
134	 * @param string $url
135	 * @return string
136	 */
137	private function removeProtocolFromUrl($url) {
138		if (strpos($url, 'https://') === 0) {
139			return substr($url, strlen('https://'));
140		} elseif (strpos($url, 'http://') === 0) {
141			return substr($url, strlen('http://'));
142		}
143
144		return $url;
145	}
146
147	/**
148	 * Strips away a potential file names and trailing slashes:
149	 * - http://localhost
150	 * - http://localhost/
151	 * - http://localhost/index.php
152	 * - http://localhost/index.php/s/{shareToken}
153	 *
154	 * all return: http://localhost
155	 *
156	 * @param string $remote
157	 * @return string
158	 */
159	protected function fixRemoteURL(string $remote): string {
160		$remote = str_replace('\\', '/', $remote);
161		if ($fileNamePosition = strpos($remote, '/index.php')) {
162			$remote = substr($remote, 0, $fileNamePosition);
163		}
164		$remote = rtrim($remote, '/');
165
166		return $remote;
167	}
168
169	/**
170	 * @param string $cloudId
171	 * @return bool
172	 */
173	public function isValidCloudId(string $cloudId): bool {
174		return strpos($cloudId, '@') !== false;
175	}
176}
177