PDF-tiedoston kummitus (The Phantom of a PDF File)
Tämä kummitustarina 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.
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ää hyvin 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 väärin 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ääritysdokumentti määrittelee: "For text strings encoded in Unicode, the first two bytes must be 254 followed by 255, representing the Unicode byte order marker, U+FEFF". Kaksoiskoodaus merkkijonoksi "\376\377" rikkoo tätä, koska BOM-merkki ei enää sovi kahteen tavuun. Oktaalimuoto on tarkoitettu käytettäväksi PDF-referenssissä määritellyssä merkistökoodausmuodossa (PDFDocEncoding). Tätä ei pidä sekoittaa Unicode-koodauksen kanssa. PDFDocEncoding on Latin-1:n ylijoukko, jossa "\376\377" tulkitaan "þÿ"-merkeiksi, ei BOM-merkiksi.
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-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 korjaus 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 väärin. Se on vastoin määrityksiä, lisää väärintulkintoja ja jopa tekee jotkut työkalut, kuten JHOVE:n, täysin hämmentyneiksi.
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. Oktaalimerkkikoodit periytyvät vanhasta PostScript-maailmasta, säilyttäen taaksepäin yhteensopivuuden vanhempien PDF-versioiden kanssa. Näitä käytetään PDFDocEncodingin kanssa, joka tukeutuu laajalti vanhentuneeseen Latin-1:een. Lopuksi, UTF-16BE:n ja oktaalimuodon kaksoiskoodaus on ehdottomasti perusteetonta.
Kirjoittanut Juha Lehtonen & Johan Kylander
Kansalliset pitkäaikaissäilytyspalvelut