From 7b1d8995e1b1764a3967dad0e73df6f60ea3bbb8 Mon Sep 17 00:00:00 2001 From: adolfintel Date: Wed, 30 Jan 2019 10:16:28 +0100 Subject: [PATCH 01/10] Implemented ID obfuscation in telemetry.php and results/index.php --- .gitignore | 3 +-- example-telemetry-resultSharing.html | 4 ++-- results/index.php | 2 ++ speedtest_worker.js | 13 +++++------ speedtest_worker.min.js | 2 +- telemetry/idObfuscation.php | 34 ++++++++++++++++++++++++++++ telemetry/telemetry.php | 10 +++++--- telemetry/telemetry_settings.php | 1 + 8 files changed, 54 insertions(+), 15 deletions(-) create mode 100644 telemetry/idObfuscation.php diff --git a/.gitignore b/.gitignore index c0f3bef..13d4e88 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1 @@ -ugly.bat -wishlist.txt +telemetry/idObfuscation_salt.php diff --git a/example-telemetry-resultSharing.html b/example-telemetry-resultSharing.html index 7a0e743..eb89c7b 100644 --- a/example-telemetry-resultSharing.html +++ b/example-telemetry-resultSharing.html @@ -190,8 +190,8 @@ function startStop(){ if(status==4){ //if testId is present, show sharing panel, otherwise do nothing try{ - var testId=Number(data.testId); - if(!isNaN(testId)){ + var testId=data.testId; + if(testId!=null){ var shareURL=window.location.href.substring(0,window.location.href.lastIndexOf("/"))+"/results/?id="+testId; I("resultsImg").src=shareURL; I("resultsURL").value=shareURL; diff --git a/results/index.php b/results/index.php index 2e6a1da..475616e 100644 --- a/results/index.php +++ b/results/index.php @@ -52,6 +52,8 @@ $WATERMARK_TEXT="HTML5 Speedtest"; $id=$_GET["id"]; include_once('../telemetry/telemetry_settings.php'); +require '../telemetry/idObfuscation.php'; +if($enable_id_obfuscation) $id=deobfuscateId($id); $conn=null; $q=null; $ispinfo=null; $dl=null; $ul=null; $ping=null; $jit=null; if($db_type=="mysql"){ diff --git a/speedtest_worker.js b/speedtest_worker.js index 87077a5..3c0219a 100644 --- a/speedtest_worker.js +++ b/speedtest_worker.js @@ -173,7 +173,7 @@ this.addEventListener("message", function(e) { if (settings.telemetry_level > 0) sendTelemetry(function(id) { testStatus = 4; - if (id != -1) testId = id; + if (id != null) testId = id; }); else testStatus = 4; return; @@ -662,15 +662,14 @@ function sendTelemetry(done) { var parts = xhr.responseText.split(" "); if (parts[0] == "id") { try { - var id = Number(parts[1]); - if (!isNaN(id)) done(id); - else done(-1); + var id = parts[1]; + done(id); } catch (e) { - done(-1); + done(null); } - } else done(-1); + } else done(null); } catch (e) { - done(-1); + done(null); } }; xhr.onerror = function() { diff --git a/speedtest_worker.min.js b/speedtest_worker.min.js index e27e9ff..2ebd28e 100644 --- a/speedtest_worker.min.js +++ b/speedtest_worker.min.js @@ -1 +1 @@ -var testStatus=-1,dlStatus="",ulStatus="",pingStatus="",jitterStatus="",clientIp="",dlProgress=0,ulProgress=0,pingProgress=0,testId="noID",log="";function tlog(s){2<=settings.telemetry_level&&(log+=Date.now()+": "+s+"\n")}function tverb(s){3<=settings.telemetry_level&&(log+=Date.now()+": "+s+"\n")}function twarn(s){2<=settings.telemetry_level&&(log+=Date.now()+" WARN: "+s+"\n"),console.warn(s)}var settings={test_order:"IP_D_U",time_ul_max:15,time_dl_max:15,time_auto:!0,time_ulGraceTime:3,time_dlGraceTime:1.5,count_ping:10,url_dl:"garbage.php",url_ul:"empty.php",url_ping:"empty.php",url_getIp:"getIP.php",getIp_ispInfo:!0,getIp_ispInfo_distance:"km",xhr_dlMultistream:10,xhr_ulMultistream:3,xhr_multistreamDelay:300,xhr_ignoreErrors:1,xhr_dlUseBlob:!1,xhr_ul_blob_megabytes:20,garbagePhp_chunkSize:20,enable_quirks:!0,ping_allowPerformanceApi:!0,overheadCompensationFactor:1.06,useMebibits:!1,telemetry_level:0,url_telemetry:"telemetry/telemetry.php",telemetry_extra:""},xhr=null,interval=null,test_pointer=0;function url_sep(url){return url.match(/\?/)?"&":"?"}function clearRequests(){if(tverb("stopping pending XHRs"),xhr){for(var i=0;i=settings.test_order.length)0settings.time_dl_max||failed)&&((failed||isNaN(dlStatus))&&(dlStatus="Fail"),clearRequests(),clearInterval(interval),dlProgress=1,tlog("dlTest: "+dlStatus+", took "+((new Date).getTime()-startT)+"ms"),done())}else t>1e3*settings.time_dlGraceTime&&(0settings.time_ul_max||failed)&&((failed||isNaN(ulStatus))&&(ulStatus="Fail"),clearRequests(),clearInterval(interval),ulProgress=1,tlog("ulTest: "+ulStatus+", took "+((new Date).getTime()-startT)+"ms"),done())}else t>1e3*settings.time_ulGraceTime&&(0=settings.test_order.length)0settings.time_dl_max||failed)&&((failed||isNaN(dlStatus))&&(dlStatus="Fail"),clearRequests(),clearInterval(interval),dlProgress=1,tlog("dlTest: "+dlStatus+", took "+((new Date).getTime()-startT)+"ms"),done())}else t>1e3*settings.time_dlGraceTime&&(0settings.time_ul_max||failed)&&((failed||isNaN(ulStatus))&&(ulStatus="Fail"),clearRequests(),clearInterval(interval),ulProgress=1,tlog("ulTest: "+ulStatus+", took "+((new Date).getTime()-startT)+"ms"),done())}else t>1e3*settings.time_ulGraceTime&&(0>1)&0xFFFFFFFF)|(($id&0x00000001)<<31); + $salt=(($salt<<1)&0xFFFFFFFF)|(($salt&0xA0000000)>>31); + } + return $id; +} +function obfuscateId($id){ + return base_convert(obfdeobf($id),10,36); +} +function deobfuscateId($id){ + return obfdeobf(base_convert($id,36,10)); +} + +//IMPORTANT: DO NOT ADD ANYTHING BELOW THE PHP CLOSING TAG, NOT EVEN EMPTY LINES! +?> \ No newline at end of file diff --git a/telemetry/telemetry.php b/telemetry/telemetry.php index ca1a8ad..58825fa 100644 --- a/telemetry/telemetry.php +++ b/telemetry/telemetry.php @@ -1,5 +1,6 @@ bind_param("ssssssssss",$ip,$ispinfo,$extra,$ua,$lang,$dl,$ul,$ping,$jitter,$log) or die("3"); $stmt->execute() or die("4"); $stmt->close() or die("5"); - echo "id ".$conn->insert_id; + $id=$conn->insert_id; + echo "id ".($enable_id_obfuscation?obfuscateId($id):$id); $conn->close() or die("6"); }elseif($db_type=="sqlite"){ @@ -41,7 +43,8 @@ if($db_type=="mysql"){ "); $stmt = $conn->prepare("INSERT INTO speedtest_users (ip,ispinfo,extra,ua,lang,dl,ul,ping,jitter,log) VALUES (?,?,?,?,?,?,?,?,?,?)") or die("2"); $stmt->execute(array($ip,$ispinfo,$extra,$ua,$lang,$dl,$ul,$ping,$jitter,$log)) or die("3"); - echo "id ".$conn->lastInsertId(); + $id=$conn->lastInsertId(); + echo "id ".($enable_id_obfuscation?obfuscateId($id):$id); $conn = null; }elseif($db_type=="postgresql"){ // Prepare connection parameters for db connection @@ -53,7 +56,8 @@ if($db_type=="mysql"){ $conn = new PDO("pgsql:$conn_host;$conn_db;$conn_user;$conn_password") or die("1"); $stmt = $conn->prepare("INSERT INTO speedtest_users (ip,ispinfo,extra,ua,lang,dl,ul,ping,jitter,log) VALUES (?,?,?,?,?,?,?,?,?,?)") or die("2"); $stmt->execute(array($ip,$ispinfo,$extra,$ua,$lang,$dl,$ul,$ping,$jitter,$log)) or die("3"); - echo "id ".$conn->lastInsertId(); + $id=$conn->lastInsertId(); + echo "id ".($enable_id_obfuscation?obfuscateId($id):$id); $conn = null; } else die("-1"); diff --git a/telemetry/telemetry_settings.php b/telemetry/telemetry_settings.php index 04355e7..f2c3522 100644 --- a/telemetry/telemetry_settings.php +++ b/telemetry/telemetry_settings.php @@ -2,6 +2,7 @@ $db_type="mysql"; //Type of db: "mysql", "sqlite" or "postgresql" $stats_password="PASSWORD"; //password to login to stats.php. Change this!!! +$enable_id_obfuscation=true; //if set to true, test IDs will be obfuscated to prevent users from guessing URLs of other tests // Sqlite3 settings $Sqlite_db_file = "../../telemetry.sql"; From 7545d2544cd769e2f884d62767901191da642674 Mon Sep 17 00:00:00 2001 From: adolfintel Date: Wed, 30 Jan 2019 10:24:15 +0100 Subject: [PATCH 02/10] Implemented id obfuscation in telemetry/stats.php --- telemetry/stats.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/telemetry/stats.php b/telemetry/stats.php index 0d21b21..13b390e 100644 --- a/telemetry/stats.php +++ b/telemetry/stats.php @@ -54,6 +54,7 @@ header('Content-Type: text/html; charset=utf-8');

HTML5 Speedtest - Stats

Please set $stats_password in telemetry_settings.php to enable access. @@ -87,9 +88,10 @@ if($stats_password=="PASSWORD"){ $q=null; if($_GET["op"]=="id"&&!empty($_POST["id"])){ $id=$_POST["id"]; + if($enable_id_obfuscation) $id=deobfuscateId($id); if($db_type=="mysql"){ $q=$conn->prepare("select id,timestamp,ip,ispinfo,ua,lang,dl,ul,ping,jitter,log,extra from speedtest_users where id=?"); - $q->bind_param("i",$_POST["id"]); + $q->bind_param("i",$id); $q->execute(); $q->store_result(); $q->bind_result($id,$timestamp,$ip,$ispinfo,$ua,$lang,$dl,$ul,$ping,$jitter,$log,$extra); @@ -129,7 +131,7 @@ if($stats_password=="PASSWORD"){ }else die(); ?> - + From c7a20897d43e0a779f0e69222c2f3d02a6d7f70f Mon Sep 17 00:00:00 2001 From: adolfintel Date: Wed, 30 Jan 2019 10:47:45 +0100 Subject: [PATCH 03/10] Fixed a bug with working directory in idObfuscation.php --- telemetry/idObfuscation.php | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/telemetry/idObfuscation.php b/telemetry/idObfuscation.php index 3ea16ca..a86726b 100644 --- a/telemetry/idObfuscation.php +++ b/telemetry/idObfuscation.php @@ -1,15 +1,16 @@ Date: Wed, 30 Jan 2019 12:30:59 +0100 Subject: [PATCH 04/10] Updated doc.md --- doc.md | 11 ++++++++++- speedtest_worker.js | 2 +- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/doc.md b/doc.md index 56e73ec..2e21627 100644 --- a/doc.md +++ b/doc.md @@ -148,7 +148,7 @@ The response from the worker is a JSON string containing these entries: * __dlProgress__: the progress of the download test as a number between 0 and 1 * __ulProgress__: the progress of the upload test as a number between 0 and 1 * __pingProgress__: the progress of the ping+jitter test as a number between 0 and 1 -* __testId__: when telemetry is active, this is the ID of the test as an integer. This string is 'noID' until the test is finished (testState 4). This ID is used for results sharing +* __testId__: when telemetry is active, this is the ID of the test in the database. This string is null until the test is finished (testState 4), or if telemetry encounters an error. This ID is used for results sharing ### Starting the test To start the test with the default settings, which is usually the best choice, send the start command to the worker: @@ -381,6 +381,15 @@ This feature requires Telemetry to be enabled, and FreeType2 must be installed i __Important:__ This feature relies on PHP functions `imagefttext` and `imageftbbox` that are well known for being problematic. The most common problem is that they can't find the font files and therefore nothing is drawn. This problem is metioned [here](http://php.net/manual/en/function.imagefttext.php) and was experienced by a lot of users. +#### Obfuscated Test IDs +By default, the telemetry generates a progressive ID for each test. Even if no sensitive information is leaked, you might not want users to be able to guess other test IDs. To avoid this, you can turn on ID obfuscation, which turns IDs into a reversible hash, much like YouTube video IDs. + +To enable this feature, edit `telemetry_settings.php` and set `enable_id_obfuscation` to true. + +From now on, all test IDs will be obfuscated using a unique salt. The IDs in the database are still progressive, but users will only know their obfuscated versions and won't be able to easily guess other IDs. + +__Important:__ Make sure PHP is allowed to write to the `telemetry` folder. The salt will be stored in a file called `idObfuscation_salt.php`. This file is like a private key, don't lose it or you won't be able to deobfuscate IDs anymore! + ### Seeing the results A basic front-end for visualizing and searching tests by ID is available in `telemetry/stats.php`. diff --git a/speedtest_worker.js b/speedtest_worker.js index 3c0219a..eeddaa8 100644 --- a/speedtest_worker.js +++ b/speedtest_worker.js @@ -15,7 +15,7 @@ var clientIp = ""; // client's IP address as reported by getIP.php var dlProgress = 0; //progress of download test 0-1 var ulProgress = 0; //progress of upload test 0-1 var pingProgress = 0; //progress of ping+jitter test 0-1 -var testId = "noID"; //test ID (sent back by telemetry if used, the string 'noID' otherwise) +var testId = null; //test ID (sent back by telemetry if used, null otherwise) var log = ""; //telemetry log function tlog(s) { From 2a4b5fa9f69c0de712fa484812c35bcaad105955 Mon Sep 17 00:00:00 2001 From: adolfintel Date: Wed, 30 Jan 2019 12:32:02 +0100 Subject: [PATCH 05/10] ID obfuscation: default off --- telemetry/telemetry_settings.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/telemetry/telemetry_settings.php b/telemetry/telemetry_settings.php index f2c3522..9d152a0 100644 --- a/telemetry/telemetry_settings.php +++ b/telemetry/telemetry_settings.php @@ -2,7 +2,7 @@ $db_type="mysql"; //Type of db: "mysql", "sqlite" or "postgresql" $stats_password="PASSWORD"; //password to login to stats.php. Change this!!! -$enable_id_obfuscation=true; //if set to true, test IDs will be obfuscated to prevent users from guessing URLs of other tests +$enable_id_obfuscation=false; //if set to true, test IDs will be obfuscated to prevent users from guessing URLs of other tests // Sqlite3 settings $Sqlite_db_file = "../../telemetry.sql"; From 7d2d62b5ef8ad4dfc09a488b0544f8b66e8c1800 Mon Sep 17 00:00:00 2001 From: adolfintel Date: Wed, 30 Jan 2019 12:32:40 +0100 Subject: [PATCH 06/10] Updated .min.js --- speedtest_worker.min.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/speedtest_worker.min.js b/speedtest_worker.min.js index 2ebd28e..0f24050 100644 --- a/speedtest_worker.min.js +++ b/speedtest_worker.min.js @@ -1 +1 @@ -var testStatus=-1,dlStatus="",ulStatus="",pingStatus="",jitterStatus="",clientIp="",dlProgress=0,ulProgress=0,pingProgress=0,testId="noID",log="";function tlog(s){2<=settings.telemetry_level&&(log+=Date.now()+": "+s+"\n")}function tverb(s){3<=settings.telemetry_level&&(log+=Date.now()+": "+s+"\n")}function twarn(s){2<=settings.telemetry_level&&(log+=Date.now()+" WARN: "+s+"\n"),console.warn(s)}var settings={test_order:"IP_D_U",time_ul_max:15,time_dl_max:15,time_auto:!0,time_ulGraceTime:3,time_dlGraceTime:1.5,count_ping:10,url_dl:"garbage.php",url_ul:"empty.php",url_ping:"empty.php",url_getIp:"getIP.php",getIp_ispInfo:!0,getIp_ispInfo_distance:"km",xhr_dlMultistream:10,xhr_ulMultistream:3,xhr_multistreamDelay:300,xhr_ignoreErrors:1,xhr_dlUseBlob:!1,xhr_ul_blob_megabytes:20,garbagePhp_chunkSize:20,enable_quirks:!0,ping_allowPerformanceApi:!0,overheadCompensationFactor:1.06,useMebibits:!1,telemetry_level:0,url_telemetry:"telemetry/telemetry.php",telemetry_extra:""},xhr=null,interval=null,test_pointer=0;function url_sep(url){return url.match(/\?/)?"&":"?"}function clearRequests(){if(tverb("stopping pending XHRs"),xhr){for(var i=0;i=settings.test_order.length)0settings.time_dl_max||failed)&&((failed||isNaN(dlStatus))&&(dlStatus="Fail"),clearRequests(),clearInterval(interval),dlProgress=1,tlog("dlTest: "+dlStatus+", took "+((new Date).getTime()-startT)+"ms"),done())}else t>1e3*settings.time_dlGraceTime&&(0settings.time_ul_max||failed)&&((failed||isNaN(ulStatus))&&(ulStatus="Fail"),clearRequests(),clearInterval(interval),ulProgress=1,tlog("ulTest: "+ulStatus+", took "+((new Date).getTime()-startT)+"ms"),done())}else t>1e3*settings.time_ulGraceTime&&(0=settings.test_order.length)0settings.time_dl_max||failed)&&((failed||isNaN(dlStatus))&&(dlStatus="Fail"),clearRequests(),clearInterval(interval),dlProgress=1,tlog("dlTest: "+dlStatus+", took "+((new Date).getTime()-startT)+"ms"),done())}else t>1e3*settings.time_dlGraceTime&&(0settings.time_ul_max||failed)&&((failed||isNaN(ulStatus))&&(ulStatus="Fail"),clearRequests(),clearInterval(interval),ulProgress=1,tlog("ulTest: "+ulStatus+", took "+((new Date).getTime()-startT)+"ms"),done())}else t>1e3*settings.time_ulGraceTime&&(0 Date: Wed, 30 Jan 2019 12:39:13 +0100 Subject: [PATCH 07/10] Added comment in idObfuscation.php --- telemetry/idObfuscation.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/telemetry/idObfuscation.php b/telemetry/idObfuscation.php index a86726b..553d1f1 100644 --- a/telemetry/idObfuscation.php +++ b/telemetry/idObfuscation.php @@ -14,6 +14,10 @@ function getObfuscationSalt(){ } return isset($OBFUSCATION_SALT)?$OBFUSCATION_SALT:0; } +/* +This is a simple reversible hash function I made for encoding and decoding test IDs. +It is not cryptographically secure, don't use it to hash passwords or something! +*/ function obfdeobf($id){ $salt=getObfuscationSalt()&0xFFFFFFFF; $id=$id&0xFFFFFFFF; From 6561d168c646f002143e45d801eff9c839b78eb2 Mon Sep 17 00:00:00 2001 From: adolfintel Date: Wed, 30 Jan 2019 17:10:27 +0100 Subject: [PATCH 08/10] Minor improvements to telemetry/stats.php --- telemetry/stats.php | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/telemetry/stats.php b/telemetry/stats.php index 13b390e..18b00ab 100644 --- a/telemetry/stats.php +++ b/telemetry/stats.php @@ -2,6 +2,9 @@ session_start(); error_reporting(0); header('Content-Type: text/html; charset=utf-8'); +header("Cache-Control: no-store, no-cache, must-revalidate, max-age=0"); +header("Cache-Control: post-check=0, pre-check=0", false); +header("Pragma: no-cache"); ?> @@ -62,7 +65,7 @@ if($stats_password=="PASSWORD"){ }else if($_SESSION["logged"]===true){ if($_GET["op"]=="logout"){ $_SESSION["logged"]=false; - ?> -
-
+ +

Search test results

+ prepare("select id,timestamp,ip,ispinfo,ua,lang,dl,ul,ping,jitter,log,extra from speedtest_users where id=?"); @@ -150,7 +154,7 @@ if($stats_password=="PASSWORD"){ }else{ if($_GET["op"]=="login"&&$_POST["password"]===$stats_password){ $_SESSION["logged"]=true; - ?>
From dc2dc1e5a17f290f03e2b8f7d24a0a5ffcd099d7 Mon Sep 17 00:00:00 2001 From: adolfintel Date: Wed, 30 Jan 2019 19:11:03 +0100 Subject: [PATCH 09/10] Bumped version to 4.7.1 --- doc.md | 2 +- speedtest_worker.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/doc.md b/doc.md index 2e21627..2b8b365 100644 --- a/doc.md +++ b/doc.md @@ -1,7 +1,7 @@ # HTML5 Speedtest > by Federico Dossena -> Version 4.7 +> Version 4.7.1 > [https://github.com/adolfintel/speedtest/](https://github.com/adolfintel/speedtest/) diff --git a/speedtest_worker.js b/speedtest_worker.js index eeddaa8..96c0e21 100644 --- a/speedtest_worker.js +++ b/speedtest_worker.js @@ -1,5 +1,5 @@ /* - HTML5 Speedtest v4.7 + HTML5 Speedtest v4.7.1 by Federico Dossena https://github.com/adolfintel/speedtest/ GNU LGPLv3 License From 513ddc5905be44502854eb9272223f4634976265 Mon Sep 17 00:00:00 2001 From: adolfintel Date: Thu, 31 Jan 2019 12:27:29 +0100 Subject: [PATCH 10/10] Simplified IE11 upload test --- speedtest_worker.js | 12 +----------- speedtest_worker.min.js | 2 +- 2 files changed, 2 insertions(+), 12 deletions(-) diff --git a/speedtest_worker.js b/speedtest_worker.js index 96c0e21..0943c51 100644 --- a/speedtest_worker.js +++ b/speedtest_worker.js @@ -460,21 +460,11 @@ function ulTest(done) { } if (ie11workaround) { // IE11 workarond: xhr.upload does not work properly, therefore we send a bunch of small 256k requests and use the onload event as progress. This is not precise, especially on fast connections - xhr[i].onload = function() { + xhr[i].onload = xhr[i].onerror = function() { tverb("ul stream progress event (ie11wa)"); totLoaded += reqsmall.size; testStream(i, 0); }; - xhr[i].onerror = function() { - // error, abort - tverb("ul stream failed (ie11wa)"); - if (settings.xhr_ignoreErrors === 0) failed = true; //abort - try { - xhr[i].abort(); - } catch (e) {} - delete xhr[i]; - if (settings.xhr_ignoreErrors === 1) testStream(i, 0); //restart stream - }; xhr[i].open("POST", settings.url_ul + url_sep(settings.url_ul) + "r=" + Math.random(), true); // random string to prevent caching xhr[i].setRequestHeader("Content-Encoding", "identity"); // disable compression (some browsers may refuse it, but data is incompressible anyway) xhr[i].send(reqsmall); diff --git a/speedtest_worker.min.js b/speedtest_worker.min.js index 0f24050..0215ae7 100644 --- a/speedtest_worker.min.js +++ b/speedtest_worker.min.js @@ -1 +1 @@ -var testStatus=-1,dlStatus="",ulStatus="",pingStatus="",jitterStatus="",clientIp="",dlProgress=0,ulProgress=0,pingProgress=0,testId=null,log="";function tlog(s){2<=settings.telemetry_level&&(log+=Date.now()+": "+s+"\n")}function tverb(s){3<=settings.telemetry_level&&(log+=Date.now()+": "+s+"\n")}function twarn(s){2<=settings.telemetry_level&&(log+=Date.now()+" WARN: "+s+"\n"),console.warn(s)}var settings={test_order:"IP_D_U",time_ul_max:15,time_dl_max:15,time_auto:!0,time_ulGraceTime:3,time_dlGraceTime:1.5,count_ping:10,url_dl:"garbage.php",url_ul:"empty.php",url_ping:"empty.php",url_getIp:"getIP.php",getIp_ispInfo:!0,getIp_ispInfo_distance:"km",xhr_dlMultistream:10,xhr_ulMultistream:3,xhr_multistreamDelay:300,xhr_ignoreErrors:1,xhr_dlUseBlob:!1,xhr_ul_blob_megabytes:20,garbagePhp_chunkSize:20,enable_quirks:!0,ping_allowPerformanceApi:!0,overheadCompensationFactor:1.06,useMebibits:!1,telemetry_level:0,url_telemetry:"telemetry/telemetry.php",telemetry_extra:""},xhr=null,interval=null,test_pointer=0;function url_sep(url){return url.match(/\?/)?"&":"?"}function clearRequests(){if(tverb("stopping pending XHRs"),xhr){for(var i=0;i=settings.test_order.length)0settings.time_dl_max||failed)&&((failed||isNaN(dlStatus))&&(dlStatus="Fail"),clearRequests(),clearInterval(interval),dlProgress=1,tlog("dlTest: "+dlStatus+", took "+((new Date).getTime()-startT)+"ms"),done())}else t>1e3*settings.time_dlGraceTime&&(0settings.time_ul_max||failed)&&((failed||isNaN(ulStatus))&&(ulStatus="Fail"),clearRequests(),clearInterval(interval),ulProgress=1,tlog("ulTest: "+ulStatus+", took "+((new Date).getTime()-startT)+"ms"),done())}else t>1e3*settings.time_ulGraceTime&&(0=settings.test_order.length)0settings.time_dl_max||failed)&&((failed||isNaN(dlStatus))&&(dlStatus="Fail"),clearRequests(),clearInterval(interval),dlProgress=1,tlog("dlTest: "+dlStatus+", took "+((new Date).getTime()-startT)+"ms"),done())}else t>1e3*settings.time_dlGraceTime&&(0settings.time_ul_max||failed)&&((failed||isNaN(ulStatus))&&(ulStatus="Fail"),clearRequests(),clearInterval(interval),ulProgress=1,tlog("ulTest: "+ulStatus+", took "+((new Date).getTime()-startT)+"ms"),done())}else t>1e3*settings.time_ulGraceTime&&(0
Test ID
Test ID
Date and time
IP and ISP Info
User agent and locale