Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions lib/composer/composer/autoload_classmap.php
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,7 @@
'OCP\\Collaboration\\Reference\\IReferenceManager' => $baseDir . '/lib/public/Collaboration/Reference/IReferenceManager.php',
'OCP\\Collaboration\\Reference\\IReferenceProvider' => $baseDir . '/lib/public/Collaboration/Reference/IReferenceProvider.php',
'OCP\\Collaboration\\Reference\\ISearchableReferenceProvider' => $baseDir . '/lib/public/Collaboration/Reference/ISearchableReferenceProvider.php',
'OCP\\Collaboration\\Reference\\LinkReferenceProvider' => $baseDir . '/lib/public/Collaboration/Reference/LinkReferenceProvider.php',
'OCP\\Collaboration\\Reference\\Reference' => $baseDir . '/lib/public/Collaboration/Reference/Reference.php',
'OCP\\Collaboration\\Reference\\RenderReferenceEvent' => $baseDir . '/lib/public/Collaboration/Reference/RenderReferenceEvent.php',
'OCP\\Collaboration\\Resources\\CollectionException' => $baseDir . '/lib/public/Collaboration/Resources/CollectionException.php',
Expand Down
1 change: 1 addition & 0 deletions lib/composer/composer/autoload_static.php
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,7 @@ class ComposerStaticInit749170dad3f5e7f9ca158f5a9f04f6a2
'OCP\\Collaboration\\Reference\\IReferenceManager' => __DIR__ . '/../../..' . '/lib/public/Collaboration/Reference/IReferenceManager.php',
'OCP\\Collaboration\\Reference\\IReferenceProvider' => __DIR__ . '/../../..' . '/lib/public/Collaboration/Reference/IReferenceProvider.php',
'OCP\\Collaboration\\Reference\\ISearchableReferenceProvider' => __DIR__ . '/../../..' . '/lib/public/Collaboration/Reference/ISearchableReferenceProvider.php',
'OCP\\Collaboration\\Reference\\LinkReferenceProvider' => __DIR__ . '/../../..' . '/lib/public/Collaboration/Reference/LinkReferenceProvider.php',
'OCP\\Collaboration\\Reference\\Reference' => __DIR__ . '/../../..' . '/lib/public/Collaboration/Reference/Reference.php',
'OCP\\Collaboration\\Reference\\RenderReferenceEvent' => __DIR__ . '/../../..' . '/lib/public/Collaboration/Reference/RenderReferenceEvent.php',
'OCP\\Collaboration\\Resources\\CollectionException' => __DIR__ . '/../../..' . '/lib/public/Collaboration/Resources/CollectionException.php',
Expand Down
159 changes: 4 additions & 155 deletions lib/private/Collaboration/Reference/LinkReferenceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
* @copyright Copyright (c) 2022 Julius Härtl <jus@bitgrid.net>
*
* @author Julius Härtl <jus@bitgrid.net>
* @author Anupam Kumar <kyteinsky@gmail.com>
*
* @license GNU AGPL version 3 or any later version
*
Expand All @@ -24,160 +25,8 @@

namespace OC\Collaboration\Reference;

use Fusonic\OpenGraph\Consumer;
use GuzzleHttp\Exception\GuzzleException;
use GuzzleHttp\Psr7\LimitStream;
use GuzzleHttp\Psr7\Utils;
use OC\Security\RateLimiting\Exception\RateLimitExceededException;
use OC\Security\RateLimiting\Limiter;
use OC\SystemConfig;
use OCP\Collaboration\Reference\IReference;
use OCP\Collaboration\Reference\IReferenceProvider;
use OCP\Collaboration\Reference\Reference;
use OCP\Files\AppData\IAppDataFactory;
use OCP\Files\NotFoundException;
use OCP\Http\Client\IClientService;
use OCP\IRequest;
use OCP\IURLGenerator;
use OCP\IUserSession;
use Psr\Log\LoggerInterface;
use OCP\Collaboration\Reference\LinkReferenceProvider as OCPLinkReferenceProvider;

class LinkReferenceProvider implements IReferenceProvider {
public const MAX_PREVIEW_SIZE = 1024 * 1024;

public const ALLOWED_CONTENT_TYPES = [
'image/png',
'image/jpg',
'image/jpeg',
'image/gif',
'image/svg+xml',
'image/webp'
];

public function __construct(
private IClientService $clientService,
private LoggerInterface $logger,
private SystemConfig $systemConfig,
private IAppDataFactory $appDataFactory,
private IURLGenerator $urlGenerator,
private Limiter $limiter,
private IUserSession $userSession,
private IRequest $request,
) {
}

public function matchReference(string $referenceText): bool {
if ($this->systemConfig->getValue('reference_opengraph', true) !== true) {
return false;
}

return (bool)preg_match(IURLGenerator::URL_REGEX, $referenceText);
}

public function resolveReference(string $referenceText): ?IReference {
if ($this->matchReference($referenceText)) {
$reference = new Reference($referenceText);
$this->fetchReference($reference);
return $reference;
}

return null;
}

private function fetchReference(Reference $reference): void {
try {
$user = $this->userSession->getUser();
if ($user) {
$this->limiter->registerUserRequest('opengraph', 10, 120, $user);
} else {
$this->limiter->registerAnonRequest('opengraph', 10, 120, $this->request->getRemoteAddress());
}
} catch (RateLimitExceededException $e) {
return;
}

$client = $this->clientService->newClient();
try {
$headResponse = $client->head($reference->getId(), [ 'timeout' => 10 ]);
} catch (\Exception $e) {
$this->logger->debug('Failed to perform HEAD request to get target metadata', ['exception' => $e]);
return;
}
$linkContentLength = $headResponse->getHeader('Content-Length');
if (is_numeric($linkContentLength) && (int) $linkContentLength > 5 * 1024 * 1024) {
$this->logger->debug('Skip resolving links pointing to content length > 5 MB');
return;
}
$linkContentType = $headResponse->getHeader('Content-Type');
$expectedContentType = 'text/html';
$suffixedExpectedContentType = $expectedContentType . ';';
$startsWithSuffixed = str_starts_with($linkContentType, $suffixedExpectedContentType);
// check the header begins with the expected content type
if ($linkContentType !== $expectedContentType && !$startsWithSuffixed) {
$this->logger->debug('Skip resolving links pointing to content type that is not "text/html"');
return;
}
try {
$response = $client->get($reference->getId(), [ 'timeout' => 10 ]);
} catch (\Exception $e) {
$this->logger->debug('Failed to fetch link for obtaining open graph data', ['exception' => $e]);
return;
}

$responseBody = (string)$response->getBody();

// OpenGraph handling
$consumer = new Consumer();
$consumer->useFallbackMode = true;
$object = $consumer->loadHtml($responseBody);

$reference->setUrl($reference->getId());

if ($object->title) {
$reference->setTitle($object->title);
}

if ($object->description) {
$reference->setDescription($object->description);
}

if ($object->images) {
try {
$host = parse_url($object->images[0]->url, PHP_URL_HOST);
if ($host === false || $host === null) {
$this->logger->warning('Could not detect host of open graph image URI for ' . $reference->getId());
} else {
$appData = $this->appDataFactory->get('core');
try {
$folder = $appData->getFolder('opengraph');
} catch (NotFoundException $e) {
$folder = $appData->newFolder('opengraph');
}
$response = $client->get($object->images[0]->url, ['timeout' => 10]);
$contentType = $response->getHeader('Content-Type');
$contentLength = $response->getHeader('Content-Length');

if (in_array($contentType, self::ALLOWED_CONTENT_TYPES, true) && $contentLength < self::MAX_PREVIEW_SIZE) {
$stream = Utils::streamFor($response->getBody());
$bodyStream = new LimitStream($stream, self::MAX_PREVIEW_SIZE, 0);
$reference->setImageContentType($contentType);
$folder->newFile(md5($reference->getId()), $bodyStream->getContents());
$reference->setImageUrl($this->urlGenerator->linkToRouteAbsolute('core.Reference.preview', ['referenceId' => md5($reference->getId())]));
}
}
} catch (GuzzleException $e) {
$this->logger->info('Failed to fetch and store the open graph image for ' . $reference->getId(), ['exception' => $e]);
} catch (\Throwable $e) {
$this->logger->error('Failed to fetch and store the open graph image for ' . $reference->getId(), ['exception' => $e]);
}
}
}

public function getCachePrefix(string $referenceId): string {
return $referenceId;
}

public function getCacheKey(string $referenceId): ?string {
return null;
}
/** @deprecated 29.0.0 Use OCP\Collaboration\Reference\LinkReferenceProvider instead */
class LinkReferenceProvider extends OCPLinkReferenceProvider {
}
Loading