prettier-run for code-formatting
This commit is contained in:
@@ -31,335 +31,350 @@ namespace WList\Net;
|
|||||||
|
|
||||||
final class ImageFetch
|
final class ImageFetch
|
||||||
{
|
{
|
||||||
/** Default User-Agents (rotieren pro Versuch) */
|
/** Default User-Agents (rotieren pro Versuch) */
|
||||||
private static array $UA_LIST = [
|
private static array $UA_LIST = [
|
||||||
// Aktuelle Desktop-Chromes/Firefox als Tarnkappe
|
// Aktuelle Desktop-Chromes/Firefox als Tarnkappe
|
||||||
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0 Safari/537.36',
|
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0 Safari/537.36',
|
||||||
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0 Safari/537.36',
|
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0 Safari/537.36',
|
||||||
'Mozilla/5.0 (Macintosh; Intel Mac OS X 14_5) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.5 Safari/605.1.15',
|
'Mozilla/5.0 (Macintosh; Intel Mac OS X 14_5) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.5 Safari/605.1.15',
|
||||||
'Mozilla/5.0 (X11; Linux x86_64; rv:129.0) Gecko/20100101 Firefox/129.0',
|
'Mozilla/5.0 (X11; Linux x86_64; rv:129.0) Gecko/20100101 Firefox/129.0',
|
||||||
];
|
|
||||||
|
|
||||||
/** Fehlercodes, bei denen sich ein Retry lohnt */
|
|
||||||
private static array $RETRY_HTTP = [429, 500, 502, 503, 504, 520, 521, 522, 523, 524];
|
|
||||||
|
|
||||||
private static function retryCurlCodes(): array
|
|
||||||
{
|
|
||||||
$list = [
|
|
||||||
\defined('CURLE_OPERATION_TIMEDOUT') ? \constant('CURLE_OPERATION_TIMEDOUT') : null,
|
|
||||||
\defined('CURLE_COULDNT_RESOLVE_HOST') ? \constant('CURLE_COULDNT_RESOLVE_HOST') : null,
|
|
||||||
\defined('CURLE_COULDNT_CONNECT') ? \constant('CURLE_COULDNT_CONNECT') : null,
|
|
||||||
\defined('CURLE_RECV_ERROR') ? \constant('CURLE_RECV_ERROR') : null,
|
|
||||||
\defined('CURLE_SEND_ERROR') ? \constant('CURLE_SEND_ERROR') : null,
|
|
||||||
\defined('CURLE_GOT_NOTHING') ? \constant('CURLE_GOT_NOTHING') : null,
|
|
||||||
\defined('CURLE_HTTP2_STREAM') ? \constant('CURLE_HTTP2_STREAM') : null, // nur wenn vorhanden
|
|
||||||
\defined('CURLE_HTTP2') ? \constant('CURLE_HTTP2') : null, // manche Builds haben nur den
|
|
||||||
];
|
];
|
||||||
// Nulls rausfiltern
|
|
||||||
return array_values(array_filter($list, static fn($v) => $v !== null));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
/** Fehlercodes, bei denen sich ein Retry lohnt */
|
||||||
|
private static array $RETRY_HTTP = [429, 500, 502, 503, 504, 520, 521, 522, 523, 524];
|
||||||
|
|
||||||
/** Öffentliche API */
|
private static function retryCurlCodes(): array
|
||||||
public static function download(string $url, array $opt = []): array
|
{
|
||||||
{
|
$list = [
|
||||||
$defaults = [
|
\defined('CURLE_OPERATION_TIMEDOUT') ? \constant('CURLE_OPERATION_TIMEDOUT') : null,
|
||||||
'max_bytes' => 8_000_000, // 8 MiB
|
\defined('CURLE_COULDNT_RESOLVE_HOST') ? \constant('CURLE_COULDNT_RESOLVE_HOST') : null,
|
||||||
'timeout' => 12, // Sek.
|
\defined('CURLE_COULDNT_CONNECT') ? \constant('CURLE_COULDNT_CONNECT') : null,
|
||||||
'connect_timeout' => 5, // Sek.
|
\defined('CURLE_RECV_ERROR') ? \constant('CURLE_RECV_ERROR') : null,
|
||||||
'max_redirects' => 5,
|
\defined('CURLE_SEND_ERROR') ? \constant('CURLE_SEND_ERROR') : null,
|
||||||
'retries' => 3,
|
\defined('CURLE_GOT_NOTHING') ? \constant('CURLE_GOT_NOTHING') : null,
|
||||||
'retry_backoff_ms' => 250, // Basis-Backoff
|
\defined('CURLE_HTTP2_STREAM') ? \constant('CURLE_HTTP2_STREAM') : null, // nur wenn vorhanden
|
||||||
'whitelist_hosts' => null, // ['ikea.com','images.ikea.com'] oder null
|
\defined('CURLE_HTTP2') ? \constant('CURLE_HTTP2') : null, // manche Builds haben nur den
|
||||||
'ip_resolve_v4' => true,
|
];
|
||||||
'referer' => 'auto', // 'auto' | 'none' | 'custom'
|
// Nulls rausfiltern
|
||||||
'custom_referer' => null,
|
return array_values(array_filter($list, static fn($v) => $v !== null));
|
||||||
'user_agents' => null, // override UA-Liste
|
|
||||||
'log_prefix' => 'imgfetch', // für error_log
|
|
||||||
];
|
|
||||||
$cfg = array_replace($defaults, $opt);
|
|
||||||
|
|
||||||
// 1) URL validieren + Host prüfen
|
|
||||||
if (!self::isValidHttpUrl($url)) {
|
|
||||||
return self::fail(null, null, 0, 'Ungültige URL');
|
|
||||||
}
|
|
||||||
$p = parse_url($url);
|
|
||||||
$host = strtolower($p['host'] ?? '');
|
|
||||||
if (!$host) {
|
|
||||||
return self::fail(null, null, 0, 'Ungültige URL (Host)');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Host-Whitelist (optional)
|
|
||||||
if (is_array($cfg['whitelist_hosts']) && count($cfg['whitelist_hosts']) > 0) {
|
/** Öffentliche API */
|
||||||
$ok = false;
|
public static function download(string $url, array $opt = []): array
|
||||||
foreach ($cfg['whitelist_hosts'] as $allowed) {
|
{
|
||||||
$allowed = strtolower($allowed);
|
$defaults = [
|
||||||
if ($host === $allowed || str_ends_with($host, '.'.$allowed)) {
|
'max_bytes' => 8_000_000, // 8 MiB
|
||||||
$ok = true; break;
|
'timeout' => 12, // Sek.
|
||||||
|
'connect_timeout' => 5, // Sek.
|
||||||
|
'max_redirects' => 5,
|
||||||
|
'retries' => 3,
|
||||||
|
'retry_backoff_ms' => 250, // Basis-Backoff
|
||||||
|
'whitelist_hosts' => null, // ['ikea.com','images.ikea.com'] oder null
|
||||||
|
'ip_resolve_v4' => true,
|
||||||
|
'referer' => 'auto', // 'auto' | 'none' | 'custom'
|
||||||
|
'custom_referer' => null,
|
||||||
|
'user_agents' => null, // override UA-Liste
|
||||||
|
'log_prefix' => 'imgfetch', // für error_log
|
||||||
|
];
|
||||||
|
$cfg = array_replace($defaults, $opt);
|
||||||
|
|
||||||
|
// 1) URL validieren + Host prüfen
|
||||||
|
if (!self::isValidHttpUrl($url)) {
|
||||||
|
return self::fail(null, null, 0, 'Ungültige URL');
|
||||||
}
|
}
|
||||||
}
|
$p = parse_url($url);
|
||||||
if (!$ok) {
|
$host = strtolower($p['host'] ?? '');
|
||||||
return self::fail(null, null, 0, 'Host nicht erlaubt');
|
if (!$host) {
|
||||||
}
|
return self::fail(null, null, 0, 'Ungültige URL (Host)');
|
||||||
}
|
|
||||||
|
|
||||||
// DNS → keine privaten IPs
|
|
||||||
if (!self::hostResolvesPublic($host)) {
|
|
||||||
return self::fail(null, null, 0, 'Host nicht öffentlich erreichbar');
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2) Tmpfile anlegen
|
|
||||||
$tmp = tempnam(sys_get_temp_dir(), 'wlimg_');
|
|
||||||
if ($tmp === false) {
|
|
||||||
return self::fail(null, null, 0, 'Temp-Datei Fehler');
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3) Vorbereitung: Header + Referer + UAs
|
|
||||||
$uaList = is_array($cfg['user_agents']) && $cfg['user_agents'] ? $cfg['user_agents'] : self::$UA_LIST;
|
|
||||||
$originRef = self::originFromUrl($url);
|
|
||||||
$referer = match ($cfg['referer']) {
|
|
||||||
'none' => null,
|
|
||||||
'custom'=> (string)$cfg['custom_referer'],
|
|
||||||
default => $originRef, // auto
|
|
||||||
};
|
|
||||||
|
|
||||||
$headers = [
|
|
||||||
'Accept: image/avif,image/webp,image/*;q=0.8,*/*;q=0.5',
|
|
||||||
'Accept-Language: de-DE,de;q=0.9,en;q=0.8',
|
|
||||||
'Cache-Control: no-cache',
|
|
||||||
'Pragma: no-cache',
|
|
||||||
// Friendly fetch hints (einige CDNs schauen da drauf)
|
|
||||||
'Sec-Fetch-Dest: image',
|
|
||||||
'Sec-Fetch-Mode: no-cors',
|
|
||||||
'Sec-Fetch-Site: cross-site',
|
|
||||||
];
|
|
||||||
|
|
||||||
// 4) Retries
|
|
||||||
$attempts = max(1, (int)$cfg['retries']);
|
|
||||||
$received = 0;
|
|
||||||
$lastHttp = null;
|
|
||||||
$lastCurlErr = null;
|
|
||||||
$finalUrl = null;
|
|
||||||
$mime = null;
|
|
||||||
$ok = false;
|
|
||||||
|
|
||||||
for ($i = 0; $i < $attempts; $i++) {
|
|
||||||
$ua = $uaList[$i % count($uaList)];
|
|
||||||
|
|
||||||
$fh = fopen($tmp, 'wb');
|
|
||||||
if ($fh === false) {
|
|
||||||
return self::fail($tmp, null, 0, 'Temp-Datei Fehler');
|
|
||||||
}
|
|
||||||
|
|
||||||
$ch = curl_init($url);
|
|
||||||
if ($ch === false) {
|
|
||||||
fclose($fh);
|
|
||||||
return self::fail($tmp, null, 0, 'Download Fehler (init)');
|
|
||||||
}
|
|
||||||
|
|
||||||
$received = 0;
|
|
||||||
$opts = [
|
|
||||||
CURLOPT_FOLLOWLOCATION => true,
|
|
||||||
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,
|
|
||||||
CURLOPT_HTTPHEADER => $headers,
|
|
||||||
CURLOPT_HEADER => false,
|
|
||||||
CURLOPT_RETURNTRANSFER => false, // stream direkt ins Filehandle
|
|
||||||
CURLOPT_FILE => $fh, // fallback, falls WRITEFUNCTION nicht greift
|
|
||||||
CURLOPT_WRITEFUNCTION => function ($ch, $data) use (&$received, $cfg, $fh) {
|
|
||||||
$len = strlen($data);
|
|
||||||
$received += $len;
|
|
||||||
if ($received > (int)$cfg['max_bytes']) {
|
|
||||||
return 0; // -> CURLE_WRITE_ERROR
|
|
||||||
}
|
|
||||||
return fwrite($fh, $data);
|
|
||||||
},
|
|
||||||
CURLOPT_ACCEPT_ENCODING => '', // gzip/br zulassen
|
|
||||||
];
|
|
||||||
if (!empty($cfg['ip_resolve_v4']) && \defined('CURLOPT_IPRESOLVE') && \defined('CURL_IPRESOLVE_V4')) {
|
|
||||||
$opts[CURLOPT_IPRESOLVE] = \constant('CURL_IPRESOLVE_V4');
|
|
||||||
}
|
|
||||||
if ($referer) {
|
|
||||||
$opts[CURLOPT_REFERER] = $referer;
|
|
||||||
}
|
|
||||||
|
|
||||||
curl_setopt_array($ch, $opts);
|
|
||||||
|
|
||||||
$exec = curl_exec($ch);
|
|
||||||
$http = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
|
||||||
$ctype = (string) (curl_getinfo($ch, CURLINFO_CONTENT_TYPE) ?: '');
|
|
||||||
$finalUrl = (string) (curl_getinfo($ch, CURLINFO_EFFECTIVE_URL) ?: $url);
|
|
||||||
$cerr = curl_errno($ch);
|
|
||||||
$cerrStr = curl_error($ch);
|
|
||||||
curl_close($ch);
|
|
||||||
fclose($fh);
|
|
||||||
|
|
||||||
$lastHttp = $http;
|
|
||||||
$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']) {
|
|
||||||
@unlink($tmp);
|
|
||||||
error_log("{$cfg['log_prefix']} size limit hit after {$received} bytes url=$url");
|
|
||||||
return self::fail(null, null, 413, 'Bild zu groß');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Erfolgspfad: 2xx + image/*
|
|
||||||
if ($exec !== false && $http >= 200 && $http < 300 && stripos($ctype, 'image/') === 0) {
|
|
||||||
$mime = $ctype;
|
|
||||||
$ok = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Nicht-Image trotz 2xx → Blockseite/HTML/CSS etc.
|
|
||||||
if ($exec !== false && $http >= 200 && $http < 300 && stripos($ctype, 'image/') !== 0) {
|
|
||||||
// Manche Server liefern leeren/fehlenden Content-Type. Letzter Rettungsanker: magic sniff via getimagesize
|
|
||||||
$probe = @getimagesize($tmp);
|
|
||||||
if ($probe !== false) {
|
|
||||||
$mime = $probe['mime'] ?? 'image/*';
|
|
||||||
$ok = true;
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
// 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");
|
|
||||||
} 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");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Backoff + Jitter vorm nächsten Versuch
|
// Host-Whitelist (optional)
|
||||||
if (($i + 1) < $attempts) {
|
if (is_array($cfg['whitelist_hosts']) && count($cfg['whitelist_hosts']) > 0) {
|
||||||
$sleepMs = (int)($cfg['retry_backoff_ms'] * (2 ** $i) + random_int(0, 150));
|
$ok = false;
|
||||||
usleep($sleepMs * 1000);
|
foreach ($cfg['whitelist_hosts'] as $allowed) {
|
||||||
}
|
$allowed = strtolower($allowed);
|
||||||
|
if ($host === $allowed || str_ends_with($host, '.' . $allowed)) {
|
||||||
|
$ok = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!$ok) {
|
||||||
|
return self::fail(null, null, 0, 'Host nicht erlaubt');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// neues Tmp für nächsten Versuch
|
// DNS → keine privaten IPs
|
||||||
$tmp = tempnam(sys_get_temp_dir(), 'wlimg_');
|
if (!self::hostResolvesPublic($host)) {
|
||||||
if ($tmp === false) {
|
return self::fail(null, null, 0, 'Host nicht öffentlich erreichbar');
|
||||||
return self::fail(null, null, 0, 'Temp-Datei Fehler');
|
}
|
||||||
}
|
|
||||||
|
// 2) Tmpfile anlegen
|
||||||
|
$tmp = tempnam(sys_get_temp_dir(), 'wlimg_');
|
||||||
|
if ($tmp === false) {
|
||||||
|
return self::fail(null, null, 0, 'Temp-Datei Fehler');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3) Vorbereitung: Header + Referer + UAs
|
||||||
|
$uaList = is_array($cfg['user_agents']) && $cfg['user_agents'] ? $cfg['user_agents'] : self::$UA_LIST;
|
||||||
|
$originRef = self::originFromUrl($url);
|
||||||
|
$referer = match ($cfg['referer']) {
|
||||||
|
'none' => null,
|
||||||
|
'custom' => (string) $cfg['custom_referer'],
|
||||||
|
default => $originRef, // auto
|
||||||
|
};
|
||||||
|
|
||||||
|
$headers = [
|
||||||
|
'Accept: image/avif,image/webp,image/*;q=0.8,*/*;q=0.5',
|
||||||
|
'Accept-Language: de-DE,de;q=0.9,en;q=0.8',
|
||||||
|
'Cache-Control: no-cache',
|
||||||
|
'Pragma: no-cache',
|
||||||
|
// Friendly fetch hints (einige CDNs schauen da drauf)
|
||||||
|
'Sec-Fetch-Dest: image',
|
||||||
|
'Sec-Fetch-Mode: no-cors',
|
||||||
|
'Sec-Fetch-Site: cross-site',
|
||||||
|
];
|
||||||
|
|
||||||
|
// 4) Retries
|
||||||
|
$attempts = max(1, (int) $cfg['retries']);
|
||||||
|
$received = 0;
|
||||||
|
$lastHttp = null;
|
||||||
|
$lastCurlErr = null;
|
||||||
|
$finalUrl = null;
|
||||||
|
$mime = null;
|
||||||
|
$ok = false;
|
||||||
|
|
||||||
|
for ($i = 0; $i < $attempts; $i++) {
|
||||||
|
$ua = $uaList[$i % count($uaList)];
|
||||||
|
|
||||||
|
$fh = fopen($tmp, 'wb');
|
||||||
|
if ($fh === false) {
|
||||||
|
return self::fail($tmp, null, 0, 'Temp-Datei Fehler');
|
||||||
|
}
|
||||||
|
|
||||||
|
$ch = curl_init($url);
|
||||||
|
if ($ch === false) {
|
||||||
|
fclose($fh);
|
||||||
|
return self::fail($tmp, null, 0, 'Download Fehler (init)');
|
||||||
|
}
|
||||||
|
|
||||||
|
$received = 0;
|
||||||
|
$opts = [
|
||||||
|
CURLOPT_FOLLOWLOCATION => true,
|
||||||
|
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,
|
||||||
|
CURLOPT_HTTPHEADER => $headers,
|
||||||
|
CURLOPT_HEADER => false,
|
||||||
|
CURLOPT_RETURNTRANSFER => false, // stream direkt ins Filehandle
|
||||||
|
CURLOPT_FILE => $fh, // fallback, falls WRITEFUNCTION nicht greift
|
||||||
|
CURLOPT_WRITEFUNCTION => function ($ch, $data) use (&$received, $cfg, $fh) {
|
||||||
|
$len = strlen($data);
|
||||||
|
$received += $len;
|
||||||
|
if ($received > (int) $cfg['max_bytes']) {
|
||||||
|
return 0; // -> CURLE_WRITE_ERROR
|
||||||
|
}
|
||||||
|
return fwrite($fh, $data);
|
||||||
|
},
|
||||||
|
CURLOPT_ACCEPT_ENCODING => '', // gzip/br zulassen
|
||||||
|
];
|
||||||
|
if (!empty($cfg['ip_resolve_v4']) && \defined('CURLOPT_IPRESOLVE') && \defined('CURL_IPRESOLVE_V4')) {
|
||||||
|
$opts[CURLOPT_IPRESOLVE] = \constant('CURL_IPRESOLVE_V4');
|
||||||
|
}
|
||||||
|
if ($referer) {
|
||||||
|
$opts[CURLOPT_REFERER] = $referer;
|
||||||
|
}
|
||||||
|
|
||||||
|
curl_setopt_array($ch, $opts);
|
||||||
|
|
||||||
|
$exec = curl_exec($ch);
|
||||||
|
$http = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||||
|
$ctype = (string) (curl_getinfo($ch, CURLINFO_CONTENT_TYPE) ?: '');
|
||||||
|
$finalUrl = (string) (curl_getinfo($ch, CURLINFO_EFFECTIVE_URL) ?: $url);
|
||||||
|
$cerr = curl_errno($ch);
|
||||||
|
$cerrStr = curl_error($ch);
|
||||||
|
curl_close($ch);
|
||||||
|
fclose($fh);
|
||||||
|
|
||||||
|
$lastHttp = $http;
|
||||||
|
$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']) {
|
||||||
|
@unlink($tmp);
|
||||||
|
error_log("{$cfg['log_prefix']} size limit hit after {$received} bytes url=$url");
|
||||||
|
return self::fail(null, null, 413, 'Bild zu groß');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Erfolgspfad: 2xx + image/*
|
||||||
|
if ($exec !== false && $http >= 200 && $http < 300 && stripos($ctype, 'image/') === 0) {
|
||||||
|
$mime = $ctype;
|
||||||
|
$ok = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Nicht-Image trotz 2xx → Blockseite/HTML/CSS etc.
|
||||||
|
if ($exec !== false && $http >= 200 && $http < 300 && stripos($ctype, 'image/') !== 0) {
|
||||||
|
// Manche Server liefern leeren/fehlenden Content-Type. Letzter Rettungsanker: magic sniff via getimagesize
|
||||||
|
$probe = @getimagesize($tmp);
|
||||||
|
if ($probe !== false) {
|
||||||
|
$mime = $probe['mime'] ?? 'image/*';
|
||||||
|
$ok = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// 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");
|
||||||
|
} 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");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Backoff + Jitter vorm nächsten Versuch
|
||||||
|
if (($i + 1) < $attempts) {
|
||||||
|
$sleepMs = (int) ($cfg['retry_backoff_ms'] * (2 ** $i) + random_int(0, 150));
|
||||||
|
usleep($sleepMs * 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
// neues Tmp für nächsten Versuch
|
||||||
|
$tmp = tempnam(sys_get_temp_dir(), 'wlimg_');
|
||||||
|
if ($tmp === false) {
|
||||||
|
return self::fail(null, null, 0, 'Temp-Datei Fehler');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$ok) {
|
||||||
|
return self::fail(null, $lastCurlErr, $lastHttp ?? 0, 'Bild-Download fehlgeschlagen');
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
'ok' => true,
|
||||||
|
'tmp_path' => $tmp,
|
||||||
|
'mime' => $mime,
|
||||||
|
'http_code' => $lastHttp ?? 200,
|
||||||
|
'curl_err' => null,
|
||||||
|
'final_url' => $finalUrl,
|
||||||
|
'bytes' => $received,
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!$ok) {
|
/** Hilfsfunktionen */
|
||||||
return self::fail(null, $lastCurlErr, $lastHttp ?? 0, 'Bild-Download fehlgeschlagen');
|
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
return [
|
private static function isValidHttpUrl(string $url): bool
|
||||||
'ok' => true,
|
{
|
||||||
'tmp_path' => $tmp,
|
if (!filter_var($url, FILTER_VALIDATE_URL))
|
||||||
'mime' => $mime,
|
return false;
|
||||||
'http_code' => $lastHttp ?? 200,
|
$p = parse_url($url);
|
||||||
'curl_err' => null,
|
if (!$p || empty($p['scheme']) || empty($p['host']))
|
||||||
'final_url' => $finalUrl,
|
return false;
|
||||||
'bytes' => $received,
|
$s = strtolower($p['scheme']);
|
||||||
];
|
return $s === 'http' || $s === 'https';
|
||||||
}
|
|
||||||
|
|
||||||
/** Hilfsfunktionen */
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static function isValidHttpUrl(string $url): bool
|
|
||||||
{
|
|
||||||
if (!filter_var($url, FILTER_VALIDATE_URL)) return false;
|
|
||||||
$p = parse_url($url);
|
|
||||||
if (!$p || empty($p['scheme']) || empty($p['host'])) return false;
|
|
||||||
$s = strtolower($p['scheme']);
|
|
||||||
return $s === 'http' || $s === 'https';
|
|
||||||
}
|
|
||||||
|
|
||||||
private static function originFromUrl(string $url): string
|
|
||||||
{
|
|
||||||
$p = parse_url($url);
|
|
||||||
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'];
|
|
||||||
}
|
}
|
||||||
return $p['scheme'].'://'.$p['host'].$port.'/';
|
|
||||||
}
|
|
||||||
|
|
||||||
private static function hostResolvesPublic(string $host): bool
|
private static function originFromUrl(string $url): string
|
||||||
{
|
{
|
||||||
$recs = @dns_get_record($host, DNS_A + DNS_AAAA);
|
$p = parse_url($url);
|
||||||
if (!$recs || !count($recs)) return false;
|
if (!$p || empty($p['scheme']) || empty($p['host']))
|
||||||
foreach ($recs as $r) {
|
return '';
|
||||||
$ip = $r['type'] === 'A' ? ($r['ip'] ?? null) : ($r['ipv6'] ?? null);
|
$port = '';
|
||||||
if (!$ip) continue;
|
if (!empty($p['port'])) {
|
||||||
if (self::isPrivateIp($ip)) return false;
|
$default = ($p['scheme'] === 'https') ? 443 : 80;
|
||||||
|
if ((int) $p['port'] !== $default)
|
||||||
|
$port = ':' . $p['port'];
|
||||||
|
}
|
||||||
|
return $p['scheme'] . '://' . $p['host'] . $port . '/';
|
||||||
}
|
}
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static function isPrivateIp(string $ip): bool
|
private static function hostResolvesPublic(string $host): bool
|
||||||
{
|
{
|
||||||
if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
|
$recs = @dns_get_record($host, DNS_A + DNS_AAAA);
|
||||||
$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'];
|
if (!$recs || !count($recs))
|
||||||
} elseif (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
|
return false;
|
||||||
$cidrs = ['::1/128','fc00::/7','fe80::/10'];
|
foreach ($recs as $r) {
|
||||||
} else {
|
$ip = $r['type'] === 'A' ? ($r['ip'] ?? null) : ($r['ipv6'] ?? null);
|
||||||
return true;
|
if (!$ip)
|
||||||
|
continue;
|
||||||
|
if (self::isPrivateIp($ip))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
foreach ($cidrs as $c) if (self::ipInCidr($ip, $c)) return true;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static function ipInCidr(string $ip, string $cidr): bool
|
private static function isPrivateIp(string $ip): bool
|
||||||
{
|
{
|
||||||
if (strpos($cidr, ':') !== false) {
|
if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
|
||||||
[$subnet, $mask] = array_pad(explode('/', $cidr, 2), 2, null);
|
$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'];
|
||||||
$mask = (int)$mask;
|
} elseif (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
|
||||||
$binIp = inet_pton($ip);
|
$cidrs = ['::1/128', 'fc00::/7', 'fe80::/10'];
|
||||||
$binSubnet = inet_pton($subnet);
|
} else {
|
||||||
if ($binIp === false || $binSubnet === false) return false;
|
return true;
|
||||||
$bytes = intdiv($mask, 8);
|
}
|
||||||
$bits = $mask % 8;
|
foreach ($cidrs as $c)
|
||||||
if ($bytes && substr($binIp, 0, $bytes) !== substr($binSubnet, 0, $bytes)) return false;
|
if (self::ipInCidr($ip, $c))
|
||||||
if ($bits) {
|
return true;
|
||||||
$b1 = ord($binIp[$bytes]) & (0xFF << (8 - $bits));
|
return false;
|
||||||
$b2 = ord($binSubnet[$bytes]) & (0xFF << (8 - $bits));
|
|
||||||
return $b1 === $b2;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
[$subnet, $mask] = array_pad(explode('/', $cidr, 2), 2, null);
|
|
||||||
$mask = (int)$mask;
|
|
||||||
$ipL = ip2long($ip);
|
|
||||||
$subL = ip2long($subnet);
|
|
||||||
if ($ipL === false || $subL === false) return false;
|
|
||||||
$maskL = -1 << (32 - $mask);
|
|
||||||
return (($ipL & $maskL) === ($subL & $maskL));
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private static function fail(?string $tmp, ?string $cerr, int $http, string $msg): array
|
private static function ipInCidr(string $ip, string $cidr): bool
|
||||||
{
|
{
|
||||||
if ($tmp && is_file($tmp)) @unlink($tmp);
|
if (strpos($cidr, ':') !== false) {
|
||||||
return [
|
[$subnet, $mask] = array_pad(explode('/', $cidr, 2), 2, null);
|
||||||
'ok' => false,
|
$mask = (int) $mask;
|
||||||
'tmp_path' => null,
|
$binIp = inet_pton($ip);
|
||||||
'mime' => null,
|
$binSubnet = inet_pton($subnet);
|
||||||
'http_code' => $http > 0 ? $http : null,
|
if ($binIp === false || $binSubnet === false)
|
||||||
'curl_err' => $cerr,
|
return false;
|
||||||
'final_url' => null,
|
$bytes = intdiv($mask, 8);
|
||||||
'bytes' => 0,
|
$bits = $mask % 8;
|
||||||
'error' => $msg,
|
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));
|
||||||
|
return $b1 === $b2;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
[$subnet, $mask] = array_pad(explode('/', $cidr, 2), 2, null);
|
||||||
|
$mask = (int) $mask;
|
||||||
|
$ipL = ip2long($ip);
|
||||||
|
$subL = ip2long($subnet);
|
||||||
|
if ($ipL === false || $subL === false)
|
||||||
|
return false;
|
||||||
|
$maskL = -1 << (32 - $mask);
|
||||||
|
return (($ipL & $maskL) === ($subL & $maskL));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function fail(?string $tmp, ?string $cerr, int $http, string $msg): array
|
||||||
|
{
|
||||||
|
if ($tmp && is_file($tmp))
|
||||||
|
@unlink($tmp);
|
||||||
|
return [
|
||||||
|
'ok' => false,
|
||||||
|
'tmp_path' => null,
|
||||||
|
'mime' => null,
|
||||||
|
'http_code' => $http > 0 ? $http : null,
|
||||||
|
'curl_err' => $cerr,
|
||||||
|
'final_url' => null,
|
||||||
|
'bytes' => 0,
|
||||||
|
'error' => $msg,
|
||||||
|
];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,11 @@ declare(strict_types=1);
|
|||||||
/* ========= Session & Bootstrap ========= */
|
/* ========= Session & Bootstrap ========= */
|
||||||
$secure = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off');
|
$secure = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off');
|
||||||
session_set_cookie_params([
|
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_name('WLISTSESSID');
|
||||||
session_start();
|
session_start();
|
||||||
@@ -14,44 +18,62 @@ require_once __DIR__ . '/config/config.php';
|
|||||||
|
|
||||||
/* ===== Debug Toggle (wie item.php) ===== */
|
/* ===== Debug Toggle (wie item.php) ===== */
|
||||||
if (!empty($app_debug)) {
|
if (!empty($app_debug)) {
|
||||||
ini_set('display_errors','1'); ini_set('display_startup_errors','1'); ini_set('log_errors','1');
|
ini_set('display_errors', '1');
|
||||||
if (!is_dir(__DIR__.'/logs')) { @mkdir(__DIR__.'/logs',0750,true); }
|
ini_set('display_startup_errors', '1');
|
||||||
ini_set('error_log', __DIR__.'/logs/php-error.log'); error_reporting(E_ALL);
|
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 {
|
} else {
|
||||||
ini_set('display_errors','0'); ini_set('display_startup_errors','0'); ini_set('log_errors','1');
|
ini_set('display_errors', '0');
|
||||||
if (!is_dir(__DIR__.'/logs')) { @mkdir(__DIR__.'/logs',0750,true); }
|
ini_set('display_startup_errors', '0');
|
||||||
ini_set('error_log', __DIR__.'/logs/php-error.log'); error_reporting(E_ALL);
|
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 ============= */
|
/* ============= Helpers ============= */
|
||||||
function fail(string $msg, int $code=400): void {
|
function fail(string $msg, int $code = 400): void
|
||||||
|
{
|
||||||
http_response_code($code);
|
http_response_code($code);
|
||||||
// kleine Flash-Message für index.php
|
// 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();
|
safe_redirect_back();
|
||||||
}
|
}
|
||||||
function db(): mysqli {
|
function db(): mysqli
|
||||||
|
{
|
||||||
global $servername, $username, $password, $db;
|
global $servername, $username, $password, $db;
|
||||||
$conn = new mysqli($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');
|
$conn->set_charset('utf8mb4');
|
||||||
return $conn;
|
return $conn;
|
||||||
}
|
}
|
||||||
function require_csrf(): void {
|
function require_csrf(): void
|
||||||
$t = (string)($_POST['csrf'] ?? '');
|
{
|
||||||
|
$t = (string) ($_POST['csrf'] ?? '');
|
||||||
if (empty($_SESSION['csrf']) || !hash_equals($_SESSION['csrf'], $t)) {
|
if (empty($_SESSION['csrf']) || !hash_equals($_SESSION['csrf'], $t)) {
|
||||||
fail('Ungültiges CSRF-Token', 403);
|
fail('Ungültiges CSRF-Token', 403);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
function e(string $s): string { return htmlspecialchars($s, ENT_QUOTES|ENT_SUBSTITUTE, 'UTF-8'); }
|
function e(string $s): string
|
||||||
function safe_redirect_back(): void {
|
{
|
||||||
$ref = (string)($_SERVER['HTTP_REFERER'] ?? '');
|
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) {
|
if ($ref === '' || stripos($ref, 'http') !== 0) {
|
||||||
$host = $_SERVER['HTTP_HOST'] ?? '';
|
$host = $_SERVER['HTTP_HOST'] ?? '';
|
||||||
$scheme = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ? 'https' : 'http';
|
$scheme = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ? 'https' : 'http';
|
||||||
header('Location: '.$scheme.'://'.$host.'/', true, 303);
|
header('Location: ' . $scheme . '://' . $host . '/', true, 303);
|
||||||
} else {
|
} else {
|
||||||
header('Location: '.$ref, true, 303);
|
header('Location: ' . $ref, true, 303);
|
||||||
}
|
}
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
@@ -63,12 +85,14 @@ if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
|||||||
}
|
}
|
||||||
require_csrf();
|
require_csrf();
|
||||||
|
|
||||||
$wishId = (int)($_POST['wishid'] ?? -1);
|
$wishId = (int) ($_POST['wishid'] ?? -1);
|
||||||
$pw = (string)($_POST['WishPassword'] ?? '');
|
$pw = (string) ($_POST['WishPassword'] ?? '');
|
||||||
$reservedstat = (int)($_POST['reservedstat'] ?? 0); // 0 = setzen, 1 = aufheben
|
$reservedstat = (int) ($_POST['reservedstat'] ?? 0); // 0 = setzen, 1 = aufheben
|
||||||
|
|
||||||
if ($wishId <= 0) fail('Ungültige Wunsch-ID', 400);
|
if ($wishId <= 0)
|
||||||
if ($pw === '') fail('Passwort erforderlich', 400);
|
fail('Ungültige Wunsch-ID', 400);
|
||||||
|
if ($pw === '')
|
||||||
|
fail('Passwort erforderlich', 400);
|
||||||
|
|
||||||
$conn = db();
|
$conn = db();
|
||||||
|
|
||||||
@@ -79,18 +103,21 @@ $stmt->bind_param('i', $wishId);
|
|||||||
$stmt->execute();
|
$stmt->execute();
|
||||||
$res = $stmt->get_result();
|
$res = $stmt->get_result();
|
||||||
if (!$res || !($row = $res->fetch_assoc())) {
|
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();
|
$stmt->close();
|
||||||
|
|
||||||
/* Existierende Reservierungen zählen */
|
/* Existierende Reservierungen zählen */
|
||||||
$cnt = 0;
|
$cnt = 0;
|
||||||
$stmt = $conn->prepare('SELECT COUNT(*) AS c FROM wishes_reservations WHERE wish_id = ?');
|
$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();
|
$stmt->execute();
|
||||||
$res = $stmt->get_result();
|
$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();
|
$stmt->close();
|
||||||
|
|
||||||
/* --- Operationen --- */
|
/* --- Operationen --- */
|
||||||
@@ -104,25 +131,26 @@ if ($reservedstat === 0) {
|
|||||||
$ins = $conn->prepare('INSERT INTO wishes_reservations (wish_id, pass_hash, created_at) VALUES (?, ?, NOW())');
|
$ins = $conn->prepare('INSERT INTO wishes_reservations (wish_id, pass_hash, created_at) VALUES (?, ?, NOW())');
|
||||||
$ins->bind_param('is', $wishId, $hash);
|
$ins->bind_param('is', $wishId, $hash);
|
||||||
if (!$ins->execute()) {
|
if (!$ins->execute()) {
|
||||||
$ins->close(); $conn->close();
|
$ins->close();
|
||||||
|
$conn->close();
|
||||||
fail('Reservierung fehlgeschlagen', 500);
|
fail('Reservierung fehlgeschlagen', 500);
|
||||||
}
|
}
|
||||||
$ins->close();
|
$ins->close();
|
||||||
$conn->close();
|
$conn->close();
|
||||||
$_SESSION['flash'] = ['msg'=>'Reservierung eingetragen','type'=>'success'];
|
$_SESSION['flash'] = ['msg' => 'Reservierung eingetragen', 'type' => 'success'];
|
||||||
safe_redirect_back();
|
safe_redirect_back();
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
// aufheben: passenden Hash suchen und genau einen Eintrag löschen
|
// aufheben: passenden Hash suchen und genau einen Eintrag löschen
|
||||||
$sel = $conn->prepare('SELECT id, pass_hash FROM wishes_reservations WHERE wish_id = ?');
|
$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();
|
$sel->execute();
|
||||||
$res = $sel->get_result();
|
$res = $sel->get_result();
|
||||||
|
|
||||||
$deleted = false;
|
$deleted = false;
|
||||||
while ($row = $res->fetch_assoc()) {
|
while ($row = $res->fetch_assoc()) {
|
||||||
$rid = (int)$row['id'];
|
$rid = (int) $row['id'];
|
||||||
$rhash= (string)$row['pass_hash'];
|
$rhash = (string) $row['pass_hash'];
|
||||||
if (password_verify($pw, $rhash)) {
|
if (password_verify($pw, $rhash)) {
|
||||||
$del = $conn->prepare('DELETE FROM wishes_reservations WHERE id = ? LIMIT 1');
|
$del = $conn->prepare('DELETE FROM wishes_reservations WHERE id = ? LIMIT 1');
|
||||||
$del->bind_param('i', $rid);
|
$del->bind_param('i', $rid);
|
||||||
@@ -135,9 +163,9 @@ if ($reservedstat === 0) {
|
|||||||
$conn->close();
|
$conn->close();
|
||||||
|
|
||||||
if ($deleted) {
|
if ($deleted) {
|
||||||
$_SESSION['flash'] = ['msg'=>'Reservierung aufgehoben','type'=>'success'];
|
$_SESSION['flash'] = ['msg' => 'Reservierung aufgehoben', 'type' => 'success'];
|
||||||
} else {
|
} 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();
|
safe_redirect_back();
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user