From b2584bc82863c1342af73f09068e9f0c58ec3984 Mon Sep 17 00:00:00 2001 From: Lars Gebhardt-Kusche Date: Mon, 24 Nov 2025 02:12:49 +0100 Subject: [PATCH] go --- api/index.php | 40 +++ api/target/.htaccess | 9 + api/target/quickcheck.php | 314 +++++++++++++++++++++ config/prod/config.php | 8 + config/staging/config.php | 8 + partials/landing/fakecheck/serialcheck.php | 92 ++++++ partials/structure/layout_start.php | 7 + public/assets/js/fakecheck.js | 178 ++++++++++++ 8 files changed, 656 insertions(+) create mode 100644 api/index.php create mode 100644 api/target/.htaccess create mode 100644 api/target/quickcheck.php create mode 100644 partials/landing/fakecheck/serialcheck.php diff --git a/api/index.php b/api/index.php new file mode 100644 index 0000000..863a8fb --- /dev/null +++ b/api/index.php @@ -0,0 +1,40 @@ + false, + 'error' => 'Unknown endpoint', + 'path' => $path, + ], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE); + break; +} diff --git a/api/target/.htaccess b/api/target/.htaccess new file mode 100644 index 0000000..d364e60 --- /dev/null +++ b/api/target/.htaccess @@ -0,0 +1,9 @@ +# api/target/.htaccess + + Require all denied + + + + Order allow,deny + Deny from all + diff --git a/api/target/quickcheck.php b/api/target/quickcheck.php new file mode 100644 index 0000000..323ca93 --- /dev/null +++ b/api/target/quickcheck.php @@ -0,0 +1,314 @@ + false, + 'error' => 'Missing required parameter: serial', + ]; + } + + // Mini-VID-Datenbank (kannst du später aus Datei/DB laden) + $VID_DB = [ + '0781' => 'SanDisk Corp.', + '0951' => 'Kingston Technology', + '054C' => 'Sony Corp.', + '1B1C' => 'Corsair', + '13FE' => 'Phison Electronics', + '8564' => 'Transcend Information, Inc.', + '090C' => 'Silicon Motion, Inc.', + '174C' => 'ASMedia Technology', + '0BC2' => 'Seagate', + // ... nach Bedarf ergänzen + ]; + + $vendorInfo = quickcheck_lookup_vendor($vid, $VID_DB); + $serialAnalysis = quickcheck_analyze_serial($serial); + $consistencyInfo = quickcheck_evaluate_consistency($vendorInfo['vendor'] ?? null, $manufacturer, $serialAnalysis); + + // Gesamtrating + $rating = 'ok'; + if ($serialAnalysis['category'] === 'invalid') { + $rating = 'invalid'; + } elseif ($serialAnalysis['category'] === 'very_suspicious' || $consistencyInfo['manufacturer_match'] === 'mismatch') { + $rating = 'suspicious'; + } elseif ($serialAnalysis['category'] === 'suspicious') { + $rating = 'needs_review'; + } + + return [ + 'success' => true, + 'rating' => $rating, + 'input' => [ + 'vid' => $vid, + 'pid' => $pid, + 'manufacturer' => $manufacturer, + 'serial' => $serial, + ], + 'vendor_detected' => $vendorInfo, + 'serial_analysis' => $serialAnalysis, + 'consistency' => $consistencyInfo, + 'messages' => [ + 'de' => [ + 'Hinweis: Diese Prüfung kann keine Echtheit garantieren, sondern bewertet nur Plausibilität und Auffälligkeiten.', + ], + ], + ]; +} + +/** + * Eingaben lesen: JSON-Body > POST > GET + */ +function quickcheck_get_input(): array +{ + $contentType = $_SERVER['CONTENT_TYPE'] ?? $_SERVER['HTTP_CONTENT_TYPE'] ?? ''; + $contentType = strtolower(trim(explode(';', $contentType)[0])); + + if ($contentType === 'application/json') { + $raw = file_get_contents('php://input'); + if ($raw !== false && $raw !== '') { + $data = json_decode($raw, true); + if (json_last_error() === JSON_ERROR_NONE && is_array($data)) { + return $data; + } + } + } + + if (!empty($_POST)) { + return $_POST; + } + if (!empty($_GET)) { + return $_GET; + } + + return []; +} + +/** + * VID normalisieren + Lookup + */ +function quickcheck_normalize_vid(string $vid): ?string +{ + $vid = strtoupper(trim($vid)); + $vid = preg_replace('/^0X/i', '', $vid); + if ($vid === '' || !preg_match('/^[0-9A-F]{1,4}$/', $vid)) { + return null; + } + return str_pad($vid, 4, '0', STR_PAD_LEFT); +} + +function quickcheck_lookup_vendor(?string $vid, array $VID_DB): array +{ + if (!$vid) { + return [ + 'vid' => null, + 'found' => false, + 'vendor' => null, + 'confidence' => 0, + ]; + } + + $norm = quickcheck_normalize_vid($vid); + if (!$norm) { + return [ + 'vid' => $vid, + 'found' => false, + 'vendor' => null, + 'confidence' => 0, + 'issue' => 'Invalid VID format', + ]; + } + + if (isset($VID_DB[$norm])) { + return [ + 'vid' => $norm, + 'found' => true, + 'vendor' => $VID_DB[$norm], + 'confidence' => 1.0, + ]; + } + + return [ + 'vid' => $norm, + 'found' => false, + 'vendor' => null, + 'confidence' => 0.2, + ]; +} + +/** + * Seriennummer grob analysieren (Plausibilität) + */ +function quickcheck_analyze_serial(string $serial): array +{ + $serial = trim($serial); + $length = strlen($serial); + $issues = []; + $score = 100; + + if ($length === 0) { + return [ + 'serial' => $serial, + 'length' => 0, + 'issues' => ['empty'], + 'score' => 0, + 'category' => 'invalid', + ]; + } + + if ($length < 4) { + $issues[] = 'too_short_critical'; + $score -= 60; + } elseif ($length < 8) { + $issues[] = 'short_suspicious'; + $score -= 30; + } + + if ($length > 32) { + $issues[] = 'very_long'; + $score -= 10; + } + + if (quickcheck_is_all_same_char($serial)) { + $issues[] = 'all_same_char'; + $score -= 50; + } + + $simplePatterns = [ + '000000', '00000000', '000000000000', + '111111', '123456', '12345678', 'ABCDEF', 'AABBCC' + ]; + if (in_array(strtoupper($serial), $simplePatterns, true)) { + $issues[] = 'simple_pattern'; + $score -= 40; + } + + if (quickcheck_is_simple_sequence($serial)) { + $issues[] = 'sequence_pattern'; + $score -= 25; + } + + $isNumeric = preg_match('/^[0-9]+$/', $serial) === 1; + $isAlpha = preg_match('/^[A-Za-z]+$/', $serial) === 1; + $isHex = preg_match('/^[0-9A-Fa-f]+$/', $serial) === 1; + $isAlnum = preg_match('/^[A-Za-z0-9]+$/', $serial) === 1; + + if ($isNumeric) { + $issues[] = 'numeric_only'; + $score -= 10; + } elseif ($isAlpha) { + $issues[] = 'letters_only'; + $score -= 10; + } + + if ($isHex && !$isNumeric && $length >= 8 && $length <= 24) { + $issues[] = 'hex_pattern_plausible'; + $score += 5; + } + + if ($isAlnum && !$isNumeric && !$isAlpha && $length >= 8) { + $issues[] = 'alnum_mixed_plausible'; + $score += 5; + } + + if ($score > 100) $score = 100; + if ($score < 0) $score = 0; + + if ($length === 0) { + $category = 'invalid'; + } elseif ($score >= 80) { + $category = 'plausible'; + } elseif ($score >= 50) { + $category = 'suspicious'; + } else { + $category = 'very_suspicious'; + } + + return [ + 'serial' => $serial, + 'length' => $length, + 'issues' => array_values(array_unique($issues)), + 'score' => $score, + 'category' => $category, + ]; +} + +function quickcheck_is_all_same_char(string $s): bool +{ + return strlen($s) > 1 && preg_match('/^(.)\1+$/', $s) === 1; +} + +function quickcheck_is_simple_sequence(string $s): bool +{ + $seqs = [ + '123456', '1234567', '12345678', '234567', '345678', + 'ABCDEFG', 'ABCDEF', 'ABCDE' + ]; + $ls = strtoupper($s); + foreach ($seqs as $pattern) { + if (strpos($ls, $ls) !== false && strpos($ls, $pattern) !== false) { + return true; + } + } + return false; +} + +/** + * Herstellerangabe vs. Vendor-Name bewerten + */ +function quickcheck_evaluate_consistency(?string $vendorName, string $userManufacturer, array $serialAnalysis): array +{ + $vendorName = $vendorName ? trim($vendorName) : ''; + $userManufacturer = trim($userManufacturer); + + $matchStatus = 'unknown'; + $notes = []; + + if ($vendorName !== '' && $userManufacturer !== '') { + $v = mb_strtolower($vendorName); + $u = mb_strtolower($userManufacturer); + + if (strpos($v, $u) !== false || strpos($u, $v) !== false) { + $matchStatus = 'match'; + $notes[] = 'Herstellerangabe passt zur ermittelten Vendor-ID.'; + } else { + $matchStatus = 'mismatch'; + $notes[] = 'Herstellerangabe weicht von der Vendor-ID ab.'; + } + } elseif ($vendorName !== '') { + $notes[] = 'Hersteller über Vendor-ID bekannt, aber keine Herstellerangabe des Nutzers.'; + } elseif ($userManufacturer !== '') { + $notes[] = 'Hersteller vom Nutzer angegeben, aber keine oder unbekannte Vendor-ID.'; + } else { + $notes[] = 'Keine Herstellerinformationen vorhanden.'; + } + + $ratingHint = 'neutral'; + if ($serialAnalysis['category'] === 'very_suspicious' || $matchStatus === 'mismatch') { + $ratingHint = 'suspicious'; + } elseif ($serialAnalysis['category'] === 'plausible' && $matchStatus === 'match') { + $ratingHint = 'good'; + } + + return [ + 'manufacturer_match' => $matchStatus, + 'notes' => $notes, + 'consistency_hint' => $ratingHint, + ]; +} diff --git a/config/prod/config.php b/config/prod/config.php index 6f4490b..27341ff 100644 --- a/config/prod/config.php +++ b/config/prod/config.php @@ -21,3 +21,11 @@ if (!defined('APP_URL_FAKECHECK')) { define('MATOMO_URL', 'https://matomo.my-statistics.info/'); define('MATOMO_ENABLED', true); define('MATOMO_SITE_ID', 7); +$env = 'prod'; +$baseUrl = 'https://usbcheck.it'; +$apiBaseUrl = 'https://api.usbcheck.it'; + +// Diese Werte später ins Template schieben: +$GLOBALS['usb_env'] = $env; +$GLOBALS['usb_base_url'] = $baseUrl; +$GLOBALS['usb_api_base'] = $apiBaseUrl; \ No newline at end of file diff --git a/config/staging/config.php b/config/staging/config.php index 72c3106..336a83b 100644 --- a/config/staging/config.php +++ b/config/staging/config.php @@ -21,3 +21,11 @@ if (!defined('APP_URL_FAKECHECK')) { define('MATOMO_URL', 'https://matomo.my-statistics.info/'); define('MATOMO_ENABLED', false); define('MATOMO_SITE_ID', 8); +$env = 'staging'; +$baseUrl = 'https://statging.usbcheck.it'; +$apiBaseUrl = 'https://api.staging.usbcheck.it'; + +// Diese Werte später ins Template schieben: +$GLOBALS['usb_env'] = $env; +$GLOBALS['usb_base_url'] = $baseUrl; +$GLOBALS['usb_api_base'] = $apiBaseUrl; \ No newline at end of file diff --git a/partials/landing/fakecheck/serialcheck.php b/partials/landing/fakecheck/serialcheck.php new file mode 100644 index 0000000..a68c5c6 --- /dev/null +++ b/partials/landing/fakecheck/serialcheck.php @@ -0,0 +1,92 @@ +
+
+

+ Seriennummer prüfen (Quickcheck) +

+

+ Gib die Seriennummer deines USB-Sticks ein und wir prüfen die Plausibilität + sowie die Konsistenz deiner Herstellerangabe zu Vendor-ID (falls vorhanden). +

+
+ +
+
+
+ + +
+ +
+
+ + +

+ Hexadezimal, z. B. 0781 (falls bekannt). +

+
+
+ + +
+
+ +
+ + +
+ + + +

+ Hinweis: Dieser Quickcheck bewertet nur Plausibilität und Konsistenz und ersetzt keinen + vollständigen Kapazitäts- oder Geschwindigkeitstest. +

+
+
+ + + + +
diff --git a/partials/structure/layout_start.php b/partials/structure/layout_start.php index dc629cd..7b67f1c 100644 --- a/partials/structure/layout_start.php +++ b/partials/structure/layout_start.php @@ -41,6 +41,13 @@ $host = $_SERVER['HTTP_HOST'] ?? ''; + + + { const baseUrl = window.fakecheckBaseUrl || ""; const locale = window.fakecheckLocale || "en"; + // Neu: API-Basis-URL (prod/staging) – über Config oder Fallback anhand Host + const apiBaseUrl = window.fakecheckApiBaseUrl || detectApiBaseUrl(); + + function detectApiBaseUrl() { + const host = window.location.hostname || ""; + // einfache Heuristik: staging-Domain → staging-API + if (host === "staging.usbcheck.it" || host.endsWith(".staging.usbcheck.it")) { + return "https://api.staging.usbcheck.it"; + } + // default: production + return "https://api.usbcheck.it"; + } + const root = document.getElementById("fc-root"); if (!root) { // Auf anderen Seiten eingebunden? Dann einfach nichts tun. @@ -682,6 +695,168 @@ document.addEventListener("DOMContentLoaded", () => { } } + // --- Seriennummer-Quickcheck (API-gestützt) ----------------------------- + + function initSerialCheckWidget() { + const rootSc = document.getElementById("serialcheck-root"); + if (!rootSc) return; // Serialcheck-Partial nicht eingebunden → nichts tun + + const form = rootSc.querySelector("#serialcheck-form"); + const errorBox = rootSc.querySelector("#serialcheck-error"); + const resultBox = rootSc.querySelector("#serialcheck-result"); + + const manufacturerInput = rootSc.querySelector("#sc-manufacturer"); + const vidInput = rootSc.querySelector("#sc-vid"); + const pidInput = rootSc.querySelector("#sc-pid"); + const serialInput = rootSc.querySelector("#sc-serial"); + + function showScError(msg) { + if (!errorBox) return; + errorBox.textContent = msg || "Es ist ein Fehler aufgetreten."; + errorBox.classList.remove("hidden"); + if (resultBox) resultBox.classList.add("hidden"); + } + + function clearScError() { + if (!errorBox) return; + errorBox.classList.add("hidden"); + errorBox.textContent = ""; + } + + function renderScResult(data) { + if (!resultBox) return; + clearScError(); + resultBox.classList.remove("hidden"); + + const rating = data.rating || "unknown"; + let ratingLabel = ""; + let ratingDesc = ""; + + if (rating === "ok") { + ratingLabel = "Plausibel"; + ratingDesc = "Keine deutlichen Auffälligkeiten erkannt."; + } else if (rating === "needs_review") { + ratingLabel = "Überprüfen empfohlen"; + ratingDesc = "Leichte Auffälligkeiten. In Kombination mit einem technischen Test ergibt sich ein klareres Bild."; + } else if (rating === "suspicious") { + ratingLabel = "Auffällig / Verdächtig"; + ratingDesc = "Deutliche Auffälligkeiten erkannt. Ein Kapazitäts-/Geschwindigkeitstest ist dringend empfohlen."; + } else if (rating === "invalid") { + ratingLabel = "Ungültig"; + ratingDesc = "Die Seriennummer konnte nicht sinnvoll bewertet werden."; + } else { + ratingLabel = "Unklar"; + ratingDesc = "Bewertung nicht eindeutig möglich."; + } + + const input = data.input || {}; + const vendorInfo = data.vendor_detected || {}; + const serialInfo = data.serial_analysis || {}; + const consistency = data.consistency || {}; + const issues = serialInfo.issues || []; + const notes = consistency.notes || []; + + const issuesList = issues.length + ? '" + : 'Keine besonderen Auffälligkeiten.'; + + const vendorLine = vendorInfo.found + ? (vendorInfo.vendor + " (VID " + vendorInfo.vid + ")") + : (vendorInfo.vid ? ("Unbekannter Hersteller für VID " + vendorInfo.vid) : "Keine Vendor-ID angegeben"); + + const notesList = notes.length + ? '" + : ""; + + resultBox.innerHTML = ` +
+ + Bewertung: ${ratingLabel} + +

${ratingDesc}

+
+ +
+

Eingabedaten

+
+
Hersteller (Angabe):
${input.manufacturer || 'keine Angabe'}
+
VID / PID:
${(input.vid || "–") + " / " + (input.pid || "–")}
+
Vendor aus VID:
${vendorLine}
+
+
+ +
+

Seriennummer-Analyse

+
+
Seriennummer:
${serialInfo.serial || ""}
+
Länge:
${serialInfo.length || 0} Zeichen
+
Kategorie:
${serialInfo.category || "-"}
+
Score:
${typeof serialInfo.score === "number" ? serialInfo.score : "-"} / 100
+
Auffälligkeiten:
${issuesList}
+
+
+ +
+

Hersteller-Konsistenz

+ ${notesList} +

+ Diese Einschätzung basiert auf Heuristiken und kann keine Echtheit garantieren. +

+
+ `; + } + + if (!form) return; + + form.addEventListener("submit", (e) => { + e.preventDefault(); + clearScError(); + if (resultBox) resultBox.classList.add("hidden"); + + const payload = { + manufacturer: manufacturerInput ? manufacturerInput.value.trim() : "", + vid: vidInput ? vidInput.value.trim() : "", + pid: pidInput ? pidInput.value.trim() : "", + serial: serialInput ? serialInput.value.trim() : "" + }; + + if (!payload.serial) { + showScError("Bitte gib eine Seriennummer ein."); + return; + } + + fetch(apiBaseUrl.replace(/\/+$/, "") + "/quickcheck", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(payload) + }) + .then(res => { + if (!res.ok) { + throw new Error("Server returned status " + res.status); + } + return res.json(); + }) + .then(data => { + if (!data || !data.success) { + throw new Error((data && data.error) || "Unerwartete Antwort vom Server."); + } + renderScResult(data); + }) + .catch(err => { + showScError("Fehler bei der Prüfung: " + err.message); + }); + }); + } + // --- Initialzustand ----------------------------------------------------- setStatus("Bereit. Wähle zuerst deinen USB-Stick aus."); @@ -689,4 +864,7 @@ document.addEventListener("DOMContentLoaded", () => { setOverallStatus("ok", "Noch kein Test durchgeführt."); logLine("USB-Browser-Test (fakecheck) geladen. Warte auf Verzeichnisauswahl und Modus."); updateStartButtonState(); + + // Serialcheck nur initialisieren, wenn das Partial vorhanden ist + initSerialCheckWidget(); });