Просмотр исходного кода

Merge remote-tracking branch 'origin/master' into newdesign

# Conflicts:
#	doc.md
#	docker/ui.php
#	index.html
#	package.json
#	speedtest.js
Stefan Stidl 3 недель назад
Родитель
Сommit
eda4148bcb

+ 5 - 5
.github/workflows/docker-publish.yml

@@ -59,19 +59,19 @@ jobs:
 
       # Set up QEMU for multi-arch builds
       - name: Set up QEMU
-        uses: docker/setup-qemu-action@v3
+        uses: docker/setup-qemu-action@v4
 
       # Set up BuildKit Docker container builder to be able to build
       # multi-platform images and export cache
       # https://github.com/docker/setup-buildx-action
       - name: Set up Docker Buildx
-        uses: docker/setup-buildx-action@v3
+        uses: docker/setup-buildx-action@v4
 
       # Login against a Docker registry except on PR
       # https://github.com/docker/login-action
       - name: Log into registry ${{ env.REGISTRY }}
         if: github.event_name != 'pull_request'
-        uses: docker/login-action@v3
+        uses: docker/login-action@v4
         with:
           registry: ${{ env.REGISTRY }}
           username: ${{ github.actor }}
@@ -81,7 +81,7 @@ jobs:
       # https://github.com/docker/metadata-action
       - name: Extract Docker metadata
         id: meta
-        uses: docker/metadata-action@v5
+        uses: docker/metadata-action@v6
         with:
           images: ${{ matrix.image }}
           tags: |
@@ -98,7 +98,7 @@ jobs:
       # https://github.com/docker/build-push-action
       - name: Build and push Docker image
         id: build-and-push
-        uses: docker/build-push-action@v6
+        uses: docker/build-push-action@v7
         with:
           context: .
           file: ${{ matrix.dockerfile }}

+ 3 - 0
.gitignore

@@ -4,3 +4,6 @@ db-dir/
 .vscode/
 node_modules/
 package-lock.json
+results/speedtest_telemetry.db
+results/speedtest_telemetry.db-shm
+results/speedtest_telemetry.db-wal

+ 4 - 0
README.md

@@ -59,6 +59,10 @@ A template to build an Android client for your LibreSpeed installation is availa
 
 A command line client is available [here](https://github.com/librespeed/speedtest-cli).
 
+## .NET client
+
+A .NET client library is available in the [`LibreSpeed.NET`](https://github.com/Memphizzz/LibreSpeed.NET) repo ([NuGet](https://www.nuget.org/packages/LibreSpeed.NET)), maintained by [MemphiZ](https://github.com/Memphizzz).
+
 ## Development
 
 If you want to contribute or develop with LibreSpeed, see [DEVELOPMENT.md](DEVELOPMENT.md) for information about using npm for development tasks, linting, and formatting.

+ 2 - 2
backend/getIP.php

@@ -134,11 +134,11 @@ function getIspInfo_ipinfoApi($ip){
     ]);
 }
 
-if (PHP_MAJOR_VERSION >= 8){
+if (PHP_VERSION_ID >= 80100){
     require_once("geoip2.phar");
 }
 function getIspInfo_ipinfoOfflineDb($ip){
-    if (PHP_MAJOR_VERSION < 8 || !file_exists(OFFLINE_IPINFO_DB_FILE) || !is_readable(OFFLINE_IPINFO_DB_FILE)){
+    if (PHP_VERSION_ID < 80100 || !file_exists(OFFLINE_IPINFO_DB_FILE) || !is_readable(OFFLINE_IPINFO_DB_FILE)){
         return null;
     }
     $reader = new MaxMind\Db\Reader(OFFLINE_IPINFO_DB_FILE);

+ 48 - 12
backend/getIP_util.php

@@ -1,19 +1,55 @@
 <?php
 
 /**
- * @return string
+ * Normalize and validate an IP address candidate from a request header.
+ *
+ * Trims whitespace, takes the first comma-separated token (for XFF-like
+ * headers that may contain a chain of addresses), and validates the result
+ * with filter_var().
+ *
+ * @param string $raw       Raw header value.
+ * @param int    $extraFlags Additional FILTER_FLAG_* flags (e.g. FILTER_FLAG_IPV6).
+ *
+ * @return string|false The validated IP string, or false on failure.
  */
-function getClientIp() {
-    if (!empty($_SERVER['HTTP_CLIENT_IP'])) {
-        $ip = $_SERVER['HTTP_CLIENT_IP'];
-    } elseif (!empty($_SERVER['HTTP_X_REAL_IP'])) {
-        $ip = $_SERVER['HTTP_X_REAL_IP'];
-    } elseif (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
-        $ip = $_SERVER['HTTP_X_FORWARDED_FOR'];
-        $ip = preg_replace('/,.*/', '', $ip); # hosts are comma-separated, client is first
-    } else {
-        $ip = $_SERVER['REMOTE_ADDR'];
+function normalizeCandidateIp($raw, $extraFlags = 0)
+{
+    $ip = trim($raw);
+    // For XFF-like values, take the first address before a comma.
+    if (($pos = strpos($ip, ',')) !== false) {
+        $ip = trim(substr($ip, 0, $pos));
+    }
+    if ($ip === '') {
+        return false;
     }
+    return filter_var($ip, FILTER_VALIDATE_IP, $extraFlags);
+}
 
-    return preg_replace('/^::ffff:/', '', $ip);
+/**
+ * @return string
+ */
+function getClientIp()
+{
+    // Cloudflare IPv6 header — must be a valid IPv6 address.
+    if (!empty($_SERVER['HTTP_CF_CONNECTING_IPV6'])) {
+        $ip = normalizeCandidateIp($_SERVER['HTTP_CF_CONNECTING_IPV6'], FILTER_FLAG_IPV6);
+        if ($ip !== false) {
+            return preg_replace('/^::ffff:/', '', $ip);
+        }
+    }
+    // Other forwarding / proxy headers — accept any valid IP.
+    foreach (['HTTP_CLIENT_IP', 'HTTP_X_REAL_IP', 'HTTP_X_FORWARDED_FOR'] as $header) {
+        if (!empty($_SERVER[$header])) {
+            $ip = normalizeCandidateIp($_SERVER[$header]);
+            if ($ip !== false) {
+                return preg_replace('/^::ffff:/', '', $ip);
+            }
+        }
+    }
+    // Fallback: REMOTE_ADDR is set by the web server and is always a single IP.
+    $ip = normalizeCandidateIp($_SERVER['REMOTE_ADDR'] ?? '');
+    if ($ip !== false) {
+        return preg_replace('/^::ffff:/', '', $ip);
+    }
+    return $_SERVER['REMOTE_ADDR'] ?? '';
 }

+ 3 - 1
docker/entrypoint.sh

@@ -121,7 +121,9 @@ if [[ "$TELEMETRY" == "true" && ("$MODE" == "frontend" || "$MODE" == "standalone
     sed -i s/\$db_type\ =\ \'.*\'/\$db_type\ =\ \'sqlite\'\/g /var/www/html/results/telemetry_settings.php
   fi
 
-  sed -i s/\$Sqlite_db_file\ =\ \'.*\'/\$Sqlite_db_file=\'\\\/database\\\/db.sql\'/g /var/www/html/results/telemetry_settings.php
+  # Override SQLite database path for Docker environment
+  # In Docker, we use /database/db.sql which is outside the web-accessible directory
+  sed -i s/\$Sqlite_db_file\ =\ .*\'/\$Sqlite_db_file=\'\\\/database\\\/db.sql\'/g /var/www/html/results/telemetry_settings.php
   sed -i s/\$stats_password\ =\ \'.*\'/\$stats_password\ =\ \'$PASSWORD\'/g /var/www/html/results/telemetry_settings.php
 
   if [ "$ENABLE_ID_OBFUSCATION" == "true" ]; then

+ 1 - 0
examples/example-multipleServers-full.html

@@ -147,6 +147,7 @@ function startStop(){
                     if(testId!=null){
                         var shareURL=window.location.href.substring(0,window.location.href.lastIndexOf("/"))+"/results/?id="+testId;
                         I("resultsImg").src=shareURL;
+                        I("resultsImg").alt="Download: "+uiData.dlStatus+" Mbps, Upload: "+uiData.ulStatus+" Mbps, Ping: "+uiData.pingStatus+" ms, Jitter: "+uiData.jitterStatus+" ms";
                         I("resultsURL").value=shareURL;
                         I("testId").innerHTML=testId;
                         I("shareArea").style.display="";

+ 15 - 0
results/telemetry_db.php

@@ -104,6 +104,21 @@ function getPdo($returnErrorMessage = false)
                 return false;
             }
 
+			// Check if directory exists and is writable
+			$db_dir = dirname($Sqlite_db_file);
+			if (!is_dir($db_dir)) {
+				if ($returnErrorMessage) {
+					return "SQLite database directory does not exist: " . $db_dir . ". Please create it and ensure it's writable by the web server.";
+				}
+				return false;
+			}
+			if (!is_writable($db_dir)) {
+				if ($returnErrorMessage) {
+					return "SQLite database directory is not writable: " . $db_dir . ". Please ensure the web server has write permissions (e.g., chmod 755 or 775).";
+				}
+				return false;
+			}
+
             $pdo = new PDO('sqlite:'.$Sqlite_db_file, null, null, $pdoOptions);
 
             # TODO: Why create table only in sqlite mode?

+ 2 - 2
results/telemetry_settings.php

@@ -1,7 +1,7 @@
 <?php
 
 // Type of db: "mssql", "mysql", "sqlite" or "postgresql"
-$db_type = 'mysql';
+$db_type = 'sqlite';
 // 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
@@ -10,7 +10,7 @@ $enable_id_obfuscation = false;
 $redact_ip_addresses = false;
 
 // Sqlite3 settings
-$Sqlite_db_file = '../../speedtest_telemetry.sql';
+$Sqlite_db_file = __DIR__ . '/../../speedtest_telemetry.db';
 
 // mssql settings
 $MsSql_server = 'DB_HOSTNAME';