Нутрощі вордівських файлів просто жах - oh, msbro!

Про складність і жуткость вордівських файлів давно ходили легенди. Відомо було, що формат цей вкрай заплутаний, а до того ж ще і повністю засекречений, так що про половину тамтешніх полів можна було тільки здогадуватися.


Ніде правди діти, що і мене ці файли цікавили, але далі першої сторінки опису я так просунутися і не зміг. Однак незакритий гештальт залишився.

Що тут можна сказати? Мимоволі пригадується старий вульгарний анекдот: ну жах. Ну просто жах, але ж не жах-жах-жах.

Слава богу, що я розбирав ці файли на Перлі, а не на якомусь автокодом Сі. Високий рівень мови і купа готових бібліотек (наприклад, для читання різних кодових сторінок) - це дар божий.

Так що робота в сукупності зайняла тиждень, і найскладнішим було зрозуміти внутрішній формат. Звичайно, розуміння це не зовсім повне, тому що моїм завданням було витягнути з документа одні тексти без будь-якого форматування, але вже це я зробив ретельно.

Отже, як же влаштовані вордовскіе файли?

Почнемо з того, що це зовсім не вордовскіе файли, а якийсь універсальний контейнер, в який упаковані власне документи. У такий контейнер засунуті всі файли Офісу, а, може бути, і ще що-небудь.

В принципі, формат OLE Storage досить розумний і не справляє враження чогось зовсім ідіотського. Але ... він абсолютно не потрібен. Фактично, це щось типу файлової системи FAT16, засунутої всередину окремого файлу. Це не те що стрілянина з гармати по горобцях, а винищення цих самих виробів ядерними торпедами. У документі, всередині якого лежить кілька файлів, не потрібна файлова система, що знаходиться не менше ніж на рівні ФС часів ДОС.

Отже, файли CDF (як їх називає юніксовская утилитка file) починаються з заголовка. У заголовку відведено місце під першу сотню записів ТРФ (таблиці розміщення файлів, в просторіччі - ФАТ). ФАТ там самий натуральний, дуже схожий на той, що можна знайти на ДОСовскіх Дискетка. Решта записи ТРФ лежать в окремих секторах, з'єднаних пов'язаним списком. Додаткові сектора бувають тільки у великих (> 7Мб) файлах.

За табличці ФАТ / ТРФ можна зібрати вміст будь-якого внутрішнього файлу (в термінології МС він називається потоком, але це тільки заплутує справу), якщо знати, з якого блоку він починається. Початкові блоки різних структур написані, природно, в заголовку. Далі з таблиці можна витягнути весь ланцюг секторів, в якому записано вміст цього псевдофайла.

Зокрема, у CDF є кореневий каталог, фізично розмазаний по купі секторів. Це справжнісінький каталог, знову-таки, дуже нагадує старий ДОСовскіх. Правда, для ефективності (або для витребеньок) він виконаний не просто лінійним списком, а збалансованим двійковим деревом. Це означає, що в нього без втрати ефективності пошуку можна записувати десятки тисяч окремих записів. Навіщо це потрібно в файлі, в якому записів буває зазвичай штук п'ять, ну іноді, двадцять, ну максимум сто штук (презентація з величезною кількістю картинок) - знають тільки в Редмонді. До речі, імена файлів в каталозі зберігаються в UTF16 - теж про всяк випадок.

За каталогом можна визначити початковий сектор будь-якого файлу і за допомогою ТРФ витягнути всю його ланцюжок розміщення.

Але це ще не все.

Оскільки розмір блоку немаленький (зазвичай 512 байт, по специфікації можливо також 4096), то при зберіганні дрібних псевдофайла, теоретично можна втратити багато вільного місця. Тому існує окреме сховище, поділене на блочки по 64 байта. Сховище знову-таки витягується по ланцюжку ФАТ.

Щоб вказати, які блочки якому файлу належать, існує окрема табличка ФАТ або, вірніше сказати, мініФАТ.

Отже, щоб дістатися до вордовского документа, треба зробити наступне:

1. Прочитати заголовок CDF
2. Завантажити в пам'ять ФАТ - таблицю розміщення файлів, зібравши її по ланцюжку секторів.
3. Завантажити табличку МініФАТ, зібравши її по ланцюжку ТРФ
4. Завантажити сховище блочков, зібравши її по ланцюжку ТРФ
5. Завантажити кореневої каталог, зібравши її по ланцюжку ТРФ
6. Розібрати каталог і перетворити його у щось читається
7. Знайти в каталозі запис WordDocument
8. Якщо це маленький файл, то зібрати його з допомогою мініТРФ зі сховища для блочков.
9. Якщо великий, то витягнути з диска сектора по ланцюжку ТРФ.

Кожен крок сам по собі не особливо складний, але в сукупності вони викликають виключно подив. Навіщо такі складнощі? Чому не можна було розмістити після заголовка звичайний лінійний каталог, а після нього безперервно, один за одним записувати внутрішні файли?

Єдине, що можна припустити - все це зроблено для того, щоб була можливість дописувати подфайли, не чіпаючи початок основного файлу. Треба зауважити, що, по-перше, це не сильно затребувана операція, оскільки всі програми зазвичай записують документи від початку до кінця в один прохід. Виняток становить тільки MS Word і то тільки в горезвісному режимі швидкого збереження, проклятому користувачами. А по-друге, навіть в цих умовах все одно не вийде не чіпати початок основного файлу, оскільки треба оновлювати каталоги, ТРФ і заголовки.

WordDocument

Формат CDF при всій своїй монструозність хоча б логічний і не дуже складний (якщо порівнювати з рештою вмісту вордовского документа). Його опис займає всього якихось двадцять сторінок - тьху в порівнянні з 300 сторінками формату Ворда.

Формат документа складно навіть назвати форматом, набагато більше до нього підійде визначення кам'яного літопису. Уявіть собі такий кам'яний обрив, на якому відбилося п'ятдесят мільйонів років історії планети. Ось мезозойский шар, ось кайнозойский, ось відбиток крила птеродактиля, а зверху вже третинні відкладення. Приблизно так само виглядає і документ зсередини.

Досить подивитися на заголовок, який займає мало не третину файлу. Заголовків цілих три. Спочатку йде один невеликий, в якому половина записів зяє дірками «Reserved» або «Not used». Раніше, в мезозої, там явно щось лежало, але потім було викинуто на звалище історії. Тут же є версія записала програми, по якій в коді, схоже, виконується величезний switch / case.

Потім йде другий заголовок, що складається з шестнадцатібітових слів. У ньому немає взагалі нічого корисного. У його початку прописаний розмір явно з таким розрахунком, що тут будуть в майбутньому відкладатися панцири найпростіших.

Після цього йде третій заголовок, на цей раз сучасний, з довгих слів (32 біта). Він неміряно довжини, на початку теж вказує кількість записів з прицілом на подальше розширення, і в основному представляє собою список, де шукати різні таблиці і шматки файлу - пари початок / розмір. Самі таблиці, до речі, лежать не тут, а в окремому псевдофайла CDF під назвою 0Table або 1Table (можливі варіанти).

Життя насправді набагато складніше. Колись в файлах дійсно був тільки текст, проте з часом під тиском користувачів і маркетингу накопичувалися різні «фічі». Під них відвели окремі потоки - під прості виноски, під виноски кінцеві, під колонтитули, під якісь textbox (то ще збочення - на вигляд текст, але не текст. Призначення толком не ясно).

Почала цих потоків вказані в спеціальних місцях заголовка, але найперший заголовок чомусь показує загальну довжину - не найбільш тексту, а текст плюс всі цих збочень. Ось і перша причина, чому в висновок багатьох утиліт потрапляють написи типу Page 1.

Теоретично, цей складний формат задіяний тільки, якщо в заголовку встановлений спеціальний прапорець fComplex. Але ... Ось на цьому черговому «але» теж проколюються багато конвертори.

Уже в наш час в документи додали можливість запису в Юникоде. При цьому постала проблема (як на мене, надумана): але ж файли виходять рівно в два рази довше. Оскільки ПО розробляють американці, які в душі взагалі не вірять в існування інших абеток, і таємно вважають, що всякі дивні літери бувають тільки в дисертаціях про Давню Грецію, та й там зустрічаються тільки іноді, перше, що спало їм на думку - відокремити чисті символи ASCII від брудних юнікодовскіх. Перші писати по байту на символ, другі - як вийде.

Якщо врахувати, що сама ця таблиця недогризків теж займає місце, а ще більше місця в файлі займають різні двійкові дерева і таблички ланцюжків секторів від формату CDF, то розміри економії тексту на символах Юникода не здивуватися уяви навіть в давньогрецьких дисертаціях. Про файлах на великому і могутньому мовою і говорити нема чого. Поклали б все в UTF-16 і не страждали. Ну заархівувати б потік, якщо вже так жаба давить.

Після героїчних зусиль з читання тексту, в ньому самому, як не дивно, немає нічого складного. Звичайний текст (з поправкою на кодування), деякі коди нижче пробілу грають службову роль. Наприклад, 0х9 позначає, як і належить, табуляцію, 0хА - кінець сторінки, 0х7 - кінець елементи таблиці і т.д. Єдина тонкість пов'язана з полями. Початок вмісту поля позначається як 0х13, кінець поля - 0х15, ім'я та параметри поля відокремлюються символом 0х14 від того, що, власне, видно в тексті користувачеві. Але ... Друга частина може мати в собі вкладене поле, чого багато програм не враховують. В результаті в тексті залишаються огризки начебто INCLUDEPICTURE або PAGEREF *.

Втім, є ще одна дрібна капость. Деякі символи можуть означати щось зовсім інше, на кшталт поточної дати. Щоб зрозуміти, простий це символ чи ні, треба розбирати таблиці властивостей символів, про які нижче. Каюсь, я просто вирізав все символи з кодом нижче пробілу, що не зовсім достатньо, але дешево, швидко і практично.

Видер текст, далі в формат я заглиблюватися не став. Це вже заняття для молодих і сильних духом - розібрати всі ці таблиці з такими багатообіцяючими назвами як CHP, PAPX, SHST, PLCF і все в тому ж дусі. Заняття зовсім вже для титанів - відтворити форматування в точності, як це робить сам Ворд.

Залишилося тільки підвести мораль, а вона банальна: що одна людина придумав, то інший завжди зламати може. Що робить формат Ворда менш ганебним, потворним і абсолютно непристосованим для задач масового обміну інформацією в гетерогенних системах.

Додаткова література: