From 606ffae1fe37197397a9747dbadc4cc655f13190 Mon Sep 17 00:00:00 2001 From: Eduard Wisch Date: Wed, 8 Apr 2026 23:25:24 +0200 Subject: [PATCH] fix: photo.php eigener Init ohne _inc.php JSON-Header MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit _inc.php setzt standardmäßig Content-Type: application/json — das hat photo.php beim Bildversand blockiert (die Blob kam als JSON rein und der Browser konnte sie nicht als Bild darstellen). Jetzt lädt photo.php Dolibarr direkt, dekodiert JWT manuell (mit Query-Param-Support für Fallback ohne Header-Auth) und sendet NUR image/jpeg-Header beim Ausliefern. Co-Authored-By: Claude Opus 4.6 (1M context) [deploy] --- api/photo.php | 82 +++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 69 insertions(+), 13 deletions(-) diff --git a/api/photo.php b/api/photo.php index 36e1f8a..f80f4c7 100644 --- a/api/photo.php +++ b/api/photo.php @@ -10,40 +10,96 @@ * relpath — relativer Pfad unter DOL_DATA_ROOT * size=small|mini (optional, nutzt automatisch das Thumb) */ -require_once __DIR__.'/_inc.php'; +// Dieser Endpoint liefert Binärdaten aus — KEIN JSON Content-Type! +// Deshalb nicht _inc.php direkt nutzen, sondern JWT + Dolibarr manuell laden. +if (!defined('NOLOGIN')) define('NOLOGIN', '1'); +if (!defined('NOCSRFCHECK')) define('NOCSRFCHECK', '1'); +if (!defined('NOTOKENRENEWAL')) define('NOTOKENRENEWAL', '1'); +if (!defined('NOREQUIREMENU')) define('NOREQUIREMENU', '1'); +if (!defined('NOREQUIREHTML')) define('NOREQUIREHTML', '1'); +if (!defined('NOREQUIREAJAX')) define('NOREQUIREAJAX', '1'); -api_authenticate(); -global $db, $user; +$res = 0; +if (!$res && !empty($_SERVER["CONTEXT_DOCUMENT_ROOT"])) $res = @include $_SERVER["CONTEXT_DOCUMENT_ROOT"]."/main.inc.php"; +$tmp = empty($_SERVER['SCRIPT_FILENAME']) ? '' : $_SERVER['SCRIPT_FILENAME']; $tmp2 = realpath(__FILE__); $i = strlen($tmp) - 1; $j = strlen($tmp2) - 1; +while ($i > 0 && $j > 0 && isset($tmp[$i]) && isset($tmp2[$j]) && $tmp[$i] == $tmp2[$j]) { $i--; $j--; } +if (!$res && $i > 0 && file_exists(substr($tmp, 0, ($i + 1))."/main.inc.php")) $res = @include substr($tmp, 0, ($i + 1))."/main.inc.php"; +if (!$res && $i > 0 && file_exists(dirname(substr($tmp, 0, ($i + 1)))."/main.inc.php")) $res = @include dirname(substr($tmp, 0, ($i + 1)))."/main.inc.php"; +if (!$res && file_exists("../../main.inc.php")) $res = @include "../../main.inc.php"; +if (!$res && file_exists("../../../main.inc.php")) $res = @include "../../../main.inc.php"; +if (!$res) die("Include of main fails"); + +require_once __DIR__.'/_jwt.php'; +require_once __DIR__.'/../lib/bericht.lib.php'; + +// Support Token via Header ODER Query-String (für ohne Header) +$token_str = ''; +$hdr = $_SERVER['HTTP_AUTHORIZATION'] ?? ''; +if ($hdr && stripos($hdr, 'bearer ') === 0) $token_str = trim(substr($hdr, 7)); +if (!$token_str && !empty($_GET['jwt'])) $token_str = (string) $_GET['jwt']; + +$payload = $token_str ? bericht_jwt_decode($token_str) : null; +if (!$payload || empty($payload['sub'])) { + http_response_code(401); + header('Content-Type: text/plain'); + echo 'Token ungültig oder fehlt'; + exit; +} + +require_once DOL_DOCUMENT_ROOT.'/user/class/user.class.php'; +$u = new User($db); +if ($u->fetch((int) $payload['sub']) <= 0 || empty($u->statut)) { + http_response_code(401); + header('Content-Type: text/plain'); + echo 'User ungültig'; + exit; +} +$u->loadRights(); +if (!$u->hasRight('bericht', 'read')) { + http_response_code(403); + header('Content-Type: text/plain'); + echo 'Permission denied'; + exit; +} +$user = $u; $relpath = (string) ($_GET['relpath'] ?? ''); $size = (string) ($_GET['size'] ?? ''); -if (empty($relpath)) api_fail('relpath fehlt'); +if (empty($relpath)) { + http_response_code(400); + header('Content-Type: text/plain'); + echo 'relpath fehlt'; + exit; +} // Whitelist if (!preg_match('#^(facture|commande|propal|bericht)/#', $relpath)) { - api_fail('Pfad nicht erlaubt', 403); + http_response_code(403); + header('Content-Type: text/plain'); + echo 'Pfad nicht erlaubt: '.$relpath; + exit; } $full = bericht_resolve_data_path($relpath); -if (!$full || !file_exists($full)) api_fail('Datei nicht gefunden', 404); +if (!$full || !file_exists($full)) { + http_response_code(404); + header('Content-Type: text/plain'); + echo 'Datei nicht gefunden: '.$relpath; + exit; +} -// Thumb-Variante (kleiner, schneller) +// Thumb-Variante if ($size === 'small' || $size === 'mini') { $dir = dirname($full); $base = pathinfo($full, PATHINFO_FILENAME); $ext = pathinfo($full, PATHINFO_EXTENSION); $thumb = $dir.'/thumbs/'.$base.'_'.$size.'.'.$ext; - if (file_exists($thumb)) { - $full = $thumb; - } + if (file_exists($thumb)) $full = $thumb; } -// Content-Type aus mimetype $mime = function_exists('dol_mimetype') ? dol_mimetype($full) : 'application/octet-stream'; -// Binary ausliefern — header() ersetzt vorherigen JSON Content-Type -header_remove('Content-Type'); header('Content-Type: '.$mime); header('Content-Length: '.filesize($full)); header('Cache-Control: private, max-age=3600');