makeTempDir(\Steuerkram\Config::get('upload_dir') . "/{$relDir}"); $storagePath = "{$relDir}/original.xml"; $absPath = \Steuerkram\Config::get('upload_dir') . "/{$storagePath}"; $xmlPayload = <<<'XML' XML; file_put_contents($absPath, $xmlPayload); $belegId = \Steuerkram\Models\Beleg::create([ 'beleg_nr' => $belegNr, 'filename' => 'rechnung.xml', 'storage_path' => $storagePath, 'mime_type' => 'text/xml', 'file_hash' => hash('sha256', $xmlPayload), 'beleg_datum' => date('Y-m-d'), 'rechnungs_nr' => null, 'betrag_brutto' => 100.00, 'belegtyp' => 'Eingangsrechnung', 'notes' => 'XSLT-XSS-Test', ]); $controller = new \Steuerkram\Controllers\BelegController(); ob_start(); $controller->serveFile(['id' => (string)$belegId]); $body = (string)ob_get_clean(); // Der Beleg muss überhaupt ausgeliefert werden (kein 403 durch den // Pfad-Check, kein 404) – sonst sagt die Disposition-Prüfung nichts aus. $this->assert( str_contains($body, 'xsl:stylesheet'), 'serveFile() liefert den XML-Beleg aus (kein 403/404)' ); // headers_list() ist unter der CLI-SAPI immer leer, der real gesetzte // Header lässt sich hier also nicht auslesen. Geprüft wird deshalb die // Auslieferungslogik in serveFile() selbst. $controllerFile = __DIR__ . '/../src/Controllers/BelegController.php'; $quelltext = (string)file_get_contents($controllerFile); $start = strpos($quelltext, 'function serveFile'); $this->assert($start !== false, 'Methode serveFile() ist im BelegController vorhanden'); $rest = substr($quelltext, (int)$start); $laenge = preg_match('/\n (?:public|private|protected) function /', $rest, $treffer, PREG_OFFSET_CAPTURE) ? $treffer[0][1] : strlen($rest); $serveFile = substr($rest, 0, $laenge); $this->assert( str_contains($serveFile, "'application/xml'") && str_contains($serveFile, "'text/xml'"), 'serveFile() erkennt XML-MIME-Typen gesondert' ); $this->assert( (bool)preg_match("/\?\s*'attachment'\s*:\s*'inline'/", $serveFile), 'XML wird als attachment ausgeliefert, alles andere weiterhin inline' ); $this->assert( (bool)preg_match("/Content-Disposition:\s*'\s*\.\s*\\\$disposition/", $serveFile), 'Der Content-Disposition-Header nutzt die ermittelte Disposition (nicht fest inline)' ); } }