2012-03-05 10:26:07 +00:00
< ? php
/*
* This file is part of Composer .
*
* ( c ) Nils Adermann < naderman @ naderman . de >
* Jordi Boggiano < j . boggiano @ seld . be >
*
* For the full copyright and license information , please view the LICENSE
* file that was distributed with this source code .
*/
namespace Composer\Test\Util ;
2020-06-16 07:35:33 +00:00
use Composer\Config ;
use Composer\IO\ConsoleIO ;
use Composer\IO\IOInterface ;
use Composer\Util\AuthHelper ;
2012-03-05 10:26:07 +00:00
use Composer\Util\RemoteFilesystem ;
2020-02-07 03:18:45 +00:00
use Composer\Test\TestCase ;
2020-06-16 07:35:33 +00:00
use PHPUnit\Framework\MockObject\MockObject ;
use ReflectionMethod ;
use ReflectionProperty ;
2012-03-05 10:26:07 +00:00
2017-11-04 14:52:13 +00:00
class RemoteFilesystemTest extends TestCase
2012-03-05 10:26:07 +00:00
{
public function testGetOptionsForUrl ()
{
2020-06-16 07:35:33 +00:00
$io = $this -> getIOInterfaceMock ();
2012-03-05 10:26:07 +00:00
$io
-> expects ( $this -> once ())
2012-11-07 12:33:50 +00:00
-> method ( 'hasAuthentication' )
2020-06-16 07:35:33 +00:00
-> willReturn ( false )
2012-03-05 10:26:07 +00:00
;
2012-10-18 16:43:31 +00:00
$res = $this -> callGetOptionsForUrl ( $io , array ( 'http://example.org' , array ()));
2012-10-19 10:25:53 +00:00
$this -> assertTrue ( isset ( $res [ 'http' ][ 'header' ]) && is_array ( $res [ 'http' ][ 'header' ]), 'getOptions must return an array with headers' );
2012-03-05 10:26:07 +00:00
}
public function testGetOptionsForUrlWithAuthorization ()
{
2020-06-16 07:35:33 +00:00
$io = $this -> getIOInterfaceMock ();
2012-03-05 10:26:07 +00:00
$io
-> expects ( $this -> once ())
2012-11-07 12:33:50 +00:00
-> method ( 'hasAuthentication' )
2020-06-16 07:35:33 +00:00
-> willReturn ( true )
2012-03-05 10:26:07 +00:00
;
$io
-> expects ( $this -> once ())
2012-11-07 12:33:50 +00:00
-> method ( 'getAuthentication' )
2020-06-16 07:35:33 +00:00
-> willReturn ( array ( 'username' => 'login' , 'password' => 'password' ))
2012-03-05 10:26:07 +00:00
;
2012-10-18 16:43:31 +00:00
$options = $this -> callGetOptionsForUrl ( $io , array ( 'http://example.org' , array ()));
2012-10-19 10:25:53 +00:00
$found = false ;
foreach ( $options [ 'http' ][ 'header' ] as $header ) {
if ( 0 === strpos ( $header , 'Authorization: Basic' )) {
$found = true ;
}
}
$this -> assertTrue ( $found , 'getOptions must have an Authorization header' );
2012-03-05 10:26:07 +00:00
}
2012-10-03 13:09:47 +00:00
public function testGetOptionsForUrlWithStreamOptions ()
{
2020-06-16 07:35:33 +00:00
$io = $this -> getIOInterfaceMock ();
2012-10-03 13:09:47 +00:00
$io
-> expects ( $this -> once ())
2012-11-07 12:33:50 +00:00
-> method ( 'hasAuthentication' )
2020-06-16 07:35:33 +00:00
-> willReturn ( true )
2012-10-03 13:09:47 +00:00
;
2020-02-07 03:18:45 +00:00
$io
-> expects ( $this -> once ())
-> method ( 'getAuthentication' )
2020-06-16 07:35:33 +00:00
-> willReturn ( array ( 'username' => null , 'password' => null ))
2020-02-07 03:18:45 +00:00
;
2012-10-03 13:09:47 +00:00
$streamOptions = array ( 'ssl' => array (
'allow_self_signed' => true ,
));
2012-10-18 16:43:31 +00:00
$res = $this -> callGetOptionsForUrl ( $io , array ( 'https://example.org' , array ()), $streamOptions );
2012-10-03 13:09:47 +00:00
$this -> assertTrue ( isset ( $res [ 'ssl' ]) && isset ( $res [ 'ssl' ][ 'allow_self_signed' ]) && true === $res [ 'ssl' ][ 'allow_self_signed' ], 'getOptions must return an array with a allow_self_signed set to true' );
}
2012-10-18 16:43:31 +00:00
public function testGetOptionsForUrlWithCallOptionsKeepsHeader ()
{
2020-06-16 07:35:33 +00:00
$io = $this -> getIOInterfaceMock ();
2012-10-18 16:43:31 +00:00
$io
-> expects ( $this -> once ())
2012-11-07 12:33:50 +00:00
-> method ( 'hasAuthentication' )
2020-06-16 07:35:33 +00:00
-> willReturn ( true )
2012-10-18 16:43:31 +00:00
;
2020-02-07 03:18:45 +00:00
$io
-> expects ( $this -> once ())
-> method ( 'getAuthentication' )
2020-06-16 07:35:33 +00:00
-> willReturn ( array ( 'username' => null , 'password' => null ))
2020-02-07 03:18:45 +00:00
;
2012-10-18 16:43:31 +00:00
$streamOptions = array ( 'http' => array (
'header' => 'Foo: bar' ,
));
$res = $this -> callGetOptionsForUrl ( $io , array ( 'https://example.org' , $streamOptions ));
$this -> assertTrue ( isset ( $res [ 'http' ][ 'header' ]), 'getOptions must return an array with a http.header key' );
2012-10-19 10:25:53 +00:00
$found = false ;
foreach ( $res [ 'http' ][ 'header' ] as $header ) {
if ( $header === 'Foo: bar' ) {
$found = true ;
}
}
$this -> assertTrue ( $found , 'getOptions must have a Foo: bar header' );
$this -> assertGreaterThan ( 1 , count ( $res [ 'http' ][ 'header' ]));
2012-10-18 16:43:31 +00:00
}
2012-03-05 10:26:07 +00:00
public function testCallbackGetFileSize ()
{
2020-06-16 07:35:33 +00:00
$fs = new RemoteFilesystem ( $this -> getIOInterfaceMock (), $this -> getConfigMock ());
2012-03-05 10:26:07 +00:00
$this -> callCallbackGet ( $fs , STREAM_NOTIFY_FILE_SIZE_IS , 0 , '' , 0 , 0 , 20 );
2020-09-10 15:21:11 +00:00
$this -> assertAttributeEqualsCustom ( 20 , 'bytesMax' , $fs );
2012-03-05 10:26:07 +00:00
}
public function testCallbackGetNotifyProgress ()
{
2020-06-16 07:35:33 +00:00
$io = $this -> getIOInterfaceMock ();
2012-03-05 10:26:07 +00:00
$io
-> expects ( $this -> once ())
2015-02-18 09:04:39 +00:00
-> method ( 'overwriteError' )
2012-03-05 10:26:07 +00:00
;
2018-11-14 16:54:19 +00:00
$fs = new RemoteFilesystem ( $io , $this -> getConfigMock ());
2012-03-05 10:26:07 +00:00
$this -> setAttribute ( $fs , 'bytesMax' , 20 );
$this -> setAttribute ( $fs , 'progress' , true );
$this -> callCallbackGet ( $fs , STREAM_NOTIFY_PROGRESS , 0 , '' , 0 , 10 , 20 );
2020-09-10 15:21:11 +00:00
$this -> assertAttributeEqualsCustom ( 50.0 , 'lastProgress' , $fs );
2012-03-05 10:26:07 +00:00
}
2014-02-26 15:01:31 +00:00
public function testCallbackGetPassesThrough404 ()
2012-03-05 10:26:07 +00:00
{
2020-06-16 07:35:33 +00:00
$fs = new RemoteFilesystem ( $this -> getIOInterfaceMock (), $this -> getConfigMock ());
2012-03-05 10:26:07 +00:00
2014-02-26 15:01:31 +00:00
$this -> assertNull ( $this -> callCallbackGet ( $fs , STREAM_NOTIFY_FAILURE , 0 , 'HTTP/1.1 404 Not Found' , 404 , 0 , 0 ));
2012-03-05 10:26:07 +00:00
}
public function testGetContents ()
{
2020-06-16 07:35:33 +00:00
$fs = new RemoteFilesystem ( $this -> getIOInterfaceMock (), $this -> getConfigMock ());
2012-03-05 10:26:07 +00:00
2020-09-10 15:21:11 +00:00
$this -> assertStringContainsString ( 'testGetContents' , $fs -> getContents ( 'http://example.org' , 'file://' . __FILE__ ));
2012-03-05 10:26:07 +00:00
}
public function testCopy ()
{
2020-06-16 07:35:33 +00:00
$fs = new RemoteFilesystem ( $this -> getIOInterfaceMock (), $this -> getConfigMock ());
2012-03-05 10:26:07 +00:00
$file = tempnam ( sys_get_temp_dir (), 'c' );
2012-03-29 13:09:01 +00:00
$this -> assertTrue ( $fs -> copy ( 'http://example.org' , 'file://' . __FILE__ , $file ));
2012-03-05 10:26:07 +00:00
$this -> assertFileExists ( $file );
2020-09-10 15:21:11 +00:00
$this -> assertStringContainsString ( 'testCopy' , file_get_contents ( $file ));
2012-03-05 10:26:07 +00:00
unlink ( $file );
}
2020-06-16 07:35:33 +00:00
public function testCopyWithNoRetryOnFailure ()
{
2020-09-10 15:21:11 +00:00
$this -> setExpectedException ( 'Composer\Downloader\TransportException' );
2020-06-16 07:35:33 +00:00
$fs = $this -> getRemoteFilesystemWithMockedMethods ( array ( 'getRemoteContents' ));
$fs -> expects ( $this -> once ()) -> method ( 'getRemoteContents' )
-> willReturnCallback ( function ( $originUrl , $fileUrl , $ctx , & $http_response_header ) {
$http_response_header = array ( 'http/1.1 401 unauthorized' );
return '' ;
});
$file = tempnam ( sys_get_temp_dir (), 'z' );
unlink ( $file );
$fs -> copy (
'http://example.org' ,
'file://' . __FILE__ ,
$file ,
true ,
array ( 'retry-auth-failure' => false )
);
}
public function testCopyWithSuccessOnRetry ()
{
$authHelper = $this -> getAuthHelperWithMockedMethods ( array ( 'promptAuthIfNeeded' ));
$fs = $this -> getRemoteFilesystemWithMockedMethods ( array ( 'getRemoteContents' ), $authHelper );
$authHelper -> expects ( $this -> once ())
-> method ( 'promptAuthIfNeeded' )
-> willReturn ( array (
'storeAuth' => true ,
'retry' => true
));
$fs -> expects ( $this -> at ( 0 ))
-> method ( 'getRemoteContents' )
-> willReturnCallback ( function ( $originUrl , $fileUrl , $ctx , & $http_response_header ) {
$http_response_header = array ( 'http/1.1 401 unauthorized' );
return '' ;
});
$fs -> expects ( $this -> at ( 1 ))
-> method ( 'getRemoteContents' )
-> willReturnCallback ( function ( $originUrl , $fileUrl , $ctx , & $http_response_header ) {
$http_response_header = array ( 'http/1.1 200 OK' );
return '<?php $copied = "Copied"; ' ;
});
$file = tempnam ( sys_get_temp_dir (), 'z' );
$copyResult = $fs -> copy (
'http://example.org' ,
'file://' . __FILE__ ,
$file ,
true ,
array ( 'retry-auth-failure' => true )
);
$this -> assertTrue ( $copyResult );
$this -> assertFileExists ( $file );
2020-09-10 15:21:11 +00:00
$this -> assertStringContainsString ( 'Copied' , file_get_contents ( $file ));
2020-06-16 07:35:33 +00:00
unlink ( $file );
}
2014-02-23 13:33:29 +00:00
/**
* @ group TLS
*/
public function testGetOptionsForUrlCreatesSecureTlsDefaults ()
{
2020-06-16 07:35:33 +00:00
$io = $this -> getIOInterfaceMock ();
2014-02-23 13:33:29 +00:00
2016-01-26 13:07:05 +00:00
$res = $this -> callGetOptionsForUrl ( $io , array ( 'example.org' , array ( 'ssl' => array ( 'cafile' => '/some/path/file.crt' ))), array (), 'http://www.example.org' );
2014-02-23 13:33:29 +00:00
$this -> assertTrue ( isset ( $res [ 'ssl' ][ 'ciphers' ]));
2020-06-16 07:35:33 +00:00
$this -> assertRegExp ( '|!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA|' , $res [ 'ssl' ][ 'ciphers' ]);
2014-02-23 13:33:29 +00:00
$this -> assertTrue ( $res [ 'ssl' ][ 'verify_peer' ]);
$this -> assertTrue ( $res [ 'ssl' ][ 'SNI_enabled' ]);
$this -> assertEquals ( 7 , $res [ 'ssl' ][ 'verify_depth' ]);
2016-01-16 16:43:33 +00:00
if ( PHP_VERSION_ID < 50600 ) {
$this -> assertEquals ( 'www.example.org' , $res [ 'ssl' ][ 'CN_match' ]);
$this -> assertEquals ( 'www.example.org' , $res [ 'ssl' ][ 'SNI_server_name' ]);
}
2014-02-23 13:33:29 +00:00
$this -> assertEquals ( '/some/path/file.crt' , $res [ 'ssl' ][ 'cafile' ]);
if ( version_compare ( PHP_VERSION , '5.4.13' ) >= 0 ) {
$this -> assertTrue ( $res [ 'ssl' ][ 'disable_compression' ]);
2014-02-23 14:15:24 +00:00
} else {
$this -> assertFalse ( isset ( $res [ 'ssl' ][ 'disable_compression' ]));
2014-02-23 13:33:29 +00:00
}
}
2016-09-28 17:08:24 +00:00
/**
* Provides URLs to public downloads at BitBucket .
*
* @ return string [][]
*/
public function provideBitbucketPublicDownloadUrls ()
{
return array (
2016-09-29 15:29:09 +00:00
array ( 'https://bitbucket.org/seldaek/composer-live-test-repo/downloads/composer-unit-test-download-me.txt' , '1234' ),
2016-09-28 17:08:24 +00:00
);
}
/**
* Tests that a BitBucket public download is correctly retrieved .
*
* @ param string $url
* @ param string $contents
* @ dataProvider provideBitbucketPublicDownloadUrls
*/
public function testBitBucketPublicDownload ( $url , $contents )
{
2020-06-16 07:35:33 +00:00
/** @var ConsoleIO $io */
2016-09-28 17:08:24 +00:00
$io = $this
-> getMockBuilder ( 'Composer\IO\ConsoleIO' )
-> disableOriginalConstructor ()
-> getMock ();
2018-11-14 16:54:19 +00:00
$rfs = new RemoteFilesystem ( $io , $this -> getConfigMock ());
2016-09-28 17:08:24 +00:00
$hostname = parse_url ( $url , PHP_URL_HOST );
$result = $rfs -> getContents ( $hostname , $url , false );
$this -> assertEquals ( $contents , $result );
}
/**
* Tests that a BitBucket public download is correctly retrieved when `bitbucket-oauth` is configured .
*
* @ param string $url
* @ param string $contents
* @ dataProvider provideBitbucketPublicDownloadUrls
*/
public function testBitBucketPublicDownloadWithAuthConfigured ( $url , $contents )
{
2020-06-16 07:35:33 +00:00
/** @var MockObject|ConsoleIO $io */
2016-09-28 17:08:24 +00:00
$io = $this
-> getMockBuilder ( 'Composer\IO\ConsoleIO' )
-> disableOriginalConstructor ()
-> getMock ();
2017-11-29 15:37:00 +00:00
$domains = array ();
2016-09-28 17:08:24 +00:00
$io
-> method ( 'hasAuthentication' )
2020-06-16 07:35:33 +00:00
-> willReturnCallback ( function ( $arg ) use ( & $domains ) {
2017-11-29 15:37:00 +00:00
$domains [] = $arg ;
// first time is called with bitbucket.org, then it redirects to bbuseruploads.s3.amazonaws.com so next time we have no auth configured
return $arg === 'bitbucket.org' ;
2020-06-16 07:35:33 +00:00
});
2016-09-28 17:08:24 +00:00
$io
2017-11-29 15:37:00 +00:00
-> expects ( $this -> at ( 1 ))
2016-09-28 17:08:24 +00:00
-> method ( 'getAuthentication' )
-> with ( 'bitbucket.org' )
-> willReturn ( array (
'username' => 'x-token-auth' ,
// This token is fake, but it matches a valid token's pattern.
2017-03-08 14:07:29 +00:00
'password' => '1A0yeK5Po3ZEeiiRiMWLivS0jirLdoGuaSGq9NvESFx1Fsdn493wUDXC8rz_1iKVRTl1GINHEUCsDxGh5lZ=' ,
2016-09-28 17:08:24 +00:00
));
2018-11-14 16:54:19 +00:00
$rfs = new RemoteFilesystem ( $io , $this -> getConfigMock ());
2016-09-28 17:08:24 +00:00
$hostname = parse_url ( $url , PHP_URL_HOST );
$result = $rfs -> getContents ( $hostname , $url , false );
$this -> assertEquals ( $contents , $result );
2017-11-29 15:37:00 +00:00
$this -> assertEquals ( array ( 'bitbucket.org' , 'bbuseruploads.s3.amazonaws.com' ), $domains );
2016-09-28 17:08:24 +00:00
}
2020-06-16 07:35:33 +00:00
private function callGetOptionsForUrl ( $io , array $args = array (), array $options = array (), $fileUrl = '' )
2012-03-05 10:26:07 +00:00
{
2018-11-14 16:54:19 +00:00
$fs = new RemoteFilesystem ( $io , $this -> getConfigMock (), $options );
2020-06-16 07:35:33 +00:00
$ref = new ReflectionMethod ( $fs , 'getOptionsForUrl' );
$prop = new ReflectionProperty ( $fs , 'fileUrl' );
2012-03-05 10:26:07 +00:00
$ref -> setAccessible ( true );
2014-02-28 18:46:49 +00:00
$prop -> setAccessible ( true );
$prop -> setValue ( $fs , $fileUrl );
2012-03-05 10:26:07 +00:00
return $ref -> invokeArgs ( $fs , $args );
}
2020-06-16 07:35:33 +00:00
/**
* @ return MockObject | Config
*/
private function getConfigMock ()
{
$config = $this -> getMockBuilder ( 'Composer\Config' ) -> getMock ();
$config
-> method ( 'get' )
-> willReturnCallback ( function ( $key ) {
if ( $key === 'github-domains' || $key === 'gitlab-domains' ) {
return array ();
}
return null ;
});
return $config ;
}
private function callCallbackGet ( RemoteFilesystem $fs , $notificationCode , $severity , $message , $messageCode , $bytesTransferred , $bytesMax )
2012-03-05 10:26:07 +00:00
{
2020-06-16 07:35:33 +00:00
$ref = new ReflectionMethod ( $fs , 'callbackGet' );
2012-03-05 10:26:07 +00:00
$ref -> setAccessible ( true );
$ref -> invoke ( $fs , $notificationCode , $severity , $message , $messageCode , $bytesTransferred , $bytesMax );
}
2020-06-16 07:35:33 +00:00
private function setAttribute ( $object , $attribute , $value )
2012-03-05 10:26:07 +00:00
{
2020-06-16 07:35:33 +00:00
$attr = new ReflectionProperty ( $object , $attribute );
2012-03-05 10:26:07 +00:00
$attr -> setAccessible ( true );
$attr -> setValue ( $object , $value );
}
2020-06-16 07:35:33 +00:00
2020-09-10 15:21:11 +00:00
private function assertAttributeEqualsCustom ( $value , $attribute , $object )
{
$attr = new ReflectionProperty ( $object , $attribute );
$attr -> setAccessible ( true );
$this -> assertSame ( $value , $attr -> getValue ( $object ));
}
2020-06-16 07:35:33 +00:00
/**
* @ return MockObject | IOInterface
*/
private function getIOInterfaceMock ()
{
return $this -> getMockBuilder ( 'Composer\IO\IOInterface' ) -> getMock ();
}
/**
* @ param array $mockedMethods
* @ param AuthHelper $authHelper
*
* @ return RemoteFilesystem | MockObject
*/
private function getRemoteFilesystemWithMockedMethods ( array $mockedMethods , AuthHelper $authHelper = null )
{
return $this -> getMockBuilder ( 'Composer\Util\RemoteFilesystem' )
-> setConstructorArgs ( array (
$this -> getIOInterfaceMock (),
$this -> getConfigMock (),
array (),
false ,
$authHelper
))
-> setMethods ( $mockedMethods )
-> getMock ();
}
/**
* @ param array $mockedMethods
*
* @ return AuthHelper | MockObject
*/
private function getAuthHelperWithMockedMethods ( array $mockedMethods )
{
return $this -> getMockBuilder ( 'Composer\Util\AuthHelper' )
-> setConstructorArgs ( array (
$this -> getIOInterfaceMock (),
$this -> getConfigMock ()
))
-> setMethods ( $mockedMethods )
-> getMock ();
}
2012-03-05 10:26:07 +00:00
}