Completed
Pull Request — master (#581)
by Victor
01:40
created

Fetcher::getFeed()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 18

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 14
CRAP Score 3

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 18
ccs 14
cts 14
cp 1
rs 9.6666
cc 3
nc 3
nop 0
crap 3
1
<?php
2
/**
3
 * @author Victor Dubiniuk <[email protected]>
4
 *
5
 * @copyright Copyright (c) 2021, ownCloud, Inc.
6
 * @license AGPL-3.0
7
 *
8
 * This code is free software: you can redistribute it and/or modify
9
 * it under the terms of the GNU Affero General Public License, version 3,
10
 * as published by the Free Software Foundation.
11
 *
12
 * This program is distributed in the hope that it will be useful,
13
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
 * GNU Affero General Public License for more details.
16
 *
17
 * You should have received a copy of the GNU Affero General Public License, version 3,
18
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
19
 *
20
 */
21
22
namespace Owncloud\Updater\Utils;
23
24
use GuzzleHttp\Client;
25
use GuzzleHttp\RequestOptions;
26
use Psr\Http\Message\ResponseInterface;
27
28
/**
29
 * Class Fetcher
30
 *
31
 * @package Owncloud\Updater\Utils
32
 */
33
class Fetcher {
34
35
	const DEFAULT_BASE_URL = 'https://updates.owncloud.com/server/';
36
37
	/**
38
	 * @var Locator $locator
39
	 */
40
	protected $locator;
41
42
	/**
43
	 * @var ConfigReader $configReader
44
	 */
45
	protected $configReader;
46
47
	/**
48
	 * @var Client $httpClient
49
	 */
50
	protected $httpClient;
51
	protected $requiredFeedEntries = [
52
		'version',
53
		'versionstring',
54
		'url'
55
	];
56
57
	/**
58
	 * Constructor
59
	 *
60
	 * @param Client $httpClient
61
	 * @param Locator $locator
62
	 * @param ConfigReader $configReader
63
	 */
64 3
	public function __construct(Client $httpClient, Locator $locator, ConfigReader $configReader){
65 3
		$this->httpClient = $httpClient;
66 3
		$this->locator = $locator;
67 3
		$this->configReader = $configReader;
68 3
	}
69
70
	/**
71
	 * Download new ownCloud package
72
	 * @param Feed $feed
73
	 * @param Callable $onProgress
74
	 * @throws \Exception
75
	 * @throws \UnexpectedValueException
76
	 */
77
	public function getOwncloud(Feed $feed, callable $onProgress){
78
		if ($feed->isValid()){
79
			$downloadPath = $this->getBaseDownloadPath($feed);
80
			if (!is_writable(dirname($downloadPath))){
81
				throw new \Exception(dirname($downloadPath) . ' is not writable.');
82
			}
83
			$url = $feed->getUrl();
84
			$response = $this->httpClient->request(
85
					'GET',
86
					$url,
87
					[
88
						RequestOptions::PROGRESS => $onProgress,
89
						RequestOptions::SINK => $downloadPath,
90
						RequestOptions::TIMEOUT => 600
91
					]
92
			);
93
			$this->validateResponse($response, $url);
94
		}
95
	}
96
97
	/**
98
	 * Produce a local path to save the package to
99
	 * @param Feed $feed
100
	 * @return string
101
	 */
102
	public function getBaseDownloadPath(Feed $feed){
103
		$basePath = $this->locator->getDownloadBaseDir();
104
		return $basePath . '/' . $feed->getDownloadedFileName();
105
	}
106
107
	/**
108
	 * Get md5 sum for the package
109
	 * @param Feed $feed
110
	 * @return string
111
	 */
112
	public function getMd5(Feed $feed){
113
		$fullChecksum = $this->download($feed->getChecksumUrl());
114
		// we got smth like "5776cbd0a95637ade4b2c0d8694d8fca  -"
115
		//strip trailing space & dash
116
		return substr($fullChecksum, 0, 32);
117
	}
118
119
	/**
120
	 * Read update feed for new releases
121
	 * @return Feed
122
	 */
123 3
	public function getFeed(){
124 3
		$url = $this->getFeedUrl();
125 3
		$xml = $this->download($url);
126 3
		$tmp = [];
127 3
		if ($xml){
128 2
			$loadEntities = libxml_disable_entity_loader(true);
129 2
			$data = @simplexml_load_string($xml);
130 2
			libxml_disable_entity_loader($loadEntities);
131 2
			if ($data !== false){
132 1
				$tmp['version'] = (string) $data->version;
133 1
				$tmp['versionstring'] = (string) $data->versionstring;
134 1
				$tmp['url'] = (string) $data->url;
135 1
				$tmp['web'] = (string) $data->web;
136
			}
137
		}
138
139 3
		return new Feed($tmp);
140
	}
141
142
	/**
143
	 * @return mixed|string
144
	 */
145 3
	public function getUpdateChannel(){
146 3
		$channel = $this->configReader->getByPath('apps.core.OC_Channel');
147 3
		if (is_null($channel)) {
148
			return $this->locator->getChannelFromVersionsFile();
149
		}
150
151 3
		return $channel;
152
	}
153
154
	/**
155
	 * Produce complete feed URL
156
	 * @return string
157
	 */
158 3
	protected function getFeedUrl(){
159 3
		$currentVersion = $this->configReader->getByPath('system.version');
160 3
		$version = explode('.', $currentVersion);
161 3
		$version['installed'] = $this->configReader->getByPath('apps.core.installedat');
162 3
		$version['updated'] = $this->configReader->getByPath('apps.core.lastupdatedat');
163 3
		$version['updatechannel'] = $this->getUpdateChannel();
164 3
		$version['edition'] = $this->configReader->getEdition();
165 3
		$version['build'] = $this->locator->getBuild();
166
167
		// Read updater server URL from config
168 3
		$updaterServerUrl = $this->configReader->get(['system', 'updater.server.url']);
169 3
		if ((bool) $updaterServerUrl === false){
170 3
			$updaterServerUrl = self::DEFAULT_BASE_URL;
171
		}
172
173 3
		$url = $updaterServerUrl . '?version=' . implode('x', $version);
174 3
		return $url;
175
	}
176
177
	/**
178
	 * Get URL content
179
	 * @param string $url
180
	 * @return string
181
	 * @throws \UnexpectedValueException
182
	 */
183 3
	protected function download($url){
184 3
		$response = $this->httpClient->request('GET', $url, [RequestOptions::TIMEOUT => 600]);
185 3
		$this->validateResponse($response, $url);
186 3
		return $response->getBody()->getContents();
187
	}
188
189
	/**
190
	 * Check if request was successful
191
	 * @param ResponseInterface $response
192
	 * @param string $url
193
	 * @throws \UnexpectedValueException
194
	 */
195 3
	protected function validateResponse(ResponseInterface $response, $url){
196 3
		if ($response->getStatusCode() !== 200){
197
			throw new \UnexpectedValueException(
198
					'Failed to download '
199
					. $url
200
					. '. Server responded with '
201
					. $response->getStatusCode()
202
					. ' instead of 200.');
203
		}
204 3
	}
205
206
}
207