Artículo técnico

Reproducir EMF y WMF no confiables con seguridad en HotXLS

Un libro de Excel puede llevar imágenes EMF y WMF, y la manera convencional de dibujar una es entregar el flujo de bytes al reproductor de metarchivos del sistema operativo. Es una decisión que vale mirar de frente: un metarchivo es un flujo de comandos serializado para una API de gráficos, y reproducirlo significa dejar que un archivo que llegó por correo maneje el controlador de gráficos. HotXLS toma la otra vía. XLSDecodeVectorScene analiza el metarchivo por sí mismo, valida la cabecera, el tamaño de cada registro, el total de registros declarado y la colocación exacta del registro de fin de archivo, rechaza de plano los registros escape y devuelve una TXLSVectorScene de comandos de dibujo primitivos que los backends de Canvas y de SVG reproducen con su propio código. No hay reproducción por el controlador en ningún punto

HotXLS analiza los bytes EMF y WMF no confiables de la hoja con XLSDecodeVectorScene en una lista de comandos TXLSVectorScene en lugar de la reproducción GDI de metarchivos
HotXLS analiza el metarchivo por sí mismo y devuelve comandos primitivos para reproducir en Canvas y SVG; la vía convencional ejecuta el flujo de bytes en la pila de gráficos

El intercambio es cobertura por contención. Una lista blanca de comandos orientada a rectángulos no reproducirá todo metarchivo que un diseñador pueda crear, así que la escena reporta cuántos registros de dibujo no pudo representar y quien llama decide qué hacer al respecto. Para un proceso de servidor que renderiza documentos que él no creó, ese intercambio está del lado correcto

Por qué la reproducción de metarchivos pega mal con entrada no confiable

Porque el formato no es una imagen, es un programa. Un flujo de registros EMF manipula una pila de estados de contexto de dispositivo, asigna y selecciona objetos de una tabla de handles, y puede llevar registros escape cuya carga útil se pasa a un controlador de dispositivo. Reproducirlo ejercita caminos de la pila de gráficos de la plataforma que se escribieron asumiendo que el metarchivo venía de una aplicación cooperadora en la misma máquina. Cuando la entrada es un adjunto de hoja de cálculo, ese supuesto desaparece, y ningún cuidado dentro de la biblioteca de hojas de cálculo ayuda porque la biblioteca no es el componente que analiza

Es el mismo razonamiento que gobierna la capa del contenedor. Un libro es un archivo ZIP, y HotXLS valida su directorio central en lugar de confiar en los desplazamientos declarados, como se describe en el artículo de validación del end-of-central-directory ZIP. Las cargas útiles de metarchivo son la siguiente capa del mismo problema

Qué comprueba el decodificador antes de dibujar nada

La validación es estructural y ocurre por adelantado, porque un analizador que empieza a dibujar y valida sobre la marcha ya actuó sobre datos que no verificó. La cabecera debe coincidir de forma estricta y no plausible. Cada registro debe declarar un tamaño que quepa en el búfer restante y sea lo bastante grande para sus campos fijos. El recuento de registros que declara la cabecera debe coincidir con los registros realmente presentes. El registro de fin de archivo debe estar exactamente donde termina el flujo, no apenas cerca, lo que cierra el truco de la basura final que esconde una segunda carga útil detrás de una imagen válida

Más allá de la estructura, el decodificador es fail-closed en la semántica. Los registros escape se rechazan, no se saltan. Un registro que cambia estado y que el decodificador no modela hace que la decodificación falle en lugar de ignorarse, porque ignorar un cambio de estado significa que cada comando de dibujo posterior se ejecuta en un estado que el archivo no pidió, y el resultado es una imagen que está mal de una forma que nadie puede predecir. Los registros de dibujo fuera del conjunto de comandos soportado son otro asunto: esos se cuentan y se saltan, porque una forma ausente es un hueco visible y reportable y no una corrupción silenciosa

XLSDecodeVectorScene comprueba por adelantado la cabecera, los tamaños de registro, los totales y la colocación del EOF, luego rechaza registros escape y cuenta registros de dibujo no soportados
Las comprobaciones estructurales corren por adelantado y la semántica fail-closed rechaza registros escape, mientras que los registros de dibujo no soportados solo se cuentan y se saltan

Los presupuestos son parte del contrato del formato

Los formatos vectoriales tienen su propia versión de la bomba de descompresión. Unos cuantos kilobytes de registros pueden declarar polilíneas con cientos de millones de puntos, o una imagen cuyas dimensiones declaradas se multiplican en terabytes. Los límites tienen entonces que ser constantes explícitas y no aquello que la máquina logre sobrevivir

// De lxVectorScene: el presupuesto de decodificación, enunciado y no implícito
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;

Dos de estos merecen una nota. El tope de profundidad de contexto de dispositivo de 32 existe porque los registros SaveDC y RestoreDC se anidan, y un flujo desbalanceado puede apilar para siempre; 32 es generoso para metarchivos reales y barato de aplicar. El tope de coordenadas existe porque las coordenadas alimentan una transformación, y un valor cerca de los límites del rango de enteros produce un resultado transformado que es infinito o se desborda, después de lo cual todo cálculo de caja delimitadora aguas abajo es un sinsentido. Acotar coordenadas al momento del análisis es mucho más fácil de razonar que defender a cada consumidor de la geometría

Constantes de presupuesto de decodificación en HotXLS lxVectorScene para registros, handles, profundidad DC, comandos, puntos, texto, tamaño de imagen y acotación de coordenadas
Cada límite es una constante con nombre aplicada durante el análisis; el tope de profundidad DC y la acotación de coordenadas merecen la mayor atención

Uso de la escena

El decodificador devuelve un objeto que ustedes poseen, un recuento de comandos, un tamaño nominal y un recuento de registros de dibujo que decidió no representar

uses
  lxVectorScene;

var
  Scene: TXLSVectorScene;
  Error: WideString;
  I: Integer;
begin
  // Data contiene la carga útil de imagen cruda tomada del libro
  if not XLSDecodeVectorScene(Data, xlsvfEmf, Scene, Error) then
  begin
    // Rechazado: cabecera, límites, totales, colocación del EOF o un presupuesto
    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;

El registro de comandos lleva todo lo que un backend necesita y nada que requiera un dispositivo: presencia, color, ancho y estilo de la pluma; presencia y color del pincel; la geometría; y para el texto la cadena, el nombre de la fuente, el tamaño, los estilos y la alineación. Eso es lo que hace que la misma escena sirva tanto al renderizador de canvas en pantalla como al escritor de SVG, y es la razón de que la vía vectorial no diverja entre vista previa y exportación. El renderizado en pantalla de contenido de hojas en general se cubre en el artículo de renderizado con cuadrícula VCL personalizada

Rechazar una imagen no daña el libro

Una propiedad importante de este diseño es que una decodificación rechazada afecta solo al renderizado. La carga útil original se queda en el modelo, así que un libro que se abre y se vuelve a guardar saca sus imágenes de metarchivo byte a byte, aunque el decodificador seguro pudiera dibujarlas o no. La vía raster acotada existente también sigue disponible como respaldo. Dicho de otro modo, el analizador estricto condiciona lo que se ejecuta, no lo que se conserva, que es la distinción que permite que un cambio motivado por seguridad se publique sin convertirse en un cambio de pérdida de datos

El manejo de objetos de dibujo en general, incluidas las partes del modelo de objetos que sobreviven a las idas y vueltas intactas, se cubre en el artículo de gráficos, imágenes y dibujos

Dónde deja esto a un despliegue de servidor

Si renderizan libros subidos por usuarios en un servicio, la posición práctica ahora es defendible: las imágenes de metarchivo se analizan con código que pueden auditar, acotadas por constantes que pueden leer, y nunca se entregan a un controlador de gráficos. La advertencia honesta es la cobertura. Los metarchivos complejos producidos por herramientas de dibujo tocarán el contador de registros saltados, y la respuesta a eso es hacer visible el contador en lugar de ensanchar la lista blanca en silencio. Una imagen que se renderiza parcialmente y lo dice es una conversación de soporte; una imagen que se renderiza mal y no dice nada es un reporte de error de un cliente

HotXLS maneja XLS, XLSX, ODS y CSV de forma nativa en Delphi y C++Builder sin Excel instalado, y la misma filosofía de análisis acotado atraviesa sus capas de contenedor, fórmulas y dibujo. Los detalles de formato y seguridad están en la página del producto HotXLS Delphi spreadsheet component