View source
<?php
namespace Drupal\Tests\cdn\Unit\File;
use Drupal\cdn\CdnSettings;
use Drupal\cdn\File\FileUrlGenerator;
use Drupal\Component\Utility\Crypt;
use Drupal\Component\Utility\UrlHelper;
use Drupal\Core\PrivateKey;
use Drupal\Core\Site\Settings;
use Drupal\Core\StreamWrapper\LocalStream;
use Drupal\Core\StreamWrapper\PublicStream;
use Drupal\Core\StreamWrapper\StreamWrapperInterface;
use Drupal\Core\StreamWrapper\StreamWrapperManagerInterface;
use Drupal\Tests\UnitTestCase;
use Prophecy\Argument;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
class FileUrlGeneratorTest extends UnitTestCase {
protected static $privateKey = 'super secret key that really is just some string';
protected function setUp() : void {
parent::setUp();
$settings = [
'hash_salt' => $this
->randomMachineName(),
];
new Settings($settings);
}
public function testGenerate($scheme, $base_path, $uri, $expected_result) {
$gen = $this
->createFileUrlGenerator($base_path, [
'status' => TRUE,
'mapping' => [
'type' => 'complex',
'fallback_domain' => 'cdn.example.com',
'domains' => [
0 => [
'type' => 'simple',
'domain' => 'static.example.com',
'conditions' => [
'extensions' => [
'css',
'js',
],
],
],
1 => [
'type' => 'auto-balanced',
'domains' => [
'img1.example.com',
'img2.example.com',
],
'conditions' => [
'extensions' => [
'jpg',
'jpeg',
'png',
],
],
],
],
],
'scheme' => $scheme,
'farfuture' => [
'status' => FALSE,
],
'stream_wrappers' => [
'public',
],
]);
$this
->assertSame($expected_result, $gen
->generate($uri));
}
public function urlProvider() {
$cases_root = [
'absolute URL' => [
'http://example.com/llama.jpg',
FALSE,
],
'scheme-relative URL' => [
'//example.com/llama.jpg',
FALSE,
],
'shipped file (fallback)' => [
'core/misc/something.else',
'//cdn.example.com/core/misc/something.else',
],
'shipped file (simple)' => [
'core/misc/simple.css',
'//static.example.com/core/misc/simple.css',
],
'shipped file (auto-balanced)' => [
'core/misc/auto-balanced.png',
'//img2.example.com/core/misc/auto-balanced.png',
],
'shipped file with querystring (e.g. in url() in CSS)' => [
'core/misc/something.else?foo=bar&baz=qux',
'//cdn.example.com/core/misc/something.else?foo=bar&baz=qux',
],
'shipped file with fragment (e.g. in url() in CSS)' => [
'core/misc/something.else#llama',
'//cdn.example.com/core/misc/something.else#llama',
],
'shipped file with querystring & fragment (e.g. in url() in CSS)' => [
'core/misc/something.else?foo=bar&baz=qux#llama',
'//cdn.example.com/core/misc/something.else?foo=bar&baz=qux#llama',
],
'managed public file (fallback)' => [
'public://something.else',
'//cdn.example.com/sites/default/files/something.else',
],
'managed public file (spublic public imple)' => [
'public://simple.css',
'//static.example.com/sites/default/files/simple.css',
],
'managed public file (auto-balanced)' => [
'public://auto-balanced.png',
'//img2.example.com/sites/default/files/auto-balanced.png',
],
'managed private file (fallback)' => [
'private://something.else',
FALSE,
],
'unicode' => [
'public://újjáépítésérol — 100% in B&W.jpg',
'//img1.example.com/sites/default/files/%C3%BAjj%C3%A1%C3%A9p%C3%ADt%C3%A9s%C3%A9rol%20%E2%80%94%20100%25%20in%20B%26W.jpg',
],
'encoded' => [
'public://llama%20.jpg',
'//img2.example.com/sites/default/files/llama%2520.jpg',
],
'reserved characters in RFC3986' => [
'public://gendelims :?#[]@ subdelims !$&\'()*+,;=.something',
'//cdn.example.com/sites/default/files/gendelims%20%3A%3F%23%5B%5D%40%20subdelims%20%21%24%26%27%28%29%2A%2B%2C%3B%3D.something',
],
];
$cases_subdir = [
'absolute URL' => [
'http://example.com/llama.jpg',
FALSE,
],
'scheme-relative URL' => [
'//example.com/llama.jpg',
FALSE,
],
'shipped file (fallback)' => [
'core/misc/feed.svg',
'//cdn.example.com/subdir/core/misc/feed.svg',
],
'shipped file (simple)' => [
'core/misc/simple.css',
'//static.example.com/subdir/core/misc/simple.css',
],
'shipped file (auto-balanced)' => [
'core/misc/auto-balanced.png',
'//img2.example.com/subdir/core/misc/auto-balanced.png',
],
'shipped file with querystring (e.g. in url() in CSS)' => [
'core/misc/something.else?foo=bar&baz=qux',
'//cdn.example.com/subdir/core/misc/something.else?foo=bar&baz=qux',
],
'shipped file with fragment (e.g. in url() in CSS)' => [
'core/misc/something.else#llama',
'//cdn.example.com/subdir/core/misc/something.else#llama',
],
'shipped file with querystring & fragment (e.g. in url() in CSS)' => [
'core/misc/something.else?foo=bar&baz=qux#llama',
'//cdn.example.com/subdir/core/misc/something.else?foo=bar&baz=qux#llama',
],
'managed public file (fallback)' => [
'public://something.else',
'//cdn.example.com/subdir/sites/default/files/something.else',
],
'managed public file (simple)' => [
'public://simple.css',
'//static.example.com/subdir/sites/default/files/simple.css',
],
'managed public file (auto-balanced)' => [
'public://auto-balanced.png',
'//img2.example.com/subdir/sites/default/files/auto-balanced.png',
],
'managed private file (fallback)' => [
'private://something.else',
FALSE,
],
'unicode' => [
'public://újjáépítésérol — 100% in B&W.jpg',
'//img1.example.com/subdir/sites/default/files/%C3%BAjj%C3%A1%C3%A9p%C3%ADt%C3%A9s%C3%A9rol%20%E2%80%94%20100%25%20in%20B%26W.jpg',
],
'encoded' => [
'public://llama%20.jpg',
'//img2.example.com/subdir/sites/default/files/llama%2520.jpg',
],
'reserved characters in RFC3986' => [
'public://gendelims :?#[]@ subdelims !$&\'()*+,;=.something',
'//cdn.example.com/subdir/sites/default/files/gendelims%20%3A%3F%23%5B%5D%40%20subdelims%20%21%24%26%27%28%29%2A%2B%2C%3B%3D.something',
],
];
$cases = [];
assert(count($cases_root) === count($cases_subdir));
foreach ($cases_root as $description => $case) {
$cases['root, ' . $description] = array_merge([
'',
], $case);
}
foreach ($cases_subdir as $description => $case) {
$cases['subdir, ' . $description] = array_merge([
'/subdir',
], $case);
}
$cases_with_scheme = [];
foreach ($cases as $description => $case) {
foreach ([
'https://',
'http://',
'//',
] as $scheme) {
list($base_path, $uri, $expected_result) = $case;
$cases_with_scheme['scheme=' . $scheme . ', ' . $description] = [
$scheme,
$base_path,
$uri,
!is_string($expected_result) ? $expected_result : $scheme . substr($expected_result, 2),
];
}
}
return $cases_with_scheme;
}
public function testGenerateFarfuture() {
$config = [
'status' => TRUE,
'mapping' => [
'type' => 'simple',
'domain' => 'cdn.example.com',
'conditions' => [],
],
'scheme' => '//',
'farfuture' => [
'status' => TRUE,
],
'stream_wrappers' => [
'public',
'file',
],
];
$llama_jpg_filename = 'llama%20 (' . $this
->randomMachineName() . ').jpg';
$llama_jpg_filepath = $this->root . '/sites/default/files/' . $llama_jpg_filename;
file_put_contents($llama_jpg_filepath, $this
->randomMachineName());
$llama_jpg_mtime = filemtime($llama_jpg_filepath);
$this
->assertTrue(file_exists($llama_jpg_filepath));
$gen = $this
->createFileUrlGenerator('', $config);
$this
->assertSame('//cdn.example.com/core/misc/does-not-exist.js', $gen
->generate('core/misc/does-not-exist.js'));
$drupal_js_mtime = filemtime($this->root . '/core/misc/drupal.js');
$drupal_js_security_token = Crypt::hmacBase64($drupal_js_mtime . ':relative:' . UrlHelper::encodePath('/core/misc/drupal.js'), static::$privateKey . Settings::getHashSalt());
$this
->assertSame('//cdn.example.com/cdn/ff/' . $drupal_js_security_token . '/' . $drupal_js_mtime . '/:relative:/core/misc/drupal.js', $gen
->generate('core/misc/drupal.js'));
$llama_jpg_security_token = Crypt::hmacBase64($llama_jpg_mtime . 'file' . UrlHelper::encodePath('/' . $llama_jpg_filepath), static::$privateKey . Settings::getHashSalt());
$this
->assertSame('//cdn.example.com/cdn/ff/' . $llama_jpg_security_token . '/' . $llama_jpg_mtime . '/file/' . UrlHelper::encodePath($llama_jpg_filepath), $gen
->generate('file://' . $llama_jpg_filepath));
$gen = $this
->createFileUrlGenerator('/subdir', $config);
$this
->assertSame('//cdn.example.com/subdir/core/misc/does-not-exist.js', $gen
->generate('core/misc/does-not-exist.js'));
$this
->assertSame('//cdn.example.com/subdir/cdn/ff/' . $drupal_js_security_token . '/' . $drupal_js_mtime . '/:relative:/core/misc/drupal.js', $gen
->generate('core/misc/drupal.js'));
$this
->assertSame('//cdn.example.com/subdir/cdn/ff/' . $llama_jpg_security_token . '/' . $llama_jpg_mtime . '/file/' . UrlHelper::encodePath($llama_jpg_filepath), $gen
->generate('file://' . $llama_jpg_filepath));
unlink($llama_jpg_filepath);
}
protected function createFileUrlGenerator($base_path, array $raw_config) {
$request = $this
->prophesize(Request::class);
$request
->getBasePath()
->willReturn($base_path);
$request
->getSchemeAndHttpHost()
->willReturn('http://example.com');
$request_stack = $this
->prophesize(RequestStack::class);
$request_stack
->getCurrentRequest()
->willReturn($request
->reveal());
$current_uri = '';
$public_stream_wrapper = $this
->prophesize(PublicStream::class);
$public_stream_wrapper
->getExternalUrl()
->will(function () use ($base_path, &$current_uri) {
return 'http://example.com' . $base_path . '/sites/default/files/' . UrlHelper::encodePath(substr($current_uri, 9));
});
$file_stream_wrapper = $this
->prophesize(LocalStream::class);
$root = $this->root;
$file_stream_wrapper
->getExternalUrl()
->will(function () use ($root, $base_path, &$current_uri) {
return 'http://example.com/inaccessible';
});
$stream_wrapper_manager = $this
->prophesize(StreamWrapperManagerInterface::class);
$stream_wrapper_manager
->getWrappers(StreamWrapperInterface::LOCAL_NORMAL)
->willReturn([
'public' => TRUE,
]);
$stream_wrapper_manager
->getViaUri(Argument::that(function ($uri) {
return substr($uri, 0, 9) === 'public://';
}))
->will(function ($args) use (&$public_stream_wrapper, &$current_uri) {
$s = $public_stream_wrapper
->reveal();
$current_uri = $args[0];
return $s;
});
$stream_wrapper_manager
->getViaUri(Argument::that(function ($uri) {
return substr($uri, 0, 7) === 'file://';
}))
->will(function ($args) use (&$file_stream_wrapper, &$current_uri) {
$s = $file_stream_wrapper
->reveal();
$current_uri = $args[0];
return $s;
});
$private_key = $this
->prophesize(PrivateKey::class);
$private_key
->get()
->willReturn(static::$privateKey);
return new FileUrlGenerator($this->root, $stream_wrapper_manager
->reveal(), $request_stack
->reveal(), $private_key
->reveal(), new CdnSettings($this
->getConfigFactoryStub([
'cdn.settings' => $raw_config,
]), $stream_wrapper_manager
->reveal()));
}
}