Skip to content

Commit eda4148

Browse files
author
Stefan Stidl
committed
Merge remote-tracking branch 'origin/master' into newdesign
# Conflicts: # doc.md # docker/ui.php # index.html # package.json # speedtest.js
2 parents 27acb2d + e1310d8 commit eda4148

File tree

9 files changed

+83
-22
lines changed

9 files changed

+83
-22
lines changed

.github/workflows/docker-publish.yml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -59,19 +59,19 @@ jobs:
5959
6060
# Set up QEMU for multi-arch builds
6161
- name: Set up QEMU
62-
uses: docker/setup-qemu-action@v3
62+
uses: docker/setup-qemu-action@v4
6363

6464
# Set up BuildKit Docker container builder to be able to build
6565
# multi-platform images and export cache
6666
# https://github.com/docker/setup-buildx-action
6767
- name: Set up Docker Buildx
68-
uses: docker/setup-buildx-action@v3
68+
uses: docker/setup-buildx-action@v4
6969

7070
# Login against a Docker registry except on PR
7171
# https://github.com/docker/login-action
7272
- name: Log into registry ${{ env.REGISTRY }}
7373
if: github.event_name != 'pull_request'
74-
uses: docker/login-action@v3
74+
uses: docker/login-action@v4
7575
with:
7676
registry: ${{ env.REGISTRY }}
7777
username: ${{ github.actor }}
@@ -81,7 +81,7 @@ jobs:
8181
# https://github.com/docker/metadata-action
8282
- name: Extract Docker metadata
8383
id: meta
84-
uses: docker/metadata-action@v5
84+
uses: docker/metadata-action@v6
8585
with:
8686
images: ${{ matrix.image }}
8787
tags: |
@@ -98,7 +98,7 @@ jobs:
9898
# https://github.com/docker/build-push-action
9999
- name: Build and push Docker image
100100
id: build-and-push
101-
uses: docker/build-push-action@v6
101+
uses: docker/build-push-action@v7
102102
with:
103103
context: .
104104
file: ${{ matrix.dockerfile }}

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,6 @@ db-dir/
44
.vscode/
55
node_modules/
66
package-lock.json
7+
results/speedtest_telemetry.db
8+
results/speedtest_telemetry.db-shm
9+
results/speedtest_telemetry.db-wal

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,10 @@ A template to build an Android client for your LibreSpeed installation is availa
5959

6060
A command line client is available [here](https://github.com/librespeed/speedtest-cli).
6161

62+
## .NET client
63+
64+
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).
65+
6266
## Development
6367

6468
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.

backend/getIP.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -134,11 +134,11 @@ function getIspInfo_ipinfoApi($ip){
134134
]);
135135
}
136136

137-
if (PHP_MAJOR_VERSION >= 8){
137+
if (PHP_VERSION_ID >= 80100){
138138
require_once("geoip2.phar");
139139
}
140140
function getIspInfo_ipinfoOfflineDb($ip){
141-
if (PHP_MAJOR_VERSION < 8 || !file_exists(OFFLINE_IPINFO_DB_FILE) || !is_readable(OFFLINE_IPINFO_DB_FILE)){
141+
if (PHP_VERSION_ID < 80100 || !file_exists(OFFLINE_IPINFO_DB_FILE) || !is_readable(OFFLINE_IPINFO_DB_FILE)){
142142
return null;
143143
}
144144
$reader = new MaxMind\Db\Reader(OFFLINE_IPINFO_DB_FILE);

backend/getIP_util.php

Lines changed: 48 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,55 @@
11
<?php
22

33
/**
4-
* @return string
4+
* Normalize and validate an IP address candidate from a request header.
5+
*
6+
* Trims whitespace, takes the first comma-separated token (for XFF-like
7+
* headers that may contain a chain of addresses), and validates the result
8+
* with filter_var().
9+
*
10+
* @param string $raw Raw header value.
11+
* @param int $extraFlags Additional FILTER_FLAG_* flags (e.g. FILTER_FLAG_IPV6).
12+
*
13+
* @return string|false The validated IP string, or false on failure.
514
*/
6-
function getClientIp() {
7-
if (!empty($_SERVER['HTTP_CLIENT_IP'])) {
8-
$ip = $_SERVER['HTTP_CLIENT_IP'];
9-
} elseif (!empty($_SERVER['HTTP_X_REAL_IP'])) {
10-
$ip = $_SERVER['HTTP_X_REAL_IP'];
11-
} elseif (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
12-
$ip = $_SERVER['HTTP_X_FORWARDED_FOR'];
13-
$ip = preg_replace('/,.*/', '', $ip); # hosts are comma-separated, client is first
14-
} else {
15-
$ip = $_SERVER['REMOTE_ADDR'];
15+
function normalizeCandidateIp($raw, $extraFlags = 0)
16+
{
17+
$ip = trim($raw);
18+
// For XFF-like values, take the first address before a comma.
19+
if (($pos = strpos($ip, ',')) !== false) {
20+
$ip = trim(substr($ip, 0, $pos));
21+
}
22+
if ($ip === '') {
23+
return false;
1624
}
25+
return filter_var($ip, FILTER_VALIDATE_IP, $extraFlags);
26+
}
1727

18-
return preg_replace('/^::ffff:/', '', $ip);
28+
/**
29+
* @return string
30+
*/
31+
function getClientIp()
32+
{
33+
// Cloudflare IPv6 header — must be a valid IPv6 address.
34+
if (!empty($_SERVER['HTTP_CF_CONNECTING_IPV6'])) {
35+
$ip = normalizeCandidateIp($_SERVER['HTTP_CF_CONNECTING_IPV6'], FILTER_FLAG_IPV6);
36+
if ($ip !== false) {
37+
return preg_replace('/^::ffff:/', '', $ip);
38+
}
39+
}
40+
// Other forwarding / proxy headers — accept any valid IP.
41+
foreach (['HTTP_CLIENT_IP', 'HTTP_X_REAL_IP', 'HTTP_X_FORWARDED_FOR'] as $header) {
42+
if (!empty($_SERVER[$header])) {
43+
$ip = normalizeCandidateIp($_SERVER[$header]);
44+
if ($ip !== false) {
45+
return preg_replace('/^::ffff:/', '', $ip);
46+
}
47+
}
48+
}
49+
// Fallback: REMOTE_ADDR is set by the web server and is always a single IP.
50+
$ip = normalizeCandidateIp($_SERVER['REMOTE_ADDR'] ?? '');
51+
if ($ip !== false) {
52+
return preg_replace('/^::ffff:/', '', $ip);
53+
}
54+
return $_SERVER['REMOTE_ADDR'] ?? '';
1955
}

docker/entrypoint.sh

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,9 @@ if [[ "$TELEMETRY" == "true" && ("$MODE" == "frontend" || "$MODE" == "standalone
121121
sed -i s/\$db_type\ =\ \'.*\'/\$db_type\ =\ \'sqlite\'\/g /var/www/html/results/telemetry_settings.php
122122
fi
123123

124-
sed -i s/\$Sqlite_db_file\ =\ \'.*\'/\$Sqlite_db_file=\'\\\/database\\\/db.sql\'/g /var/www/html/results/telemetry_settings.php
124+
# Override SQLite database path for Docker environment
125+
# In Docker, we use /database/db.sql which is outside the web-accessible directory
126+
sed -i s/\$Sqlite_db_file\ =\ .*\'/\$Sqlite_db_file=\'\\\/database\\\/db.sql\'/g /var/www/html/results/telemetry_settings.php
125127
sed -i s/\$stats_password\ =\ \'.*\'/\$stats_password\ =\ \'$PASSWORD\'/g /var/www/html/results/telemetry_settings.php
126128

127129
if [ "$ENABLE_ID_OBFUSCATION" == "true" ]; then

examples/example-multipleServers-full.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,7 @@
147147
if(testId!=null){
148148
var shareURL=window.location.href.substring(0,window.location.href.lastIndexOf("/"))+"/results/?id="+testId;
149149
I("resultsImg").src=shareURL;
150+
I("resultsImg").alt="Download: "+uiData.dlStatus+" Mbps, Upload: "+uiData.ulStatus+" Mbps, Ping: "+uiData.pingStatus+" ms, Jitter: "+uiData.jitterStatus+" ms";
150151
I("resultsURL").value=shareURL;
151152
I("testId").innerHTML=testId;
152153
I("shareArea").style.display="";

results/telemetry_db.php

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,21 @@ function getPdo($returnErrorMessage = false)
104104
return false;
105105
}
106106

107+
// Check if directory exists and is writable
108+
$db_dir = dirname($Sqlite_db_file);
109+
if (!is_dir($db_dir)) {
110+
if ($returnErrorMessage) {
111+
return "SQLite database directory does not exist: " . $db_dir . ". Please create it and ensure it's writable by the web server.";
112+
}
113+
return false;
114+
}
115+
if (!is_writable($db_dir)) {
116+
if ($returnErrorMessage) {
117+
return "SQLite database directory is not writable: " . $db_dir . ". Please ensure the web server has write permissions (e.g., chmod 755 or 775).";
118+
}
119+
return false;
120+
}
121+
107122
$pdo = new PDO('sqlite:'.$Sqlite_db_file, null, null, $pdoOptions);
108123

109124
# TODO: Why create table only in sqlite mode?

results/telemetry_settings.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<?php
22

33
// Type of db: "mssql", "mysql", "sqlite" or "postgresql"
4-
$db_type = 'mysql';
4+
$db_type = 'sqlite';
55
// Password to login to stats.php. Change this!!!
66
$stats_password = 'PASSWORD';
77
// If set to true, test IDs will be obfuscated to prevent users from guessing URLs of other tests
@@ -10,7 +10,7 @@
1010
$redact_ip_addresses = false;
1111

1212
// Sqlite3 settings
13-
$Sqlite_db_file = '../../speedtest_telemetry.sql';
13+
$Sqlite_db_file = __DIR__ . '/../../speedtest_telemetry.db';
1414

1515
// mssql settings
1616
$MsSql_server = 'DB_HOSTNAME';

0 commit comments

Comments
 (0)