PluginProbe
WP Coder – Insert & Manage Code Snippets / 3.0.1
WP Coder – Insert & Manage Code Snippets v3.0.1
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
wp-coder / classes / Optimization / Obfuscator.php

Obfuscator.php in WP Coder – Insert & Manage Code Snippets 3.0.1, at classes/Optimization/Obfuscator.php

510 lines 16.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
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 ÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþ
366 private function _encode95( $charCode ) {
367 $res = '';
368 if ( $charCode >= $this->_encoding ) {
369 $res = $this->_encode95( $charCode / $this->_encoding );
370 }
371
372 return $res . chr( ( $charCode % $this->_encoding ) + 161 );
373 }
374
375 private function _safeRegExp( $string ) {
376 return '/' . preg_replace( '/\$/', '\\\$', $string ) . '/';
377 }
378
379 private function _encodePrivate( $charCode ) {
380 return "_" . $charCode;
381 }
382
383 // protect characters used by the parser
384 private function _escape( $script ) {
385 return preg_replace( '/([\\\\\'])/', '\\\$1', $script );
386 }
387
388 // protect high-ascii characters already in the script
389 private function _escape95( $script ) {
390 return preg_replace_callback(
391 '/[\\xa1-\\xff]/',
392 array( &$this, '_escape95Bis' ),
393 $script
394 );
395 }
396
397 private function _escape95Bis( $match ) {
398 return '\x' . ( (string) dechex( ord( $match ) ) );
399 }
400
401
402 private function _getJSFunction( $aName ) {
403 if ( defined( 'self::JSFUNCTION' . $aName ) ) {
404 return constant( 'self::JSFUNCTION' . $aName );
405 } else {
406 return '';
407 }
408 }
409
410 // JavaScript Functions used.
411 // Note : In Dean's version, these functions are converted
412 // with 'String(aFunctionName);'.
413 // This internal conversion complete the original code, ex :
414 // 'while (aBool) anAction();' is converted to
415 // 'while (aBool) { anAction(); }'.
416 // The JavaScript functions below are corrected.
417
418 // unpacking function - this is the boot strap function
419 // data extracted from this packing routine is passed to
420 // this function when decoded in the target
421 // NOTE ! : without the ';' final.
422 const JSFUNCTION_unpack =
423
424 'function($packed, $ascii, $count, $keywords, $encode, $decode) {
425 while ($count--) {
426 if ($keywords[$count]) {
427 $packed = $packed.replace(new RegExp(\'\\\\b\' + $encode($count) + \'\\\\b\', \'g\'), $keywords[$count]);
428 }
429 }
430 return $packed;
431 }';
432 /*
433 'function($packed, $ascii, $count, $keywords, $encode, $decode) {
434 while ($count--)
435 if ($keywords[$count])
436 $packed = $packed.replace(new RegExp(\'\\\\b\' + $encode($count) + \'\\\\b\', \'g\'), $keywords[$count]);
437 return $packed;
438 }';
439 */
440
441 // code-snippet inserted into the unpacker to speed up decoding
442 const JSFUNCTION_decodeBody =
443 //_decode = function() {
444 // does the browser support String.replace where the
445 // replacement value is a function?
446
447 ' if (!\'\'.replace(/^/, String)) {
448 // decode all the values we need
449 while ($count--) {
450 $decode[$encode($count)] = $keywords[$count] || $encode($count);
451 }
452 // global replacement function
453 $keywords = [function ($encoded) {return $decode[$encoded]}];
454 // generic match
455 $encode = function () {return \'\\\\w+\'};
456 // reset the loop counter - we are now doing a global replace
457 $count = 1;
458 }
459 ';
460 //};
461 /*
462 ' if (!\'\'.replace(/^/, String)) {
463 // decode all the values we need
464 while ($count--) $decode[$encode($count)] = $keywords[$count] || $encode($count);
465 // global replacement function
466 $keywords = [function ($encoded) {return $decode[$encoded]}];
467 // generic match
468 $encode = function () {return\'\\\\w+\'};
469 // reset the loop counter - we are now doing a global replace
470 $count = 1;
471 }';
472 */
473
474 // zero encoding
475 // characters: 0123456789
476 const JSFUNCTION_encode10 =
477 'function($charCode) {
478 return $charCode;
479 }';//;';
480
481 // inherent base36 support
482 // characters: 0123456789abcdefghijklmnopqrstuvwxyz
483 const JSFUNCTION_encode36 =
484 'function($charCode) {
485 return $charCode.toString(36);
486 }';//;';
487
488 // hitch a ride on base36 and add the upper case alpha characters
489 // characters: 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
490 const JSFUNCTION_encode62 =
491 'function($charCode) {
492 return ($charCode < _encoding ? \'\' : arguments.callee(parseInt($charCode / _encoding))) +
493 (($charCode = $charCode % _encoding) > 35 ? String.fromCharCode($charCode + 29) : $charCode.toString(36));
494 }';
495
496 // use high-ascii values
497 // characters: ¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄ�
498 ÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþ
499 const JSFUNCTION_encode95 =
500 'function($charCode) {
501 return ($charCode < _encoding ? \'\' : arguments.callee($charCode / _encoding)) +
502 String.fromCharCode($charCode % _encoding + 161);
503 }';
504
505 }
506
507
508
509
510