1
0
mirror of https://github.com/mrclay/minify.git synced 2025-07-31 19:30:29 +02:00

Prep for 2.1.3

HTTP_ConditionalGet: no more "must-revalidate"
HTTP_Encoder: prefers gzip over deflate
Minify: no more deflate encoding
File cache works w/o setting $min_cachePath
Minify_Source: + public $contentType
This commit is contained in:
Steve Clay
2009-06-30 19:44:55 +00:00
parent bfb4a18b85
commit 03673e0928
13 changed files with 104 additions and 64 deletions

View File

@@ -1,5 +1,17 @@
Minify Release History Minify Release History
Version 2.1.3
* HTTP fixes
* ETag generation now valid (different when gzipped)
* Vary header always sent when Accept-Encoding is sniffed
* Cache-Control no longer has "must-revalidate" due to webkit bug
See: http://mrclay.org/index.php/2009/02/24/safari-4-beta-cache-controlmust-revalidate-bug/
* Dropped deflate encoding. Browser and proxy support could be buggy.
See: http://stackoverflow.com/questions/883841/
* File cache now works w/o setting $min_cachePath
* Allow setting contentType in Minify_Source objects
* No more 5.3 deprecation warnings: split() removed
Version 2.1.2 Version 2.1.2
* Javascript fixes * Javascript fixes
* Debug mode no longer confused by "*/*" in strings/RegExps (jQuery) * Debug mode no longer confused by "*/*" in strings/RegExps (jQuery)

View File

@@ -1,5 +1,9 @@
<?php <?php
if (phpversion() < 5) {
exit('Minify requires PHP5 or greater.');
}
// check for auto-encoding // check for auto-encoding
$encodeOutput = (function_exists('gzdeflate') $encodeOutput = (function_exists('gzdeflate')
&& !ini_get('zlib.output_compression')); && !ini_get('zlib.output_compression'));

View File

@@ -19,7 +19,7 @@ require 'Minify.php';
Minify::$uploaderHoursBehind = $min_uploaderHoursBehind; Minify::$uploaderHoursBehind = $min_uploaderHoursBehind;
Minify::setCache( Minify::setCache(
isset($min_cachePath) ? $min_cachePath : null isset($min_cachePath) ? $min_cachePath : ''
,$min_cacheFileLocking ,$min_cacheFileLocking
); );

View File

@@ -150,7 +150,7 @@ class HTTP_ConditionalGet {
} elseif (isset($spec['contentHash'])) { // Use the hash as the ETag } elseif (isset($spec['contentHash'])) { // Use the hash as the ETag
$this->_setEtag($spec['contentHash'] . $etagAppend, $scope); $this->_setEtag($spec['contentHash'] . $etagAppend, $scope);
} }
$this->_headers['Cache-Control'] = "max-age={$maxAge}, {$scope}, must-revalidate"; $this->_headers['Cache-Control'] = "max-age={$maxAge}, {$scope}";
// invalidate cache if disabled, otherwise check // invalidate cache if disabled, otherwise check
$this->cacheIsValid = (isset($spec['invalidate']) && $spec['invalidate']) $this->cacheIsValid = (isset($spec['invalidate']) && $spec['invalidate'])
? false ? false
@@ -166,7 +166,7 @@ class HTTP_ConditionalGet {
* Otherwise something like: * Otherwise something like:
* <code> * <code>
* array( * array(
* 'Cache-Control' => 'max-age=0, public, must-revalidate' * 'Cache-Control' => 'max-age=0, public'
* ,'ETag' => '"foobar"' * ,'ETag' => '"foobar"'
* ) * )
* </code> * </code>
@@ -306,7 +306,7 @@ class HTTP_ConditionalGet {
$clientEtagList = get_magic_quotes_gpc() $clientEtagList = get_magic_quotes_gpc()
? stripslashes($_SERVER['HTTP_IF_NONE_MATCH']) ? stripslashes($_SERVER['HTTP_IF_NONE_MATCH'])
: $_SERVER['HTTP_IF_NONE_MATCH']; : $_SERVER['HTTP_IF_NONE_MATCH'];
$clientEtags = split(',', $clientEtagList); $clientEtags = explode(',', $clientEtagList);
$compareTo = $this->normalizeEtag($this->_etag); $compareTo = $this->normalizeEtag($this->_etag);
foreach ($clientEtags as $clientEtag) { foreach ($clientEtags as $clientEtag) {

View File

@@ -178,16 +178,19 @@ class HTTP_Encoder {
* this will return ('', ''), the "identity" encoding. * this will return ('', ''), the "identity" encoding.
* *
* A syntax-aware scan is done of the Accept-Encoding, so the method must * A syntax-aware scan is done of the Accept-Encoding, so the method must
* be non 0. The methods are favored in order of deflate, gzip, then * be non 0. The methods are favored in order of gzip, deflate, then
* compress. deflate is always smallest and generally faster! * compress. Deflate is always smallest and generally faster, but is
* rarely sent by servers, so client support could be buggier.
* *
* @param bool $allowCompress allow the older compress encoding * @param bool $allowCompress allow the older compress encoding
*
* @param bool $allowDeflate allow the more recent deflate encoding
* *
* @return array two values, 1st is the actual encoding method, 2nd is the * @return array two values, 1st is the actual encoding method, 2nd is the
* alias of that method to use in the Content-Encoding header (some browsers * alias of that method to use in the Content-Encoding header (some browsers
* call gzip "x-gzip" etc.) * call gzip "x-gzip" etc.)
*/ */
public static function getAcceptedEncoding($allowCompress = true) public static function getAcceptedEncoding($allowCompress = true, $allowDeflate = true)
{ {
// @link http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html // @link http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
@@ -197,23 +200,31 @@ class HTTP_Encoder {
return array('', ''); return array('', '');
} }
$ae = $_SERVER['HTTP_ACCEPT_ENCODING']; $ae = $_SERVER['HTTP_ACCEPT_ENCODING'];
$aeRev = strrev($ae); // gzip checks (quick)
// Fast tests for common AEs. If these don't pass we have to do if (0 === strpos($ae, 'gzip,') // most browsers
// slow regex parsing || 0 === strpos($ae, 'deflate, gzip,') // opera
if (0 === strpos($aeRev, 'etalfed ,') // ie, webkit ) {
|| 0 === strpos($aeRev, 'etalfed,') // gecko return array('gzip', 'gzip');
|| 0 === strpos($ae, 'deflate,') // opera 9.5b
// slow parsing
|| preg_match(
'@(?:^|,)\\s*deflate\\s*(?:$|,|;\\s*q=(?:0\\.|1))@', $ae)) {
return array('deflate', 'deflate');
} }
// gzip checks (slow)
if (preg_match( if (preg_match(
'@(?:^|,)\\s*((?:x-)?gzip)\\s*(?:$|,|;\\s*q=(?:0\\.|1))@' '@(?:^|,)\\s*((?:x-)?gzip)\\s*(?:$|,|;\\s*q=(?:0\\.|1))@'
,$ae ,$ae
,$m)) { ,$m)) {
return array('gzip', $m[1]); return array('gzip', $m[1]);
} }
if ($allowDeflate) {
// deflate checks
$aeRev = strrev($ae);
if (0 === strpos($aeRev, 'etalfed ,') // ie, webkit
|| 0 === strpos($aeRev, 'etalfed,') // gecko
|| 0 === strpos($ae, 'deflate,') // opera
// slow parsing
|| preg_match(
'@(?:^|,)\\s*deflate\\s*(?:$|,|;\\s*q=(?:0\\.|1))@', $ae)) {
return array('deflate', 'deflate');
}
}
if ($allowCompress && preg_match( if ($allowCompress && preg_match(
'@(?:^|,)\\s*((?:x-)?compress)\\s*(?:$|,|;\\s*q=(?:0\\.|1))@' '@(?:^|,)\\s*((?:x-)?compress)\\s*(?:$|,|;\\s*q=(?:0\\.|1))@'
,$ae ,$ae

View File

@@ -29,7 +29,7 @@ require_once 'Minify/Source.php';
*/ */
class Minify { class Minify {
const VERSION = '2.2.0'; const VERSION = '2.1.3';
const TYPE_CSS = 'text/css'; const TYPE_CSS = 'text/css';
const TYPE_HTML = 'text/html'; const TYPE_HTML = 'text/html';
// there is some debate over the ideal JS Content-Type, but this is the // there is some debate over the ideal JS Content-Type, but this is the
@@ -100,7 +100,7 @@ class Minify {
* *
* 'encodeMethod' : generally you should let this be determined by * 'encodeMethod' : generally you should let this be determined by
* HTTP_Encoder (leave null), but you can force a particular encoding * HTTP_Encoder (leave null), but you can force a particular encoding
* to be returned, by setting this to 'gzip', 'deflate', or '' (no encoding) * to be returned, by setting this to 'gzip' or '' (no encoding)
* *
* 'encodeLevel' : level of encoding compression (0 to 9, default 9) * 'encodeLevel' : level of encoding compression (0 to 9, default 9)
* *
@@ -210,8 +210,8 @@ class Minify {
require_once 'HTTP/Encoder.php'; require_once 'HTTP/Encoder.php';
// depending on what the client accepts, $contentEncoding may be // depending on what the client accepts, $contentEncoding may be
// 'x-gzip' while our internal encodeMethod is 'gzip'. Calling // 'x-gzip' while our internal encodeMethod is 'gzip'. Calling
// getAcceptedEncoding() with false leaves out compress as an option. // getAcceptedEncoding(false, false) leaves out compress and deflate as options.
list(self::$_options['encodeMethod'], $contentEncoding) = HTTP_Encoder::getAcceptedEncoding(false); list(self::$_options['encodeMethod'], $contentEncoding) = HTTP_Encoder::getAcceptedEncoding(false, false);
} }
} else { } else {
self::$_options['encodeMethod'] = ''; // identity (no encoding) self::$_options['encodeMethod'] = ''; // identity (no encoding)
@@ -267,12 +267,9 @@ class Minify {
// output the content, as they do not require ever loading the file into // output the content, as they do not require ever loading the file into
// memory. // memory.
$cacheId = 'minify_' . self::_getCacheId(); $cacheId = 'minify_' . self::_getCacheId();
$encodingExtension = self::$_options['encodeMethod'] $fullCacheId = (self::$_options['encodeMethod'])
? ('deflate' === self::$_options['encodeMethod'] ? $cacheId . '.gz'
? '.zd' : $cacheId;
: '.zg')
: '';
$fullCacheId = $cacheId . $encodingExtension;
// check cache for valid entry // check cache for valid entry
$cacheIsReady = self::$_cache->isValid($fullCacheId, self::$_options['lastModifiedTime']); $cacheIsReady = self::$_cache->isValid($fullCacheId, self::$_options['lastModifiedTime']);
if ($cacheIsReady) { if ($cacheIsReady) {
@@ -281,9 +278,8 @@ class Minify {
// generate & cache content // generate & cache content
$content = self::_combineMinify(); $content = self::_combineMinify();
self::$_cache->store($cacheId, $content); self::$_cache->store($cacheId, $content);
if (function_exists('gzdeflate')) { if (function_exists('gzencode')) {
self::$_cache->store($cacheId . '.zd', gzdeflate($content, self::$_options['encodeLevel'])); self::$_cache->store($cacheId . '.gz', gzencode($content, self::$_options['encodeLevel']));
self::$_cache->store($cacheId . '.zg', gzencode($content, self::$_options['encodeLevel']));
} }
} }
} else { } else {
@@ -293,9 +289,7 @@ class Minify {
} }
if (! $cacheIsReady && self::$_options['encodeMethod']) { if (! $cacheIsReady && self::$_options['encodeMethod']) {
// still need to encode // still need to encode
$content = ('deflate' === self::$_options['encodeMethod']) $content = gzencode($content, self::$_options['encodeLevel']);
? gzdeflate($content, self::$_options['encodeLevel'])
: gzencode($content, self::$_options['encodeLevel']);
} }
// add headers // add headers
@@ -374,11 +368,11 @@ class Minify {
if (isset($_SERVER['SERVER_SOFTWARE']) if (isset($_SERVER['SERVER_SOFTWARE'])
&& 0 === strpos($_SERVER['SERVER_SOFTWARE'], 'Microsoft-IIS/') && 0 === strpos($_SERVER['SERVER_SOFTWARE'], 'Microsoft-IIS/')
) { ) {
$_SERVER['DOCUMENT_ROOT'] = substr( $_SERVER['DOCUMENT_ROOT'] = rtrim(substr(
$_SERVER['PATH_TRANSLATED'] $_SERVER['PATH_TRANSLATED']
,0 ,0
,strlen($_SERVER['PATH_TRANSLATED']) - strlen($_SERVER['SCRIPT_NAME']) ,strlen($_SERVER['PATH_TRANSLATED']) - strlen($_SERVER['SCRIPT_NAME'])
); ), '\\');
if ($unsetPathInfo) { if ($unsetPathInfo) {
unset($_SERVER['PATH_INFO']); unset($_SERVER['PATH_INFO']);
} }

View File

@@ -265,9 +265,6 @@ class Minify_CSS_UriRewriter {
if ($realPath !== false) { if ($realPath !== false) {
$path = $realPath; $path = $realPath;
} }
$last = $path[strlen($path) - 1]; return rtrim($path, '/\\');
return ($last === '/' || $last === '\\')
? substr($path, 0, strlen($path) - 1)
: $path;
} }
} }

View File

@@ -110,6 +110,16 @@ class Minify_Cache_File {
} }
} }
/**
* Fetch the cache path used
*
* @return string
*/
public function getPath()
{
return $this->_path;
}
private $_path = null; private $_path = null;
private $_locking = null; private $_locking = null;
} }

View File

@@ -35,6 +35,11 @@ class Minify_Source {
*/ */
public $filepath = null; public $filepath = null;
/**
* @var string HTTP Content Type (Minify requires one of the constants Minify::TYPE_*)
*/
public $contentType = null;
/** /**
* Create a Minify_Source * Create a Minify_Source
* *
@@ -54,6 +59,17 @@ class Minify_Source {
if (0 === strpos($spec['filepath'], '//')) { if (0 === strpos($spec['filepath'], '//')) {
$spec['filepath'] = $_SERVER['DOCUMENT_ROOT'] . substr($spec['filepath'], 1); $spec['filepath'] = $_SERVER['DOCUMENT_ROOT'] . substr($spec['filepath'], 1);
} }
$segments = explode('.', $spec['filepath']);
$ext = strtolower(array_pop($segments));
switch ($ext) {
case 'js' : $this->contentType = 'application/x-javascript';
break;
case 'css' : $this->contentType = 'text/css';
break;
case 'htm' : // fallthrough
case 'html' : $this->contentType = 'text/html';
break;
}
$this->filepath = $spec['filepath']; $this->filepath = $spec['filepath'];
$this->_id = $spec['filepath']; $this->_id = $spec['filepath'];
$this->lastModified = filemtime($spec['filepath']) $this->lastModified = filemtime($spec['filepath'])
@@ -70,6 +86,9 @@ class Minify_Source {
? $spec['lastModified'] ? $spec['lastModified']
: time(); : time();
} }
if (isset($spec['contentType'])) {
$this->contentType = $spec['contentType'];
}
if (isset($spec['minifier'])) { if (isset($spec['minifier'])) {
$this->minifier = $spec['minifier']; $this->minifier = $spec['minifier'];
} }
@@ -143,7 +162,7 @@ class Minify_Source {
} }
/** /**
* Guess content type from the first filename extension available * Get content type from a group of sources
* *
* This is called if the user doesn't pass in a 'contentType' options * This is called if the user doesn't pass in a 'contentType' options
* *
@@ -153,18 +172,9 @@ class Minify_Source {
*/ */
public static function getContentType($sources) public static function getContentType($sources)
{ {
$exts = array(
'css' => Minify::TYPE_CSS
,'js' => Minify::TYPE_JS
,'html' => Minify::TYPE_HTML
);
foreach ($sources as $source) { foreach ($sources as $source) {
if (null !== $source->filepath) { if ($source->contentType !== null) {
$segments = explode('.', $source->filepath); return $source->contentType;
$ext = array_pop($segments);
if (isset($exts[$ext])) {
return $exts[$ext];
}
} }
} }
return 'text/plain'; return 'text/plain';

View File

@@ -19,7 +19,7 @@ function test_HTTP_ConditionalGet()
'Vary' => 'Accept-Encoding' 'Vary' => 'Accept-Encoding'
,'Last-Modified' => $gmtTime ,'Last-Modified' => $gmtTime
,'ETag' => "\"pri{$lmTime}\"" ,'ETag' => "\"pri{$lmTime}\""
,'Cache-Control' => 'max-age=0, private, must-revalidate' ,'Cache-Control' => 'max-age=0, private'
,'_responseCode' => 'HTTP/1.0 304 Not Modified' ,'_responseCode' => 'HTTP/1.0 304 Not Modified'
,'isValid' => true ,'isValid' => true
) )
@@ -32,7 +32,7 @@ function test_HTTP_ConditionalGet()
'Vary' => 'Accept-Encoding' 'Vary' => 'Accept-Encoding'
,'Last-Modified' => $gmtTime ,'Last-Modified' => $gmtTime
,'ETag' => "\"pri{$lmTime}\"" ,'ETag' => "\"pri{$lmTime}\""
,'Cache-Control' => 'max-age=0, private, must-revalidate' ,'Cache-Control' => 'max-age=0, private'
,'_responseCode' => 'HTTP/1.0 304 Not Modified' ,'_responseCode' => 'HTTP/1.0 304 Not Modified'
,'isValid' => true ,'isValid' => true
) )
@@ -45,7 +45,7 @@ function test_HTTP_ConditionalGet()
'Vary' => 'Accept-Encoding' 'Vary' => 'Accept-Encoding'
,'Last-Modified' => $gmtTime ,'Last-Modified' => $gmtTime
,'ETag' => "\"pri{$lmTime}\"" ,'ETag' => "\"pri{$lmTime}\""
,'Cache-Control' => 'max-age=0, private, must-revalidate' ,'Cache-Control' => 'max-age=0, private'
,'_responseCode' => 'HTTP/1.0 304 Not Modified' ,'_responseCode' => 'HTTP/1.0 304 Not Modified'
,'isValid' => true ,'isValid' => true
) )
@@ -58,7 +58,7 @@ function test_HTTP_ConditionalGet()
'Vary' => 'Accept-Encoding' 'Vary' => 'Accept-Encoding'
,'Last-Modified' => $gmtTime ,'Last-Modified' => $gmtTime
,'ETag' => "\"pri{$lmTime};gz\"" ,'ETag' => "\"pri{$lmTime};gz\""
,'Cache-Control' => 'max-age=0, private, must-revalidate' ,'Cache-Control' => 'max-age=0, private'
,'_responseCode' => 'HTTP/1.0 304 Not Modified' ,'_responseCode' => 'HTTP/1.0 304 Not Modified'
,'isValid' => true ,'isValid' => true
) )
@@ -71,7 +71,7 @@ function test_HTTP_ConditionalGet()
'Vary' => 'Accept-Encoding' 'Vary' => 'Accept-Encoding'
,'Last-Modified' => $gmtTime ,'Last-Modified' => $gmtTime
,'ETag' => "\"pri{$lmTime};gz\"" ,'ETag' => "\"pri{$lmTime};gz\""
,'Cache-Control' => 'max-age=0, private, must-revalidate' ,'Cache-Control' => 'max-age=0, private'
,'isValid' => false ,'isValid' => false
) )
) )
@@ -83,7 +83,7 @@ function test_HTTP_ConditionalGet()
'Vary' => 'Accept-Encoding' 'Vary' => 'Accept-Encoding'
,'Last-Modified' => $gmtTime ,'Last-Modified' => $gmtTime
,'ETag' => "\"pri{$lmTime};gz\"" ,'ETag' => "\"pri{$lmTime};gz\""
,'Cache-Control' => 'max-age=0, private, must-revalidate' ,'Cache-Control' => 'max-age=0, private'
,'isValid' => false ,'isValid' => false
) )
) )
@@ -95,7 +95,7 @@ function test_HTTP_ConditionalGet()
'Vary' => 'Accept-Encoding' 'Vary' => 'Accept-Encoding'
,'Last-Modified' => $gmtTime ,'Last-Modified' => $gmtTime
,'ETag' => "\"pri{$lmTime};gz\"" ,'ETag' => "\"pri{$lmTime};gz\""
,'Cache-Control' => 'max-age=0, private, must-revalidate' ,'Cache-Control' => 'max-age=0, private'
,'isValid' => false ,'isValid' => false
) )
) )

View File

@@ -36,7 +36,7 @@ function test_HTTP_Encoder()
,array( ,array(
'ua' => 'Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1)' 'ua' => 'Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1)'
,'ae' => 'gzip, deflate' ,'ae' => 'gzip, deflate'
,'exp' => array('deflate', 'deflate') ,'exp' => array('gzip', 'gzip')
,'desc' => 'IE6 w/ "enhanced security"' ,'desc' => 'IE6 w/ "enhanced security"'
) )
,array( ,array(
@@ -48,7 +48,7 @@ function test_HTTP_Encoder()
,array( ,array(
'ua' => 'Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; en) Opera 9.25' 'ua' => 'Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; en) Opera 9.25'
,'ae' => 'gzip,deflate' ,'ae' => 'gzip,deflate'
,'exp' => array('deflate', 'deflate') ,'exp' => array('gzip', 'gzip')
,'desc' => 'Opera identifying as IE6' ,'desc' => 'Opera identifying as IE6'
) )
); );

View File

@@ -29,7 +29,7 @@ function test_Minify()
'Vary' => 'Accept-Encoding', 'Vary' => 'Accept-Encoding',
'Last-Modified' => gmdate('D, d M Y H:i:s \G\M\T', $lastModified), 'Last-Modified' => gmdate('D, d M Y H:i:s \G\M\T', $lastModified),
'ETag' => "\"pub{$lastModified}\"", 'ETag' => "\"pub{$lastModified}\"",
'Cache-Control' => 'max-age=1800, public, must-revalidate', 'Cache-Control' => 'max-age=1800, public',
'_responseCode' => 'HTTP/1.0 304 Not Modified', '_responseCode' => 'HTTP/1.0 304 Not Modified',
) )
); );
@@ -70,7 +70,7 @@ function test_Minify()
'Vary' => 'Accept-Encoding', 'Vary' => 'Accept-Encoding',
'Last-Modified' => gmdate('D, d M Y H:i:s \G\M\T', $lastModified), 'Last-Modified' => gmdate('D, d M Y H:i:s \G\M\T', $lastModified),
'ETag' => "\"pub{$lastModified}\"", 'ETag' => "\"pub{$lastModified}\"",
'Cache-Control' => 'max-age=86400, public, must-revalidate', 'Cache-Control' => 'max-age=86400, public',
'Content-Length' => strlen($content), 'Content-Length' => strlen($content),
'Content-Type' => 'application/x-javascript; charset=utf-8', 'Content-Type' => 'application/x-javascript; charset=utf-8',
) )
@@ -185,7 +185,7 @@ function test_Minify()
'Vary' => 'Accept-Encoding', 'Vary' => 'Accept-Encoding',
'Last-Modified' => gmdate('D, d M Y H:i:s \G\M\T', $lastModified), 'Last-Modified' => gmdate('D, d M Y H:i:s \G\M\T', $lastModified),
'ETag' => "\"pub{$lastModified}\"", 'ETag' => "\"pub{$lastModified}\"",
'Cache-Control' => 'max-age=0, public, must-revalidate', 'Cache-Control' => 'max-age=0, public',
'Content-Length' => strlen($expectedContent), 'Content-Length' => strlen($expectedContent),
'Content-Type' => 'text/css; charset=utf-8', 'Content-Type' => 'text/css; charset=utf-8',
) )

View File

@@ -13,6 +13,8 @@ function test_Minify_Cache_File()
$cache = new Minify_Cache_File($minifyCachePath); $cache = new Minify_Cache_File($minifyCachePath);
echo "NOTE: Minify_Cache_File : path is set to: '" . $cache->getPath() . "'.\n";
assertTrue(true === $cache->store($id, $data), $prefix . 'store'); assertTrue(true === $cache->store($id, $data), $prefix . 'store');
assertTrue(strlen($data) === $cache->getSize($id), $prefix . 'getSize'); assertTrue(strlen($data) === $cache->getSize($id), $prefix . 'getSize');