diff --git a/min/config.php b/min/config.php index 6154529..040908e 100644 --- a/min/config.php +++ b/min/config.php @@ -28,6 +28,13 @@ $min_allowDebugFlag = false; $min_errorLogger = false; +/** + * Allow use of the Minify URI Builder app. If you no longer need + * this, set to false. + **/ +$min_enableBuilder = true; + + /** * For best performance, specify your temp directory here. Otherwise Minify * will have to load extra code to guess. Some examples below: @@ -44,10 +51,11 @@ $min_errorLogger = false; * E.g. '/home/accountname/public_html' or 'c:\\xampp\\htdocs' * * If /min/ is directly inside your document root, just uncomment the - * second line: + * second line. The third line might work on some Apache servers. */ $min_documentRoot = ''; //$min_documentRoot = substr(__FILE__, 0, strlen(__FILE__) - 15); +//$min_documentRoot = $_SERVER['SUBDOMAIN_DOCUMENT_ROOT']; /** @@ -57,13 +65,6 @@ $min_documentRoot = ''; $min_cacheFileLocking = true; -/** - * Allow use of the Minify URI Builder app. If you no longer need - * this, set to false. - **/ -$min_enableBuilder = true; - - /** * Maximum age of browser cache in seconds. After this period, the browser * will send another conditional GET. Use a longer period for lower traffic @@ -82,7 +83,7 @@ $min_serveOptions['maxAge'] = 1800; * You will still need to include the directory in the * f or b GET parameters. * - * // = DOCUMENT_ROOT + * // = shortcut for DOCUMENT_ROOT */ //$min_serveOptions['minApp']['allowDirs'] = array('//js', '//css'); diff --git a/min/lib/JSMin.php b/min/lib/JSMin.php index c3a1f3b..3d9858e 100644 --- a/min/lib/JSMin.php +++ b/min/lib/JSMin.php @@ -46,268 +46,286 @@ * -- * * @package JSMin - * @author Ryan Grove - * @author Steve Clay (modifications) + * @author Ryan Grove (PHP port) + * @author Steve Clay (modifications + cleanup) + * @author Andrea Giammarchi (spaceBeforeRegExp) * @copyright 2002 Douglas Crockford (jsmin.c) * @copyright 2008 Ryan Grove (PHP port) * @license http://opensource.org/licenses/mit-license.php MIT License - * @version 1.1.1 (2008-03-02) * @link http://code.google.com/p/jsmin-php/ */ class JSMin { - const ORD_LF = 10; - const ORD_SPACE = 32; + const ORD_LF = 10; + const ORD_SPACE = 32; - protected $a = ''; - protected $b = ''; - protected $input = ''; - protected $inputIndex = 0; - protected $inputLength = 0; - protected $lookAhead = null; - protected $output = ''; + protected $a = ''; + protected $b = ''; + protected $input = ''; + protected $inputIndex = 0; + protected $inputLength = 0; + protected $lookAhead = null; + protected $output = ''; - // -- Public Static Methods -------------------------------------------------- - - public static function minify($js) { - $jsmin = new JSMin($js); - return $jsmin->min(); - } - - // -- Public Instance Methods ------------------------------------------------ - - public function __construct($input) { - $this->input = str_replace("\r\n", "\n", $input); - $this->inputLength = strlen($this->input); - } - - // -- Protected Instance Methods --------------------------------------------- - - protected function action($d) { - switch($d) { - case 1: - $this->output .= $this->a; - - case 2: - $this->a = $this->b; - - if ($this->a === "'" || $this->a === '"') { - for (;;) { - $this->output .= $this->a; - $this->a = $this->get(); - - if ($this->a === $this->b) { - break; - } - - if (ord($this->a) <= self::ORD_LF) { - throw new JSMinException('Unterminated string literal.'); - } - - if ($this->a === '\\') { - $this->output .= $this->a; - $this->a = $this->get(); - } - } - } - - case 3: - $this->b = $this->next(); - - if ($this->b === '/' && ( - $this->a === '(' || $this->a === ',' || $this->a === '=' || - $this->a === ':' || $this->a === '[' || $this->a === '!' || - $this->a === '&' || $this->a === '|' || $this->a === '?')) { - - $this->output .= $this->a . $this->b; - - for (;;) { - $this->a = $this->get(); - - if ($this->a === '/') { - break; - } elseif ($this->a === '\\') { - $this->output .= $this->a; - $this->a = $this->get(); - } elseif (ord($this->a) <= self::ORD_LF) { - throw new JSMinException('Unterminated regular expression '. - 'literal.'); - } - - $this->output .= $this->a; - } - - $this->b = $this->next(); - } - } - } - - protected function get() { - $c = $this->lookAhead; - $this->lookAhead = null; - - if ($c === null) { - if ($this->inputIndex < $this->inputLength) { - $c = $this->input[$this->inputIndex]; - $this->inputIndex += 1; - } else { - $c = null; - } + /** + * Minify Javascript + * + * @param string $js Javascript to be minified + * @return string + */ + public static function minify($js) + { + $jsmin = new JSMin($js); + return $jsmin->min(); } - if ($c === "\r") { - return "\n"; + protected function __construct($input) + { + $this->input = str_replace("\r\n", "\n", $input); + $this->inputLength = strlen($this->input); } - if ($c === null || $c === "\n" || ord($c) >= self::ORD_SPACE) { - return $c; - } - - return ' '; - } - - protected function isAlphaNum($c) { - return ord($c) > 126 || $c === '\\' || preg_match('/^[\w\$]$/', $c) === 1; - } - - protected function min() { - $this->a = "\n"; - $this->action(3); - - while ($this->a !== null) { - switch ($this->a) { - case ' ': - if ($this->isAlphaNum($this->b)) { - $this->action(1); - } else { - $this->action(2); - } - break; - - case "\n": - switch ($this->b) { - case '{': - case '[': - case '(': - case '+': - case '-': - $this->action(1); - break; - - case ' ': - $this->action(3); - break; - - default: - if ($this->isAlphaNum($this->b)) { - $this->action(1); - } - else { - $this->action(2); - } - } - break; - - default: - switch ($this->b) { - case ' ': - if ($this->isAlphaNum($this->a)) { - $this->action(1); - break; - } - - $this->action(3); - break; - - case "\n": - switch ($this->a) { - case '}': - case ']': - case ')': - case '+': - case '-': - case '"': - case "'": - $this->action(1); - break; - - default: - if ($this->isAlphaNum($this->a)) { - $this->action(1); - } - else { - $this->action(3); - } - } - break; - - default: - $this->action(1); - break; - } - } - } - - return $this->output; - } - - protected function next() { - $get = $this->get(); - - if ($get === '/') { - $commentContents = ''; - switch($this->peek()) { - case '/': - // "//" comment - for (;;) { - $get = $this->get(); - $commentContents .= $get; - if (ord($get) <= self::ORD_LF) { - return preg_match('/^\\/@(?:cc_on|if|elif|else|end)\\b/', $commentContents) - ? "/{$commentContents}" - : $get; - } - } - - case '*': - // "/* */" comment - $this->get(); - for (;;) { - $get = $this->get(); - switch($get) { - case '*': - if ($this->peek() === '/') { - $this->get(); - if (0 === strpos($commentContents, '!')) { - // YUI Compressor style - return "\n/*" . substr($commentContents, 1) . "*/\n"; - } - return preg_match('/^@(?:cc_on|if|elif|else|end)\\b/', $commentContents) - ? "/*{$commentContents}*/" // IE conditional compilation - : ' '; + protected function action($d) + { + switch ($d) { + case 1: + $this->output .= $this->a; + // fallthrough + case 2: + $this->a = $this->b; + if ($this->a === "'" || $this->a === '"') { + for (;;) { + $this->output .= $this->a; + $this->a = $this->get(); + if ($this->a === $this->b) { + break; + } + if (ord($this->a) <= self::ORD_LF) { + throw new JSMinException('Unterminated string literal.'); + } + if ($this->a === '\\') { + $this->output .= $this->a; + $this->a = $this->get(); + } + } } - break; - - case null: - throw new JSMinException('Unterminated comment.'); - } - $commentContents .= $get; - } - - default: - return $get; - } + // fallthrough + case 3: + $this->b = $this->next(); + if ($this->b === '/') { + switch ($this->a) { + case "\n": + case ' ': + if (! $this->spaceBeforeRegExp($this->output)) { + break; + } + case '{': + case ';': + case '(': + case ',': + case '=': + case ':': + case '[': + case '!': + case '&': + case '|': + case '?': + $this->output .= $this->a.$this->b; + for (;;) { + $this->a = $this->get(); + if ($this->a === '/') { + break; // for (;;) + } elseif ($this->a === '\\') { + $this->output .= $this->a; + $this->a = $this->get(); + } elseif (ord($this->a) <= self::ORD_LF) { + throw new JSMinException('Unterminated regular expression literal.'); + } + $this->output .= $this->a; + } + $this->b = $this->next(); + break; // switch ($this->a) + // end case ? + } + } + break; // switch ($d) + // end case 3 + } } - return $get; - } + protected function get() + { + $c = $this->lookAhead; + $this->lookAhead = null; + if ($c === null) { + if ($this->inputIndex < $this->inputLength) { + $c = $this->input[$this->inputIndex]; + $this->inputIndex += 1; + } else { + $c = null; + } + } + return ($c === "\r") + ? "\n" + : ($c === null || $c === "\n" || ord($c) >= self::ORD_SPACE + ? $c + : ' '); + } - protected function peek() { - $this->lookAhead = $this->get(); - return $this->lookAhead; - } + protected function isAlphaNum($c) + { + return (ord($c) > 126 + || $c === '\\' + || preg_match('/^[\w\$]$/', $c) === 1); + } + + protected function min() + { + $this->a = "\n"; + $this->action(3); + + while ($this->a !== null) { + switch ($this->a) { + case ' ': + if ($this->isAlphaNum($this->b)) { + $this->action(1); + } else { + $this->action(2); + } + break; + case "\n": + switch ($this->b) { + case '{': + case '[': + case '(': + case '+': + case '-': + $this->action(1); + break; + case ' ': + $this->action(3); + break; + default: + if ($this->isAlphaNum($this->b)) { + $this->action(1); + } else { + $this->action(2); + } + } + break; + default: + switch ($this->b) { + case ' ': + if ($this->isAlphaNum($this->a)) { + $this->action(1); + break; // switch ($this->b) + } + $this->action(3); + break; // switch ($this->b) + case "\n": + switch ($this->a) { + case '}': + case ']': + case ')': + case '+': + case '-': + case '"': + case "'": + $this->action(1); + break; // switch ($this->a) + default: + if ($this->isAlphaNum($this->a)) { + $this->action(1); + } else { + $this->action(3); + } + } + break; // switch ($this->b) + default: + $this->action(1); + break; // switch ($this->b) + } + // end default + } + } + return $this->output; + } + + protected function next() + { + $get = $this->get(); + if ($get === '/') { + $commentContents = ''; + switch ($this->peek()) { + case '/': + // "//" comment + for (;;) { + $get = $this->get(); + $commentContents .= $get; + if (ord($get) <= self::ORD_LF) { + return preg_match('/^\\/@(?:cc_on|if|elif|else|end)\\b/', $commentContents) + ? "/{$commentContents}" + : $get; + } + } + case '*': + // "/* */" comment + $this->get(); + for (;;) { + $get = $this->get(); + switch ($get) { + case '*': + if ($this->peek() === '/') { + $this->get(); + if (0 === strpos($commentContents, '!')) { + // YUI Compressor style + return "\n/*" . substr($commentContents, 1) . "*/\n"; + } + return preg_match('/^@(?:cc_on|if|elif|else|end)\\b/', $commentContents) + ? "/*{$commentContents}*/" // IE conditional compilation + : ' '; + } + break; + case null: + throw new JSMinException('Unterminated comment.'); + } + $commentContents .= $get; + } + default: + return $get; + } + } + return $get; + } + + protected function peek() + { + $this->lookAhead = $this->get(); + return $this->lookAhead; + } + + protected function spaceBeforeRegExp($output) + { + $length = strlen($output); + $isSpace = false; + $tmp = ""; + foreach (array("case", "else", "in", "return", "typeof") as $word) { + if ($length === strlen($word)) { + $isSpace = ($word === $output); + } elseif ($length > strlen($word)) { + $tmp = substr($output, $length - strlen($word) - 1); + $isSpace = (substr($tmp, 1) === $word) && ! $this->isAlphaNum($tmp[0]); + } + if ($isSpace) { + break; + } + } + return ($length < 2) + ? true + : $isSpace; + } } -// -- Exceptions --------------------------------------------------------------- -class JSMinException extends Exception {} -?> \ No newline at end of file +class JSMinException extends Exception { + +} diff --git a/min/lib/Minify.php b/min/lib/Minify.php index e70054e..7400430 100644 --- a/min/lib/Minify.php +++ b/min/lib/Minify.php @@ -369,6 +369,7 @@ class Minify { * 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 @@ -376,6 +377,12 @@ class Minify { : null; } + /** + * Send message to the error log (if set) + * + * @param string $msg message to log + * @return null + */ public static function logError($msg) { if (! self::$_logger) return; self::$_logger->log($msg, 'Minify'); diff --git a/min_unit_tests/_test_files/js/issue74.js b/min_unit_tests/_test_files/js/issue74.js new file mode 100644 index 0000000..e12eaeb --- /dev/null +++ b/min_unit_tests/_test_files/js/issue74.js @@ -0,0 +1,4 @@ + +function testIssue74() { + return /'/; +} diff --git a/min_unit_tests/_test_files/js/issue74.min.js b/min_unit_tests/_test_files/js/issue74.min.js new file mode 100644 index 0000000..3442a73 --- /dev/null +++ b/min_unit_tests/_test_files/js/issue74.min.js @@ -0,0 +1 @@ +function testIssue74(){return /'/;} \ No newline at end of file diff --git a/min_unit_tests/test_Minify_Javascript.php b/min_unit_tests/test_Minify_Javascript.php index 1f0a85e..33b8a0e 100644 --- a/min_unit_tests/test_Minify_Javascript.php +++ b/min_unit_tests/test_Minify_Javascript.php @@ -18,6 +18,18 @@ function test_Javascript() echo "---Expected: " .strlen($minExpected). " bytes\n\n{$minExpected}\n\n"; echo "---Source: " .strlen($src). " bytes\n\n{$src}\n\n\n"; } + + $src = file_get_contents($thisDir . '/_test_files/js/issue74.js'); + $minExpected = file_get_contents($thisDir . '/_test_files/js/issue74.min.js'); + $minOutput = Minify_Javascript::minify($src); + + $passed = assertTrue($minExpected == $minOutput, 'Minify_Javascript : Quotes in RegExp literals (Issue 74)'); + + if (__FILE__ === realpath($_SERVER['SCRIPT_FILENAME'])) { + echo "\n---Output: " .strlen($minOutput). " bytes\n\n{$minOutput}\n\n"; + echo "---Expected: " .strlen($minExpected). " bytes\n\n{$minExpected}\n\n"; + echo "---Source: " .strlen($src). " bytes\n\n{$src}\n\n\n"; + } } test_Javascript(); diff --git a/min_unit_tests/test_Minify_Lines.php b/min_unit_tests/test_Minify_Lines.php index f6fd661..c518fce 100644 --- a/min_unit_tests/test_Minify_Lines.php +++ b/min_unit_tests/test_Minify_Lines.php @@ -9,6 +9,8 @@ function test_Lines() $exp = file_get_contents("{$thisDir}/_test_files/minify/lines_output.js"); + Minify::setCache(null); // no cache + $ret = Minify::serve('Files', array( 'debug' => true ,'quiet' => true diff --git a/min_unit_tests/test_environment.php b/min_unit_tests/test_environment.php index 4237e90..94b3af3 100644 --- a/min_unit_tests/test_environment.php +++ b/min_unit_tests/test_environment.php @@ -30,8 +30,12 @@ function test_environment() ,'environment : DOCUMENT_ROOT should be real path and contain this test file' ); if (! $noSlash || ! $goodRoot) { - echo "!NOTE: If you cannot modify DOCUMENT_ROOT, see this comment for a workaround:" - ,"\n http://code.google.com/p/minify/issues/detail?id=68#c6\n"; + echo "!NOTE: environment : If you cannot modify DOCUMENT_ROOT, consider " + . "setting \$min_documentRoot in config.php\n"; + } + if (isset($_SERVER['SUBDOMAIN_DOCUMENT_ROOT'])) { + echo "!NOTE: environment : \$_SERVER['SUBDOMAIN_DOCUMENT_ROOT'] is set. " + . "You may need to set \$min_documentRoot to this in config.php\n"; } $thisUrl = 'http://' @@ -47,7 +51,8 @@ function test_environment() return; } if ('1' === $oc) { - echo "!WARN: environment : zlib.output_compression is enabled in php.ini or .htaccess.\n"; + echo "!WARN: environment : zlib.output_compression is enabled in php.ini" + . " or .htaccess.\n"; } $fp = fopen($thisUrl . '?hello=1', 'r', false, stream_context_create(array(