Книга Excel може нести зображення EMF і WMF, і звичайний спосіб намалювати одне — віддати потік байт системному програвачу метафайлів. Те рішення варте прямого погляду: метафайл — це серіалізований потік команд для графічного API, і відтворити його означає дозволити файлу, що прибув поштою, керувати графічним драйвером. HotXLS бере інший маршрут. XLSDecodeVectorScene сама розбирає метафайл, перевіряє заголовок, кожен розмір запису, заявлений загальний підрахунок записів і точне розміщення запису кінця файла, беззастережно відкидає escape-записи і повертає TXLSVectorScene з примітивних команд малювання, які бекенди Canvas та SVG повторюють власним кодом. Жодного відтворення драйвером немає в жодний момент
Той обмін — покриття заради стримання. Білий список команд, орієнтованих на прямокутники, не відтворить кожен метафайл, який може творити дизайнер, тож сцена звітує, скільки записів малювання вона не змогла подати, а викликач вирішує, що з цим робити. Для серверного процесу, що рендерить документи, яких він не творив, той обмін повернений правильним боком
Чому відтворення метафайла погано пасує до недовірених входів?
Бо формат — не картина, а програма. Потік записів EMF маніпулює стеком стану контексту пристрою, розподіляє та вибирає об'єкти з таблиці дескрипторів і може нести escape-записи, чиє корисне навантаження передається драйверу пристрою. Відтворення його вправляє шляхи в графічному стеку платформи, написані з припущення, що метафайл прийшов від співпрацюючого застосунку на тій самій машині. Коли вхід — вкладення електронної таблиці, те припущення зникло, і жодна обережність усередині бібліотеки таблиць не допоможе, бо бібліотека — не той компонент, що розбирає
Це та сама логіка, що керує шаром контейнерів. Книга — це архів ZIP, і HotXLS перевіряє його центральний каталог, а не довіряє заявленим зміщенням, як описано в статті про перевірку ZIP end-of-central-directory. Корисні навантаження метафайлів — наступний шар тієї самої проблеми
Що декодер перевіряє, перш ніж що-небудь намалювати
Перевірка структурна і відбувається завчасно, бо парсер, що починає малювати і перевіряє на ходу, уже діяв на даних, які не звірив. Заголовок мусить збігатися строго, а не правдоподібно. Кожен запис мусить заявляти розмір, що вміщується в решту буфера і достатній для власних фіксованих полів. Підрахунок записів, який заявляє заголовок, мусить збігатися з записами, що справді присутні. Запис кінця файла мусить сидіти точно там, де потік скінчився, а не просто десь поруч, — це закриває трюк із хвостовим сміттям, що ховає друге навантаження за чинною картиною
Понад структуру декодер відмовляє безпечно на семантиці. Escape-записи відкидаються, а не пропускаються. Запис, що змінює стан і який декодер не моделює, робить декодування невдалим, а не ігнорується, бо ігнорування зміни стану означає: кожна наступна команда малювання виконується в стані, якого файл не просив, і результат — картина, неправильна так, як ніхто не передбачить. Записи малювання поза підтримуваним набором команд — інша справа: їх рахують і пропускають, бо відсутня фігура — видима, звітована прогалина, а не мовчазне пошкодження
Бюджети — частина контракту формату
Векторні формати мають власну версію бомби розстискання. Кілька кілобайт записів можуть заявити полівінії зі сотнями мільйонів точок чи зображення, чиї заявлені розміри перемножуються в терабайти. Межі тому мусять бути явними константами, а не тим, що машина випадково пережила
// З lxVectorScene: бюджет декодування, заявлений явно, а не приховано
XL_VECTOR_MAX_RECORDS = 1000000;
XL_VECTOR_MAX_HANDLES = 4096;
XL_VECTOR_MAX_DC_DEPTH = 32;
XL_VECTOR_MAX_COMMANDS = 100000;
XL_VECTOR_MAX_POINTS_PER_RECORD = 100000;
XL_VECTOR_MAX_TOTAL_POINTS = 2000000;
XL_VECTOR_MAX_TEXT_CHARS = 4096;
XL_VECTOR_MAX_TOTAL_TEXT_CHARS = 1000000;
XL_VECTOR_MAX_IMAGE_SIDE = 8192;
XL_VECTOR_MAX_IMAGE_PIXELS = 32 * 1024 * 1024;
XL_VECTOR_MAX_IMAGE_BYTES = 64 * 1024 * 1024;
XL_VECTOR_MAX_COORD = 1000000000;
Дві з них заслуговують примітки. Стеля глибини контексту пристрою 32 існує, бо записи SaveDC та RestoreDC гніздяться, і неврівноважений потік може штовхати вічно; 32 щедро для справжніх метафайлів і дешево в примусі. Стеля координат існує, бо координати живлять трансформацію, і значення біля меж діапазону цілих дає перетворений результат, що або нескінченний, або згортається, після чого кожне обчислення обвідної рамки внизу за течією — нісенітниця. Обмеження координат під час розбору набагато легше осмислити, ніж захищати кожного споживача геометрії
Користування сценою
Декодер віддає об'єкт, яким ви володієте, кількість команд, номінальний розмір і кількість записів малювання, які він вирішив не подавати
uses
lxVectorScene;
var
Scene: TXLSVectorScene;
Error: WideString;
I: Integer;
begin
// Data тримає сире навантаження картинки, взяте з книги
if not XLSDecodeVectorScene(Data, xlsvfEmf, Scene, Error) then
begin
// Відкинуто: заголовок, межі, підсумки, розміщення EOF чи бюджет
LogReject('metafile rejected: ' + Error);
Exit;
end;
try
if Scene.SkippedDrawRecords > 0 then
LogWarning(Format('%d drawing records outside the safe subset',
[Scene.SkippedDrawRecords]));
for I := 0 to Scene.Count - 1 do
case Scene.Commands[I].Kind of
xlsvcRectangle: DrawRect(Scene.Commands[I]);
xlsvcEllipse: DrawEllipse(Scene.Commands[I]);
xlsvcPolyline,
xlsvcPolygon,
xlsvcBezier: DrawPath(Scene.Commands[I]);
xlsvcText: DrawText(Scene.Commands[I]);
xlsvcImage: DrawImage(Scene.Commands[I]);
end;
finally
Scene.Free;
end;
end;
Запис команди несе все, що потрібно бекенду, і нічого, що вимагає пристрою: присутність пера, колір, товщину та стиль; присутність пензля та колір; геометрію; а для тексту — рядок, назву шрифта, розмір, стилі та вирівнювання. Саме це робить ту саму сцену придатною і для екранного рендерера канви, і для автора SVG, і тому векторний шлях не розходиться між переглядом та експортом. Екранний рендеринг вмісту аркуша загалом охоплено в статті про власний рендеринг сітки VCL
Відкидання картинки не пошкоджує книгу
Важлива властивість цієї конструкції: відкинуте декодування стосується лише рендерингу. Оригінальне навантаження лишається в моделі, тож книга, яку відкрили й знову зберегли, виносить свої зображення метафайлів байт у байт, чи міг безпечний декодер їх намалювати, чи ні. Наявний обмежений растровий шлях також лишається доступним як резерв. Інакше кажучи, строгий парсер перепоняє тому, що виконується, а не тому, що зберігається, — та відмінність дозволяє випустити зміну, мотивовану безпекою, не перетворивши її на зміну, що втрачає дані
Обробку об'єктів малювання загалом, включно з частинами об'єктної моделі, що виживають у туди й назад недоторканими, охоплено в статті про діаграми, зображення та малюнки
Де це лишає серверне розгортання
Якщо ви рендерите завантажені користувачами книги в службі, практична позиція тепер оборонна: зображення метафайлів розбирає код, який ви можете перевірити, обмежений константами, які ви можете прочитати, і жодного разу не віддається графічному драйверу. Чесне застереження — покриття. Складні метафайли від інструментів малювання впратимуть на лічильник пропущених записів, і відповідь — винести лічильник на поверхню, а не тихо розширювати білий список. Картинка, що рендериться частково і каже про це, — розмова підтримки; картинка, що рендериться неправильно і мовчить, — звіт про ваду від клієнта
HotXLS обробляє XLS, XLSX, ODS та CSV власними засобами в Delphi та C++Builder без уставленого Excel, і та сама філософія обмеженого розбору проходить крізь його шари контейнерів, формул та малюнків. Деталі формату та безпеки наведені на сторінці продукту HotXLS Delphi spreadsheet component