Commit baaeacb1b3177336f8f088f6efbf515011495775

Authored by Edmar Moretti
1 parent 6f9fe6a0

--no commit message

pacotes/packer/class.JavaScriptPacker.php 0 → 100644
... ... @@ -0,0 +1,736 @@
  1 +<?php
  2 +/* 7 December 2006. version 1.0
  3 + *
  4 + * This is the php version of the Dean Edwards JavaScript 's Packer,
  5 + * Based on :
  6 + *
  7 + * ParseMaster, version 1.0.2 (2005-08-19) Copyright 2005, Dean Edwards
  8 + * a multi-pattern parser.
  9 + * KNOWN BUG: erroneous behavior when using escapeChar with a replacement
  10 + * value that is a function
  11 + *
  12 + * packer, version 2.0.2 (2005-08-19) Copyright 2004-2005, Dean Edwards
  13 + *
  14 + * License: http://creativecommons.org/licenses/LGPL/2.1/
  15 + *
  16 + * Ported to PHP by Nicolas Martin.
  17 + *
  18 + * ----------------------------------------------------------------------
  19 + *
  20 + * examples of usage :
  21 + * $myPacker = new JavaScriptPacker($script, 62, true, false);
  22 + * $packed = $myPacker->pack();
  23 + *
  24 + * or
  25 + *
  26 + * $myPacker = new JavaScriptPacker($script, 'Normal', true, false);
  27 + * $packed = $myPacker->pack();
  28 + *
  29 + * or (default values)
  30 + *
  31 + * $myPacker = new JavaScriptPacker($script);
  32 + * $packed = $myPacker->pack();
  33 + *
  34 + *
  35 + * params of the constructor :
  36 + * $script: the JavaScript to pack, string.
  37 + * $encoding: level of encoding, int or string :
  38 + * 0,10,62,95 or 'None', 'Numeric', 'Normal', 'High ASCII'.
  39 + * default: 62.
  40 + * $fastDecode: include the fast decoder in the packed result, boolean.
  41 + * default : true.
  42 + * $specialChars: if you are flagged your private and local variables
  43 + * in the script, boolean.
  44 + * default: false.
  45 + *
  46 + * The pack() method return the compressed JavasScript, as a string.
  47 + *
  48 + * see http://dean.edwards.name/packer/usage/ for more information.
  49 + *
  50 + * Notes :
  51 + * # need PHP 5 . Tested with PHP 5.1.2
  52 + *
  53 + * # The packed result may be different than with the Dean Edwards
  54 + * version, but with the same length. The reason is that the PHP
  55 + * function usort to sort array don't necessarily preserve the
  56 + * original order of two equal member. The Javascript sort function
  57 + * in fact preserve this order (but that's not require by the
  58 + * ECMAScript standard). So the encoded keywords order can be
  59 + * different in the two results.
  60 + *
  61 + * # Be careful with the 'High ASCII' Level encoding if you use
  62 + * UTF-8 in your files...
  63 + */
  64 +
  65 +
  66 +class JavaScriptPacker {
  67 + // constants
  68 + const IGNORE = '$1';
  69 +
  70 + // validate parameters
  71 + private $_script = '';
  72 + private $_encoding = 62;
  73 + private $_fastDecode = true;
  74 + private $_specialChars = false;
  75 +
  76 + private $LITERAL_ENCODING = array(
  77 + 'None' => 0,
  78 + 'Numeric' => 10,
  79 + 'Normal' => 62,
  80 + 'High ASCII' => 95
  81 + );
  82 +
  83 + public function __construct($_script, $_encoding = 62, $_fastDecode = true, $_specialChars = false)
  84 + {
  85 + $this->_script = $_script . "\n";
  86 + if (array_key_exists($_encoding, $this->LITERAL_ENCODING))
  87 + $_encoding = $this->LITERAL_ENCODING[$_encoding];
  88 + $this->_encoding = min((int)$_encoding, 95);
  89 + $this->_fastDecode = $_fastDecode;
  90 + $this->_specialChars = $_specialChars;
  91 + }
  92 +
  93 + public function pack() {
  94 + $this->_addParser('_basicCompression');
  95 + if ($this->_specialChars)
  96 + $this->_addParser('_encodeSpecialChars');
  97 + if ($this->_encoding)
  98 + $this->_addParser('_encodeKeywords');
  99 +
  100 + // go!
  101 + return $this->_pack($this->_script);
  102 + }
  103 +
  104 + // apply all parsing routines
  105 + private function _pack($script) {
  106 + for ($i = 0; isset($this->_parsers[$i]); $i++) {
  107 + $script = call_user_func(array(&$this,$this->_parsers[$i]), $script);
  108 + }
  109 + return $script;
  110 + }
  111 +
  112 + // keep a list of parsing functions, they'll be executed all at once
  113 + private $_parsers = array();
  114 + private function _addParser($parser) {
  115 + $this->_parsers[] = $parser;
  116 + }
  117 +
  118 + // zero encoding - just removal of white space and comments
  119 + private function _basicCompression($script) {
  120 + $parser = new ParseMaster();
  121 + // make safe
  122 + $parser->escapeChar = '\\';
  123 + // protect strings
  124 + $parser->add('/\'[^\'\\n\\r]*\'/', self::IGNORE);
  125 + $parser->add('/"[^"\\n\\r]*"/', self::IGNORE);
  126 + // remove comments
  127 + $parser->add('/\\/\\/[^\\n\\r]*[\\n\\r]/', ' ');
  128 + $parser->add('/\\/\\*[^*]*\\*+([^\\/][^*]*\\*+)*\\//', ' ');
  129 + // protect regular expressions
  130 + $parser->add('/\\s+(\\/[^\\/\\n\\r\\*][^\\/\\n\\r]*\\/g?i?)/', '$2'); // IGNORE
  131 + $parser->add('/[^\\w\\x24\\/\'"*)\\?:]\\/[^\\/\\n\\r\\*][^\\/\\n\\r]*\\/g?i?/', self::IGNORE);
  132 + // remove: ;;; doSomething();
  133 + if ($this->_specialChars) $parser->add('/;;;[^\\n\\r]+[\\n\\r]/');
  134 + // remove redundant semi-colons
  135 + $parser->add('/\\(;;\\)/', self::IGNORE); // protect for (;;) loops
  136 + $parser->add('/;+\\s*([};])/', '$2');
  137 + // apply the above
  138 + $script = $parser->exec($script);
  139 +
  140 + // remove white-space
  141 + $parser->add('/(\\b|\\x24)\\s+(\\b|\\x24)/', '$2 $3');
  142 + $parser->add('/([+\\-])\\s+([+\\-])/', '$2 $3');
  143 + $parser->add('/\\s+/', '');
  144 + // done
  145 + return $parser->exec($script);
  146 + }
  147 +
  148 + private function _encodeSpecialChars($script) {
  149 + $parser = new ParseMaster();
  150 + // replace: $name -> n, $$name -> na
  151 + $parser->add('/((\\x24+)([a-zA-Z$_]+))(\\d*)/',
  152 + array('fn' => '_replace_name')
  153 + );
  154 + // replace: _name -> _0, double-underscore (__name) is ignored
  155 + $regexp = '/\\b_[A-Za-z\\d]\\w*/';
  156 + // build the word list
  157 + $keywords = $this->_analyze($script, $regexp, '_encodePrivate');
  158 + // quick ref
  159 + $encoded = $keywords['encoded'];
  160 +
  161 + $parser->add($regexp,
  162 + array(
  163 + 'fn' => '_replace_encoded',
  164 + 'data' => $encoded
  165 + )
  166 + );
  167 + return $parser->exec($script);
  168 + }
  169 +
  170 + private function _encodeKeywords($script) {
  171 + // escape high-ascii values already in the script (i.e. in strings)
  172 + if ($this->_encoding > 62)
  173 + $script = $this->_escape95($script);
  174 + // create the parser
  175 + $parser = new ParseMaster();
  176 + $encode = $this->_getEncoder($this->_encoding);
  177 + // for high-ascii, don't encode single character low-ascii
  178 + $regexp = ($this->_encoding > 62) ? '/\\w\\w+/' : '/\\w+/';
  179 + // build the word list
  180 + $keywords = $this->_analyze($script, $regexp, $encode);
  181 + $encoded = $keywords['encoded'];
  182 +
  183 + // encode
  184 + $parser->add($regexp,
  185 + array(
  186 + 'fn' => '_replace_encoded',
  187 + 'data' => $encoded
  188 + )
  189 + );
  190 + if (empty($script)) return $script;
  191 + else {
  192 + //$res = $parser->exec($script);
  193 + //$res = $this->_bootStrap($res, $keywords);
  194 + //return $res;
  195 + return $this->_bootStrap($parser->exec($script), $keywords);
  196 + }
  197 + }
  198 +
  199 + private function _analyze($script, $regexp, $encode) {
  200 + // analyse
  201 + // retreive all words in the script
  202 + $all = array();
  203 + preg_match_all($regexp, $script, $all);
  204 + $_sorted = array(); // list of words sorted by frequency
  205 + $_encoded = array(); // dictionary of word->encoding
  206 + $_protected = array(); // instances of "protected" words
  207 + $all = $all[0]; // simulate the javascript comportement of global match
  208 + if (!empty($all)) {
  209 + $unsorted = array(); // same list, not sorted
  210 + $protected = array(); // "protected" words (dictionary of word->"word")
  211 + $value = array(); // dictionary of charCode->encoding (eg. 256->ff)
  212 + $this->_count = array(); // word->count
  213 + $i = count($all); $j = 0; //$word = null;
  214 + // count the occurrences - used for sorting later
  215 + do {
  216 + --$i;
  217 + $word = '$' . $all[$i];
  218 + if (!isset($this->_count[$word])) {
  219 + $this->_count[$word] = 0;
  220 + $unsorted[$j] = $word;
  221 + // make a dictionary of all of the protected words in this script
  222 + // these are words that might be mistaken for encoding
  223 + //if (is_string($encode) && method_exists($this, $encode))
  224 + $values[$j] = call_user_func(array(&$this, $encode), $j);
  225 + $protected['$' . $values[$j]] = $j++;
  226 + }
  227 + // increment the word counter
  228 + $this->_count[$word]++;
  229 + } while ($i > 0);
  230 + // prepare to sort the word list, first we must protect
  231 + // words that are also used as codes. we assign them a code
  232 + // equivalent to the word itself.
  233 + // e.g. if "do" falls within our encoding range
  234 + // then we store keywords["do"] = "do";
  235 + // this avoids problems when decoding
  236 + $i = count($unsorted);
  237 + do {
  238 + $word = $unsorted[--$i];
  239 + if (isset($protected[$word]) /*!= null*/) {
  240 + $_sorted[$protected[$word]] = substr($word, 1);
  241 + $_protected[$protected[$word]] = true;
  242 + $this->_count[$word] = 0;
  243 + }
  244 + } while ($i);
  245 +
  246 + // sort the words by frequency
  247 + // Note: the javascript and php version of sort can be different :
  248 + // in php manual, usort :
  249 + // " If two members compare as equal,
  250 + // their order in the sorted array is undefined."
  251 + // so the final packed script is different of the Dean's javascript version
  252 + // but equivalent.
  253 + // the ECMAscript standard does not guarantee this behaviour,
  254 + // and thus not all browsers (e.g. Mozilla versions dating back to at
  255 + // least 2003) respect this.
  256 + usort($unsorted, array(&$this, '_sortWords'));
  257 + $j = 0;
  258 + // because there are "protected" words in the list
  259 + // we must add the sorted words around them
  260 + do {
  261 + if (!isset($_sorted[$i]))
  262 + $_sorted[$i] = substr($unsorted[$j++], 1);
  263 + $_encoded[$_sorted[$i]] = $values[$i];
  264 + } while (++$i < count($unsorted));
  265 + }
  266 + return array(
  267 + 'sorted' => $_sorted,
  268 + 'encoded' => $_encoded,
  269 + 'protected' => $_protected);
  270 + }
  271 +
  272 + private $_count = array();
  273 + private function _sortWords($match1, $match2) {
  274 + return $this->_count[$match2] - $this->_count[$match1];
  275 + }
  276 +
  277 + // build the boot function used for loading and decoding
  278 + private function _bootStrap($packed, $keywords) {
  279 + $ENCODE = $this->_safeRegExp('$encode\\($count\\)');
  280 +
  281 + // $packed: the packed script
  282 + $packed = "'" . $this->_escape($packed) . "'";
  283 +
  284 + // $ascii: base for encoding
  285 + $ascii = min(count($keywords['sorted']), $this->_encoding);
  286 + if ($ascii == 0) $ascii = 1;
  287 +
  288 + // $count: number of words contained in the script
  289 + $count = count($keywords['sorted']);
  290 +
  291 + // $keywords: list of words contained in the script
  292 + foreach ($keywords['protected'] as $i=>$value) {
  293 + $keywords['sorted'][$i] = '';
  294 + }
  295 + // convert from a string to an array
  296 + ksort($keywords['sorted']);
  297 + $keywords = "'" . implode('|',$keywords['sorted']) . "'.split('|')";
  298 +
  299 + $encode = ($this->_encoding > 62) ? '_encode95' : $this->_getEncoder($ascii);
  300 + $encode = $this->_getJSFunction($encode);
  301 + $encode = preg_replace('/_encoding/','$ascii', $encode);
  302 + $encode = preg_replace('/arguments\\.callee/','$encode', $encode);
  303 + $inline = '\\$count' . ($ascii > 10 ? '.toString(\\$ascii)' : '');
  304 +
  305 + // $decode: code snippet to speed up decoding
  306 + if ($this->_fastDecode) {
  307 + // create the decoder
  308 + $decode = $this->_getJSFunction('_decodeBody');
  309 + if ($this->_encoding > 62)
  310 + $decode = preg_replace('/\\\\w/', '[\\xa1-\\xff]', $decode);
  311 + // perform the encoding inline for lower ascii values
  312 + elseif ($ascii < 36)
  313 + $decode = preg_replace($ENCODE, $inline, $decode);
  314 + // special case: when $count==0 there are no keywords. I want to keep
  315 + // the basic shape of the unpacking funcion so i'll frig the code...
  316 + if ($count == 0)
  317 + $decode = preg_replace($this->_safeRegExp('($count)\\s*=\\s*1'), '$1=0', $decode, 1);
  318 + }
  319 +
  320 + // boot function
  321 + $unpack = $this->_getJSFunction('_unpack');
  322 + if ($this->_fastDecode) {
  323 + // insert the decoder
  324 + $this->buffer = $decode;
  325 + $unpack = preg_replace_callback('/\\{/', array(&$this, '_insertFastDecode'), $unpack, 1);
  326 + }
  327 + $unpack = preg_replace('/"/', "'", $unpack);
  328 + if ($this->_encoding > 62) { // high-ascii
  329 + // get rid of the word-boundaries for regexp matches
  330 + $unpack = preg_replace('/\'\\\\\\\\b\'\s*\\+|\\+\s*\'\\\\\\\\b\'/', '', $unpack);
  331 + }
  332 + if ($ascii > 36 || $this->_encoding > 62 || $this->_fastDecode) {
  333 + // insert the encode function
  334 + $this->buffer = $encode;
  335 + $unpack = preg_replace_callback('/\\{/', array(&$this, '_insertFastEncode'), $unpack, 1);
  336 + } else {
  337 + // perform the encoding inline
  338 + $unpack = preg_replace($ENCODE, $inline, $unpack);
  339 + }
  340 + // pack the boot function too
  341 + $unpackPacker = new JavaScriptPacker($unpack, 0, false, true);
  342 + $unpack = $unpackPacker->pack();
  343 +
  344 + // arguments
  345 + $params = array($packed, $ascii, $count, $keywords);
  346 + if ($this->_fastDecode) {
  347 + $params[] = 0;
  348 + $params[] = '{}';
  349 + }
  350 + $params = implode(',', $params);
  351 +
  352 + // the whole thing
  353 + return 'eval(' . $unpack . '(' . $params . "))\n";
  354 + }
  355 +
  356 + private $buffer;
  357 + private function _insertFastDecode($match) {
  358 + return '{' . $this->buffer . ';';
  359 + }
  360 + private function _insertFastEncode($match) {
  361 + return '{$encode=' . $this->buffer . ';';
  362 + }
  363 +
  364 + // mmm.. ..which one do i need ??
  365 + private function _getEncoder($ascii) {
  366 + return $ascii > 10 ? $ascii > 36 ? $ascii > 62 ?
  367 + '_encode95' : '_encode62' : '_encode36' : '_encode10';
  368 + }
  369 +
  370 + // zero encoding
  371 + // characters: 0123456789
  372 + private function _encode10($charCode) {
  373 + return $charCode;
  374 + }
  375 +
  376 + // inherent base36 support
  377 + // characters: 0123456789abcdefghijklmnopqrstuvwxyz
  378 + private function _encode36($charCode) {
  379 + return base_convert($charCode, 10, 36);
  380 + }
  381 +
  382 + // hitch a ride on base36 and add the upper case alpha characters
  383 + // characters: 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
  384 + private function _encode62($charCode) {
  385 + $res = '';
  386 + if ($charCode >= $this->_encoding) {
  387 + $res = $this->_encode62((int)($charCode / $this->_encoding));
  388 + }
  389 + $charCode = $charCode % $this->_encoding;
  390 +
  391 + if ($charCode > 35)
  392 + return $res . chr($charCode + 29);
  393 + else
  394 + return $res . base_convert($charCode, 10, 36);
  395 + }
  396 +
  397 + // use high-ascii values
  398 + // characters: ¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþ
  399 + private function _encode95($charCode) {
  400 + $res = '';
  401 + if ($charCode >= $this->_encoding)
  402 + $res = $this->_encode95($charCode / $this->_encoding);
  403 +
  404 + return $res . chr(($charCode % $this->_encoding) + 161);
  405 + }
  406 +
  407 + private function _safeRegExp($string) {
  408 + return '/'.preg_replace('/\$/', '\\\$', $string).'/';
  409 + }
  410 +
  411 + private function _encodePrivate($charCode) {
  412 + return "_" . $charCode;
  413 + }
  414 +
  415 + // protect characters used by the parser
  416 + private function _escape($script) {
  417 + return preg_replace('/([\\\\\'])/', '\\\$1', $script);
  418 + }
  419 +
  420 + // protect high-ascii characters already in the script
  421 + private function _escape95($script) {
  422 + return preg_replace_callback(
  423 + '/[\\xa1-\\xff]/',
  424 + array(&$this, '_escape95Bis'),
  425 + $script
  426 + );
  427 + }
  428 + private function _escape95Bis($match) {
  429 + return '\x'.((string)dechex(ord($match)));
  430 + }
  431 +
  432 +
  433 + private function _getJSFunction($aName) {
  434 + if (defined('self::JSFUNCTION'.$aName))
  435 + return constant('self::JSFUNCTION'.$aName);
  436 + else
  437 + return '';
  438 + }
  439 +
  440 + // JavaScript Functions used.
  441 + // Note : In Dean's version, these functions are converted
  442 + // with 'String(aFunctionName);'.
  443 + // This internal conversion complete the original code, ex :
  444 + // 'while (aBool) anAction();' is converted to
  445 + // 'while (aBool) { anAction(); }'.
  446 + // The JavaScript functions below are corrected.
  447 +
  448 + // unpacking function - this is the boot strap function
  449 + // data extracted from this packing routine is passed to
  450 + // this function when decoded in the target
  451 + // NOTE ! : without the ';' final.
  452 + const JSFUNCTION_unpack =
  453 +
  454 +'function($packed, $ascii, $count, $keywords, $encode, $decode) {
  455 + while ($count--) {
  456 + if ($keywords[$count]) {
  457 + $packed = $packed.replace(new RegExp(\'\\\\b\' + $encode($count) + \'\\\\b\', \'g\'), $keywords[$count]);
  458 + }
  459 + }
  460 + return $packed;
  461 +}';
  462 +/*
  463 +'function($packed, $ascii, $count, $keywords, $encode, $decode) {
  464 + while ($count--)
  465 + if ($keywords[$count])
  466 + $packed = $packed.replace(new RegExp(\'\\\\b\' + $encode($count) + \'\\\\b\', \'g\'), $keywords[$count]);
  467 + return $packed;
  468 +}';
  469 +*/
  470 +
  471 + // code-snippet inserted into the unpacker to speed up decoding
  472 + const JSFUNCTION_decodeBody =
  473 +//_decode = function() {
  474 +// does the browser support String.replace where the
  475 +// replacement value is a function?
  476 +
  477 +' if (!\'\'.replace(/^/, String)) {
  478 + // decode all the values we need
  479 + while ($count--) {
  480 + $decode[$encode($count)] = $keywords[$count] || $encode($count);
  481 + }
  482 + // global replacement function
  483 + $keywords = [function ($encoded) {return $decode[$encoded]}];
  484 + // generic match
  485 + $encode = function () {return \'\\\\w+\'};
  486 + // reset the loop counter - we are now doing a global replace
  487 + $count = 1;
  488 + }
  489 +';
  490 +//};
  491 +/*
  492 +' if (!\'\'.replace(/^/, String)) {
  493 + // decode all the values we need
  494 + while ($count--) $decode[$encode($count)] = $keywords[$count] || $encode($count);
  495 + // global replacement function
  496 + $keywords = [function ($encoded) {return $decode[$encoded]}];
  497 + // generic match
  498 + $encode = function () {return\'\\\\w+\'};
  499 + // reset the loop counter - we are now doing a global replace
  500 + $count = 1;
  501 + }';
  502 +*/
  503 +
  504 + // zero encoding
  505 + // characters: 0123456789
  506 + const JSFUNCTION_encode10 =
  507 +'function($charCode) {
  508 + return $charCode;
  509 +}';//;';
  510 +
  511 + // inherent base36 support
  512 + // characters: 0123456789abcdefghijklmnopqrstuvwxyz
  513 + const JSFUNCTION_encode36 =
  514 +'function($charCode) {
  515 + return $charCode.toString(36);
  516 +}';//;';
  517 +
  518 + // hitch a ride on base36 and add the upper case alpha characters
  519 + // characters: 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
  520 + const JSFUNCTION_encode62 =
  521 +'function($charCode) {
  522 + return ($charCode < _encoding ? \'\' : arguments.callee(parseInt($charCode / _encoding))) +
  523 + (($charCode = $charCode % _encoding) > 35 ? String.fromCharCode($charCode + 29) : $charCode.toString(36));
  524 +}';
  525 +
  526 + // use high-ascii values
  527 + // characters: ¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþ
  528 + const JSFUNCTION_encode95 =
  529 +'function($charCode) {
  530 + return ($charCode < _encoding ? \'\' : arguments.callee($charCode / _encoding)) +
  531 + String.fromCharCode($charCode % _encoding + 161);
  532 +}';
  533 +
  534 +}
  535 +
  536 +
  537 +class ParseMaster {
  538 + public $ignoreCase = false;
  539 + public $escapeChar = '';
  540 +
  541 + // constants
  542 + const EXPRESSION = 0;
  543 + const REPLACEMENT = 1;
  544 + const LENGTH = 2;
  545 +
  546 + // used to determine nesting levels
  547 + private $GROUPS = '/\\(/';//g
  548 + private $SUB_REPLACE = '/\\$\\d/';
  549 + private $INDEXED = '/^\\$\\d+$/';
  550 + private $TRIM = '/([\'"])\\1\\.(.*)\\.\\1\\1$/';
  551 + private $ESCAPE = '/\\\./';//g
  552 + private $QUOTE = '/\'/';
  553 + private $DELETED = '/\\x01[^\\x01]*\\x01/';//g
  554 +
  555 + public function add($expression, $replacement = '') {
  556 + // count the number of sub-expressions
  557 + // - add one because each pattern is itself a sub-expression
  558 + $length = 1 + preg_match_all($this->GROUPS, $this->_internalEscape((string)$expression), $out);
  559 +
  560 + // treat only strings $replacement
  561 + if (is_string($replacement)) {
  562 + // does the pattern deal with sub-expressions?
  563 + if (preg_match($this->SUB_REPLACE, $replacement)) {
  564 + // a simple lookup? (e.g. "$2")
  565 + if (preg_match($this->INDEXED, $replacement)) {
  566 + // store the index (used for fast retrieval of matched strings)
  567 + $replacement = (int)(substr($replacement, 1)) - 1;
  568 + } else { // a complicated lookup (e.g. "Hello $2 $1")
  569 + // build a function to do the lookup
  570 + $quote = preg_match($this->QUOTE, $this->_internalEscape($replacement))
  571 + ? '"' : "'";
  572 + $replacement = array(
  573 + 'fn' => '_backReferences',
  574 + 'data' => array(
  575 + 'replacement' => $replacement,
  576 + 'length' => $length,
  577 + 'quote' => $quote
  578 + )
  579 + );
  580 + }
  581 + }
  582 + }
  583 + // pass the modified arguments
  584 + if (!empty($expression)) $this->_add($expression, $replacement, $length);
  585 + else $this->_add('/^$/', $replacement, $length);
  586 + }
  587 +
  588 + public function exec($string) {
  589 + // execute the global replacement
  590 + $this->_escaped = array();
  591 +
  592 + // simulate the _patterns.toSTring of Dean
  593 + $regexp = '/';
  594 + foreach ($this->_patterns as $reg) {
  595 + $regexp .= '(' . substr($reg[self::EXPRESSION], 1, -1) . ')|';
  596 + }
  597 + $regexp = substr($regexp, 0, -1) . '/';
  598 + $regexp .= ($this->ignoreCase) ? 'i' : '';
  599 +
  600 + $string = $this->_escape($string, $this->escapeChar);
  601 + $string = preg_replace_callback(
  602 + $regexp,
  603 + array(
  604 + &$this,
  605 + '_replacement'
  606 + ),
  607 + $string
  608 + );
  609 + $string = $this->_unescape($string, $this->escapeChar);
  610 +
  611 + return preg_replace($this->DELETED, '', $string);
  612 + }
  613 +
  614 + public function reset() {
  615 + // clear the patterns collection so that this object may be re-used
  616 + $this->_patterns = array();
  617 + }
  618 +
  619 + // private
  620 + private $_escaped = array(); // escaped characters
  621 + private $_patterns = array(); // patterns stored by index
  622 +
  623 + // create and add a new pattern to the patterns collection
  624 + private function _add() {
  625 + $arguments = func_get_args();
  626 + $this->_patterns[] = $arguments;
  627 + }
  628 +
  629 + // this is the global replace function (it's quite complicated)
  630 + private function _replacement($arguments) {
  631 + if (empty($arguments)) return '';
  632 +
  633 + $i = 1; $j = 0;
  634 + // loop through the patterns
  635 + while (isset($this->_patterns[$j])) {
  636 + $pattern = $this->_patterns[$j++];
  637 + // do we have a result?
  638 + if (isset($arguments[$i]) && ($arguments[$i] != '')) {
  639 + $replacement = $pattern[self::REPLACEMENT];
  640 +
  641 + if (is_array($replacement) && isset($replacement['fn'])) {
  642 +
  643 + if (isset($replacement['data'])) $this->buffer = $replacement['data'];
  644 + return call_user_func(array(&$this, $replacement['fn']), $arguments, $i);
  645 +
  646 + } elseif (is_int($replacement)) {
  647 + return $arguments[$replacement + $i];
  648 +
  649 + }
  650 + $delete = ($this->escapeChar == '' ||
  651 + strpos($arguments[$i], $this->escapeChar) === false)
  652 + ? '' : "\x01" . $arguments[$i] . "\x01";
  653 + return $delete . $replacement;
  654 +
  655 + // skip over references to sub-expressions
  656 + } else {
  657 + $i += $pattern[self::LENGTH];
  658 + }
  659 + }
  660 + }
  661 +
  662 + private function _backReferences($match, $offset) {
  663 + $replacement = $this->buffer['replacement'];
  664 + $quote = $this->buffer['quote'];
  665 + $i = $this->buffer['length'];
  666 + while ($i) {
  667 + $replacement = str_replace('$'.$i--, $match[$offset + $i], $replacement);
  668 + }
  669 + return $replacement;
  670 + }
  671 +
  672 + private function _replace_name($match, $offset){
  673 + $length = strlen($match[$offset + 2]);
  674 + $start = $length - max($length - strlen($match[$offset + 3]), 0);
  675 + return substr($match[$offset + 1], $start, $length) . $match[$offset + 4];
  676 + }
  677 +
  678 + private function _replace_encoded($match, $offset) {
  679 + return $this->buffer[$match[$offset]];
  680 + }
  681 +
  682 +
  683 + // php : we cannot pass additional data to preg_replace_callback,
  684 + // and we cannot use &$this in create_function, so let's go to lower level
  685 + private $buffer;
  686 +
  687 + // encode escaped characters
  688 + private function _escape($string, $escapeChar) {
  689 + if ($escapeChar) {
  690 + $this->buffer = $escapeChar;
  691 + return preg_replace_callback(
  692 + '/\\' . $escapeChar . '(.)' .'/',
  693 + array(&$this, '_escapeBis'),
  694 + $string
  695 + );
  696 +
  697 + } else {
  698 + return $string;
  699 + }
  700 + }
  701 + private function _escapeBis($match) {
  702 + $this->_escaped[] = $match[1];
  703 + return $this->buffer;
  704 + }
  705 +
  706 + // decode escaped characters
  707 + private function _unescape($string, $escapeChar) {
  708 + if ($escapeChar) {
  709 + $regexp = '/'.'\\'.$escapeChar.'/';
  710 + $this->buffer = array('escapeChar'=> $escapeChar, 'i' => 0);
  711 + return preg_replace_callback
  712 + (
  713 + $regexp,
  714 + array(&$this, '_unescapeBis'),
  715 + $string
  716 + );
  717 +
  718 + } else {
  719 + return $string;
  720 + }
  721 + }
  722 + private function _unescapeBis() {
  723 + if (!empty($this->_escaped[$this->buffer['i']])) {
  724 + $temp = $this->_escaped[$this->buffer['i']];
  725 + } else {
  726 + $temp = '';
  727 + }
  728 + $this->buffer['i']++;
  729 + return $this->buffer['escapeChar'] . $temp;
  730 + }
  731 +
  732 + private function _internalEscape($string) {
  733 + return preg_replace($this->ESCAPE, '', $string);
  734 + }
  735 +}
  736 +?>
... ...
pacotes/packer/class.JavaScriptPacker.php4 0 → 100644
... ... @@ -0,0 +1,732 @@
  1 +<?php
  2 +/* 7 December 2006. version 1.0
  3 + *
  4 + * This is the php version of the Dean Edwards JavaScript 's Packer,
  5 + * Based on :
  6 + *
  7 + * ParseMaster, version 1.0.2 (2005-08-19) Copyright 2005, Dean Edwards
  8 + * a multi-pattern parser.
  9 + * KNOWN BUG: erroneous behavior when using escapeChar with a replacement
  10 + * value that is a function
  11 + *
  12 + * packer, version 2.0.2 (2005-08-19) Copyright 2004-2005, Dean Edwards
  13 + *
  14 + * License: http://creativecommons.org/licenses/LGPL/2.1/
  15 + *
  16 + * Ported to PHP by Nicolas Martin.
  17 + * modified by Mark Fabrizio Jr. to work with php 4
  18 + *
  19 + * ----------------------------------------------------------------------
  20 + *
  21 + * examples of usage :
  22 + * $myPacker = new JavaScriptPacker($script, 62, true, false);
  23 + * $packed = $myPacker->pack();
  24 + *
  25 + * or
  26 + *
  27 + * $myPacker = new JavaScriptPacker($script, 'Normal', true, false);
  28 + * $packed = $myPacker->pack();
  29 + *
  30 + * or (default values)
  31 + *
  32 + * $myPacker = new JavaScriptPacker($script);
  33 + * $packed = $myPacker->pack();
  34 + *
  35 + *
  36 + * params of the constructor :
  37 + * $script: the JavaScript to pack, string.
  38 + * $encoding: level of encoding, int or string :
  39 + * 0,10,62,95 or 'None', 'Numeric', 'Normal', 'High ASCII'.
  40 + * default: 62.
  41 + * $fastDecode: include the fast decoder in the packed result, boolean.
  42 + * default : true.
  43 + * $specialChars: if you are flagged your private and local variables
  44 + * in the script, boolean.
  45 + * default: false.
  46 + *
  47 + * The pack() method return the compressed JavasScript, as a string.
  48 + *
  49 + * see http://dean.edwards.name/packer/usage/ for more information.
  50 + *
  51 + * Notes :
  52 + * # [del]need PHP 5 . Tested with PHP 5.1.2[/del]
  53 + * this is a modified version for PHP 4
  54 + *
  55 + * # The packed result may be different than with the Dean Edwards
  56 + * version, but with the same length. The reason is that the PHP
  57 + * function usort to sort array don't necessarily preserve the
  58 + * original order of two equal member. The Javascript sort function
  59 + * in fact preserve this order (but that's not require by the
  60 + * ECMAScript standard). So the encoded keywords order can be
  61 + * different in the two results.
  62 + *
  63 + * # Be careful with the 'High ASCII' Level encoding if you use
  64 + * UTF-8 in your files...
  65 + */
  66 +
  67 + /*
  68 + * modified by Mark Fabrizio Jr. to work with php 4
  69 + */
  70 +
  71 +
  72 +class JavaScriptPacker {
  73 + var $IGNORE = '$1';
  74 +
  75 + // validate parameters
  76 + var $_script = '';
  77 + var $_encoding = 62;
  78 + var $_fastDecode = true;
  79 + var $_specialChars = false;
  80 +
  81 + var $LITERAL_ENCODING = array(
  82 + 'None' => 0,
  83 + 'Numeric' => 10,
  84 + 'Normal' => 62,
  85 + 'High ASCII' => 95
  86 + );
  87 +
  88 + function JavaScriptPacker($_script, $_encoding = 62, $_fastDecode = true, $_specialChars = false)
  89 + {
  90 + $this->_script = $_script . "\n";
  91 + if (array_key_exists($_encoding, $this->LITERAL_ENCODING))
  92 + $_encoding = $this->LITERAL_ENCODING[$_encoding];
  93 + $this->_encoding = min((int)$_encoding, 95);
  94 + $this->_fastDecode = $_fastDecode;
  95 + $this->_specialChars = $_specialChars;
  96 + }
  97 +
  98 + function pack() {
  99 + $this->_addParser('_basicCompression');
  100 + if ($this->_specialChars)
  101 + $this->_addParser('_encodeSpecialChars');
  102 + if ($this->_encoding)
  103 + $this->_addParser('_encodeKeywords');
  104 +
  105 + // go!
  106 + return $this->_pack($this->_script);
  107 + }
  108 +
  109 + // apply all parsing routines
  110 + function _pack($script) {
  111 + for ($i = 0; isset($this->_parsers[$i]); $i++) {
  112 + $script = call_user_func(array(&$this,$this->_parsers[$i]), $script);
  113 + }
  114 + return $script;
  115 + }
  116 +
  117 + // keep a list of parsing functions, they'll be executed all at once
  118 + var $_parsers = array();
  119 + function _addParser($parser) {
  120 + $this->_parsers[] = $parser;
  121 + }
  122 +
  123 + // zero encoding - just removal of white space and comments
  124 + function _basicCompression($script) {
  125 + $parser = new ParseMaster();
  126 + // make safe
  127 + $parser->escapeChar = '\\';
  128 + // protect strings
  129 + $parser->add('/\'[^\'\\n\\r]*\'/',$this->IGNORE);
  130 + $parser->add('/"[^"\\n\\r]*"/', $this->IGNORE);
  131 + // remove comments
  132 + $parser->add('/\\/\\/[^\\n\\r]*[\\n\\r]/', ' ');
  133 + $parser->add('/\\/\\*[^*]*\\*+([^\\/][^*]*\\*+)*\\//', ' ');
  134 + // protect regular expressions
  135 + $parser->add('/\\s+(\\/[^\\/\\n\\r\\*][^\\/\\n\\r]*\\/g?i?)/', '$2'); // IGNORE
  136 + $parser->add('/[^\\w\\x24\\/\'"*)\\?:]\\/[^\\/\\n\\r\\*][^\\/\\n\\r]*\\/g?i?/', $this->IGNORE);
  137 + // remove: ;;; doSomething();
  138 + if ($this->_specialChars) $parser->add('/;;;[^\\n\\r]+[\\n\\r]/');
  139 + // remove redundant semi-colons
  140 + $parser->add('/\\(;;\\)/', $this->IGNORE); // protect for (;;) loops
  141 + $parser->add('/;+\\s*([};])/', '$2');
  142 + // apply the above
  143 + $script = $parser->exec($script);
  144 +
  145 + // remove white-space
  146 + $parser->add('/(\\b|\\x24)\\s+(\\b|\\x24)/', '$2 $3');
  147 + $parser->add('/([+\\-])\\s+([+\\-])/', '$2 $3');
  148 + $parser->add('/\\s+/', '');
  149 + // done
  150 + return $parser->exec($script);
  151 + }
  152 +
  153 + function _encodeSpecialChars($script) {
  154 + $parser = new ParseMaster();
  155 + // replace: $name -> n, $$name -> na
  156 + $parser->add('/((\\x24+)([a-zA-Z$_]+))(\\d*)/',
  157 + array('fn' => '_replace_name')
  158 + );
  159 + // replace: _name -> _0, double-underscore (__name) is ignored
  160 + $regexp = '/\\b_[A-Za-z\\d]\\w*/';
  161 + // build the word list
  162 + $keywords = $this->_analyze($script, $regexp, '_encodePrivate');
  163 + // quick ref
  164 + $encoded = $keywords['encoded'];
  165 +
  166 + $parser->add($regexp,
  167 + array(
  168 + 'fn' => '_replace_encoded',
  169 + 'data' => $encoded
  170 + )
  171 + );
  172 + return $parser->exec($script);
  173 + }
  174 +
  175 + function _encodeKeywords($script) {
  176 + // escape high-ascii values already in the script (i.e. in strings)
  177 + if ($this->_encoding > 62)
  178 + $script = $this->_escape95($script);
  179 + // create the parser
  180 + $parser = new ParseMaster();
  181 + $encode = $this->_getEncoder($this->_encoding);
  182 + // for high-ascii, don't encode single character low-ascii
  183 + $regexp = ($this->_encoding > 62) ? '/\\w\\w+/' : '/\\w+/';
  184 + // build the word list
  185 + $keywords = $this->_analyze($script, $regexp, $encode);
  186 + $encoded = $keywords['encoded'];
  187 +
  188 + // encode
  189 + $parser->add($regexp,
  190 + array(
  191 + 'fn' => '_replace_encoded',
  192 + 'data' => $encoded
  193 + )
  194 + );
  195 + if (empty($script)) return $script;
  196 + else {
  197 + //$res = $parser->exec($script);
  198 + //$res = $this->_bootStrap($res, $keywords);
  199 + //return $res;
  200 + return $this->_bootStrap($parser->exec($script), $keywords);
  201 + }
  202 + }
  203 +
  204 + function _analyze($script, $regexp, $encode) {
  205 + // analyse
  206 + // retreive all words in the script
  207 + $all = array();
  208 + preg_match_all($regexp, $script, $all);
  209 + $_sorted = array(); // list of words sorted by frequency
  210 + $_encoded = array(); // dictionary of word->encoding
  211 + $_protected = array(); // instances of "protected" words
  212 + $all = $all[0]; // simulate the javascript comportement of global match
  213 + if (!empty($all)) {
  214 + $unsorted = array(); // same list, not sorted
  215 + $protected = array(); // "protected" words (dictionary of word->"word")
  216 + $value = array(); // dictionary of charCode->encoding (eg. 256->ff)
  217 + $this->_count = array(); // word->count
  218 + $i = count($all); $j = 0; //$word = null;
  219 + // count the occurrences - used for sorting later
  220 + do {
  221 + --$i;
  222 + $word = '$' . $all[$i];
  223 + if (!isset($this->_count[$word])) {
  224 + $this->_count[$word] = 0;
  225 + $unsorted[$j] = $word;
  226 + // make a dictionary of all of the protected words in this script
  227 + // these are words that might be mistaken for encoding
  228 + //if (is_string($encode) && method_exists($this, $encode))
  229 + $values[$j] = call_user_func(array(&$this, $encode), $j);
  230 + $protected['$' . $values[$j]] = $j++;
  231 + }
  232 + // increment the word counter
  233 + $this->_count[$word]++;
  234 + } while ($i > 0);
  235 + // prepare to sort the word list, first we must protect
  236 + // words that are also used as codes. we assign them a code
  237 + // equivalent to the word itself.
  238 + // e.g. if "do" falls within our encoding range
  239 + // then we store keywords["do"] = "do";
  240 + // this avoids problems when decoding
  241 + $i = count($unsorted);
  242 + do {
  243 + $word = $unsorted[--$i];
  244 + if (isset($protected[$word]) /*!= null*/) {
  245 + $_sorted[$protected[$word]] = substr($word, 1);
  246 + $_protected[$protected[$word]] = true;
  247 + $this->_count[$word] = 0;
  248 + }
  249 + } while ($i);
  250 +
  251 + // sort the words by frequency
  252 + // Note: the javascript and php version of sort can be different :
  253 + // in php manual, usort :
  254 + // " If two members compare as equal,
  255 + // their order in the sorted array is undefined."
  256 + // so the final packed script is different of the Dean's javascript version
  257 + // but equivalent.
  258 + // the ECMAscript standard does not guarantee this behaviour,
  259 + // and thus not all browsers (e.g. Mozilla versions dating back to at
  260 + // least 2003) respect this.
  261 + usort($unsorted, array(&$this, '_sortWords'));
  262 + $j = 0;
  263 + // because there are "protected" words in the list
  264 + // we must add the sorted words around them
  265 + do {
  266 + if (!isset($_sorted[$i]))
  267 + $_sorted[$i] = substr($unsorted[$j++], 1);
  268 + $_encoded[$_sorted[$i]] = $values[$i];
  269 + } while (++$i < count($unsorted));
  270 + }
  271 + return array(
  272 + 'sorted' => $_sorted,
  273 + 'encoded' => $_encoded,
  274 + 'protected' => $_protected);
  275 + }
  276 +
  277 + var $_count = array();
  278 + function _sortWords($match1, $match2) {
  279 + return $this->_count[$match2] - $this->_count[$match1];
  280 + }
  281 +
  282 + // build the boot function used for loading and decoding
  283 + function _bootStrap($packed, $keywords) {
  284 + $ENCODE = $this->_safeRegExp('$encode\\($count\\)');
  285 +
  286 + // $packed: the packed script
  287 + $packed = "'" . $this->_escape($packed) . "'";
  288 +
  289 + // $ascii: base for encoding
  290 + $ascii = min(count($keywords['sorted']), $this->_encoding);
  291 + if ($ascii == 0) $ascii = 1;
  292 +
  293 + // $count: number of words contained in the script
  294 + $count = count($keywords['sorted']);
  295 +
  296 + // $keywords: list of words contained in the script
  297 + foreach ($keywords['protected'] as $i=>$value) {
  298 + $keywords['sorted'][$i] = '';
  299 + }
  300 + // convert from a string to an array
  301 + ksort($keywords['sorted']);
  302 + $keywords = "'" . implode('|',$keywords['sorted']) . "'.split('|')";
  303 +
  304 + $encode = ($this->_encoding > 62) ? '_encode95' : $this->_getEncoder($ascii);
  305 + $encode = $this->_getJSFunction($encode);
  306 + $encode = preg_replace('/_encoding/','$ascii', $encode);
  307 + $encode = preg_replace('/arguments\\.callee/','$encode', $encode);
  308 + $inline = '\\$count' . ($ascii > 10 ? '.toString(\\$ascii)' : '');
  309 +
  310 + // $decode: code snippet to speed up decoding
  311 + if ($this->_fastDecode) {
  312 + // create the decoder
  313 + $decode = $this->_getJSFunction('_decodeBody');
  314 + if ($this->_encoding > 62)
  315 + $decode = preg_replace('/\\\\w/', '[\\xa1-\\xff]', $decode);
  316 + // perform the encoding inline for lower ascii values
  317 + elseif ($ascii < 36)
  318 + $decode = preg_replace($ENCODE, $inline, $decode);
  319 + // special case: when $count==0 there are no keywords. I want to keep
  320 + // the basic shape of the unpacking funcion so i'll frig the code...
  321 + if ($count == 0)
  322 + $decode = preg_replace($this->_safeRegExp('($count)\\s*=\\s*1'), '$1=0', $decode, 1);
  323 + }
  324 +
  325 + // boot function
  326 + $unpack = $this->_getJSFunction('_unpack');
  327 + if ($this->_fastDecode) {
  328 + // insert the decoder
  329 + $this->buffer = $decode;
  330 + $unpack = preg_replace_callback('/\\{/', array(&$this, '_insertFastDecode'), $unpack, 1);
  331 + }
  332 + $unpack = preg_replace('/"/', "'", $unpack);
  333 + if ($this->_encoding > 62) { // high-ascii
  334 + // get rid of the word-boundaries for regexp matches
  335 + $unpack = preg_replace('/\'\\\\\\\\b\'\s*\\+|\\+\s*\'\\\\\\\\b\'/', '', $unpack);
  336 + }
  337 + if ($ascii > 36 || $this->_encoding > 62 || $this->_fastDecode) {
  338 + // insert the encode function
  339 + $this->buffer = $encode;
  340 + $unpack = preg_replace_callback('/\\{/', array(&$this, '_insertFastEncode'), $unpack, 1);
  341 + } else {
  342 + // perform the encoding inline
  343 + $unpack = preg_replace($ENCODE, $inline, $unpack);
  344 + }
  345 + // pack the boot function too
  346 + $unpackPacker = new JavaScriptPacker($unpack, 0, false, true);
  347 + $unpack = $unpackPacker->pack();
  348 +
  349 + // arguments
  350 + $params = array($packed, $ascii, $count, $keywords);
  351 + if ($this->_fastDecode) {
  352 + $params[] = 0;
  353 + $params[] = '{}';
  354 + }
  355 + $params = implode(',', $params);
  356 +
  357 + // the whole thing
  358 + return 'eval(' . $unpack . '(' . $params . "))\n";
  359 + }
  360 +
  361 + var $buffer;
  362 + function _insertFastDecode($match) {
  363 + return '{' . $this->buffer . ';';
  364 + }
  365 + function _insertFastEncode($match) {
  366 + return '{$encode=' . $this->buffer . ';';
  367 + }
  368 +
  369 + // mmm.. ..which one do i need ??
  370 + function _getEncoder($ascii) {
  371 + return $ascii > 10 ? $ascii > 36 ? $ascii > 62 ?
  372 + '_encode95' : '_encode62' : '_encode36' : '_encode10';
  373 + }
  374 +
  375 + // zero encoding
  376 + // characters: 0123456789
  377 + function _encode10($charCode) {
  378 + return $charCode;
  379 + }
  380 +
  381 + // inherent base36 support
  382 + // characters: 0123456789abcdefghijklmnopqrstuvwxyz
  383 + function _encode36($charCode) {
  384 + return base_convert($charCode, 10, 36);
  385 + }
  386 +
  387 + // hitch a ride on base36 and add the upper case alpha characters
  388 + // characters: 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
  389 + function _encode62($charCode) {
  390 + $res = '';
  391 + if ($charCode >= $this->_encoding) {
  392 + $res = $this->_encode62((int)($charCode / $this->_encoding));
  393 + }
  394 + $charCode = $charCode % $this->_encoding;
  395 +
  396 + if ($charCode > 35)
  397 + return $res . chr($charCode + 29);
  398 + else
  399 + return $res . base_convert($charCode, 10, 36);
  400 + }
  401 +
  402 + // use high-ascii values
  403 + // characters: ¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÃ?ÂÃÄÅÆÇÈÉÊËÌÃ?ÎÃ?Ã?ÑÒÓÔÕÖרÙÚÛÜÃ?Þßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþ
  404 + function _encode95($charCode) {
  405 + $res = '';
  406 + if ($charCode >= $this->_encoding)
  407 + $res = $this->_encode95($charCode / $this->_encoding);
  408 +
  409 + return $res . chr(($charCode % $this->_encoding) + 161);
  410 + }
  411 +
  412 + function _safeRegExp($string) {
  413 + return '/'.preg_replace('/\$/', '\\\$', $string).'/';
  414 + }
  415 +
  416 + function _encodePrivate($charCode) {
  417 + return "_" . $charCode;
  418 + }
  419 +
  420 + // protect characters used by the parser
  421 + function _escape($script) {
  422 + return preg_replace('/([\\\\\'])/', '\\\$1', $script);
  423 + }
  424 +
  425 + // protect high-ascii characters already in the script
  426 + function _escape95($script) {
  427 + return preg_replace_callback(
  428 + '/[\\xa1-\\xff]/',
  429 + array(&$this, '_escape95Bis'),
  430 + $script
  431 + );
  432 + }
  433 + function _escape95Bis($match) {
  434 + return '\x'.((string)dechex(ord($match)));
  435 + }
  436 +
  437 +
  438 + function _getJSFunction($aName) {
  439 + $func = 'JSFUNCTION'.$aName;
  440 + if (isset($this->$func)){
  441 + return $this->$func;
  442 + }
  443 + else
  444 + return '';
  445 + }
  446 +
  447 + // JavaScript Functions used.
  448 + // Note : In Dean's version, these functions are converted
  449 + // with 'String(aFunctionName);'.
  450 + // This internal conversion complete the original code, ex :
  451 + // 'while (aBool) anAction();' is converted to
  452 + // 'while (aBool) { anAction(); }'.
  453 + // The JavaScript functions below are corrected.
  454 +
  455 + // unpacking function - this is the boot strap function
  456 + // data extracted from this packing routine is passed to
  457 + // this function when decoded in the target
  458 + // NOTE ! : without the ';' final.
  459 + var $JSFUNCTION_unpack = 'function($packed, $ascii, $count, $keywords, $encode, $decode) {
  460 + while ($count--) {
  461 + if ($keywords[$count]) {
  462 + $packed = $packed.replace(new RegExp(\'\\\\b\' + $encode($count) + \'\\\\b\', \'g\'), $keywords[$count]);
  463 + }
  464 + }
  465 + return $packed;
  466 +}';
  467 +/*
  468 +'function($packed, $ascii, $count, $keywords, $encode, $decode) {
  469 + while ($count--)
  470 + if ($keywords[$count])
  471 + $packed = $packed.replace(new RegExp(\'\\\\b\' + $encode($count) + \'\\\\b\', \'g\'), $keywords[$count]);
  472 + return $packed;
  473 +}';
  474 +*/
  475 +
  476 + // code-snippet inserted into the unpacker to speed up decoding
  477 + var $JSFUNCTION_decodeBody = ' if (!\'\'.replace(/^/, String)) {
  478 + // decode all the values we need
  479 + while ($count--) {
  480 + $decode[$encode($count)] = $keywords[$count] || $encode($count);
  481 + }
  482 + // global replacement function
  483 + $keywords = [function ($encoded) {return $decode[$encoded]}];
  484 + // generic match
  485 + $encode = function () {return \'\\\\w+\'};
  486 + // reset the loop counter - we are now doing a global replace
  487 + $count = 1;
  488 + }
  489 +';
  490 +//};
  491 +/*
  492 +' if (!\'\'.replace(/^/, String)) {
  493 + // decode all the values we need
  494 + while ($count--) $decode[$encode($count)] = $keywords[$count] || $encode($count);
  495 + // global replacement function
  496 + $keywords = [function ($encoded) {return $decode[$encoded]}];
  497 + // generic match
  498 + $encode = function () {return\'\\\\w+\'};
  499 + // reset the loop counter - we are now doing a global replace
  500 + $count = 1;
  501 + }';
  502 +*/
  503 +
  504 + // zero encoding
  505 + // characters: 0123456789
  506 + var $JSFUNCTION_encode10 = 'function($charCode) {
  507 + return $charCode;
  508 +}';//;';
  509 +
  510 + // inherent base36 support
  511 + // characters: 0123456789abcdefghijklmnopqrstuvwxyz
  512 + var $JSFUNCTION_encode36 = 'function($charCode) {
  513 + return $charCode.toString(36);
  514 +}';//;';
  515 +
  516 + // hitch a ride on base36 and add the upper case alpha characters
  517 + // characters: 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
  518 + var $JSFUNCTION_encode62 = 'function($charCode) {
  519 + return ($charCode < _encoding ? \'\' : arguments.callee(parseInt($charCode / _encoding))) +
  520 + (($charCode = $charCode % _encoding) > 35 ? String.fromCharCode($charCode + 29) : $charCode.toString(36));
  521 +}';
  522 +
  523 + // use high-ascii values
  524 + // characters: ¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÃ?ÂÃÄÅÆÇÈÉÊËÌÃ?ÎÃ?Ã?ÑÒÓÔÕÖרÙÚÛÜÃ?Þßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþ
  525 + var $JSFUNCTION_encode95 = 'function($charCode) {
  526 + return ($charCode < _encoding ? \'\' : arguments.callee($charCode / _encoding)) +
  527 + String.fromCharCode($charCode % _encoding + 161);
  528 +}';
  529 +
  530 +}
  531 +
  532 +
  533 +class ParseMaster {
  534 + var $ignoreCase = false;
  535 + var $escapeChar = '';
  536 +
  537 + // constants
  538 + var $EXPRESSION = 0;
  539 + var $REPLACEMENT = 1;
  540 + var $LENGTH = 2;
  541 +
  542 + // used to determine nesting levels
  543 + var $GROUPS = '/\\(/';//g
  544 + var $SUB_REPLACE = '/\\$\\d/';
  545 + var $INDEXED = '/^\\$\\d+$/';
  546 + var $TRIM = '/([\'"])\\1\\.(.*)\\.\\1\\1$/';
  547 + var $ESCAPE = '/\\\./';//g
  548 + var $QUOTE = '/\'/';
  549 + var $DELETED = '/\\x01[^\\x01]*\\x01/';//g
  550 +
  551 + function add($expression, $replacement = '') {
  552 + // count the number of sub-expressions
  553 + // - add one because each pattern is itself a sub-expression
  554 + $length = 1 + preg_match_all($this->GROUPS, $this->_internalEscape((string)$expression), $out);
  555 +
  556 + // treat only strings $replacement
  557 + if (is_string($replacement)) {
  558 + // does the pattern deal with sub-expressions?
  559 + if (preg_match($this->SUB_REPLACE, $replacement)) {
  560 + // a simple lookup? (e.g. "$2")
  561 + if (preg_match($this->INDEXED, $replacement)) {
  562 + // store the index (used for fast retrieval of matched strings)
  563 + $replacement = (int)(substr($replacement, 1)) - 1;
  564 + } else { // a complicated lookup (e.g. "Hello $2 $1")
  565 + // build a function to do the lookup
  566 + $quote = preg_match($this->QUOTE, $this->_internalEscape($replacement))
  567 + ? '"' : "'";
  568 + $replacement = array(
  569 + 'fn' => '_backReferences',
  570 + 'data' => array(
  571 + 'replacement' => $replacement,
  572 + 'length' => $length,
  573 + 'quote' => $quote
  574 + )
  575 + );
  576 + }
  577 + }
  578 + }
  579 + // pass the modified arguments
  580 + if (!empty($expression)) $this->_add($expression, $replacement, $length);
  581 + else $this->_add('/^$/', $replacement, $length);
  582 + }
  583 +
  584 + function exec($string) {
  585 + // execute the global replacement
  586 + $this->_escaped = array();
  587 +
  588 + // simulate the _patterns.toSTring of Dean
  589 + $regexp = '/';
  590 + foreach ($this->_patterns as $reg) {
  591 + $regexp .= '(' . substr($reg[$this->EXPRESSION], 1, -1) . ')|';
  592 + }
  593 + $regexp = substr($regexp, 0, -1) . '/';
  594 + $regexp .= ($this->ignoreCase) ? 'i' : '';
  595 +
  596 + $string = $this->_escape($string, $this->escapeChar);
  597 + $string = preg_replace_callback(
  598 + $regexp,
  599 + array(
  600 + &$this,
  601 + '_replacement'
  602 + ),
  603 + $string
  604 + );
  605 + $string = $this->_unescape($string, $this->escapeChar);
  606 +
  607 + return preg_replace($this->DELETED, '', $string);
  608 + }
  609 +
  610 + function reset() {
  611 + // clear the patterns collection so that this object may be re-used
  612 + $this->_patterns = array();
  613 + }
  614 +
  615 + // private
  616 + var $_escaped = array(); // escaped characters
  617 + var $_patterns = array(); // patterns stored by index
  618 +
  619 + // create and add a new pattern to the patterns collection
  620 + function _add() {
  621 + $arguments = func_get_args();
  622 + $this->_patterns[] = $arguments;
  623 + }
  624 +
  625 + // this is the global replace function (it's quite complicated)
  626 + function _replacement($arguments) {
  627 + if (empty($arguments)) return '';
  628 +
  629 + $i = 1; $j = 0;
  630 + // loop through the patterns
  631 + while (isset($this->_patterns[$j])) {
  632 + $pattern = $this->_patterns[$j++];
  633 + // do we have a result?
  634 + if (isset($arguments[$i]) && ($arguments[$i] != '')) {
  635 + $replacement = $pattern[$this->REPLACEMENT];
  636 +
  637 + if (is_array($replacement) && isset($replacement['fn'])) {
  638 +
  639 + if (isset($replacement['data'])) $this->buffer = $replacement['data'];
  640 + return call_user_func(array(&$this, $replacement['fn']), $arguments, $i);
  641 +
  642 + } elseif (is_int($replacement)) {
  643 + return $arguments[$replacement + $i];
  644 +
  645 + }
  646 + $delete = ($this->escapeChar == '' ||
  647 + strpos($arguments[$i], $this->escapeChar) === false)
  648 + ? '' : "\x01" . $arguments[$i] . "\x01";
  649 + return $delete . $replacement;
  650 +
  651 + // skip over references to sub-expressions
  652 + } else {
  653 + $i += $pattern[$this->LENGTH];
  654 + }
  655 + }
  656 + }
  657 +
  658 + function _backReferences($match, $offset) {
  659 + $replacement = $this->buffer['replacement'];
  660 + $quote = $this->buffer['quote'];
  661 + $i = $this->buffer['length'];
  662 + while ($i) {
  663 + $replacement = str_replace('$'.$i--, $match[$offset + $i], $replacement);
  664 + }
  665 + return $replacement;
  666 + }
  667 +
  668 + function _replace_name($match, $offset){
  669 + $length = strlen($match[$offset + 2]);
  670 + $start = $length - max($length - strlen($match[$offset + 3]), 0);
  671 + return substr($match[$offset + 1], $start, $length) . $match[$offset + 4];
  672 + }
  673 +
  674 + function _replace_encoded($match, $offset) {
  675 + return $this->buffer[$match[$offset]];
  676 + }
  677 +
  678 +
  679 + // php : we cannot pass additional data to preg_replace_callback,
  680 + // and we cannot use &$this in create_function, so let's go to lower level
  681 + var $buffer;
  682 +
  683 + // encode escaped characters
  684 + function _escape($string, $escapeChar) {
  685 + if ($escapeChar) {
  686 + $this->buffer = $escapeChar;
  687 + return preg_replace_callback(
  688 + '/\\' . $escapeChar . '(.)' .'/',
  689 + array(&$this, '_escapeBis'),
  690 + $string
  691 + );
  692 +
  693 + } else {
  694 + return $string;
  695 + }
  696 + }
  697 + function _escapeBis($match) {
  698 + $this->_escaped[] = $match[1];
  699 + return $this->buffer;
  700 + }
  701 +
  702 + // decode escaped characters
  703 + function _unescape($string, $escapeChar) {
  704 + if ($escapeChar) {
  705 + $regexp = '/'.'\\'.$escapeChar.'/';
  706 + $this->buffer = array('escapeChar'=> $escapeChar, 'i' => 0);
  707 + return preg_replace_callback
  708 + (
  709 + $regexp,
  710 + array(&$this, '_unescapeBis'),
  711 + $string
  712 + );
  713 +
  714 + } else {
  715 + return $string;
  716 + }
  717 + }
  718 + function _unescapeBis() {
  719 + if (!empty($this->_escaped[$this->buffer['i']])) {
  720 + $temp = $this->_escaped[$this->buffer['i']];
  721 + } else {
  722 + $temp = '';
  723 + }
  724 + $this->buffer['i']++;
  725 + return $this->buffer['escapeChar'] . $temp;
  726 + }
  727 +
  728 + function _internalEscape($string) {
  729 + return preg_replace($this->ESCAPE, '', $string);
  730 + }
  731 +}
  732 +?>
... ...
pacotes/packer/example-file.php 0 → 100644
... ... @@ -0,0 +1,34 @@
  1 +<?php
  2 +// you can pass this script to PHP CLI to convert your file.
  3 +
  4 +// adapt these 2 paths to your files.
  5 +$src = 'myScript-src.js';
  6 +$out = 'myScript.js';
  7 +
  8 +// or uncomment these lines to use the argc and argv passed by CLI :
  9 +/*
  10 +if ($argc >= 3) {
  11 + $src = $argv[1];
  12 + $out = $argv[2];
  13 +} else {
  14 + echo 'you must specify a source file and a result filename',"\n";
  15 + echo 'example :', "\n", 'php example-file.php myScript-src.js myPackedScript.js',"\n";
  16 + return;
  17 +}
  18 +*/
  19 +
  20 +require 'class.JavaScriptPacker.php';
  21 +
  22 +$script = file_get_contents($src);
  23 +
  24 +$t1 = microtime(true);
  25 +
  26 +$packer = new JavaScriptPacker($script, 'Normal', true, false);
  27 +$packed = $packer->pack();
  28 +
  29 +$t2 = microtime(true);
  30 +$time = sprintf('%.4f', ($t2 - $t1) );
  31 +echo 'script ', $src, ' packed in ' , $out, ', in ', $time, ' s.', "\n";
  32 +
  33 +file_put_contents($out, $packed);
  34 +?>
... ...
pacotes/packer/example-inline.php 0 → 100644
... ... @@ -0,0 +1,88 @@
  1 +<?php
  2 +
  3 +$treat = false;
  4 +if (isset($_POST['src'])) {
  5 + $script = $_POST['src'];
  6 + if (get_magic_quotes_gpc())
  7 + $script = stripslashes($script);
  8 + $encoding = (int)$_POST['ascii_encoding'];
  9 + $fast_decode = isset($_POST['fast_decode']) && $_POST['fast_decode'];
  10 + $special_char = isset($_POST['special_char'])&& $_POST['special_char'];
  11 +
  12 + require 'class.JavaScriptPacker.php';
  13 + $t1 = microtime(true);
  14 + $packer = new JavaScriptPacker($script, $encoding, $fast_decode, $special_char);
  15 + $packed = $packer->pack();
  16 + $t2 = microtime(true);
  17 +
  18 + $originalLength = strlen($script);
  19 + $packedLength = strlen($packed);
  20 + $ratio = number_format($packedLength / $originalLength, 3);
  21 + $time = sprintf('%.4f', ($t2 - $t1) );
  22 +
  23 + $treat = true;
  24 +}
  25 +?>
  26 +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
  27 + "http://www.w3.org/TR/html4/strict.dtd">
  28 +<html>
  29 +<head>
  30 +<title>JavaScript Packer in PHP</title>
  31 +<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
  32 +<style type="text/css">
  33 +.result {
  34 + border: 1px blue dashed;
  35 + color: black;
  36 + background-color: #e5e5e5;
  37 + padding: 0.2em;
  38 +}
  39 +</style>
  40 +<script type="text/javascript">
  41 +function decode() {
  42 + var packed = document.getElementById('packed');
  43 + eval("packed.value=String" + packed.value.slice(4));
  44 +}
  45 +</script>
  46 +</head>
  47 +<body>
  48 + <form action="<?php echo $_SERVER['PHP_SELF']?>" method="post">
  49 + <div>
  50 + <h3>script to pack:</h3>
  51 + <textarea name="src" id="src" rows="10" cols="80"><?php if($treat) echo htmlspecialchars($script);?></textarea>
  52 + </div>
  53 + <!-- <fieldset> -->
  54 + <div>
  55 + <label for="ascii-encoding">Encoding:</label>
  56 + <select name="ascii_encoding" id="ascii-encoding">
  57 + <option value="0"<?php if ($treat && $encoding == 0) echo ' selected'?>>None</option>
  58 + <option value="10"<?php if ($treat && $encoding == 10) echo ' selected'?>>Numeric</option>
  59 + <option value="62"<?php if (!$treat) echo 'selected';if ($treat && $encoding == 62) echo ' selected';?>>Normal</option>
  60 + <option value="95"<?php if ($treat && $encoding == 95) echo ' selected'?>>High ASCII</option>
  61 + </select>
  62 + <label>
  63 + Fast Decode:
  64 + <input type="checkbox" name="fast_decode" id="fast-decode"<?php if (!$treat || $fast_decode) echo ' checked'?>>
  65 + </label>
  66 + <label>
  67 + Special Characters:
  68 + <input type="checkbox" name="special_char" id="special-char"<?php if ($treat && $special_char) echo ' checked'?>>
  69 + </label>
  70 + <input type="submit" value="Pack">
  71 + </div>
  72 + <!-- </fieldset> -->
  73 + </form>
  74 +
  75 + <?php if ($treat) {?>
  76 + <div id="result">
  77 + <h3>packed result:</h3>
  78 + <textarea id="packed" class="result" rows="10" cols="80" readonly="readonly"><?php echo htmlspecialchars($packed);?></textarea>
  79 + <p>
  80 + compression ratio:
  81 + <?php echo $originalLength, '/', $packedLength, ' = ',$ratio; ?>
  82 + <br>performed in <?php echo $time; ?> s.
  83 + </p>
  84 + <p><button type="button" onclick="decode()">decode</button></p>
  85 + </div>
  86 + <?php };//end if($treat)?>
  87 +</body>
  88 +</html>
... ...
pacotes/packer/readme.txt 0 → 100644
... ... @@ -0,0 +1,12 @@
  1 +PHP 5 :
  2 +use class.JavaScriptPacker.php (Tested under PHP 5.1.2, 5.1.3 et 5.1.4.)
  3 + - usage and comments in source.
  4 + - there is 2 examples in this archive for PHP 5,
  5 + 'example-file.php' and example-inline.php
  6 +
  7 +PHP 4 :
  8 +use class.JavaScriptPacker.php4, it's an adaptation for PHP4.
  9 +The examples in this archive will not work directly with PHP 4, they are
  10 +writed in PHP5, you'll need to adapt them. If some functions are missing in
  11 +PHP 4, you can find their equivalent here :
  12 +http://pear.php.net/package/PHP_Compat
... ...