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
16 changes: 16 additions & 0 deletions config/config.sample.php
Original file line number Diff line number Diff line change
Expand Up @@ -520,6 +520,12 @@
/**
* The URL of your proxy server, for example ``proxy.example.com:8081``.
*
* Note: Guzzle (the http library used by Nextcloud) is reading the environment
* variables HTTP_PROXY (only for cli request), HTTPS_PROXY, and NO_PROXY by default.
*
* If you configure proxy with Nextcloud any default configuration by Guzzle
* is overwritten. Make sure to set ``proxyexclude`` accordingly if necessary.
*
* Defaults to ``''`` (empty string)
*/
'proxy' => '',
Expand All @@ -532,6 +538,16 @@
*/
'proxyuserpwd' => '',

/**
* List of host names that should not be proxied to.
* For example: ``['.mit.edu', 'foo.com']``.
*
* Hint: Use something like ``explode(',', getenv('NO_PROXY'))`` to sync this
* value with the global NO_PROXY option.
*
* Defaults to empty array.
*/
'proxyexclude' => [],

/**
* Deleted Items (trash bin)
Expand Down
41 changes: 34 additions & 7 deletions lib/private/Http/Client/Client.php
Original file line number Diff line number Diff line change
Expand Up @@ -65,12 +65,20 @@ public function __construct(
}

private function buildRequestOptions(array $options): array {
$proxy = $this->getProxyUri();

$defaults = [
RequestOptions::PROXY => $this->getProxyUri(),
RequestOptions::VERIFY => $this->getCertBundle(),
RequestOptions::TIMEOUT => 30,
];

// Only add RequestOptions::PROXY if Nextcloud is explicitly
// configured to use a proxy. This is needed in order not to override
// Guzzle default values.
if($proxy !== null) {
$defaults[RequestOptions::PROXY] = $proxy;
}

$options = array_merge($defaults, $options);

if (!isset($options[RequestOptions::HEADERS]['User-Agent'])) {
Expand All @@ -96,24 +104,43 @@ private function getCertBundle(): string {
}

/**
* Get the proxy URI
* Returns a null or an associative array specifiying the proxy URI for
* 'http' and 'https' schemes, in addition to a 'no' key value pair
* providing a list of host names that should not be proxied to.
*
* @return array|null
*
* The return array looks like:
* [
* 'http' => 'username:[email protected]',
* 'https' => 'username:[email protected]',
* 'no' => ['foo.com', 'bar.com']
* ]
*
* @return string|null
*/
private function getProxyUri(): ?string {
private function getProxyUri(): ?array {
$proxyHost = $this->config->getSystemValue('proxy', '');

if ($proxyHost === '' || $proxyHost === null) {
return null;
}

$proxyUserPwd = $this->config->getSystemValue('proxyuserpwd', '');
if ($proxyUserPwd !== '' && $proxyUserPwd !== null) {
$proxyHost = $proxyUserPwd . '@' . $proxyHost;
}

$proxy = [
'http' => $proxyHost,
'https' => $proxyHost,
];

if ($proxyUserPwd === '' || $proxyUserPwd === null) {
return $proxyHost;
$proxyExclude = $this->config->getSystemValue('proxyexclude', []);
if ($proxyExclude !== [] && $proxyExclude !== null) {
$proxy['no'] = $proxyExclude;
}

return $proxyUserPwd . '@' . $proxyHost;
return $proxy;
}

/**
Expand Down
150 changes: 139 additions & 11 deletions tests/lib/Http/Client/ClientTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,15 @@ public function testGetProxyUriProxyHostEmptyPassword(): void {
->method('getSystemValue')
->with('proxyuserpwd', null)
->willReturn(null);
$this->assertSame('foo', self::invokePrivate($this->client, 'getProxyUri'));
$this->config
->expects($this->at(2))
->method('getSystemValue')
->with('proxyexclude', [])
->willReturn([]);
$this->assertEquals([
'http' => 'foo',
'https' => 'foo'
], self::invokePrivate($this->client, 'getProxyUri'));
}

public function testGetProxyUriProxyHostWithPassword(): void {
Expand All @@ -87,7 +95,58 @@ public function testGetProxyUriProxyHostWithPassword(): void {
})
)
->willReturn('username:password');
$this->assertSame('username:password@foo', self::invokePrivate($this->client, 'getProxyUri'));
$this->config
->expects($this->at(2))
->method('getSystemValue')
->with(
$this->equalTo('proxyexclude'),
$this->callback(function ($input) {
return $input === [];
})
)
->willReturn([]);
$this->assertEquals([
'http' => 'username:password@foo',
'https' => 'username:password@foo'
], self::invokePrivate($this->client, 'getProxyUri'));
}

public function testGetProxyUriProxyHostWithPasswordAndExclude(): void {
$this->config
->expects($this->at(0))
->method('getSystemValue')
->with(
$this->equalTo('proxy'),
$this->callback(function ($input) {
return $input === '';
})
)
->willReturn('foo');
$this->config
->expects($this->at(1))
->method('getSystemValue')
->with(
$this->equalTo('proxyuserpwd'),
$this->callback(function ($input) {
return $input === '';
})
)
->willReturn('username:password');
$this->config
->expects($this->at(2))
->method('getSystemValue')
->with(
$this->equalTo('proxyexclude'),
$this->callback(function ($input) {
return $input === [];
})
)
->willReturn(['bar']);
$this->assertEquals([
'http' => 'username:password@foo',
'https' => 'username:password@foo',
'no' => ['bar']
], self::invokePrivate($this->client, 'getProxyUri'));
}

private function setUpDefaultRequestOptions(): void {
Expand All @@ -101,6 +160,11 @@ private function setUpDefaultRequestOptions(): void {
->method('getSystemValue')
->with('proxyuserpwd', null)
->willReturn(null);
$this->config
->expects($this->at(2))
->method('getSystemValue')
->with('proxyexclude', [])
->willReturn([]);
$this->certificateManager
->expects($this->once())
->method('getAbsoluteBundlePath')
Expand All @@ -109,7 +173,10 @@ private function setUpDefaultRequestOptions(): void {

$this->defaultRequestOptions = [
'verify' => '/my/path.crt',
'proxy' => 'foo',
'proxy' => [
'http' => 'foo',
'https' => 'foo'
],
'headers' => [
'User-Agent' => 'Nextcloud Server Crawler',
],
Expand All @@ -131,7 +198,10 @@ public function testGetWithOptions(): void {

$options = array_merge($this->defaultRequestOptions, [
'verify' => false,
'proxy' => 'bar',
'proxy' => [
'http' => 'bar',
'https' => 'bar'
],
]);

$this->guzzleClient->method('request')
Expand All @@ -154,7 +224,10 @@ public function testPostWithOptions(): void {

$options = array_merge($this->defaultRequestOptions, [
'verify' => false,
'proxy' => 'bar',
'proxy' => [
'http' => 'bar',
'https' => 'bar'
],
]);

$this->guzzleClient->method('request')
Expand All @@ -177,7 +250,10 @@ public function testPutWithOptions(): void {

$options = array_merge($this->defaultRequestOptions, [
'verify' => false,
'proxy' => 'bar',
'proxy' => [
'http' => 'bar',
'https' => 'bar'
],
]);

$this->guzzleClient->method('request')
Expand All @@ -200,7 +276,10 @@ public function testDeleteWithOptions(): void {

$options = array_merge($this->defaultRequestOptions, [
'verify' => false,
'proxy' => 'bar',
'proxy' => [
'http' => 'bar',
'https' => 'bar'
],
]);

$this->guzzleClient->method('request')
Expand All @@ -223,7 +302,10 @@ public function testOptionsWithOptions(): void {

$options = array_merge($this->defaultRequestOptions, [
'verify' => false,
'proxy' => 'bar',
'proxy' => [
'http' => 'bar',
'https' => 'bar'
],
]);

$this->guzzleClient->method('request')
Expand All @@ -246,7 +328,10 @@ public function testHeadWithOptions(): void {

$options = array_merge($this->defaultRequestOptions, [
'verify' => false,
'proxy' => 'bar',
'proxy' => [
'http' => 'bar',
'https' => 'bar'
],
]);

$this->guzzleClient->method('request')
Expand All @@ -268,7 +353,6 @@ public function testSetDefaultOptionsWithNotInstalled(): void {

$this->assertEquals([
'verify' => \OC::$SERVERROOT . '/resources/config/ca-bundle.crt',
'proxy' => null,
'headers' => [
'User-Agent' => 'Nextcloud Server Crawler'
],
Expand All @@ -287,6 +371,11 @@ public function testSetDefaultOptionsWithProxy(): void {
->method('getSystemValue')
->with('proxyuserpwd', null)
->willReturn(null);
$this->config
->expects($this->at(2))
->method('getSystemValue')
->with('proxyexclude', [])
->willReturn([]);
$this->certificateManager
->expects($this->once())
->method('getAbsoluteBundlePath')
Expand All @@ -295,7 +384,46 @@ public function testSetDefaultOptionsWithProxy(): void {

$this->assertEquals([
'verify' => '/my/path.crt',
'proxy' => 'foo',
'proxy' => [
'http' => 'foo',
'https' => 'foo'
],
'headers' => [
'User-Agent' => 'Nextcloud Server Crawler'
],
'timeout' => 30,
], self::invokePrivate($this->client, 'buildRequestOptions', [[]]));
}

public function testSetDefaultOptionsWithProxyAndExclude(): void {
$this->config
->expects($this->at(0))
->method('getSystemValue')
->with('proxy', null)
->willReturn('foo');
$this->config
->expects($this->at(1))
->method('getSystemValue')
->with('proxyuserpwd', null)
->willReturn(null);
$this->config
->expects($this->at(2))
->method('getSystemValue')
->with('proxyexclude', [])
->willReturn(['bar']);
$this->certificateManager
->expects($this->once())
->method('getAbsoluteBundlePath')
->with(null)
->willReturn('/my/path.crt');

$this->assertEquals([
'verify' => '/my/path.crt',
'proxy' => [
'http' => 'foo',
'https' => 'foo',
'no' => ['bar']
],
'headers' => [
'User-Agent' => 'Nextcloud Server Crawler'
],
Expand Down