mirror of
https://github.com/mrclay/minify.git
synced 2025-08-09 07:36:56 +02:00
Issue 214 (remove traversals in prepended links)
This commit is contained in:
@@ -12,13 +12,6 @@
|
|||||||
*/
|
*/
|
||||||
class Minify_CSS_UriRewriter {
|
class Minify_CSS_UriRewriter {
|
||||||
|
|
||||||
/**
|
|
||||||
* Defines which class to call as part of callbacks, change this
|
|
||||||
* if you extend Minify_CSS_UriRewriter
|
|
||||||
* @var string
|
|
||||||
*/
|
|
||||||
protected static $className = 'Minify_CSS_UriRewriter';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* rewrite() and rewriteRelative() append debugging information here
|
* rewrite() and rewriteRelative() append debugging information here
|
||||||
* @var string
|
* @var string
|
||||||
@@ -26,7 +19,7 @@ class Minify_CSS_UriRewriter {
|
|||||||
public static $debugText = '';
|
public static $debugText = '';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Rewrite file relative URIs as root relative in CSS files
|
* In CSS content, rewrite file relative URIs as root relative
|
||||||
*
|
*
|
||||||
* @param string $css
|
* @param string $css
|
||||||
*
|
*
|
||||||
@@ -83,7 +76,7 @@ class Minify_CSS_UriRewriter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Prepend a path to relative URIs in CSS files
|
* In CSS content, prepend a path to relative URIs
|
||||||
*
|
*
|
||||||
* @param string $css
|
* @param string $css
|
||||||
*
|
*
|
||||||
@@ -107,73 +100,8 @@ class Minify_CSS_UriRewriter {
|
|||||||
return $css;
|
return $css;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @var string directory of this stylesheet
|
* Get a root relative URI from a file relative URI
|
||||||
*/
|
|
||||||
private static $_currentDir = '';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var string DOC_ROOT
|
|
||||||
*/
|
|
||||||
private static $_docRoot = '';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var array directory replacements to map symlink targets back to their
|
|
||||||
* source (within the document root) E.g. '/var/www/symlink' => '/var/realpath'
|
|
||||||
*/
|
|
||||||
private static $_symlinks = array();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @var string path to prepend
|
|
||||||
*/
|
|
||||||
private static $_prependPath = null;
|
|
||||||
|
|
||||||
private static function _trimUrls($css)
|
|
||||||
{
|
|
||||||
return preg_replace('/
|
|
||||||
url\\( # url(
|
|
||||||
\\s*
|
|
||||||
([^\\)]+?) # 1 = URI (assuming does not contain ")")
|
|
||||||
\\s*
|
|
||||||
\\) # )
|
|
||||||
/x', 'url($1)', $css);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static function _processUriCB($m)
|
|
||||||
{
|
|
||||||
// $m matched either '/@import\\s+([\'"])(.*?)[\'"]/' or '/url\\(\\s*([^\\)\\s]+)\\s*\\)/'
|
|
||||||
$isImport = ($m[0][0] === '@');
|
|
||||||
// determine URI and the quote character (if any)
|
|
||||||
if ($isImport) {
|
|
||||||
$quoteChar = $m[1];
|
|
||||||
$uri = $m[2];
|
|
||||||
} else {
|
|
||||||
// $m[1] is either quoted or not
|
|
||||||
$quoteChar = ($m[1][0] === "'" || $m[1][0] === '"')
|
|
||||||
? $m[1][0]
|
|
||||||
: '';
|
|
||||||
$uri = ($quoteChar === '')
|
|
||||||
? $m[1]
|
|
||||||
: substr($m[1], 1, strlen($m[1]) - 2);
|
|
||||||
}
|
|
||||||
// analyze URI
|
|
||||||
if ('/' !== $uri[0] // root-relative
|
|
||||||
&& false === strpos($uri, '//') // protocol (non-data)
|
|
||||||
&& 0 !== strpos($uri, 'data:') // data protocol
|
|
||||||
) {
|
|
||||||
// URI is file-relative: rewrite depending on options
|
|
||||||
$uri = (self::$_prependPath !== null)
|
|
||||||
? (self::$_prependPath . $uri)
|
|
||||||
: self::rewriteRelative($uri, self::$_currentDir, self::$_docRoot, self::$_symlinks);
|
|
||||||
}
|
|
||||||
return $isImport
|
|
||||||
? "@import {$quoteChar}{$uri}{$quoteChar}"
|
|
||||||
: "url({$quoteChar}{$uri}{$quoteChar})";
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Rewrite a file relative URI as root relative
|
|
||||||
*
|
*
|
||||||
* <code>
|
* <code>
|
||||||
* Minify_CSS_UriRewriter::rewriteRelative(
|
* Minify_CSS_UriRewriter::rewriteRelative(
|
||||||
@@ -239,18 +167,35 @@ class Minify_CSS_UriRewriter {
|
|||||||
|
|
||||||
$uri = strtr($path, '/\\', '//');
|
$uri = strtr($path, '/\\', '//');
|
||||||
|
|
||||||
// remove /./ and /../ where possible
|
$uri = self::removeDots($uri);
|
||||||
$uri = str_replace('/./', '/', $uri);
|
|
||||||
// inspired by patch from Oleg Cherniy
|
|
||||||
do {
|
|
||||||
$uri = preg_replace('@/[^/]+/\\.\\./@', '/', $uri, 1, $changed);
|
|
||||||
} while ($changed);
|
|
||||||
|
|
||||||
self::$debugText .= "traversals removed : {$uri}\n\n";
|
self::$debugText .= "traversals removed : {$uri}\n\n";
|
||||||
|
|
||||||
return $uri;
|
return $uri;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove instances of "./" and "../" where possible from a root-relative URI
|
||||||
|
* @param string $uri
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public static function removeDots($uri)
|
||||||
|
{
|
||||||
|
$uri = str_replace('/./', '/', $uri);
|
||||||
|
// inspired by patch from Oleg Cherniy
|
||||||
|
do {
|
||||||
|
$uri = preg_replace('@/[^/]+/\\.\\./@', '/', $uri, 1, $changed);
|
||||||
|
} while ($changed);
|
||||||
|
return $uri;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Defines which class to call as part of callbacks, change this
|
||||||
|
* if you extend Minify_CSS_UriRewriter
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected static $className = 'Minify_CSS_UriRewriter';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get realpath with any trailing slash removed. If realpath() fails,
|
* Get realpath with any trailing slash removed. If realpath() fails,
|
||||||
* just remove the trailing slash.
|
* just remove the trailing slash.
|
||||||
@@ -267,4 +212,79 @@ class Minify_CSS_UriRewriter {
|
|||||||
}
|
}
|
||||||
return rtrim($path, '/\\');
|
return rtrim($path, '/\\');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string directory of this stylesheet
|
||||||
|
*/
|
||||||
|
private static $_currentDir = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string DOC_ROOT
|
||||||
|
*/
|
||||||
|
private static $_docRoot = '';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var array directory replacements to map symlink targets back to their
|
||||||
|
* source (within the document root) E.g. '/var/www/symlink' => '/var/realpath'
|
||||||
|
*/
|
||||||
|
private static $_symlinks = array();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string path to prepend
|
||||||
|
*/
|
||||||
|
private static $_prependPath = null;
|
||||||
|
|
||||||
|
private static function _trimUrls($css)
|
||||||
|
{
|
||||||
|
return preg_replace('/
|
||||||
|
url\\( # url(
|
||||||
|
\\s*
|
||||||
|
([^\\)]+?) # 1 = URI (assuming does not contain ")")
|
||||||
|
\\s*
|
||||||
|
\\) # )
|
||||||
|
/x', 'url($1)', $css);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function _processUriCB($m)
|
||||||
|
{
|
||||||
|
// $m matched either '/@import\\s+([\'"])(.*?)[\'"]/' or '/url\\(\\s*([^\\)\\s]+)\\s*\\)/'
|
||||||
|
$isImport = ($m[0][0] === '@');
|
||||||
|
// determine URI and the quote character (if any)
|
||||||
|
if ($isImport) {
|
||||||
|
$quoteChar = $m[1];
|
||||||
|
$uri = $m[2];
|
||||||
|
} else {
|
||||||
|
// $m[1] is either quoted or not
|
||||||
|
$quoteChar = ($m[1][0] === "'" || $m[1][0] === '"')
|
||||||
|
? $m[1][0]
|
||||||
|
: '';
|
||||||
|
$uri = ($quoteChar === '')
|
||||||
|
? $m[1]
|
||||||
|
: substr($m[1], 1, strlen($m[1]) - 2);
|
||||||
|
}
|
||||||
|
// analyze URI
|
||||||
|
if ('/' !== $uri[0] // root-relative
|
||||||
|
&& false === strpos($uri, '//') // protocol (non-data)
|
||||||
|
&& 0 !== strpos($uri, 'data:') // data protocol
|
||||||
|
) {
|
||||||
|
// URI is file-relative: rewrite depending on options
|
||||||
|
if (self::$_prependPath === null) {
|
||||||
|
$uri = self::rewriteRelative($uri, self::$_currentDir, self::$_docRoot, self::$_symlinks);
|
||||||
|
} else {
|
||||||
|
$uri = self::$_prependPath . $uri;
|
||||||
|
if ($uri[0] === '/') {
|
||||||
|
$root = '';
|
||||||
|
$rootRelative = $uri;
|
||||||
|
$uri = $root . self::removeDots($rootRelative);
|
||||||
|
} elseif (preg_match('@^((https?\:)?//([^/]+))/@', $uri, $m) && (false !== strpos($m[3], '.'))) {
|
||||||
|
$root = $m[1];
|
||||||
|
$rootRelative = substr($uri, strlen($root));
|
||||||
|
$uri = $root . self::removeDots($rootRelative);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $isImport
|
||||||
|
? "@import {$quoteChar}{$uri}{$quoteChar}"
|
||||||
|
: "url({$quoteChar}{$uri}{$quoteChar})";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
14
min_unit_tests/_test_files/css_uriRewriter/exp_prepend.css
Normal file
14
min_unit_tests/_test_files/css_uriRewriter/exp_prepend.css
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
@import "http://cnd.com/A/B/foo.css";
|
||||||
|
@import 'http://cnd.com/A/B/bar/foo.css' print;
|
||||||
|
@import 'http://cnd.com/A/bar/foo.css' print;
|
||||||
|
@import 'http://cnd.com/foo.css' print;
|
||||||
|
@import '/css/foo.css'; /* abs, should not alter */
|
||||||
|
@import 'http://foo.com/css/foo.css'; /* abs, should not alter */
|
||||||
|
@import url(http://cnd.com/A/foo.css) tv, projection;
|
||||||
|
@import url("/css/foo.css"); /* abs, should not alter */
|
||||||
|
@import url(/css2/foo.css); /* abs, should not alter */
|
||||||
|
@import url(data:image/gif;base64,AAAA); /* data, should not alter */
|
||||||
|
foo {background:url('http://cnd.com/A/B/bar/foo.png')}
|
||||||
|
foo {background:url('http://foo.com/css/foo.css');} /* abs, should not alter */
|
||||||
|
foo {background:url("//foo.com/css/foo.css");} /* protocol relative, should not alter */
|
||||||
|
foo {background:url(data:image/gif;base64,AAAA);} /* data, should not alter */
|
14
min_unit_tests/_test_files/css_uriRewriter/exp_prepend2.css
Normal file
14
min_unit_tests/_test_files/css_uriRewriter/exp_prepend2.css
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
@import "//cnd.com/A/B/foo.css";
|
||||||
|
@import '//cnd.com/A/B/bar/foo.css' print;
|
||||||
|
@import '//cnd.com/A/bar/foo.css' print;
|
||||||
|
@import '//cnd.com/foo.css' print;
|
||||||
|
@import '/css/foo.css'; /* abs, should not alter */
|
||||||
|
@import 'http://foo.com/css/foo.css'; /* abs, should not alter */
|
||||||
|
@import url(//cnd.com/A/foo.css) tv, projection;
|
||||||
|
@import url("/css/foo.css"); /* abs, should not alter */
|
||||||
|
@import url(/css2/foo.css); /* abs, should not alter */
|
||||||
|
@import url(data:image/gif;base64,AAAA); /* data, should not alter */
|
||||||
|
foo {background:url('//cnd.com/A/B/bar/foo.png')}
|
||||||
|
foo {background:url('http://foo.com/css/foo.css');} /* abs, should not alter */
|
||||||
|
foo {background:url("//foo.com/css/foo.css");} /* protocol relative, should not alter */
|
||||||
|
foo {background:url(data:image/gif;base64,AAAA);} /* data, should not alter */
|
@@ -17,7 +17,7 @@ function test_Minify_CSS_UriRewriter()
|
|||||||
,$thisDir // use DOCUMENT_ROOT = '/full/path/to/min_unit_tests'
|
,$thisDir // use DOCUMENT_ROOT = '/full/path/to/min_unit_tests'
|
||||||
);
|
);
|
||||||
|
|
||||||
$passed = assertTrue($expected === $actual, 'Minify_CSS_UriRewriter');
|
$passed = assertTrue($expected === $actual, 'Minify_CSS_UriRewriter : rewrite');
|
||||||
if (__FILE__ === realpath($_SERVER['SCRIPT_FILENAME'])) {
|
if (__FILE__ === realpath($_SERVER['SCRIPT_FILENAME'])) {
|
||||||
echo "\n---Input:\n\n{$in}\n";
|
echo "\n---Input:\n\n{$in}\n";
|
||||||
echo "\n---Output: " .countBytes($actual). " bytes\n\n{$actual}\n\n";
|
echo "\n---Output: " .countBytes($actual). " bytes\n\n{$actual}\n\n";
|
||||||
@@ -30,6 +30,45 @@ function test_Minify_CSS_UriRewriter()
|
|||||||
, Minify_CSS_UriRewriter::$debugText;
|
, Minify_CSS_UriRewriter::$debugText;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Minify_CSS_UriRewriter::$debugText = '';
|
||||||
|
$in = file_get_contents($thisDir . '/_test_files/css_uriRewriter/in.css');
|
||||||
|
$expected = file_get_contents($thisDir . '/_test_files/css_uriRewriter/exp_prepend.css');
|
||||||
|
$actual = Minify_CSS_UriRewriter::prepend($in, 'http://cnd.com/A/B/');
|
||||||
|
|
||||||
|
$passed = assertTrue($expected === $actual, 'Minify_CSS_UriRewriter : prepend1');
|
||||||
|
if (__FILE__ === realpath($_SERVER['SCRIPT_FILENAME'])) {
|
||||||
|
echo "\n---Input:\n\n{$in}\n";
|
||||||
|
echo "\n---Output: " .countBytes($actual). " bytes\n\n{$actual}\n\n";
|
||||||
|
if (!$passed) {
|
||||||
|
echo "---Expected: " .countBytes($expected). " bytes\n\n{$expected}\n\n\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
// show debugging only when test run directly
|
||||||
|
echo "--- Minify_CSS_UriRewriter::\$debugText\n\n"
|
||||||
|
, Minify_CSS_UriRewriter::$debugText;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Minify_CSS_UriRewriter::$debugText = '';
|
||||||
|
$in = file_get_contents($thisDir . '/_test_files/css_uriRewriter/in.css');
|
||||||
|
$expected = file_get_contents($thisDir . '/_test_files/css_uriRewriter/exp_prepend2.css');
|
||||||
|
$actual = Minify_CSS_UriRewriter::prepend($in, '//cnd.com/A/B/');
|
||||||
|
|
||||||
|
$passed = assertTrue($expected === $actual, 'Minify_CSS_UriRewriter : prepend2');
|
||||||
|
if (__FILE__ === realpath($_SERVER['SCRIPT_FILENAME'])) {
|
||||||
|
echo "\n---Input:\n\n{$in}\n";
|
||||||
|
echo "\n---Output: " .countBytes($actual). " bytes\n\n{$actual}\n\n";
|
||||||
|
if (!$passed) {
|
||||||
|
echo "---Expected: " .countBytes($expected). " bytes\n\n{$expected}\n\n\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
// show debugging only when test run directly
|
||||||
|
echo "--- Minify_CSS_UriRewriter::\$debugText\n\n"
|
||||||
|
, Minify_CSS_UriRewriter::$debugText;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
Minify_CSS_UriRewriter::$debugText = '';
|
Minify_CSS_UriRewriter::$debugText = '';
|
||||||
$in = '../../../../assets/skins/sam/sprite.png';
|
$in = '../../../../assets/skins/sam/sprite.png';
|
||||||
$exp = '/yui/assets/skins/sam/sprite.png';
|
$exp = '/yui/assets/skins/sam/sprite.png';
|
||||||
|
Reference in New Issue
Block a user