├── .gitignore ├── css ├── asc.png ├── bg.png ├── desc.png └── fantomtest.css ├── favicon.ico ├── img ├── cookie.png ├── incapsula.png ├── spinner.gif ├── digitalocean.png ├── speed-limit-20.png ├── google.svg ├── cloudflare.svg ├── facebook.svg ├── edgecast.svg ├── aws.svg ├── microsoft.svg ├── stackpath.svg ├── gcp.svg ├── fastly.svg ├── attention.svg ├── azure.svg └── akamai.svg ├── screenshot1.png ├── screenshot2.png ├── banner.conf.sample ├── override_functions.php.sample ├── docker-compose.yml ├── Dockerfile ├── README.md ├── get_har.php ├── conf_default.php ├── get_ssl_ciphers.php ├── get_dns.php ├── get_mtr.php ├── waterfall.php ├── tools_ssl.php ├── get_ssl_cert.php ├── get_url.php ├── index.php └── tools.php /.gitignore: -------------------------------------------------------------------------------- 1 | conf.php 2 | -------------------------------------------------------------------------------- /css/asc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vvuksan/fantomTest/main/css/asc.png -------------------------------------------------------------------------------- /css/bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vvuksan/fantomTest/main/css/bg.png -------------------------------------------------------------------------------- /css/desc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vvuksan/fantomTest/main/css/desc.png -------------------------------------------------------------------------------- /favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vvuksan/fantomTest/main/favicon.ico -------------------------------------------------------------------------------- /img/cookie.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vvuksan/fantomTest/main/img/cookie.png -------------------------------------------------------------------------------- /img/incapsula.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vvuksan/fantomTest/main/img/incapsula.png -------------------------------------------------------------------------------- /img/spinner.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vvuksan/fantomTest/main/img/spinner.gif -------------------------------------------------------------------------------- /screenshot1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vvuksan/fantomTest/main/screenshot1.png -------------------------------------------------------------------------------- /screenshot2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vvuksan/fantomTest/main/screenshot2.png -------------------------------------------------------------------------------- /img/digitalocean.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vvuksan/fantomTest/main/img/digitalocean.png -------------------------------------------------------------------------------- /img/speed-limit-20.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vvuksan/fantomTest/main/img/speed-limit-20.png -------------------------------------------------------------------------------- /banner.conf.sample: -------------------------------------------------------------------------------- 1 | 26 | $conf['harpoon_server_url'] = "http://har-poon:3000/throw" 27 | 28 | 29 | * You can override any value in conf_default.php with the value in conf.php 30 | * Now open up fantomTest in your browser. 31 | 32 | Configuration 33 | ============= 34 | 35 | If you install fantomtest on multiple nodes ie. say you have servers in Europe, USA you can access stats 36 | from a single interface by configuring URLs in conf.php. Simply add following to your conf file. 37 | 38 | $conf['remotes'][] = array("name" => "US", "provider" => "http_get", "base_url" => "http://myurl.usa/fantomtest/"); 39 | 40 | to add additional ones simply repeat the line with the new name and URL. 41 | 42 | License 43 | ======= 44 | Apache 45 | -------------------------------------------------------------------------------- /img/google.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /get_har.php: -------------------------------------------------------------------------------- 1 | "URL is not valid" ) ); 26 | exit(1); 27 | } 28 | 29 | isset($_REQUEST['include_image']) && $_REQUEST['include_image'] == 1 ? $include_image = true : $include_image = false; 30 | 31 | isset($_REQUEST['harviewer']) && $_REQUEST['harviewer'] == 1 ? $harviewer = true : $harviewer = false; 32 | 33 | if ( isset($conf['prerender_server_url']) ) { 34 | 35 | $query_args = array ( 36 | "url" => $url, 37 | "followRedirects" => true, 38 | "pageLoadTimeout" => 50000, 39 | "renderType" => "har" 40 | ); 41 | $results = array(); 42 | $results['har'] = json_decode(file_get_contents($conf['prerender_server_url'] . "?" . http_build_query($query_args)), TRUE); 43 | 44 | } else if ( isset($conf['harrr_server_url']) ) { 45 | $payload = array ( 46 | "url" => $url, 47 | "waitForDuration" => 25000, 48 | ); 49 | 50 | $opts = array( 51 | 'http' => array( 52 | 'method' => "POST", 53 | 'header' => "Content-Type: application/json\r\n", 54 | 'content' => json_encode($payload) 55 | ) 56 | ); 57 | $context = stream_context_create($opts); 58 | 59 | $results['har'] = json_decode(@file_get_contents($conf['harrr_server_url'], false, $context), TRUE); 60 | 61 | } else { 62 | $results = get_har_using_phantomjs($url, $include_image, $harviewer ); 63 | } 64 | 65 | if ( $harviewer ) 66 | print "onInputData("; 67 | 68 | print json_encode($results); 69 | 70 | if ( $harviewer ) 71 | print ");"; 72 | 73 | 74 | } 75 | 76 | ?> 77 | -------------------------------------------------------------------------------- /img/cloudflare.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 20 | 21 | Cloudflare logo 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /img/facebook.svg: -------------------------------------------------------------------------------- 1 | 2 | 13 | 15 | 18 | 21 | 22 | 23 | 25 | 26 | 28 | image/svg+xml 29 | 31 | 32 | 33 | 34 | 35 | 38 | 41 | 44 | 47 | 51 | 52 | 55 | 59 | 60 | 61 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /conf_default.php: -------------------------------------------------------------------------------- 1 | /har"; 27 | 28 | # For IP to AS resolution use local file-based cache. If following defined use the file as the cache file 29 | #$conf['cache_file'] = "/var/www/cache/cache.json"; 30 | # Cache time 31 | #$conf['cache_time'] = 8640000; 32 | 33 | if ( getenv('HARPOON_URL') !== false ) { 34 | $conf['harpoon_server_url'] = getenv('HARPOON_URL'); 35 | } 36 | 37 | # These are the headers that can be used in the URL test. If you are finding you are using the 38 | # the same headers all the time you can set them here to default to a value 39 | # For example Accept-Language:es || User-Agent:Mozilla 40 | $conf['arbitrary_headers'] = ""; 41 | 42 | $conf['allowed_dns_query_types'] = array ( 43 | "A", 44 | "AAAA", 45 | "CNAME", 46 | "MX", 47 | "SOA", 48 | "TXT", 49 | "NS", 50 | "CAA" 51 | ); 52 | 53 | $conf['allowed_http_methods'] = array( 54 | "GET", 55 | "POST", 56 | "HEAD", 57 | "DELETE", 58 | "OPTIONS", 59 | "PURGE", 60 | "PATCH", 61 | "PUT" 62 | ); 63 | 64 | # Should ping/mtr be enabled. Make sure paths to ping and mtr are correct. Otherwise 65 | # the tab will be disabled 66 | $conf['pingmtr_enabled'] = true; 67 | $conf['ping_bin'] = "/bin/ping"; 68 | $conf['ping6_bin'] = "/bin/ping6"; 69 | $conf['mtr_bin'] = "/usr/bin/mtr"; 70 | # 71 | if ( !( is_executable($conf['mtr_bin']) && ! is_executable($conf['ping_bin'] = "/bin/ping") ) ) { 72 | $conf['pingmtr_enabled'] = false; 73 | } 74 | 75 | # Should NMAP be available 76 | $conf['nmap_bin'] = "/usr/bin/nmap"; 77 | 78 | $conf['jquery_js_path'] = "https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.min.js"; 79 | $conf['jqueryui_js_path'] = "https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.14.1/jquery-ui.min.js"; 80 | $conf['jqueryui_css_path'] = "https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.14.1/themes/flick/jquery-ui.min.css"; 81 | $conf['jquery_tablesorter'] = "https://cdnjs.cloudflare.com/ajax/libs/jquery.tablesorter/2.25.7/js/jquery.tablesorter.min.js"; 82 | 83 | # Read README on what are remotes 84 | #$conf['remotes'][] = array("name" => "US", "provider" => "http_get", "base_url" => "http://myurl.usa/fantomtest/"); 85 | #$conf['remotes'][] = array("name" => "Europe", "provider" => "http_get", "base_url" => "http://myurl.eu/fantomtest/"); 86 | -------------------------------------------------------------------------------- /img/edgecast.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 8 | 9 | 10 | 13 | 14 | 15 | 16 | 17 | 20 | 21 | 22 | 23 | 24 | 28 | 29 | 30 | 31 | 32 | 35 | 36 | 37 | 38 | 39 | 42 | 43 | 44 | 45 | 46 | 49 | 50 | 51 | 52 | 53 | 57 | 58 | 59 | 60 | 61 | 63 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /img/aws.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 9 | 10 | 31 | 32 | 34 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /img/microsoft.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 12 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /img/stackpath.svg: -------------------------------------------------------------------------------- 1 | stackpath_logo-freelogovectors.net_ -------------------------------------------------------------------------------- /css/fantomtest.css: -------------------------------------------------------------------------------- 1 | body{ 2 | font: 62.5% "Trebuchet MS",sans-serif; 3 | margin: 10px; 4 | } 5 | .bar { 6 | width: 600px; 7 | background: white; 8 | display: block; 9 | } 10 | 11 | .curl_bar { 12 | background: white; 13 | display: block; 14 | } 15 | 16 | .compressed_yes, .compressed_no, .compressed_none { 17 | color: white; 18 | border: none; 19 | text-decoration: none; 20 | display: inline-block; 21 | font-size: 5px; 22 | } 23 | .compressed_yes { 24 | background-color: green; 25 | } 26 | .compressed_no { 27 | font-weight: 900; 28 | background-color: red; 29 | } 30 | .compressed_none { 31 | background-color: blue; 32 | } 33 | .http2 { 34 | background-color: orange; 35 | } 36 | 37 | .http3 { 38 | background-color: lightcoral; 39 | } 40 | 41 | .websockets { 42 | background-color: lightblue; 43 | } 44 | 45 | .time_legend { 46 | text-align: center; 47 | color: white; 48 | } 49 | 50 | .dns_time { 51 | background: #1F7C83; 52 | } 53 | 54 | .conn_time { 55 | background: #E58226; 56 | } 57 | 58 | .ssl_conn_time { 59 | background: #C141CD; 60 | } 61 | 62 | .request_time { 63 | background: #FF9786; 64 | } 65 | 66 | .time_to_first_byte { 67 | background: #1FE11F; 68 | } 69 | 70 | .transfer_time { 71 | background: #1977DD; 72 | } 73 | 74 | #banner { 75 | background: lightyellow; 76 | text-align: center; 77 | font-size: 16px; 78 | border: 1px solid #000; 79 | } 80 | 81 | .vendor_img { 82 | height: 15px; 83 | } 84 | 85 | .fill { 86 | float: left; 87 | } 88 | .harview { 89 | font-size: 12px; 90 | } 91 | .normal { 92 | background: white; 93 | } 94 | .redirect { 95 | background: #FFFC86; 96 | } 97 | .error { 98 | background: #FF9786; 99 | } 100 | 101 | .response_101 { 102 | background: #BDF0EB; 103 | } 104 | 105 | .response_200 { 106 | } 107 | 108 | .response_301 { 109 | background: #FFFC86; 110 | } 111 | 112 | .response_302 { 113 | background: #FFFC86; 114 | } 115 | 116 | .response_404 { 117 | background: #FF9786; 118 | } 119 | 120 | .response_403 { 121 | background: #FF9786; 122 | } 123 | 124 | .http_headers { 125 | font-size: 8px; 126 | } 127 | 128 | .cache_servers { 129 | font-size: 8px; 130 | } 131 | 132 | .number { 133 | text-align: right; 134 | } 135 | 136 | .x-cache-HIT { 137 | background: lightgreen; 138 | } 139 | .x-cache-MISS { 140 | background: orange; 141 | } 142 | 143 | .yes-gzip { 144 | background: lightgreen; 145 | } 146 | .no-gzip { 147 | background: orange; 148 | } 149 | 150 | .ssl-cert-invalid { 151 | background: #FFF5EE; 152 | font-size: 18px; 153 | } 154 | 155 | table.tablesorter { 156 | font-family:arial; 157 | margin:10px 0pt 15px; 158 | font-size: 10pt; 159 | width: 100%; 160 | text-align: left; 161 | } 162 | table.tablesorter thead tr th { 163 | background-color: #dddddd; 164 | border: 1px solid #FFF; 165 | font-size: 8pt; 166 | padding: 4px; 167 | } 168 | table.tablesorter tfoot tr th { 169 | background-color: #dddddd; 170 | border: 1px solid #FFF; 171 | font-size: 11pt; 172 | padding: 4px; 173 | } 174 | table.tablesorter tbody td { 175 | color: #3D3D3D; 176 | padding: 4px; 177 | vertical-align: top; 178 | } 179 | table.tablesorter thead tr .headerSortUp { 180 | background-image: url(asc.png); 181 | } 182 | table.tablesorter thead tr .headerSortDown { 183 | background-image: url(desc.png); 184 | } 185 | table.tablesorter thead tr .headerSortDown, table.tablesorter thead tr .headerSortUp { 186 | background-color: #8dbdd8; 187 | } 188 | 189 | table.tablesorter .header { 190 | background-image: url(bg.png); 191 | background-repeat: no-repeat; 192 | border-left: 1px solid #FFF; 193 | border-right: 1px solid #000; 194 | border-top: 1px solid #FFF; 195 | padding-left: 30px; 196 | padding-top: 8px; 197 | height: auto; 198 | } 199 | -------------------------------------------------------------------------------- /img/gcp.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /img/fastly.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 12 | 14 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /get_ssl_ciphers.php: -------------------------------------------------------------------------------- 1 | 1 && $_REQUEST['port'] < 65536 ? $_REQUEST['port'] : 443; 33 | } 34 | 35 | $site_id = is_numeric($_REQUEST['site_id']) ? $_REQUEST['site_id'] : -1; 36 | 37 | # Need name of this script so we can execute the same on remote nodes 38 | $conf['remote_exe'] = basename ( __FILE__ ); 39 | 40 | /////////////////////////////////////////////////////////////////////////////// 41 | // site_id == -1 means run only on this node. This is the only time 42 | // we don't run stuff elsewhere 43 | /////////////////////////////////////////////////////////////////////////////// 44 | if ( $_REQUEST['site_id'] == -1 ) { 45 | 46 | # First make sure nmap is available 47 | if ( !is_executable($conf['nmap_bin']) ) { 48 | die("NMAP is not executable. Current path is to to " . $conf['nmap_bin'] . " please set \$conf['nmap_bin'] in conf.php to proper path"); 49 | } 50 | 51 | ?> 52 |

Ciphers

53 | 54 | Running

55 | 65 |
66 |
67 |
 68 |     
 71 |     
72 |
73 | 74 | $remote ) { 85 | 86 | print "
87 |
"; 88 | 89 | print "
"; 90 | 91 | print "
"; 92 | 93 | print ' 94 | 99 |

'; 100 | 101 | } 102 | 103 | } else if ( isset($conf['remotes'][$site_id]['name'] ) ) { 104 | $sslOptions=array("ssl"=>array("verify_peer"=>false,"verify_peer_name"=>false)); 105 | print "

" .$conf['remotes'][$site_id]['name']. "

"; 106 | print "
"; 107 | print (file_get_contents($conf['remotes'][$site_id]['base_url'] . $conf['remote_exe'] . "?site_id=-1" . 108 | "&hostname=" . $_REQUEST['hostname'] . "&port=" . $port, FALSE, stream_context_create($sslOptions) )); 109 | print "
"; 110 | 111 | 112 | } else { 113 | die("No valid site_id supplied"); 114 | } 115 | 116 | ?> 117 | -------------------------------------------------------------------------------- /get_dns.php: -------------------------------------------------------------------------------- 1 | $remote ) { 61 | 62 | $url = $remote['base_url'] . $conf['remote_exe'] . "?json=1&site_id=-1&hostname=" 63 | . htmlentities($_REQUEST['hostname']) . "&query_type=" . $query_type; 64 | $url_parts = parse_url($url); 65 | $curly[$id] = curl_init(); 66 | curl_setopt($curly[$id], CURLOPT_HEADER, 1); 67 | # How long to wait for CURL response. DNS responses should be quick so 4 seconds should be plenty 68 | curl_setopt($curly[$id], CURLOPT_TIMEOUT, 4); 69 | curl_setopt($curly[$id], CURLOPT_RETURNTRANSFER, 1); 70 | switch ( $url_parts['scheme'] ) { 71 | case "http": 72 | curl_setopt($curly[$id], CURLOPT_PROTOCOLS, CURLPROTO_HTTP); 73 | curl_setopt($curly[$id], CURLOPT_REDIR_PROTOCOLS, CURLPROTO_HTTP); 74 | break; 75 | case "https": 76 | curl_setopt($curly[$id], CURLOPT_PROTOCOLS, CURLPROTO_HTTPS); 77 | curl_setopt($curly[$id], CURLOPT_REDIR_PROTOCOLS, CURLPROTO_HTTPS); 78 | break; 79 | default: 80 | die("

Invalid protocol supplied. You need either http:// or https://

"); 81 | } 82 | 83 | curl_setopt($curly[$id], CURLOPT_ENCODING , "gzip"); 84 | curl_setopt($curly[$id], CURLOPT_URL, $url); 85 | # Disable SSL peer verify ie. don't check remote side SSL certificates 86 | if ( ! $conf['ssl_peer_verify'] ) { 87 | curl_setopt($curly[$id], CURLOPT_SSL_VERIFYPEER, FALSE); 88 | curl_setopt($curly[$id], CURLOPT_SSL_VERIFYHOST, FALSE); 89 | curl_setopt($curly[$id], CURLOPT_VERBOSE , TRUE); 90 | } 91 | curl_multi_add_handle($mh, $curly[$id]); 92 | } 93 | 94 | // execute the handles 95 | $running = null; 96 | do { 97 | curl_multi_exec($mh, $running); 98 | } while($running > 0); 99 | 100 | $results = array(); 101 | 102 | foreach($curly as $id => $c) { 103 | 104 | if(curl_errno($c)) { 105 | print "

" . curl_error($c) . "

"; 106 | } 107 | 108 | $response = curl_multi_getcontent($c); 109 | 110 | if ( $response != "" ) { 111 | list($header, $content) = explode("\r\n\r\n", $response); 112 | $results[$id] = json_decode($content, TRUE); 113 | } 114 | 115 | } 116 | 117 | #print "
"; print_r($results);
118 |   print_dns_results($results);
119 | 
120 | } else if ( isset($conf['remotes'][$site_id]['name'] ) ) {
121 |   $sslOptions=array("ssl"=>array("verify_peer"=>false,"verify_peer_name"=>false));
122 |   $content = file_get_contents($conf['remotes'][$site_id]['base_url'] . $conf['remote_exe'] . "?json=1&site_id=-1" .
123 |     "&hostname=" . htmlentities($_REQUEST['hostname'] ) . "&query_type=" . $query_type, FALSE, stream_context_create($sslOptions)) ;
124 | 
125 |   $results[$site_id] = json_decode($content, TRUE);
126 |   
127 |   print_dns_results($results);
128 | 
129 | } else {
130 |     die("No valid site_id supplied");
131 | }
132 | 


--------------------------------------------------------------------------------
/get_mtr.php:
--------------------------------------------------------------------------------
  1 |  0 and $_REQUEST['ping_count'] <= 20 ) {
 37 |     $ping_count = intval($_REQUEST['ping_count']);
 38 | } else {
 39 |     $ping_count = 10;
 40 | }
 41 | 
 42 | $site_id = isset($_REQUEST['site_id']) && is_numeric($_REQUEST['site_id']) ? $_REQUEST['site_id'] : -1;
 43 | 
 44 | $conf['remote_exe'] = "get_mtr.php";
 45 | 
 46 | ///////////////////////////////////////////////////////////////////////////////
 47 | // site_id == -1 means run only on this node. This is the only time
 48 | // we don't run stuff elsewhere
 49 | ///////////////////////////////////////////////////////////////////////////////
 50 | if ( $_REQUEST['site_id'] == -1 ) {
 51 | 
 52 | ?>
 53 | 
 54 |     

Ping

55 |
56 |
 57 |     
 64 |     
65 |
66 | 67 |

MTR

68 |
69 |
 70 |     
 73 |     
74 |
75 | 76 | $remote ) { 86 | 87 | print "
88 |
"; 89 | 90 | print "
"; 91 | 92 | #print (file_get_contents($conf['remotes'][$index]['base_url'] . "get_mtr.php?site_id=-1" . 93 | #"&hostname=" . $user['hostname'] )); 94 | print "
"; 95 | 96 | $args[] = 'hostname=' . htmlentities($user['hostname']); 97 | $args[] = 'ping_count=' . $ping_count; 98 | 99 | print ' 100 | 105 |

'; 106 | 107 | } 108 | 109 | /////////////////////////////////////////////////////////////////////////////// 110 | // Otherwise if it's not a local node or all nodes it's a specific node 111 | /////////////////////////////////////////////////////////////////////////////// 112 | 113 | } else if ( isset($conf['remotes'][$site_id]['name'] ) ) { 114 | 115 | print "

" .$conf['remotes'][$site_id]['name']. "

"; 116 | print "
"; 117 | $args[] = 'hostname=' . htmlentities($user['hostname']); 118 | $args[] = 'ping_count=' . $ping_count; 119 | $url = $conf['remotes'][$site_id]['base_url'] . $conf['remote_exe'] . "?site_id=-1&" . join("&", $args); 120 | $sslOptions=array("ssl"=>array("verify_peer"=>false,"verify_peer_name"=>false)); 121 | print (file_get_contents($url, FALSE, stream_context_create($sslOptions))); 122 | print "
"; 123 | 124 | 125 | } else { 126 | die("No valid site_id supplied"); 127 | } 128 | 129 | ?> 130 | -------------------------------------------------------------------------------- /waterfall.php: -------------------------------------------------------------------------------- 1 | 7 | 17 |
18 |
19 |

20 | Alert: URL is invalid. Please check for any invalid characters, spaces, etc.

21 |
22 |
23 | $url, 41 | "followRedirects" => true, 42 | "waitAfterLastRequest" => 2000, 43 | "pageDoneCheckInterval" => 1500, 44 | "pageLoadTimeout" => 50000, 45 | "renderType" => "har" 46 | ); 47 | $results = array(); 48 | $results['har'] = json_decode(file_get_contents($conf['prerender_server_url'] . "?" . http_build_query($query_args)), TRUE); 49 | } else if ( isset($conf['harrr_server_url']) ) { 50 | $payload = array ( 51 | "url" => $url, 52 | #, "waitForDuration" => 25000 53 | ); 54 | 55 | $opts = array( 56 | 'http' => array( 57 | 'method' => "POST", 58 | 'header' => "Content-Type: application/json\r\n", 59 | 'content' => json_encode($payload) 60 | ) 61 | ); 62 | $context = stream_context_create($opts); 63 | 64 | $results['har'] = json_decode(file_get_contents($conf['harrr_server_url'], false, $context), TRUE); 65 | } else if ( isset($conf['harpoon_server_url']) ) { 66 | 67 | $opts = array( 68 | 'http' => array( 69 | 'method' => "GET", 70 | 'header' => "Content-Type: application/json\r\n", 71 | ) 72 | ); 73 | $context = stream_context_create($opts); 74 | 75 | $results = json_decode(file_get_contents($conf['harpoon_server_url'] . "?url=" . $url, false, $context), TRUE); 76 | 77 | } else { 78 | $results = get_har_using_phantomjs($url, $include_image, $harviewer ); 79 | } 80 | 81 | } 82 | // Check whether phantomjs succeeded 83 | if ( isset( $results['success']) and $results['success'] == 0 ) { 84 | ?> 85 |
86 |
87 |

88 | Alert:

89 |
90 |
91 | 110 |
111 |
112 |

113 | There was an error uploading the HAR. Please check the size.

114 |
115 |
116 | 117 | 127 |
128 |
129 |

130 | No URL or HAR supplied

131 |
132 |
133 | 134 | 137 | -------------------------------------------------------------------------------- /tools_ssl.php: -------------------------------------------------------------------------------- 1 | 1, 27 | "name" => $parsed_cert["name"], 28 | "key_identifier" => $parsed_cert["extensions"]["subjectKeyIdentifier"] 29 | ); 30 | } 31 | } 32 | 33 | #print_r($ca_certs); 34 | 35 | return $ca_certs; 36 | 37 | } 38 | 39 | ################################################################################################# 40 | # 41 | ################################################################################################# 42 | function check_certificate_chain($hostname, $port, $sni_hostname, $debug = 0) { 43 | 44 | // Turn off all error reporting 45 | error_reporting(0); 46 | 47 | # Get list of all certificates on the local machine 48 | $ca_certs = get_ca_certs(); 49 | 50 | # First we are gonna test whether certificate is good based on our local CA store 51 | $ssloptions = array( 52 | "capture_peer_cert_chain" => true, 53 | "allow_self_signed" => false, 54 | "verify_peer_name" => true, 55 | "verify_peer" => true, 56 | ); 57 | 58 | # Are we doing SNI requests 59 | if ( $sni_hostname != "" ) { 60 | $ssloptions["SNI_enabled"] = true; 61 | # Need to figure out why this doesn't work 62 | $ssloptions["SNI_server_name"] = $sni_hostname; 63 | } else { 64 | $ssloptions["SNI_enabled"] = false; 65 | } 66 | 67 | $ctx = stream_context_create( array("ssl" => $ssloptions) ); 68 | 69 | # Let's establish a SSL connection 70 | $fp = stream_socket_client("ssl://$hostname:$port", $errno, $errstr, 4, STREAM_CLIENT_CONNECT, $ctx); 71 | if (!$fp) { 72 | $success = 0; 73 | } else { 74 | $success = 1; 75 | } 76 | 77 | # If TLS connection failed let's try with loose connection rules 78 | if ( $success == 0 ) { 79 | $ssloptions = array( 80 | "capture_peer_cert_chain" => true, 81 | "allow_self_signed"=> true, 82 | "verify_peer_name" => false, 83 | "verify_peer"=> false 84 | ); 85 | 86 | # Set SSL stream context 87 | $ctx = stream_context_create( array("ssl" => $ssloptions) ); 88 | 89 | # Let's establish a SSL connection 90 | $fp = stream_socket_client("ssl://{$hostname}:{$port}", $errno, $errstr, 4, STREAM_CLIENT_CONNECT, $ctx); 91 | } 92 | 93 | # Grab the context parameters like certificate chain etc. 94 | $captured_certs = array(); 95 | 96 | if ( !$fp ) { 97 | return(array("message" => $errstr)); 98 | } 99 | 100 | $cont = stream_context_get_params($fp); 101 | 102 | # Let's go through captured certificates 103 | foreach($cont["options"]["ssl"]["peer_certificate_chain"] as $cert) { 104 | $parsed_cert = openssl_x509_parse($cert); 105 | $host_cert = isset($parsed_cert["extensions"]["basicConstraints"]) && $parsed_cert["extensions"]["basicConstraints"] == "CA:FALSE" ? 1 : 0; 106 | if ( $host_cert ) { 107 | $issuer_cn = $parsed_cert["issuer"]["CN"]; 108 | } 109 | # Let's derive full ISSUER name 110 | $issuer_name = ""; 111 | if ( isset($parsed_cert["issuer"]) ) { 112 | foreach ( $parsed_cert["issuer"] as $key => $value ) { 113 | $issuer_name .= "/" . $key . "=" . $value; 114 | } 115 | } 116 | 117 | $parsed_cert["ISSUER_NAME"] = $issuer_name; 118 | 119 | ksort($parsed_cert); 120 | $captured_certs[] = $parsed_cert; 121 | 122 | $subject_cn = $parsed_cert["subject"]["CN"]; 123 | $certificates[$subject_cn] = array( 124 | $parsed_cert["subject"]["CN"], 125 | "issuer_cn" => $parsed_cert["issuer"]["CN"], 126 | "host_cert" => $host_cert 127 | ); 128 | } 129 | 130 | $end = 1; 131 | 132 | # Keep how many times we have gone through the chain to avoid an infinite loop 133 | # in case of an unforseen issue 134 | $count = 0; 135 | ################################################################################## 136 | # Let's walk down the certificate chain 137 | ################################################################################## 138 | if ( ! $success ) { 139 | while ( $end and $count < 6 ) { 140 | 141 | $count++; 142 | if ( isset($certificates[$issuer_cn] )) { 143 | if ( $debug ) print "Found " . $issuer_cn . " on the chain. Checking next\n"; 144 | $issuer_cn = $certificates[$issuer_cn]["issuer_cn"]; 145 | } else if ( isset($ca_certs[$issuer_cn])) { 146 | if ( $debug ) print "Found " . $issuer_cn . " on in the CA store. We are good\n"; 147 | $success = 1; 148 | $end = 0; 149 | } else { 150 | $success = 0; 151 | $end = 0; 152 | } 153 | } 154 | } 155 | 156 | if ( strtotime($parsed_cert["VALIDTO"]) < time() ) { 157 | $failure_message = "Certificate expired"; 158 | } else { 159 | $failure_message = "Issuer \"" . $issuer_cn . "\" not found in intermediates or CA store"; 160 | } 161 | 162 | fclose($fp); 163 | 164 | return(array( 165 | "certs" => $captured_certs, 166 | "success" => $success, 167 | "message" => $success ? "" : $failure_message 168 | ) 169 | ); 170 | 171 | } // end of function 172 | -------------------------------------------------------------------------------- /img/attention.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 11 | 13 | 15 | 19 | 23 | 24 | 26 | 30 | 34 | 38 | 39 | 41 | 45 | 49 | 50 | 52 | 56 | 60 | 61 | 63 | 67 | 71 | 72 | 82 | 92 | 101 | 102 | 104 | 106 | 110 | 114 | 118 | 122 | 123 | 124 | 125 | -------------------------------------------------------------------------------- /get_ssl_cert.php: -------------------------------------------------------------------------------- 1 | 1 && $_REQUEST['port'] < 65536 ? $_REQUEST['port'] : 443; 52 | } 53 | 54 | $site_id = isset($_REQUEST['site_id']) && is_numeric($_REQUEST['site_id']) ? $_REQUEST['site_id'] : -1; 55 | 56 | # Need name of this script so we can execute the same on remote nodes 57 | $conf['remote_exe'] = basename ( __FILE__ ); 58 | 59 | /////////////////////////////////////////////////////////////////////////////// 60 | // site_id == -1 means run only on this node. This is the only time 61 | // we don't run stuff elsewhere 62 | /////////////////////////////////////////////////////////////////////////////// 63 | if ( $_REQUEST['site_id'] == -1 ) { 64 | 65 | ?> 66 | Verify yourself with OpenSSL command: 67 |
68 |
echo "HEAD / HTTP/1.1" |  openssl s_client -showcerts  -connect  | openssl x509  -noout  -text
69 |
70 | or gnutls-cli command: 71 |
72 |
echo -n | gnutls-cli --print-cert -p   | openssl x509  -noout  -text
73 |
74 | 75 | "; 100 | } else { 101 | print "
"; 102 | print "

This certificate is invalid

"; 103 | print "Possible reasons (not exhaustive):
"; 104 | print htmlentities($results["message"]) . "
"; 105 | 106 | } 107 | 108 | foreach($results['certs'] as $cert) { 109 | print " 110 | "; 111 | 112 | foreach ( $cert as $key => $parts ) { 113 | # Gonna skip purposes for now 114 | if ( $key == "purposes" ) 115 | continue; 116 | 117 | # Convert UNIX dates into human readable 118 | if ( preg_match("/^VALID(.*)_T$/i", $key) ) 119 | $parts = date('r', $parts); 120 | print ""; 121 | if ( is_array($parts) ) { 122 | print ""; 127 | } else { 128 | print ""; 129 | } 130 | } 131 | 132 | print "
KeyDetails
" . htmlentities(strtoupper($key)) . ""; 123 | foreach ( $parts as $subkey => $value ) { 124 | print ""; 125 | } 126 | print "
" . htmlentities(strtoupper($subkey)) . "" . htmlentities($value) ."
" . htmlentities($parts) . "
"; 133 | # Only care about our SSL certs that have SAN entries in them 134 | } 135 | 136 | print "
"; 137 | 138 | 139 | /////////////////////////////////////////////////////////////////////////////// 140 | // site_id == -100 means run on all remotes. So loop through individual 141 | // remotes and make AJAX calls 142 | /////////////////////////////////////////////////////////////////////////////// 143 | } else if ( $site_id == -100 ) { 144 | 145 | // Get results from all remotes 146 | foreach ( $conf['remotes'] as $index => $remote ) { 147 | 148 | print "
149 |
"; 150 | 151 | print "
"; 152 | print "
"; 153 | 154 | print ' 155 | 160 |

'; 161 | 162 | } 163 | 164 | } else if ( isset($conf['remotes'][$site_id]['name'] ) ) { 165 | $sslOptions=array("ssl"=>array("verify_peer"=>false,"verify_peer_name"=>false)); 166 | 167 | print "

" .$conf['remotes'][$site_id]['name']. "

"; 168 | print "
"; 169 | print (file_get_contents($conf['remotes'][$site_id]['base_url'] . $conf['remote_exe'] . "?site_id=-1" . 170 | "&hostname=" . $_REQUEST['hostname'] . "&port=" . $port . "&sni_name=" . $sni_name, FALSE, stream_context_create($sslOptions) )); 171 | print "
"; 172 | 173 | 174 | } else { 175 | die("No valid site_id supplied"); 176 | } 177 | 178 | ?> 179 | -------------------------------------------------------------------------------- /img/azure.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 21 | 44 | 46 | 47 | 49 | image/svg+xml 50 | 52 | 53 | 54 | 55 | 56 | 61 | 65 | 70 | 71 | 72 | 73 | -------------------------------------------------------------------------------- /get_url.php: -------------------------------------------------------------------------------- 1 | 0 ) { 32 | 33 | $request['site_id'] = isset($my_req['site_id']) && is_numeric($my_req['site_id']) ? $my_req['site_id'] : -1; 34 | $site_id = $request['site_id']; 35 | if ( isset($my_req['timeout']) && is_numeric($my_req['timeout']) and $my_req['timeout'] < 120 ) { 36 | $request['timeout'] = $my_req['timeout']; 37 | } else { 38 | $request['timeout'] = 60; 39 | } 40 | 41 | if ( isset($my_req['protocol']) && $my_req['protocol'] == "http1.1" ) { 42 | $request['protocol'] = "http1.1"; 43 | } else { 44 | $request['protocol'] = "http2"; 45 | } 46 | 47 | if ( isset($my_req['arbitrary_headers']) and $my_req['arbitrary_headers'] != "" ) { 48 | # We need to make sure once we explode around || there are no spaces since that 49 | # causes curl to barf 50 | $temp_array = explode("||", htmlentities($my_req['arbitrary_headers'])); 51 | foreach ( $temp_array as $header ) { 52 | $request['request_headers'][] = trim($header); 53 | } 54 | } else if ( isset($my_req['request_headers']) ) { 55 | $request['request_headers'] = $my_req['request_headers']; 56 | } else { 57 | $request['request_headers'] = array(); 58 | } 59 | 60 | ###################################################################### 61 | # Default override IP to nothing 62 | ###################################################################### 63 | $override_ip = ""; 64 | 65 | # Let's verify that the override IP is actually a resolvable DNS name or an IP 66 | if ( isset($my_req['override_ip']) ) { 67 | $ip_input = trim($my_req['override_ip']); 68 | if(filter_var($ip_input, FILTER_VALIDATE_IP)) { 69 | $override_ip = $ip_input; 70 | } else { 71 | $override_ip = gethostbyname($ip_input); 72 | # If resolution fails it just returns hostname back. Reset override_ip back 73 | if ( $override_ip == $ip_input ) 74 | $override_ip =""; 75 | } 76 | } 77 | 78 | if ( $override_ip != "" ) { 79 | $request['override_ip'] = $override_ip; 80 | } 81 | 82 | ###################################################################### 83 | # Default override IP to nothing 84 | ###################################################################### 85 | if ( $conf['allow_proxy_for_url_check'] && isset($my_req['http_proxy']) ) { 86 | $request['http_proxy'] = $my_req['http_proxy']; 87 | } 88 | 89 | ###################################################################### 90 | # Check we got one of the allowable methods. Otherwise default to GET 91 | ###################################################################### 92 | if ( isset($my_req['method']) && in_array($my_req['method'], $conf['allowed_http_methods']) ) { 93 | $request['method'] = $my_req['method']; 94 | } else { 95 | $request['method'] = "GET"; 96 | } 97 | 98 | ###################################################################### 99 | # Set the payload if it exists 100 | ###################################################################### 101 | if ( isset($my_req['payload']) ) { 102 | $request['payload'] = $my_req['payload']; 103 | } else { 104 | $request['payload'] = ""; 105 | } 106 | 107 | if ( isset($my_req['url-content-type']) ) { 108 | $request['request_headers'][] = "Content-Type: " . $my_req['url-content-type']; 109 | } 110 | 111 | $request['url'] = trim($my_req['url']); 112 | } 113 | 114 | if ( $request['site_id'] == -1 ) { 115 | 116 | $record = get_curl_timings_with_headers($request); 117 | 118 | if ( isset($my_req['json']) && $my_req['json'] == 1 ) { 119 | header('Content-type: application/json'); 120 | print json_encode($record); 121 | exit(1); 122 | } 123 | 124 | $results = array(); 125 | $results["-1"] = $record; 126 | print_url_results($results, $request); 127 | 128 | } else if ( $request['site_id'] == -100 ) { 129 | 130 | $mh = curl_multi_init(); 131 | 132 | // Get results from all remotes 133 | foreach ( $conf['remotes'] as $id => $remote ) { 134 | 135 | $args[] = "json=1"; 136 | $args[] = "site_id=-1"; 137 | $args[] = "url=" . htmlentities($my_req['url']); 138 | $args[] = "arbitrary_headers=" . htmlentities($my_req['arbitrary_headers']); 139 | 140 | $url = $remote['base_url'] . $conf['remote_exe'] . "?" . join("&", $args); 141 | $url_parts = parse_url($url); 142 | $curly[$id] = curl_init(); 143 | curl_setopt($curly[$id], CURLOPT_HEADER, 1); 144 | curl_setopt($curly[$id], CURLOPT_TIMEOUT, $request['timeout']); 145 | curl_setopt($curly[$id], CURLOPT_RETURNTRANSFER, 1); 146 | switch ( $url_parts['scheme'] ) { 147 | case "http": 148 | curl_setopt($curly[$id], CURLOPT_PROTOCOLS, CURLPROTO_HTTP); 149 | curl_setopt($curly[$id], CURLOPT_REDIR_PROTOCOLS, CURLPROTO_HTTP); 150 | break; 151 | case "https": 152 | curl_setopt($curly[$id], CURLOPT_PROTOCOLS, CURLPROTO_HTTPS); 153 | curl_setopt($curly[$id], CURLOPT_REDIR_PROTOCOLS, CURLPROTO_HTTPS); 154 | break; 155 | default: 156 | die("

Invalid protocol supplied. You need either http:// or https://

"); 157 | } 158 | 159 | curl_setopt($curly[$id], CURLOPT_ENCODING , "gzip"); 160 | curl_setopt($curly[$id], CURLOPT_URL, $url); 161 | 162 | # Disable SSL peer verify ie. don't check remote side SSL certificates 163 | if ( ! $conf['ssl_peer_verify'] ) { 164 | curl_setopt($curly[$id], CURLOPT_SSL_VERIFYPEER, FALSE); 165 | curl_setopt($curly[$id], CURLOPT_SSL_VERIFYHOST, FALSE); 166 | curl_setopt($curly[$id], CURLOPT_VERBOSE , TRUE); 167 | } 168 | curl_multi_add_handle($mh, $curly[$id]); 169 | } 170 | 171 | // execute the handles 172 | $running = null; 173 | do { 174 | curl_multi_exec($mh, $running); 175 | } while($running > 0); 176 | 177 | $results = array(); 178 | 179 | foreach($curly as $id => $c) { 180 | 181 | if(curl_errno($c)) { 182 | print "

" . curl_error($c) . "

"; 183 | } 184 | 185 | $response = curl_multi_getcontent($c); 186 | 187 | if ( $response != "" ) { 188 | list($header, $content) = explode("\r\n\r\n", $response); 189 | $results[$id] = json_decode($content, TRUE); 190 | } 191 | 192 | } 193 | 194 | print_url_results($results); 195 | 196 | } else if ( isset($conf['remotes'][$site_id]['name'] ) ) { 197 | 198 | $url = $conf['remotes'][$site_id]['base_url'] . "get_url.php?json=1&site_id=-1&url=" . htmlentities($my_req['url']); 199 | $sslOptions=array("ssl"=>array("verify_peer"=>false,"verify_peer_name"=>false)); 200 | 201 | $results[$site_id] = json_decode( file_get_contents($url, FALSE, stream_context_create($sslOptions)) , TRUE ); 202 | print_url_results($results); 203 | 204 | } else { 205 | die("No valid site_id supplied"); 206 | } 207 | ?> 208 | 213 | -------------------------------------------------------------------------------- /img/akamai.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | image/svg+xml -------------------------------------------------------------------------------- /index.php: -------------------------------------------------------------------------------- 1 | 15 | 16 | 17 | 18 | FantomTest Tools 19 | 20 | 21 | 22 | 23 | 24 | 25 | 205 | 206 | 207 | "; 210 | include_once("./banner.php"); 211 | print ""; 212 | } 213 | ?> 214 |
215 | 241 | 242 | 243 | 249 | 250 |
251 | 274 |
275 |
276 |
277 | 278 | 281 | 288 |
289 | 357 |
358 |
359 | 360 |
361 | 368 |
369 | 401 |
402 |
403 |
404 | 405 | 410 | 411 |
412 | 435 |
436 |
437 |
438 | 441 | 442 |
443 | 467 |
468 | 469 |
470 |
471 | 472 | 475 |
476 | 499 |
500 |
501 |
502 | 505 | 506 | 520 | 521 | 522 | -------------------------------------------------------------------------------- /tools.php: -------------------------------------------------------------------------------- 1 | 0 && $json[$c-1] != '\\') { 126 | $in_string = !$in_string; 127 | } 128 | default: 129 | $new_json .= $char; 130 | break; 131 | } 132 | } 133 | 134 | return $new_json; 135 | } 136 | 137 | ////////////////////////////////////////////////////////////////////////////// 138 | // Generate waterfall chart from HAR (HTTP Archive file) 139 | ////////////////////////////////////////////////////////////////////////////// 140 | function generate_waterfall($har) { 141 | 142 | global $conf; 143 | # This variable will keep the start time of the whole request chain. 144 | $min_start_time = 10000000000; 145 | 146 | # Read the cache file 147 | if( isset($conf['cache_file']) && is_readable($conf['cache_file']) && filesize($conf['cache_file']) > 20 ) { 148 | $cache_age = time() - filemtime($conf['cache_file']); 149 | if ( $cache_age < $conf['cache_time'] ) 150 | $ip_to_as_cache = json_decode(file_get_contents($conf['cache_file']), TRUE); 151 | } else { 152 | $ip_to_as_cache = array(); 153 | } 154 | 155 | # When did the page load finish 156 | $max_end_time = 0; 157 | 158 | if ( !isset($har['log']['entries']) ) { 159 | print "Couldn't retrieve the HTTP Archive"; 160 | exit; 161 | } 162 | 163 | foreach ( $har['log']['entries'] as $key => $request ) { 164 | 165 | $started_time = $request['startedDateTime']; 166 | $request_duration = $request['time'] / 1000; 167 | $url = $request['request']['url']; 168 | $resp_code = intval($request['response']['status']); 169 | $resp_size = isset($request['response']['content']['size']) ? floatval($request['response']['content']['size']) : 0; 170 | 171 | // Extract the milliseconds since strtotime doesn't seem to retain it 172 | if ( preg_match("/(.*)T(.*)\.(.*)(Z)/", $started_time, $out) ) { 173 | $milli = $out[3]; 174 | } else { 175 | $milli = 0; 176 | } 177 | 178 | $start_time = floatval(strtotime($started_time) . "." . $milli); 179 | $end_time = $start_time + $request_duration; 180 | 181 | # Trying to find the start time of the first request 182 | if ( $start_time < $min_start_time ) 183 | $min_start_time = $start_time; 184 | 185 | # Find out when the last request ended 186 | if ( $end_time > $max_end_time ) 187 | $max_end_time = $end_time; 188 | 189 | $resp_headers = array(); 190 | $req_headers = array(); 191 | foreach ( $request['request']['headers'] as $index => $header ) { 192 | $req_headers[$header['name']] = $header['value']; 193 | } 194 | foreach ( $request['response']['headers'] as $index => $header ) { 195 | $header_name = strtolower($header['name']); 196 | $resp_headers[$header_name] = $header['value']; 197 | } 198 | 199 | ksort($resp_headers); 200 | 201 | if ( isset($request['serverIPAddress']) ) { 202 | $server_ip = $request['serverIPAddress']; 203 | } else { 204 | $server_ip = false; 205 | } 206 | 207 | $requests[] = array( 208 | "url" => $url, 209 | "start_time" => $start_time, 210 | "dns_time" => !isset($request['timings']['dns']) || $request['timings']['dns'] <= 0 ? 0 : $request['timings']['dns'] / 1000, 211 | "connect_time" => !isset($request['timings']['connect']) || $request['timings']['connect'] <= 0 ? 0 : $request['timings']['connect'] / 1000, 212 | "ssl_time" => !isset($request['timings']['ssl']) || $request['timings']['ssl'] <= 0 ? 0 : $request['timings']['ssl'] / 1000, 213 | "wait_time" => !isset($request['timings']['wait']) ? 0 : $request['timings']['wait'] / 1000, 214 | "download_time" => !isset($request['timings']['receive']) ? 0 : $request['timings']['receive'] / 1000, 215 | "duration" => $request_duration, 216 | "size" => $resp_size, 217 | "resp_code" => intval($resp_code), 218 | "http_version" => $request['response']['httpVersion'], 219 | "server_ip" => $server_ip, 220 | "req_headers" => $req_headers, 221 | "resp_headers" => $resp_headers 222 | ); 223 | 224 | unset($req_headers, $resp_headers); 225 | 226 | } 227 | 228 | // If min_start_time is unchanged from original there was an error and 229 | // HAR file was invalid. 230 | if ( $min_start_time == 10000000000 ) { 231 | print "

Error

";
 232 |         print_r($har);
 233 |         exit(1);
 234 |     }
 235 |     
 236 |     # Total time to fetch the page and all resources
 237 |     $total_time = $max_end_time - $min_start_time;
 238 |     
 239 |     $content_breakdown = array( "html" => 0,  "images" => 0, "js" => 0, "json" => 0, "css" => 0, "fonts" => 0);
 240 |     $websockets_anchors = array();
 241 |     
 242 |     $haroutput = '
 243 |         
 244 |             
 245 |             
 246 |             
 247 |             
 248 |             
 249 |             
 250 |             
 251 |             
 252 |             
 253 |         '
 254 |     ;
 255 | 
 256 |     foreach ( $requests as $key => $request ) {
 257 |         $time_offset = $request["start_time"] - $min_start_time;
 258 | 
 259 |         $white_space = round(($time_offset / $total_time) * 100);
 260 |         $dns_time_bar = ceil(($request["dns_time"] / $total_time) * 100);
 261 |         $connect_time_bar = ceil(($request["connect_time"] / $total_time) * 100);
 262 |         $ssl_time_bar = ceil(($request["ssl_time"] / $total_time) * 100);
 263 |         $wait_time_bar = ceil(($request["wait_time"] / $total_time) * 100);
 264 |         $download_time_bar = ceil(($request["download_time"] / $total_time) * 100);
 265 | 
 266 |         $haroutput .= "\n";
 267 |         if ( $request["resp_code"] == 101 ) {
 268 |           $haroutput .= "";
 269 |           $websockets_anchors[] = "ws" . $key;
 270 |         } else {
 271 |           $haroutput .= "";
 272 |         }
 273 | 
 274 |         # Output the request url but shrink the screen output to 50 characters
 275 |         $haroutput .= "";
 407 | 
 408 |         ################################################################################################################
 409 |         ############################################# Server IP ########################################################
 410 |         ################################################################################################################
 411 |         # Let's determine the AS number and details
 412 |         if ($request['server_ip']) {
 413 |           $ip_parts = explode(".", $request['server_ip']);
 414 |           array_pop($ip_parts);
 415 |           $ip_prefix = join(".", $ip_parts);
 416 |           if ( !isset($ip_to_as_cache[$ip_prefix]) ) {
 417 |             $ip_details = ip_to_as_info($request['server_ip']);
 418 |             $ip_to_as_cache[$ip_prefix] = array( "as_number" => $ip_details["as_number"], "as_name" => $ip_details["as_name"]);
 419 |           }
 420 |           $frontend_ip_provider = "UNK";
 421 |           # Instead of showing text for some of the most common AS let's use an image
 422 |           if ( $ip_to_as_cache[$ip_prefix]["as_number"] == "AS16509" || $ip_to_as_cache[$ip_prefix]["as_number"] == "AS14618" ) {
 423 |             $img_or_as_name = '';
 424 |             $frontend_ip_provider = "AWS";
 425 |           } else if ( $ip_to_as_cache[$ip_prefix]["as_number"] == "AS15169" ) {
 426 |             $img_or_as_name = '';
 427 |             $frontend_ip_provider = "Google";
 428 |           } else if ( $ip_to_as_cache[$ip_prefix]["as_number"] == "AS20940" || $ip_to_as_cache[$ip_prefix]["as_number"] == "AS16625" ) {
 429 |             $img_or_as_name = '';
 430 |             $frontend_ip_provider = "Akamai";
 431 |           } else if ( $ip_to_as_cache[$ip_prefix]["as_number"] == "AS54113" ) {
 432 |             $img_or_as_name = '';
 433 |             $frontend_ip_provider = "Fastly";
 434 |           } else if ( $ip_to_as_cache[$ip_prefix]["as_number"] == "AS8068" ) {
 435 |             $img_or_as_name = '';
 436 |             $frontend_ip_provider = "Microsoft";
 437 |           } else if ( $ip_to_as_cache[$ip_prefix]["as_number"] == "AS8075" ) {
 438 |             $img_or_as_name = '';
 439 |             $frontend_ip_provider = "Azure";
 440 |           } else if ( $ip_to_as_cache[$ip_prefix]["as_number"] == "AS32934" ) {
 441 |             $img_or_as_name = '';
 442 |             $frontend_ip_provider = "Facebook";
 443 |           } else if ( in_array($ip_to_as_cache[$ip_prefix]["as_number"], array("AS13335", "AS209242", "AS139242" ) ) ) {
 444 |             $img_or_as_name = '';
 445 |             if ( in_array($ip_to_as_cache[$ip_prefix]["as_number"], array("AS209242", "AS139242" ) ) )
 446 |               $img_or_as_name .= " BYOIP";
 447 |             $frontend_ip_provider = "Cloudflare";
 448 |           } else if ( $ip_to_as_cache[$ip_prefix]["as_number"] == "AS396982" ) {
 449 |             $img_or_as_name = '';
 450 |             $frontend_ip_provider = "GCP";
 451 |           } else if ( $ip_to_as_cache[$ip_prefix]["as_number"] == "AS15133" ) {
 452 |             $img_or_as_name = '';
 453 |             $frontend_ip_provider = "Edgecast";
 454 |           } else if ( $ip_to_as_cache[$ip_prefix]["as_number"] == "AS135340" || $ip_to_as_cache[$ip_prefix]["as_number"] == "AS133165" || $ip_to_as_cache[$ip_prefix]["as_number"] == "AS14061" ) {
 455 |             $img_or_as_name = '';
 456 |             $frontend_ip_provider = "Digital Ocean";
 457 |           } else if ( $ip_to_as_cache[$ip_prefix]["as_number"] == "AS20446" ) {
 458 |             $img_or_as_name = '';
 459 |             $frontend_ip_provider = "Stackpath";
 460 |           } else if ( $ip_to_as_cache[$ip_prefix]["as_number"] == "AS19551" ) {
 461 |             $img_or_as_name = '';
 462 |             $frontend_ip_provider = "Incapsula";
 463 |           } else {
 464 |             $img_or_as_name = $ip_to_as_cache[$ip_prefix]["as_name"];
 465 |           }
 466 |           $haroutput .= "";
 467 |         } else {
 468 |           $haroutput .= "";
 469 |         }
 470 | 
 471 |         ################################################################################################################
 472 |         ############################################# Identify CDN #####################################################
 473 |         ################################################################################################################
 474 |         $server = "";
 475 |         $hit_or_miss = "UNK";
 476 |         $hit_or_miss_css = "UNK";
 477 | 
 478 |         # Let's try to identify some CDNs. This is Fastly
 479 |         if ( isset($request['resp_headers']['server']) && preg_match("/^imgix/i", $request['resp_headers']['server']) ) {
 480 |             $server = "ImgIX";
 481 |         } else if ( isset($request['resp_headers']['x-served-by']) && preg_match("/^cache-/", $request['resp_headers']['x-served-by']) ) {
 482 |             $server = "Fastly " . str_replace("cache-", "", $request['resp_headers']['x-served-by']);
 483 |         # Check if Server header provided. It's used by NetDNA and Edgecast
 484 |         }  else if ( isset($request['resp_headers']['server']) && preg_match("/^EC[A-Z]/", $request['resp_headers']['server'])  ) {
 485 |             $server = trim(preg_replace("/^EC[A-Z]/", "Edgecast", $request['resp_headers']['server']));
 486 | 
 487 |         } else if ( isset($request['resp_headers']['server']) && preg_match("/^NetDNA/i", $request['resp_headers']['server']) ) {
 488 |             $server = trim($request['resp_headers']['server']);
 489 |         # CloudFront
 490 |         }
 491 |         else if ( isset($request['resp_headers']['via']) && preg_match("/CloudFront/i", $request['resp_headers']['via']) ) {
 492 |             $server = "CloudFront";
 493 |             if ( isset($request['resp_headers']['x-amz-cf-pop']) ) {
 494 |               $server .= " " . $request['resp_headers']['x-amz-cf-pop'];
 495 |             }
 496 |         # ChinaCache
 497 |         } 
 498 |         else if ( isset($request['resp_headers']['powered-by-chinacache']) ) {
 499 |             $server = "ChinaCache";
 500 |         # Incapsula
 501 |         }
 502 |         else if ( isset($request['resp_headers']['x-instart-request-id']) ) {
 503 |             $server = "Instart";
 504 |         }
 505 |         else if ( isset($request['resp_headers']['quant-server']) ) {
 506 |             $server = "QuantCDN";
 507 | 
 508 |         }
 509 |         else if ( isset($request['resp_headers']['section-io-id']) ) {
 510 |             $server = "Section.io";
 511 |             if ( isset($request['resp_headers']['section-io-cache'])) {
 512 |               $hit_or_miss_css = $request['resp_headers']['section-io-cache'];
 513 |               $hit_or_miss = $request['resp_headers']['section-io-cache'];
 514 |             }            
 515 |         }
 516 |         else if ( isset($request['resp_headers']['x-cdn']) and $request['resp_headers']['x-cdn'] == "Incapsula" ) {
 517 |             $server = "Incapsula";
 518 |         }
 519 |         else if ( isset($request['resp_headers']['server']) && preg_match("/^Footprint Distributor/i", $request['resp_headers']['server']) ) {
 520 |             $server = "Level3";
 521 |         }
 522 |         else if ( isset($request['resp_headers']['server']) && preg_match("/^Windows-Azure-Blob/i", $request['resp_headers']['server']) ) {
 523 |             $server = "Azure Blob Storage";
 524 |         }
 525 |         else if ( isset($request['resp_headers']['x-yottaa-optimizations']) or isset($request['resp_headers']['x-yottaa-metrics']) ) {
 526 |             $server = "Yottaa";
 527 |         }
 528 |         # CD Networks
 529 |         else if ( isset($request['resp_headers']['x-px']) ) {
 530 |             if ( preg_match("/.*\.(.*)\.cdngp.net/i", $request['resp_headers']['x-px'], $out )) {
 531 |               $edge_location = " " .$out[1];
 532 |             } else {
 533 |               $edge_location = "";
 534 |             }
 535 |             $server = "CDNetworks" . $edge_location;
 536 |         }
 537 |         # Cloudflare
 538 |         else if ( isset($request['resp_headers']['cf-ray']) ) {
 539 |             $server = "CF: " . preg_replace('/^(.*)-/', '', $request['resp_headers']['cf-ray']);
 540 |             if ( isset($request['resp_headers']['cf-cache-status'])) {
 541 |               $hit_or_miss_css = $request['resp_headers']['cf-cache-status'];
 542 |               $hit_or_miss = $request['resp_headers']['cf-cache-status'];
 543 |             }
 544 |         }
 545 |         # Highwinds
 546 |         else if ( isset($request['resp_headers']['x-hw']) ) {
 547 |             $server = substr("Stackpath " . preg_replace("/\d+\.(.*),\d+\.(.*)/", "$1, $2", $request['resp_headers']['x-hw']), 0, 16);
 548 |         }
 549 |         # Match Akamai headers
 550 |         else if ( isset($request['resp_headers']['x-cache']) && preg_match("/(\w+) from.*akamai/i", $request['resp_headers']['x-cache'], $out) ) {
 551 |             $server = "Akamai";
 552 |             $hit_or_miss = $out[1];
 553 |         } else if ( isset($request['resp_headers']['server']) && preg_match("/^Akamai/i", $request['resp_headers']['server']) ) {
 554 |             $server = $request['resp_headers']['server'];
 555 |         } else if ( isset($request['resp_headers']['server']) && preg_match("/^CDNNet/i", $request['resp_headers']['server']) ) {
 556 |             $server = "CDN.Net";
 557 |         } else if ( isset($request['resp_headers']['server']) && preg_match("/keycdn/i", $request['resp_headers']['server']) ) {
 558 |             $server = "KeyCDN";
 559 |         } else if ( isset($request['resp_headers']['server']) && preg_match("/vercel/i", $request['resp_headers']['server']) ) {
 560 |             $server = "Vercel";
 561 |         } else if ( isset($request['resp_headers']['server']) && preg_match("/netlify/i", $request['resp_headers']['server']) ) {
 562 |             $server = "Netlify";
 563 |         } else if ( (isset($request['resp_headers']['server']) && preg_match("/envoy/i", $request['resp_headers']['server'])) || isset($request['resp_headers']['x-envoy-upstream-service-time']) ) {
 564 |             $server = "Istio Envoy";
 565 |         # Not exhaustive way to identify Google
 566 |         } else if ( preg_match("/(youtube|gstatic|doubleclick|google).*\.(com|net)\//i", $request["url"]) ) {
 567 |             $server = "Google";
 568 |         # Not exhaustive way to identify Facebook 
 569 |         } else if ( preg_match("/(facebook|fbcdn).*\.(com|net)\//i", $request["url"]) ) {
 570 |             $server = "Facebook";
 571 |         } else if ( preg_match("/s3.*amazonaws/i", $request["url"]) ) {
 572 |             $server = "AWS S3";
 573 |         } else if ( isset($request['resp_headers']['set-cookie']) && preg_match("/AWS(A|E)LB/i", $request['resp_headers']['set-cookie'], $out ) ) {
 574 |             $server = "AWS " . $out[1] . "LB";
 575 |         } else if ( preg_match("/bing\.com\//i", $request["url"]) ) {
 576 |             $server = "MS Bing";
 577 |         } else if ( preg_match("/(yahoo|ytimg)\.com\//i", $request["url"]) ) {
 578 |             $server = "Yahoo";
 579 |         } else if ( isset($request['resp_headers']['server']) && $request['resp_headers']['server'] == "UploadServer" &&  isset($request['resp_headers']['x-goog-storage-class']) ) {
 580 |             $server = "Google Storage";
 581 |         } else if ( isset($request['resp_headers']['server']) && $request['resp_headers']['server'] == "Azion IMS" ) {
 582 |             $server = "AzionCDN";
 583 |         } else if ( isset($request['resp_headers']['server']) && preg_match("/^NWS/i" , $request['resp_headers']['server'] ) ) {
 584 |             $server = "Tencent";
 585 |         } else if ( isset($request['resp_headers']['server']) && preg_match("/leasewebcdn/i" , $request['resp_headers']['server'] ) ) {
 586 |             $server = "LeaseWeb CDN";
 587 |         } else if ( isset($request['resp_headers']['server']) && preg_match("/bunnycdn/i" , $request['resp_headers']['server'] ) ) {
 588 |             $server = "BunnyCDN";
 589 |         } else if ( isset($request['resp_headers']['server']) && $request['resp_headers']['server'] == "DOSarrest" ) {
 590 |             $server = "DOSarrest";
 591 |         } else if ( isset($request['resp_headers']['server']) && preg_match("/keycdn/i", $request['resp_headers']['server']) ) {
 592 |            if ( isset($request['resp_headers']['x-edge-location']) ) {
 593 |               $edge_location = " " .$request['resp_headers']['x-edge-location'];
 594 |             } else {
 595 |               $edge_location = "";
 596 |             }
 597 |         } else if ( isset($request['resp_headers']['x-via']) && preg_match("/1\.1 (.*) \(Cdn Cache Server/i", $request['resp_headers']['x-via'], $out ) ) {
 598 |             $server = "Quantil " .  $out[1];
 599 |         } else if ( isset($request['resp_headers']['via']) && preg_match("/1\.1 (.*)squid/i", $request['resp_headers']['via'], $out ) ) {
 600 |             $server = "Squid";
 601 |         } else if ( isset($request['resp_headers']['server']) && preg_match("/myracloud/i", $request['resp_headers']['server'] )) {
 602 |             $server = "MyraCloud";
 603 |         } else if ( isset($request['resp_headers']['server']) && $request['resp_headers']['server'] == "EdgePrismSSL" ) {
 604 |             if ( isset($request['resp_headers']['x-server-name']) )
 605 |               $cache_node = $request['resp_headers']['x-server-name'];
 606 |             else
 607 |               $cache_node = "";
 608 |             $server = "Limelight " . $cache_node;
 609 |         } else if ( isset($request['resp_headers']['x-distil-cs'])  ) {
 610 |             $server = "Distil";
 611 |             $hit_or_miss = $request['resp_headers']['x-distil-cs'];
 612 |         } else if ( isset($request['resp_headers']['server']) && $request['resp_headers']['server'] == "CDN77-Turbo" ) {
 613 |             $edge_location = isset($request['resp_headers']['x-edge-location']) ? " " . htmlentities($request['resp_headers']['x-edge-location']) : "";
 614 |             $server = "CDN77" . $edge_location;
 615 |         } else if ( isset($request['resp_headers']['x-li-pop']) ) {
 616 |             $server = "LinkedIn";
 617 |         } else if ( preg_match("/^wss:\/\//", $request["url"]) ) {
 618 |             $server = "WebSockets";
 619 |         }
 620 | 
 621 |         $cms = array();
 622 |         ##############################################################################################
 623 |         # Figure out if a specific CMS or backend storage is being used
 624 |         if ( isset($request['resp_headers']['x-ah-environment']) ) {
 625 |             $cms[] = "Acquia";
 626 |         } else if ( isset($request['resp_headers']['x-vtex-cache-status']) ) {
 627 |             $cms[] = "VTEX";
 628 |         } else if ( isset($request['resp_headers']['x-drupal-cache']) ) {
 629 |             $cms[] = "Drupal";
 630 |         } else if ( isset($request['resp_headers']['x-amz-apigw-id']) ) {
 631 |             $cms[] = "AWS API GW";
 632 |         } else if ( isset($request['resp_headers']['x-shopid']) || preg_match("/shopify/", $request["url"]) ) {
 633 |             $cms[] = "Shopify";
 634 |         } else if ( isset($request['resp_headers']['server']) && preg_match("/Contentful/i", $request['resp_headers']['server'] ) ) {
 635 |             $cms[] = "Contentful";
 636 |         } else if ( isset($request['resp_headers']['x-varnish']) || isset($request['resp_headers']['via']) && preg_match("/varnish/i", $request['resp_headers']['via']) ) {
 637 |           if ( !preg_match("/fastly/i", $server) ) 
 638 |             $cms[] = "Varnish";
 639 |         # Magento version 1
 640 |         } else if ( isset($request['resp_headers']['wp-super-cache']) || isset($request['resp_headers']['x-pingback']) || preg_match("/\/wp-content\//i", $request["url"] )
 641 |                    || (isset($request['resp_headers']['link']) && preg_match("/wp-json|wp\.me/i", $request['resp_headers']['link']))) {
 642 |             $cms[] = "Wordpress";
 643 |         } else if ( isset($request['resp_headers']['set-cookie']) && preg_match("/frontend=/i", $request['resp_headers']['set-cookie'] ) ) {
 644 |             $cms[] = "Magento1";
 645 |         } else if ( isset($request['resp_headers']['server']) && preg_match("/squarespace/i", $request['resp_headers']['server'] ) ) {
 646 |             $cms[] = "Squarespace";
 647 |         } else if ( preg_match("/\/wcsstore\//i", $request["url"] ) || (isset($request['resp_headers']['server']) && preg_match("/websphere/i", $request['resp_headers']['server'] ) ) ) {
 648 |             $cms[] = "WebSphere";
 649 |         } else if ( isset($request['resp_headers']['set-cookie']) && preg_match("/Demandware/i", $request['resp_headers']['set-cookie'] ) ) {
 650 |             $cms[] = "Demandware";
 651 |         # Let's see if the request was in some form or shape backed by S3 ie. it was served by a CDN but storage was actually
 652 |         # S3. Append only if server was determined not to be AWS S3 since we don't need double output
 653 |         } else if ( ( isset($request['resp_headers']['x-amz-id-2']) || isset($request['resp_headers']['x-amz-request-id']) || (isset($request['resp_headers']['server']) && $request['resp_headers']['server'] == "AmazonS3")  ) && $server != "AWS S3" ) {
 654 |             $cms[] = "S3";
 655 |         } else if ( ( isset($request['resp_headers']['akamai-grn'])  && $frontend_ip_provider != "Akamai" ) ) {
 656 |             $cms[] = "AKAM bcknd";
 657 |         # Same with Google Cloud Storage (GCS)
 658 |         } else if ( isset($request['resp_headers']['server']) && preg_match("/cloudinary/i", $request['resp_headers']['server']) ) {
 659 |             $cms[] = "Cloudinary";
 660 |         } else if ( isset($request['resp_headers']['x-goog-generation']) && $server != "Google Storage" ) {
 661 |             $cms[] = "GCS";
 662 |         } else if ( isset($request['resp_headers']['x-ms-blob-type']) ) {
 663 |             $cms[] = "Azure BlobStorage";
 664 |         } else if ( (isset($request['resp_headers']['server']) && $request['resp_headers']['server'] == "Cowboy") || (isset($request['resp_headers']['via']) && preg_match("/vegur/i", $request['resp_headers']['via']) )) {
 665 |             $cms[] = "Heroku";
 666 |         # Yottaa may be using other CDNs for their static delivery
 667 |         } else if ( isset($request['resp_headers']['x-yottaa-optimizations']) and $server != "Yottaa" ) {
 668 |             $cms[] = "Yottaa";
 669 |         }
 670 | 
 671 |         if ( isset($request['resp_headers']['set-cookie']) && preg_match("/BIGipServer|MRHSession/i", $request['resp_headers']['set-cookie'] ) ) {
 672 |             $cms[] = "F5 BIGIP";
 673 |         } else if ( isset($request['resp_headers']['set-cookie']) && preg_match("/NSC_Qspe/i", $request['resp_headers']['set-cookie'] ) ) {
 674 |             $cms[] = "NetScaler";
 675 |         }
 676 |         
 677 |         # Bot Challenge or WAF
 678 |         if ( isset($request['resp_headers']['cf-mitigated'])  ) {
 679 |             $cms[] = "Bot Challenge";
 680 |         } else if ( preg_match("/_Incapsula_Resource/", $request['url'] )  ) {
 681 |             $cms[] = "Bot Challenge";
 682 |         } else if (  isset($request['resp_headers']['x-amzn-waf-challenge-id'] ) || isset($request['resp_headers']['x-amzn-waf-action'])  ) {
 683 |             $cms[] = "AWS WAF";
 684 |         } else if ( preg_match("/sdk\.awswaf\.com/i", $request['url'] ) ) {
 685 |           $cms[] = "AWS WAF";
 686 |         }
 687 | 
 688 |         if ( preg_match("/px\-(cdn|translator|cloud|client)|perimeterx/", $request['url'] ) ) {
 689 |             $cms[] = "Human/PX";
 690 |         }
 691 | 
 692 |         if ( preg_match("/hyva/i", $request['url'] ) ) {
 693 |           $cms[] = "Hyva";
 694 |         }
 695 | 
 696 |         if ( preg_match("/datadome\.co/", $request['url'] ) || ( isset($request['resp_headers']['set-cookie']) && preg_match("/datadome=/i", $request['resp_headers']['set-cookie'] ) ) ) {
 697 |             $cms[] = "Datadome";
 698 |         }
 699 | 
 700 |         if ( isset($request['resp_headers']['set-cookie']) && preg_match("/bm_sv=/i", $request['resp_headers']['set-cookie'] ) ) {
 701 |           $cms[] = "Akamai Botman";
 702 |       }
 703 | 
 704 | 
 705 |         if ( isset($request['resp_headers']['x-yext-site'])  ) {
 706 |             $cms[] = "Yext";
 707 |         }
 708 | 
 709 |         if ( isset($request['resp_headers']['via']) && preg_match("/google$/i", $request['resp_headers']['via']) ) {
 710 |             $cms[] = "Google Cloud";
 711 |         }
 712 | 
 713 |         if ( preg_match("/\/_next\//i", $request["url"] ) ) {
 714 |             $cms[] = "Next.js";
 715 |         }
 716 | 
 717 |         if ( isset($request['resp_headers']['x-dw-request-base-id']) || preg_match("/on\/demandware/i", $request['url']) ) {
 718 |             $cms[] = "Salesforce Commerce";
 719 |         }
 720 | 
 721 |         if ( isset($request['resp_headers']['x-akamai-transformed']) ||
 722 |            ( isset($request['resp_headers']['server-timing']) && preg_match("/^ak_/i", $request['resp_headers']['server-timing']) ) ) {
 723 |             $cms[] = "Akamai FEO";
 724 |         }
 725 | 
 726 | 
 727 | 
 728 |         if ( isset($request['resp_headers']['x-cache']) && preg_match("/function|LambdaGeneratedResponse/i", $request['resp_headers']['x-cache'] ) ) {
 729 |             $cms[] = "Lambda@Edge";
 730 |             $request['resp_headers']['x-cache'] = "Lambda";
 731 |         }
 732 | 
 733 |         # It's a tracking pixel or it's an ad
 734 |         if ( ( isset($request['resp_headers']['content-type']) && preg_match("/image\/gif/i", $request['resp_headers']['content-type']) )
 735 |           && intval($request['resp_headers']['content-length']) < 50 )  {
 736 |             $cms[] = "Tracking pixel";
 737 |         } else if ( preg_match("/googleads|pagead/", $request['url'] ) ) {
 738 |             $cms[] = "Ads";
 739 |         # Or is it a font
 740 |         } else if ( preg_match("/fonts\.googleapis\.com/", $request['url'] ) ) {
 741 |             $cms[] = "Fonts";
 742 |         } else if ( preg_match("/www\.googletagmanager\.com/", $request['url'] ) ) {
 743 |             $cms[] = "Tag Manager";
 744 |         } else if ( preg_match("/(gstatic|google)\.com\/recaptcha/", $request['url'] ) ) {
 745 |             $cms[] = "Recaptcha";
 746 |         }
 747 | 
 748 | 
 749 |         if ( count($cms) > 0 ) {
 750 |             $server .= " (" . join(",", $cms) . ")";
 751 |         }
 752 |         unset($cms);
 753 | 
 754 |         if ( $server == "" )
 755 |             $server = "Unknown";
 756 | 
 757 |         # Let's see if we can find any Cache headers and can identify whether request was a HIT or a MISS
 758 |         if ( isset($request['resp_headers']['x-cache']) && $hit_or_miss == "UNK" ) {
 759 |           # We already figured out for Akamai whether's it's a hit or miss so don't do anything
 760 |           if ( $server == "Akamai")
 761 |             continue;
 762 |           $hit_or_miss = strtoupper(preg_replace("/ from Cloudfront/i", "", $request['resp_headers']['x-cache']));
 763 | 
 764 |         }
 765 | 
 766 |         if ( $hit_or_miss != "UNK" ) {
 767 |           if ( preg_match("/(TCP_HIT|TCP_MEM_HIT|HIT$)/i", $hit_or_miss )) {
 768 |               $hit_or_miss_css = "HIT";
 769 |           } else {
 770 |               $hit_or_miss_css = "MISS";
 771 |           }
 772 |         }
 773 | 
 774 |         $haroutput .= '' .
 775 |         '' .
 776 |         '
 777 |         
 778 |         
 779 |         ";
 794 | 
 795 |     }
 796 | 
 797 |     unset($requests);
 798 |     unset($har);
 799 | 
 800 |     $haroutput .= '
#URLIPServerHit?Resp CodeDurSize (bytes)
" . $key . "" . $key . "" . substr($request["url"],0,50) . ""; 276 | 277 | $content_encoding = false; 278 | $content_type = false; 279 | 280 | # Add button that toggles response headers 281 | $haroutput .= ' 282 | 283 | "; 306 | 307 | # If response is HTTP/2 or HTTP/3 put a nice button to identify it 308 | if ( preg_match("/(h|http)\/?([2-3])/i", $request['http_version'], $out) ) 309 | $haroutput .= " "; 310 | 311 | $compressable = false; 312 | 313 | if ( preg_match("/text\/html/i", $content_type_full ) ) { 314 | $content_type = "HTML"; 315 | $compressable = true; 316 | $content_breakdown["html"] += $request["size"]; 317 | } else if ( preg_match("/text\/css/i", $content_type_full ) ){ 318 | $content_type = "CSS"; 319 | $compressable = true; 320 | $content_breakdown["css"] += $request["size"]; 321 | } else if ( preg_match("/javascript|text\/js/i", $content_type_full ) ){ 322 | $content_type = "JS"; 323 | $compressable = true; 324 | $content_breakdown["js"] += $request["size"]; 325 | } else if ( preg_match("/image\/(gif|png|jpeg|avif|webp)/i", $content_type_full, $out ) ) { 326 | $content_type = strtoupper($out[1]); 327 | $content_breakdown["images"] += $request["size"]; 328 | unset($out); 329 | } else if ( preg_match("/json/i", $content_type_full ) ) { 330 | $content_type = "JSON"; 331 | $content_breakdown["json"] += $request["size"]; 332 | $compressable = true; 333 | } else if ( preg_match("/svg/i", $content_type_full ) ) { 334 | $content_type = "SVG"; 335 | $content_breakdown["images"] += $request["size"]; 336 | $compressable = true; 337 | # WOFF is already compressed https://developers.googleblog.com/2015/02/smaller-fonts-with-woff-20-and-unicode.html 338 | } else if ( preg_match("/woff/i", $content_type_full ) ){ 339 | $content_type = "FONT"; 340 | $content_breakdown["fonts"] += $request["size"]; 341 | } else if ( preg_match("/font/i", $content_type_full ) ){ 342 | $content_type = "FONT"; 343 | $content_breakdown["fonts"] += $request["size"]; 344 | $compressable = true; 345 | } else if ( preg_match("/text\/plain/i", $content_type_full ) ){ 346 | $content_type = "TXT"; 347 | $compressable = true; 348 | } else if ( preg_match("/octet/i", $content_type_full ) ){ 349 | $content_type = "BIN"; 350 | } else if ( preg_match("/xml/i", $content_type_full ) ){ 351 | $content_type = "XML"; 352 | $compressable = true; 353 | } else { 354 | $content_type = ""; 355 | } 356 | 357 | $addl = ""; 358 | # Check if content type is compressable and only on 2k+ files 359 | if ( $request["size"] > 2048 && $compressable ) { 360 | if ( $content_encoding ) { 361 | $compressed = "yes"; 362 | $addl = "title=\"Compressed\""; 363 | } else { 364 | $compressed = "no"; 365 | $addl = "title=\"Not compressed however should be compressable\""; 366 | } 367 | } else { 368 | if ( $content_encoding ) { 369 | $compressed = "yes"; 370 | $addl = "title=\"Compressed\""; 371 | } else { 372 | $compressed = "none"; 373 | $addl = "title=\"Not compressable\""; 374 | } 375 | } 376 | 377 | if ( $content_type != "" ) 378 | $haroutput .= " "; 379 | 380 | ############################################################################################################### 381 | # Let's check for questionable practices 382 | $questionable_practice = array(); 383 | # Let's check for questionable practices 384 | if ( isset($request['resp_headers']['vary']) ) { 385 | if ( preg_match("/User-Agent/i", $request['resp_headers']['vary'] ) ) { 386 | $questionable_practice[] = "User-Agent used in Vary"; 387 | } 388 | if ( preg_match("/Cookie/i", $request['resp_headers']['vary'] ) ) { 389 | $questionable_practice[] = "Cookie used in Vary"; 390 | } 391 | } 392 | 393 | if ( sizeof($questionable_practice) > 0 ) { 394 | $haroutput .= ""; 395 | 396 | } 397 | 398 | unset($questionable_practice); 399 | 400 | ############################################################################################################### 401 | # Identify requests that Set Cookies 402 | if ( isset($request['resp_headers']['set-cookie']) ) { 403 | $haroutput .= ""; 404 | } 405 | 406 | $haroutput .= "" . $img_or_as_name . " " . $request['server_ip'] . " ' . htmlentities($server) . '' . htmlentities($hit_or_miss) . '' . htmlentities($request["resp_code"]) . '' . number_format($request["duration"], 3) . '' . number_format($request["size"]) . '' . 780 | ' '; 781 | 782 | if ( $dns_time_bar > 0 ) 783 | $haroutput .= ' '; 784 | if ( $connect_time_bar > 0 ) 785 | $haroutput .= ' '; 786 | if ( $ssl_time_bar > 0 ) 787 | $haroutput .= ' '; 788 | if ( $wait_time_bar > 0 ) 789 | $haroutput .= ' '; 790 | if ( $download_time_bar > 0 ) 791 | $haroutput .= ' '; 792 | 793 | $haroutput .= "
801 | 807 | '; 808 | 809 | $header = ' 810 | Page download time is ' . sprintf("%.3f", $total_time) . 's. Content breakdown is '; 811 | foreach ( $content_breakdown as $key => $value ) { 812 | $header .= strtoupper($key) . "=" . number_format(intval($value/1000)) . "kB "; 813 | } 814 | 815 | # Add links to click to Websocket anchors 816 | if ( count($websockets_anchors) > 0 ) { 817 | $header .= "Websockets "; 818 | foreach ( $websockets_anchors as $key => $value ) { 819 | $plus1 = $key + 1; 820 | $header .= " " . $plus1 . ""; 821 | } 822 | } 823 | 824 | # If we should cache the IP to AS info persist it to disk 825 | if ( isset($conf["cache_file"]) ) { 826 | file_put_contents($conf["cache_file"], json_encode($ip_to_as_cache)); 827 | } 828 | 829 | return $header . $haroutput; 830 | 831 | } // end of function generate_waterfall() 832 | 833 | ////////////////////////////////////////////////////////////////////////////// 834 | // Use Phantom JS to produce a JSON containing the HTTP archive and the 835 | // Image 836 | ////////////////////////////////////////////////////////////////////////////// 837 | function get_har_using_phantomjs($original_url, $include_image = true) { 838 | 839 | global $conf; 840 | 841 | $url = validate_url($original_url); 842 | 843 | if ( $url === FALSE ) { 844 | print json_encode( array( "error" => "URL is not valid" ) ); 845 | exit(1); 846 | } 847 | 848 | /////////////////////////////////////////////////////////////////////////// 849 | // Can't supply suffix for the temp file therefore we'll first create the 850 | // tempname then rename it with .png extension since that is what PhantomJS 851 | // expects 852 | $tmpfname1 = tempnam("/tmp", "phantom"); 853 | $tmpfname = $tmpfname1 . ".png"; 854 | rename($tmpfname1, $tmpfname); 855 | 856 | $command = $conf['phantomjs_exec'] . " '" . $url . "' " . $tmpfname; 857 | if ( $conf['debug'] == 1 ) 858 | error_log($command); 859 | exec($command, $output_array, $ret_value); 860 | 861 | // For some reason you may get DEBUG statements in the output e.g. ** (:32751): DEBUG: NP_Initialize\ 862 | // Let's get rid of them. Look for first occurence of { 863 | foreach ( $output_array as $key => $line ) { 864 | if ( preg_match("/^{/", $line) ) { 865 | break; 866 | } else 867 | $output_array[$key] = ""; 868 | 869 | } 870 | 871 | # Same thing at the end we may end up with stuff like this 872 | # Unsafe JavaScript attempt to access frame with URL about:blank from frame 873 | $found_closing_curly_brace = false; 874 | 875 | foreach ( $output_array as $key => $line ) { 876 | if ( ! $found_closing_curly_brace && preg_match("/^}/", $line) ) { 877 | $found_closing_curly_brace = 1; 878 | } else if ( $found_closing_curly_brace ) 879 | $output_array[$key] = ""; 880 | } 881 | 882 | //////////////////////////////////////////////////////////////////////////// 883 | // Phantom JS exited normally. It doesn't mean URL properly loaded just 884 | // that Phantom didn't fail for other reasons ie. can't execute 885 | //////////////////////////////////////////////////////////////////////////// 886 | if ( $ret_value == 0 ) { 887 | $output = join("\n", $output_array); 888 | $har = json_decode($output, TRUE); 889 | 890 | // If har_array is null JSON could not be parsed 891 | if ( $har === NULL ) { 892 | 893 | $out = array( "success" => 0, "error_message" => "PhantomJS ran successfully however output couldn't be parsed."); 894 | 895 | } else { 896 | 897 | if ( filesize($tmpfname) != 0 && $include_image ) 898 | $imgbinary = base64_encode(fread(fopen($tmpfname, "r"), filesize($tmpfname))); 899 | else 900 | $imgbinary = false; 901 | unlink($tmpfname); 902 | 903 | $out = array ( "har" => $har, "screenshot" => $imgbinary, "success" => 1 ); 904 | 905 | } 906 | 907 | // If har_array is null JSON could not be parsed 908 | return $out; 909 | 910 | } else { 911 | 912 | return array( "success" => 0, "error_message" => "PhantomJS exited abnormally. Please check your webserver error log" ); 913 | } 914 | 915 | } 916 | 917 | ############################################################################################# 918 | # Get DNS record 919 | ############################################################################################# 920 | function get_dns_record($dns_name, $query_type = "A", $include_timing = false, $include_resolver = false) { 921 | 922 | $start_time = microtime(TRUE); 923 | switch ( $query_type ) { 924 | case "A": 925 | $record_type = DNS_A; 926 | break; 927 | case "CNAME": 928 | $record_type = DNS_CNAME; 929 | break; 930 | case "AAAA": 931 | $record_type = DNS_AAAA; 932 | break; 933 | case "MX": 934 | $record_type = DNS_MX; 935 | break; 936 | case "SOA": 937 | $record_type = DNS_SOA; 938 | break; 939 | case "TXT": 940 | $record_type = DNS_TXT; 941 | break; 942 | default: 943 | $record_type = DNS_A; 944 | 945 | } 946 | 947 | $result = dns_get_record($dns_name, $record_type); 948 | 949 | if ( $result === false ) { 950 | return array( "records" => array(), "resolver_ip" => "DNS resolution failure" ); 951 | } 952 | 953 | # Calculate query time 954 | $query_time = microtime(TRUE) - $start_time; 955 | 956 | $response = array(); 957 | 958 | $response["records"] = $result; 959 | 960 | # Find out what is the IP address of my DNS resolve. We can obtain that by whoami.fastly.net 961 | if ( $include_resolver ) { 962 | $resolver_ip_record = dns_get_record("whoami.fastly.net", DNS_A); 963 | $response["resolver_ip"] = isset($resolver_ip_record[0]['ip']) ? $resolver_ip_record[0]['ip'] : "Unknown"; 964 | } 965 | 966 | if ( $include_timing ) { 967 | $response["query_time"] = $query_time; 968 | } 969 | 970 | return $response; 971 | 972 | } 973 | 974 | 975 | ############################################################################################ 976 | # Format IP address to print out 977 | ############################################################################################ 978 | if ( !function_exists("format_ip_address") ) { 979 | 980 | function format_ip_address($ip) { 981 | return($ip); 982 | } 983 | 984 | } 985 | 986 | ############################################################################################# 987 | # Get DNS record 988 | ############################################################################################# 989 | function print_dns_results($results) { 990 | 991 | global $conf; 992 | 993 | if ( count($results) > 0 ) { 994 | 995 | # Find max time 996 | $max_time = 0; 997 | 998 | foreach ( $results as $site_id => $result ) { 999 | if ( $result['query_time'] > $max_time ) 1000 | $max_time = $result['query_time']; 1001 | } 1002 | ?> 1003 | 1004 | 1005 | 1006 | 1007 | 1008 | 1009 | 1010 | 1011 | 1012 | 1013 | 1014 | 1015 | 1016 | 1021 | $result ) { 1023 | 1024 | if ( $site_id == "-1") 1025 | $site_name = "Local"; 1026 | else 1027 | $site_name = $conf['remotes'][$site_id]['name']; 1028 | 1029 | if ( ! isset($result['records']) or count($result['records']) == 0 ) { 1030 | print " 1031 | 1032 | 1033 | 1034 | 1035 | 1036 | 1037 | "; 1038 | continue; 1039 | } 1040 | 1041 | $query_time_in_ms = round($result['query_time'] * 1000); 1042 | $resolver_ip = preg_match("/^74.125/", $result['resolver_ip']) ? $result['resolver_ip'] . " - Google DNS" : $result['resolver_ip']; 1043 | 1044 | foreach( $result['records'] as $index => $record ) { 1045 | switch ( $record['type'] ) { 1046 | case "A": 1047 | $record_output = format_ip_address($record['ip']) . " " . ip_to_as_image_or_text($record['ip']); 1048 | break; 1049 | case "AAAA": 1050 | $record_output = format_ip_address($record['ipv6']); 1051 | break; 1052 | case "CNAME": 1053 | $record_output = htmlentities($record['target']); 1054 | break; 1055 | case "TXT": 1056 | $record_output = htmlentities($record['txt']); 1057 | break; 1058 | case "MX": 1059 | $record_output = $record['pri'] . " " . htmlentities($record['target']); 1060 | break; 1061 | case "SOA": 1062 | $record_output = htmlentities($record['mname']) . " " . htmlentities($record['rname']) 1063 | . " Serial: " . htmlentities($record['serial']) . " Rfrsh: " . htmlentities($record['refresh']) 1064 | . " Retry/NegTTL: " . htmlentities($record['retry']) . " Expire: " . htmlentities($record['expire']) 1065 | . " MinTTL: " . htmlentities($record['minimum-ttl']); 1066 | break; 1067 | default: 1068 | $record_output = "No data. Maybe query type is unknown"; 1069 | } 1070 | 1071 | 1072 | print " 1073 | 1074 | 1075 | 1076 | 1077 | 1078 | 1079 | "; 1080 | print ""; 1083 | print ""; 1084 | } 1085 | } 1086 | ?> 1087 | 1088 |
Site NameHostnameResolver IPTTLTypeRecordQuery Time (ms)
" . $site_name . "No ResultsNANANANo Results0
" . $site_name . "" . $record['host'] . "" . $resolver_ip . " " . ip_to_as_image_or_text($resolver_ip) ."" . $record['ttl'] . "" . $record['type'] . "" . $record_output . ""; 1081 | print '' .$query_time_in_ms .''; 1082 | print "
1089 | '; 1144 | } else if ( $ip_details["as_number"] == "AS15169" ) { 1145 | $img_or_as_name = ''; 1146 | } else if ( $ip_details["as_number"] == "AS20940" || $ip_details["as_number"] == "AS16625" ) { 1147 | $img_or_as_name = ''; 1148 | } else if ( $ip_details["as_number"] == "AS54113" ) { 1149 | $img_or_as_name = ''; 1150 | } else if ( $ip_details["as_number"] == "AS8068" ) { 1151 | $img_or_as_name = ''; 1152 | } else if ( $ip_details["as_number"] == "AS8075" ) { 1153 | $img_or_as_name = ''; 1154 | } else if ( $ip_details["as_number"] == "AS32934" ) { 1155 | $img_or_as_name = ''; 1156 | } else if ( in_array($ip_details["as_number"], array("AS13335", "AS209242", "AS139242" ) ) ) { 1157 | $img_or_as_name = ''; 1158 | } else if ( $ip_details["as_number"] == "AS396982" ) { 1159 | $img_or_as_name = ''; 1160 | } else if ( $ip_details["as_number"] == "AS15133" ) { 1161 | $img_or_as_name = ''; 1162 | } else if ( $ip_details["as_number"] == "AS135340" || $ip_details["as_number"] == "AS133165" || $ip_details["as_number"] == "AS14061" ) { 1163 | $img_or_as_name = ''; 1164 | } else if ( $ip_details["as_number"] == "AS20446" ) { 1165 | $img_or_as_name = ''; 1166 | } else if ( $ip_details["as_number"] == "AS19551" ) { 1167 | $img_or_as_name = ''; 1168 | } else { 1169 | $img_or_as_name = $ip_details["as_name"]; 1170 | } 1171 | 1172 | return($img_or_as_name); 1173 | 1174 | } 1175 | 1176 | ############################################################################################# 1177 | # Get Curl timings 1178 | ############################################################################################# 1179 | function get_curl_timings_with_headers(&$request) { 1180 | 1181 | global $conf; 1182 | 1183 | $url = validate_url($request['url']); 1184 | 1185 | if ( $url === FALSE ) { 1186 | print json_encode( array( "error" => "URL is not valid" ) ); 1187 | exit(1); 1188 | } 1189 | 1190 | $url_parts = parse_url($url); 1191 | 1192 | $curly = curl_init(); 1193 | curl_setopt($curly, CURLOPT_HEADER, 1); 1194 | curl_setopt($curly, CURLOPT_TIMEOUT, $request['timeout']); 1195 | curl_setopt($curly, CURLOPT_RETURNTRANSFER, 1); 1196 | if ( $request['protocol'] == "http1.1" ) { 1197 | curl_setopt($curly, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1); 1198 | } 1199 | if ( $request['method'] != "GET" ) { 1200 | curl_setopt($curly, CURLOPT_CUSTOMREQUEST, $request['method']); 1201 | } 1202 | if ( $conf['allow_proxy_for_url_check'] && isset($request['http_proxy']) ) { 1203 | curl_setopt($curly, CURLOPT_PROXY, $request['http_proxy']); 1204 | } 1205 | $dest_port = "443"; 1206 | switch ( $url_parts['scheme'] ) { 1207 | case "http": 1208 | curl_setopt($curly, CURLOPT_PROTOCOLS, CURLPROTO_HTTP); 1209 | curl_setopt($curly, CURLOPT_REDIR_PROTOCOLS, CURLPROTO_HTTP); 1210 | $dest_port = "80"; 1211 | break; 1212 | case "https": 1213 | curl_setopt($curly, CURLOPT_PROTOCOLS, CURLPROTO_HTTPS); 1214 | curl_setopt($curly, CURLOPT_REDIR_PROTOCOLS, CURLPROTO_HTTPS); 1215 | break; 1216 | default: 1217 | die("

Invalid protocol supplied. You need either http:// or https://

"); 1218 | } 1219 | 1220 | # Set the Resolve Flag if the override IP is set 1221 | if ( isset($request['override_ip']) ) { 1222 | $override_array[] = $url_parts["host"] . ":" . $dest_port . ":" . $request['override_ip']; 1223 | curl_setopt($curly, CURLOPT_RESOLVE, $override_array); 1224 | } 1225 | 1226 | if ( !in_array($request['method'], array("GET", "HEAD") ) ) { 1227 | $request_headers[] = "content-length: " . strlen($request['payload']); 1228 | curl_setopt($curly, CURLOPT_POSTFIELDS, $request['payload']); 1229 | } 1230 | 1231 | curl_setopt($curly, CURLOPT_ENCODING , "gzip"); 1232 | curl_setopt($curly, CURLOPT_HTTPHEADER, $request['request_headers'] ); 1233 | curl_setopt($curly, CURLOPT_URL, $url); 1234 | 1235 | curl_exec($curly); 1236 | 1237 | $response = curl_multi_getcontent($curly); 1238 | 1239 | if(curl_errno($curly)) { 1240 | $results = array("return_code" => 400, "response_size" => 0, "content_type" => "none", "error_message" => curl_error($curly) ); 1241 | } else { 1242 | # If we are using a HTTP proxy there will be another entry 1243 | if ( $conf['allow_proxy_for_url_check'] && isset($request['http_proxy']) && $request['http_proxy'] != "" ) { 1244 | list($proxy_header, $header, $content) = explode("\r\n\r\n", $response); 1245 | } else { 1246 | list($header, $content) = explode("\r\n\r\n", $response); 1247 | } 1248 | 1249 | $info = curl_getinfo($curly); 1250 | $results = array( 1251 | "return_code" => $info['http_code'], 1252 | "error_message" => "", 1253 | "content_type" => $info['content_type'], 1254 | "response_size" => $info['size_download'], 1255 | "header_size" => $info['header_size'], 1256 | "headers_string" => $header, 1257 | "response_body" => $content, 1258 | "md5" => md5($content), 1259 | "dns_lookuptime" => $info['namelookup_time'], 1260 | "connect_time" => $info['connect_time'] - $info['namelookup_time'], 1261 | "pretransfer_time" => $info['pretransfer_time'] - $info['connect_time'], 1262 | "starttransfer_time" => $info['starttransfer_time'] - $info['pretransfer_time'], 1263 | "transfer_time" => $info['total_time'] - $info['starttransfer_time'], 1264 | "total_time" => $info['total_time'], 1265 | "primary_ip" => isset($info['primary_ip']) ? $info['primary_ip']: "Not avail" 1266 | ); 1267 | } 1268 | 1269 | curl_close($curly); 1270 | return $results; 1271 | 1272 | } 1273 | 1274 | ############################################################################################# 1275 | # Print cURL results 1276 | ############################################################################################# 1277 | function print_url_results($records, $request = array()) { 1278 | 1279 | global $conf; 1280 | ksort($request); 1281 | 1282 | print '
1283 |

Power User Input

1284 |
1285 |
'; 1286 | 1287 | if ( $conf['show_url_timing_bar'] ) { 1288 | print "
"; 1289 | print 'DNS time'; 1290 | print 'Conn Time'; 1291 | print 'Request Sent'; 1292 | print 'TTFB wait'; 1293 | print 'Transfer Time'; 1294 | print "

"; 1295 | } 1296 | 1297 | print " 1298 | 1299 | 1300 | 1301 | 1302 | 1303 | "; 1304 | 1305 | if ( $conf['cdn_detection'] ) { 1306 | print " 1307 | "; 1308 | } 1309 | 1310 | print " 1311 | 1312 | 1313 | "; 1314 | if ( $conf['show_url_timing_bar'] ) { 1315 | print " 1316 | 1317 | 1318 | 1319 | 1320 | "; 1321 | } 1322 | print ""; 1323 | 1324 | foreach ( $records as $id => $record ) { 1325 | # Try to identify CDNs 1326 | if ( $conf['cdn_detection'] ) { 1327 | 1328 | if ( preg_match("/.*X-Served-By: (.*)\n/i", $record['headers_string'], $out) ) { 1329 | $xservedby = $out[1]; 1330 | } else { 1331 | $xservedby = "NA"; 1332 | } 1333 | 1334 | if ( preg_match("/.*X-Cache: (.*)\n/", $record['headers_string'], $out) ) { 1335 | $cache_hit = trim($out[1]); 1336 | } else { 1337 | $cache_hit = "NA"; 1338 | } 1339 | 1340 | } 1341 | 1342 | if ( $id == -1 ) { 1343 | $site_name = "Local"; 1344 | } else { 1345 | $site_name = $conf['remotes'][$id]['name']; 1346 | 1347 | } 1348 | print ""; 1349 | 1350 | if ( $record['error_message'] != "" ) { 1351 | print ""; 1352 | print ""; 1353 | print ""; 1354 | print ""; 1355 | print ""; 1356 | print ""; 1357 | print ""; 1358 | 1359 | } else { 1360 | # Sort the response headers by name 1361 | $resp_headers = explode("\r\n", $record['headers_string']); 1362 | sort($resp_headers); 1363 | 1364 | print ""; 1370 | 1371 | print ""; 1379 | 1380 | $gzip = preg_match("/Content-Encoding: (gzip|br)/i", $record['headers_string']) ? "Yes" : "No"; 1381 | 1382 | print ""; 1383 | if ( $conf['cdn_detection'] ) { 1384 | $cache_hit_styling = preg_match("/HIT$/", $cache_hit ) ? "x-cache-HIT" : "x-cache-MISS"; 1385 | 1386 | print "" . 1387 | ""; 1388 | } 1389 | print "" . 1390 | "" . 1391 | "" . 1392 | ""; 1393 | 1394 | if ( $conf['show_url_timing_bar'] ) { 1395 | "" . 1396 | "" . 1397 | "" . 1398 | "" . 1399 | "" . 1400 | ""; 1401 | } 1402 | 1403 | } 1404 | 1405 | print ""; 1406 | 1407 | # Make the bar graph of response 1408 | if ( $conf['show_url_timing_bar'] ) { 1409 | print ""; 1420 | 1421 | } // foreach ( $records as $record ) { 1422 | 1423 | print "
RemoteResponse headersResponse BodyResolved IPX-Served-ByCache Hit?CompressedResp codeResp sizeHdr sizeDNS timeConnect TimeRequest SentTime to First ByteTx TimeTotal Time
" . $site_name . "" . $record['error_message'] . "" . $record['return_code'] . "  
"; 1365 | print "
"; 1366 | 1367 | print "
" . htmlentities(join("\n", $resp_headers)) ;
1368 |       print "
"; 1369 | print "
"; 1372 | print "
". 1373 | "
"; 1374 | 1375 | print "
"; 1376 | print "
" . htmlentities($record['response_body']) ;
1377 |       print "
"; 1378 | print "
" . $record['primary_ip'] . "
" . ip_to_as_image_or_text($record['primary_ip']) . "
" . $xservedby . "" . $cache_hit . "" . $gzip . "" . $record['return_code'] . "" . number_format($record['response_size']) . "" . number_format($record['header_size']) . "" . number_format($record['dns_lookuptime'],3) . "" . number_format($record['connect_time'],3) . "" . number_format($record['pretransfer_time'], 3) . "" . number_format($record['starttransfer_time'], 3) . "" . number_format($record['transfer_time'], 3) . "" . number_format($record['total_time'], 3) . "
"; 1410 | print ""; 1411 | print ' '; 1412 | print ' '; 1413 | print ' '; 1414 | print ' '; 1415 | print ' '; 1416 | print ""; 1417 | } 1418 | 1419 | print "
"; 1424 | 1425 | } 1426 | --------------------------------------------------------------------------------