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

# Conflicts:
#	doc.md
#	docker/ui.php
#	index.html
#	package.json
#	speedtest.js
This commit is contained in:
Stefan Stidl 2026-03-18 11:15:03 +01:00
commit eda4148bcb
9 changed files with 83 additions and 22 deletions

View file

@ -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
.gitignore vendored
View file

@ -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

View file

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

View file

@ -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);

View file

@ -1,19 +1,55 @@
<?php
/**
* 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 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 string
*/
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 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);
}
}
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'] ?? '';
}

View file

@ -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

View file

@ -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="";

View file

@ -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?

View file

@ -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';