中繼資料:建構 XMP 封包與串流式讀取
中繼資料模組是引擎的 XMP 層。它會建構 PDF 以中繼資料串流形式夾帶的 XMP 封包。它會讀取既有封包,而不將整份文件載入記憶體。它也會輸出引擎稽核軌跡的 XMP 擴充。
composer require nextpdf/core:^3概念總覽
標題為「概念總覽」的區段PDF 會透過附加在文件目錄上的中繼資料串流中的 XMP 封包,承載文件層級的中繼資料 —— ISO 32000-2 §14.3。本模組負責產生與取用該封包。它的介面刻意維持小而聚焦:NextPDF\Metadata\Xmp 之下的三個類別。
XmpMetadataBuilder 產生該封包。它會將一組屬性集合序列化為格式正確的 XMP 文件,並包裹在標準的 <?xpacket?> 處理指令之中。它使用 XMP 規格固定的標準封包 GUID 與位元組順序標記。其輸出是 Writer 會嵌入為中繼資料串流的位元組字串 —— 也就是 §14.3 所描述的 PDF 內 XMP 表示形式。
XmpStreamReader 取用封包。它的設計假設輸入可能是惡意的。解析前,來源會以 64 KB 為單位串流寫入有界限的暫存檔。寫入期間會強制施加總位元組上限。解析期間,libxml 的實體載入器會被設為 null,解析後再還原。DOCTYPE 會觸發直接拒絕。它提供 iterateProperties(),這是一個產生器,會針對每個葉節點元素產出 (namespaceUri, localName, textContent) 元組,而不會將整棵樹實體化 —— 任一時刻,解析器中都只有目前的元素與其文字節點存活。過大的封包會引發 PacketTooLargeException;格式錯誤的 XML、DOCTYPE 或非 UTF-8 輸入則會引發 InvalidConfigException。
XmpAuditFieldEmitter 是引擎專屬的擴充。它會將 AuditReport 算繪成 nextpdfAudit 命名空間下的自訂 XMP 欄位,因此文件的符合性稽核會以符合標準的 XMP 形式隨檔案一同流通,而不是以附屬檔案形式存在。它所算繪的 AuditReport 並非由發射器本身產生:只有在 CssRenderingMode::Audit 之下執行算繪,並搭配呼叫端提供的 auditCollector(透過 Config(auditCollector: ...) 設定)時,才會啟用此項充實處理。收集器由呼叫端驅動 —— 呼叫端餵入資料,發射器則算繪它收集到的內容。它比核心 XMP 介面更新(@since 5.4.0)。建構器與讀取器為 @since 2.0.0。
API 介面
標題為「API 介面」的區段| 類別 | 主要成員 | 角色 |
|---|---|---|
XmpMetadataBuilder | build(): string、XPACKET_GUID、XPACKET_BOM | 將一組屬性集合序列化為 XMP 封包(@since 2.0.0) |
XmpStreamReader | iterateProperties(mixed $source, int $byteCap = DEFAULT_BYTE_CAP): \Generator、DEFAULT_BYTE_CAP | 有界限、串流式、拒絕 DOCTYPE 的 XMP 讀取器(@since 2.0.0) |
PacketTooLargeException | 繼承 NextPdfException | 當 XMP 封包超過位元組上限時引發(@since 2.0.0) |
XmpAuditFieldEmitter | render(?AuditReport $report): string、NAMESPACE_URI | 將稽核軌跡算繪為自訂 XMP 欄位(@since 5.4.0) |
執行 composer docs:generate-api-php -- --module=Metadata 可取得完整的 PHPDoc 表格。
程式碼範例 —— 快速上手
標題為「程式碼範例 —— 快速上手」的區段在明確的位元組上限內,從既有的 XMP 封包串流取出屬性。
<?php
declare(strict_types=1);
require_once __DIR__ . '/../vendor/autoload.php';
use NextPDF\Metadata\Xmp\XmpStreamReader;
$reader = new XmpStreamReader();
foreach ($reader->iterateProperties(file_get_contents('/srv/in/xmp.xml'), byteCap: 1_048_576) as [$ns, $name, $value]) { printf("%s:%s = %s\n", $ns, $name, $value);}程式碼範例 —— 正式環境
標題為「程式碼範例 —— 正式環境」的區段以防禦性方式讀取封包,將模組的型別化失敗對映到應用層級結果,而不讓原始解析器錯誤逸出。
<?php
declare(strict_types=1);
require_once __DIR__ . '/../vendor/autoload.php';
use NextPDF\Exception\InvalidConfigException;use NextPDF\Metadata\Xmp\PacketTooLargeException;use NextPDF\Metadata\Xmp\XmpStreamReader;use Psr\Log\LoggerInterface;
final readonly class XmpIngestService{ public function __construct( private XmpStreamReader $reader, private LoggerInterface $logger, ) {}
/** * @param resource|string $source A stream resource or XMP byte string. * * @return array<string, string> Flattened "ns:name" => value map. */ public function ingest(mixed $source): array { $properties = [];
try { // Cap untrusted XMP at 4 MB regardless of the 1 GiB default. foreach ($this->reader->iterateProperties($source, byteCap: 4_194_304) as [$ns, $name, $value]) { $properties["{$ns}:{$name}"] = $value; } } catch (PacketTooLargeException $e) { $this->logger->warning('XMP packet exceeded ingest cap; rejected.', ['error' => $e->getMessage()]);
return []; } catch (InvalidConfigException $e) { $this->logger->warning('XMP packet malformed or unsafe; rejected.', ['error' => $e->getMessage()]);
return []; }
return $properties; }}邊界情況與陷阱
標題為「邊界情況與陷阱」的區段XmpStreamReader會直接拒絕任何 DOCTYPE。這是 XXE 防禦措施,而不是驗證上的便利手段 —— 需要 DOCTYPE 的封包不會被接受;請在上游先行清理。- 位元組上限預設為 1 GiB(
DEFAULT_BYTE_CAP)。此預設值是上限,而非建議值。對於不受信任的輸入,請傳入較緊的byteCap。 iterateProperties()是一個產生器。只能取用一次;對它迭代兩次並不會重播。- 讀取器在解析期間會將 libxml 的實體載入器設為 null,並於之後還原。請勿在同一個請求中,將它與其他仰賴實體載入器的 libxml 解析作業並行執行。
XmpAuditFieldEmitter::render(null)是有效的,並會產出空白算繪結果;null 的AuditReport代表「無稽核」,而非錯誤。
建構器的資源耗用與屬性數量呈線性關係。讀取器的記憶體用量由最長的單一文字段主導,而不是文件大小,因為解析器中只有目前的元素存活 —— 大型封包會以串流方式處理,而不是載入。預設參考工作負載落在 1500 ms 牆鐘時間/64 MB 峰值的預算之內。其可重現性設定檔為 structural:XMP 封包會記錄修改時間戳記。相同邏輯中繼資料的兩次建構,會在那些欄位上有所差異,但結構相同。
安全性注意事項
標題為「安全性注意事項」的區段XmpStreamReader 用來解析不受信任的 XML,並已依此強化。搭配強制位元組上限的串流式分塊,可界定記憶體放大類型的阻斷服務攻擊。DOCTYPE 會被拒絕,藉此封鎖 XXE。LIBXML_NONET 會封鎖網路實體解析。非 UTF-8 的輸入會被拒絕。對於任何來自外部的封包,仍應設定適合部署環境的 byteCap,而非倚賴 GB 等級的預設值。當 XMP 屬性值重新進入應用程式時,請將其視為不受信任的字串。請參閱 /modules/core/security/ 中的引擎威脅模型。
符合性
標題為「符合性」的區段XmpMetadataBuilder 所產生的封包,是 ISO 32000-2 §14.3 () 所定義的 PDF 內 XMP 中繼資料串流表示形式。XMP 序列化形式本身則由 XMP 規格(ISO 16684-1)規範,而該規格並不在可驗證的引用語料庫中。該需求是以編號引用,而非以區塊釘選。這些都是由 src/Metadata/Xmp/ 所產生、並由 tests/Unit/Metadata/Xmp/ 所驗證的實作事實。針對某個設定檔(PDF/A、PDF/UA)的端對端中繼資料符合性,則由 /modules/core/conformance/ 中所描述的 oracle 與黃金測試套件來驗證。
另請參閱
標題為「另請參閱」的區段- Document 模組 —— DPM 搭配的 DPart 樹。
- Audit 模組 —— 產生發射器算繪的
AuditReport。 - Writer 模組 —— 將封包嵌入為中繼資料串流。
- 符合性總覽
- 引擎安全性模型