contactsManager = $contactsManager; $this->urlGenerator = $urlGenerator; $this->userManager = $userManager; $this->memCache = $cacheFactory->createDistributed('cloud_id_'); $eventDispatcher->addListener(UserChangedEvent::class, [$this, 'handleUserEvent']); $eventDispatcher->addListener(CardUpdatedEvent::class, [$this, 'handleCardEvent']); } public function handleUserEvent(Event $event): void { if ($event instanceof UserChangedEvent && $event->getFeature() === 'displayName') { $userId = $event->getUser()->getUID(); $key = $userId . '@local'; unset($this->cache[$key]); $this->memCache->remove($key); } } public function handleCardEvent(Event $event): void { if ($event instanceof CardUpdatedEvent) { $data = $event->getCardData()['carddata']; foreach (explode("\r\n", $data) as $line) { if (str_starts_with($line, 'CLOUD;')) { $parts = explode(':', $line, 2); if (isset($parts[1])) { $key = $parts[1]; unset($this->cache[$key]); $this->memCache->remove($key); } } } } } /** * @param string $cloudId * @return ICloudId * @throws \InvalidArgumentException */ public function resolveCloudId(string $cloudId): ICloudId { // TODO magic here to get the url and user instead of just splitting on @ if (!$this->isValidCloudId($cloudId)) { throw new \InvalidArgumentException('Invalid cloud id'); } // Find the first character that is not allowed in user names $id = $this->stripShareLinkFragments($cloudId); $posSlash = strpos($id, '/'); $posColon = strpos($id, ':'); if ($posSlash === false && $posColon === false) { $invalidPos = \strlen($id); } elseif ($posSlash === false) { $invalidPos = $posColon; } elseif ($posColon === false) { $invalidPos = $posSlash; } else { $invalidPos = min($posSlash, $posColon); } $lastValidAtPos = strrpos($id, '@', $invalidPos - strlen($id)); if ($lastValidAtPos !== false) { $user = substr($id, 0, $lastValidAtPos); $remote = substr($id, $lastValidAtPos + 1); $this->userManager->validateUserId($user); if (!empty($user) && !empty($remote)) { $remote = $this->ensureDefaultProtocol($remote); return new CloudId($id, $user, $remote, $this->getDisplayNameFromContact($id)); } } throw new \InvalidArgumentException('Invalid cloud id'); } protected function getDisplayNameFromContact(string $cloudId): ?string { $addressBookEntries = $this->contactsManager->search($cloudId, ['CLOUD'], [ 'limit' => 1, 'enumeration' => false, 'fullmatch' => false, 'strict_search' => true, ]); foreach ($addressBookEntries as $entry) { if (isset($entry['CLOUD'])) { foreach ($entry['CLOUD'] as $cloudID) { if ($cloudID === $cloudId) { // Warning, if user decides to make their full name local only, // no FN is found on federated servers if (isset($entry['FN'])) { return $entry['FN']; } else { return $cloudID; } } } } } return null; } /** * @param string $user * @param string|null $remote * @return CloudId */ public function getCloudId(string $user, ?string $remote): ICloudId { $isLocal = $remote === null; if ($isLocal) { $remote = rtrim($this->urlGenerator->getAbsoluteURL('/'), '/'); } // note that for remote id's we don't strip the protocol for the remote we use to construct the CloudId // this way if a user has an explicit non-https cloud id this will be preserved // we do still use the version without protocol for looking up the display name $remote = $this->stripShareLinkFragments($remote); $host = $this->removeProtocolFromUrl($remote); $remote = $this->ensureDefaultProtocol($remote); $key = $user . '@' . ($isLocal ? 'local' : $host); $cached = $this->cache[$key] ?? $this->memCache->get($key); if ($cached) { $this->cache[$key] = $cached; // put items from memcache into local cache return new CloudId($cached['id'], $cached['user'], $cached['remote'], $cached['displayName']); } if ($isLocal) { $localUser = $this->userManager->get($user); $displayName = $localUser ? $localUser->getDisplayName() : ''; } else { $displayName = $this->getDisplayNameFromContact($user . '@' . $host); } // For the visible cloudID we only strip away https $id = $user . '@' . $this->removeProtocolFromUrl($remote, true); $data = [ 'id' => $id, 'user' => $user, 'remote' => $remote, 'displayName' => $displayName, ]; $this->cache[$key] = $data; $this->memCache->set($key, $data, 15 * 60); return new CloudId($id, $user, $remote, $displayName); } /** * @param string $url * @return string */ public function removeProtocolFromUrl(string $url, bool $httpsOnly = false): string { if (str_starts_with($url, 'https://')) { return substr($url, 8); } if (!$httpsOnly && str_starts_with($url, 'http://')) { return substr($url, 7); } return $url; } protected function ensureDefaultProtocol(string $remote): string { if (!str_contains($remote, '://')) { $remote = 'https://' . $remote; } return $remote; } /** * Strips away a potential file names and trailing slashes: * - http://localhost * - http://localhost/ * - http://localhost/index.php * - http://localhost/index.php/s/{shareToken} * * all return: http://localhost * * @param string $remote * @return string */ protected function stripShareLinkFragments(string $remote): string { $remote = str_replace('\\', '/', $remote); if ($fileNamePosition = strpos($remote, '/index.php')) { $remote = substr($remote, 0, $fileNamePosition); } $remote = rtrim($remote, '/'); return $remote; } /** * @param string $cloudId * @return bool */ public function isValidCloudId(string $cloudId): bool { return str_contains($cloudId, '@'); } } ef='#n77'>77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155