PluginProbe
WP Coder – Insert & Manage Code Snippets / trunk
WP Coder – Insert & Manage Code Snippets vtrunk
4.5.1 1.1 2.3.1 2.3.2 2.4.1 2.5.1 2.5.2 2.5.3 2.5.4 2.5.5 2.5.6 3.0 3.0.1 3.0.2 3.0.3 3.0.4 3.0.5 3.1 3.1.1 3.2 3.2.1 3.3 3.4 3.5 3.5.1 All 39 releases
← All changes | classes/Optimization/Obfuscator.php +507 -507 3.0trunk View file →
@@ -1,507 +1,507 @@
1 -<?php
2 -
3 -namespace WPCoder\Optimization;
4 -
5 -defined( 'ABSPATH' ) || exit;
6 -
7 -class Obfuscator {
8 - // constants
9 - const IGNORE = '$1';
10 -
11 - // validate parameters
12 - private $_script = '';
13 - private $_encoding = 62;
14 - private $_fastDecode = true;
15 - private $_specialChars = false;
16 -
17 - private $LITERAL_ENCODING = array(
18 - 'None' => 0,
19 - 'Numeric' => 10,
20 - 'Normal' => 62,
21 - 'High ASCII' => 95
22 - );
23 -
24 - public function __construct( $_script, $_encoding = 62, $_fastDecode = true, $_specialChars = false ) {
25 - $this->_script = $_script . "\n";
26 - if ( array_key_exists( $_encoding, $this->LITERAL_ENCODING ) ) {
27 - $_encoding = $this->LITERAL_ENCODING[ $_encoding ];
28 - }
29 - $this->_encoding = min( (int) $_encoding, 95 );
30 - $this->_fastDecode = $_fastDecode;
31 - $this->_specialChars = $_specialChars;
32 - }
33 -
34 - public function pack() {
35 - $this->_addParser( '_basicCompression' );
36 - if ( $this->_specialChars ) {
37 - $this->_addParser( '_encodeSpecialChars' );
38 - }
39 - if ( $this->_encoding ) {
40 - $this->_addParser( '_encodeKeywords' );
41 - }
42 -
43 - // go!
44 - return $this->_pack( $this->_script );
45 - }
46 -
47 - // apply all parsing routines
48 - private function _pack( $script ) {
49 - for ( $i = 0; isset( $this->_parsers[ $i ] ); $i ++ ) {
50 - $script = call_user_func( array( &$this, $this->_parsers[ $i ] ), $script );
51 - }
52 -
53 - return $script;
54 - }
55 -
56 - // keep a list of parsing functions, they'll be executed all at once
57 - private $_parsers = array();
58 -
59 - private function _addParser( $parser ) {
60 - $this->_parsers[] = $parser;
61 - }
62 -
63 - // zero encoding - just removal of white space and comments
64 - private function _basicCompression( $script ) {
65 - $parser = new ParseMaster();
66 - // make safe
67 - $parser->escapeChar = '\\';
68 - // protect strings
69 - $parser->add( '/\'[^\'\\n\\r]*\'/', self::IGNORE );
70 - $parser->add( '/"[^"\\n\\r]*"/', self::IGNORE );
71 - // remove comments
72 - $parser->add( '/\\/\\/[^\\n\\r]*[\\n\\r]/', ' ' );
73 - $parser->add( '/\\/\\*[^*]*\\*+([^\\/][^*]*\\*+)*\\//', ' ' );
74 - // protect regular expressions
75 - $parser->add( '/\\s+(\\/[^\\/\\n\\r\\*][^\\/\\n\\r]*\\/g?i?)/', '$2' ); // IGNORE
76 - $parser->add( '/[^\\w\\x24\\/\'"*)\\?:]\\/[^\\/\\n\\r\\*][^\\/\\n\\r]*\\/g?i?/', self::IGNORE );
77 - // remove: ;;; doSomething();
78 - if ( $this->_specialChars ) {
79 - $parser->add( '/;;;[^\\n\\r]+[\\n\\r]/' );
80 - }
81 - // remove redundant semi-colons
82 - $parser->add( '/\\(;;\\)/', self::IGNORE ); // protect for (;;) loops
83 - $parser->add( '/;+\\s*([};])/', '$2' );
84 - // apply the above
85 - $script = $parser->exec( $script );
86 -
87 - // remove white-space
88 - $parser->add( '/(\\b|\\x24)\\s+(\\b|\\x24)/', '$2 $3' );
89 - $parser->add( '/([+\\-])\\s+([+\\-])/', '$2 $3' );
90 - $parser->add( '/\\s+/', '' );
91 -
92 - // done
93 - return $parser->exec( $script );
94 - }
95 -
96 - private function _encodeSpecialChars( $script ) {
97 - $parser = new ParseMaster();
98 - // replace: $name -> n, $$name -> na
99 - $parser->add(
100 - '/((\\x24+)([a-zA-Z$_]+))(\\d*)/',
101 - array( 'fn' => '_replace_name' )
102 - );
103 - // replace: _name -> _0, double-underscore (__name) is ignored
104 - $regexp = '/\\b_[A-Za-z\\d]\\w*/';
105 - // build the word list
106 - $keywords = $this->_analyze( $script, $regexp, '_encodePrivate' );
107 - // quick ref
108 - $encoded = $keywords['encoded'];
109 -
110 - $parser->add(
111 - $regexp,
112 - array(
113 - 'fn' => '_replace_encoded',
114 - 'data' => $encoded
115 - )
116 - );
117 -
118 - return $parser->exec( $script );
119 - }
120 -
121 - private function _encodeKeywords( $script ) {
122 - // escape high-ascii values already in the script (i.e. in strings)
123 - if ( $this->_encoding > 62 ) {
124 - $script = $this->_escape95( $script );
125 - }
126 - // create the parser
127 - $parser = new ParseMaster();
128 - $encode = $this->_getEncoder( $this->_encoding );
129 - // for high-ascii, don't encode single character low-ascii
130 - $regexp = ( $this->_encoding > 62 ) ? '/\\w\\w+/' : '/\\w+/';
131 - // build the word list
132 - $keywords = $this->_analyze( $script, $regexp, $encode );
133 - $encoded = $keywords['encoded'];
134 -
135 - // encode
136 - $parser->add(
137 - $regexp,
138 - array(
139 - 'fn' => '_replace_encoded',
140 - 'data' => $encoded
141 - )
142 - );
143 - if ( empty( $script ) ) {
144 - return $script;
145 - } else {
146 - //$res = $parser->exec($script);
147 - //$res = $this->_bootStrap($res, $keywords);
148 - //return $res;
149 - return $this->_bootStrap( $parser->exec( $script ), $keywords );
150 - }
151 - }
152 -
153 - private function _analyze( $script, $regexp, $encode ) {
154 - // analyse
155 - // retreive all words in the script
156 - $all = array();
157 - preg_match_all( $regexp, $script, $all );
158 - $_sorted = array(); // list of words sorted by frequency
159 - $_encoded = array(); // dictionary of word->encoding
160 - $_protected = array(); // instances of "protected" words
161 - $all = $all[0]; // simulate the javascript comportement of global match
162 - if ( ! empty( $all ) ) {
163 - $unsorted = array(); // same list, not sorted
164 - $protected = array(); // "protected" words (dictionary of word->"word")
165 - $value = array(); // dictionary of charCode->encoding (eg. 256->ff)
166 - $this->_count = array(); // word->count
167 - $i = count( $all );
168 - $j = 0; //$word = null;
169 - // count the occurrences - used for sorting later
170 - do {
171 - -- $i;
172 - $word = '$' . $all[ $i ];
173 - if ( ! isset( $this->_count[ $word ] ) ) {
174 - $this->_count[ $word ] = 0;
175 - $unsorted[ $j ] = $word;
176 - // make a dictionary of all of the protected words in this script
177 - // these are words that might be mistaken for encoding
178 - //if (is_string($encode) && method_exists($this, $encode))
179 - $values[ $j ] = call_user_func( array( &$this, $encode ), $j );
180 - $protected[ '$' . $values[ $j ] ] = $j ++;
181 - }
182 - // increment the word counter
183 - $this->_count[ $word ] ++;
184 - } while ( $i > 0 );
185 - // prepare to sort the word list, first we must protect
186 - // words that are also used as codes. we assign them a code
187 - // equivalent to the word itself.
188 - // e.g. if "do" falls within our encoding range
189 - // then we store keywords["do"] = "do";
190 - // this avoids problems when decoding
191 - $i = count( $unsorted );
192 - do {
193 - $word = $unsorted[ -- $i ];
194 - if ( isset( $protected[ $word ] ) /*!= null*/ ) {
195 - $_sorted[ $protected[ $word ] ] = substr( $word, 1 );
196 - $_protected[ $protected[ $word ] ] = true;
197 - $this->_count[ $word ] = 0;
198 - }
199 - } while ( $i );
200 -
201 - // sort the words by frequency
202 - // Note: the javascript and php version of sort can be different :
203 - // in php manual, usort :
204 - // " If two members compare as equal,
205 - // their order in the sorted array is undefined."
206 - // so the final packed script is different of the Dean's javascript version
207 - // but equivalent.
208 - // the ECMAscript standard does not guarantee this behaviour,
209 - // and thus not all browsers (e.g. Mozilla versions dating back to at
210 - // least 2003) respect this.
211 - usort( $unsorted, array( &$this, '_sortWords' ) );
212 - $j = 0;
213 - // because there are "protected" words in the list
214 - // we must add the sorted words around them
215 - do {
216 - if ( ! isset( $_sorted[ $i ] ) ) {
217 - $_sorted[ $i ] = substr( $unsorted[ $j ++ ], 1 );
218 - }
219 - $_encoded[ $_sorted[ $i ] ] = $values[ $i ];
220 - } while ( ++ $i < count( $unsorted ) );
221 - }
222 -
223 - return array(
224 - 'sorted' => $_sorted,
225 - 'encoded' => $_encoded,
226 - 'protected' => $_protected
227 - );
228 - }
229 -
230 - private $_count = array();
231 -
232 - private function _sortWords( $match1, $match2 ) {
233 - return $this->_count[ $match2 ] - $this->_count[ $match1 ];
234 - }
235 -
236 - // build the boot function used for loading and decoding
237 - private function _bootStrap( $packed, $keywords ) {
238 - $ENCODE = $this->_safeRegExp( '$encode\\($count\\)' );
239 -
240 - // $packed: the packed script
241 - $packed = "'" . $this->_escape( $packed ) . "'";
242 -
243 - // $ascii: base for encoding
244 - $ascii = min( count( $keywords['sorted'] ), $this->_encoding );
245 - if ( $ascii == 0 ) {
246 - $ascii = 1;
247 - }
248 -
249 - // $count: number of words contained in the script
250 - $count = count( $keywords['sorted'] );
251 -
252 - // $keywords: list of words contained in the script
253 - foreach ( $keywords['protected'] as $i => $value ) {
254 - $keywords['sorted'][ $i ] = '';
255 - }
256 - // convert from a string to an array
257 - ksort( $keywords['sorted'] );
258 - $keywords = "'" . implode( '|', $keywords['sorted'] ) . "'.split('|')";
259 -
260 - $encode = ( $this->_encoding > 62 ) ? '_encode95' : $this->_getEncoder( $ascii );
261 - $encode = $this->_getJSFunction( $encode );
262 - $encode = preg_replace( '/_encoding/', '$ascii', $encode );
263 - $encode = preg_replace( '/arguments\\.callee/', '$encode', $encode );
264 - $inline = '\\$count' . ( $ascii > 10 ? '.toString(\\$ascii)' : '' );
265 -
266 - // $decode: code snippet to speed up decoding
267 - if ( $this->_fastDecode ) {
268 - // create the decoder
269 - $decode = $this->_getJSFunction( '_decodeBody' );
270 - if ( $this->_encoding > 62 ) {
271 - $decode = preg_replace( '/\\\\w/', '[\\xa1-\\xff]', $decode );
272 - } // perform the encoding inline for lower ascii values
273 - elseif ( $ascii < 36 ) {
274 - $decode = preg_replace( $ENCODE, $inline, $decode );
275 - }
276 - // special case: when $count==0 there are no keywords. I want to keep
277 - // the basic shape of the unpacking funcion so i'll frig the code...
278 - if ( $count == 0 ) {
279 - $decode = preg_replace( $this->_safeRegExp( '($count)\\s*=\\s*1' ), '$1=0', $decode, 1 );
280 - }
281 - }
282 -
283 - // boot function
284 - $unpack = $this->_getJSFunction( '_unpack' );
285 - if ( $this->_fastDecode ) {
286 - // insert the decoder
287 - $this->buffer = $decode;
288 - $unpack = preg_replace_callback( '/\\{/', array( &$this, '_insertFastDecode' ), $unpack, 1 );
289 - }
290 - $unpack = preg_replace( '/"/', "'", $unpack );
291 - if ( $this->_encoding > 62 ) { // high-ascii
292 - // get rid of the word-boundaries for regexp matches
293 - $unpack = preg_replace( '/\'\\\\\\\\b\'\s*\\+|\\+\s*\'\\\\\\\\b\'/', '', $unpack );
294 - }
295 - if ( $ascii > 36 || $this->_encoding > 62 || $this->_fastDecode ) {
296 - // insert the encode function
297 - $this->buffer = $encode;
298 - $unpack = preg_replace_callback( '/\\{/', array( &$this, '_insertFastEncode' ), $unpack, 1 );
299 - } else {
300 - // perform the encoding inline
301 - $unpack = preg_replace( $ENCODE, $inline, $unpack );
302 - }
303 - // pack the boot function too
304 - $unpackPacker = new Obfuscator( $unpack, 0, false, true );
305 - $unpack = $unpackPacker->pack();
306 -
307 - // arguments
308 - $params = array( $packed, $ascii, $count, $keywords );
309 - if ( $this->_fastDecode ) {
310 - $params[] = 0;
311 - $params[] = '{}';
312 - }
313 - $params = implode( ',', $params );
314 -
315 - // the whole thing
316 - return 'eval(' . $unpack . '(' . $params . "))\n";
317 - }
318 -
319 - private $buffer;
320 -
321 - private function _insertFastDecode( $match ) {
322 - return '{' . $this->buffer . ';';
323 - }
324 -
325 - private function _insertFastEncode( $match ) {
326 - return '{$encode=' . $this->buffer . ';';
327 - }
328 -
329 - // mmm.. ..which one do i need ??
330 - private function _getEncoder( $ascii ) {
331 - return $ascii > 10 ? $ascii > 36 ? $ascii > 62 ?
332 - '_encode95' : '_encode62' : '_encode36' : '_encode10';
333 - }
334 -
335 - // zero encoding
336 - // characters: 0123456789
337 - private function _encode10( $charCode ) {
338 - return $charCode;
339 - }
340 -
341 - // inherent base36 support
342 - // characters: 0123456789abcdefghijklmnopqrstuvwxyz
343 - private function _encode36( $charCode ) {
344 - return base_convert( $charCode, 10, 36 );
345 - }
346 -
347 - // hitch a ride on base36 and add the upper case alpha characters
348 - // characters: 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
349 - private function _encode62( $charCode ) {
350 - $res = '';
351 - if ( $charCode >= $this->_encoding ) {
352 - $res = $this->_encode62( (int) ( $charCode / $this->_encoding ) );
353 - }
354 - $charCode = $charCode % $this->_encoding;
355 -
356 - if ( $charCode > 35 ) {
357 - return $res . chr( $charCode + 29 );
358 - } else {
359 - return $res . base_convert( $charCode, 10, 36 );
360 - }
361 - }
362 -
363 - // use high-ascii values
364 - // characters: ¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþ
365 - private function _encode95( $charCode ) {
366 - $res = '';
367 - if ( $charCode >= $this->_encoding ) {
368 - $res = $this->_encode95( $charCode / $this->_encoding );
369 - }
370 -
371 - return $res . chr( ( $charCode % $this->_encoding ) + 161 );
372 - }
373 -
374 - private function _safeRegExp( $string ) {
375 - return '/' . preg_replace( '/\$/', '\\\$', $string ) . '/';
376 - }
377 -
378 - private function _encodePrivate( $charCode ) {
379 - return "_" . $charCode;
380 - }
381 -
382 - // protect characters used by the parser
383 - private function _escape( $script ) {
384 - return preg_replace( '/([\\\\\'])/', '\\\$1', $script );
385 - }
386 -
387 - // protect high-ascii characters already in the script
388 - private function _escape95( $script ) {
389 - return preg_replace_callback(
390 - '/[\\xa1-\\xff]/',
391 - array( &$this, '_escape95Bis' ),
392 - $script
393 - );
394 - }
395 -
396 - private function _escape95Bis( $match ) {
397 - return '\x' . ( (string) dechex( ord( $match ) ) );
398 - }
399 -
400 -
401 - private function _getJSFunction( $aName ) {
402 - if ( defined( 'self::JSFUNCTION' . $aName ) ) {
403 - return constant( 'self::JSFUNCTION' . $aName );
404 - } else {
405 - return '';
406 - }
407 - }
408 -
409 - // JavaScript Functions used.
410 - // Note : In Dean's version, these functions are converted
411 - // with 'String(aFunctionName);'.
412 - // This internal conversion complete the original code, ex :
413 - // 'while (aBool) anAction();' is converted to
414 - // 'while (aBool) { anAction(); }'.
415 - // The JavaScript functions below are corrected.
416 -
417 - // unpacking function - this is the boot strap function
418 - // data extracted from this packing routine is passed to
419 - // this function when decoded in the target
420 - // NOTE ! : without the ';' final.
421 - const JSFUNCTION_unpack =
422 -
423 - 'function($packed, $ascii, $count, $keywords, $encode, $decode) {
424 - while ($count--) {
425 - if ($keywords[$count]) {
426 - $packed = $packed.replace(new RegExp(\'\\\\b\' + $encode($count) + \'\\\\b\', \'g\'), $keywords[$count]);
427 - }
428 - }
429 - return $packed;
430 - }';
431 - /*
432 - 'function($packed, $ascii, $count, $keywords, $encode, $decode) {
433 - while ($count--)
434 - if ($keywords[$count])
435 - $packed = $packed.replace(new RegExp(\'\\\\b\' + $encode($count) + \'\\\\b\', \'g\'), $keywords[$count]);
436 - return $packed;
437 - }';
438 - */
439 -
440 - // code-snippet inserted into the unpacker to speed up decoding
441 - const JSFUNCTION_decodeBody =
442 -//_decode = function() {
443 -// does the browser support String.replace where the
444 -// replacement value is a function?
445 -
446 - ' if (!\'\'.replace(/^/, String)) {
447 - // decode all the values we need
448 - while ($count--) {
449 - $decode[$encode($count)] = $keywords[$count] || $encode($count);
450 - }
451 - // global replacement function
452 - $keywords = [function ($encoded) {return $decode[$encoded]}];
453 - // generic match
454 - $encode = function () {return \'\\\\w+\'};
455 - // reset the loop counter - we are now doing a global replace
456 - $count = 1;
457 - }
458 - ';
459 -//};
460 - /*
461 - ' if (!\'\'.replace(/^/, String)) {
462 - // decode all the values we need
463 - while ($count--) $decode[$encode($count)] = $keywords[$count] || $encode($count);
464 - // global replacement function
465 - $keywords = [function ($encoded) {return $decode[$encoded]}];
466 - // generic match
467 - $encode = function () {return\'\\\\w+\'};
468 - // reset the loop counter - we are now doing a global replace
469 - $count = 1;
470 - }';
471 - */
472 -
473 - // zero encoding
474 - // characters: 0123456789
475 - const JSFUNCTION_encode10 =
476 - 'function($charCode) {
477 - return $charCode;
478 - }';//;';
479 -
480 - // inherent base36 support
481 - // characters: 0123456789abcdefghijklmnopqrstuvwxyz
482 - const JSFUNCTION_encode36 =
483 - 'function($charCode) {
484 - return $charCode.toString(36);
485 - }';//;';
486 -
487 - // hitch a ride on base36 and add the upper case alpha characters
488 - // characters: 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
489 - const JSFUNCTION_encode62 =
490 - 'function($charCode) {
491 - return ($charCode < _encoding ? \'\' : arguments.callee(parseInt($charCode / _encoding))) +
492 - (($charCode = $charCode % _encoding) > 35 ? String.fromCharCode($charCode + 29) : $charCode.toString(36));
493 - }';
494 -
495 - // use high-ascii values
496 - // characters: ¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþ
497 - const JSFUNCTION_encode95 =
498 - 'function($charCode) {
499 - return ($charCode < _encoding ? \'\' : arguments.callee($charCode / _encoding)) +
500 - String.fromCharCode($charCode % _encoding + 161);
501 - }';
502 -
503 -}
504 -
505 -
506 -
507 -
1 +<?php
2 +
3 +namespace WPCoder\Optimization;
4 +
5 +defined( 'ABSPATH' ) || exit;
6 +
7 +class Obfuscator {
8 + // constants
9 + const IGNORE = '$1';
10 +
11 + // validate parameters
12 + private $_script = '';
13 + private $_encoding = 62;
14 + private $_fastDecode = true;
15 + private $_specialChars = false;
16 +
17 + private $LITERAL_ENCODING = array(
18 + 'None' => 0,
19 + 'Numeric' => 10,
20 + 'Normal' => 62,
21 + 'High ASCII' => 95
22 + );
23 +
24 + public function __construct( $_script, $_encoding = 62, $_fastDecode = true, $_specialChars = false ) {
25 + $this->_script = $_script . "\n";
26 + if ( array_key_exists( $_encoding, $this->LITERAL_ENCODING ) ) {
27 + $_encoding = $this->LITERAL_ENCODING[ $_encoding ];
28 + }
29 + $this->_encoding = min( (int) $_encoding, 95 );
30 + $this->_fastDecode = $_fastDecode;
31 + $this->_specialChars = $_specialChars;
32 + }
33 +
34 + public function pack() {
35 + $this->_addParser( '_basicCompression' );
36 + if ( $this->_specialChars ) {
37 + $this->_addParser( '_encodeSpecialChars' );
38 + }
39 + if ( $this->_encoding ) {
40 + $this->_addParser( '_encodeKeywords' );
41 + }
42 +
43 + // go!
44 + return $this->_pack( $this->_script );
45 + }
46 +
47 + // apply all parsing routines
48 + private function _pack( $script ) {
49 + for ( $i = 0; isset( $this->_parsers[ $i ] ); $i ++ ) {
50 + $script = call_user_func( array( &$this, $this->_parsers[ $i ] ), $script );
51 + }
52 +
53 + return $script;
54 + }
55 +
56 + // keep a list of parsing functions, they'll be executed all at once
57 + private $_parsers = array();
58 +
59 + private function _addParser( $parser ) {
60 + $this->_parsers[] = $parser;
61 + }
62 +
63 + // zero encoding - just removal of white space and comments
64 + private function _basicCompression( $script ) {
65 + $parser = new ParseMaster();
66 + // make safe
67 + $parser->escapeChar = '\\';
68 + // protect strings
69 + $parser->add( '/\'[^\'\\n\\r]*\'/', self::IGNORE );
70 + $parser->add( '/"[^"\\n\\r]*"/', self::IGNORE );
71 + // remove comments
72 + $parser->add( '/\\/\\/[^\\n\\r]*[\\n\\r]/', ' ' );
73 + $parser->add( '/\\/\\*[^*]*\\*+([^\\/][^*]*\\*+)*\\//', ' ' );
74 + // protect regular expressions
75 + $parser->add( '/\\s+(\\/[^\\/\\n\\r\\*][^\\/\\n\\r]*\\/g?i?)/', '$2' ); // IGNORE
76 + $parser->add( '/[^\\w\\x24\\/\'"*)\\?:]\\/[^\\/\\n\\r\\*][^\\/\\n\\r]*\\/g?i?/', self::IGNORE );
77 + // remove: ;;; doSomething();
78 + if ( $this->_specialChars ) {
79 + $parser->add( '/;;;[^\\n\\r]+[\\n\\r]/' );
80 + }
81 + // remove redundant semi-colons
82 + $parser->add( '/\\(;;\\)/', self::IGNORE ); // protect for (;;) loops
83 + $parser->add( '/;+\\s*([};])/', '$2' );
84 + // apply the above
85 + $script = $parser->exec( $script );
86 +
87 + // remove white-space
88 + $parser->add( '/(\\b|\\x24)\\s+(\\b|\\x24)/', '$2 $3' );
89 + $parser->add( '/([+\\-])\\s+([+\\-])/', '$2 $3' );
90 + $parser->add( '/\\s+/', '' );
91 +
92 + // done
93 + return $parser->exec( $script );
94 + }
95 +
96 + private function _encodeSpecialChars( $script ) {
97 + $parser = new ParseMaster();
98 + // replace: $name -> n, $$name -> na
99 + $parser->add(
100 + '/((\\x24+)([a-zA-Z$_]+))(\\d*)/',
101 + array( 'fn' => '_replace_name' )
102 + );
103 + // replace: _name -> _0, double-underscore (__name) is ignored
104 + $regexp = '/\\b_[A-Za-z\\d]\\w*/';
105 + // build the word list
106 + $keywords = $this->_analyze( $script, $regexp, '_encodePrivate' );
107 + // quick ref
108 + $encoded = $keywords['encoded'];
109 +
110 + $parser->add(
111 + $regexp,
112 + array(
113 + 'fn' => '_replace_encoded',
114 + 'data' => $encoded
115 + )
116 + );
117 +
118 + return $parser->exec( $script );
119 + }
120 +
121 + private function _encodeKeywords( $script ) {
122 + // escape high-ascii values already in the script (i.e. in strings)
123 + if ( $this->_encoding > 62 ) {
124 + $script = $this->_escape95( $script );
125 + }
126 + // create the parser
127 + $parser = new ParseMaster();
128 + $encode = $this->_getEncoder( $this->_encoding );
129 + // for high-ascii, don't encode single character low-ascii
130 + $regexp = ( $this->_encoding > 62 ) ? '/\\w\\w+/' : '/\\w+/';
131 + // build the word list
132 + $keywords = $this->_analyze( $script, $regexp, $encode );
133 + $encoded = $keywords['encoded'];
134 +
135 + // encode
136 + $parser->add(
137 + $regexp,
138 + array(
139 + 'fn' => '_replace_encoded',
140 + 'data' => $encoded
141 + )
142 + );
143 + if ( empty( $script ) ) {
144 + return $script;
145 + } else {
146 + //$res = $parser->exec($script);
147 + //$res = $this->_bootStrap($res, $keywords);
148 + //return $res;
149 + return $this->_bootStrap( $parser->exec( $script ), $keywords );
150 + }
151 + }
152 +
153 + private function _analyze( $script, $regexp, $encode ) {
154 + // analyse
155 + // retreive all words in the script
156 + $all = array();
157 + preg_match_all( $regexp, $script, $all );
158 + $_sorted = array(); // list of words sorted by frequency
159 + $_encoded = array(); // dictionary of word->encoding
160 + $_protected = array(); // instances of "protected" words
161 + $all = $all[0]; // simulate the javascript comportement of global match
162 + if ( ! empty( $all ) ) {
163 + $unsorted = array(); // same list, not sorted
164 + $protected = array(); // "protected" words (dictionary of word->"word")
165 + $value = array(); // dictionary of charCode->encoding (eg. 256->ff)
166 + $this->_count = array(); // word->count
167 + $i = count( $all );
168 + $j = 0; //$word = null;
169 + // count the occurrences - used for sorting later
170 + do {
171 + -- $i;
172 + $word = '$' . $all[ $i ];
173 + if ( ! isset( $this->_count[ $word ] ) ) {
174 + $this->_count[ $word ] = 0;
175 + $unsorted[ $j ] = $word;
176 + // make a dictionary of all of the protected words in this script
177 + // these are words that might be mistaken for encoding
178 + //if (is_string($encode) && method_exists($this, $encode))
179 + $values[ $j ] = call_user_func( array( &$this, $encode ), $j );
180 + $protected[ '$' . $values[ $j ] ] = $j ++;
181 + }
182 + // increment the word counter
183 + $this->_count[ $word ] ++;
184 + } while ( $i > 0 );
185 + // prepare to sort the word list, first we must protect
186 + // words that are also used as codes. we assign them a code
187 + // equivalent to the word itself.
188 + // e.g. if "do" falls within our encoding range
189 + // then we store keywords["do"] = "do";
190 + // this avoids problems when decoding
191 + $i = count( $unsorted );
192 + do {
193 + $word = $unsorted[ -- $i ];
194 + if ( isset( $protected[ $word ] ) /*!= null*/ ) {
195 + $_sorted[ $protected[ $word ] ] = substr( $word, 1 );
196 + $_protected[ $protected[ $word ] ] = true;
197 + $this->_count[ $word ] = 0;
198 + }
199 + } while ( $i );
200 +
201 + // sort the words by frequency
202 + // Note: the javascript and php version of sort can be different :
203 + // in php manual, usort :
204 + // " If two members compare as equal,
205 + // their order in the sorted array is undefined."
206 + // so the final packed script is different of the Dean's javascript version
207 + // but equivalent.
208 + // the ECMAscript standard does not guarantee this behaviour,
209 + // and thus not all browsers (e.g. Mozilla versions dating back to at
210 + // least 2003) respect this.
211 + usort( $unsorted, array( &$this, '_sortWords' ) );
212 + $j = 0;
213 + // because there are "protected" words in the list
214 + // we must add the sorted words around them
215 + do {
216 + if ( ! isset( $_sorted[ $i ] ) ) {
217 + $_sorted[ $i ] = substr( $unsorted[ $j ++ ], 1 );
218 + }
219 + $_encoded[ $_sorted[ $i ] ] = $values[ $i ];
220 + } while ( ++ $i < count( $unsorted ) );
221 + }
222 +
223 + return array(
224 + 'sorted' => $_sorted,
225 + 'encoded' => $_encoded,
226 + 'protected' => $_protected
227 + );
228 + }
229 +
230 + private $_count = array();
231 +
232 + private function _sortWords( $match1, $match2 ) {
233 + return $this->_count[ $match2 ] - $this->_count[ $match1 ];
234 + }
235 +
236 + // build the boot function used for loading and decoding
237 + private function _bootStrap( $packed, $keywords ) {
238 + $ENCODE = $this->_safeRegExp( '$encode\\($count\\)' );
239 +
240 + // $packed: the packed script
241 + $packed = "'" . $this->_escape( $packed ) . "'";
242 +
243 + // $ascii: base for encoding
244 + $ascii = min( count( $keywords['sorted'] ), $this->_encoding );
245 + if ( $ascii == 0 ) {
246 + $ascii = 1;
247 + }
248 +
249 + // $count: number of words contained in the script
250 + $count = count( $keywords['sorted'] );
251 +
252 + // $keywords: list of words contained in the script
253 + foreach ( $keywords['protected'] as $i => $value ) {
254 + $keywords['sorted'][ $i ] = '';
255 + }
256 + // convert from a string to an array
257 + ksort( $keywords['sorted'] );
258 + $keywords = "'" . implode( '|', $keywords['sorted'] ) . "'.split('|')";
259 +
260 + $encode = ( $this->_encoding > 62 ) ? '_encode95' : $this->_getEncoder( $ascii );
261 + $encode = $this->_getJSFunction( $encode );
262 + $encode = preg_replace( '/_encoding/', '$ascii', $encode );
263 + $encode = preg_replace( '/arguments\\.callee/', '$encode', $encode );
264 + $inline = '\\$count' . ( $ascii > 10 ? '.toString(\\$ascii)' : '' );
265 +
266 + // $decode: code snippet to speed up decoding
267 + if ( $this->_fastDecode ) {
268 + // create the decoder
269 + $decode = $this->_getJSFunction( '_decodeBody' );
270 + if ( $this->_encoding > 62 ) {
271 + $decode = preg_replace( '/\\\\w/', '[\\xa1-\\xff]', $decode );
272 + } // perform the encoding inline for lower ascii values
273 + elseif ( $ascii < 36 ) {
274 + $decode = preg_replace( $ENCODE, $inline, $decode );
275 + }
276 + // special case: when $count==0 there are no keywords. I want to keep
277 + // the basic shape of the unpacking funcion so i'll frig the code...
278 + if ( $count == 0 ) {
279 + $decode = preg_replace( $this->_safeRegExp( '($count)\\s*=\\s*1' ), '$1=0', $decode, 1 );
280 + }
281 + }
282 +
283 + // boot function
284 + $unpack = $this->_getJSFunction( '_unpack' );
285 + if ( $this->_fastDecode ) {
286 + // insert the decoder
287 + $this->buffer = $decode;
288 + $unpack = preg_replace_callback( '/\\{/', array( &$this, '_insertFastDecode' ), $unpack, 1 );
289 + }
290 + $unpack = preg_replace( '/"/', "'", $unpack );
291 + if ( $this->_encoding > 62 ) { // high-ascii
292 + // get rid of the word-boundaries for regexp matches
293 + $unpack = preg_replace( '/\'\\\\\\\\b\'\s*\\+|\\+\s*\'\\\\\\\\b\'/', '', $unpack );
294 + }
295 + if ( $ascii > 36 || $this->_encoding > 62 || $this->_fastDecode ) {
296 + // insert the encode function
297 + $this->buffer = $encode;
298 + $unpack = preg_replace_callback( '/\\{/', array( &$this, '_insertFastEncode' ), $unpack, 1 );
299 + } else {
300 + // perform the encoding inline
301 + $unpack = preg_replace( $ENCODE, $inline, $unpack );
302 + }
303 + // pack the boot function too
304 + $unpackPacker = new Obfuscator( $unpack, 0, false, true );
305 + $unpack = $unpackPacker->pack();
306 +
307 + // arguments
308 + $params = array( $packed, $ascii, $count, $keywords );
309 + if ( $this->_fastDecode ) {
310 + $params[] = 0;
311 + $params[] = '{}';
312 + }
313 + $params = implode( ',', $params );
314 +
315 + // the whole thing
316 + return 'eval(' . $unpack . '(' . $params . "))\n";
317 + }
318 +
319 + private $buffer;
320 +
321 + private function _insertFastDecode( $match ) {
322 + return '{' . $this->buffer . ';';
323 + }
324 +
325 + private function _insertFastEncode( $match ) {
326 + return '{$encode=' . $this->buffer . ';';
327 + }
328 +
329 + // mmm.. ..which one do i need ??
330 + private function _getEncoder( $ascii ) {
331 + return $ascii > 10 ? $ascii > 36 ? $ascii > 62 ?
332 + '_encode95' : '_encode62' : '_encode36' : '_encode10';
333 + }
334 +
335 + // zero encoding
336 + // characters: 0123456789
337 + private function _encode10( $charCode ) {
338 + return $charCode;
339 + }
340 +
341 + // inherent base36 support
342 + // characters: 0123456789abcdefghijklmnopqrstuvwxyz
343 + private function _encode36( $charCode ) {
344 + return base_convert( $charCode, 10, 36 );
345 + }
346 +
347 + // hitch a ride on base36 and add the upper case alpha characters
348 + // characters: 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
349 + private function _encode62( $charCode ) {
350 + $res = '';
351 + if ( $charCode >= $this->_encoding ) {
352 + $res = $this->_encode62( (int) ( $charCode / $this->_encoding ) );
353 + }
354 + $charCode = $charCode % $this->_encoding;
355 +
356 + if ( $charCode > 35 ) {
357 + return $res . chr( $charCode + 29 );
358 + } else {
359 + return $res . base_convert( $charCode, 10, 36 );
360 + }
361 + }
362 +
363 + // use high-ascii values
364 + // characters: ¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþ
365 + private function _encode95( $charCode ) {
366 + $res = '';
367 + if ( $charCode >= $this->_encoding ) {
368 + $res = $this->_encode95( $charCode / $this->_encoding );
369 + }
370 +
371 + return $res . chr( ( $charCode % $this->_encoding ) + 161 );
372 + }
373 +
374 + private function _safeRegExp( $string ) {
375 + return '/' . preg_replace( '/\$/', '\\\$', $string ) . '/';
376 + }
377 +
378 + private function _encodePrivate( $charCode ) {
379 + return "_" . $charCode;
380 + }
381 +
382 + // protect characters used by the parser
383 + private function _escape( $script ) {
384 + return preg_replace( '/([\\\\\'])/', '\\\$1', $script );
385 + }
386 +
387 + // protect high-ascii characters already in the script
388 + private function _escape95( $script ) {
389 + return preg_replace_callback(
390 + '/[\\xa1-\\xff]/',
391 + array( &$this, '_escape95Bis' ),
392 + $script
393 + );
394 + }
395 +
396 + private function _escape95Bis( $match ) {
397 + return '\x' . ( (string) dechex( ord( $match ) ) );
398 + }
399 +
400 +
401 + private function _getJSFunction( $aName ) {
402 + if ( defined( 'self::JSFUNCTION' . $aName ) ) {
403 + return constant( 'self::JSFUNCTION' . $aName );
404 + } else {
405 + return '';
406 + }
407 + }
408 +
409 + // JavaScript Functions used.
410 + // Note : In Dean's version, these functions are converted
411 + // with 'String(aFunctionName);'.
412 + // This internal conversion complete the original code, ex :
413 + // 'while (aBool) anAction();' is converted to
414 + // 'while (aBool) { anAction(); }'.
415 + // The JavaScript functions below are corrected.
416 +
417 + // unpacking function - this is the boot strap function
418 + // data extracted from this packing routine is passed to
419 + // this function when decoded in the target
420 + // NOTE ! : without the ';' final.
421 + const JSFUNCTION_unpack =
422 +
423 + 'function($packed, $ascii, $count, $keywords, $encode, $decode) {
424 + while ($count--) {
425 + if ($keywords[$count]) {
426 + $packed = $packed.replace(new RegExp(\'\\\\b\' + $encode($count) + \'\\\\b\', \'g\'), $keywords[$count]);
427 + }
428 + }
429 + return $packed;
430 + }';
431 + /*
432 + 'function($packed, $ascii, $count, $keywords, $encode, $decode) {
433 + while ($count--)
434 + if ($keywords[$count])
435 + $packed = $packed.replace(new RegExp(\'\\\\b\' + $encode($count) + \'\\\\b\', \'g\'), $keywords[$count]);
436 + return $packed;
437 + }';
438 + */
439 +
440 + // code-snippet inserted into the unpacker to speed up decoding
441 + const JSFUNCTION_decodeBody =
442 +//_decode = function() {
443 +// does the browser support String.replace where the
444 +// replacement value is a function?
445 +
446 + ' if (!\'\'.replace(/^/, String)) {
447 + // decode all the values we need
448 + while ($count--) {
449 + $decode[$encode($count)] = $keywords[$count] || $encode($count);
450 + }
451 + // global replacement function
452 + $keywords = [function ($encoded) {return $decode[$encoded]}];
453 + // generic match
454 + $encode = function () {return \'\\\\w+\'};
455 + // reset the loop counter - we are now doing a global replace
456 + $count = 1;
457 + }
458 + ';
459 +//};
460 + /*
461 + ' if (!\'\'.replace(/^/, String)) {
462 + // decode all the values we need
463 + while ($count--) $decode[$encode($count)] = $keywords[$count] || $encode($count);
464 + // global replacement function
465 + $keywords = [function ($encoded) {return $decode[$encoded]}];
466 + // generic match
467 + $encode = function () {return\'\\\\w+\'};
468 + // reset the loop counter - we are now doing a global replace
469 + $count = 1;
470 + }';
471 + */
472 +
473 + // zero encoding
474 + // characters: 0123456789
475 + const JSFUNCTION_encode10 =
476 + 'function($charCode) {
477 + return $charCode;
478 + }';//;';
479 +
480 + // inherent base36 support
481 + // characters: 0123456789abcdefghijklmnopqrstuvwxyz
482 + const JSFUNCTION_encode36 =
483 + 'function($charCode) {
484 + return $charCode.toString(36);
485 + }';//;';
486 +
487 + // hitch a ride on base36 and add the upper case alpha characters
488 + // characters: 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
489 + const JSFUNCTION_encode62 =
490 + 'function($charCode) {
491 + return ($charCode < _encoding ? \'\' : arguments.callee(parseInt($charCode / _encoding))) +
492 + (($charCode = $charCode % _encoding) > 35 ? String.fromCharCode($charCode + 29) : $charCode.toString(36));
493 + }';
494 +
495 + // use high-ascii values
496 + // characters: ¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþ
497 + const JSFUNCTION_encode95 =
498 + 'function($charCode) {
499 + return ($charCode < _encoding ? \'\' : arguments.callee($charCode / _encoding)) +
500 + String.fromCharCode($charCode % _encoding + 161);
501 + }';
502 +
503 +}
504 +
505 +
506 +
507 +