瀏覽代碼

Refactor `results/` PHP code (#369)

* put code to insert and query data from database into separate file

* simplify obfuscation salt file handling

* use new DB interaction functions in telemetry.php

* use new DB interaction functions in stats.php

and fix indentation levels

* format telemetry settings file

* use new function for interacting with DB in index.php

* move drawing of the image into function and try to comment each section with what it does

* reorder lines for parts of the image to align with the order they appear on the image

* bugfix: display obfuscated and deobfuscated id in stats if id obfuscation is enabled

* improve error handling

* add missing PHPDocs to functions

* imageftbbox returns an array on success and false on failure so to check if the font is usable, check if we got an array

* fix dsn for postgres

* fix limit sql statement for postgresql

* remove obsolete require statement

* use require instead of require_once since the settings file might need to be loaded multiple times

because it just contains plain variables which will just get loaded into the current scope

* move require statements to the top of the file

* make sure files are readable before requiring them

* add constant to refer to the telemetry settings file and check if it is readable before loading it

* return null if no speedtest result was found for the given id and show according message to the user instead of just exiting

* use existing constant instead of string for telemetry settings file name

* uniformly use single quotes instead of double quotes as most code places already used single quotes

* somehow some tabs sneaked in, replace them to uniformly use spaces

* mysql now uses pdo, too, reflect that in the requirements documentation

* pass username and password as constructor parameters instead of via DSN
Bernd Stellwag 5 年之前
父節點
當前提交
fb7575b67b
共有 8 個文件被更改,包括 712 次插入448 次删除
  1. 8 2
      backend/getIP.php
  2. 2 2
      doc.md
  3. 67 38
      results/idObfuscation.php
  4. 207 158
      results/index.php
  5. 158 159
      results/stats.php
  6. 32 71
      results/telemetry.php
  7. 220 0
      results/telemetry_db.php
  8. 18 18
      results/telemetry_settings.php

+ 8 - 2
backend/getIP.php

@@ -80,7 +80,10 @@ function getLocalOrPrivateIpInfo($ip)
  */
 function getIpInfoTokenString()
 {
-    if (!file_exists(API_KEY_FILE)) {
+    if (
+        !file_exists(API_KEY_FILE)
+        || !is_readable(API_KEY_FILE)
+    ) {
         return '';
     }
 
@@ -139,7 +142,10 @@ function getIsp($rawIspInfo)
 function getServerLocation()
 {
     $serverLoc = null;
-    if (file_exists(SERVER_LOCATION_CACHE_FILE)) {
+    if (
+        file_exists(SERVER_LOCATION_CACHE_FILE)
+        && is_readable(SERVER_LOCATION_CACHE_FILE)
+    ) {
         require SERVER_LOCATION_CACHE_FILE;
     }
     if (is_string($serverLoc) && !empty($serverLoc)) {

+ 2 - 2
doc.md

@@ -44,7 +44,7 @@ Server side, you'll need:
 * PHP 5.4 or newer, a 64-bit version is strongly recommended
 * OpenSSL and its PHP module (this is usually installed automatically by most distros)
 * If you want to store test results (telemetry), one of the following:
-    - MySQL/MariaDB and the mysqli PHP module
+    - MySQL/MariaDB and its PHP PDO module
     - PostgreSQL and its PHP PDO module
     - SQLite 3 and its PHP PDO module
 * If you want to enable results sharing:
@@ -137,7 +137,7 @@ Requirements:
 * Apache 2 (nginx and IIS also supported). A fast connection is not mandatory, but is still recommended
 * PHP 5.4 or newer
 * If you want to store test results (telemetry), one of the following:
-    - MySQL/MariaDB and the mysqli PHP module
+    - MySQL/MariaDB and its PHP PDO module
     - PostgreSQL and its PHP PDO module
     - SQLite 3 and its PHP PDO module
 * If you want to enable results sharing:

+ 67 - 38
results/idObfuscation.php

@@ -1,43 +1,72 @@
 <?php
-function getObfuscationSalt(){
-	$saltFile=dirname(__FILE__)."/idObfuscation_salt.php";
-	if(file_exists($saltFile)){
-		require $saltFile;
-	}else{
-		$bytes=openssl_random_pseudo_bytes(4);
-		$sf=fopen($saltFile,"w");
-		fwrite($sf,chr(60)."?php\n");
-		fwrite($sf,'$OBFUSCATION_SALT=0x'.bin2hex($bytes).";\n");
-		fwrite($sf,"?".chr(62));
-		fclose($sf);
-		require $saltFile;
-	}
-	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,$dec){
-	$salt=getObfuscationSalt()&0xFFFFFFFF;
-	$id=$id&0xFFFFFFFF;
-	if($dec){
-		$id=$id^$salt;
-		$id=(($id&0xAAAAAAAA)>>1)|($id&0x55555555)<<1;
-		$id=(($id&0x0000FFFF)<<16)|(($id&0xFFFF0000)>>16);
-		return $id;
-	}else{
-		$id=(($id&0x0000FFFF)<<16)|(($id&0xFFFF0000)>>16);
-		$id=(($id&0xAAAAAAAA)>>1)|($id&0x55555555)<<1;
-		return $id^$salt;
-	}
+
+define('ID_OBFUSCATION_SALT_FILE', __DIR__.'/idObfuscation_salt.php');
+
+/**
+ * @return string|int
+ */
+function getObfuscationSalt()
+{
+    if (!file_exists(ID_OBFUSCATION_SALT_FILE)) {
+        $bytes = openssl_random_pseudo_bytes(4);
+
+        $saltData = "<?php\n\n\$OBFUSCATION_SALT = 0x".bin2hex($bytes).";\n";
+        file_put_contents(ID_OBFUSCATION_SALT_FILE, $saltData);
+    }
+
+    if (
+        file_exists(ID_OBFUSCATION_SALT_FILE)
+        && is_readable(ID_OBFUSCATION_SALT_FILE)
+    ) {
+        require ID_OBFUSCATION_SALT_FILE;
+    }
+
+    return isset($OBFUSCATION_SALT) ? $OBFUSCATION_SALT : 0;
 }
-function obfuscateId($id){
-	return str_pad(base_convert(obfdeobf($id+1,false),10,36),7,0,STR_PAD_LEFT);
+
+/**
+ * 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!
+ *
+ * @param int|string $id
+ * @param bool $dec
+ *
+ * @return int|string
+ */
+function obfdeobf($id, $dec)
+{
+    $salt = getObfuscationSalt() & 0xFFFFFFFF;
+    $id &= 0xFFFFFFFF;
+    if ($dec) {
+        $id ^= $salt;
+        $id = (($id & 0xAAAAAAAA) >> 1) | ($id & 0x55555555) << 1;
+        $id = (($id & 0x0000FFFF) << 16) | (($id & 0xFFFF0000) >> 16);
+
+        return $id;
+    }
+
+    $id = (($id & 0x0000FFFF) << 16) | (($id & 0xFFFF0000) >> 16);
+    $id = (($id & 0xAAAAAAAA) >> 1) | ($id & 0x55555555) << 1;
+
+    return $id ^ $salt;
 }
-function deobfuscateId($id){
-	return obfdeobf(base_convert($id,36,10),true)-1;
+
+/**
+ * @param int $id
+ *
+ * @return string
+ */
+function obfuscateId($id)
+{
+    return str_pad(base_convert(obfdeobf($id + 1, false), 10, 36), 7, 0, STR_PAD_LEFT);
 }
 
-//IMPORTANT: DO NOT ADD ANYTHING BELOW THE PHP CLOSING TAG, NOT EVEN EMPTY LINES!
-?>
+/**
+ * @param string $id
+ *
+ * @return int
+ */
+function deobfuscateId($id)
+{
+    return obfdeobf(base_convert($id, 36, 10), true) - 1;
+}

+ 207 - 158
results/index.php

@@ -1,164 +1,213 @@
 <?php
-$WATERMARK_TEXT="LibreSpeed";
+
+require_once 'telemetry_db.php';
 
 error_reporting(0);
-putenv('GDFONTPATH=' . realpath('.'));
-function tryFont($name){
-	$rp=realpath('.');
-	if(imageftbbox(12,0,$name,"M")[5]==0){
-		$name=$rp."/".$name.".ttf";
-		if(imageftbbox(12,0,$name,"M")[5]==0){
-			return null;
-		}
-	}
-	return $name;
+putenv('GDFONTPATH='.realpath('.'));
+
+/**
+ * @param string $name
+ *
+ * @return string|null
+ */
+function tryFont($name)
+{
+    if (is_array(imageftbbox(12, 0, $name, 'M'))) {
+        return $name;
+    }
+
+    $fullPathToFont = realpath('.').'/'.$name.'.ttf';
+    if (is_array(imageftbbox(12, 0, $fullPathToFont, 'M'))) {
+        return $fullPathToFont;
+    }
+
+    return null;
 }
-function format($d){
-    if($d<10) return number_format($d,2,".","");
-    if($d<100) return number_format($d,1,".","");
-    return number_format($d,0,".","");
+
+/**
+ * @param int|float $d
+ *
+ * @return string
+ */
+function format($d)
+{
+    if ($d < 10) {
+        return number_format($d, 2, '.', '');
+    }
+    if ($d < 100) {
+        return number_format($d, 1, '.', '');
+    }
+
+    return number_format($d, 0, '.', '');
+}
+
+/**
+ * @param array $speedtest
+ *
+ * @return array
+ */
+function formatSpeedtestDataForImage($speedtest)
+{
+    // format values for the image
+    $speedtest['dl'] = format($speedtest['dl']);
+    $speedtest['ul'] = format($speedtest['ul']);
+    $speedtest['ping'] = format($speedtest['ping']);
+    $speedtest['jit'] = format($speedtest['jitter']);
+
+    $ispinfo = json_decode($speedtest['ispinfo'], true)['processedString'];
+    $dash = strpos($ispinfo, '-');
+    if ($dash !== false) {
+        $ispinfo = substr($ispinfo, $dash + 2);
+        $par = strrpos($ispinfo, '(');
+        if ($par !== false) {
+            $ispinfo = substr($ispinfo, 0, $par);
+        }
+    } else {
+        $ispinfo = '';
+    }
+
+    $speedtest['ispinfo'] = $ispinfo;
+
+    return $speedtest;
+}
+
+/**
+ * @param array $speedtest
+ *
+ * @return void
+ */
+function drawImage($speedtest)
+{
+    // format values for the image
+    $data = formatSpeedtestDataForImage($speedtest);
+    $dl = $data['dl'];
+    $ul = $data['ul'];
+    $ping = $data['ping'];
+    $jit = $data['jitter'];
+    $ispinfo = $data['ispinfo'];
+
+    // initialize the image
+    $SCALE = 1.25;
+    $SMALL_SEP = 8 * $SCALE;
+    $WIDTH = 400 * $SCALE;
+    $HEIGHT = 229 * $SCALE;
+    $im = imagecreatetruecolor($WIDTH, $HEIGHT);
+    $BACKGROUND_COLOR = imagecolorallocate($im, 255, 255, 255);
+
+    // configure fonts
+    $FONT_LABEL = tryFont('OpenSans-Semibold');
+    $FONT_LABEL_SIZE = 14 * $SCALE;
+    $FONT_LABEL_SIZE_BIG = 16 * $SCALE;
+
+    $FONT_METER = tryFont('OpenSans-Light');
+    $FONT_METER_SIZE = 20 * $SCALE;
+    $FONT_METER_SIZE_BIG = 22 * $SCALE;
+
+    $FONT_MEASURE = tryFont('OpenSans-Semibold');
+    $FONT_MEASURE_SIZE = 12 * $SCALE;
+    $FONT_MEASURE_SIZE_BIG = 12 * $SCALE;
+
+    $FONT_ISP = tryFont('OpenSans-Semibold');
+    $FONT_ISP_SIZE = 9 * $SCALE;
+
+    $FONT_WATERMARK = tryFont('OpenSans-Light');
+    $FONT_WATERMARK_SIZE = 8 * $SCALE;
+
+    // configure text colors
+    $TEXT_COLOR_LABEL = imagecolorallocate($im, 40, 40, 40);
+    $TEXT_COLOR_PING_METER = imagecolorallocate($im, 170, 96, 96);
+    $TEXT_COLOR_JIT_METER = imagecolorallocate($im, 170, 96, 96);
+    $TEXT_COLOR_DL_METER = imagecolorallocate($im, 96, 96, 170);
+    $TEXT_COLOR_UL_METER = imagecolorallocate($im, 96, 96, 96);
+    $TEXT_COLOR_MEASURE = imagecolorallocate($im, 40, 40, 40);
+    $TEXT_COLOR_ISP = imagecolorallocate($im, 40, 40, 40);
+    $SEPARATOR_COLOR = imagecolorallocate($im, 192, 192, 192);
+    $TEXT_COLOR_WATERMARK = imagecolorallocate($im, 160, 160, 160);
+
+    // configure positioning or the different parts on the image
+    $POSITION_X_PING = 125 * $SCALE;
+    $POSITION_Y_PING_LABEL = 24 * $SCALE;
+    $POSITION_Y_PING_METER = 60 * $SCALE;
+    $POSITION_Y_PING_MEASURE = 60 * $SCALE;
+
+    $POSITION_X_JIT = 275 * $SCALE;
+    $POSITION_Y_JIT_LABEL = 24 * $SCALE;
+    $POSITION_Y_JIT_METER = 60 * $SCALE;
+    $POSITION_Y_JIT_MEASURE = 60 * $SCALE;
+
+    $POSITION_X_DL = 120 * $SCALE;
+    $POSITION_Y_DL_LABEL = 105 * $SCALE;
+    $POSITION_Y_DL_METER = 143 * $SCALE;
+    $POSITION_Y_DL_MEASURE = 169 * $SCALE;
+
+    $POSITION_X_UL = 280 * $SCALE;
+    $POSITION_Y_UL_LABEL = 105 * $SCALE;
+    $POSITION_Y_UL_METER = 143 * $SCALE;
+    $POSITION_Y_UL_MEASURE = 169 * $SCALE;
+
+    $POSITION_X_ISP = 4 * $SCALE;
+    $POSITION_Y_ISP = 205 * $SCALE;
+
+    $SEPARATOR_Y = 211 * $SCALE;
+
+    $POSITION_Y_WATERMARK = 223 * $SCALE;
+
+    // configure labels
+    $MBPS_TEXT = 'Mbps';
+    $MS_TEXT = 'ms';
+    $PING_TEXT = 'Ping';
+    $JIT_TEXT = 'Jitter';
+    $DL_TEXT = 'Download';
+    $UL_TEXT = 'Upload';
+    $WATERMARK_TEXT = 'LibreSpeed';
+
+    // create text boxes for each part of the image
+    $mbpsBbox = imageftbbox($FONT_MEASURE_SIZE_BIG, 0, $FONT_MEASURE, $MBPS_TEXT);
+    $msBbox = imageftbbox($FONT_MEASURE_SIZE, 0, $FONT_MEASURE, $MS_TEXT);
+    $pingBbox = imageftbbox($FONT_LABEL_SIZE, 0, $FONT_LABEL, $PING_TEXT);
+    $pingMeterBbox = imageftbbox($FONT_METER_SIZE, 0, $FONT_METER, $ping);
+    $jitBbox = imageftbbox($FONT_LABEL_SIZE, 0, $FONT_LABEL, $JIT_TEXT);
+    $jitMeterBbox = imageftbbox($FONT_METER_SIZE, 0, $FONT_METER, $jit);
+    $dlBbox = imageftbbox($FONT_LABEL_SIZE_BIG, 0, $FONT_LABEL, $DL_TEXT);
+    $dlMeterBbox = imageftbbox($FONT_METER_SIZE_BIG, 0, $FONT_METER, $dl);
+    $ulBbox = imageftbbox($FONT_LABEL_SIZE_BIG, 0, $FONT_LABEL, $UL_TEXT);
+    $ulMeterBbox = imageftbbox($FONT_METER_SIZE_BIG, 0, $FONT_METER, $ul);
+    $watermarkBbox = imageftbbox($FONT_WATERMARK_SIZE, 0, $FONT_WATERMARK, $WATERMARK_TEXT);
+    $POSITION_X_WATERMARK = $WIDTH - $watermarkBbox[4] - 4 * $SCALE;
+
+    // put the parts together to draw the image
+    imagefilledrectangle($im, 0, 0, $WIDTH, $HEIGHT, $BACKGROUND_COLOR);
+    // ping
+    imagefttext($im, $FONT_LABEL_SIZE, 0, $POSITION_X_PING - $pingBbox[4] / 2, $POSITION_Y_PING_LABEL, $TEXT_COLOR_LABEL, $FONT_LABEL, $PING_TEXT);
+    imagefttext($im, $FONT_METER_SIZE, 0, $POSITION_X_PING - $pingMeterBbox[4] / 2 - $msBbox[4] / 2 - $SMALL_SEP / 2, $POSITION_Y_PING_METER, $TEXT_COLOR_PING_METER, $FONT_METER, $ping);
+    imagefttext($im, $FONT_MEASURE_SIZE, 0, $POSITION_X_PING + $pingMeterBbox[4] / 2 + $SMALL_SEP / 2 - $msBbox[4] / 2, $POSITION_Y_PING_MEASURE, $TEXT_COLOR_MEASURE, $FONT_MEASURE, $MS_TEXT);
+    // jitter
+    imagefttext($im, $FONT_LABEL_SIZE, 0, $POSITION_X_JIT - $jitBbox[4] / 2, $POSITION_Y_JIT_LABEL, $TEXT_COLOR_LABEL, $FONT_LABEL, $JIT_TEXT);
+    imagefttext($im, $FONT_METER_SIZE, 0, $POSITION_X_JIT - $jitMeterBbox[4] / 2 - $msBbox[4] / 2 - $SMALL_SEP / 2, $POSITION_Y_JIT_METER, $TEXT_COLOR_JIT_METER, $FONT_METER, $jit);
+    imagefttext($im, $FONT_MEASURE_SIZE, 0, $POSITION_X_JIT + $jitMeterBbox[4] / 2 + $SMALL_SEP / 2 - $msBbox[4] / 2, $POSITION_Y_JIT_MEASURE, $TEXT_COLOR_MEASURE, $FONT_MEASURE, $MS_TEXT);
+    // dl
+    imagefttext($im, $FONT_LABEL_SIZE_BIG, 0, $POSITION_X_DL - $dlBbox[4] / 2, $POSITION_Y_DL_LABEL, $TEXT_COLOR_LABEL, $FONT_LABEL, $DL_TEXT);
+    imagefttext($im, $FONT_METER_SIZE_BIG, 0, $POSITION_X_DL - $dlMeterBbox[4] / 2, $POSITION_Y_DL_METER, $TEXT_COLOR_DL_METER, $FONT_METER, $dl);
+    imagefttext($im, $FONT_MEASURE_SIZE_BIG, 0, $POSITION_X_DL - $mbpsBbox[4] / 2, $POSITION_Y_DL_MEASURE, $TEXT_COLOR_MEASURE, $FONT_MEASURE, $MBPS_TEXT);
+    // ul
+    imagefttext($im, $FONT_LABEL_SIZE_BIG, 0, $POSITION_X_UL - $ulBbox[4] / 2, $POSITION_Y_UL_LABEL, $TEXT_COLOR_LABEL, $FONT_LABEL, $UL_TEXT);
+    imagefttext($im, $FONT_METER_SIZE_BIG, 0, $POSITION_X_UL - $ulMeterBbox[4] / 2, $POSITION_Y_UL_METER, $TEXT_COLOR_UL_METER, $FONT_METER, $ul);
+    imagefttext($im, $FONT_MEASURE_SIZE_BIG, 0, $POSITION_X_UL - $mbpsBbox[4] / 2, $POSITION_Y_UL_MEASURE, $TEXT_COLOR_MEASURE, $FONT_MEASURE, $MBPS_TEXT);
+    // isp
+    imagefttext($im, $FONT_ISP_SIZE, 0, $POSITION_X_ISP, $POSITION_Y_ISP, $TEXT_COLOR_ISP, $FONT_ISP, $ispinfo);
+    // separator
+    imagefilledrectangle($im, 0, $SEPARATOR_Y, $WIDTH, $SEPARATOR_Y, $SEPARATOR_COLOR);
+    // watermark
+    imagefttext($im, $FONT_WATERMARK_SIZE, 0, $POSITION_X_WATERMARK, $POSITION_Y_WATERMARK, $TEXT_COLOR_WATERMARK, $FONT_WATERMARK, $WATERMARK_TEXT);
+
+    // send the image to the browser
+    header('Content-Type: image/png');
+    imagepng($im);
+}
+
+$speedtest = getSpeedtestUserById($_GET['id']);
+if (!is_array($speedtest)) {
+    exit(1);
 }
 
-$SCALE=1.25;
-$WIDTH=400*$SCALE;
-$HEIGHT=229*$SCALE;
-$im=imagecreatetruecolor($WIDTH,$HEIGHT);
-$BACKGROUND_COLOR=imagecolorallocate($im,255,255,255);
-$FONT_LABEL=tryFont("OpenSans-Semibold");
-$FONT_LABEL_SIZE=14*$SCALE;
-$FONT_LABEL_SIZE_BIG=16*$SCALE;
-$FONT_METER=tryFont("OpenSans-Light");
-$FONT_METER_SIZE=20*$SCALE;
-$FONT_METER_SIZE_BIG=22*$SCALE;
-$FONT_MEASURE=tryFont("OpenSans-Semibold");
-$FONT_MEASURE_SIZE=12*$SCALE;
-$FONT_MEASURE_SIZE_BIG=12*$SCALE;
-$FONT_ISP=tryFont("OpenSans-Semibold");
-$FONT_ISP_SIZE=9*$SCALE;
-$FONT_WATERMARK=tryFont("OpenSans-Light");
-$FONT_WATERMARK_SIZE=8*$SCALE;
-$TEXT_COLOR_LABEL=imagecolorallocate($im,40,40,40);
-$TEXT_COLOR_DL_METER=imagecolorallocate($im,96,96,170);
-$TEXT_COLOR_UL_METER=imagecolorallocate($im,96,96,96);
-$TEXT_COLOR_PING_METER=imagecolorallocate($im,170,96,96);
-$TEXT_COLOR_JIT_METER=imagecolorallocate($im,170,96,96);
-$TEXT_COLOR_MEASURE=imagecolorallocate($im,40,40,40);
-$TEXT_COLOR_ISP=imagecolorallocate($im,40,40,40);
-$TEXT_COLOR_WATERMARK=imagecolorallocate($im,160,160,160);
-$POSITION_Y_DL_LABEL=105*$SCALE;
-$POSITION_Y_UL_LABEL=105*$SCALE;
-$POSITION_Y_PING_LABEL=24*$SCALE;
-$POSITION_Y_JIT_LABEL=24*$SCALE;
-$POSITION_Y_DL_METER=143*$SCALE;
-$POSITION_Y_UL_METER=143*$SCALE;
-$POSITION_Y_PING_METER=60*$SCALE;
-$POSITION_Y_JIT_METER=60*$SCALE;
-$POSITION_Y_DL_MEASURE=169*$SCALE;
-$POSITION_Y_UL_MEASURE=169*$SCALE;
-$POSITION_Y_PING_MEASURE=60*$SCALE;
-$POSITION_Y_JIT_MEASURE=60*$SCALE;
-$POSITION_Y_ISP=205*$SCALE;
-$POSITION_X_DL=120*$SCALE;
-$POSITION_X_UL=280*$SCALE;
-$POSITION_X_PING=125*$SCALE;
-$POSITION_X_JIT=275*$SCALE;
-$POSITION_X_ISP=4*$SCALE;
-$SMALL_SEP=8*$SCALE;
-$SEPARATOR_Y=211*$SCALE;
-$SEPARATOR_COLOR=imagecolorallocate($im,192,192,192);
-$POSITION_Y_WATERMARK=223*$SCALE;
-$DL_TEXT="Download";
-$UL_TEXT="Upload";
-$PING_TEXT="Ping";
-$JIT_TEXT="Jitter";
-$MBPS_TEXT="Mbps";
-$MS_TEXT="ms";
-
-$id=$_GET["id"];
-include_once('telemetry_settings.php');
-require '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"){
-	$conn = new mysqli($MySql_hostname, $MySql_username, $MySql_password, $MySql_databasename, $MySql_port);
-	$q = $conn->prepare("select ispinfo,dl,ul,ping,jitter from speedtest_users where id=?");
-	$q->bind_param("i",$id);
-	$q->execute();
-	$q->bind_result($ispinfo,$dl,$ul,$ping,$jit);
-	$q->fetch();
-}else if($db_type=="sqlite"){
-	$conn = new PDO("sqlite:$Sqlite_db_file") or die();
-	$q=$conn->prepare("select ispinfo,dl,ul,ping,jitter from speedtest_users where id=?") or die();
-	$q->execute(array($id)) or die();
-	$row=$q->fetch() or die();
-	$ispinfo=$row["ispinfo"];
-	$dl=$row["dl"];
-	$ul=$row["ul"];
-	$ping=$row["ping"];
-	$jit=$row["jitter"];
-	$conn=null;
-}else if($db_type=="postgresql"){
-    $conn_host = "host=$PostgreSql_hostname";
-    $conn_db = "dbname=$PostgreSql_databasename";
-    $conn_user = "user=$PostgreSql_username";
-    $conn_password = "password=$PostgreSql_password";
-    $conn = new PDO("pgsql:$conn_host;$conn_db;$conn_user;$conn_password") or die();
-	$q=$conn->prepare("select ispinfo,dl,ul,ping,jitter from speedtest_users where id=?") or die();
-	$q->execute(array($id)) or die();
-	$row=$q->fetch() or die();
-	$ispinfo=$row["ispinfo"];
-	$dl=$row["dl"];
-	$ul=$row["ul"];
-	$ping=$row["ping"];
-	$jit=$row["jitter"];
-	$conn=null;
-}else die();
-
-$dl=format($dl);
-$ul=format($ul);
-$ping=format($ping);
-$jit=format($jit);
-
-$ispinfo=json_decode($ispinfo,true)["processedString"];
-$dash=strpos($ispinfo,"-");
-if(!($dash===FALSE)){
-	$ispinfo=substr($ispinfo,$dash+2);
-	$par=strrpos($ispinfo,"(");
-	if(!($par===FALSE)) $ispinfo=substr($ispinfo,0,$par);
-}else $ispinfo="";
-
-$dlBbox=imageftbbox($FONT_LABEL_SIZE_BIG,0,$FONT_LABEL,$DL_TEXT);
-$ulBbox=imageftbbox($FONT_LABEL_SIZE_BIG,0,$FONT_LABEL,$UL_TEXT);
-$pingBbox=imageftbbox($FONT_LABEL_SIZE,0,$FONT_LABEL,$PING_TEXT);
-$jitBbox=imageftbbox($FONT_LABEL_SIZE,0,$FONT_LABEL,$JIT_TEXT);
-$dlMeterBbox=imageftbbox($FONT_METER_SIZE_BIG,0,$FONT_METER,$dl);
-$ulMeterBbox=imageftbbox($FONT_METER_SIZE_BIG,0,$FONT_METER,$ul);
-$pingMeterBbox=imageftbbox($FONT_METER_SIZE,0,$FONT_METER,$ping);
-$jitMeterBbox=imageftbbox($FONT_METER_SIZE,0,$FONT_METER,$jit);
-$mbpsBbox=imageftbbox($FONT_MEASURE_SIZE_BIG,0,$FONT_MEASURE,$MBPS_TEXT);
-$msBbox=imageftbbox($FONT_MEASURE_SIZE,0,$FONT_MEASURE,$MS_TEXT);
-$watermarkBbox=imageftbbox($FONT_WATERMARK_SIZE,0,$FONT_WATERMARK,$WATERMARK_TEXT);
-$POSITION_X_WATERMARK=$WIDTH-$watermarkBbox[4]-4*$SCALE;
-
-imagefilledrectangle($im, 0, 0, $WIDTH, $HEIGHT, $BACKGROUND_COLOR);
-imagefttext($im,$FONT_LABEL_SIZE_BIG,0,$POSITION_X_DL-$dlBbox[4]/2,$POSITION_Y_DL_LABEL,$TEXT_COLOR_LABEL,$FONT_LABEL,$DL_TEXT);
-imagefttext($im,$FONT_LABEL_SIZE_BIG,0,$POSITION_X_UL-$ulBbox[4]/2,$POSITION_Y_UL_LABEL,$TEXT_COLOR_LABEL,$FONT_LABEL,$UL_TEXT);
-imagefttext($im,$FONT_LABEL_SIZE,0,$POSITION_X_PING-$pingBbox[4]/2,$POSITION_Y_PING_LABEL,$TEXT_COLOR_LABEL,$FONT_LABEL,$PING_TEXT);
-imagefttext($im,$FONT_LABEL_SIZE,0,$POSITION_X_JIT-$jitBbox[4]/2,$POSITION_Y_JIT_LABEL,$TEXT_COLOR_LABEL,$FONT_LABEL,$JIT_TEXT);
-imagefttext($im,$FONT_METER_SIZE_BIG,0,$POSITION_X_DL-$dlMeterBbox[4]/2,$POSITION_Y_DL_METER,$TEXT_COLOR_DL_METER,$FONT_METER,$dl);
-imagefttext($im,$FONT_METER_SIZE_BIG,0,$POSITION_X_UL-$ulMeterBbox[4]/2,$POSITION_Y_UL_METER,$TEXT_COLOR_UL_METER,$FONT_METER,$ul);
-imagefttext($im,$FONT_METER_SIZE,0,$POSITION_X_PING-$pingMeterBbox[4]/2-$msBbox[4]/2-$SMALL_SEP/2,$POSITION_Y_PING_METER,$TEXT_COLOR_PING_METER,$FONT_METER,$ping);
-imagefttext($im,$FONT_METER_SIZE,0,$POSITION_X_JIT-$jitMeterBbox[4]/2-$msBbox[4]/2-$SMALL_SEP/2,$POSITION_Y_JIT_METER,$TEXT_COLOR_JIT_METER,$FONT_METER,$jit);
-imagefttext($im,$FONT_MEASURE_SIZE_BIG,0,$POSITION_X_DL-$mbpsBbox[4]/2,$POSITION_Y_DL_MEASURE,$TEXT_COLOR_MEASURE,$FONT_MEASURE,$MBPS_TEXT);
-imagefttext($im,$FONT_MEASURE_SIZE_BIG,0,$POSITION_X_UL-$mbpsBbox[4]/2,$POSITION_Y_UL_MEASURE,$TEXT_COLOR_MEASURE,$FONT_MEASURE,$MBPS_TEXT);
-imagefttext($im,$FONT_MEASURE_SIZE,0,$POSITION_X_PING+$pingMeterBbox[4]/2+$SMALL_SEP/2-$msBbox[4]/2,$POSITION_Y_PING_MEASURE,$TEXT_COLOR_MEASURE,$FONT_MEASURE,$MS_TEXT);
-imagefttext($im,$FONT_MEASURE_SIZE,0,$POSITION_X_JIT+$jitMeterBbox[4]/2+$SMALL_SEP/2-$msBbox[4]/2,$POSITION_Y_JIT_MEASURE,$TEXT_COLOR_MEASURE,$FONT_MEASURE,$MS_TEXT);
-imagefttext($im,$FONT_ISP_SIZE,0,$POSITION_X_ISP,$POSITION_Y_ISP,$TEXT_COLOR_ISP,$FONT_ISP,$ispinfo);
-imagefttext($im,$FONT_WATERMARK_SIZE,0,$POSITION_X_WATERMARK,$POSITION_Y_WATERMARK,$TEXT_COLOR_WATERMARK,$FONT_WATERMARK,$WATERMARK_TEXT);
-imagefilledrectangle($im, 0, $SEPARATOR_Y, $WIDTH, $SEPARATOR_Y, $SEPARATOR_COLOR);
-header('Content-Type: image/png');
-imagepng($im);
-imagedestroy($im);
-
-?>
+drawImage($speedtest);

+ 158 - 159
results/stats.php

@@ -1,6 +1,10 @@
 <?php
 session_start();
 error_reporting(0);
+
+require 'telemetry_settings.php';
+require_once 'telemetry_db.php';
+
 header('Content-Type: text/html; charset=utf-8');
 header('Cache-Control: no-store, no-cache, must-revalidate, max-age=0, s-maxage=0');
 header('Cache-Control: post-check=0, pre-check=0', false);
@@ -8,163 +12,158 @@ header('Pragma: no-cache');
 ?>
 <!DOCTYPE html>
 <html>
-<head>
-<title>LibreSpeed - Stats</title>
-<style type="text/css">
-	html,body{
-		margin:0;
-		padding:0;
-		border:none;
-		width:100%; min-height:100%;
-	}
-	html{
-		background-color: hsl(198,72%,35%);
-		font-family: "Segoe UI","Roboto",sans-serif;
-	}
-	body{
-		background-color:#FFFFFF;
-		box-sizing:border-box;
-		width:100%;
-		max-width:70em;
-		margin:4em auto;
-		box-shadow:0 1em 6em #00000080;
-		padding:1em 1em 4em 1em;
-		border-radius:0.4em;
-	}
-	h1,h2,h3,h4,h5,h6{
-		font-weight:300;
-		margin-bottom: 0.1em;
-	}
-	h1{
-		text-align:center;
-	}
-	table{
-		margin:2em 0;
-		width:100%;
-	}
-	table, tr, th, td {
-		border: 1px solid #AAAAAA;
-	}
-	th {
-		width: 6em;
-	}
-	td {
-		word-break: break-all;
-	}
-</style>
-</head>
-<body>
-<h1>LibreSpeed - Stats</h1>
-<?php
-include_once("telemetry_settings.php");
-require "idObfuscation.php";
-if($stats_password=="PASSWORD"){
-	?>
-		Please set $stats_password in telemetry_settings.php to enable access.
-	<?php
-}else if($_SESSION["logged"]===true){
-	if($_GET["op"]=="logout"){
-		$_SESSION["logged"]=false;
-		?><script type="text/javascript">window.location=location.protocol+"//"+location.host+location.pathname;</script><?php
-	}else{
-		$conn=null;
-		if($db_type=="mysql"){
-			$conn = new mysqli($MySql_hostname, $MySql_username, $MySql_password, $MySql_databasename, $MySql_port);
-		}else if($db_type=="sqlite"){
-			$conn = new PDO("sqlite:$Sqlite_db_file");
-		} else if($db_type=="postgresql"){
-			$conn_host = "host=$PostgreSql_hostname";
-			$conn_db = "dbname=$PostgreSql_databasename";
-			$conn_user = "user=$PostgreSql_username";
-			$conn_password = "password=$PostgreSql_password";
-			$conn = new PDO("pgsql:$conn_host;$conn_db;$conn_user;$conn_password");
-		}else die();
-?>
-	<form action="stats.php" method="GET"><input type="hidden" name="op" value="logout" /><input type="submit" value="Logout" /></form>
-	<form action="stats.php" method="GET">
-		<h3>Search test results</h6>
-		<input type="hidden" name="op" value="id" />
-		<input type="text" name="id" id="id" placeholder="Test ID" value=""/>
-		<input type="submit" value="Find" />
-		<input type="submit" onclick="document.getElementById('id').value=''" value="Show last 100 tests" />
-	</form>
-	<?php
-		$q=null;
-		if($_GET["op"]=="id"&&!empty($_GET["id"])){
-			$id=$_GET["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",$id);
-				$q->execute();
-				$q->store_result();
-				$q->bind_result($id,$timestamp,$ip,$ispinfo,$ua,$lang,$dl,$ul,$ping,$jitter,$log,$extra);
-			} else if($db_type=="sqlite"||$db_type=="postgresql"){
-				$q=$conn->prepare("select id,timestamp,ip,ispinfo,ua,lang,dl,ul,ping,jitter,log,extra from speedtest_users where id=?");
-				$q->execute(array($id));
-			} else die();
-		}else{
-			if($db_type=="mysql"){
-				$q=$conn->prepare("select id,timestamp,ip,ispinfo,ua,lang,dl,ul,ping,jitter,log,extra from speedtest_users order by timestamp desc limit 0,100");
-				$q->execute();
-				$q->store_result();
-				$q->bind_result($id,$timestamp,$ip,$ispinfo,$ua,$lang,$dl,$ul,$ping,$jitter,$log,$extra);
-			} else if($db_type=="sqlite"||$db_type=="postgresql"){
-				$q=$conn->prepare("select id,timestamp,ip,ispinfo,ua,lang,dl,ul,ping,jitter,log,extra from speedtest_users order by timestamp desc limit 100");
-				$q->execute();
-			}else die();
-		}
-		while(true){
-			$id=null; $timestamp=null; $ip=null; $ispinfo=null; $ua=null; $lang=null; $dl=null; $ul=null; $ping=null; $jitter=null; $log=null; $extra=null;
-			if($db_type=="mysql"){
-				if(!$q->fetch()) break;
-			} else if($db_type=="sqlite"||$db_type=="postgresql"){
-				if(!($row=$q->fetch())) break;
-				$id=$row["id"];
-				$timestamp=$row["timestamp"];
-				$ip=$row["ip"];
-				$ispinfo=$row["ispinfo"];
-				$ua=$row["ua"];
-				$lang=$row["lang"];
-				$dl=$row["dl"];
-				$ul=$row["ul"];
-				$ping=$row["ping"];
-				$jitter=$row["jitter"];
-				$log=$row["log"];
-				$extra=$row["extra"];
-			}else die();
-	?>
-		<table>
-			<tr><th>Test ID</th><td><?=htmlspecialchars(($enable_id_obfuscation?(obfuscateId($id)." (deobfuscated: ".$id.")"):$id), ENT_HTML5, 'UTF-8') ?></td></tr>
-			<tr><th>Date and time</th><td><?=htmlspecialchars($timestamp, ENT_HTML5, 'UTF-8') ?></td></tr>
-			<tr><th>IP and ISP Info</th><td><?=$ip ?><br/><?=htmlspecialchars($ispinfo, ENT_HTML5, 'UTF-8') ?></td></tr>
-			<tr><th>User agent and locale</th><td><?=$ua ?><br/><?=htmlspecialchars($lang, ENT_HTML5, 'UTF-8') ?></td></tr>
-			<tr><th>Download speed</th><td><?=htmlspecialchars($dl, ENT_HTML5, 'UTF-8') ?></td></tr>
-			<tr><th>Upload speed</th><td><?=htmlspecialchars($ul, ENT_HTML5, 'UTF-8') ?></td></tr>
-			<tr><th>Ping</th><td><?=htmlspecialchars($ping, ENT_HTML5, 'UTF-8') ?></td></tr>
-			<tr><th>Jitter</th><td><?=htmlspecialchars($jitter, ENT_HTML5, 'UTF-8') ?></td></tr>
-			<tr><th>Log</th><td><?=htmlspecialchars($log, ENT_HTML5, 'UTF-8') ?></td></tr>
-			<tr><th>Extra info</th><td><?=htmlspecialchars($extra, ENT_HTML5, 'UTF-8') ?></td></tr>
-		</table>
-	<?php
-		}
-	?>
-<?php
-	}
-}else{
-	if($_GET["op"]=="login"&&$_POST["password"]===$stats_password){
-		$_SESSION["logged"]=true;
-		?><script type="text/javascript">window.location=location.protocol+"//"+location.host+location.pathname;</script><?php
-	}else{
-?>
-	<form action="stats.php?op=login" method="POST">
-		<h3>Login</h3>
-		<input type="password" name="password" placeholder="Password" value=""/>
-		<input type="submit" value="Login" />
-	</form>
-<?php
-	}
-}
-?>
-</body>
+    <head>
+        <title>LibreSpeed - Stats</title>
+        <style type="text/css">
+            html,body{
+                margin:0;
+                padding:0;
+                border:none;
+                width:100%; min-height:100%;
+            }
+            html{
+                background-color: hsl(198,72%,35%);
+                font-family: "Segoe UI","Roboto",sans-serif;
+            }
+            body{
+                background-color:#FFFFFF;
+                box-sizing:border-box;
+                width:100%;
+                max-width:70em;
+                margin:4em auto;
+                box-shadow:0 1em 6em #00000080;
+                padding:1em 1em 4em 1em;
+                border-radius:0.4em;
+            }
+            h1,h2,h3,h4,h5,h6{
+                font-weight:300;
+                margin-bottom: 0.1em;
+            }
+            h1{
+                text-align:center;
+            }
+            table{
+                margin:2em 0;
+                width:100%;
+            }
+            table, tr, th, td {
+                border: 1px solid #AAAAAA;
+            }
+            th {
+                width: 6em;
+            }
+            td {
+                word-break: break-all;
+            }
+            div {
+                margin: 1em 0;
+            }
+        </style>
+    </head>
+    <body>
+        <h1>LibreSpeed - Stats</h1>
+        <?php
+        if (!isset($stats_password) || $stats_password === 'PASSWORD') {
+            ?>
+                Please set $stats_password in telemetry_settings.php to enable access.
+            <?php
+        } elseif ($_SESSION['logged'] === true) {
+            if ($_GET['op'] === 'logout') {
+                $_SESSION['logged'] = false;
+                ?><script type="text/javascript">window.location=location.protocol+"//"+location.host+location.pathname;</script><?php
+            } else {
+                ?>
+                <form action="stats.php" method="GET"><input type="hidden" name="op" value="logout" /><input type="submit" value="Logout" /></form>
+                <form action="stats.php" method="GET">
+                    <h3>Search test results</h3>
+                    <input type="hidden" name="op" value="id" />
+                    <input type="text" name="id" id="id" placeholder="Test ID" value=""/>
+                    <input type="submit" value="Find" />
+                    <input type="submit" onclick="document.getElementById('id').value=''" value="Show last 100 tests" />
+                </form>
+                <?php
+                if ($_GET['op'] === 'id' && !empty($_GET['id'])) {
+                    $speedtest = getSpeedtestUserById($_GET['id']);
+                    $speedtests = [];
+                    if (false === $speedtest) {
+                        echo '<div>There was an error trying to fetch the speedtest result for ID "'.$_GET['id'].'".</div>';
+                    } elseif (null === $speedtest) {
+                        echo '<div>Could not find a speedtest result for ID "'.$_GET['id'].'".</div>';
+                    } else {
+                        $speedtests = [$speedtest];
+                    }
+                } else {
+                    $speedtests = getLatestSpeedtestUsers();
+                    if (false === $speedtests) {
+                        echo '<div>There was an error trying to fetch latest speedtest results.</div>';
+                    } elseif (empty($speedtests)) {
+                        echo '<div>Could not find any speedtest results in database.</div>';
+                    }
+                }
+                foreach ($speedtests as $speedtest) {
+                    ?>
+                    <table>
+                        <tr>
+                            <th>Test ID</th>
+                            <td><?= htmlspecialchars($speedtest['id_formatted'], ENT_HTML5, 'UTF-8') ?></td>
+                        </tr>
+                        <tr>
+                            <th>Date and time</th>
+                            <td><?= htmlspecialchars($speedtest['timestamp'], ENT_HTML5, 'UTF-8') ?></td>
+                        </tr>
+                        <tr>
+                            <th>IP and ISP Info</th>
+                            <td>
+                                <?= htmlspecialchars($speedtest['ip'], ENT_HTML5, 'UTF-8') ?><br/>
+                                <?= htmlspecialchars($speedtest['ispinfo'], ENT_HTML5, 'UTF-8') ?>
+                            </td>
+                        </tr>
+                        <tr>
+                            <th>User agent and locale</th>
+                            <td><?= htmlspecialchars($speedtest['ua'], ENT_HTML5, 'UTF-8') ?><br/>
+                                <?= htmlspecialchars($speedtest['lang'], ENT_HTML5, 'UTF-8') ?>
+                            </td>
+                        </tr>
+                        <tr>
+                            <th>Download speed</th>
+                            <td><?= htmlspecialchars($speedtest['dl'], ENT_HTML5, 'UTF-8') ?></td>
+                        </tr>
+                        <tr>
+                            <th>Upload speed</th>
+                            <td><?= htmlspecialchars($speedtest['ul'], ENT_HTML5, 'UTF-8') ?></td>
+                        </tr>
+                        <tr>
+                            <th>Ping</th>
+                            <td><?= htmlspecialchars($speedtest['ping'], ENT_HTML5, 'UTF-8') ?></td>
+                        </tr>
+                        <tr>
+                            <th>Jitter</th>
+                            <td><?= htmlspecialchars($speedtest['jitter'], ENT_HTML5, 'UTF-8') ?></td>
+                        </tr>
+                        <tr>
+                            <th>Log</th>
+                            <td><?= htmlspecialchars($speedtest['log'], ENT_HTML5, 'UTF-8') ?></td>
+                        </tr>
+                        <tr>
+                            <th>Extra info</th>
+                            <td><?= htmlspecialchars($speedtest['extra'], ENT_HTML5, 'UTF-8') ?></td>
+                        </tr>
+                    </table>
+                    <?php
+                }
+            }
+        } elseif ($_GET['op'] === 'login' && $_POST['password'] === $stats_password) {
+            $_SESSION['logged'] = true;
+            ?><script type="text/javascript">window.location=location.protocol+"//"+location.host+location.pathname;</script><?php
+        } else {
+            ?>
+            <form action="stats.php?op=login" method="POST">
+                <h3>Login</h3>
+                <input type="password" name="password" placeholder="Password" value=""/>
+                <input type="submit" value="Login" />
+            </form>
+            <?php
+        }
+        ?>
+    </body>
 </html>

+ 32 - 71
results/telemetry.php

@@ -1,81 +1,42 @@
 <?php
-include_once('telemetry_settings.php');
-require 'idObfuscation.php';
 
-$ip=($_SERVER['REMOTE_ADDR']);
-$ispinfo=($_POST["ispinfo"]);
-$extra=($_POST["extra"]);
-$ua=($_SERVER['HTTP_USER_AGENT']);
-$lang=""; if(isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) $lang=($_SERVER['HTTP_ACCEPT_LANGUAGE']);
-$dl=($_POST["dl"]);
-$ul=($_POST["ul"]);
-$ping=($_POST["ping"]);
-$jitter=($_POST["jitter"]);
-$log=($_POST["log"]);
+require 'telemetry_settings.php';
+require_once 'telemetry_db.php';
 
-if($redact_ip_addresses){
-    $ip="0.0.0.0";
-    $ipv4_regex='/(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)/';
-    $ipv6_regex='/(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))/';
-    $hostname_regex='/"hostname":"([^\\\\"]|\\\\")*"/';
-    $ispinfo=preg_replace($ipv4_regex,"0.0.0.0",$ispinfo);
-    $ispinfo=preg_replace($ipv6_regex,"0.0.0.0",$ispinfo);
-    $ispinfo=preg_replace($hostname_regex,"\"hostname\":\"REDACTED\"",$ispinfo);
-    $log=preg_replace($ipv4_regex,"0.0.0.0",$log);
-    $log=preg_replace($ipv6_regex,"0.0.0.0",$log);
-    $log=preg_replace($hostname_regex,"\"hostname\":\"REDACTED\"",$log);
+$ip = $_SERVER['REMOTE_ADDR'];
+$ispinfo = $_POST['ispinfo'];
+$extra = $_POST['extra'];
+$ua = $_SERVER['HTTP_USER_AGENT'];
+$lang = '';
+if (isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) {
+    $lang = $_SERVER['HTTP_ACCEPT_LANGUAGE'];
+}
+$dl = $_POST['dl'];
+$ul = $_POST['ul'];
+$ping = $_POST['ping'];
+$jitter = $_POST['jitter'];
+$log = $_POST['log'];
+
+if (isset($redact_ip_addresses) && true === $redact_ip_addresses) {
+    $ip = '0.0.0.0';
+    $ipv4_regex = '/(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)/';
+    $ipv6_regex = '/(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))/';
+    $hostname_regex = '/"hostname":"([^\\\\"]|\\\\")*"/';
+    $ispinfo = preg_replace($ipv4_regex, '0.0.0.0', $ispinfo);
+    $ispinfo = preg_replace($ipv6_regex, '0.0.0.0', $ispinfo);
+    $ispinfo = preg_replace($hostname_regex, '"hostname":"REDACTED"', $ispinfo);
+    $log = preg_replace($ipv4_regex, '0.0.0.0', $log);
+    $log = preg_replace($ipv6_regex, '0.0.0.0', $log);
+    $log = preg_replace($hostname_regex, '"hostname":"REDACTED"', $log);
 }
 
 header('Cache-Control: no-store, no-cache, must-revalidate, max-age=0, s-maxage=0');
 header('Cache-Control: post-check=0, pre-check=0', false);
 header('Pragma: no-cache');
 
-if($db_type=="mysql"){
-    $conn = new mysqli($MySql_hostname, $MySql_username, $MySql_password, $MySql_databasename, $MySql_port) 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->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");
-	$id=$conn->insert_id;
-	echo "id ".($enable_id_obfuscation?obfuscateId($id):$id);
-    $conn->close() or die("6");
-
-}elseif($db_type=="sqlite"){
-    $conn = new PDO("sqlite:$Sqlite_db_file") or die("1");
-    $conn->exec("
-        CREATE TABLE IF NOT EXISTS `speedtest_users` (
-        `id`    INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
-		`ispinfo`    text,
-		`extra`    text,
-        `timestamp`     timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
-        `ip`    text NOT NULL,
-        `ua`    text NOT NULL,
-        `lang`  text NOT NULL,
-        `dl`    text,
-        `ul`    text,
-        `ping`  text,
-        `jitter`        text,
-        `log`   longtext
-        );
-    ");
-    $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");
-	$id=$conn->lastInsertId();
-	echo "id ".($enable_id_obfuscation?obfuscateId($id):$id);
-    $conn = null;
-}elseif($db_type=="postgresql"){
-    // Prepare connection parameters for db connection
-    $conn_host = "host=$PostgreSql_hostname";
-    $conn_db = "dbname=$PostgreSql_databasename";
-    $conn_user = "user=$PostgreSql_username";
-    $conn_password = "password=$PostgreSql_password";
-    // Create db connection
-    $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");
-	$id=$conn->lastInsertId();
-	echo "id ".($enable_id_obfuscation?obfuscateId($id):$id);
-    $conn = null;
+$id = insertSpeedtestUser($ip, $ispinfo, $extra, $ua, $lang, $dl, $ul, $ping, $jitter, $log);
+if (false === $id) {
+    exit(1);
 }
-else die("-1");
-?>
+
+echo 'id '.$id;

+ 220 - 0
results/telemetry_db.php

@@ -0,0 +1,220 @@
+<?php
+
+require_once 'idObfuscation.php';
+
+define('TELEMETRY_SETTINGS_FILE', 'telemetry_settings.php');
+
+/**
+ * @return PDO|false
+ */
+function getPdo()
+{
+    if (
+        !file_exists(TELEMETRY_SETTINGS_FILE)
+        || !is_readable(TELEMETRY_SETTINGS_FILE)
+    ) {
+        return false;
+    }
+
+    require TELEMETRY_SETTINGS_FILE;
+
+    if (!isset($db_type)) {
+        return false;
+    }
+
+    $pdoOptions = [
+        PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
+    ];
+
+    try {
+        if ('mysql' === $db_type) {
+            if (!isset(
+                $MySql_hostname,
+                $MySql_port,
+                $MySql_databasename,
+                $MySql_username,
+                $MySql_password
+            )) {
+                return false;
+            }
+
+            $dsn = 'mysql:'
+                .'host='.$MySql_hostname
+                .';port='.$MySql_port
+                .';dbname='.$MySql_databasename;
+
+            return new PDO($dsn, $MySql_username, $MySql_password, $pdoOptions);
+        }
+
+        if ('sqlite' === $db_type) {
+            if (!isset($Sqlite_db_file)) {
+                return false;
+            }
+
+            $pdo = new PDO('sqlite:'.$Sqlite_db_file, null, null, $pdoOptions);
+
+            $pdo->exec('
+                CREATE TABLE IF NOT EXISTS `speedtest_users` (
+                `id`    INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
+                `ispinfo`    text,
+                `extra`    text,
+                `timestamp`     timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
+                `ip`    text NOT NULL,
+                `ua`    text NOT NULL,
+                `lang`  text NOT NULL,
+                `dl`    text,
+                `ul`    text,
+                `ping`  text,
+                `jitter`        text,
+                `log`   longtext
+                );
+            ');
+
+            return $pdo;
+        }
+
+        if ('postgresql' === $db_type) {
+            if (!isset(
+                $PostgreSql_hostname,
+                $PostgreSql_databasename,
+                $PostgreSql_username,
+                $PostgreSql_password
+            )) {
+                return false;
+            }
+
+            $dsn = 'pgsql:'
+                .'host='.$PostgreSql_hostname
+                .';dbname='.$PostgreSql_databasename;
+
+            return new PDO($dsn, $PostgreSql_username, $PostgreSql_password, $pdoOptions);
+        }
+    } catch (Exception $e) {
+        return false;
+    }
+
+    return false;
+}
+
+/**
+ * @return bool
+ */
+function isObfuscationEnabled()
+{
+    require TELEMETRY_SETTINGS_FILE;
+
+    return
+        isset($enable_id_obfuscation)
+        && true === $enable_id_obfuscation;
+}
+
+/**
+ * @return string|false returns the id of the inserted column or false on error
+ */
+function insertSpeedtestUser($ip, $ispinfo, $extra, $ua, $lang, $dl, $ul, $ping, $jitter, $log)
+{
+    $pdo = getPdo();
+    if (!($pdo instanceof PDO)) {
+        return false;
+    }
+
+    try {
+        $stmt = $pdo->prepare(
+            'INSERT INTO speedtest_users
+        (ip,ispinfo,extra,ua,lang,dl,ul,ping,jitter,log)
+        VALUES (?,?,?,?,?,?,?,?,?,?)'
+        );
+        $stmt->execute([
+            $ip, $ispinfo, $extra, $ua, $lang, $dl, $ul, $ping, $jitter, $log
+        ]);
+        $id = $pdo->lastInsertId();
+    } catch (Exception $e) {
+        return false;
+    }
+
+    if (isObfuscationEnabled()) {
+        return obfuscateId($id);
+    }
+
+    return $id;
+}
+
+/**
+ * @param int|string $id
+ *
+ * @return array|null|false returns the speedtest data as array, null
+ *                          if no data is found for the given id or
+ *                          false if there was an error
+ *
+ * @throws RuntimeException
+ */
+function getSpeedtestUserById($id)
+{
+    $pdo = getPdo();
+    if (!($pdo instanceof PDO)) {
+        return false;
+    }
+
+    if (isObfuscationEnabled()) {
+        $id = deobfuscateId($id);
+    }
+
+    try {
+        $stmt = $pdo->prepare(
+            'SELECT
+            id, timestamp, ip, ispinfo, ua, lang, dl, ul, ping, jitter, log, extra
+            FROM speedtest_users
+            WHERE id = :id'
+        );
+        $stmt->bindValue(':id', $id, PDO::PARAM_INT);
+        $stmt->execute();
+        $row = $stmt->fetch(PDO::FETCH_ASSOC);
+    } catch (Exception $e) {
+        return false;
+    }
+
+    if (!is_array($row)) {
+        return null;
+    }
+
+    $row['id_formatted'] = $row['id'];
+    if (isObfuscationEnabled()) {
+        $row['id_formatted'] = obfuscateId($row['id']).' (deobfuscated: '.$row['id'].')';
+    }
+
+    return $row;
+}
+
+/**
+ * @return array|false
+ */
+function getLatestSpeedtestUsers()
+{
+    $pdo = getPdo();
+    if (!($pdo instanceof PDO)) {
+        return false;
+    }
+
+    try {
+        $stmt = $pdo->query(
+            'SELECT
+            id, timestamp, ip, ispinfo, ua, lang, dl, ul, ping, jitter, log, extra
+            FROM speedtest_users
+            ORDER BY timestamp DESC
+            LIMIT 100'
+        );
+
+        $rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
+
+        foreach ($rows as $i => $row) {
+            $rows[$i]['id_formatted'] = $row['id'];
+            if (isObfuscationEnabled()) {
+                $rows[$i]['id_formatted'] = obfuscateId($row['id']).' (deobfuscated: '.$row['id'].')';
+            }
+        }
+    } catch (Exception $e) {
+        return false;
+    }
+
+    return $rows;
+}

+ 18 - 18
results/telemetry_settings.php

@@ -1,26 +1,26 @@
 <?php
 
-$db_type="mysql"; //Type of db: "mysql", "sqlite" or "postgresql"
-$stats_password="PASSWORD"; //password to login to stats.php. Change this!!!
-$enable_id_obfuscation=false; //if set to true, test IDs will be obfuscated to prevent users from guessing URLs of other tests
-$redact_ip_addresses=false; //if set to true, IP addresses will be redacted from IP and ISP info fields, as well as the log
+// Type of db: "mysql", "sqlite" or "postgresql"
+$db_type = 'mysql';
+// Password to login to stats.php. Change this!!!
+$stats_password = 'PASSWORD';
+// 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, IP addresses will be redacted from IP and ISP info fields, as well as the log
+$redact_ip_addresses = false;
 
 // Sqlite3 settings
-$Sqlite_db_file = "../../speedtest_telemetry.sql";
+$Sqlite_db_file = '../../speedtest_telemetry.sql';
 
 // Mysql settings
-$MySql_username="USERNAME";
-$MySql_password="PASSWORD";
-$MySql_hostname="DB_HOSTNAME";
-$MySql_databasename="DB_NAME";
-$MySql_port="3306";
+$MySql_username = 'USERNAME';
+$MySql_password = 'PASSWORD';
+$MySql_hostname = 'DB_HOSTNAME';
+$MySql_databasename = 'DB_NAME';
+$MySql_port = '3306';
 
 // Postgresql settings
-$PostgreSql_username="USERNAME";
-$PostgreSql_password="PASSWORD";
-$PostgreSql_hostname="DB_HOSTNAME";
-$PostgreSql_databasename="DB_NAME";
-
-
-//IMPORTANT: DO NOT ADD ANYTHING BELOW THIS PHP CLOSING TAG, NOT EVEN EMPTY LINES!
-?>
+$PostgreSql_username = 'USERNAME';
+$PostgreSql_password = 'PASSWORD';
+$PostgreSql_hostname = 'DB_HOSTNAME';
+$PostgreSql_databasename = 'DB_NAME';