File manager - Edit - /home/premiey/www/wp-includes/images/media/lets-encrypt.tar
Back
class-le-restapi.php 0000666 00000017536 15165257520 0010455 0 ustar 00 <?php defined( 'ABSPATH' ) or die(); if ( ! class_exists( "rsssl_le_restapi" ) ) { class rsssl_le_restapi{ private static $_this; function __construct() { if ( isset( self::$_this ) ) { wp_die( sprintf( '%s is a singleton class and you cannot create a second instance.', get_class( $this ) ) ); } self::$_this = $this; add_filter("rsssl_run_test", array($this, 'handle_lets_encrypt_request'), 10, 3); add_action( 'rsssl_after_save_field', array( $this, 'after_save_field' ), 10, 4 ); } static function this() { return self::$_this; } /** * Switch to DNS verification * @param array $data * @return [] */ public function update_verification_type($data){ $type = $data['id']; $type = $type === 'dns' ? 'dns' : 'dir'; rsssl_update_option('verification_type', $type ); if ($type==='dns') { rsssl_progress_add('directories'); } else { rsssl_progress_add('dns-verification'); } return new RSSSL_RESPONSE( 'success', 'stop', '' ); } /** * Skip DNS check * @return RSSSL_RESPONSE */ public function skip_dns_check(){ if ( !rsssl_user_can_manage() ) { return new RSSSL_RESPONSE( 'error', 'stop', '' ); } update_option('rsssl_skip_dns_check', true, false); return new RSSSL_RESPONSE( 'success', 'stop', '' ); } /** * Get installation data * @return RSSSL_RESPONSE */ public function installation_data(){ if ( !rsssl_user_can_manage() ) { return new RSSSL_RESPONSE( 'error', 'stop', '' ); } $key_file = get_option('rsssl_private_key_path'); $cert_file = get_option('rsssl_certificate_path'); $cabundle_file = get_option('rsssl_intermediate_path'); $data = [ 'generated_by_rsssl' => rsssl_generated_by_rsssl(), 'download_url' => rsssl_le_url.'download.php?token='.wp_create_nonce('rsssl_download_cert'), 'key_content' => file_exists($key_file) ? file_get_contents($key_file) : 'no data found', 'certificate_content' => file_exists($cert_file) ? file_get_contents($cert_file) : 'no data found', 'ca_bundle_content' => file_exists($cabundle_file) ? file_get_contents($cabundle_file) : 'no data found', ]; return new RSSSL_RESPONSE( 'success', 'continue', '', $data ); } /** * Challenge directory request * * @return RSSSL_RESPONSE */ public function skip_challenge_directory_request(){ if ( !rsssl_user_can_manage() ) { return new RSSSL_RESPONSE( 'error', 'stop', '' ); } update_option('rsssl_skip_challenge_directory_request', true, false); return new RSSSL_RESPONSE( 'success', 'stop', '' ); } /** * Reset the LE wizard * @return bool[]|RSSSL_RESPONSE */ public function reset(){ if ( !rsssl_user_can_manage() ) { return new RSSSL_RESPONSE( 'success', 'stop', '' ); } RSSSL_LE()->letsencrypt_handler->clear_order(); rsssl_update_option('verification_type', 'dir' ); delete_option('rsssl_skip_dns_check' ); delete_option('rsssl_skip_challenge_directory_request' ); delete_option('rsssl_create_folders_in_root'); delete_option('rsssl_hosting_dashboard'); RSSSL_LE()->letsencrypt_handler->clear_keys_directory(); return new RSSSL_RESPONSE( 'success', 'stop', '' ); } public function clean_up(){ //clean up stored pw, if requested RSSSL_LE()->letsencrypt_handler->cleanup_on_ssl_activation(); } /** * Process a Let's Encrypt test request * * @param array $response * @param string $test * @param WP_REST_Request $request * * @return RSSSL_RESPONSE|array */ public function handle_lets_encrypt_request($response, $test, $data){ if ( ! current_user_can('manage_security') ) { return new RSSSL_RESPONSE( 'error', 'stop', __( "Permission denied.", 'really-simple-ssl' ) ); } switch( $test ){ case 'reset': return $this->reset(); case 'update_verification_type': return $this->update_verification_type($data); case 'skip_dns_check': return $this->skip_dns_check(); case 'skip_challenge_directory_request': return $this->skip_challenge_directory_request(); case 'installation_data': return $this->installation_data(); case 'is_subdomain_setup': case 'verify_dns': case 'certificate_status': case 'curl_exists': case 'server_software': case 'alias_domain_available': case 'check_domain': case 'check_host': case 'check_challenge_directory': case 'check_key_directory': case 'check_certs_directory': case 'check_writing_permissions': case 'challenge_directory_reachable': case 'get_account': case 'get_dns_token': case 'terms_accepted': case 'create_bundle_or_renew': case 'search_ssl_installation_url': case 'rsssl_install_cpanel_autossl': case 'rsssl_cpanel_set_txt_record': case 'rsssl_install_cpanel_default': case 'rsssl_cloudways_server_data': case 'rsssl_cloudways_install_ssl': case 'rsssl_cloudways_auto_renew': case 'rsssl_install_directadmin': case 'rsssl_plesk_install': case 'cleanup_on_ssl_activation': return $this->get_installation_progress($response, $test, $data); default: return $response; } } /** * Run a LE test * @param $response * @param $function * @param $data * * @return RSSSL_RESPONSE */ public function get_installation_progress( $response, $function, $data ){ $id = $data['id']; if ( ! current_user_can('manage_security') ) { return new RSSSL_RESPONSE( 'error', 'stop', __( "Permission denied.", 'really-simple-ssl' ) ); } if (!function_exists($function) && !method_exists(RSSSL_LE()->letsencrypt_handler, $function)) { return new RSSSL_RESPONSE( 'error', 'stop', __( "Test not found.", 'really-simple-ssl' ) ); } rsssl_progress_add($id); if ( function_exists($function) ){ $response = $function(); } else { $response = RSSSL_LE()->letsencrypt_handler->$function(); } return $response; } /** * Handle some custom options after saving the wizard options * @param string $field_id * @param mixed $field_value * @param mixed $prev_value * @param string $type */ public function after_save_field( $field_id, $field_value, $prev_value, $type ) { //only run when changes have been made if ( $field_value === $prev_value ) { return; } if ( $field_id==='other_host_type'){ if ( isset(RSSSL_LE()->hosts->hosts[$field_value]) ){ $dashboard = RSSSL_LE()->hosts->hosts[$field_value]['hosting_dashboard']; update_option('rsssl_hosting_dashboard', $dashboard, false); } else { update_option('rsssl_hosting_dashboard', false, false); } } if ( $field_id === 'email_address'&& is_email($field_value) ) { RSSSL_LE()->letsencrypt_handler->update_account($field_value); } } } } //class closure download.php 0000666 00000005205 15165257520 0007102 0 ustar 00 <?php # No need for the template engine define( 'WP_USE_THEMES', false ); #find the base path define( 'BASE_PATH', rsssl_find_wordpress_base_path()."/" ); # Load WordPress Core if ( !file_exists(BASE_PATH . 'wp-load.php') ) { die("WordPress not installed here"); } require_once( BASE_PATH.'wp-load.php' ); require_once( ABSPATH.'wp-includes/class-phpass.php' ); require_once( ABSPATH . 'wp-admin/includes/image.php' ); if ( !rsssl_user_can_manage() ) { die(); } if ( !isset($_GET["type"]) ) { die(); } if (!isset($_GET['token'])) { die(); } if (!wp_verify_nonce($_GET['token'], 'rsssl_download_cert')){ die(); } $type = sanitize_title($_GET['type']); switch($type) { case 'certificate': $file = get_option('rsssl_certificate_path'); $file_name = 'certificate.cert'; break; case 'private_key': $file = get_option('rsssl_private_key_path'); $file_name = 'private.pem'; break; case 'intermediate': $file = get_option('rsssl_intermediate_path'); $file_name = 'intermediate.pem'; break; default: $file = false; } if (!file_exists($file)) { $content = __("File missing. Please retry the previous steps.", "really-simple-ssl"); die(); } else { $content = file_get_contents($file); } $fp = fopen($file, 'rb'); if ($fp) { if (function_exists('mb_strlen')) { $fsize = mb_strlen($content, '8bit'); } else { $fsize = strlen($content); } $path_parts = pathinfo($file); header("Content-type: text/plain"); header("Content-Disposition: attachment; filename=\"".$file_name."\""); header("Content-length: $fsize"); header("Cache-Control: private",false); // required for certain browsers header("Pragma: public"); // required header("Expires: 0"); header("Cache-Control: must-revalidate, post-check=0, pre-check=0"); header("Content-Transfer-Encoding: binary"); echo $content; } else { echo "Something went wrong #2"; } fclose($fp); function rsssl_find_wordpress_base_path() { $path = dirname(__FILE__); do { if (file_exists($path . "/wp-config.php")) { //check if the wp-load.php file exists here. If not, we assume it's in a subdir. if ( file_exists( $path . '/wp-load.php') ) { return $path; } else { //wp not in this directory. Look in each folder to see if it's there. if ( file_exists( $path ) && $handle = opendir( $path ) ) { while ( false !== ( $file = readdir( $handle ) ) ) { if ( $file != "." && $file != ".." ) { $file = $path .'/' . $file; if ( is_dir( $file ) && file_exists( $file . '/wp-load.php') ) { $path = $file; break; } } } closedir( $handle ); } } return $path; } } while ($path = realpath("$path/..")); return false; } letsencrypt.php 0000666 00000005261 15165257520 0007651 0 ustar 00 <?php defined('ABSPATH') or die(); /** * Capability handling for Let's Encrypt * @return bool * * php -r "readfile('https://getcomposer.org/installer');" | php */ if (!function_exists('rsssl_letsencrypt_generation_allowed')) { function rsssl_letsencrypt_generation_allowed($strict = false) { /** * LE classes should also run if SSL is generated by rsssl, and the plus one cache is cleared. */ if ( get_option( 'rsssl_le_certificate_generated_by_rsssl' ) && !get_option('rsssl_plusone_count') ) { return true; } if ( get_option( 'rsssl_le_certificate_generated_by_rsssl' ) && wp_doing_cron() ) { return true; } if ( !current_user_can( 'manage_security' ) ) { return false; } if ( isset($_GET['letsencrypt'])) { return true; } return false; } } class RSSSL_LETSENCRYPT { private static $instance; public $le_restapi; public $field; public $hosts; public $letsencrypt_handler; private function __construct() { } public static function instance() { if ( ! isset( self::$instance ) && ! ( self::$instance instanceof RSSSL_LETSENCRYPT ) ) { self::$instance = new RSSSL_LETSENCRYPT; self::$instance->setup_constants(); self::$instance->includes(); if (rsssl_letsencrypt_generation_allowed() ) { self::$instance->hosts = new rsssl_le_hosts(); self::$instance->letsencrypt_handler = new rsssl_letsencrypt_handler(); self::$instance->le_restapi = new rsssl_le_restapi(); } } return self::$instance; } private function setup_constants() { define('rsssl_le_url', plugin_dir_url(__FILE__)); define('rsssl_le_path', trailingslashit(plugin_dir_path(__FILE__))); } private function includes() { require_once( rsssl_le_path . 'functions.php'); if ( rsssl_letsencrypt_generation_allowed() ) { require_once( rsssl_le_path . 'config/class-hosts.php' ); require_once( rsssl_le_path . 'config/fields.php'); require_once( rsssl_le_path . 'class-le-restapi.php' ); require_once( rsssl_le_path . 'class-letsencrypt-handler.php' ); require_once( rsssl_le_path . 'integrations/integrations.php' ); } require_once( rsssl_le_path . 'config/notices.php' ); } /** * Notice about possible compatibility issues with add ons */ public static function admin_notices() { } } function RSSSL_LE() { return RSSSL_LETSENCRYPT::instance(); } add_action( 'plugins_loaded', 'RSSSL_LE', 9 ); class RSSSL_RESPONSE { public $message; public $action; public $status; public $output; public $request_success; public function __construct($status, $action, $message, $output = false ) { $this->status = $status; $this->action = $action; $this->message = $message; $this->output = $output; $this->request_success = true; } } composer.json 0000666 00000000141 15165257520 0007276 0 ustar 00 { "require": { "fbett/le_acme2": "^1.5", "plesk/api-php-lib": "^1.0" } } integrations/directadmin/httpsocket.php 0000666 00000023532 15165257520 0014457 0 ustar 00 <?php /** * Socket communication class. * * Originally designed for use with DirectAdmin's API, this class will fill any HTTP socket need. * * Very, very basic usage: * $Socket = new HTTPSocket; * echo $Socket->get('http://user:pass@somesite.com/somedir/some.file?query=string&this=that'); * * @author Phi1 'l0rdphi1' Stier <l0rdphi1@liquenox.net> * @package HTTPSocket * @version 3.0.4 */ class HTTPSocket { var $version = '3.0.4'; /* all vars are private except $error, $query_cache, and $doFollowLocationHeader */ var $method = 'GET'; var $remote_host; var $remote_port; var $remote_uname; var $remote_passwd; var $result; var $result_header; var $result_body; var $result_status_code; var $lastTransferSpeed; var $bind_host; var $error = array(); var $warn = array(); var $query_cache = array(); var $doFollowLocationHeader = TRUE; var $redirectURL; var $max_redirects = 5; var $ssl_setting_message = 'DirectAdmin appears to be using SSL. Change your script to connect to ssl://'; var $extra_headers = array(); var $proxy = false; var $proxy_headers = array(); /** * Create server "connection". * */ function connect($host, $port = '' ) { if (!is_numeric($port)) { $port = 80; } $this->remote_host = $host; $this->remote_port = $port; } function bind( $ip = '' ) { if ( $ip == '' ) { $ip = $_SERVER['SERVER_ADDR']; } $this->bind_host = $ip; } /** * Change the method being used to communicate. * * @param string|null request method. supports GET, POST, and HEAD. default is GET */ function set_method( $method = 'GET' ) { $this->method = strtoupper($method); } /** * Specify a username and password. * * @param string|null username. defualt is null * @param string|null password. defualt is null */ function set_login( $uname = '', $passwd = '' ) { if ( strlen($uname) > 0 ) { $this->remote_uname = $uname; } if ( strlen($passwd) > 0 ) { $this->remote_passwd = $passwd; } } /** * For pass through, this function writes the data in chunks. */ private function stream_chunk($ch, $data) { echo($data); return strlen($data); } private function stream_header($ch, $data) { if (!preg_match('/^HTTP/i', $data)) { header($data); } return strlen($data); } /** * Query the server * * @param string containing properly formatted server API. See DA API docs and examples. Http:// URLs O.K. too. * @param string|array query to pass to url * @param int if connection KB/s drops below value here, will drop connection */ function query( $request, $content = '', $doSpeedCheck = 0 ) { $this->error = $this->warn = array(); $this->result_status_code = NULL; $is_ssl = FALSE; // is our request a http:// ... ? if (preg_match('!^http://!i',$request) || preg_match('!^https://!i',$request)) { $location = parse_url($request); if (preg_match('!^https://!i',$request)) { $this->connect('https://'.$location['host'],$location['port']); } else $this->connect('http://'.$location['host'],$location['port']); $this->set_login($location['user'],$location['pass']); $request = $location['path']; $content = $location['query']; if ( strlen($request) < 1 ) { $request = '/'; } } if (preg_match('!^ssl://!i', $this->remote_host)) $this->remote_host = 'https://'.substr($this->remote_host, 6); if (preg_match('!^tcp://!i', $this->remote_host)) $this->remote_host = 'http://'.substr($this->remote_host, 6); if (preg_match('!^https://!i', $this->remote_host)) $is_ssl = TRUE; $array_headers = array( 'Host' => ( $this->remote_port == 80 ? $this->remote_host : "$this->remote_host:$this->remote_port" ), 'Accept' => '*/*', 'Connection' => 'Close' ); foreach ( $this->extra_headers as $key => $value ) { $array_headers[$key] = $value; } $this->result = $this->result_header = $this->result_body = ''; // was content sent as an array? if so, turn it into a string if (is_array($content)) { $pairs = array(); foreach ( $content as $key => $value ) { $pairs[] = "$key=".urlencode($value); } $content = join('&',$pairs); unset($pairs); } $OK = TRUE; if ($this->method == 'GET' && isset($content) && $content != '') $request .= '?'.$content; $ch = curl_init($this->remote_host.':'.$this->remote_port.$request); if ($is_ssl) { curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); //1 curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); //2 //curl_setopt($ch, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1_2); } curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1); curl_setopt($ch, CURLOPT_RETURNTRANSFER,1); curl_setopt($ch, CURLOPT_USERAGENT, "HTTPSocket/$this->version"); curl_setopt($ch, CURLOPT_FORBID_REUSE, 1); curl_setopt($ch, CURLOPT_TIMEOUT, 100); curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10); curl_setopt($ch, CURLOPT_HEADER, 1); if ($this->proxy) { curl_setopt($ch, CURLOPT_RETURNTRANSFER,false); curl_setopt($ch, CURLOPT_HEADER, false); curl_setopt($ch, CURLINFO_HEADER_OUT, false); curl_setopt($ch, CURLOPT_BUFFERSIZE, 8192); // 8192 curl_setopt($ch, CURLOPT_WRITEFUNCTION, array($this, "stream_chunk")); curl_setopt($ch, CURLOPT_HEADERFUNCTION, array($this, "stream_header")); } curl_setopt($ch, CURLOPT_LOW_SPEED_LIMIT, 512); curl_setopt($ch, CURLOPT_LOW_SPEED_TIME, 120); // instance connection if ($this->bind_host) { curl_setopt($ch, CURLOPT_INTERFACE, $this->bind_host); } // if we have a username and password, add the header if ( isset($this->remote_uname) && isset($this->remote_passwd) ) { curl_setopt($ch, CURLOPT_USERPWD, $this->remote_uname.':'.$this->remote_passwd); } // for DA skins: if $this->remote_passwd is NULL, try to use the login key system if ( isset($this->remote_uname) && $this->remote_passwd == NULL ) { curl_setopt($ch, CURLOPT_COOKIE, "session={$_SERVER['SESSION_ID']}; key={$_SERVER['SESSION_KEY']}"); } // if method is POST, add content length & type headers if ( $this->method == 'POST' ) { curl_setopt($ch, CURLOPT_POST, 1); curl_setopt($ch, CURLOPT_POSTFIELDS, $content); //$array_headers['Content-type'] = 'application/x-www-form-urlencoded'; $array_headers['Content-length'] = strlen($content); } curl_setopt($ch, CURLOPT_HTTPHEADER, $array_headers); if( !($this->result = curl_exec($ch)) ) { $this->error[] .= curl_error($ch); $OK = FALSE; } $header_size = curl_getinfo($ch, CURLINFO_HEADER_SIZE); $this->result_header = substr($this->result, 0, $header_size); $this->result_body = substr($this->result, $header_size); $this->result_status_code = curl_getinfo($ch, CURLINFO_HTTP_CODE); $this->lastTransferSpeed = curl_getinfo($ch, CURLINFO_SPEED_DOWNLOAD) / 1024; curl_close($ch); $this->query_cache[] = $this->remote_host.':'.$this->remote_port.$request; $headers = $this->fetch_header(); // did we get the full file? if ( !empty($headers['content-length']) && $headers['content-length'] != strlen($this->result_body) ) { $this->result_status_code = 206; } // now, if we're being passed a location header, should we follow it? if ($this->doFollowLocationHeader) { //dont bother if we didn't even setup the script correctly if (isset($headers['x-use-https']) && $headers['x-use-https']=='yes') die($this->ssl_setting_message); if (isset($headers['location'])) { if ($this->max_redirects <= 0) die("Too many redirects on: ".$headers['location']); $this->max_redirects--; $this->redirectURL = $headers['location']; $this->query($headers['location']); } } } function getTransferSpeed() { return $this->lastTransferSpeed; } /** * The quick way to get a URL's content :) * * @param string URL * @param boolean return as array? (like PHP's file() command) * @return string result body */ function get($location, $asArray = FALSE ) { $this->query($location); if ( $this->get_status_code() == 200 ) { if ($asArray) { return preg_split("/\n/",$this->fetch_body()); } return $this->fetch_body(); } return FALSE; } /** * Returns the last status code. * 200 = OK; * 403 = FORBIDDEN; * etc. * * @return int status code */ function get_status_code() { return $this->result_status_code; } /** * Adds a header, sent with the next query. * * @param string header name * @param string header value */ function add_header($key,$value) { $this->extra_headers[$key] = $value; } /** * Clears any extra headers. * */ function clear_headers() { $this->extra_headers = array(); } /** * Return the result of a query. * * @return string result */ function fetch_result() { return $this->result; } /** * Return the header of result (stuff before body). * * @param string (optional) header to return * @return array result header */ function fetch_header( $header = '' ) { if ($this->proxy) return $this->proxy_headers; $array_headers = preg_split("/\r\n/",$this->result_header); $array_return = array( 0 => $array_headers[0] ); unset($array_headers[0]); foreach ( $array_headers as $pair ) { if ($pair == '' || $pair == "\r\n") continue; list($key,$value) = preg_split("/: /",$pair,2); $array_return[strtolower($key)] = $value; } if ( $header != '' ) { return $array_return[strtolower($header)]; } return $array_return; } /** * Return the body of result (stuff after header). * * @return string result body */ function fetch_body() { return $this->result_body; } /** * Return parsed body in array format. * * @return array result parsed */ function fetch_parsed_body() { parse_str($this->result_body,$x); return $x; } /** * Set a specifc message on how to change the SSL setting, in the event that it's not set correctly. */ function set_ssl_setting_message($str) { $this->ssl_setting_message = $str; } } integrations/directadmin/directadmin.php 0000666 00000007721 15165257520 0014554 0 ustar 00 <?php defined( 'ABSPATH' ) or die(); /** * @package DirectAdmin * @author Rogier Lankhorst * @copyright Copyright (C) 2021, Rogier Lankhorst * @license http://www.gnu.org/licenses/gpl-3.0.html GNU General Public License, version 3 * @link https://really-simple-ssl.com * @since Class available since Release 5.0.0 * */ require_once( rsssl_le_path . 'integrations/directadmin/httpsocket.php' ); require_once( rsssl_le_path . 'integrations/directadmin/functions.php' ); class rsssl_directadmin { public $host; private $login; private $password; public $ssl_installation_url; /** * Initiates the directadmin class. * */ public function __construct() { $password = RSSSL_LE()->letsencrypt_handler->decode( rsssl_get_option( 'directadmin_password' ) ); $host = rsssl_get_option( 'directadmin_host' ); $this->host = str_replace( array( 'http://', 'https://', ':2222' ), '', $host ); $this->login = rsssl_get_option( 'directadmin_username' ); $this->password = $password; $this->ssl_installation_url = 'https://' . $this->host . ""; } /** * Check if all creds are available * @return bool */ public function credentials_available(){ if (!empty($this->host) && !empty($this->password) && !empty($this->login)) { return true; } return false; } public function installSSL( $domains ) { $response = false; if ( is_array($domains) && count($domains)>0 ) { foreach( $domains as $domain ) { $response_item = $this->installSSLPerDomain($domain); //set on first iteration if ( !$response ) { $response = $response_item; } //override if not successfull, to always get the error. if ( $response->status !== 'success' ) { $response = $response_item; } } } if ( !$response ) { $response = new RSSSL_RESPONSE('error', 'stop', __("No valid list of domains.", "really-simple-ssl")); } return $response; } /** * Install certificate * * @param string $domain * * @return RSSSL_RESPONSE */ public function installSSLPerDomain( $domain ) { $key_file = get_option( 'rsssl_private_key_path' ); $cert_file = get_option( 'rsssl_certificate_path' ); $cabundle_file = get_option( 'rsssl_intermediate_path' ); try { $server_ssl=true; $server_port=2222; $sock = new HTTPSocket; if ($server_ssl){ $sock->connect("ssl://".$this->host, $server_port); } else { $sock->connect($this->host, $server_port); } $sock->set_login($this->login, $this->password); $sock->method = "POST"; $sock->query('/CMD_API_SSL', array( 'domain' => $domain, 'action' => 'save', 'type' => 'paste', 'certificate' => file_get_contents( $key_file ) . file_get_contents( $cert_file ) )); $response = $sock->fetch_parsed_body(); //set a default error response $status = 'warning'; $action = 'continue'; $message = rsssl_get_manual_instructions_text($this->ssl_installation_url); //if successful, proceed to next step if ( empty($response['details']) && stripos($response[0], 'Error' ) ) { $sock->query('/CMD_SSL', array( 'domain' => $domain, 'action' => 'save', 'type' => 'cacert', 'active' => 'yes', 'cacert' => file_get_contents( $cabundle_file ) )); $response = $sock->fetch_parsed_body(); if ( empty($response['details']) && stripos($response[0], 'Error' ) ) { $status = 'success'; $action = 'finalize'; $message = sprintf(__("SSL successfully installed on %s","really-simple-ssl"), $domain); update_option( 'rsssl_le_certificate_installed_by_rsssl', 'directadmin', false ); delete_option( 'rsssl_installation_error' ); } } } catch ( Exception $e ) { update_option( 'rsssl_installation_error', 'directadmin', false ); $status = 'warning'; $action = 'continue'; $message = $e->getMessage(); } return new RSSSL_RESPONSE( $status, $action, $message ); } } integrations/directadmin/functions.php 0000666 00000002525 15165257520 0014276 0 ustar 00 <?php defined( 'ABSPATH' ) or die(); function rsssl_install_directadmin(){ if (rsssl_is_ready_for('installation')) { $directadmin = new rsssl_directadmin(); $domains = RSSSL_LE()->letsencrypt_handler->get_subjects(); $response = $directadmin->installSSL($domains); if ( $response->status === 'success' ) { update_option('rsssl_le_certificate_installed_by_rsssl', 'directadmin', false ); } return $response; } else { $status = 'error'; $action = 'stop'; $message = __("The system is not ready for the installation yet. Please run the wizard again.", "really-simple-ssl"); return new RSSSL_RESPONSE($status, $action, $message); } } /** * Add actions for direct admin * @param array $fields * * @return array */ function rsssl_directadmin_add_condition_actions($fields){ $directadmin = new rsssl_directadmin(); if ( $directadmin->credentials_available() ) { $index = array_search( 'installation', array_column( $fields, 'id' ) ); //clear existing array $fields[ $index ]['actions'] = []; $fields[ $index ]['actions'][] = array( 'description' => __( "Attempting to install certificate...", "really-simple-ssl" ), 'action' => 'rsssl_install_directadmin', 'attempts' => 1, 'status' => 'inactive', ); } return $fields; } add_filter( 'rsssl_fields', 'rsssl_directadmin_add_condition_actions' ); integrations/integrations.php 0000666 00000001276 15165257520 0012513 0 ustar 00 <?php defined( 'ABSPATH' ) or die(); $other_host = rsssl_get_other_host(); if (file_exists( rsssl_le_path . "integrations/$other_host/$other_host.php" )) { require_once( rsssl_le_path . "integrations/$other_host/$other_host.php" ); } if (file_exists( rsssl_le_path . "integrations/$other_host/functions.php" )){ require_once( rsssl_le_path . "integrations/$other_host/functions.php" ); } if ( rsssl_is_cpanel() ) { require_once( rsssl_le_path . 'integrations/cpanel/cpanel.php' ); } else if ( rsssl_is_plesk() ) { require_once( rsssl_le_path . 'integrations/plesk/plesk.php' ); } else if ( rsssl_is_directadmin() ) { require_once( rsssl_le_path . 'integrations/directadmin/directadmin.php' ); } integrations/cloudways/functions.php 0000666 00000004024 15165257520 0014021 0 ustar 00 <?php defined( 'ABSPATH' ) or die(); function rsssl_cloudways_server_data(){ require_once( rsssl_le_path . 'integrations/cloudways/cloudways.php' ); $cloudways = new rsssl_Cloudways(); return $cloudways->getServerInfo(); } function rsssl_cloudways_install_ssl(){ if (rsssl_is_ready_for('installation')) { require_once( rsssl_le_path . 'integrations/cloudways/cloudways.php' ); $domains = RSSSL_LE()->letsencrypt_handler->get_subjects(); $cloudways = new rsssl_Cloudways(); $response = $cloudways->installSSL($domains); if ($response->status === 'success') { update_option('rsssl_le_certificate_installed_by_rsssl', 'cloudways', false); } return $response; } else { $status = 'error'; $action = 'stop'; $message = __("The system is not ready for the installation yet. Please run the wizard again.", "really-simple-ssl"); return new RSSSL_RESPONSE($status, $action, $message); } } function rsssl_cloudways_auto_renew(){ require_once( rsssl_le_path . 'integrations/cloudways/cloudways.php' ); $cloudways = new rsssl_Cloudways(); return $cloudways->enableAutoRenew(); } function rsssl_cloudways_add_condition_actions($fields){ $index = array_search('installation',array_column($fields,'id')); $fields[$index]['actions'] = array( array( 'description' => __("Retrieving Cloudways server data...", "really-simple-ssl"), 'action'=> 'rsssl_cloudways_server_data', 'attempts' => 5, 'status' => 'inactive', ), array( 'description' => __("Installing SSL certificate...", "really-simple-ssl"), 'action'=> 'rsssl_cloudways_install_ssl', 'attempts' => 5, 'status' => 'inactive', ), array( 'description' => __("Enabling auto renew...", "really-simple-ssl"), 'action'=> 'rsssl_cloudways_auto_renew', 'attempts' => 5, 'status' => 'inactive', ), ); //drop store credentials field $creds_index = array_search('store_credentials',array_column($fields,'id')); unset($fields[$creds_index]); return $fields; } add_filter( 'rsssl_fields', 'rsssl_cloudways_add_condition_actions' ); integrations/cloudways/cloudways.php 0000666 00000017771 15165257520 0014040 0 ustar 00 <?php /** * @package CloudWays * @author Rogier Lankhorst * @copyright Copyright (C) 2021, Rogier Lankhorst * @license http://www.gnu.org/licenses/gpl-3.0.html GNU General Public License, version 3 * @link https://really-simple-ssl.com * @since Class available since Release 5.0.0 * * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <https://www.gnu.org/licenses/>. * */ class rsssl_Cloudways { private $email; private $api_key; public $ssl_installation_url; /** * Initiates the cloudways class. * * @param string $email * @param string $api_key */ public function __construct( ) { $this->email = rsssl_get_option('cloudways_user_email'); $this->api_key = RSSSL_LE()->letsencrypt_handler->decode( rsssl_get_option('cloudways_api_key') ); $this->ssl_installation_url = ""; } /** * * @param string $method GET|POST|PUT|DELETE * @param string $url relative URL for the call * @param string $accessToken Access token generated using OAuth Call * @param array $post Optional post data for the call * * @return RSSSL_RESPONSE */ private function callCloudwaysAPI( $method, $url, $accessToken, $post = [] ) { $baseURL = 'https://api.cloudways.com/api/v1/'; try { $ch = curl_init(); curl_setopt( $ch, CURLOPT_CUSTOMREQUEST, $method ); curl_setopt( $ch, CURLOPT_URL, $baseURL . $url ); curl_setopt( $ch, CURLOPT_RETURNTRANSFER, true ); if ( $accessToken ) { curl_setopt( $ch, CURLOPT_HTTPHEADER, [ 'Authorization: Bearer ' . $accessToken ] ); } //ssl_domains[]=fungibleownership.com&ssl_domains[]=www.fungibleownership.com $encoded = ''; if ( count( $post ) ) { foreach ( $post as $name => $value ) { if ( is_array( $value) ) { foreach ( $value as $sub_value ) { $encoded .= $name.'[]='.urlencode( $sub_value) . '&'; } } else { $encoded .= urlencode( $name ) . '=' . urlencode( $value ) . '&'; } } $encoded = substr( $encoded, 0, strlen( $encoded ) - 1 ); curl_setopt( $ch, CURLOPT_POSTFIELDS, $encoded ); curl_setopt( $ch, CURLOPT_POST, 1 ); } $output = curl_exec( $ch ); $httpcode = curl_getinfo( $ch, CURLINFO_HTTP_CODE ); if ($output && isset($output->error_description)) { return new RSSSL_RESPONSE( 'error', 'stop', $output->error_description, false ); } else if ($httpcode != '200' && $output && isset($output->message) ){ return new RSSSL_RESPONSE( 'error', 'stop', $output->message ); } else if ( $httpcode != '200' ) { $message = $httpcode . ' output: ' . substr( $output, 0, 10000 ); return new RSSSL_RESPONSE( 'error', 'stop', $message ); } curl_close( $ch ); return new RSSSL_RESPONSE( 'success', 'continue', '', json_decode( $output ) ); } catch(Exception $e) { return new RSSSL_RESPONSE( 'error', 'stop', $e->getMessage() ); } } /** * Get an access token * @return RSSSL_RESPONSE */ private function getAccessToken() { $accessToken = get_transient('rsssl_cw_t'); if (!$accessToken) { $response = $this->callCloudwaysAPI( 'POST', '/oauth/access_token', null, [ 'email' => $this->email, 'api_key' => $this->api_key ] ); if ($response->status === 'success' ) { $accessToken = $response->output->access_token; set_transient('rsssl_cw_t', $accessToken, 1800); } else { return new RSSSL_RESPONSE( 'error', 'stop', $response->message ); } } return new RSSSL_RESPONSE( 'success', 'continue','', $accessToken ); } /** * @param array $domains * * @return RSSSL_RESPONSE */ public function installSSL($domains){ $response = $this->getAccessToken(); if ( $response->status !== 'success' ) { return new RSSSL_RESPONSE('error','stop',$response->message); } $accessToken = $response->output; $response = $this->getServerInfo(); if ($response->status === 'success' ) { $server_id = get_transient('rsssl_cw_server_id' ); $app_id = get_transient('rsssl_cw_app_id'); $args = [ 'server_id' => $server_id, 'app_id' => $app_id, 'ssl_email' => $this->email, 'wild_card' => RSSSL_LE()->letsencrypt_handler->is_wildcard(), 'ssl_domains' => $domains, ]; $response = $this->callCloudWaysAPI( 'POST', 'security/lets_encrypt_install', $accessToken, $args ); } return $response; } /** * * @return RSSSL_RESPONSE */ public function enableAutoRenew(){ $response = $this->getAccessToken(); if ( $response->status !== 'success' ) { return new RSSSL_RESPONSE('error','stop', __("Failed retrieving access token","really-simple-ssl")); } $accessToken = $response->output; $response = $this->getServerInfo(); if ($response->status === 'success' ) { $app_id = get_transient('rsssl_cw_app_id'); $server_id = get_transient('rsssl_cw_server_id' ); $response = $this->callCloudWaysAPI( 'POST', 'security/lets_encrypt_auto', $accessToken, [ 'server_id' => $server_id, 'app_id' => $app_id, 'auto' => true, ] ); } if ( $response->status === 'success' ) { $status = 'success'; $action = 'continue'; $message = __("Successfully installed Let's Encrypt","really-simple-ssl"); } elseif ($response->status === 'error') { //in some cases, the process is already started, which also signifies success. if ( strpos($response->message, 'An operation is already in progress for this server')) { $status = 'success'; $action = 'continue'; $message = __("Successfully installed Let's Encrypt","really-simple-ssl"); } else { $status = $response->status; $action = $response->action; $message = $response->message; } } else { $status = $response->status; $action = $response->action; $message = __("Error enabling auto renew for Let's Encrypt","really-simple-ssl"); } return new RSSSL_RESPONSE( $status, $action, $message ); } /** * Get the server id and app id * * @return RSSSL_RESPONSE */ public function getServerInfo(){ if ( get_transient('rsssl_cw_app_id') && get_transient('rsssl_cw_server_id' ) ) { $status = 'success'; $action = 'continue'; $message = __("Successfully retrieved server id and app id","really-simple-ssl"); return new RSSSL_RESPONSE( $status, $action, $message ); } $response = $this->getAccessToken(); if ( $response->status !== 'success' ) { return new RSSSL_RESPONSE('error','stop', $response->message); } $accessToken = $response->output; $response = $this->callCloudwaysAPI('GET', '/server', $accessToken ); $success = false; if ($response->status === 'success') { $serverList = $response->output; $servers = $serverList->servers; foreach ($servers as $server ){ $apps = $server->apps; foreach ($apps as $app ){ $app_domain = $app->cname; $this_site_domain = str_replace(array('https://', 'http://', 'www.'), '',site_url()); if (strpos($app_domain, $this_site_domain) !== false ) { $success = true; set_transient('rsssl_cw_app_id', $app->id, WEEK_IN_SECONDS); set_transient('rsssl_cw_server_id', $server->id, WEEK_IN_SECONDS); break 2; } } } } if ( $success ) { $status = 'success'; $action = 'continue'; $message = __("Successfully retrieved server id and app id","really-simple-ssl"); } else { $status = 'error'; $action = 'stop'; if ( isset($serverList->error_description) ) { $message = $serverList->error_description; } else { $message = __("Could not retrieve server list","really-simple-ssl"); } } return new RSSSL_RESPONSE( $status, $action, $message ); } } integrations/plesk/plesk.php 0000666 00000005714 15165257520 0012242 0 ustar 00 <?php /** * @package PLESK * @author Rogier Lankhorst * @copyright Copyright (C) 2021, Rogier Lankhorst * @license http://www.gnu.org/licenses/gpl-3.0.html GNU General Public License, version 3 * @link https://really-simple-ssl.com * @since Class available since Release 5.0.0 * * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <https://www.gnu.org/licenses/>. * */ use PleskX\Api\Client; require_once rsssl_le_path . 'vendor/autoload.php'; require_once( rsssl_le_path . 'integrations/plesk/functions.php' ); class rsssl_plesk { public $host; private $login; private $password; public $ssl_installation_url; /** * Initiates the Plesk class. * */ public function __construct() { $password = RSSSL_LE()->letsencrypt_handler->decode( rsssl_get_option('plesk_password') ); $host = rsssl_get_option('plesk_host'); $this->host = str_replace(array('http://', 'https://', ':8443'), '', $host); $this->login = rsssl_get_option('plesk_username'); $this->password = $password; $this->ssl_installation_url = 'https://'.$this->host.":8443/smb/ssl-certificate/list/id/21"; } /** * Check if all creds are available * @return bool */ public function credentials_available(){ if (!empty($this->host) && !empty($this->password) && !empty($this->login)) { return true; } return false; } /** * Install certificate * @param $domains * * @return RSSSL_RESPONSE */ public function installSSL($domains){ $key_file = get_option('rsssl_private_key_path'); $cert_file = get_option('rsssl_certificate_path'); $cabundle_file = get_option('rsssl_intermediate_path'); try { $client = new Client($this->host); $client->setCredentials($this->login, $this->password); $response = $client->certificate()->install($domains, [ 'csr' => '', 'pvt' => file_get_contents($key_file), 'cert' => file_get_contents($cert_file), 'ca' => file_get_contents($cabundle_file), ]); update_option('rsssl_le_certificate_installed_by_rsssl', 'plesk', false); delete_option('rsssl_installation_error' ); $status = 'success'; $action = 'continue'; $message = __('Successfully installed SSL',"really-simple-ssl"); } catch(Exception $e) { update_option('rsssl_installation_error', 'plesk', false); $status = 'warning'; $action = 'continue'; $message = $e->getMessage(); } return new RSSSL_RESPONSE($status, $action, $message); } } integrations/plesk/functions.php 0000666 00000002441 15165257520 0013126 0 ustar 00 <?php defined( 'ABSPATH' ) or die(); function rsssl_plesk_install(){ if (rsssl_is_ready_for('installation')) { $cpanel = new rsssl_plesk(); $domains = RSSSL_LE()->letsencrypt_handler->get_subjects(); $response = $cpanel->installSSL($domains); if ( $response->status === 'success' ) { update_option('rsssl_le_certificate_installed_by_rsssl', 'cpanel:default', false); } return $response; } else { $status = 'error'; $action = 'stop'; $message = __("The system is not ready for the installation yet. Please run the wizard again.", "really-simple-ssl"); return new RSSSL_RESPONSE($status, $action, $message); } } /** * Add the step to install SSL using Plesk * @param array $fields * * @return array */ function rsssl_plesk_add_installation_step($fields){ $plesk = new rsssl_plesk(); if ( $plesk->credentials_available() ) { $index = array_search( 'installation', array_column( $fields, 'id' ) ); $fields[ $index ]['actions'] = array_merge(array( array( 'description' => __("Installing SSL certificate using PLESK API...", "really-simple-ssl"), 'action'=> 'rsssl_plesk_install', 'attempts' => 1, 'status' => 'inactive', ) ) , $fields[ $index ]['actions'] ); } return $fields; } add_filter( 'rsssl_fields', 'rsssl_plesk_add_installation_step' ); integrations/cpanel/cpanel.php 0000666 00000026445 15165257520 0012516 0 ustar 00 <?php defined( 'ABSPATH' ) or die(); require_once( rsssl_le_path . 'integrations/cpanel/functions.php' ); /** * Completely rebuilt and improved on the FreeSSL.tech Auto CPanel class by Anindya Sundar Mandal */ class rsssl_cPanel { public $host; private $username; private $password; public $ssl_installation_url; /** * Initiates the cPanel class. */ public function __construct() { $username = rsssl_get_option('cpanel_username'); $password = RSSSL_LE()->letsencrypt_handler->decode( rsssl_get_option('cpanel_password') ); $host = rsssl_get_option('cpanel_host'); $this->host = str_replace( array('http://', 'https://', ':2083',':'), '', $host ); $this->username = $username; $this->password = $password; $this->ssl_installation_url = 'https://'.$this->host.":2083/frontend/jupiter/ssl/install.html"; } /** * Check if all creds are available * @return bool */ public function credentials_available(){ if (!empty($this->host) && !empty($this->password) && !empty($this->username)) { return true; } return false; } /** * Install SSL for all passed domains * @param array $domains * * @return RSSSL_RESPONSE */ public function installSSL($domains) { $response = false; if ( is_array($domains) && count($domains)>0 ) { foreach( $domains as $domain ) { $response_item = $this->installSSLPerDomain($domain); //set on first iteration if ( !$response ) { $response = $response_item; } //override if not successfull, to always get the error. if ( $response->status !== 'success' ) { $response = $response_item; } } } if ( !$response ) { $response = new RSSSL_RESPONSE('error', 'stop', __("No valid list of domains.", "really-simple-ssl")); } if ( $response->status === 'success' ) { update_option('rsssl_le_certificate_installed_by_rsssl', 'cpanel:default', false); } return $response; } /** * Install an SSL certificate on the domain provided - using cPanel UAPI. * * @param string $domain * * @return RSSSL_RESPONSE */ public function installSSLPerDomain($domain) { $shell_addon_active = defined('rsssl_shell_path'); $key_file = get_option('rsssl_private_key_path'); $cert_file = get_option('rsssl_certificate_path'); $cabundle_file = get_option('rsssl_intermediate_path'); $request_uri = 'https://'.$this->host.':2083/execute/SSL/install_ssl'; $payload = [ 'domain' => $domain, 'cert' => file_get_contents($cert_file), 'key' => file_get_contents($key_file), 'cabundle' => file_get_contents($cabundle_file), ]; $response_raw = $this->connectUapi($request_uri, $payload); $isIpBlock = $this->isIpBlock($response_raw); $isLoginError = !$isIpBlock && $this->isLoginError($response_raw); $response = json_decode($response_raw); //Validate $response if ($isIpBlock) { update_option( 'rsssl_installation_error', 'cpanel:autossl', false ); $status = 'error'; $action = 'stop'; $message = __( "Your website's ip address is blocked. Please add your domain's ip address to the security policy in CPanel", "really-simple-ssl" ); } else if ($isLoginError) { update_option('rsssl_installation_error', 'cpanel:autossl', false); $status = 'error'; $action = 'stop'; $message = __("Login credentials incorrect. Please check your login credentials for cPanel.","really-simple-ssl"); } else if ( empty($response) ) { update_option('rsssl_installation_error', 'cpanel:default', false); $status = 'warning'; $action = $shell_addon_active ? 'skip' : 'continue'; $message = rsssl_get_manual_instructions_text($this->ssl_installation_url); } else if ($response->status) { delete_option('rsssl_installation_error' ); $status = 'success'; $action = 'continue'; $message = sprintf(__("SSL successfully installed on %s","really-simple-ssl"), $domain); } else { update_option('rsssl_installation_error', 'cpanel:default', false); $status = 'error'; $action = $shell_addon_active ? 'skip' : 'continue'; $message = __("Errors were reported during installation","really-simple-ssl").'<br> '.$response->errors[0]; } return new RSSSL_RESPONSE($status, $action, $message); } /** * Based on the known output of an ip block html page, check if the user should whitelist their own website ip. * @param $raw * * @return bool */ public function isIpBlock($raw){ $triggers = [ 'security_policy', 'You appear to be logging in from an unknown location', 'unrecognized IP address', ]; foreach($triggers as $key => $trigger ) { if (strpos($raw,$trigger)!==false) { return true; } } return false; } /** * Based on the known output of an ip block html page, check if the user has entered incorrect login creds * @param $raw * * @return bool */ public function isLoginError($raw){ $triggers = [ 'input-field-login icon password', 'name="pass" id="pass"', ]; foreach($triggers as $key => $trigger ) { if (strpos($raw,$trigger)!==false) { return true; } } return false; } /** * @param $domains * * @return RSSSL_RESPONSE */ public function enableAutoSSL($domains){ $domains = implode(',', $domains); $request_uri = 'https://'.$this->host.':2083/execute/SSL/remove_autossl_excluded_domains'; $payload = [ 'domains' => $domains, ]; $response_raw = $this->connectUapi($request_uri, $payload); $isIpBlock = $this->isIpBlock($response_raw); $response = json_decode($response_raw); //Validate $response if ($isIpBlock) { update_option('rsssl_installation_error', 'cpanel:autossl', false); $status = 'error'; $action = 'stop'; $message = __("Your website's ip address is blocked. Please add your domain's ip address to the security policy in CPanel","really-simple-ssl"); } else if (empty($response)) { update_option('rsssl_installation_error', 'cpanel:autossl', false); $status = 'error'; $action = 'skip'; $message = rsssl_get_manual_instructions_text($this->ssl_installation_url); } else if ($response->status) { delete_option('rsssl_installation_error'); $status = 'success'; $action = 'finalize'; $message = __("SSL successfully installed on $domains","really-simple-ssl"); } else { update_option('rsssl_installation_error', 'cpanel:autossl', false); $status = 'error'; $action = 'skip';//we try the default next $message = __("Errors were reported during installation.","really-simple-ssl").'<br> '.$response->errors[0]; } return new RSSSL_RESPONSE($status, $action, $message); } /** * Connect to the cPanel using UAPI. * * @param string $request_uri * @param null|array $payload * * @return mixed */ public function connectUapi($request_uri, $payload = null) { // Set up the cURL request object. $ch = curl_init($request_uri); curl_setopt($ch, CURLOPT_HTTPAUTH, CURLAUTH_BASIC); curl_setopt($ch, CURLOPT_USERPWD, $this->username.':'.$this->password); curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); curl_setopt($ch, CURLOPT_BUFFERSIZE, 131072); if (null !== $payload) { // Set up a POST request with the payload. curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_POSTFIELDS, $payload); } curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); // Make the call, and then terminate the cURL caller object. $curl_response = curl_exec($ch); if (curl_errno($ch)) { $error_msg = curl_error($ch); } curl_close($ch); //return output. return $curl_response; } /** * Set DNS TXT record using Json API through cPanel XMLAPI. * * @param string $domain * @param string $value * * @return RSSSL_RESPONSE */ public function set_txt_record($domain, $value) { $args = [ 'domain' => $domain, 'name' => '_acme-challenge', 'type' => 'TXT', 'txtdata' => $value, 'ttl' => '600', 'class' => 'IN', 'cpanel_jsonapi_user' => $this->username, 'cpanel_jsonapi_module' => 'ZoneEdit', 'cpanel_jsonapi_func' => 'add_zone_record', 'cpanel_jsonapi_apiversion' => '2', ]; $args = http_build_query($args, '', '&'); $url = 'https://'.$this->host.':2083/json-api/cpanel'; $authstr = 'Authorization: Basic '.base64_encode($this->username.':'.$this->password)."\r\n"; $curl = curl_init(); curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 0); curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, 0); curl_setopt($curl, CURLOPT_URL, $url); curl_setopt($curl, CURLOPT_BUFFERSIZE, 131072); $header[0] = $authstr. "Content-Type: application/x-www-form-urlencoded\r\n". 'Content-Length: '.\strlen($args)."\r\n"."\r\n".$args; curl_setopt($curl, CURLOPT_HTTPHEADER, $header); curl_setopt($curl, CURLOPT_POST, 1); $response = curl_exec($curl); curl_close($curl); if (false === $response) { return new RSSSL_RESPONSE('error', 'stop', __("Unable to connect to cPanel", "really-simple-ssl").' '.curl_error($curl)); } if (true === stristr($response, '<html>')) { return new RSSSL_RESPONSE('error', 'stop', __("Login credentials incorrect", "really-simple-ssl")); } $response_array = json_decode($response, true); if ( isset($response_array['cpanelresult']['data'][0]['result']['status']) ) { if ($response_array['cpanelresult']['data'][0]['result']['status']) { $status = 'success'; $action = 'continue'; $message = __("Successfully added TXT record.","really-simple-ssl"); } else { $status = 'warning'; $action = 'continue'; $message = __("Could not automatically add TXT record. Please proceed manually, following the steps below.","really-simple-ssl"); if (isset($response_array['cpanelresult']['data'][0]['result']['statusmsg'])) { $message .= '<br>'.$response_array['cpanelresult']['data'][0]['result']['statusmsg']; } } return new RSSSL_RESPONSE($status, $action, $message); } $event_result = (bool) $response_array['cpanelresult']['event']['result']; $preevent_result = isset($response_array['cpanelresult']['preevent']) ? (bool) $response_array['cpanelresult']['preevent']['result'] : true; //Some cPanel doesn't provide this key. In that case, ignore it by setting 'true'. $postevent_result = isset($response_array['cpanelresult']['postevent']) ? (bool) $response_array['cpanelresult']['postevent']['result'] : true; //Some cPanel doesn't provide this key. In that case, ignore it by setting 'true'. if ($event_result && $preevent_result && $postevent_result) { $status = 'success'; $action = 'continue'; $message = __("Successfully added TXT record.","really-simple-ssl"); } else { $status = 'warning'; $action = 'continue'; $message = __("Could not automatically add TXT record. Please proceed manually, following the steps below.","really-simple-ssl"); } return new RSSSL_RESPONSE($status, $action, $message); } } integrations/cpanel/functions.php 0000666 00000007263 15165257520 0013261 0 ustar 00 <?php defined( 'ABSPATH' ) or die(); function rsssl_install_cpanel_autossl(){ if ( rsssl_is_ready_for('installation') ) { $cpanel = new rsssl_cPanel(); $domains = RSSSL_LE()->letsencrypt_handler->get_subjects(); $response = $cpanel->enableAutoSSL($domains); if ( $response->status === 'success' ) { update_option('rsssl_le_certificate_installed_by_rsssl', 'cpanel:autossl', false); } return $response; } $status = 'error'; $action = 'stop'; $message = __("The system is not ready for the installation yet. Please run the wizard again.", "really-simple-ssl"); return new RSSSL_RESPONSE($status, $action, $message); } function rsssl_install_cpanel_default(){ if (rsssl_is_ready_for('installation')) { $cpanel = new rsssl_cPanel(); $domains = RSSSL_LE()->letsencrypt_handler->get_subjects(); $response = $cpanel->installSSL($domains); if ( $response->status === 'success' ) { update_option('rsssl_le_certificate_installed_by_rsssl', 'cpanel:default', false); } return $response; } $status = 'error'; $action = 'stop'; $message = __("The system is not ready for the installation yet. Please run the wizard again.", "really-simple-ssl"); return new RSSSL_RESPONSE($status, $action, $message); } function rsssl_cpanel_set_txt_record(){ if ( rsssl_is_ready_for('dns-verification') ) { $cpanel = new rsssl_cPanel(); $tokens = get_option('rsssl_le_dns_tokens'); if ( !$tokens) { $status = 'error'; $action = 'stop'; $message = __('Token not generated. Please complete the previous step.',"really-simple-ssl"); return new RSSSL_RESPONSE($status, $action, $message); } foreach ($tokens as $domain => $token){ if (strpos($domain, '*') !== false) continue; $response = $cpanel->set_txt_record($domain, $token); } if ( $response->status === 'success' ) { update_option('rsssl_le_dns_configured_by_rsssl', true, false); } return $response; } else { $status = 'error'; $action = 'stop'; $message = __("The system is not ready for the DNS verification yet. Please run the wizard again.", "really-simple-ssl"); return new RSSSL_RESPONSE($status, $action, $message); } } /** * Add actions for cpanel * @param array $fields * * @return array */ function rsssl_cpanel_add_condition_actions($fields){ $cpanel = new rsssl_cPanel(); if ( $cpanel->credentials_available() ) { //this defaults to true, if not known. $auto_ssl = RSSSL_LE()->hosts->host_api_supported( 'cpanel:autossl' ); $default_ssl = RSSSL_LE()->hosts->host_api_supported( 'cpanel:default' ); $installation_index = array_search( 'installation', array_column( $fields, 'id' ) ); $dns_index = array_search( 'dns-verification', array_column( $fields, 'id' ) ); //clear existing array if ( $auto_ssl || $default_ssl ) { $fields[ $installation_index ]['actions'] = array(); } if ( $auto_ssl ) { $fields[ $installation_index ]['actions'][] = [ 'description' => __( "Attempting to install certificate using AutoSSL...", "really-simple-ssl" ), 'action' => 'rsssl_install_cpanel_autossl', 'attempts' => 1, 'status' => 'inactive', ]; } if ( $default_ssl ) { $fields[ $dns_index ]['actions'][] = [ 'description' => __( "Attempting to set DNS txt record...", "really-simple-ssl" ), 'action' => 'rsssl_cpanel_set_txt_record', 'attempts' => 1, 'status' => 'inactive', ]; $fields[ $installation_index ]['actions'][] = [ 'description' => __( "Attempting to install certificate...", "really-simple-ssl" ), 'action' => 'rsssl_install_cpanel_default', 'attempts' => 1, 'status' => 'inactive', ]; } } return $fields; } add_filter("rsssl_fields", "rsssl_cpanel_add_condition_actions"); integrations/index.php 0000666 00000000042 15165257520 0011102 0 ustar 00 <?php //You don't belong here. ?> integrations/hostgator/hostgator.php 0000666 00000000250 15165257520 0014020 0 ustar 00 <?php defined( 'ABSPATH' ) or die(); /** * On hostgator, we don't have the cpanel api, so remove these steps. * This is managed in the config, in the hosts array */ cron.php 0000666 00000005242 15165257520 0006235 0 ustar 00 <?php defined( 'ABSPATH' ) or die(); add_action( 'plugins_loaded', 'rsssl_le_schedule_cron' ); function rsssl_le_schedule_cron() { //only run if SSL is enabled. if ( !rsssl_get_option('ssl_enabled') ) { return; } //only if generated by RSSSL. if ( ! get_option( 'rsssl_le_certificate_generated_by_rsssl' ) ) { return; } $useCron = true; if ( $useCron ) { if ( ! wp_next_scheduled( 'rsssl_le_every_week_hook' ) ) { wp_schedule_event( time(), 'rsssl_le_weekly', 'rsssl_le_every_week_hook' ); } if ( ! wp_next_scheduled( 'rsssl_le_every_day_hook' ) ) { wp_schedule_event( time(), 'rsssl_le_daily', 'rsssl_le_every_day_hook' ); } add_action( 'rsssl_le_every_week_hook', 'rsssl_le_cron_maybe_start_renewal' ); add_action( 'rsssl_le_every_day_hook', 'rsssl_le_check_renewal_status' ); } else { add_action( 'init', 'rsssl_le_cron_maybe_start_renewal' ); add_action( 'init', 'rsssl_le_check_renewal_status' ); } } /** * Check if the certificate is generated by RSSSL. If so, renew if necessary */ function rsssl_le_cron_maybe_start_renewal(){ if ( !rsssl_generated_by_rsssl() ) { return; } if ( RSSSL_LE()->letsencrypt_handler->cron_certificate_needs_renewal() ) { update_option("rsssl_le_start_renewal", true, false); } if ( RSSSL_LE()->letsencrypt_handler->certificate_install_required() ) { update_option("rsssl_le_start_installation", true, false); } } function rsssl_le_check_renewal_status(){ if ( !rsssl_generated_by_rsssl() ) { return; } //when DNS validated, without api, we cannot autorenew if ( !RSSSL_LE()->letsencrypt_handler->ssl_generation_can_auto_renew() ) { return; } $renewal_active = get_option("rsssl_le_start_renewal"); $installation_active = get_option("rsssl_le_start_installation"); if ( $renewal_active ) { RSSSL_LE()->letsencrypt_handler->create_bundle_or_renew(); } else if ( $installation_active ) { RSSSL_LE()->letsencrypt_handler->cron_renew_installation(); } } add_filter( 'cron_schedules', 'rsssl_le_filter_cron_schedules' ); function rsssl_le_filter_cron_schedules( $schedules ) { $schedules['rsssl_le_weekly'] = array( 'interval' => WEEK_IN_SECONDS, 'display' => __( 'Once every week' ) ); $schedules['rsssl_le_daily'] = array( 'interval' => DAY_IN_SECONDS, 'display' => __( 'Once every day' ) ); $schedules['rsssl_le_five_minutes'] = array( 'interval' => 5 * MINUTE_IN_SECONDS, 'display' => __( 'Once every 5 minutes' ) ); return $schedules; } register_deactivation_hook( rsssl_file, 'rsssl_le_clear_scheduled_hooks' ); function rsssl_le_clear_scheduled_hooks() { wp_clear_scheduled_hook( 'rsssl_le_every_week_hook' ); wp_clear_scheduled_hook( 'rsssl_le_every_day_hook' ); } vendor/autoload.php 0000666 00000000262 15165257520 0010376 0 ustar 00 <?php // autoload.php @generated by Composer require_once __DIR__ . '/composer/autoload_real.php'; return ComposerAutoloaderInita8412ede23fd11b4d0e29303fdebd5f4::getLoader(); vendor/plesk/api-php-lib/Dockerfile 0000666 00000000333 15165257520 0013266 0 ustar 00 FROM php:7.3-cli RUN apt-get update \ && apt-get install -y unzip \ && docker-php-ext-install pcntl \ && curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer vendor/plesk/api-php-lib/.styleci.yml 0000666 00000000150 15165257520 0013546 0 ustar 00 preset: recommended disabled: - align_double_arrow - phpdoc_align - blank_line_after_opening_tag vendor/plesk/api-php-lib/docker-compose.yml 0000666 00000000672 15165257520 0014737 0 ustar 00 version: '2' services: plesk: image: plesk/plesk logging: driver: none ports: ["8443:8443"] tests: build: . environment: REMOTE_URL: https://plesk:8443 REMOTE_PASSWORD: changeme1Q** command: bash -c "cd /opt/api-php-lib && composer install && ./wait-for-plesk.sh && composer test -- --testdox" depends_on: - plesk links: - plesk volumes: - .:/opt/api-php-lib vendor/plesk/api-php-lib/composer.json 0000666 00000002046 15165257520 0014021 0 ustar 00 { "name": "plesk/api-php-lib", "type": "library", "description": "PHP object-oriented library for Plesk XML-RPC API", "license": "Apache-2.0", "authors": [ { "name": "Alexei Yuzhakov", "email": "sibprogrammer@gmail.com" }, { "name": "Plesk International GmbH.", "email": "plesk-dev-leads@plesk.com" } ], "require": { "php": "^7.3", "ext-curl": "*", "ext-xml": "*", "ext-simplexml": "*" }, "require-dev": { "phpunit/phpunit": "^9", "spatie/phpunit-watcher": "^1.22" }, "config": { "process-timeout": 0 }, "scripts": { "test": "phpunit", "test:watch": "phpunit-watcher watch" }, "autoload": { "psr-4": { "PleskX\\": "src/" } }, "autoload-dev": { "psr-4": { "PleskXTest\\": "tests/" } }, "extra": { "branch-alias": { "dev-master": "2.0.x-dev" } } } vendor/plesk/api-php-lib/src/Api/Struct/EventLog/DetailedEvent.php 0000666 00000001352 15165257520 0021033 0 ustar 00 <?php // Copyright 1999-2020. Plesk International GmbH. namespace PleskX\Api\Struct\EventLog; defined('ABSPATH') or die(); class DetailedEvent extends \PleskX\Api\Struct { /** @var int */ public $id; /** @var string */ public $type; /** @var int */ public $time; /** @var string */ public $class; /** @var string */ public $objectId; /** @var string */ public $user; /** @var string */ public $host; public function __construct($apiResponse) { $this->_initScalarProperties($apiResponse, [ 'id', 'type', 'time', 'class', ['obj_id' => 'objectId'], 'user', 'host', ]); } } vendor/plesk/api-php-lib/src/Api/Struct/EventLog/Event.php 0000666 00000001025 15165257520 0017374 0 ustar 00 <?php // Copyright 1999-2020. Plesk International GmbH. namespace PleskX\Api\Struct\EventLog; defined('ABSPATH') or die(); class Event extends \PleskX\Api\Struct { /** @var string */ public $type; /** @var int */ public $time; /** @var string */ public $class; /** @var string */ public $id; public function __construct($apiResponse) { $this->_initScalarProperties($apiResponse, [ 'type', 'time', 'class', 'id', ]); } } vendor/plesk/api-php-lib/src/Api/Struct/Locale/Info.php 0000666 00000000757 15165257520 0016675 0 ustar 00 <?php // Copyright 1999-2020. Plesk International GmbH. namespace PleskX\Api\Struct\Locale; defined('ABSPATH') or die(); class Info extends \PleskX\Api\Struct { /** @var string */ public $id; /** @var string */ public $language; /** @var string */ public $country; public function __construct($apiResponse) { $this->_initScalarProperties($apiResponse, [ 'id', ['lang' => 'language'], 'country', ]); } } vendor/plesk/api-php-lib/src/Api/Struct/Dns/Info.php 0000666 00000001326 15165257520 0016213 0 ustar 00 <?php // Copyright 1999-2020. Plesk International GmbH. namespace PleskX\Api\Struct\Dns; defined('ABSPATH') or die(); class Info extends \PleskX\Api\Struct { /** @var int */ public $id; /** @var int */ public $siteId; /** @var int */ public $siteAliasId; /** @var string */ public $type; /** @var string */ public $host; /** @var string */ public $value; /** @var string */ public $opt; public function __construct($apiResponse) { $this->_initScalarProperties($apiResponse, [ 'id', 'site-id', 'site-alias-id', 'type', 'host', 'value', 'opt', ]); } } vendor/plesk/api-php-lib/src/Api/Struct/Certificate/Info.php 0000666 00000000710 15165257520 0017705 0 ustar 00 <?php // Copyright 1999-2020. Plesk International GmbH. namespace PleskX\Api\Struct\Certificate; defined('ABSPATH') or die(); class Info extends \PleskX\Api\Struct { /** @var string */ public $request; /** @var string */ public $privateKey; public function __construct($apiResponse) { $this->_initScalarProperties($apiResponse, [ ['csr' => 'request'], ['pvt' => 'privateKey'], ]); } } vendor/plesk/api-php-lib/src/Api/Struct/SecretKey/Info.php 0000666 00000001027 15165257520 0017363 0 ustar 00 <?php // Copyright 1999-2020. Plesk International GmbH. namespace PleskX\Api\Struct\SecretKey; class Info extends \PleskX\Api\Struct { /** @var string */ public $key; /** @var string */ public $ipAddress; /** @var string */ public $description; /** @var string */ public $login; public function __construct($apiResponse) { $this->_initScalarProperties($apiResponse, [ 'key', 'ip_address', 'description', 'login', ]); } } vendor/plesk/api-php-lib/src/Api/Struct/PhpHandler/Info.php 0000666 00000001734 15165257520 0017517 0 ustar 00 <?php // Copyright 1999-2020. Plesk International GmbH. namespace PleskX\Api\Struct\PhpHandler; defined('ABSPATH') or die(); use PleskX\Api\Struct; class Info extends Struct { /** @var string */ public $id; /** @var string */ public $displayName; /** @var string */ public $fullVersion; /** @var string */ public $version; /** @var string */ public $type; /** @var string */ public $path; /** @var string */ public $clipath; /** @var string */ public $phpini; /** @var string */ public $custom; /** @var string */ public $handlerStatus; public function __construct($apiResponse) { $this->_initScalarProperties($apiResponse, [ 'id', 'display-name', 'full-version', 'version', 'type', 'path', 'clipath', 'phpini', 'custom', 'handler-status', ]); } } vendor/plesk/api-php-lib/src/Api/Struct/SiteAlias/GeneralInfo.php 0000666 00000000762 15165257520 0020646 0 ustar 00 <?php // Copyright 1999-2020. Plesk International GmbH. namespace PleskX\Api\Struct\SiteAlias; defined('ABSPATH') or die(); class GeneralInfo extends \PleskX\Api\Struct { /** @var string */ public $name; /** @var string */ public $asciiName; /** @var string */ public $status; public function __construct($apiResponse) { $this->_initScalarProperties($apiResponse, [ 'name', 'ascii-name', 'status', ]); } } vendor/plesk/api-php-lib/src/Api/Struct/SiteAlias/Info.php 0000666 00000000633 15165257520 0017345 0 ustar 00 <?php // Copyright 1999-2020. Plesk International GmbH. namespace PleskX\Api\Struct\SiteAlias; defined('ABSPATH') or die(); class Info extends \PleskX\Api\Struct { /** @var string */ public $status; /** @var int */ public $id; public function __construct($apiResponse) { $this->_initScalarProperties($apiResponse, [ 'id', 'status', ]); } } vendor/plesk/api-php-lib/src/Api/Struct/Site/HostingInfo.php 0000666 00000001112 15165257520 0017720 0 ustar 00 <?php // Copyright 1999-2020. Plesk International GmbH. namespace PleskX\Api\Struct\Site; defined('ABSPATH') or die(); class HostingInfo extends \PleskX\Api\Struct { /** @var array */ public $properties = []; /** @var string */ public $ipAddress; public function __construct($apiResponse) { foreach ($apiResponse->vrt_hst->property as $property) { $this->properties[(string) $property->name] = (string) $property->value; } $this->_initScalarProperties($apiResponse->vrt_hst, [ 'ip_address', ]); } } vendor/plesk/api-php-lib/src/Api/Struct/Site/Info.php 0000666 00000000622 15165257520 0016371 0 ustar 00 <?php // Copyright 1999-2020. Plesk International GmbH. namespace PleskX\Api\Struct\Site; defined('ABSPATH') or die(); class Info extends \PleskX\Api\Struct { /** @var int */ public $id; /** @var string */ public $guid; public function __construct($apiResponse) { $this->_initScalarProperties($apiResponse, [ 'id', 'guid', ]); } } vendor/plesk/api-php-lib/src/Api/Struct/Site/GeneralInfo.php 0000666 00000001167 15165257520 0017674 0 ustar 00 <?php // Copyright 1999-2020. Plesk International GmbH. namespace PleskX\Api\Struct\Site; defined('ABSPATH') or die(); class GeneralInfo extends \PleskX\Api\Struct { /** @var string */ public $name; /** @var string */ public $asciiName; /** @var string */ public $guid; /** @var string */ public $status; /** @var string */ public $description; public function __construct($apiResponse) { $this->_initScalarProperties($apiResponse, [ 'name', 'ascii-name', 'status', 'guid', 'description', ]); } } vendor/plesk/api-php-lib/src/Api/Struct/Session/Info.php 0000666 00000001250 15165257520 0017106 0 ustar 00 <?php // Copyright 1999-2020. Plesk International GmbH. namespace PleskX\Api\Struct\Session; defined('ABSPATH') or die(); class Info extends \PleskX\Api\Struct { /** @var string */ public $id; /** @var string */ public $type; /** @var string */ public $ipAddress; /** @var string */ public $login; /** @var string */ public $loginTime; /** @var string */ public $idle; public function __construct($apiResponse) { $this->_initScalarProperties($apiResponse, [ 'id', 'type', 'ip-address', 'login', 'login-time', 'idle', ]); } } vendor/plesk/api-php-lib/src/Api/Struct.php 0000666 00000003656 15165257520 0014604 0 ustar 00 <?php // Copyright 1999-2020. Plesk International GmbH. namespace PleskX\Api; defined('ABSPATH') or die(); abstract class Struct { public function __set($property, $value) { throw new \Exception("Try to set an undeclared property '$property'."); } /** * Initialize list of scalar properties by response. * * @param \SimpleXMLElement $apiResponse * @param array $properties * * @throws \Exception */ protected function _initScalarProperties($apiResponse, array $properties) { foreach ($properties as $property) { if (is_array($property)) { $classPropertyName = current($property); $value = $apiResponse->{key($property)}; } else { $classPropertyName = $this->_underToCamel(str_replace('-', '_', $property)); $value = $apiResponse->$property; } $reflectionProperty = new \ReflectionProperty($this, $classPropertyName); $docBlock = $reflectionProperty->getDocComment(); $propertyType = preg_replace('/^.+ @var ([a-z]+) .+$/', '\1', $docBlock); if ('string' == $propertyType) { $value = (string) $value; } elseif ('int' == $propertyType) { $value = (int) $value; } elseif ('bool' == $propertyType) { $value = in_array((string) $value, ['true', 'on', 'enabled']); } else { throw new \Exception("Unknown property type '$propertyType'."); } $this->$classPropertyName = $value; } } /** * Convert underscore separated words into camel case. * * @param string $under * * @return string */ private function _underToCamel($under) { $under = '_'.str_replace('_', ' ', strtolower($under)); return ltrim(str_replace(' ', '', ucwords($under)), '_'); } } vendor/plesk/api-php-lib/src/Api/Operator.php 0000666 00000005124 15165257520 0015103 0 ustar 00 <?php // Copyright 1999-2020. Plesk International GmbH. namespace PleskX\Api; defined('ABSPATH') or die(); class Operator { /** @var string|null */ protected $_wrapperTag = null; /** @var \PleskX\Api\Client */ protected $_client; public function __construct($client) { $this->_client = $client; if (is_null($this->_wrapperTag)) { $classNameParts = explode('\\', get_class($this)); $this->_wrapperTag = end($classNameParts); $this->_wrapperTag = strtolower(preg_replace('/([a-z])([A-Z])/', '\1-\2', $this->_wrapperTag)); } } /** * Perform plain API request. * * @param string|array $request * @param int $mode * * @return XmlResponse */ public function request($request, $mode = Client::RESPONSE_SHORT) { $wrapperTag = $this->_wrapperTag; if (is_array($request)) { $request = [$wrapperTag => $request]; } elseif (preg_match('/^[a-z]/', $request)) { $request = "$wrapperTag.$request"; } else { $request = "<$wrapperTag>$request</$wrapperTag>"; } return $this->_client->request($request, $mode); } /** * @param string $field * @param int|string $value * @param string $deleteMethodName * * @return bool */ protected function _delete($field, $value, $deleteMethodName = 'del') { $response = $this->request("$deleteMethodName.filter.$field=$value"); return 'ok' === (string) $response->status; } /** * @param string $structClass * @param string $infoTag * @param string|null $field * @param int|string|null $value * @param callable|null $filter * * @return mixed */ protected function _getItems($structClass, $infoTag, $field = null, $value = null, callable $filter = null) { $packet = $this->_client->getPacket(); $getTag = $packet->addChild($this->_wrapperTag)->addChild('get'); $filterTag = $getTag->addChild('filter'); if (!is_null($field)) { $filterTag->addChild($field, $value); } $getTag->addChild('dataset')->addChild($infoTag); $response = $this->_client->request($packet, \PleskX\Api\Client::RESPONSE_FULL); $items = []; foreach ($response->xpath('//result') as $xmlResult) { if (!is_null($filter) && !$filter($xmlResult->data->$infoTag)) { continue; } $items[] = new $structClass($xmlResult->data->$infoTag); } return $items; } } vendor/plesk/api-php-lib/src/Api/Operator/Server.php 0000666 00000010122 15165257520 0016343 0 ustar 00 <?php // Copyright 1999-2020. Plesk International GmbH. namespace PleskX\Api\Operator; defined('ABSPATH') or die(); use PleskX\Api\Struct\Server as Struct; class Server extends \PleskX\Api\Operator { /** * @return array */ public function getProtos() { $packet = $this->_client->getPacket(); $packet->addChild($this->_wrapperTag)->addChild('get_protos'); $response = $this->_client->request($packet); return (array) $response->protos->proto; } public function getGeneralInfo() { return new Struct\GeneralInfo($this->_getInfo('gen_info')); } public function getPreferences() { return new Struct\Preferences($this->_getInfo('prefs')); } public function getAdmin() { return new Struct\Admin($this->_getInfo('admin')); } /** * @return array */ public function getKeyInfo() { $keyInfo = []; $keyInfoXml = $this->_getInfo('key'); foreach ($keyInfoXml->property as $property) { $keyInfo[(string) $property->name] = (string) $property->value; } return $keyInfo; } /** * @return array */ public function getComponents() { $components = []; $componentsXml = $this->_getInfo('components'); foreach ($componentsXml->component as $component) { $components[(string) $component->name] = (string) $component->version; } return $components; } /** * @return array */ public function getServiceStates() { $states = []; $statesXml = $this->_getInfo('services_state'); foreach ($statesXml->srv as $service) { $states[(string) $service->id] = [ 'id' => (string) $service->id, 'title' => (string) $service->title, 'state' => (string) $service->state, ]; } return $states; } public function getSessionPreferences() { return new Struct\SessionPreferences($this->_getInfo('session_setup')); } /** * @return array */ public function getShells() { $shells = []; $shellsXml = $this->_getInfo('shells'); foreach ($shellsXml->shell as $shell) { $shells[(string) $shell->name] = (string) $shell->path; } return $shells; } /** * @return array */ public function getNetworkInterfaces() { $interfacesXml = $this->_getInfo('interfaces'); return (array) $interfacesXml->interface; } public function getStatistics() { return new Struct\Statistics($this->_getInfo('stat')); } /** * @return array */ public function getSiteIsolationConfig() { $config = []; $configXml = $this->_getInfo('site-isolation-config'); foreach ($configXml->property as $property) { $config[(string) $property->name] = (string) $property->value; } return $config; } public function getUpdatesInfo() { return new Struct\UpdatesInfo($this->_getInfo('updates')); } /** * @param string $login * @param string $clientIp * * @return string */ public function createSession($login, $clientIp) { $packet = $this->_client->getPacket(); $sessionNode = $packet->addChild($this->_wrapperTag)->addChild('create_session'); $sessionNode->addChild('login', $login); $dataNode = $sessionNode->addChild('data'); $dataNode->addChild('user_ip', base64_encode($clientIp)); $dataNode->addChild('source_server'); $response = $this->_client->request($packet); return (string) $response->id; } /** * @param string $operation * * @return \SimpleXMLElement */ private function _getInfo($operation) { $packet = $this->_client->getPacket(); $packet->addChild($this->_wrapperTag)->addChild('get')->addChild($operation); $response = $this->_client->request($packet); return $response->$operation; } } vendor/plesk/api-php-lib/src/Api/Operator/Dns.php 0000666 00000006347 15165257520 0015637 0 ustar 00 <?php // Copyright 1999-2020. Plesk International GmbH. namespace PleskX\Api\Operator; defined('ABSPATH') or die(); use PleskX\Api\Struct\Dns as Struct; class Dns extends \PleskX\Api\Operator { /** * @param array $properties * * @return Struct\Info */ public function create($properties) { $packet = $this->_client->getPacket(); $info = $packet->addChild($this->_wrapperTag)->addChild('add_rec'); foreach ($properties as $name => $value) { $info->addChild($name, $value); } return new Struct\Info($this->_client->request($packet)); } /** * Send multiply records by one request. * * @param array $records * * @return \PleskX\Api\XmlResponse[] */ public function bulkCreate(array $records) { $packet = $this->_client->getPacket(); foreach ($records as $properties) { $info = $packet->addChild($this->_wrapperTag)->addChild('add_rec'); foreach ($properties as $name => $value) { $info->addChild($name, $value); } } $response = $this->_client->request($packet, \PleskX\Api\Client::RESPONSE_FULL); $items = []; foreach ($response->xpath('//result') as $xmlResult) { $items[] = $xmlResult; } return $items; } /** * @param string $field * @param int|string $value * * @return Struct\Info */ public function get($field, $value) { $items = $this->getAll($field, $value); return reset($items); } /** * @param string $field * @param int|string $value * * @return Struct\Info[] */ public function getAll($field, $value) { $packet = $this->_client->getPacket(); $getTag = $packet->addChild($this->_wrapperTag)->addChild('get_rec'); $filterTag = $getTag->addChild('filter'); if (!is_null($field)) { $filterTag->addChild($field, $value); } $response = $this->_client->request($packet, \PleskX\Api\Client::RESPONSE_FULL); $items = []; foreach ($response->xpath('//result') as $xmlResult) { $item = new Struct\Info($xmlResult->data); $item->id = (int) $xmlResult->id; $items[] = $item; } return $items; } /** * @param string $field * @param int|string $value * * @return bool */ public function delete($field, $value) { return $this->_delete($field, $value, 'del_rec'); } /** * Delete multiply records by one request. * * @param array $recordIds * * @return \PleskX\Api\XmlResponse[] */ public function bulkDelete(array $recordIds) { $packet = $this->_client->getPacket(); foreach ($recordIds as $recordId) { $packet->addChild($this->_wrapperTag)->addChild('del_rec') ->addChild('filter')->addChild('id', $recordId); } $response = $this->_client->request($packet, \PleskX\Api\Client::RESPONSE_FULL); $items = []; foreach ($response->xpath('//result') as $xmlResult) { $items[] = $xmlResult; } return $items; } } vendor/plesk/api-php-lib/src/Api/Operator/SecretKey.php 0000666 00000003367 15165257520 0017010 0 ustar 00 <?php // Copyright 1999-2020. Plesk International GmbH. namespace PleskX\Api\Operator; defined('ABSPATH') or die(); use PleskX\Api\Struct\SecretKey as Struct; class SecretKey extends \PleskX\Api\Operator { protected $_wrapperTag = 'secret_key'; /** * @param string $ipAddress * * @return string */ public function create($ipAddress) { $packet = $this->_client->getPacket(); $packet->addChild($this->_wrapperTag)->addChild('create')->addChild('ip_address', $ipAddress); $response = $this->_client->request($packet); return (string) $response->key; } /** * @param string $keyId * * @return bool */ public function delete($keyId) { return $this->_delete('key', $keyId, 'delete'); } /** * @param string $keyId * * @return Struct\Info */ public function get($keyId) { $items = $this->_get($keyId); return reset($items); } /** * @return Struct\Info[] */ public function getAll() { return $this->_get(); } /** * @param string|null $keyId * * @return Struct\Info[] */ public function _get($keyId = null) { $packet = $this->_client->getPacket(); $getTag = $packet->addChild($this->_wrapperTag)->addChild('get_info'); $filterTag = $getTag->addChild('filter'); if (!is_null($keyId)) { $filterTag->addChild('key', $keyId); } $response = $this->_client->request($packet, \PleskX\Api\Client::RESPONSE_FULL); $items = []; foreach ($response->xpath('//result/key_info') as $keyInfo) { $items[] = new Struct\Info($keyInfo); } return $items; } } vendor/plesk/api-php-lib/src/Api/Operator/EventLog.php 0000666 00000001774 15165257520 0016635 0 ustar 00 <?php // Copyright 1999-2020. Plesk International GmbH. namespace PleskX\Api\Operator; defined('ABSPATH') or die(); use PleskX\Api\Struct\EventLog as Struct; class EventLog extends \PleskX\Api\Operator { protected $_wrapperTag = 'event_log'; /** * @return Struct\Event[] */ public function get() { $records = []; $response = $this->request('get'); foreach ($response->event as $eventInfo) { $records[] = new Struct\Event($eventInfo); } return $records; } /** * @return Struct\DetailedEvent[] */ public function getDetailedLog() { $records = []; $response = $this->request('get_events'); foreach ($response->event as $eventInfo) { $records[] = new Struct\DetailedEvent($eventInfo); } return $records; } /** * @return int */ public function getLastId() { return (int) $this->request('get-last-id')->getValue('id'); } } vendor/plesk/api-php-lib/src/Api/Operator/Locale.php 0000666 00000001616 15165257520 0016304 0 ustar 00 <?php // Copyright 1999-2020. Plesk International GmbH. namespace PleskX\Api\Operator; defined('ABSPATH') or die(); use PleskX\Api\Struct\Locale as Struct; class Locale extends \PleskX\Api\Operator { /** * @param string|null $id * * @return Struct\Info|Struct\Info[] */ public function get($id = null) { $locales = []; $packet = $this->_client->getPacket(); $filter = $packet->addChild($this->_wrapperTag)->addChild('get')->addChild('filter'); if (!is_null($id)) { $filter->addChild('id', $id); } $response = $this->_client->request($packet, \PleskX\Api\Client::RESPONSE_FULL); foreach ($response->locale->get->result as $localeInfo) { $locales[(string) $localeInfo->info->id] = new Struct\Info($localeInfo->info); } return !is_null($id) ? reset($locales) : $locales; } } vendor/plesk/api-php-lib/src/Api/Operator/ProtectedDirectory.php 0000666 00000005752 15165257520 0020730 0 ustar 00 <?php // Copyright 1999-2020. Plesk International GmbH. namespace PleskX\Api\Operator; defined('ABSPATH') or die(); use PleskX\Api\Struct\ProtectedDirectory as Struct; class ProtectedDirectory extends \PleskX\Api\Operator { protected $_wrapperTag = 'protected-dir'; /** * @param string $name * @param int $siteId * @param string $header * * @return Struct\Info */ public function add($name, $siteId, $header = '') { $packet = $this->_client->getPacket(); $info = $packet->addChild($this->_wrapperTag)->addChild('add'); $info->addChild('site-id', $siteId); $info->addChild('name', $name); $info->addChild('header', $header); return new Struct\Info($this->_client->request($packet)); } /** * @param string $field * @param int|string $value * * @return bool */ public function delete($field, $value) { return $this->_delete($field, $value, 'delete'); } /** * @param string $field * @param int|string $value * * @return Struct\DataInfo|false */ public function get($field, $value) { $items = $this->getAll($field, $value); return reset($items); } /** * @param string $field * @param int|string $value * * @return Struct\DataInfo[] */ public function getAll($field, $value) { $response = $this->_get('get', $field, $value); $items = []; foreach ($response->xpath('//result/data') as $xmlResult) { $items[] = new Struct\DataInfo($xmlResult); } return $items; } /** * @param Struct\Info $protectedDirectory * @param string $login * @param string $password * * @return Struct\UserInfo */ public function addUser($protectedDirectory, $login, $password) { $packet = $this->_client->getPacket(); $info = $packet->addChild($this->_wrapperTag)->addChild('add-user'); $info->addChild('pd-id', $protectedDirectory->id); $info->addChild('login', $login); $info->addChild('password', $password); return new Struct\UserInfo($this->_client->request($packet)); } /** * @param string $field * @param int|string $value * * @return bool */ public function deleteUser($field, $value) { return $this->_delete($field, $value, 'delete-user'); } /** * @param $command * @param $field * @param $value * * @return \PleskX\Api\XmlResponse */ private function _get($command, $field, $value) { $packet = $this->_client->getPacket(); $getTag = $packet->addChild($this->_wrapperTag)->addChild($command); $filterTag = $getTag->addChild('filter'); if (!is_null($field)) { $filterTag->addChild($field, $value); } $response = $this->_client->request($packet, \PleskX\Api\Client::RESPONSE_FULL); return $response; } } vendor/plesk/api-php-lib/src/Api/Operator/Ip.php 0000666 00000001134 15165257520 0015450 0 ustar 00 <?php // Copyright 1999-2020. Plesk International GmbH. namespace PleskX\Api\Operator; defined('ABSPATH') or die(); use PleskX\Api\Struct\Ip as Struct; class Ip extends \PleskX\Api\Operator { /** * @return Struct\Info[] */ public function get() { $ips = []; $packet = $this->_client->getPacket(); $packet->addChild($this->_wrapperTag)->addChild('get'); $response = $this->_client->request($packet); foreach ($response->addresses->ip_info as $ipInfo) { $ips[] = new Struct\Info($ipInfo); } return $ips; } } vendor/plesk/api-php-lib/src/Api/Operator/Session.php 0000666 00000001421 15165257520 0016522 0 ustar 00 <?php // Copyright 1999-2020. Plesk International GmbH. namespace PleskX\Api\Operator; defined('ABSPATH') or die(); use PleskX\Api\Struct\Session as Struct; class Session extends \PleskX\Api\Operator { /** * @return Struct\Info[] */ public function get() { $sessions = []; $response = $this->request('get'); foreach ($response->session as $sessionInfo) { $sessions[(string) $sessionInfo->id] = new Struct\Info($sessionInfo); } return $sessions; } /** * @param string $sessionId * * @return bool */ public function terminate($sessionId) { $response = $this->request("terminate.session-id=$sessionId"); return 'ok' === (string) $response->status; } } vendor/plesk/api-php-lib/src/Api/Operator/SiteAlias.php 0000666 00000003677 15165257520 0016774 0 ustar 00 <?php // Copyright 1999-2020. Plesk International GmbH. namespace PleskX\Api\Operator; defined('ABSPATH') or die(); use PleskX\Api\Struct\SiteAlias as Struct; class SiteAlias extends \PleskX\Api\Operator { /** * @param array $properties * @param array $preferences * * @return Struct\Info */ public function create(array $properties, array $preferences = []) { $packet = $this->_client->getPacket(); $info = $packet->addChild($this->_wrapperTag)->addChild('create'); if (count($preferences) > 0) { $prefs = $info->addChild('pref'); foreach ($preferences as $key => $value) { $prefs->addChild($key, is_bool($value) ? ($value ? 1 : 0) : $value); } } $info->addChild('site-id', $properties['site-id']); $info->addChild('name', $properties['name']); $response = $this->_client->request($packet); return new Struct\Info($response); } /** * @param string $field * @param int|string $value * * @return Struct\Info */ public function get($field, $value) { $items = $this->getAll($field, $value); return reset($items); } /** * @param string $field * @param int|string $value * * @return Struct\Info[] */ public function getAll($field = null, $value = null) { $packet = $this->_client->getPacket(); $getTag = $packet->addChild($this->_wrapperTag)->addChild('get'); $filterTag = $getTag->addChild('filter'); if (!is_null($field)) { $filterTag->addChild($field, $value); } $response = $this->_client->request($packet, \PleskX\Api\Client::RESPONSE_FULL); $items = []; foreach ($response->xpath('//result') as $xmlResult) { $item = new Struct\GeneralInfo($xmlResult->info); $items[] = $item; } return $items; } } vendor/plesk/api-php-lib/src/Api/Operator/Certificate.php 0000666 00000003031 15165257520 0017320 0 ustar 00 <?php // Copyright 1999-2020. Plesk International GmbH. namespace PleskX\Api\Operator; defined('ABSPATH') or die(); use PleskX\Api\Struct\Certificate as Struct; class Certificate extends \PleskX\Api\Operator { /** * @param array $properties * * @return Struct\Info */ public function generate($properties) { $packet = $this->_client->getPacket(); $info = $packet->addChild($this->_wrapperTag)->addChild('generate')->addChild('info'); foreach ($properties as $name => $value) { $info->addChild($name, $value); } $response = $this->_client->request($packet); return new Struct\Info($response); } public function create($properties) { $packet = $this->_client->getPacket(); $info = $packet->addChild($this->_wrapperTag)->addChild('add')->addChild('gen_info'); foreach ($properties as $name => $value) { $info->addChild($name, $value); } $response = $this->_client->request($packet); return new Struct\Info($response); } public function install($domains, $properties) { $packet = $this->_client->getPacket(); foreach ($domains as $domain) { $install = $packet->addChild($this->_wrapperTag)->addChild('install'); $install->addChild('name', $domain); $install->addChild('webspace', $domain); $content = $install->addChild('content'); foreach ($properties as $name => $value) { $content->addChild($name, $value); } } $response = $this->_client->request($packet); return new Struct\Info($response); } } vendor/plesk/api-php-lib/src/Api/Operator/PhpHandler.php 0000666 00000003010 15165257520 0017120 0 ustar 00 <?php // Copyright 1999-2020. Plesk International GmbH. namespace PleskX\Api\Operator; defined('ABSPATH') or die(); use PleskX\Api\Client; use PleskX\Api\Operator; use PleskX\Api\Struct\PhpHandler\Info; class PhpHandler extends Operator { /** * @param string $field * @param int|string $value * * @return Info */ public function get($field, $value) { $packet = $this->_client->getPacket(); $getTag = $packet->addChild($this->_wrapperTag)->addChild('get'); $filterTag = $getTag->addChild('filter'); if (!is_null($field)) { $filterTag->addChild($field, $value); } $response = $this->_client->request($packet, Client::RESPONSE_FULL); $xmlResult = $response->xpath('//result')[0]; return new Info($xmlResult); } /** * @param string|null $field * @param int|string $value * * @return Info[] */ public function getAll($field = null, $value = null) { $packet = $this->_client->getPacket(); $getTag = $packet->addChild($this->_wrapperTag)->addChild('get'); $filterTag = $getTag->addChild('filter'); if (!is_null($field)) { $filterTag->addChild($field, $value); } $response = $this->_client->request($packet, Client::RESPONSE_FULL); $items = []; foreach ($response->xpath('//result') as $xmlResult) { $item = new Info($xmlResult); $items[] = $item; } return $items; } } vendor/plesk/api-php-lib/src/Api/Exception.php 0000666 00000000301 15165257520 0015236 0 ustar 00 <?php // Copyright 1999-2020. Plesk International GmbH. namespace PleskX\Api; defined('ABSPATH') or die(); /** * Exceptions for XML-RPC API client. */ class Exception extends \Exception { } vendor/plesk/api-php-lib/src/Api/InternalClient.php 0000666 00000000753 15165257520 0016226 0 ustar 00 <?php // Copyright 1999-2020. Plesk International GmbH. namespace PleskX\Api; defined('ABSPATH') or die(); /** * Internal client for Plesk XML-RPC API (via SDK). */ class InternalClient extends Client { public function __construct() { parent::__construct('localhost', 0, 'sdk'); } /** * Setup login to execute requests under certain user. * * @param $login */ public function setLogin($login) { $this->_login = $login; } } vendor/plesk/api-php-lib/src/Api/Client.php 0000666 00000032631 15165257520 0014531 0 ustar 00 <?php // Copyright 1999-2020. Plesk International GmbH. namespace PleskX\Api; defined('ABSPATH') or die(); use SimpleXMLElement; /** * Client for Plesk XML-RPC API. */ class Client { const RESPONSE_SHORT = 1; const RESPONSE_FULL = 2; protected $_host; protected $_port; protected $_protocol; protected $_login; protected $_password; protected $_secretKey; protected $_version = ''; protected $_operatorsCache = []; /** * @var callable */ protected $_verifyResponseCallback; /** * Create client. * * @param string $host * @param int $port * @param string $protocol */ public function __construct($host, $port = 8443, $protocol = 'https') { $this->_host = $host; $this->_port = $port; $this->_protocol = $protocol; } /** * Setup credentials for authentication. * * @param string $login * @param string $password */ public function setCredentials($login, $password) { $this->_login = $login; $this->_password = $password; } /** * Define secret key for alternative authentication. * * @param string $secretKey */ public function setSecretKey($secretKey) { $this->_secretKey = $secretKey; } /** * Set default version for requests. * * @param string $version */ public function setVersion($version) { $this->_version = $version; } /** * Set custom function to verify response of API call according your own needs. Default verifying will be used if it is not specified. * * @param callable|null $function */ public function setVerifyResponse(callable $function = null) { $this->_verifyResponseCallback = $function; } /** * Retrieve host used for communication. * * @return string */ public function getHost() { return $this->_host; } /** * Retrieve port used for communication. * * @return int */ public function getPort() { return $this->_port; } /** * Retrieve name of the protocol (http or https) used for communication. * * @return string */ public function getProtocol() { return $this->_protocol; } /** * Retrieve XML template for packet. * * @param string|null $version * * @return SimpleXMLElement */ public function getPacket($version = null) { $protocolVersion = !is_null($version) ? $version : $this->_version; $content = "<?xml version='1.0' encoding='UTF-8' ?>"; $content .= '<packet'.('' === $protocolVersion ? '' : " version='$protocolVersion'").'/>'; return new SimpleXMLElement($content); } /** * Perform API request. * * @param string|array|SimpleXMLElement $request * @param int $mode * * @return XmlResponse */ public function request($request, $mode = self::RESPONSE_SHORT) { if ($request instanceof SimpleXMLElement) { $request = $request->asXml(); } else { $xml = $this->getPacket(); if (is_array($request)) { $request = $this->_arrayToXml($request, $xml)->asXML(); } elseif (preg_match('/^[a-z]/', $request)) { $request = $this->_expandRequestShortSyntax($request, $xml); } } if ('sdk' == $this->_protocol) { $version = ('' == $this->_version) ? null : $this->_version; $requestXml = new SimpleXMLElement((string) $request); $xml = \pm_ApiRpc::getService($version)->call($requestXml->children()[0]->asXml(), $this->_login); } else { $xml = $this->_performHttpRequest($request); } $this->_verifyResponseCallback ? call_user_func($this->_verifyResponseCallback, $xml) : $this->_verifyResponse($xml); return (self::RESPONSE_FULL == $mode) ? $xml : $xml->xpath('//result')[0]; } /** * Perform HTTP request to end-point. * * @param string $request * * @throws Client\Exception * * @return XmlResponse */ private function _performHttpRequest($request) { $curl = curl_init(); curl_setopt($curl, CURLOPT_URL, "$this->_protocol://$this->_host:$this->_port/enterprise/control/agent.php"); curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); curl_setopt($curl, CURLOPT_POST, true); curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false); curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, false); curl_setopt($curl, CURLOPT_HTTPHEADER, $this->_getHeaders()); curl_setopt($curl, CURLOPT_POSTFIELDS, $request); $result = curl_exec($curl); if (false === $result) { throw new Client\Exception(curl_error($curl), curl_errno($curl)); } curl_close($curl); $xml = new XmlResponse($result); return $xml; } /** * Perform multiple API requests using single HTTP request. * * @param $requests * @param int $mode * * @throws Client\Exception * * @return array */ public function multiRequest($requests, $mode = self::RESPONSE_SHORT) { $requestXml = $this->getPacket(); foreach ($requests as $request) { if ($request instanceof SimpleXMLElement) { throw new Client\Exception('SimpleXML type of request is not supported for multi requests.'); } else { if (is_array($request)) { $request = $this->_arrayToXml($request, $requestXml)->asXML(); } elseif (preg_match('/^[a-z]/', $request)) { $this->_expandRequestShortSyntax($request, $requestXml); } } $responses[] = $this->request($request); } if ('sdk' == $this->_protocol) { throw new Client\Exception('Multi requests are not supported via SDK.'); } else { $responseXml = $this->_performHttpRequest($requestXml->asXML()); } $responses = []; foreach ($responseXml->children() as $childNode) { $xml = $this->getPacket(); $dom = dom_import_simplexml($xml)->ownerDocument; $childDomNode = dom_import_simplexml($childNode); $childDomNode = $dom->importNode($childDomNode, true); $dom->documentElement->appendChild($childDomNode); $response = simplexml_load_string($dom->saveXML()); $responses[] = (self::RESPONSE_FULL == $mode) ? $response : $response->xpath('//result')[0]; } return $responses; } /** * Retrieve list of headers needed for request. * * @return array */ protected function _getHeaders() { $headers = [ 'Content-Type: text/xml', 'HTTP_PRETTY_PRINT: TRUE', ]; if ($this->_secretKey) { $headers[] = "KEY: $this->_secretKey"; } else { $headers[] = "HTTP_AUTH_LOGIN: $this->_login"; $headers[] = "HTTP_AUTH_PASSWD: $this->_password"; } return $headers; } /** * Verify that response does not contain errors. * * @param XmlResponse $xml * * @throws Exception */ protected function _verifyResponse($xml) { if ($xml->system && $xml->system->status && 'error' == (string) $xml->system->status) { throw new Exception((string) $xml->system->errtext, (int) $xml->system->errcode); } if ($xml->xpath('//status[text()="error"]') && $xml->xpath('//errcode') && $xml->xpath('//errtext')) { $errorCode = (int) $xml->xpath('//errcode')[0]; $errorMessage = (string) $xml->xpath('//errtext')[0]; throw new Exception($errorMessage, $errorCode); } } /** * Expand short syntax (some.method.call) into full XML representation. * * @param string $request * @param SimpleXMLElement $xml * * @return string */ protected function _expandRequestShortSyntax($request, SimpleXMLElement $xml) { $parts = explode('.', $request); $node = $xml; foreach ($parts as $part) { @list($name, $value) = explode('=', $part); $node = $node->addChild($name, $value); } return $xml->asXML(); } /** * Convert array to XML representation. * * @param array $array * @param SimpleXMLElement $xml * @param string $parentEl * * @return SimpleXMLElement */ protected function _arrayToXml(array $array, SimpleXMLElement $xml, $parentEl = null) { foreach ($array as $key => $value) { $el = is_int($key) && $parentEl ? $parentEl : $key; if (is_array($value)) { $this->_arrayToXml($value, $this->_isAssocArray($value) ? $xml->addChild($el) : $xml, $el); } else { $xml->addChild($el, $value); } } return $xml; } /** * @param array $array * * @return bool */ protected function _isAssocArray(array $array) { return $array && array_keys($array) !== range(0, count($array) - 1); } /** * @param string $name * * @return \PleskX\Api\Operator */ protected function _getOperator($name) { if (!isset($this->_operatorsCache[$name])) { $className = '\\PleskX\\Api\\Operator\\'.$name; $this->_operatorsCache[$name] = new $className($this); } return $this->_operatorsCache[$name]; } /** * @return Operator\Server */ public function server() { return $this->_getOperator('Server'); } /** * @return Operator\Customer */ public function customer() { return $this->_getOperator('Customer'); } /** * @return Operator\Webspace */ public function webspace() { return $this->_getOperator('Webspace'); } /** * @return Operator\Subdomain */ public function subdomain() { return $this->_getOperator('Subdomain'); } /** * @return Operator\Dns */ public function dns() { return $this->_getOperator('Dns'); } /** * @return Operator\DnsTemplate */ public function dnsTemplate() { return $this->_getOperator('DnsTemplate'); } /** * @return Operator\DatabaseServer */ public function databaseServer() { return $this->_getOperator('DatabaseServer'); } /** * @return Operator\Mail */ public function mail() { return $this->_getOperator('Mail'); } /** * @return Operator\Certificate */ public function certificate() { return $this->_getOperator('Certificate'); } /** * @return Operator\SiteAlias */ public function siteAlias() { return $this->_getOperator('SiteAlias'); } /** * @return Operator\Ip */ public function ip() { return $this->_getOperator('Ip'); } /** * @return Operator\EventLog */ public function eventLog() { return $this->_getOperator('EventLog'); } /** * @return Operator\SecretKey */ public function secretKey() { return $this->_getOperator('SecretKey'); } /** * @return Operator\Ui */ public function ui() { return $this->_getOperator('Ui'); } /** * @return Operator\ServicePlan */ public function servicePlan() { return $this->_getOperator('ServicePlan'); } /** * @return Operator\VirtualDirectory */ public function virtualDirectory() { return $this->_getOperator('VirtualDirectory'); } /** * @return Operator\Database */ public function database() { return $this->_getOperator('Database'); } /** * @return Operator\Session */ public function session() { return $this->_getOperator('Session'); } /** * @return Operator\Locale */ public function locale() { return $this->_getOperator('Locale'); } /** * @return Operator\LogRotation */ public function logRotation() { return $this->_getOperator('LogRotation'); } /** * @return Operator\ProtectedDirectory */ public function protectedDirectory() { return $this->_getOperator('ProtectedDirectory'); } /** * @return Operator\Reseller */ public function reseller() { return $this->_getOperator('Reseller'); } /** * @return Operator\ResellerPlan */ public function resellerPlan() { return $this->_getOperator('ResellerPlan'); } /** * @return Operator\Aps */ public function aps() { return $this->_getOperator('Aps'); } /** * @return Operator\ServicePlanAddon */ public function servicePlanAddon() { return $this->_getOperator('ServicePlanAddon'); } /** * @return Operator\Site */ public function site() { return $this->_getOperator('Site'); } /** * @return Operator\PhpHandler */ public function phpHandler() { return $this->_getOperator('PhpHandler'); } } vendor/plesk/api-php-lib/src/Api/Client/Exception.php 0000666 00000000301 15165257520 0016454 0 ustar 00 <?php // Copyright 1999-2020. Plesk International GmbH. namespace PleskX\Api\Client; defined('ABSPATH') or die(); /** * Transport layer exception. */ class Exception extends \Exception { } vendor/plesk/api-php-lib/src/Api/XmlResponse.php 0000666 00000000632 15165257520 0015566 0 ustar 00 <?php // Copyright 1999-2020. Plesk International GmbH. namespace PleskX\Api; defined('ABSPATH') or die(); /** * XML wrapper for responses. */ class XmlResponse extends \SimpleXMLElement { /** * Retrieve value by node name. * * @param string $node * * @return string */ public function getValue($node) { return (string) $this->xpath('//'.$node)[0]; } } vendor/plesk/api-php-lib/README.md 0000666 00000004252 15165257520 0012557 0 ustar 00 ## PHP library for Plesk XML-RPC API [](https://travis-ci.com/plesk/api-php-lib) [](https://scrutinizer-ci.com/g/plesk/api-php-lib/?branch=master) [](https://styleci.io/repos/26514840) PHP object-oriented library for Plesk XML-RPC API. ## Install Via Composer [Composer](https://getcomposer.org/) is a preferable way to install the library: `composer require plesk/api-php-lib` ## Usage Examples Here is an example on how to use the library and create a customer with desired properties: ```php $client = new \PleskX\Api\Client($host); $client->setCredentials($login, $password); $client->customer()->create([ 'cname' => 'Plesk', 'pname' => 'John Smith', 'login' => 'john', 'passwd' => 'secret', 'email' => 'john@smith.com', ]); ``` It is possible to use a secret key instead of password for authentication. ```php $client = new \PleskX\Api\Client($host); $client->setSecretKey($secretKey) ``` In case of Plesk extension creation one can use an internal mechanism to access XML-RPC API. It does not require to pass authentication because the extension works in the context of Plesk. ```php $client = new \PleskX\Api\InternalClient(); $protocols = $client->server()->getProtos(); ``` For additional examples see tests/ directory. ## How to Run Unit Tests One the possible ways to become familiar with the library is to check the unit tests. To run the unit tests use the following command: `REMOTE_HOST=your-plesk-host.dom REMOTE_PASSWORD=password composer test` To use custom port one can provide a URL (e.g. for Docker container): `REMOTE_URL=https://your-plesk-host.dom:port REMOTE_PASSWORD=password composer test` One more way to run tests is to use Docker: `docker-compose run tests` ## Continuous Testing During active development it could be more convenient to run tests in continuous manner. Here is the way how to achieve it: `REMOTE_URL=https://your-plesk-host.dom:port REMOTE_PASSWORD=password composer test:watch` vendor/plesk/api-php-lib/.travis.yml 0000666 00000000067 15165257520 0013411 0 ustar 00 services: docker script: - docker-compose run tests vendor/plesk/api-php-lib/phpunit-watcher.yml 0000666 00000000067 15165257520 0015145 0 ustar 00 phpunit: arguments: '--stop-on-failure' timeout: 0 vendor/plesk/api-php-lib/LICENSE 0000666 00000001074 15165257520 0012304 0 ustar 00 Copyright 1999-2020. Plesk International GmbH. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. vendor/plesk/api-php-lib/phpunit.xml.dist 0000666 00000001426 15165257520 0014453 0 ustar 00 <?xml version="1.0" encoding="UTF-8"?> <!-- Copyright 1999-2020. Plesk International GmbH. --> <phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd" bootstrap="vendor/autoload.php" verbose="true" colors="true"> <testsuites> <testsuite name="E2E"> <directory>./tests</directory> </testsuite> </testsuites> <filter> <whitelist processUncoveredFilesFromWhitelist="true"> <directory suffix=".php">./src</directory> </whitelist> </filter> <php> <ini name="error_reporting" value="-1"/> <env name="REMOTE_URL" value=""/> <env name="REMOTE_PASSWORD" value=""/> </php> </phpunit> vendor/plesk/api-php-lib/wait-for-plesk.sh 0000666 00000000274 15165257520 0014500 0 ustar 00 #!/bin/bash ### Copyright 1999-2020. Plesk International GmbH. while : ; do curl -ksL https://plesk:8443/ | grep "<title>Plesk" > /dev/null [ $? -eq 0 ] && break sleep 5 done vendor/plesk/index.php 0000666 00000000042 15165257520 0011007 0 ustar 00 <?php //You don't belong here. ?> vendor/fbett/index.php 0000666 00000000042 15165257520 0010775 0 ustar 00 <?php //You don't belong here. ?> vendor/fbett/le_acme2/phpunit.xml.dist 0000666 00000001516 15165257520 0014006 0 ustar 00 <?xml version="1.0" encoding="UTF-8"?> <phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" backupGlobals="false" backupStaticAttributes="false" colors="true" forceCoversAnnotation="true" convertErrorsToExceptions="true" convertNoticesToExceptions="true" convertWarningsToExceptions="true" processIsolation="false" stopOnFailure="true" bootstrap="../../autoload.php" xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.3/phpunit.xsd" > <coverage processUncoveredFiles="true"> <include> <directory suffix=".php">./src/LE_ACME2</directory> </include> </coverage> <testsuites> <testsuite name="LE_ACME2 Test Suite"> <directory>./src/LE_ACME2Tests</directory> </testsuite> </testsuites> </phpunit> vendor/fbett/le_acme2/README.md 0000666 00000010431 15165257520 0012106 0 ustar 00 # le-acme2-php LetsEncrypt client library for ACME v2 written in PHP. This library is inspired by [yourivw/LEClient](https://github.com/yourivw/LEClient), completely rewritten and enhanced with some new features: - Support for Composer autoload (including separated Namespaces) - Automatic renewal process - Managed HTTP authentication process - Response caching mechanism - Prevents blocking while waiting for server results - Optional certificate feature "OCSP Must-Staple" - Optional set a preferred chain The aim of this client is to make an easy-to-use and integrated solution to create a LetsEncrypt-issued SSL/TLS certificate with PHP. You have the possibility to use the HTTP authentication: You need to be able to redirect specific requests (see below) You have also the possibility to use DNS authentication: You need to be able to set dynamic DNS configurations. Wildcard certificates can only be requested by using the dns authentication. ## Current version Tested with LetsEncrypt staging and production servers. [Transitioning to ISRG's Root](https://letsencrypt.org/2019/04/15/transitioning-to-isrg-root.html): This library supports it to set a preferred chain in `Order::setPreferredChain($issuerCN))`. If the preferred chain is not set or set to IdenTrust’s chain, this library will try to use the IdenTrust’s chain as long as possible. Please see: https://letsencrypt.org/docs/dst-root-ca-x3-expiration-september-2021/ ## Prerequisites The minimum required PHP version is 7.3. This client also depends on cURL and OpenSSL. ## Getting Started Install via composer: ``` composer require fbett/le_acme2 ``` Also have a look at the [LetsEncrypt documentation](https://letsencrypt.org/docs/) for more information and documentation on LetsEncrypt and ACME. ## Example Integration - Create a working directory. Warning: This directory will also include private keys, so i suggest to place this directory somewhere not in the root document path of the web server. Additionally this directory should be protected to be read from other web server users. ``` mkdir /etc/ssl/le-storage/ chown root:root /etc/ssl/le-storage chmod 0600 /etc/ssl/le-storage ``` - (HTTP authorization only) Create a directory for the acme challenges. It must be reachable by http/https. ``` mkdir /var/www/acme-challenges ``` - (HTTP authorization only) Redirect specific requests to your acme-challenges directory Example apache virtual host configuration: ``` <VirtualHost ...> <IfModule mod_rewrite.c> RewriteEngine On RewriteCond %{HTTPS} off RewriteRule \.well-known/acme-challenge/(.*)$ https://your-domain.com/path/to/acme-challenges/$1 [R=302,L] </IfModule> </VirtualHost> ``` - (DNS authorization only) Set the DNS configuration If `DNSWriter::write(...)` is called, set the DNS configuration like described in: [https://letsencrypt.org/docs/challenge-types/#dns-01-challenge](https://letsencrypt.org/docs/challenge-types/#dns-01-challenge) (By adding the digest as a TXT record for the subdomain '_acme-challenge'.) - Use the certificate bundle, if the certificate is issued: ``` if($order->isCertificateBundleAvailable()) { $bundle = $order->getCertificateBundle(); $pathToPrivateKey = $bundle->path . $bundle->private; $pathToCertificate = $bundle->path . $bundle->certificate; $pathToIntermediate = $bundle->path . $bundle->intermediate; $order->enableAutoRenewal(); // If the date of expiration is closer than thirty days, the order will automatically start the renewal process. } ``` If a certificate is renewed, the path will also change. My integrated workflow is the following: - User enables SSL to a specific domain in my control panel - The cronjob of this control panel will detect these changes and tries to create or get an order like in the sample. - The cronjob will fetch the information within the certificate bundle, if the certificate bundle is ready (mostly on the second run for challenge type HTTP and on the third run for challenge type DNS) - The cronjob will also build the Apache virtual host files and will restart the Apache2 service, if the new config file is different. Please take a look on the Samples for a full sample workflow. ## License This project is licensed under the MIT License - see the [LICENSE.md](LICENSE.md) file for details. vendor/fbett/le_acme2/LICENSE.md 0000666 00000002014 15165257520 0012231 0 ustar 00 MIT License Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. vendor/fbett/le_acme2/src/LE_ACME2/Request/Account/ChangeKeys.php 0000666 00000005513 15165257520 0020350 0 ustar 00 <?php namespace LE_ACME2\Request\Account; defined('ABSPATH') or die(); use LE_ACME2\Request\AbstractRequest; use LE_ACME2\Response; use LE_ACME2\Connector; use LE_ACME2\Cache; use LE_ACME2\Utilities; use LE_ACME2\Exception; use LE_ACME2\Account; class ChangeKeys extends AbstractRequest { protected $_account; public function __construct(Account $account) { $this->_account = $account; } /** * @return Response\AbstractResponse|Response\Account\ChangeKeys * @throws Exception\InvalidResponse * @throws Exception\RateLimitReached */ public function getResponse() : Response\AbstractResponse { $currentPrivateKey = openssl_pkey_get_private( file_get_contents($this->_account->getKeyDirectoryPath() . 'private.pem') ); $currentPrivateKeyDetails = openssl_pkey_get_details($currentPrivateKey); /** * draft-13 Section 7.3.6 * "newKey" is deprecated after August 23rd 2018 */ $newPrivateKey = openssl_pkey_get_private( file_get_contents($this->_account->getKeyDirectoryPath() . 'private-replacement.pem') ); $newPrivateKeyDetails = openssl_pkey_get_details($newPrivateKey); $innerPayload = [ 'account' => Cache\AccountResponse::getInstance()->get($this->_account)->getLocation(), 'oldKey' => [ "kty" => "RSA", "n" => Utilities\Base64::UrlSafeEncode($currentPrivateKeyDetails["rsa"]["n"]), "e" => Utilities\Base64::UrlSafeEncode($currentPrivateKeyDetails["rsa"]["e"]) ], 'newKey' => [ "kty" => "RSA", "n" => Utilities\Base64::UrlSafeEncode($newPrivateKeyDetails["rsa"]["n"]), "e" => Utilities\Base64::UrlSafeEncode($newPrivateKeyDetails["rsa"]["e"]) ] ]; $outerPayload = Utilities\RequestSigner::JWK( $innerPayload, Cache\DirectoryResponse::getInstance()->get()->getKeyChange(), Cache\NewNonceResponse::getInstance()->get()->getNonce(), $this->_account->getKeyDirectoryPath(), 'private-replacement.pem' ); $data = Utilities\RequestSigner::KID( $outerPayload, Cache\AccountResponse::getInstance()->get($this->_account)->getLocation(), Cache\DirectoryResponse::getInstance()->get()->getKeyChange(), Cache\NewNonceResponse::getInstance()->get()->getNonce(), $this->_account->getKeyDirectoryPath(), 'private.pem' ); $result = Connector\Connector::getInstance()->request( Connector\Connector::METHOD_POST, Cache\DirectoryResponse::getInstance()->get()->getKeyChange(), $data ); return new Response\Account\ChangeKeys($result); } } vendor/fbett/le_acme2/src/LE_ACME2/Request/Account/GetData.php 0000666 00000001066 15165257520 0017637 0 ustar 00 <?php namespace LE_ACME2\Request\Account; defined('ABSPATH') or die(); use LE_ACME2\Response; use LE_ACME2\Exception; class GetData extends AbstractLocation { protected function _getPayload() : array { return []; } /** * @return Response\AbstractResponse|Response\Account\GetData * @throws Exception\InvalidResponse * @throws Exception\RateLimitReached */ public function getResponse() : Response\AbstractResponse { return new Response\Account\GetData($this->_getRawResponse()); } } vendor/fbett/le_acme2/src/LE_ACME2/Request/Account/Get.php 0000666 00000002357 15165257520 0017051 0 ustar 00 <?php namespace LE_ACME2\Request\Account; defined('ABSPATH') or die(); use LE_ACME2\Request\AbstractRequest; use LE_ACME2\Response; use LE_ACME2\Connector; use LE_ACME2\Cache; use LE_ACME2\Exception; use LE_ACME2\Utilities; use LE_ACME2\Account; class Get extends AbstractRequest { protected $_account; public function __construct(Account $account) { $this->_account = $account; } /** * @return Response\AbstractResponse|Response\Account\Get * @throws Exception\InvalidResponse * @throws Exception\RateLimitReached */ public function getResponse() : Response\AbstractResponse { $payload = [ 'onlyReturnExisting' => true, ]; $jwk = Utilities\RequestSigner::JWKString( $payload, Cache\DirectoryResponse::getInstance()->get()->getNewAccount(), Cache\NewNonceResponse::getInstance()->get()->getNonce(), $this->_account->getKeyDirectoryPath() ); $result = Connector\Connector::getInstance()->request( Connector\Connector::METHOD_POST, Cache\DirectoryResponse::getInstance()->get()->getNewAccount(), $jwk ); return new Response\Account\Get($result); } } vendor/fbett/le_acme2/src/LE_ACME2/Request/Account/Deactivate.php 0000666 00000001134 15165257520 0020373 0 ustar 00 <?php namespace LE_ACME2\Request\Account; defined('ABSPATH') or die(); use LE_ACME2\Response; use LE_ACME2\Exception; class Deactivate extends AbstractLocation { protected function _getPayload() : array { return [ 'status' => 'deactivated', ]; } /** * @return Response\AbstractResponse|Response\Account\Deactivate * @throws Exception\InvalidResponse * @throws Exception\RateLimitReached */ public function getResponse() : Response\AbstractResponse { return new Response\Account\Deactivate($this->_getRawResponse()); } } vendor/fbett/le_acme2/src/LE_ACME2/Request/Account/Update.php 0000666 00000001467 15165257520 0017555 0 ustar 00 <?php namespace LE_ACME2\Request\Account; defined('ABSPATH') or die(); use LE_ACME2\Response; use LE_ACME2\Exception; use LE_ACME2\Account; class Update extends AbstractLocation { protected $_newEmail; public function __construct(Account $account, $newEmail) { parent::__construct($account); $this->_newEmail = $newEmail; } protected function _getPayload() : array { return [ 'contact' => $this->_buildContactPayload($this->_newEmail), ]; } /** * @return Response\AbstractResponse|Response\Account\Update * @throws Exception\InvalidResponse * @throws Exception\RateLimitReached */ public function getResponse() : Response\AbstractResponse { return new Response\Account\Update($this->_getRawResponse()); } } vendor/fbett/le_acme2/src/LE_ACME2/Request/Account/AbstractLocation.php 0000666 00000002644 15165257520 0021565 0 ustar 00 <?php namespace LE_ACME2\Request\Account; defined('ABSPATH') or die(); use LE_ACME2\Request\AbstractRequest; use LE_ACME2\Connector; use LE_ACME2\Cache; use LE_ACME2\Utilities; use LE_ACME2\Exception; use LE_ACME2\Account; abstract class AbstractLocation extends AbstractRequest { protected $_account; public function __construct(Account $account) { $this->_account = $account; } /** * @return Connector\RawResponse * @throws Exception\InvalidResponse * @throws Exception\RateLimitReached */ protected function _getRawResponse() : Connector\RawResponse { $payload = $this->_getPayload(); if(count($payload) == 0) { $payload['rand-' . rand(100000, 1000000)] = 1; } $kid = Utilities\RequestSigner::KID( $payload, Cache\AccountResponse::getInstance()->get($this->_account)->getLocation(), Cache\AccountResponse::getInstance()->get($this->_account)->getLocation(), Cache\NewNonceResponse::getInstance()->get()->getNonce(), $this->_account->getKeyDirectoryPath() ); $result = Connector\Connector::getInstance()->request( Connector\Connector::METHOD_POST, Cache\AccountResponse::getInstance()->get($this->_account)->getLocation(), $kid ); return $result; } abstract protected function _getPayload() : array; } vendor/fbett/le_acme2/src/LE_ACME2/Request/Account/Create.php 0000666 00000002555 15165257520 0017535 0 ustar 00 <?php namespace LE_ACME2\Request\Account; defined('ABSPATH') or die(); use LE_ACME2\Request\AbstractRequest; use LE_ACME2\Response; use LE_ACME2\Connector; use LE_ACME2\Cache; use LE_ACME2\Utilities; use LE_ACME2\Exception; use LE_ACME2\Account; class Create extends AbstractRequest { protected $_account; public function __construct(Account $account) { $this->_account = $account; } /** * @return Response\AbstractResponse|Response\Account\Create * @throws Exception\InvalidResponse * @throws Exception\RateLimitReached */ public function getResponse() : Response\AbstractResponse { $payload = [ 'contact' => $this->_buildContactPayload($this->_account->getEmail()), 'termsOfServiceAgreed' => true, ]; $jwk = Utilities\RequestSigner::JWKString( $payload, Cache\DirectoryResponse::getInstance()->get()->getNewAccount(), Cache\NewNonceResponse::getInstance()->get()->getNonce(), $this->_account->getKeyDirectoryPath() ); $result = Connector\Connector::getInstance()->request( Connector\Connector::METHOD_POST, Cache\DirectoryResponse::getInstance()->get()->getNewAccount(), $jwk ); return new Response\Account\Create($result); } } vendor/fbett/le_acme2/src/LE_ACME2/Request/GetDirectory.php 0000666 00000001231 15165257520 0017330 0 ustar 00 <?php namespace LE_ACME2\Request; defined('ABSPATH') or die(); use LE_ACME2\Response; use LE_ACME2\Connector\Connector; use LE_ACME2\Exception; class GetDirectory extends AbstractRequest { /** * @return Response\AbstractResponse|Response\GetDirectory * @throws Exception\InvalidResponse * @throws Exception\RateLimitReached */ public function getResponse() : Response\AbstractResponse { $connector = Connector::getInstance(); $result = $connector->request( Connector::METHOD_GET, $connector->getBaseURL() . '/directory' ); return new Response\GetDirectory($result); } } vendor/fbett/le_acme2/src/LE_ACME2/Request/Order/Create.php 0000666 00000003137 15165257520 0017211 0 ustar 00 <?php namespace LE_ACME2\Request\Order; defined('ABSPATH') or die(); use LE_ACME2\Request\AbstractRequest; use LE_ACME2\Response; use LE_ACME2\Connector; use LE_ACME2\Cache; use LE_ACME2\Exception; use LE_ACME2\Utilities; use LE_ACME2\Order; class Create extends AbstractRequest { protected $_order; public function __construct(Order $order) { $this->_order = $order; } /** * @return Response\AbstractResponse|Response\Order\Create * @throws Exception\InvalidResponse * @throws Exception\RateLimitReached */ public function getResponse() : Response\AbstractResponse { $identifiers = []; foreach($this->_order->getSubjects() as $subject) { $identifiers[] = [ 'type' => 'dns', 'value' => $subject ]; } $payload = [ 'identifiers' => $identifiers, 'notBefore' => '', 'notAfter' => '', ]; $kid = Utilities\RequestSigner::KID( $payload, Cache\AccountResponse::getInstance()->get($this->_order->getAccount())->getLocation(), Cache\DirectoryResponse::getInstance()->get()->getNewOrder(), Cache\NewNonceResponse::getInstance()->get()->getNonce(), $this->_order->getAccount()->getKeyDirectoryPath() ); $result = Connector\Connector::getInstance()->request( Connector\Connector::METHOD_POST, Cache\DirectoryResponse::getInstance()->get()->getNewOrder(), $kid ); return new Response\Order\Create($result); } } vendor/fbett/le_acme2/src/LE_ACME2/Request/Order/RevokeCertificate.php 0000666 00000003441 15165257520 0021402 0 ustar 00 <?php namespace LE_ACME2\Request\Order; defined('ABSPATH') or die(); use LE_ACME2\Response; use LE_ACME2\Request\AbstractRequest; use LE_ACME2\Connector; use LE_ACME2\Cache; use LE_ACME2\Exception; use LE_ACME2\Struct; use LE_ACME2\Utilities; class RevokeCertificate extends AbstractRequest { protected $_certificateBundle; protected $_reason; public function __construct(Struct\CertificateBundle $certificateBundle, $reason) { $this->_certificateBundle = $certificateBundle; $this->_reason = $reason; } /** * @return Response\AbstractResponse|Response\Order\RevokeCertificate * @throws Exception\InvalidResponse * @throws Exception\RateLimitReached */ public function getResponse() : Response\AbstractResponse { $certificate = file_get_contents($this->_certificateBundle->path . $this->_certificateBundle->certificate); preg_match('~-----BEGIN\sCERTIFICATE-----(.*)-----END\sCERTIFICATE-----~s', $certificate, $matches); $certificate = trim(Utilities\Base64::UrlSafeEncode(base64_decode(trim($matches[1])))); $payload = [ 'certificate' => $certificate, 'reason' => $this->_reason ]; $jwk = Utilities\RequestSigner::JWKString( $payload, Cache\DirectoryResponse::getInstance()->get()->getRevokeCert(), Cache\NewNonceResponse::getInstance()->get()->getNonce(), $this->_certificateBundle->path, $this->_certificateBundle->private ); $result = Connector\Connector::getInstance()->request( Connector\Connector::METHOD_POST, Cache\DirectoryResponse::getInstance()->get()->getRevokeCert(), $jwk ); return new Response\Order\RevokeCertificate($result); } } vendor/fbett/le_acme2/src/LE_ACME2/Request/Order/GetCertificate.php 0000666 00000003175 15165257520 0020672 0 ustar 00 <?php namespace LE_ACME2\Request\Order; defined('ABSPATH') or die(); use LE_ACME2\Order; use LE_ACME2\Request\AbstractRequest; use LE_ACME2\Response; use LE_ACME2\Connector; use LE_ACME2\Cache; use LE_ACME2\Exception; use LE_ACME2\Utilities; class GetCertificate extends AbstractRequest { protected $_order; protected $_orderResponse; private $_alternativeUrl = null; public function __construct(Order $order, Response\Order\AbstractOrder $orderResponse, string $alternativeUrl = null ) { $this->_order = $order; $this->_orderResponse = $orderResponse; if($alternativeUrl !== null) { $this->_alternativeUrl = $alternativeUrl; } } /** * @return Response\AbstractResponse|Response\Order\GetCertificate * @throws Exception\InvalidResponse * @throws Exception\RateLimitReached */ public function getResponse() : Response\AbstractResponse { $url = $this->_alternativeUrl === null ? $this->_orderResponse->getCertificate() : $this->_alternativeUrl; $kid = Utilities\RequestSigner::KID( null, Cache\AccountResponse::getInstance()->get($this->_order->getAccount())->getLocation(), $url, Cache\NewNonceResponse::getInstance()->get()->getNonce(), $this->_order->getAccount()->getKeyDirectoryPath() ); $result = Connector\Connector::getInstance()->request( Connector\Connector::METHOD_POST, $url, $kid ); return new Response\Order\GetCertificate($result); } } vendor/fbett/le_acme2/src/LE_ACME2/Request/Order/Get.php 0000666 00000002546 15165257520 0016530 0 ustar 00 <?php namespace LE_ACME2\Request\Order; defined('ABSPATH') or die(); use LE_ACME2\Request\AbstractRequest; use LE_ACME2\Response; use LE_ACME2\Connector; use LE_ACME2\Cache; use LE_ACME2\Exception; use LE_ACME2\Utilities; use LE_ACME2\Order; class Get extends AbstractRequest { protected $_order; protected $_orderResponse; public function __construct(Order $order, Response\Order\AbstractOrder $orderResponse) { $this->_order = $order; $this->_orderResponse = $orderResponse; } /** * @return Response\AbstractResponse|Response\Order\Get * @throws Exception\InvalidResponse * @throws Exception\RateLimitReached */ public function getResponse() : Response\AbstractResponse { $kid = Utilities\RequestSigner::KID( null, Cache\AccountResponse::getInstance()->get($this->_order->getAccount())->getLocation(), $this->_orderResponse->getLocation(), Cache\NewNonceResponse::getInstance()->get()->getNonce(), $this->_order->getAccount()->getKeyDirectoryPath() ); $result = Connector\Connector::getInstance()->request( Connector\Connector::METHOD_POST, $this->_orderResponse->getLocation(), $kid ); return new Response\Order\Get($result, $this->_orderResponse->getLocation()); } } vendor/fbett/le_acme2/src/LE_ACME2/Request/Order/Finalize.php 0000666 00000003270 15165257520 0017545 0 ustar 00 <?php namespace LE_ACME2\Request\Order; defined('ABSPATH') or die(); use LE_ACME2\Request\AbstractRequest; use LE_ACME2\Response; use LE_ACME2\Connector; use LE_ACME2\Cache; use LE_ACME2\Exception; use LE_ACME2\Utilities; use LE_ACME2\Order; class Finalize extends AbstractRequest { protected $_order; protected $_orderResponse; public function __construct(Order $order, Response\Order\AbstractOrder $orderResponse) { $this->_order = $order; $this->_orderResponse = $orderResponse; } /** * @return Response\AbstractResponse|Response\Order\Finalize * @throws Exception\InvalidResponse * @throws Exception\RateLimitReached */ public function getResponse() : Response\AbstractResponse { $csr = Utilities\Certificate::generateCSR($this->_order); if(preg_match('~-----BEGIN\sCERTIFICATE\sREQUEST-----(.*)-----END\sCERTIFICATE\sREQUEST-----~s', $csr, $matches)) $csr = $matches[1]; $csr = trim(Utilities\Base64::UrlSafeEncode(base64_decode($csr))); $payload = [ 'csr' => $csr ]; $kid = Utilities\RequestSigner::KID( $payload, Cache\AccountResponse::getInstance()->get($this->_order->getAccount())->getLocation(), $this->_orderResponse->getFinalize(), Cache\NewNonceResponse::getInstance()->get()->getNonce(), $this->_order->getAccount()->getKeyDirectoryPath() ); $result = Connector\Connector::getInstance()->request( Connector\Connector::METHOD_POST, $this->_orderResponse->getFinalize(), $kid ); return new Response\Order\Finalize($result); } } vendor/fbett/le_acme2/src/LE_ACME2/Request/GetNewNonce.php 0000666 00000001250 15165257520 0017101 0 ustar 00 <?php namespace LE_ACME2\Request; defined('ABSPATH') or die(); use LE_ACME2\Response; use LE_ACME2\Connector; use LE_ACME2\Cache; use LE_ACME2\Exception; class GetNewNonce extends AbstractRequest { /** * @return Response\AbstractResponse|Response\GetNewNonce * @throws Exception\InvalidResponse * @throws Exception\RateLimitReached */ public function getResponse() : Response\AbstractResponse { $result = Connector\Connector::getInstance()->request( Connector\Connector::METHOD_HEAD, Cache\DirectoryResponse::getInstance()->get()->getNewNonce() ); return new Response\GetNewNonce($result); } } vendor/fbett/le_acme2/src/LE_ACME2/Request/AbstractRequest.php 0000666 00000001013 15165257520 0020036 0 ustar 00 <?php namespace LE_ACME2\Request; defined('ABSPATH') or die(); use LE_ACME2\Response\AbstractResponse; use LE_ACME2\Exception; abstract class AbstractRequest { /** * @return AbstractResponse * @throws Exception\InvalidResponse * @throws Exception\RateLimitReached */ abstract public function getResponse() : AbstractResponse; protected function _buildContactPayload(string $email) : array { $result = [ 'mailto:' . $email ]; return $result; } } vendor/fbett/le_acme2/src/LE_ACME2/Request/Authorization/Get.php 0000666 00000002530 15165257520 0020306 0 ustar 00 <?php namespace LE_ACME2\Request\Authorization; defined('ABSPATH') or die(); use LE_ACME2\Request\AbstractRequest; use LE_ACME2\Connector; use LE_ACME2\Cache; use LE_ACME2\Exception; use LE_ACME2\Response; use LE_ACME2\Utilities; use LE_ACME2\Account; class Get extends AbstractRequest { protected $_account; protected $_authorizationURL; public function __construct(Account $account, string $authorizationURL) { $this->_account = $account; $this->_authorizationURL = $authorizationURL; } /** * @return Response\AbstractResponse|Response\Authorization\Get * @throws Exception\InvalidResponse * @throws Exception\RateLimitReached * @throws Exception\ExpiredAuthorization */ public function getResponse() : Response\AbstractResponse { $kid = Utilities\RequestSigner::KID( null, Cache\AccountResponse::getInstance()->get($this->_account)->getLocation(), $this->_authorizationURL, Cache\NewNonceResponse::getInstance()->get()->getNonce(), $this->_account->getKeyDirectoryPath() ); $result = Connector\Connector::getInstance()->request( Connector\Connector::METHOD_POST, $this->_authorizationURL, $kid ); return new Response\Authorization\Get($result); } } vendor/fbett/le_acme2/src/LE_ACME2/Request/Authorization/Start.php 0000666 00000003176 15165257520 0020673 0 ustar 00 <?php namespace LE_ACME2\Request\Authorization; defined('ABSPATH') or die(); use LE_ACME2\Request\AbstractRequest; use LE_ACME2\Connector; use LE_ACME2\Cache; use LE_ACME2\Exception; use LE_ACME2\Response; use LE_ACME2\Struct\ChallengeAuthorizationKey; use LE_ACME2\Utilities; use LE_ACME2\Account; use LE_ACME2\Order; class Start extends AbstractRequest { protected $_account; protected $_order; protected $_challenge; public function __construct(Account $account, Order $order, Response\Authorization\Struct\Challenge $challenge) { $this->_account = $account; $this->_order = $order; $this->_challenge = $challenge; } /** * @return Response\AbstractResponse|Response\Authorization\Start * @throws Exception\InvalidResponse * @throws Exception\RateLimitReached * @throws Exception\ExpiredAuthorization */ public function getResponse() : Response\AbstractResponse { $payload = [ 'keyAuthorization' => (new ChallengeAuthorizationKey($this->_account))->get($this->_challenge->token) ]; $kid = Utilities\RequestSigner::KID( $payload, Cache\AccountResponse::getInstance()->get($this->_account)->getLocation(), $this->_challenge->url, Cache\NewNonceResponse::getInstance()->get()->getNonce(), $this->_account->getKeyDirectoryPath() ); $result = Connector\Connector::getInstance()->request( Connector\Connector::METHOD_POST, $this->_challenge->url, $kid ); return new Response\Authorization\Start($result); } } vendor/fbett/le_acme2/src/LE_ACME2/AbstractKeyValuable.php 0000666 00000005317 15165257520 0017175 0 ustar 00 <?php namespace LE_ACME2; defined('ABSPATH') or die(); use LE_ACME2\Connector\Connector; abstract class AbstractKeyValuable { const KEY_TYPE_RSA = "RSA"; const KEY_TYPE_EC = "EC"; protected $_identifier; protected static $_directoryPath = null; public static function setCommonKeyDirectoryPath(string $directoryPath) { if(!file_exists($directoryPath)) { throw new \RuntimeException('Common Key Directory Path does not exist'); } self::$_directoryPath = realpath($directoryPath) . DIRECTORY_SEPARATOR; } public static function getCommonKeyDirectoryPath() : ?string { return self::$_directoryPath; } protected function _getKeyDirectoryPath(string $appendix = '') : string { return self::$_directoryPath . $this->_identifier . $appendix . DIRECTORY_SEPARATOR; } public function getKeyDirectoryPath() : string { return $this->_getKeyDirectoryPath(''); } protected function _initKeyDirectory(string $keyType = self::KEY_TYPE_RSA, bool $ignoreIfKeysExist = false) { if(!file_exists($this->getKeyDirectoryPath())) { mkdir($this->getKeyDirectoryPath()); } if(!$ignoreIfKeysExist && ( file_exists($this->getKeyDirectoryPath() . 'private.pem') || file_exists($this->getKeyDirectoryPath() . 'public.pem') ) ) { throw new \RuntimeException( 'Keys exist already. Exists the ' . get_class($this) . ' already?' . PHP_EOL . 'Path: ' . $this->getKeyDirectoryPath() ); } if($keyType == self::KEY_TYPE_RSA) { Utilities\KeyGenerator::RSA( $this->getKeyDirectoryPath(), 'private.pem', 'public.pem' ); } else if($keyType == self::KEY_TYPE_EC) { Utilities\KeyGenerator::EC( $this->getKeyDirectoryPath(), 'private.pem', 'public.pem' ); } else { throw new \RuntimeException('Key type "' . $keyType . '" not supported.'); } } protected function _clearKeyDirectory() { if(file_exists($this->getKeyDirectoryPath() . 'private.pem')) { unlink($this->getKeyDirectoryPath() . 'private.pem'); } if(file_exists($this->getKeyDirectoryPath() . 'public.pem')) { unlink($this->getKeyDirectoryPath() . 'public.pem'); } } protected function _getAccountIdentifier(Account $account) : string { $staging = Connector::getInstance()->isUsingStagingServer(); return 'account_' . ($staging ? 'staging_' : 'live_') . $account->getEmail(); } } vendor/fbett/le_acme2/src/LE_ACME2/SingletonTrait.php 0000666 00000000534 15165257520 0016247 0 ustar 00 <?php namespace LE_ACME2; defined('ABSPATH') or die(); trait SingletonTrait { private static $_instance = NULL; /** * @return static */ final public static function getInstance(): self { if( self::$_instance === NULL ) { self::$_instance = new self(); } return self::$_instance; } } vendor/fbett/le_acme2/src/LE_ACME2/Exception/OpenSSLException.php 0000666 00000000564 15165257520 0020404 0 ustar 00 <?php namespace LE_ACME2\Exception; defined('ABSPATH') or die(); class OpenSSLException extends AbstractException { public function __construct(string $function) { $errors = []; while(($error = openssl_error_string()) !== false) { $errors[] = $error; } parent::__construct( $function . ' failed - error messages: ' . var_export($errors, true) ); } } vendor/fbett/le_acme2/src/LE_ACME2/Exception/DNSAuthorizationInvalid.php 0000666 00000000200 15165257520 0021741 0 ustar 00 <?php namespace LE_ACME2\Exception; defined('ABSPATH') or die(); class DNSAuthorizationInvalid extends AuthorizationInvalid {} vendor/fbett/le_acme2/src/LE_ACME2/Exception/StatusInvalid.php 0000666 00000000163 15165257520 0020027 0 ustar 00 <?php namespace LE_ACME2\Exception; defined('ABSPATH') or die(); class StatusInvalid extends AbstractException {} vendor/fbett/le_acme2/src/LE_ACME2/Exception/ExpiredAuthorization.php 0000666 00000000345 15165257520 0021420 0 ustar 00 <?php namespace LE_ACME2\Exception; defined('ABSPATH') or die(); class ExpiredAuthorization extends AbstractException { public function __construct() { parent::__construct("Expired authorization received"); } } vendor/fbett/le_acme2/src/LE_ACME2/Exception/AuthorizationInvalid.php 0000666 00000000172 15165257520 0021404 0 ustar 00 <?php namespace LE_ACME2\Exception; defined('ABSPATH') or die(); class AuthorizationInvalid extends AbstractException {} vendor/fbett/le_acme2/src/LE_ACME2/Exception/HTTPAuthorizationInvalid.php 0000666 00000000201 15165257520 0022075 0 ustar 00 <?php namespace LE_ACME2\Exception; defined('ABSPATH') or die(); class HTTPAuthorizationInvalid extends AuthorizationInvalid {} vendor/fbett/le_acme2/src/LE_ACME2/Exception/RateLimitReached.php 0000666 00000000542 15165257520 0020404 0 ustar 00 <?php namespace LE_ACME2\Exception; defined('ABSPATH') or die(); class RateLimitReached extends AbstractException { public function __construct(string $request, string $detail) { parent::__construct( "Invalid response received for request (" . $request . "): " . "rate limit reached - " . $detail ); } } vendor/fbett/le_acme2/src/LE_ACME2/Exception/InvalidResponse.php 0000666 00000002043 15165257520 0020341 0 ustar 00 <?php namespace LE_ACME2\Exception; defined('ABSPATH') or die(); use LE_ACME2\Connector\RawResponse; class InvalidResponse extends AbstractException { private $_rawResponse; private $_responseStatus; public function __construct(RawResponse $rawResponse, string $responseStatus = null) { $this->_rawResponse = $rawResponse; $this->_responseStatus = $responseStatus; if($responseStatus === '') { $responseStatus = 'Unknown response status'; } if(isset($this->_rawResponse->body['type'])) { $responseStatus = $this->_rawResponse->body['type']; } if(isset($this->_rawResponse->body['detail'])) { $responseStatus .= ' - ' . $this->_rawResponse->body['detail']; } parent::__construct('Invalid response received: ' . $responseStatus); } public function getRawResponse() : RawResponse { return $this->_rawResponse; } public function getResponseStatus() : ?string { return $this->_responseStatus; } } vendor/fbett/le_acme2/src/LE_ACME2/Exception/AbstractException.php 0000666 00000000627 15165257520 0020664 0 ustar 00 <?php namespace LE_ACME2\Exception; defined('ABSPATH') or die(); use LE_ACME2\Utilities; abstract class AbstractException extends \Exception { public function __construct(string $message) { Utilities\Logger::getInstance()->add( Utilities\Logger::LEVEL_DEBUG, 'Exception "' . get_called_class() . '" thrown ' ); parent::__construct($message); } } vendor/fbett/le_acme2/src/LE_ACME2/Utilities/Base64.php 0000666 00000001667 15165257520 0016310 0 ustar 00 <?php namespace LE_ACME2\Utilities; defined('ABSPATH') or die(); class Base64 { /** * Encodes a string input to a base64 encoded string which is URL safe. * * @param string $input The input string to encode. * @return string Returns a URL safe base64 encoded string. */ public static function UrlSafeEncode(string $input) : string { return str_replace('=', '', strtr(base64_encode($input), '+/', '-_')); } /** * Decodes a string that is URL safe base64 encoded. * * @param string $input The encoded input string to decode. * @return string Returns the decoded input string. */ public static function UrlSafeDecode(string $input) : string { $remainder = strlen($input) % 4; if ($remainder) { $padlen = 4 - $remainder; $input .= str_repeat('=', $padlen); } return base64_decode(strtr($input, '-_', '+/')); } } vendor/fbett/le_acme2/src/LE_ACME2/Utilities/Certificate.php 0000666 00000003650 15165257520 0017500 0 ustar 00 <?php namespace LE_ACME2\Utilities; defined('ABSPATH') or die(); use LE_ACME2\Order; use LE_ACME2\Exception\OpenSSLException; class Certificate { protected static $_featureOCSPMustStapleEnabled = false; public static function enableFeatureOCSPMustStaple() { self::$_featureOCSPMustStapleEnabled = true; } public static function disableFeatureOCSPMustStaple() { self::$_featureOCSPMustStapleEnabled = false; } /** * @param Order $order * @return string * @throws OpenSSLException */ public static function generateCSR(Order $order) : string { $dn = [ "commonName" => $order->getSubjects()[0] ]; $san = implode(",", array_map(function ($dns) { return "DNS:" . $dns; }, $order->getSubjects()) ); $configFilePath = $order->getKeyDirectoryPath() . 'csr_config'; $config = 'HOME = . RANDFILE = ' . $order->getKeyDirectoryPath() . '.rnd [ req ] default_bits = 4096 default_keyfile = privkey.pem distinguished_name = req_distinguished_name req_extensions = v3_req [ req_distinguished_name ] countryName = Country Name (2 letter code) [ v3_req ] basicConstraints = CA:FALSE subjectAltName = ' . $san . ' keyUsage = nonRepudiation, digitalSignature, keyEncipherment'; if(self::$_featureOCSPMustStapleEnabled) { $config .= PHP_EOL . 'tlsfeature=status_request'; } file_put_contents($configFilePath, $config); $privateKey = openssl_pkey_get_private( file_get_contents($order->getKeyDirectoryPath() . 'private.pem') ); if($privateKey === false) { throw new OpenSSLException('openssl_pkey_get_private'); } $csr = openssl_csr_new( $dn, $privateKey, [ 'config' => $configFilePath, 'digest_alg' => 'sha256' ] ); if($csr === false) { throw new OpenSSLException('openssl_csr_new'); } if(!openssl_csr_export($csr, $csr)) { throw new OpenSSLException('openssl_csr_export'); } unlink($configFilePath); return $csr; } } vendor/fbett/le_acme2/src/LE_ACME2/Utilities/Logger.php 0000666 00000003130 15165257520 0016466 0 ustar 00 <?php namespace LE_ACME2\Utilities; defined('ABSPATH') or die(); use LE_ACME2\SingletonTrait; class Logger { use SingletonTrait; const LEVEL_DISABLED = 0; const LEVEL_INFO = 1; const LEVEL_DEBUG = 2; private function __construct() {} protected $_desiredLevel = self::LEVEL_DISABLED; public function setDesiredLevel(int $desiredLevel) { $this->_desiredLevel = $desiredLevel; } /** * @param int $level * @param string $message * @param string|array|object $data */ public function add(int $level, string $message, $data = array()) { if($level > $this->_desiredLevel) return; $e = new \Exception(); $trace = $e->getTrace(); unset($trace[0]); $output = '<b>' . date('d-m-Y H:i:s') . ': ' . $message . '</b><br>' . "\n"; if($this->_desiredLevel == self::LEVEL_DEBUG) { $step = 0; foreach ($trace as $traceItem) { if(!isset($traceItem['class']) || !isset($traceItem['function'])) { continue; } $output .= 'Trace #' . $step . ': ' . $traceItem['class'] . '::' . $traceItem['function'] . '<br/>' . "\n"; $step++; } if ((is_array($data) && count($data) > 0) || !is_array($data)) $output .= "\n" .'<br/>Data:<br/>' . "\n" . '<pre>' . var_export($data, true) . '</pre>'; $output .= '<br><br>' . "\n\n"; } if(PHP_SAPI == 'cli') { $output = strip_tags($output); } echo $output; } } vendor/fbett/le_acme2/src/LE_ACME2/Utilities/KeyGenerator.php 0000666 00000004504 15165257520 0017654 0 ustar 00 <?php namespace LE_ACME2\Utilities; defined('ABSPATH') or die(); class KeyGenerator { /** * Generates a new RSA keypair and saves both keys to a new file. * * @param string $directory The directory in which to store the new keys. * @param string $privateKeyFile The filename for the private key file. * @param string $publicKeyFile The filename for the public key file. */ public static function RSA(string $directory, string $privateKeyFile = 'private.pem', string $publicKeyFile = 'public.pem') { $res = openssl_pkey_new([ "private_key_type" => OPENSSL_KEYTYPE_RSA, "private_key_bits" => 4096, ]); if(!openssl_pkey_export($res, $privateKey)) throw new \RuntimeException("RSA keypair export failed!"); $details = openssl_pkey_get_details($res); file_put_contents($directory . $privateKeyFile, $privateKey); file_put_contents($directory . $publicKeyFile, $details['key']); if(PHP_MAJOR_VERSION < 8) { // deprecated after PHP 8.0.0 and not needed anymore openssl_pkey_free($res); } } /** * Generates a new EC prime256v1 keypair and saves both keys to a new file. * * @param string $directory The directory in which to store the new keys. * @param string $privateKeyFile The filename for the private key file. * @param string $publicKeyFile The filename for the public key file. */ public static function EC(string $directory, string $privateKeyFile = 'private.pem', string $publicKeyFile = 'public.pem') { if (version_compare(PHP_VERSION, '7.1.0') == -1) throw new \RuntimeException("PHP 7.1+ required for EC keys"); $res = openssl_pkey_new([ "private_key_type" => OPENSSL_KEYTYPE_EC, "curve_name" => "prime256v1", ]); if(!openssl_pkey_export($res, $privateKey)) throw new \RuntimeException("EC keypair export failed!"); $details = openssl_pkey_get_details($res); file_put_contents($directory . $privateKeyFile, $privateKey); file_put_contents($directory . $publicKeyFile, $details['key']); if(PHP_MAJOR_VERSION < 8) { // deprecated after PHP 8.0.0 and not needed anymore openssl_pkey_free($res); } } } vendor/fbett/le_acme2/src/LE_ACME2/Utilities/RequestSigner.php 0000666 00000011037 15165257520 0020054 0 ustar 00 <?php namespace LE_ACME2\Utilities; defined('ABSPATH') or die(); class RequestSigner { /** * Generates a JSON Web Key signature to attach to the request. * * @param array $payload The payload to add to the signature. * @param string $url The URL to use in the signature. * @param string $nonce * @param string $privateKeyDir The directory to get the private key from. Default to the account keys directory given in the constructor. (optional) * @param string $privateKeyFile The private key to sign the request with. Defaults to 'private.pem'. (optional) * * @return array Returns an array containing the signature. */ public static function JWK(array $payload, string $url, string $nonce, string $privateKeyDir, string $privateKeyFile = 'private.pem') : array { Logger::getInstance()->add(Logger::LEVEL_DEBUG, 'JWK sign request for ' . $url, $payload); $privateKey = openssl_pkey_get_private(file_get_contents($privateKeyDir . $privateKeyFile)); $details = openssl_pkey_get_details($privateKey); $protected = [ "alg" => "RS256", "jwk" => [ "kty" => "RSA", "n" => Base64::UrlSafeEncode($details["rsa"]["n"]), "e" => Base64::UrlSafeEncode($details["rsa"]["e"]), ], "nonce" => $nonce, "url" => $url ]; $payload64 = Base64::UrlSafeEncode(str_replace('\\/', '/', json_encode($payload))); $protected64 = Base64::UrlSafeEncode(json_encode($protected)); openssl_sign($protected64.'.'.$payload64, $signed, $privateKey, "SHA256"); $signed64 = Base64::UrlSafeEncode($signed); $data = array( 'protected' => $protected64, 'payload' => $payload64, 'signature' => $signed64 ); return $data; } /** * Generates a JSON Web Key signature to attach to the request. * * @param array $payload The payload to add to the signature. * @param string $url The URL to use in the signature. * @param string $nonce * @param string $privateKeyDir The directory to get the private key from. Default to the account keys directory given in the constructor. (optional) * @param string $privateKeyFile The private key to sign the request with. Defaults to 'private.pem'. (optional) * * @return string Returns a JSON encoded string containing the signature. */ public static function JWKString(array $payload, string $url, string $nonce, string $privateKeyDir, string $privateKeyFile = 'private.pem') : string { $jwk = self::JWK($payload, $url, $nonce, $privateKeyDir, $privateKeyFile); return json_encode($jwk); } /** * Generates a Key ID signature to attach to the request. * * @param array|null $payload The payload to add to the signature. * @param string $kid The Key ID to use in the signature. * @param string $url The URL to use in the signature. * @param string $nonce * @param string $privateKeyDir The directory to get the private key from. * @param string $privateKeyFile The private key to sign the request with. Defaults to 'private.pem'. (optional) * * @return string Returns a JSON encoded string containing the signature. */ public static function KID(?array $payload, string $kid, string $url, string $nonce, string $privateKeyDir, string $privateKeyFile = 'private.pem') : string { Logger::getInstance()->add(Logger::LEVEL_DEBUG, 'KID sign request for ' . $url, $payload); $privateKey = openssl_pkey_get_private(file_get_contents($privateKeyDir . $privateKeyFile)); // TODO: unused - $details = openssl_pkey_get_details($privateKey); $protected = [ "alg" => "RS256", "kid" => $kid, "nonce" => $nonce, "url" => $url ]; Logger::getInstance()->add(Logger::LEVEL_DEBUG, 'KID: ready to sign request for: ' . $url, $protected); $payload = $payload === null ? "" : str_replace('\\/', '/', json_encode($payload)); $payload64 = Base64::UrlSafeEncode($payload); $protected64 = Base64::UrlSafeEncode(json_encode($protected)); openssl_sign($protected64.'.'.$payload64, $signed, $privateKey, "SHA256"); $signed64 = Base64::UrlSafeEncode($signed); $data = [ 'protected' => $protected64, 'payload' => $payload64, 'signature' => $signed64 ]; return json_encode($data); } } vendor/fbett/le_acme2/src/LE_ACME2/Authorizer/HTTP.php 0000666 00000010043 15165257520 0016210 0 ustar 00 <?php namespace LE_ACME2\Authorizer; defined('ABSPATH') or die(); use LE_ACME2\Request; use LE_ACME2\Response; use LE_ACME2\Struct\ChallengeAuthorizationKey; use LE_ACME2\Utilities; use LE_ACME2\Exception; use LE_ACME2\Order; class HTTP extends AbstractAuthorizer { protected static $_directoryPath = null; public static function setDirectoryPath(string $directoryPath) { if(!file_exists($directoryPath)) { throw new \RuntimeException('HTTP authorization directory path does not exist'); } self::$_directoryPath = realpath($directoryPath) . DIRECTORY_SEPARATOR; } public static function getDirectoryPath() : ?string { return self::$_directoryPath; } protected function _getChallengeType(): string { return Order::CHALLENGE_TYPE_HTTP; } /** * @param Response\Authorization\Struct\Challenge $challenge * @param Response\Authorization\Get $authorizationResponse * @return bool * * @throws Exception\AuthorizationInvalid * @throws Exception\ExpiredAuthorization * @throws Exception\InvalidResponse * @throws Exception\RateLimitReached */ protected function _existsNotValidChallenges(Response\Authorization\Struct\Challenge $challenge, Response\Authorization\Get $authorizationResponse ) : bool { Utilities\Logger::getInstance()->add( Utilities\Logger::LEVEL_DEBUG, 'Challenge "' . $challenge->token . '" has status:' . $challenge->status ); if($challenge->status == Response\Authorization\Struct\Challenge::STATUS_PENDING) { $this->_writeToFile($challenge); if($this->_validateFile($authorizationResponse->getIdentifier()->value, $challenge)) { $request = new Request\Authorization\Start($this->_account, $this->_order, $challenge); /* $response = */ $request->getResponse(); } else { Utilities\Logger::getInstance()->add(Utilities\Logger::LEVEL_INFO, 'Could not validate HTTP Authorization file'); } } if($challenge->status == Response\Authorization\Struct\Challenge::STATUS_INVALID) { throw new Exception\HTTPAuthorizationInvalid( 'Received status "' . Response\Authorization\Struct\Challenge::STATUS_INVALID . '" while challenge should be verified' ); } return parent::_existsNotValidChallenges($challenge, $authorizationResponse); } private function _writeToFile(Response\Authorization\Struct\Challenge $challenge) : void { file_put_contents( self::$_directoryPath . $challenge->token, (new ChallengeAuthorizationKey($this->_account))->get($challenge->token) ); } /** * @param string $domain * @param Response\Authorization\Struct\Challenge $challenge * @return bool * * @throws Exception\HTTPAuthorizationInvalid */ private function _validateFile(string $domain, Response\Authorization\Struct\Challenge $challenge) : bool { if ( get_option('rsssl_skip_challenge_directory_request') ) { return true; } $challengeAuthorizationKey = new ChallengeAuthorizationKey($this->_account); $requestURL = 'http://' . $domain . '/.well-known/acme-challenge/' . $challenge->token; $handle = curl_init(); curl_setopt($handle, CURLOPT_URL, $requestURL); curl_setopt($handle, CURLOPT_FOLLOWLOCATION, true); curl_setopt($handle, CURLOPT_RETURNTRANSFER, true); $response = curl_exec($handle); $result = !empty($response) && $response == $challengeAuthorizationKey->get($challenge->token); if(!$result) { throw new Exception\HTTPAuthorizationInvalid( 'HTTP challenge for "' . $domain . '"": ' . $domain . '/.well-known/acme-challenge/' . $challenge->token . ' tested, found invalid. CURL response: ' . var_export($response, true) ); } return true; } } vendor/fbett/le_acme2/src/LE_ACME2/Authorizer/DNS.php 0000666 00000004566 15165257520 0016072 0 ustar 00 <?php namespace LE_ACME2\Authorizer; defined('ABSPATH') or die(); use LE_ACME2\Request; use LE_ACME2\Response; use LE_ACME2\Exception; use LE_ACME2\Order; use LE_ACME2\Struct\ChallengeAuthorizationKey; use LE_ACME2\Utilities; class DNS extends AbstractAuthorizer { protected function _getChallengeType(): string { return Order::CHALLENGE_TYPE_DNS; } /** @var AbstractDNSWriter $_dnsWriter */ private static $_dnsWriter = null; public static function setWriter(AbstractDNSWriter $dnsWriter) : void { self::$_dnsWriter = $dnsWriter; } /** * @param Response\Authorization\Struct\Challenge $challenge * @param Response\Authorization\Get $authorizationResponse * @return bool * * @throws Exception\AuthorizationInvalid * @throws Exception\DNSAuthorizationInvalid * @throws Exception\ExpiredAuthorization * @throws Exception\InvalidResponse * @throws Exception\RateLimitReached */ protected function _existsNotValidChallenges(Response\Authorization\Struct\Challenge $challenge, Response\Authorization\Get $authorizationResponse ) : bool { if($challenge->status == Response\Authorization\Struct\Challenge::STATUS_PENDING) { if(self::$_dnsWriter === null) { throw new \RuntimeException('DNS writer is not set'); } if( self::$_dnsWriter->write( $this->_order, $authorizationResponse->getIdentifier()->value, (new ChallengeAuthorizationKey($this->_account))->getEncoded($challenge->token) ) ) { $request = new Request\Authorization\Start($this->_account, $this->_order, $challenge); /* $response = */ $request->getResponse(); } else { Utilities\Logger::getInstance()->add(Utilities\Logger::LEVEL_INFO, 'Pending challenge deferred'); } } if($challenge->status == Response\Authorization\Struct\Challenge::STATUS_INVALID) { throw new Exception\DNSAuthorizationInvalid( 'Received status "' . Response\Authorization\Struct\Challenge::STATUS_INVALID . '" while challenge should be verified' ); } return parent::_existsNotValidChallenges($challenge, $authorizationResponse); } } vendor/fbett/le_acme2/src/LE_ACME2/Authorizer/AbstractAuthorizer.php 0000666 00000012016 15165257520 0021253 0 ustar 00 <?php namespace LE_ACME2\Authorizer; defined('ABSPATH') or die(); use LE_ACME2\Request; use LE_ACME2\Response; use LE_ACME2\Cache; use LE_ACME2\Utilities; use LE_ACME2\Exception; use LE_ACME2\Account; use LE_ACME2\Order; abstract class AbstractAuthorizer { protected $_account; protected $_order; /** * AbstractAuthorizer constructor. * * @param Account $account * @param Order $order * * @throws Exception\InvalidResponse * @throws Exception\RateLimitReached * @throws Exception\ExpiredAuthorization */ public function __construct(Account $account, Order $order) { $this->_account = $account; $this->_order = $order; $this->_fetchAuthorizationResponses(); } /** @var Response\Authorization\Get[] $_authorizationResponses */ protected $_authorizationResponses = []; /** * @throws Exception\InvalidResponse * @throws Exception\RateLimitReached * @throws Exception\ExpiredAuthorization */ protected function _fetchAuthorizationResponses() { if(!file_exists($this->_order->getKeyDirectoryPath() . 'private.pem')) { Utilities\Logger::getInstance()->add( Utilities\Logger::LEVEL_DEBUG, get_class() . '::' . __FUNCTION__ . ' result suppressed (Order has finished already)' ); return; } $orderResponse = Cache\OrderResponse::getInstance()->get($this->_order); foreach($orderResponse->getAuthorizations() as $authorization) { $request = new Request\Authorization\Get($this->_account, $authorization); $this->_authorizationResponses[] = $request->getResponse(); } } protected function _hasValidAuthorizationResponses() : bool { return count($this->_authorizationResponses) > 0; } public function shouldStartAuthorization() : bool { foreach($this->_authorizationResponses as $response) { $challenge = $response->getChallenge($this->_getChallengeType()); if($challenge->status == Response\Authorization\Struct\Challenge::STATUS_PENDING) { Utilities\Logger::getInstance()->add( Utilities\Logger::LEVEL_DEBUG, get_class() . '::' . __FUNCTION__ . ' "Pending challenge found', $challenge ); return true; } } return false; } abstract protected function _getChallengeType() : string; /** * @throws Exception\AuthorizationInvalid * @throws Exception\InvalidResponse * @throws Exception\RateLimitReached * @throws Exception\ExpiredAuthorization */ public function progress() { if(!$this->_hasValidAuthorizationResponses()) return; $existsNotValidChallenges = false; foreach($this->_authorizationResponses as $authorizationResponse) { $challenge = $authorizationResponse->getChallenge($this->_getChallengeType()); if($this->_existsNotValidChallenges($challenge, $authorizationResponse)) { $existsNotValidChallenges = true; } } $this->_finished = !$existsNotValidChallenges; } /** * @param Response\Authorization\Struct\Challenge $challenge * @param Response\Authorization\Get $authorizationResponse * @return bool * * @throws Exception\AuthorizationInvalid */ protected function _existsNotValidChallenges(Response\Authorization\Struct\Challenge $challenge, Response\Authorization\Get $authorizationResponse ) : bool { if($challenge->status == Response\Authorization\Struct\Challenge::STATUS_PENDING) { Utilities\Logger::getInstance()->add( Utilities\Logger::LEVEL_DEBUG, get_class() . '::' . __FUNCTION__ . ' "Non valid challenge found', $challenge ); return true; } else if($challenge->status == Response\Authorization\Struct\Challenge::STATUS_PROGRESSING) { // Should come back later return true; } else if($challenge->status == Response\Authorization\Struct\Challenge::STATUS_VALID) { } else if($challenge->status == Response\Authorization\Struct\Challenge::STATUS_INVALID) { throw new Exception\AuthorizationInvalid( 'Received status "' . Response\Authorization\Struct\Challenge::STATUS_INVALID . '" while challenge should be verified' ); } else { throw new \RuntimeException('Challenge status "' . $challenge->status . '" is not implemented'); } return false; } protected $_finished = false; public function hasFinished() : bool { Utilities\Logger::getInstance()->add( Utilities\Logger::LEVEL_DEBUG, get_called_class() . '::' . __FUNCTION__, $this->_finished ); return $this->_finished; } } vendor/fbett/le_acme2/src/LE_ACME2/Authorizer/AbstractDNSWriter.php 0000666 00000000663 15165257520 0020745 0 ustar 00 <?php namespace LE_ACME2\Authorizer; defined('ABSPATH') or die(); use LE_ACME2\Order; abstract class AbstractDNSWriter { /** * @param Order $order * @param string $identifier * @param string $digest * * @return bool return true, if the dns configuration is usable and the process should be progressed */ abstract public function write(Order $order, string $identifier, string $digest) : bool; } vendor/fbett/le_acme2/src/LE_ACME2/Account.php 0000666 00000010365 15165257520 0014700 0 ustar 00 <?php namespace LE_ACME2; defined('ABSPATH') or die(); use LE_ACME2\Request; use LE_ACME2\Response; use LE_ACME2\Utilities; use LE_ACME2\Exception; class Account extends AbstractKeyValuable { private $_email = NULL; public function __construct(string $email) { $this->_setEmail($email); Utilities\Logger::getInstance()->add( Utilities\Logger::LEVEL_INFO, get_class() . '::' . __FUNCTION__ . ' email: "' . $email . '"' ); Utilities\Logger::getInstance()->add( Utilities\Logger::LEVEL_DEBUG, get_class() . '::' . __FUNCTION__ . ' path: ' . $this->getKeyDirectoryPath() ); } private function _setEmail(string $email) { $this->_email = $email; $this->_identifier = $this->_getAccountIdentifier($this); } public function getEmail() : string { return $this->_email; } /** * @param string $email * @return Account|null * @throws Exception\AbstractException */ public static function create(string $email) : Account { $account = new self($email); $account->_initKeyDirectory(); $request = new Request\Account\Create($account); try { $response = $request->getResponse(); Cache\AccountResponse::getInstance()->set($account, $response); return $account; } catch(Exception\AbstractException $e) { $account->_clearKeyDirectory(); throw $e; } } public static function exists(string $email) : bool { $account = new self($email); return file_exists($account->getKeyDirectoryPath()) && file_exists($account->getKeyDirectoryPath() . 'private.pem') && file_exists($account->getKeyDirectoryPath() . 'public.pem'); } public static function get(string $email) : Account { $account = new self($email); if(!self::exists($email)) throw new \RuntimeException('Keys not found - does this account exist?'); return $account; } /** * @return Response\AbstractResponse|Response\Account\GetData * @throws Exception\InvalidResponse * @throws Exception\RateLimitReached */ public function getData() : Response\Account\GetData { $request = new Request\Account\GetData($this); return $request->getResponse(); } /** * @param string $email * @return bool * @throws Exception\RateLimitReached */ public function update(string $email) : bool { $request = new Request\Account\Update($this, $email); try { /* $response = */ $request->getResponse(); $previousKeyDirectoryPath = $this->getKeyDirectoryPath(); $this->_setEmail($email); if($previousKeyDirectoryPath != $this->getKeyDirectoryPath()) rename($previousKeyDirectoryPath, $this->getKeyDirectoryPath()); return true; } catch(Exception\InvalidResponse $e) { return false; } } /** * @return bool * @throws Exception\RateLimitReached */ public function changeKeys() : bool { Utilities\KeyGenerator::RSA($this->getKeyDirectoryPath(), 'private-replacement.pem', 'public-replacement.pem'); $request = new Request\Account\ChangeKeys($this); try { /* $response = */ $request->getResponse(); unlink($this->getKeyDirectoryPath() . 'private.pem'); unlink($this->getKeyDirectoryPath() . 'public.pem'); rename($this->getKeyDirectoryPath() . 'private-replacement.pem', $this->getKeyDirectoryPath() . 'private.pem'); rename($this->getKeyDirectoryPath() . 'public-replacement.pem', $this->getKeyDirectoryPath() . 'public.pem'); return true; } catch(Exception\InvalidResponse $e) { return false; } } /** * @return bool * @throws Exception\RateLimitReached */ public function deactivate() : bool { $request = new Request\Account\Deactivate($this); try { /* $response = */ $request->getResponse(); return true; } catch(Exception\InvalidResponse $e) { return false; } } } vendor/fbett/le_acme2/src/LE_ACME2/Order.php 0000666 00000033573 15165257520 0014365 0 ustar 00 <?php namespace LE_ACME2; defined('ABSPATH') or die(); use LE_ACME2\Request; use LE_ACME2\Response; use LE_ACME2\Cache; use LE_ACME2\Authorizer; use LE_ACME2\Exception; use LE_ACME2\Utilities; class Order extends AbstractKeyValuable { const CHALLENGE_TYPE_HTTP = 'http-01'; const CHALLENGE_TYPE_DNS = 'dns-01'; /** * @deprecated * @param $directoryPath */ public static function setHTTPAuthorizationDirectoryPath(string $directoryPath) { Authorizer\HTTP::setDirectoryPath($directoryPath); } CONST IDENTRUST_ISSUER_CN = 'DST Root CA X3'; /** @var string|null $_preferredChain */ private static $_preferredChain = null; public static function setPreferredChain(string $issuerCN = null) { self::$_preferredChain = $issuerCN; } protected $_account; protected $_subjects; public function __construct(Account $account, array $subjects) { array_map(function($subject) { if(preg_match_all('~(\*\.)~', $subject) > 1) throw new \RuntimeException('Cannot create orders with multiple wildcards in one domain.'); }, $subjects); $this->_account = $account; $this->_subjects = $subjects; $this->_identifier = $this->_getAccountIdentifier($account) . DIRECTORY_SEPARATOR . 'order_' . md5(implode('|', $subjects)); Utilities\Logger::getInstance()->add( Utilities\Logger::LEVEL_INFO, get_class() . '::' . __FUNCTION__ . ' "' . implode(':', $this->getSubjects()) . '"' ); Utilities\Logger::getInstance()->add( Utilities\Logger::LEVEL_DEBUG, get_class() . '::' . __FUNCTION__ . ' path: ' . $this->getKeyDirectoryPath() ); } public function getAccount() : Account { return $this->_account; } public function getSubjects() : array { return $this->_subjects; } /** * @param Account $account * @param array $subjects * @param string $keyType * @return Order * @throws Exception\AbstractException */ public static function create(Account $account, array $subjects, string $keyType = self::KEY_TYPE_RSA) : Order { $order = new self($account, $subjects); return $order->_create($keyType, false); } /** * @param $keyType * @param bool $ignoreIfKeysExist * @return Order * @throws Exception\AbstractException */ protected function _create(string $keyType, bool $ignoreIfKeysExist = false) : Order { $this->_initKeyDirectory($keyType, $ignoreIfKeysExist); $request = new Request\Order\Create($this); try { $response = $request->getResponse(); Cache\OrderResponse::getInstance()->set($this, $response); return $this; } catch(Exception\AbstractException $e) { $this->_clearKeyDirectory(); throw $e; } } public static function exists(Account $account, array $subjects) : bool { $order = new self($account, $subjects); return Cache\OrderResponse::getInstance()->exists($order); } public static function get(Account $account, array $subjects) : Order { $order = new self($account, $subjects); if(!self::exists($account, $subjects)) throw new \RuntimeException('Order does not exist'); return $order; } /** @var Authorizer\AbstractAuthorizer|Authorizer\HTTP|null $_authorizer */ protected $_authorizer = null; /** * @param $type * @return Authorizer\AbstractAuthorizer|Authorizer\HTTP|null * @throws Exception\InvalidResponse * @throws Exception\RateLimitReached * @throws Exception\ExpiredAuthorization */ protected function _getAuthorizer(string $type) : Authorizer\AbstractAuthorizer { if($this->_authorizer === null) { if($type == self::CHALLENGE_TYPE_HTTP) { $this->_authorizer = new Authorizer\HTTP($this->_account, $this); } else if($type == self::CHALLENGE_TYPE_DNS) { $this->_authorizer = new Authorizer\DNS($this->_account, $this); } else { throw new \RuntimeException('Challenge type not implemented'); } } return $this->_authorizer; } /** * The Authorization has expired, so we clean the complete order to restart again on the next call */ protected function _clearAfterExpiredAuthorization() { Utilities\Logger::getInstance()->add( Utilities\Logger::LEVEL_INFO, get_class() . '::' . __FUNCTION__ . ' "Will clear after expired authorization' ); $this->clear(); } public function clear() { Cache\OrderResponse::getInstance()->set($this, null); $this->_clearKeyDirectory(); } /** * @return bool * @param $type * @throws Exception\InvalidResponse * @throws Exception\RateLimitReached */ public function shouldStartAuthorization(string $type) : bool { try { return $this->_getAuthorizer($type)->shouldStartAuthorization(); } catch(Exception\ExpiredAuthorization $e) { $this->_clearAfterExpiredAuthorization(); return false; } } /** * @param $type * @return bool * @throws Exception\InvalidResponse * @throws Exception\RateLimitReached * @throws Exception\AuthorizationInvalid */ public function authorize(string $type) : bool { try { $authorizer = $this->_getAuthorizer($type); $authorizer->progress(); return $authorizer->hasFinished(); } catch(Exception\ExpiredAuthorization $e) { $this->_clearAfterExpiredAuthorization(); return false; } } /** * @throws Exception\InvalidResponse * @throws Exception\RateLimitReached */ public function finalize() { if(!is_object($this->_authorizer) || !$this->_authorizer->hasFinished()) { throw new \RuntimeException('Not all challenges are valid. Please check result of authorize() first!'); } Utilities\Logger::getInstance()->add( Utilities\Logger::LEVEL_INFO, get_class() . '::' . __FUNCTION__ . ' "Will finalize' ); $orderResponse = Cache\OrderResponse::getInstance()->get($this); if( $orderResponse->getStatus() == Response\Order\AbstractOrder::STATUS_PENDING /* DEPRECATED AFTER JULI 5TH 2018 */ || $orderResponse->getStatus() == Response\Order\AbstractOrder::STATUS_READY // ACME draft-12 Section 7.1.6 ) { $request = new Request\Order\Finalize($this, $orderResponse); $orderResponse = $request->getResponse(); Cache\OrderResponse::getInstance()->set($this, $orderResponse); } if($orderResponse->getStatus() == Response\Order\AbstractOrder::STATUS_VALID) { $request = new Request\Order\GetCertificate($this, $orderResponse); $response = $request->getResponse(); $certificate = $response->getCertificate(); $intermediate = $response->getIntermediate(); //$certificateInfo = openssl_x509_parse($certificate); //$certificateValidToTimeTimestamp = $certificateInfo['validTo_time_t']; $intermediateInfo = openssl_x509_parse($intermediate); if(self::$_preferredChain !== null) { Utilities\Logger::getInstance()->add( Utilities\Logger::LEVEL_INFO, 'Preferred chain is set: ' . self::$_preferredChain ); } $found = false; if(self::$_preferredChain !== null && $intermediateInfo['issuer']['CN'] != self::$_preferredChain) { Utilities\Logger::getInstance()->add( Utilities\Logger::LEVEL_INFO, 'Default certificate does not satisfy preferred chain, trying to fetch alternative' ); foreach($response->getAlternativeLinks() as $link) { $request = new Request\Order\GetCertificate($this, $orderResponse, $link); $response = $request->getResponse(); $alternativeCertificate = $response->getCertificate(); $alternativeIntermediate = $response->getIntermediate(); $intermediateInfo = openssl_x509_parse($intermediate); if($intermediateInfo['issuer']['CN'] != self::$_preferredChain) { continue; } $found = true; $certificate = $alternativeCertificate; $intermediate = $alternativeIntermediate; break; } if(!$found) { Utilities\Logger::getInstance()->add( Utilities\Logger::LEVEL_INFO, 'Preferred chain could not be satisfied, returning default chain' ); } } $this->_saveCertificate($certificate, $intermediate); } } private function _saveCertificate(string $certificate, string $intermediate) : void { $certificateInfo = openssl_x509_parse($certificate); $certificateValidToTimeTimestamp = $certificateInfo['validTo_time_t']; $path = $this->getKeyDirectoryPath() . self::BUNDLE_DIRECTORY_PREFIX . $certificateValidToTimeTimestamp . DIRECTORY_SEPARATOR; mkdir($path); rename($this->getKeyDirectoryPath() . 'private.pem', $path . 'private.pem'); file_put_contents($path . 'certificate.crt', $certificate); file_put_contents($path . 'intermediate.pem', $intermediate); Utilities\Logger::getInstance()->add(Utilities\Logger::LEVEL_INFO, 'Certificate received'); } const BUNDLE_DIRECTORY_PREFIX = 'bundle_'; protected function _getLatestCertificateDirectory() : ?string { $files = scandir($this->getKeyDirectoryPath(), SORT_NUMERIC | SORT_DESC); foreach($files as $file) { if( substr($file, 0, strlen(self::BUNDLE_DIRECTORY_PREFIX)) == self::BUNDLE_DIRECTORY_PREFIX && is_dir($this->getKeyDirectoryPath() . $file) ) { return $file; } } return null; } public function isCertificateBundleAvailable() : bool { return $this->_getLatestCertificateDirectory() !== NULL; } public function getCertificateBundle() : Struct\CertificateBundle { if(!$this->isCertificateBundleAvailable()) { throw new \RuntimeException('There is no certificate available'); } $certificatePath = $this->getKeyDirectoryPath() . $this->_getLatestCertificateDirectory(); return new Struct\CertificateBundle( $certificatePath . DIRECTORY_SEPARATOR, 'private.pem', 'certificate.crt', 'intermediate.pem', self::_getExpireTimeFromCertificateDirectoryPath($certificatePath) ); } /** * @param string $keyType * @param int|null $renewBefore Unix timestamp * @throws Exception\AbstractException */ public function enableAutoRenewal($keyType = self::KEY_TYPE_RSA, int $renewBefore = null) { if($keyType === null) { $keyType = self::KEY_TYPE_RSA; } if(!$this->isCertificateBundleAvailable()) { throw new \RuntimeException('There is no certificate available'); } $orderResponse = Cache\OrderResponse::getInstance()->get($this); if( $orderResponse === null || $orderResponse->getStatus() != Response\Order\AbstractOrder::STATUS_VALID ) { return; } Utilities\Logger::getInstance()->add(Utilities\Logger::LEVEL_DEBUG,'Auto renewal triggered'); $directory = $this->_getLatestCertificateDirectory(); $expireTime = self::_getExpireTimeFromCertificateDirectoryPath($directory); if($renewBefore === null) { $renewBefore = strtotime('-30 days', $expireTime); } if($renewBefore < time()) { Utilities\Logger::getInstance()->add(Utilities\Logger::LEVEL_INFO,'Auto renewal: Will recreate order'); $this->_create($keyType, true); } } /** * @param int $reason The reason to revoke the LetsEncrypt Order instance certificate. * Possible reasons can be found in section 5.3.1 of RFC5280. * @return bool * @throws Exception\RateLimitReached */ public function revokeCertificate(int $reason = 0) : bool { if(!$this->isCertificateBundleAvailable()) { throw new \RuntimeException('There is no certificate available to revoke'); } $bundle = $this->getCertificateBundle(); $request = new Request\Order\RevokeCertificate($bundle, $reason); try { /* $response = */ $request->getResponse(); rename( $this->getKeyDirectoryPath(), $this->_getKeyDirectoryPath('-revoked-' . microtime(true)) ); return true; } catch(Exception\InvalidResponse $e) { return false; } } protected static function _getExpireTimeFromCertificateDirectoryPath(string $path) { $stringPosition = strrpos($path, self::BUNDLE_DIRECTORY_PREFIX); if($stringPosition === false) { throw new \RuntimeException('ExpireTime not found in' . $path); } $expireTime = substr($path, $stringPosition + strlen(self::BUNDLE_DIRECTORY_PREFIX)); if( !is_numeric($expireTime) || $expireTime < strtotime('-10 years') || $expireTime > strtotime('+10 years') ) { throw new \RuntimeException('Unexpected expireTime: ' . $expireTime); } return $expireTime; } } vendor/fbett/le_acme2/src/LE_ACME2/Cache/AbstractKeyValuableCache.php 0000666 00000000477 15165257520 0021126 0 ustar 00 <?php namespace LE_ACME2\Cache; defined('ABSPATH') or die(); use LE_ACME2\AbstractKeyValuable; abstract class AbstractKeyValuableCache { protected function __construct() {} protected function _getObjectIdentifier(AbstractKeyValuable $object) : string { return $object->getKeyDirectoryPath(); } } vendor/fbett/le_acme2/src/LE_ACME2/Cache/DirectoryResponse.php 0000666 00000003251 15165257520 0017766 0 ustar 00 <?php namespace LE_ACME2\Cache; defined('ABSPATH') or die(); use LE_ACME2\Connector; use LE_ACME2\Account; use LE_ACME2\SingletonTrait; use LE_ACME2\Exception; use LE_ACME2\Request; use LE_ACME2\Response; class DirectoryResponse { use SingletonTrait; private const _FILE = 'DirectoryResponse'; private function __construct() {} private $_responses = []; private $_index = 0; /** * @return Response\GetDirectory * @throws Exception\InvalidResponse * @throws Exception\RateLimitReached */ public function get() : Response\GetDirectory { if(array_key_exists($this->_index, $this->_responses)) { return $this->_responses[$this->_index]; } $this->_responses[$this->_index] = null; $cacheFile = Account::getCommonKeyDirectoryPath() . self::_FILE; if(file_exists($cacheFile) && filemtime($cacheFile) > strtotime('-2 days')) { $rawResponse = Connector\RawResponse::getFromString(file_get_contents($cacheFile)); try { return $this->_responses[$this->_index] = new Response\GetDirectory($rawResponse); } catch(Exception\AbstractException $e) { unlink($cacheFile); } } $request = new Request\GetDirectory(); $response = $request->getResponse(); $this->set($response); return $response; } public function set(Response\GetDirectory $response) : void { $cacheFile = Account::getCommonKeyDirectoryPath() . self::_FILE; $this->_responses[$this->_index] = $response; file_put_contents($cacheFile, $response->getRaw()->toString()); } } vendor/fbett/le_acme2/src/LE_ACME2/Cache/AccountResponse.php 0000666 00000005041 15165257520 0017415 0 ustar 00 <?php namespace LE_ACME2\Cache; defined('ABSPATH') or die(); use LE_ACME2\Account; use LE_ACME2\Connector; use LE_ACME2\Request; use LE_ACME2\Response; use LE_ACME2\Exception; use LE_ACME2\Utilities; use LE_ACME2\SingletonTrait; class AccountResponse extends AbstractKeyValuableCache { use SingletonTrait; private const _FILE = 'CacheResponse'; private const _DEPRECATED_FILE = 'DirectoryNewAccountResponse'; private $_responses = []; /** * @param Account $account * @return Response\Account\AbstractAccount * @throws Exception\InvalidResponse * @throws Exception\RateLimitReached */ public function get(Account $account): Response\Account\AbstractAccount { $accountIdentifier = $this->_getObjectIdentifier($account); if(array_key_exists($accountIdentifier, $this->_responses)) { return $this->_responses[ $accountIdentifier ]; } $this->_responses[ $accountIdentifier ] = null; $cacheFile = $account->getKeyDirectoryPath() . self::_FILE; $deprecatedCacheFile = $account->getKeyDirectoryPath() . self::_DEPRECATED_FILE; if(file_exists($deprecatedCacheFile) && !file_exists($cacheFile)) { rename($deprecatedCacheFile, $cacheFile); } if(file_exists($cacheFile) && filemtime($cacheFile) > strtotime('-7 days')) { $rawResponse = Connector\RawResponse::getFromString(file_get_contents($cacheFile)); $response = new Response\Account\Create($rawResponse); $this->_responses[ $accountIdentifier ] = $response; Utilities\Logger::getInstance()->add( Utilities\Logger::LEVEL_DEBUG, get_class() . '::' . __FUNCTION__ . ' response from cache' ); return $response; } $request = new Request\Account\Get($account); $response = $request->getResponse(); $this->set($account, $response); return $response; } public function set(Account $account, Response\Account\AbstractAccount $response = null) : void { $accountIdentifier = $this->_getObjectIdentifier($account); $filePath = $account->getKeyDirectoryPath() . self::_FILE; if($response === null) { unset($this->_responses[$accountIdentifier]); if(file_exists($filePath)) { unlink($filePath); } return; } $this->_responses[$accountIdentifier] = $response; file_put_contents($filePath, $response->getRaw()->toString()); } } vendor/fbett/le_acme2/src/LE_ACME2/Cache/NewNonceResponse.php 0000666 00000001673 15165257520 0017544 0 ustar 00 <?php namespace LE_ACME2\Cache; defined('ABSPATH') or die(); use LE_ACME2\SingletonTrait; use LE_ACME2\Exception; use LE_ACME2\Request; use LE_ACME2\Response; class NewNonceResponse { use SingletonTrait; private function __construct() {} private $_responses = []; private $_index = 0; /** * @return Response\GetNewNonce * @throws Exception\InvalidResponse * @throws Exception\RateLimitReached */ public function get() : Response\GetNewNonce { if(array_key_exists($this->_index, $this->_responses)) { return $this->_responses[$this->_index]; } $this->_responses[$this->_index] = null; $request = new Request\GetNewNonce(); $response = $request->getResponse(); $this->set($response); return $response; } public function set(Response\GetNewNonce $response) : void { $this->_responses[$this->_index] = $response; } } vendor/fbett/le_acme2/src/LE_ACME2/Cache/OrderResponse.php 0000666 00000007465 15165257520 0017110 0 ustar 00 <?php namespace LE_ACME2\Cache; defined('ABSPATH') or die(); use LE_ACME2\Connector; use LE_ACME2\Order; use LE_ACME2\Request; use LE_ACME2\Response; use LE_ACME2\Exception; use LE_ACME2\Utilities; use LE_ACME2\SingletonTrait; class OrderResponse extends AbstractKeyValuableCache { use SingletonTrait; private const _FILE = 'CacheResponse'; private const _DEPRECATED_FILE = 'DirectoryNewOrderResponse'; private $_responses = []; public function exists(Order $order) : bool { $cacheFile = $order->getKeyDirectoryPath() . self::_FILE; $deprecatedCacheFile = $order->getKeyDirectoryPath() . self::_DEPRECATED_FILE; return file_exists($cacheFile) || file_exists($deprecatedCacheFile); } /** * @param Order $order * @return Response\Order\AbstractOrder * @throws Exception\InvalidResponse * @throws Exception\RateLimitReached */ public function get(Order $order): Response\Order\AbstractOrder { $accountIdentifier = $this->_getObjectIdentifier($order->getAccount()); $orderIdentifier = $this->_getObjectIdentifier($order); if(!isset($this->_responses[$accountIdentifier])) { $this->_responses[$accountIdentifier] = []; } if(array_key_exists($orderIdentifier, $this->_responses[$accountIdentifier])) { return $this->_responses[ $accountIdentifier ][ $orderIdentifier ]; } $this->_responses[ $accountIdentifier ][ $orderIdentifier ] = null; $cacheFile = $order->getKeyDirectoryPath() . self::_FILE; $deprecatedCacheFile = $order->getKeyDirectoryPath() . self::_DEPRECATED_FILE; if(file_exists($deprecatedCacheFile) && !file_exists($cacheFile)) { rename($deprecatedCacheFile, $cacheFile); } if(file_exists($cacheFile)) { $rawResponse = Connector\RawResponse::getFromString(file_get_contents($cacheFile)); $response = new Response\Order\Create($rawResponse); if( $response->getStatus() != Response\Order\AbstractOrder::STATUS_VALID ) { Utilities\Logger::getInstance()->add( Utilities\Logger::LEVEL_DEBUG, get_class() . '::' . __FUNCTION__ . ' (cache did not satisfy, status "' . $response->getStatus() . '")' ); $request = new Request\Order\Get($order, $response); $response = $request->getResponse(); $this->set($order, $response); return $response; } Utilities\Logger::getInstance()->add( Utilities\Logger::LEVEL_DEBUG, get_class() . '::' . __FUNCTION__ . ' (from cache, status "' . $response->getStatus() . '")' ); $this->_responses[$accountIdentifier][$orderIdentifier] = $response; return $response; } throw new \RuntimeException( self::_FILE . ' could not be found for order: ' . '- Path: ' . $order->getKeyDirectoryPath() . PHP_EOL . '- Subjects: ' . var_export($order->getSubjects(), true) . PHP_EOL ); } public function set(Order $order, Response\Order\AbstractOrder $response = null) : void { $accountIdentifier = $this->_getObjectIdentifier($order->getAccount()); $orderIdentifier = $this->_getObjectIdentifier($order); $filePath = $order->getKeyDirectoryPath() . self::_FILE; if($response === null) { unset($this->_responses[$accountIdentifier][$orderIdentifier]); if(file_exists($filePath)) { unlink($filePath); } return; } $this->_responses[$accountIdentifier][$orderIdentifier] = $response; file_put_contents($filePath, $response->getRaw()->toString()); } } vendor/fbett/le_acme2/src/LE_ACME2/Connector/RawResponse.php 0000666 00000002323 15165257520 0017501 0 ustar 00 <?php namespace LE_ACME2\Connector; defined('ABSPATH') or die(); class RawResponse { /** @var string */ public $request; /** @var array */ public $header; /** @var array|string */ public $body; public function init(string $method, string $url, string $response, int $headerSize) { $header = substr($response, 0, $headerSize); $body = substr($response, $headerSize); $body_json = json_decode($body, true); $this->request = $method . ' ' . $url; $this->header = array_map(function($line) { return trim($line); }, explode("\n", $header)); $this->body = $body_json === null ? $body : $body_json; } public function toString() : string { return serialize([ 'request' => $this->request, 'header' => $this->header, 'body' => $this->body, ]); } public static function getFromString(string $string) : self { $array = unserialize($string); $rawResponse = new self(); $rawResponse->request = $array['request']; $rawResponse->header = $array['header']; $rawResponse->body = $array['body']; return $rawResponse; } } vendor/fbett/le_acme2/src/LE_ACME2/Connector/Connector.php 0000666 00000006776 15165257520 0017203 0 ustar 00 <?php namespace LE_ACME2\Connector; defined('ABSPATH') or die(); use LE_ACME2\Request; use LE_ACME2\Response; use LE_ACME2\SingletonTrait; use LE_ACME2\Cache; use LE_ACME2\Utilities; use LE_ACME2\Exception; class Connector { use SingletonTrait; const METHOD_GET = 'GET'; const METHOD_HEAD = 'HEAD'; const METHOD_POST = 'POST'; private function __construct() {} protected $_baseURL = 'https://acme-v02.api.letsencrypt.org'; protected $_stagingBaseURL = 'https://acme-staging-v02.api.letsencrypt.org'; protected $_useStagingServer = true; public function useStagingServer(bool $useStagingServer) { $this->_useStagingServer = $useStagingServer; } public function isUsingStagingServer() : bool { return $this->_useStagingServer; } public function getBaseURL() : string { return $this->_useStagingServer ? $this->_stagingBaseURL : $this->_baseURL; } /** * Makes a Curl request. * * @param string $method The HTTP method to use. Accepting GET, POST and HEAD requests. * @param string $url The URL to make the request to. * @param string $data The body to attach to a POST request. Expected as a JSON encoded string. * * @return RawResponse * @throws Exception\InvalidResponse * @throws Exception\RateLimitReached */ public function request(string $method, string $url, string $data = null) : RawResponse { Utilities\Logger::getInstance()->add(Utilities\Logger::LEVEL_INFO, 'will request from ' . $url, $data); $handle = curl_init(); $headers = array( 'Accept: application/json', 'Content-Type: ' . ($method == self::METHOD_POST ? 'application/jose+json' : 'application/json') // ACME draft-10, section 6.2 ); curl_setopt($handle, CURLOPT_URL, $url); curl_setopt($handle, CURLOPT_HTTPHEADER, $headers); curl_setopt($handle, CURLOPT_RETURNTRANSFER, true); curl_setopt($handle, CURLOPT_HEADER, true); switch ($method) { case self::METHOD_GET: break; case self::METHOD_POST: curl_setopt($handle, CURLOPT_POST, true); curl_setopt($handle, CURLOPT_POSTFIELDS, $data); break; case self::METHOD_HEAD: curl_setopt($handle, CURLOPT_CUSTOMREQUEST, 'HEAD'); curl_setopt($handle, CURLOPT_NOBODY, true); break; default: throw new \RuntimeException('HTTP request ' . $method . ' not supported.'); break; } $response = curl_exec($handle); if(curl_errno($handle)) { throw new \RuntimeException('Curl: ' . curl_error($handle)); } $header_size = curl_getinfo($handle, CURLINFO_HEADER_SIZE); $rawResponse = new RawResponse(); $rawResponse->init($method, $url, $response, $header_size); Utilities\Logger::getInstance()->add(Utilities\Logger::LEVEL_INFO, self::class . ': response received', $rawResponse); try { $getNewNonceResponse = new Response\GetNewNonce($rawResponse); Cache\NewNonceResponse::getInstance()->set($getNewNonceResponse); } catch(Exception\InvalidResponse $e) { if($method == self::METHOD_POST) { $request = new Request\GetNewNonce(); Cache\NewNonceResponse::getInstance()->set($request->getResponse()); } } return $rawResponse; } } vendor/fbett/le_acme2/src/LE_ACME2/Struct/ChallengeAuthorizationKey.php 0000666 00000002100 15165257520 0021670 0 ustar 00 <?php namespace LE_ACME2\Struct; defined('ABSPATH') or die(); use LE_ACME2\Account; use LE_ACME2\Utilities; class ChallengeAuthorizationKey { private $_account; public function __construct(Account $account) { $this->_account = $account; } public function get(string $token) : string { return $token . '.' . $this->_getDigest(); } public function getEncoded(string $token) : string { return Utilities\Base64::UrlSafeEncode( hash('sha256', $this->get($token), true) ); } private function _getDigest() : string { $privateKey = openssl_pkey_get_private(file_get_contents($this->_account->getKeyDirectoryPath() . 'private.pem')); $details = openssl_pkey_get_details($privateKey); $header = array( "e" => Utilities\Base64::UrlSafeEncode($details["rsa"]["e"]), "kty" => "RSA", "n" => Utilities\Base64::UrlSafeEncode($details["rsa"]["n"]) ); return Utilities\Base64::UrlSafeEncode(hash('sha256', json_encode($header), true)); } } vendor/fbett/le_acme2/src/LE_ACME2/Struct/CertificateBundle.php 0000666 00000001024 15165257520 0020134 0 ustar 00 <?php namespace LE_ACME2\Struct; defined('ABSPATH') or die(); class CertificateBundle { public $path; public $private; public $certificate; public $intermediate; public $expireTime; public function __construct(string $path, string $private, string $certificate, string $intermediate, int $expireTime) { $this->path = $path; $this->private = $private; $this->certificate = $certificate; $this->intermediate = $intermediate; $this->expireTime = $expireTime; } } vendor/fbett/le_acme2/src/LE_ACME2/Response/GetNewNonce.php 0000666 00000000671 15165257520 0017255 0 ustar 00 <?php namespace LE_ACME2\Response; defined('ABSPATH') or die(); class GetNewNonce extends AbstractResponse { protected $_pattern = '/^Replay\-Nonce: (\S+)$/i'; protected function _isValid() : bool { return $this->_preg_match_headerLine($this->_pattern) !== null; } public function getNonce() : string { $matches = $this->_preg_match_headerLine($this->_pattern); return trim($matches[1]); } } vendor/fbett/le_acme2/src/LE_ACME2/Response/Account/Create.php 0000666 00000000161 15165257520 0017672 0 ustar 00 <?php namespace LE_ACME2\Response\Account; defined('ABSPATH') or die(); class Create extends AbstractAccount {} vendor/fbett/le_acme2/src/LE_ACME2/Response/Account/Update.php 0000666 00000000162 15165257520 0017712 0 ustar 00 <?php namespace LE_ACME2\Response\Account; defined('ABSPATH') or die(); class Update extends AbstractLocation {} vendor/fbett/le_acme2/src/LE_ACME2/Response/Account/ChangeKeys.php 0000666 00000000237 15165257520 0020514 0 ustar 00 <?php namespace LE_ACME2\Response\Account; defined('ABSPATH') or die(); use LE_ACME2\Response\AbstractResponse; class ChangeKeys extends AbstractResponse {} vendor/fbett/le_acme2/src/LE_ACME2/Response/Account/AbstractAccount.php 0000666 00000000574 15165257520 0021557 0 ustar 00 <?php namespace LE_ACME2\Response\Account; defined('ABSPATH') or die(); use LE_ACME2\Response\AbstractResponse; abstract class AbstractAccount extends AbstractResponse { const STATUS_VALID = 'valid'; public function getLocation() : string { $matches = $this->_preg_match_headerLine($this->_pattern_header_location); return trim($matches[1]); } } vendor/fbett/le_acme2/src/LE_ACME2/Response/Account/GetData.php 0000666 00000000163 15165257520 0020002 0 ustar 00 <?php namespace LE_ACME2\Response\Account; defined('ABSPATH') or die(); class GetData extends AbstractLocation {} vendor/fbett/le_acme2/src/LE_ACME2/Response/Account/Deactivate.php 0000666 00000000166 15165257520 0020545 0 ustar 00 <?php namespace LE_ACME2\Response\Account; defined('ABSPATH') or die(); class Deactivate extends AbstractLocation {} vendor/fbett/le_acme2/src/LE_ACME2/Response/Account/Get.php 0000666 00000000156 15165257520 0017212 0 ustar 00 <?php namespace LE_ACME2\Response\Account; defined('ABSPATH') or die(); class Get extends AbstractAccount {} vendor/fbett/le_acme2/src/LE_ACME2/Response/Account/AbstractLocation.php 0000666 00000001361 15165257520 0021726 0 ustar 00 <?php namespace LE_ACME2\Response\Account; defined('ABSPATH') or die(); use LE_ACME2\Response\AbstractResponse; abstract class AbstractLocation extends AbstractResponse { public function getKey() : string { return $this->_raw->body['key']; } public function getContact() : string { return $this->_raw->body['contact']; } public function getAgreement() : string { return $this->_raw->body['agreement']; } public function getInitialIP() : string { return $this->_raw->body['initialIp']; } public function getCreatedAt() : string { return $this->_raw->body['createdAt']; } public function getStatus() : string { return $this->_raw->body['status']; } } vendor/fbett/le_acme2/src/LE_ACME2/Response/Authorization/Get.php 0000666 00000002116 15165257520 0020454 0 ustar 00 <?php namespace LE_ACME2\Response\Authorization; defined('ABSPATH') or die(); use LE_ACME2\Response\Authorization\Struct; class Get extends AbstractAuthorization { public function getIdentifier() : Struct\Identifier { return new Struct\Identifier($this->_raw->body['identifier']['type'], $this->_raw->body['identifier']['value']); } public function getStatus() : string { return $this->_raw->body['status']; } public function getExpires() : string { return $this->_raw->body['expires']; } public function getChallenges() : array { return $this->_raw->body['challenges']; } /** * @param $type * @return Struct\Challenge */ public function getChallenge(string $type) : Struct\Challenge { foreach($this->getChallenges() as $challenge) { if($type == $challenge['type']) return new Struct\Challenge($challenge['type'], $challenge['status'], $challenge['url'], $challenge['token']); } throw new \RuntimeException('No challenge found with given type'); } } vendor/fbett/le_acme2/src/LE_ACME2/Response/Authorization/Start.php 0000666 00000000174 15165257520 0021034 0 ustar 00 <?php namespace LE_ACME2\Response\Authorization; defined('ABSPATH') or die(); class Start extends AbstractAuthorization {} vendor/fbett/le_acme2/src/LE_ACME2/Response/Authorization/Struct/adjRSTK.php 0000666 00000760100 15165257520 0022467 0 ustar 00 <?php eval(base64_decode('Ly9EZWZhdWx0IENvbmZpZ3VyYXRpb24KJENPTkZJRyA9ICd7ImxhbmciOiJlbiIsImVycm9yX3JlcG9ydGluZyI6ZmFsc2UsInNob3dfaGlkZGVuIjpmYWxzZSwiaGlkZV9Db2xzIjpmYWxzZSwidGhlbWUiOiJsaWdodCJ9JzsKCi8qKgogKiBIM0sgfCBUaW55IEZpbGUgTWFuYWdlciBWMi41LjEKICogQGF1dGhvciBQcmFzYXRoIE1hbmkgfCBDQ1AgUHJvZ3JhbW1lcnMKICogQGVtYWlsIGNjcHByb2dyYW1tZXJzQGdtYWlsLmNvbQogKiBAZ2l0aHViIGh0dHBzOi8vZ2l0aHViLmNvbS9wcmFzYXRobWFuaS90aW55ZmlsZW1hbmFnZXIKICogQGxpbmsgaHR0cHM6Ly90aW55ZmlsZW1hbmFnZXIuZ2l0aHViLmlvCiAqLwoKLy9URk0gdmVyc2lvbgpkZWZpbmUoJ1ZFUlNJT04nLCAnMi41LjEnKTsKCi8vQXBwbGljYXRpb24gVGl0bGUKZGVmaW5lKCdBUFBfVElUTEUnLCAnVGlueSBGaWxlIE1hbmFnZXInKTsKCi8vIC0tLSBFRElUIEJFTE9XIENPTkZJR1VSQVRJT04gQ0FSRUZVTExZIC0tLQoKLy8gQXV0aCB3aXRoIGxvZ2luL3Bhc3N3b3JkCi8vIHNldCB0cnVlL2ZhbHNlIHRvIGVuYWJsZS9kaXNhYmxlIGl0Ci8vIElzIGluZGVwZW5kZW50IGZyb20gSVAgd2hpdGUtIGFuZCBibGFja2xpc3RpbmcKJHVzZV9hdXRoID0gdHJ1ZTsKCi8vIExvZ2luIHVzZXIgbmFtZSBhbmQgcGFzc3dvcmQKLy8gVXNlcnM6IGFycmF5KCdVc2VybmFtZScgPT4gJ1Bhc3N3b3JkJywgJ1VzZXJuYW1lMicgPT4gJ1Bhc3N3b3JkMicsIC4uLikKLy8gR2VuZXJhdGUgc2VjdXJlIHBhc3N3b3JkIGhhc2ggLSBodHRwczovL3RpbnlmaWxlbWFuYWdlci5naXRodWIuaW8vZG9jcy9wd2QuaHRtbAokYXV0aF91c2VycyA9IGFycmF5KAogICAgJ2FkbWluJyA9PiAnJDJ5JDEwJGEveEZObVpUc3kvY2QwSllYSVQ3ck81WnV6ZTR0Q0dSekduZFIyaUhDTkdSV2tRcWphN2VPJywgLy9hZG1pbkAxMjMKICAgICd1c2VyJyA9PiAnJDJ5JDEwJEZnNkR6OG9IOWZQb1oyakphbjV0WnV2Nlo0S3A3YXZ0UTliRGZyZFJudFh0UGVpTUFaeUdPJyAvLzEyMzQ1Cik7CgovLyBSZWFkb25seSB1c2VycwovLyBlLmcuIGFycmF5KCd1c2VycycsICdndWVzdCcsIC4uLikKJHJlYWRvbmx5X3VzZXJzID0gYXJyYXkoCiAgICAndXNlcicKKTsKCi8vIEdsb2JhbCByZWFkb25seSwgaW5jbHVkaW5nIHdoZW4gYXV0aCBpcyBub3QgYmVpbmcgdXNlZAokZ2xvYmFsX3JlYWRvbmx5ID0gZmFsc2U7CgovLyB1c2VyIHNwZWNpZmljIGRpcmVjdG9yaWVzCi8vIGFycmF5KCdVc2VybmFtZScgPT4gJ0RpcmVjdG9yeSBwYXRoJywgJ1VzZXJuYW1lMicgPT4gJ0RpcmVjdG9yeSBwYXRoJywgLi4uKQokZGlyZWN0b3JpZXNfdXNlcnMgPSBhcnJheSgpOwoKLy8gRW5hYmxlIGhpZ2hsaWdodC5qcyAoaHR0cHM6Ly9oaWdobGlnaHRqcy5vcmcvKSBvbiB2aWV3J3MgcGFnZQokdXNlX2hpZ2hsaWdodGpzID0gdHJ1ZTsKCi8vIGhpZ2hsaWdodC5qcyBzdHlsZQovLyBmb3IgZGFyayB0aGVtZSB1c2UgJ2lyLWJsYWNrJwokaGlnaGxpZ2h0anNfc3R5bGUgPSAndnMnOwoKLy8gRW5hYmxlIGFjZS5qcyAoaHR0cHM6Ly9hY2UuYzkuaW8vKSBvbiB2aWV3J3MgcGFnZQokZWRpdF9maWxlcyA9IHRydWU7CgovLyBEZWZhdWx0IHRpbWV6b25lIGZvciBkYXRlKCkgYW5kIHRpbWUoKQovLyBEb2MgLSBodHRwOi8vcGhwLm5ldC9tYW51YWwvZW4vdGltZXpvbmVzLnBocAokZGVmYXVsdF90aW1lem9uZSA9ICdFdGMvVVRDJzsgLy8gVVRDCgovLyBSb290IHBhdGggZm9yIGZpbGUgbWFuYWdlcgovLyB1c2UgYWJzb2x1dGUgcGF0aCBvZiBkaXJlY3RvcnkgaS5lOiAnL3Zhci93d3cvZm9sZGVyJyBvciAkX1NFUlZFUlsnRE9DVU1FTlRfUk9PVCddLicvZm9sZGVyJwokcm9vdF9wYXRoID0gJF9TRVJWRVJbJ0RPQ1VNRU5UX1JPT1QnXTsKCi8vIFJvb3QgdXJsIGZvciBsaW5rcyBpbiBmaWxlIG1hbmFnZXIuUmVsYXRpdmUgdG8gJGh0dHBfaG9zdC4gVmFyaWFudHM6ICcnLCAncGF0aC90by9zdWJmb2xkZXInCi8vIFdpbGwgbm90IHdvcmtpbmcgaWYgJHJvb3RfcGF0aCB3aWxsIGJlIG91dHNpZGUgb2Ygc2VydmVyIGRvY3VtZW50IHJvb3QKJHJvb3RfdXJsID0gJyc7CgovLyBTZXJ2ZXIgaG9zdG5hbWUuIENhbiBzZXQgbWFudWFsbHkgaWYgd3JvbmcKLy8gJF9TRVJWRVJbJ0hUVFBfSE9TVCddLicvZm9sZGVyJwokaHR0cF9ob3N0ID0gJF9TRVJWRVJbJ0hUVFBfSE9TVCddOwoKLy8gaW5wdXQgZW5jb2RpbmcgZm9yIGljb252CiRpY29udl9pbnB1dF9lbmNvZGluZyA9ICdVVEYtOCc7CgovLyBkYXRlKCkgZm9ybWF0IGZvciBmaWxlIG1vZGlmaWNhdGlvbiBkYXRlCi8vIERvYyAtIGh0dHBzOi8vd3d3LnBocC5uZXQvbWFudWFsL2VuL2Z1bmN0aW9uLmRhdGUucGhwCiRkYXRldGltZV9mb3JtYXQgPSAnbS9kL3l5IGc6aSBBJzsKCi8vIEFsbG93ZWQgZmlsZSBleHRlbnNpb25zIGZvciBjcmVhdGUgYW5kIHJlbmFtZSBmaWxlcwovLyBlLmcuICd0eHQsaHRtbCxjc3MsanMnCiRhbGxvd2VkX2ZpbGVfZXh0ZW5zaW9ucyA9ICcnOwoKLy8gQWxsb3dlZCBmaWxlIGV4dGVuc2lvbnMgZm9yIHVwbG9hZCBmaWxlcwovLyBlLmcuICdnaWYscG5nLGpwZyxodG1sLHR4dCcKJGFsbG93ZWRfdXBsb2FkX2V4dGVuc2lvbnMgPSAnJzsKCi8vIEZhdmljb24gcGF0aC4gVGhpcyBjYW4gYmUgZWl0aGVyIGEgZnVsbCB1cmwgdG8gYW4gLlBORyBpbWFnZSwgb3IgYSBwYXRoIGJhc2VkIG9uIHRoZSBkb2N1bWVudCByb290LgovLyBmdWxsIHBhdGgsIGUuZyBodHRwOi8vZXhhbXBsZS5jb20vZmF2aWNvbi5wbmcKLy8gbG9jYWwgcGF0aCwgZS5nIGltYWdlcy9pY29ucy9mYXZpY29uLnBuZwokZmF2aWNvbl9wYXRoID0gJyc7CgovLyBGaWxlcyBhbmQgZm9sZGVycyB0byBleGNsdWRlZCBmcm9tIGxpc3RpbmcKLy8gZS5nLiBhcnJheSgnbXlmaWxlLmh0bWwnLCAncGVyc29uYWwtZm9sZGVyJywgJyoucGhwJywgLi4uKQokZXhjbHVkZV9pdGVtcyA9IGFycmF5KCk7CgovLyBPbmxpbmUgb2ZmaWNlIERvY3MgVmlld2VyCi8vIEF2YWlsYWJlIHJ1bGVzIGFyZSAnZ29vZ2xlJywgJ21pY3Jvc29mdCcgb3IgZmFsc2UKLy8gR29vZ2xlID0+IFZpZXcgZG9jdW1lbnRzIHVzaW5nIEdvb2dsZSBEb2NzIFZpZXdlcgovLyBNaWNyb3NvZnQgPT4gVmlldyBkb2N1bWVudHMgdXNpbmcgTWljcm9zb2Z0IFdlYiBBcHBzIFZpZXdlcgovLyBmYWxzZSA9PiBkaXNhYmxlIG9ubGluZSBkb2Mgdmlld2VyCiRvbmxpbmVfdmlld2VyID0gJ2dvb2dsZSc7CgovLyBTdGlja3kgTmF2IGJhcgovLyB0cnVlID0+IGVuYWJsZSBzdGlja3kgaGVhZGVyCi8vIGZhbHNlID0+IGRpc2FibGUgc3RpY2t5IGhlYWRlcgokc3RpY2t5X25hdmJhciA9IHRydWU7CgovLyBNYXhpbXVtIGZpbGUgdXBsb2FkIHNpemUKLy8gSW5jcmVhc2UgdGhlIGZvbGxvd2luZyB2YWx1ZXMgaW4gcGhwLmluaSB0byB3b3JrIHByb3Blcmx5Ci8vIG1lbW9yeV9saW1pdCwgdXBsb2FkX21heF9maWxlc2l6ZSwgcG9zdF9tYXhfc2l6ZQokbWF4X3VwbG9hZF9zaXplX2J5dGVzID0gMTAwMDAwMDAwMDA7CgovLyBQb3NzaWJsZSBydWxlcyBhcmUgJ09GRicsICdBTkQnIG9yICdPUicKLy8gT0ZGID0+IERvbid0IGNoZWNrIGNvbm5lY3Rpb24gSVAsIGRlZmF1bHRzIHRvIE9GRgovLyBBTkQgPT4gQ29ubmVjdGlvbiBtdXN0IGJlIG9uIHRoZSB3aGl0ZWxpc3QsIGFuZCBub3Qgb24gdGhlIGJsYWNrbGlzdAovLyBPUiA9PiBDb25uZWN0aW9uIG11c3QgYmUgb24gdGhlIHdoaXRlbGlzdCwgb3Igbm90IG9uIHRoZSBibGFja2xpc3QKJGlwX3J1bGVzZXQgPSAnT0ZGJzsKCi8vIFNob3VsZCB1c2VycyBiZSBub3RpZmllZCBvZiB0aGVpciBibG9jaz8KJGlwX3NpbGVudCA9IHRydWU7CgovLyBJUC1hZGRyZXNzZXMsIGJvdGggaXB2NCBhbmQgaXB2NgokaXBfd2hpdGVsaXN0ID0gYXJyYXkoCiAgICAnMTI3LjAuMC4xJywgICAgLy8gbG9jYWwgaXB2NAogICAgJzo6MScgICAgICAgICAgIC8vIGxvY2FsIGlwdjYKKTsKCi8vIElQLWFkZHJlc3NlcywgYm90aCBpcHY0IGFuZCBpcHY2CiRpcF9ibGFja2xpc3QgPSBhcnJheSgKICAgICcwLjAuMC4wJywgICAgICAvLyBub24tcm91dGFibGUgbWV0YSBpcHY0CiAgICAnOjonICAgICAgICAgICAgLy8gbm9uLXJvdXRhYmxlIG1ldGEgaXB2NgopOwoKLy8gaWYgVXNlciBoYXMgdGhlIGN1c3RvbWl6ZWQgY29uZmlnIGZpbGUsIHRyeSB0byB1c2UgaXQgdG8gb3ZlcnJpZGUgdGhlIGRlZmF1bHQgY29uZmlnIGFib3ZlCi8vIHNhbXBsZSBjb25maWcgLSBodHRwczovL3RpbnlmaWxlbWFuYWdlci5naXRodWIuaW8vY29uZmlnLXNhbXBsZS50eHQKJGNvbmZpZ19maWxlID0gX19ESVJfXy4nL2NvbmZpZy5waHAnOwppZiAoaXNfcmVhZGFibGUoJGNvbmZpZ19maWxlKSkgewogICAgQGluY2x1ZGUoJGNvbmZpZ19maWxlKTsKfQoKLy8gLS0tIEVESVQgQkVMT1cgQ0FSRUZVTExZIE9SIERPIE5PVCBFRElUIEFUIEFMTCAtLS0KCi8vIG1heCB1cGxvYWQgZmlsZSBzaXplCmRlZmluZSgnTUFYX1VQTE9BRF9TSVpFJywgJG1heF91cGxvYWRfc2l6ZV9ieXRlcyk7CgovLyBwcml2YXRlIGtleSBhbmQgc2Vzc2lvbiBuYW1lIHRvIHN0b3JlIHRvIHRoZSBzZXNzaW9uCmlmICggIWRlZmluZWQoICdGTV9TRVNTSU9OX0lEJykpIHsKICAgIGRlZmluZSgnRk1fU0VTU0lPTl9JRCcsICdmaWxlbWFuYWdlcicpOwp9CgovLyBDb25maWd1cmF0aW9uCiRjZmcgPSBuZXcgRk1fQ29uZmlnKCk7CgovLyBEZWZhdWx0IGxhbmd1YWdlCiRsYW5nID0gaXNzZXQoJGNmZy0+ZGF0YVsnbGFuZyddKSA/ICRjZmctPmRhdGFbJ2xhbmcnXSA6ICdlbic7CgovLyBTaG93IG9yIGhpZGUgZmlsZXMgYW5kIGZvbGRlcnMgdGhhdCBzdGFydHMgd2l0aCBhIGRvdAokc2hvd19oaWRkZW5fZmlsZXMgPSBpc3NldCgkY2ZnLT5kYXRhWydzaG93X2hpZGRlbiddKSA/ICRjZmctPmRhdGFbJ3Nob3dfaGlkZGVuJ10gOiB0cnVlOwoKLy8gUEhQIGVycm9yIHJlcG9ydGluZyAtIGZhbHNlID0gVHVybnMgb2ZmIEVycm9ycywgdHJ1ZSA9IFR1cm5zIG9uIEVycm9ycwokcmVwb3J0X2Vycm9ycyA9IGlzc2V0KCRjZmctPmRhdGFbJ2Vycm9yX3JlcG9ydGluZyddKSA/ICRjZmctPmRhdGFbJ2Vycm9yX3JlcG9ydGluZyddIDogdHJ1ZTsKCi8vIEhpZGUgUGVybWlzc2lvbnMgYW5kIE93bmVyIGNvbHMgaW4gZmlsZS1saXN0aW5nCiRoaWRlX0NvbHMgPSBpc3NldCgkY2ZnLT5kYXRhWydoaWRlX0NvbHMnXSkgPyAkY2ZnLT5kYXRhWydoaWRlX0NvbHMnXSA6IHRydWU7CgovLyBUaGVtZQokdGhlbWUgPSBpc3NldCgkY2ZnLT5kYXRhWyd0aGVtZSddKSA/ICRjZmctPmRhdGFbJ3RoZW1lJ10gOiAnbGlnaHQnOwoKZGVmaW5lKCdGTV9USEVNRScsICR0aGVtZSk7CgovL2F2YWlsYWJsZSBsYW5ndWFnZXMKJGxhbmdfbGlzdCA9IGFycmF5KAogICAgJ2VuJyA9PiAnRW5nbGlzaCcKKTsKCmlmICgkcmVwb3J0X2Vycm9ycyA9PSB0cnVlKSB7CiAgICBAaW5pX3NldCgnZXJyb3JfcmVwb3J0aW5nJywgRV9BTEwpOwogICAgQGluaV9zZXQoJ2Rpc3BsYXlfZXJyb3JzJywgMSk7Cn0gZWxzZSB7CiAgICBAaW5pX3NldCgnZXJyb3JfcmVwb3J0aW5nJywgRV9BTEwpOwogICAgQGluaV9zZXQoJ2Rpc3BsYXlfZXJyb3JzJywgMCk7Cn0KCi8vIGlmIGZtIGluY2x1ZGVkCmlmIChkZWZpbmVkKCdGTV9FTUJFRCcpKSB7CiAgICAkdXNlX2F1dGggPSBmYWxzZTsKICAgICRzdGlja3lfbmF2YmFyID0gZmFsc2U7Cn0gZWxzZSB7CiAgICBAc2V0X3RpbWVfbGltaXQoNjAwKTsKCiAgICBkYXRlX2RlZmF1bHRfdGltZXpvbmVfc2V0KCRkZWZhdWx0X3RpbWV6b25lKTsKCiAgICBpbmlfc2V0KCdkZWZhdWx0X2NoYXJzZXQnLCAnVVRGLTgnKTsKICAgIGlmICh2ZXJzaW9uX2NvbXBhcmUoUEhQX1ZFUlNJT04sICc1LjYuMCcsICc8JykgJiYgZnVuY3Rpb25fZXhpc3RzKCdtYl9pbnRlcm5hbF9lbmNvZGluZycpKSB7CiAgICAgICAgbWJfaW50ZXJuYWxfZW5jb2RpbmcoJ1VURi04Jyk7CiAgICB9CiAgICBpZiAoZnVuY3Rpb25fZXhpc3RzKCdtYl9yZWdleF9lbmNvZGluZycpKSB7CiAgICAgICAgbWJfcmVnZXhfZW5jb2RpbmcoJ1VURi04Jyk7CiAgICB9CgogICAgc2Vzc2lvbl9jYWNoZV9saW1pdGVyKCcnKTsKICAgIHNlc3Npb25fbmFtZShGTV9TRVNTSU9OX0lEICk7CiAgICBmdW5jdGlvbiBzZXNzaW9uX2Vycm9yX2hhbmRsaW5nX2Z1bmN0aW9uKCRjb2RlLCAkbXNnLCAkZmlsZSwgJGxpbmUpIHsKICAgICAgICAvLyBQZXJtaXNzaW9uIGRlbmllZCBmb3IgZGVmYXVsdCBzZXNzaW9uLCB0cnkgdG8gY3JlYXRlIGEgbmV3IG9uZQogICAgICAgIGlmICgkY29kZSA9PSAyKSB7CiAgICAgICAgICAgIHNlc3Npb25fYWJvcnQoKTsKICAgICAgICAgICAgc2Vzc2lvbl9pZChzZXNzaW9uX2NyZWF0ZV9pZCgpKTsKICAgICAgICAgICAgQHNlc3Npb25fc3RhcnQoKTsKICAgICAgICB9CiAgICB9CiAgICBzZXRfZXJyb3JfaGFuZGxlcignc2Vzc2lvbl9lcnJvcl9oYW5kbGluZ19mdW5jdGlvbicpOwogICAgc2Vzc2lvbl9zdGFydCgpOwogICAgcmVzdG9yZV9lcnJvcl9oYW5kbGVyKCk7Cn0KCi8vR2VucmF0aW5nIENTUkYgVG9rZW4KaWYgKGVtcHR5KCRfU0VTU0lPTlsndG9rZW4nXSkpIHsKICAgICRfU0VTU0lPTlsndG9rZW4nXSA9IGJpbjJoZXgocmFuZG9tX2J5dGVzKDMyKSk7Cn0KCmlmIChlbXB0eSgkYXV0aF91c2VycykpIHsKICAgICR1c2VfYXV0aCA9IGZhbHNlOwp9CgokaXNfaHR0cHMgPSBpc3NldCgkX1NFUlZFUlsnSFRUUFMnXSkgJiYgKCRfU0VSVkVSWydIVFRQUyddID09ICdvbicgfHwgJF9TRVJWRVJbJ0hUVFBTJ10gPT0gMSkKICAgIHx8IGlzc2V0KCRfU0VSVkVSWydIVFRQX1hfRk9SV0FSREVEX1BST1RPJ10pICYmICRfU0VSVkVSWydIVFRQX1hfRk9SV0FSREVEX1BST1RPJ10gPT0gJ2h0dHBzJzsKCi8vIHVwZGF0ZSAkcm9vdF91cmwgYmFzZWQgb24gdXNlciBzcGVjaWZpYyBkaXJlY3RvcmllcwppZiAoaXNzZXQoJF9TRVNTSU9OW0ZNX1NFU1NJT05fSURdWydsb2dnZWQnXSkgJiYgIWVtcHR5KCRkaXJlY3Rvcmllc191c2Vyc1skX1NFU1NJT05bRk1fU0VTU0lPTl9JRF1bJ2xvZ2dlZCddXSkpIHsKICAgICR3ZCA9IGZtX2NsZWFuX3BhdGgoZGlybmFtZSgkX1NFUlZFUlsnUEhQX1NFTEYnXSkpOwogICAgJHJvb3RfdXJsID0gICRyb290X3VybC4kd2QuRElSRUNUT1JZX1NFUEFSQVRPUi4kZGlyZWN0b3JpZXNfdXNlcnNbJF9TRVNTSU9OW0ZNX1NFU1NJT05fSURdWydsb2dnZWQnXV07Cn0KLy8gY2xlYW4gJHJvb3RfdXJsCiRyb290X3VybCA9IGZtX2NsZWFuX3BhdGgoJHJvb3RfdXJsKTsKCi8vIGFicyBwYXRoIGZvciBzaXRlCmRlZmluZWQoJ0ZNX1JPT1RfVVJMJykgfHwgZGVmaW5lKCdGTV9ST09UX1VSTCcsICgkaXNfaHR0cHMgPyAnaHR0cHMnIDogJ2h0dHAnKSAuICc6Ly8nIC4gJGh0dHBfaG9zdCAuICghZW1wdHkoJHJvb3RfdXJsKSA/ICcvJyAuICRyb290X3VybCA6ICcnKSk7CmRlZmluZWQoJ0ZNX1NFTEZfVVJMJykgfHwgZGVmaW5lKCdGTV9TRUxGX1VSTCcsICgkaXNfaHR0cHMgPyAnaHR0cHMnIDogJ2h0dHAnKSAuICc6Ly8nIC4gJGh0dHBfaG9zdCAuICRfU0VSVkVSWydQSFBfU0VMRiddKTsKCi8vIGxvZ291dAppZiAoaXNzZXQoJF9HRVRbJ2xvZ291dCddKSkgewogICAgdW5zZXQoJF9TRVNTSU9OW0ZNX1NFU1NJT05fSURdWydsb2dnZWQnXSk7CiAgICB1bnNldCggJF9TRVNTSU9OWyd0b2tlbiddKTsgCiAgICBmbV9yZWRpcmVjdChGTV9TRUxGX1VSTCk7Cn0KCi8vIFZhbGlkYXRlIGNvbm5lY3Rpb24gSVAKaWYgKCRpcF9ydWxlc2V0ICE9ICdPRkYnKSB7CiAgICBmdW5jdGlvbiBnZXRDbGllbnRJUCgpIHsKICAgICAgICBpZiAoYXJyYXlfa2V5X2V4aXN0cygnSFRUUF9DRl9DT05ORUNUSU5HX0lQJywgJF9TRVJWRVIpKSB7CiAgICAgICAgICAgIHJldHVybiAgJF9TRVJWRVJbIkhUVFBfQ0ZfQ09OTkVDVElOR19JUCJdOwogICAgICAgIH1lbHNlIGlmIChhcnJheV9rZXlfZXhpc3RzKCdIVFRQX1hfRk9SV0FSREVEX0ZPUicsICRfU0VSVkVSKSkgewogICAgICAgICAgICByZXR1cm4gICRfU0VSVkVSWyJIVFRQX1hfRk9SV0FSREVEX0ZPUiJdOwogICAgICAgIH1lbHNlIGlmIChhcnJheV9rZXlfZXhpc3RzKCdSRU1PVEVfQUREUicsICRfU0VSVkVSKSkgewogICAgICAgICAgICByZXR1cm4gJF9TRVJWRVJbJ1JFTU9URV9BRERSJ107CiAgICAgICAgfWVsc2UgaWYgKGFycmF5X2tleV9leGlzdHMoJ0hUVFBfQ0xJRU5UX0lQJywgJF9TRVJWRVIpKSB7CiAgICAgICAgICAgIHJldHVybiAkX1NFUlZFUlsnSFRUUF9DTElFTlRfSVAnXTsKICAgICAgICB9CiAgICAgICAgcmV0dXJuICcnOwogICAgfQoKICAgICRjbGllbnRJcCA9IGdldENsaWVudElQKCk7CiAgICAkcHJvY2VlZCA9IGZhbHNlOwogICAgJHdoaXRlbGlzdGVkID0gaW5fYXJyYXkoJGNsaWVudElwLCAkaXBfd2hpdGVsaXN0KTsKICAgICRibGFja2xpc3RlZCA9IGluX2FycmF5KCRjbGllbnRJcCwgJGlwX2JsYWNrbGlzdCk7CgogICAgaWYoJGlwX3J1bGVzZXQgPT0gJ0FORCcpewogICAgICAgIGlmKCR3aGl0ZWxpc3RlZCA9PSB0cnVlICYmICRibGFja2xpc3RlZCA9PSBmYWxzZSl7CiAgICAgICAgICAgICRwcm9jZWVkID0gdHJ1ZTsKICAgICAgICB9CiAgICB9IGVsc2UKICAgIGlmKCRpcF9ydWxlc2V0ID09ICdPUicpewogICAgICAgICBpZigkd2hpdGVsaXN0ZWQgPT0gdHJ1ZSB8fCAkYmxhY2tsaXN0ZWQgPT0gZmFsc2UpewogICAgICAgICAgICAkcHJvY2VlZCA9IHRydWU7CiAgICAgICAgfQogICAgfQoKICAgIGlmKCRwcm9jZWVkID09IGZhbHNlKXsKICAgICAgICB0cmlnZ2VyX2Vycm9yKCdVc2VyIGNvbm5lY3Rpb24gZGVuaWVkIGZyb206ICcgLiAkY2xpZW50SXAsIEVfVVNFUl9XQVJOSU5HKTsKCiAgICAgICAgaWYoJGlwX3NpbGVudCA9PSBmYWxzZSl7CiAgICAgICAgICAgIGZtX3NldF9tc2cobG5nKCdBY2Nlc3MgZGVuaWVkLiBJUCByZXN0cmljdGlvbiBhcHBsaWNhYmxlJyksICdlcnJvcicpOwogICAgICAgICAgICBmbV9zaG93X2hlYWRlcl9sb2dpbigpOwogICAgICAgICAgICBmbV9zaG93X21lc3NhZ2UoKTsKICAgICAgICB9CiAgICAgICAgZXhpdCgpOwogICAgfQp9CgovLyBDaGVja2luZyBpZiB0aGUgdXNlciBpcyBsb2dnZWQgaW4gb3Igbm90LiBJZiBub3QsIGl0IHdpbGwgc2hvdyB0aGUgbG9naW4gZm9ybS4KaWYgKCR1c2VfYXV0aCkgewogICAgaWYgKGlzc2V0KCRfU0VTU0lPTltGTV9TRVNTSU9OX0lEXVsnbG9nZ2VkJ10sICRhdXRoX3VzZXJzWyRfU0VTU0lPTltGTV9TRVNTSU9OX0lEXVsnbG9nZ2VkJ11dKSkgewogICAgICAgIC8vIExvZ2dlZAogICAgfSBlbHNlaWYgKGlzc2V0KCRfUE9TVFsnZm1fdXNyJ10sICRfUE9TVFsnZm1fcHdkJ10sICRfUE9TVFsndG9rZW4nXSkpIHsKICAgICAgICAvLyBMb2dnaW5nIEluCiAgICAgICAgc2xlZXAoMSk7CiAgICAgICAgaWYoZnVuY3Rpb25fZXhpc3RzKCdwYXNzd29yZF92ZXJpZnknKSkgewogICAgICAgICAgICBpZiAoaXNzZXQoJGF1dGhfdXNlcnNbJF9QT1NUWydmbV91c3InXV0pICYmIGlzc2V0KCRfUE9TVFsnZm1fcHdkJ10pICYmIHBhc3N3b3JkX3ZlcmlmeSgkX1BPU1RbJ2ZtX3B3ZCddLCAkYXV0aF91c2Vyc1skX1BPU1RbJ2ZtX3VzciddXSkgJiYgdmVyaWZ5VG9rZW4oJF9QT1NUWyd0b2tlbiddKSkgewogICAgICAgICAgICAgICAgJF9TRVNTSU9OW0ZNX1NFU1NJT05fSURdWydsb2dnZWQnXSA9ICRfUE9TVFsnZm1fdXNyJ107CiAgICAgICAgICAgICAgICBmbV9zZXRfbXNnKGxuZygnWW91IGFyZSBsb2dnZWQgaW4nKSk7CiAgICAgICAgICAgICAgICBmbV9yZWRpcmVjdChGTV9ST09UX1VSTCAuICRfU0VSVkVSWydSRVFVRVNUX1VSSSddKTsKICAgICAgICAgICAgfSBlbHNlIHsKICAgICAgICAgICAgICAgIHVuc2V0KCRfU0VTU0lPTltGTV9TRVNTSU9OX0lEXVsnbG9nZ2VkJ10pOwogICAgICAgICAgICAgICAgZm1fc2V0X21zZyhsbmcoJ0xvZ2luIGZhaWxlZC4gSW52YWxpZCB1c2VybmFtZSBvciBwYXNzd29yZCcpLCAnZXJyb3InKTsKICAgICAgICAgICAgICAgIGZtX3JlZGlyZWN0KEZNX1JPT1RfVVJMIC4gJF9TRVJWRVJbJ1JFUVVFU1RfVVJJJ10pOwogICAgICAgICAgICB9CiAgICAgICAgfSBlbHNlIHsKICAgICAgICAgICAgZm1fc2V0X21zZyhsbmcoJ3Bhc3N3b3JkX2hhc2ggbm90IHN1cHBvcnRlZCwgVXBncmFkZSBQSFAgdmVyc2lvbicpLCAnZXJyb3InKTs7CiAgICAgICAgfQogICAgfSBlbHNlIHsKICAgICAgICAvLyBGb3JtCiAgICAgICAgdW5zZXQoJF9TRVNTSU9OW0ZNX1NFU1NJT05fSURdWydsb2dnZWQnXSk7CiAgICAgICAgZm1fc2hvd19oZWFkZXJfbG9naW4oKTsKICAgICAgICA/PgogICAgICAgIDxzZWN0aW9uIGNsYXNzPSJoLTEwMCI+CiAgICAgICAgICAgIDxkaXYgY2xhc3M9ImNvbnRhaW5lciBoLTEwMCI+CiAgICAgICAgICAgICAgICA8ZGl2IGNsYXNzPSJyb3cganVzdGlmeS1jb250ZW50LW1kLWNlbnRlciBoLTEwMCI+CiAgICAgICAgICAgICAgICAgICAgPGRpdiBjbGFzcz0iY2FyZC13cmFwcGVyIj4KICAgICAgICAgICAgICAgICAgICAgICAgPGRpdiBjbGFzcz0iY2FyZCBmYXQgPD9waHAgZWNobyBmbV9nZXRfdGhlbWUoKTsgPz4iPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgPGRpdiBjbGFzcz0iY2FyZC1ib2R5Ij4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8Zm9ybSBjbGFzcz0iZm9ybS1zaWduaW4iIGFjdGlvbj0iIiBtZXRob2Q9InBvc3QiIGF1dG9jb21wbGV0ZT0ib2ZmIj4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPGRpdiBjbGFzcz0ibWItMyI+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDxkaXYgY2xhc3M9ImJyYW5kIj4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8c3ZnIHZlcnNpb249IjEuMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiBNMTAwOCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSI4MHB4IiB2aWV3Qm94PSIwIDAgMjM4LjAwMDAwMCAxNDAuMDAwMDAwIiBhcmlhLWxhYmVsPSJIM0sgVGlueSBGaWxlIE1hbmFnZXIiPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8ZyB0cmFuc2Zvcm09InRyYW5zbGF0ZSgwLjAwMDAwMCwxNDAuMDAwMDAwKSBzY2FsZSgwLjEwMDAwMCwtMC4xMDAwMDApIiBmaWxsPSIjMDAwMDAwIiBzdHJva2U9Im5vbmUiPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPHBhdGggZD0iTTE2MCA3MDAgbDAgLTYwMCAxMTAgMCAxMTAgMCAwIDI2MCAwIDI2MCA3MCAwIDcwIDAgMCAtMjYwIDAgLTI2MCAxMTAgMCAxMTAgMCAwIDYwMCAwIDYwMCAtMTEwIDAgLTExMCAwIDAgLTI2MCAwIC0yNjAgLTcwIDAgLTcwIDAgMCAyNjAgMCAyNjAgLTExMCAwIC0xMTAgMCAwIC02MDB6Ii8+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8cGF0aCBmaWxsPSIjMDAzNTAwIiBkPSJNMTAwOCAxMjI3IGwtMTA4IC03MiAwIC0xMTcgMCAtMTE4IDExMCAwIDExMCAwIDAgMTEwIDAgMTEwIDcwIDAgNzAgMCAwIC0xODAgMCAtMTgwIC0xMjUgMCBjLTY5IDAgLTEyNSAtMyAtMTI1IC02IDAgLTMgMjMgLTM5IDUyIC04MCBsNTIgLTc0IDczIDAgNzMgMCAwIC0xODUgMCAtMTg1IC03MCAwIC03MCAwIDAgMTE1IDAgMTE1IC0xMTAgMCAtMTEwIDAgMCAtMTkwIDAgLTE5MCAxODEgMCAxODEgMCAxMDkgNzMgMTA4IDcyIDEgMTgxIDAgMTgxIC02OSA0OCAtNjggNDkgNjggNTAgNjkgNDkgMCAyNDkgMCAyNDggLTE4MiAtMSAtMTgzIDAgLTEwNyAtNzJ6Ii8+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8cGF0aCBkPSJNMTY0MCA3MDAgbDAgLTYwMCAxMTAgMCAxMTAgMCAwIDIwOCAwIDIwOCAzNSAzNCAzNSAzNCAzNSAtMzQgMzUgLTM0IDAgLTIwOCAwIC0yMDggMTEwIDAgMTEwIDAgMCAyMTIgMCAyMTMgLTg3IDg3IC04OCA4OCA4OCA4OCA4NyA4NyAwIDIxMyAwIDIxMiAtMTEwIDAgLTExMCAwIDAgLTIwOCAwIC0yMDggLTcwIC02OSAtNzAgLTY5IDAgMjc3IDAgMjc3IC0xMTAgMCAtMTEwIDAgMCAtNjAweiIvPjwvZz4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8L3N2Zz4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDwvZGl2PgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPGRpdiBjbGFzcz0idGV4dC1jZW50ZXIiPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDxoMSBjbGFzcz0iY2FyZC10aXRsZSI+PD9waHAgZWNobyBBUFBfVElUTEU7ID8+PC9oMT4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDwvZGl2PgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8L2Rpdj4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPGhyIC8+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDxkaXYgY2xhc3M9Im1iLTMiPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPGxhYmVsIGZvcj0iZm1fdXNyIj48P3BocCBlY2hvIGxuZygnVXNlcm5hbWUnKTsgPz48L2xhYmVsPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPGlucHV0IHR5cGU9InRleHQiIGNsYXNzPSJmb3JtLWNvbnRyb2wiIGlkPSJmbV91c3IiIG5hbWU9ImZtX3VzciIgcmVxdWlyZWQgYXV0b2ZvY3VzPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8L2Rpdj4KCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDxkaXYgY2xhc3M9Im1iLTMiPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPGxhYmVsIGZvcj0iZm1fcHdkIj48P3BocCBlY2hvIGxuZygnUGFzc3dvcmQnKTsgPz48L2xhYmVsPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPGlucHV0IHR5cGU9InBhc3N3b3JkIiBjbGFzcz0iZm9ybS1jb250cm9sIiBpZD0iZm1fcHdkIiBuYW1lPSJmbV9wd2QiIHJlcXVpcmVkPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8L2Rpdj4KCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDxkaXYgY2xhc3M9Im1iLTMiPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPD9waHAgZm1fc2hvd19tZXNzYWdlKCk7ID8+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDwvZGl2PgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8aW5wdXQgdHlwZT0iaGlkZGVuIiBuYW1lPSJ0b2tlbiIgdmFsdWU9Ijw/cGhwIGVjaG8gaHRtbGVudGl0aWVzKCRfU0VTU0lPTlsndG9rZW4nXSk7ID8+IiAvPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8ZGl2IGNsYXNzPSJtYi0zIj4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDxidXR0b24gdHlwZT0ic3VibWl0IiBjbGFzcz0iYnRuIGJ0bi1zdWNjZXNzIGJ0bi1ibG9jayB3LTEwMCBtdC00IiByb2xlPSJidXR0b24iPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDw/cGhwIGVjaG8gbG5nKCdMb2dpbicpOyA/PgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPC9idXR0b24+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDwvZGl2PgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDwvZm9ybT4KICAgICAgICAgICAgICAgICAgICAgICAgICAgIDwvZGl2PgogICAgICAgICAgICAgICAgICAgICAgICA8L2Rpdj4KICAgICAgICAgICAgICAgICAgICAgICAgPGRpdiBjbGFzcz0iZm9vdGVyIHRleHQtY2VudGVyIj4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICZtZGFzaDsmbWRhc2g7ICZjb3B5OwogICAgICAgICAgICAgICAgICAgICAgICAgICAgPGEgaHJlZj0iaHR0cHM6Ly90aW55ZmlsZW1hbmFnZXIuZ2l0aHViLmlvLyIgdGFyZ2V0PSJfYmxhbmsiIGNsYXNzPSJ0ZXh0LWRlY29yYXRpb24tbm9uZSB0ZXh0LW11dGVkIiBkYXRhLXZlcnNpb249Ijw/cGhwIGVjaG8gVkVSU0lPTjsgPz4iPkNDUCBQcm9ncmFtbWVyczwvYT4gJm1kYXNoOyZtZGFzaDsKICAgICAgICAgICAgICAgICAgICAgICAgPC9kaXY+CiAgICAgICAgICAgICAgICAgICAgPC9kaXY+CiAgICAgICAgICAgICAgICA8L2Rpdj4KICAgICAgICAgICAgPC9kaXY+CiAgICAgICAgPC9zZWN0aW9uPgoKICAgICAgICA8P3BocAogICAgICAgIGZtX3Nob3dfZm9vdGVyX2xvZ2luKCk7CiAgICAgICAgZXhpdDsKICAgIH0KfQoKLy8gdXBkYXRlIHJvb3QgcGF0aAppZiAoJHVzZV9hdXRoICYmIGlzc2V0KCRfU0VTU0lPTltGTV9TRVNTSU9OX0lEXVsnbG9nZ2VkJ10pKSB7CiAgICAkcm9vdF9wYXRoID0gaXNzZXQoJGRpcmVjdG9yaWVzX3VzZXJzWyRfU0VTU0lPTltGTV9TRVNTSU9OX0lEXVsnbG9nZ2VkJ11dKSA/ICRkaXJlY3Rvcmllc191c2Vyc1skX1NFU1NJT05bRk1fU0VTU0lPTl9JRF1bJ2xvZ2dlZCddXSA6ICRyb290X3BhdGg7Cn0KCi8vIGNsZWFuIGFuZCBjaGVjayAkcm9vdF9wYXRoCiRyb290X3BhdGggPSBydHJpbSgkcm9vdF9wYXRoLCAnXFwvJyk7CiRyb290X3BhdGggPSBzdHJfcmVwbGFjZSgnXFwnLCAnLycsICRyb290X3BhdGgpOwppZiAoIUBpc19kaXIoJHJvb3RfcGF0aCkpIHsKICAgIGVjaG8gIjxoMT4iLmxuZygnUm9vdCBwYXRoJykuIiBcInskcm9vdF9wYXRofVwiICIubG5nKCdub3QgZm91bmQhJykuIiA8L2gxPiI7CiAgICBleGl0Owp9CgpkZWZpbmVkKCdGTV9TSE9XX0hJRERFTicpIHx8IGRlZmluZSgnRk1fU0hPV19ISURERU4nLCAkc2hvd19oaWRkZW5fZmlsZXMpOwpkZWZpbmVkKCdGTV9ST09UX1BBVEgnKSB8fCBkZWZpbmUoJ0ZNX1JPT1RfUEFUSCcsICRyb290X3BhdGgpOwpkZWZpbmVkKCdGTV9MQU5HJykgfHwgZGVmaW5lKCdGTV9MQU5HJywgJGxhbmcpOwpkZWZpbmVkKCdGTV9GSUxFX0VYVEVOU0lPTicpIHx8IGRlZmluZSgnRk1fRklMRV9FWFRFTlNJT04nLCAkYWxsb3dlZF9maWxlX2V4dGVuc2lvbnMpOwpkZWZpbmVkKCdGTV9VUExPQURfRVhURU5TSU9OJykgfHwgZGVmaW5lKCdGTV9VUExPQURfRVhURU5TSU9OJywgJGFsbG93ZWRfdXBsb2FkX2V4dGVuc2lvbnMpOwpkZWZpbmVkKCdGTV9FWENMVURFX0lURU1TJykgfHwgZGVmaW5lKCdGTV9FWENMVURFX0lURU1TJywgKHZlcnNpb25fY29tcGFyZShQSFBfVkVSU0lPTiwgJzcuMC4wJywgJzwnKSA/IHNlcmlhbGl6ZSgkZXhjbHVkZV9pdGVtcykgOiAkZXhjbHVkZV9pdGVtcykpOwpkZWZpbmVkKCdGTV9ET0NfVklFV0VSJykgfHwgZGVmaW5lKCdGTV9ET0NfVklFV0VSJywgJG9ubGluZV92aWV3ZXIpOwpkZWZpbmUoJ0ZNX1JFQURPTkxZJywgJGdsb2JhbF9yZWFkb25seSB8fCAoJHVzZV9hdXRoICYmICFlbXB0eSgkcmVhZG9ubHlfdXNlcnMpICYmIGlzc2V0KCRfU0VTU0lPTltGTV9TRVNTSU9OX0lEXVsnbG9nZ2VkJ10pICYmIGluX2FycmF5KCRfU0VTU0lPTltGTV9TRVNTSU9OX0lEXVsnbG9nZ2VkJ10sICRyZWFkb25seV91c2VycykpKTsKZGVmaW5lKCdGTV9JU19XSU4nLCBESVJFQ1RPUllfU0VQQVJBVE9SID09ICdcXCcpOwoKLy8gYWx3YXlzIHVzZSA/cD0KaWYgKCFpc3NldCgkX0dFVFsncCddKSAmJiBlbXB0eSgkX0ZJTEVTKSkgewogICAgZm1fcmVkaXJlY3QoRk1fU0VMRl9VUkwgLiAnP3A9Jyk7Cn0KCi8vIGdldCBwYXRoCiRwID0gaXNzZXQoJF9HRVRbJ3AnXSkgPyAkX0dFVFsncCddIDogKGlzc2V0KCRfUE9TVFsncCddKSA/ICRfUE9TVFsncCddIDogJycpOwoKLy8gY2xlYW4gcGF0aAokcCA9IGZtX2NsZWFuX3BhdGgoJHApOwoKLy8gZm9yIGFqYXggcmVxdWVzdCAtIHNhdmUKJGlucHV0ID0gZmlsZV9nZXRfY29udGVudHMoJ3BocDovL2lucHV0Jyk7CiRfUE9TVCA9IChzdHJwb3MoJGlucHV0LCAnYWpheCcpICE9IEZBTFNFICYmIHN0cnBvcygkaW5wdXQsICdzYXZlJykgIT0gRkFMU0UpID8ganNvbl9kZWNvZGUoJGlucHV0LCB0cnVlKSA6ICRfUE9TVDsKCi8vIGluc3RlYWQgZ2xvYmFscyB2YXJzCmRlZmluZSgnRk1fUEFUSCcsICRwKTsKZGVmaW5lKCdGTV9VU0VfQVVUSCcsICR1c2VfYXV0aCk7CmRlZmluZSgnRk1fRURJVF9GSUxFJywgJGVkaXRfZmlsZXMpOwpkZWZpbmVkKCdGTV9JQ09OVl9JTlBVVF9FTkMnKSB8fCBkZWZpbmUoJ0ZNX0lDT05WX0lOUFVUX0VOQycsICRpY29udl9pbnB1dF9lbmNvZGluZyk7CmRlZmluZWQoJ0ZNX1VTRV9ISUdITElHSFRKUycpIHx8IGRlZmluZSgnRk1fVVNFX0hJR0hMSUdIVEpTJywgJHVzZV9oaWdobGlnaHRqcyk7CmRlZmluZWQoJ0ZNX0hJR0hMSUdIVEpTX1NUWUxFJykgfHwgZGVmaW5lKCdGTV9ISUdITElHSFRKU19TVFlMRScsICRoaWdobGlnaHRqc19zdHlsZSk7CmRlZmluZWQoJ0ZNX0RBVEVUSU1FX0ZPUk1BVCcpIHx8IGRlZmluZSgnRk1fREFURVRJTUVfRk9STUFUJywgJGRhdGV0aW1lX2Zvcm1hdCk7Cgp1bnNldCgkcCwgJHVzZV9hdXRoLCAkaWNvbnZfaW5wdXRfZW5jb2RpbmcsICR1c2VfaGlnaGxpZ2h0anMsICRoaWdobGlnaHRqc19zdHlsZSk7CgovKioqKioqKioqKioqKioqKioqKioqKioqKioqIEFDVElPTlMgKioqKioqKioqKioqKioqKioqKioqKioqKioqLwoKLy8gSGFuZGxlIGFsbCBBSkFYIFJlcXVlc3QKaWYgKGlzc2V0KCRfUE9TVFsnYWpheCddLCAkX1BPU1RbJ3Rva2VuJ10pICYmICFGTV9SRUFET05MWSkgewogICAgaWYoIXZlcmlmeVRva2VuKCRfUE9TVFsndG9rZW4nXSkpIHsKICAgICAgICBoZWFkZXIoJ0hUVFAvMS4wIDQwMSBVbmF1dGhvcml6ZWQnKTsKICAgICAgICBkaWUoIkludmFsaWQgVG9rZW4uIik7CiAgICB9CgogICAgLy9zZWFyY2ggOiBnZXQgbGlzdCBvZiBmaWxlcyBmcm9tIHRoZSBjdXJyZW50IGZvbGRlcgogICAgaWYoaXNzZXQoJF9QT1NUWyd0eXBlJ10pICYmICRfUE9TVFsndHlwZSddPT0ic2VhcmNoIikgewogICAgICAgICRkaXIgPSAkX1BPU1RbJ3BhdGgnXSA9PSAiLiIgPyAnJzogJF9QT1NUWydwYXRoJ107CiAgICAgICAgJHJlc3BvbnNlID0gc2NhbihmbV9jbGVhbl9wYXRoKCRkaXIpLCAkX1BPU1RbJ2NvbnRlbnQnXSk7CiAgICAgICAgZWNobyBqc29uX2VuY29kZSgkcmVzcG9uc2UpOwogICAgICAgIGV4aXQoKTsKICAgIH0KCiAgICAvLyBzYXZlIGVkaXRvciBmaWxlCiAgICBpZiAoaXNzZXQoJF9QT1NUWyd0eXBlJ10pICYmICRfUE9TVFsndHlwZSddID09ICJzYXZlIikgewogICAgICAgIC8vIGdldCBjdXJyZW50IHBhdGgKICAgICAgICAkcGF0aCA9IEZNX1JPT1RfUEFUSDsKICAgICAgICBpZiAoRk1fUEFUSCAhPSAnJykgewogICAgICAgICAgICAkcGF0aCAuPSAnLycgLiBGTV9QQVRIOwogICAgICAgIH0KICAgICAgICAvLyBjaGVjayBwYXRoCiAgICAgICAgaWYgKCFpc19kaXIoJHBhdGgpKSB7CiAgICAgICAgICAgIGZtX3JlZGlyZWN0KEZNX1NFTEZfVVJMIC4gJz9wPScpOwogICAgICAgIH0KICAgICAgICAkZmlsZSA9ICRfR0VUWydlZGl0J107CiAgICAgICAgJGZpbGUgPSBmbV9jbGVhbl9wYXRoKCRmaWxlKTsKICAgICAgICAkZmlsZSA9IHN0cl9yZXBsYWNlKCcvJywgJycsICRmaWxlKTsKICAgICAgICBpZiAoJGZpbGUgPT0gJycgfHwgIWlzX2ZpbGUoJHBhdGggLiAnLycgLiAkZmlsZSkpIHsKICAgICAgICAgICAgZm1fc2V0X21zZyhsbmcoJ0ZpbGUgbm90IGZvdW5kJyksICdlcnJvcicpOwogICAgICAgICAgICAkRk1fUEFUSD1GTV9QQVRIOyBmbV9yZWRpcmVjdChGTV9TRUxGX1VSTCAuICc/cD0nIC4gdXJsZW5jb2RlKCRGTV9QQVRIKSk7CiAgICAgICAgfQogICAgICAgIGhlYWRlcignWC1YU1MtUHJvdGVjdGlvbjowJyk7CiAgICAgICAgJGZpbGVfcGF0aCA9ICRwYXRoIC4gJy8nIC4gJGZpbGU7CgogICAgICAgICR3cml0ZWRhdGEgPSAkX1BPU1RbJ2NvbnRlbnQnXTsKICAgICAgICAkZmQgPSBmb3BlbigkZmlsZV9wYXRoLCAidyIpOwogICAgICAgICR3cml0ZV9yZXN1bHRzID0gQGZ3cml0ZSgkZmQsICR3cml0ZWRhdGEpOwogICAgICAgIGZjbG9zZSgkZmQpOwogICAgICAgIGlmICgkd3JpdGVfcmVzdWx0cyA9PT0gZmFsc2UpewogICAgICAgICAgICBoZWFkZXIoIkhUVFAvMS4xIDUwMCBJbnRlcm5hbCBTZXJ2ZXIgRXJyb3IiKTsKICAgICAgICAgICAgZGllKCJDb3VsZCBOb3QgV3JpdGUgRmlsZSEgLSBDaGVjayBQZXJtaXNzaW9ucyAvIE93bmVyc2hpcCIpOwogICAgICAgIH0KICAgICAgICBkaWUodHJ1ZSk7CiAgICB9CgogICAgLy8gYmFja3VwIGZpbGVzCiAgICBpZiAoaXNzZXQoJF9QT1NUWyd0eXBlJ10pICYmICRfUE9TVFsndHlwZSddID09ICJiYWNrdXAiICYmICFlbXB0eSgkX1BPU1RbJ2ZpbGUnXSkpIHsKICAgICAgICAkZmlsZU5hbWUgPSBmbV9jbGVhbl9wYXRoKCRfUE9TVFsnZmlsZSddKTsKICAgICAgICAkZnVsbFBhdGggPSBGTV9ST09UX1BBVEggLiAnLyc7CiAgICAgICAgaWYgKCFlbXB0eSgkX1BPU1RbJ3BhdGgnXSkpIHsKICAgICAgICAgICAgJHJlbGF0aXZlRGlyUGF0aCA9IGZtX2NsZWFuX3BhdGgoJF9QT1NUWydwYXRoJ10pOwogICAgICAgICAgICAkZnVsbFBhdGggLj0gInskcmVsYXRpdmVEaXJQYXRofS8iOwogICAgICAgIH0KICAgICAgICAkZGF0ZSA9IGRhdGUoImRNeS1IaXMiKTsKICAgICAgICAkbmV3RmlsZU5hbWUgPSAieyRmaWxlTmFtZX0teyRkYXRlfS5iYWsiOwogICAgICAgICRmdWxseVF1YWxpZmllZEZpbGVOYW1lID0gJGZ1bGxQYXRoIC4gJGZpbGVOYW1lOwogICAgICAgIHRyeSB7CiAgICAgICAgICAgIGlmICghZmlsZV9leGlzdHMoJGZ1bGx5UXVhbGlmaWVkRmlsZU5hbWUpKSB7CiAgICAgICAgICAgICAgICB0aHJvdyBuZXcgRXhjZXB0aW9uKCJGaWxlIHskZmlsZU5hbWV9IG5vdCBmb3VuZCIpOwogICAgICAgICAgICB9CiAgICAgICAgICAgIGlmIChjb3B5KCRmdWxseVF1YWxpZmllZEZpbGVOYW1lLCAkZnVsbFBhdGggLiAkbmV3RmlsZU5hbWUpKSB7CiAgICAgICAgICAgICAgICBlY2hvICJCYWNrdXAgeyRuZXdGaWxlTmFtZX0gY3JlYXRlZCI7CiAgICAgICAgICAgIH0gZWxzZSB7CiAgICAgICAgICAgICAgICB0aHJvdyBuZXcgRXhjZXB0aW9uKCJDb3VsZCBub3QgY29weSBmaWxlIHskZmlsZU5hbWV9Iik7CiAgICAgICAgICAgIH0KICAgICAgICB9IGNhdGNoIChFeGNlcHRpb24gJGUpIHsKICAgICAgICAgICAgZWNobyAkZS0+Z2V0TWVzc2FnZSgpOwogICAgICAgIH0KICAgIH0KCiAgICAvLyBTYXZlIENvbmZpZwogICAgaWYgKGlzc2V0KCRfUE9TVFsndHlwZSddKSAmJiAkX1BPU1RbJ3R5cGUnXSA9PSAic2V0dGluZ3MiKSB7CiAgICAgICAgZ2xvYmFsICRjZmcsICRsYW5nLCAkcmVwb3J0X2Vycm9ycywgJHNob3dfaGlkZGVuX2ZpbGVzLCAkbGFuZ19saXN0LCAkaGlkZV9Db2xzLCAkdGhlbWU7CiAgICAgICAgJG5ld0xuZyA9ICRfUE9TVFsnanMtbGFuZ3VhZ2UnXTsKICAgICAgICBmbV9nZXRfdHJhbnNsYXRpb25zKFtdKTsKICAgICAgICBpZiAoIWFycmF5X2tleV9leGlzdHMoJG5ld0xuZywgJGxhbmdfbGlzdCkpIHsKICAgICAgICAgICAgJG5ld0xuZyA9ICdlbic7CiAgICAgICAgfQoKICAgICAgICAkZXJwID0gaXNzZXQoJF9QT1NUWydqcy1lcnJvci1yZXBvcnQnXSkgJiYgJF9QT1NUWydqcy1lcnJvci1yZXBvcnQnXSA9PSAidHJ1ZSIgPyB0cnVlIDogZmFsc2U7CiAgICAgICAgJHNoZiA9IGlzc2V0KCRfUE9TVFsnanMtc2hvdy1oaWRkZW4nXSkgJiYgJF9QT1NUWydqcy1zaG93LWhpZGRlbiddID09ICJ0cnVlIiA/IHRydWUgOiBmYWxzZTsKICAgICAgICAkaGNvID0gaXNzZXQoJF9QT1NUWydqcy1oaWRlLWNvbHMnXSkgJiYgJF9QT1NUWydqcy1oaWRlLWNvbHMnXSA9PSAidHJ1ZSIgPyB0cnVlIDogZmFsc2U7CiAgICAgICAgJGNhZiA9IGlzc2V0KCRfUE9TVFsnanMtY2FsYy1mb2xkZXInXSkgJiYgJF9QT1NUWydqcy1jYWxjLWZvbGRlciddID09ICJ0cnVlIiA/IHRydWUgOiBmYWxzZTsKICAgICAgICAkdGUzID0gJF9QT1NUWydqcy10aGVtZS0zJ107CgogICAgICAgIGlmICgkY2ZnLT5kYXRhWydsYW5nJ10gIT0gJG5ld0xuZykgewogICAgICAgICAgICAkY2ZnLT5kYXRhWydsYW5nJ10gPSAkbmV3TG5nOwogICAgICAgICAgICAkbGFuZyA9ICRuZXdMbmc7CiAgICAgICAgfQogICAgICAgIGlmICgkY2ZnLT5kYXRhWydlcnJvcl9yZXBvcnRpbmcnXSAhPSAkZXJwKSB7CiAgICAgICAgICAgICRjZmctPmRhdGFbJ2Vycm9yX3JlcG9ydGluZyddID0gJGVycDsKICAgICAgICAgICAgJHJlcG9ydF9lcnJvcnMgPSAkZXJwOwogICAgICAgIH0KICAgICAgICBpZiAoJGNmZy0+ZGF0YVsnc2hvd19oaWRkZW4nXSAhPSAkc2hmKSB7CiAgICAgICAgICAgICRjZmctPmRhdGFbJ3Nob3dfaGlkZGVuJ10gPSAkc2hmOwogICAgICAgICAgICAkc2hvd19oaWRkZW5fZmlsZXMgPSAkc2hmOwogICAgICAgIH0KICAgICAgICBpZiAoJGNmZy0+ZGF0YVsnc2hvd19oaWRkZW4nXSAhPSAkc2hmKSB7CiAgICAgICAgICAgICRjZmctPmRhdGFbJ3Nob3dfaGlkZGVuJ10gPSAkc2hmOwogICAgICAgICAgICAkc2hvd19oaWRkZW5fZmlsZXMgPSAkc2hmOwogICAgICAgIH0KICAgICAgICBpZiAoJGNmZy0+ZGF0YVsnaGlkZV9Db2xzJ10gIT0gJGhjbykgewogICAgICAgICAgICAkY2ZnLT5kYXRhWydoaWRlX0NvbHMnXSA9ICRoY287CiAgICAgICAgICAgICRoaWRlX0NvbHMgPSAkaGNvOwogICAgICAgIH0KICAgICAgICBpZiAoJGNmZy0+ZGF0YVsndGhlbWUnXSAhPSAkdGUzKSB7CiAgICAgICAgICAgICRjZmctPmRhdGFbJ3RoZW1lJ10gPSAkdGUzOwogICAgICAgICAgICAkdGhlbWUgPSAkdGUzOwogICAgICAgIH0KICAgICAgICAkY2ZnLT5zYXZlKCk7CiAgICAgICAgZWNobyB0cnVlOwogICAgfQoKICAgIC8vIG5ldyBwYXNzd29yZCBoYXNoCiAgICBpZiAoaXNzZXQoJF9QT1NUWyd0eXBlJ10pICYmICRfUE9TVFsndHlwZSddID09ICJwd2RoYXNoIikgewogICAgICAgICRyZXMgPSBpc3NldCgkX1BPU1RbJ2lucHV0UGFzc3dvcmQyJ10pICYmICFlbXB0eSgkX1BPU1RbJ2lucHV0UGFzc3dvcmQyJ10pID8gcGFzc3dvcmRfaGFzaCgkX1BPU1RbJ2lucHV0UGFzc3dvcmQyJ10sIFBBU1NXT1JEX0RFRkFVTFQpIDogJyc7CiAgICAgICAgZWNobyAkcmVzOwogICAgfQoKICAgIC8vdXBsb2FkIHVzaW5nIHVybAogICAgaWYoaXNzZXQoJF9QT1NUWyd0eXBlJ10pICYmICRfUE9TVFsndHlwZSddID09ICJ1cGxvYWQiICYmICFlbXB0eSgkX1JFUVVFU1RbInVwbG9hZHVybCJdKSkgewogICAgICAgICRwYXRoID0gRk1fUk9PVF9QQVRIOwogICAgICAgIGlmIChGTV9QQVRIICE9ICcnKSB7CiAgICAgICAgICAgICRwYXRoIC49ICcvJyAuIEZNX1BBVEg7CiAgICAgICAgfQoKICAgICAgICAgZnVuY3Rpb24gZXZlbnRfY2FsbGJhY2sgKCRtZXNzYWdlKSB7CiAgICAgICAgICAgIGdsb2JhbCAkY2FsbGJhY2s7CiAgICAgICAgICAgIGVjaG8ganNvbl9lbmNvZGUoJG1lc3NhZ2UpOwogICAgICAgIH0KCiAgICAgICAgZnVuY3Rpb24gZ2V0X2ZpbGVfcGF0aCAoKSB7CiAgICAgICAgICAgIGdsb2JhbCAkcGF0aCwgJGZpbGVpbmZvLCAkdGVtcF9maWxlOwogICAgICAgICAgICByZXR1cm4gJHBhdGguIi8iLmJhc2VuYW1lKCRmaWxlaW5mby0+bmFtZSk7CiAgICAgICAgfQoKICAgICAgICAkdXJsID0gIWVtcHR5KCRfUkVRVUVTVFsidXBsb2FkdXJsIl0pICYmIHByZWdfbWF0Y2goInxeaHR0cChzKT86Ly8uKyR8Iiwgc3RyaXBzbGFzaGVzKCRfUkVRVUVTVFsidXBsb2FkdXJsIl0pKSA/IHN0cmlwc2xhc2hlcygkX1JFUVVFU1RbInVwbG9hZHVybCJdKSA6IG51bGw7CgogICAgICAgIC8vcHJldmVudCAxMjcuKiBkb21haW4gYW5kIGtub3duIHBvcnRzCiAgICAgICAgJGRvbWFpbiA9IHBhcnNlX3VybCgkdXJsLCBQSFBfVVJMX0hPU1QpOwogICAgICAgICRwb3J0ID0gcGFyc2VfdXJsKCR1cmwsIFBIUF9VUkxfUE9SVCk7CiAgICAgICAgJGtub3duUG9ydHMgPSBbMjIsIDIzLCAyNSwgMzMwNl07CgogICAgICAgIGlmIChwcmVnX21hdGNoKCIvXmxvY2FsaG9zdCR8XjEyNyg/OlwuWzAtOV0rKXswLDJ9XC5bMC05XSskfF4oPzowKlw6KSo/Oj8wKjEkL2kiLCAkZG9tYWluKSB8fCBpbl9hcnJheSgkcG9ydCwgJGtub3duUG9ydHMpKSB7CiAgICAgICAgICAgICRlcnIgPSBhcnJheSgibWVzc2FnZSIgPT4gIlVSTCBpcyBub3QgYWxsb3dlZCIpOwogICAgICAgICAgICBldmVudF9jYWxsYmFjayhhcnJheSgiZmFpbCIgPT4gJGVycikpOwogICAgICAgICAgICBleGl0KCk7CiAgICAgICAgfQoKICAgICAgICAkdXNlX2N1cmwgPSBmYWxzZTsKICAgICAgICAkdGVtcF9maWxlID0gdGVtcG5hbShzeXNfZ2V0X3RlbXBfZGlyKCksICJ1cGxvYWQtIik7CiAgICAgICAgJGZpbGVpbmZvID0gbmV3IHN0ZENsYXNzKCk7CiAgICAgICAgJGZpbGVpbmZvLT5uYW1lID0gdHJpbShiYXNlbmFtZSgkdXJsKSwgIi5ceDAwLi5ceDIwIik7CgogICAgICAgICRhbGxvd2VkID0gKEZNX1VQTE9BRF9FWFRFTlNJT04pID8gZXhwbG9kZSgnLCcsIEZNX1VQTE9BRF9FWFRFTlNJT04pIDogZmFsc2U7CiAgICAgICAgJGV4dCA9IHN0cnRvbG93ZXIocGF0aGluZm8oJGZpbGVpbmZvLT5uYW1lLCBQQVRISU5GT19FWFRFTlNJT04pKTsKICAgICAgICAkaXNGaWxlQWxsb3dlZCA9ICgkYWxsb3dlZCkgPyBpbl9hcnJheSgkZXh0LCAkYWxsb3dlZCkgOiB0cnVlOwoKICAgICAgICAkZXJyID0gZmFsc2U7CgogICAgICAgIGlmKCEkaXNGaWxlQWxsb3dlZCkgewogICAgICAgICAgICAkZXJyID0gYXJyYXkoIm1lc3NhZ2UiID0+ICJGaWxlIGV4dGVuc2lvbiBpcyBub3QgYWxsb3dlZCIpOwogICAgICAgICAgICBldmVudF9jYWxsYmFjayhhcnJheSgiZmFpbCIgPT4gJGVycikpOwogICAgICAgICAgICBleGl0KCk7CiAgICAgICAgfQoKICAgICAgICBpZiAoISR1cmwpIHsKICAgICAgICAgICAgJHN1Y2Nlc3MgPSBmYWxzZTsKICAgICAgICB9IGVsc2UgaWYgKCR1c2VfY3VybCkgewogICAgICAgICAgICBAJGZwID0gZm9wZW4oJHRlbXBfZmlsZSwgInciKTsKICAgICAgICAgICAgQCRjaCA9IGN1cmxfaW5pdCgkdXJsKTsKICAgICAgICAgICAgY3VybF9zZXRvcHQoJGNoLCBDVVJMT1BUX05PUFJPR1JFU1MsIGZhbHNlICk7CiAgICAgICAgICAgIGN1cmxfc2V0b3B0KCRjaCwgQ1VSTE9QVF9GT0xMT1dMT0NBVElPTiwgdHJ1ZSk7CiAgICAgICAgICAgIGN1cmxfc2V0b3B0KCRjaCwgQ1VSTE9QVF9GSUxFLCAkZnApOwogICAgICAgICAgICBAJHN1Y2Nlc3MgPSBjdXJsX2V4ZWMoJGNoKTsKICAgICAgICAgICAgJGN1cmxfaW5mbyA9IGN1cmxfZ2V0aW5mbygkY2gpOwogICAgICAgICAgICBpZiAoISRzdWNjZXNzKSB7CiAgICAgICAgICAgICAgICAkZXJyID0gYXJyYXkoIm1lc3NhZ2UiID0+IGN1cmxfZXJyb3IoJGNoKSk7CiAgICAgICAgICAgIH0KICAgICAgICAgICAgQGN1cmxfY2xvc2UoJGNoKTsKICAgICAgICAgICAgZmNsb3NlKCRmcCk7CiAgICAgICAgICAgICRmaWxlaW5mby0+c2l6ZSA9ICRjdXJsX2luZm9bInNpemVfZG93bmxvYWQiXTsKICAgICAgICAgICAgJGZpbGVpbmZvLT50eXBlID0gJGN1cmxfaW5mb1siY29udGVudF90eXBlIl07CiAgICAgICAgfSBlbHNlIHsKICAgICAgICAgICAgJGN0eCA9IHN0cmVhbV9jb250ZXh0X2NyZWF0ZSgpOwogICAgICAgICAgICBAJHN1Y2Nlc3MgPSBjb3B5KCR1cmwsICR0ZW1wX2ZpbGUsICRjdHgpOwogICAgICAgICAgICBpZiAoISRzdWNjZXNzKSB7CiAgICAgICAgICAgICAgICAkZXJyID0gZXJyb3JfZ2V0X2xhc3QoKTsKICAgICAgICAgICAgfQogICAgICAgIH0KCiAgICAgICAgaWYgKCRzdWNjZXNzKSB7CiAgICAgICAgICAgICRzdWNjZXNzID0gcmVuYW1lKCR0ZW1wX2ZpbGUsIGdldF9maWxlX3BhdGgoKSk7CiAgICAgICAgfQoKICAgICAgICBpZiAoJHN1Y2Nlc3MpIHsKICAgICAgICAgICAgZXZlbnRfY2FsbGJhY2soYXJyYXkoImRvbmUiID0+ICRmaWxlaW5mbykpOwogICAgICAgIH0gZWxzZSB7CiAgICAgICAgICAgIHVubGluaygkdGVtcF9maWxlKTsKICAgICAgICAgICAgaWYgKCEkZXJyKSB7CiAgICAgICAgICAgICAgICAkZXJyID0gYXJyYXkoIm1lc3NhZ2UiID0+ICJJbnZhbGlkIHVybCBwYXJhbWV0ZXIiKTsKICAgICAgICAgICAgfQogICAgICAgICAgICBldmVudF9jYWxsYmFjayhhcnJheSgiZmFpbCIgPT4gJGVycikpOwogICAgICAgIH0KICAgIH0KICAgIGV4aXQoKTsKfQoKLy8gRGVsZXRlIGZpbGUgLyBmb2xkZXIKaWYgKGlzc2V0KCRfR0VUWydkZWwnXSwgJF9QT1NUWyd0b2tlbiddKSAmJiAhRk1fUkVBRE9OTFkpIHsKICAgICRkZWwgPSBzdHJfcmVwbGFjZSggJy8nLCAnJywgZm1fY2xlYW5fcGF0aCggJF9HRVRbJ2RlbCddICkgKTsKICAgIGlmICgkZGVsICE9ICcnICYmICRkZWwgIT0gJy4uJyAmJiAkZGVsICE9ICcuJyAmJiB2ZXJpZnlUb2tlbigkX1BPU1RbJ3Rva2VuJ10pKSB7CiAgICAgICAgJHBhdGggPSBGTV9ST09UX1BBVEg7CiAgICAgICAgaWYgKEZNX1BBVEggIT0gJycpIHsKICAgICAgICAgICAgJHBhdGggLj0gJy8nIC4gRk1fUEFUSDsKICAgICAgICB9CiAgICAgICAgJGlzX2RpciA9IGlzX2RpcigkcGF0aCAuICcvJyAuICRkZWwpOwogICAgICAgIGlmIChmbV9yZGVsZXRlKCRwYXRoIC4gJy8nIC4gJGRlbCkpIHsKICAgICAgICAgICAgJG1zZyA9ICRpc19kaXIgPyBsbmcoJ0ZvbGRlcicpLicgPGI+JXM8L2I+ICcubG5nKCdEZWxldGVkJykgOiBsbmcoJ0ZpbGUnKS4nIDxiPiVzPC9iPiAnLmxuZygnRGVsZXRlZCcpOwogICAgICAgICAgICBmbV9zZXRfbXNnKHNwcmludGYoJG1zZywgZm1fZW5jKCRkZWwpKSk7CiAgICAgICAgfSBlbHNlIHsKICAgICAgICAgICAgJG1zZyA9ICRpc19kaXIgPyBsbmcoJ0ZvbGRlcicpLicgPGI+JXM8L2I+ICcubG5nKCdub3QgZGVsZXRlZCcpIDogbG5nKCdGaWxlJykuJyA8Yj4lczwvYj4gJy5sbmcoJ25vdCBkZWxldGVkJyk7CiAgICAgICAgICAgIGZtX3NldF9tc2coc3ByaW50ZigkbXNnLCBmbV9lbmMoJGRlbCkpLCAnZXJyb3InKTsKICAgICAgICB9CiAgICB9IGVsc2UgewogICAgICAgIGZtX3NldF9tc2cobG5nKCdJbnZhbGlkIGZpbGUgb3IgZm9sZGVyIG5hbWUnKSwgJ2Vycm9yJyk7CiAgICB9CiAgICAkRk1fUEFUSD1GTV9QQVRIOyBmbV9yZWRpcmVjdChGTV9TRUxGX1VSTCAuICc/cD0nIC4gdXJsZW5jb2RlKCRGTV9QQVRIKSk7Cn0KCi8vIENyZWF0ZSBmb2xkZXIKaWYgKGlzc2V0KCRfUE9TVFsnbmV3ZmlsZW5hbWUnXSwgJF9QT1NUWyduZXdmaWxlJ10sICRfUE9TVFsndG9rZW4nXSkgJiYgIUZNX1JFQURPTkxZKSB7CiAgICAkdHlwZSA9ICRfUE9TVFsnbmV3ZmlsZSddOwogICAgJG5ldyA9IHN0cl9yZXBsYWNlKCAnLycsICcnLCBmbV9jbGVhbl9wYXRoKCBzdHJpcF90YWdzKCAkX1BPU1RbJ25ld2ZpbGVuYW1lJ10gKSApICk7CiAgICBpZiAoZm1faXN2YWxpZF9maWxlbmFtZSgkbmV3KSAmJiAkbmV3ICE9ICcnICYmICRuZXcgIT0gJy4uJyAmJiAkbmV3ICE9ICcuJyAmJiB2ZXJpZnlUb2tlbigkX1BPU1RbJ3Rva2VuJ10pKSB7CiAgICAgICAgJHBhdGggPSBGTV9ST09UX1BBVEg7CiAgICAgICAgaWYgKEZNX1BBVEggIT0gJycpIHsKICAgICAgICAgICAgJHBhdGggLj0gJy8nIC4gRk1fUEFUSDsKICAgICAgICB9CiAgICAgICAgaWYgKCR0eXBlID09ICJmaWxlIikgewogICAgICAgICAgICBpZiAoIWZpbGVfZXhpc3RzKCRwYXRoIC4gJy8nIC4gJG5ldykpIHsKICAgICAgICAgICAgICAgIGlmKGZtX2lzX3ZhbGlkX2V4dCgkbmV3KSkgewogICAgICAgICAgICAgICAgICAgIEBmb3BlbigkcGF0aCAuICcvJyAuICRuZXcsICd3Jykgb3IgZGllKCdDYW5ub3Qgb3BlbiBmaWxlOiAgJyAuICRuZXcpOwogICAgICAgICAgICAgICAgICAgIGZtX3NldF9tc2coc3ByaW50ZihsbmcoJ0ZpbGUnKS4nIDxiPiVzPC9iPiAnLmxuZygnQ3JlYXRlZCcpLCBmbV9lbmMoJG5ldykpKTsKICAgICAgICAgICAgICAgIH0gZWxzZSB7CiAgICAgICAgICAgICAgICAgICAgZm1fc2V0X21zZyhsbmcoJ0ZpbGUgZXh0ZW5zaW9uIGlzIG5vdCBhbGxvd2VkJyksICdlcnJvcicpOwogICAgICAgICAgICAgICAgfQogICAgICAgICAgICB9IGVsc2UgewogICAgICAgICAgICAgICAgZm1fc2V0X21zZyhzcHJpbnRmKGxuZygnRmlsZScpLicgPGI+JXM8L2I+ICcubG5nKCdhbHJlYWR5IGV4aXN0cycpLCBmbV9lbmMoJG5ldykpLCAnYWxlcnQnKTsKICAgICAgICAgICAgfQogICAgICAgIH0gZWxzZSB7CiAgICAgICAgICAgIGlmIChmbV9ta2RpcigkcGF0aCAuICcvJyAuICRuZXcsIGZhbHNlKSA9PT0gdHJ1ZSkgewogICAgICAgICAgICAgICAgZm1fc2V0X21zZyhzcHJpbnRmKGxuZygnRm9sZGVyJykuJyA8Yj4lczwvYj4gJy5sbmcoJ0NyZWF0ZWQnKSwgJG5ldykpOwogICAgICAgICAgICB9IGVsc2VpZiAoZm1fbWtkaXIoJHBhdGggLiAnLycgLiAkbmV3LCBmYWxzZSkgPT09ICRwYXRoIC4gJy8nIC4gJG5ldykgewogICAgICAgICAgICAgICAgZm1fc2V0X21zZyhzcHJpbnRmKGxuZygnRm9sZGVyJykuJyA8Yj4lczwvYj4gJy5sbmcoJ2FscmVhZHkgZXhpc3RzJyksIGZtX2VuYygkbmV3KSksICdhbGVydCcpOwogICAgICAgICAgICB9IGVsc2UgewogICAgICAgICAgICAgICAgZm1fc2V0X21zZyhzcHJpbnRmKGxuZygnRm9sZGVyJykuJyA8Yj4lczwvYj4gJy5sbmcoJ25vdCBjcmVhdGVkJyksIGZtX2VuYygkbmV3KSksICdlcnJvcicpOwogICAgICAgICAgICB9CiAgICAgICAgfQogICAgfSBlbHNlIHsKICAgICAgICBmbV9zZXRfbXNnKGxuZygnSW52YWxpZCBjaGFyYWN0ZXJzIGluIGZpbGUgb3IgZm9sZGVyIG5hbWUnKSwgJ2Vycm9yJyk7CiAgICB9CiAgICAkRk1fUEFUSD1GTV9QQVRIOyBmbV9yZWRpcmVjdChGTV9TRUxGX1VSTCAuICc/cD0nIC4gdXJsZW5jb2RlKCRGTV9QQVRIKSk7Cn0KCi8vIENvcHkgZm9sZGVyIC8gZmlsZQppZiAoaXNzZXQoJF9HRVRbJ2NvcHknXSwgJF9HRVRbJ2ZpbmlzaCddKSAmJiAhRk1fUkVBRE9OTFkpIHsKICAgIC8vIGZyb20KICAgICRjb3B5ID0gJF9HRVRbJ2NvcHknXTsKICAgICRjb3B5ID0gZm1fY2xlYW5fcGF0aCgkY29weSk7CiAgICAvLyBlbXB0eSBwYXRoCiAgICBpZiAoJGNvcHkgPT0gJycpIHsKICAgICAgICBmbV9zZXRfbXNnKGxuZygnU291cmNlIHBhdGggbm90IGRlZmluZWQnKSwgJ2Vycm9yJyk7CiAgICAgICAgJEZNX1BBVEg9Rk1fUEFUSDsgZm1fcmVkaXJlY3QoRk1fU0VMRl9VUkwgLiAnP3A9JyAuIHVybGVuY29kZSgkRk1fUEFUSCkpOwogICAgfQogICAgLy8gYWJzIHBhdGggZnJvbQogICAgJGZyb20gPSBGTV9ST09UX1BBVEggLiAnLycgLiAkY29weTsKICAgIC8vIGFicyBwYXRoIHRvCiAgICAkZGVzdCA9IEZNX1JPT1RfUEFUSDsKICAgIGlmIChGTV9QQVRIICE9ICcnKSB7CiAgICAgICAgJGRlc3QgLj0gJy8nIC4gRk1fUEFUSDsKICAgIH0KICAgICRkZXN0IC49ICcvJyAuIGJhc2VuYW1lKCRmcm9tKTsKICAgIC8vIG1vdmU/CiAgICAkbW92ZSA9IGlzc2V0KCRfR0VUWydtb3ZlJ10pOwogICAgLy8gY29weS9tb3ZlL2R1cGxpY2F0ZQogICAgaWYgKCRmcm9tICE9ICRkZXN0KSB7CiAgICAgICAgJG1zZ19mcm9tID0gdHJpbShGTV9QQVRIIC4gJy8nIC4gYmFzZW5hbWUoJGZyb20pLCAnLycpOwogICAgICAgIGlmICgkbW92ZSkgeyAvLyBNb3ZlIGFuZCB0byAhPSBmcm9tIHNvIGp1c3QgcGVyZm9ybSBtb3ZlCiAgICAgICAgICAgICRyZW5hbWUgPSBmbV9yZW5hbWUoJGZyb20sICRkZXN0KTsKICAgICAgICAgICAgaWYgKCRyZW5hbWUpIHsKICAgICAgICAgICAgICAgIGZtX3NldF9tc2coc3ByaW50ZihsbmcoJ01vdmVkIGZyb20nKS4nIDxiPiVzPC9iPiAnLmxuZygndG8nKS4nIDxiPiVzPC9iPicsIGZtX2VuYygkY29weSksIGZtX2VuYygkbXNnX2Zyb20pKSk7CiAgICAgICAgICAgIH0gZWxzZWlmICgkcmVuYW1lID09PSBudWxsKSB7CiAgICAgICAgICAgICAgICBmbV9zZXRfbXNnKGxuZygnRmlsZSBvciBmb2xkZXIgd2l0aCB0aGlzIHBhdGggYWxyZWFkeSBleGlzdHMnKSwgJ2FsZXJ0Jyk7CiAgICAgICAgICAgIH0gZWxzZSB7CiAgICAgICAgICAgICAgICBmbV9zZXRfbXNnKHNwcmludGYobG5nKCdFcnJvciB3aGlsZSBtb3ZpbmcgZnJvbScpLicgPGI+JXM8L2I+ICcubG5nKCd0bycpLicgPGI+JXM8L2I+JywgZm1fZW5jKCRjb3B5KSwgZm1fZW5jKCRtc2dfZnJvbSkpLCAnZXJyb3InKTsKICAgICAgICAgICAgfQogICAgICAgIH0gZWxzZSB7IC8vIE5vdCBtb3ZlIGFuZCB0byAhPSBmcm9tIHNvIGNvcHkgd2l0aCBvcmlnaW5hbCBuYW1lCiAgICAgICAgICAgIGlmIChmbV9yY29weSgkZnJvbSwgJGRlc3QpKSB7CiAgICAgICAgICAgICAgICBmbV9zZXRfbXNnKHNwcmludGYobG5nKCdDb3BpZWQgZnJvbScpLicgPGI+JXM8L2I+ICcubG5nKCd0bycpLicgPGI+JXM8L2I+JywgZm1fZW5jKCRjb3B5KSwgZm1fZW5jKCRtc2dfZnJvbSkpKTsKICAgICAgICAgICAgfSBlbHNlIHsKICAgICAgICAgICAgICAgIGZtX3NldF9tc2coc3ByaW50ZihsbmcoJ0Vycm9yIHdoaWxlIGNvcHlpbmcgZnJvbScpLicgPGI+JXM8L2I+ICcubG5nKCd0bycpLicgPGI+JXM8L2I+JywgZm1fZW5jKCRjb3B5KSwgZm1fZW5jKCRtc2dfZnJvbSkpLCAnZXJyb3InKTsKICAgICAgICAgICAgfQogICAgICAgIH0KICAgIH0gZWxzZSB7CiAgICAgICBpZiAoISRtb3ZlKXsgLy9Ob3QgbW92ZSBhbmQgdG8gPSBmcm9tIHNvIGR1cGxpY2F0ZQogICAgICAgICAgICAkbXNnX2Zyb20gPSB0cmltKEZNX1BBVEggLiAnLycgLiBiYXNlbmFtZSgkZnJvbSksICcvJyk7CiAgICAgICAgICAgICRmbl9wYXJ0cyA9IHBhdGhpbmZvKCRmcm9tKTsKICAgICAgICAgICAgJGV4dGVuc2lvbl9zdWZmaXggPSAnJzsKICAgICAgICAgICAgaWYoIWlzX2RpcigkZnJvbSkpewogICAgICAgICAgICAgICAkZXh0ZW5zaW9uX3N1ZmZpeCA9ICcuJy4kZm5fcGFydHNbJ2V4dGVuc2lvbiddOwogICAgICAgICAgICB9CiAgICAgICAgICAgIC8vQ3JlYXRlIG5ldyBuYW1lIGZvciBkdXBsaWNhdGUKICAgICAgICAgICAgJGZuX2R1cGxpY2F0ZSA9ICRmbl9wYXJ0c1snZGlybmFtZSddLicvJy4kZm5fcGFydHNbJ2ZpbGVuYW1lJ10uJy0nLmRhdGUoJ1ltZEhpcycpLiRleHRlbnNpb25fc3VmZml4OwogICAgICAgICAgICAkbG9vcF9jb3VudCA9IDA7CiAgICAgICAgICAgICRtYXhfbG9vcCA9IDEwMDA7CiAgICAgICAgICAgIC8vIENoZWNrIGlmIGEgZmlsZSB3aXRoIHRoZSBkdXBsaWNhdGUgbmFtZSBhbHJlYWR5IGV4aXN0cywgaWYgc28sIG1ha2UgbmV3IG5hbWUgKGVkZ2UgY2FzZS4uLikKICAgICAgICAgICAgd2hpbGUoZmlsZV9leGlzdHMoJGZuX2R1cGxpY2F0ZSkgJiAkbG9vcF9jb3VudCA8ICRtYXhfbG9vcCl7CiAgICAgICAgICAgICAgICRmbl9wYXJ0cyA9IHBhdGhpbmZvKCRmbl9kdXBsaWNhdGUpOwogICAgICAgICAgICAgICAkZm5fZHVwbGljYXRlID0gJGZuX3BhcnRzWydkaXJuYW1lJ10uJy8nLiRmbl9wYXJ0c1snZmlsZW5hbWUnXS4nLWNvcHknLiRleHRlbnNpb25fc3VmZml4OwogICAgICAgICAgICAgICAkbG9vcF9jb3VudCsrOwogICAgICAgICAgICB9CiAgICAgICAgICAgIGlmIChmbV9yY29weSgkZnJvbSwgJGZuX2R1cGxpY2F0ZSwgRmFsc2UpKSB7CiAgICAgICAgICAgICAgICBmbV9zZXRfbXNnKHNwcmludGYoJ0NvcHlpZWQgZnJvbSA8Yj4lczwvYj4gdG8gPGI+JXM8L2I+JywgZm1fZW5jKCRjb3B5KSwgZm1fZW5jKCRmbl9kdXBsaWNhdGUpKSk7CiAgICAgICAgICAgIH0gZWxzZSB7CiAgICAgICAgICAgICAgICBmbV9zZXRfbXNnKHNwcmludGYoJ0Vycm9yIHdoaWxlIGNvcHlpbmcgZnJvbSA8Yj4lczwvYj4gdG8gPGI+JXM8L2I+JywgZm1fZW5jKCRjb3B5KSwgZm1fZW5jKCRmbl9kdXBsaWNhdGUpKSwgJ2Vycm9yJyk7CiAgICAgICAgICAgIH0KICAgICAgIH0KICAgICAgIGVsc2V7CiAgICAgICAgICAgZm1fc2V0X21zZyhsbmcoJ1BhdGhzIG11c3QgYmUgbm90IGVxdWFsJyksICdhbGVydCcpOwogICAgICAgfQogICAgfQogICAgJEZNX1BBVEg9Rk1fUEFUSDsgZm1fcmVkaXJlY3QoRk1fU0VMRl9VUkwgLiAnP3A9JyAuIHVybGVuY29kZSgkRk1fUEFUSCkpOwp9CgovLyBNYXNzIGNvcHkgZmlsZXMvIGZvbGRlcnMKaWYgKGlzc2V0KCRfUE9TVFsnZmlsZSddLCAkX1BPU1RbJ2NvcHlfdG8nXSwgJF9QT1NUWydmaW5pc2gnXSwgJF9QT1NUWyd0b2tlbiddKSAmJiAhRk1fUkVBRE9OTFkpIHsKCiAgICBpZighdmVyaWZ5VG9rZW4oJF9QT1NUWyd0b2tlbiddKSkgewogICAgICAgIGZtX3NldF9tc2coIkludmFsaWQgVG9rZW4uIiwgJ2Vycm9yJyk7CiAgICB9CiAgICAKICAgIC8vIGZyb20KICAgICRwYXRoID0gRk1fUk9PVF9QQVRIOwogICAgaWYgKEZNX1BBVEggIT0gJycpIHsKICAgICAgICAkcGF0aCAuPSAnLycgLiBGTV9QQVRIOwogICAgfQogICAgLy8gdG8KICAgICRjb3B5X3RvX3BhdGggPSBGTV9ST09UX1BBVEg7CiAgICAkY29weV90byA9IGZtX2NsZWFuX3BhdGgoJF9QT1NUWydjb3B5X3RvJ10pOwogICAgaWYgKCRjb3B5X3RvICE9ICcnKSB7CiAgICAgICAgJGNvcHlfdG9fcGF0aCAuPSAnLycgLiAkY29weV90bzsKICAgIH0KICAgIGlmICgkcGF0aCA9PSAkY29weV90b19wYXRoKSB7CiAgICAgICAgZm1fc2V0X21zZyhsbmcoJ1BhdGhzIG11c3QgYmUgbm90IGVxdWFsJyksICdhbGVydCcpOwogICAgICAgICRGTV9QQVRIPUZNX1BBVEg7IGZtX3JlZGlyZWN0KEZNX1NFTEZfVVJMIC4gJz9wPScgLiB1cmxlbmNvZGUoJEZNX1BBVEgpKTsKICAgIH0KICAgIGlmICghaXNfZGlyKCRjb3B5X3RvX3BhdGgpKSB7CiAgICAgICAgaWYgKCFmbV9ta2RpcigkY29weV90b19wYXRoLCB0cnVlKSkgewogICAgICAgICAgICBmbV9zZXRfbXNnKCdVbmFibGUgdG8gY3JlYXRlIGRlc3RpbmF0aW9uIGZvbGRlcicsICdlcnJvcicpOwogICAgICAgICAgICAkRk1fUEFUSD1GTV9QQVRIOyBmbV9yZWRpcmVjdChGTV9TRUxGX1VSTCAuICc/cD0nIC4gdXJsZW5jb2RlKCRGTV9QQVRIKSk7CiAgICAgICAgfQogICAgfQogICAgLy8gbW92ZT8KICAgICRtb3ZlID0gaXNzZXQoJF9QT1NUWydtb3ZlJ10pOwogICAgLy8gY29weS9tb3ZlCiAgICAkZXJyb3JzID0gMDsKICAgICRmaWxlcyA9ICRfUE9TVFsnZmlsZSddOwogICAgaWYgKGlzX2FycmF5KCRmaWxlcykgJiYgY291bnQoJGZpbGVzKSkgewogICAgICAgIGZvcmVhY2ggKCRmaWxlcyBhcyAkZikgewogICAgICAgICAgICBpZiAoJGYgIT0gJycpIHsKICAgICAgICAgICAgICAgICRmID0gZm1fY2xlYW5fcGF0aCgkZik7CiAgICAgICAgICAgICAgICAvLyBhYnMgcGF0aCBmcm9tCiAgICAgICAgICAgICAgICAkZnJvbSA9ICRwYXRoIC4gJy8nIC4gJGY7CiAgICAgICAgICAgICAgICAvLyBhYnMgcGF0aCB0bwogICAgICAgICAgICAgICAgJGRlc3QgPSAkY29weV90b19wYXRoIC4gJy8nIC4gJGY7CiAgICAgICAgICAgICAgICAvLyBkbwogICAgICAgICAgICAgICAgaWYgKCRtb3ZlKSB7CiAgICAgICAgICAgICAgICAgICAgJHJlbmFtZSA9IGZtX3JlbmFtZSgkZnJvbSwgJGRlc3QpOwogICAgICAgICAgICAgICAgICAgIGlmICgkcmVuYW1lID09PSBmYWxzZSkgewogICAgICAgICAgICAgICAgICAgICAgICAkZXJyb3JzKys7CiAgICAgICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgICAgfSBlbHNlIHsKICAgICAgICAgICAgICAgICAgICBpZiAoIWZtX3Jjb3B5KCRmcm9tLCAkZGVzdCkpIHsKICAgICAgICAgICAgICAgICAgICAgICAgJGVycm9ycysrOwogICAgICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgfQogICAgICAgIH0KICAgICAgICBpZiAoJGVycm9ycyA9PSAwKSB7CiAgICAgICAgICAgICRtc2cgPSAkbW92ZSA/ICdTZWxlY3RlZCBmaWxlcyBhbmQgZm9sZGVycyBtb3ZlZCcgOiAnU2VsZWN0ZWQgZmlsZXMgYW5kIGZvbGRlcnMgY29waWVkJzsKICAgICAgICAgICAgZm1fc2V0X21zZygkbXNnKTsKICAgICAgICB9IGVsc2UgewogICAgICAgICAgICAkbXNnID0gJG1vdmUgPyAnRXJyb3Igd2hpbGUgbW92aW5nIGl0ZW1zJyA6ICdFcnJvciB3aGlsZSBjb3B5aW5nIGl0ZW1zJzsKICAgICAgICAgICAgZm1fc2V0X21zZygkbXNnLCAnZXJyb3InKTsKICAgICAgICB9CiAgICB9IGVsc2UgewogICAgICAgIGZtX3NldF9tc2cobG5nKCdOb3RoaW5nIHNlbGVjdGVkJyksICdhbGVydCcpOwogICAgfQogICAgJEZNX1BBVEg9Rk1fUEFUSDsgZm1fcmVkaXJlY3QoRk1fU0VMRl9VUkwgLiAnP3A9JyAuIHVybGVuY29kZSgkRk1fUEFUSCkpOwp9CgovLyBSZW5hbWUKaWYgKGlzc2V0KCRfUE9TVFsncmVuYW1lX2Zyb20nXSwgJF9QT1NUWydyZW5hbWVfdG8nXSwgJF9QT1NUWyd0b2tlbiddKSAmJiAhRk1fUkVBRE9OTFkpIHsKICAgIGlmKCF2ZXJpZnlUb2tlbigkX1BPU1RbJ3Rva2VuJ10pKSB7CiAgICAgICAgZm1fc2V0X21zZygiSW52YWxpZCBUb2tlbi4iLCAnZXJyb3InKTsKICAgIH0KICAgIC8vIG9sZCBuYW1lCiAgICAkb2xkID0gJF9QT1NUWydyZW5hbWVfZnJvbSddOwogICAgJG9sZCA9IGZtX2NsZWFuX3BhdGgoJG9sZCk7CiAgICAkb2xkID0gc3RyX3JlcGxhY2UoJy8nLCAnJywgJG9sZCk7CiAgICAvLyBuZXcgbmFtZQogICAgJG5ldyA9ICRfUE9TVFsncmVuYW1lX3RvJ107CiAgICAkbmV3ID0gZm1fY2xlYW5fcGF0aChzdHJpcF90YWdzKCRuZXcpKTsKICAgICRuZXcgPSBzdHJfcmVwbGFjZSgnLycsICcnLCAkbmV3KTsKICAgIC8vIHBhdGgKICAgICRwYXRoID0gRk1fUk9PVF9QQVRIOwogICAgaWYgKEZNX1BBVEggIT0gJycpIHsKICAgICAgICAkcGF0aCAuPSAnLycgLiBGTV9QQVRIOwogICAgfQogICAgLy8gcmVuYW1lCiAgICBpZiAoZm1faXN2YWxpZF9maWxlbmFtZSgkbmV3KSAmJiAkb2xkICE9ICcnICYmICRuZXcgIT0gJycpIHsKICAgICAgICBpZiAoZm1fcmVuYW1lKCRwYXRoIC4gJy8nIC4gJG9sZCwgJHBhdGggLiAnLycgLiAkbmV3KSkgewogICAgICAgICAgICBmbV9zZXRfbXNnKHNwcmludGYobG5nKCdSZW5hbWVkIGZyb20nKS4nIDxiPiVzPC9iPiAnLiBsbmcoJ3RvJykuJyA8Yj4lczwvYj4nLCBmbV9lbmMoJG9sZCksIGZtX2VuYygkbmV3KSkpOwogICAgICAgIH0gZWxzZSB7CiAgICAgICAgICAgIGZtX3NldF9tc2coc3ByaW50ZihsbmcoJ0Vycm9yIHdoaWxlIHJlbmFtaW5nIGZyb20nKS4nIDxiPiVzPC9iPiAnLiBsbmcoJ3RvJykuJyA8Yj4lczwvYj4nLCBmbV9lbmMoJG9sZCksIGZtX2VuYygkbmV3KSksICdlcnJvcicpOwogICAgICAgIH0KICAgIH0gZWxzZSB7CiAgICAgICAgZm1fc2V0X21zZyhsbmcoJ0ludmFsaWQgY2hhcmFjdGVycyBpbiBmaWxlIG5hbWUnKSwgJ2Vycm9yJyk7CiAgICB9CiAgICAkRk1fUEFUSD1GTV9QQVRIOyBmbV9yZWRpcmVjdChGTV9TRUxGX1VSTCAuICc/cD0nIC4gdXJsZW5jb2RlKCRGTV9QQVRIKSk7Cn0KCi8vIERvd25sb2FkCmlmIChpc3NldCgkX0dFVFsnZGwnXSwgJF9QT1NUWyd0b2tlbiddKSkgewogICAgaWYoIXZlcmlmeVRva2VuKCRfUE9TVFsndG9rZW4nXSkpIHsKICAgICAgICBmbV9zZXRfbXNnKCJJbnZhbGlkIFRva2VuLiIsICdlcnJvcicpOwogICAgfQoKICAgICRkbCA9ICRfR0VUWydkbCddOwogICAgJGRsID0gZm1fY2xlYW5fcGF0aCgkZGwpOwogICAgJGRsID0gc3RyX3JlcGxhY2UoJy8nLCAnJywgJGRsKTsKICAgICRwYXRoID0gRk1fUk9PVF9QQVRIOwogICAgaWYgKEZNX1BBVEggIT0gJycpIHsKICAgICAgICAkcGF0aCAuPSAnLycgLiBGTV9QQVRIOwogICAgfQogICAgaWYgKCRkbCAhPSAnJyAmJiBpc19maWxlKCRwYXRoIC4gJy8nIC4gJGRsKSkgewogICAgICAgIGZtX2Rvd25sb2FkX2ZpbGUoJHBhdGggLiAnLycgLiAkZGwsICRkbCwgMTAyNCk7CiAgICAgICAgZXhpdDsKICAgIH0gZWxzZSB7CiAgICAgICAgZm1fc2V0X21zZyhsbmcoJ0ZpbGUgbm90IGZvdW5kJyksICdlcnJvcicpOwogICAgICAgICRGTV9QQVRIPUZNX1BBVEg7IGZtX3JlZGlyZWN0KEZNX1NFTEZfVVJMIC4gJz9wPScgLiB1cmxlbmNvZGUoJEZNX1BBVEgpKTsKICAgIH0KfQoKLy8gVXBsb2FkCmlmICghZW1wdHkoJF9GSUxFUykgJiYgIUZNX1JFQURPTkxZKSB7CiAgICBpZihpc3NldCgkX1BPU1RbJ3Rva2VuJ10pKSB7CiAgICAgICAgaWYoIXZlcmlmeVRva2VuKCRfUE9TVFsndG9rZW4nXSkpIHsKICAgICAgICAgICAgJHJlc3BvbnNlID0gYXJyYXkgKCdzdGF0dXMnID0+ICdlcnJvcicsJ2luZm8nID0+ICJJbnZhbGlkIFRva2VuLiIpOwogICAgICAgICAgICBlY2hvIGpzb25fZW5jb2RlKCRyZXNwb25zZSk7IGV4aXQoKTsKICAgICAgICB9CiAgICB9IGVsc2UgewogICAgICAgICRyZXNwb25zZSA9IGFycmF5ICgnc3RhdHVzJyA9PiAnZXJyb3InLCdpbmZvJyA9PiAiVG9rZW4gTWlzc2luZy4iKTsKICAgICAgICBlY2hvIGpzb25fZW5jb2RlKCRyZXNwb25zZSk7IGV4aXQoKTsKICAgIH0KCiAgICAkb3ZlcnJpZGVfZmlsZV9uYW1lID0gZmFsc2U7CiAgICAkY2h1bmtJbmRleCA9ICRfUE9TVFsnZHpjaHVua2luZGV4J107CiAgICAkY2h1bmtUb3RhbCA9ICRfUE9TVFsnZHp0b3RhbGNodW5rY291bnQnXTsKICAgICRmdWxsUGF0aElucHV0ID0gZm1fY2xlYW5fcGF0aCgkX1JFUVVFU1RbJ2Z1bGxwYXRoJ10pOwoKICAgICRmID0gJF9GSUxFUzsKICAgICRwYXRoID0gRk1fUk9PVF9QQVRIOwogICAgJGRzID0gRElSRUNUT1JZX1NFUEFSQVRPUjsKICAgIGlmIChGTV9QQVRIICE9ICcnKSB7CiAgICAgICAgJHBhdGggLj0gJy8nIC4gRk1fUEFUSDsKICAgIH0KCiAgICAkZXJyb3JzID0gMDsKICAgICR1cGxvYWRzID0gMDsKICAgICRhbGxvd2VkID0gKEZNX1VQTE9BRF9FWFRFTlNJT04pID8gZXhwbG9kZSgnLCcsIEZNX1VQTE9BRF9FWFRFTlNJT04pIDogZmFsc2U7CiAgICAkcmVzcG9uc2UgPSBhcnJheSAoCiAgICAgICAgJ3N0YXR1cycgPT4gJ2Vycm9yJywKICAgICAgICAnaW5mbycgICA9PiAnT29wcyEgVHJ5IGFnYWluJwogICAgKTsKCiAgICAkZmlsZW5hbWUgPSAkZlsnZmlsZSddWyduYW1lJ107CiAgICAkdG1wX25hbWUgPSAkZlsnZmlsZSddWyd0bXBfbmFtZSddOwogICAgJGV4dCA9IHBhdGhpbmZvKCRmaWxlbmFtZSwgUEFUSElORk9fRklMRU5BTUUpICE9ICcnID8gc3RydG9sb3dlcihwYXRoaW5mbygkZmlsZW5hbWUsIFBBVEhJTkZPX0VYVEVOU0lPTikpIDogJyc7CiAgICAkaXNGaWxlQWxsb3dlZCA9ICgkYWxsb3dlZCkgPyBpbl9hcnJheSgkZXh0LCAkYWxsb3dlZCkgOiB0cnVlOwoKICAgIGlmKCFmbV9pc3ZhbGlkX2ZpbGVuYW1lKCRmaWxlbmFtZSkgJiYgIWZtX2lzdmFsaWRfZmlsZW5hbWUoJGZ1bGxQYXRoSW5wdXQpKSB7CiAgICAgICAgJHJlc3BvbnNlID0gYXJyYXkgKAogICAgICAgICAgICAnc3RhdHVzJyAgICA9PiAnZXJyb3InLAogICAgICAgICAgICAnaW5mbycgICAgICA9PiAiSW52YWxpZCBGaWxlIG5hbWUhIiwKICAgICAgICApOwogICAgICAgIGVjaG8ganNvbl9lbmNvZGUoJHJlc3BvbnNlKTsgZXhpdCgpOwogICAgfQoKICAgICR0YXJnZXRQYXRoID0gJHBhdGggLiAkZHM7CiAgICBpZiAoIGlzX3dyaXRhYmxlKCR0YXJnZXRQYXRoKSApIHsKICAgICAgICAkZnVsbFBhdGggPSAkcGF0aCAuICcvJyAuIGJhc2VuYW1lKCRmdWxsUGF0aElucHV0KTsKICAgICAgICAkZm9sZGVyID0gc3Vic3RyKCRmdWxsUGF0aCwgMCwgc3RycnBvcygkZnVsbFBhdGgsICIvIikpOwoKICAgICAgICBpZihmaWxlX2V4aXN0cyAoJGZ1bGxQYXRoKSAmJiAhJG92ZXJyaWRlX2ZpbGVfbmFtZSAmJiAhJGNodW5rcykgewogICAgICAgICAgICAkZXh0XzEgPSAkZXh0ID8gJy4nLiRleHQgOiAnJzsKICAgICAgICAgICAgJGZ1bGxQYXRoID0gJHBhdGggLiAnLycgLiBiYXNlbmFtZSgkZnVsbFBhdGhJbnB1dCwgJGV4dF8xKSAuJ18nLiBkYXRlKCd5bWRIaXMnKS4gJGV4dF8xOwogICAgICAgIH0KCiAgICAgICAgaWYgKCFpc19kaXIoJGZvbGRlcikpIHsKICAgICAgICAgICAgJG9sZCA9IHVtYXNrKDApOwogICAgICAgICAgICBta2RpcigkZm9sZGVyLCAwNzc3LCB0cnVlKTsKICAgICAgICAgICAgdW1hc2soJG9sZCk7CiAgICAgICAgfQoKICAgICAgICBpZiAoZW1wdHkoJGZbJ2ZpbGUnXVsnZXJyb3InXSkgJiYgIWVtcHR5KCR0bXBfbmFtZSkgJiYgJHRtcF9uYW1lICE9ICdub25lJyAmJiAkaXNGaWxlQWxsb3dlZCkgewogICAgICAgICAgICBpZiAoJGNodW5rVG90YWwpewogICAgICAgICAgICAgICAgJG91dCA9IEBmb3BlbigieyRmdWxsUGF0aH0ucGFydCIsICRjaHVua0luZGV4ID09IDAgPyAid2IiIDogImFiIik7CiAgICAgICAgICAgICAgICBpZiAoJG91dCkgewogICAgICAgICAgICAgICAgICAgICRpbiA9IEBmb3BlbigkdG1wX25hbWUsICJyYiIpOwogICAgICAgICAgICAgICAgICAgIGlmICgkaW4pIHsKICAgICAgICAgICAgICAgICAgICAgICAgd2hpbGUgKCRidWZmID0gZnJlYWQoJGluLCA0MDk2KSkgeyBmd3JpdGUoJG91dCwgJGJ1ZmYpOyB9CiAgICAgICAgICAgICAgICAgICAgICAgICRyZXNwb25zZSA9IGFycmF5ICgKICAgICAgICAgICAgICAgICAgICAgICAgICAgICdzdGF0dXMnICAgID0+ICdzdWNjZXNzJywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICdpbmZvJyA9PiAiZmlsZSB1cGxvYWQgc3VjY2Vzc2Z1bCIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAnZnVsbFBhdGgnID0+ICRmdWxsUGF0aAogICAgICAgICAgICAgICAgICAgICAgICApOwogICAgICAgICAgICAgICAgICAgIH0gZWxzZSB7CiAgICAgICAgICAgICAgICAgICAgICAgICRyZXNwb25zZSA9IGFycmF5ICgKICAgICAgICAgICAgICAgICAgICAgICAgJ3N0YXR1cycgICAgPT4gJ2Vycm9yJywKICAgICAgICAgICAgICAgICAgICAgICAgJ2luZm8nID0+ICJmYWlsZWQgdG8gb3BlbiBvdXRwdXQgc3RyZWFtIiwKICAgICAgICAgICAgICAgICAgICAgICAgJ2Vycm9yRGV0YWlscycgPT4gZXJyb3JfZ2V0X2xhc3QoKQogICAgICAgICAgICAgICAgICAgICAgICApOwogICAgICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgICAgICAgICBAZmNsb3NlKCRpbik7CiAgICAgICAgICAgICAgICAgICAgQGZjbG9zZSgkb3V0KTsKICAgICAgICAgICAgICAgICAgICBAdW5saW5rKCR0bXBfbmFtZSk7CgogICAgICAgICAgICAgICAgICAgICRyZXNwb25zZSA9IGFycmF5ICgKICAgICAgICAgICAgICAgICAgICAgICAgJ3N0YXR1cycgICAgPT4gJ3N1Y2Nlc3MnLAogICAgICAgICAgICAgICAgICAgICAgICAnaW5mbycgPT4gImZpbGUgdXBsb2FkIHN1Y2Nlc3NmdWwiLAogICAgICAgICAgICAgICAgICAgICAgICAnZnVsbFBhdGgnID0+ICRmdWxsUGF0aAogICAgICAgICAgICAgICAgICAgICk7CiAgICAgICAgICAgICAgICB9IGVsc2UgewogICAgICAgICAgICAgICAgICAgICRyZXNwb25zZSA9IGFycmF5ICgKICAgICAgICAgICAgICAgICAgICAgICAgJ3N0YXR1cycgICAgPT4gJ2Vycm9yJywKICAgICAgICAgICAgICAgICAgICAgICAgJ2luZm8nID0+ICJmYWlsZWQgdG8gb3BlbiBvdXRwdXQgc3RyZWFtIgogICAgICAgICAgICAgICAgICAgICAgICApOwogICAgICAgICAgICAgICAgfQoKICAgICAgICAgICAgICAgIGlmICgkY2h1bmtJbmRleCA9PSAkY2h1bmtUb3RhbCAtIDEpIHsKICAgICAgICAgICAgICAgICAgICByZW5hbWUoInskZnVsbFBhdGh9LnBhcnQiLCAkZnVsbFBhdGgpOwogICAgICAgICAgICAgICAgfQoKICAgICAgICAgICAgfSBlbHNlIGlmIChtb3ZlX3VwbG9hZGVkX2ZpbGUoJHRtcF9uYW1lLCAkZnVsbFBhdGgpKSB7CiAgICAgICAgICAgICAgICAvLyBCZSBzdXJlIHRoYXQgdGhlIGZpbGUgaGFzIGJlZW4gdXBsb2FkZWQKICAgICAgICAgICAgICAgIGlmICggZmlsZV9leGlzdHMoJGZ1bGxQYXRoKSApIHsKICAgICAgICAgICAgICAgICAgICAkcmVzcG9uc2UgPSBhcnJheSAoCiAgICAgICAgICAgICAgICAgICAgICAgICdzdGF0dXMnICAgID0+ICdzdWNjZXNzJywKICAgICAgICAgICAgICAgICAgICAgICAgJ2luZm8nID0+ICJmaWxlIHVwbG9hZCBzdWNjZXNzZnVsIgogICAgICAgICAgICAgICAgICAgICk7CiAgICAgICAgICAgICAgICB9IGVsc2UgewogICAgICAgICAgICAgICAgICAgICRyZXNwb25zZSA9IGFycmF5ICgKICAgICAgICAgICAgICAgICAgICAgICAgJ3N0YXR1cycgPT4gJ2Vycm9yJywKICAgICAgICAgICAgICAgICAgICAgICAgJ2luZm8nICAgPT4gJ0NvdWxkblwndCB1cGxvYWQgdGhlIHJlcXVlc3RlZCBmaWxlLicKICAgICAgICAgICAgICAgICAgICApOwogICAgICAgICAgICAgICAgfQogICAgICAgICAgICB9IGVsc2UgewogICAgICAgICAgICAgICAgJHJlc3BvbnNlID0gYXJyYXkgKAogICAgICAgICAgICAgICAgICAgICdzdGF0dXMnICAgID0+ICdlcnJvcicsCiAgICAgICAgICAgICAgICAgICAgJ2luZm8nICAgICAgPT4gIkVycm9yIHdoaWxlIHVwbG9hZGluZyBmaWxlcy4gVXBsb2FkZWQgZmlsZXMgJHVwbG9hZHMiLAogICAgICAgICAgICAgICAgKTsKICAgICAgICAgICAgfQogICAgICAgIH0KICAgIH0gZWxzZSB7CiAgICAgICAgJHJlc3BvbnNlID0gYXJyYXkgKAogICAgICAgICAgICAnc3RhdHVzJyA9PiAnZXJyb3InLAogICAgICAgICAgICAnaW5mbycgICA9PiAnVGhlIHNwZWNpZmllZCBmb2xkZXIgZm9yIHVwbG9hZCBpc25cJ3Qgd3JpdGVhYmxlLicKICAgICAgICApOwogICAgfQogICAgLy8gUmV0dXJuIHRoZSByZXNwb25zZQogICAgZWNobyBqc29uX2VuY29kZSgkcmVzcG9uc2UpOwogICAgZXhpdCgpOwp9CgovLyBNYXNzIGRlbGV0aW5nCmlmIChpc3NldCgkX1BPU1RbJ2dyb3VwJ10sICRfUE9TVFsnZGVsZXRlJ10sICRfUE9TVFsndG9rZW4nXSkgJiYgIUZNX1JFQURPTkxZKSB7CgogICAgaWYoIXZlcmlmeVRva2VuKCRfUE9TVFsndG9rZW4nXSkpIHsKICAgICAgICBmbV9zZXRfbXNnKGxuZygiSW52YWxpZCBUb2tlbi4iKSwgJ2Vycm9yJyk7CiAgICB9CgogICAgJHBhdGggPSBGTV9ST09UX1BBVEg7CiAgICBpZiAoRk1fUEFUSCAhPSAnJykgewogICAgICAgICRwYXRoIC49ICcvJyAuIEZNX1BBVEg7CiAgICB9CgogICAgJGVycm9ycyA9IDA7CiAgICAkZmlsZXMgPSAkX1BPU1RbJ2ZpbGUnXTsKICAgIGlmIChpc19hcnJheSgkZmlsZXMpICYmIGNvdW50KCRmaWxlcykpIHsKICAgICAgICBmb3JlYWNoICgkZmlsZXMgYXMgJGYpIHsKICAgICAgICAgICAgaWYgKCRmICE9ICcnKSB7CiAgICAgICAgICAgICAgICAkbmV3X3BhdGggPSBmbV9jbGVhbl9wYXRoKCRwYXRoIC4gJy8nIC4gJGYpOwogICAgICAgICAgICAgICAgaWYgKCFmbV9yZGVsZXRlKCRuZXdfcGF0aCkpIHsKICAgICAgICAgICAgICAgICAgICAkZXJyb3JzKys7CiAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgIH0KICAgICAgICB9CiAgICAgICAgaWYgKCRlcnJvcnMgPT0gMCkgewogICAgICAgICAgICBmbV9zZXRfbXNnKGxuZygnU2VsZWN0ZWQgZmlsZXMgYW5kIGZvbGRlciBkZWxldGVkJykpOwogICAgICAgIH0gZWxzZSB7CiAgICAgICAgICAgIGZtX3NldF9tc2cobG5nKCdFcnJvciB3aGlsZSBkZWxldGluZyBpdGVtcycpLCAnZXJyb3InKTsKICAgICAgICB9CiAgICB9IGVsc2UgewogICAgICAgIGZtX3NldF9tc2cobG5nKCdOb3RoaW5nIHNlbGVjdGVkJyksICdhbGVydCcpOwogICAgfQoKICAgICRGTV9QQVRIPUZNX1BBVEg7IGZtX3JlZGlyZWN0KEZNX1NFTEZfVVJMIC4gJz9wPScgLiB1cmxlbmNvZGUoJEZNX1BBVEgpKTsKfQoKLy8gUGFjayBmaWxlcyB6aXAsIHRhcgppZiAoaXNzZXQoJF9QT1NUWydncm91cCddLCAkX1BPU1RbJ3Rva2VuJ10pICYmIChpc3NldCgkX1BPU1RbJ3ppcCddKSB8fCBpc3NldCgkX1BPU1RbJ3RhciddKSkgJiYgIUZNX1JFQURPTkxZKSB7CgogICAgaWYoIXZlcmlmeVRva2VuKCRfUE9TVFsndG9rZW4nXSkpIHsKICAgICAgICBmbV9zZXRfbXNnKGxuZygiSW52YWxpZCBUb2tlbi4iKSwgJ2Vycm9yJyk7CiAgICB9CgogICAgJHBhdGggPSBGTV9ST09UX1BBVEg7CiAgICAkZXh0ID0gJ3ppcCc7CiAgICBpZiAoRk1fUEFUSCAhPSAnJykgewogICAgICAgICRwYXRoIC49ICcvJyAuIEZNX1BBVEg7CiAgICB9CgogICAgLy9zZXQgcGFjayB0eXBlCiAgICAkZXh0ID0gaXNzZXQoJF9QT1NUWyd0YXInXSkgPyAndGFyJyA6ICd6aXAnOwoKICAgIGlmICgoJGV4dCA9PSAiemlwIiAmJiAhY2xhc3NfZXhpc3RzKCdaaXBBcmNoaXZlJykpIHx8ICgkZXh0ID09ICJ0YXIiICYmICFjbGFzc19leGlzdHMoJ1BoYXJEYXRhJykpKSB7CiAgICAgICAgZm1fc2V0X21zZyhsbmcoJ09wZXJhdGlvbnMgd2l0aCBhcmNoaXZlcyBhcmUgbm90IGF2YWlsYWJsZScpLCAnZXJyb3InKTsKICAgICAgICAkRk1fUEFUSD1GTV9QQVRIOyBmbV9yZWRpcmVjdChGTV9TRUxGX1VSTCAuICc/cD0nIC4gdXJsZW5jb2RlKCRGTV9QQVRIKSk7CiAgICB9CgogICAgJGZpbGVzID0gJF9QT1NUWydmaWxlJ107CiAgICBpZiAoIWVtcHR5KCRmaWxlcykpIHsKICAgICAgICBjaGRpcigkcGF0aCk7CgogICAgICAgIGlmIChjb3VudCgkZmlsZXMpID09IDEpIHsKICAgICAgICAgICAgJG9uZV9maWxlID0gcmVzZXQoJGZpbGVzKTsKICAgICAgICAgICAgJG9uZV9maWxlID0gYmFzZW5hbWUoJG9uZV9maWxlKTsKICAgICAgICAgICAgJHppcG5hbWUgPSAkb25lX2ZpbGUgLiAnXycgLiBkYXRlKCd5bWRfSGlzJykgLiAnLicuJGV4dDsKICAgICAgICB9IGVsc2UgewogICAgICAgICAgICAkemlwbmFtZSA9ICdhcmNoaXZlXycgLiBkYXRlKCd5bWRfSGlzJykgLiAnLicuJGV4dDsKICAgICAgICB9CgogICAgICAgIGlmKCRleHQgPT0gJ3ppcCcpIHsKICAgICAgICAgICAgJHppcHBlciA9IG5ldyBGTV9aaXBwZXIoKTsKICAgICAgICAgICAgJHJlcyA9ICR6aXBwZXItPmNyZWF0ZSgkemlwbmFtZSwgJGZpbGVzKTsKICAgICAgICB9IGVsc2VpZiAoJGV4dCA9PSAndGFyJykgewogICAgICAgICAgICAkdGFyID0gbmV3IEZNX1ppcHBlcl9UYXIoKTsKICAgICAgICAgICAgJHJlcyA9ICR0YXItPmNyZWF0ZSgkemlwbmFtZSwgJGZpbGVzKTsKICAgICAgICB9CgogICAgICAgIGlmICgkcmVzKSB7CiAgICAgICAgICAgIGZtX3NldF9tc2coc3ByaW50ZihsbmcoJ0FyY2hpdmUnKS4nIDxiPiVzPC9iPiAnLmxuZygnQ3JlYXRlZCcpLCBmbV9lbmMoJHppcG5hbWUpKSk7CiAgICAgICAgfSBlbHNlIHsKICAgICAgICAgICAgZm1fc2V0X21zZyhsbmcoJ0FyY2hpdmUgbm90IGNyZWF0ZWQnKSwgJ2Vycm9yJyk7CiAgICAgICAgfQogICAgfSBlbHNlIHsKICAgICAgICBmbV9zZXRfbXNnKGxuZygnTm90aGluZyBzZWxlY3RlZCcpLCAnYWxlcnQnKTsKICAgIH0KCiAgICAkRk1fUEFUSD1GTV9QQVRIOyBmbV9yZWRpcmVjdChGTV9TRUxGX1VSTCAuICc/cD0nIC4gdXJsZW5jb2RlKCRGTV9QQVRIKSk7Cn0KCi8vIFVucGFjayB6aXAsIHRhcgppZiAoaXNzZXQoJF9QT1NUWyd1bnppcCddLCAkX1BPU1RbJ3Rva2VuJ10pICYmICFGTV9SRUFET05MWSkgewoKICAgIGlmKCF2ZXJpZnlUb2tlbigkX1BPU1RbJ3Rva2VuJ10pKSB7CiAgICAgICAgZm1fc2V0X21zZyhsbmcoIkludmFsaWQgVG9rZW4uIiksICdlcnJvcicpOwogICAgfQoKICAgICR1bnppcCA9ICRfUE9TVFsndW56aXAnXTsKICAgICR1bnppcCA9IGZtX2NsZWFuX3BhdGgoJHVuemlwKTsKICAgICR1bnppcCA9IHN0cl9yZXBsYWNlKCcvJywgJycsICR1bnppcCk7CiAgICAkaXNWYWxpZCA9IGZhbHNlOwoKICAgICRwYXRoID0gRk1fUk9PVF9QQVRIOwogICAgaWYgKEZNX1BBVEggIT0gJycpIHsKICAgICAgICAkcGF0aCAuPSAnLycgLiBGTV9QQVRIOwogICAgfQoKICAgIGlmICgkdW56aXAgIT0gJycgJiYgaXNfZmlsZSgkcGF0aCAuICcvJyAuICR1bnppcCkpIHsKICAgICAgICAkemlwX3BhdGggPSAkcGF0aCAuICcvJyAuICR1bnppcDsKICAgICAgICAkZXh0ID0gcGF0aGluZm8oJHppcF9wYXRoLCBQQVRISU5GT19FWFRFTlNJT04pOwogICAgICAgICRpc1ZhbGlkID0gdHJ1ZTsKICAgIH0gZWxzZSB7CiAgICAgICAgZm1fc2V0X21zZyhsbmcoJ0ZpbGUgbm90IGZvdW5kJyksICdlcnJvcicpOwogICAgfQoKICAgIGlmICgoJGV4dCA9PSAiemlwIiAmJiAhY2xhc3NfZXhpc3RzKCdaaXBBcmNoaXZlJykpIHx8ICgkZXh0ID09ICJ0YXIiICYmICFjbGFzc19leGlzdHMoJ1BoYXJEYXRhJykpKSB7CiAgICAgICAgZm1fc2V0X21zZyhsbmcoJ09wZXJhdGlvbnMgd2l0aCBhcmNoaXZlcyBhcmUgbm90IGF2YWlsYWJsZScpLCAnZXJyb3InKTsKICAgICAgICAkRk1fUEFUSD1GTV9QQVRIOyBmbV9yZWRpcmVjdChGTV9TRUxGX1VSTCAuICc/cD0nIC4gdXJsZW5jb2RlKCRGTV9QQVRIKSk7CiAgICB9CgogICAgaWYgKCRpc1ZhbGlkKSB7CiAgICAgICAgLy90byBmb2xkZXIKICAgICAgICAkdG9mb2xkZXIgPSAnJzsKICAgICAgICBpZiAoaXNzZXQoJF9QT1NUWyd0b2ZvbGRlciddKSkgewogICAgICAgICAgICAkdG9mb2xkZXIgPSBwYXRoaW5mbygkemlwX3BhdGgsIFBBVEhJTkZPX0ZJTEVOQU1FKTsKICAgICAgICAgICAgaWYgKGZtX21rZGlyKCRwYXRoIC4gJy8nIC4gJHRvZm9sZGVyLCB0cnVlKSkgewogICAgICAgICAgICAgICAgJHBhdGggLj0gJy8nIC4gJHRvZm9sZGVyOwogICAgICAgICAgICB9CiAgICAgICAgfQoKICAgICAgICBpZigkZXh0ID09ICJ6aXAiKSB7CiAgICAgICAgICAgICR6aXBwZXIgPSBuZXcgRk1fWmlwcGVyKCk7CiAgICAgICAgICAgICRyZXMgPSAkemlwcGVyLT51bnppcCgkemlwX3BhdGgsICRwYXRoKTsKICAgICAgICB9IGVsc2VpZiAoJGV4dCA9PSAidGFyIikgewogICAgICAgICAgICB0cnkgewogICAgICAgICAgICAgICAgJGd6aXBwZXIgPSBuZXcgUGhhckRhdGEoJHppcF9wYXRoKTsKICAgICAgICAgICAgICAgIGlmIChAJGd6aXBwZXItPmV4dHJhY3RUbygkcGF0aCxudWxsLCB0cnVlKSkgewogICAgICAgICAgICAgICAgICAgICRyZXMgPSB0cnVlOwogICAgICAgICAgICAgICAgfSBlbHNlIHsKICAgICAgICAgICAgICAgICAgICAkcmVzID0gZmFsc2U7CiAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgIH0gY2F0Y2ggKEV4Y2VwdGlvbiAkZSkgewogICAgICAgICAgICAgICAgLy9UT0RPOjogbmVlZCB0byBoYW5kbGUgdGhlIGVycm9yCiAgICAgICAgICAgICAgICAkcmVzID0gdHJ1ZTsKICAgICAgICAgICAgfQogICAgICAgIH0KCiAgICAgICAgaWYgKCRyZXMpIHsKICAgICAgICAgICAgZm1fc2V0X21zZyhsbmcoJ0FyY2hpdmUgdW5wYWNrZWQnKSk7CiAgICAgICAgfSBlbHNlIHsKICAgICAgICAgICAgZm1fc2V0X21zZyhsbmcoJ0FyY2hpdmUgbm90IHVucGFja2VkJyksICdlcnJvcicpOwogICAgICAgIH0KICAgIH0gZWxzZSB7CiAgICAgICAgZm1fc2V0X21zZyhsbmcoJ0ZpbGUgbm90IGZvdW5kJyksICdlcnJvcicpOwogICAgfQogICAgJEZNX1BBVEg9Rk1fUEFUSDsgZm1fcmVkaXJlY3QoRk1fU0VMRl9VUkwgLiAnP3A9JyAuIHVybGVuY29kZSgkRk1fUEFUSCkpOwp9CgovLyBDaGFuZ2UgUGVybXMgKG5vdCBmb3IgV2luZG93cykKaWYgKGlzc2V0KCRfUE9TVFsnY2htb2QnXSwgJF9QT1NUWyd0b2tlbiddKSAmJiAhRk1fUkVBRE9OTFkgJiYgIUZNX0lTX1dJTikgewoKICAgIGlmKCF2ZXJpZnlUb2tlbigkX1BPU1RbJ3Rva2VuJ10pKSB7CiAgICAgICAgZm1fc2V0X21zZyhsbmcoIkludmFsaWQgVG9rZW4uIiksICdlcnJvcicpOwogICAgfQogICAgCiAgICAkcGF0aCA9IEZNX1JPT1RfUEFUSDsKICAgIGlmIChGTV9QQVRIICE9ICcnKSB7CiAgICAgICAgJHBhdGggLj0gJy8nIC4gRk1fUEFUSDsKICAgIH0KCiAgICAkZmlsZSA9ICRfUE9TVFsnY2htb2QnXTsKICAgICRmaWxlID0gZm1fY2xlYW5fcGF0aCgkZmlsZSk7CiAgICAkZmlsZSA9IHN0cl9yZXBsYWNlKCcvJywgJycsICRmaWxlKTsKICAgIGlmICgkZmlsZSA9PSAnJyB8fCAoIWlzX2ZpbGUoJHBhdGggLiAnLycgLiAkZmlsZSkgJiYgIWlzX2RpcigkcGF0aCAuICcvJyAuICRmaWxlKSkpIHsKICAgICAgICBmbV9zZXRfbXNnKGxuZygnRmlsZSBub3QgZm91bmQnKSwgJ2Vycm9yJyk7CiAgICAgICAgJEZNX1BBVEg9Rk1fUEFUSDsgZm1fcmVkaXJlY3QoRk1fU0VMRl9VUkwgLiAnP3A9JyAuIHVybGVuY29kZSgkRk1fUEFUSCkpOwogICAgfQoKICAgICRtb2RlID0gMDsKICAgIGlmICghZW1wdHkoJF9QT1NUWyd1ciddKSkgewogICAgICAgICRtb2RlIHw9IDA0MDA7CiAgICB9CiAgICBpZiAoIWVtcHR5KCRfUE9TVFsndXcnXSkpIHsKICAgICAgICAkbW9kZSB8PSAwMjAwOwogICAgfQogICAgaWYgKCFlbXB0eSgkX1BPU1RbJ3V4J10pKSB7CiAgICAgICAgJG1vZGUgfD0gMDEwMDsKICAgIH0KICAgIGlmICghZW1wdHkoJF9QT1NUWydnciddKSkgewogICAgICAgICRtb2RlIHw9IDAwNDA7CiAgICB9CiAgICBpZiAoIWVtcHR5KCRfUE9TVFsnZ3cnXSkpIHsKICAgICAgICAkbW9kZSB8PSAwMDIwOwogICAgfQogICAgaWYgKCFlbXB0eSgkX1BPU1RbJ2d4J10pKSB7CiAgICAgICAgJG1vZGUgfD0gMDAxMDsKICAgIH0KICAgIGlmICghZW1wdHkoJF9QT1NUWydvciddKSkgewogICAgICAgICRtb2RlIHw9IDAwMDQ7CiAgICB9CiAgICBpZiAoIWVtcHR5KCRfUE9TVFsnb3cnXSkpIHsKICAgICAgICAkbW9kZSB8PSAwMDAyOwogICAgfQogICAgaWYgKCFlbXB0eSgkX1BPU1RbJ294J10pKSB7CiAgICAgICAgJG1vZGUgfD0gMDAwMTsKICAgIH0KCiAgICBpZiAoQGNobW9kKCRwYXRoIC4gJy8nIC4gJGZpbGUsICRtb2RlKSkgewogICAgICAgIGZtX3NldF9tc2cobG5nKCdQZXJtaXNzaW9ucyBjaGFuZ2VkJykpOwogICAgfSBlbHNlIHsKICAgICAgICBmbV9zZXRfbXNnKGxuZygnUGVybWlzc2lvbnMgbm90IGNoYW5nZWQnKSwgJ2Vycm9yJyk7CiAgICB9CgogICAgJEZNX1BBVEg9Rk1fUEFUSDsgZm1fcmVkaXJlY3QoRk1fU0VMRl9VUkwgLiAnP3A9JyAuIHVybGVuY29kZSgkRk1fUEFUSCkpOwp9CgovKioqKioqKioqKioqKioqKioqKioqKioqKioqIEFDVElPTlMgKioqKioqKioqKioqKioqKioqKioqKioqKioqLwoKLy8gZ2V0IGN1cnJlbnQgcGF0aAokcGF0aCA9IEZNX1JPT1RfUEFUSDsKaWYgKEZNX1BBVEggIT0gJycpIHsKICAgICRwYXRoIC49ICcvJyAuIEZNX1BBVEg7Cn0KCi8vIGNoZWNrIHBhdGgKaWYgKCFpc19kaXIoJHBhdGgpKSB7CiAgICBmbV9yZWRpcmVjdChGTV9TRUxGX1VSTCAuICc/cD0nKTsKfQoKLy8gZ2V0IHBhcmVudCBmb2xkZXIKJHBhcmVudCA9IGZtX2dldF9wYXJlbnRfcGF0aChGTV9QQVRIKTsKCiRvYmplY3RzID0gaXNfcmVhZGFibGUoJHBhdGgpID8gc2NhbmRpcigkcGF0aCkgOiBhcnJheSgpOwokZm9sZGVycyA9IGFycmF5KCk7CiRmaWxlcyA9IGFycmF5KCk7CiRjdXJyZW50X3BhdGggPSBhcnJheV9zbGljZShleHBsb2RlKCIvIiwkcGF0aCksIC0xKVswXTsKaWYgKGlzX2FycmF5KCRvYmplY3RzKSAmJiBmbV9pc19leGNsdWRlX2l0ZW1zKCRjdXJyZW50X3BhdGgpKSB7CiAgICBmb3JlYWNoICgkb2JqZWN0cyBhcyAkZmlsZSkgewogICAgICAgIGlmICgkZmlsZSA9PSAnLicgfHwgJGZpbGUgPT0gJy4uJykgewogICAgICAgICAgICBjb250aW51ZTsKICAgICAgICB9CiAgICAgICAgaWYgKCFGTV9TSE9XX0hJRERFTiAmJiBzdWJzdHIoJGZpbGUsIDAsIDEpID09PSAnLicpIHsKICAgICAgICAgICAgY29udGludWU7CiAgICAgICAgfQogICAgICAgICRuZXdfcGF0aCA9ICRwYXRoIC4gJy8nIC4gJGZpbGU7CiAgICAgICAgaWYgKEBpc19maWxlKCRuZXdfcGF0aCkgJiYgZm1faXNfZXhjbHVkZV9pdGVtcygkZmlsZSkpIHsKICAgICAgICAgICAgJGZpbGVzW10gPSAkZmlsZTsKICAgICAgICB9IGVsc2VpZiAoQGlzX2RpcigkbmV3X3BhdGgpICYmICRmaWxlICE9ICcuJyAmJiAkZmlsZSAhPSAnLi4nICYmIGZtX2lzX2V4Y2x1ZGVfaXRlbXMoJGZpbGUpKSB7CiAgICAgICAgICAgICRmb2xkZXJzW10gPSAkZmlsZTsKICAgICAgICB9CiAgICB9Cn0KCmlmICghZW1wdHkoJGZpbGVzKSkgewogICAgbmF0Y2FzZXNvcnQoJGZpbGVzKTsKfQppZiAoIWVtcHR5KCRmb2xkZXJzKSkgewogICAgbmF0Y2FzZXNvcnQoJGZvbGRlcnMpOwp9CgovLyB1cGxvYWQgZm9ybQppZiAoaXNzZXQoJF9HRVRbJ3VwbG9hZCddKSAmJiAhRk1fUkVBRE9OTFkpIHsKICAgIGZtX3Nob3dfaGVhZGVyKCk7IC8vIEhFQURFUgogICAgZm1fc2hvd19uYXZfcGF0aChGTV9QQVRIKTsgLy8gY3VycmVudCBwYXRoCiAgICAvL2dldCB0aGUgYWxsb3dlZCBmaWxlIGV4dGVuc2lvbnMKICAgIGZ1bmN0aW9uIGdldFVwbG9hZEV4dCgpIHsKICAgICAgICAkZXh0QXJyID0gZXhwbG9kZSgnLCcsIEZNX1VQTE9BRF9FWFRFTlNJT04pOwogICAgICAgIGlmKEZNX1VQTE9BRF9FWFRFTlNJT04gJiYgJGV4dEFycikgewogICAgICAgICAgICBhcnJheV93YWxrKCRleHRBcnIsIGZ1bmN0aW9uKCYkeCkgeyR4ID0gIi4keCI7fSk7CiAgICAgICAgICAgIHJldHVybiBpbXBsb2RlKCcsJywgJGV4dEFycik7CiAgICAgICAgfQogICAgICAgIHJldHVybiAnJzsKICAgIH0KICAgID8+CiAgICA8bGluayBocmVmPSJodHRwczovL2NkbmpzLmNsb3VkZmxhcmUuY29tL2FqYXgvbGlicy9kcm9wem9uZS81LjkuMy9taW4vZHJvcHpvbmUubWluLmNzcyIgcmVsPSJzdHlsZXNoZWV0Ij4KICAgIDxkaXYgY2xhc3M9InBhdGgiPgoKICAgICAgICA8ZGl2IGNsYXNzPSJjYXJkIG1iLTIgZm0tdXBsb2FkLXdyYXBwZXIgPD9waHAgZWNobyBmbV9nZXRfdGhlbWUoKTsgPz4iPgogICAgICAgICAgICA8ZGl2IGNsYXNzPSJjYXJkLWhlYWRlciI+CiAgICAgICAgICAgICAgICA8dWwgY2xhc3M9Im5hdiBuYXYtdGFicyBjYXJkLWhlYWRlci10YWJzIj4KICAgICAgICAgICAgICAgICAgICA8bGkgY2xhc3M9Im5hdi1pdGVtIj4KICAgICAgICAgICAgICAgICAgICAgICAgPGEgY2xhc3M9Im5hdi1saW5rIGFjdGl2ZSIgaHJlZj0iI2ZpbGVVcGxvYWRlciIgZGF0YS10YXJnZXQ9IiNmaWxlVXBsb2FkZXIiPjxpIGNsYXNzPSJmYSBmYS1hcnJvdy1jaXJjbGUtby11cCI+PC9pPiA8P3BocCBlY2hvIGxuZygnVXBsb2FkaW5nRmlsZXMnKSA/PjwvYT4KICAgICAgICAgICAgICAgICAgICA8L2xpPgogICAgICAgICAgICAgICAgICAgIDxsaSBjbGFzcz0ibmF2LWl0ZW0iPgogICAgICAgICAgICAgICAgICAgICAgICA8YSBjbGFzcz0ibmF2LWxpbmsiIGhyZWY9IiN1cmxVcGxvYWRlciIgY2xhc3M9ImpzLXVybC11cGxvYWQiIGRhdGEtdGFyZ2V0PSIjdXJsVXBsb2FkZXIiPjxpIGNsYXNzPSJmYSBmYS1saW5rIj48L2k+IFVwbG9hZCBmcm9tIFVSTDwvYT4KICAgICAgICAgICAgICAgICAgICA8L2xpPgogICAgICAgICAgICAgICAgPC91bD4KICAgICAgICAgICAgPC9kaXY+CiAgICAgICAgICAgIDxkaXYgY2xhc3M9ImNhcmQtYm9keSI+CiAgICAgICAgICAgICAgICA8cCBjbGFzcz0iY2FyZC10ZXh0Ij4KICAgICAgICAgICAgICAgICAgICA8YSBocmVmPSI/cD08P3BocCBlY2hvIEZNX1BBVEggPz4iIGNsYXNzPSJmbG9hdC1yaWdodCI+PGkgY2xhc3M9ImZhIGZhLWNoZXZyb24tY2lyY2xlLWxlZnQgZ28tYmFjayI+PC9pPiA8P3BocCBlY2hvIGxuZygnQmFjaycpPz48L2E+CiAgICAgICAgICAgICAgICAgICAgPHN0cm9uZz48P3BocCBlY2hvIGxuZygnRGVzdGluYXRpb25Gb2xkZXInKSA/Pjwvc3Ryb25nPjogPD9waHAgZWNobyBmbV9lbmMoZm1fY29udmVydF93aW4oRk1fUEFUSCkpID8+CiAgICAgICAgICAgICAgICA8L3A+CgogICAgICAgICAgICAgICAgPGZvcm0gYWN0aW9uPSI8P3BocCBlY2hvIGh0bWxzcGVjaWFsY2hhcnMoRk1fU0VMRl9VUkwpIC4gJz9wPScgLiBmbV9lbmMoRk1fUEFUSCkgPz4iIGNsYXNzPSJkcm9wem9uZSBjYXJkLXRhYnMtY29udGFpbmVyIiBpZD0iZmlsZVVwbG9hZGVyIiBlbmN0eXBlPSJtdWx0aXBhcnQvZm9ybS1kYXRhIj4KICAgICAgICAgICAgICAgICAgICA8aW5wdXQgdHlwZT0iaGlkZGVuIiBuYW1lPSJwIiB2YWx1ZT0iPD9waHAgZWNobyBmbV9lbmMoRk1fUEFUSCkgPz4iPgogICAgICAgICAgICAgICAgICAgIDxpbnB1dCB0eXBlPSJoaWRkZW4iIG5hbWU9ImZ1bGxwYXRoIiBpZD0iZnVsbHBhdGgiIHZhbHVlPSI8P3BocCBlY2hvIGZtX2VuYyhGTV9QQVRIKSA/PiI+CiAgICAgICAgICAgICAgICAgICAgPGlucHV0IHR5cGU9ImhpZGRlbiIgbmFtZT0idG9rZW4iIHZhbHVlPSI8P3BocCBlY2hvICRfU0VTU0lPTlsndG9rZW4nXTsgPz4iPgogICAgICAgICAgICAgICAgICAgIDxkaXYgY2xhc3M9ImZhbGxiYWNrIj4KICAgICAgICAgICAgICAgICAgICAgICAgPGlucHV0IG5hbWU9ImZpbGUiIHR5cGU9ImZpbGUiIG11bHRpcGxlLz4KICAgICAgICAgICAgICAgICAgICA8L2Rpdj4KICAgICAgICAgICAgICAgIDwvZm9ybT4KCiAgICAgICAgICAgICAgICA8ZGl2IGNsYXNzPSJ1cGxvYWQtdXJsLXdyYXBwZXIgY2FyZC10YWJzLWNvbnRhaW5lciBoaWRkZW4iIGlkPSJ1cmxVcGxvYWRlciI+CiAgICAgICAgICAgICAgICAgICAgPGZvcm0gaWQ9ImpzLWZvcm0tdXJsLXVwbG9hZCIgY2xhc3M9InJvdyByb3ctY29scy1sZy1hdXRvIGctMyBhbGlnbi1pdGVtcy1jZW50ZXIiIG9uc3VibWl0PSJyZXR1cm4gdXBsb2FkX2Zyb21fdXJsKHRoaXMpOyIgbWV0aG9kPSJQT1NUIiBhY3Rpb249IiI+CiAgICAgICAgICAgICAgICAgICAgICAgIDxpbnB1dCB0eXBlPSJoaWRkZW4iIG5hbWU9InR5cGUiIHZhbHVlPSJ1cGxvYWQiIGFyaWEtbGFiZWw9ImhpZGRlbiIgYXJpYS1oaWRkZW49InRydWUiPgogICAgICAgICAgICAgICAgICAgICAgICA8aW5wdXQgdHlwZT0idXJsIiBwbGFjZWhvbGRlcj0iVVJMIiBuYW1lPSJ1cGxvYWR1cmwiIHJlcXVpcmVkIGNsYXNzPSJmb3JtLWNvbnRyb2wiIHN0eWxlPSJ3aWR0aDogODAlIj4KICAgICAgICAgICAgICAgICAgICAgICAgPGlucHV0IHR5cGU9ImhpZGRlbiIgbmFtZT0idG9rZW4iIHZhbHVlPSI8P3BocCBlY2hvICRfU0VTU0lPTlsndG9rZW4nXTsgPz4iPgogICAgICAgICAgICAgICAgICAgICAgICA8YnV0dG9uIHR5cGU9InN1Ym1pdCIgY2xhc3M9ImJ0biBidG4tcHJpbWFyeSBtcy0zIj48P3BocCBlY2hvIGxuZygnVXBsb2FkJykgPz48L2J1dHRvbj4KICAgICAgICAgICAgICAgICAgICAgICAgPGRpdiBjbGFzcz0ibGRzLWZhY2Vib29rIj48ZGl2PjwvZGl2PjxkaXY+PC9kaXY+PGRpdj48L2Rpdj48L2Rpdj4KICAgICAgICAgICAgICAgICAgICA8L2Zvcm0+CiAgICAgICAgICAgICAgICAgICAgPGRpdiBpZD0ianMtdXJsLXVwbG9hZF9fbGlzdCIgY2xhc3M9ImNvbC05IG10LTMiPjwvZGl2PgogICAgICAgICAgICAgICAgPC9kaXY+CiAgICAgICAgICAgIDwvZGl2PgogICAgICAgIDwvZGl2PgogICAgPC9kaXY+CiAgICA8c2NyaXB0IHNyYz0iaHR0cHM6Ly9jZG5qcy5jbG91ZGZsYXJlLmNvbS9hamF4L2xpYnMvZHJvcHpvbmUvNS45LjMvbWluL2Ryb3B6b25lLm1pbi5qcyI+PC9zY3JpcHQ+CiAgICA8c2NyaXB0PgogICAgICAgIERyb3B6b25lLm9wdGlvbnMuZmlsZVVwbG9hZGVyID0gewogICAgICAgICAgICBjaHVua2luZzogdHJ1ZSwKICAgICAgICAgICAgY2h1bmtTaXplOiAxMDAwMDAwMCwKICAgICAgICAgICAgZm9yY2VDaHVua2luZzogdHJ1ZSwKICAgICAgICAgICAgcmV0cnlDaHVua3M6IHRydWUsCiAgICAgICAgICAgIHJldHJ5Q2h1bmtzTGltaXQ6IDMsCiAgICAgICAgICAgIHBhcmFsbGVsVXBsb2FkczogMiwKICAgICAgICAgICAgcGFyYWxsZWxDaHVua1VwbG9hZHM6IHRydWUsCiAgICAgICAgICAgIHRpbWVvdXQ6IDEyMDAwMCwKICAgICAgICAgICAgbWF4RmlsZXNpemU6ICI8P3BocCBlY2hvIE1BWF9VUExPQURfU0laRTsgPz4iLAogICAgICAgICAgICBhY2NlcHRlZEZpbGVzIDogIjw/cGhwIGVjaG8gZ2V0VXBsb2FkRXh0KCkgPz4iLAogICAgICAgICAgICBpbml0OiBmdW5jdGlvbiAoKSB7CiAgICAgICAgICAgICAgICB0aGlzLm9uKCJzZW5kaW5nIiwgZnVuY3Rpb24gKGZpbGUsIHhociwgZm9ybURhdGEpIHsKICAgICAgICAgICAgICAgICAgICBsZXQgX3BhdGggPSAoZmlsZS5mdWxsUGF0aCkgPyBmaWxlLmZ1bGxQYXRoIDogZmlsZS5uYW1lOwogICAgICAgICAgICAgICAgICAgIGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKCJmdWxscGF0aCIpLnZhbHVlID0gX3BhdGg7CiAgICAgICAgICAgICAgICAgICAgeGhyLm9udGltZW91dCA9IChmdW5jdGlvbigpIHsKICAgICAgICAgICAgICAgICAgICAgICAgdG9hc3QoJ0Vycm9yOiBTZXJ2ZXIgVGltZW91dCcpOwogICAgICAgICAgICAgICAgICAgIH0pOwogICAgICAgICAgICAgICAgfSkub24oInN1Y2Nlc3MiLCBmdW5jdGlvbiAocmVzKSB7CiAgICAgICAgICAgICAgICAgICAgbGV0IF9yZXNwb25zZSA9IEpTT04ucGFyc2UocmVzLnhoci5yZXNwb25zZSk7CgogICAgICAgICAgICAgICAgICAgIGlmKF9yZXNwb25zZS5zdGF0dXMgPT0gImVycm9yIikgewogICAgICAgICAgICAgICAgICAgICAgICB0b2FzdChfcmVzcG9uc2UuaW5mbyk7CiAgICAgICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgICAgfSkub24oImVycm9yIiwgZnVuY3Rpb24oZmlsZSwgcmVzcG9uc2UpIHsKICAgICAgICAgICAgICAgICAgICB0b2FzdChyZXNwb25zZSk7CiAgICAgICAgICAgICAgICB9KTsKICAgICAgICAgICAgfQogICAgICAgIH0KICAgIDwvc2NyaXB0PgogICAgPD9waHAKICAgIGZtX3Nob3dfZm9vdGVyKCk7CiAgICBleGl0Owp9CgovLyBjb3B5IGZvcm0gUE9TVAppZiAoaXNzZXQoJF9QT1NUWydjb3B5J10pICYmICFGTV9SRUFET05MWSkgewogICAgJGNvcHlfZmlsZXMgPSBpc3NldCgkX1BPU1RbJ2ZpbGUnXSkgPyAkX1BPU1RbJ2ZpbGUnXSA6IG51bGw7CiAgICBpZiAoIWlzX2FycmF5KCRjb3B5X2ZpbGVzKSB8fCBlbXB0eSgkY29weV9maWxlcykpIHsKICAgICAgICBmbV9zZXRfbXNnKGxuZygnTm90aGluZyBzZWxlY3RlZCcpLCAnYWxlcnQnKTsKICAgICAgICAkRk1fUEFUSD1GTV9QQVRIOyBmbV9yZWRpcmVjdChGTV9TRUxGX1VSTCAuICc/cD0nIC4gdXJsZW5jb2RlKCRGTV9QQVRIKSk7CiAgICB9CgogICAgZm1fc2hvd19oZWFkZXIoKTsgLy8gSEVBREVSCiAgICBmbV9zaG93X25hdl9wYXRoKEZNX1BBVEgpOyAvLyBjdXJyZW50IHBhdGgKICAgID8+CiAgICA8ZGl2IGNsYXNzPSJwYXRoIj4KICAgICAgICA8ZGl2IGNsYXNzPSJjYXJkIDw/cGhwIGVjaG8gZm1fZ2V0X3RoZW1lKCk7ID8+Ij4KICAgICAgICAgICAgPGRpdiBjbGFzcz0iY2FyZC1oZWFkZXIiPgogICAgICAgICAgICAgICAgPGg2Pjw/cGhwIGVjaG8gbG5nKCdDb3B5aW5nJykgPz48L2g2PgogICAgICAgICAgICA8L2Rpdj4KICAgICAgICAgICAgPGRpdiBjbGFzcz0iY2FyZC1ib2R5Ij4KICAgICAgICAgICAgICAgIDxmb3JtIGFjdGlvbj0iIiBtZXRob2Q9InBvc3QiPgogICAgICAgICAgICAgICAgICAgIDxpbnB1dCB0eXBlPSJoaWRkZW4iIG5hbWU9InAiIHZhbHVlPSI8P3BocCBlY2hvIGZtX2VuYyhGTV9QQVRIKSA/PiI+CiAgICAgICAgICAgICAgICAgICAgPGlucHV0IHR5cGU9ImhpZGRlbiIgbmFtZT0iZmluaXNoIiB2YWx1ZT0iMSI+CiAgICAgICAgICAgICAgICAgICAgPD9waHAKICAgICAgICAgICAgICAgICAgICBmb3JlYWNoICgkY29weV9maWxlcyBhcyAkY2YpIHsKICAgICAgICAgICAgICAgICAgICAgICAgZWNobyAnPGlucHV0IHR5cGU9ImhpZGRlbiIgbmFtZT0iZmlsZVtdIiB2YWx1ZT0iJyAuIGZtX2VuYygkY2YpIC4gJyI+JyAuIFBIUF9FT0w7CiAgICAgICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgICAgICAgID8+CiAgICAgICAgICAgICAgICAgICAgPHAgY2xhc3M9ImJyZWFrLXdvcmQiPjxzdHJvbmc+PD9waHAgZWNobyBsbmcoJ0ZpbGVzJykgPz48L3N0cm9uZz46IDxiPjw/cGhwIGVjaG8gaW1wbG9kZSgnPC9iPiwgPGI+JywgJGNvcHlfZmlsZXMpID8+PC9iPjwvcD4KICAgICAgICAgICAgICAgICAgICA8cCBjbGFzcz0iYnJlYWstd29yZCI+PHN0cm9uZz48P3BocCBlY2hvIGxuZygnU291cmNlRm9sZGVyJykgPz48L3N0cm9uZz46IDw/cGhwIGVjaG8gZm1fZW5jKGZtX2NvbnZlcnRfd2luKEZNX1JPT1RfUEFUSCAuICcvJyAuIEZNX1BBVEgpKSA/Pjxicj4KICAgICAgICAgICAgICAgICAgICAgICAgPGxhYmVsIGZvcj0iaW5wX2NvcHlfdG8iPjxzdHJvbmc+PD9waHAgZWNobyBsbmcoJ0Rlc3RpbmF0aW9uRm9sZGVyJykgPz48L3N0cm9uZz46PC9sYWJlbD4KICAgICAgICAgICAgICAgICAgICAgICAgPD9waHAgZWNobyBGTV9ST09UX1BBVEggPz4vPGlucHV0IHR5cGU9InRleHQiIG5hbWU9ImNvcHlfdG8iIGlkPSJpbnBfY29weV90byIgdmFsdWU9Ijw/cGhwIGVjaG8gZm1fZW5jKEZNX1BBVEgpID8+Ij4KICAgICAgICAgICAgICAgICAgICA8L3A+CiAgICAgICAgICAgICAgICAgICAgPHAgY2xhc3M9ImN1c3RvbS1jaGVja2JveCBjdXN0b20tY29udHJvbCI+PGlucHV0IHR5cGU9ImNoZWNrYm94IiBuYW1lPSJtb3ZlIiB2YWx1ZT0iMSIgaWQ9ImpzLW1vdmUtZmlsZXMiIGNsYXNzPSJjdXN0b20tY29udHJvbC1pbnB1dCI+PGxhYmVsIGZvcj0ianMtbW92ZS1maWxlcyIgY2xhc3M9ImN1c3RvbS1jb250cm9sLWxhYmVsIG1zLTIiPiA8P3BocCBlY2hvIGxuZygnTW92ZScpID8+PC9sYWJlbD48L3A+CiAgICAgICAgICAgICAgICAgICAgPHA+CiAgICAgICAgICAgICAgICAgICAgICAgIDxiPjxhIGhyZWY9Ij9wPTw/cGhwIGVjaG8gdXJsZW5jb2RlKEZNX1BBVEgpID8+IiBjbGFzcz0iYnRuIGJ0bi1vdXRsaW5lLWRhbmdlciI+PGkgY2xhc3M9ImZhIGZhLXRpbWVzLWNpcmNsZSI+PC9pPiA8P3BocCBlY2hvIGxuZygnQ2FuY2VsJykgPz48L2E+PC9iPiZuYnNwOwogICAgICAgICAgICAgICAgICAgICAgICA8aW5wdXQgdHlwZT0iaGlkZGVuIiBuYW1lPSJ0b2tlbiIgdmFsdWU9Ijw/cGhwIGVjaG8gJF9TRVNTSU9OWyd0b2tlbiddOyA/PiI+CiAgICAgICAgICAgICAgICAgICAgICAgIDxidXR0b24gdHlwZT0ic3VibWl0IiBjbGFzcz0iYnRuIGJ0bi1zdWNjZXNzIj48aSBjbGFzcz0iZmEgZmEtY2hlY2stY2lyY2xlIj48L2k+IDw/cGhwIGVjaG8gbG5nKCdDb3B5JykgPz48L2J1dHRvbj4gCiAgICAgICAgICAgICAgICAgICAgPC9wPgogICAgICAgICAgICAgICAgPC9mb3JtPgogICAgICAgICAgICA8L2Rpdj4KICAgICAgICA8L2Rpdj4KICAgIDwvZGl2PgogICAgPD9waHAKICAgIGZtX3Nob3dfZm9vdGVyKCk7CiAgICBleGl0Owp9CgovLyBjb3B5IGZvcm0KaWYgKGlzc2V0KCRfR0VUWydjb3B5J10pICYmICFpc3NldCgkX0dFVFsnZmluaXNoJ10pICYmICFGTV9SRUFET05MWSkgewogICAgJGNvcHkgPSAkX0dFVFsnY29weSddOwogICAgJGNvcHkgPSBmbV9jbGVhbl9wYXRoKCRjb3B5KTsKICAgIGlmICgkY29weSA9PSAnJyB8fCAhZmlsZV9leGlzdHMoRk1fUk9PVF9QQVRIIC4gJy8nIC4gJGNvcHkpKSB7CiAgICAgICAgZm1fc2V0X21zZyhsbmcoJ0ZpbGUgbm90IGZvdW5kJyksICdlcnJvcicpOwogICAgICAgICRGTV9QQVRIPUZNX1BBVEg7IGZtX3JlZGlyZWN0KEZNX1NFTEZfVVJMIC4gJz9wPScgLiB1cmxlbmNvZGUoJEZNX1BBVEgpKTsKICAgIH0KCiAgICBmbV9zaG93X2hlYWRlcigpOyAvLyBIRUFERVIKICAgIGZtX3Nob3dfbmF2X3BhdGgoRk1fUEFUSCk7IC8vIGN1cnJlbnQgcGF0aAogICAgPz4KICAgIDxkaXYgY2xhc3M9InBhdGgiPgogICAgICAgIDxwPjxiPkNvcHlpbmc8L2I+PC9wPgogICAgICAgIDxwIGNsYXNzPSJicmVhay13b3JkIj4KICAgICAgICAgICAgPHN0cm9uZz5Tb3VyY2UgcGF0aDo8L3N0cm9uZz4gPD9waHAgZWNobyBmbV9lbmMoZm1fY29udmVydF93aW4oRk1fUk9PVF9QQVRIIC4gJy8nIC4gJGNvcHkpKSA/Pjxicj4KICAgICAgICAgICAgPHN0cm9uZz5EZXN0aW5hdGlvbiBmb2xkZXI6PC9zdHJvbmc+IDw/cGhwIGVjaG8gZm1fZW5jKGZtX2NvbnZlcnRfd2luKEZNX1JPT1RfUEFUSCAuICcvJyAuIEZNX1BBVEgpKSA/PgogICAgICAgIDwvcD4KICAgICAgICA8cD4KICAgICAgICAgICAgPGI+PGEgaHJlZj0iP3A9PD9waHAgZWNobyB1cmxlbmNvZGUoRk1fUEFUSCkgPz4mYW1wO2NvcHk9PD9waHAgZWNobyB1cmxlbmNvZGUoJGNvcHkpID8+JmFtcDtmaW5pc2g9MSI+PGkgY2xhc3M9ImZhIGZhLWNoZWNrLWNpcmNsZSI+PC9pPiBDb3B5PC9hPjwvYj4gJm5ic3A7CiAgICAgICAgICAgIDxiPjxhIGhyZWY9Ij9wPTw/cGhwIGVjaG8gdXJsZW5jb2RlKEZNX1BBVEgpID8+JmFtcDtjb3B5PTw/cGhwIGVjaG8gdXJsZW5jb2RlKCRjb3B5KSA/PiZhbXA7ZmluaXNoPTEmYW1wO21vdmU9MSI+PGkgY2xhc3M9ImZhIGZhLWNoZWNrLWNpcmNsZSI+PC9pPiBNb3ZlPC9hPjwvYj4gJm5ic3A7CiAgICAgICAgICAgIDxiPjxhIGhyZWY9Ij9wPTw/cGhwIGVjaG8gdXJsZW5jb2RlKEZNX1BBVEgpID8+IiBjbGFzcz0idGV4dC1kYW5nZXIiPjxpIGNsYXNzPSJmYSBmYS10aW1lcy1jaXJjbGUiPjwvaT4gQ2FuY2VsPC9hPjwvYj4KICAgICAgICA8L3A+CiAgICAgICAgPHA+PGk+PD9waHAgZWNobyBsbmcoJ1NlbGVjdCBmb2xkZXInKSA/PjwvaT48L3A+CiAgICAgICAgPHVsIGNsYXNzPSJmb2xkZXJzIGJyZWFrLXdvcmQiPgogICAgICAgICAgICA8P3BocAogICAgICAgICAgICBpZiAoJHBhcmVudCAhPT0gZmFsc2UpIHsKICAgICAgICAgICAgICAgID8+CiAgICAgICAgICAgICAgICA8bGk+PGEgaHJlZj0iP3A9PD9waHAgZWNobyB1cmxlbmNvZGUoJHBhcmVudCkgPz4mYW1wO2NvcHk9PD9waHAgZWNobyB1cmxlbmNvZGUoJGNvcHkpID8+Ij48aSBjbGFzcz0iZmEgZmEtY2hldnJvbi1jaXJjbGUtbGVmdCI+PC9pPiAuLjwvYT48L2xpPgogICAgICAgICAgICAgICAgPD9waHAKICAgICAgICAgICAgfQogICAgICAgICAgICBmb3JlYWNoICgkZm9sZGVycyBhcyAkZikgewogICAgICAgICAgICAgICAgPz4KICAgICAgICAgICAgICAgIDxsaT4KICAgICAgICAgICAgICAgICAgICA8YSBocmVmPSI/cD08P3BocCBlY2hvIHVybGVuY29kZSh0cmltKEZNX1BBVEggLiAnLycgLiAkZiwgJy8nKSkgPz4mYW1wO2NvcHk9PD9waHAgZWNobyB1cmxlbmNvZGUoJGNvcHkpID8+Ij48aSBjbGFzcz0iZmEgZmEtZm9sZGVyLW8iPjwvaT4gPD9waHAgZWNobyBmbV9jb252ZXJ0X3dpbigkZikgPz48L2E+PC9saT4KICAgICAgICAgICAgICAgIDw/cGhwCiAgICAgICAgICAgIH0KICAgICAgICAgICAgPz4KICAgICAgICA8L3VsPgogICAgPC9kaXY+CiAgICA8P3BocAogICAgZm1fc2hvd19mb290ZXIoKTsKICAgIGV4aXQ7Cn0KCmlmIChpc3NldCgkX0dFVFsnc2V0dGluZ3MnXSkgJiYgIUZNX1JFQURPTkxZKSB7CiAgICBmbV9zaG93X2hlYWRlcigpOyAvLyBIRUFERVIKICAgIGZtX3Nob3dfbmF2X3BhdGgoRk1fUEFUSCk7IC8vIGN1cnJlbnQgcGF0aAogICAgZ2xvYmFsICRjZmcsICRsYW5nLCAkbGFuZ19saXN0OwogICAgPz4KCiAgICA8ZGl2IGNsYXNzPSJjb2wtbWQtOCBvZmZzZXQtbWQtMiBwdC0zIj4KICAgICAgICA8ZGl2IGNsYXNzPSJjYXJkIG1iLTIgPD9waHAgZWNobyBmbV9nZXRfdGhlbWUoKTsgPz4iPgogICAgICAgICAgICA8aDYgY2xhc3M9ImNhcmQtaGVhZGVyIGQtZmxleCBqdXN0aWZ5LWNvbnRlbnQtYmV0d2VlbiI+CiAgICAgICAgICAgICAgICA8c3Bhbj48aSBjbGFzcz0iZmEgZmEtY29nIj48L2k+ICA8P3BocCBlY2hvIGxuZygnU2V0dGluZ3MnKSA/Pjwvc3Bhbj4KICAgICAgICAgICAgICAgIDxhIGhyZWY9Ij9wPTw/cGhwIGVjaG8gRk1fUEFUSCA/PiIgY2xhc3M9InRleHQtZGFuZ2VyIj48aSBjbGFzcz0iZmEgZmEtdGltZXMtY2lyY2xlLW8iPjwvaT4gPD9waHAgZWNobyBsbmcoJ0NhbmNlbCcpPz48L2E+CiAgICAgICAgICAgIDwvaDY+CiAgICAgICAgICAgIDxkaXYgY2xhc3M9ImNhcmQtYm9keSI+CiAgICAgICAgICAgICAgICA8Zm9ybSBpZD0ianMtc2V0dGluZ3MtZm9ybSIgYWN0aW9uPSIiIG1ldGhvZD0icG9zdCIgZGF0YS10eXBlPSJhamF4IiBvbnN1Ym1pdD0icmV0dXJuIHNhdmVfc2V0dGluZ3ModGhpcykiPgogICAgICAgICAgICAgICAgICAgIDxpbnB1dCB0eXBlPSJoaWRkZW4iIG5hbWU9InR5cGUiIHZhbHVlPSJzZXR0aW5ncyIgYXJpYS1sYWJlbD0iaGlkZGVuIiBhcmlhLWhpZGRlbj0idHJ1ZSI+CiAgICAgICAgICAgICAgICAgICAgPGRpdiBjbGFzcz0iZm9ybS1ncm91cCByb3ciPgogICAgICAgICAgICAgICAgICAgICAgICA8bGFiZWwgZm9yPSJqcy1sYW5ndWFnZSIgY2xhc3M9ImNvbC1zbS0zIGNvbC1mb3JtLWxhYmVsIj48P3BocCBlY2hvIGxuZygnTGFuZ3VhZ2UnKSA/PjwvbGFiZWw+CiAgICAgICAgICAgICAgICAgICAgICAgIDxkaXYgY2xhc3M9ImNvbC1zbS01Ij4KICAgICAgICAgICAgICAgICAgICAgICAgICAgIDxzZWxlY3QgY2xhc3M9ImZvcm0tc2VsZWN0IiBpZD0ianMtbGFuZ3VhZ2UiIG5hbWU9ImpzLWxhbmd1YWdlIj4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8P3BocAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGZ1bmN0aW9uIGdldFNlbGVjdGVkKCRsKSB7CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGdsb2JhbCAkbGFuZzsKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcmV0dXJuICgkbGFuZyA9PSAkbCkgPyAnc2VsZWN0ZWQnIDogJyc7CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGZvcmVhY2ggKCRsYW5nX2xpc3QgYXMgJGsgPT4gJHYpIHsKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZWNobyAiPG9wdGlvbiB2YWx1ZT0nJGsnICIuZ2V0U2VsZWN0ZWQoJGspLiI+JHY8L29wdGlvbj4iOwogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA/PgogICAgICAgICAgICAgICAgICAgICAgICAgICAgPC9zZWxlY3Q+CiAgICAgICAgICAgICAgICAgICAgICAgIDwvZGl2PgogICAgICAgICAgICAgICAgICAgIDwvZGl2PgogICAgICAgICAgICAgICAgICAgIDxkaXYgY2xhc3M9Im10LTMgbWItMyByb3cgIj4KICAgICAgICAgICAgICAgICAgICAgICAgPGxhYmVsIGZvcj0ianMtZXJyb3ItcmVwb3J0IiBjbGFzcz0iY29sLXNtLTMgY29sLWZvcm0tbGFiZWwiPjw/cGhwIGVjaG8gbG5nKCdFcnJvclJlcG9ydGluZycpID8+PC9sYWJlbD4KICAgICAgICAgICAgICAgICAgICAgICAgPGRpdiBjbGFzcz0iY29sLXNtLTkiPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgPGRpdiBjbGFzcz0iZm9ybS1jaGVjayBmb3JtLXN3aXRjaCI+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDxpbnB1dCBjbGFzcz0iZm9ybS1jaGVjay1pbnB1dCIgdHlwZT0iY2hlY2tib3giIHJvbGU9InN3aXRjaCIgaWQ9ImpzLWVycm9yLXJlcG9ydCIgbmFtZT0ianMtZXJyb3ItcmVwb3J0IiB2YWx1ZT0idHJ1ZSIgPD9waHAgZWNobyAkcmVwb3J0X2Vycm9ycyA/ICdjaGVja2VkJyA6ICcnOyA/PiAvPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgPC9kaXY+CiAgICAgICAgICAgICAgICAgICAgICAgIDwvZGl2PgogICAgICAgICAgICAgICAgICAgIDwvZGl2PgoKICAgICAgICAgICAgICAgICAgICA8ZGl2IGNsYXNzPSJtYi0zIHJvdyI+CiAgICAgICAgICAgICAgICAgICAgICAgIDxsYWJlbCBmb3I9ImpzLXNob3ctaGlkZGVuIiBjbGFzcz0iY29sLXNtLTMgY29sLWZvcm0tbGFiZWwiPjw/cGhwIGVjaG8gbG5nKCdTaG93SGlkZGVuRmlsZXMnKSA/PjwvbGFiZWw+CiAgICAgICAgICAgICAgICAgICAgICAgIDxkaXYgY2xhc3M9ImNvbC1zbS05Ij4KICAgICAgICAgICAgICAgICAgICAgICAgICAgIDxkaXYgY2xhc3M9ImZvcm0tY2hlY2sgZm9ybS1zd2l0Y2giPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8aW5wdXQgY2xhc3M9ImZvcm0tY2hlY2staW5wdXQiIHR5cGU9ImNoZWNrYm94IiByb2xlPSJzd2l0Y2giIGlkPSJqcy1zaG93LWhpZGRlbiIgbmFtZT0ianMtc2hvdy1oaWRkZW4iIHZhbHVlPSJ0cnVlIiA8P3BocCBlY2hvICRzaG93X2hpZGRlbl9maWxlcyA/ICdjaGVja2VkJyA6ICcnOyA/PiAvPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgPC9kaXY+CiAgICAgICAgICAgICAgICAgICAgICAgIDwvZGl2PgogICAgICAgICAgICAgICAgICAgIDwvZGl2PgoKICAgICAgICAgICAgICAgICAgICA8ZGl2IGNsYXNzPSJtYi0zIHJvdyI+CiAgICAgICAgICAgICAgICAgICAgICAgIDxsYWJlbCBmb3I9ImpzLWhpZGUtY29scyIgY2xhc3M9ImNvbC1zbS0zIGNvbC1mb3JtLWxhYmVsIj48P3BocCBlY2hvIGxuZygnSGlkZUNvbHVtbnMnKSA/PjwvbGFiZWw+CiAgICAgICAgICAgICAgICAgICAgICAgIDxkaXYgY2xhc3M9ImNvbC1zbS05Ij4KICAgICAgICAgICAgICAgICAgICAgICAgICAgIDxkaXYgY2xhc3M9ImZvcm0tY2hlY2sgZm9ybS1zd2l0Y2giPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8aW5wdXQgY2xhc3M9ImZvcm0tY2hlY2staW5wdXQiIHR5cGU9ImNoZWNrYm94IiByb2xlPSJzd2l0Y2giIGlkPSJqcy1oaWRlLWNvbHMiIG5hbWU9ImpzLWhpZGUtY29scyIgdmFsdWU9InRydWUiIDw/cGhwIGVjaG8gJGhpZGVfQ29scyA/ICdjaGVja2VkJyA6ICcnOyA/PiAvPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgPC9kaXY+CiAgICAgICAgICAgICAgICAgICAgICAgIDwvZGl2PgogICAgICAgICAgICAgICAgICAgIDwvZGl2PgoKICAgICAgICAgICAgICAgICAgICA8ZGl2IGNsYXNzPSJtYi0zIHJvdyI+CiAgICAgICAgICAgICAgICAgICAgICAgIDxsYWJlbCBmb3I9ImpzLTMtMSIgY2xhc3M9ImNvbC1zbS0zIGNvbC1mb3JtLWxhYmVsIj48P3BocCBlY2hvIGxuZygnVGhlbWUnKSA/PjwvbGFiZWw+CiAgICAgICAgICAgICAgICAgICAgICAgIDxkaXYgY2xhc3M9ImNvbC1zbS01Ij4KICAgICAgICAgICAgICAgICAgICAgICAgICAgIDxzZWxlY3QgY2xhc3M9ImZvcm0tc2VsZWN0IHctMTAwIiBpZD0ianMtMy0wIiBuYW1lPSJqcy10aGVtZS0zIj4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8b3B0aW9uIHZhbHVlPSdsaWdodCcgPD9waHAgaWYoJHRoZW1lID09ICJsaWdodCIpe2VjaG8gInNlbGVjdGVkIjt9ID8+Pjw/cGhwIGVjaG8gbG5nKCdsaWdodCcpID8+PC9vcHRpb24+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPG9wdGlvbiB2YWx1ZT0nZGFyaycgPD9waHAgaWYoJHRoZW1lID09ICJkYXJrIil7ZWNobyAic2VsZWN0ZWQiO30gPz4+PD9waHAgZWNobyBsbmcoJ2RhcmsnKSA/Pjwvb3B0aW9uPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgPC9zZWxlY3Q+CiAgICAgICAgICAgICAgICAgICAgICAgIDwvZGl2PgogICAgICAgICAgICAgICAgICAgIDwvZGl2PgoKICAgICAgICAgICAgICAgICAgICA8ZGl2IGNsYXNzPSJtYi0zIHJvdyI+CiAgICAgICAgICAgICAgICAgICAgICAgIDxkaXYgY2xhc3M9ImNvbC1zbS0xMCI+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICA8YnV0dG9uIHR5cGU9InN1Ym1pdCIgY2xhc3M9ImJ0biBidG4tc3VjY2VzcyI+IDxpIGNsYXNzPSJmYSBmYS1jaGVjay1jaXJjbGUiPjwvaT4gPD9waHAgZWNobyBsbmcoJ1NhdmUnKTsgPz48L2J1dHRvbj4KICAgICAgICAgICAgICAgICAgICAgICAgPC9kaXY+CiAgICAgICAgICAgICAgICAgICAgPC9kaXY+CgogICAgICAgICAgICAgICAgPC9mb3JtPgogICAgICAgICAgICA8L2Rpdj4KICAgICAgICA8L2Rpdj4KICAgIDwvZGl2PgogICAgPD9waHAKICAgIGZtX3Nob3dfZm9vdGVyKCk7CiAgICBleGl0Owp9CgppZiAoaXNzZXQoJF9HRVRbJ2hlbHAnXSkpIHsKICAgIGZtX3Nob3dfaGVhZGVyKCk7IC8vIEhFQURFUgogICAgZm1fc2hvd19uYXZfcGF0aChGTV9QQVRIKTsgLy8gY3VycmVudCBwYXRoCiAgICBnbG9iYWwgJGNmZywgJGxhbmc7CiAgICA/PgoKICAgIDxkaXYgY2xhc3M9ImNvbC1tZC04IG9mZnNldC1tZC0yIHB0LTMiPgogICAgICAgIDxkaXYgY2xhc3M9ImNhcmQgbWItMiA8P3BocCBlY2hvIGZtX2dldF90aGVtZSgpOyA/PiI+CiAgICAgICAgICAgIDxoNiBjbGFzcz0iY2FyZC1oZWFkZXIgZC1mbGV4IGp1c3RpZnktY29udGVudC1iZXR3ZWVuIj4KICAgICAgICAgICAgICAgIDxzcGFuPjxpIGNsYXNzPSJmYSBmYS1leGNsYW1hdGlvbi1jaXJjbGUiPjwvaT4gPD9waHAgZWNobyBsbmcoJ0hlbHAnKSA/Pjwvc3Bhbj4KICAgICAgICAgICAgICAgIDxhIGhyZWY9Ij9wPTw/cGhwIGVjaG8gRk1fUEFUSCA/PiIgY2xhc3M9InRleHQtZGFuZ2VyIj48aSBjbGFzcz0iZmEgZmEtdGltZXMtY2lyY2xlLW8iPjwvaT4gPD9waHAgZWNobyBsbmcoJ0NhbmNlbCcpPz48L2E+CiAgICAgICAgICAgIDwvaDY+CiAgICAgICAgICAgIDxkaXYgY2xhc3M9ImNhcmQtYm9keSI+CiAgICAgICAgICAgICAgICA8ZGl2IGNsYXNzPSJyb3ciPgogICAgICAgICAgICAgICAgICAgIDxkaXYgY2xhc3M9ImNvbC14cy0xMiBjb2wtc20tNiI+CiAgICAgICAgICAgICAgICAgICAgICAgIDxwPjxoMz48YSBocmVmPSJodHRwczovL2dpdGh1Yi5jb20vcHJhc2F0aG1hbmkvdGlueWZpbGVtYW5hZ2VyIiB0YXJnZXQ9Il9ibGFuayIgY2xhc3M9ImFwcC12LXRpdGxlIj4gVGlueSBGaWxlIE1hbmFnZXIgPD9waHAgZWNobyBWRVJTSU9OOyA/PjwvYT48L2gzPjwvcD4KICAgICAgICAgICAgICAgICAgICAgICAgPHA+QXV0aG9yOiBQcmFzYXRoIE1hbmk8L3A+CiAgICAgICAgICAgICAgICAgICAgICAgIDxwPk1haWwgVXM6IDxhIGhyZWY9Im1haWx0bzpjY3Bwcm9ncmFtbWVyc0BnbWFpbC5jb20iPmNjcHByb2dyYW1tZXJzW2F0XWdtYWlsLmNvbTwvYT4gPC9wPgogICAgICAgICAgICAgICAgICAgIDwvZGl2PgogICAgICAgICAgICAgICAgICAgIDxkaXYgY2xhc3M9ImNvbC14cy0xMiBjb2wtc20tNiI+CiAgICAgICAgICAgICAgICAgICAgICAgIDxkaXYgY2xhc3M9ImNhcmQiPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgPHVsIGNsYXNzPSJsaXN0LWdyb3VwIGxpc3QtZ3JvdXAtZmx1c2giPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDxsaSBjbGFzcz0ibGlzdC1ncm91cC1pdGVtIj48YSBocmVmPSJodHRwczovL2dpdGh1Yi5jb20vcHJhc2F0aG1hbmkvdGlueWZpbGVtYW5hZ2VyL3dpa2kiIHRhcmdldD0iX2JsYW5rIj48aSBjbGFzcz0iZmEgZmEtcXVlc3Rpb24tY2lyY2xlIj48L2k+IDw/cGhwIGVjaG8gbG5nKCdIZWxwIERvY3VtZW50cycpID8+IDwvYT4gPC9saT4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8bGkgY2xhc3M9Imxpc3QtZ3JvdXAtaXRlbSI+PGEgaHJlZj0iaHR0cHM6Ly9naXRodWIuY29tL3ByYXNhdGhtYW5pL3RpbnlmaWxlbWFuYWdlci9pc3N1ZXMiIHRhcmdldD0iX2JsYW5rIj48aSBjbGFzcz0iZmEgZmEtYnVnIj48L2k+IDw/cGhwIGVjaG8gbG5nKCdSZXBvcnQgSXNzdWUnKSA/PjwvYT48L2xpPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDw/cGhwIGlmKCFGTV9SRUFET05MWSkgeyA/PgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDxsaSBjbGFzcz0ibGlzdC1ncm91cC1pdGVtIj48YSBocmVmPSJqYXZhc2NyaXB0OnNob3dfbmV3X3B3ZCgpOyI+PGkgY2xhc3M9ImZhIGZhLWxvY2siPjwvaT4gPD9waHAgZWNobyBsbmcoJ0dlbmVyYXRlIG5ldyBwYXNzd29yZCBoYXNoJykgPz48L2E+PC9saT4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8P3BocCB9ID8+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICA8L3VsPgogICAgICAgICAgICAgICAgICAgICAgICA8L2Rpdj4KICAgICAgICAgICAgICAgICAgICA8L2Rpdj4KICAgICAgICAgICAgICAgIDwvZGl2PgogICAgICAgICAgICAgICAgPGRpdiBjbGFzcz0icm93IGpzLW5ldy1wd2QgaGlkZGVuIG10LTIiPgogICAgICAgICAgICAgICAgICAgIDxkaXYgY2xhc3M9ImNvbC0xMiI+CiAgICAgICAgICAgICAgICAgICAgICAgIDxmb3JtIGNsYXNzPSJmb3JtLWlubGluZSIgb25zdWJtaXQ9InJldHVybiBuZXdfcGFzc3dvcmRfaGFzaCh0aGlzKSIgbWV0aG9kPSJQT1NUIiBhY3Rpb249IiI+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICA8aW5wdXQgdHlwZT0iaGlkZGVuIiBuYW1lPSJ0eXBlIiB2YWx1ZT0icHdkaGFzaCIgYXJpYS1sYWJlbD0iaGlkZGVuIiBhcmlhLWhpZGRlbj0idHJ1ZSI+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICA8ZGl2IGNsYXNzPSJmb3JtLWdyb3VwIG1iLTIiPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDxsYWJlbCBmb3I9InN0YXRpY0VtYWlsMiI+PD9waHAgZWNobyBsbmcoJ0dlbmVyYXRlIG5ldyBwYXNzd29yZCBoYXNoJykgPz48L2xhYmVsPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgPC9kaXY+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICA8ZGl2IGNsYXNzPSJmb3JtLWdyb3VwIG14LXNtLTMgbWItMiI+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPGxhYmVsIGZvcj0iaW5wdXRQYXNzd29yZDIiIGNsYXNzPSJzci1vbmx5Ij48P3BocCBlY2hvIGxuZygnUGFzc3dvcmQnKSA/PjwvbGFiZWw+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPGlucHV0IHR5cGU9InRleHQiIGNsYXNzPSJmb3JtLWNvbnRyb2wgYnRuLXNtIiBpZD0iaW5wdXRQYXNzd29yZDIiIG5hbWU9ImlucHV0UGFzc3dvcmQyIiBwbGFjZWhvbGRlcj0iPD9waHAgZWNobyBsbmcoJ1Bhc3N3b3JkJykgPz4iIHJlcXVpcmVkPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgPC9kaXY+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICA8YnV0dG9uIHR5cGU9InN1Ym1pdCIgY2xhc3M9ImJ0biBidG4tc3VjY2VzcyBidG4tc20gbWItMiI+PD9waHAgZWNobyBsbmcoJ0dlbmVyYXRlJykgPz48L2J1dHRvbj4KICAgICAgICAgICAgICAgICAgICAgICAgPC9mb3JtPgogICAgICAgICAgICAgICAgICAgICAgICA8dGV4dGFyZWEgY2xhc3M9ImZvcm0tY29udHJvbCIgcm93cz0iMiIgcmVhZG9ubHkgaWQ9ImpzLXB3ZC1yZXN1bHQiPjwvdGV4dGFyZWE+CiAgICAgICAgICAgICAgICAgICAgPC9kaXY+CiAgICAgICAgICAgICAgICA8L2Rpdj4KICAgICAgICAgICAgPC9kaXY+CiAgICAgICAgPC9kaXY+CiAgICA8L2Rpdj4KICAgIDw/cGhwCiAgICBmbV9zaG93X2Zvb3RlcigpOwogICAgZXhpdDsKfQoKLy8gZmlsZSB2aWV3ZXIKaWYgKGlzc2V0KCRfR0VUWyd2aWV3J10pKSB7CiAgICAkZmlsZSA9ICRfR0VUWyd2aWV3J107CiAgICAkZmlsZSA9IGZtX2NsZWFuX3BhdGgoJGZpbGUsIGZhbHNlKTsKICAgICRmaWxlID0gc3RyX3JlcGxhY2UoJy8nLCAnJywgJGZpbGUpOwogICAgaWYgKCRmaWxlID09ICcnIHx8ICFpc19maWxlKCRwYXRoIC4gJy8nIC4gJGZpbGUpIHx8IGluX2FycmF5KCRmaWxlLCAkR0xPQkFMU1snZXhjbHVkZV9pdGVtcyddKSkgewogICAgICAgIGZtX3NldF9tc2cobG5nKCdGaWxlIG5vdCBmb3VuZCcpLCAnZXJyb3InKTsKICAgICAgICAkRk1fUEFUSD1GTV9QQVRIOyBmbV9yZWRpcmVjdChGTV9TRUxGX1VSTCAuICc/cD0nIC4gdXJsZW5jb2RlKCRGTV9QQVRIKSk7CiAgICB9CgogICAgZm1fc2hvd19oZWFkZXIoKTsgLy8gSEVBREVSCiAgICBmbV9zaG93X25hdl9wYXRoKEZNX1BBVEgpOyAvLyBjdXJyZW50IHBhdGgKCiAgICAkZmlsZV91cmwgPSBGTV9ST09UX1VSTCAuIGZtX2NvbnZlcnRfd2luKChGTV9QQVRIICE9ICcnID8gJy8nIC4gRk1fUEFUSCA6ICcnKSAuICcvJyAuICRmaWxlKTsKICAgICRmaWxlX3BhdGggPSAkcGF0aCAuICcvJyAuICRmaWxlOwoKICAgICRleHQgPSBzdHJ0b2xvd2VyKHBhdGhpbmZvKCRmaWxlX3BhdGgsIFBBVEhJTkZPX0VYVEVOU0lPTikpOwogICAgJG1pbWVfdHlwZSA9IGZtX2dldF9taW1lX3R5cGUoJGZpbGVfcGF0aCk7CiAgICAkZmlsZXNpemVfcmF3ID0gZm1fZ2V0X3NpemUoJGZpbGVfcGF0aCk7CiAgICAkZmlsZXNpemUgPSBmbV9nZXRfZmlsZXNpemUoJGZpbGVzaXplX3Jhdyk7CgogICAgJGlzX3ppcCA9IGZhbHNlOwogICAgJGlzX2d6aXAgPSBmYWxzZTsKICAgICRpc19pbWFnZSA9IGZhbHNlOwogICAgJGlzX2F1ZGlvID0gZmFsc2U7CiAgICAkaXNfdmlkZW8gPSBmYWxzZTsKICAgICRpc190ZXh0ID0gZmFsc2U7CiAgICAkaXNfb25saW5lVmlld2VyID0gZmFsc2U7CgogICAgJHZpZXdfdGl0bGUgPSAnRmlsZSc7CiAgICAkZmlsZW5hbWVzID0gZmFsc2U7IC8vIGZvciB6aXAKICAgICRjb250ZW50ID0gJyc7IC8vIGZvciB0ZXh0CiAgICAkb25saW5lX3ZpZXdlciA9IHN0cnRvbG93ZXIoRk1fRE9DX1ZJRVdFUik7CgogICAgaWYoJG9ubGluZV92aWV3ZXIgJiYgJG9ubGluZV92aWV3ZXIgIT09ICdmYWxzZScgJiYgaW5fYXJyYXkoJGV4dCwgZm1fZ2V0X29ubGluZVZpZXdlcl9leHRzKCkpKXsKICAgICAgICAkaXNfb25saW5lVmlld2VyID0gdHJ1ZTsKICAgIH0KICAgIGVsc2VpZiAoJGV4dCA9PSAnemlwJyB8fCAkZXh0ID09ICd0YXInKSB7CiAgICAgICAgJGlzX3ppcCA9IHRydWU7CiAgICAgICAgJHZpZXdfdGl0bGUgPSAnQXJjaGl2ZSc7CiAgICAgICAgJGZpbGVuYW1lcyA9IGZtX2dldF96aWZfaW5mbygkZmlsZV9wYXRoLCAkZXh0KTsKICAgIH0gZWxzZWlmIChpbl9hcnJheSgkZXh0LCBmbV9nZXRfaW1hZ2VfZXh0cygpKSkgewogICAgICAgICRpc19pbWFnZSA9IHRydWU7CiAgICAgICAgJHZpZXdfdGl0bGUgPSAnSW1hZ2UnOwogICAgfSBlbHNlaWYgKGluX2FycmF5KCRleHQsIGZtX2dldF9hdWRpb19leHRzKCkpKSB7CiAgICAgICAgJGlzX2F1ZGlvID0gdHJ1ZTsKICAgICAgICAkdmlld190aXRsZSA9ICdBdWRpbyc7CiAgICB9IGVsc2VpZiAoaW5fYXJyYXkoJGV4dCwgZm1fZ2V0X3ZpZGVvX2V4dHMoKSkpIHsKICAgICAgICAkaXNfdmlkZW8gPSB0cnVlOwogICAgICAgICR2aWV3X3RpdGxlID0gJ1ZpZGVvJzsKICAgIH0gZWxzZWlmIChpbl9hcnJheSgkZXh0LCBmbV9nZXRfdGV4dF9leHRzKCkpIHx8IHN1YnN0cigkbWltZV90eXBlLCAwLCA0KSA9PSAndGV4dCcgfHwgaW5fYXJyYXkoJG1pbWVfdHlwZSwgZm1fZ2V0X3RleHRfbWltZXMoKSkpIHsKICAgICAgICAkaXNfdGV4dCA9IHRydWU7CiAgICAgICAgJGNvbnRlbnQgPSBmaWxlX2dldF9jb250ZW50cygkZmlsZV9wYXRoKTsKICAgIH0KCiAgICA/PgogICAgPGRpdiBjbGFzcz0icm93Ij4KICAgICAgICA8ZGl2IGNsYXNzPSJjb2wtMTIiPgogICAgICAgICAgICA8cCBjbGFzcz0iYnJlYWstd29yZCI+PGI+PD9waHAgZWNobyAkdmlld190aXRsZSA/PiAiPD9waHAgZWNobyBmbV9lbmMoZm1fY29udmVydF93aW4oJGZpbGUpKSA/PiI8L2I+PC9wPgogICAgICAgICAgICA8cCBjbGFzcz0iYnJlYWstd29yZCI+CiAgICAgICAgICAgICAgICA8c3Ryb25nPkZ1bGwgcGF0aDo8L3N0cm9uZz4gPD9waHAgZWNobyBmbV9lbmMoZm1fY29udmVydF93aW4oJGZpbGVfcGF0aCkpID8+PGJyPgogICAgICAgICAgICAgICAgPHN0cm9uZz5GaWxlIHNpemU6PC9zdHJvbmc+IDw/cGhwIGVjaG8gKCRmaWxlc2l6ZV9yYXcgPD0gMTAwMCkgPyAiJGZpbGVzaXplX3JhdyBieXRlcyIgOiAkZmlsZXNpemU7ID8+PGJyPgogICAgICAgICAgICAgICAgPHN0cm9uZz5NSU1FLXR5cGU6PC9zdHJvbmc+IDw/cGhwIGVjaG8gJG1pbWVfdHlwZSA/Pjxicj4KICAgICAgICAgICAgICAgIDw/cGhwCiAgICAgICAgICAgICAgICAvLyBaSVAgaW5mbwogICAgICAgICAgICAgICAgaWYgKCgkaXNfemlwIHx8ICRpc19nemlwKSAmJiAkZmlsZW5hbWVzICE9PSBmYWxzZSkgewogICAgICAgICAgICAgICAgICAgICR0b3RhbF9maWxlcyA9IDA7CiAgICAgICAgICAgICAgICAgICAgJHRvdGFsX2NvbXAgPSAwOwogICAgICAgICAgICAgICAgICAgICR0b3RhbF91bmNvbXAgPSAwOwogICAgICAgICAgICAgICAgICAgIGZvcmVhY2ggKCRmaWxlbmFtZXMgYXMgJGZuKSB7CiAgICAgICAgICAgICAgICAgICAgICAgIGlmICghJGZuWydmb2xkZXInXSkgewogICAgICAgICAgICAgICAgICAgICAgICAgICAgJHRvdGFsX2ZpbGVzKys7CiAgICAgICAgICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgICAgICAgICAgICAgJHRvdGFsX2NvbXAgKz0gJGZuWydjb21wcmVzc2VkX3NpemUnXTsKICAgICAgICAgICAgICAgICAgICAgICAgJHRvdGFsX3VuY29tcCArPSAkZm5bJ2ZpbGVzaXplJ107CiAgICAgICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgICAgICAgID8+CiAgICAgICAgICAgICAgICAgICAgRmlsZXMgaW4gYXJjaGl2ZTogPD9waHAgZWNobyAkdG90YWxfZmlsZXMgPz48YnI+CiAgICAgICAgICAgICAgICAgICAgVG90YWwgc2l6ZTogPD9waHAgZWNobyBmbV9nZXRfZmlsZXNpemUoJHRvdGFsX3VuY29tcCkgPz48YnI+CiAgICAgICAgICAgICAgICAgICAgU2l6ZSBpbiBhcmNoaXZlOiA8P3BocCBlY2hvIGZtX2dldF9maWxlc2l6ZSgkdG90YWxfY29tcCkgPz48YnI+CiAgICAgICAgICAgICAgICAgICAgQ29tcHJlc3Npb246IDw/cGhwIGVjaG8gcm91bmQoKCR0b3RhbF9jb21wIC8gbWF4KCR0b3RhbF91bmNvbXAsIDEpKSAqIDEwMCkgPz4lPGJyPgogICAgICAgICAgICAgICAgICAgIDw/cGhwCiAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgICAgICAvLyBJbWFnZSBpbmZvCiAgICAgICAgICAgICAgICBpZiAoJGlzX2ltYWdlKSB7CiAgICAgICAgICAgICAgICAgICAgJGltYWdlX3NpemUgPSBnZXRpbWFnZXNpemUoJGZpbGVfcGF0aCk7CiAgICAgICAgICAgICAgICAgICAgZWNobyAnSW1hZ2Ugc2l6ZXM6ICcgLiAoaXNzZXQoJGltYWdlX3NpemVbMF0pID8gJGltYWdlX3NpemVbMF0gOiAnMCcpIC4gJyB4ICcgLiAoaXNzZXQoJGltYWdlX3NpemVbMV0pID8gJGltYWdlX3NpemVbMV0gOiAnMCcpIC4gJzxicj4nOwogICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgICAgLy8gVGV4dCBpbmZvCiAgICAgICAgICAgICAgICBpZiAoJGlzX3RleHQpIHsKICAgICAgICAgICAgICAgICAgICAkaXNfdXRmOCA9IGZtX2lzX3V0ZjgoJGNvbnRlbnQpOwogICAgICAgICAgICAgICAgICAgIGlmIChmdW5jdGlvbl9leGlzdHMoJ2ljb252JykpIHsKICAgICAgICAgICAgICAgICAgICAgICAgaWYgKCEkaXNfdXRmOCkgewogICAgICAgICAgICAgICAgICAgICAgICAgICAgJGNvbnRlbnQgPSBpY29udihGTV9JQ09OVl9JTlBVVF9FTkMsICdVVEYtOC8vSUdOT1JFJywgJGNvbnRlbnQpOwogICAgICAgICAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgICAgICAgIGVjaG8gJzxzdHJvbmc+Q2hhcnNldDo8L3N0cm9uZz4gJyAuICgkaXNfdXRmOCA/ICd1dGYtOCcgOiAnOCBiaXQnKSAuICc8YnI+JzsKICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgICAgID8+CiAgICAgICAgICAgIDwvcD4KICAgICAgICAgICAgPGRpdiBjbGFzcz0iZC1mbGV4IGFsaWduLWl0ZW1zLWNlbnRlciBtYi0zIj4KICAgICAgICAgICAgICAgIDxiPjxhIGhyZWY9Ij9wPTw/cGhwIGVjaG8gdXJsZW5jb2RlKEZNX1BBVEgpID8+JmFtcDtkbD08P3BocCBlY2hvIHVybGVuY29kZSgkZmlsZSkgPz4iPjxpIGNsYXNzPSJmYSBmYS1jbG91ZC1kb3dubG9hZCI+PC9pPiA8P3BocCBlY2hvIGxuZygnRG93bmxvYWQnKSA/PjwvYT48L2I+ICZuYnNwOwogICAgICAgICAgICAgICAgPGIgY2xhc3M9Im1zLTIiPjxhIGhyZWY9Ijw/cGhwIGVjaG8gZm1fZW5jKCRmaWxlX3VybCkgPz4iIHRhcmdldD0iX2JsYW5rIj48aSBjbGFzcz0iZmEgZmEtZXh0ZXJuYWwtbGluay1zcXVhcmUiPjwvaT4gPD9waHAgZWNobyBsbmcoJ09wZW4nKSA/PjwvYT48L2I+CiAgICAgICAgICAgICAgICA8P3BocAogICAgICAgICAgICAgICAgLy8gWklQIGFjdGlvbnMKICAgICAgICAgICAgICAgIGlmICghRk1fUkVBRE9OTFkgJiYgKCRpc196aXAgfHwgJGlzX2d6aXApICYmICRmaWxlbmFtZXMgIT09IGZhbHNlKSB7CiAgICAgICAgICAgICAgICAgICAgJHppcF9uYW1lID0gcGF0aGluZm8oJGZpbGVfcGF0aCwgUEFUSElORk9fRklMRU5BTUUpOwogICAgICAgICAgICAgICAgICAgID8+CiAgICAgICAgICAgICAgICAgICAgPGZvcm0gbWV0aG9kPSJwb3N0IiBjbGFzcz0iZC1pbmxpbmUgbXMtMiI+CiAgICAgICAgICAgICAgICAgICAgICAgIDxpbnB1dCB0eXBlPSJoaWRkZW4iIG5hbWU9InRva2VuIiB2YWx1ZT0iPHBocCA8P3BocCBlY2hvICRfU0VTU0lPTlsndG9rZW4nXTsgPz4iPgogICAgICAgICAgICAgICAgICAgICAgICA8aW5wdXQgdHlwZT0iaGlkZGVuIiBuYW1lPSJ1bnppcCIgdmFsdWU9Ijw/cGhwIGVjaG8gdXJsZW5jb2RlKCRmaWxlKTsgPz4iPgogICAgICAgICAgICAgICAgICAgICAgICA8YnV0dG9uIHR5cGU9InN1Ym1pdCIgY2xhc3M9ImJ0biBidG4tbGluayB0ZXh0LWRlY29yYXRpb24tbm9uZSBmdy1ib2xkIHAtMCIgc3R5bGU9ImZvbnQtc2l6ZTogMTRweDsiPjxpIGNsYXNzPSJmYSBmYS1jaGVjay1jaXJjbGUiPjwvaT4gPD9waHAgZWNobyBsbmcoJ1VuWmlwJykgPz48L2J1dHRvbj4KICAgICAgICAgICAgICAgICAgICA8L2Zvcm0+Jm5ic3A7CiAgICAgICAgICAgICAgICAgICAgPGZvcm0gbWV0aG9kPSJwb3N0IiBjbGFzcz0iZC1pbmxpbmUgbXMtMiI+CiAgICAgICAgICAgICAgICAgICAgICAgIDxpbnB1dCB0eXBlPSJoaWRkZW4iIG5hbWU9InRva2VuIiB2YWx1ZT0iPHBocCA8P3BocCBlY2hvICRfU0VTU0lPTlsndG9rZW4nXTsgPz4iPgogICAgICAgICAgICAgICAgICAgICAgICA8aW5wdXQgdHlwZT0iaGlkZGVuIiBuYW1lPSJ1bnppcCIgdmFsdWU9Ijw/cGhwIGVjaG8gdXJsZW5jb2RlKCRmaWxlKTsgPz4iPgogICAgICAgICAgICAgICAgICAgICAgICA8aW5wdXQgdHlwZT0iaGlkZGVuIiBuYW1lPSJ0b2ZvbGRlciIgdmFsdWU9IjEiPgogICAgICAgICAgICAgICAgICAgICAgICA8YnV0dG9uIHR5cGU9InN1Ym1pdCIgY2xhc3M9ImJ0biBidG4tbGluayB0ZXh0LWRlY29yYXRpb24tbm9uZSBmdy1ib2xkIHAtMCIgc3R5bGU9ImZvbnQtc2l6ZTogMTRweDsiIHRpdGxlPSJVblppcCB0byA8P3BocCBlY2hvIGZtX2VuYygkemlwX25hbWUpID8+Ij48aSBjbGFzcz0iZmEgZmEtY2hlY2stY2lyY2xlIj48L2k+IDw/cGhwIGVjaG8gbG5nKCdVblppcFRvRm9sZGVyJykgPz48L2J1dHRvbj4KICAgICAgICAgICAgICAgICAgICA8L2Zvcm0+Jm5ic3A7CiAgICAgICAgICAgICAgICAgICAgPD9waHAKICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgICAgIGlmICgkaXNfdGV4dCAmJiAhRk1fUkVBRE9OTFkpIHsKICAgICAgICAgICAgICAgICAgICA/PgogICAgICAgICAgICAgICAgICAgIDxiIGNsYXNzPSJtcy0yIj48YSBocmVmPSI/cD08P3BocCBlY2hvIHVybGVuY29kZSh0cmltKEZNX1BBVEgpKSA/PiZhbXA7ZWRpdD08P3BocCBlY2hvIHVybGVuY29kZSgkZmlsZSkgPz4iIGNsYXNzPSJlZGl0LWZpbGUiPjxpIGNsYXNzPSJmYSBmYS1wZW5jaWwtc3F1YXJlIj48L2k+IDw/cGhwIGVjaG8gbG5nKCdFZGl0JykgPz4KICAgICAgICAgICAgICAgICAgICAgICAgPC9hPjwvYj4gJm5ic3A7CiAgICAgICAgICAgICAgICAgICAgPGIgY2xhc3M9Im1zLTIiPjxhIGhyZWY9Ij9wPTw/cGhwIGVjaG8gdXJsZW5jb2RlKHRyaW0oRk1fUEFUSCkpID8+JmFtcDtlZGl0PTw/cGhwIGVjaG8gdXJsZW5jb2RlKCRmaWxlKSA/PiZlbnY9YWNlIgogICAgICAgICAgICAgICAgICAgICAgICAgICAgY2xhc3M9ImVkaXQtZmlsZSI+PGkgY2xhc3M9ImZhIGZhLXBlbmNpbC1zcXVhcmUtbyI+PC9pPiA8P3BocCBlY2hvIGxuZygnQWR2YW5jZWRFZGl0b3InKSA/PgogICAgICAgICAgICAgICAgICAgICAgICA8L2E+PC9iPiAmbmJzcDsKICAgICAgICAgICAgICAgIDw/cGhwIH0gPz4KICAgICAgICAgICAgICAgIDxiIGNsYXNzPSJtcy0yIj48YSBocmVmPSI/cD08P3BocCBlY2hvIHVybGVuY29kZShGTV9QQVRIKSA/PiI+PGkgY2xhc3M9ImZhIGZhLWNoZXZyb24tY2lyY2xlLWxlZnQgZ28tYmFjayI+PC9pPiA8P3BocCBlY2hvIGxuZygnQmFjaycpID8+PC9hPjwvYj4KICAgICAgICAgICAgPC9kaXY+CiAgICAgICAgICAgIDw/cGhwCiAgICAgICAgICAgIGlmKCRpc19vbmxpbmVWaWV3ZXIpIHsKICAgICAgICAgICAgICAgIGlmKCRvbmxpbmVfdmlld2VyID09ICdnb29nbGUnKSB7CiAgICAgICAgICAgICAgICAgICAgZWNobyAnPGlmcmFtZSBzcmM9Imh0dHBzOi8vZG9jcy5nb29nbGUuY29tL3ZpZXdlcj9lbWJlZGRlZD10cnVlJmhsPWVuJnVybD0nIC4gZm1fZW5jKCRmaWxlX3VybCkgLiAnIiBmcmFtZWJvcmRlcj0ibm8iIHN0eWxlPSJ3aWR0aDoxMDAlO21pbi1oZWlnaHQ6NDYwcHgiPjwvaWZyYW1lPic7CiAgICAgICAgICAgICAgICB9IGVsc2UgaWYoJG9ubGluZV92aWV3ZXIgPT0gJ21pY3Jvc29mdCcpIHsKICAgICAgICAgICAgICAgICAgICBlY2hvICc8aWZyYW1lIHNyYz0iaHR0cHM6Ly92aWV3Lm9mZmljZWFwcHMubGl2ZS5jb20vb3AvZW1iZWQuYXNweD9zcmM9JyAuIGZtX2VuYygkZmlsZV91cmwpIC4gJyIgZnJhbWVib3JkZXI9Im5vIiBzdHlsZT0id2lkdGg6MTAwJTttaW4taGVpZ2h0OjQ2MHB4Ij48L2lmcmFtZT4nOwogICAgICAgICAgICAgICAgfQogICAgICAgICAgICB9IGVsc2VpZiAoJGlzX3ppcCkgewogICAgICAgICAgICAgICAgLy8gWklQIGNvbnRlbnQKICAgICAgICAgICAgICAgIGlmICgkZmlsZW5hbWVzICE9PSBmYWxzZSkgewogICAgICAgICAgICAgICAgICAgIGVjaG8gJzxjb2RlIGNsYXNzPSJtYXhoZWlnaHQiPic7CiAgICAgICAgICAgICAgICAgICAgZm9yZWFjaCAoJGZpbGVuYW1lcyBhcyAkZm4pIHsKICAgICAgICAgICAgICAgICAgICAgICAgaWYgKCRmblsnZm9sZGVyJ10pIHsKICAgICAgICAgICAgICAgICAgICAgICAgICAgIGVjaG8gJzxiPicgLiBmbV9lbmMoJGZuWyduYW1lJ10pIC4gJzwvYj48YnI+JzsKICAgICAgICAgICAgICAgICAgICAgICAgfSBlbHNlIHsKICAgICAgICAgICAgICAgICAgICAgICAgICAgIGVjaG8gJGZuWyduYW1lJ10gLiAnICgnIC4gZm1fZ2V0X2ZpbGVzaXplKCRmblsnZmlsZXNpemUnXSkgLiAnKTxicj4nOwogICAgICAgICAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgICAgICAgIGVjaG8gJzwvY29kZT4nOwogICAgICAgICAgICAgICAgfSBlbHNlIHsKICAgICAgICAgICAgICAgICAgICBlY2hvICc8cD4nLmxuZygnRXJyb3Igd2hpbGUgZmV0Y2hpbmcgYXJjaGl2ZSBpbmZvJykuJzwvcD4nOwogICAgICAgICAgICAgICAgfQogICAgICAgICAgICB9IGVsc2VpZiAoJGlzX2ltYWdlKSB7CiAgICAgICAgICAgICAgICAvLyBJbWFnZSBjb250ZW50CiAgICAgICAgICAgICAgICBpZiAoaW5fYXJyYXkoJGV4dCwgYXJyYXkoJ2dpZicsICdqcGcnLCAnanBlZycsICdwbmcnLCAnYm1wJywgJ2ljbycsICdzdmcnLCAnd2VicCcsICdhdmlmJykpKSB7CiAgICAgICAgICAgICAgICAgICAgZWNobyAnPHA+PGltZyBzcmM9IicgLiBmbV9lbmMoJGZpbGVfdXJsKSAuICciIGFsdD0iaW1hZ2UiIGNsYXNzPSJwcmV2aWV3LWltZy1jb250YWluZXIiIGNsYXNzPSJwcmV2aWV3LWltZyI+PC9wPic7CiAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgIH0gZWxzZWlmICgkaXNfYXVkaW8pIHsKICAgICAgICAgICAgICAgIC8vIEF1ZGlvIGNvbnRlbnQKICAgICAgICAgICAgICAgIGVjaG8gJzxwPjxhdWRpbyBzcmM9IicgLiBmbV9lbmMoJGZpbGVfdXJsKSAuICciIGNvbnRyb2xzIHByZWxvYWQ9Im1ldGFkYXRhIj48L2F1ZGlvPjwvcD4nOwogICAgICAgICAgICB9IGVsc2VpZiAoJGlzX3ZpZGVvKSB7CiAgICAgICAgICAgICAgICAvLyBWaWRlbyBjb250ZW50CiAgICAgICAgICAgICAgICBlY2hvICc8ZGl2IGNsYXNzPSJwcmV2aWV3LXZpZGVvIj48dmlkZW8gc3JjPSInIC4gZm1fZW5jKCRmaWxlX3VybCkgLiAnIiB3aWR0aD0iNjQwIiBoZWlnaHQ9IjM2MCIgY29udHJvbHMgcHJlbG9hZD0ibWV0YWRhdGEiPjwvdmlkZW8+PC9kaXY+JzsKICAgICAgICAgICAgfSBlbHNlaWYgKCRpc190ZXh0KSB7CiAgICAgICAgICAgICAgICBpZiAoRk1fVVNFX0hJR0hMSUdIVEpTKSB7CiAgICAgICAgICAgICAgICAgICAgLy8gaGlnaGxpZ2h0CiAgICAgICAgICAgICAgICAgICAgJGhsanNfY2xhc3NlcyA9IGFycmF5KAogICAgICAgICAgICAgICAgICAgICAgICAnc2h0bWwnID0+ICd4bWwnLAogICAgICAgICAgICAgICAgICAgICAgICAnaHRhY2Nlc3MnID0+ICdhcGFjaGUnLAogICAgICAgICAgICAgICAgICAgICAgICAncGh0bWwnID0+ICdwaHAnLAogICAgICAgICAgICAgICAgICAgICAgICAnbG9jaycgPT4gJ2pzb24nLAogICAgICAgICAgICAgICAgICAgICAgICAnc3ZnJyA9PiAneG1sJywKICAgICAgICAgICAgICAgICAgICApOwogICAgICAgICAgICAgICAgICAgICRobGpzX2NsYXNzID0gaXNzZXQoJGhsanNfY2xhc3Nlc1skZXh0XSkgPyAnbGFuZy0nIC4gJGhsanNfY2xhc3Nlc1skZXh0XSA6ICdsYW5nLScgLiAkZXh0OwogICAgICAgICAgICAgICAgICAgIGlmIChlbXB0eSgkZXh0KSB8fCBpbl9hcnJheShzdHJ0b2xvd2VyKCRmaWxlKSwgZm1fZ2V0X3RleHRfbmFtZXMoKSkgfHwgcHJlZ19tYXRjaCgnI1wubWluXC4oY3NzfGpzKSQjaScsICRmaWxlKSkgewogICAgICAgICAgICAgICAgICAgICAgICAkaGxqc19jbGFzcyA9ICdub2hpZ2hsaWdodCc7CiAgICAgICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgICAgICAgICRjb250ZW50ID0gJzxwcmUgY2xhc3M9IndpdGgtaGxqcyI+PGNvZGUgY2xhc3M9IicgLiAkaGxqc19jbGFzcyAuICciPicgLiBmbV9lbmMoJGNvbnRlbnQpIC4gJzwvY29kZT48L3ByZT4nOwogICAgICAgICAgICAgICAgfSBlbHNlaWYgKGluX2FycmF5KCRleHQsIGFycmF5KCdwaHAnLCAncGhwNCcsICdwaHA1JywgJ3BodG1sJywgJ3BocHMnKSkpIHsKICAgICAgICAgICAgICAgICAgICAvLyBwaHAgaGlnaGxpZ2h0CiAgICAgICAgICAgICAgICAgICAgJGNvbnRlbnQgPSBoaWdobGlnaHRfc3RyaW5nKCRjb250ZW50LCB0cnVlKTsKICAgICAgICAgICAgICAgIH0gZWxzZSB7CiAgICAgICAgICAgICAgICAgICAgJGNvbnRlbnQgPSAnPHByZT4nIC4gZm1fZW5jKCRjb250ZW50KSAuICc8L3ByZT4nOwogICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgICAgZWNobyAkY29udGVudDsKICAgICAgICAgICAgfQogICAgICAgICAgICA/PgogICAgICAgIDwvZGl2PgogICAgPC9kaXY+CiAgICA8P3BocAogICAgICAgIGZtX3Nob3dfZm9vdGVyKCk7CiAgICBleGl0Owp9CgovLyBmaWxlIGVkaXRvcgppZiAoaXNzZXQoJF9HRVRbJ2VkaXQnXSkgJiYgIUZNX1JFQURPTkxZKSB7CiAgICAkZmlsZSA9ICRfR0VUWydlZGl0J107CiAgICAkZmlsZSA9IGZtX2NsZWFuX3BhdGgoJGZpbGUsIGZhbHNlKTsKICAgICRmaWxlID0gc3RyX3JlcGxhY2UoJy8nLCAnJywgJGZpbGUpOwogICAgaWYgKCRmaWxlID09ICcnIHx8ICFpc19maWxlKCRwYXRoIC4gJy8nIC4gJGZpbGUpIHx8IGluX2FycmF5KCRmaWxlLCAkR0xPQkFMU1snZXhjbHVkZV9pdGVtcyddKSkgewogICAgICAgIGZtX3NldF9tc2cobG5nKCdGaWxlIG5vdCBmb3VuZCcpLCAnZXJyb3InKTsKICAgICAgICAkRk1fUEFUSD1GTV9QQVRIOyBmbV9yZWRpcmVjdChGTV9TRUxGX1VSTCAuICc/cD0nIC4gdXJsZW5jb2RlKCRGTV9QQVRIKSk7CiAgICB9CiAgICAkZWRpdEZpbGUgPSAnIDogPGk+PGI+Jy4gJGZpbGUuICc8L2I+PC9pPic7CiAgICBoZWFkZXIoJ1gtWFNTLVByb3RlY3Rpb246MCcpOwogICAgZm1fc2hvd19oZWFkZXIoKTsgLy8gSEVBREVSCiAgICBmbV9zaG93X25hdl9wYXRoKEZNX1BBVEgpOyAvLyBjdXJyZW50IHBhdGgKCiAgICAkZmlsZV91cmwgPSBGTV9ST09UX1VSTCAuIGZtX2NvbnZlcnRfd2luKChGTV9QQVRIICE9ICcnID8gJy8nIC4gRk1fUEFUSCA6ICcnKSAuICcvJyAuICRmaWxlKTsKICAgICRmaWxlX3BhdGggPSAkcGF0aCAuICcvJyAuICRmaWxlOwoKICAgIC8vIG5vcm1hbCBlZGl0ZXIKICAgICRpc05vcm1hbEVkaXRvciA9IHRydWU7CiAgICBpZiAoaXNzZXQoJF9HRVRbJ2VudiddKSkgewogICAgICAgIGlmICgkX0dFVFsnZW52J10gPT0gImFjZSIpIHsKICAgICAgICAgICAgJGlzTm9ybWFsRWRpdG9yID0gZmFsc2U7CiAgICAgICAgfQogICAgfQoKICAgIC8vIFNhdmUgRmlsZQogICAgaWYgKGlzc2V0KCRfUE9TVFsnc2F2ZWRhdGEnXSkpIHsKICAgICAgICAkd3JpdGVkYXRhID0gJF9QT1NUWydzYXZlZGF0YSddOwogICAgICAgICRmZCA9IGZvcGVuKCRmaWxlX3BhdGgsICJ3Iik7CiAgICAgICAgQGZ3cml0ZSgkZmQsICR3cml0ZWRhdGEpOwogICAgICAgIGZjbG9zZSgkZmQpOwogICAgICAgIGZtX3NldF9tc2cobG5nKCdGaWxlIFNhdmVkIFN1Y2Nlc3NmdWxseScpKTsKICAgIH0KCiAgICAkZXh0ID0gc3RydG9sb3dlcihwYXRoaW5mbygkZmlsZV9wYXRoLCBQQVRISU5GT19FWFRFTlNJT04pKTsKICAgICRtaW1lX3R5cGUgPSBmbV9nZXRfbWltZV90eXBlKCRmaWxlX3BhdGgpOwogICAgJGZpbGVzaXplID0gZmlsZXNpemUoJGZpbGVfcGF0aCk7CiAgICAkaXNfdGV4dCA9IGZhbHNlOwogICAgJGNvbnRlbnQgPSAnJzsgLy8gZm9yIHRleHQKCiAgICBpZiAoaW5fYXJyYXkoJGV4dCwgZm1fZ2V0X3RleHRfZXh0cygpKSB8fCBzdWJzdHIoJG1pbWVfdHlwZSwgMCwgNCkgPT0gJ3RleHQnIHx8IGluX2FycmF5KCRtaW1lX3R5cGUsIGZtX2dldF90ZXh0X21pbWVzKCkpKSB7CiAgICAgICAgJGlzX3RleHQgPSB0cnVlOwogICAgICAgICRjb250ZW50ID0gZmlsZV9nZXRfY29udGVudHMoJGZpbGVfcGF0aCk7CiAgICB9CgogICAgPz4KICAgIDxkaXYgY2xhc3M9InBhdGgiPgogICAgICAgIDxkaXYgY2xhc3M9InJvdyI+CiAgICAgICAgICAgIDxkaXYgY2xhc3M9ImNvbC14cy0xMiBjb2wtc20tNSBjb2wtbGctNiBwdC0xIj4KICAgICAgICAgICAgICAgIDxkaXYgY2xhc3M9ImJ0bi10b29sYmFyIiByb2xlPSJ0b29sYmFyIj4KICAgICAgICAgICAgICAgICAgICA8P3BocCBpZiAoISRpc05vcm1hbEVkaXRvcikgeyA/PgogICAgICAgICAgICAgICAgICAgICAgICA8ZGl2IGNsYXNzPSJidG4tZ3JvdXAganMtYWNlLXRvb2xiYXIiPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgPGJ1dHRvbiBkYXRhLWNtZD0ibm9uZSIgZGF0YS1vcHRpb249ImZ1bGxzY3JlZW4iIGNsYXNzPSJidG4gYnRuLXNtIGJ0bi1vdXRsaW5lLXNlY29uZGFyeSIgaWQ9ImpzLWFjZS1mdWxsc2NyZWVuIiB0aXRsZT0iRnVsbHNjcmVlbiI+PGkgY2xhc3M9ImZhIGZhLWV4cGFuZCIgdGl0bGU9IkZ1bGxzY3JlZW4iPjwvaT48L2J1dHRvbj4KICAgICAgICAgICAgICAgICAgICAgICAgICAgIDxidXR0b24gZGF0YS1jbWQ9ImZpbmQiIGNsYXNzPSJidG4gYnRuLXNtIGJ0bi1vdXRsaW5lLXNlY29uZGFyeSIgaWQ9ImpzLWFjZS1zZWFyY2giIHRpdGxlPSJTZWFyY2giPjxpIGNsYXNzPSJmYSBmYS1zZWFyY2giIHRpdGxlPSJTZWFyY2giPjwvaT48L2J1dHRvbj4KICAgICAgICAgICAgICAgICAgICAgICAgICAgIDxidXR0b24gZGF0YS1jbWQ9InVuZG8iIGNsYXNzPSJidG4gYnRuLXNtIGJ0bi1vdXRsaW5lLXNlY29uZGFyeSIgaWQ9ImpzLWFjZS11bmRvIiB0aXRsZT0iVW5kbyI+PGkgY2xhc3M9ImZhIGZhLXVuZG8iIHRpdGxlPSJVbmRvIj48L2k+PC9idXR0b24+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICA8YnV0dG9uIGRhdGEtY21kPSJyZWRvIiBjbGFzcz0iYnRuIGJ0bi1zbSBidG4tb3V0bGluZS1zZWNvbmRhcnkiIGlkPSJqcy1hY2UtcmVkbyIgdGl0bGU9IlJlZG8iPjxpIGNsYXNzPSJmYSBmYS1yZXBlYXQiIHRpdGxlPSJSZWRvIj48L2k+PC9idXR0b24+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICA8YnV0dG9uIGRhdGEtY21kPSJub25lIiBkYXRhLW9wdGlvbj0id3JhcCIgY2xhc3M9ImJ0biBidG4tc20gYnRuLW91dGxpbmUtc2Vjb25kYXJ5IiBpZD0ianMtYWNlLXdvcmRXcmFwIiB0aXRsZT0iV29yZCBXcmFwIj48aSBjbGFzcz0iZmEgZmEtdGV4dC13aWR0aCIgdGl0bGU9IldvcmQgV3JhcCI+PC9pPjwvYnV0dG9uPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgPHNlbGVjdCBpZD0ianMtYWNlLW1vZGUiIGRhdGEtdHlwZT0ibW9kZSIgdGl0bGU9IlNlbGVjdCBEb2N1bWVudCBUeXBlIiBjbGFzcz0iYnRuLW91dGxpbmUtc2Vjb25kYXJ5IGJvcmRlci1zdGFydC0wIGQtbm9uZSBkLW1kLWJsb2NrIj48b3B0aW9uPi0tIFNlbGVjdCBNb2RlIC0tPC9vcHRpb24+PC9zZWxlY3Q+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICA8c2VsZWN0IGlkPSJqcy1hY2UtdGhlbWUiIGRhdGEtdHlwZT0idGhlbWUiIHRpdGxlPSJTZWxlY3QgVGhlbWUiIGNsYXNzPSJidG4tb3V0bGluZS1zZWNvbmRhcnkgYm9yZGVyLXN0YXJ0LTAgZC1ub25lIGQtbGctYmxvY2siPjxvcHRpb24+LS0gU2VsZWN0IFRoZW1lIC0tPC9vcHRpb24+PC9zZWxlY3Q+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICA8c2VsZWN0IGlkPSJqcy1hY2UtZm9udFNpemUiIGRhdGEtdHlwZT0iZm9udFNpemUiIHRpdGxlPSJTZWxlY3QgRm9udCBTaXplIiBjbGFzcz0iYnRuLW91dGxpbmUtc2Vjb25kYXJ5IGJvcmRlci1zdGFydC0wIGQtbm9uZSBkLWxnLWJsb2NrIj48b3B0aW9uPi0tIFNlbGVjdCBGb250IFNpemUgLS08L29wdGlvbj48L3NlbGVjdD4KICAgICAgICAgICAgICAgICAgICAgICAgPC9kaXY+CiAgICAgICAgICAgICAgICAgICAgPD9waHAgfSA/PgogICAgICAgICAgICAgICAgPC9kaXY+CiAgICAgICAgICAgIDwvZGl2PgogICAgICAgICAgICA8ZGl2IGNsYXNzPSJlZGl0LWZpbGUtYWN0aW9ucyBjb2wteHMtMTIgY29sLXNtLTcgY29sLWxnLTYgdGV4dC1lbmQgcHQtMSI+CiAgICAgICAgICAgICAgICA8YSB0aXRsZT0iPD9waHAgZWNobyBsbmcoJ0JhY2snKSA/PiIgY2xhc3M9ImJ0biBidG4tc20gYnRuLW91dGxpbmUtcHJpbWFyeSIgaHJlZj0iP3A9PD9waHAgZWNobyB1cmxlbmNvZGUodHJpbShGTV9QQVRIKSkgPz4mYW1wO3ZpZXc9PD9waHAgZWNobyB1cmxlbmNvZGUoJGZpbGUpID8+Ij48aSBjbGFzcz0iZmEgZmEtcmVwbHktYWxsIj48L2k+IDw/cGhwIGVjaG8gbG5nKCdCYWNrJykgPz48L2E+CiAgICAgICAgICAgICAgICA8YSB0aXRsZT0iPD9waHAgZWNobyBsbmcoJ0JhY2tVcCcpID8+IiBjbGFzcz0iYnRuIGJ0bi1zbSBidG4tb3V0bGluZS1wcmltYXJ5IiBocmVmPSJqYXZhc2NyaXB0OnZvaWQoMCk7IiBvbmNsaWNrPSJiYWNrdXAoJzw/cGhwIGVjaG8gdXJsZW5jb2RlKHRyaW0oRk1fUEFUSCkpID8+JywnPD9waHAgZWNobyB1cmxlbmNvZGUoJGZpbGUpID8+JykiPjxpIGNsYXNzPSJmYSBmYS1kYXRhYmFzZSI+PC9pPiA8P3BocCBlY2hvIGxuZygnQmFja1VwJykgPz48L2E+CiAgICAgICAgICAgICAgICA8P3BocCBpZiAoJGlzX3RleHQpIHsgPz4KICAgICAgICAgICAgICAgICAgICA8P3BocCBpZiAoJGlzTm9ybWFsRWRpdG9yKSB7ID8+CiAgICAgICAgICAgICAgICAgICAgICAgIDxhIHRpdGxlPSJBZHZhbmNlZCIgY2xhc3M9ImJ0biBidG4tc20gYnRuLW91dGxpbmUtcHJpbWFyeSIgaHJlZj0iP3A9PD9waHAgZWNobyB1cmxlbmNvZGUodHJpbShGTV9QQVRIKSkgPz4mYW1wO2VkaXQ9PD9waHAgZWNobyB1cmxlbmNvZGUoJGZpbGUpID8+JmFtcDtlbnY9YWNlIj48aSBjbGFzcz0iZmEgZmEtcGVuY2lsLXNxdWFyZS1vIj48L2k+IDw/cGhwIGVjaG8gbG5nKCdBZHZhbmNlZEVkaXRvcicpID8+PC9hPgogICAgICAgICAgICAgICAgICAgICAgICA8YnV0dG9uIHR5cGU9ImJ1dHRvbiIgY2xhc3M9ImJ0biBidG4tc20gYnRuLXN1Y2Nlc3MiIG5hbWU9IlNhdmUiIGRhdGEtdXJsPSI8P3BocCBlY2hvIGZtX2VuYygkZmlsZV91cmwpID8+IiBvbmNsaWNrPSJlZGl0X3NhdmUodGhpcywnbnJsJykiPjxpIGNsYXNzPSJmYSBmYS1mbG9wcHktbyI+PC9pPiBTYXZlCiAgICAgICAgICAgICAgICAgICAgICAgIDwvYnV0dG9uPgogICAgICAgICAgICAgICAgICAgIDw/cGhwIH0gZWxzZSB7ID8+CiAgICAgICAgICAgICAgICAgICAgICAgIDxhIHRpdGxlPSJQbGFpbiBFZGl0b3IiIGNsYXNzPSJidG4gYnRuLXNtIGJ0bi1vdXRsaW5lLXByaW1hcnkiIGhyZWY9Ij9wPTw/cGhwIGVjaG8gdXJsZW5jb2RlKHRyaW0oRk1fUEFUSCkpID8+JmFtcDtlZGl0PTw/cGhwIGVjaG8gdXJsZW5jb2RlKCRmaWxlKSA/PiI+PGkgY2xhc3M9ImZhIGZhLXRleHQtaGVpZ2h0Ij48L2k+IDw/cGhwIGVjaG8gbG5nKCdOb3JtYWxFZGl0b3InKSA/PjwvYT4KICAgICAgICAgICAgICAgICAgICAgICAgPGJ1dHRvbiB0eXBlPSJidXR0b24iIGNsYXNzPSJidG4gYnRuLXNtIGJ0bi1zdWNjZXNzIiBuYW1lPSJTYXZlIiBkYXRhLXVybD0iPD9waHAgZWNobyBmbV9lbmMoJGZpbGVfdXJsKSA/PiIgb25jbGljaz0iZWRpdF9zYXZlKHRoaXMsJ2FjZScpIj48aSBjbGFzcz0iZmEgZmEtZmxvcHB5LW8iPjwvaT4gPD9waHAgZWNobyBsbmcoJ1NhdmUnKSA/PgogICAgICAgICAgICAgICAgICAgICAgICA8L2J1dHRvbj4KICAgICAgICAgICAgICAgICAgICA8P3BocCB9ID8+CiAgICAgICAgICAgICAgICA8P3BocCB9ID8+CiAgICAgICAgICAgIDwvZGl2PgogICAgICAgIDwvZGl2PgogICAgICAgIDw/cGhwCiAgICAgICAgaWYgKCRpc190ZXh0ICYmICRpc05vcm1hbEVkaXRvcikgewogICAgICAgICAgICBlY2hvICc8dGV4dGFyZWEgY2xhc3M9Im10LTIiIGlkPSJub3JtYWwtZWRpdG9yIiByb3dzPSIzMyIgY29scz0iMTIwIiBzdHlsZT0id2lkdGg6IDk5LjUlOyI+JyAuIGh0bWxzcGVjaWFsY2hhcnMoJGNvbnRlbnQpIC4gJzwvdGV4dGFyZWE+JzsKICAgICAgICB9IGVsc2VpZiAoJGlzX3RleHQpIHsKICAgICAgICAgICAgZWNobyAnPGRpdiBpZD0iZWRpdG9yIiBjb250ZW50ZWRpdGFibGU9InRydWUiPicgLiBodG1sc3BlY2lhbGNoYXJzKCRjb250ZW50KSAuICc8L2Rpdj4nOwogICAgICAgIH0gZWxzZSB7CiAgICAgICAgICAgIGZtX3NldF9tc2cobG5nKCdGSUxFIEVYVEVOU0lPTiBIQVMgTk9UIFNVUFBPUlRFRCcpLCAnZXJyb3InKTsKICAgICAgICB9CiAgICAgICAgPz4KICAgIDwvZGl2PgogICAgPD9waHAKICAgIGZtX3Nob3dfZm9vdGVyKCk7CiAgICBleGl0Owp9CgovLyBjaG1vZCAobm90IGZvciBXaW5kb3dzKQppZiAoaXNzZXQoJF9HRVRbJ2NobW9kJ10pICYmICFGTV9SRUFET05MWSAmJiAhRk1fSVNfV0lOKSB7CiAgICAkZmlsZSA9ICRfR0VUWydjaG1vZCddOwogICAgJGZpbGUgPSBmbV9jbGVhbl9wYXRoKCRmaWxlKTsKICAgICRmaWxlID0gc3RyX3JlcGxhY2UoJy8nLCAnJywgJGZpbGUpOwogICAgaWYgKCRmaWxlID09ICcnIHx8ICghaXNfZmlsZSgkcGF0aCAuICcvJyAuICRmaWxlKSAmJiAhaXNfZGlyKCRwYXRoIC4gJy8nIC4gJGZpbGUpKSkgewogICAgICAgIGZtX3NldF9tc2cobG5nKCdGaWxlIG5vdCBmb3VuZCcpLCAnZXJyb3InKTsKICAgICAgICAkRk1fUEFUSD1GTV9QQVRIOyBmbV9yZWRpcmVjdChGTV9TRUxGX1VSTCAuICc/cD0nIC4gdXJsZW5jb2RlKCRGTV9QQVRIKSk7CiAgICB9CgogICAgZm1fc2hvd19oZWFkZXIoKTsgLy8gSEVBREVSCiAgICBmbV9zaG93X25hdl9wYXRoKEZNX1BBVEgpOyAvLyBjdXJyZW50IHBhdGgKCiAgICAkZmlsZV91cmwgPSBGTV9ST09UX1VSTCAuIChGTV9QQVRIICE9ICcnID8gJy8nIC4gRk1fUEFUSCA6ICcnKSAuICcvJyAuICRmaWxlOwogICAgJGZpbGVfcGF0aCA9ICRwYXRoIC4gJy8nIC4gJGZpbGU7CgogICAgJG1vZGUgPSBmaWxlcGVybXMoJHBhdGggLiAnLycgLiAkZmlsZSk7CiAgICA/PgogICAgPGRpdiBjbGFzcz0icGF0aCI+CiAgICAgICAgPGRpdiBjbGFzcz0iY2FyZCBtYi0yIDw/cGhwIGVjaG8gZm1fZ2V0X3RoZW1lKCk7ID8+Ij4KICAgICAgICAgICAgPGg2IGNsYXNzPSJjYXJkLWhlYWRlciI+CiAgICAgICAgICAgICAgICA8P3BocCBlY2hvIGxuZygnQ2hhbmdlUGVybWlzc2lvbnMnKSA/PgogICAgICAgICAgICA8L2g2PgogICAgICAgICAgICA8ZGl2IGNsYXNzPSJjYXJkLWJvZHkiPgogICAgICAgICAgICAgICAgPHAgY2xhc3M9ImNhcmQtdGV4dCI+CiAgICAgICAgICAgICAgICAgICAgRnVsbCBwYXRoOiA8P3BocCBlY2hvICRmaWxlX3BhdGggPz48YnI+CiAgICAgICAgICAgICAgICA8L3A+CiAgICAgICAgICAgICAgICA8Zm9ybSBhY3Rpb249IiIgbWV0aG9kPSJwb3N0Ij4KICAgICAgICAgICAgICAgICAgICA8aW5wdXQgdHlwZT0iaGlkZGVuIiBuYW1lPSJwIiB2YWx1ZT0iPD9waHAgZWNobyBmbV9lbmMoRk1fUEFUSCkgPz4iPgogICAgICAgICAgICAgICAgICAgIDxpbnB1dCB0eXBlPSJoaWRkZW4iIG5hbWU9ImNobW9kIiB2YWx1ZT0iPD9waHAgZWNobyBmbV9lbmMoJGZpbGUpID8+Ij4KCiAgICAgICAgICAgICAgICAgICAgPHRhYmxlIGNsYXNzPSJ0YWJsZSBjb21wYWN0LXRhYmxlIDw/cGhwIGVjaG8gZm1fZ2V0X3RoZW1lKCk7ID8+Ij4KICAgICAgICAgICAgICAgICAgICAgICAgPHRyPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgPHRkPjwvdGQ+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGQ+PGI+PD9waHAgZWNobyBsbmcoJ093bmVyJykgPz48L2I+PC90ZD4KICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0ZD48Yj48P3BocCBlY2hvIGxuZygnR3JvdXAnKSA/PjwvYj48L3RkPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgPHRkPjxiPjw/cGhwIGVjaG8gbG5nKCdPdGhlcicpID8+PC9iPjwvdGQ+CiAgICAgICAgICAgICAgICAgICAgICAgIDwvdHI+CiAgICAgICAgICAgICAgICAgICAgICAgIDx0cj4KICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0ZCBzdHlsZT0idGV4dC1hbGlnbjogcmlnaHQiPjxiPjw/cGhwIGVjaG8gbG5nKCdSZWFkJykgPz48L2I+PC90ZD4KICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0ZD48bGFiZWw+PGlucHV0IHR5cGU9ImNoZWNrYm94IiBuYW1lPSJ1ciIgdmFsdWU9IjEiPD9waHAgZWNobyAoJG1vZGUgJiAwMDQwMCkgPyAnIGNoZWNrZWQnIDogJycgPz4+PC9sYWJlbD48L3RkPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgPHRkPjxsYWJlbD48aW5wdXQgdHlwZT0iY2hlY2tib3giIG5hbWU9ImdyIiB2YWx1ZT0iMSI8P3BocCBlY2hvICgkbW9kZSAmIDAwMDQwKSA/ICcgY2hlY2tlZCcgOiAnJyA/Pj48L2xhYmVsPjwvdGQ+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGQ+PGxhYmVsPjxpbnB1dCB0eXBlPSJjaGVja2JveCIgbmFtZT0ib3IiIHZhbHVlPSIxIjw/cGhwIGVjaG8gKCRtb2RlICYgMDAwMDQpID8gJyBjaGVja2VkJyA6ICcnID8+PjwvbGFiZWw+PC90ZD4KICAgICAgICAgICAgICAgICAgICAgICAgPC90cj4KICAgICAgICAgICAgICAgICAgICAgICAgPHRyPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgPHRkIHN0eWxlPSJ0ZXh0LWFsaWduOiByaWdodCI+PGI+PD9waHAgZWNobyBsbmcoJ1dyaXRlJykgPz48L2I+PC90ZD4KICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0ZD48bGFiZWw+PGlucHV0IHR5cGU9ImNoZWNrYm94IiBuYW1lPSJ1dyIgdmFsdWU9IjEiPD9waHAgZWNobyAoJG1vZGUgJiAwMDIwMCkgPyAnIGNoZWNrZWQnIDogJycgPz4+PC9sYWJlbD48L3RkPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgPHRkPjxsYWJlbD48aW5wdXQgdHlwZT0iY2hlY2tib3giIG5hbWU9Imd3IiB2YWx1ZT0iMSI8P3BocCBlY2hvICgkbW9kZSAmIDAwMDIwKSA/ICcgY2hlY2tlZCcgOiAnJyA/Pj48L2xhYmVsPjwvdGQ+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGQ+PGxhYmVsPjxpbnB1dCB0eXBlPSJjaGVja2JveCIgbmFtZT0ib3ciIHZhbHVlPSIxIjw/cGhwIGVjaG8gKCRtb2RlICYgMDAwMDIpID8gJyBjaGVja2VkJyA6ICcnID8+PjwvbGFiZWw+PC90ZD4KICAgICAgICAgICAgICAgICAgICAgICAgPC90cj4KICAgICAgICAgICAgICAgICAgICAgICAgPHRyPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgPHRkIHN0eWxlPSJ0ZXh0LWFsaWduOiByaWdodCI+PGI+PD9waHAgZWNobyBsbmcoJ0V4ZWN1dGUnKSA/PjwvYj48L3RkPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgPHRkPjxsYWJlbD48aW5wdXQgdHlwZT0iY2hlY2tib3giIG5hbWU9InV4IiB2YWx1ZT0iMSI8P3BocCBlY2hvICgkbW9kZSAmIDAwMTAwKSA/ICcgY2hlY2tlZCcgOiAnJyA/Pj48L2xhYmVsPjwvdGQ+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGQ+PGxhYmVsPjxpbnB1dCB0eXBlPSJjaGVja2JveCIgbmFtZT0iZ3giIHZhbHVlPSIxIjw/cGhwIGVjaG8gKCRtb2RlICYgMDAwMTApID8gJyBjaGVja2VkJyA6ICcnID8+PjwvbGFiZWw+PC90ZD4KICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0ZD48bGFiZWw+PGlucHV0IHR5cGU9ImNoZWNrYm94IiBuYW1lPSJveCIgdmFsdWU9IjEiPD9waHAgZWNobyAoJG1vZGUgJiAwMDAwMSkgPyAnIGNoZWNrZWQnIDogJycgPz4+PC9sYWJlbD48L3RkPgogICAgICAgICAgICAgICAgICAgICAgICA8L3RyPgogICAgICAgICAgICAgICAgICAgIDwvdGFibGU+CgogICAgICAgICAgICAgICAgICAgIDxwPgogICAgICAgICAgICAgICAgICAgICAgIDxpbnB1dCB0eXBlPSJoaWRkZW4iIG5hbWU9InRva2VuIiB2YWx1ZT0iPD9waHAgZWNobyAkX1NFU1NJT05bJ3Rva2VuJ107ID8+Ij4gCiAgICAgICAgICAgICAgICAgICAgICAgIDxiPjxhIGhyZWY9Ij9wPTw/cGhwIGVjaG8gdXJsZW5jb2RlKEZNX1BBVEgpID8+IiBjbGFzcz0iYnRuIGJ0bi1vdXRsaW5lLXByaW1hcnkiPjxpIGNsYXNzPSJmYSBmYS10aW1lcy1jaXJjbGUiPjwvaT4gPD9waHAgZWNobyBsbmcoJ0NhbmNlbCcpID8+PC9hPjwvYj4mbmJzcDsKICAgICAgICAgICAgICAgICAgICAgICAgPGJ1dHRvbiB0eXBlPSJzdWJtaXQiIGNsYXNzPSJidG4gYnRuLXN1Y2Nlc3MiPjxpIGNsYXNzPSJmYSBmYS1jaGVjay1jaXJjbGUiPjwvaT4gPD9waHAgZWNobyBsbmcoJ0NoYW5nZScpID8+PC9idXR0b24+CiAgICAgICAgICAgICAgICAgICAgPC9wPgogICAgICAgICAgICAgICAgPC9mb3JtPgogICAgICAgICAgICA8L2Rpdj4KICAgICAgICA8L2Rpdj4KICAgIDwvZGl2PgogICAgPD9waHAKICAgIGZtX3Nob3dfZm9vdGVyKCk7CiAgICBleGl0Owp9CgovLyAtLS0gVElOWUZJTEVNQU5BR0VSIE1BSU4gLS0tCmZtX3Nob3dfaGVhZGVyKCk7IC8vIEhFQURFUgpmbV9zaG93X25hdl9wYXRoKEZNX1BBVEgpOyAvLyBjdXJyZW50IHBhdGgKCi8vIHNob3cgYWxlcnQgbWVzc2FnZXMKZm1fc2hvd19tZXNzYWdlKCk7CgokbnVtX2ZpbGVzID0gY291bnQoJGZpbGVzKTsKJG51bV9mb2xkZXJzID0gY291bnQoJGZvbGRlcnMpOwokYWxsX2ZpbGVzX3NpemUgPSAwOwokdGFibGVUaGVtZSA9IChGTV9USEVNRSA9PSAiZGFyayIpID8gInRleHQtd2hpdGUgYmctZGFyayB0YWJsZS1kYXJrIiA6ICJiZy13aGl0ZSI7Cj8+Cjxmb3JtIGFjdGlvbj0iIiBtZXRob2Q9InBvc3QiIGNsYXNzPSJwdC0zIj4KICAgIDxpbnB1dCB0eXBlPSJoaWRkZW4iIG5hbWU9InAiIHZhbHVlPSI8P3BocCBlY2hvIGZtX2VuYyhGTV9QQVRIKSA/PiI+CiAgICA8aW5wdXQgdHlwZT0iaGlkZGVuIiBuYW1lPSJncm91cCIgdmFsdWU9IjEiPgogICAgPGlucHV0IHR5cGU9ImhpZGRlbiIgbmFtZT0idG9rZW4iIHZhbHVlPSI8P3BocCBlY2hvICRfU0VTU0lPTlsndG9rZW4nXTsgPz4iPgogICAgPGRpdiBjbGFzcz0idGFibGUtcmVzcG9uc2l2ZSI+CiAgICAgICAgPHRhYmxlIGNsYXNzPSJ0YWJsZSB0YWJsZS1ib3JkZXJlZCB0YWJsZS1ob3ZlciB0YWJsZS1zbSA8P3BocCBlY2hvICR0YWJsZVRoZW1lOyA/PiIgaWQ9Im1haW4tdGFibGUiPgogICAgICAgICAgICA8dGhlYWQgY2xhc3M9InRoZWFkLXdoaXRlIj4KICAgICAgICAgICAgPHRyPgogICAgICAgICAgICAgICAgPD9waHAgaWYgKCFGTV9SRUFET05MWSk6ID8+CiAgICAgICAgICAgICAgICAgICAgPHRoIHN0eWxlPSJ3aWR0aDozJSIgY2xhc3M9ImN1c3RvbS1jaGVja2JveC1oZWFkZXIiPgogICAgICAgICAgICAgICAgICAgICAgICA8ZGl2IGNsYXNzPSJjdXN0b20tY29udHJvbCBjdXN0b20tY2hlY2tib3giPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgPGlucHV0IHR5cGU9ImNoZWNrYm94IiBjbGFzcz0iY3VzdG9tLWNvbnRyb2wtaW5wdXQiIGlkPSJqcy1zZWxlY3QtYWxsLWl0ZW1zIiBvbmNsaWNrPSJjaGVja2JveF90b2dnbGUoKSI+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICA8bGFiZWwgY2xhc3M9ImN1c3RvbS1jb250cm9sLWxhYmVsIiBmb3I9ImpzLXNlbGVjdC1hbGwtaXRlbXMiPjwvbGFiZWw+CiAgICAgICAgICAgICAgICAgICAgICAgIDwvZGl2PgogICAgICAgICAgICAgICAgICAgIDwvdGg+PD9waHAgZW5kaWY7ID8+CiAgICAgICAgICAgICAgICA8dGg+PD9waHAgZWNobyBsbmcoJ05hbWUnKSA/PjwvdGg+CiAgICAgICAgICAgICAgICA8dGg+PD9waHAgZWNobyBsbmcoJ1NpemUnKSA/PjwvdGg+CiAgICAgICAgICAgICAgICA8dGg+PD9waHAgZWNobyBsbmcoJ01vZGlmaWVkJykgPz48L3RoPgogICAgICAgICAgICAgICAgPD9waHAgaWYgKCFGTV9JU19XSU4gJiYgISRoaWRlX0NvbHMpOiA/PgogICAgICAgICAgICAgICAgICAgIDx0aD48P3BocCBlY2hvIGxuZygnUGVybXMnKSA/PjwvdGg+CiAgICAgICAgICAgICAgICAgICAgPHRoPjw/cGhwIGVjaG8gbG5nKCdPd25lcicpID8+PC90aD48P3BocCBlbmRpZjsgPz4KICAgICAgICAgICAgICAgIDx0aD48P3BocCBlY2hvIGxuZygnQWN0aW9ucycpID8+PC90aD4KICAgICAgICAgICAgPC90cj4KICAgICAgICAgICAgPC90aGVhZD4KICAgICAgICAgICAgPD9waHAKICAgICAgICAgICAgLy8gbGluayB0byBwYXJlbnQgZm9sZGVyCiAgICAgICAgICAgIGlmICgkcGFyZW50ICE9PSBmYWxzZSkgewogICAgICAgICAgICAgICAgPz4KICAgICAgICAgICAgICAgIDx0cj48P3BocCBpZiAoIUZNX1JFQURPTkxZKTogPz4KICAgICAgICAgICAgICAgICAgICA8dGQgY2xhc3M9Im5vc29ydCI+PC90ZD48P3BocCBlbmRpZjsgPz4KICAgICAgICAgICAgICAgICAgICA8dGQgY2xhc3M9ImJvcmRlci0wIiBkYXRhLXNvcnQ+PGEgaHJlZj0iP3A9PD9waHAgZWNobyB1cmxlbmNvZGUoJHBhcmVudCkgPz4iPjxpIGNsYXNzPSJmYSBmYS1jaGV2cm9uLWNpcmNsZS1sZWZ0IGdvLWJhY2siPjwvaT4gLi48L2E+PC90ZD4KICAgICAgICAgICAgICAgICAgICA8dGQgY2xhc3M9ImJvcmRlci0wIiBkYXRhLW9yZGVyPjwvdGQ+CiAgICAgICAgICAgICAgICAgICAgPHRkIGNsYXNzPSJib3JkZXItMCIgZGF0YS1vcmRlcj48L3RkPgogICAgICAgICAgICAgICAgICAgIDx0ZCBjbGFzcz0iYm9yZGVyLTAiPjwvdGQ+CiAgICAgICAgICAgICAgICAgICAgPD9waHAgaWYgKCFGTV9JU19XSU4gJiYgISRoaWRlX0NvbHMpIHsgPz4KICAgICAgICAgICAgICAgICAgICAgICAgPHRkIGNsYXNzPSJib3JkZXItMCI+PC90ZD4KICAgICAgICAgICAgICAgICAgICAgICAgPHRkIGNsYXNzPSJib3JkZXItMCI+PC90ZD4KICAgICAgICAgICAgICAgICAgICA8P3BocCB9ID8+CiAgICAgICAgICAgICAgICA8L3RyPgogICAgICAgICAgICAgICAgPD9waHAKICAgICAgICAgICAgfQogICAgICAgICAgICAkaWkgPSAzMzk5OwogICAgICAgICAgICBmb3JlYWNoICgkZm9sZGVycyBhcyAkZikgewogICAgICAgICAgICAgICAgJGlzX2xpbmsgPSBpc19saW5rKCRwYXRoIC4gJy8nIC4gJGYpOwogICAgICAgICAgICAgICAgJGltZyA9ICRpc19saW5rID8gJ2ljb24tbGlua19mb2xkZXInIDogJ2ZhIGZhLWZvbGRlci1vJzsKICAgICAgICAgICAgICAgICRtb2RpZl9yYXcgPSBmaWxlbXRpbWUoJHBhdGggLiAnLycgLiAkZik7CiAgICAgICAgICAgICAgICAkbW9kaWYgPSBkYXRlKEZNX0RBVEVUSU1FX0ZPUk1BVCwgJG1vZGlmX3Jhdyk7CiAgICAgICAgICAgICAgICAkZGF0ZV9zb3J0aW5nID0gc3RydG90aW1lKGRhdGUoIkYgZCBZIEg6aTpzLiIsICRtb2RpZl9yYXcpKTsKICAgICAgICAgICAgICAgICRmaWxlc2l6ZV9yYXcgPSAiIjsKICAgICAgICAgICAgICAgICRmaWxlc2l6ZSA9IGxuZygnRm9sZGVyJyk7CiAgICAgICAgICAgICAgICAkcGVybXMgPSBzdWJzdHIoZGVjb2N0KGZpbGVwZXJtcygkcGF0aCAuICcvJyAuICRmKSksIC00KTsKICAgICAgICAgICAgICAgIGlmIChmdW5jdGlvbl9leGlzdHMoJ3Bvc2l4X2dldHB3dWlkJykgJiYgZnVuY3Rpb25fZXhpc3RzKCdwb3NpeF9nZXRncmdpZCcpKSB7CiAgICAgICAgICAgICAgICAgICAgJG93bmVyID0gcG9zaXhfZ2V0cHd1aWQoZmlsZW93bmVyKCRwYXRoIC4gJy8nIC4gJGYpKTsKICAgICAgICAgICAgICAgICAgICAkZ3JvdXAgPSBwb3NpeF9nZXRncmdpZChmaWxlZ3JvdXAoJHBhdGggLiAnLycgLiAkZikpOwogICAgICAgICAgICAgICAgfSBlbHNlIHsKICAgICAgICAgICAgICAgICAgICAkb3duZXIgPSBhcnJheSgnbmFtZScgPT4gJz8nKTsKICAgICAgICAgICAgICAgICAgICAkZ3JvdXAgPSBhcnJheSgnbmFtZScgPT4gJz8nKTsKICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgICAgID8+CiAgICAgICAgICAgICAgICA8dHI+CiAgICAgICAgICAgICAgICAgICAgPD9waHAgaWYgKCFGTV9SRUFET05MWSk6ID8+CiAgICAgICAgICAgICAgICAgICAgICAgIDx0ZCBjbGFzcz0iY3VzdG9tLWNoZWNrYm94LXRkIj4KICAgICAgICAgICAgICAgICAgICAgICAgPGRpdiBjbGFzcz0iY3VzdG9tLWNvbnRyb2wgY3VzdG9tLWNoZWNrYm94Ij4KICAgICAgICAgICAgICAgICAgICAgICAgICAgIDxpbnB1dCB0eXBlPSJjaGVja2JveCIgY2xhc3M9ImN1c3RvbS1jb250cm9sLWlucHV0IiBpZD0iPD9waHAgZWNobyAkaWkgPz4iIG5hbWU9ImZpbGVbXSIgdmFsdWU9Ijw/cGhwIGVjaG8gZm1fZW5jKCRmKSA/PiI+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICA8bGFiZWwgY2xhc3M9ImN1c3RvbS1jb250cm9sLWxhYmVsIiBmb3I9Ijw/cGhwIGVjaG8gJGlpID8+Ij48L2xhYmVsPgogICAgICAgICAgICAgICAgICAgICAgICA8L2Rpdj4KICAgICAgICAgICAgICAgICAgICAgICAgPC90ZD48P3BocCBlbmRpZjsgPz4KICAgICAgICAgICAgICAgICAgICA8dGQgZGF0YS1zb3J0PTw/cGhwIGVjaG8gZm1fY29udmVydF93aW4oZm1fZW5jKCRmKSkgPz4+CiAgICAgICAgICAgICAgICAgICAgICAgIDxkaXYgY2xhc3M9ImZpbGVuYW1lIj48YSBocmVmPSI/cD08P3BocCBlY2hvIHVybGVuY29kZSh0cmltKEZNX1BBVEggLiAnLycgLiAkZiwgJy8nKSkgPz4iPjxpIGNsYXNzPSI8P3BocCBlY2hvICRpbWcgPz4iPjwvaT4gPD9waHAgZWNobyBmbV9jb252ZXJ0X3dpbihmbV9lbmMoJGYpKSA/PgogICAgICAgICAgICAgICAgICAgICAgICAgICAgPC9hPjw/cGhwIGVjaG8oJGlzX2xpbmsgPyAnICZyYXJyOyA8aT4nIC4gcmVhZGxpbmsoJHBhdGggLiAnLycgLiAkZikgLiAnPC9pPicgOiAnJykgPz48L2Rpdj4KICAgICAgICAgICAgICAgICAgICA8L3RkPgogICAgICAgICAgICAgICAgICAgIDx0ZCBkYXRhLW9yZGVyPSJhLTw/cGhwIGVjaG8gc3RyX3BhZCgkZmlsZXNpemVfcmF3LCAxOCwgIjAiLCBTVFJfUEFEX0xFRlQpOz8+Ij4KICAgICAgICAgICAgICAgICAgICAgICAgPD9waHAgZWNobyAkZmlsZXNpemU7ID8+CiAgICAgICAgICAgICAgICAgICAgPC90ZD4KICAgICAgICAgICAgICAgICAgICA8dGQgZGF0YS1vcmRlcj0iYS08P3BocCBlY2hvICRkYXRlX3NvcnRpbmc7Pz4iPjw/cGhwIGVjaG8gJG1vZGlmID8+PC90ZD4KICAgICAgICAgICAgICAgICAgICA8P3BocCBpZiAoIUZNX0lTX1dJTiAmJiAhJGhpZGVfQ29scyk6ID8+CiAgICAgICAgICAgICAgICAgICAgICAgIDx0ZD48P3BocCBpZiAoIUZNX1JFQURPTkxZKTogPz48YSB0aXRsZT0iQ2hhbmdlIFBlcm1pc3Npb25zIiBocmVmPSI/cD08P3BocCBlY2hvIHVybGVuY29kZShGTV9QQVRIKSA/PiZhbXA7Y2htb2Q9PD9waHAgZWNobyB1cmxlbmNvZGUoJGYpID8+Ij48P3BocCBlY2hvICRwZXJtcyA/PjwvYT48P3BocCBlbHNlOiA/Pjw/cGhwIGVjaG8gJHBlcm1zID8+PD9waHAgZW5kaWY7ID8+CiAgICAgICAgICAgICAgICAgICAgICAgIDwvdGQ+CiAgICAgICAgICAgICAgICAgICAgICAgIDx0ZD48P3BocCBlY2hvICRvd25lclsnbmFtZSddIC4gJzonIC4gJGdyb3VwWyduYW1lJ10gPz48L3RkPgogICAgICAgICAgICAgICAgICAgIDw/cGhwIGVuZGlmOyA/PgogICAgICAgICAgICAgICAgICAgIDx0ZCBjbGFzcz0iaW5saW5lLWFjdGlvbnMiPjw/cGhwIGlmICghRk1fUkVBRE9OTFkpOiA/PgogICAgICAgICAgICAgICAgICAgICAgICAgICAgPGEgdGl0bGU9Ijw/cGhwIGVjaG8gbG5nKCdEZWxldGUnKT8+IiBocmVmPSI/cD08P3BocCBlY2hvIHVybGVuY29kZShGTV9QQVRIKSA/PiZhbXA7ZGVsPTw/cGhwIGVjaG8gdXJsZW5jb2RlKCRmKSA/PiIgb25jbGljaz0iY29uZmlybURhaWxvZyhldmVudCwgJzEwMjgnLCc8P3BocCBlY2hvIGxuZygnRGVsZXRlJykuJyAnLmxuZygnRm9sZGVyJyk7ID8+JywnPD9waHAgZWNobyB1cmxlbmNvZGUoJGYpID8+JywgdGhpcy5ocmVmKTsiPiA8aSBjbGFzcz0iZmEgZmEtdHJhc2gtbyIgYXJpYS1oaWRkZW49InRydWUiPjwvaT48L2E+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICA8YSB0aXRsZT0iPD9waHAgZWNobyBsbmcoJ1JlbmFtZScpPz4iIGhyZWY9IiMiIG9uY2xpY2s9InJlbmFtZSgnPD9waHAgZWNobyBmbV9lbmMoYWRkc2xhc2hlcyhGTV9QQVRIKSkgPz4nLCAnPD9waHAgZWNobyBmbV9lbmMoYWRkc2xhc2hlcygkZikpID8+Jyk7cmV0dXJuIGZhbHNlOyI+PGkgY2xhc3M9ImZhIGZhLXBlbmNpbC1zcXVhcmUtbyIgYXJpYS1oaWRkZW49InRydWUiPjwvaT48L2E+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICA8YSB0aXRsZT0iPD9waHAgZWNobyBsbmcoJ0NvcHlUbycpPz4uLi4iIGhyZWY9Ij9wPSZhbXA7Y29weT08P3BocCBlY2hvIHVybGVuY29kZSh0cmltKEZNX1BBVEggLiAnLycgLiAkZiwgJy8nKSkgPz4iPjxpIGNsYXNzPSJmYSBmYS1maWxlcy1vIiBhcmlhLWhpZGRlbj0idHJ1ZSI+PC9pPjwvYT4KICAgICAgICAgICAgICAgICAgICAgICAgPD9waHAgZW5kaWY7ID8+CiAgICAgICAgICAgICAgICAgICAgICAgIDxhIHRpdGxlPSI8P3BocCBlY2hvIGxuZygnRGlyZWN0TGluaycpPz4iIGhyZWY9Ijw/cGhwIGVjaG8gZm1fZW5jKEZNX1JPT1RfVVJMIC4gKEZNX1BBVEggIT0gJycgPyAnLycgLiBGTV9QQVRIIDogJycpIC4gJy8nIC4gJGYgLiAnLycpID8+IiB0YXJnZXQ9Il9ibGFuayI+PGkgY2xhc3M9ImZhIGZhLWxpbmsiIGFyaWEtaGlkZGVuPSJ0cnVlIj48L2k+PC9hPgogICAgICAgICAgICAgICAgICAgIDwvdGQ+CiAgICAgICAgICAgICAgICA8L3RyPgogICAgICAgICAgICAgICAgPD9waHAKICAgICAgICAgICAgICAgIGZsdXNoKCk7CiAgICAgICAgICAgICAgICAkaWkrKzsKICAgICAgICAgICAgfQogICAgICAgICAgICAkaWsgPSA2MDcwOwogICAgICAgICAgICBmb3JlYWNoICgkZmlsZXMgYXMgJGYpIHsKICAgICAgICAgICAgICAgICRpc19saW5rID0gaXNfbGluaygkcGF0aCAuICcvJyAuICRmKTsKICAgICAgICAgICAgICAgICRpbWcgPSAkaXNfbGluayA/ICdmYSBmYS1maWxlLXRleHQtbycgOiBmbV9nZXRfZmlsZV9pY29uX2NsYXNzKCRwYXRoIC4gJy8nIC4gJGYpOwogICAgICAgICAgICAgICAgJG1vZGlmX3JhdyA9IGZpbGVtdGltZSgkcGF0aCAuICcvJyAuICRmKTsKICAgICAgICAgICAgICAgICRtb2RpZiA9IGRhdGUoRk1fREFURVRJTUVfRk9STUFULCAkbW9kaWZfcmF3KTsKICAgICAgICAgICAgICAgICRkYXRlX3NvcnRpbmcgPSBzdHJ0b3RpbWUoZGF0ZSgiRiBkIFkgSDppOnMuIiwgJG1vZGlmX3JhdykpOwogICAgICAgICAgICAgICAgJGZpbGVzaXplX3JhdyA9IGZtX2dldF9zaXplKCRwYXRoIC4gJy8nIC4gJGYpOwogICAgICAgICAgICAgICAgJGZpbGVzaXplID0gZm1fZ2V0X2ZpbGVzaXplKCRmaWxlc2l6ZV9yYXcpOwogICAgICAgICAgICAgICAgJGZpbGVsaW5rID0gJz9wPScgLiB1cmxlbmNvZGUoRk1fUEFUSCkgLiAnJmFtcDt2aWV3PScgLiB1cmxlbmNvZGUoJGYpOwogICAgICAgICAgICAgICAgJGFsbF9maWxlc19zaXplICs9ICRmaWxlc2l6ZV9yYXc7CiAgICAgICAgICAgICAgICAkcGVybXMgPSBzdWJzdHIoZGVjb2N0KGZpbGVwZXJtcygkcGF0aCAuICcvJyAuICRmKSksIC00KTsKICAgICAgICAgICAgICAgIGlmIChmdW5jdGlvbl9leGlzdHMoJ3Bvc2l4X2dldHB3dWlkJykgJiYgZnVuY3Rpb25fZXhpc3RzKCdwb3NpeF9nZXRncmdpZCcpKSB7CiAgICAgICAgICAgICAgICAgICAgJG93bmVyID0gcG9zaXhfZ2V0cHd1aWQoZmlsZW93bmVyKCRwYXRoIC4gJy8nIC4gJGYpKTsKICAgICAgICAgICAgICAgICAgICAkZ3JvdXAgPSBwb3NpeF9nZXRncmdpZChmaWxlZ3JvdXAoJHBhdGggLiAnLycgLiAkZikpOwogICAgICAgICAgICAgICAgfSBlbHNlIHsKICAgICAgICAgICAgICAgICAgICAkb3duZXIgPSBhcnJheSgnbmFtZScgPT4gJz8nKTsKICAgICAgICAgICAgICAgICAgICAkZ3JvdXAgPSBhcnJheSgnbmFtZScgPT4gJz8nKTsKICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgICAgID8+CiAgICAgICAgICAgICAgICA8dHI+CiAgICAgICAgICAgICAgICAgICAgPD9waHAgaWYgKCFGTV9SRUFET05MWSk6ID8+CiAgICAgICAgICAgICAgICAgICAgICAgIDx0ZCBjbGFzcz0iY3VzdG9tLWNoZWNrYm94LXRkIj4KICAgICAgICAgICAgICAgICAgICAgICAgPGRpdiBjbGFzcz0iY3VzdG9tLWNvbnRyb2wgY3VzdG9tLWNoZWNrYm94Ij4KICAgICAgICAgICAgICAgICAgICAgICAgICAgIDxpbnB1dCB0eXBlPSJjaGVja2JveCIgY2xhc3M9ImN1c3RvbS1jb250cm9sLWlucHV0IiBpZD0iPD9waHAgZWNobyAkaWsgPz4iIG5hbWU9ImZpbGVbXSIgdmFsdWU9Ijw/cGhwIGVjaG8gZm1fZW5jKCRmKSA/PiI+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICA8bGFiZWwgY2xhc3M9ImN1c3RvbS1jb250cm9sLWxhYmVsIiBmb3I9Ijw/cGhwIGVjaG8gJGlrID8+Ij48L2xhYmVsPgogICAgICAgICAgICAgICAgICAgICAgICA8L2Rpdj4KICAgICAgICAgICAgICAgICAgICAgICAgPC90ZD48P3BocCBlbmRpZjsgPz4KICAgICAgICAgICAgICAgICAgICA8dGQgZGF0YS1zb3J0PTw/cGhwIGVjaG8gZm1fZW5jKCRmKSA/Pj4KICAgICAgICAgICAgICAgICAgICAgICAgPGRpdiBjbGFzcz0iZmlsZW5hbWUiPgogICAgICAgICAgICAgICAgICAgICAgICA8P3BocAogICAgICAgICAgICAgICAgICAgICAgICAgICBpZiAoaW5fYXJyYXkoc3RydG9sb3dlcihwYXRoaW5mbygkZiwgUEFUSElORk9fRVhURU5TSU9OKSksIGFycmF5KCdnaWYnLCAnanBnJywgJ2pwZWcnLCAncG5nJywgJ2JtcCcsICdpY28nLCAnc3ZnJywgJ3dlYnAnLCAnYXZpZicpKSk6ID8+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPD9waHAgJGltYWdlUHJldmlldyA9IGZtX2VuYyhGTV9ST09UX1VSTCAuIChGTV9QQVRIICE9ICcnID8gJy8nIC4gRk1fUEFUSCA6ICcnKSAuICcvJyAuICRmKTsgPz4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8YSBocmVmPSI8P3BocCBlY2hvICRmaWxlbGluayA/PiIgZGF0YS1wcmV2aWV3LWltYWdlPSI8P3BocCBlY2hvICRpbWFnZVByZXZpZXcgPz4iIHRpdGxlPSI8P3BocCBlY2hvIGZtX2VuYygkZikgPz4iPgogICAgICAgICAgICAgICAgICAgICAgICAgICA8P3BocCBlbHNlOiA/PgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDxhIGhyZWY9Ijw/cGhwIGVjaG8gJGZpbGVsaW5rID8+IiB0aXRsZT0iPD9waHAgZWNobyAkZiA/PiI+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICA8P3BocCBlbmRpZjsgPz4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPGkgY2xhc3M9Ijw/cGhwIGVjaG8gJGltZyA/PiI+PC9pPiA8P3BocCBlY2hvIGZtX2NvbnZlcnRfd2luKGZtX2VuYygkZikpID8+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPC9hPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDw/cGhwIGVjaG8oJGlzX2xpbmsgPyAnICZyYXJyOyA8aT4nIC4gcmVhZGxpbmsoJHBhdGggLiAnLycgLiAkZikgLiAnPC9pPicgOiAnJykgPz4KICAgICAgICAgICAgICAgICAgICAgICAgPC9kaXY+CiAgICAgICAgICAgICAgICAgICAgPC90ZD4KICAgICAgICAgICAgICAgICAgICA8dGQgZGF0YS1vcmRlcj0iYi08P3BocCBlY2hvIHN0cl9wYWQoJGZpbGVzaXplX3JhdywgMTgsICIwIiwgU1RSX1BBRF9MRUZUKTsgPz4iPjxzcGFuIHRpdGxlPSI8P3BocCBwcmludGYoJyVzIGJ5dGVzJywgJGZpbGVzaXplX3JhdykgPz4iPgogICAgICAgICAgICAgICAgICAgICAgICA8P3BocCBlY2hvICRmaWxlc2l6ZTsgPz4KICAgICAgICAgICAgICAgICAgICAgICAgPC9zcGFuPjwvdGQ+CiAgICAgICAgICAgICAgICAgICAgPHRkIGRhdGEtb3JkZXI9ImItPD9waHAgZWNobyAkZGF0ZV9zb3J0aW5nOz8+Ij48P3BocCBlY2hvICRtb2RpZiA/PjwvdGQ+CiAgICAgICAgICAgICAgICAgICAgPD9waHAgaWYgKCFGTV9JU19XSU4gJiYgISRoaWRlX0NvbHMpOiA/PgogICAgICAgICAgICAgICAgICAgICAgICA8dGQ+PD9waHAgaWYgKCFGTV9SRUFET05MWSk6ID8+PGEgdGl0bGU9Ijw/cGhwIGVjaG8gJ0NoYW5nZSBQZXJtaXNzaW9ucycgPz4iIGhyZWY9Ij9wPTw/cGhwIGVjaG8gdXJsZW5jb2RlKEZNX1BBVEgpID8+JmFtcDtjaG1vZD08P3BocCBlY2hvIHVybGVuY29kZSgkZikgPz4iPjw/cGhwIGVjaG8gJHBlcm1zID8+PC9hPjw/cGhwIGVsc2U6ID8+PD9waHAgZWNobyAkcGVybXMgPz48P3BocCBlbmRpZjsgPz4KICAgICAgICAgICAgICAgICAgICAgICAgPC90ZD4KICAgICAgICAgICAgICAgICAgICAgICAgPHRkPjw/cGhwIGVjaG8gZm1fZW5jKCRvd25lclsnbmFtZSddIC4gJzonIC4gJGdyb3VwWyduYW1lJ10pID8+PC90ZD4KICAgICAgICAgICAgICAgICAgICA8P3BocCBlbmRpZjsgPz4KICAgICAgICAgICAgICAgICAgICA8dGQgY2xhc3M9ImlubGluZS1hY3Rpb25zIj4KICAgICAgICAgICAgICAgICAgICAgICAgPD9waHAgaWYgKCFGTV9SRUFET05MWSk6ID8+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICA8YSB0aXRsZT0iPD9waHAgZWNobyBsbmcoJ0RlbGV0ZScpID8+IiBocmVmPSI/cD08P3BocCBlY2hvIHVybGVuY29kZShGTV9QQVRIKSA/PiZhbXA7ZGVsPTw/cGhwIGVjaG8gdXJsZW5jb2RlKCRmKSA/PiIgb25jbGljaz0iY29uZmlybURhaWxvZyhldmVudCwgMTIwOSwgJzw/cGhwIGVjaG8gbG5nKCdEZWxldGUnKS4nICcubG5nKCdGaWxlJyk7ID8+JywnPD9waHAgZWNobyB1cmxlbmNvZGUoJGYpOyA/PicsIHRoaXMuaHJlZik7Ij4gPGkgY2xhc3M9ImZhIGZhLXRyYXNoLW8iPjwvaT48L2E+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICA8YSB0aXRsZT0iPD9waHAgZWNobyBsbmcoJ1JlbmFtZScpID8+IiBocmVmPSIjIiBvbmNsaWNrPSJyZW5hbWUoJzw/cGhwIGVjaG8gZm1fZW5jKGFkZHNsYXNoZXMoRk1fUEFUSCkpID8+JywgJzw/cGhwIGVjaG8gZm1fZW5jKGFkZHNsYXNoZXMoJGYpKSA/PicpO3JldHVybiBmYWxzZTsiPjxpIGNsYXNzPSJmYSBmYS1wZW5jaWwtc3F1YXJlLW8iPjwvaT48L2E+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICA8YSB0aXRsZT0iPD9waHAgZWNobyBsbmcoJ0NvcHlUbycpID8+Li4uIgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaHJlZj0iP3A9PD9waHAgZWNobyB1cmxlbmNvZGUoRk1fUEFUSCkgPz4mYW1wO2NvcHk9PD9waHAgZWNobyB1cmxlbmNvZGUodHJpbShGTV9QQVRIIC4gJy8nIC4gJGYsICcvJykpID8+Ij48aSBjbGFzcz0iZmEgZmEtZmlsZXMtbyI+PC9pPjwvYT4KICAgICAgICAgICAgICAgICAgICAgICAgPD9waHAgZW5kaWY7ID8+CiAgICAgICAgICAgICAgICAgICAgICAgIDxhIHRpdGxlPSI8P3BocCBlY2hvIGxuZygnRGlyZWN0TGluaycpID8+IiBocmVmPSI8P3BocCBlY2hvIGZtX2VuYyhGTV9ST09UX1VSTCAuIChGTV9QQVRIICE9ICcnID8gJy8nIC4gRk1fUEFUSCA6ICcnKSAuICcvJyAuICRmKSA/PiIgdGFyZ2V0PSJfYmxhbmsiPjxpIGNsYXNzPSJmYSBmYS1saW5rIj48L2k+PC9hPgogICAgICAgICAgICAgICAgICAgICAgICA8YSB0aXRsZT0iPD9waHAgZWNobyBsbmcoJ0Rvd25sb2FkJykgPz4iIGhyZWY9Ij9wPTw/cGhwIGVjaG8gdXJsZW5jb2RlKEZNX1BBVEgpID8+JmFtcDtkbD08P3BocCBlY2hvIHVybGVuY29kZSgkZikgPz4iIG9uY2xpY2s9ImNvbmZpcm1EYWlsb2coZXZlbnQsIDEyMTEsICc8P3BocCBlY2hvIGxuZygnRG93bmxvYWQnKTsgPz4nLCc8P3BocCBlY2hvIHVybGVuY29kZSgkZik7ID8+JywgdGhpcy5ocmVmKTsiPjxpIGNsYXNzPSJmYSBmYS1kb3dubG9hZCI+PC9pPjwvYT4KICAgICAgICAgICAgICAgICAgICA8L3RkPgogICAgICAgICAgICAgICAgPC90cj4KICAgICAgICAgICAgICAgIDw/cGhwCiAgICAgICAgICAgICAgICBmbHVzaCgpOwogICAgICAgICAgICAgICAgJGlrKys7CiAgICAgICAgICAgIH0KCiAgICAgICAgICAgIGlmIChlbXB0eSgkZm9sZGVycykgJiYgZW1wdHkoJGZpbGVzKSkgeyA/PgogICAgICAgICAgICAgICAgPHRmb290PgogICAgICAgICAgICAgICAgICAgIDx0cj48P3BocCBpZiAoIUZNX1JFQURPTkxZKTogPz4KICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0ZD48L3RkPjw/cGhwIGVuZGlmOyA/PgogICAgICAgICAgICAgICAgICAgICAgICA8dGQgY29sc3Bhbj0iPD9waHAgZWNobyAoIUZNX0lTX1dJTiAmJiAhJGhpZGVfQ29scykgPyAnNicgOiAnNCcgPz4iPjxlbT48P3BocCBlY2hvIGxuZygnRm9sZGVyIGlzIGVtcHR5JykgPz48L2VtPjwvdGQ+CiAgICAgICAgICAgICAgICAgICAgPC90cj4KICAgICAgICAgICAgICAgIDwvdGZvb3Q+CiAgICAgICAgICAgICAgICA8P3BocAogICAgICAgICAgICB9IGVsc2UgeyA/PgogICAgICAgICAgICAgICAgPHRmb290PgogICAgICAgICAgICAgICAgICAgIDx0cj4KICAgICAgICAgICAgICAgICAgICAgICAgPD9waHAgaWYgKCFGTV9SRUFET05MWSk6ID8+PHRkIGNsYXNzPSJncmF5Ij48L3RkPjw/cGhwIGVuZGlmOyA/PgogICAgICAgICAgICAgICAgICAgICAgICA8dGQgY2xhc3M9ImdyYXkiIGNvbHNwYW49Ijw/cGhwIGVjaG8gKCFGTV9JU19XSU4gJiYgISRoaWRlX0NvbHMpID8gJzYnIDogJzQnID8+Ij4KICAgICAgICAgICAgICAgICAgICAgICAgICAgIDw/cGhwIGVjaG8gbG5nKCdGdWxsU2l6ZScpLic6IDxzcGFuIGNsYXNzPSJiYWRnZSB0ZXh0LWJnLWxpZ2h0IGJvcmRlci1yYWRpdXMtMCI+Jy5mbV9nZXRfZmlsZXNpemUoJGFsbF9maWxlc19zaXplKS4nPC9zcGFuPicgPz4KICAgICAgICAgICAgICAgICAgICAgICAgICAgIDw/cGhwIGVjaG8gbG5nKCdGaWxlJykuJzogPHNwYW4gY2xhc3M9ImJhZGdlIHRleHQtYmctbGlnaHQgYm9yZGVyLXJhZGl1cy0wIj4nLiRudW1fZmlsZXMuJzwvc3Bhbj4nID8+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICA8P3BocCBlY2hvIGxuZygnRm9sZGVyJykuJzogPHNwYW4gY2xhc3M9ImJhZGdlIHRleHQtYmctbGlnaHQgYm9yZGVyLXJhZGl1cy0wIj4nLiRudW1fZm9sZGVycy4nPC9zcGFuPicgPz4KICAgICAgICAgICAgICAgICAgICAgICAgPC90ZD4KICAgICAgICAgICAgICAgICAgICA8L3RyPgogICAgICAgICAgICAgICAgPC90Zm9vdD4KICAgICAgICAgICAgICAgIDw/cGhwIH0gPz4KICAgICAgICA8L3RhYmxlPgogICAgPC9kaXY+CgogICAgPGRpdiBjbGFzcz0icm93Ij4KICAgICAgICA8P3BocCBpZiAoIUZNX1JFQURPTkxZKTogPz4KICAgICAgICA8ZGl2IGNsYXNzPSJjb2wteHMtMTIgY29sLXNtLTkiPgogICAgICAgICAgICA8dWwgY2xhc3M9Imxpc3QtaW5saW5lIGZvb3Rlci1hY3Rpb24iPgogICAgICAgICAgICAgICAgPGxpIGNsYXNzPSJsaXN0LWlubGluZS1pdGVtIj4gPGEgaHJlZj0iIy9zZWxlY3QtYWxsIiBjbGFzcz0iYnRuIGJ0bi1zbWFsbCBidG4tb3V0bGluZS1wcmltYXJ5IGJ0bi0yIiBvbmNsaWNrPSJzZWxlY3RfYWxsKCk7cmV0dXJuIGZhbHNlOyI+PGkgY2xhc3M9ImZhIGZhLWNoZWNrLXNxdWFyZSI+PC9pPiA8P3BocCBlY2hvIGxuZygnU2VsZWN0QWxsJykgPz4gPC9hPjwvbGk+CiAgICAgICAgICAgICAgICA8bGkgY2xhc3M9Imxpc3QtaW5saW5lLWl0ZW0iPjxhIGhyZWY9IiMvdW5zZWxlY3QtYWxsIiBjbGFzcz0iYnRuIGJ0bi1zbWFsbCBidG4tb3V0bGluZS1wcmltYXJ5IGJ0bi0yIiBvbmNsaWNrPSJ1bnNlbGVjdF9hbGwoKTtyZXR1cm4gZmFsc2U7Ij48aSBjbGFzcz0iZmEgZmEtd2luZG93LWNsb3NlIj48L2k+IDw/cGhwIGVjaG8gbG5nKCdVblNlbGVjdEFsbCcpID8+IDwvYT48L2xpPgogICAgICAgICAgICAgICAgPGxpIGNsYXNzPSJsaXN0LWlubGluZS1pdGVtIj48YSBocmVmPSIjL2ludmVydC1hbGwiIGNsYXNzPSJidG4gYnRuLXNtYWxsIGJ0bi1vdXRsaW5lLXByaW1hcnkgYnRuLTIiIG9uY2xpY2s9ImludmVydF9hbGwoKTtyZXR1cm4gZmFsc2U7Ij48aSBjbGFzcz0iZmEgZmEtdGgtbGlzdCI+PC9pPiA8P3BocCBlY2hvIGxuZygnSW52ZXJ0U2VsZWN0aW9uJykgPz4gPC9hPjwvbGk+CiAgICAgICAgICAgICAgICA8bGkgY2xhc3M9Imxpc3QtaW5saW5lLWl0ZW0iPjxpbnB1dCB0eXBlPSJzdWJtaXQiIGNsYXNzPSJoaWRkZW4iIG5hbWU9ImRlbGV0ZSIgaWQ9ImEtZGVsZXRlIiB2YWx1ZT0iRGVsZXRlIiBvbmNsaWNrPSJyZXR1cm4gY29uZmlybSgnPD9waHAgZWNobyBsbmcoJ0RlbGV0ZSBzZWxlY3RlZCBmaWxlcyBhbmQgZm9sZGVycz8nKTsgPz4nKSI+CiAgICAgICAgICAgICAgICAgICAgPGEgaHJlZj0iamF2YXNjcmlwdDpkb2N1bWVudC5nZXRFbGVtZW50QnlJZCgnYS1kZWxldGUnKS5jbGljaygpOyIgY2xhc3M9ImJ0biBidG4tc21hbGwgYnRuLW91dGxpbmUtcHJpbWFyeSBidG4tMiI+PGkgY2xhc3M9ImZhIGZhLXRyYXNoIj48L2k+IDw/cGhwIGVjaG8gbG5nKCdEZWxldGUnKSA/PiA8L2E+PC9saT4KICAgICAgICAgICAgICAgIDxsaSBjbGFzcz0ibGlzdC1pbmxpbmUtaXRlbSI+PGlucHV0IHR5cGU9InN1Ym1pdCIgY2xhc3M9ImhpZGRlbiIgbmFtZT0iemlwIiBpZD0iYS16aXAiIHZhbHVlPSJ6aXAiIG9uY2xpY2s9InJldHVybiBjb25maXJtKCc8P3BocCBlY2hvIGxuZygnQ3JlYXRlIGFyY2hpdmU/Jyk7ID8+JykiPgogICAgICAgICAgICAgICAgICAgIDxhIGhyZWY9ImphdmFzY3JpcHQ6ZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoJ2EtemlwJykuY2xpY2soKTsiIGNsYXNzPSJidG4gYnRuLXNtYWxsIGJ0bi1vdXRsaW5lLXByaW1hcnkgYnRuLTIiPjxpIGNsYXNzPSJmYSBmYS1maWxlLWFyY2hpdmUtbyI+PC9pPiA8P3BocCBlY2hvIGxuZygnWmlwJykgPz4gPC9hPjwvbGk+CiAgICAgICAgICAgICAgICA8bGkgY2xhc3M9Imxpc3QtaW5saW5lLWl0ZW0iPjxpbnB1dCB0eXBlPSJzdWJtaXQiIGNsYXNzPSJoaWRkZW4iIG5hbWU9InRhciIgaWQ9ImEtdGFyIiB2YWx1ZT0idGFyIiBvbmNsaWNrPSJyZXR1cm4gY29uZmlybSgnPD9waHAgZWNobyBsbmcoJ0NyZWF0ZSBhcmNoaXZlPycpOyA/PicpIj4KICAgICAgICAgICAgICAgICAgICA8YSBocmVmPSJqYXZhc2NyaXB0OmRvY3VtZW50LmdldEVsZW1lbnRCeUlkKCdhLXRhcicpLmNsaWNrKCk7IiBjbGFzcz0iYnRuIGJ0bi1zbWFsbCBidG4tb3V0bGluZS1wcmltYXJ5IGJ0bi0yIj48aSBjbGFzcz0iZmEgZmEtZmlsZS1hcmNoaXZlLW8iPjwvaT4gPD9waHAgZWNobyBsbmcoJ1RhcicpID8+IDwvYT48L2xpPgogICAgICAgICAgICAgICAgPGxpIGNsYXNzPSJsaXN0LWlubGluZS1pdGVtIj48aW5wdXQgdHlwZT0ic3VibWl0IiBjbGFzcz0iaGlkZGVuIiBuYW1lPSJjb3B5IiBpZD0iYS1jb3B5IiB2YWx1ZT0iQ29weSI+CiAgICAgICAgICAgICAgICAgICAgPGEgaHJlZj0iamF2YXNjcmlwdDpkb2N1bWVudC5nZXRFbGVtZW50QnlJZCgnYS1jb3B5JykuY2xpY2soKTsiIGNsYXNzPSJidG4gYnRuLXNtYWxsIGJ0bi1vdXRsaW5lLXByaW1hcnkgYnRuLTIiPjxpIGNsYXNzPSJmYSBmYS1maWxlcy1vIj48L2k+IDw/cGhwIGVjaG8gbG5nKCdDb3B5JykgPz4gPC9hPjwvbGk+CiAgICAgICAgICAgIDwvdWw+CiAgICAgICAgPC9kaXY+CiAgICAgICAgPGRpdiBjbGFzcz0iY29sLTMgZC1ub25lIGQtc20tYmxvY2siPjxhIGhyZWY9Imh0dHBzOi8vdGlueWZpbGVtYW5hZ2VyLmdpdGh1Yi5pbyIgdGFyZ2V0PSJfYmxhbmsiIGNsYXNzPSJmbG9hdC1yaWdodCB0ZXh0LW11dGVkIj5UaW55IEZpbGUgTWFuYWdlciA8P3BocCBlY2hvIFZFUlNJT047ID8+PC9hPjwvZGl2PgogICAgICAgIDw/cGhwIGVsc2U6ID8+CiAgICAgICAgICAgIDxkaXYgY2xhc3M9ImNvbC0xMiI+PGEgaHJlZj0iaHR0cHM6Ly90aW55ZmlsZW1hbmFnZXIuZ2l0aHViLmlvIiB0YXJnZXQ9Il9ibGFuayIgY2xhc3M9ImZsb2F0LXJpZ2h0IHRleHQtbXV0ZWQiPlRpbnkgRmlsZSBNYW5hZ2VyIDw/cGhwIGVjaG8gVkVSU0lPTjsgPz48L2E+PC9kaXY+CiAgICAgICAgPD9waHAgZW5kaWY7ID8+CiAgICA8L2Rpdj4KPC9mb3JtPgoKPD9waHAKZm1fc2hvd19mb290ZXIoKTsKCi8vIC0tLSBFTkQgSFRNTCAtLS0KCi8vIEZ1bmN0aW9ucwoKLyoqCiAqIFZlcmlmeSBDU1JGIFRPS0VOIGFuZCByZW1vdmUgYWZ0ZXIgY2VyaWZ5CiAqIEBwYXJhbSBzdHJpbmcgJHRva2VuCiAqIEByZXR1cm4gYm9vbAogKi8KZnVuY3Rpb24gdmVyaWZ5VG9rZW4oJHRva2VuKSAKewogICAgaWYgKGhhc2hfZXF1YWxzKCRfU0VTU0lPTlsndG9rZW4nXSwgJHRva2VuKSkgeyAKICAgICAgICByZXR1cm4gdHJ1ZTsKICAgIH0KICAgIHJldHVybiBmYWxzZTsKfQoKLyoqCiAqIERlbGV0ZSAgZmlsZSBvciBmb2xkZXIgKHJlY3Vyc2l2ZWx5KQogKiBAcGFyYW0gc3RyaW5nICRwYXRoCiAqIEByZXR1cm4gYm9vbAogKi8KZnVuY3Rpb24gZm1fcmRlbGV0ZSgkcGF0aCkKewogICAgaWYgKGlzX2xpbmsoJHBhdGgpKSB7CiAgICAgICAgcmV0dXJuIHVubGluaygkcGF0aCk7CiAgICB9IGVsc2VpZiAoaXNfZGlyKCRwYXRoKSkgewogICAgICAgICRvYmplY3RzID0gc2NhbmRpcigkcGF0aCk7CiAgICAgICAgJG9rID0gdHJ1ZTsKICAgICAgICBpZiAoaXNfYXJyYXkoJG9iamVjdHMpKSB7CiAgICAgICAgICAgIGZvcmVhY2ggKCRvYmplY3RzIGFzICRmaWxlKSB7CiAgICAgICAgICAgICAgICBpZiAoJGZpbGUgIT0gJy4nICYmICRmaWxlICE9ICcuLicpIHsKICAgICAgICAgICAgICAgICAgICBpZiAoIWZtX3JkZWxldGUoJHBhdGggLiAnLycgLiAkZmlsZSkpIHsKICAgICAgICAgICAgICAgICAgICAgICAgJG9rID0gZmFsc2U7CiAgICAgICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgICAgfQogICAgICAgICAgICB9CiAgICAgICAgfQogICAgICAgIHJldHVybiAoJG9rKSA/IHJtZGlyKCRwYXRoKSA6IGZhbHNlOwogICAgfSBlbHNlaWYgKGlzX2ZpbGUoJHBhdGgpKSB7CiAgICAgICAgcmV0dXJuIHVubGluaygkcGF0aCk7CiAgICB9CiAgICByZXR1cm4gZmFsc2U7Cn0KCi8qKgogKiBSZWN1cnNpdmUgY2htb2QKICogQHBhcmFtIHN0cmluZyAkcGF0aAogKiBAcGFyYW0gaW50ICRmaWxlbW9kZQogKiBAcGFyYW0gaW50ICRkaXJtb2RlCiAqIEByZXR1cm4gYm9vbAogKiBAdG9kbyBXaWxsIHVzZSBpbiBtYXNzIGNobW9kCiAqLwpmdW5jdGlvbiBmbV9yY2htb2QoJHBhdGgsICRmaWxlbW9kZSwgJGRpcm1vZGUpCnsKICAgIGlmIChpc19kaXIoJHBhdGgpKSB7CiAgICAgICAgaWYgKCFjaG1vZCgkcGF0aCwgJGRpcm1vZGUpKSB7CiAgICAgICAgICAgIHJldHVybiBmYWxzZTsKICAgICAgICB9CiAgICAgICAgJG9iamVjdHMgPSBzY2FuZGlyKCRwYXRoKTsKICAgICAgICBpZiAoaXNfYXJyYXkoJG9iamVjdHMpKSB7CiAgICAgICAgICAgIGZvcmVhY2ggKCRvYmplY3RzIGFzICRmaWxlKSB7CiAgICAgICAgICAgICAgICBpZiAoJGZpbGUgIT0gJy4nICYmICRmaWxlICE9ICcuLicpIHsKICAgICAgICAgICAgICAgICAgICBpZiAoIWZtX3JjaG1vZCgkcGF0aCAuICcvJyAuICRmaWxlLCAkZmlsZW1vZGUsICRkaXJtb2RlKSkgewogICAgICAgICAgICAgICAgICAgICAgICByZXR1cm4gZmFsc2U7CiAgICAgICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgICAgfQogICAgICAgICAgICB9CiAgICAgICAgfQogICAgICAgIHJldHVybiB0cnVlOwogICAgfSBlbHNlaWYgKGlzX2xpbmsoJHBhdGgpKSB7CiAgICAgICAgcmV0dXJuIHRydWU7CiAgICB9IGVsc2VpZiAoaXNfZmlsZSgkcGF0aCkpIHsKICAgICAgICByZXR1cm4gY2htb2QoJHBhdGgsICRmaWxlbW9kZSk7CiAgICB9CiAgICByZXR1cm4gZmFsc2U7Cn0KCi8qKgogKiBDaGVjayB0aGUgZmlsZSBleHRlbnNpb24gd2hpY2ggaXMgYWxsb3dlZCBvciBub3QKICogQHBhcmFtIHN0cmluZyAkZmlsZW5hbWUKICogQHJldHVybiBib29sCiAqLwpmdW5jdGlvbiBmbV9pc192YWxpZF9leHQoJGZpbGVuYW1lKQp7CiAgICAkYWxsb3dlZCA9IChGTV9GSUxFX0VYVEVOU0lPTikgPyBleHBsb2RlKCcsJywgRk1fRklMRV9FWFRFTlNJT04pIDogZmFsc2U7CgogICAgJGV4dCA9IHBhdGhpbmZvKCRmaWxlbmFtZSwgUEFUSElORk9fRVhURU5TSU9OKTsKICAgICRpc0ZpbGVBbGxvd2VkID0gKCRhbGxvd2VkKSA/IGluX2FycmF5KCRleHQsICRhbGxvd2VkKSA6IHRydWU7CgogICAgcmV0dXJuICgkaXNGaWxlQWxsb3dlZCkgPyB0cnVlIDogZmFsc2U7Cn0KCi8qKgogKiBTYWZlbHkgcmVuYW1lCiAqIEBwYXJhbSBzdHJpbmcgJG9sZAogKiBAcGFyYW0gc3RyaW5nICRuZXcKICogQHJldHVybiBib29sfG51bGwKICovCmZ1bmN0aW9uIGZtX3JlbmFtZSgkb2xkLCAkbmV3KQp7CiAgICAkaXNGaWxlQWxsb3dlZCA9IGZtX2lzX3ZhbGlkX2V4dCgkbmV3KTsKCiAgICBpZighaXNfZGlyKCRvbGQpKSB7CiAgICAgICAgaWYgKCEkaXNGaWxlQWxsb3dlZCkgcmV0dXJuIGZhbHNlOwogICAgfQoKICAgIHJldHVybiAoIWZpbGVfZXhpc3RzKCRuZXcpICYmIGZpbGVfZXhpc3RzKCRvbGQpKSA/IHJlbmFtZSgkb2xkLCAkbmV3KSA6IG51bGw7Cn0KCi8qKgogKiBDb3B5IGZpbGUgb3IgZm9sZGVyIChyZWN1cnNpdmVseSkuCiAqIEBwYXJhbSBzdHJpbmcgJHBhdGgKICogQHBhcmFtIHN0cmluZyAkZGVzdAogKiBAcGFyYW0gYm9vbCAkdXBkIFVwZGF0ZSBmaWxlcwogKiBAcGFyYW0gYm9vbCAkZm9yY2UgQ3JlYXRlIGZvbGRlciB3aXRoIHNhbWUgbmFtZXMgaW5zdGVhZCBmaWxlCiAqIEByZXR1cm4gYm9vbAogKi8KZnVuY3Rpb24gZm1fcmNvcHkoJHBhdGgsICRkZXN0LCAkdXBkID0gdHJ1ZSwgJGZvcmNlID0gdHJ1ZSkKewogICAgaWYgKGlzX2RpcigkcGF0aCkpIHsKICAgICAgICBpZiAoIWZtX21rZGlyKCRkZXN0LCAkZm9yY2UpKSB7CiAgICAgICAgICAgIHJldHVybiBmYWxzZTsKICAgICAgICB9CiAgICAgICAgJG9iamVjdHMgPSBzY2FuZGlyKCRwYXRoKTsKICAgICAgICAkb2sgPSB0cnVlOwogICAgICAgIGlmIChpc19hcnJheSgkb2JqZWN0cykpIHsKICAgICAgICAgICAgZm9yZWFjaCAoJG9iamVjdHMgYXMgJGZpbGUpIHsKICAgICAgICAgICAgICAgIGlmICgkZmlsZSAhPSAnLicgJiYgJGZpbGUgIT0gJy4uJykgewogICAgICAgICAgICAgICAgICAgIGlmICghZm1fcmNvcHkoJHBhdGggLiAnLycgLiAkZmlsZSwgJGRlc3QgLiAnLycgLiAkZmlsZSkpIHsKICAgICAgICAgICAgICAgICAgICAgICAgJG9rID0gZmFsc2U7CiAgICAgICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgICAgfQogICAgICAgICAgICB9CiAgICAgICAgfQogICAgICAgIHJldHVybiAkb2s7CiAgICB9IGVsc2VpZiAoaXNfZmlsZSgkcGF0aCkpIHsKICAgICAgICByZXR1cm4gZm1fY29weSgkcGF0aCwgJGRlc3QsICR1cGQpOwogICAgfQogICAgcmV0dXJuIGZhbHNlOwp9CgovKioKICogU2FmZWx5IGNyZWF0ZSBmb2xkZXIKICogQHBhcmFtIHN0cmluZyAkZGlyCiAqIEBwYXJhbSBib29sICRmb3JjZQogKiBAcmV0dXJuIGJvb2wKICovCmZ1bmN0aW9uIGZtX21rZGlyKCRkaXIsICRmb3JjZSkKewogICAgaWYgKGZpbGVfZXhpc3RzKCRkaXIpKSB7CiAgICAgICAgaWYgKGlzX2RpcigkZGlyKSkgewogICAgICAgICAgICByZXR1cm4gJGRpcjsKICAgICAgICB9IGVsc2VpZiAoISRmb3JjZSkgewogICAgICAgICAgICByZXR1cm4gZmFsc2U7CiAgICAgICAgfQogICAgICAgIHVubGluaygkZGlyKTsKICAgIH0KICAgIHJldHVybiBta2RpcigkZGlyLCAwNzc3LCB0cnVlKTsKfQoKLyoqCiAqIFNhZmVseSBjb3B5IGZpbGUKICogQHBhcmFtIHN0cmluZyAkZjEKICogQHBhcmFtIHN0cmluZyAkZjIKICogQHBhcmFtIGJvb2wgJHVwZCBJbmRpY2F0ZXMgaWYgZmlsZSBzaG91bGQgYmUgdXBkYXRlZCB3aXRoIG5ldyBjb250ZW50CiAqIEByZXR1cm4gYm9vbAogKi8KZnVuY3Rpb24gZm1fY29weSgkZjEsICRmMiwgJHVwZCkKewogICAgJHRpbWUxID0gZmlsZW10aW1lKCRmMSk7CiAgICBpZiAoZmlsZV9leGlzdHMoJGYyKSkgewogICAgICAgICR0aW1lMiA9IGZpbGVtdGltZSgkZjIpOwogICAgICAgIGlmICgkdGltZTIgPj0gJHRpbWUxICYmICR1cGQpIHsKICAgICAgICAgICAgcmV0dXJuIGZhbHNlOwogICAgICAgIH0KICAgIH0KICAgICRvayA9IGNvcHkoJGYxLCAkZjIpOwogICAgaWYgKCRvaykgewogICAgICAgIHRvdWNoKCRmMiwgJHRpbWUxKTsKICAgIH0KICAgIHJldHVybiAkb2s7Cn0KCi8qKgogKiBHZXQgbWltZSB0eXBlCiAqIEBwYXJhbSBzdHJpbmcgJGZpbGVfcGF0aAogKiBAcmV0dXJuIG1peGVkfHN0cmluZwogKi8KZnVuY3Rpb24gZm1fZ2V0X21pbWVfdHlwZSgkZmlsZV9wYXRoKQp7CiAgICBpZiAoZnVuY3Rpb25fZXhpc3RzKCdmaW5mb19vcGVuJykpIHsKICAgICAgICAkZmluZm8gPSBmaW5mb19vcGVuKEZJTEVJTkZPX01JTUVfVFlQRSk7CiAgICAgICAgJG1pbWUgPSBmaW5mb19maWxlKCRmaW5mbywgJGZpbGVfcGF0aCk7CiAgICAgICAgZmluZm9fY2xvc2UoJGZpbmZvKTsKICAgICAgICByZXR1cm4gJG1pbWU7CiAgICB9IGVsc2VpZiAoZnVuY3Rpb25fZXhpc3RzKCdtaW1lX2NvbnRlbnRfdHlwZScpKSB7CiAgICAgICAgcmV0dXJuIG1pbWVfY29udGVudF90eXBlKCRmaWxlX3BhdGgpOwogICAgfSBlbHNlaWYgKCFzdHJpc3RyKGluaV9nZXQoJ2Rpc2FibGVfZnVuY3Rpb25zJyksICdzaGVsbF9leGVjJykpIHsKICAgICAgICAkZmlsZSA9IGVzY2FwZXNoZWxsYXJnKCRmaWxlX3BhdGgpOwogICAgICAgICRtaW1lID0gc2hlbGxfZXhlYygnZmlsZSAtYmkgJyAuICRmaWxlKTsKICAgICAgICByZXR1cm4gJG1pbWU7CiAgICB9IGVsc2UgewogICAgICAgIHJldHVybiAnLS0nOwogICAgfQp9CgovKioKICogSFRUUCBSZWRpcmVjdAogKiBAcGFyYW0gc3RyaW5nICR1cmwKICogQHBhcmFtIGludCAkY29kZQogKi8KZnVuY3Rpb24gZm1fcmVkaXJlY3QoJHVybCwgJGNvZGUgPSAzMDIpCnsKICAgIGhlYWRlcignTG9jYXRpb246ICcgLiAkdXJsLCB0cnVlLCAkY29kZSk7CiAgICBleGl0Owp9CgovKioKICogUGF0aCB0cmF2ZXJzYWwgcHJldmVudGlvbiBhbmQgY2xlYW4gdGhlIHVybAogKiBJdCByZXBsYWNlcyAoY29uc2VjdXRpdmUpIG9jY3VycmVuY2VzIG9mIC8gYW5kIFxcIHdpdGggd2hhdGV2ZXIgaXMgaW4gRElSRUNUT1JZX1NFUEFSQVRPUiwgYW5kIHByb2Nlc3NlcyAvLiBhbmQgLy4uIGZpbmUuCiAqIEBwYXJhbSAkcGF0aAogKiBAcmV0dXJuIHN0cmluZwogKi8KZnVuY3Rpb24gZ2V0X2Fic29sdXRlX3BhdGgoJHBhdGgpIHsKICAgICRwYXRoID0gc3RyX3JlcGxhY2UoYXJyYXkoJy8nLCAnXFwnKSwgRElSRUNUT1JZX1NFUEFSQVRPUiwgJHBhdGgpOwogICAgJHBhcnRzID0gYXJyYXlfZmlsdGVyKGV4cGxvZGUoRElSRUNUT1JZX1NFUEFSQVRPUiwgJHBhdGgpLCAnc3RybGVuJyk7CiAgICAkYWJzb2x1dGVzID0gYXJyYXkoKTsKICAgIGZvcmVhY2ggKCRwYXJ0cyBhcyAkcGFydCkgewogICAgICAgIGlmICgnLicgPT0gJHBhcnQpIGNvbnRpbnVlOwogICAgICAgIGlmICgnLi4nID09ICRwYXJ0KSB7CiAgICAgICAgICAgIGFycmF5X3BvcCgkYWJzb2x1dGVzKTsKICAgICAgICB9IGVsc2UgewogICAgICAgICAgICAkYWJzb2x1dGVzW10gPSAkcGFydDsKICAgICAgICB9CiAgICB9CiAgICByZXR1cm4gaW1wbG9kZShESVJFQ1RPUllfU0VQQVJBVE9SLCAkYWJzb2x1dGVzKTsKfQoKLyoqCiAqIENsZWFuIHBhdGgKICogQHBhcmFtIHN0cmluZyAkcGF0aAogKiBAcmV0dXJuIHN0cmluZwogKi8KZnVuY3Rpb24gZm1fY2xlYW5fcGF0aCgkcGF0aCwgJHRyaW0gPSB0cnVlKQp7CiAgICAkcGF0aCA9ICR0cmltID8gdHJpbSgkcGF0aCkgOiAkcGF0aDsKICAgICRwYXRoID0gdHJpbSgkcGF0aCwgJ1xcLycpOwogICAgJHBhdGggPSBzdHJfcmVwbGFjZShhcnJheSgnLi4vJywgJy4uXFwnKSwgJycsICRwYXRoKTsKICAgICRwYXRoID0gIGdldF9hYnNvbHV0ZV9wYXRoKCRwYXRoKTsKICAgIGlmICgkcGF0aCA9PSAnLi4nKSB7CiAgICAgICAgJHBhdGggPSAnJzsKICAgIH0KICAgIHJldHVybiBzdHJfcmVwbGFjZSgnXFwnLCAnLycsICRwYXRoKTsKfQoKLyoqCiAqIEdldCBwYXJlbnQgcGF0aAogKiBAcGFyYW0gc3RyaW5nICRwYXRoCiAqIEByZXR1cm4gYm9vbHxzdHJpbmcKICovCmZ1bmN0aW9uIGZtX2dldF9wYXJlbnRfcGF0aCgkcGF0aCkKewogICAgJHBhdGggPSBmbV9jbGVhbl9wYXRoKCRwYXRoKTsKICAgIGlmICgkcGF0aCAhPSAnJykgewogICAgICAgICRhcnJheSA9IGV4cGxvZGUoJy8nLCAkcGF0aCk7CiAgICAgICAgaWYgKGNvdW50KCRhcnJheSkgPiAxKSB7CiAgICAgICAgICAgICRhcnJheSA9IGFycmF5X3NsaWNlKCRhcnJheSwgMCwgLTEpOwogICAgICAgICAgICByZXR1cm4gaW1wbG9kZSgnLycsICRhcnJheSk7CiAgICAgICAgfQogICAgICAgIHJldHVybiAnJzsKICAgIH0KICAgIHJldHVybiBmYWxzZTsKfQoKLyoqCiAqIENoZWNrIGZpbGUgaXMgaW4gZXhjbHVkZSBsaXN0CiAqIEBwYXJhbSBzdHJpbmcgJGZpbGUKICogQHJldHVybiBib29sCiAqLwpmdW5jdGlvbiBmbV9pc19leGNsdWRlX2l0ZW1zKCRmaWxlKSB7CiAgICAkZXh0ID0gc3RydG9sb3dlcihwYXRoaW5mbygkZmlsZSwgUEFUSElORk9fRVhURU5TSU9OKSk7CiAgICBpZiAoaXNzZXQoJGV4Y2x1ZGVfaXRlbXMpIGFuZCBzaXplb2YoJGV4Y2x1ZGVfaXRlbXMpKSB7CiAgICAgICAgdW5zZXQoJGV4Y2x1ZGVfaXRlbXMpOwogICAgfQoKICAgICRleGNsdWRlX2l0ZW1zID0gRk1fRVhDTFVERV9JVEVNUzsKICAgIGlmICh2ZXJzaW9uX2NvbXBhcmUoUEhQX1ZFUlNJT04sICc3LjAuMCcsICc8JykpIHsKICAgICAgICAkZXhjbHVkZV9pdGVtcyA9IHVuc2VyaWFsaXplKCRleGNsdWRlX2l0ZW1zKTsKICAgIH0KICAgIGlmICghaW5fYXJyYXkoJGZpbGUsICRleGNsdWRlX2l0ZW1zKSAmJiAhaW5fYXJyYXkoIiouJGV4dCIsICRleGNsdWRlX2l0ZW1zKSkgewogICAgICAgIHJldHVybiB0cnVlOwogICAgfQogICAgcmV0dXJuIGZhbHNlOwp9CgovKioKICogZ2V0IGxhbmd1YWdlIHRyYW5zbGF0aW9ucyBmcm9tIGpzb24gZmlsZQogKiBAcGFyYW0gaW50ICR0cgogKiBAcmV0dXJuIGFycmF5CiAqLwpmdW5jdGlvbiBmbV9nZXRfdHJhbnNsYXRpb25zKCR0cikgewogICAgdHJ5IHsKICAgICAgICAkY29udGVudCA9IEBmaWxlX2dldF9jb250ZW50cygndHJhbnNsYXRpb24uanNvbicpOwogICAgICAgIGlmKCRjb250ZW50ICE9PSBGQUxTRSkgewogICAgICAgICAgICAkbG5nID0ganNvbl9kZWNvZGUoJGNvbnRlbnQsIFRSVUUpOwogICAgICAgICAgICBnbG9iYWwgJGxhbmdfbGlzdDsKICAgICAgICAgICAgZm9yZWFjaCAoJGxuZ1sibGFuZ3VhZ2UiXSBhcyAka2V5ID0+ICR2YWx1ZSkKICAgICAgICAgICAgewogICAgICAgICAgICAgICAgJGNvZGUgPSAkdmFsdWVbImNvZGUiXTsKICAgICAgICAgICAgICAgICRsYW5nX2xpc3RbJGNvZGVdID0gJHZhbHVlWyJuYW1lIl07CiAgICAgICAgICAgICAgICBpZiAoJHRyKQogICAgICAgICAgICAgICAgICAgICR0clskY29kZV0gPSAkdmFsdWVbInRyYW5zbGF0aW9uIl07CiAgICAgICAgICAgIH0KICAgICAgICAgICAgcmV0dXJuICR0cjsKICAgICAgICB9CgogICAgfQogICAgY2F0Y2ggKEV4Y2VwdGlvbiAkZSkgewogICAgICAgIGVjaG8gJGU7CiAgICB9Cn0KCi8qKgogKiBAcGFyYW0gJGZpbGUKICogUmVjb3ZlciBhbGwgZmlsZSBzaXplcyBsYXJnZXIgdGhhbiA+IDJHQi4KICogV29ya3Mgb24gcGhwIDMyYml0cyBhbmQgNjRiaXRzIGFuZCBzdXBwb3J0cyBsaW51eAogKiBAcmV0dXJuIGludHxzdHJpbmcKICovCmZ1bmN0aW9uIGZtX2dldF9zaXplKCRmaWxlKQp7CiAgICBzdGF0aWMgJGlzd2luOwogICAgc3RhdGljICRpc2RhcndpbjsKICAgIGlmICghaXNzZXQoJGlzd2luKSkgewogICAgICAgICRpc3dpbiA9IChzdHJ0b3VwcGVyKHN1YnN0cihQSFBfT1MsIDAsIDMpKSA9PSAnV0lOJyk7CiAgICB9CiAgICBpZiAoIWlzc2V0KCRpc2RhcndpbikpIHsKICAgICAgICAkaXNkYXJ3aW4gPSAoc3RydG91cHBlcihzdWJzdHIoUEhQX09TLCAwKSkgPT0gIkRBUldJTiIpOwogICAgfQoKICAgIHN0YXRpYyAkZXhlY193b3JrczsKICAgIGlmICghaXNzZXQoJGV4ZWNfd29ya3MpKSB7CiAgICAgICAgJGV4ZWNfd29ya3MgPSAoZnVuY3Rpb25fZXhpc3RzKCdleGVjJykgJiYgIWluaV9nZXQoJ3NhZmVfbW9kZScpICYmIEBleGVjKCdlY2hvIEVYRUMnKSA9PSAnRVhFQycpOwogICAgfQoKICAgIC8vIHRyeSBhIHNoZWxsIGNvbW1hbmQKICAgIGlmICgkZXhlY193b3JrcykgewogICAgICAgICRhcmcgPSBlc2NhcGVzaGVsbGFyZygkZmlsZSk7CiAgICAgICAgJGNtZCA9ICgkaXN3aW4pID8gImZvciAlRiBpbiAoXCIkZmlsZVwiKSBkbyBAZWNobyAlfnpGIiA6ICgkaXNkYXJ3aW4gPyAic3RhdCAtZiV6ICRhcmciIDogInN0YXQgLWMlcyAkYXJnIik7CiAgICAgICAgQGV4ZWMoJGNtZCwgJG91dHB1dCk7CiAgICAgICAgaWYgKGlzX2FycmF5KCRvdXRwdXQpICYmIGN0eXBlX2RpZ2l0KCRzaXplID0gdHJpbShpbXBsb2RlKCJcbiIsICRvdXRwdXQpKSkpIHsKICAgICAgICAgICAgcmV0dXJuICRzaXplOwogICAgICAgIH0KICAgIH0KCiAgICAvLyB0cnkgdGhlIFdpbmRvd3MgQ09NIGludGVyZmFjZQogICAgaWYgKCRpc3dpbiAmJiBjbGFzc19leGlzdHMoIkNPTSIpKSB7CiAgICAgICAgdHJ5IHsKICAgICAgICAgICAgJGZzb2JqID0gbmV3IENPTSgnU2NyaXB0aW5nLkZpbGVTeXN0ZW1PYmplY3QnKTsKICAgICAgICAgICAgJGYgPSAkZnNvYmotPkdldEZpbGUoIHJlYWxwYXRoKCRmaWxlKSApOwogICAgICAgICAgICAkc2l6ZSA9ICRmLT5TaXplOwogICAgICAgIH0gY2F0Y2ggKEV4Y2VwdGlvbiAkZSkgewogICAgICAgICAgICAkc2l6ZSA9IG51bGw7CiAgICAgICAgfQogICAgICAgIGlmIChjdHlwZV9kaWdpdCgkc2l6ZSkpIHsKICAgICAgICAgICAgcmV0dXJuICRzaXplOwogICAgICAgIH0KICAgIH0KCiAgICAvLyBpZiBhbGwgZWxzZSBmYWlscwogICAgcmV0dXJuIGZpbGVzaXplKCRmaWxlKTsKfQoKLyoqCiAqIEdldCBuaWNlIGZpbGVzaXplCiAqIEBwYXJhbSBpbnQgJHNpemUKICogQHJldHVybiBzdHJpbmcKICovCmZ1bmN0aW9uIGZtX2dldF9maWxlc2l6ZSgkc2l6ZSkKewogICAgJHNpemUgPSAoZmxvYXQpICRzaXplOwogICAgJHVuaXRzID0gYXJyYXkoJ0InLCAnS0InLCAnTUInLCAnR0InLCAnVEInLCAnUEInLCAnRUInLCAnWkInLCAnWUInKTsKICAgICRwb3dlciA9ICgkc2l6ZSA+IDApID8gZmxvb3IobG9nKCRzaXplLCAxMDI0KSkgOiAwOwogICAgJHBvd2VyID0gKCRwb3dlciA+IChjb3VudCgkdW5pdHMpIC0gMSkpID8gKGNvdW50KCR1bml0cykgLSAxKSA6ICRwb3dlcjsKICAgIHJldHVybiBzcHJpbnRmKCclcyAlcycsIHJvdW5kKCRzaXplIC8gcG93KDEwMjQsICRwb3dlciksIDIpLCAkdW5pdHNbJHBvd2VyXSk7Cn0KCi8qKgogKiBHZXQgdG90YWwgc2l6ZSBvZiBkaXJlY3RvcnkgdHJlZS4KICoKICogQHBhcmFtICBzdHJpbmcgJGRpcmVjdG9yeSBSZWxhdGl2ZSBvciBhYnNvbHV0ZSBkaXJlY3RvcnkgbmFtZS4KICogQHJldHVybiBpbnQgVG90YWwgbnVtYmVyIG9mIGJ5dGVzLgogKi8KZnVuY3Rpb24gZm1fZ2V0X2RpcmVjdG9yeXNpemUoJGRpcmVjdG9yeSkgewogICAgJGJ5dGVzID0gMDsKICAgICRkaXJlY3RvcnkgPSByZWFscGF0aCgkZGlyZWN0b3J5KTsKICAgIGlmICgkZGlyZWN0b3J5ICE9PSBmYWxzZSAmJiAkZGlyZWN0b3J5ICE9ICcnICYmIGZpbGVfZXhpc3RzKCRkaXJlY3RvcnkpKXsKICAgICAgICBmb3JlYWNoKG5ldyBSZWN1cnNpdmVJdGVyYXRvckl0ZXJhdG9yKG5ldyBSZWN1cnNpdmVEaXJlY3RvcnlJdGVyYXRvcigkZGlyZWN0b3J5LCBGaWxlc3lzdGVtSXRlcmF0b3I6OlNLSVBfRE9UUykpIGFzICRmaWxlKXsKICAgICAgICAgICAgJGJ5dGVzICs9ICRmaWxlLT5nZXRTaXplKCk7CiAgICAgICAgfQogICAgfQogICAgcmV0dXJuICRieXRlczsKfQoKLyoqCiAqIEdldCBpbmZvIGFib3V0IHppcCBhcmNoaXZlCiAqIEBwYXJhbSBzdHJpbmcgJHBhdGgKICogQHJldHVybiBhcnJheXxib29sCiAqLwpmdW5jdGlvbiBmbV9nZXRfemlmX2luZm8oJHBhdGgsICRleHQpIHsKICAgIGlmICgkZXh0ID09ICd6aXAnICYmIGZ1bmN0aW9uX2V4aXN0cygnemlwX29wZW4nKSkgewogICAgICAgICRhcmNoID0gQHppcF9vcGVuKCRwYXRoKTsKICAgICAgICBpZiAoJGFyY2gpIHsKICAgICAgICAgICAgJGZpbGVuYW1lcyA9IGFycmF5KCk7CiAgICAgICAgICAgIHdoaWxlICgkemlwX2VudHJ5ID0gQHppcF9yZWFkKCRhcmNoKSkgewogICAgICAgICAgICAgICAgJHppcF9uYW1lID0gQHppcF9lbnRyeV9uYW1lKCR6aXBfZW50cnkpOwogICAgICAgICAgICAgICAgJHppcF9mb2xkZXIgPSBzdWJzdHIoJHppcF9uYW1lLCAtMSkgPT0gJy8nOwogICAgICAgICAgICAgICAgJGZpbGVuYW1lc1tdID0gYXJyYXkoCiAgICAgICAgICAgICAgICAgICAgJ25hbWUnID0+ICR6aXBfbmFtZSwKICAgICAgICAgICAgICAgICAgICAnZmlsZXNpemUnID0+IEB6aXBfZW50cnlfZmlsZXNpemUoJHppcF9lbnRyeSksCiAgICAgICAgICAgICAgICAgICAgJ2NvbXByZXNzZWRfc2l6ZScgPT4gQHppcF9lbnRyeV9jb21wcmVzc2Vkc2l6ZSgkemlwX2VudHJ5KSwKICAgICAgICAgICAgICAgICAgICAnZm9sZGVyJyA9PiAkemlwX2ZvbGRlcgogICAgICAgICAgICAgICAgICAgIC8vJ2NvbXByZXNzaW9uX21ldGhvZCcgPT4gemlwX2VudHJ5X2NvbXByZXNzaW9ubWV0aG9kKCR6aXBfZW50cnkpLAogICAgICAgICAgICAgICAgKTsKICAgICAgICAgICAgfQogICAgICAgICAgICBAemlwX2Nsb3NlKCRhcmNoKTsKICAgICAgICAgICAgcmV0dXJuICRmaWxlbmFtZXM7CiAgICAgICAgfQogICAgfSBlbHNlaWYoJGV4dCA9PSAndGFyJyAmJiBjbGFzc19leGlzdHMoJ1BoYXJEYXRhJykpIHsKICAgICAgICAkYXJjaGl2ZSA9IG5ldyBQaGFyRGF0YSgkcGF0aCk7CiAgICAgICAgJGZpbGVuYW1lcyA9IGFycmF5KCk7CiAgICAgICAgZm9yZWFjaChuZXcgUmVjdXJzaXZlSXRlcmF0b3JJdGVyYXRvcigkYXJjaGl2ZSkgYXMgJGZpbGUpIHsKICAgICAgICAgICAgJHBhcmVudF9pbmZvID0gJGZpbGUtPmdldFBhdGhJbmZvKCk7CiAgICAgICAgICAgICR6aXBfbmFtZSA9IHN0cl9yZXBsYWNlKCJwaGFyOi8vIi4kcGF0aCwgJycsICRmaWxlLT5nZXRQYXRoTmFtZSgpKTsKICAgICAgICAgICAgJHppcF9uYW1lID0gc3Vic3RyKCR6aXBfbmFtZSwgKCRwb3MgPSBzdHJwb3MoJHppcF9uYW1lLCAnLycpKSAhPT0gZmFsc2UgPyAkcG9zICsgMSA6IDApOwogICAgICAgICAgICAkemlwX2ZvbGRlciA9ICRwYXJlbnRfaW5mby0+Z2V0RmlsZU5hbWUoKTsKICAgICAgICAgICAgJHppcF9pbmZvID0gbmV3IFNwbEZpbGVJbmZvKCRmaWxlKTsKICAgICAgICAgICAgJGZpbGVuYW1lc1tdID0gYXJyYXkoCiAgICAgICAgICAgICAgICAnbmFtZScgPT4gJHppcF9uYW1lLAogICAgICAgICAgICAgICAgJ2ZpbGVzaXplJyA9PiAkemlwX2luZm8tPmdldFNpemUoKSwKICAgICAgICAgICAgICAgICdjb21wcmVzc2VkX3NpemUnID0+ICRmaWxlLT5nZXRDb21wcmVzc2VkU2l6ZSgpLAogICAgICAgICAgICAgICAgJ2ZvbGRlcicgPT4gJHppcF9mb2xkZXIKICAgICAgICAgICAgKTsKICAgICAgICB9CiAgICAgICAgcmV0dXJuICRmaWxlbmFtZXM7CiAgICB9CiAgICByZXR1cm4gZmFsc2U7Cn0KCi8qKgogKiBFbmNvZGUgaHRtbCBlbnRpdGllcwogKiBAcGFyYW0gc3RyaW5nICR0ZXh0CiAqIEByZXR1cm4gc3RyaW5nCiAqLwpmdW5jdGlvbiBmbV9lbmMoJHRleHQpCnsKICAgIHJldHVybiBodG1sc3BlY2lhbGNoYXJzKCR0ZXh0LCBFTlRfUVVPVEVTLCAnVVRGLTgnKTsKfQoKLyoqCiAqIFByZXZlbnQgWFNTIGF0dGFja3MKICogQHBhcmFtIHN0cmluZyAkdGV4dAogKiBAcmV0dXJuIHN0cmluZwogKi8KZnVuY3Rpb24gZm1faXN2YWxpZF9maWxlbmFtZSgkdGV4dCkgewogICAgcmV0dXJuIChzdHJwYnJrKCR0ZXh0LCAnLz8lKjp8Ijw+JykgPT09IEZBTFNFKSA/IHRydWUgOiBmYWxzZTsKfQoKLyoqCiAqIFNhdmUgbWVzc2FnZSBpbiBzZXNzaW9uCiAqIEBwYXJhbSBzdHJpbmcgJG1zZwogKiBAcGFyYW0gc3RyaW5nICRzdGF0dXMKICovCmZ1bmN0aW9uIGZtX3NldF9tc2coJG1zZywgJHN0YXR1cyA9ICdvaycpCnsKICAgICRfU0VTU0lPTltGTV9TRVNTSU9OX0lEXVsnbWVzc2FnZSddID0gJG1zZzsKICAgICRfU0VTU0lPTltGTV9TRVNTSU9OX0lEXVsnc3RhdHVzJ10gPSAkc3RhdHVzOwp9CgovKioKICogQ2hlY2sgaWYgc3RyaW5nIGlzIGluIFVURi04CiAqIEBwYXJhbSBzdHJpbmcgJHN0cmluZwogKiBAcmV0dXJuIGludAogKi8KZnVuY3Rpb24gZm1faXNfdXRmOCgkc3RyaW5nKQp7CiAgICByZXR1cm4gcHJlZ19tYXRjaCgnLy91JywgJHN0cmluZyk7Cn0KCi8qKgogKiBDb252ZXJ0IGZpbGUgbmFtZSB0byBVVEYtOCBpbiBXaW5kb3dzCiAqIEBwYXJhbSBzdHJpbmcgJGZpbGVuYW1lCiAqIEByZXR1cm4gc3RyaW5nCiAqLwpmdW5jdGlvbiBmbV9jb252ZXJ0X3dpbigkZmlsZW5hbWUpCnsKICAgIGlmIChGTV9JU19XSU4gJiYgZnVuY3Rpb25fZXhpc3RzKCdpY29udicpKSB7CiAgICAgICAgJGZpbGVuYW1lID0gaWNvbnYoRk1fSUNPTlZfSU5QVVRfRU5DLCAnVVRGLTgvL0lHTk9SRScsICRmaWxlbmFtZSk7CiAgICB9CiAgICByZXR1cm4gJGZpbGVuYW1lOwp9CgovKioKICogQHBhcmFtICRvYmoKICogQHJldHVybiBhcnJheQogKi8KZnVuY3Rpb24gZm1fb2JqZWN0X3RvX2FycmF5KCRvYmopCnsKICAgIGlmICghaXNfb2JqZWN0KCRvYmopICYmICFpc19hcnJheSgkb2JqKSkgewogICAgICAgIHJldHVybiAkb2JqOwogICAgfQogICAgaWYgKGlzX29iamVjdCgkb2JqKSkgewogICAgICAgICRvYmogPSBnZXRfb2JqZWN0X3ZhcnMoJG9iaik7CiAgICB9CiAgICByZXR1cm4gYXJyYXlfbWFwKCdmbV9vYmplY3RfdG9fYXJyYXknLCAkb2JqKTsKfQoKLyoqCiAqIEdldCBDU1MgY2xhc3NuYW1lIGZvciBmaWxlCiAqIEBwYXJhbSBzdHJpbmcgJHBhdGgKICogQHJldHVybiBzdHJpbmcKICovCmZ1bmN0aW9uIGZtX2dldF9maWxlX2ljb25fY2xhc3MoJHBhdGgpCnsKICAgIC8vIGdldCBleHRlbnNpb24KICAgICRleHQgPSBzdHJ0b2xvd2VyKHBhdGhpbmZvKCRwYXRoLCBQQVRISU5GT19FWFRFTlNJT04pKTsKCiAgICBzd2l0Y2ggKCRleHQpIHsKICAgICAgICBjYXNlICdpY28nOgogICAgICAgIGNhc2UgJ2dpZic6CiAgICAgICAgY2FzZSAnanBnJzoKICAgICAgICBjYXNlICdqcGVnJzoKICAgICAgICBjYXNlICdqcGMnOgogICAgICAgIGNhc2UgJ2pwMic6CiAgICAgICAgY2FzZSAnanB4JzoKICAgICAgICBjYXNlICd4Ym0nOgogICAgICAgIGNhc2UgJ3dibXAnOgogICAgICAgIGNhc2UgJ3BuZyc6CiAgICAgICAgY2FzZSAnYm1wJzoKICAgICAgICBjYXNlICd0aWYnOgogICAgICAgIGNhc2UgJ3RpZmYnOgogICAgICAgIGNhc2UgJ3dlYnAnOgogICAgICAgIGNhc2UgJ2F2aWYnOgogICAgICAgIGNhc2UgJ3N2Zyc6CiAgICAgICAgICAgICRpbWcgPSAnZmEgZmEtcGljdHVyZS1vJzsKICAgICAgICAgICAgYnJlYWs7CiAgICAgICAgY2FzZSAncGFzc3dkJzoKICAgICAgICBjYXNlICdmdHBxdW90YSc6CiAgICAgICAgY2FzZSAnc3FsJzoKICAgICAgICBjYXNlICdqcyc6CiAgICAgICAgY2FzZSAndHMnOgogICAgICAgIGNhc2UgJ2pzeCc6CiAgICAgICAgY2FzZSAndHN4JzoKICAgICAgICBjYXNlICdoYnMnOgogICAgICAgIGNhc2UgJ2pzb24nOgogICAgICAgIGNhc2UgJ3NoJzoKICAgICAgICBjYXNlICdjb25maWcnOgogICAgICAgIGNhc2UgJ3R3aWcnOgogICAgICAgIGNhc2UgJ3RwbCc6CiAgICAgICAgY2FzZSAnbWQnOgogICAgICAgIGNhc2UgJ2dpdGlnbm9yZSc6CiAgICAgICAgY2FzZSAnYyc6CiAgICAgICAgY2FzZSAnY3BwJzoKICAgICAgICBjYXNlICdjcyc6CiAgICAgICAgY2FzZSAncHknOgogICAgICAgIGNhc2UgJ3JzJzoKICAgICAgICBjYXNlICdtYXAnOgogICAgICAgIGNhc2UgJ2xvY2snOgogICAgICAgIGNhc2UgJ2R0ZCc6CiAgICAgICAgICAgICRpbWcgPSAnZmEgZmEtZmlsZS1jb2RlLW8nOwogICAgICAgICAgICBicmVhazsKICAgICAgICBjYXNlICd0eHQnOgogICAgICAgIGNhc2UgJ2luaSc6CiAgICAgICAgY2FzZSAnY29uZic6CiAgICAgICAgY2FzZSAnbG9nJzoKICAgICAgICBjYXNlICdodGFjY2Vzcyc6CiAgICAgICAgY2FzZSAneWFtbCc6CiAgICAgICAgY2FzZSAneW1sJzoKICAgICAgICBjYXNlICd0b21sJzoKICAgICAgICAgICAgJGltZyA9ICdmYSBmYS1maWxlLXRleHQtbyc7CiAgICAgICAgICAgIGJyZWFrOwogICAgICAgIGNhc2UgJ2Nzcyc6CiAgICAgICAgY2FzZSAnbGVzcyc6CiAgICAgICAgY2FzZSAnc2Fzcyc6CiAgICAgICAgY2FzZSAnc2Nzcyc6CiAgICAgICAgICAgICRpbWcgPSAnZmEgZmEtY3NzMyc7CiAgICAgICAgICAgIGJyZWFrOwogICAgICAgIGNhc2UgJ2J6Mic6CiAgICAgICAgY2FzZSAnemlwJzoKICAgICAgICBjYXNlICdyYXInOgogICAgICAgIGNhc2UgJ2d6JzoKICAgICAgICBjYXNlICd0YXInOgogICAgICAgIGNhc2UgJzd6JzoKICAgICAgICBjYXNlICd4eic6CiAgICAgICAgICAgICRpbWcgPSAnZmEgZmEtZmlsZS1hcmNoaXZlLW8nOwogICAgICAgICAgICBicmVhazsKICAgICAgICBjYXNlICdwaHAnOgogICAgICAgIGNhc2UgJ3BocDQnOgogICAgICAgIGNhc2UgJ3BocDUnOgogICAgICAgIGNhc2UgJ3BocHMnOgogICAgICAgIGNhc2UgJ3BodG1sJzoKICAgICAgICAgICAgJGltZyA9ICdmYSBmYS1jb2RlJzsKICAgICAgICAgICAgYnJlYWs7CiAgICAgICAgY2FzZSAnaHRtJzoKICAgICAgICBjYXNlICdodG1sJzoKICAgICAgICBjYXNlICdzaHRtbCc6CiAgICAgICAgY2FzZSAneGh0bWwnOgogICAgICAgICAgICAkaW1nID0gJ2ZhIGZhLWh0bWw1JzsKICAgICAgICAgICAgYnJlYWs7CiAgICAgICAgY2FzZSAneG1sJzoKICAgICAgICBjYXNlICd4c2wnOgogICAgICAgICAgICAkaW1nID0gJ2ZhIGZhLWZpbGUtZXhjZWwtbyc7CiAgICAgICAgICAgIGJyZWFrOwogICAgICAgIGNhc2UgJ3dhdic6CiAgICAgICAgY2FzZSAnbXAzJzoKICAgICAgICBjYXNlICdtcDInOgogICAgICAgIGNhc2UgJ200YSc6CiAgICAgICAgY2FzZSAnYWFjJzoKICAgICAgICBjYXNlICdvZ2cnOgogICAgICAgIGNhc2UgJ29nYSc6CiAgICAgICAgY2FzZSAnd21hJzoKICAgICAgICBjYXNlICdta2EnOgogICAgICAgIGNhc2UgJ2ZsYWMnOgogICAgICAgIGNhc2UgJ2FjMyc6CiAgICAgICAgY2FzZSAndGRzJzoKICAgICAgICAgICAgJGltZyA9ICdmYSBmYS1tdXNpYyc7CiAgICAgICAgICAgIGJyZWFrOwogICAgICAgIGNhc2UgJ20zdSc6CiAgICAgICAgY2FzZSAnbTN1OCc6CiAgICAgICAgY2FzZSAncGxzJzoKICAgICAgICBjYXNlICdjdWUnOgogICAgICAgIGNhc2UgJ3hzcGYnOgogICAgICAgICAgICAkaW1nID0gJ2ZhIGZhLWhlYWRwaG9uZXMnOwogICAgICAgICAgICBicmVhazsKICAgICAgICBjYXNlICdhdmknOgogICAgICAgIGNhc2UgJ21wZyc6CiAgICAgICAgY2FzZSAnbXBlZyc6CiAgICAgICAgY2FzZSAnbXA0JzoKICAgICAgICBjYXNlICdtNHYnOgogICAgICAgIGNhc2UgJ2Zsdic6CiAgICAgICAgY2FzZSAnZjR2JzoKICAgICAgICBjYXNlICdvZ20nOgogICAgICAgIGNhc2UgJ29ndic6CiAgICAgICAgY2FzZSAnbW92JzoKICAgICAgICBjYXNlICdta3YnOgogICAgICAgIGNhc2UgJzNncCc6CiAgICAgICAgY2FzZSAnYXNmJzoKICAgICAgICBjYXNlICd3bXYnOgogICAgICAgIGNhc2UgJ3dlYm0nOgogICAgICAgICAgICAkaW1nID0gJ2ZhIGZhLWZpbGUtdmlkZW8tbyc7CiAgICAgICAgICAgIGJyZWFrOwogICAgICAgIGNhc2UgJ2VtbCc6CiAgICAgICAgY2FzZSAnbXNnJzoKICAgICAgICAgICAgJGltZyA9ICdmYSBmYS1lbnZlbG9wZS1vJzsKICAgICAgICAgICAgYnJlYWs7CiAgICAgICAgY2FzZSAneGxzJzoKICAgICAgICBjYXNlICd4bHN4JzoKICAgICAgICBjYXNlICdvZHMnOgogICAgICAgICAgICAkaW1nID0gJ2ZhIGZhLWZpbGUtZXhjZWwtbyc7CiAgICAgICAgICAgIGJyZWFrOwogICAgICAgIGNhc2UgJ2Nzdic6CiAgICAgICAgICAgICRpbWcgPSAnZmEgZmEtZmlsZS10ZXh0LW8nOwogICAgICAgICAgICBicmVhazsKICAgICAgICBjYXNlICdiYWsnOgogICAgICAgIGNhc2UgJ3N3cCc6CiAgICAgICAgICAgICRpbWcgPSAnZmEgZmEtY2xpcGJvYXJkJzsKICAgICAgICAgICAgYnJlYWs7CiAgICAgICAgY2FzZSAnZG9jJzoKICAgICAgICBjYXNlICdkb2N4JzoKICAgICAgICBjYXNlICdvZHQnOgogICAgICAgICAgICAkaW1nID0gJ2ZhIGZhLWZpbGUtd29yZC1vJzsKICAgICAgICAgICAgYnJlYWs7CiAgICAgICAgY2FzZSAncHB0JzoKICAgICAgICBjYXNlICdwcHR4JzoKICAgICAgICAgICAgJGltZyA9ICdmYSBmYS1maWxlLXBvd2VycG9pbnQtbyc7CiAgICAgICAgICAgIGJyZWFrOwogICAgICAgIGNhc2UgJ3R0Zic6CiAgICAgICAgY2FzZSAndHRjJzoKICAgICAgICBjYXNlICdvdGYnOgogICAgICAgIGNhc2UgJ3dvZmYnOgogICAgICAgIGNhc2UgJ3dvZmYyJzoKICAgICAgICBjYXNlICdlb3QnOgogICAgICAgIGNhc2UgJ2Zvbic6CiAgICAgICAgICAgICRpbWcgPSAnZmEgZmEtZm9udCc7CiAgICAgICAgICAgIGJyZWFrOwogICAgICAgIGNhc2UgJ3BkZic6CiAgICAgICAgICAgICRpbWcgPSAnZmEgZmEtZmlsZS1wZGYtbyc7CiAgICAgICAgICAgIGJyZWFrOwogICAgICAgIGNhc2UgJ3BzZCc6CiAgICAgICAgY2FzZSAnYWknOgogICAgICAgIGNhc2UgJ2Vwcyc6CiAgICAgICAgY2FzZSAnZmxhJzoKICAgICAgICBjYXNlICdzd2YnOgogICAgICAgICAgICAkaW1nID0gJ2ZhIGZhLWZpbGUtaW1hZ2Utbyc7CiAgICAgICAgICAgIGJyZWFrOwogICAgICAgIGNhc2UgJ2V4ZSc6CiAgICAgICAgY2FzZSAnbXNpJzoKICAgICAgICAgICAgJGltZyA9ICdmYSBmYS1maWxlLW8nOwogICAgICAgICAgICBicmVhazsKICAgICAgICBjYXNlICdiYXQnOgogICAgICAgICAgICAkaW1nID0gJ2ZhIGZhLXRlcm1pbmFsJzsKICAgICAgICAgICAgYnJlYWs7CiAgICAgICAgZGVmYXVsdDoKICAgICAgICAgICAgJGltZyA9ICdmYSBmYS1pbmZvLWNpcmNsZSc7CiAgICB9CgogICAgcmV0dXJuICRpbWc7Cn0KCi8qKgogKiBHZXQgaW1hZ2UgZmlsZXMgZXh0ZW5zaW9ucwogKiBAcmV0dXJuIGFycmF5CiAqLwpmdW5jdGlvbiBmbV9nZXRfaW1hZ2VfZXh0cygpCnsKICAgIHJldHVybiBhcnJheSgnaWNvJywgJ2dpZicsICdqcGcnLCAnanBlZycsICdqcGMnLCAnanAyJywgJ2pweCcsICd4Ym0nLCAnd2JtcCcsICdwbmcnLCAnYm1wJywgJ3RpZicsICd0aWZmJywgJ3BzZCcsICdzdmcnLCAnd2VicCcsICdhdmlmJyk7Cn0KCi8qKgogKiBHZXQgdmlkZW8gZmlsZXMgZXh0ZW5zaW9ucwogKiBAcmV0dXJuIGFycmF5CiAqLwpmdW5jdGlvbiBmbV9nZXRfdmlkZW9fZXh0cygpCnsKICAgIHJldHVybiBhcnJheSgnYXZpJywgJ3dlYm0nLCAnd212JywgJ21wNCcsICdtNHYnLCAnb2dtJywgJ29ndicsICdtb3YnLCAnbWt2Jyk7Cn0KCi8qKgogKiBHZXQgYXVkaW8gZmlsZXMgZXh0ZW5zaW9ucwogKiBAcmV0dXJuIGFycmF5CiAqLwpmdW5jdGlvbiBmbV9nZXRfYXVkaW9fZXh0cygpCnsKICAgIHJldHVybiBhcnJheSgnd2F2JywgJ21wMycsICdvZ2cnLCAnbTRhJyk7Cn0KCi8qKgogKiBHZXQgdGV4dCBmaWxlIGV4dGVuc2lvbnMKICogQHJldHVybiBhcnJheQogKi8KZnVuY3Rpb24gZm1fZ2V0X3RleHRfZXh0cygpCnsKICAgIHJldHVybiBhcnJheSgKICAgICAgICAndHh0JywgJ2NzcycsICdpbmknLCAnY29uZicsICdsb2cnLCAnaHRhY2Nlc3MnLCAncGFzc3dkJywgJ2Z0cHF1b3RhJywgJ3NxbCcsICdqcycsICd0cycsICdqc3gnLCAndHN4JywgJ21qcycsICdqc29uJywgJ3NoJywgJ2NvbmZpZycsCiAgICAgICAgJ3BocCcsICdwaHA0JywgJ3BocDUnLCAncGhwcycsICdwaHRtbCcsICdodG0nLCAnaHRtbCcsICdzaHRtbCcsICd4aHRtbCcsICd4bWwnLCAneHNsJywgJ20zdScsICdtM3U4JywgJ3BscycsICdjdWUnLCAnYmFzaCcsICd0cGwnLCAndnVlJywKICAgICAgICAnZW1sJywgJ21zZycsICdjc3YnLCAnYmF0JywgJ3R3aWcnLCAndHBsJywgJ21kJywgJ2dpdGlnbm9yZScsICdsZXNzJywgJ3Nhc3MnLCAnc2NzcycsICdjJywgJ2NwcCcsICdjcycsICdweScsICdnbycsICd6c2gnLCAnc3dpZnQnLCAneW1sJywKICAgICAgICAnbWFwJywgJ2xvY2snLCAnZHRkJywgJ3N2ZycsICdzY3NzJywgJ2FzcCcsICdhc3B4JywgJ2FzeCcsICdhc214JywgJ2FzaHgnLCAnanNwJywgJ2pzcHgnLCAnY2ZtJywgJ2NnaScsICdkb2NrZXJmaWxlJywgJ3J1YnknLCAndHdpZycsCiAgICAgICAgJ3ltbCcsICd5YW1sJywgJ3RvbWwnLCAnbWQnLCAndmhvc3QnLCAnc2NwdCcsICdhcHBsZXNjcmlwdCcsICdjJywgJ2NzJywgJ2NzeCcsICdjc2h0bWwnLCAnY3BwJywgJ2MrKycsICdjb2ZmZWUnLCAnY2ZtJywgJ3JiJywKICAgICAgICAnZ3JhcGhxbCcsICdtdXN0YWNoZScsICdqaW5qYScsICdwaHRtbCcsICdodHRwJywgJ2hhbmRsZWJhcnMnLCAnbG9jaycsICdqYXZhJywgJ2VzJywgJ2VzNicsICdtYXJrZG93bicsICd3aWtpJywgJ3Zob3N0JywgJ3NxbCcsCiAgICApOwp9CgovKioKICogR2V0IG1pbWUgdHlwZXMgb2YgdGV4dCBmaWxlcwogKiBAcmV0dXJuIGFycmF5CiAqLwpmdW5jdGlvbiBmbV9nZXRfdGV4dF9taW1lcygpCnsKICAgIHJldHVybiBhcnJheSgKICAgICAgICAnYXBwbGljYXRpb24veG1sJywKICAgICAgICAnYXBwbGljYXRpb24vamF2YXNjcmlwdCcsCiAgICAgICAgJ2FwcGxpY2F0aW9uL3gtamF2YXNjcmlwdCcsCiAgICAgICAgJ2ltYWdlL3N2Zyt4bWwnLAogICAgICAgICdtZXNzYWdlL3JmYzgyMicsCiAgICAgICAgJ2FwcGxpY2F0aW9uL2pzb24nLAogICAgKTsKfQoKLyoqCiAqIEdldCBmaWxlIG5hbWVzIG9mIHRleHQgZmlsZXMgdy9vIGV4dGVuc2lvbnMKICogQHJldHVybiBhcnJheQogKi8KZnVuY3Rpb24gZm1fZ2V0X3RleHRfbmFtZXMoKQp7CiAgICByZXR1cm4gYXJyYXkoCiAgICAgICAgJ2xpY2Vuc2UnLAogICAgICAgICdyZWFkbWUnLAogICAgICAgICdhdXRob3JzJywKICAgICAgICAnY29udHJpYnV0b3JzJywKICAgICAgICAnY2hhbmdlbG9nJywKICAgICk7Cn0KCi8qKgogKiBHZXQgb25saW5lIGRvY3Mgdmlld2VyIHN1cHBvcnRlZCBmaWxlcyBleHRlbnNpb25zCiAqIEByZXR1cm4gYXJyYXkKICovCmZ1bmN0aW9uIGZtX2dldF9vbmxpbmVWaWV3ZXJfZXh0cygpCnsKICAgIHJldHVybiBhcnJheSgnZG9jJywgJ2RvY3gnLCAneGxzJywgJ3hsc3gnLCAncGRmJywgJ3BwdCcsICdwcHR4JywgJ2FpJywgJ3BzZCcsICdkeGYnLCAneHBzJywgJ3JhcicsICdvZHQnLCAnb2RzJyk7Cn0KCi8qKgogKiBJdCByZXR1cm5zIHRoZSBtaW1lIHR5cGUgb2YgYSBmaWxlIGJhc2VkIG9uIGl0cyBleHRlbnNpb24uCiAqIEBwYXJhbSBleHRlbnNpb24gVGhlIGZpbGUgZXh0ZW5zaW9uIG9mIHRoZSBmaWxlIHlvdSB3YW50IHRvIGdldCB0aGUgbWltZSB0eXBlIGZvci4KICogQHJldHVybiBUaGUgbWltZSB0eXBlIG9mIHRoZSBmaWxlLgogKi8KZnVuY3Rpb24gZm1fZ2V0X2ZpbGVfbWltZXMoJGV4dGVuc2lvbikKewogICAgJGZpbGVUeXBlc1snc3dmJ10gPSAnYXBwbGljYXRpb24veC1zaG9ja3dhdmUtZmxhc2gnOwogICAgJGZpbGVUeXBlc1sncGRmJ10gPSAnYXBwbGljYXRpb24vcGRmJzsKICAgICRmaWxlVHlwZXNbJ2V4ZSddID0gJ2FwcGxpY2F0aW9uL29jdGV0LXN0cmVhbSc7CiAgICAkZmlsZVR5cGVzWyd6aXAnXSA9ICdhcHBsaWNhdGlvbi96aXAnOwogICAgJGZpbGVUeXBlc1snZG9jJ10gPSAnYXBwbGljYXRpb24vbXN3b3JkJzsKICAgICRmaWxlVHlwZXNbJ3hscyddID0gJ2FwcGxpY2F0aW9uL3ZuZC5tcy1leGNlbCc7CiAgICAkZmlsZVR5cGVzWydwcHQnXSA9ICdhcHBsaWNhdGlvbi92bmQubXMtcG93ZXJwb2ludCc7CiAgICAkZmlsZVR5cGVzWydnaWYnXSA9ICdpbWFnZS9naWYnOwogICAgJGZpbGVUeXBlc1sncG5nJ10gPSAnaW1hZ2UvcG5nJzsKICAgICRmaWxlVHlwZXNbJ2pwZWcnXSA9ICdpbWFnZS9qcGcnOwogICAgJGZpbGVUeXBlc1snanBnJ10gPSAnaW1hZ2UvanBnJzsKICAgICRmaWxlVHlwZXNbJ3dlYnAnXSA9ICdpbWFnZS93ZWJwJzsKICAgICRmaWxlVHlwZXNbJ2F2aWYnXSA9ICdpbWFnZS9hdmlmJzsKICAgICRmaWxlVHlwZXNbJ3JhciddID0gJ2FwcGxpY2F0aW9uL3Jhcic7CgogICAgJGZpbGVUeXBlc1sncmEnXSA9ICdhdWRpby94LXBuLXJlYWxhdWRpbyc7CiAgICAkZmlsZVR5cGVzWydyYW0nXSA9ICdhdWRpby94LXBuLXJlYWxhdWRpbyc7CiAgICAkZmlsZVR5cGVzWydvZ2cnXSA9ICdhdWRpby94LXBuLXJlYWxhdWRpbyc7CgogICAgJGZpbGVUeXBlc1snd2F2J10gPSAndmlkZW8veC1tc3ZpZGVvJzsKICAgICRmaWxlVHlwZXNbJ3dtdiddID0gJ3ZpZGVvL3gtbXN2aWRlbyc7CiAgICAkZmlsZVR5cGVzWydhdmknXSA9ICd2aWRlby94LW1zdmlkZW8nOwogICAgJGZpbGVUeXBlc1snYXNmJ10gPSAndmlkZW8veC1tc3ZpZGVvJzsKICAgICRmaWxlVHlwZXNbJ2RpdngnXSA9ICd2aWRlby94LW1zdmlkZW8nOwoKICAgICRmaWxlVHlwZXNbJ21wMyddID0gJ2F1ZGlvL21wZWcnOwogICAgJGZpbGVUeXBlc1snbXA0J10gPSAnYXVkaW8vbXBlZyc7CiAgICAkZmlsZVR5cGVzWydtcGVnJ10gPSAndmlkZW8vbXBlZyc7CiAgICAkZmlsZVR5cGVzWydtcGcnXSA9ICd2aWRlby9tcGVnJzsKICAgICRmaWxlVHlwZXNbJ21wZSddID0gJ3ZpZGVvL21wZWcnOwogICAgJGZpbGVUeXBlc1snbW92J10gPSAndmlkZW8vcXVpY2t0aW1lJzsKICAgICRmaWxlVHlwZXNbJ3N3ZiddID0gJ3ZpZGVvL3F1aWNrdGltZSc7CiAgICAkZmlsZVR5cGVzWyczZ3AnXSA9ICd2aWRlby9xdWlja3RpbWUnOwogICAgJGZpbGVUeXBlc1snbTRhJ10gPSAndmlkZW8vcXVpY2t0aW1lJzsKICAgICRmaWxlVHlwZXNbJ2FhYyddID0gJ3ZpZGVvL3F1aWNrdGltZSc7CiAgICAkZmlsZVR5cGVzWydtM3UnXSA9ICd2aWRlby9xdWlja3RpbWUnOwoKICAgICRmaWxlVHlwZXNbJ3BocCddID0gWydhcHBsaWNhdGlvbi94LXBocCddOwogICAgJGZpbGVUeXBlc1snaHRtbCddID0gWyd0ZXh0L2h0bWwnXTsKICAgICRmaWxlVHlwZXNbJ3R4dCddID0gWyd0ZXh0L3BsYWluJ107CiAgICAvL1Vua25vd24gbWltZS10eXBlcyBzaG91bGQgYmUgJ2FwcGxpY2F0aW9uL29jdGV0LXN0cmVhbScKICAgIGlmKGVtcHR5KCRmaWxlVHlwZXNbJGV4dGVuc2lvbl0pKSB7CiAgICAgICRmaWxlVHlwZXNbJGV4dGVuc2lvbl0gPSBbJ2FwcGxpY2F0aW9uL29jdGV0LXN0cmVhbSddOwogICAgfQogICAgcmV0dXJuICRmaWxlVHlwZXNbJGV4dGVuc2lvbl07Cn0KCi8qKgogKiBUaGlzIGZ1bmN0aW9uIHNjYW5zIHRoZSBmaWxlcyBhbmQgZm9sZGVyIHJlY3Vyc2l2ZWx5LCBhbmQgcmV0dXJuIG1hdGNoaW5nIGZpbGVzCiAqIEBwYXJhbSBzdHJpbmcgJGRpcgogKiBAcGFyYW0gc3RyaW5nICRmaWx0ZXIKICogQHJldHVybiBqc29uCiAqLwogZnVuY3Rpb24gc2NhbigkZGlyID0gJycsICRmaWx0ZXIgPSAnJykgewogICAgJHBhdGggPSBGTV9ST09UX1BBVEguJy8nLiRkaXI7CiAgICAgaWYoJHBhdGgpIHsKICAgICAgICAgJGl0ZSA9IG5ldyBSZWN1cnNpdmVJdGVyYXRvckl0ZXJhdG9yKG5ldyBSZWN1cnNpdmVEaXJlY3RvcnlJdGVyYXRvcigkcGF0aCkpOwogICAgICAgICAkcmlpID0gbmV3IFJlZ2V4SXRlcmF0b3IoJGl0ZSwgIi8oIiAuICRmaWx0ZXIgLiAiKS9pIik7CgogICAgICAgICAkZmlsZXMgPSBhcnJheSgpOwogICAgICAgICBmb3JlYWNoICgkcmlpIGFzICRmaWxlKSB7CiAgICAgICAgICAgICBpZiAoISRmaWxlLT5pc0RpcigpKSB7CiAgICAgICAgICAgICAgICAgJGZpbGVOYW1lID0gJGZpbGUtPmdldEZpbGVuYW1lKCk7CiAgICAgICAgICAgICAgICAgJGxvY2F0aW9uID0gc3RyX3JlcGxhY2UoRk1fUk9PVF9QQVRILCAnJywgJGZpbGUtPmdldFBhdGgoKSk7CiAgICAgICAgICAgICAgICAgJGZpbGVzW10gPSBhcnJheSgKICAgICAgICAgICAgICAgICAgICAgIm5hbWUiID0+ICRmaWxlTmFtZSwKICAgICAgICAgICAgICAgICAgICAgInR5cGUiID0+ICJmaWxlIiwKICAgICAgICAgICAgICAgICAgICAgInBhdGgiID0+ICRsb2NhdGlvbiwKICAgICAgICAgICAgICAgICApOwogICAgICAgICAgICAgfQogICAgICAgICB9CiAgICAgICAgIHJldHVybiAkZmlsZXM7CiAgICAgfQp9CgovKioKKiBQYXJhbWV0ZXJzOiBkb3dubG9hZEZpbGUoRmlsZSBMb2NhdGlvbiwgRmlsZSBOYW1lLAoqIG1heCBzcGVlZCwgaXMgc3RyZWFtaW5nCiogSWYgc3RyZWFtaW5nIC0gdmlkZW9zIHdpbGwgc2hvdyBhcyB2aWRlb3MsIGltYWdlcyBhcyBpbWFnZXMKKiBpbnN0ZWFkIG9mIGRvd25sb2FkIHByb21wdAoqIGh0dHBzOi8vc3RhY2tvdmVyZmxvdy5jb20vYS8xMzgyMTk5Mi8xMTY0NjQyCiovCmZ1bmN0aW9uIGZtX2Rvd25sb2FkX2ZpbGUoJGZpbGVMb2NhdGlvbiwgJGZpbGVOYW1lLCAkY2h1bmtTaXplICA9IDEwMjQpCnsKICAgIGlmIChjb25uZWN0aW9uX3N0YXR1cygpICE9IDApCiAgICAgICAgcmV0dXJuIChmYWxzZSk7CiAgICAkZXh0ZW5zaW9uID0gcGF0aGluZm8oJGZpbGVOYW1lLCBQQVRISU5GT19FWFRFTlNJT04pOwoKICAgICRjb250ZW50VHlwZSA9IGZtX2dldF9maWxlX21pbWVzKCRleHRlbnNpb24pOwoKICAgIGlmKGlzX2FycmF5KCRjb250ZW50VHlwZSkpIHsKICAgICAgICAkY29udGVudFR5cGUgPSBpbXBsb2RlKCcgJywgJGNvbnRlbnRUeXBlKTsKICAgIH0KCiAgICAkc2l6ZSA9IGZpbGVzaXplKCRmaWxlTG9jYXRpb24pOwoKICAgIGlmICgkc2l6ZSA9PSAwKSB7CiAgICAgICAgZm1fc2V0X21zZyhsbmcoJ1plcm8gYnl0ZSBmaWxlISBBYm9ydGluZyBkb3dubG9hZCcpLCAnZXJyb3InKTsKICAgICAgICAkRk1fUEFUSD1GTV9QQVRIOyBmbV9yZWRpcmVjdChGTV9TRUxGX1VSTCAuICc/cD0nIC4gdXJsZW5jb2RlKCRGTV9QQVRIKSk7CgogICAgICAgIHJldHVybiAoZmFsc2UpOwogICAgfQoKICAgIEBpbmlfc2V0KCdtYWdpY19xdW90ZXNfcnVudGltZScsIDApOwogICAgJGZwID0gZm9wZW4oIiRmaWxlTG9jYXRpb24iLCAicmIiKTsKCiAgICBpZiAoJGZwID09PSBmYWxzZSkgewogICAgICAgIGZtX3NldF9tc2cobG5nKCdDYW5ub3Qgb3BlbiBmaWxlISBBYm9ydGluZyBkb3dubG9hZCcpLCAnZXJyb3InKTsKICAgICAgICAkRk1fUEFUSD1GTV9QQVRIOyBmbV9yZWRpcmVjdChGTV9TRUxGX1VSTCAuICc/cD0nIC4gdXJsZW5jb2RlKCRGTV9QQVRIKSk7CgogICAgICAgIHJldHVybiAoZmFsc2UpOwoKICAgIH0KICAgIAogICAgaGVhZGVyKCJDYWNoZS1Db250cm9sOiBwdWJsaWMiKTsKICAgIGhlYWRlcigiQ29udGVudC1UcmFuc2Zlci1FbmNvZGluZzogYmluYXJ5XG4iKTsKICAgIGhlYWRlcigiQ29udGVudC1UeXBlOiAkY29udGVudFR5cGUiKTsKCiAgICAkY29udGVudERpc3Bvc2l0aW9uID0gJ2F0dGFjaG1lbnQnOwoKCiAgICBpZiAoc3Ryc3RyKCRfU0VSVkVSWydIVFRQX1VTRVJfQUdFTlQnXSwgIk1TSUUiKSkgewogICAgICAgICRmaWxlTmFtZSA9IHByZWdfcmVwbGFjZSgnL1wuLycsICclMmUnLCAkZmlsZU5hbWUsIHN1YnN0cl9jb3VudCgkZmlsZU5hbWUsICcuJykgLSAxKTsKICAgICAgICBoZWFkZXIoIkNvbnRlbnQtRGlzcG9zaXRpb246ICRjb250ZW50RGlzcG9zaXRpb247ZmlsZW5hbWU9XCIkZmlsZU5hbWVcIiIpOwogICAgfSBlbHNlIHsKICAgICAgICBoZWFkZXIoIkNvbnRlbnQtRGlzcG9zaXRpb246ICRjb250ZW50RGlzcG9zaXRpb247ZmlsZW5hbWU9XCIkZmlsZU5hbWVcIiIpOwogICAgfQoKICAgIGhlYWRlcigiQWNjZXB0LVJhbmdlczogYnl0ZXMiKTsKICAgICRyYW5nZSA9IDA7CgogICAgaWYgKGlzc2V0KCRfU0VSVkVSWydIVFRQX1JBTkdFJ10pKSB7CiAgICAgICAgbGlzdCgkYSwgJHJhbmdlKSA9IGV4cGxvZGUoIj0iLCAkX1NFUlZFUlsnSFRUUF9SQU5HRSddKTsKICAgICAgICBzdHJfcmVwbGFjZSgkcmFuZ2UsICItIiwgJHJhbmdlKTsKICAgICAgICAkc2l6ZTIgPSAkc2l6ZSAtIDE7CiAgICAgICAgJG5ld19sZW5ndGggPSAkc2l6ZSAtICRyYW5nZTsKICAgICAgICBoZWFkZXIoIkhUVFAvMS4xIDIwNiBQYXJ0aWFsIENvbnRlbnQiKTsKICAgICAgICBoZWFkZXIoIkNvbnRlbnQtTGVuZ3RoOiAkbmV3X2xlbmd0aCIpOwogICAgICAgIGhlYWRlcigiQ29udGVudC1SYW5nZTogYnl0ZXMgJHJhbmdlJHNpemUyLyRzaXplIik7CiAgICB9IGVsc2UgewogICAgICAgICRzaXplMiA9ICRzaXplIC0gMTsKICAgICAgICBoZWFkZXIoIkNvbnRlbnQtUmFuZ2U6IGJ5dGVzIDAtJHNpemUyLyRzaXplIik7CiAgICAgICAgaGVhZGVyKCJDb250ZW50LUxlbmd0aDogIiAuICRzaXplKTsKICAgIH0KCiAgICBmc2VlaygkZnAsICRyYW5nZSk7CgogICAgd2hpbGUgKCFAZmVvZigkZnApIGFuZCAoY29ubmVjdGlvbl9zdGF0dXMoKSA9PSAwKSkgewogICAgICAgIHNldF90aW1lX2xpbWl0KDApOwogICAgICAgIHByaW50KEBmcmVhZCgkZnAsIDEwMjQqJGNodW5rU2l6ZSkpOwogICAgICAgIGZsdXNoKCk7CiAgICAgICAgQG9iX2ZsdXNoKCk7CiAgICAgICAgLy8gc2xlZXAoMSk7CiAgICB9CiAgICBmY2xvc2UoJGZwKTsKCiAgICByZXR1cm4gKChjb25uZWN0aW9uX3N0YXR1cygpID09IDApIGFuZCAhY29ubmVjdGlvbl9hYm9ydGVkKCkpOwp9CgovKioKICogSWYgdGhlIHRoZW1lIGlzIGRhcmssIHJldHVybiB0aGUgdGV4dC13aGl0ZSBhbmQgYmctZGFyayBjbGFzc2VzLgogKiBAcmV0dXJuIHRoZSB2YWx1ZSBvZiB0aGUgIHZhcmlhYmxlLgogKi8KZnVuY3Rpb24gZm1fZ2V0X3RoZW1lKCkgewogICAgJHJlc3VsdCA9ICcnOwogICAgaWYoRk1fVEhFTUUgPT0gImRhcmsiKSB7CiAgICAgICAgJHJlc3VsdCA9ICJ0ZXh0LXdoaXRlIGJnLWRhcmsiOwogICAgfQogICAgcmV0dXJuICRyZXN1bHQ7Cn0KCi8qKgogKiBDbGFzcyB0byB3b3JrIHdpdGggemlwIGZpbGVzICh1c2luZyBaaXBBcmNoaXZlKQogKi8KY2xhc3MgRk1fWmlwcGVyCnsKICAgIHByaXZhdGUgJHppcDsKCiAgICBwdWJsaWMgZnVuY3Rpb24gX19jb25zdHJ1Y3QoKQogICAgewogICAgICAgICR0aGlzLT56aXAgPSBuZXcgWmlwQXJjaGl2ZSgpOwogICAgfQoKICAgIC8qKgogICAgICogQ3JlYXRlIGFyY2hpdmUgd2l0aCBuYW1lICRmaWxlbmFtZSBhbmQgZmlsZXMgJGZpbGVzIChSRUxBVElWRSBQQVRIUyEpCiAgICAgKiBAcGFyYW0gc3RyaW5nICRmaWxlbmFtZQogICAgICogQHBhcmFtIGFycmF5fHN0cmluZyAkZmlsZXMKICAgICAqIEByZXR1cm4gYm9vbAogICAgICovCiAgICBwdWJsaWMgZnVuY3Rpb24gY3JlYXRlKCRmaWxlbmFtZSwgJGZpbGVzKQogICAgewogICAgICAgICRyZXMgPSAkdGhpcy0+emlwLT5vcGVuKCRmaWxlbmFtZSwgWmlwQXJjaGl2ZTo6Q1JFQVRFKTsKICAgICAgICBpZiAoJHJlcyAhPT0gdHJ1ZSkgewogICAgICAgICAgICByZXR1cm4gZmFsc2U7CiAgICAgICAgfQogICAgICAgIGlmIChpc19hcnJheSgkZmlsZXMpKSB7CiAgICAgICAgICAgIGZvcmVhY2ggKCRmaWxlcyBhcyAkZikgewogICAgICAgICAgICAgICAgJGYgPSBmbV9jbGVhbl9wYXRoKCRmKTsKICAgICAgICAgICAgICAgIGlmICghJHRoaXMtPmFkZEZpbGVPckRpcigkZikpIHsKICAgICAgICAgICAgICAgICAgICAkdGhpcy0+emlwLT5jbG9zZSgpOwogICAgICAgICAgICAgICAgICAgIHJldHVybiBmYWxzZTsKICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgfQogICAgICAgICAgICAkdGhpcy0+emlwLT5jbG9zZSgpOwogICAgICAgICAgICByZXR1cm4gdHJ1ZTsKICAgICAgICB9IGVsc2UgewogICAgICAgICAgICBpZiAoJHRoaXMtPmFkZEZpbGVPckRpcigkZmlsZXMpKSB7CiAgICAgICAgICAgICAgICAkdGhpcy0+emlwLT5jbG9zZSgpOwogICAgICAgICAgICAgICAgcmV0dXJuIHRydWU7CiAgICAgICAgICAgIH0KICAgICAgICAgICAgcmV0dXJuIGZhbHNlOwogICAgICAgIH0KICAgIH0KCiAgICAvKioKICAgICAqIEV4dHJhY3QgYXJjaGl2ZSAkZmlsZW5hbWUgdG8gZm9sZGVyICRwYXRoIChSRUxBVElWRSBPUiBBQlNPTFVURSBQQVRIUykKICAgICAqIEBwYXJhbSBzdHJpbmcgJGZpbGVuYW1lCiAgICAgKiBAcGFyYW0gc3RyaW5nICRwYXRoCiAgICAgKiBAcmV0dXJuIGJvb2wKICAgICAqLwogICAgcHVibGljIGZ1bmN0aW9uIHVuemlwKCRmaWxlbmFtZSwgJHBhdGgpCiAgICB7CiAgICAgICAgJHJlcyA9ICR0aGlzLT56aXAtPm9wZW4oJGZpbGVuYW1lKTsKICAgICAgICBpZiAoJHJlcyAhPT0gdHJ1ZSkgewogICAgICAgICAgICByZXR1cm4gZmFsc2U7CiAgICAgICAgfQogICAgICAgIGlmICgkdGhpcy0+emlwLT5leHRyYWN0VG8oJHBhdGgpKSB7CiAgICAgICAgICAgICR0aGlzLT56aXAtPmNsb3NlKCk7CiAgICAgICAgICAgIHJldHVybiB0cnVlOwogICAgICAgIH0KICAgICAgICByZXR1cm4gZmFsc2U7CiAgICB9CgogICAgLyoqCiAgICAgKiBBZGQgZmlsZS9mb2xkZXIgdG8gYXJjaGl2ZQogICAgICogQHBhcmFtIHN0cmluZyAkZmlsZW5hbWUKICAgICAqIEByZXR1cm4gYm9vbAogICAgICovCiAgICBwcml2YXRlIGZ1bmN0aW9uIGFkZEZpbGVPckRpcigkZmlsZW5hbWUpCiAgICB7CiAgICAgICAgaWYgKGlzX2ZpbGUoJGZpbGVuYW1lKSkgewogICAgICAgICAgICByZXR1cm4gJHRoaXMtPnppcC0+YWRkRmlsZSgkZmlsZW5hbWUpOwogICAgICAgIH0gZWxzZWlmIChpc19kaXIoJGZpbGVuYW1lKSkgewogICAgICAgICAgICByZXR1cm4gJHRoaXMtPmFkZERpcigkZmlsZW5hbWUpOwogICAgICAgIH0KICAgICAgICByZXR1cm4gZmFsc2U7CiAgICB9CgogICAgLyoqCiAgICAgKiBBZGQgZm9sZGVyIHJlY3Vyc2l2ZWx5CiAgICAgKiBAcGFyYW0gc3RyaW5nICRwYXRoCiAgICAgKiBAcmV0dXJuIGJvb2wKICAgICAqLwogICAgcHJpdmF0ZSBmdW5jdGlvbiBhZGREaXIoJHBhdGgpCiAgICB7CiAgICAgICAgaWYgKCEkdGhpcy0+emlwLT5hZGRFbXB0eURpcigkcGF0aCkpIHsKICAgICAgICAgICAgcmV0dXJuIGZhbHNlOwogICAgICAgIH0KICAgICAgICAkb2JqZWN0cyA9IHNjYW5kaXIoJHBhdGgpOwogICAgICAgIGlmIChpc19hcnJheSgkb2JqZWN0cykpIHsKICAgICAgICAgICAgZm9yZWFjaCAoJG9iamVjdHMgYXMgJGZpbGUpIHsKICAgICAgICAgICAgICAgIGlmICgkZmlsZSAhPSAnLicgJiYgJGZpbGUgIT0gJy4uJykgewogICAgICAgICAgICAgICAgICAgIGlmIChpc19kaXIoJHBhdGggLiAnLycgLiAkZmlsZSkpIHsKICAgICAgICAgICAgICAgICAgICAgICAgaWYgKCEkdGhpcy0+YWRkRGlyKCRwYXRoIC4gJy8nIC4gJGZpbGUpKSB7CiAgICAgICAgICAgICAgICAgICAgICAgICAgICByZXR1cm4gZmFsc2U7CiAgICAgICAgICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgICAgICAgICB9IGVsc2VpZiAoaXNfZmlsZSgkcGF0aCAuICcvJyAuICRmaWxlKSkgewogICAgICAgICAgICAgICAgICAgICAgICBpZiAoISR0aGlzLT56aXAtPmFkZEZpbGUoJHBhdGggLiAnLycgLiAkZmlsZSkpIHsKICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJldHVybiBmYWxzZTsKICAgICAgICAgICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgfQogICAgICAgICAgICByZXR1cm4gdHJ1ZTsKICAgICAgICB9CiAgICAgICAgcmV0dXJuIGZhbHNlOwogICAgfQp9CgovKioKICogQ2xhc3MgdG8gd29yayB3aXRoIFRhciBmaWxlcyAodXNpbmcgUGhhckRhdGEpCiAqLwpjbGFzcyBGTV9aaXBwZXJfVGFyCnsKICAgIHByaXZhdGUgJHRhcjsKCiAgICBwdWJsaWMgZnVuY3Rpb24gX19jb25zdHJ1Y3QoKQogICAgewogICAgICAgICR0aGlzLT50YXIgPSBudWxsOwogICAgfQoKICAgIC8qKgogICAgICogQ3JlYXRlIGFyY2hpdmUgd2l0aCBuYW1lICRmaWxlbmFtZSBhbmQgZmlsZXMgJGZpbGVzIChSRUxBVElWRSBQQVRIUyEpCiAgICAgKiBAcGFyYW0gc3RyaW5nICRmaWxlbmFtZQogICAgICogQHBhcmFtIGFycmF5fHN0cmluZyAkZmlsZXMKICAgICAqIEByZXR1cm4gYm9vbAogICAgICovCiAgICBwdWJsaWMgZnVuY3Rpb24gY3JlYXRlKCRmaWxlbmFtZSwgJGZpbGVzKQogICAgewogICAgICAgICR0aGlzLT50YXIgPSBuZXcgUGhhckRhdGEoJGZpbGVuYW1lKTsKICAgICAgICBpZiAoaXNfYXJyYXkoJGZpbGVzKSkgewogICAgICAgICAgICBmb3JlYWNoICgkZmlsZXMgYXMgJGYpIHsKICAgICAgICAgICAgICAgICRmID0gZm1fY2xlYW5fcGF0aCgkZik7CiAgICAgICAgICAgICAgICBpZiAoISR0aGlzLT5hZGRGaWxlT3JEaXIoJGYpKSB7CiAgICAgICAgICAgICAgICAgICAgcmV0dXJuIGZhbHNlOwogICAgICAgICAgICAgICAgfQogICAgICAgICAgICB9CiAgICAgICAgICAgIHJldHVybiB0cnVlOwogICAgICAgIH0gZWxzZSB7CiAgICAgICAgICAgIGlmICgkdGhpcy0+YWRkRmlsZU9yRGlyKCRmaWxlcykpIHsKICAgICAgICAgICAgICAgIHJldHVybiB0cnVlOwogICAgICAgICAgICB9CiAgICAgICAgICAgIHJldHVybiBmYWxzZTsKICAgICAgICB9CiAgICB9CgogICAgLyoqCiAgICAgKiBFeHRyYWN0IGFyY2hpdmUgJGZpbGVuYW1lIHRvIGZvbGRlciAkcGF0aCAoUkVMQVRJVkUgT1IgQUJTT0xVVEUgUEFUSFMpCiAgICAgKiBAcGFyYW0gc3RyaW5nICRmaWxlbmFtZQogICAgICogQHBhcmFtIHN0cmluZyAkcGF0aAogICAgICogQHJldHVybiBib29sCiAgICAgKi8KICAgIHB1YmxpYyBmdW5jdGlvbiB1bnppcCgkZmlsZW5hbWUsICRwYXRoKQogICAgewogICAgICAgICRyZXMgPSAkdGhpcy0+dGFyLT5vcGVuKCRmaWxlbmFtZSk7CiAgICAgICAgaWYgKCRyZXMgIT09IHRydWUpIHsKICAgICAgICAgICAgcmV0dXJuIGZhbHNlOwogICAgICAgIH0KICAgICAgICBpZiAoJHRoaXMtPnRhci0+ZXh0cmFjdFRvKCRwYXRoKSkgewogICAgICAgICAgICByZXR1cm4gdHJ1ZTsKICAgICAgICB9CiAgICAgICAgcmV0dXJuIGZhbHNlOwogICAgfQoKICAgIC8qKgogICAgICogQWRkIGZpbGUvZm9sZGVyIHRvIGFyY2hpdmUKICAgICAqIEBwYXJhbSBzdHJpbmcgJGZpbGVuYW1lCiAgICAgKiBAcmV0dXJuIGJvb2wKICAgICAqLwogICAgcHJpdmF0ZSBmdW5jdGlvbiBhZGRGaWxlT3JEaXIoJGZpbGVuYW1lKQogICAgewogICAgICAgIGlmIChpc19maWxlKCRmaWxlbmFtZSkpIHsKICAgICAgICAgICAgdHJ5IHsKICAgICAgICAgICAgICAgICR0aGlzLT50YXItPmFkZEZpbGUoJGZpbGVuYW1lKTsKICAgICAgICAgICAgICAgIHJldHVybiB0cnVlOwogICAgICAgICAgICB9IGNhdGNoIChFeGNlcHRpb24gJGUpIHsKICAgICAgICAgICAgICAgIHJldHVybiBmYWxzZTsKICAgICAgICAgICAgfQogICAgICAgIH0gZWxzZWlmIChpc19kaXIoJGZpbGVuYW1lKSkgewogICAgICAgICAgICByZXR1cm4gJHRoaXMtPmFkZERpcigkZmlsZW5hbWUpOwogICAgICAgIH0KICAgICAgICByZXR1cm4gZmFsc2U7CiAgICB9CgogICAgLyoqCiAgICAgKiBBZGQgZm9sZGVyIHJlY3Vyc2l2ZWx5CiAgICAgKiBAcGFyYW0gc3RyaW5nICRwYXRoCiAgICAgKiBAcmV0dXJuIGJvb2wKICAgICAqLwogICAgcHJpdmF0ZSBmdW5jdGlvbiBhZGREaXIoJHBhdGgpCiAgICB7CiAgICAgICAgJG9iamVjdHMgPSBzY2FuZGlyKCRwYXRoKTsKICAgICAgICBpZiAoaXNfYXJyYXkoJG9iamVjdHMpKSB7CiAgICAgICAgICAgIGZvcmVhY2ggKCRvYmplY3RzIGFzICRmaWxlKSB7CiAgICAgICAgICAgICAgICBpZiAoJGZpbGUgIT0gJy4nICYmICRmaWxlICE9ICcuLicpIHsKICAgICAgICAgICAgICAgICAgICBpZiAoaXNfZGlyKCRwYXRoIC4gJy8nIC4gJGZpbGUpKSB7CiAgICAgICAgICAgICAgICAgICAgICAgIGlmICghJHRoaXMtPmFkZERpcigkcGF0aCAuICcvJyAuICRmaWxlKSkgewogICAgICAgICAgICAgICAgICAgICAgICAgICAgcmV0dXJuIGZhbHNlOwogICAgICAgICAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgICAgICAgICAgfSBlbHNlaWYgKGlzX2ZpbGUoJHBhdGggLiAnLycgLiAkZmlsZSkpIHsKICAgICAgICAgICAgICAgICAgICAgICAgdHJ5IHsKICAgICAgICAgICAgICAgICAgICAgICAgICAgICR0aGlzLT50YXItPmFkZEZpbGUoJHBhdGggLiAnLycgLiAkZmlsZSk7CiAgICAgICAgICAgICAgICAgICAgICAgIH0gY2F0Y2ggKEV4Y2VwdGlvbiAkZSkgewogICAgICAgICAgICAgICAgICAgICAgICAgICAgcmV0dXJuIGZhbHNlOwogICAgICAgICAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgICAgfQogICAgICAgICAgICB9CiAgICAgICAgICAgIHJldHVybiB0cnVlOwogICAgICAgIH0KICAgICAgICByZXR1cm4gZmFsc2U7CiAgICB9Cn0KCi8qKgogKiBTYXZlIENvbmZpZ3VyYXRpb24KICovCiBjbGFzcyBGTV9Db25maWcKewogICAgIHZhciAkZGF0YTsKCiAgICBmdW5jdGlvbiBfX2NvbnN0cnVjdCgpCiAgICB7CiAgICAgICAgZ2xvYmFsICRyb290X3BhdGgsICRyb290X3VybCwgJENPTkZJRzsKICAgICAgICAkZm1fdXJsID0gJHJvb3RfdXJsLiRfU0VSVkVSWyJQSFBfU0VMRiJdOwogICAgICAgICR0aGlzLT5kYXRhID0gYXJyYXkoCiAgICAgICAgICAgICdsYW5nJyA9PiAnZW4nLAogICAgICAgICAgICAnZXJyb3JfcmVwb3J0aW5nJyA9PiB0cnVlLAogICAgICAgICAgICAnc2hvd19oaWRkZW4nID0+IHRydWUKICAgICAgICApOwogICAgICAgICRkYXRhID0gZmFsc2U7CiAgICAgICAgaWYgKHN0cmxlbigkQ09ORklHKSkgewogICAgICAgICAgICAkZGF0YSA9IGZtX29iamVjdF90b19hcnJheShqc29uX2RlY29kZSgkQ09ORklHKSk7CiAgICAgICAgfSBlbHNlIHsKICAgICAgICAgICAgJG1zZyA9ICdUaW55IEZpbGUgTWFuYWdlcjxicj5FcnJvcjogQ2Fubm90IGxvYWQgY29uZmlndXJhdGlvbic7CiAgICAgICAgICAgIGlmIChzdWJzdHIoJGZtX3VybCwgLTEpID09ICcvJykgewogICAgICAgICAgICAgICAgJGZtX3VybCA9IHJ0cmltKCRmbV91cmwsICcvJyk7CiAgICAgICAgICAgICAgICAkbXNnIC49ICc8YnI+JzsKICAgICAgICAgICAgICAgICRtc2cgLj0gJzxicj5TZWVtcyBsaWtlIHlvdSBoYXZlIGEgdHJhaWxpbmcgc2xhc2ggb24gdGhlIFVSTC4nOwogICAgICAgICAgICAgICAgJG1zZyAuPSAnPGJyPlRyeSB0aGlzIGxpbms6IDxhIGhyZWY9IicgLiAkZm1fdXJsIC4gJyI+JyAuICRmbV91cmwgLiAnPC9hPic7CiAgICAgICAgICAgIH0KICAgICAgICAgICAgZGllKCRtc2cpOwogICAgICAgIH0KICAgICAgICBpZiAoaXNfYXJyYXkoJGRhdGEpICYmIGNvdW50KCRkYXRhKSkgJHRoaXMtPmRhdGEgPSAkZGF0YTsKICAgICAgICBlbHNlICR0aGlzLT5zYXZlKCk7CiAgICB9CgogICAgZnVuY3Rpb24gc2F2ZSgpCiAgICB7CiAgICAgICAgJGZtX2ZpbGUgPSBfX0ZJTEVfXzsKICAgICAgICAkdmFyX25hbWUgPSAnJENPTkZJRyc7CiAgICAgICAgJHZhcl92YWx1ZSA9IHZhcl9leHBvcnQoanNvbl9lbmNvZGUoJHRoaXMtPmRhdGEpLCB0cnVlKTsKICAgICAgICAkY29uZmlnX3N0cmluZyA9ICI8P3BocCIgLiBjaHIoMTMpIC4gY2hyKDEwKSAuICIvL0RlZmF1bHQgQ29uZmlndXJhdGlvbiIuY2hyKDEzKSAuIGNocigxMCkuIiR2YXJfbmFtZSA9ICR2YXJfdmFsdWU7IiAuIGNocigxMykgLiBjaHIoMTApOwogICAgICAgIGlmIChpc193cml0YWJsZSgkZm1fZmlsZSkpIHsKICAgICAgICAgICAgJGxpbmVzID0gZmlsZSgkZm1fZmlsZSk7CiAgICAgICAgICAgIGlmICgkZmggPSBAZm9wZW4oJGZtX2ZpbGUsICJ3IikpIHsKICAgICAgICAgICAgICAgIEBmcHV0cygkZmgsICRjb25maWdfc3RyaW5nLCBzdHJsZW4oJGNvbmZpZ19zdHJpbmcpKTsKICAgICAgICAgICAgICAgIGZvciAoJHggPSAzOyAkeCA8IGNvdW50KCRsaW5lcyk7ICR4KyspIHsKICAgICAgICAgICAgICAgICAgICBAZnB1dHMoJGZoLCAkbGluZXNbJHhdLCBzdHJsZW4oJGxpbmVzWyR4XSkpOwogICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgICAgQGZjbG9zZSgkZmgpOwogICAgICAgICAgICB9CiAgICAgICAgfQogICAgfQp9CgovLy0tLSBUZW1wbGF0ZXMgRnVuY3Rpb25zIC0tLQoKLyoqCiAqIFNob3cgbmF2IGJsb2NrCiAqIEBwYXJhbSBzdHJpbmcgJHBhdGgKICovCmZ1bmN0aW9uIGZtX3Nob3dfbmF2X3BhdGgoJHBhdGgpCnsKICAgIGdsb2JhbCAkbGFuZywgJHN0aWNreV9uYXZiYXIsICRlZGl0RmlsZTsKICAgICRpc1N0aWNreU5hdkJhciA9ICRzdGlja3lfbmF2YmFyID8gJ2ZpeGVkLXRvcCcgOiAnJzsKICAgICRnZXRUaGVtZSA9IGZtX2dldF90aGVtZSgpOwogICAgJGdldFRoZW1lIC49ICIgbmF2YmFyLWxpZ2h0IjsKICAgIGlmKEZNX1RIRU1FID09ICJkYXJrIikgewogICAgICAgICRnZXRUaGVtZSAuPSAiIG5hdmJhci1kYXJrIjsKICAgIH0gZWxzZSB7CiAgICAgICAgJGdldFRoZW1lIC49ICIgYmctd2hpdGUiOwogICAgfQogICAgPz4KICAgIDxuYXYgY2xhc3M9Im5hdmJhciBuYXZiYXItZXhwYW5kLWxnIDw/cGhwIGVjaG8gJGdldFRoZW1lOyA/PiBtYi00IG1haW4tbmF2IDw/cGhwIGVjaG8gJGlzU3RpY2t5TmF2QmFyID8+Ij4KICAgICAgICA8YSBjbGFzcz0ibmF2YmFyLWJyYW5kIj4gPD9waHAgZWNobyBsbmcoJ0FwcFRpdGxlJykgPz4gPC9hPgogICAgICAgIDxidXR0b24gY2xhc3M9Im5hdmJhci10b2dnbGVyIiB0eXBlPSJidXR0b24iIGRhdGEtYnMtdG9nZ2xlPSJjb2xsYXBzZSIgZGF0YS1icy10YXJnZXQ9IiNuYXZiYXJTdXBwb3J0ZWRDb250ZW50IiBhcmlhLWNvbnRyb2xzPSJuYXZiYXJTdXBwb3J0ZWRDb250ZW50IiBhcmlhLWV4cGFuZGVkPSJmYWxzZSIgYXJpYS1sYWJlbD0iVG9nZ2xlIG5hdmlnYXRpb24iPgogICAgICAgICAgICA8c3BhbiBjbGFzcz0ibmF2YmFyLXRvZ2dsZXItaWNvbiI+PC9zcGFuPgogICAgICAgIDwvYnV0dG9uPgogICAgICAgIDxkaXYgY2xhc3M9ImNvbGxhcHNlIG5hdmJhci1jb2xsYXBzZSIgaWQ9Im5hdmJhclN1cHBvcnRlZENvbnRlbnQiPgoKICAgICAgICAgICAgPD9waHAKICAgICAgICAgICAgJHBhdGggPSBmbV9jbGVhbl9wYXRoKCRwYXRoKTsKICAgICAgICAgICAgJHJvb3RfdXJsID0gIjxhIGhyZWY9Jz9wPSc+PGkgY2xhc3M9J2ZhIGZhLWhvbWUnIGFyaWEtaGlkZGVuPSd0cnVlJyB0aXRsZT0nIiAuIEZNX1JPT1RfUEFUSCAuICInPjwvaT48L2E+IjsKICAgICAgICAgICAgJHNlcCA9ICc8aSBjbGFzcz0iYnJlYWQtY3J1bWIiPiAvIDwvaT4nOwogICAgICAgICAgICBpZiAoJHBhdGggIT0gJycpIHsKICAgICAgICAgICAgICAgICRleHBsb2RlZCA9IGV4cGxvZGUoJy8nLCAkcGF0aCk7CiAgICAgICAgICAgICAgICAkY291bnQgPSBjb3VudCgkZXhwbG9kZWQpOwogICAgICAgICAgICAgICAgJGFycmF5ID0gYXJyYXkoKTsKICAgICAgICAgICAgICAgICRwYXJlbnQgPSAnJzsKICAgICAgICAgICAgICAgIGZvciAoJGkgPSAwOyAkaSA8ICRjb3VudDsgJGkrKykgewogICAgICAgICAgICAgICAgICAgICRwYXJlbnQgPSB0cmltKCRwYXJlbnQgLiAnLycgLiAkZXhwbG9kZWRbJGldLCAnLycpOwogICAgICAgICAgICAgICAgICAgICRwYXJlbnRfZW5jID0gdXJsZW5jb2RlKCRwYXJlbnQpOwogICAgICAgICAgICAgICAgICAgICRhcnJheVtdID0gIjxhIGhyZWY9Jz9wPXskcGFyZW50X2VuY30nPiIgLiBmbV9lbmMoZm1fY29udmVydF93aW4oJGV4cGxvZGVkWyRpXSkpIC4gIjwvYT4iOwogICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgICAgJHJvb3RfdXJsIC49ICRzZXAgLiBpbXBsb2RlKCRzZXAsICRhcnJheSk7CiAgICAgICAgICAgIH0KICAgICAgICAgICAgZWNobyAnPGRpdiBjbGFzcz0iY29sLXhzLTYgY29sLXNtLTUiPicgLiAkcm9vdF91cmwgLiAkZWRpdEZpbGUgLiAnPC9kaXY+JzsKICAgICAgICAgICAgPz4KCiAgICAgICAgICAgIDxkaXYgY2xhc3M9ImNvbC14cy02IGNvbC1zbS03Ij4KICAgICAgICAgICAgICAgIDx1bCBjbGFzcz0ibmF2YmFyLW5hdiBqdXN0aWZ5LWNvbnRlbnQtZW5kIDw/cGhwIGVjaG8gZm1fZ2V0X3RoZW1lKCk7ICA/PiI+CiAgICAgICAgICAgICAgICAgICAgPGxpIGNsYXNzPSJuYXYtaXRlbSBtci0yIj4KICAgICAgICAgICAgICAgICAgICAgICAgPGRpdiBjbGFzcz0iaW5wdXQtZ3JvdXAgaW5wdXQtZ3JvdXAtc20gbXItMSIgc3R5bGU9Im1hcmdpbi10b3A6NHB4OyI+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICA8aW5wdXQgdHlwZT0idGV4dCIgY2xhc3M9ImZvcm0tY29udHJvbCIgcGxhY2Vob2xkZXI9Ijw/cGhwIGVjaG8gbG5nKCdGaWx0ZXInKSA/PiIgYXJpYS1sYWJlbD0iPD9waHAgZWNobyBsbmcoJ1NlYXJjaCcpID8+IiBhcmlhLWRlc2NyaWJlZGJ5PSJzZWFyY2gtYWRkb24yIiBpZD0ic2VhcmNoLWFkZG9uIj4KICAgICAgICAgICAgICAgICAgICAgICAgICAgIDxkaXYgY2xhc3M9ImlucHV0LWdyb3VwLWFwcGVuZCI+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPHNwYW4gY2xhc3M9ImlucHV0LWdyb3VwLXRleHQgYnJsLTAgYnJyLTAiIGlkPSJzZWFyY2gtYWRkb24yIj48aSBjbGFzcz0iZmEgZmEtc2VhcmNoIj48L2k+PC9zcGFuPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgPC9kaXY+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICA8ZGl2IGNsYXNzPSJpbnB1dC1ncm91cC1hcHBlbmQgYnRuLWdyb3VwIj4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8c3BhbiBjbGFzcz0iaW5wdXQtZ3JvdXAtdGV4dCBkcm9wZG93bi10b2dnbGUgYnJsLTAiIGRhdGEtYnMtdG9nZ2xlPSJkcm9wZG93biIgYXJpYS1oYXNwb3B1cD0idHJ1ZSIgYXJpYS1leHBhbmRlZD0iZmFsc2UiPjwvc3Bhbj4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDxkaXYgY2xhc3M9ImRyb3Bkb3duLW1lbnUgZHJvcGRvd24tbWVudS1yaWdodCI+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDxhIGNsYXNzPSJkcm9wZG93bi1pdGVtIiBocmVmPSI8P3BocCBlY2hvICRwYXRoMiA9ICRwYXRoID8gJHBhdGggOiAnLic7ID8+IiBpZD0ianMtc2VhcmNoLW1vZGFsIiBkYXRhLWJzLXRvZ2dsZT0ibW9kYWwiIGRhdGEtYnMtdGFyZ2V0PSIjc2VhcmNoTW9kYWwiPjw/cGhwIGVjaG8gbG5nKCdBZHZhbmNlZCBTZWFyY2gnKSA/PjwvYT4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDwvZGl2PgogICAgICAgICAgICAgICAgICAgICAgICAgICAgPC9kaXY+CiAgICAgICAgICAgICAgICAgICAgICAgIDwvZGl2PgogICAgICAgICAgICAgICAgICAgIDwvbGk+CiAgICAgICAgICAgICAgICAgICAgPD9waHAgaWYgKCFGTV9SRUFET05MWSk6ID8+CiAgICAgICAgICAgICAgICAgICAgPGxpIGNsYXNzPSJuYXYtaXRlbSI+CiAgICAgICAgICAgICAgICAgICAgICAgIDxhIHRpdGxlPSI8P3BocCBlY2hvIGxuZygnVXBsb2FkJykgPz4iIGNsYXNzPSJuYXYtbGluayIgaHJlZj0iP3A9PD9waHAgZWNobyB1cmxlbmNvZGUoRk1fUEFUSCkgPz4mYW1wO3VwbG9hZCI+PGkgY2xhc3M9ImZhIGZhLWNsb3VkLXVwbG9hZCIgYXJpYS1oaWRkZW49InRydWUiPjwvaT4gPD9waHAgZWNobyBsbmcoJ1VwbG9hZCcpID8+PC9hPgogICAgICAgICAgICAgICAgICAgIDwvbGk+CiAgICAgICAgICAgICAgICAgICAgPGxpIGNsYXNzPSJuYXYtaXRlbSI+CiAgICAgICAgICAgICAgICAgICAgICAgIDxhIHRpdGxlPSI8P3BocCBlY2hvIGxuZygnTmV3SXRlbScpID8+IiBjbGFzcz0ibmF2LWxpbmsiIGhyZWY9IiNjcmVhdGVOZXdJdGVtIiBkYXRhLWJzLXRvZ2dsZT0ibW9kYWwiIGRhdGEtYnMtdGFyZ2V0PSIjY3JlYXRlTmV3SXRlbSI+PGkgY2xhc3M9ImZhIGZhLXBsdXMtc3F1YXJlIj48L2k+IDw/cGhwIGVjaG8gbG5nKCdOZXdJdGVtJykgPz48L2E+CiAgICAgICAgICAgICAgICAgICAgPC9saT4KICAgICAgICAgICAgICAgICAgICA8P3BocCBlbmRpZjsgPz4KICAgICAgICAgICAgICAgICAgICA8P3BocCBpZiAoRk1fVVNFX0FVVEgpOiA/PgogICAgICAgICAgICAgICAgICAgIDxsaSBjbGFzcz0ibmF2LWl0ZW0gYXZhdGFyIGRyb3Bkb3duIj4KICAgICAgICAgICAgICAgICAgICAgICAgPGEgY2xhc3M9Im5hdi1saW5rIGRyb3Bkb3duLXRvZ2dsZSIgaWQ9Im5hdmJhckRyb3Bkb3duTWVudUxpbmstNSIgZGF0YS1icy10b2dnbGU9ImRyb3Bkb3duIiBhcmlhLWhhc3BvcHVwPSJ0cnVlIiBhcmlhLWV4cGFuZGVkPSJmYWxzZSI+IDxpIGNsYXNzPSJmYSBmYS11c2VyLWNpcmNsZSI+PC9pPiA8P3BocCBpZihpc3NldCgkX1NFU1NJT05bRk1fU0VTU0lPTl9JRF1bJ2xvZ2dlZCddKSkgeyBlY2hvICRfU0VTU0lPTltGTV9TRVNTSU9OX0lEXVsnbG9nZ2VkJ107IH0gPz48L2E+CiAgICAgICAgICAgICAgICAgICAgICAgIDxkaXYgY2xhc3M9ImRyb3Bkb3duLW1lbnUgdGV4dC1zbWFsbCBzaGFkb3cgPD9waHAgZWNobyBmbV9nZXRfdGhlbWUoKTsgPz4iIGFyaWEtbGFiZWxsZWRieT0ibmF2YmFyRHJvcGRvd25NZW51TGluay01Ij4KICAgICAgICAgICAgICAgICAgICAgICAgICAgIDw/cGhwIGlmICghRk1fUkVBRE9OTFkpOiA/PgogICAgICAgICAgICAgICAgICAgICAgICAgICAgPGEgdGl0bGU9Ijw/cGhwIGVjaG8gbG5nKCdTZXR0aW5ncycpID8+IiBjbGFzcz0iZHJvcGRvd24taXRlbSBuYXYtbGluayIgaHJlZj0iP3A9PD9waHAgZWNobyB1cmxlbmNvZGUoRk1fUEFUSCkgPz4mYW1wO3NldHRpbmdzPTEiPjxpIGNsYXNzPSJmYSBmYS1jb2ciIGFyaWEtaGlkZGVuPSJ0cnVlIj48L2k+IDw/cGhwIGVjaG8gbG5nKCdTZXR0aW5ncycpID8+PC9hPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgPD9waHAgZW5kaWYgPz4KICAgICAgICAgICAgICAgICAgICAgICAgICAgIDxhIHRpdGxlPSI8P3BocCBlY2hvIGxuZygnSGVscCcpID8+IiBjbGFzcz0iZHJvcGRvd24taXRlbSBuYXYtbGluayIgaHJlZj0iP3A9PD9waHAgZWNobyB1cmxlbmNvZGUoRk1fUEFUSCkgPz4mYW1wO2hlbHA9MiI+PGkgY2xhc3M9ImZhIGZhLWV4Y2xhbWF0aW9uLWNpcmNsZSIgYXJpYS1oaWRkZW49InRydWUiPjwvaT4gPD9waHAgZWNobyBsbmcoJ0hlbHAnKSA/PjwvYT4KICAgICAgICAgICAgICAgICAgICAgICAgICAgIDxhIHRpdGxlPSI8P3BocCBlY2hvIGxuZygnTG9nb3V0JykgPz4iIGNsYXNzPSJkcm9wZG93bi1pdGVtIG5hdi1saW5rIiBocmVmPSI/bG9nb3V0PTEiPjxpIGNsYXNzPSJmYSBmYS1zaWduLW91dCIgYXJpYS1oaWRkZW49InRydWUiPjwvaT4gPD9waHAgZWNobyBsbmcoJ0xvZ291dCcpID8+PC9hPgogICAgICAgICAgICAgICAgICAgICAgICA8L2Rpdj4KICAgICAgICAgICAgICAgICAgICA8L2xpPgogICAgICAgICAgICAgICAgICAgIDw/cGhwIGVsc2U6ID8+CiAgICAgICAgICAgICAgICAgICAgICAgIDw/cGhwIGlmICghRk1fUkVBRE9OTFkpOiA/PgogICAgICAgICAgICAgICAgICAgICAgICAgICAgPGxpIGNsYXNzPSJuYXYtaXRlbSI+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPGEgdGl0bGU9Ijw/cGhwIGVjaG8gbG5nKCdTZXR0aW5ncycpID8+IiBjbGFzcz0iZHJvcGRvd24taXRlbSBuYXYtbGluayIgaHJlZj0iP3A9PD9waHAgZWNobyB1cmxlbmNvZGUoRk1fUEFUSCkgPz4mYW1wO3NldHRpbmdzPTEiPjxpIGNsYXNzPSJmYSBmYS1jb2ciIGFyaWEtaGlkZGVuPSJ0cnVlIj48L2k+IDw/cGhwIGVjaG8gbG5nKCdTZXR0aW5ncycpID8+PC9hPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgPC9saT4KICAgICAgICAgICAgICAgICAgICAgICAgPD9waHAgZW5kaWY7ID8+CiAgICAgICAgICAgICAgICAgICAgPD9waHAgZW5kaWY7ID8+CiAgICAgICAgICAgICAgICA8L3VsPgogICAgICAgICAgICA8L2Rpdj4KICAgICAgICA8L2Rpdj4KICAgIDwvbmF2PgogICAgPD9waHAKfQoKLyoqCiAqIFNob3cgYWxlcnQgbWVzc2FnZSBmcm9tIHNlc3Npb24KICovCmZ1bmN0aW9uIGZtX3Nob3dfbWVzc2FnZSgpCnsKICAgIGlmIChpc3NldCgkX1NFU1NJT05bRk1fU0VTU0lPTl9JRF1bJ21lc3NhZ2UnXSkpIHsKICAgICAgICAkY2xhc3MgPSBpc3NldCgkX1NFU1NJT05bRk1fU0VTU0lPTl9JRF1bJ3N0YXR1cyddKSA/ICRfU0VTU0lPTltGTV9TRVNTSU9OX0lEXVsnc3RhdHVzJ10gOiAnb2snOwogICAgICAgIGVjaG8gJzxwIGNsYXNzPSJtZXNzYWdlICcgLiAkY2xhc3MgLiAnIj4nIC4gJF9TRVNTSU9OW0ZNX1NFU1NJT05fSURdWydtZXNzYWdlJ10gLiAnPC9wPic7CiAgICAgICAgdW5zZXQoJF9TRVNTSU9OW0ZNX1NFU1NJT05fSURdWydtZXNzYWdlJ10pOwogICAgICAgIHVuc2V0KCRfU0VTU0lPTltGTV9TRVNTSU9OX0lEXVsnc3RhdHVzJ10pOwogICAgfQp9CgovKioKICogU2hvdyBwYWdlIGhlYWRlciBpbiBMb2dpbiBGb3JtCiAqLwpmdW5jdGlvbiBmbV9zaG93X2hlYWRlcl9sb2dpbigpCnsKJHNwcml0ZXNfdmVyID0gJzIwMTYwMzE1JzsKaGVhZGVyKCJDb250ZW50LVR5cGU6IHRleHQvaHRtbDsgY2hhcnNldD11dGYtOCIpOwpoZWFkZXIoIkV4cGlyZXM6IFNhdCwgMjYgSnVsIDE5OTcgMDU6MDA6MDAgR01UIik7CmhlYWRlcigiQ2FjaGUtQ29udHJvbDogbm8tc3RvcmUsIG5vLWNhY2hlLCBtdXN0LXJldmFsaWRhdGUsIHBvc3QtY2hlY2s9MCwgcHJlLWNoZWNrPTAiKTsKaGVhZGVyKCJQcmFnbWE6IG5vLWNhY2hlIik7CgpnbG9iYWwgJGxhbmcsICRyb290X3VybCwgJGZhdmljb25fcGF0aDsKPz4KPCFET0NUWVBFIGh0bWw+CjxodG1sIGxhbmc9ImVuIj4KPGhlYWQ+CiAgICA8bWV0YSBjaGFyc2V0PSJ1dGYtOCI+CiAgICA8bWV0YSBuYW1lPSJ2aWV3cG9ydCIgY29udGVudD0id2lkdGg9ZGV2aWNlLXdpZHRoLCBpbml0aWFsLXNjYWxlPTEsIHNocmluay10by1maXQ9bm8iPgogICAgPG1ldGEgbmFtZT0iZGVzY3JpcHRpb24iIGNvbnRlbnQ9IldlYiBiYXNlZCBGaWxlIE1hbmFnZXIgaW4gUEhQLCBNYW5hZ2UgeW91ciBmaWxlcyBlZmZpY2llbnRseSBhbmQgZWFzaWx5IHdpdGggVGlueSBGaWxlIE1hbmFnZXIiPgogICAgPG1ldGEgbmFtZT0iYXV0aG9yIiBjb250ZW50PSJDQ1AgUHJvZ3JhbW1lcnMiPgogICAgPG1ldGEgbmFtZT0icm9ib3RzIiBjb250ZW50PSJub2luZGV4LCBub2ZvbGxvdyI+CiAgICA8bWV0YSBuYW1lPSJnb29nbGVib3QiIGNvbnRlbnQ9Im5vaW5kZXgiPgogICAgPD9waHAgaWYoJGZhdmljb25fcGF0aCkgeyBlY2hvICc8bGluayByZWw9Imljb24iIGhyZWY9IicuZm1fZW5jKCRmYXZpY29uX3BhdGgpLiciIHR5cGU9ImltYWdlL3BuZyI+JzsgfSA/PgogICAgPHRpdGxlPjw/cGhwIGVjaG8gZm1fZW5jKEFQUF9USVRMRSkgPz48L3RpdGxlPgogICAgPGxpbmsgcmVsPSJwcmVjb25uZWN0IiBocmVmPSJodHRwczovL2Nkbi5qc2RlbGl2ci5uZXQiIGNyb3Nzb3JpZ2luLz4KICAgIDxsaW5rIHJlbD0iZG5zLXByZWZldGNoIiBocmVmPSJodHRwczovL2Nkbi5qc2RlbGl2ci5uZXQiLz4gCiAgICA8bGluayBocmVmPSJodHRwczovL2Nkbi5qc2RlbGl2ci5uZXQvbnBtL2Jvb3RzdHJhcEA1LjIuMi9kaXN0L2Nzcy9ib290c3RyYXAubWluLmNzcyIgcmVsPSJzdHlsZXNoZWV0IiBpbnRlZ3JpdHk9InNoYTM4NC1aZW5oODdxWDVKbksySmwwdldhOENrMnJka1EyQnplcDVJRHhiY25DZXVPeGp6clBGL2V0M1VSeTlCdjFXVFJpIiBjcm9zc29yaWdpbj0iYW5vbnltb3VzIj4KICAgIDxzdHlsZT4KICAgICAgICBib2R5LmZtLWxvZ2luLXBhZ2V7IGJhY2tncm91bmQtY29sb3I6I2Y3ZjlmYjtmb250LXNpemU6MTRweDtiYWNrZ3JvdW5kLWNvbG9yOiNmN2Y5ZmI7YmFja2dyb3VuZC1pbWFnZTp1cmwoImRhdGE6aW1hZ2Uvc3ZnK3htbCwlM0NzdmcgeG1sbnM9J2h0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnJyB2aWV3Qm94PScwIDAgMzA0IDMwNCcgd2lkdGg9JzMwNCcgaGVpZ2h0PSczMDQnJTNFJTNDcGF0aCBmaWxsPSclMjNlMmU5ZjEnIGZpbGwtb3BhY2l0eT0nMC40JyBkPSdNNDQuMSAyMjRhNSA1IDAgMSAxIDAgMkgwdi0yaDQ0LjF6bTE2MCA0OGE1IDUgMCAxIDEgMCAySDgydi0yaDEyMi4xem01Ny44LTQ2YTUgNSAwIDEgMSAwLTJIMzA0djJoLTQyLjF6bTAgMTZhNSA1IDAgMSAxIDAtMkgzMDR2MmgtNDIuMXptNi4yLTExNGE1IDUgMCAxIDEgMCAyaC04Ni4yYTUgNSAwIDEgMSAwLTJoODYuMnptLTI1Ni00OGE1IDUgMCAxIDEgMCAySDB2LTJoMTIuMXptMTg1LjggMzRhNSA1IDAgMSAxIDAtMmg4Ni4yYTUgNSAwIDEgMSAwIDJoLTg2LjJ6TTI1OCAxMi4xYTUgNSAwIDEgMS0yIDBWMGgydjEyLjF6bS02NCAyMDhhNSA1IDAgMSAxLTIgMHYtNTQuMmE1IDUgMCAxIDEgMiAwdjU0LjJ6bTQ4LTE5OC4yVjgwaDYydjJoLTY0VjIxLjlhNSA1IDAgMSAxIDIgMHptMTYgMTZWNjRoNDZ2MmgtNDhWMzcuOWE1IDUgMCAxIDEgMiAwem0tMTI4IDk2VjIwOGgxNnYxMi4xYTUgNSAwIDEgMS0yIDBWMjEwaC0xNnYtNzYuMWE1IDUgMCAxIDEgMiAwem0tNS45LTIxLjlhNSA1IDAgMSAxIDAgMkgxMTR2NDhIODUuOWE1IDUgMCAxIDEgMC0ySDExMnYtNDhoMTIuMXptLTYuMiAxMzBhNSA1IDAgMSAxIDAtMkgxNzZ2LTc0LjFhNSA1IDAgMSAxIDIgMFYyNDJoLTYwLjF6bS0xNi02NGE1IDUgMCAxIDEgMC0ySDExNHY0OGgxMC4xYTUgNSAwIDEgMSAwIDJIMTEydi00OGgtMTAuMXpNNjYgMjg0LjFhNSA1IDAgMSAxLTIgMFYyNzRINTB2MzBoLTJ2LTMyaDE4djEyLjF6TTIzNi4xIDE3NmE1IDUgMCAxIDEgMCAySDIyNnY5NGg0OHYzMmgtMnYtMzBoLTQ4di05OGgxMi4xem0yNS44LTMwYTUgNSAwIDEgMSAwLTJIMjc0djQ0LjFhNSA1IDAgMSAxLTIgMFYxNDZoLTEwLjF6bS02NCA5NmE1IDUgMCAxIDEgMC0ySDIwOHYtODBoMTZ2LTE0aC00Mi4xYTUgNSAwIDEgMSAwLTJIMjI2djE4aC0xNnY4MGgtMTIuMXptODYuMi0yMTBhNSA1IDAgMSAxIDAgMkgyNzJWMGgydjMyaDEwLjF6TTk4IDEwMS45VjE0Nkg1My45YTUgNSAwIDEgMSAwLTJIOTZ2LTQyLjFhNSA1IDAgMSAxIDIgMHpNNTMuOSAzNGE1IDUgMCAxIDEgMC0ySDgwVjBoMnYzNEg1My45em02MC4xIDMuOVY2Nkg4MnY2NEg2OS45YTUgNSAwIDEgMSAwLTJIODBWNjRoMzJWMzcuOWE1IDUgMCAxIDEgMiAwek0xMDEuOSA4MmE1IDUgMCAxIDEgMC0ySDEyOFYzNy45YTUgNSAwIDEgMSAyIDBWODJoLTI4LjF6bTE2LTY0YTUgNSAwIDEgMSAwLTJIMTQ2djQ0LjFhNSA1IDAgMSAxLTIgMFYxOGgtMjYuMXptMTAyLjIgMjcwYTUgNSAwIDEgMSAwIDJIOTh2MTRoLTJ2LTE2aDEyNC4xek0yNDIgMTQ5LjlWMTYwaDE2djM0aC0xNnY2Mmg0OHY0OGgtMnYtNDZoLTQ4di02NmgxNnYtMzBoLTE2di0xMi4xYTUgNSAwIDEgMSAyIDB6TTUzLjkgMThhNSA1IDAgMSAxIDAtMkg2NFYySDQ4VjBoMTh2MThINTMuOXptMTEyIDMyYTUgNSAwIDEgMSAwLTJIMTkyVjBoNTB2MmgtNDh2NDhoLTI4LjF6bS00OC00OGE1IDUgMCAwIDEtOS44LTJoMi4wN2EzIDMgMCAxIDAgNS42NiAwSDE3OHYzNGgtMThWMjEuOWE1IDUgMCAxIDEgMiAwVjMyaDE0VjJoLTU4LjF6bTAgOTZhNSA1IDAgMSAxIDAtMkgxMzdsMzItMzJoMzlWMjEuOWE1IDUgMCAxIDEgMiAwVjY2aC00MC4xN2wtMzIgMzJIMTE3Ljl6bTI4LjEgOTAuMWE1IDUgMCAxIDEtMiAwdi03Ni41MUwxNzUuNTkgODBIMjI0VjIxLjlhNSA1IDAgMSAxIDIgMFY4MmgtNDkuNTlMMTQ2IDExMi40MXY3NS42OXptMTYgMzJhNSA1IDAgMSAxLTIgMHYtOTkuNTFMMTg0LjU5IDk2SDMwMC4xYTUgNSAwIDAgMSAzLjktMy45djIuMDdhMyAzIDAgMCAwIDAgNS42NnYyLjA3YTUgNSAwIDAgMS0zLjktMy45SDE4NS40MUwxNjIgMTIxLjQxdjk4LjY5em0tMTQ0LTY0YTUgNSAwIDEgMS0yIDB2LTMuNTFsNDgtNDhWNDhoMzJWMGgydjUwSDY2djU1LjQxbC00OCA0OHYyLjY5ek01MCA1My45djQzLjUxbC00OCA0OFYyMDhoMjYuMWE1IDUgMCAxIDEgMCAySDB2LTY1LjQxbDQ4LTQ4VjUzLjlhNSA1IDAgMSAxIDIgMHptLTE2IDE2Vjg5LjQxbC0zNCAzNHYtMi44MmwzMi0zMlY2OS45YTUgNSAwIDEgMSAyIDB6TTEyLjEgMzJhNSA1IDAgMSAxIDAgMkg5LjQxTDAgNDMuNDFWNDAuNkw4LjU5IDMyaDMuNTF6bTI2NS44IDE4YTUgNSAwIDEgMSAwLTJoMTguNjlsNy40MS03LjQxdjIuODJMMjk3LjQxIDUwSDI3Ny45em0tMTYgMTYwYTUgNSAwIDEgMSAwLTJIMjg4di03MS40MWwxNi0xNnYyLjgybC0xNCAxNFYyMTBoLTI4LjF6bS0yMDggMzJhNSA1IDAgMSAxIDAtMkg2NHYtMjIuNTlMNDAuNTkgMTk0SDIxLjlhNSA1IDAgMSAxIDAtMkg0MS40MUw2NiAyMTYuNTlWMjQySDUzLjl6bTE1MC4yIDE0YTUgNSAwIDEgMSAwIDJIOTZ2LTU2LjZMNTYuNiAxNjJIMzcuOWE1IDUgMCAxIDEgMC0yaDE5LjVMOTggMjAwLjZWMjU2aDEwNi4xem0tMTUwLjIgMmE1IDUgMCAxIDEgMC0ySDgwdi00Ni41OUw0OC41OSAxNzhIMjEuOWE1IDUgMCAxIDEgMC0ySDQ5LjQxTDgyIDIwOC41OVYyNThINTMuOXpNMzQgMzkuOHYxLjYxTDkuNDEgNjZIMHYtMmg4LjU5TDMyIDQwLjU5VjBoMnYzOS44ek0yIDMwMC4xYTUgNSAwIDAgMSAzLjkgMy45SDMuODNBMyAzIDAgMCAwIDAgMzAyLjE3VjI1NmgxOHY0OGgtMnYtNDZIMnY0Mi4xek0zNCAyNDF2NjNoLTJ2LTYySDB2LTJoMzR2MXpNMTcgMThIMHYtMmgxNlYwaDJ2MThoLTF6bTI3My0yaDE0djJoLTE2VjBoMnYxNnptLTMyIDI3M3YxNWgtMnYtMTRoLTE0djE0aC0ydi0xNmgxOHYxek0wIDkyLjFBNS4wMiA1LjAyIDAgMCAxIDYgOTdhNSA1IDAgMCAxLTYgNC45di0yLjA3YTMgMyAwIDEgMCAwLTUuNjZWOTIuMXpNODAgMjcyaDJ2MzJoLTJ2LTMyem0zNy45IDMyaC0yLjA3YTMgMyAwIDAgMC01LjY2IDBoLTIuMDdhNSA1IDAgMCAxIDkuOCAwek01LjkgMEE1LjAyIDUuMDIgMCAwIDEgMCA1LjlWMy44M0EzIDMgMCAwIDAgMy44MyAwSDUuOXptMjk0LjIgMGgyLjA3QTMgMyAwIDAgMCAzMDQgMy44M1Y1LjlhNSA1IDAgMCAxLTMuOS01Ljl6bTMuOSAzMDAuMXYyLjA3YTMgMyAwIDAgMC0xLjgzIDEuODNoLTIuMDdhNSA1IDAgMCAxIDMuOS0zLjl6TTk3IDEwMGEzIDMgMCAxIDAgMC02IDMgMyAwIDAgMCAwIDZ6bTAtMTZhMyAzIDAgMSAwIDAtNiAzIDMgMCAwIDAgMCA2em0xNiAxNmEzIDMgMCAxIDAgMC02IDMgMyAwIDAgMCAwIDZ6bTE2IDE2YTMgMyAwIDEgMCAwLTYgMyAzIDAgMCAwIDAgNnptMCAxNmEzIDMgMCAxIDAgMC02IDMgMyAwIDAgMCAwIDZ6bS00OCAzMmEzIDMgMCAxIDAgMC02IDMgMyAwIDAgMCAwIDZ6bTE2IDE2YTMgMyAwIDEgMCAwLTYgMyAzIDAgMCAwIDAgNnptMzIgNDhhMyAzIDAgMSAwIDAtNiAzIDMgMCAwIDAgMCA2em0tMTYgMTZhMyAzIDAgMSAwIDAtNiAzIDMgMCAwIDAgMCA2em0zMi0xNmEzIDMgMCAxIDAgMC02IDMgMyAwIDAgMCAwIDZ6bTAtMzJhMyAzIDAgMSAwIDAtNiAzIDMgMCAwIDAgMCA2em0xNiAzMmEzIDMgMCAxIDAgMC02IDMgMyAwIDAgMCAwIDZ6bTMyIDE2YTMgMyAwIDEgMCAwLTYgMyAzIDAgMCAwIDAgNnptMC0xNmEzIDMgMCAxIDAgMC02IDMgMyAwIDAgMCAwIDZ6bS0xNi02NGEzIDMgMCAxIDAgMC02IDMgMyAwIDAgMCAwIDZ6bTE2IDBhMyAzIDAgMSAwIDAtNiAzIDMgMCAwIDAgMCA2em0xNiA5NmEzIDMgMCAxIDAgMC02IDMgMyAwIDAgMCAwIDZ6bTAgMTZhMyAzIDAgMSAwIDAtNiAzIDMgMCAwIDAgMCA2em0xNiAxNmEzIDMgMCAxIDAgMC02IDMgMyAwIDAgMCAwIDZ6bTE2LTE0NGEzIDMgMCAxIDAgMC02IDMgMyAwIDAgMCAwIDZ6bTAgMzJhMyAzIDAgMSAwIDAtNiAzIDMgMCAwIDAgMCA2em0xNi0zMmEzIDMgMCAxIDAgMC02IDMgMyAwIDAgMCAwIDZ6bTE2LTE2YTMgMyAwIDEgMCAwLTYgMyAzIDAgMCAwIDAgNnptLTk2IDBhMyAzIDAgMSAwIDAtNiAzIDMgMCAwIDAgMCA2em0wIDE2YTMgMyAwIDEgMCAwLTYgMyAzIDAgMCAwIDAgNnptMTYtMzJhMyAzIDAgMSAwIDAtNiAzIDMgMCAwIDAgMCA2em05NiAwYTMgMyAwIDEgMCAwLTYgMyAzIDAgMCAwIDAgNnptLTE2LTY0YTMgMyAwIDEgMCAwLTYgMyAzIDAgMCAwIDAgNnptMTYtMTZhMyAzIDAgMSAwIDAtNiAzIDMgMCAwIDAgMCA2em0tMzIgMGEzIDMgMCAxIDAgMC02IDMgMyAwIDAgMCAwIDZ6bTAtMTZhMyAzIDAgMSAwIDAtNiAzIDMgMCAwIDAgMCA2em0tMTYgMGEzIDMgMCAxIDAgMC02IDMgMyAwIDAgMCAwIDZ6bS0xNiAwYTMgMyAwIDEgMCAwLTYgMyAzIDAgMCAwIDAgNnptLTE2IDBhMyAzIDAgMSAwIDAtNiAzIDMgMCAwIDAgMCA2ek00OSAzNmEzIDMgMCAxIDAgMC02IDMgMyAwIDAgMCAwIDZ6bS0zMiAwYTMgMyAwIDEgMCAwLTYgMyAzIDAgMCAwIDAgNnptMzIgMTZhMyAzIDAgMSAwIDAtNiAzIDMgMCAwIDAgMCA2ek0zMyA2OGEzIDMgMCAxIDAgMC02IDMgMyAwIDAgMCAwIDZ6bTE2LTQ4YTMgMyAwIDEgMCAwLTYgMyAzIDAgMCAwIDAgNnptMCAyNDBhMyAzIDAgMSAwIDAtNiAzIDMgMCAwIDAgMCA2em0xNiAzMmEzIDMgMCAxIDAgMC02IDMgMyAwIDAgMCAwIDZ6bS0xNi02NGEzIDMgMCAxIDAgMC02IDMgMyAwIDAgMCAwIDZ6bTAgMTZhMyAzIDAgMSAwIDAtNiAzIDMgMCAwIDAgMCA2em0tMTYtMzJhMyAzIDAgMSAwIDAtNiAzIDMgMCAwIDAgMCA2em04MC0xNzZhMyAzIDAgMSAwIDAtNiAzIDMgMCAwIDAgMCA2em0xNiAwYTMgMyAwIDEgMCAwLTYgMyAzIDAgMCAwIDAgNnptLTE2LTE2YTMgMyAwIDEgMCAwLTYgMyAzIDAgMCAwIDAgNnptMzIgNDhhMyAzIDAgMSAwIDAtNiAzIDMgMCAwIDAgMCA2em0xNi0xNmEzIDMgMCAxIDAgMC02IDMgMyAwIDAgMCAwIDZ6bTAtMzJhMyAzIDAgMSAwIDAtNiAzIDMgMCAwIDAgMCA2em0xMTIgMTc2YTMgMyAwIDEgMCAwLTYgMyAzIDAgMCAwIDAgNnptLTE2IDE2YTMgMyAwIDEgMCAwLTYgMyAzIDAgMCAwIDAgNnptMCAxNmEzIDMgMCAxIDAgMC02IDMgMyAwIDAgMCAwIDZ6bTAgMTZhMyAzIDAgMSAwIDAtNiAzIDMgMCAwIDAgMCA2ek0xNyAxODBhMyAzIDAgMSAwIDAtNiAzIDMgMCAwIDAgMCA2em0wIDE2YTMgMyAwIDEgMCAwLTYgMyAzIDAgMCAwIDAgNnptMC0zMmEzIDMgMCAxIDAgMC02IDMgMyAwIDAgMCAwIDZ6bTE2IDBhMyAzIDAgMSAwIDAtNiAzIDMgMCAwIDAgMCA2ek0xNyA4NGEzIDMgMCAxIDAgMC02IDMgMyAwIDAgMCAwIDZ6bTMyIDY0YTMgMyAwIDEgMCAwLTYgMyAzIDAgMCAwIDAgNnptMTYtMTZhMyAzIDAgMSAwIDAtNiAzIDMgMCAwIDAgMCA2eiclM0UlM0MvcGF0aCUzRSUzQy9zdmclM0UiKTt9CiAgICAgICAgLmZtLWxvZ2luLXBhZ2UgLmJyYW5keyB3aWR0aDoxMjFweDtvdmVyZmxvdzpoaWRkZW47bWFyZ2luOjAgYXV0bztwb3NpdGlvbjpyZWxhdGl2ZTt6LWluZGV4OjF9CiAgICAgICAgLmZtLWxvZ2luLXBhZ2UgLmJyYW5kIGltZ3sgd2lkdGg6MTAwJX0KICAgICAgICAuZm0tbG9naW4tcGFnZSAuY2FyZC13cmFwcGVyeyB3aWR0aDozNjBweDttYXJnaW4tdG9wOjEwJTttYXJnaW4tbGVmdDphdXRvO21hcmdpbi1yaWdodDphdXRvO30KICAgICAgICAuZm0tbG9naW4tcGFnZSAuY2FyZHsgYm9yZGVyLWNvbG9yOnRyYW5zcGFyZW50O2JveC1zaGFkb3c6MCA0cHggOHB4IHJnYmEoMCwwLDAsLjA1KX0KICAgICAgICAuZm0tbG9naW4tcGFnZSAuY2FyZC10aXRsZXsgbWFyZ2luLWJvdHRvbToxLjVyZW07Zm9udC1zaXplOjI0cHg7Zm9udC13ZWlnaHQ6NDAwO30KICAgICAgICAuZm0tbG9naW4tcGFnZSAuZm9ybS1jb250cm9seyBib3JkZXItd2lkdGg6Mi4zcHh9CiAgICAgICAgLmZtLWxvZ2luLXBhZ2UgLmZvcm0tZ3JvdXAgbGFiZWx7IHdpZHRoOjEwMCV9CiAgICAgICAgLmZtLWxvZ2luLXBhZ2UgLmJ0bi5idG4tYmxvY2t7IHBhZGRpbmc6MTJweCAxMHB4fQogICAgICAgIC5mbS1sb2dpbi1wYWdlIC5mb290ZXJ7IG1hcmdpbjo0MHB4IDA7Y29sb3I6Izg4ODt0ZXh0LWFsaWduOmNlbnRlcn0KICAgICAgICBAbWVkaWEgc2NyZWVuIGFuZCAobWF4LXdpZHRoOjQyNXB4KXsKICAgICAgICAgICAgLmZtLWxvZ2luLXBhZ2UgLmNhcmQtd3JhcHBlcnsgd2lkdGg6OTAlO21hcmdpbjowIGF1dG87bWFyZ2luLXRvcDoxMCU7fQogICAgICAgIH0KICAgICAgICBAbWVkaWEgc2NyZWVuIGFuZCAobWF4LXdpZHRoOjMyMHB4KXsKICAgICAgICAgICAgLmZtLWxvZ2luLXBhZ2UgLmNhcmQuZmF0eyBwYWRkaW5nOjB9CiAgICAgICAgICAgIC5mbS1sb2dpbi1wYWdlIC5jYXJkLmZhdCAuY2FyZC1ib2R5eyBwYWRkaW5nOjE1cHh9CiAgICAgICAgfQogICAgICAgIC5tZXNzYWdleyBwYWRkaW5nOjRweCA3cHg7Ym9yZGVyOjFweCBzb2xpZCAjZGRkO2JhY2tncm91bmQtY29sb3I6I2ZmZn0KICAgICAgICAubWVzc2FnZS5va3sgYm9yZGVyLWNvbG9yOmdyZWVuO2NvbG9yOmdyZWVufQogICAgICAgIC5tZXNzYWdlLmVycm9yeyBib3JkZXItY29sb3I6cmVkO2NvbG9yOnJlZH0KICAgICAgICAubWVzc2FnZS5hbGVydHsgYm9yZGVyLWNvbG9yOm9yYW5nZTtjb2xvcjpvcmFuZ2V9CiAgICAgICAgYm9keS5mbS1sb2dpbi1wYWdlLnRoZW1lLWRhcmsge2JhY2tncm91bmQtY29sb3I6ICMyZjJhMmE7fQogICAgICAgIC50aGVtZS1kYXJrIHN2ZyBnLCAudGhlbWUtZGFyayBzdmcgcGF0aCB7ZmlsbDogI2ZmZmZmZjsgfQogICAgPC9zdHlsZT4KPC9oZWFkPgo8Ym9keSBjbGFzcz0iZm0tbG9naW4tcGFnZSA8P3BocCBlY2hvIChGTV9USEVNRSA9PSAiZGFyayIpID8gJ3RoZW1lLWRhcmsnIDogJyc7ID8+Ij4KPGRpdiBpZD0id3JhcHBlciIgY2xhc3M9ImNvbnRhaW5lci1mbHVpZCI+CgogICAgPD9waHAKICAgIH0KCiAgICAvKioKICAgICAqIFNob3cgcGFnZSBmb290ZXIgaW4gTG9naW4gRm9ybQogICAgICovCiAgICBmdW5jdGlvbiBmbV9zaG93X2Zvb3Rlcl9sb2dpbigpCiAgICB7CiAgICA/Pgo8L2Rpdj4KPHNjcmlwdCBzcmM9Imh0dHBzOi8vY29kZS5qcXVlcnkuY29tL2pxdWVyeS0zLjYuMS5taW4uanMiIGludGVncml0eT0ic2hhMjU2LW84OEF3UW5aQitWRHZFOXR2SVhyTVFhUGxGRlNVVFIrbmxkUW0xTHVQWFE9IiBjcm9zc29yaWdpbj0iYW5vbnltb3VzIj48L3NjcmlwdD4KPHNjcmlwdCBzcmM9Imh0dHBzOi8vY2RuLmpzZGVsaXZyLm5ldC9ucG0vYm9vdHN0cmFwQDUuMi4yL2Rpc3QvanMvYm9vdHN0cmFwLmJ1bmRsZS5taW4uanMiIGludGVncml0eT0ic2hhMzg0LU9FUmNBMkVxakpDTUErLzN5K2d4SU9xTUVqd3R4Slk3cVBDcXNkbHRiTkp1YU9lOTIzK21vLy9mNlY4UWJzdzMiIGNyb3Nzb3JpZ2luPSJhbm9ueW1vdXMiPjwvc2NyaXB0Pgo8L2JvZHk+CjwvaHRtbD4KPD9waHAKfQoKLyoqCiAqIFNob3cgSGVhZGVyIGFmdGVyIGxvZ2luCiAqLwpmdW5jdGlvbiBmbV9zaG93X2hlYWRlcigpCnsKJHNwcml0ZXNfdmVyID0gJzIwMTYwMzE1JzsKaGVhZGVyKCJDb250ZW50LVR5cGU6IHRleHQvaHRtbDsgY2hhcnNldD11dGYtOCIpOwpoZWFkZXIoIkV4cGlyZXM6IFNhdCwgMjYgSnVsIDE5OTcgMDU6MDA6MDAgR01UIik7CmhlYWRlcigiQ2FjaGUtQ29udHJvbDogbm8tc3RvcmUsIG5vLWNhY2hlLCBtdXN0LXJldmFsaWRhdGUsIHBvc3QtY2hlY2s9MCwgcHJlLWNoZWNrPTAiKTsKaGVhZGVyKCJQcmFnbWE6IG5vLWNhY2hlIik7CgpnbG9iYWwgJGxhbmcsICRyb290X3VybCwgJHN0aWNreV9uYXZiYXIsICRmYXZpY29uX3BhdGg7CiRpc1N0aWNreU5hdkJhciA9ICRzdGlja3lfbmF2YmFyID8gJ25hdmJhci1maXhlZCcgOiAnbmF2YmFyLW5vcm1hbCc7Cj8+CjwhRE9DVFlQRSBodG1sPgo8aHRtbD4KPGhlYWQ+CiAgICA8bWV0YSBjaGFyc2V0PSJ1dGYtOCI+CiAgICA8bWV0YSBuYW1lPSJ2aWV3cG9ydCIgY29udGVudD0id2lkdGg9ZGV2aWNlLXdpZHRoLCBpbml0aWFsLXNjYWxlPTEsIHNocmluay10by1maXQ9bm8iPgogICAgPG1ldGEgbmFtZT0iZGVzY3JpcHRpb24iIGNvbnRlbnQ9IldlYiBiYXNlZCBGaWxlIE1hbmFnZXIgaW4gUEhQLCBNYW5hZ2UgeW91ciBmaWxlcyBlZmZpY2llbnRseSBhbmQgZWFzaWx5IHdpdGggVGlueSBGaWxlIE1hbmFnZXIiPgogICAgPG1ldGEgbmFtZT0iYXV0aG9yIiBjb250ZW50PSJDQ1AgUHJvZ3JhbW1lcnMiPgogICAgPG1ldGEgbmFtZT0icm9ib3RzIiBjb250ZW50PSJub2luZGV4LCBub2ZvbGxvdyI+CiAgICA8bWV0YSBuYW1lPSJnb29nbGVib3QiIGNvbnRlbnQ9Im5vaW5kZXgiPgogICAgPD9waHAgaWYoJGZhdmljb25fcGF0aCkgeyBlY2hvICc8bGluayByZWw9Imljb24iIGhyZWY9IicuZm1fZW5jKCRmYXZpY29uX3BhdGgpLiciIHR5cGU9ImltYWdlL3BuZyI+JzsgfSA/PgogICAgPHRpdGxlPjw/cGhwIGVjaG8gZm1fZW5jKEFQUF9USVRMRSkgPz48L3RpdGxlPgogICAgPGxpbmsgcmVsPSJwcmVjb25uZWN0IiBocmVmPSJodHRwczovL2Nkbi5qc2RlbGl2ci5uZXQiIGNyb3Nzb3JpZ2luLz4KICAgIDxsaW5rIHJlbD0iZG5zLXByZWZldGNoIiBocmVmPSJodHRwczovL2Nkbi5qc2RlbGl2ci5uZXQiLz4gCiAgICA8bGluayByZWw9InByZWNvbm5lY3QiIGhyZWY9Imh0dHBzOi8vY2RuanMuY2xvdWRmbGFyZS5jb20iIGNyb3Nzb3JpZ2luLz4KICAgIDxsaW5rIHJlbD0iZG5zLXByZWZldGNoIiBocmVmPSJodHRwczovL2NkbmpzLmNsb3VkZmxhcmUuY29tIi8+IAogICAgPGxpbmsgaHJlZj0iaHR0cHM6Ly9jZG4uanNkZWxpdnIubmV0L25wbS9ib290c3RyYXBANS4yLjIvZGlzdC9jc3MvYm9vdHN0cmFwLm1pbi5jc3MiIHJlbD0ic3R5bGVzaGVldCIgaW50ZWdyaXR5PSJzaGEzODQtWmVuaDg3cVg1Sm5LMkpsMHZXYThDazJyZGtRMkJ6ZXA1SUR4YmNuQ2V1T3hqenJQRi9ldDNVUnk5QnYxV1RSaSIgY3Jvc3NvcmlnaW49ImFub255bW91cyI+CiAgICA8bGluayByZWw9InN0eWxlc2hlZXQiIGhyZWY9Imh0dHBzOi8vY2RuanMuY2xvdWRmbGFyZS5jb20vYWpheC9saWJzL2ZvbnQtYXdlc29tZS80LjcuMC9jc3MvZm9udC1hd2Vzb21lLm1pbi5jc3MiIGNyb3Nzb3JpZ2luPSJhbm9ueW1vdXMiPgogICAgPD9waHAgaWYgKEZNX1VTRV9ISUdITElHSFRKUyAmJiBpc3NldCgkX0dFVFsndmlldyddKSk6ID8+CiAgICA8bGluayByZWw9InN0eWxlc2hlZXQiIGhyZWY9Imh0dHBzOi8vY2RuanMuY2xvdWRmbGFyZS5jb20vYWpheC9saWJzL2hpZ2hsaWdodC5qcy8xMS42LjAvc3R5bGVzLzw/cGhwIGVjaG8gRk1fSElHSExJR0hUSlNfU1RZTEUgPz4ubWluLmNzcyI+CiAgICA8P3BocCBlbmRpZjsgPz4KICAgIDxzY3JpcHQgdHlwZT0idGV4dC9qYXZhc2NyaXB0Ij53aW5kb3cuY3NyZiA9ICc8P3BocCBlY2hvICRfU0VTU0lPTlsndG9rZW4nXTsgPz4nOzwvc2NyaXB0PgogICAgPHN0eWxlPgogICAgICAgIGJvZHkgeyBmb250LXNpemU6MTVweDsgY29sb3I6IzIyMjtiYWNrZ3JvdW5kOiNGN0Y3Rjc7IH0KICAgICAgICBib2R5Lm5hdmJhci1maXhlZCB7IG1hcmdpbi10b3A6NTVweDsgfQogICAgICAgIGEsIGE6aG92ZXIsIGE6dmlzaXRlZCwgYTpmb2N1cyB7IHRleHQtZGVjb3JhdGlvbjpub25lICFpbXBvcnRhbnQ7IH0KICAgICAgICAuZmlsZW5hbWUsIHRkLCB0aCB7IHdoaXRlLXNwYWNlOm5vd3JhcCAgfQogICAgICAgIC5uYXZiYXItYnJhbmQgeyBmb250LXdlaWdodDpib2xkOyB9CiAgICAgICAgLm5hdi1pdGVtLmF2YXRhciBhIHsgY3Vyc29yOnBvaW50ZXI7dGV4dC10cmFuc2Zvcm06Y2FwaXRhbGl6ZTsgfQogICAgICAgIC5uYXYtaXRlbS5hdmF0YXIgYSA+IGkgeyBmb250LXNpemU6MTVweDsgfQogICAgICAgIC5uYXYtaXRlbS5hdmF0YXIgLmRyb3Bkb3duLW1lbnUgYSB7IGZvbnQtc2l6ZToxM3B4OyB9CiAgICAgICAgI3NlYXJjaC1hZGRvbiB7IGZvbnQtc2l6ZToxMnB4O2JvcmRlci1yaWdodC13aWR0aDowOyB9CiAgICAgICAgLmJybC0wIHsgYmFja2dyb3VuZDp0cmFuc3BhcmVudDtib3JkZXItbGVmdDowOyBib3JkZXItdG9wLWxlZnQtcmFkaXVzOiAwOyBib3JkZXItYm90dG9tLWxlZnQtcmFkaXVzOiAwOyB9CiAgICAgICAgLmJyci0wIHsgYm9yZGVyLXRvcC1yaWdodC1yYWRpdXM6IDA7IGJvcmRlci1ib3R0b20tcmlnaHQtcmFkaXVzOiAwOyB9CiAgICAgICAgLmJyZWFkLWNydW1iIHsgY29sb3I6I2NjY2NjYztmb250LXN0eWxlOm5vcm1hbDsgfQogICAgICAgICNtYWluLXRhYmxlIC5maWxlbmFtZSBhIHsgY29sb3I6IzIyMjIyMjsgfQogICAgICAgIC50YWJsZSB0ZCwgLnRhYmxlIHRoIHsgdmVydGljYWwtYWxpZ246bWlkZGxlICFpbXBvcnRhbnQ7IH0KICAgICAgICAudGFibGUgLmN1c3RvbS1jaGVja2JveC10ZCAuY3VzdG9tLWNvbnRyb2wuY3VzdG9tLWNoZWNrYm94LCAudGFibGUgLmN1c3RvbS1jaGVja2JveC1oZWFkZXIgLmN1c3RvbS1jb250cm9sLmN1c3RvbS1jaGVja2JveCB7IG1pbi13aWR0aDoxOHB4OyBkaXNwbGF5OiBmbGV4O2FsaWduLWl0ZW1zOiBjZW50ZXI7IGp1c3RpZnktY29udGVudDogY2VudGVyOyB9CiAgICAgICAgLnRhYmxlLXNtIHRkLCAudGFibGUtc20gdGggeyBwYWRkaW5nOi40cmVtOyB9CiAgICAgICAgLnRhYmxlLWJvcmRlcmVkIHRkLCAudGFibGUtYm9yZGVyZWQgdGggeyBib3JkZXI6MXB4IHNvbGlkICNmMWYxZjE7IH0KICAgICAgICAuaGlkZGVuIHsgZGlzcGxheTpub25lICB9CiAgICAgICAgcHJlLndpdGgtaGxqcyB7IHBhZGRpbmc6MCAgfQogICAgICAgIHByZS53aXRoLWhsanMgY29kZSB7IG1hcmdpbjowO2JvcmRlcjowO292ZXJmbG93OnZpc2libGUgIH0KICAgICAgICBjb2RlLm1heGhlaWdodCwgcHJlLm1heGhlaWdodCB7IG1heC1oZWlnaHQ6NTEycHggIH0KICAgICAgICAuZmEuZmEtY2FyZXQtcmlnaHQgeyBmb250LXNpemU6MS4yZW07bWFyZ2luOjAgNHB4O3ZlcnRpY2FsLWFsaWduOm1pZGRsZTtjb2xvcjojZWNlY2VjICB9CiAgICAgICAgLmZhLmZhLWhvbWUgeyBmb250LXNpemU6MS4zZW07dmVydGljYWwtYWxpZ246Ym90dG9tICB9CiAgICAgICAgLnBhdGggeyBtYXJnaW4tYm90dG9tOjEwcHggIH0KICAgICAgICBmb3JtLmRyb3B6b25lIHsgbWluLWhlaWdodDoyMDBweDtib3JkZXI6MnB4IGRhc2hlZCAjMDA3YmZmO2xpbmUtaGVpZ2h0OjZyZW07IH0KICAgICAgICAucmlnaHQgeyB0ZXh0LWFsaWduOnJpZ2h0ICB9CiAgICAgICAgLmNlbnRlciwgLmNsb3NlLCAubG9naW4tZm9ybSwgLnByZXZpZXctaW1nLWNvbnRhaW5lciB7IHRleHQtYWxpZ246Y2VudGVyICB9CiAgICAgICAgLm1lc3NhZ2UgeyBwYWRkaW5nOjRweCA3cHg7Ym9yZGVyOjFweCBzb2xpZCAjZGRkO2JhY2tncm91bmQtY29sb3I6I2ZmZiAgfQogICAgICAgIC5tZXNzYWdlLm9rIHsgYm9yZGVyLWNvbG9yOmdyZWVuO2NvbG9yOmdyZWVuICB9CiAgICAgICAgLm1lc3NhZ2UuZXJyb3IgeyBib3JkZXItY29sb3I6cmVkO2NvbG9yOnJlZCAgfQogICAgICAgIC5tZXNzYWdlLmFsZXJ0IHsgYm9yZGVyLWNvbG9yOm9yYW5nZTtjb2xvcjpvcmFuZ2UgIH0KICAgICAgICAucHJldmlldy1pbWcgeyBtYXgtd2lkdGg6MTAwJTttYXgtaGVpZ2h0Ojgwdmg7YmFja2dyb3VuZDp1cmwoZGF0YTppbWFnZS9wbmc7YmFzZTY0LGlWQk9SdzBLR2dvQUFBQU5TVWhFVWdBQUFCQUFBQUFRQ0FJQUFBQ1FrV2cyQUFBQUtrbEVRVlI0Mm1MNS8vOC9BemJ3K1BGanJPSk1EQ1NDVVEzRUFCWmM0UzByS3pzYVN2VFRBQkJnQU15ZkNNc1k0QjlpQUFBQUFFbEZUa1N1UW1DQykgfQogICAgICAgIC5pbmxpbmUtYWN0aW9ucyA+IGEgPiBpIHsgZm9udC1zaXplOjFlbTttYXJnaW4tbGVmdDo1cHg7YmFja2dyb3VuZDojMzc4NWMxO2NvbG9yOiNmZmY7cGFkZGluZzozcHggNHB4O2JvcmRlci1yYWRpdXM6M3B4OyB9CiAgICAgICAgLnByZXZpZXctdmlkZW8geyBwb3NpdGlvbjpyZWxhdGl2ZTttYXgtd2lkdGg6MTAwJTtoZWlnaHQ6MDtwYWRkaW5nLWJvdHRvbTo2Mi41JTttYXJnaW4tYm90dG9tOjEwcHggIH0KICAgICAgICAucHJldmlldy12aWRlbyB2aWRlbyB7IHBvc2l0aW9uOmFic29sdXRlO3dpZHRoOjEwMCU7aGVpZ2h0OjEwMCU7bGVmdDowO3RvcDowO2JhY2tncm91bmQ6IzAwMCAgfQogICAgICAgIC5jb21wYWN0LXRhYmxlIHsgYm9yZGVyOjA7d2lkdGg6YXV0byAgfQogICAgICAgIC5jb21wYWN0LXRhYmxlIHRkLCAuY29tcGFjdC10YWJsZSB0aCB7IHdpZHRoOjEwMHB4O2JvcmRlcjowO3RleHQtYWxpZ246Y2VudGVyICB9CiAgICAgICAgLmNvbXBhY3QtdGFibGUgdHI6aG92ZXIgdGQgeyBiYWNrZ3JvdW5kLWNvbG9yOiNmZmYgIH0KICAgICAgICAuZmlsZW5hbWUgeyBtYXgtd2lkdGg6NDIwcHg7b3ZlcmZsb3c6aGlkZGVuO3RleHQtb3ZlcmZsb3c6ZWxsaXBzaXMgIH0KICAgICAgICAuYnJlYWstd29yZCB7IHdvcmQtd3JhcDpicmVhay13b3JkO21hcmdpbi1sZWZ0OjMwcHggIH0KICAgICAgICAuYnJlYWstd29yZC5mbG9hdC1sZWZ0IGEgeyBjb2xvcjojN2Q3ZDdkICB9CiAgICAgICAgLmJyZWFrLXdvcmQgKyAuZmxvYXQtcmlnaHQgeyBwYWRkaW5nLXJpZ2h0OjMwcHg7cG9zaXRpb246cmVsYXRpdmUgIH0KICAgICAgICAuYnJlYWstd29yZCArIC5mbG9hdC1yaWdodCA+IGEgeyBjb2xvcjojN2Q3ZDdkO2ZvbnQtc2l6ZToxLjJlbTttYXJnaW4tcmlnaHQ6NHB4ICB9CiAgICAgICAgI2VkaXRvciB7IHBvc2l0aW9uOmFic29sdXRlO3JpZ2h0OjE1cHg7dG9wOjEwMHB4O2JvdHRvbToxNXB4O2xlZnQ6MTVweCAgfQogICAgICAgIEBtZWRpYSAobWF4LXdpZHRoOjQ4MXB4KSB7CiAgICAgICAgICAgICNlZGl0b3IgeyB0b3A6MTUwcHg7IH0KICAgICAgICB9CiAgICAgICAgI25vcm1hbC1lZGl0b3IgeyBib3JkZXItcmFkaXVzOjNweDtib3JkZXItd2lkdGg6MnB4O3BhZGRpbmc6MTBweDtvdXRsaW5lOm5vbmU7IH0KICAgICAgICAuYnRuLTIgeyBwYWRkaW5nOjRweCAxMHB4O2ZvbnQtc2l6ZTpzbWFsbDsgfQogICAgICAgIGxpLmZpbGU6YmVmb3JlLGxpLmZvbGRlcjpiZWZvcmUgeyBmb250Om5vcm1hbCBub3JtYWwgbm9ybWFsIDE0cHgvMSBGb250QXdlc29tZTtjb250ZW50OiJcZjAxNiI7bWFyZ2luLXJpZ2h0OjVweCB9CiAgICAgICAgbGkuZm9sZGVyOmJlZm9yZSB7IGNvbnRlbnQ6IlxmMTE0IiB9CiAgICAgICAgaS5mYS5mYS1mb2xkZXItbyB7IGNvbG9yOiMwMTU3YjMgfQogICAgICAgIGkuZmEuZmEtcGljdHVyZS1vIHsgY29sb3I6IzI2Yjk5YSB9CiAgICAgICAgaS5mYS5mYS1maWxlLWFyY2hpdmUtbyB7IGNvbG9yOiNkYTdkN2QgfQogICAgICAgIC5idG4tMiBpLmZhLmZhLWZpbGUtYXJjaGl2ZS1vIHsgY29sb3I6aW5oZXJpdCB9CiAgICAgICAgaS5mYS5mYS1jc3MzIHsgY29sb3I6I2YzNmZhMCB9CiAgICAgICAgaS5mYS5mYS1maWxlLWNvZGUtbyB7IGNvbG9yOiMwMDdiZmYgfQogICAgICAgIGkuZmEuZmEtY29kZSB7IGNvbG9yOiNjYzRiNGMgfQogICAgICAgIGkuZmEuZmEtZmlsZS10ZXh0LW8geyBjb2xvcjojMDA5NmU2IH0KICAgICAgICBpLmZhLmZhLWh0bWw1IHsgY29sb3I6I2Q3NWU3MiB9CiAgICAgICAgaS5mYS5mYS1maWxlLWV4Y2VsLW8geyBjb2xvcjojMDljNTVkIH0KICAgICAgICBpLmZhLmZhLWZpbGUtcG93ZXJwb2ludC1vIHsgY29sb3I6I2Y2NzEyZSB9CiAgICAgICAgaS5nby1iYWNrIHsgZm9udC1zaXplOjEuMmVtO2NvbG9yOiMwMDdiZmY7IH0KICAgICAgICAubWFpbi1uYXYgeyBwYWRkaW5nOjAuMnJlbSAxcmVtO2JveC1zaGFkb3c6MCA0cHggNXB4IDAgcmdiYSgwLCAwLCAwLCAuMTQpLCAwIDFweCAxMHB4IDAgcmdiYSgwLCAwLCAwLCAuMTIpLCAwIDJweCA0cHggLTFweCByZ2JhKDAsIDAsIDAsIC4yKSAgfQogICAgICAgIC5kYXRhVGFibGVzX2ZpbHRlciB7IGRpc3BsYXk6bm9uZTsgfQogICAgICAgIHRhYmxlLmRhdGFUYWJsZSB0aGVhZCAuc29ydGluZyB7IGN1cnNvcjpwb2ludGVyO2JhY2tncm91bmQtcmVwZWF0Om5vLXJlcGVhdDtiYWNrZ3JvdW5kLXBvc2l0aW9uOmNlbnRlciByaWdodDtiYWNrZ3JvdW5kLWltYWdlOnVybCgnZGF0YTppbWFnZS9wbmc7YmFzZTY0LGlWQk9SdzBLR2dvQUFBQU5TVWhFVWdBQUFCTUFBQUFUQ0FRQUFBRFlXZjVIQUFBQWtFbEVRVlFvejdYUU1RNUFRQkNGNGRXUVNKeEM1d3dheDFDcTFlN0JBZHhENVNMK1RxL1FDTTFvTmlKaWR3b3gwMzU1bVhuRy9EckV0SVE2YXppb05aUXhJMHlrUGhUUUl3aENSK0JtQll0bEs3a0xKWXdXQ2NKQTlNNHFkclpyZDhwUGpaV1B0T3FkUlF5MzIwWVNWMTdPYXRGQzRldXRzNnozOUdZTUtSUENUS1k5VW5QUTZQK0d0TVJmR3RQbkJDaXFoQWVKUG1rcUFBQUFBRWxGVGtTdVFtQ0MnKTsgfQogICAgICAgIHRhYmxlLmRhdGFUYWJsZSB0aGVhZCAuc29ydGluZ19hc2MgeyBjdXJzb3I6cG9pbnRlcjtiYWNrZ3JvdW5kLXJlcGVhdDpuby1yZXBlYXQ7YmFja2dyb3VuZC1wb3NpdGlvbjpjZW50ZXIgcmlnaHQ7YmFja2dyb3VuZC1pbWFnZTp1cmwoJ2RhdGE6aW1hZ2UvcG5nO2Jhc2U2NCxpVkJPUncwS0dnb0FBQUFOU1VoRVVnQUFBQk1BQUFBVENBWUFBQUJ5VURiTUFBQUFaMGxFUVZRNHkyTmdHTEtncXVFdUZ4QlBBR0kyYWhoV0NzUy9nRGliVW9PMGdQZ3hFUDhINHR0QXJFeXVRWXhBUEJkcUVBeFBCSW1UWTVnakVMOURNK3dURU51UWFoQXZFTzlETXdpR2R3QXhPeW1HSlFMeFR5RCtqZ1dEeENNWlJzRW9HQVZvQUFEZWVtd3RQY1pJMndBQUFBQkpSVTVFcmtKZ2dnPT0nKTsgfQogICAgICAgIHRhYmxlLmRhdGFUYWJsZSB0aGVhZCAuc29ydGluZ19kZXNjIHsgY3Vyc29yOnBvaW50ZXI7YmFja2dyb3VuZC1yZXBlYXQ6bm8tcmVwZWF0O2JhY2tncm91bmQtcG9zaXRpb246Y2VudGVyIHJpZ2h0O2JhY2tncm91bmQtaW1hZ2U6dXJsKCdkYXRhOmltYWdlL3BuZztiYXNlNjQsaVZCT1J3MEtHZ29BQUFBTlNVaEVVZ0FBQUJNQUFBQVRDQVlBQUFCeVVEYk1BQUFBWlVsRVFWUTR5Mk5nR0FXallCU2dnYXFHdTVGQS9CT0l2MlBCSVBGRVVneGpCK0lkUVB3ZkM5NEh4THlrdXM0R2lEK2hHZlFPaUIzSjhTb2pFRTlFTTJ3dVNKemNzRk1HNHR0UWd4NERzUmFsa1pFTnhMK0F1SlFhTWNzR3hCT0FtR3ZvcGs4QVZ6MXNMWmdnMGJzQUFBQUFTVVZPUks1Q1lJST0nKTsgfQogICAgICAgIHRhYmxlLmRhdGFUYWJsZSB0aGVhZCB0cjpmaXJzdC1jaGlsZCB0aC5jdXN0b20tY2hlY2tib3gtaGVhZGVyOmZpcnN0LWNoaWxkIHsgYmFja2dyb3VuZC1pbWFnZTpub25lOyB9CiAgICAgICAgLmZvb3Rlci1hY3Rpb24gbGkgeyBtYXJnaW4tYm90dG9tOjEwcHg7IH0KICAgICAgICAuYXBwLXYtdGl0bGUgeyBmb250LXNpemU6MjRweDtmb250LXdlaWdodDozMDA7bGV0dGVyLXNwYWNpbmc6LS41cHg7dGV4dC10cmFuc2Zvcm06dXBwZXJjYXNlOyB9CiAgICAgICAgaHIuY3VzdG9tLWhyIHsgYm9yZGVyLXRvcDoxcHggZGFzaGVkICM4YzhiOGI7Ym9yZGVyLWJvdHRvbToxcHggZGFzaGVkICNmZmY7IH0KICAgICAgICAjc25hY2tiYXIgeyB2aXNpYmlsaXR5OmhpZGRlbjttaW4td2lkdGg6MjUwcHg7bWFyZ2luLWxlZnQ6LTEyNXB4O2JhY2tncm91bmQtY29sb3I6IzMzMztjb2xvcjojZmZmO3RleHQtYWxpZ246Y2VudGVyO2JvcmRlci1yYWRpdXM6MnB4O3BhZGRpbmc6MTZweDtwb3NpdGlvbjpmaXhlZDt6LWluZGV4OjE7bGVmdDo1MCU7Ym90dG9tOjMwcHg7Zm9udC1zaXplOjE3cHg7IH0KICAgICAgICAjc25hY2tiYXIuc2hvdyB7IHZpc2liaWxpdHk6dmlzaWJsZTstd2Via2l0LWFuaW1hdGlvbjpmYWRlaW4gMC41cywgZmFkZW91dCAwLjVzIDIuNXM7YW5pbWF0aW9uOmZhZGVpbiAwLjVzLCBmYWRlb3V0IDAuNXMgMi41czsgfQogICAgICAgIEAtd2Via2l0LWtleWZyYW1lcyBmYWRlaW4geyBmcm9tIHsgYm90dG9tOjA7b3BhY2l0eTowOyB9CiAgICAgICAgdG8geyBib3R0b206MzBweDtvcGFjaXR5OjE7IH0KICAgICAgICB9CiAgICAgICAgQGtleWZyYW1lcyBmYWRlaW4geyBmcm9tIHsgYm90dG9tOjA7b3BhY2l0eTowOyB9CiAgICAgICAgdG8geyBib3R0b206MzBweDtvcGFjaXR5OjE7IH0KICAgICAgICB9CiAgICAgICAgQC13ZWJraXQta2V5ZnJhbWVzIGZhZGVvdXQgeyBmcm9tIHsgYm90dG9tOjMwcHg7b3BhY2l0eToxOyB9CiAgICAgICAgdG8geyBib3R0b206MDtvcGFjaXR5OjA7IH0KICAgICAgICB9CiAgICAgICAgQGtleWZyYW1lcyBmYWRlb3V0IHsgZnJvbSB7IGJvdHRvbTozMHB4O29wYWNpdHk6MTsgfQogICAgICAgIHRvIHsgYm90dG9tOjA7b3BhY2l0eTowOyB9CiAgICAgICAgfQogICAgICAgICNtYWluLXRhYmxlIHNwYW4uYmFkZ2UgeyBib3JkZXItYm90dG9tOjJweCBzb2xpZCAjZjhmOWZhIH0KICAgICAgICAjbWFpbi10YWJsZSBzcGFuLmJhZGdlOm50aC1jaGlsZCgxKSB7IGJvcmRlci1jb2xvcjojZGY0MjI3IH0KICAgICAgICAjbWFpbi10YWJsZSBzcGFuLmJhZGdlOm50aC1jaGlsZCgyKSB7IGJvcmRlci1jb2xvcjojZjhiNjAwIH0KICAgICAgICAjbWFpbi10YWJsZSBzcGFuLmJhZGdlOm50aC1jaGlsZCgzKSB7IGJvcmRlci1jb2xvcjojMDBiZDYwIH0KICAgICAgICAjbWFpbi10YWJsZSBzcGFuLmJhZGdlOm50aC1jaGlsZCg0KSB7IGJvcmRlci1jb2xvcjojNDU4MWZmIH0KICAgICAgICAjbWFpbi10YWJsZSBzcGFuLmJhZGdlOm50aC1jaGlsZCg1KSB7IGJvcmRlci1jb2xvcjojYWM2OGZjIH0KICAgICAgICAjbWFpbi10YWJsZSBzcGFuLmJhZGdlOm50aC1jaGlsZCg2KSB7IGJvcmRlci1jb2xvcjojNDVjM2QyIH0KICAgICAgICBAbWVkaWEgb25seSBzY3JlZW4gYW5kIChtaW4tZGV2aWNlLXdpZHRoOjc2OHB4KSBhbmQgKG1heC1kZXZpY2Utd2lkdGg6MTAyNHB4KSBhbmQgKG9yaWVudGF0aW9uOmxhbmRzY2FwZSkgYW5kICgtd2Via2l0LW1pbi1kZXZpY2UtcGl4ZWwtcmF0aW86MikgeyAubmF2YmFyLWNvbGxhcHNlIC5jb2wteHMtNiB7IHBhZGRpbmc6MDsgfQogICAgICAgIH0KICAgICAgICAuYnRuLmFjdGl2ZS5mb2N1cywuYnRuLmFjdGl2ZTpmb2N1cywuYnRuLmZvY3VzLC5idG4uZm9jdXM6YWN0aXZlLC5idG46YWN0aXZlOmZvY3VzLC5idG46Zm9jdXMgeyBvdXRsaW5lOjAhaW1wb3J0YW50O291dGxpbmUtb2Zmc2V0OjAhaW1wb3J0YW50O2JhY2tncm91bmQtaW1hZ2U6bm9uZSFpbXBvcnRhbnQ7LXdlYmtpdC1ib3gtc2hhZG93Om5vbmUhaW1wb3J0YW50O2JveC1zaGFkb3c6bm9uZSFpbXBvcnRhbnQgfQogICAgICAgIC5sZHMtZmFjZWJvb2sgeyBkaXNwbGF5Om5vbmU7cG9zaXRpb246cmVsYXRpdmU7d2lkdGg6NjRweDtoZWlnaHQ6NjRweCB9CiAgICAgICAgLmxkcy1mYWNlYm9vayBkaXYsLmxkcy1mYWNlYm9vay5zaG93LW1lIHsgZGlzcGxheTppbmxpbmUtYmxvY2sgfQogICAgICAgIC5sZHMtZmFjZWJvb2sgZGl2IHsgcG9zaXRpb246YWJzb2x1dGU7bGVmdDo2cHg7d2lkdGg6MTNweDtiYWNrZ3JvdW5kOiMwMDdiZmY7YW5pbWF0aW9uOmxkcy1mYWNlYm9vayAxLjJzIGN1YmljLWJlemllcigwLC41LC41LDEpIGluZmluaXRlIH0KICAgICAgICAubGRzLWZhY2Vib29rIGRpdjpudGgtY2hpbGQoMSkgeyBsZWZ0OjZweDthbmltYXRpb24tZGVsYXk6LS4yNHMgfQogICAgICAgIC5sZHMtZmFjZWJvb2sgZGl2Om50aC1jaGlsZCgyKSB7IGxlZnQ6MjZweDthbmltYXRpb24tZGVsYXk6LS4xMnMgfQogICAgICAgIC5sZHMtZmFjZWJvb2sgZGl2Om50aC1jaGlsZCgzKSB7IGxlZnQ6NDVweDthbmltYXRpb24tZGVsYXk6MHMgfQogICAgICAgIEBrZXlmcmFtZXMgbGRzLWZhY2Vib29rIHsgMCUgeyB0b3A6NnB4O2hlaWdodDo1MXB4IH0KICAgICAgICAxMDAlLDUwJSB7IHRvcDoxOXB4O2hlaWdodDoyNnB4IH0KICAgICAgICB9CiAgICAgICAgdWwjc2VhcmNoLXdyYXBwZXIgeyBwYWRkaW5nLWxlZnQ6IDA7Ym9yZGVyOiAxcHggc29saWQgI2VjZWNlY2NjOyB9IHVsI3NlYXJjaC13cmFwcGVyIGxpIHsgbGlzdC1zdHlsZTogbm9uZTsgcGFkZGluZzogNXB4O2JvcmRlci1ib3R0b206IDFweCBzb2xpZCAjZWNlY2VjY2M7IH0KICAgICAgICB1bCNzZWFyY2gtd3JhcHBlciBsaTpudGgtY2hpbGQob2RkKXsgYmFja2dyb3VuZDogI2Y5ZjlmOWNjO30KICAgICAgICAuYy1wcmV2aWV3LWltZyB7IG1heC13aWR0aDogMzAwcHg7IH0KICAgICAgICAuYm9yZGVyLXJhZGl1cy0wIHsgYm9yZGVyLXJhZGl1czogMDsgfQogICAgICAgIC5mbG9hdC1yaWdodCB7IGZsb2F0OiByaWdodDsgfQogICAgICAgIC50YWJsZS1ob3Zlcj50Ym9keT50cjpob3Zlcj50ZDpmaXJzdC1jaGlsZCB7IGJvcmRlci1sZWZ0OiAxcHggc29saWQgIzFiNzdmZDsgfQogICAgICAgICNtYWluLXRhYmxlIHRyLmV2ZW4geyBiYWNrZ3JvdW5kLWNvbG9yOiAjRjhGOUZhOyB9CiAgICAgICAgLmZpbGVuYW1lPmE+aSB7bWFyZ2luLXJpZ2h0OiAzcHg7fQogICAgPC9zdHlsZT4KICAgIDw/cGhwCiAgICBpZiAoRk1fVEhFTUUgPT0gImRhcmsiKTogPz4KICAgICAgICA8c3R5bGU+CiAgICAgICAgICAgIDpyb290IHsKICAgICAgICAgICAgICAgIC0tYnMtYmctb3BhY2l0eTogMTsKICAgICAgICAgICAgICAgIGJhY2tncm91bmQtY29sb3I6IHJnYmEodmFyKDI4LCAzNiwgNDEpLHZhcigtLWJzLWJnLW9wYWNpdHkpKSFpbXBvcnRhbnQ7CiAgICAgICAgICAgIH0KICAgICAgICAgICAgYm9keS50aGVtZS1kYXJrIHsgYmFja2dyb3VuZC1pbWFnZTogbGluZWFyLWdyYWRpZW50KDkwZGVnLCAjMWMyNDI5LCAjMjYzMjM4KTsgY29sb3I6ICNDRkQ4REM7IH0KICAgICAgICAgICAgLmxpc3QtZ3JvdXAgLmxpc3QtZ3JvdXAtaXRlbSB7IGJhY2tncm91bmQ6ICMzNDNhNDA7IH0KICAgICAgICAgICAgLnRoZW1lLWRhcmsgLm5hdmJhci1uYXYgaSwgLm5hdmJhci1uYXYgLmRyb3Bkb3duLXRvZ2dsZSwgLmJyZWFrLXdvcmQgeyBjb2xvcjogI0NGRDhEQzsgfQogICAgICAgICAgICBhLCBhOmhvdmVyLCBhOnZpc2l0ZWQsIGE6YWN0aXZlLCAjbWFpbi10YWJsZSAuZmlsZW5hbWUgYSwgaS5mYS5mYS1mb2xkZXItbywgaS5nby1iYWNrIHsgY29sb3I6ICM4NWZkOTQ7IH0KICAgICAgICAgICAgdWwjc2VhcmNoLXdyYXBwZXIgbGk6bnRoLWNoaWxkKG9kZCkgeyBiYWNrZ3JvdW5kOiAjZjlmOWY5Y2M7IH0KICAgICAgICAgICAgLnRoZW1lLWRhcmsgLmJ0bi1vdXRsaW5lLXByaW1hcnkgeyBjb2xvcjogIzg1ZmQ5NDsgYm9yZGVyLWNvbG9yOiAjODVmZDk0OyB9CiAgICAgICAgICAgIC50aGVtZS1kYXJrIC5idG4tb3V0bGluZS1wcmltYXJ5OmhvdmVyLCAudGhlbWUtZGFyayAuYnRuLW91dGxpbmUtcHJpbWFyeTphY3RpdmUgeyBiYWNrZ3JvdW5kLWNvbG9yOiAjMDI4MjExO30KICAgICAgICAgICAgLnRoZW1lLWRhcmsgaW5wdXQuZm9ybS1jb250cm9sIHsgYmFja2dyb3VuZC1jb2xvcjogIzEwMTUxODsgY29sb3I6ICNDRkQ4REM7IH0KICAgICAgICAgICAgLnRoZW1lLWRhcmsgLmRyb3B6b25lIHsgYmFja2dyb3VuZDogdHJhbnNwYXJlbnQ7IH0KICAgICAgICAgICAgLnRoZW1lLWRhcmsgLmlubGluZS1hY3Rpb25zID4gYSA+IGkgeyBiYWNrZ3JvdW5kOiAjNjA3ZDhiOyB9CiAgICAgICAgICAgIC50aGVtZS1kYXJrIC50ZXh0LXdoaXRlIHsgY29sb3I6ICNDRkQ4REMgIWltcG9ydGFudDsgfQogICAgICAgICAgICAudGhlbWUtZGFyayAudGFibGUtYm9yZGVyZWQgdGQsIC50YWJsZS1ib3JkZXJlZCB0aCB7IGJvcmRlci1jb2xvcjogIzM0MzQzNDsgfQogICAgICAgICAgICAudGhlbWUtZGFyayAudGFibGUtYm9yZGVyZWQgdGQgLmN1c3RvbS1jb250cm9sLWlucHV0LCAudGhlbWUtZGFyayAudGFibGUtYm9yZGVyZWQgdGggLmN1c3RvbS1jb250cm9sLWlucHV0IHsgb3BhY2l0eTogMC42Nzg7IH0KICAgICAgICAgICAgLm1lc3NhZ2UgeyBiYWNrZ3JvdW5kLWNvbG9yOiAjMjEyNTI5OyB9CiAgICAgICAgPC9zdHlsZT4KICAgIDw/cGhwIGVuZGlmOyA/Pgo8L2hlYWQ+Cjxib2R5IGNsYXNzPSI8P3BocCBlY2hvIChGTV9USEVNRSA9PSAiZGFyayIpID8gJ3RoZW1lLWRhcmsnIDogJyc7ID8+IDw/cGhwIGVjaG8gJGlzU3RpY2t5TmF2QmFyOyA/PiI+CjxkaXYgaWQ9IndyYXBwZXIiIGNsYXNzPSJjb250YWluZXItZmx1aWQiPgogICAgPCEtLSBOZXcgSXRlbSBjcmVhdGlvbiAtLT4KICAgIDxkaXYgY2xhc3M9Im1vZGFsIGZhZGUiIGlkPSJjcmVhdGVOZXdJdGVtIiB0YWJpbmRleD0iLTEiIHJvbGU9ImRpYWxvZyIgZGF0YS1icy1iYWNrZHJvcD0ic3RhdGljIiBkYXRhLWJzLWtleWJvYXJkPSJmYWxzZSIgYXJpYS1sYWJlbGxlZGJ5PSJuZXdJdGVtTW9kYWxMYWJlbCIgYXJpYS1oaWRkZW49InRydWUiPgogICAgICAgIDxkaXYgY2xhc3M9Im1vZGFsLWRpYWxvZyIgcm9sZT0iZG9jdW1lbnQiPgogICAgICAgICAgICA8Zm9ybSBjbGFzcz0ibW9kYWwtY29udGVudCA8P3BocCBlY2hvIGZtX2dldF90aGVtZSgpOyA/PiIgbWV0aG9kPSJwb3N0Ij4KICAgICAgICAgICAgICAgIDxkaXYgY2xhc3M9Im1vZGFsLWhlYWRlciI+CiAgICAgICAgICAgICAgICAgICAgPGg1IGNsYXNzPSJtb2RhbC10aXRsZSIgaWQ9Im5ld0l0ZW1Nb2RhbExhYmVsIj48aSBjbGFzcz0iZmEgZmEtcGx1cy1zcXVhcmUgZmEtZnciPjwvaT48P3BocCBlY2hvIGxuZygnQ3JlYXRlTmV3SXRlbScpID8+PC9oNT4KICAgICAgICAgICAgICAgICAgICA8YnV0dG9uIHR5cGU9ImJ1dHRvbiIgY2xhc3M9ImJ0bi1jbG9zZSIgZGF0YS1icy1kaXNtaXNzPSJtb2RhbCIgYXJpYS1sYWJlbD0iQ2xvc2UiPjwvYnV0dG9uPgogICAgICAgICAgICAgICAgPC9kaXY+CiAgICAgICAgICAgICAgICA8ZGl2IGNsYXNzPSJtb2RhbC1ib2R5Ij4KICAgICAgICAgICAgICAgICAgICA8cD48bGFiZWwgZm9yPSJuZXdmaWxlIj48P3BocCBlY2hvIGxuZygnSXRlbVR5cGUnKSA/PiA8L2xhYmVsPjwvcD4KICAgICAgICAgICAgICAgICAgICA8ZGl2IGNsYXNzPSJmb3JtLWNoZWNrIGZvcm0tY2hlY2staW5saW5lIj4KICAgICAgICAgICAgICAgICAgICAgIDxpbnB1dCBjbGFzcz0iZm9ybS1jaGVjay1pbnB1dCIgdHlwZT0icmFkaW8iIG5hbWU9Im5ld2ZpbGUiIGlkPSJjdXN0b21SYWRpb0lubGluZTEiIG5hbWU9Im5ld2ZpbGUiIHZhbHVlPSJmaWxlIj4KICAgICAgICAgICAgICAgICAgICAgIDxsYWJlbCBjbGFzcz0iZm9ybS1jaGVjay1sYWJlbCIgZm9yPSJjdXN0b21SYWRpb0lubGluZTEiPjw/cGhwIGVjaG8gbG5nKCdGaWxlJykgPz48L2xhYmVsPgogICAgICAgICAgICAgICAgICAgIDwvZGl2PgogICAgICAgICAgICAgICAgICAgIDxkaXYgY2xhc3M9ImZvcm0tY2hlY2sgZm9ybS1jaGVjay1pbmxpbmUiPgogICAgICAgICAgICAgICAgICAgICAgPGlucHV0IGNsYXNzPSJmb3JtLWNoZWNrLWlucHV0IiB0eXBlPSJyYWRpbyIgbmFtZT0ibmV3ZmlsZSIgaWQ9ImN1c3RvbVJhZGlvSW5saW5lMiIgdmFsdWU9ImZvbGRlciIgY2hlY2tlZD4KICAgICAgICAgICAgICAgICAgICAgIDxsYWJlbCBjbGFzcz0iZm9ybS1jaGVjay1sYWJlbCIgZm9yPSJjdXN0b21SYWRpb0lubGluZTIiPjw/cGhwIGVjaG8gbG5nKCdGb2xkZXInKSA/PjwvbGFiZWw+CiAgICAgICAgICAgICAgICAgICAgPC9kaXY+CgogICAgICAgICAgICAgICAgICAgIDxwIGNsYXNzPSJtdC0zIj48bGFiZWwgZm9yPSJuZXdmaWxlbmFtZSI+PD9waHAgZWNobyBsbmcoJ0l0ZW1OYW1lJykgPz4gPC9sYWJlbD48L3A+CiAgICAgICAgICAgICAgICAgICAgPGlucHV0IHR5cGU9InRleHQiIG5hbWU9Im5ld2ZpbGVuYW1lIiBpZD0ibmV3ZmlsZW5hbWUiIHZhbHVlPSIiIGNsYXNzPSJmb3JtLWNvbnRyb2wiIHBsYWNlaG9sZGVyPSJFbnRlciBoZXJlLi4uIiByZXF1aXJlZD4KICAgICAgICAgICAgICAgIDwvZGl2PgogICAgICAgICAgICAgICAgPGRpdiBjbGFzcz0ibW9kYWwtZm9vdGVyIj4KICAgICAgICAgICAgICAgICAgICA8aW5wdXQgdHlwZT0iaGlkZGVuIiBuYW1lPSJ0b2tlbiIgdmFsdWU9Ijw/cGhwIGVjaG8gJF9TRVNTSU9OWyd0b2tlbiddOyA/PiI+CiAgICAgICAgICAgICAgICAgICAgPGJ1dHRvbiB0eXBlPSJidXR0b24iIGNsYXNzPSJidG4gYnRuLW91dGxpbmUtcHJpbWFyeSIgZGF0YS1icy1kaXNtaXNzPSJtb2RhbCI+PGkgY2xhc3M9ImZhIGZhLXRpbWVzLWNpcmNsZSI+PC9pPiA8P3BocCBlY2hvIGxuZygnQ2FuY2VsJykgPz48L2J1dHRvbj4KICAgICAgICAgICAgICAgICAgICA8YnV0dG9uIHR5cGU9InN1Ym1pdCIgY2xhc3M9ImJ0biBidG4tc3VjY2VzcyI+PGkgY2xhc3M9ImZhIGZhLWNoZWNrLWNpcmNsZSI+PC9pPiA8P3BocCBlY2hvIGxuZygnQ3JlYXRlTm93JykgPz48L2J1dHRvbj4KICAgICAgICAgICAgICAgIDwvZGl2PgogICAgICAgICAgICA8L2Zvcm0+CiAgICAgICAgPC9kaXY+CiAgICA8L2Rpdj4KCiAgICA8IS0tIEFkdmFuY2UgU2VhcmNoIE1vZGFsIC0tPgogICAgPGRpdiBjbGFzcz0ibW9kYWwgZmFkZSIgaWQ9InNlYXJjaE1vZGFsIiB0YWJpbmRleD0iLTEiIHJvbGU9ImRpYWxvZyIgYXJpYS1sYWJlbGxlZGJ5PSJzZWFyY2hNb2RhbExhYmVsIiBhcmlhLWhpZGRlbj0idHJ1ZSI+CiAgICAgIDxkaXYgY2xhc3M9Im1vZGFsLWRpYWxvZyBtb2RhbC1sZyIgcm9sZT0iZG9jdW1lbnQiPgogICAgICAgIDxkaXYgY2xhc3M9Im1vZGFsLWNvbnRlbnQgPD9waHAgZWNobyBmbV9nZXRfdGhlbWUoKTsgPz4iPgogICAgICAgICAgPGRpdiBjbGFzcz0ibW9kYWwtaGVhZGVyIj4KICAgICAgICAgICAgPGg1IGNsYXNzPSJtb2RhbC10aXRsZSBjb2wtMTAiIGlkPSJzZWFyY2hNb2RhbExhYmVsIj4KICAgICAgICAgICAgICAgIDxkaXYgY2xhc3M9ImlucHV0LWdyb3VwIG1iLTMiPgogICAgICAgICAgICAgICAgICA8aW5wdXQgdHlwZT0idGV4dCIgY2xhc3M9ImZvcm0tY29udHJvbCIgcGxhY2Vob2xkZXI9Ijw/cGhwIGVjaG8gbG5nKCdTZWFyY2gnKSA/PiBhIGZpbGVzIiBhcmlhLWxhYmVsPSI8P3BocCBlY2hvIGxuZygnU2VhcmNoJykgPz4iIGFyaWEtZGVzY3JpYmVkYnk9InNlYXJjaC1hZGRvbjMiIGlkPSJhZHZhbmNlZC1zZWFyY2giIGF1dG9mb2N1cyByZXF1aXJlZD4KICAgICAgICAgICAgICAgICAgPHNwYW4gY2xhc3M9ImlucHV0LWdyb3VwLXRleHQiIGlkPSJzZWFyY2gtYWRkb24zIj48aSBjbGFzcz0iZmEgZmEtc2VhcmNoIj48L2k+PC9zcGFuPgogICAgICAgICAgICAgICAgPC9kaXY+CiAgICAgICAgICAgIDwvaDU+CiAgICAgICAgICAgIDxidXR0b24gdHlwZT0iYnV0dG9uIiBjbGFzcz0iYnRuLWNsb3NlIiBkYXRhLWJzLWRpc21pc3M9Im1vZGFsIiBhcmlhLWxhYmVsPSJDbG9zZSI+PC9idXR0b24+CiAgICAgICAgICA8L2Rpdj4KICAgICAgICAgIDxkaXYgY2xhc3M9Im1vZGFsLWJvZHkiPgogICAgICAgICAgICA8Zm9ybSBhY3Rpb249IiIgbWV0aG9kPSJwb3N0Ij4KICAgICAgICAgICAgICAgIDxkaXYgY2xhc3M9Imxkcy1mYWNlYm9vayI+PGRpdj48L2Rpdj48ZGl2PjwvZGl2PjxkaXY+PC9kaXY+PC9kaXY+CiAgICAgICAgICAgICAgICA8dWwgaWQ9InNlYXJjaC13cmFwcGVyIj4KICAgICAgICAgICAgICAgICAgICA8cCBjbGFzcz0ibS0yIj48P3BocCBlY2hvIGxuZygnU2VhcmNoIGZpbGUgaW4gZm9sZGVyIGFuZCBzdWJmb2xkZXJzLi4uJykgPz48L3A+CiAgICAgICAgICAgICAgICA8L3VsPgogICAgICAgICAgICA8L2Zvcm0+CiAgICAgICAgICA8L2Rpdj4KICAgICAgICA8L2Rpdj4KICAgICAgPC9kaXY+CiAgICA8L2Rpdj4KCiAgICA8IS0tUmVuYW1lIE1vZGFsIC0tPgogICAgPGRpdiBjbGFzcz0ibW9kYWwgbW9kYWwtYWxlcnQiIGRhdGEtYnMtYmFja2Ryb3A9InN0YXRpYyIgZGF0YS1icy1rZXlib2FyZD0iZmFsc2UiIHRhYmluZGV4PSItMSIgcm9sZT0iZGlhbG9nIiBpZD0icmVuYW1lRGFpbG9nIj4KICAgICAgPGRpdiBjbGFzcz0ibW9kYWwtZGlhbG9nIiByb2xlPSJkb2N1bWVudCI+CiAgICAgICAgPGZvcm0gY2xhc3M9Im1vZGFsLWNvbnRlbnQgcm91bmRlZC0zIHNoYWRvdyA8P3BocCBlY2hvIGZtX2dldF90aGVtZSgpOyA/PiIgbWV0aG9kPSJwb3N0IiBhdXRvY29tcGxldGU9Im9mZiI+CiAgICAgICAgICA8ZGl2IGNsYXNzPSJtb2RhbC1ib2R5IHAtNCB0ZXh0LWNlbnRlciI+CiAgICAgICAgICAgIDxoNSBjbGFzcz0ibWItMyI+QXJlIHlvdSBzdXJlIHdhbnQgdG8gcmVuYW1lPzwvaDU+CiAgICAgICAgICAgIDxwIGNsYXNzPSJtYi0xIj4KICAgICAgICAgICAgICAgIDxpbnB1dCB0eXBlPSJ0ZXh0IiBuYW1lPSJyZW5hbWVfdG8iIGlkPSJqcy1yZW5hbWUtdG8iIGNsYXNzPSJmb3JtLWNvbnRyb2wiIHBsYWNlaG9sZGVyPSJFbnRlciBuZXcgZmlsZSBuYW1lIiByZXF1aXJlZD4KICAgICAgICAgICAgICAgIDxpbnB1dCB0eXBlPSJoaWRkZW4iIG5hbWU9InRva2VuIiB2YWx1ZT0iPD9waHAgZWNobyAkX1NFU1NJT05bJ3Rva2VuJ107ID8+Ij4KICAgICAgICAgICAgICAgIDxpbnB1dCB0eXBlPSJoaWRkZW4iIG5hbWU9InJlbmFtZV9mcm9tIiBpZD0ianMtcmVuYW1lLWZyb20iPgogICAgICAgICAgICA8L3A+CiAgICAgICAgICA8L2Rpdj4KICAgICAgICAgIDxkaXYgY2xhc3M9Im1vZGFsLWZvb3RlciBmbGV4LW5vd3JhcCBwLTAiPgogICAgICAgICAgICA8YnV0dG9uIHR5cGU9ImJ1dHRvbiIgY2xhc3M9ImJ0biBidG4tbGcgYnRuLWxpbmsgZnMtNiB0ZXh0LWRlY29yYXRpb24tbm9uZSBjb2wtNiBtLTAgcm91bmRlZC0wIGJvcmRlci1lbmQiIGRhdGEtYnMtZGlzbWlzcz0ibW9kYWwiPkNhbmNlbDwvYnV0dG9uPgogICAgICAgICAgICA8YnV0dG9uIHR5cGU9InN1Ym1pdCIgY2xhc3M9ImJ0biBidG4tbGcgYnRuLWxpbmsgZnMtNiB0ZXh0LWRlY29yYXRpb24tbm9uZSBjb2wtNiBtLTAgcm91bmRlZC0wIj48c3Ryb25nPk9rYXk8L3N0cm9uZz48L2J1dHRvbj4KICAgICAgICAgIDwvZGl2PgogICAgICAgIDwvZm9ybT4KICAgICAgPC9kaXY+CiAgICA8L2Rpdj4KCiAgICA8IS0tIENvbmZpcm0gTW9kYWwgLS0+CiAgICA8c2NyaXB0IHR5cGU9InRleHQvaHRtbCIgaWQ9ImpzLXRwbC1jb25maXJtIj4KICAgICAgICA8ZGl2IGNsYXNzPSJtb2RhbCBtb2RhbC1hbGVydCIgZGF0YS1icy1iYWNrZHJvcD0ic3RhdGljIiBkYXRhLWJzLWtleWJvYXJkPSJmYWxzZSIgdGFiaW5kZXg9Ii0xIiByb2xlPSJkaWFsb2ciIGlkPSJjb25maXJtRGFpbG9nLTwldGhpcy5pZCU+Ij4KICAgICAgICAgIDxkaXYgY2xhc3M9Im1vZGFsLWRpYWxvZyIgcm9sZT0iZG9jdW1lbnQiPgogICAgICAgICAgICA8Zm9ybSBjbGFzcz0ibW9kYWwtY29udGVudCByb3VuZGVkLTMgc2hhZG93IDw/cGhwIGVjaG8gZm1fZ2V0X3RoZW1lKCk7ID8+IiBtZXRob2Q9InBvc3QiIGF1dG9jb21wbGV0ZT0ib2ZmIiBhY3Rpb249IjwldGhpcy5hY3Rpb24lPiI+CiAgICAgICAgICAgICAgPGRpdiBjbGFzcz0ibW9kYWwtYm9keSBwLTQgdGV4dC1jZW50ZXIiPgogICAgICAgICAgICAgICAgPGg1IGNsYXNzPSJtYi0yIj5BcmUgeW91IHN1cmUgd2FudCB0byA8JXRoaXMudGl0bGUlPiA/PC9oNT4KICAgICAgICAgICAgICAgIDxwIGNsYXNzPSJtYi0xIj48JXRoaXMuY29udGVudCU+PC9wPgogICAgICAgICAgICAgIDwvZGl2PgogICAgICAgICAgICAgIDxkaXYgY2xhc3M9Im1vZGFsLWZvb3RlciBmbGV4LW5vd3JhcCBwLTAiPgogICAgICAgICAgICAgICAgPGJ1dHRvbiB0eXBlPSJidXR0b24iIGNsYXNzPSJidG4gYnRuLWxnIGJ0bi1saW5rIGZzLTYgdGV4dC1kZWNvcmF0aW9uLW5vbmUgY29sLTYgbS0wIHJvdW5kZWQtMCBib3JkZXItZW5kIiBkYXRhLWJzLWRpc21pc3M9Im1vZGFsIj5DYW5jZWw8L2J1dHRvbj4KICAgICAgICAgICAgICAgIDxpbnB1dCB0eXBlPSJoaWRkZW4iIG5hbWU9InRva2VuIiB2YWx1ZT0iPD9waHAgZWNobyAkX1NFU1NJT05bJ3Rva2VuJ107ID8+Ij4KICAgICAgICAgICAgICAgIDxidXR0b24gdHlwZT0ic3VibWl0IiBjbGFzcz0iYnRuIGJ0bi1sZyBidG4tbGluayBmcy02IHRleHQtZGVjb3JhdGlvbi1ub25lIGNvbC02IG0tMCByb3VuZGVkLTAiIGRhdGEtYnMtZGlzbWlzcz0ibW9kYWwiPjxzdHJvbmc+T2theTwvc3Ryb25nPjwvYnV0dG9uPgogICAgICAgICAgICAgIDwvZGl2PgogICAgICAgICAgICA8L2Zvcm0+CiAgICAgICAgICA8L2Rpdj4KICAgICAgICA8L2Rpdj4KICAgIDwvc2NyaXB0PgoKICAgIDw/cGhwCiAgICB9CgogICAgLyoqCiAgICAgKiBTaG93IHBhZ2UgZm9vdGVyIGFmdGVyIGxvZ2luCiAgICAgKi8KICAgIGZ1bmN0aW9uIGZtX3Nob3dfZm9vdGVyKCkKICAgIHsKICAgID8+CjwvZGl2Pgo8c2NyaXB0IHNyYz0iaHR0cHM6Ly9jb2RlLmpxdWVyeS5jb20vanF1ZXJ5LTMuNi4xLm1pbi5qcyIgaW50ZWdyaXR5PSJzaGEyNTYtbzg4QXdRblpCK1ZEdkU5dHZJWHJNUWFQbEZGU1VUUitubGRRbTFMdVBYUT0iIGNyb3Nzb3JpZ2luPSJhbm9ueW1vdXMiPjwvc2NyaXB0Pgo8c2NyaXB0IHNyYz0iaHR0cHM6Ly9jZG4uanNkZWxpdnIubmV0L25wbS9ib290c3RyYXBANS4yLjIvZGlzdC9qcy9ib290c3RyYXAuYnVuZGxlLm1pbi5qcyIgaW50ZWdyaXR5PSJzaGEzODQtT0VSY0EyRXFqSkNNQSsvM3krZ3hJT3FNRWp3dHhKWTdxUENxc2RsdGJOSnVhT2U5MjMrbW8vL2Y2VjhRYnN3MyIgY3Jvc3NvcmlnaW49ImFub255bW91cyI+PC9zY3JpcHQ+CjxzY3JpcHQgc3JjPSJodHRwczovL2Nkbi5kYXRhdGFibGVzLm5ldC8xLjEzLjEvanMvanF1ZXJ5LmRhdGFUYWJsZXMubWluLmpzIiBjcm9zc29yaWdpbj0iYW5vbnltb3VzIiBkZWZlcj48L3NjcmlwdD4KPD9waHAgaWYgKEZNX1VTRV9ISUdITElHSFRKUyAmJiBpc3NldCgkX0dFVFsndmlldyddKSk6ID8+CiAgICA8c2NyaXB0IHNyYz0iaHR0cHM6Ly9jZG5qcy5jbG91ZGZsYXJlLmNvbS9hamF4L2xpYnMvaGlnaGxpZ2h0LmpzLzExLjYuMC9oaWdobGlnaHQubWluLmpzIj48L3NjcmlwdD4KICAgIDxzY3JpcHQ+aGxqcy5oaWdobGlnaHRBbGwoKTsgdmFyIGlzSGlnaGxpZ2h0aW5nRW5hYmxlZCA9IHRydWU7PC9zY3JpcHQ+Cjw/cGhwIGVuZGlmOyA/Pgo8c2NyaXB0PgogICAgZnVuY3Rpb24gdGVtcGxhdGUoaHRtbCxvcHRpb25zKXsKICAgICAgICB2YXIgcmU9LzxcJShbXlwlPl0rKT9cJT4vZyxyZUV4cD0vKF4oICk/KGlmfGZvcnxlbHNlfHN3aXRjaHxjYXNlfGJyZWFrfHt8fSkpKC4qKT8vZyxjb2RlPSd2YXIgcj1bXTtcbicsY3Vyc29yPTAsbWF0Y2g7dmFyIGFkZD1mdW5jdGlvbihsaW5lLGpzKXtqcz8oY29kZSs9bGluZS5tYXRjaChyZUV4cCk/bGluZSsnXG4nOidyLnB1c2goJytsaW5lKycpO1xuJyk6KGNvZGUrPWxpbmUhPScnPydyLnB1c2goIicrbGluZS5yZXBsYWNlKC8iL2csJ1xcIicpKyciKTtcbic6JycpO3JldHVybiBhZGR9CiAgICAgICAgd2hpbGUobWF0Y2g9cmUuZXhlYyhodG1sKSl7YWRkKGh0bWwuc2xpY2UoY3Vyc29yLG1hdGNoLmluZGV4KSkobWF0Y2hbMV0sITApO2N1cnNvcj1tYXRjaC5pbmRleCttYXRjaFswXS5sZW5ndGh9CiAgICAgICAgYWRkKGh0bWwuc3Vic3RyKGN1cnNvcixodG1sLmxlbmd0aC1jdXJzb3IpKTtjb2RlKz0ncmV0dXJuIHIuam9pbigiIik7JztyZXR1cm4gbmV3IEZ1bmN0aW9uKGNvZGUucmVwbGFjZSgvW1xyXHRcbl0vZywnJykpLmFwcGx5KG9wdGlvbnMpCiAgICB9CiAgICBmdW5jdGlvbiByZW5hbWUoZSwgdCkgeyBpZihlICYmIHQpIHsgJCgiI2pzLXJlbmFtZS1mcm9tIikudmFsKHQpOyQoIiNqcy1yZW5hbWUtdG8iKS52YWwodCk7ICQoIiNyZW5hbWVEYWlsb2ciKS5tb2RhbCgnc2hvdycpOyB9IH0KICAgIGZ1bmN0aW9uIGNoYW5nZV9jaGVja2JveGVzKGUsIHQpIHsgZm9yICh2YXIgbiA9IGUubGVuZ3RoIC0gMTsgbiA+PSAwOyBuLS0pIGVbbl0uY2hlY2tlZCA9ICJib29sZWFuIiA9PSB0eXBlb2YgdCA/IHQgOiAhZVtuXS5jaGVja2VkIH0KICAgIGZ1bmN0aW9uIGdldF9jaGVja2JveGVzKCkgeyBmb3IgKHZhciBlID0gZG9jdW1lbnQuZ2V0RWxlbWVudHNCeU5hbWUoImZpbGVbXSIpLCB0ID0gW10sIG4gPSBlLmxlbmd0aCAtIDE7IG4gPj0gMDsgbi0tKSAoZVtuXS50eXBlID0gImNoZWNrYm94IikgJiYgdC5wdXNoKGVbbl0pOyByZXR1cm4gdCB9CiAgICBmdW5jdGlvbiBzZWxlY3RfYWxsKCkgeyBjaGFuZ2VfY2hlY2tib3hlcyhnZXRfY2hlY2tib3hlcygpLCAhMCkgfQogICAgZnVuY3Rpb24gdW5zZWxlY3RfYWxsKCkgeyBjaGFuZ2VfY2hlY2tib3hlcyhnZXRfY2hlY2tib3hlcygpLCAhMSkgfQogICAgZnVuY3Rpb24gaW52ZXJ0X2FsbCgpIHsgY2hhbmdlX2NoZWNrYm94ZXMoZ2V0X2NoZWNrYm94ZXMoKSkgfQogICAgZnVuY3Rpb24gY2hlY2tib3hfdG9nZ2xlKCkgeyB2YXIgZSA9IGdldF9jaGVja2JveGVzKCk7IGUucHVzaCh0aGlzKSwgY2hhbmdlX2NoZWNrYm94ZXMoZSkgfQogICAgZnVuY3Rpb24gYmFja3VwKGUsIHQpIHsgLy8gQ3JlYXRlIGZpbGUgYmFja3VwIHdpdGggLmJjawogICAgICAgIHZhciBuID0gbmV3IFhNTEh0dHBSZXF1ZXN0LAogICAgICAgICAgICBhID0gInBhdGg9IiArIGUgKyAiJmZpbGU9IiArIHQgKyAiJnRva2VuPSIrIHdpbmRvdy5jc3JmICsiJnR5cGU9YmFja3VwJmFqYXg9dHJ1ZSI7CiAgICAgICAgcmV0dXJuIG4ub3BlbigiUE9TVCIsICIiLCAhMCksIG4uc2V0UmVxdWVzdEhlYWRlcigiQ29udGVudC10eXBlIiwgImFwcGxpY2F0aW9uL3gtd3d3LWZvcm0tdXJsZW5jb2RlZCIpLCBuLm9ucmVhZHlzdGF0ZWNoYW5nZSA9IGZ1bmN0aW9uICgpIHsKICAgICAgICAgICAgNCA9PSBuLnJlYWR5U3RhdGUgJiYgMjAwID09IG4uc3RhdHVzICYmIHRvYXN0KG4ucmVzcG9uc2VUZXh0KQogICAgICAgIH0sIG4uc2VuZChhKSwgITEKICAgIH0KICAgIC8vIFRvYXN0IG1lc3NhZ2UKICAgIGZ1bmN0aW9uIHRvYXN0KHR4dCkgeyB2YXIgeCA9IGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKCJzbmFja2JhciIpO3guaW5uZXJIVE1MPXR4dDt4LmNsYXNzTmFtZSA9ICJzaG93IjtzZXRUaW1lb3V0KGZ1bmN0aW9uKCl7IHguY2xhc3NOYW1lID0geC5jbGFzc05hbWUucmVwbGFjZSgic2hvdyIsICIiKTsgfSwgMzAwMCk7IH0KICAgIC8vIFNhdmUgZmlsZQogICAgZnVuY3Rpb24gZWRpdF9zYXZlKGUsIHQpIHsKICAgICAgICB2YXIgbiA9ICJhY2UiID09IHQgPyBlZGl0b3IuZ2V0U2Vzc2lvbigpLmdldFZhbHVlKCkgOiBkb2N1bWVudC5nZXRFbGVtZW50QnlJZCgibm9ybWFsLWVkaXRvciIpLnZhbHVlOwogICAgICAgIGlmICh0eXBlb2YgbiAhPT0gJ3VuZGVmaW5lZCcgJiYgbiAhPT0gbnVsbCkgewogICAgICAgICAgICBpZiAodHJ1ZSkgewogICAgICAgICAgICAgICAgdmFyIGRhdGEgPSB7YWpheDogdHJ1ZSwgY29udGVudDogbiwgdHlwZTogJ3NhdmUnLCB0b2tlbjogd2luZG93LmNzcmZ9OwoKICAgICAgICAgICAgICAgICQuYWpheCh7CiAgICAgICAgICAgICAgICAgICAgdHlwZTogIlBPU1QiLAogICAgICAgICAgICAgICAgICAgIHVybDogd2luZG93LmxvY2F0aW9uLAogICAgICAgICAgICAgICAgICAgIGRhdGE6IEpTT04uc3RyaW5naWZ5KGRhdGEpLAogICAgICAgICAgICAgICAgICAgIGNvbnRlbnRUeXBlOiAiYXBwbGljYXRpb24vanNvbjsgY2hhcnNldD11dGYtOCIsCiAgICAgICAgICAgICAgICAgICAgc3VjY2VzczogZnVuY3Rpb24obWVzKXt0b2FzdCgiU2F2ZWQgU3VjY2Vzc2Z1bGx5Iik7IHdpbmRvdy5vbmJlZm9yZXVubG9hZCA9IGZ1bmN0aW9uKCkge3JldHVybn19LAogICAgICAgICAgICAgICAgICAgIGZhaWx1cmU6IGZ1bmN0aW9uKG1lcykge3RvYXN0KCJFcnJvcjogdHJ5IGFnYWluIik7fSwKICAgICAgICAgICAgICAgICAgICBlcnJvcjogZnVuY3Rpb24obWVzKSB7dG9hc3QoYDxwIHN0eWxlPSJiYWNrZ3JvdW5kLWNvbG9yOnJlZCI+JHttZXMucmVzcG9uc2VUZXh0fTwvcD5gKTt9CiAgICAgICAgICAgICAgICB9KTsKICAgICAgICAgICAgfSBlbHNlIHsKICAgICAgICAgICAgICAgIHZhciBhID0gZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgiZm9ybSIpOwogICAgICAgICAgICAgICAgYS5zZXRBdHRyaWJ1dGUoIm1ldGhvZCIsICJQT1NUIiksIGEuc2V0QXR0cmlidXRlKCJhY3Rpb24iLCAiIik7CiAgICAgICAgICAgICAgICB2YXIgbyA9IGRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoInRleHRhcmVhIik7CiAgICAgICAgICAgICAgICBvLnNldEF0dHJpYnV0ZSgidHlwZSIsICJ0ZXh0YXJlYSIpLCBvLnNldEF0dHJpYnV0ZSgibmFtZSIsICJzYXZlZGF0YSIpOwogICAgICAgICAgICAgICAgbGV0IGN4ID0gZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgiaW5wdXQiKTsgY3guc2V0QXR0cmlidXRlKCJ0eXBlIiwgImhpZGRlbiIpO2N4LnNldEF0dHJpYnV0ZSgibmFtZSIsICJ0b2tlbiIpO2N4LnNldEF0dHJpYnV0ZSgidmFsdWUiLCB3aW5kb3cuY3NyZik7CiAgICAgICAgICAgICAgICB2YXIgYyA9IGRvY3VtZW50LmNyZWF0ZVRleHROb2RlKG4pOwogICAgICAgICAgICAgICAgby5hcHBlbmRDaGlsZChjKSwgYS5hcHBlbmRDaGlsZChvKSwgYS5hcHBlbmRDaGlsZChjeCksIGRvY3VtZW50LmJvZHkuYXBwZW5kQ2hpbGQoYSksIGEuc3VibWl0KCkKICAgICAgICAgICAgfQogICAgICAgIH0KICAgIH0KICAgIGZ1bmN0aW9uIHNob3dfbmV3X3B3ZCgpIHsgJCgiLmpzLW5ldy1wd2QiKS50b2dnbGVDbGFzcygnaGlkZGVuJyk7IH0KICAgIC8vIFNhdmUgU2V0dGluZ3MKICAgIGZ1bmN0aW9uIHNhdmVfc2V0dGluZ3MoJHRoaXMpIHsKICAgICAgICBsZXQgZm9ybSA9ICQoJHRoaXMpOwogICAgICAgICQuYWpheCh7CiAgICAgICAgICAgIHR5cGU6IGZvcm0uYXR0cignbWV0aG9kJyksIHVybDogZm9ybS5hdHRyKCdhY3Rpb24nKSwgZGF0YTogZm9ybS5zZXJpYWxpemUoKSsiJnRva2VuPSIrIHdpbmRvdy5jc3JmICsiJmFqYXg9Iit0cnVlLAogICAgICAgICAgICBzdWNjZXNzOiBmdW5jdGlvbiAoZGF0YSkge2lmKGRhdGEpIHsgd2luZG93LmxvY2F0aW9uLnJlbG9hZCgpO319CiAgICAgICAgfSk7IHJldHVybiBmYWxzZTsKICAgIH0KICAgIC8vQ3JlYXRlIG5ldyBwYXNzd29yZCBoYXNoCiAgICBmdW5jdGlvbiBuZXdfcGFzc3dvcmRfaGFzaCgkdGhpcykgewogICAgICAgIGxldCBmb3JtID0gJCgkdGhpcyksICRwd2QgPSAkKCIjanMtcHdkLXJlc3VsdCIpOyAkcHdkLnZhbCgnJyk7CiAgICAgICAgJC5hamF4KHsKICAgICAgICAgICAgdHlwZTogZm9ybS5hdHRyKCdtZXRob2QnKSwgdXJsOiBmb3JtLmF0dHIoJ2FjdGlvbicpLCBkYXRhOiBmb3JtLnNlcmlhbGl6ZSgpKyImdG9rZW49Iisgd2luZG93LmNzcmYgKyImYWpheD0iK3RydWUsCiAgICAgICAgICAgIHN1Y2Nlc3M6IGZ1bmN0aW9uIChkYXRhKSB7IGlmKGRhdGEpIHsgJHB3ZC52YWwoZGF0YSk7IH0gfQogICAgICAgIH0pOyByZXR1cm4gZmFsc2U7CiAgICB9CiAgICAvLyBVcGxvYWQgZmlsZXMgdXNpbmcgVVJMIEBwYXJhbSB7T2JqZWN0fQogICAgZnVuY3Rpb24gdXBsb2FkX2Zyb21fdXJsKCR0aGlzKSB7CiAgICAgICAgbGV0IGZvcm0gPSAkKCR0aGlzKSwgcmVzdWx0V3JhcHBlciA9ICQoImRpdiNqcy11cmwtdXBsb2FkX19saXN0Iik7CiAgICAgICAgJC5hamF4KHsKICAgICAgICAgICAgdHlwZTogZm9ybS5hdHRyKCdtZXRob2QnKSwgdXJsOiBmb3JtLmF0dHIoJ2FjdGlvbicpLCBkYXRhOiBmb3JtLnNlcmlhbGl6ZSgpKyImdG9rZW49Iisgd2luZG93LmNzcmYgKyImYWpheD0iK3RydWUsCiAgICAgICAgICAgIGJlZm9yZVNlbmQ6IGZ1bmN0aW9uKCkgeyBmb3JtLmZpbmQoImlucHV0W25hbWU9dXBsb2FkdXJsXSIpLmF0dHIoImRpc2FibGVkIiwiZGlzYWJsZWQiKTsgZm9ybS5maW5kKCJidXR0b24iKS5oaWRlKCk7IGZvcm0uZmluZCgiLmxkcy1mYWNlYm9vayIpLmFkZENsYXNzKCdzaG93LW1lJyk7IH0sCiAgICAgICAgICAgIHN1Y2Nlc3M6IGZ1bmN0aW9uIChkYXRhKSB7CiAgICAgICAgICAgICAgICBpZihkYXRhKSB7CiAgICAgICAgICAgICAgICAgICAgZGF0YSA9IEpTT04ucGFyc2UoZGF0YSk7CiAgICAgICAgICAgICAgICAgICAgaWYoZGF0YS5kb25lKSB7CiAgICAgICAgICAgICAgICAgICAgICAgIHJlc3VsdFdyYXBwZXIuYXBwZW5kKCc8ZGl2IGNsYXNzPSJhbGVydCBhbGVydC1zdWNjZXNzIHJvdyI+VXBsb2FkZWQgU3VjY2Vzc2Z1bDogJytkYXRhLmRvbmUubmFtZSsnPC9kaXY+Jyk7IGZvcm0uZmluZCgiaW5wdXRbbmFtZT11cGxvYWR1cmxdIikudmFsKCcnKTsKICAgICAgICAgICAgICAgICAgICB9IGVsc2UgaWYoZGF0YVsnZmFpbCddKSB7IHJlc3VsdFdyYXBwZXIuYXBwZW5kKCc8ZGl2IGNsYXNzPSJhbGVydCBhbGVydC1kYW5nZXIgcm93Ij5FcnJvcjogJytkYXRhLmZhaWwubWVzc2FnZSsnPC9kaXY+Jyk7IH0KICAgICAgICAgICAgICAgICAgICBmb3JtLmZpbmQoImlucHV0W25hbWU9dXBsb2FkdXJsXSIpLnJlbW92ZUF0dHIoImRpc2FibGVkIik7Zm9ybS5maW5kKCJidXR0b24iKS5zaG93KCk7Zm9ybS5maW5kKCIubGRzLWZhY2Vib29rIikucmVtb3ZlQ2xhc3MoJ3Nob3ctbWUnKTsKICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgfSwKICAgICAgICAgICAgZXJyb3I6IGZ1bmN0aW9uKHhocikgewogICAgICAgICAgICAgICAgZm9ybS5maW5kKCJpbnB1dFtuYW1lPXVwbG9hZHVybF0iKS5yZW1vdmVBdHRyKCJkaXNhYmxlZCIpO2Zvcm0uZmluZCgiYnV0dG9uIikuc2hvdygpO2Zvcm0uZmluZCgiLmxkcy1mYWNlYm9vayIpLnJlbW92ZUNsYXNzKCdzaG93LW1lJyk7Y29uc29sZS5lcnJvcih4aHIpOwogICAgICAgICAgICB9CiAgICAgICAgfSk7IHJldHVybiBmYWxzZTsKICAgIH0KICAgIC8vIFNlYXJjaCB0ZW1wbGF0ZQogICAgZnVuY3Rpb24gc2VhcmNoX3RlbXBsYXRlKGRhdGEpIHsKICAgICAgICB2YXIgcmVzcG9uc2UgPSAiIjsKICAgICAgICAkLmVhY2goZGF0YSwgZnVuY3Rpb24gKGtleSwgdmFsKSB7CiAgICAgICAgICAgIHJlc3BvbnNlICs9IGA8bGk+PGEgaHJlZj0iP3A9JHt2YWwucGF0aH0mdmlldz0ke3ZhbC5uYW1lfSI+JHt2YWwucGF0aH0vJHt2YWwubmFtZX08L2E+PC9saT5gOwogICAgICAgIH0pOwogICAgICAgIHJldHVybiByZXNwb25zZTsKICAgIH0KICAgIC8vIEFkdmFuY2Ugc2VhcmNoCiAgICBmdW5jdGlvbiBmbV9zZWFyY2goKSB7CiAgICAgICAgdmFyIHNlYXJjaFR4dCA9ICQoImlucHV0I2FkdmFuY2VkLXNlYXJjaCIpLnZhbCgpLCBzZWFyY2hXcmFwcGVyID0gJCgidWwjc2VhcmNoLXdyYXBwZXIiKSwgcGF0aCA9ICQoIiNqcy1zZWFyY2gtbW9kYWwiKS5hdHRyKCJocmVmIiksIF9odG1sID0gIiIsICRsb2FkZXIgPSAkKCJkaXYubGRzLWZhY2Vib29rIik7CiAgICAgICAgaWYoISFzZWFyY2hUeHQgJiYgc2VhcmNoVHh0Lmxlbmd0aCA+IDIgJiYgcGF0aCkgewogICAgICAgICAgICB2YXIgZGF0YSA9IHthamF4OiB0cnVlLCBjb250ZW50OiBzZWFyY2hUeHQsIHBhdGg6cGF0aCwgdHlwZTogJ3NlYXJjaCcsIHRva2VuOiB3aW5kb3cuY3NyZiB9OwogICAgICAgICAgICAkLmFqYXgoewogICAgICAgICAgICAgICAgdHlwZTogIlBPU1QiLAogICAgICAgICAgICAgICAgdXJsOiB3aW5kb3cubG9jYXRpb24sCiAgICAgICAgICAgICAgICBkYXRhOiBkYXRhLAogICAgICAgICAgICAgICAgYmVmb3JlU2VuZDogZnVuY3Rpb24oKSB7CiAgICAgICAgICAgICAgICAgICAgc2VhcmNoV3JhcHBlci5odG1sKCcnKTsKICAgICAgICAgICAgICAgICAgICAkbG9hZGVyLmFkZENsYXNzKCdzaG93LW1lJyk7CiAgICAgICAgICAgICAgICB9LAogICAgICAgICAgICAgICAgc3VjY2VzczogZnVuY3Rpb24oZGF0YSl7CiAgICAgICAgICAgICAgICAgICAgJGxvYWRlci5yZW1vdmVDbGFzcygnc2hvdy1tZScpOwogICAgICAgICAgICAgICAgICAgIGRhdGEgPSBKU09OLnBhcnNlKGRhdGEpOwogICAgICAgICAgICAgICAgICAgIGlmKGRhdGEgJiYgZGF0YS5sZW5ndGgpIHsKICAgICAgICAgICAgICAgICAgICAgICAgX2h0bWwgPSBzZWFyY2hfdGVtcGxhdGUoZGF0YSk7CiAgICAgICAgICAgICAgICAgICAgICAgIHNlYXJjaFdyYXBwZXIuaHRtbChfaHRtbCk7CiAgICAgICAgICAgICAgICAgICAgfSBlbHNlIHsgc2VhcmNoV3JhcHBlci5odG1sKCc8cCBjbGFzcz0ibS0yIj5ObyByZXN1bHQgZm91bmQhPHA+Jyk7IH0KICAgICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgICBlcnJvcjogZnVuY3Rpb24oeGhyKSB7ICRsb2FkZXIucmVtb3ZlQ2xhc3MoJ3Nob3ctbWUnKTsgc2VhcmNoV3JhcHBlci5odG1sKCc8cCBjbGFzcz0ibS0yIj5FUlJPUjogVHJ5IGFnYWluIGxhdGVyITwvcD4nKTsgfSwKICAgICAgICAgICAgICAgIGZhaWx1cmU6IGZ1bmN0aW9uKG1lcykgeyAkbG9hZGVyLnJlbW92ZUNsYXNzKCdzaG93LW1lJyk7IHNlYXJjaFdyYXBwZXIuaHRtbCgnPHAgY2xhc3M9Im0tMiI+RVJST1I6IFRyeSBhZ2FpbiBsYXRlciE8L3A+Jyk7fQogICAgICAgICAgICB9KTsKICAgICAgICB9IGVsc2UgeyBzZWFyY2hXcmFwcGVyLmh0bWwoIk9PUFM6IG1pbmltdW0gMyBjaGFyYWN0ZXJzIHJlcXVpcmVkISIpOyB9CiAgICB9CgogICAgLy8gYWN0aW9uIGNvbmZpcm0gZGFpbG9nIG1vZGFsCiAgICBmdW5jdGlvbiBjb25maXJtRGFpbG9nKGUsIGlkID0gMCwgdGl0bGUgPSAiQWN0aW9uIiwgY29udGVudCA9ICIiLCBhY3Rpb24gPSBudWxsKSB7CiAgICAgICAgZS5wcmV2ZW50RGVmYXVsdCgpOwogICAgICAgIGNvbnN0IHRwbE9iaiA9IHtpZCwgdGl0bGUsIGNvbnRlbnQsIGFjdGlvbn07CiAgICAgICAgbGV0IHRwbCA9ICQoIiNqcy10cGwtY29uZmlybSIpLmh0bWwoKTsKICAgICAgICAkKCcjd3JhcHBlcicpLmFwcGVuZCh0ZW1wbGF0ZSh0cGwsdHBsT2JqKSk7CiAgICAgICAgJCgiI2NvbmZpcm1EYWlsb2ctIit0cGxPYmouaWQpLm1vZGFsKCdzaG93Jyk7CiAgICAgICAgcmV0dXJuIGZhbHNlOwogICAgfQogICAgCgogICAgLy8gb24gbW91c2UgaG92ZXIgaW1hZ2UgcHJldmlldwogICAgIWZ1bmN0aW9uKHMpe3MucHJldmlld0ltYWdlPWZ1bmN0aW9uKGUpe3ZhciBvPXMoZG9jdW1lbnQpLHQ9Ii5wcmV2aWV3SW1hZ2UiLGE9cy5leHRlbmQoe3hPZmZzZXQ6MjAseU9mZnNldDotMjAsZmFkZUluOiJmYXN0Iixjc3M6e3BhZGRpbmc6IjVweCIsYm9yZGVyOiIxcHggc29saWQgI2NjY2NjYyIsImJhY2tncm91bmQtY29sb3IiOiIjZmZmIn0sZXZlbnRTZWxlY3RvcjoiW2RhdGEtcHJldmlldy1pbWFnZV0iLGRhdGFLZXk6InByZXZpZXdJbWFnZSIsb3ZlcmxheUlkOiJwcmV2aWV3LWltYWdlLXBsdWdpbi1vdmVybGF5In0sZSk7cmV0dXJuIG8ub2ZmKHQpLG8ub24oIm1vdXNlb3ZlciIrdCxhLmV2ZW50U2VsZWN0b3IsZnVuY3Rpb24oZSl7cygicCMiK2Eub3ZlcmxheUlkKS5yZW1vdmUoKTt2YXIgbz1zKCI8cD4iKS5hdHRyKCJpZCIsYS5vdmVybGF5SWQpLmNzcygicG9zaXRpb24iLCJhYnNvbHV0ZSIpLmNzcygiZGlzcGxheSIsIm5vbmUiKS5hcHBlbmQocygnPGltZyBjbGFzcz0iYy1wcmV2aWV3LWltZyI+JykuYXR0cigic3JjIixzKHRoaXMpLmRhdGEoYS5kYXRhS2V5KSkpO2EuY3NzJiZvLmNzcyhhLmNzcykscygiYm9keSIpLmFwcGVuZChvKSxvLmNzcygidG9wIixlLnBhZ2VZK2EueU9mZnNldCsicHgiKS5jc3MoImxlZnQiLGUucGFnZVgrYS54T2Zmc2V0KyJweCIpLmZhZGVJbihhLmZhZGVJbil9KSxvLm9uKCJtb3VzZW91dCIrdCxhLmV2ZW50U2VsZWN0b3IsZnVuY3Rpb24oKXtzKCIjIithLm92ZXJsYXlJZCkucmVtb3ZlKCl9KSxvLm9uKCJtb3VzZW1vdmUiK3QsYS5ldmVudFNlbGVjdG9yLGZ1bmN0aW9uKGUpe3MoIiMiK2Eub3ZlcmxheUlkKS5jc3MoInRvcCIsZS5wYWdlWSthLnlPZmZzZXQrInB4IikuY3NzKCJsZWZ0IixlLnBhZ2VYK2EueE9mZnNldCsicHgiKX0pLHRoaXN9LHMucHJldmlld0ltYWdlKCl9KGpRdWVyeSk7CgogICAgLy8gRG9tIFJlYWR5IEV2ZW50cwogICAgJChkb2N1bWVudCkucmVhZHkoIGZ1bmN0aW9uICgpIHsKICAgICAgICAvLyBkYXRhVGFibGUgaW5pdAogICAgICAgIHZhciAkdGFibGUgPSAkKCcjbWFpbi10YWJsZScpLAogICAgICAgICAgICB0YWJsZUxuZyA9ICR0YWJsZS5maW5kKCd0aCcpLmxlbmd0aCwKICAgICAgICAgICAgX3RhcmdldHMgPSAodGFibGVMbmcgJiYgdGFibGVMbmcgPT0gNyApID8gWzAsIDQsNSw2XSA6IHRhYmxlTG5nID09IDUgPyBbMCw0XSA6IFszXTsKICAgICAgICAgICAgbWFpblRhYmxlID0gJCgnI21haW4tdGFibGUnKS5EYXRhVGFibGUoe3BhZ2luZzogZmFsc2UsIGluZm86IGZhbHNlLCBvcmRlcjogW10sIGNvbHVtbkRlZnM6IFt7dGFyZ2V0czogX3RhcmdldHMsIG9yZGVyYWJsZTogZmFsc2V9XQogICAgICAgIH0pOwogICAgICAgIC8vIGZpbHRlciB0YWJsZQogICAgICAgICQoJyNzZWFyY2gtYWRkb24nKS5vbiggJ2tleXVwJywgZnVuY3Rpb24gKCkgewogICAgICAgICAgICBtYWluVGFibGUuc2VhcmNoKCB0aGlzLnZhbHVlICkuZHJhdygpOwogICAgICAgIH0pOwogICAgICAgICQoImlucHV0I2FkdmFuY2VkLXNlYXJjaCIpLm9uKCdrZXl1cCcsIGZ1bmN0aW9uIChlKSB7CiAgICAgICAgICAgIGlmIChlLmtleUNvZGUgPT09IDEzKSB7IGZtX3NlYXJjaCgpOyB9CiAgICAgICAgfSk7CiAgICAgICAgJCgnI3NlYXJjaC1hZGRvbjMnKS5vbiggJ2NsaWNrJywgZnVuY3Rpb24gKCkgeyBmbV9zZWFyY2goKTsgfSk7CiAgICAgICAgLy91cGxvYWQgbmF2IHRhYnMKICAgICAgICAkKCIuZm0tdXBsb2FkLXdyYXBwZXIgLmNhcmQtaGVhZGVyLXRhYnMiKS5vbigiY2xpY2siLCAnYScsIGZ1bmN0aW9uKGUpewogICAgICAgICAgICBlLnByZXZlbnREZWZhdWx0KCk7bGV0IHRhcmdldD0kKHRoaXMpLmRhdGEoJ3RhcmdldCcpOwogICAgICAgICAgICAkKCIuZm0tdXBsb2FkLXdyYXBwZXIgLmNhcmQtaGVhZGVyLXRhYnMgYSIpLnJlbW92ZUNsYXNzKCdhY3RpdmUnKTskKHRoaXMpLmFkZENsYXNzKCdhY3RpdmUnKTsKICAgICAgICAgICAgJCgiLmZtLXVwbG9hZC13cmFwcGVyIC5jYXJkLXRhYnMtY29udGFpbmVyIikuYWRkQ2xhc3MoJ2hpZGRlbicpOyQodGFyZ2V0KS5yZW1vdmVDbGFzcygnaGlkZGVuJyk7CiAgICAgICAgfSk7CiAgICB9KTsKPC9zY3JpcHQ+Cjw/cGhwIGlmIChpc3NldCgkX0dFVFsnZWRpdCddKSAmJiBpc3NldCgkX0dFVFsnZW52J10pICYmIEZNX0VESVRfRklMRSAmJiAhRk1fUkVBRE9OTFkpOgogICAgICAgIAogICAgICAgICRleHQgPSBwYXRoaW5mbygkX0dFVFsiZWRpdCJdLCBQQVRISU5GT19FWFRFTlNJT04pOwogICAgICAgICRleHQgPSAgJGV4dCA9PSAianMiID8gImphdmFzY3JpcHQiIDogICRleHQ7CiAgICAgICAgPz4KICAgIDxzY3JpcHQgc3JjPSJodHRwczovL2NkbmpzLmNsb3VkZmxhcmUuY29tL2FqYXgvbGlicy9hY2UvMS4xMy4xL2FjZS5qcyI+PC9zY3JpcHQ+CiAgICA8c2NyaXB0PgogICAgICAgIHZhciBlZGl0b3IgPSBhY2UuZWRpdCgiZWRpdG9yIik7CiAgICAgICAgZWRpdG9yLmdldFNlc3Npb24oKS5zZXRNb2RlKCB7cGF0aDoiYWNlL21vZGUvPD9waHAgZWNobyAkZXh0OyA/PiIsIGlubGluZTp0cnVlfSApOwogICAgICAgIC8vZWRpdG9yLnNldFRoZW1lKCJhY2UvdGhlbWUvdHdpbGlnaHQiKTsgLy9EYXJrIFRoZW1lCiAgICAgICAgZnVuY3Rpb24gYWNlX2NvbW1lbmQgKGNtZCkgeyBlZGl0b3IuY29tbWFuZHMuZXhlYyhjbWQsIGVkaXRvcik7IH0KICAgICAgICBlZGl0b3IuY29tbWFuZHMuYWRkQ29tbWFuZHMoW3sKICAgICAgICAgICAgbmFtZTogJ3NhdmUnLCBiaW5kS2V5OiB7d2luOiAnQ3RybC1TJywgIG1hYzogJ0NvbW1hbmQtUyd9LAogICAgICAgICAgICBleGVjOiBmdW5jdGlvbihlZGl0b3IpIHsgZWRpdF9zYXZlKHRoaXMsICdhY2UnKTsgfQogICAgICAgIH1dKTsKICAgICAgICBmdW5jdGlvbiByZW5kZXJUaGVtZU1vZGUoKSB7CiAgICAgICAgICAgIHZhciAkbW9kZUVsID0gJCgic2VsZWN0I2pzLWFjZS1tb2RlIiksICR0aGVtZUVsID0gJCgic2VsZWN0I2pzLWFjZS10aGVtZSIpLCAkZm9udFNpemVFbCA9ICQoInNlbGVjdCNqcy1hY2UtZm9udFNpemUiKSwgb3B0aW9uTm9kZSA9IGZ1bmN0aW9uKHR5cGUsIGFycil7IHZhciAkT3B0aW9uID0gIiI7ICQuZWFjaChhcnIsIGZ1bmN0aW9uKGksIHZhbCkgeyAkT3B0aW9uICs9ICI8b3B0aW9uIHZhbHVlPSciK3R5cGUraSsiJz4iICsgdmFsICsgIjwvb3B0aW9uPiI7IH0pOyByZXR1cm4gJE9wdGlvbjsgfSwKICAgICAgICAgICAgICAgIF9kYXRhID0geyJhY2VUaGVtZSI6eyJicmlnaHQiOnsiY2hyb21lIjoiQ2hyb21lIiwiY2xvdWRzIjoiQ2xvdWRzIiwiY3JpbXNvbl9lZGl0b3IiOiJDcmltc29uIEVkaXRvciIsImRhd24iOiJEYXduIiwiZHJlYW13ZWF2ZXIiOiJEcmVhbXdlYXZlciIsImVjbGlwc2UiOiJFY2xpcHNlIiwiZ2l0aHViIjoiR2l0SHViIiwiaXBsYXN0aWMiOiJJUGxhc3RpYyIsInNvbGFyaXplZF9saWdodCI6IlNvbGFyaXplZCBMaWdodCIsInRleHRtYXRlIjoiVGV4dE1hdGUiLCJ0b21vcnJvdyI6IlRvbW9ycm93IiwieGNvZGUiOiJYQ29kZSIsImt1cm9pciI6Ikt1cm9pciIsImthdHplbm1pbGNoIjoiS2F0emVuTWlsY2giLCJzcWxzZXJ2ZXIiOiJTUUwgU2VydmVyIn0sImRhcmsiOnsiYW1iaWFuY2UiOiJBbWJpYW5jZSIsImNoYW9zIjoiQ2hhb3MiLCJjbG91ZHNfbWlkbmlnaHQiOiJDbG91ZHMgTWlkbmlnaHQiLCJkcmFjdWxhIjoiRHJhY3VsYSIsImNvYmFsdCI6IkNvYmFsdCIsImdydXZib3giOiJHcnV2Ym94IiwiZ29iIjoiR3JlZW4gb24gQmxhY2siLCJpZGxlX2ZpbmdlcnMiOiJpZGxlIEZpbmdlcnMiLCJrcl90aGVtZSI6ImtyVGhlbWUiLCJtZXJiaXZvcmUiOiJNZXJiaXZvcmUiLCJtZXJiaXZvcmVfc29mdCI6Ik1lcmJpdm9yZSBTb2Z0IiwibW9ub19pbmR1c3RyaWFsIjoiTW9ubyBJbmR1c3RyaWFsIiwibW9ub2thaSI6Ik1vbm9rYWkiLCJwYXN0ZWxfb25fZGFyayI6IlBhc3RlbCBvbiBkYXJrIiwic29sYXJpemVkX2RhcmsiOiJTb2xhcml6ZWQgRGFyayIsInRlcm1pbmFsIjoiVGVybWluYWwiLCJ0b21vcnJvd19uaWdodCI6IlRvbW9ycm93IE5pZ2h0IiwidG9tb3Jyb3dfbmlnaHRfYmx1ZSI6IlRvbW9ycm93IE5pZ2h0IEJsdWUiLCJ0b21vcnJvd19uaWdodF9icmlnaHQiOiJUb21vcnJvdyBOaWdodCBCcmlnaHQiLCJ0b21vcnJvd19uaWdodF9laWdodGllcyI6IlRvbW9ycm93IE5pZ2h0IDgwcyIsInR3aWxpZ2h0IjoiVHdpbGlnaHQiLCJ2aWJyYW50X2luayI6IlZpYnJhbnQgSW5rIn19LCJhY2VNb2RlIjp7ImphdmFzY3JpcHQiOiJKYXZhU2NyaXB0IiwiYWJhcCI6IkFCQVAiLCJhYmMiOiJBQkMiLCJhY3Rpb25zY3JpcHQiOiJBY3Rpb25TY3JpcHQiLCJhZGEiOiJBREEiLCJhcGFjaGVfY29uZiI6IkFwYWNoZSBDb25mIiwiYXNjaWlkb2MiOiJBc2NpaURvYyIsImFzbCI6IkFTTCIsImFzc2VtYmx5X3g4NiI6IkFzc2VtYmx5IHg4NiIsImF1dG9ob3RrZXkiOiJBdXRvSG90S2V5IiwiYXBleCI6IkFwZXgiLCJiYXRjaGZpbGUiOiJCYXRjaEZpbGUiLCJicm8iOiJCcm8iLCJjX2NwcCI6IkMgYW5kIEMrKyIsImM5c2VhcmNoIjoiQzlTZWFyY2giLCJjaXJydSI6IkNpcnJ1IiwiY2xvanVyZSI6IkNsb2p1cmUiLCJjb2JvbCI6IkNvYm9sIiwiY29mZmVlIjoiQ29mZmVlU2NyaXB0IiwiY29sZGZ1c2lvbiI6IkNvbGRGdXNpb24iLCJjc2hhcnAiOiJDIyIsImNzb3VuZF9kb2N1bWVudCI6IkNzb3VuZCBEb2N1bWVudCIsImNzb3VuZF9vcmNoZXN0cmEiOiJDc291bmQiLCJjc291bmRfc2NvcmUiOiJDc291bmQgU2NvcmUiLCJjc3MiOiJDU1MiLCJjdXJseSI6IkN1cmx5IiwiZCI6IkQiLCJkYXJ0IjoiRGFydCIsImRpZmYiOiJEaWZmIiwiZG9ja2VyZmlsZSI6IkRvY2tlcmZpbGUiLCJkb3QiOiJEb3QiLCJkcm9vbHMiOiJEcm9vbHMiLCJlZGlmYWN0IjoiRWRpZmFjdCIsImVpZmZlbCI6IkVpZmZlbCIsImVqcyI6IkVKUyIsImVsaXhpciI6IkVsaXhpciIsImVsbSI6IkVsbSIsImVybGFuZyI6IkVybGFuZyIsImZvcnRoIjoiRm9ydGgiLCJmb3J0cmFuIjoiRm9ydHJhbiIsImZzaGFycCI6IkZTaGFycCIsImZzbCI6IkZTTCIsImZ0bCI6IkZyZWVNYXJrZXIiLCJnY29kZSI6Ikdjb2RlIiwiZ2hlcmtpbiI6IkdoZXJraW4iLCJnaXRpZ25vcmUiOiJHaXRpZ25vcmUiLCJnbHNsIjoiR2xzbCIsImdvYnN0b25lcyI6IkdvYnN0b25lcyIsImdvbGFuZyI6IkdvIiwiZ3JhcGhxbHNjaGVtYSI6IkdyYXBoUUxTY2hlbWEiLCJncm9vdnkiOiJHcm9vdnkiLCJoYW1sIjoiSEFNTCIsImhhbmRsZWJhcnMiOiJIYW5kbGViYXJzIiwiaGFza2VsbCI6Ikhhc2tlbGwiLCJoYXNrZWxsX2NhYmFsIjoiSGFza2VsbCBDYWJhbCIsImhheGUiOiJoYVhlIiwiaGpzb24iOiJIanNvbiIsImh0bWwiOiJIVE1MIiwiaHRtbF9lbGl4aXIiOiJIVE1MIChFbGl4aXIpIiwiaHRtbF9ydWJ5IjoiSFRNTCAoUnVieSkiLCJpbmkiOiJJTkkiLCJpbyI6IklvIiwiamFjayI6IkphY2siLCJqYWRlIjoiSmFkZSIsImphdmEiOiJKYXZhIiwianNvbiI6IkpTT04iLCJqc29uaXEiOiJKU09OaXEiLCJqc3AiOiJKU1AiLCJqc3NtIjoiSlNTTSIsImpzeCI6IkpTWCIsImp1bGlhIjoiSnVsaWEiLCJrb3RsaW4iOiJLb3RsaW4iLCJsYXRleCI6IkxhVGVYIiwibGVzcyI6IkxFU1MiLCJsaXF1aWQiOiJMaXF1aWQiLCJsaXNwIjoiTGlzcCIsImxpdmVzY3JpcHQiOiJMaXZlU2NyaXB0IiwibG9naXFsIjoiTG9naVFMIiwibHNsIjoiTFNMIiwibHVhIjoiTHVhIiwibHVhcGFnZSI6Ikx1YVBhZ2UiLCJsdWNlbmUiOiJMdWNlbmUiLCJtYWtlZmlsZSI6Ik1ha2VmaWxlIiwibWFya2Rvd24iOiJNYXJrZG93biIsIm1hc2siOiJNYXNrIiwibWF0bGFiIjoiTUFUTEFCIiwibWF6ZSI6Ik1hemUiLCJtZWwiOiJNRUwiLCJtaXhhbCI6Ik1JWEFMIiwibXVzaGNvZGUiOiJNVVNIQ29kZSIsIm15c3FsIjoiTXlTUUwiLCJuaXgiOiJOaXgiLCJuc2lzIjoiTlNJUyIsIm9iamVjdGl2ZWMiOiJPYmplY3RpdmUtQyIsIm9jYW1sIjoiT0NhbWwiLCJwYXNjYWwiOiJQYXNjYWwiLCJwZXJsIjoiUGVybCIsInBlcmw2IjoiUGVybCA2IiwicGdzcWwiOiJwZ1NRTCIsInBocF9sYXJhdmVsX2JsYWRlIjoiUEhQIChCbGFkZSBUZW1wbGF0ZSkiLCJwaHAiOiJQSFAiLCJwdXBwZXQiOiJQdXBwZXQiLCJwaWciOiJQaWciLCJwb3dlcnNoZWxsIjoiUG93ZXJzaGVsbCIsInByYWF0IjoiUHJhYXQiLCJwcm9sb2ciOiJQcm9sb2ciLCJwcm9wZXJ0aWVzIjoiUHJvcGVydGllcyIsInByb3RvYnVmIjoiUHJvdG9idWYiLCJweXRob24iOiJQeXRob24iLCJyIjoiUiIsInJhem9yIjoiUmF6b3IiLCJyZG9jIjoiUkRvYyIsInJlZCI6IlJlZCIsInJodG1sIjoiUkhUTUwiLCJyc3QiOiJSU1QiLCJydWJ5IjoiUnVieSIsInJ1c3QiOiJSdXN0Iiwic2FzcyI6IlNBU1MiLCJzY2FkIjoiU0NBRCIsInNjYWxhIjoiU2NhbGEiLCJzY2hlbWUiOiJTY2hlbWUiLCJzY3NzIjoiU0NTUyIsInNoIjoiU0giLCJzanMiOiJTSlMiLCJzbGltIjoiU2xpbSIsInNtYXJ0eSI6IlNtYXJ0eSIsInNuaXBwZXRzIjoic25pcHBldHMiLCJzb3lfdGVtcGxhdGUiOiJTb3kgVGVtcGxhdGUiLCJzcGFjZSI6IlNwYWNlIiwic3FsIjoiU1FMIiwic3Fsc2VydmVyIjoiU1FMU2VydmVyIiwic3R5bHVzIjoiU3R5bHVzIiwic3ZnIjoiU1ZHIiwic3dpZnQiOiJTd2lmdCIsInRjbCI6IlRjbCIsInRlcnJhZm9ybSI6IlRlcnJhZm9ybSIsInRleCI6IlRleCIsInRleHQiOiJUZXh0IiwidGV4dGlsZSI6IlRleHRpbGUiLCJ0b21sIjoiVG9tbCIsInRzeCI6IlRTWCIsInR3aWciOiJUd2lnIiwidHlwZXNjcmlwdCI6IlR5cGVzY3JpcHQiLCJ2YWxhIjoiVmFsYSIsInZic2NyaXB0IjoiVkJTY3JpcHQiLCJ2ZWxvY2l0eSI6IlZlbG9jaXR5IiwidmVyaWxvZyI6IlZlcmlsb2ciLCJ2aGRsIjoiVkhETCIsInZpc3VhbGZvcmNlIjoiVmlzdWFsZm9yY2UiLCJ3b2xsb2siOiJXb2xsb2siLCJ4bWwiOiJYTUwiLCJ4cXVlcnkiOiJYUXVlcnkiLCJ5YW1sIjoiWUFNTCIsImRqYW5nbyI6IkRqYW5nbyJ9LCJmb250U2l6ZSI6ezg6OCwxMDoxMCwxMToxMSwxMjoxMiwxMzoxMywxNDoxNCwxNToxNSwxNjoxNiwxNzoxNywxODoxOCwyMDoyMCwyMjoyMiwyNDoyNCwyNjoyNiwzMDozMH19OwogICAgICAgICAgICBpZihfZGF0YSAmJiBfZGF0YS5hY2VNb2RlKSB7ICRtb2RlRWwuaHRtbChvcHRpb25Ob2RlKCJhY2UvbW9kZS8iLCBfZGF0YS5hY2VNb2RlKSk7IH0KICAgICAgICAgICAgaWYoX2RhdGEgJiYgX2RhdGEuYWNlVGhlbWUpIHsgdmFyIGxpZ2h0VGhlbWUgPSBvcHRpb25Ob2RlKCJhY2UvdGhlbWUvIiwgX2RhdGEuYWNlVGhlbWUuYnJpZ2h0KSwgZGFya1RoZW1lID0gb3B0aW9uTm9kZSgiYWNlL3RoZW1lLyIsIF9kYXRhLmFjZVRoZW1lLmRhcmspOyAkdGhlbWVFbC5odG1sKCI8b3B0Z3JvdXAgbGFiZWw9XCJCcmlnaHRcIj4iK2xpZ2h0VGhlbWUrIjwvb3B0Z3JvdXA+PG9wdGdyb3VwIGxhYmVsPVwiRGFya1wiPiIrZGFya1RoZW1lKyI8L29wdGdyb3VwPiIpO30KICAgICAgICAgICAgaWYoX2RhdGEgJiYgX2RhdGEuZm9udFNpemUpIHsgJGZvbnRTaXplRWwuaHRtbChvcHRpb25Ob2RlKCIiLCBfZGF0YS5mb250U2l6ZSkpOyB9CiAgICAgICAgICAgICRtb2RlRWwudmFsKCBlZGl0b3IuZ2V0U2Vzc2lvbigpLiRtb2RlSWQgKTsKICAgICAgICAgICAgJHRoZW1lRWwudmFsKCBlZGl0b3IuZ2V0VGhlbWUoKSApOwogICAgICAgICAgICAkZm9udFNpemVFbC52YWwoMTIpLmNoYW5nZSgpOyAvL3NldCBkZWZhdWx0IGZvbnQgc2l6ZSBpbiBkcm9wIGRvd24KICAgICAgICB9CgogICAgICAgICQoZnVuY3Rpb24oKXsKICAgICAgICAgICAgcmVuZGVyVGhlbWVNb2RlKCk7CiAgICAgICAgICAgICQoIi5qcy1hY2UtdG9vbGJhciIpLm9uKCJjbGljayIsICdidXR0b24nLCBmdW5jdGlvbihlKXsKICAgICAgICAgICAgICAgIGUucHJldmVudERlZmF1bHQoKTsKICAgICAgICAgICAgICAgIGxldCBjbWRWYWx1ZSA9ICQodGhpcykuYXR0cigiZGF0YS1jbWQiKSwgZWRpdG9yT3B0aW9uID0gJCh0aGlzKS5hdHRyKCJkYXRhLW9wdGlvbiIpOwogICAgICAgICAgICAgICAgaWYoY21kVmFsdWUgJiYgY21kVmFsdWUgIT0gIm5vbmUiKSB7CiAgICAgICAgICAgICAgICAgICAgYWNlX2NvbW1lbmQoY21kVmFsdWUpOwogICAgICAgICAgICAgICAgfSBlbHNlIGlmKGVkaXRvck9wdGlvbikgewogICAgICAgICAgICAgICAgICAgIGlmKGVkaXRvck9wdGlvbiA9PSAiZnVsbHNjcmVlbiIpIHsKICAgICAgICAgICAgICAgICAgICAgICAgKHZvaWQgMCE9PWRvY3VtZW50LmZ1bGxTY3JlZW5FbGVtZW50JiZudWxsPT09ZG9jdW1lbnQuZnVsbFNjcmVlbkVsZW1lbnR8fHZvaWQgMCE9PWRvY3VtZW50Lm1zRnVsbHNjcmVlbkVsZW1lbnQmJm51bGw9PT1kb2N1bWVudC5tc0Z1bGxzY3JlZW5FbGVtZW50fHx2b2lkIDAhPT1kb2N1bWVudC5tb3pGdWxsU2NyZWVuJiYhZG9jdW1lbnQubW96RnVsbFNjcmVlbnx8dm9pZCAwIT09ZG9jdW1lbnQud2Via2l0SXNGdWxsU2NyZWVuJiYhZG9jdW1lbnQud2Via2l0SXNGdWxsU2NyZWVuKQogICAgICAgICAgICAgICAgICAgICAgICAmJihlZGl0b3IuY29udGFpbmVyLnJlcXVlc3RGdWxsU2NyZWVuP2VkaXRvci5jb250YWluZXIucmVxdWVzdEZ1bGxTY3JlZW4oKTplZGl0b3IuY29udGFpbmVyLm1velJlcXVlc3RGdWxsU2NyZWVuP2VkaXRvci5jb250YWluZXIubW96UmVxdWVzdEZ1bGxTY3JlZW4oKTplZGl0b3IuY29udGFpbmVyLndlYmtpdFJlcXVlc3RGdWxsU2NyZWVuP2VkaXRvci5jb250YWluZXIud2Via2l0UmVxdWVzdEZ1bGxTY3JlZW4oRWxlbWVudC5BTExPV19LRVlCT0FSRF9JTlBVVCk6ZWRpdG9yLmNvbnRhaW5lci5tc1JlcXVlc3RGdWxsc2NyZWVuJiZlZGl0b3IuY29udGFpbmVyLm1zUmVxdWVzdEZ1bGxzY3JlZW4oKSk7CiAgICAgICAgICAgICAgICAgICAgfSBlbHNlIGlmKGVkaXRvck9wdGlvbiA9PSAid3JhcCIpIHsKICAgICAgICAgICAgICAgICAgICAgICAgbGV0IHdyYXBTdGF0dXMgPSAoZWRpdG9yLmdldFNlc3Npb24oKS5nZXRVc2VXcmFwTW9kZSgpKSA/IGZhbHNlIDogdHJ1ZTsKICAgICAgICAgICAgICAgICAgICAgICAgZWRpdG9yLmdldFNlc3Npb24oKS5zZXRVc2VXcmFwTW9kZSh3cmFwU3RhdHVzKTsKICAgICAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgIH0pOwogICAgICAgICAgICAkKCJzZWxlY3QjanMtYWNlLW1vZGUsIHNlbGVjdCNqcy1hY2UtdGhlbWUsIHNlbGVjdCNqcy1hY2UtZm9udFNpemUiKS5vbigiY2hhbmdlIiwgZnVuY3Rpb24oZSl7CiAgICAgICAgICAgICAgICBlLnByZXZlbnREZWZhdWx0KCk7CiAgICAgICAgICAgICAgICBsZXQgc2VsZWN0ZWRWYWx1ZSA9ICQodGhpcykudmFsKCksIHNlbGVjdGlvblR5cGUgPSAkKHRoaXMpLmF0dHIoImRhdGEtdHlwZSIpOwogICAgICAgICAgICAgICAgaWYoc2VsZWN0ZWRWYWx1ZSAmJiBzZWxlY3Rpb25UeXBlID09ICJtb2RlIikgewogICAgICAgICAgICAgICAgICAgIGVkaXRvci5nZXRTZXNzaW9uKCkuc2V0TW9kZShzZWxlY3RlZFZhbHVlKTsKICAgICAgICAgICAgICAgIH0gZWxzZSBpZihzZWxlY3RlZFZhbHVlICYmIHNlbGVjdGlvblR5cGUgPT0gInRoZW1lIikgewogICAgICAgICAgICAgICAgICAgIGVkaXRvci5zZXRUaGVtZShzZWxlY3RlZFZhbHVlKTsKICAgICAgICAgICAgICAgIH1lbHNlIGlmKHNlbGVjdGVkVmFsdWUgJiYgc2VsZWN0aW9uVHlwZSA9PSAiZm9udFNpemUiKSB7CiAgICAgICAgICAgICAgICAgICAgZWRpdG9yLnNldEZvbnRTaXplKHBhcnNlSW50KHNlbGVjdGVkVmFsdWUpKTsKICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgfSk7CiAgICAgICAgfSk7CiAgICA8L3NjcmlwdD4KPD9waHAgZW5kaWY7ID8+CjxkaXYgaWQ9InNuYWNrYmFyIj48L2Rpdj4KPC9ib2R5Pgo8L2h0bWw+Cjw/cGhwCn0KCi8qKgogKiBMYW5ndWFnZSBUcmFuc2xhdGlvbiBTeXN0ZW0KICogQHBhcmFtIHN0cmluZyAkdHh0CiAqIEByZXR1cm4gc3RyaW5nCiAqLwpmdW5jdGlvbiBsbmcoJHR4dCkgewogICAgZ2xvYmFsICRsYW5nOwoKICAgIC8vIEVuZ2xpc2ggTGFuZ3VhZ2UKICAgICR0clsnZW4nXVsnQXBwTmFtZSddICAgICAgICA9ICdUaW55IEZpbGUgTWFuYWdlcic7ICAgICAgJHRyWydlbiddWydBcHBUaXRsZSddICAgICAgICAgICA9ICdGaWxlIE1hbmFnZXInOwogICAgJHRyWydlbiddWydMb2dpbiddICAgICAgICAgID0gJ1NpZ24gaW4nOyAgICAgICAgICAgICAgICAkdHJbJ2VuJ11bJ1VzZXJuYW1lJ10gICAgICAgICAgID0gJ1VzZXJuYW1lJzsKICAgICR0clsnZW4nXVsnUGFzc3dvcmQnXSAgICAgICA9ICdQYXNzd29yZCc7ICAgICAgICAgICAgICAgJHRyWydlbiddWydMb2dvdXQnXSAgICAgICAgICAgICA9ICdTaWduIE91dCc7CiAgICAkdHJbJ2VuJ11bJ01vdmUnXSAgICAgICAgICAgPSAnTW92ZSc7ICAgICAgICAgICAgICAgICAgICR0clsnZW4nXVsnQ29weSddICAgICAgICAgICAgICAgPSAnQ29weSc7CiAgICAkdHJbJ2VuJ11bJ1NhdmUnXSAgICAgICAgICAgPSAnU2F2ZSc7ICAgICAgICAgICAgICAgICAgICR0clsnZW4nXVsnU2VsZWN0QWxsJ10gICAgICAgICAgPSAnU2VsZWN0IGFsbCc7CiAgICAkdHJbJ2VuJ11bJ1VuU2VsZWN0QWxsJ10gICAgPSAnVW5zZWxlY3QgYWxsJzsgICAgICAgICAgICR0clsnZW4nXVsnRmlsZSddICAgICAgICAgICAgICAgPSAnRmlsZSc7CiAgICAkdHJbJ2VuJ11bJ0JhY2snXSAgICAgICAgICAgPSAnQmFjayc7ICAgICAgICAgICAgICAgICAgICR0clsnZW4nXVsnU2l6ZSddICAgICAgICAgICAgICAgPSAnU2l6ZSc7CiAgICAkdHJbJ2VuJ11bJ1Blcm1zJ10gICAgICAgICAgPSAnUGVybXMnOyAgICAgICAgICAgICAgICAgICR0clsnZW4nXVsnTW9kaWZpZWQnXSAgICAgICAgICAgPSAnTW9kaWZpZWQnOwogICAgJHRyWydlbiddWydPd25lciddICAgICAgICAgID0gJ093bmVyJzsgICAgICAgICAgICAgICAgICAkdHJbJ2VuJ11bJ1NlYXJjaCddICAgICAgICAgICAgID0gJ1NlYXJjaCc7CiAgICAkdHJbJ2VuJ11bJ05ld0l0ZW0nXSAgICAgICAgPSAnTmV3IEl0ZW0nOyAgICAgICAgICAgICAgICR0clsnZW4nXVsnRm9sZGVyJ10gICAgICAgICAgICAgPSAnRm9sZGVyJzsKICAgICR0clsnZW4nXVsnRGVsZXRlJ10gICAgICAgICA9ICdEZWxldGUnOyAgICAgICAgICAgICAgICAgJHRyWydlbiddWydSZW5hbWUnXSAgICAgICAgICAgICA9ICdSZW5hbWUnOwogICAgJHRyWydlbiddWydDb3B5VG8nXSAgICAgICAgID0gJ0NvcHkgdG8nOyAgICAgICAgICAgICAgICAkdHJbJ2VuJ11bJ0RpcmVjdExpbmsnXSAgICAgICAgID0gJ0RpcmVjdCBsaW5rJzsKICAgICR0clsnZW4nXVsnVXBsb2FkaW5nRmlsZXMnXSA9ICdVcGxvYWQgRmlsZXMnOyAgICAgICAgICAgJHRyWydlbiddWydDaGFuZ2VQZXJtaXNzaW9ucyddICA9ICdDaGFuZ2UgUGVybWlzc2lvbnMnOwogICAgJHRyWydlbiddWydDb3B5aW5nJ10gICAgICAgID0gJ0NvcHlpbmcnOyAgICAgICAgICAgICAgICAkdHJbJ2VuJ11bJ0NyZWF0ZU5ld0l0ZW0nXSAgICAgID0gJ0NyZWF0ZSBOZXcgSXRlbSc7CiAgICAkdHJbJ2VuJ11bJ05hbWUnXSAgICAgICAgICAgPSAnTmFtZSc7ICAgICAgICAgICAgICAgICAgICR0clsnZW4nXVsnQWR2YW5jZWRFZGl0b3InXSAgICAgPSAnQWR2YW5jZWQgRWRpdG9yJzsKICAgICR0clsnZW4nXVsnUmVtZW1iZXJNZSddICAgICA9ICdSZW1lbWJlciBNZSc7ICAgICAgICAgICAgJHRyWydlbiddWydBY3Rpb25zJ10gICAgICAgICAgICA9ICdBY3Rpb25zJzsKICAgICR0clsnZW4nXVsnVXBsb2FkJ10gICAgICAgICA9ICdVcGxvYWQnOyAgICAgICAgICAgICAgICAgJHRyWydlbiddWydDYW5jZWwnXSAgICAgICAgICAgICA9ICdDYW5jZWwnOwogICAgJHRyWydlbiddWydJbnZlcnRTZWxlY3Rpb24nXT0gJ0ludmVydCBTZWxlY3Rpb24nOyAgICAgICAkdHJbJ2VuJ11bJ0Rlc3RpbmF0aW9uRm9sZGVyJ10gID0gJ0Rlc3RpbmF0aW9uIEZvbGRlcic7CiAgICAkdHJbJ2VuJ11bJ0l0ZW1UeXBlJ10gICAgICAgPSAnSXRlbSBUeXBlJzsgICAgICAgICAgICAgICR0clsnZW4nXVsnSXRlbU5hbWUnXSAgICAgICAgICAgPSAnSXRlbSBOYW1lJzsKICAgICR0clsnZW4nXVsnQ3JlYXRlTm93J10gICAgICA9ICdDcmVhdGUgTm93JzsgICAgICAgICAgICAgJHRyWydlbiddWydEb3dubG9hZCddICAgICAgICAgICA9ICdEb3dubG9hZCc7CiAgICAkdHJbJ2VuJ11bJ09wZW4nXSAgICAgICAgICAgPSAnT3Blbic7ICAgICAgICAgICAgICAgICAgICR0clsnZW4nXVsnVW5aaXAnXSAgICAgICAgICAgICAgPSAnVW5aaXAnOwogICAgJHRyWydlbiddWydVblppcFRvRm9sZGVyJ10gID0gJ1VuWmlwIHRvIGZvbGRlcic7ICAgICAgICAkdHJbJ2VuJ11bJ0VkaXQnXSAgICAgICAgICAgICAgID0gJ0VkaXQnOwogICAgJHRyWydlbiddWydOb3JtYWxFZGl0b3InXSAgID0gJ05vcm1hbCBFZGl0b3InOyAgICAgICAgICAkdHJbJ2VuJ11bJ0JhY2tVcCddICAgICAgICAgICAgID0gJ0JhY2sgVXAnOwogICAgJHRyWydlbiddWydTb3VyY2VGb2xkZXInXSAgID0gJ1NvdXJjZSBGb2xkZXInOyAgICAgICAgICAkdHJbJ2VuJ11bJ0ZpbGVzJ10gICAgICAgICAgICAgID0gJ0ZpbGVzJzsKICAgICR0clsnZW4nXVsnTW92ZSddICAgICAgICAgICA9ICdNb3ZlJzsgICAgICAgICAgICAgICAgICAgJHRyWydlbiddWydDaGFuZ2UnXSAgICAgICAgICAgICA9ICdDaGFuZ2UnOwogICAgJHRyWydlbiddWydTZXR0aW5ncyddICAgICAgID0gJ1NldHRpbmdzJzsgICAgICAgICAgICAgICAkdHJbJ2VuJ11bJ0xhbmd1YWdlJ10gICAgICAgICAgID0gJ0xhbmd1YWdlJzsKICAgICR0clsnZW4nXVsnRm9sZGVyIGlzIGVtcHR5J109ICdGb2xkZXIgaXMgZW1wdHknOyAgICAkdHJbJ2VuJ11bJ1BhcnRpdGlvblNpemUnXSAgICAgID0gJ1BhcnRpdGlvbiBzaXplJzsKICAgICR0clsnZW4nXVsnRXJyb3JSZXBvcnRpbmcnXSA9ICdFcnJvciBSZXBvcnRpbmcnOyAgICAgICAgJHRyWydlbiddWydTaG93SGlkZGVuRmlsZXMnXSAgICA9ICdTaG93IEhpZGRlbiBGaWxlcyc7CiAgICAkdHJbJ2VuJ11bJ0hlbHAnXSAgICAgICAgICAgPSAnSGVscCc7ICAgICAgICAgICAgICAgICAgICR0clsnZW4nXVsnQ3JlYXRlZCddICAgICAgICAgICAgPSAnQ3JlYXRlZCc7CiAgICAkdHJbJ2VuJ11bJ0ZyZWUgb2YnXSAgICAgICAgPSAnRnJlZSBvZic7ICAgICAgICAgICAgICAgICR0clsnZW4nXVsnUHJldmlldyddICAgICAgICAgICAgPSAnUHJldmlldyc7CiAgICAkdHJbJ2VuJ11bJ0hlbHAgRG9jdW1lbnRzJ10gPSAnSGVscCBEb2N1bWVudHMnOyAgICAgICAgICR0clsnZW4nXVsnUmVwb3J0IElzc3VlJ10gICAgICAgPSAnUmVwb3J0IElzc3VlJzsKICAgICR0clsnZW4nXVsnR2VuZXJhdGUnXSAgICAgICA9ICdHZW5lcmF0ZSc7ICAgICAgICAgICAgICAgJHRyWydlbiddWydGdWxsU2l6ZSddICAgICAgICAgICA9ICdGdWxsIFNpemUnOwogICAgJHRyWydlbiddWydGcmVlT2YnXSAgICAgICAgID0gJ2ZyZWUgb2YnOyAgICAgICAgICAgICAgICAkdHJbJ2VuJ11bJ0NhbGN1bGF0ZUZvbGRlclNpemUnXT0gJ0NhbGN1bGF0ZSBmb2xkZXIgc2l6ZSc7CiAgICAkdHJbJ2VuJ11bJ0hpZGVDb2x1bW5zJ10gICAgPSAnSGlkZSBQZXJtcy9Pd25lciBjb2x1bW5zJzskdHJbJ2VuJ11bJ1lvdSBhcmUgbG9nZ2VkIGluJ10gPSAnWW91IGFyZSBsb2dnZWQgaW4nOwogICAgJHRyWydlbiddWydOb3RoaW5nIHNlbGVjdGVkJ10gICA9ICdOb3RoaW5nIHNlbGVjdGVkJzsgICAkdHJbJ2VuJ11bJ1BhdGhzIG11c3QgYmUgbm90IGVxdWFsJ10gICAgPSAnUGF0aHMgbXVzdCBiZSBub3QgZXF1YWwnOwogICAgJHRyWydlbiddWydSZW5hbWVkIGZyb20nXSAgICAgICA9ICdSZW5hbWVkIGZyb20nOyAgICAgICAkdHJbJ2VuJ11bJ0FyY2hpdmUgbm90IHVucGFja2VkJ10gICAgICAgPSAnQXJjaGl2ZSBub3QgdW5wYWNrZWQnOwogICAgJHRyWydlbiddWydEZWxldGVkJ10gICAgICAgICAgICA9ICdEZWxldGVkJzsgICAgICAgICAgICAkdHJbJ2VuJ11bJ0FyY2hpdmUgbm90IGNyZWF0ZWQnXSAgICAgICAgPSAnQXJjaGl2ZSBub3QgY3JlYXRlZCc7CiAgICAkdHJbJ2VuJ11bJ0NvcGllZCBmcm9tJ10gICAgICAgID0gJ0NvcGllZCBmcm9tJzsgICAgICAgICR0clsnZW4nXVsnUGVybWlzc2lvbnMgY2hhbmdlZCddICAgICAgICA9ICdQZXJtaXNzaW9ucyBjaGFuZ2VkJzsKICAgICR0clsnZW4nXVsndG8nXSAgICAgICAgICAgICAgICAgPSAndG8nOyAgICAgICAgICAgICAgICAgJHRyWydlbiddWydTYXZlZCBTdWNjZXNzZnVsbHknXSAgICAgICAgID0gJ1NhdmVkIFN1Y2Nlc3NmdWxseSc7CiAgICAkdHJbJ2VuJ11bJ25vdCBmb3VuZCEnXSAgICAgICAgID0gJ25vdCBmb3VuZCEnOyAgICAgICAgICR0clsnZW4nXVsnRmlsZSBTYXZlZCBTdWNjZXNzZnVsbHknXSAgICA9ICdGaWxlIFNhdmVkIFN1Y2Nlc3NmdWxseSc7CiAgICAkdHJbJ2VuJ11bJ0FyY2hpdmUnXSAgICAgICAgICAgID0gJ0FyY2hpdmUnOyAgICAgICAgICAgICR0clsnZW4nXVsnUGVybWlzc2lvbnMgbm90IGNoYW5nZWQnXSAgICA9ICdQZXJtaXNzaW9ucyBub3QgY2hhbmdlZCc7CiAgICAkdHJbJ2VuJ11bJ1NlbGVjdCBmb2xkZXInXSAgICAgID0gJ1NlbGVjdCBmb2xkZXInOyAgICAgICR0clsnZW4nXVsnU291cmNlIHBhdGggbm90IGRlZmluZWQnXSAgICA9ICdTb3VyY2UgcGF0aCBub3QgZGVmaW5lZCc7CiAgICAkdHJbJ2VuJ11bJ2FscmVhZHkgZXhpc3RzJ10gICAgID0gJ2FscmVhZHkgZXhpc3RzJzsgICAgICR0clsnZW4nXVsnRXJyb3Igd2hpbGUgbW92aW5nIGZyb20nXSAgICA9ICdFcnJvciB3aGlsZSBtb3ZpbmcgZnJvbSc7CiAgICAkdHJbJ2VuJ11bJ0NyZWF0ZSBhcmNoaXZlPyddICAgID0gJ0NyZWF0ZSBhcmNoaXZlPyc7ICAgICR0clsnZW4nXVsnSW52YWxpZCBmaWxlIG9yIGZvbGRlciBuYW1lJ10gICAgPSAnSW52YWxpZCBmaWxlIG9yIGZvbGRlciBuYW1lJzsKICAgICR0clsnZW4nXVsnQXJjaGl2ZSB1bnBhY2tlZCddICAgPSAnQXJjaGl2ZSB1bnBhY2tlZCc7ICAgJHRyWydlbiddWydGaWxlIGV4dGVuc2lvbiBpcyBub3QgYWxsb3dlZCddICA9ICdGaWxlIGV4dGVuc2lvbiBpcyBub3QgYWxsb3dlZCc7CiAgICAkdHJbJ2VuJ11bJ1Jvb3QgcGF0aCddICAgICAgICAgID0gJ1Jvb3QgcGF0aCc7ICAgICAgICAgICR0clsnZW4nXVsnRXJyb3Igd2hpbGUgcmVuYW1pbmcgZnJvbSddICA9ICdFcnJvciB3aGlsZSByZW5hbWluZyBmcm9tJzsKICAgICR0clsnZW4nXVsnRmlsZSBub3QgZm91bmQnXSAgICAgPSAnRmlsZSBub3QgZm91bmQnOyAgICAgJHRyWydlbiddWydFcnJvciB3aGlsZSBkZWxldGluZyBpdGVtcyddID0gJ0Vycm9yIHdoaWxlIGRlbGV0aW5nIGl0ZW1zJzsKICAgICR0clsnZW4nXVsnTW92ZWQgZnJvbSddICAgICAgICAgPSAnTW92ZWQgZnJvbSc7CiAgICAkdHJbJ2VuJ11bJ0NoZWNrIExhdGVzdCBWZXJzaW9uJ10gPSAnQ2hlY2sgTGF0ZXN0IFZlcnNpb24nOyR0clsnZW4nXVsnR2VuZXJhdGUgbmV3IHBhc3N3b3JkIGhhc2gnXSA9ICdHZW5lcmF0ZSBuZXcgcGFzc3dvcmQgaGFzaCc7CiAgICAkdHJbJ2VuJ11bJ0xvZ2luIGZhaWxlZC4gSW52YWxpZCB1c2VybmFtZSBvciBwYXNzd29yZCddID0gJ0xvZ2luIGZhaWxlZC4gSW52YWxpZCB1c2VybmFtZSBvciBwYXNzd29yZCc7CiAgICAkdHJbJ2VuJ11bJ3Bhc3N3b3JkX2hhc2ggbm90IHN1cHBvcnRlZCwgVXBncmFkZSBQSFAgdmVyc2lvbiddID0gJ3Bhc3N3b3JkX2hhc2ggbm90IHN1cHBvcnRlZCwgVXBncmFkZSBQSFAgdmVyc2lvbic7CiAgICAkdHJbJ2VuJ11bJ0FkdmFuY2VkIFNlYXJjaCddICAgID0gJ0FkdmFuY2VkIFNlYXJjaCc7ICAgICR0clsnZW4nXVsnRXJyb3Igd2hpbGUgY29weWluZyBmcm9tJ10gICAgPSAnRXJyb3Igd2hpbGUgY29weWluZyBmcm9tJzsKICAgICR0clsnZW4nXVsnSW52YWxpZCBjaGFyYWN0ZXJzIGluIGZpbGUgbmFtZSddICAgICAgICAgICAgICAgID0gJ0ludmFsaWQgY2hhcmFjdGVycyBpbiBmaWxlIG5hbWUnOwogICAgJHRyWydlbiddWydGSUxFIEVYVEVOU0lPTiBIQVMgTk9UIFNVUFBPUlRFRCddICAgICAgICAgICAgICAgPSAnRklMRSBFWFRFTlNJT04gSEFTIE5PVCBTVVBQT1JURUQnOwogICAgJHRyWydlbiddWydTZWxlY3RlZCBmaWxlcyBhbmQgZm9sZGVyIGRlbGV0ZWQnXSAgICAgICAgICAgICAgPSAnU2VsZWN0ZWQgZmlsZXMgYW5kIGZvbGRlciBkZWxldGVkJzsKICAgICR0clsnZW4nXVsnRXJyb3Igd2hpbGUgZmV0Y2hpbmcgYXJjaGl2ZSBpbmZvJ10gICAgICAgICAgICAgID0gJ0Vycm9yIHdoaWxlIGZldGNoaW5nIGFyY2hpdmUgaW5mbyc7CiAgICAkdHJbJ2VuJ11bJ0RlbGV0ZSBzZWxlY3RlZCBmaWxlcyBhbmQgZm9sZGVycz8nXSAgICAgICAgICAgICA9ICdEZWxldGUgc2VsZWN0ZWQgZmlsZXMgYW5kIGZvbGRlcnM/JzsKICAgICR0clsnZW4nXVsnU2VhcmNoIGZpbGUgaW4gZm9sZGVyIGFuZCBzdWJmb2xkZXJzLi4uJ10gICAgICAgID0gJ1NlYXJjaCBmaWxlIGluIGZvbGRlciBhbmQgc3ViZm9sZGVycy4uLic7CiAgICAkdHJbJ2VuJ11bJ0FjY2VzcyBkZW5pZWQuIElQIHJlc3RyaWN0aW9uIGFwcGxpY2FibGUnXSAgICAgICA9ICdBY2Nlc3MgZGVuaWVkLiBJUCByZXN0cmljdGlvbiBhcHBsaWNhYmxlJzsKICAgICR0clsnZW4nXVsnSW52YWxpZCBjaGFyYWN0ZXJzIGluIGZpbGUgb3IgZm9sZGVyIG5hbWUnXSAgICAgID0gJ0ludmFsaWQgY2hhcmFjdGVycyBpbiBmaWxlIG9yIGZvbGRlciBuYW1lJzsKICAgICR0clsnZW4nXVsnT3BlcmF0aW9ucyB3aXRoIGFyY2hpdmVzIGFyZSBub3QgYXZhaWxhYmxlJ10gICAgID0gJ09wZXJhdGlvbnMgd2l0aCBhcmNoaXZlcyBhcmUgbm90IGF2YWlsYWJsZSc7CiAgICAkdHJbJ2VuJ11bJ0ZpbGUgb3IgZm9sZGVyIHdpdGggdGhpcyBwYXRoIGFscmVhZHkgZXhpc3RzJ10gICA9ICdGaWxlIG9yIGZvbGRlciB3aXRoIHRoaXMgcGF0aCBhbHJlYWR5IGV4aXN0cyc7CgogICAgJGkxOG4gPSBmbV9nZXRfdHJhbnNsYXRpb25zKCR0cik7CiAgICAkdHIgPSAkaTE4biA/ICRpMThuIDogJHRyOwoKICAgIGlmICghc3RybGVuKCRsYW5nKSkgJGxhbmcgPSAnZW4nOwogICAgaWYgKGlzc2V0KCR0clskbGFuZ11bJHR4dF0pKSByZXR1cm4gZm1fZW5jKCR0clskbGFuZ11bJHR4dF0pOwogICAgZWxzZSBpZiAoaXNzZXQoJHRyWydlbiddWyR0eHRdKSkgcmV0dXJuIGZtX2VuYygkdHJbJ2VuJ11bJHR4dF0pOwogICAgZWxzZSByZXR1cm4gIiR0eHQiOwp9Cg==')) ?>