setTimeout(30); $process->run(); if ($process->isSuccessful() && is_file($tmpOut) && filesize($tmpOut) > 0) { $xml = file_get_contents($tmpOut); $xml = is_string($xml) ? trim($xml) : ''; $xml = preg_replace('/^\xEF\xBB\xBF/', '', $xml) ?? $xml; if ($xml === '') { $attemptErrors[] = implode(' ', $cmd) . ' => XML estratto vuoto'; continue; } // Alcuni tool possono produrre preamboli/MIME: estraiamo dall'inizio del tag se presente. $pos = false; // Troviamo SOLO il root element (non Header/Body), con prefisso opzionale. if (preg_match('/<\s*(?:[a-z0-9_\-]+:)?FatturaElettronica\b/i', $xml, $m, PREG_OFFSET_CAPTURE)) { $pos = $m[0][1] ?? false; } if ($pos !== false && is_int($pos) && $pos > 0) { $xml = substr($xml, $pos); $xml = is_string($xml) ? trim($xml) : ''; } if ($this->looksLikeCompleteFatturaXml($xml)) { return $xml; } // Alcuni fallback (es. scansione DER) possono trovare solo un frammento iniziale. // Se manca la chiusura del root, non accettiamo l'output: continuiamo a provare altri comandi. if (preg_match('/<\s*(?:[a-z0-9_\-]+:)?FatturaElettronica\b/i', $xml)) { $attemptErrors[] = implode(' ', $cmd) . ' => XML estratto contiene il root ma sembra incompleto (manca la chiusura di FatturaElettronica). Len=' . strlen($xml); continue; } $snippet = substr($xml, 0, 200); $snippet = is_string($snippet) ? preg_replace('/\s+/', ' ', $snippet) : ''; $attemptErrors[] = implode(' ', $cmd) . ' => Contenuto estratto non sembra FatturaPA XML. Snippet: ' . (string) $snippet; continue; } $err = trim((string) ($process->getErrorOutput() ?: $process->getOutput())); $attemptErrors[] = implode(' ', $cmd) . ' => ' . ($err !== '' ? $err : 'errore sconosciuto'); } // Fallback finale (senza openssl): alcuni P7M SdI sono DER PKCS#7 SignedData // e contengono l'XML come OCTET STRING. In questi casi possiamo estrarre in modo deterministico. $fallbackXml = $this->tryExtractXmlFromDerPkcs7($p7mPath); if (is_string($fallbackXml) && trim($fallbackXml) !== '') { return $fallbackXml; } $last = null; if ($attemptErrors) { for ($i = count($attemptErrors) - 1; $i >= 0; $i--) { $e = (string) $attemptErrors[$i]; if (str_contains($e, 'XML estratto') || str_contains($e, 'Contenuto estratto')) { $last = $e; break; } } if (! $last) { $last = (string) end($attemptErrors); } } throw new RuntimeException('Impossibile estrarre XML dal P7M' . ($last ? ": {$last}" : '.')); } finally { try { @unlink($tmpOut); } catch (\Throwable) { // ignore } foreach ($tmpInputs as $p) { try { @unlink($p); } catch (\Throwable) { // ignore } } } } private function tryExtractXmlFromDerPkcs7(string $path): ?string { $bin = @file_get_contents($path); if (! is_string($bin) || $bin === '') { return null; } $len = strlen($bin); for ($i = 0; $i < $len - 4; $i++) { // OCTET STRING tag if (ord($bin[$i]) !== 0x04) { continue; } $off = $i + 1; [$contentLen, $contentOff] = $this->readDerLength($bin, $off); if ($contentLen === null || $contentOff === null) { continue; } if ($contentOff + $contentLen > $len || $contentLen < 20) { continue; } $payload = substr($bin, $contentOff, $contentLen); if (! is_string($payload) || $payload === '') { continue; } // Trim BOM if present if (str_starts_with($payload, "\xEF\xBB\xBF")) { $payload = substr($payload, 3); } $probe = ltrim($payload); // Quick signature check if (! (str_starts_with($probe, 'looksLikeCompleteFatturaXml($xml)) { return $xml; } } return null; } private function looksLikeCompleteFatturaXml(string $xml): bool { if (! preg_match('/<\s*(?:(?
[a-z0-9_\-]+):)?FatturaElettronica\b/i', $xml, $m)) { return false; } $prefix = $m['p'] ?? null; if (is_string($prefix) && $prefix !== '') { return preg_match('/<\s*\/\s*' . preg_quote($prefix, '/') . ':FatturaElettronica\s*>/i', $xml) === 1; } // Root senza prefisso: chiusura classica. return preg_match('/<\s*\/\s*FatturaElettronica\s*>/i', $xml) === 1; } /** * @return array{0:int|null,1:int|null} [length, offsetAfterLength] */ private function readDerLength(string $bin, int $offset): array { $total = strlen($bin); if ($offset >= $total) { return [null, null]; } $b = ord($bin[$offset]); $offset++; if ($b < 0x80) { return [$b, $offset]; } $n = $b & 0x7F; if ($n === 0 || $n > 4) { return [null, null]; } if ($offset + $n > $total) { return [null, null]; } $l = 0; for ($i = 0; $i < $n; $i++) { $l = ($l << 8) | ord($bin[$offset + $i]); } return [$l, $offset + $n]; } }