Passed
Push — master ( 7cb433...81357b )
by Pauli
03:13
created

RelayStreamResponse::__construct()   F

Complexity

Conditions 12
Paths 576

Size

Total Lines 47
Code Lines 31

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 12
eloc 31
c 1
b 0
f 0
nc 576
nop 1
dl 0
loc 47
rs 3.3888

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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 2024, 2025
11
 */
12
13
namespace OCA\Music\Http;
14
15
use OCP\AppFramework\Http\ICallbackResponse;
16
use OCP\AppFramework\Http\IOutput;
17
use OCP\AppFramework\Http\Response;
18
use OCP\AppFramework\Http;
19
20
use OCA\Music\Utility\HttpUtil;
21
22
/**
23
 * Response which relays a radio stream or similar from an original source URL
24
 */
25
class RelayStreamResponse extends Response implements ICallbackResponse {
26
	private string $url;
27
	private ?int $contentLength;
28
	/** @var resource $context */
29
	private $context;
30
31
	public function __construct(string $url) {
32
		parent::__construct();
33
34
		$this->url = $url;
35
		$this->contentLength = null;
36
37
		$reqHeaders = [];
38
		if (isset($_SERVER['HTTP_ACCEPT'])) {
39
			$reqHeaders['Accept'] = $_SERVER['HTTP_ACCEPT'];
40
		}
41
		if (isset($_SERVER['HTTP_RANGE'])) {
42
			$reqHeaders['Range'] = $_SERVER['HTTP_RANGE'];
43
		}
44
45
		$this->context = HttpUtil::createContext(null, $reqHeaders, /*maxRedirects=*/0);
46
47
		// Get headers from the source and relay the important ones to our client. Handle the redirection manually
48
		// since the platform redirection didn't seem to work in all cases, see https://github.com/owncloud/music/issues/1209.
49
		$redirectsAllowed = 20;
50
		do {
51
			$sourceHeaders = HttpUtil::getUrlHeaders($this->url, $this->context, /*convertKeysToLower=*/true);
52
			$status = $sourceHeaders['status_code'];
53
			$redirect = ($status >= 300 && $status < 400 && isset($sourceHeaders['location']));
54
			if ($redirect) {
55
				if ($redirectsAllowed-- > 0) {
56
					$this->url = $sourceHeaders['location'];
57
				} else {
58
					$redirect = false;
59
					$status = Http::STATUS_LOOP_DETECTED;
60
				}
61
			}
62
		} while ($redirect);
63
64
		$this->setStatus($status);
65
66
		if (isset($sourceHeaders['content-type'])) {
67
			$this->addHeader('Content-Type', $sourceHeaders['content-type']);
68
		}
69
		if (isset($sourceHeaders['accept-ranges'])) {
70
			$this->addHeader('Accept-Ranges', $sourceHeaders['accept-ranges']);
71
		}
72
		if (isset($sourceHeaders['content-range'])) {
73
			$this->addHeader('Content-Range', $sourceHeaders['content-range']);
74
		}
75
		if (isset($sourceHeaders['content-length'])) {
76
			$this->addHeader('Content-Length', $sourceHeaders['content-length']);
77
			$this->contentLength = (int)$sourceHeaders['content-length'];
78
		}
79
	}
80
81
	public function callback(IOutput $output) {
82
		// The content length is absent for stream-like sources. 0-length indicates that
83
		// there is no body to transfer.
84
		if ($this->contentLength === null || $this->contentLength > 0) {
85
			$inStream = \fopen($this->url, 'rb', false, $this->context);
86
			if ($inStream !== false) {
87
				$outStream = \fopen('php://output', 'wb');
88
89
				if ($outStream !== false) {
90
					\stream_copy_to_stream($inStream, $outStream);
91
					\fclose($outStream);
92
				}
93
				\fclose($inStream);
94
			}
95
		}
96
	}
97
}
98