Use at own risk. Programs haven't been thoroughly tested.
Class: JSObfuscate
Scrambles and Compresses Javascript source Very usefull if you want to make it harder for others to interpret your Javascript source files. I looked hard for a free obfuscator with really good scrambling and compression rates, but I couldn't find one so I created this. Its not possible to unscramble generated code, since it replaces all local variables and all function with random names. It has an average compression rate of 50%. Longer JS files give a better overall compression rate. Feel free to improve it, but please leave credits in tact, and tell me what you've changed, so I can improve it as well!
Info
@version 0.3
@link http://kevin.vanzonneveld.net
@function obfuscateString obfuscates a JS string
@function obfuscateFile obfuscates a .JS file
Example
echo JSObfuscate::obfuscateString(" function setChildProduct(origin){ var strInputName,strOutputFile,strCategory,strProduct; if(!confirm(strInputName+' and '+strOutputFile+' and '+strCategory+' and '+strProduct)){ deny('everything'); } } funtion deny(strdata){ alert('everything'); } ",false);
Outputs
var wX=window;var dX=document;funtion deny(strdata){alert('everything');} function Xw(M){var L,s,p,O;if(!confirm(L+' and '+s+' and '+p+' and '+O)){deny('everything');}} function setChildProduct(M){Xw(M);}
Source Code
download source<? abstract class JSObfuscate { static private $globalReplacing=array("dX"=>"document","wX"=>"window"); static private $reserverdReplacing=array("e"=>true,"in"=>true,"do"=>true,"if"=>true,"id"=>true); static private $functionAlias=array(); static private $keeptrack_rnd=array(); static private $jsSeperationString = "\s|\=|\.|\?|\,|\(|\)|\[|\]|\+|\:|\;|\!|\||\<|\>|\/|\-|\*"; static private $jsTrimString = "\(|\)|\{|\}|\,|\;|\:|\?|\=\=\=|\=\=|\!\=|\&\&|\<\=|\>\=|\>|\<|\|\||\*|\/|\=|\+|\-"; static public function obfuscateString($jsbody,$copyright=" Copyright(c) 2006 True, Kevin van Zonneveld "){ // links al the obfuscation function together and produces new js code! $szo = strlen($jsbody); $jsbody = self::_cleanupBody($jsbody); $functions = self::_getFunctions($jsbody); $nwcode = self::_compileBody($functions); $nwcode = self::_obfuscateFunctionNames($nwcode); $nwcode = self::_compressBody($nwcode); $sze = strlen($nwcode); $header=""; if($copyright ==false){ $header .= "/***".$copyright."***/\n"; $header .= "/***\n"; $header .= "\tEncoded by JSObfuscate 0.3\n"; $header .= "\t original size: ".$szo."\n"; $header .= "\t encoded size: ".$sze."\n"; $header .= "\t compression r: ".number_format(100-($sze/$szo*100),2) ."%\n"; $header .= "***/\n"; } return $header. $nwcode; } static public function obfuscateFile($jsfilepath){ // read file self::obfuscateString(implode("\n",file($jsfilepath))); } static private function _compressBody($jsbody){ //remove whitespace around operators and such $jsbody = preg_replace("/( |\t)*(".self::$jsTrimString.")( |\t)*/","$2",$jsbody); $jsbody = str_replace("\nfunction ","function^^^^^ ",$jsbody); $jsbody = str_replace("\nString.prototype.","String.prototype.^^^^^",$jsbody); $jsbody = str_replace("\nArray.prototype.","Array.prototype.^^^^^",$jsbody); $jsbody = str_replace("\n","",$jsbody); $jsbody = str_replace("function^^^^^ ","\nfunction ",$jsbody); $jsbody = str_replace("String.prototype.^^^^^","\nString.prototype.",$jsbody); $jsbody = str_replace("Array.prototype.^^^^^","\nArray.prototype.",$jsbody); foreach(self::$globalReplacing as $short=>$large){ // replace the document variable with a shorter one $jsbody = preg_replace("/(".self::$jsSeperationString.")$large(".self::$jsSeperationString.")/","$1$short$2",$jsbody); // make the sorter document variable available in the js body $jsbody = "var $short=$large;".$jsbody; } return $jsbody; } static private function _obfuscateFunctionNames($jsbody=false){ if(!$jsbody) return false; // first find & replace all functions with new names preg_match_all("/\nfunction (\w+)\s?\(([a-zA-Z0-9, ]*)\)/",$jsbody,$rslt); foreach($rslt[1] as $k=>$fname){ self::$functionAlias[$fname] = self::_retRandomName("global"); $functionAttributes[$fname] = $rslt[2][$k]; $jsbody = preg_replace("/(".self::$jsSeperationString.")$fname(".self::$jsSeperationString.")/", "$1".self::$functionAlias[$fname]."$2",$jsbody); } // randomly sort the functionalias array //self::$functionAlias = _assarrayShuffle(self::$functionAlias); // create references to the new functions so that other applications can stil find them $jsbody.= "\n"; foreach(self::$functionAlias as $fname=>$alias){ $jsbody.= "function $fname(".$functionAttributes[$fname]."){".self::$functionAlias[$fname]. "(".$functionAttributes[$fname].");}"; } return $jsbody; } static private function _obfuscateFunctionBlock($fname="",$blcar=false,$attr=false,$vars=false){ if(!$blcar || !is_array($blcar)) return false; if(!$attr || !is_array($attr)) $attr=array(); if(!$vars || !is_array($vars)) $vars=array(); // replace all attributes AND local variables with a random name $seekar = array(); $replar = array(); $avars = array_merge($attr,$vars); $avars = array_unique($avars); $block = implode("\n",$blcar); foreach($avars as $k=>$seekvar){ if( !isset(self::$reserverdReplacing[$seekvar]) && $seekvar){ $repvar = self::_retRandomName($fname); $seekar[] = "/(".self::$jsSeperationString.")$seekvar(".self::$jsSeperationString.")/"; $replar[] = "$1$repvar$2"; if(!isset($repvar) || !$seekvar){ die("There was no repvar for seekvar: $seekvar ($repvar)"); } } } // do it if(count($seekar)){ if(count($replar)!=count($seekar)){ print_r($seekar); print_r($replar); die("COUNT OF SEEKAR != REPLAR "); } $block = preg_replace($seekar,$replar,$block); // needs to be done 2 times for overlapping matches like "lHI = (lHI?lHI:this);" $block = preg_replace($seekar,$replar,$block); } return explode("\n",$block); } static private function _retRandomName($scope="global"){ // e,f,d have been removed to prevent conflicts with e, if, do, etc. $pattern = "123456789abcghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; while($cnt<5000){ $nme = $pattern{rand(11,57)}; if($scope=="global"){ $nme .= $pattern{rand(0,57)}; } $todo = intval(count(self::$keeptrack_rnd[$scope])/40); if($todo>1){ for($i=0;$i++;$i<$todo){ $nme .= $pattern{rand(0,57)}; } } if( !isset(self::$keeptrack_rnd[$scope][$nme]) && !isset(self::$keeptrack_rnd["global"][$nme]) && !isset(self::$globalReplacing[$nme]) && !isset(self::$reserverdReplacing[$nme]) ){ self::$keeptrack_rnd[$scope][$nme]=true; return $nme; break; } $cnt++; } die("SAFETY LIMIT OF 5000 TRIES HIT WITHOUT RESULTING IN A GOOD RANDOMNAME!"); } static private function _getFunctions($jsbody){ $arrFBlocks = array(); $fi=0; $frecording=false; // search all lines $jsbodylines = explode("\n",$jsbody); foreach($jsbodylines as $clnr=>$cline){ $cline = trim($cline); $curlevel += substr_count($cline,"{"); $curlevel -= substr_count($cline,"}"); if($frecording == false){ if(substr($cline,0,strlen("function "))=="function "){ $frecording = true; $fi++; $fname = trim(self::_retBetween($cline,"function ","(",false,false)); $arrFBlocks[$fname]["name"] = $fname; $arrFBlocks[$fname]["attributes"] = explode( ",",self::_retBetween($cline,"(",")", false,false) ); foreach($arrFBlocks[$fname]["attributes"] as $attk=>$attv){ $arrFBlocks[$fname]["attributes"][$attk] = trim($attv); if(!trim($attv)){ unset($arrFBlocks[$fname]["attributes"][$attk]); } } $arrFBlocks[$fname]["level"] = $curlevel; $arrFBlocks[$fname]["line"] = $clnr; $arrFBlocks[$fname]["number"] = $fi; } elseif($cline){ $arrFBlocks["!NONFUNC!"]["block"][] = str_pad($cline,$curlevel," ",STR_PAD_LEFT); } } else{ if(substr($cline,0,strlen("var "))=="var "){ $p = substr($cline,strlen("var ")); if(substr_count($p,"=")){ $pis = explode("=",$p); $arrFBlocks[$fname]["vars"][] = str_replace(";","",trim($pis[0])); } else{ $pp = explode(",", $p); foreach($pp as $pk=>$pv){ $pis = explode("=",$pv); $arrFBlocks[$fname]["vars"][] = str_replace(";","",trim($pis[0])); } } } elseif($arrFBlocks[$fname]["level"]-1 == $curlevel){ $arrFBlocks[$fname]["block"] = self::_obfuscateFunctionBlock($fname, array_slice($jsbodylines,$arrFBlocks[$fname]["line"],($clnr-$arrFBlocks[$fname]["line"]+1)) , $arrFBlocks[$fname]["attributes"],($arrFBlocks[$fname]["vars"]) ); $frecording = false; } } } return $arrFBlocks; } static private function _cleanupBody($jsbody){ // convert )\n{ to { $jsbody = preg_replace("/\)(\s)*\{/","){",$jsbody); // convert tabs->spaces while(substr_count($jsbody,"\t")){ $jsbody = str_replace("\t"," ",$jsbody); } // clean multiple spaces while(substr_count($jsbody," ")){ $jsbody = str_replace(" "," ",$jsbody); } // convert nw->cr while(substr_count($jsbody,"\r")){ $jsbody = str_replace("\r","\n",$jsbody); } // trim all lines $jsbodylines = explode("\n",$jsbody); foreach($jsbodylines as $k=>$v){ $jsbodylines[$k] = trim($v); if(!$jsbodylines[$k]){ unset($jsbodylines[$k]); } } $jsbody = implode("\n",$jsbodylines); // clean multiple breaks while(substr_count($jsbody,"\n ")){ $jsbody = str_replace("\n ","\n",$jsbody); } // clean multiple breaks while(substr_count($jsbody,"\n\n")){ $jsbody = str_replace("\n\n","\n",$jsbody); } // Remove multiline comment $mlcomment = '/\/\*(?!-)[\x00-\xff]*?\*\//'; $jsbody = preg_replace($mlcomment,"",$jsbody); // Remove single line comment $slcomment = '/[^:]\/\/.*/'; $jsbody = preg_replace($slcomment,"",$jsbody); return $jsbody; } static private function _assarrayShuffle ($array) { while (count($array) > 0) { $val = array_rand($array); $new_arr[$val] = $array[$val]; unset($array[$val]); } return $new_arr; } static private function _compileBody($functionarray){ $buf = ""; $functionarray = self::_assarrayShuffle($functionarray); //asort($functionarray); foreach($functionarray as $fname=>$far){ if($fname == "!NONFUNC!"){ $buf = implode("\n",$far[block])."\n".$buf; } else{ //write function $buf = $buf."\n". implode("\n",$far[block]); } } return $buf; } static private function _retBetween($base,$left,$right, $insensitive=true,$include_findstr=false){ //this function finds a string between 2 strings //(c) kevin $basl = strlen($base); $posl = ($insensitive?strpos(strtolower($base),strtolower($left)):strpos($base,$left)); $foundl = ($insensitive?substr_count(strtolower($base),strtolower($left)):substr_count($base,$left)); $base = substr($base,$posl+($include_findstr?0:strlen($left)),$basl); $posr = ($insensitive?strpos(strtolower($base),strtolower($right),0):strpos($base,$right,0)); $foundr = ($insensitive?substr_count(strtolower($base),strtolower($right)):substr_count($base,$right)); $base = substr($base,0,($posr)+($include_findstr?strlen($right):0)); return (!$foundl||!$foundr?false:$base); } } ?>
#2. Kevin on 21 March 2008
Thanks, Kevin
#1. MD on 19 March 2008
http://joliclic.free.fr/php/javascript-packer/en/
searching is usually usually quicker than writing your own code from scratch, but you might miss an enjoyment like I did:) Excellent work though!