Fixed multiple url() issues with streams

* Fixed `url()` returning wrong path if using stream with grav root path in it, eg: `user-data://shop` when Grav is in `/shop`
* Fixed `url()` not returning a path to non-existing file (`user-data://shop` => `/user/data/shop`) if it is set to fail gracefully
* Fixed `url()` returning false on unknown streams, such as `ftp://domain.com`, they should be treated as external URL
This commit is contained in:
Matias Griese
2019-06-28 13:36:37 +03:00
parent 02f544f813
commit 69b39b4b21
3 changed files with 150 additions and 40 deletions

View File

@@ -8,6 +8,9 @@
1. [](#bugfix)
* Fixed some potential issues when `$grav['user']` is not set
* Fixed error when calling `Media::add($name, null)`
* Fixed `url()` returning wrong path if using stream with grav root path in it, eg: `user-data://shop` when Grav is in `/shop`
* Fixed `url()` not returning a path to non-existing file (`user-data://shop` => `/user/data/shop`) if it is set to fail gracefully
* Fixed `url()` returning false on unknown streams, such as `ftp://domain.com`, they should be treated as external URL
# v1.6.11
## 06/21/2019

View File

@@ -43,43 +43,80 @@ abstract class Utils
}
}
if (Grav::instance()['config']->get('system.absolute_urls', false)) {
$domain = true;
}
$input = (string)$input;
if (Uri::isExternal($input)) {
return $input;
}
$grav = Grav::instance();
/** @var Uri $uri */
$uri = Grav::instance()['uri'];
$uri = $grav['uri'];
$root = $uri->rootUrl();
$input = Utils::replaceFirstOccurrence($root, '', $input);
$input = ltrim((string)$input, '/');
if (Utils::contains((string)$input, '://')) {
if (static::contains((string)$input, '://')) {
/** @var UniformResourceLocator $locator */
$locator = Grav::instance()['locator'];
$locator = $grav['locator'];
$parts = Uri::parseUrl($input);
if ($parts) {
try {
$resource = $locator->findResource("{$parts['scheme']}://{$parts['host']}{$parts['path']}", false);
} catch (\Exception $e) {
return $fail_gracefully ? $input : false;
if (is_array($parts)) {
// Make sure we always have scheme, host, port and path.
$scheme = $parts['scheme'] ?? '';
$host = $parts['host'] ?? '';
$port = $parts['port'] ?? '';
$path = $parts['path'] ?? '';
if ($scheme && !$port) {
// If URL has a scheme, we need to check if it's one of Grav streams.
if (!$locator->schemeExists($scheme)) {
// If scheme does not exists as a stream, assume it's external.
return str_replace(' ', '%20', $input);
}
// Attempt to find the resource (because of parse_url() we need to put host back to path).
$resource = $locator->findResource("{$scheme}://{$host}{$path}", false);
if ($resource === false) {
if (!$fail_gracefully) {
return false;
}
// Return location where the file would be if it was saved.
$resource = $locator->findResource("{$scheme}://{$host}{$path}", false, true);
}
} elseif ($host || $port) {
// If URL doesn't have scheme but has host or port, it is external.
return str_replace(' ', '%20', $input);
}
if ($resource && isset($parts['query'])) {
$resource = $resource . '?' . $parts['query'];
if (!empty($resource)) {
// Add query string back.
if (isset($parts['query'])) {
$resource .= '?' . $parts['query'];
}
// Add fragment back.
if (isset($parts['fragment'])) {
$resource .= '#' . $parts['fragment'];
}
}
} else {
// Not a valid URL (can still be a stream).
$resource = $locator->findResource($input, false);
}
} else {
$root = $uri->rootUrl();
if (static::startsWith($input, $root)) {
$input = static::replaceFirstOccurrence($root, '', $input);
}
$input = ltrim($input, '/');
$resource = $input;
}
@@ -87,6 +124,8 @@ abstract class Utils
return false;
}
$domain = $domain ?: $grav['config']->get('system.absolute_urls', false);
return rtrim($uri->rootUrl($domain), '/') . '/' . ($resource ?? '');
}

View File

@@ -382,30 +382,60 @@ class UtilsTest extends \Codeception\TestCase\Test
// Fail hard
$this->assertSame(false, Utils::url('', true));
$this->assertSame(false, Utils::url(''));
$this->assertSame(false, Utils::url('foo://bar/baz'));
$this->assertSame(false, Utils::url(new stdClass()));
$this->assertSame(false, Utils::url(['foo','bar','baz']));
$this->assertSame(false, Utils::url('user://does/not/exist'));
// Fail Gracefully
$this->assertSame('/', Utils::url('/', false, true));
$this->assertSame('/', Utils::url('', false, true));
$this->assertSame('foo://bar/baz', Utils::url('foo://bar/baz', false, true));
$this->assertSame('/', Utils::url(new stdClass(), false, true));
$this->assertSame('/', Utils::url(['foo','bar','baz'], false, true));
$this->assertSame('/user/does/not/exist', Utils::url('user://does/not/exist', false, true));
// Simple paths
$this->assertSame('/', Utils::url('/'));
$this->assertSame('http://testing.dev/', Utils::url('/', true));
$this->assertSame('http://testing.dev/path1', Utils::url('/path1', true));
$this->assertSame('/path1', Utils::url('/path1'));
$this->assertSame('/path1/path2', Utils::url('/path1/path2'));
$this->assertSame('/random/path1/path2', Utils::url('/random/path1/path2'));
$this->assertSame('/foobar.jpg', Utils::url('/foobar.jpg'));
$this->assertSame('/path1/foobar.jpg', Utils::url('/path1/foobar.jpg'));
$this->assertSame('/path1/path2/foobar.jpg', Utils::url('/path1/path2/foobar.jpg'));
$this->assertSame('/random/path1/path2/foobar.jpg', Utils::url('/random/path1/path2/foobar.jpg'));
// Simple paths with domain
$this->assertSame('http://testing.dev/', Utils::url('/', true));
$this->assertSame('http://testing.dev/path1', Utils::url('/path1', true));
$this->assertSame('http://testing.dev/path1/path2', Utils::url('/path1/path2', true));
$this->assertSame('http://testing.dev/random/path1/path2', Utils::url('/random/path1/path2', true));
$this->assertSame('http://testing.dev/foobar.jpg', Utils::url('/foobar.jpg', true));
$this->assertSame('http://testing.dev/path1/foobar.jpg', Utils::url('/path1/foobar.jpg', true));
$this->assertSame('http://testing.dev/path1/path2/foobar.jpg', Utils::url('/path1/path2/foobar.jpg', true));
$this->assertSame('http://testing.dev/random/path1/path2/foobar.jpg', Utils::url('/random/path1/path2/foobar.jpg', true));
// Relative paths from Grav root.
$this->assertSame('/subdir', Utils::url('subdir'));
$this->assertSame('/subdir/path1', Utils::url('subdir/path1'));
$this->assertSame('/subdir/path1/path2', Utils::url('subdir/path1/path2'));
$this->assertSame('/path1', Utils::url('path1'));
$this->assertSame('/path1/path2', Utils::url('path1/path2'));
$this->assertSame('/foobar.jpg', Utils::url('foobar.jpg'));
$this->assertSame('http://testing.dev/foobar.jpg', Utils::url('foobar.jpg', true));
// Relative paths from Grav root with domain.
$this->assertSame('http://testing.dev/foobar.jpg', Utils::url('foobar.jpg', true));
$this->assertSame('http://testing.dev/foobar.jpg', Utils::url('/foobar.jpg', true));
$this->assertSame('http://testing.dev/path1/foobar.jpg', Utils::url('/path1/foobar.jpg', true));
$this->assertSame('/foobar.jpg', Utils::url('/foobar.jpg'));
$this->assertSame('/foobar.jpg', Utils::url('foobar.jpg'));
$this->assertSame('/path1/foobar.jpg', Utils::url('/path1/foobar.jpg'));
$this->assertSame('/path1/path2/foobar.jpg', Utils::url('/path1/path2/foobar.jpg'));
// All Non-existing streams should be treated as external URI / protocol.
$this->assertSame('http://domain.com/path', Utils::url('http://domain.com/path'));
$this->assertSame('ftp://domain.com/path', Utils::url('ftp://domain.com/path'));
$this->assertSame('sftp://domain.com/path', Utils::url('sftp://domain.com/path'));
$this->assertSame('ssh://domain.com', Utils::url('ssh://domain.com'));
$this->assertSame('pop://domain.com', Utils::url('pop://domain.com'));
$this->assertSame('foo://bar/baz', Utils::url('foo://bar/baz'));
$this->assertSame('foo://bar/baz', Utils::url('foo://bar/baz', true));
// $this->assertSame('mailto:joe@domain.com', Utils::url('mailto:joe@domain.com', true)); // FIXME <-
}
public function testUrlWithRoot()
@@ -415,31 +445,69 @@ class UtilsTest extends \Codeception\TestCase\Test
// Fail hard
$this->assertSame(false, Utils::url('', true));
$this->assertSame(false, Utils::url(''));
$this->assertSame(false, Utils::url('foo://bar/baz'));
$this->assertSame(false, Utils::url(new stdClass()));
$this->assertSame(false, Utils::url(['foo','bar','baz']));
$this->assertSame(false, Utils::url('user://does/not/exist'));
// Fail Gracefully
$this->assertSame('/subdir/', Utils::url('/', false, true));
$this->assertSame('/subdir/', Utils::url('', false, true));
$this->assertSame('foo://bar/baz', Utils::url('foo://bar/baz', false, true));
$this->assertSame('/subdir/', Utils::url(new stdClass(), false, true));
$this->assertSame('/subdir/', Utils::url(['foo','bar','baz'], false, true));
$this->assertSame('/subdir/user/does/not/exist', Utils::url('user://does/not/exist', false, true));
$this->assertSame('http://testing.dev/subdir/', Utils::url('/', true));
$this->assertSame('http://testing.dev/subdir/path1', Utils::url('/path1', true));
$this->assertSame('http://testing.dev/subdir/path1', Utils::url('/subdir/path1', true));
// Simple paths
$this->assertSame('/subdir/', Utils::url('/'));
$this->assertSame('/subdir/path1', Utils::url('/path1'));
$this->assertSame('/subdir/path1/path2', Utils::url('/path1/path2'));
$this->assertSame('/subdir/path1/path2', Utils::url('/subdir/path1/path2'));
$this->assertSame('http://testing.dev/subdir/foobar.jpg', Utils::url('foobar.jpg', true));
$this->assertSame('http://testing.dev/subdir/foobar.jpg', Utils::url('/foobar.jpg', true));
$this->assertSame('http://testing.dev/subdir/foobar.jpg', Utils::url('/subdir/foobar.jpg', true));
$this->assertSame('http://testing.dev/subdir/path1/foobar.jpg', Utils::url('/path1/foobar.jpg', true));
$this->assertSame('http://testing.dev/subdir/path1/foobar.jpg', Utils::url('/subdir/path1/foobar.jpg', true));
$this->assertSame('/subdir/random/path1/path2', Utils::url('/random/path1/path2'));
$this->assertSame('/subdir/foobar.jpg', Utils::url('/foobar.jpg'));
$this->assertSame('/subdir/foobar.jpg', Utils::url('foobar.jpg'));
$this->assertSame('/subdir/foobar.jpg', Utils::url('/subdir/foobar.jpg'));
$this->assertSame('/subdir/path1/foobar.jpg', Utils::url('/path1/foobar.jpg'));
$this->assertSame('/subdir/path1/path2/foobar.jpg', Utils::url('/path1/path2/foobar.jpg'));
$this->assertSame('/subdir/random/path1/path2/foobar.jpg', Utils::url('/random/path1/path2/foobar.jpg'));
// Simple paths with domain
$this->assertSame('http://testing.dev/subdir/', Utils::url('/', true));
$this->assertSame('http://testing.dev/subdir/path1', Utils::url('/path1', true));
$this->assertSame('http://testing.dev/subdir/path1/path2', Utils::url('/path1/path2', true));
$this->assertSame('http://testing.dev/subdir/random/path1/path2', Utils::url('/random/path1/path2', true));
$this->assertSame('http://testing.dev/subdir/foobar.jpg', Utils::url('/foobar.jpg', true));
$this->assertSame('http://testing.dev/subdir/path1/foobar.jpg', Utils::url('/path1/foobar.jpg', true));
$this->assertSame('http://testing.dev/subdir/path1/path2/foobar.jpg', Utils::url('/path1/path2/foobar.jpg', true));
$this->assertSame('http://testing.dev/subdir/random/path1/path2/foobar.jpg', Utils::url('/random/path1/path2/foobar.jpg', true));
// Paths including the grav base.
$this->assertSame('/subdir/', Utils::url('/subdir'));
$this->assertSame('/subdir/path1', Utils::url('/subdir/path1'));
$this->assertSame('/subdir/path1/path2', Utils::url('/subdir/path1/path2'));
$this->assertSame('/subdir/foobar.jpg', Utils::url('/subdir/foobar.jpg'));
$this->assertSame('/subdir/path1/foobar.jpg', Utils::url('/subdir/path1/foobar.jpg'));
// Relative paths from Grav root with domain.
$this->assertSame('http://testing.dev/subdir/', Utils::url('/subdir', true));
$this->assertSame('http://testing.dev/subdir/path1', Utils::url('/subdir/path1', true));
$this->assertSame('http://testing.dev/subdir/path1/path2', Utils::url('/subdir/path1/path2', true));
$this->assertSame('http://testing.dev/subdir/foobar.jpg', Utils::url('/subdir/foobar.jpg', true));
$this->assertSame('http://testing.dev/subdir/path1/foobar.jpg', Utils::url('/subdir/path1/foobar.jpg', true));
// Relative paths from Grav root.
$this->assertSame('/subdir/subdir', Utils::url('subdir'));
$this->assertSame('/subdir/subdir/path1', Utils::url('subdir/path1'));
$this->assertSame('/subdir/subdir/path1/path2', Utils::url('subdir/path1/path2'));
$this->assertSame('/subdir/path1', Utils::url('path1'));
$this->assertSame('/subdir/path1/path2', Utils::url('path1/path2'));
$this->assertSame('/subdir/foobar.jpg', Utils::url('foobar.jpg'));
$this->assertSame('http://testing.dev/subdir/foobar.jpg', Utils::url('foobar.jpg', true));
// All Non-existing streams should be treated as external URI / protocol.
$this->assertSame('http://domain.com/path', Utils::url('http://domain.com/path'));
$this->assertSame('ftp://domain.com/path', Utils::url('ftp://domain.com/path'));
$this->assertSame('sftp://domain.com/path', Utils::url('sftp://domain.com/path'));
$this->assertSame('ssh://domain.com', Utils::url('ssh://domain.com'));
$this->assertSame('pop://domain.com', Utils::url('pop://domain.com'));
$this->assertSame('foo://bar/baz', Utils::url('foo://bar/baz'));
$this->assertSame('foo://bar/baz', Utils::url('foo://bar/baz', true));
// $this->assertSame('mailto:joe@domain.com', Utils::url('mailto:joe@domain.com', true)); // FIXME <-
}
public function testUrlWithStreams()