1
0
mirror of https://github.com/mrclay/minify.git synced 2025-08-13 17:44:00 +02:00

Merge branch '2.x' into merge2x

Conflicts:
	.gitignore
	HISTORY.txt
	README.md
	config.php
	docs/CookBook.wiki.md
	docs/CustomSource.wiki.md
	lib/Minify/CSS/UriRewriter.php
	min/lib/DooDigestAuth.php
	min/lib/FirePHP.php
	min/lib/JSMinPlus.php
	min/lib/Minify/Controller/Base.php
	min/lib/Minify/Loader.php
	min/lib/Minify/Logger.php
This commit is contained in:
Steve Clay
2016-05-12 11:03:16 -04:00
15 changed files with 4432 additions and 5 deletions

3
.gitignore vendored
View File

@@ -4,7 +4,8 @@
[._]s[a-w][a-z]
.DS_Store
/test
/.idea/
/composer.lock
/vendor/
/vendor
/.php_cs.cache

View File

@@ -12,6 +12,14 @@
* Missing spec no longer redirects, instead links to docs
* Minify::VERSION is an int that tracks the major version number
## Version 2.3.0 (2016-03-11)
* Adds `$min_concatOnly` option to just concatenate files
* Deprecates use of Minify_Loader
* Deprecates use of Minify_Logger
* Deprecates use of JSMinPlus
* Deprecates use of FirePHP
* Deprecates use of DooDigestAuth
## Version 2.2.1 (2014-10-30)
* Builder styled with Bootstrap (thanks to help from acidvertigo)
* Update CSSmin to v.2.4.8

View File

@@ -56,6 +56,14 @@ $min_allowDebugFlag = false;
//$min_cachePath = '/tmp';
//$min_cachePath = preg_replace('/^\\d+;/', '', session_save_path());
/**
* Path to Minify's lib folder. If you happen to move it, change
* this accordingly.
*/
$min_libPath = dirname(__FILE__) . '/lib';
/**
* To use APC/Memcache/ZendPlatform for cache storage, require the class and
* set $min_cachePath to an instance. Example below:
@@ -191,4 +199,3 @@ $min_uploaderHoursBehind = 0;
*/
//$min_factories['minify'] = ... a callable accepting a Minify\App object
//$min_factories['controller'] = ... a callable accepting a Minify\App object

View File

@@ -67,12 +67,16 @@ class Minify_CSS_UriRewriter {
$css = self::_trimUrls($css);
$css = self::_owlifySvgPaths($css);
// rewrite
$css = preg_replace_callback('/@import\\s+([\'"])(.*?)[\'"]/'
,array(self::$className, '_processUriCB'), $css);
$css = preg_replace_callback('/url\\(\\s*([\'"](.*?)[\'"]|[^\\)\\s]+)\\s*\\)/'
,array(self::$className, '_processUriCB'), $css);
$css = self::_unOwlify($css);
return $css;
}
@@ -91,12 +95,16 @@ class Minify_CSS_UriRewriter {
$css = self::_trimUrls($css);
$css = self::_owlifySvgPaths($css);
// append
$css = preg_replace_callback('/@import\\s+([\'"])(.*?)[\'"]/'
,array(self::$className, '_processUriCB'), $css);
$css = preg_replace_callback('/url\\(\\s*([\'"](.*?)[\'"]|[^\\)\\s]+)\\s*\\)/'
,array(self::$className, '_processUriCB'), $css);
$css = self::_unOwlify($css);
self::$_prependPath = null;
return $css;
@@ -308,4 +316,29 @@ class Minify_CSS_UriRewriter {
? "@import {$quoteChar}{$uri}{$quoteChar}"
: "url({$quoteChar}{$uri}{$quoteChar})";
}
/**
* Mungs some inline SVG URL declarations so they won't be touched
*
* @link https://github.com/mrclay/minify/issues/517
* @see _unOwlify
*
* @param string $css
* @return string
*/
private static function _owlifySvgPaths($css) {
return preg_replace('~\b((?:clip-path|mask|-webkit-mask)\s*\:\s*)url(\(\s*#\w+\s*\))~', '$1owl$2', $css);
}
/**
* Undo work of _owlify
*
* @see _owlifySvgPaths
*
* @param string $css
* @return string
*/
private static function _unOwlify($css) {
return preg_replace('~\b((?:clip-path|mask|-webkit-mask)\s*\:\s*)owl~', '$1url', $css);
}
}

View File

@@ -157,16 +157,19 @@ class Minify_Source_Factory {
}
if ($this->options['checkAllowDirs']) {
$allowDirs = (array)$this->options['allowDirs'];
$inAllowedDir = false;
foreach ((array)$this->options['allowDirs'] as $allowDir) {
foreach ($allowDirs as $allowDir) {
if (strpos($this->getNormalizedPath($spec['filepath']), $this->getNormalizedPath($allowDir)) === 0) {
$inAllowedDir = true;
}
}
if (!$inAllowedDir) {
throw new Minify_Source_FactoryException("File '{$spec['filepath']}' is outside \$allowDirs."
. " If the path is resolved via an alias/symlink, look into the \$min_symlinks option.");
$allowDirsStr = implode(';', $allowDirs);
throw new Minify_Source_FactoryException("File '{$spec['filepath']}' is outside \$allowDirs "
. "($allowDirsStr). If the path is resolved via an alias/symlink, look into the "
. "\$min_symlinks option.");
}
}

123
min/lib/DooDigestAuth.php Normal file
View File

@@ -0,0 +1,123 @@
<?php
/**
* DooDigestAuth class file.
*
* @author Leng Sheng Hong <darkredz@gmail.com>
* @link http://www.doophp.com/
* @copyright Copyright &copy; 2009 Leng Sheng Hong
* @license http://www.doophp.com/license
*/
/**
* Handles HTTP digest authentication
*
* <p>HTTP digest authentication can be used with the URI router.
* HTTP digest is much more recommended over the use of HTTP Basic auth which doesn't provide any encryption.
* If you are running PHP on Apache in CGI/FastCGI mode, you would need to
* add the following line to your .htaccess for digest auth to work correctly.</p>
* <code>RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization},L]</code>
*
* <p>This class is tested under Apache 2.2 and Cherokee web server. It should work in both mod_php and cgi mode.</p>
*
* @author Leng Sheng Hong <darkredz@gmail.com>
* @version $Id: DooDigestAuth.php 1000 2009-07-7 18:27:22
* @package doo.auth
* @since 1.0
*
* @deprecated 2.3 This will be removed in Minify 3.0
*/
class DooDigestAuth{
/**
* Authenticate against a list of username and passwords.
*
* <p>HTTP Digest Authentication doesn't work with PHP in CGI mode,
* you have to add this into your .htaccess <code>RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization},L]</code></p>
*
* @param string $realm Name of the authentication session
* @param array $users An assoc array of username and password: array('uname1'=>'pwd1', 'uname2'=>'pwd2')
* @param string $fail_msg Message to be displayed if the User cancel the login
* @param string $fail_url URL to be redirect if the User cancel the login
* @return string The username if login success.
*/
public static function http_auth($realm, $users, $fail_msg=NULL, $fail_url=NULL){
$realm = "Restricted area - $realm";
//user => password
//$users = array('admin' => '1234', 'guest' => 'guest');
if(!empty($_SERVER['REDIRECT_HTTP_AUTHORIZATION']) && strpos($_SERVER['REDIRECT_HTTP_AUTHORIZATION'], 'Digest')===0){
$_SERVER['PHP_AUTH_DIGEST'] = $_SERVER['REDIRECT_HTTP_AUTHORIZATION'];
}
if (empty($_SERVER['PHP_AUTH_DIGEST'])) {
header('WWW-Authenticate: Digest realm="'.$realm.
'",qop="auth",nonce="'.uniqid().'",opaque="'.md5($realm).'"');
header('HTTP/1.1 401 Unauthorized');
if($fail_msg!=NULL)
die($fail_msg);
if($fail_url!=NULL)
die("<script>window.location.href = '$fail_url'</script>");
exit;
}
// analyze the PHP_AUTH_DIGEST variable
if (!($data = self::http_digest_parse($_SERVER['PHP_AUTH_DIGEST'])) || !isset($users[$data['username']])){
header('WWW-Authenticate: Digest realm="'.$realm.
'",qop="auth",nonce="'.uniqid().'",opaque="'.md5($realm).'"');
header('HTTP/1.1 401 Unauthorized');
if($fail_msg!=NULL)
die($fail_msg);
if($fail_url!=NULL)
die("<script>window.location.href = '$fail_url'</script>");
exit;
}
// generate the valid response
$A1 = md5($data['username'] . ':' . $realm . ':' . $users[$data['username']]);
$A2 = md5($_SERVER['REQUEST_METHOD'].':'.$data['uri']);
$valid_response = md5($A1.':'.$data['nonce'].':'.$data['nc'].':'.$data['cnonce'].':'.$data['qop'].':'.$A2);
if ($data['response'] != $valid_response){
header('HTTP/1.1 401 Unauthorized');
header('WWW-Authenticate: Digest realm="'.$realm.
'",qop="auth",nonce="'.uniqid().'",opaque="'.md5($realm).'"');
if($fail_msg!=NULL)
die($fail_msg);
if($fail_url!=NULL)
die("<script>window.location.href = '$fail_url'</script>");
exit;
}
// ok, valid username & password
return $data['username'];
}
/**
* Method to parse the http auth header, works with IE.
*
* Internet Explorer returns a qop="xxxxxxxxxxx" in the header instead of qop=xxxxxxxxxxx as most browsers do.
*
* @param string $txt header string to parse
* @return array An assoc array of the digest auth session
*/
private static function http_digest_parse($txt)
{
$res = preg_match("/username=\"([^\"]+)\"/i", $txt, $match);
$data['username'] = (isset($match[1]))?$match[1]:null;
$res = preg_match('/nonce=\"([^\"]+)\"/i', $txt, $match);
$data['nonce'] = $match[1];
$res = preg_match('/nc=([0-9]+)/i', $txt, $match);
$data['nc'] = $match[1];
$res = preg_match('/cnonce=\"([^\"]+)\"/i', $txt, $match);
$data['cnonce'] = $match[1];
$res = preg_match('/qop=([^,]+)/i', $txt, $match);
$data['qop'] = str_replace('"','',$match[1]);
$res = preg_match('/uri=\"([^\"]+)\"/i', $txt, $match);
$data['uri'] = $match[1];
$res = preg_match('/response=\"([^\"]+)\"/i', $txt, $match);
$data['response'] = $match[1];
return $data;
}
}

1843
min/lib/FirePHP.php Normal file

File diff suppressed because it is too large Load Diff

2090
min/lib/JSMinPlus.php Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,223 @@
<?php
/**
* Class Minify_Controller_Base
* @package Minify
*/
/**
* Base class for Minify controller
*
* The controller class validates a request and uses it to create sources
* for minification and set options like contentType. It's also responsible
* for loading minifier code upon request.
*
* @package Minify
* @author Stephen Clay <steve@mrclay.org>
*/
abstract class Minify_Controller_Base {
/**
* Setup controller sources and set an needed options for Minify::source
*
* You must override this method in your subclass controller to set
* $this->sources. If the request is NOT valid, make sure $this->sources
* is left an empty array. Then strip any controller-specific options from
* $options and return it. To serve files, $this->sources must be an array of
* Minify_Source objects.
*
* @param array $options controller and Minify options
*
* @return array $options Minify::serve options
*/
abstract public function setupSources($options);
/**
* Get default Minify options for this controller.
*
* Override in subclass to change defaults
*
* @return array options for Minify
*/
public function getDefaultMinifyOptions() {
return array(
'isPublic' => true
,'encodeOutput' => function_exists('gzdeflate')
,'encodeMethod' => null // determine later
,'encodeLevel' => 9
,'minifierOptions' => array() // no minifier options
,'contentTypeCharset' => 'utf-8'
,'maxAge' => 1800 // 30 minutes
,'rewriteCssUris' => true
,'bubbleCssImports' => false
,'quiet' => false // serve() will send headers and output
,'debug' => false
,'concatOnly' => false
// if you override these, the response codes MUST be directly after
// the first space.
,'badRequestHeader' => 'HTTP/1.0 400 Bad Request'
,'errorHeader' => 'HTTP/1.0 500 Internal Server Error'
// callback function to see/modify content of all sources
,'postprocessor' => null
// file to require to load preprocessor
,'postprocessorRequire' => null
);
}
/**
* Get default minifiers for this controller.
*
* Override in subclass to change defaults
*
* @return array minifier callbacks for common types
*/
public function getDefaultMinifers() {
$ret[Minify::TYPE_JS] = array('JSMin', 'minify');
$ret[Minify::TYPE_CSS] = array('Minify_CSS', 'minify');
$ret[Minify::TYPE_HTML] = array('Minify_HTML', 'minify');
return $ret;
}
/**
* Is a user-given file within an allowable directory, 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 array $safeDirs directories where files are safe to serve. Files can also
* be in subdirectories of these directories.
*
* @return bool file is safe
*
* @deprecated use checkAllowDirs, checkNotHidden instead
*/
public static function _fileIsSafe($file, $safeDirs)
{
$pathOk = false;
foreach ((array)$safeDirs as $safeDir) {
if (strpos($file, $safeDir) === 0) {
$pathOk = true;
break;
}
}
$base = basename($file);
if (! $pathOk || ! is_file($file) || $base[0] === '.') {
return false;
}
list($revExt) = explode('.', strrev($base));
return in_array(strrev($revExt), array('js', 'css', 'html', 'txt'));
}
/**
* @param string $file
* @param array $allowDirs
* @param string $uri
* @return bool
* @throws Exception
*/
public static function checkAllowDirs($file, $allowDirs, $uri)
{
foreach ((array)$allowDirs as $allowDir) {
if (strpos($file, $allowDir) === 0) {
return true;
}
}
$allowDirs = implode(';', array_values($allowDirs));
throw new Exception("File '$file' is outside \$allowDirs ($allowDirs). If the path is"
. " resolved via an alias/symlink, look into the \$min_symlinks option."
. " E.g. \$min_symlinks['/" . dirname($uri) . "'] = '" . dirname($file) . "';");
}
/**
* @param string $file
* @throws Exception
*/
public static function checkNotHidden($file)
{
$b = basename($file);
if (0 === strpos($b, '.')) {
throw new Exception("Filename '$b' starts with period (may be hidden)");
}
}
/**
* instances of Minify_Source, which provide content and any individual minification needs.
*
* @var Minify_Source[]
*/
public $sources = array();
/**
* Short name to place inside cache id
*
* The setupSources() method may choose to set this, making it easier to
* recognize a particular set of sources/settings in the cache folder. It
* will be filtered and truncated to make the final cache id <= 250 bytes.
*
* @var string
*/
public $selectionId = '';
/**
* Mix in default controller options with user-given options
*
* @param array $options user options
*
* @return array mixed options
*/
public final function mixInDefaultOptions($options)
{
$ret = array_merge(
$this->getDefaultMinifyOptions(), $options
);
if (! isset($options['minifiers'])) {
$options['minifiers'] = array();
}
$ret['minifiers'] = array_merge(
$this->getDefaultMinifers(), $options['minifiers']
);
return $ret;
}
/**
* Analyze sources (if there are any) and set $options 'contentType'
* and 'lastModifiedTime' if they already aren't.
*
* @param array $options options for Minify
*
* @return array options for Minify
*/
public final function analyzeSources($options = array())
{
if ($this->sources) {
if (! isset($options['contentType'])) {
$options['contentType'] = Minify_Source::getContentType($this->sources);
}
// last modified is needed for caching, even if setExpires is set
if (! isset($options['lastModifiedTime'])) {
$max = 0;
foreach ($this->sources as $source) {
$max = max($source->lastModified, $max);
}
$options['lastModifiedTime'] = $max;
}
}
return $options;
}
/**
* Send message to the Minify logger
*
* @param string $msg
*
* @return null
*/
public function log($msg) {
Minify_Logger::log($msg);
}
}

33
min/lib/Minify/Loader.php Normal file
View File

@@ -0,0 +1,33 @@
<?php
/**
* Class Minify_Loader
* @package Minify
*/
/**
* Class autoloader
*
* @package Minify
* @author Stephen Clay <steve@mrclay.org>
*
* @deprecated 2.3 This will be removed in Minify 3.0
*/
class Minify_Loader {
public function loadClass($class)
{
$file = dirname(dirname(__FILE__)) . DIRECTORY_SEPARATOR;
$file .= strtr($class, "\\_", DIRECTORY_SEPARATOR . DIRECTORY_SEPARATOR) . '.php';
if (is_readable($file)) {
require $file;
}
}
/**
* @deprecated 2.3 This will be removed in Minify 3.0
*/
static public function register()
{
$inst = new self();
spl_autoload_register(array($inst, 'loadClass'));
}
}

47
min/lib/Minify/Logger.php Normal file
View File

@@ -0,0 +1,47 @@
<?php
/**
* Class Minify_Logger
* @package Minify
*/
/**
* Message logging class
*
* @package Minify
* @author Stephen Clay <steve@mrclay.org>
*
* @deprecated 2.3 This will be removed in Minify 3.0
*/
class Minify_Logger {
/**
* Set logger object.
*
* The object should have a method "log" that accepts a value as 1st argument and
* an optional string label as the 2nd.
*
* @param mixed $obj or a "falsey" value to disable
* @return null
*/
public static function setLogger($obj = null) {
self::$_logger = $obj
? $obj
: null;
}
/**
* Pass a message to the logger (if set)
*
* @param string $msg message to log
* @return null
*/
public static function log($msg, $label = 'Minify') {
if (! self::$_logger) return;
self::$_logger->log($msg, $label);
}
/**
* @var mixed logger object (like FirePHP) or null (i.e. no logger available)
*/
private static $_logger = null;
}

View File

@@ -8,6 +8,10 @@
@import url("/css/foo.css"); /* abs, should not alter */
@import url(/css2/foo.css); /* abs, should not alter */
@import url(foo:bar); /* scheme, should not alter */
foo {clip-path:url(#c1)} /* inline clip path, should not alter */
foo {clip-path:url(/_test_files/css_uriRewriter/foo.svg#c1)}
foo {mask: url(#c1)} /* should not alter */
foo {-webkit-mask: url(#c1)} /* should not alter */
foo {background:url('/_test_files/css_uriRewriter/bar/foo.png')}
foo {background:url('http://foo.com/css/foo.css');} /* scheme, should not alter */
foo {background:url("//foo.com/css/foo.css");} /* protocol relative, should not alter */

View File

@@ -8,6 +8,10 @@
@import url("/css/foo.css"); /* abs, should not alter */
@import url(/css2/foo.css); /* abs, should not alter */
@import url(foo:bar); /* scheme, should not alter */
foo {clip-path:url(#c1)} /* inline clip path, should not alter */
foo {clip-path:url(http://cnd.com/A/B/foo.svg#c1)}
foo {mask: url(#c1)} /* should not alter */
foo {-webkit-mask: url(#c1)} /* should not alter */
foo {background:url('http://cnd.com/A/B/bar/foo.png')}
foo {background:url('http://foo.com/css/foo.css');} /* scheme, should not alter */
foo {background:url("//foo.com/css/foo.css");} /* protocol relative, should not alter */

View File

@@ -8,6 +8,10 @@
@import url("/css/foo.css"); /* abs, should not alter */
@import url(/css2/foo.css); /* abs, should not alter */
@import url(foo:bar); /* scheme, should not alter */
foo {clip-path:url(#c1)} /* inline clip path, should not alter */
foo {clip-path:url(//cnd.com/A/B/foo.svg#c1)}
foo {mask: url(#c1)} /* should not alter */
foo {-webkit-mask: url(#c1)} /* should not alter */
foo {background:url('//cnd.com/A/B/bar/foo.png')}
foo {background:url('http://foo.com/css/foo.css');} /* scheme, should not alter */
foo {background:url("//foo.com/css/foo.css");} /* protocol relative, should not alter */

View File

@@ -8,6 +8,10 @@
@import url("/css/foo.css"); /* abs, should not alter */
@import url(/css2/foo.css); /* abs, should not alter */
@import url(foo:bar); /* scheme, should not alter */
foo {clip-path:url(#c1)} /* inline clip path, should not alter */
foo {clip-path:url(foo.svg#c1)}
foo {mask: url( #c1 )} /* should not alter */
foo {-webkit-mask: url( #c1 )} /* should not alter */
foo {background:url('bar/foo.png')}
foo {background:url('http://foo.com/css/foo.css');} /* scheme, should not alter */
foo {background:url("//foo.com/css/foo.css");} /* protocol relative, should not alter */