Passed
Push — master ( 1e85cb...6741b3 )
by Joas
16:42 queued 18s
created

LinkReferenceProvider::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 9
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 8
nc 1
nop 8
dl 0
loc 9
rs 10
c 0
b 0
f 0

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

1
<?php
2
3
declare(strict_types=1);
4
/**
5
 * @copyright Copyright (c) 2022 Julius Härtl <[email protected]>
6
 *
7
 * @author Julius Härtl <[email protected]>
8
 *
9
 * @license GNU AGPL version 3 or any later version
10
 *
11
 * This program is free software: you can redistribute it and/or modify
12
 * it under the terms of the GNU Affero General Public License as
13
 * published by the Free Software Foundation, either version 3 of the
14
 * License, or (at your option) any later version.
15
 *
16
 * This program is distributed in the hope that it will be useful,
17
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19
 * GNU Affero General Public License for more details.
20
 *
21
 * You should have received a copy of the GNU Affero General Public License
22
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
23
 */
24
25
namespace OC\Collaboration\Reference;
26
27
use Fusonic\OpenGraph\Consumer;
28
use GuzzleHttp\Exception\GuzzleException;
29
use GuzzleHttp\Psr7\LimitStream;
30
use GuzzleHttp\Psr7\Utils;
31
use OC\Security\RateLimiting\Exception\RateLimitExceededException;
32
use OC\Security\RateLimiting\Limiter;
33
use OC\SystemConfig;
34
use OCP\Collaboration\Reference\IReference;
35
use OCP\Collaboration\Reference\IReferenceProvider;
36
use OCP\Collaboration\Reference\Reference;
37
use OCP\Files\AppData\IAppDataFactory;
38
use OCP\Files\NotFoundException;
39
use OCP\Http\Client\IClientService;
40
use OCP\IRequest;
41
use OCP\IURLGenerator;
42
use OCP\IUserSession;
43
use Psr\Log\LoggerInterface;
44
45
class LinkReferenceProvider implements IReferenceProvider {
46
	public const MAX_PREVIEW_SIZE = 1024 * 1024;
47
48
	public const ALLOWED_CONTENT_TYPES = [
49
		'image/png',
50
		'image/jpg',
51
		'image/jpeg',
52
		'image/gif',
53
		'image/svg+xml',
54
		'image/webp'
55
	];
56
57
	private IClientService $clientService;
58
	private LoggerInterface $logger;
59
	private SystemConfig $systemConfig;
60
	private IAppDataFactory $appDataFactory;
61
	private IURLGenerator $urlGenerator;
62
	private Limiter $limiter;
63
	private IUserSession $userSession;
64
	private IRequest $request;
65
66
	public function __construct(IClientService $clientService, LoggerInterface $logger, SystemConfig $systemConfig, IAppDataFactory $appDataFactory, IURLGenerator $urlGenerator, Limiter $limiter, IUserSession $userSession, IRequest $request) {
67
		$this->clientService = $clientService;
68
		$this->logger = $logger;
69
		$this->systemConfig = $systemConfig;
70
		$this->appDataFactory = $appDataFactory;
71
		$this->urlGenerator = $urlGenerator;
72
		$this->limiter = $limiter;
73
		$this->userSession = $userSession;
74
		$this->request = $request;
75
	}
76
77
	public function matchReference(string $referenceText): bool {
78
		if ($this->systemConfig->getValue('reference_opengraph', true) !== true) {
79
			return false;
80
		}
81
82
		return (bool)preg_match(IURLGenerator::URL_REGEX, $referenceText);
83
	}
84
85
	public function resolveReference(string $referenceText): ?IReference {
86
		if ($this->matchReference($referenceText)) {
87
			$reference = new Reference($referenceText);
88
			$this->fetchReference($reference);
89
			return $reference;
90
		}
91
92
		return null;
93
	}
94
95
	private function fetchReference(Reference $reference): void {
96
		try {
97
			$user = $this->userSession->getUser();
98
			if ($user) {
99
				$this->limiter->registerUserRequest('opengraph', 10, 120, $user);
100
			} else {
101
				$this->limiter->registerAnonRequest('opengraph', 10, 120, $this->request->getRemoteAddress());
102
			}
103
		} catch (RateLimitExceededException $e) {
104
			return;
105
		}
106
107
		$client = $this->clientService->newClient();
108
		try {
109
			$headResponse = $client->head($reference->getId(), [ 'timeout' => 10 ]);
110
		} catch (\Exception $e) {
111
			$this->logger->debug('Failed to perform HEAD request to get target metadata', ['exception' => $e]);
112
			return;
113
		}
114
		$linkContentLength = $headResponse->getHeader('Content-Length');
115
		if (is_numeric($linkContentLength) && (int) $linkContentLength > 5 * 1024 * 1024) {
116
			$this->logger->debug('Skip resolving links pointing to content length > 5 MB');
117
			return;
118
		}
119
		$linkContentType = $headResponse->getHeader('Content-Type');
120
		$expectedContentType = 'text/html';
121
		$suffixedExpectedContentType = $expectedContentType . ';';
122
		$startsWithSuffixed = substr($linkContentType, 0, strlen($suffixedExpectedContentType)) === $suffixedExpectedContentType;
123
		// check the header begins with the expected content type
124
		if ($linkContentType !== $expectedContentType && !$startsWithSuffixed) {
125
			$this->logger->debug('Skip resolving links pointing to content type that is not "text/html"');
126
			return;
127
		}
128
		try {
129
			$response = $client->get($reference->getId(), [ 'timeout' => 10 ]);
130
		} catch (\Exception $e) {
131
			$this->logger->debug('Failed to fetch link for obtaining open graph data', ['exception' => $e]);
132
			return;
133
		}
134
135
		$responseBody = (string)$response->getBody();
136
137
		// OpenGraph handling
138
		$consumer = new Consumer();
139
		$consumer->useFallbackMode = true;
140
		$object = $consumer->loadHtml($responseBody);
141
142
		$reference->setUrl($reference->getId());
143
144
		if ($object->title) {
145
			$reference->setTitle($object->title);
146
		}
147
148
		if ($object->description) {
149
			$reference->setDescription($object->description);
150
		}
151
152
		if ($object->images) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $object->images of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
153
			try {
154
				$appData = $this->appDataFactory->get('core');
155
				try {
156
					$folder = $appData->getFolder('opengraph');
157
				} catch (NotFoundException $e) {
158
					$folder = $appData->newFolder('opengraph');
159
				}
160
				$response = $client->get($object->images[0]->url, [ 'timeout' => 10 ]);
161
				$contentType = $response->getHeader('Content-Type');
162
				$contentLength = $response->getHeader('Content-Length');
163
164
				if (in_array($contentType, self::ALLOWED_CONTENT_TYPES, true) && $contentLength < self::MAX_PREVIEW_SIZE) {
165
					$stream = Utils::streamFor($response->getBody());
166
					$bodyStream = new LimitStream($stream, self::MAX_PREVIEW_SIZE, 0);
167
					$reference->setImageContentType($contentType);
168
					$folder->newFile(md5($reference->getId()), $bodyStream->getContents());
169
					$reference->setImageUrl($this->urlGenerator->linkToRouteAbsolute('core.Reference.preview', ['referenceId' => md5($reference->getId())]));
170
				}
171
			} catch (GuzzleException $e) {
172
				$this->logger->info('Failed to fetch and store the open graph image for ' . $reference->getId(), ['exception' => $e]);
173
			} catch (\Throwable $e) {
174
				$this->logger->error('Failed to fetch and store the open graph image for ' . $reference->getId(), ['exception' => $e]);
175
			}
176
		}
177
	}
178
179
	public function getCachePrefix(string $referenceId): string {
180
		return $referenceId;
181
	}
182
183
	public function getCacheKey(string $referenceId): ?string {
184
		return null;
185
	}
186
}
187