1
0
mirror of https://github.com/mrclay/minify.git synced 2025-08-09 15:46:34 +02:00

+ URI rewriter in CSS min, fix for protocol-relative URIs, + partial minify.php compatibility

This commit is contained in:
Steve Clay
2008-03-01 20:23:50 +00:00
parent 381bd8856d
commit d86d79b7d2
8 changed files with 169 additions and 52 deletions

View File

@@ -55,34 +55,6 @@ class Minify {
: $path;
}
/**
* Create a controller instance and handle the request
*
* @param string type This should be the filename of the controller without
* extension. e.g. 'Group'
*
* @param array $ctrlOptions options for the controller's constructor
*
* @param array $minOptions options passed on to Minify
*
* @return mixed false on failure or array of content and headers sent
*/
public static function serveold($type, $ctrlOptions = array(), $minOptions = array()) {
$class = 'Minify_Controller_' . $type;
if (! class_exists($class, false)) {
require_once "Minify/Controller/{$type}.php";
}
$ctrl = new $class($ctrlOptions, $minOptions);
$ret = self::handleRequest($ctrl);
if (false === $ret) {
if (! isset($ctrl->minOptions['quiet']) || ! $ctrl->minOptions['quiet']) {
header("HTTP/1.0 400 Bad Request");
exit('400 Bad Request');
}
}
return $ret;
}
/**
* Serve a request for a minified file.
*
@@ -219,7 +191,7 @@ class Minify {
// add headers to those from ConditionalGet
//$headers['Content-Length'] = strlen($content);
$headers['Content-Type'] = (null !== self::$_options['contentTypeCharset'])
? self::$_options['contentType'] . ';charset=' . self::$_options['contentTypeCharset']
? self::$_options['contentType'] . '; charset=' . self::$_options['contentTypeCharset']
: self::$_options['contentType'];
if (self::$_options['encodeMethod'] !== '') {
$headers['Content-Encoding'] = $contentEncoding;

View File

@@ -64,15 +64,21 @@ class Minify_CSS {
$css = preg_replace('/#([a-f\\d])\\1([a-f\\d])\\2([a-f\\d])\\3([\\s;\\}])/i'
, '#$1$2$3$4', $css);
$rewrite = false;
if (isset($options['prependRelativePath'])) {
self::$_tempPrepend = $options['prependRelativePath'];
$rewrite = true;
} elseif (isset($options['currentPath'])) {
self::$_tempCurrentPath = $options['currentPath'];
$rewrite = true;
}
if ($rewrite) {
$css = preg_replace_callback('/@import ([\'"])(.*?)[\'"]\\s*;/'
,array('Minify_CSS', '_urlCB'), $css);
$css = preg_replace_callback('/url\\(([^\\)]+)\\)/'
,array('Minify_CSS', '_urlCB'), $css);
}
self::$_tempPrepend = self::$_tempCurrentPath = '';
return trim($css);
}
@@ -88,6 +94,11 @@ class Minify_CSS {
*/
private static $_tempPrepend = '';
/**
* @var string path of this stylesheet for rewriting purposes
*/
private static $_tempCurrentPath = '';
/**
* Process what looks like a comment and return a replacement
*
@@ -157,17 +168,24 @@ class Minify_CSS {
? $m[1]
: substr($m[1], 1, strlen($m[1]) - 2);
}
if ('/' === $url[0]) {
if ('/' === $url[1]) {
// protocol relative URI!
$url = '//' . self::$_tempPrepend . substr($url, 2);
}
} else {
if ('/' !== $url[0]) {
if (strpos($url, '//') > 0) {
// probably starts with protocol, do not alter
} else {
// relative URI
$url = self::$_tempPrepend . $url;
// relative URI, rewrite!
if (self::$_tempPrepend) {
$url = self::$_tempPrepend . $url;
} else {
// rewrite absolute url from scratch!
// prepend path with current dir separator (OS-independent)
$path = self::$_tempCurrentPath
. DIRECTORY_SEPARATOR . strtr($url, '/', DIRECTORY_SEPARATOR);
// strip doc root
$path = substr($path, strlen($_SERVER['DOCUMENT_ROOT']));
// fix to absolute URL
$url = strtr($path, DIRECTORY_SEPARATOR, '/');
$url = str_replace('/./', '/', $url);
}
}
}
if ($isImport) {

View File

@@ -88,6 +88,32 @@ abstract class Minify_Controller_Base {
}
}
/**
* Is a user-given file within document root, existing,
* and having an extension js/css/html/txt
*
* This is a convenience function for controllers that have to accept
* user-given paths
*
* @param string $file full file path (already processed by realpath())
* @param string $docRoot root where files are safe to serve
* @return bool file is safe
*/
public static function _fileIsSafe($file, $docRoot)
{
if (strpos($file, $docRoot) !== 0 || ! file_exists($file)) {
return false;
}
$base = basename($file);
if ($base[0] === '.') {
return false;
}
list($revExt) = explode('.', strrev($base));
return in_array(strrev($revExt), array('js', 'css', 'html', 'txt'));
}
/*public static function _haveSameExt
/**
* @var array instances of Minify_Source, which provide content and
* any individual minification needs.
@@ -125,7 +151,8 @@ abstract class Minify_Controller_Base {
*
* @return array options for Minify
*/
public final function analyzeSources($options = array()) {
public final function analyzeSources($options = array())
{
if ($this->sources) {
if (! isset($options['contentType'])) {
$options['contentType'] = Minify_Source::getContentType($this->sources);

View File

@@ -113,9 +113,9 @@ class Minify_Source {
public static function getContentType($sources)
{
$exts = array(
'css' => 'text/css'
,'js' => 'application/x-javascript'
,'html' => 'text/html'
'css' => Minify::TYPE_CSS
,'js' => Minify::TYPE_JS
,'html' => Minify::TYPE_HTML
);
foreach ($sources as $source) {
if (null !== $source->_filepath) {

View File

@@ -1,9 +1,10 @@
@import "foo.css";
@import 'bar/foo.css';
@import '/css/foo.css';
@import 'http://foo.com/css/foo.css';
@import url(./foo.css);
@import url("/css/foo.css");
@import url(/css2/foo.css);
@import '/css/foo.css'; /* abs, should not alter */
@import 'http://foo.com/css/foo.css'; /* abs, should not alter */
@import url(../foo.css);
@import url("/css/foo.css"); /* abs, should not alter */
@import url(/css2/foo.css); /* abs, should not alter */
foo {background:url('bar/foo.png')}
foo {background:url('http://foo.com/css/foo.css');}
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 */

View File

@@ -1 +1 @@
@import "../foo.css";@import '../bar/foo.css';@import '/css/foo.css';@import 'http://foo.com/css/foo.css';@import url(.././foo.css);@import url("/css/foo.css");@import url(/css2/foo.css);foo{background:url('../bar/foo.png')}foo{background:url('http://foo.com/css/foo.css')}
@import "../foo.css";@import '../bar/foo.css';@import '/css/foo.css';@import 'http://foo.com/css/foo.css';@import url(../../foo.css);@import url("/css/foo.css");@import url(/css2/foo.css);foo{background:url('../bar/foo.png')}foo{background:url('http://foo.com/css/foo.css')}foo{background:url("//foo.com/css/foo.css")}

View File

@@ -25,8 +25,8 @@ foreach ($list as $item) {
if ($minExpected !== $minOutput) {
echo "\n---Source\n\n{$src}";
echo "\n---Expected\n\n{$minExpected}";
echo "\n---Output\n\n{$minOutput}\n\n\n\n";
echo "\n\n---Expected\n\n{$minExpected}";
echo "\n\n---Output\n\n{$minOutput}\n\n\n\n";
}
}

99
web/version1/minify.php Normal file
View File

@@ -0,0 +1,99 @@
<?php
//define('MINIFY_BASE_DIR', realpath($_SERVER['DOCUMENT_ROOT'] . '/_3rd_party'));
require '../config.php'; // just to set include_path
require 'Minify.php';
require 'Minify/Controller/Base.php';
if (!defined('MINIFY_BASE_DIR')) {
// files cannot be served above this
define('MINIFY_BASE_DIR', realpath($_SERVER['DOCUMENT_ROOT']));
}
if (!defined('MINIFY_CACHE_DIR')) {
define('MINIFY_CACHE_DIR', sys_get_temp_dir());
}
if (!defined('MINIFY_ENCODING')) {
define('MINIFY_ENCODING', 'utf-8');
}
if (!defined('MINIFY_MAX_FILES')) {
define('MINIFY_MAX_FILES', 16);
}
if (!defined('MINIFY_REWRITE_CSS_URLS')) {
define('MINIFY_REWRITE_CSS_URLS', true);
}
if (!defined('MINIFY_USE_CACHE')) {
define('MINIFY_USE_CACHE', true);
}
class V1Controller extends Minify_Controller_Base {
// setup $this->sources and return $options
public function setupSources($options) {
$options['badRequestHeader'] = 'HTTP/1.0 404 Not Found';
$options['contentTypeCharset'] = MINIFY_ENCODING;
// The following restrictions are to limit the URLs that minify will
// respond to. Ideally there should be only one way to reference a file.
if (! isset($_GET['files'])
// verify at least one file, files are single comma separated,
// and are all same extension
|| ! preg_match('/^[^,]+\\.(css|js)(,[^,]+\\.\\1)*$/', $_GET['files'], $m)
// no "//" (makes URL rewriting easier)
|| strpos($_GET['files'], '//') !== false
// no "\"
|| strpos($_GET['files'], '\\') !== false
// no "./"
|| preg_match('/(?:^|[^\\.])\\.\\//', $_GET['files'])
) {
return $options;
}
$extension = $m[1];
$files = explode(',', $_GET['files']);
if (count($files) > MINIFY_MAX_FILES) {
return $options;
}
// strings for prepending to relative/absolute paths
$prependRelPaths = dirname($_SERVER['SCRIPT_FILENAME'])
. DIRECTORY_SEPARATOR;
$prependAbsPaths = $_SERVER['DOCUMENT_ROOT'];
$sources = array();
$goodFiles = array();
$hasBadSource = false;
foreach ($files as $file) {
// prepend appropriate string for abs/rel paths
$file = ($file[0] === '/' ? $prependAbsPaths : $prependRelPaths) . $file;
// make sure a real file!
$file = realpath($file);
// don't allow unsafe or duplicate files
if (parent::_fileIsSafe($file, MINIFY_BASE_DIR)
&& !in_array($file, $goodFiles))
{
$goodFiles[] = $file;
$srcOptions = array(
'filepath' => $file
);
if ('css' === $extension && MINIFY_REWRITE_CSS_URLS) {
$srcOptions['minifyOptions']['currentPath'] = dirname($file);
}
$this->sources[] = new Minify_Source($srcOptions);
} else {
$hasBadSource = true;
break;
}
}
if ($hasBadSource) {
$this->sources = array();
}
return $options;
}
}
$v1 = new V1Controller();
if (MINIFY_USE_CACHE) {
Minify::useServerCache(MINIFY_CACHE_DIR);
}
Minify::serve($v1);