PDF-tiedoston kummitus (The Phantom of a PDF File)
Muutoshistoria:
- Päivitys 11.11.2024: Viittaamme nyt uusimpaan PDF-määritykseen ISO 32000-2:2020. UTF-16BE- ja oktaalimuodon kaksoiskoodauksen ei enää katsota rikkovan määritystä, mutta pohdimme hieman enemmän oktaalimuodon määrittelyä. Pitkäaikaissäilyttämisen näkökulmasta kaksoiskoodausta ei edelleenkään suositella. Tätä käsitellään hieman enemmän kirjoituksen loppunäytöksessä.
Tämä kummitustarina on kirjoitettu Halloweenin 2024 yhteydessä. Se perustuu tositapahtumiin. Kyseessä ollut PDF-tiedosto on kuitenkin korvattu tähän tarinaan testitiedostolla.
Esinäytös (Prologue)
Saimme äskettäin yksinkertaisen PDF 1.4 -tiedoston, joka ei läpäissyt kaikkia tarkistuksia validoinnissa. JHOVE (v. 1.30.0) raportoi, että PDF-tiedosto on rikki ("Not well-formed"). JHOVE:n ilmoittama virheviesti oli kielioppivirhe "PDF_HUL_66 Lexical Error" tavupaikassa 1888. Tämä tarkoittaa, että PDF-tiedoston teknisessä "kielessä" on jotain vikaa PDF-tiedoston sisäisessä rakenteessa.
Lupasimme analysoida tiedoston katsoaksemme, mikä siinä oli vikana. Voi pojat, mikä reissu!
Näytös 1: PDF-rakenteen (haudan)kaivelua
Aloimme tutkia tiedostoa heksaeditorilla. Tavupaikka 1888 oli keskellä zlib/deflate-koodattua kuvadataa. JHOVE:lla ei pitäisi olla syytä tehdä mitään kielioppitarkistusta kuvadatan keskellä. Tämä herätti heti epäilyksemme siitä, että jotain outoa oli meneillään. Kuvadataobjektin sanakirja on:
<< /N 3 /Alternate /DeviceRGB /Length 3187 /Filter /FlateDecode >>
Dataosion eli striimin todellinen pituus on 3187 tavua, joten sanakirjan tiedoissa, kuten ilmoitetussa pituudessa, ei ole mitään vikaa. Striimiobjektin tunniste on "7 0", ja siihen viitataan objektista "6 0" seuraavasti:
[ /ICCBased 7 0 R ]
Seurasimme objektiviittauksia ja päädyimme lopulta PDF-tiedoston ensimmäiseen elementtiin. Mitään poikkeuksellista ei löytynyt mistään.
Mitä seuraavaksi? On aika palata JHOVE:n palauttamaan virheilmoitukseen.
JHOVE ilmoittaa kielioppivirheestä (Lexical error), kun numeerisessa arvossa on odottamaton merkki tai kun PDF-sanakirjaobjekti ei pääty oikein merkkeihin ">>". PDF-tiedostossa on 10 objektia, joista 9 aivan oikein alkaa "<<"-merkeillä ja päättyy vastaavasti ">>"-merkkeihin. Jäljellä oleva objekti on lista, jota aivan oikein ympäröivät hakasulkeet "[" ja "]". Kolmessa objektissa on striimejä, joista jokainen alkaa aivan oikein sanalla "stream" ja päättyy sanaan "endstream". Pituusparametri Length on oikein kaikissa striimiobjekteissa. Kaikki objektit alkavat "X 0 obj"-merkeillä (jossa X on objektin indeksi) ja päättyvät "endobj"-sanaan asianmukaisesti.
Aloimme tarkastella xref-taulukkoa, joka sisältää objektien tavupaikat. Kaikki tavupaikat viittaavat objektien alkuun, ja objektit on numeroitu oikein suhteessa xref-taulukon indekseihin.
Kokonaisuudessaan PDF-rakenne näyttää hyvältä.
Väliaika: Tiedoston korjaaminen Acrobat Readerilla
Tiedämme, että jotkut PDF:n perusongelmat voidaan korjata yksinkertaisesti avaamalla tiedosto Adobe Acrobat Reader -ohjelmalla ja tallentamalla se uuteen tiedostoon ilman muutoksia. Päätimme tehdä tämän tälle PDF-tiedostolle. Kun tiedoston tallentaa Acrobat Readerilla, JHOVE ilmoittaa nyt uuden tiedoston olevan validi: "Well-formed and valid".
Avasimme molemmat tiedostot Acrobat Readerissa ja ne näyttivät identtisiltä. Käydessämme läpi PDF-tiedostorakennetta heksaeditorillamme, korjatun tiedoston rakenne oli hieman erilainen alkuperäiseen verrattuna. Tiedoston tallentaminen Acrobat Readerilla aiheutti pieniä muutoksia PDF-rakenteen objekteihin, mutta ensisilmäyksellä muutoksissa ei näyttänyt olevan mitään erikoista. Varmuuden vuoksi laskimme alkuperäisen ja uuden PDF-tiedoston sisältämien dataosioiden tarkistussummat vahvistaaksemme, että striimeihin ei ole tullut muutoksia.
Ongelma ei ole alkuperäisen tiedoston tavupaikassa 1888, eikä missään siihen suoraan tai epäsuorasti viittaavassa, vaan jossain muualla.
Näytös 2: Se on tuolla! PDF-tiedoston kummitus!
Tähän asti olimme vain tarkasti tarkastaneet kohteet, joista on suora tai epäsuora viittaus objektiin, jonka tavupaikassa virhe syntyy. Koska ne ovat kunnossa, syyn täytyy olla joissain muissa PDF-tiedoston objekteissa. Lopulta tajusimme, että PDF-tiedostoa korjatessa Producer-kenttä (tuottaja) on dokumentin tietohakemistossa (Document Information Dictionary) muuttunut. Alkuperäinen Producer-kenttä näyttää oudolta:
/Producer (\376\377\000P\000D\000F\000 \000P\000h\000a\000n\000t\000o\000m\000\000)
Acrobat Readerin avulla korjatussa tiedostossa se on:
/Producer (PDF Phantom)
Alkuperäinen Producer-kenttä alkaa 8-tavuisella merkkijonolla "\376\377" (heksakoodit 5C 33 37 36 5C 33 37 37). PDF-tiedostomuoto sisältää PostScript-muodosta perityn oktaalimerkkikoodausmuodon, ja sitä käytetään nyt tässä metadatassa. Oktaaliarvot 376 ja 377 vastaavat heksa-arvoja FE ja FF. Tämä viittaa BOM-merkkiin (tavujärjestysmerkki, byte order marker, heksa-arvo FEFF), jota käytetään UTF-16BE-koodauksessa. Tämä tarkoittaa, että UTF-16BE-koodattu merkkijono on kaksoiskoodattu PDF-tiedostomuodon oktaalimuodolla. Samoin alimerkkijono "\000" viittaa vain null-tavuun, ja varsinaisen sisällön voi poimia näiden null-koodien välistä: \000P --> P, \000D --> D, \000F --> F, jne.
PDF-määritys ISO 32000-2:2020 määrittelee kohdassa 7.9.2.2.1: "For text strings encoded in UTF-16BE, the first two bytes shall be 254 followed by 255. These two bytes represent the Unicode byte order marker, ZERO WIDTH NO-BREAK SPACE (U+FEFF), indicating that the string is encoded in the UTF-16BE (big-endian) encoding scheme specified in Unicode.". Kaksoiskoodauksessa "\376\377" BOM-tavujärjestysmerkkiä ei enää soviteta kahteen tavuun. Oktaalikoodit vastaavat kuitenkin näitä kahta tavua ja ovat osa kirjaimellisten merkkijonojen (literal string) määritelmää, eivät tekstimerkkijonojen (text string).
Määrittelyn kohdassa 7.3.4.2 oktaalikoodausta kuvataan "character code ddd", joka intuitiivisesti antaa ymmärtää, että oktaalikoodi olisi dekoodattava merkiksi. Kuitenkin luvun lopussa tähän lisätään: "However, any 8-bit value may appear in a string, represented either as itself or with the \ddd notation described." On melko epäselvää, pitäisikö yksittäinen oktaalimuotoinen "merkkikoodi" dekoodata merkiksi vai merkin osana olevaksi tavuksi. UTF-16BE-koodatussa merkkijonossa tässä on ero, koska kaikki merkit vievät 2 tai 4 tavua.
JHOVE ei pysty käsittelemään alkuperäisen tiedoston kaksoiskoodausta. JHOVE ei löydä sulkevaa sulkumerkkiä ")" Producer-kentästä tietääkseen, milloin kenttä päättyy. Näin ollen se jatkaa tiedoston lukemista, kunnes lopulta saavuttaa tavupaikassa 1888 ongelman (kielioppivirhe, Lexical error), jota ei voi enää tulkita. Dokumentin tietohakemisto -objektin Producer-kenttä on olemassa PDF-tiedoston tavupaikassa 87-169. Tässä oli aikamoinen jäljitys takaisin virheilmoituksen tavupaikasta 1888.
UTF-16BE-merkit käyttävät 2 tai 4 tavua kukin, ja alun perin JHOVE yhdistää ")"-merkin viereisen tavun (tai viereisten tavujen) kanssa merkin muodostamiseksi. Pelkkä välilyönnin lisääminen ennen päättyvää sulkumerkkiä ")" saa tiedoston validiksi JHOVE:n mukaan, mutta sen seurauksena JHOVE tuottaa hölynpölyä metatietoon tuottajasta (Producer).
Näytös 3: XMP kilttinä kummituksena
Acrobat Reader ymmärtää, että tuottajan pitäisi olla "PDF Phantom", ja tallentaa sen Producer-kenttään käyttäen tavallista US-ASCII-koodausta. Testataksemme korjauksen toimivuutta, muutimme tuottajaksi heksaeditorilla "Ananasakäämä", jotta se sisältäisi skandinaavisia merkkejä, ja koodasimme sen uudelleen UTF-16BE:n ja oktaalimuodon yhdistelmällä. Meidän tuli myös varmistua xref-taulukon tavupaikkojen ja startxref-tavupaikan käsittelystä, koska muiden objektien tavupaikat muuttuivat. Producer-kenttä testissämme näytti tältä:
/Producer (\376\377\000A\000n\000a\000n\000a\000s\000a\000k\000\344\000\344\000m\000\344\000\000)
Oktaaliluvut \000\344 vastaavat heksalukuja 00E4, joka on UTF-16BE:ssä "ä"-merkki. Avasimme ja tallensimme testitiedoston Acrobat Readerilla, ja tulos näytti mielenkiintoiselta. Saimme:
/Producer (PDF Phantom)
Toisin sanoen pääsimme juuri eroon "PDF Phantom”:sta, ja nyt se on taas olemassa!
Pelottavaa!
Lopulta selvisi, että koska tiedostossa on myös XMP-metadataa, täysin eri objektissa PDF-tiedoston sisällä, Acrobat Reader ylikirjoittaa dokumentin tietohakemisto -objektissa (Document Information Dictionary) olevan Producer-kentän XMP-metatiedon Producer-kentällä, joka näyttää tältä:
<pdf:Producer>PDF Phantom</pdf:Producer>
Näytös 4: Kummituksen muodonmuutos
Teimme toisen testin, jossa poistimme XMP-metadatan alkuperäisestä tiedostosta. Tämän seurauksena Acrobat Reader näyttää pystyvän poistamaan oktaalimuodon, mutta se säilyttää UTF-16BE-koodauksen "PDF Phantom" -merkkijonolle. Tuloksena on tämä:
/Producer (<fe><ff><00>P<00>D<00>F<00> <00>P<00>h<00>a<00>n<00>t<00>o<00>m<00><00>)
Tässä <fe>, <ff> ja <00> ovat ei-tulostuvia tavuja (yksi tavu kukin), jotka vastaavat vastaavasti heksakoodeja FE, FF ja 00.
Validi muutos on vaihtaa Producer-kenttä käyttämään joko PDFDocEncoding:ia tai UTF-16BE:tä. UTF-16BE on ainoa sallittu Unicode-muoto merkkijonojen koodaamiseen PDF 1.x -tiedostossa. Kuitenkin sekä UTF-16BE- että oktaalimuodon käyttäminen yhdessä samalle kentälle on kyseenalaista. Ainakin se lisää väärintulkinnan riskiä ja jopa tekee jotkut työkalut, kuten JHOVE:n, täysin hämmentyneiksi. Tämä saattaa aiheuttaa ongelmia tulevaisuudessa.
Loppunäytös (Finale)
Suosittelisimme pitkäaikaissäilyttämisen näkökulmasta koodaustasolla tavallisen US-ASCII:n käyttöä (ilman PDFDocEncodingin sisältämää oktaalimuotoa) PDF-metatietokentissä. Jos se ei ole mahdollista, voidaan käyttää UTF-16BE-koodausta. Jotkut merkit US-ASCII:ssa, kuten sulut tai rivinvaihdot, vaativat pakomerkin (escape character) "\", tarkoittaen, että merkkiä käytetään merkkijonossa eikä osana PDF-syntaksia. Tämä lienee nykyisin standardoiduin tapa koodata metadatan merkkijonoja. PDFDocEncodingin kanssa käytettäessä oktaalimerkkikoodit periytyvät vanhasta PostScript-maailmasta, joka tukeutuu laajalti vanhentuneeseen Latin-1:een.
Lopuksi, UTF-16BE:n ja oktaalimuodon kaksoiskoodaus on kyseenalaista. Pitkäaikaissäilyttämisen näkökulmasta koodaus useaan kertaan, kuten UTF-16BE lisättynä oktaalikoodauksella, ei ehkä ole järkevää käytännössä. Pitkällä aikavälillä jokainen tarpeeton koodauskerros nostaa riskiä aiheuttaa ongelmia tulevaisuudessa. JHOVE ei todennäköisesti ole ainoa ohjelmisto, joka hämmentyy metatietojen kaksoiskoodauksista. Tulevissa migraatioissa tällaiset asiat on tunnistettava ja käsiteltävä jotenkin saatavilla olevalla ohjelmistotuella. Ellemme ratkaise asiaa tänään.
Kirjoittanut Juha Lehtonen & Johan Kylander
Kansalliset pitkäaikaissäilytyspalvelut