Passed
Push — master ( ab3682...d0b3e5 )
by Pauli
11:54
created

HttpUtil::loadFromUrl()   B

Complexity

Conditions 6
Paths 9

Size

Total Lines 40
Code Lines 26

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 6
eloc 26
c 1
b 0
f 0
nc 9
nop 3
dl 0
loc 40
rs 8.8817
1
<?php declare(strict_types=1);
2
3
/**
4
 * ownCloud - Music app
5
 *
6
 * This file is licensed under the Affero General Public License version 3 or
7
 * later. See the COPYING file.
8
 *
9
 * @author Pauli Järvinen <[email protected]>
10
 * @copyright Pauli Järvinen 2022
11
 */
12
13
namespace OCA\Music\Utility;
14
15
/**
16
 * Static utility functions to work with HTTP requests
17
 */
18
class HttpUtil {
19
20
	/**
21
	 * Use HTTP GET to load the requested URL
22
	 * @return array with three keys: ['content' => string|false, 'status_code' => int, 'message' => string]
23
	 */
24
	public static function loadFromUrl(string $url, ?int $maxLength=null, ?int $timeout_s=null) : array {
25
		$status_code = 0;
26
		$content_type = null;
27
28
		if (!Util::startsWith($url, 'http://', /*ignoreCase=*/true)
29
				&& !Util::startsWith($url, 'https://', /*ignoreCase=*/true)) {
30
			$content = false;
31
			$message = 'URL scheme must be HTTP or HTTPS';
32
		} else {
33
			$opts = [
34
				'http' => [
35
					'header' => self::userAgentHeader(),	// some servers don't allow requests without a user agent header
36
					'ignore_errors' => true,				// don't emit warnings for bad/unavailable URL, we handle errors manually
37
					'max_redirects' => 2
38
				]
39
			];
40
			if ($timeout_s !== null) {
41
				$opts['http']['timeout'] = $timeout_s;
42
			}
43
			$context = \stream_context_create($opts);
44
45
			// The length parameter of file_get_contents isn't nullable prior to PHP8.0
46
			if ($maxLength === null) {
47
				$content = @\file_get_contents($url, false, $context);
48
			} else {
49
				$content = @\file_get_contents($url, false, $context, 0, $maxLength);
50
			}
51
52
			// It's some PHP magic that calling file_get_contents creates and populates also a local
53
			// variable array $http_response_header, provided that the server could be reached.
54
			if (!empty($http_response_header)) {
55
				list($version, $status_code, $message) = \explode(' ', $http_response_header[0], 3);
56
				$status_code = (int)$status_code;
57
				$content_type = self::findHeader($http_response_header, 'Content-Type');
58
			} else {
59
				$message = 'The requested URL did not respond';
60
			}
61
		}
62
63
		return \compact('content', 'status_code', 'message', 'content_type');
64
	}
65
66
	public static function userAgentHeader() : string {
67
		// Note: the following is deprecated since NC14 but the replacement
68
		// \OCP\App\IAppManager::getAppVersion is not available before NC14.
69
		$appVersion = \OCP\App::getAppVersion('music');
70
71
		return 'User-Agent: OCMusic/' . $appVersion;
72
	}
73
74
	private static function findHeader(array $headers, string $headerKey) : ?string {
75
		foreach ($headers as $header) {
76
			$find = \strstr($header, $headerKey . ':');
77
			if ($find !== false) {
78
				return \trim(\substr($find, \strlen($headerKey)+1));
79
			}
80
		}
81
		return null;
82
	}
83
}
84