prettier-run for code-formatting

This commit is contained in:
2025-09-15 08:25:49 +02:00
parent c7ed7ef903
commit 216110992f
2 changed files with 387 additions and 344 deletions

View File

@@ -94,8 +94,9 @@ final class ImageFetch
$ok = false;
foreach ($cfg['whitelist_hosts'] as $allowed) {
$allowed = strtolower($allowed);
if ($host === $allowed || str_ends_with($host, '.'.$allowed)) {
$ok = true; break;
if ($host === $allowed || str_ends_with($host, '.' . $allowed)) {
$ok = true;
break;
}
}
if (!$ok) {
@@ -119,7 +120,7 @@ final class ImageFetch
$originRef = self::originFromUrl($url);
$referer = match ($cfg['referer']) {
'none' => null,
'custom'=> (string)$cfg['custom_referer'],
'custom' => (string) $cfg['custom_referer'],
default => $originRef, // auto
};
@@ -135,7 +136,7 @@ final class ImageFetch
];
// 4) Retries
$attempts = max(1, (int)$cfg['retries']);
$attempts = max(1, (int) $cfg['retries']);
$received = 0;
$lastHttp = null;
$lastCurlErr = null;
@@ -160,9 +161,9 @@ final class ImageFetch
$received = 0;
$opts = [
CURLOPT_FOLLOWLOCATION => true,
CURLOPT_MAXREDIRS => (int)$cfg['max_redirects'],
CURLOPT_CONNECTTIMEOUT => (int)$cfg['connect_timeout'],
CURLOPT_TIMEOUT => (int)$cfg['timeout'],
CURLOPT_MAXREDIRS => (int) $cfg['max_redirects'],
CURLOPT_CONNECTTIMEOUT => (int) $cfg['connect_timeout'],
CURLOPT_TIMEOUT => (int) $cfg['timeout'],
CURLOPT_USERAGENT => $ua,
CURLOPT_SSL_VERIFYPEER => true,
CURLOPT_SSL_VERIFYHOST => 2,
@@ -173,7 +174,7 @@ final class ImageFetch
CURLOPT_WRITEFUNCTION => function ($ch, $data) use (&$received, $cfg, $fh) {
$len = strlen($data);
$received += $len;
if ($received > (int)$cfg['max_bytes']) {
if ($received > (int) $cfg['max_bytes']) {
return 0; // -> CURLE_WRITE_ERROR
}
return fwrite($fh, $data);
@@ -202,7 +203,7 @@ final class ImageFetch
$lastCurlErr = $cerrStr;
// Abbruch durch Größenlimit → als 413 semantisch behandeln (Payload Too Large)
if ($exec === false && $cerr === CURLE_WRITE_ERROR && $received > (int)$cfg['max_bytes']) {
if ($exec === false && $cerr === CURLE_WRITE_ERROR && $received > (int) $cfg['max_bytes']) {
@unlink($tmp);
error_log("{$cfg['log_prefix']} size limit hit after {$received} bytes url=$url");
return self::fail(null, null, 413, 'Bild zu groß');
@@ -227,18 +228,18 @@ final class ImageFetch
// sonst retry
@unlink($tmp);
$doRetry = ($i + 1) < $attempts;
error_log("{$cfg['log_prefix']} bad ctype http=$http ctype={$ctype} retry=".($doRetry?'1':'0')." url=$url");
error_log("{$cfg['log_prefix']} bad ctype http=$http ctype={$ctype} retry=" . ($doRetry ? '1' : '0') . " url=$url");
} else {
// Fehler oder Non-2xx → ggf. retry
@unlink($tmp);
$doRetry = ($i + 1) < $attempts &&
(in_array($http, self::$RETRY_HTTP, true) || in_array($cerr, self::retryCurlCodes(), true) || $http === 0);
error_log("{$cfg['log_prefix']} fail http=$http curl={$cerr}:{$cerrStr} ua#{$i} retry=".($doRetry?'1':'0')." url=$url");
error_log("{$cfg['log_prefix']} fail http=$http curl={$cerr}:{$cerrStr} ua#{$i} retry=" . ($doRetry ? '1' : '0') . " url=$url");
}
// Backoff + Jitter vorm nächsten Versuch
if (($i + 1) < $attempts) {
$sleepMs = (int)($cfg['retry_backoff_ms'] * (2 ** $i) + random_int(0, 150));
$sleepMs = (int) ($cfg['retry_backoff_ms'] * (2 ** $i) + random_int(0, 150));
usleep($sleepMs * 1000);
}
@@ -269,16 +270,19 @@ final class ImageFetch
public static function safeFileNameFromUrl(string $url): string
{
$stripped = strtok($url, '?#');
$ext = strtolower(pathinfo((string)$stripped, PATHINFO_EXTENSION));
if (!preg_match('/^[a-z0-9]{1,5}$/i', $ext)) $ext = 'jpg';
return bin2hex(random_bytes(10)).'.'.$ext;
$ext = strtolower(pathinfo((string) $stripped, PATHINFO_EXTENSION));
if (!preg_match('/^[a-z0-9]{1,5}$/i', $ext))
$ext = 'jpg';
return bin2hex(random_bytes(10)) . '.' . $ext;
}
private static function isValidHttpUrl(string $url): bool
{
if (!filter_var($url, FILTER_VALIDATE_URL)) return false;
if (!filter_var($url, FILTER_VALIDATE_URL))
return false;
$p = parse_url($url);
if (!$p || empty($p['scheme']) || empty($p['host'])) return false;
if (!$p || empty($p['scheme']) || empty($p['host']))
return false;
$s = strtolower($p['scheme']);
return $s === 'http' || $s === 'https';
}
@@ -286,23 +290,28 @@ final class ImageFetch
private static function originFromUrl(string $url): string
{
$p = parse_url($url);
if (!$p || empty($p['scheme']) || empty($p['host'])) return '';
if (!$p || empty($p['scheme']) || empty($p['host']))
return '';
$port = '';
if (!empty($p['port'])) {
$default = ($p['scheme'] === 'https') ? 443 : 80;
if ((int)$p['port'] !== $default) $port = ':'.$p['port'];
if ((int) $p['port'] !== $default)
$port = ':' . $p['port'];
}
return $p['scheme'].'://'.$p['host'].$port.'/';
return $p['scheme'] . '://' . $p['host'] . $port . '/';
}
private static function hostResolvesPublic(string $host): bool
{
$recs = @dns_get_record($host, DNS_A + DNS_AAAA);
if (!$recs || !count($recs)) return false;
if (!$recs || !count($recs))
return false;
foreach ($recs as $r) {
$ip = $r['type'] === 'A' ? ($r['ip'] ?? null) : ($r['ipv6'] ?? null);
if (!$ip) continue;
if (self::isPrivateIp($ip)) return false;
if (!$ip)
continue;
if (self::isPrivateIp($ip))
return false;
}
return true;
}
@@ -310,13 +319,15 @@ final class ImageFetch
private static function isPrivateIp(string $ip): bool
{
if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
$cidrs = ['10.0.0.0/8','172.16.0.0/12','192.168.0.0/16','127.0.0.0/8','169.254.0.0/16'];
$cidrs = ['10.0.0.0/8', '172.16.0.0/12', '192.168.0.0/16', '127.0.0.0/8', '169.254.0.0/16'];
} elseif (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
$cidrs = ['::1/128','fc00::/7','fe80::/10'];
$cidrs = ['::1/128', 'fc00::/7', 'fe80::/10'];
} else {
return true;
}
foreach ($cidrs as $c) if (self::ipInCidr($ip, $c)) return true;
foreach ($cidrs as $c)
if (self::ipInCidr($ip, $c))
return true;
return false;
}
@@ -324,13 +335,15 @@ final class ImageFetch
{
if (strpos($cidr, ':') !== false) {
[$subnet, $mask] = array_pad(explode('/', $cidr, 2), 2, null);
$mask = (int)$mask;
$mask = (int) $mask;
$binIp = inet_pton($ip);
$binSubnet = inet_pton($subnet);
if ($binIp === false || $binSubnet === false) return false;
if ($binIp === false || $binSubnet === false)
return false;
$bytes = intdiv($mask, 8);
$bits = $mask % 8;
if ($bytes && substr($binIp, 0, $bytes) !== substr($binSubnet, 0, $bytes)) return false;
if ($bytes && substr($binIp, 0, $bytes) !== substr($binSubnet, 0, $bytes))
return false;
if ($bits) {
$b1 = ord($binIp[$bytes]) & (0xFF << (8 - $bits));
$b2 = ord($binSubnet[$bytes]) & (0xFF << (8 - $bits));
@@ -339,10 +352,11 @@ final class ImageFetch
return true;
} else {
[$subnet, $mask] = array_pad(explode('/', $cidr, 2), 2, null);
$mask = (int)$mask;
$mask = (int) $mask;
$ipL = ip2long($ip);
$subL = ip2long($subnet);
if ($ipL === false || $subL === false) return false;
if ($ipL === false || $subL === false)
return false;
$maskL = -1 << (32 - $mask);
return (($ipL & $maskL) === ($subL & $maskL));
}
@@ -350,7 +364,8 @@ final class ImageFetch
private static function fail(?string $tmp, ?string $cerr, int $http, string $msg): array
{
if ($tmp && is_file($tmp)) @unlink($tmp);
if ($tmp && is_file($tmp))
@unlink($tmp);
return [
'ok' => false,
'tmp_path' => null,

View File

@@ -4,7 +4,11 @@ declare(strict_types=1);
/* ========= Session & Bootstrap ========= */
$secure = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off');
session_set_cookie_params([
'lifetime'=>0,'path'=>'/','secure'=>$secure,'httponly'=>true,'samesite'=>'Lax',
'lifetime' => 0,
'path' => '/',
'secure' => $secure,
'httponly' => true,
'samesite' => 'Lax',
]);
session_name('WLISTSESSID');
session_start();
@@ -14,44 +18,62 @@ require_once __DIR__ . '/config/config.php';
/* ===== Debug Toggle (wie item.php) ===== */
if (!empty($app_debug)) {
ini_set('display_errors','1'); ini_set('display_startup_errors','1'); ini_set('log_errors','1');
if (!is_dir(__DIR__.'/logs')) { @mkdir(__DIR__.'/logs',0750,true); }
ini_set('error_log', __DIR__.'/logs/php-error.log'); error_reporting(E_ALL);
ini_set('display_errors', '1');
ini_set('display_startup_errors', '1');
ini_set('log_errors', '1');
if (!is_dir(__DIR__ . '/logs')) {
@mkdir(__DIR__ . '/logs', 0750, true);
}
ini_set('error_log', __DIR__ . '/logs/php-error.log');
error_reporting(E_ALL);
} else {
ini_set('display_errors','0'); ini_set('display_startup_errors','0'); ini_set('log_errors','1');
if (!is_dir(__DIR__.'/logs')) { @mkdir(__DIR__.'/logs',0750,true); }
ini_set('error_log', __DIR__.'/logs/php-error.log'); error_reporting(E_ALL);
ini_set('display_errors', '0');
ini_set('display_startup_errors', '0');
ini_set('log_errors', '1');
if (!is_dir(__DIR__ . '/logs')) {
@mkdir(__DIR__ . '/logs', 0750, true);
}
ini_set('error_log', __DIR__ . '/logs/php-error.log');
error_reporting(E_ALL);
}
/* ============= Helpers ============= */
function fail(string $msg, int $code=400): void {
function fail(string $msg, int $code = 400): void
{
http_response_code($code);
// kleine Flash-Message für index.php
$_SESSION['flash'] = ['msg'=>$msg,'type'=> ($code>=400?'danger':'success')];
$_SESSION['flash'] = ['msg' => $msg, 'type' => ($code >= 400 ? 'danger' : 'success')];
safe_redirect_back();
}
function db(): mysqli {
function db(): mysqli
{
global $servername, $username, $password, $db;
$conn = new mysqli($servername, $username, $password, $db);
if ($conn->connect_error) fail('Interner Fehler (DB)', 500);
if ($conn->connect_error)
fail('Interner Fehler (DB)', 500);
$conn->set_charset('utf8mb4');
return $conn;
}
function require_csrf(): void {
$t = (string)($_POST['csrf'] ?? '');
function require_csrf(): void
{
$t = (string) ($_POST['csrf'] ?? '');
if (empty($_SESSION['csrf']) || !hash_equals($_SESSION['csrf'], $t)) {
fail('Ungültiges CSRF-Token', 403);
}
}
function e(string $s): string { return htmlspecialchars($s, ENT_QUOTES|ENT_SUBSTITUTE, 'UTF-8'); }
function safe_redirect_back(): void {
$ref = (string)($_SERVER['HTTP_REFERER'] ?? '');
function e(string $s): string
{
return htmlspecialchars($s, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
}
function safe_redirect_back(): void
{
$ref = (string) ($_SERVER['HTTP_REFERER'] ?? '');
if ($ref === '' || stripos($ref, 'http') !== 0) {
$host = $_SERVER['HTTP_HOST'] ?? '';
$scheme = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ? 'https' : 'http';
header('Location: '.$scheme.'://'.$host.'/', true, 303);
header('Location: ' . $scheme . '://' . $host . '/', true, 303);
} else {
header('Location: '.$ref, true, 303);
header('Location: ' . $ref, true, 303);
}
exit;
}
@@ -63,12 +85,14 @@ if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
}
require_csrf();
$wishId = (int)($_POST['wishid'] ?? -1);
$pw = (string)($_POST['WishPassword'] ?? '');
$reservedstat = (int)($_POST['reservedstat'] ?? 0); // 0 = setzen, 1 = aufheben
$wishId = (int) ($_POST['wishid'] ?? -1);
$pw = (string) ($_POST['WishPassword'] ?? '');
$reservedstat = (int) ($_POST['reservedstat'] ?? 0); // 0 = setzen, 1 = aufheben
if ($wishId <= 0) fail('Ungültige Wunsch-ID', 400);
if ($pw === '') fail('Passwort erforderlich', 400);
if ($wishId <= 0)
fail('Ungültige Wunsch-ID', 400);
if ($pw === '')
fail('Passwort erforderlich', 400);
$conn = db();
@@ -79,18 +103,21 @@ $stmt->bind_param('i', $wishId);
$stmt->execute();
$res = $stmt->get_result();
if (!$res || !($row = $res->fetch_assoc())) {
$stmt->close(); $conn->close(); fail('Wunsch nicht gefunden', 404);
$stmt->close();
$conn->close();
fail('Wunsch nicht gefunden', 404);
}
$qty = max(1, (int)$row['qty']);
$qty = max(1, (int) $row['qty']);
$stmt->close();
/* Existierende Reservierungen zählen */
$cnt = 0;
$stmt = $conn->prepare('SELECT COUNT(*) AS c FROM wishes_reservations WHERE wish_id = ?');
$stmt->bind_param('i',$wishId);
$stmt->bind_param('i', $wishId);
$stmt->execute();
$res = $stmt->get_result();
if ($res && ($row = $res->fetch_assoc())) $cnt = (int)$row['c'];
if ($res && ($row = $res->fetch_assoc()))
$cnt = (int) $row['c'];
$stmt->close();
/* --- Operationen --- */
@@ -104,25 +131,26 @@ if ($reservedstat === 0) {
$ins = $conn->prepare('INSERT INTO wishes_reservations (wish_id, pass_hash, created_at) VALUES (?, ?, NOW())');
$ins->bind_param('is', $wishId, $hash);
if (!$ins->execute()) {
$ins->close(); $conn->close();
$ins->close();
$conn->close();
fail('Reservierung fehlgeschlagen', 500);
}
$ins->close();
$conn->close();
$_SESSION['flash'] = ['msg'=>'Reservierung eingetragen','type'=>'success'];
$_SESSION['flash'] = ['msg' => 'Reservierung eingetragen', 'type' => 'success'];
safe_redirect_back();
} else {
// aufheben: passenden Hash suchen und genau einen Eintrag löschen
$sel = $conn->prepare('SELECT id, pass_hash FROM wishes_reservations WHERE wish_id = ?');
$sel->bind_param('i',$wishId);
$sel->bind_param('i', $wishId);
$sel->execute();
$res = $sel->get_result();
$deleted = false;
while ($row = $res->fetch_assoc()) {
$rid = (int)$row['id'];
$rhash= (string)$row['pass_hash'];
$rid = (int) $row['id'];
$rhash = (string) $row['pass_hash'];
if (password_verify($pw, $rhash)) {
$del = $conn->prepare('DELETE FROM wishes_reservations WHERE id = ? LIMIT 1');
$del->bind_param('i', $rid);
@@ -135,9 +163,9 @@ if ($reservedstat === 0) {
$conn->close();
if ($deleted) {
$_SESSION['flash'] = ['msg'=>'Reservierung aufgehoben','type'=>'success'];
$_SESSION['flash'] = ['msg' => 'Reservierung aufgehoben', 'type' => 'success'];
} else {
$_SESSION['flash'] = ['msg'=>'Kein passender Reservierungseintrag gefunden (Passwort korrekt?)','type'=>'warning'];
$_SESSION['flash'] = ['msg' => 'Kein passender Reservierungseintrag gefunden (Passwort korrekt?)', 'type' => 'warning'];
}
safe_redirect_back();
}