@@ -0,0 +1,58 @@
|
||||
"""Decode only bounded, embedded raster assets. Never follow document filenames."""
|
||||
import base64
|
||||
import io
|
||||
import struct
|
||||
import warnings
|
||||
import zlib
|
||||
import olefile
|
||||
from PIL import Image
|
||||
from .record_decoder import binary_records, ParseError
|
||||
|
||||
MAX_ASSET = 16 * 1024 * 1024
|
||||
MAX_PIXELS = 16_000_000
|
||||
Image.MAX_IMAGE_PIXELS = MAX_PIXELS
|
||||
|
||||
def asset_name(name): return name.replace('\\', '/').rsplit('/', 1)[-1].casefold()
|
||||
|
||||
def read_images(data):
|
||||
if not data.startswith(olefile.MAGIC): return {}, []
|
||||
assets, errors = {}, []
|
||||
try:
|
||||
with olefile.OleFileIO(io.BytesIO(data)) as ole:
|
||||
if not ole.exists('Storage'): return assets, errors
|
||||
raw = ole.openstream('Storage').read()
|
||||
total = 0
|
||||
for record in binary_records(raw, 'Storage'):
|
||||
if not record.binary: continue
|
||||
payload = base64.b64decode(record.properties['BINARY_BASE64'])
|
||||
if len(payload) < 6 or payload[0] != 0xd0:
|
||||
errors.append('Unsupported embedded storage framing'); continue
|
||||
n = payload[1]
|
||||
if len(payload) < n+6: raise ParseError('Truncated embedded storage asset')
|
||||
name = asset_name(payload[2:2+n].decode('cp1252', errors='replace'))
|
||||
count = struct.unpack_from('<I', payload, 2+n)[0]
|
||||
if count != len(payload)-6-n: raise ParseError('Invalid embedded compressed size')
|
||||
decoder = zlib.decompressobj()
|
||||
raster = decoder.decompress(payload[6+n:], MAX_ASSET+1)
|
||||
if len(raster) > MAX_ASSET or not decoder.eof or decoder.unused_data:
|
||||
raise ParseError('Embedded raster exceeds decoding limit or has invalid compression')
|
||||
total += len(raster)
|
||||
if total > 32*1024*1024: raise ParseError('Total embedded raster size limit exceeded')
|
||||
with warnings.catch_warnings():
|
||||
warnings.simplefilter('error', Image.DecompressionBombWarning)
|
||||
with Image.open(io.BytesIO(raster)) as source:
|
||||
if source.width*source.height > MAX_PIXELS: raise ParseError('Embedded raster pixel limit exceeded')
|
||||
if source.format not in {'PNG','BMP','JPEG','TIFF','GIF'}:
|
||||
errors.append('Unsupported embedded raster format'); continue
|
||||
if getattr(source, 'n_frames', 1) != 1:
|
||||
errors.append('Multi-frame raster skipped'); continue
|
||||
image = source.convert('RGBA')
|
||||
output = io.BytesIO(); image.save(output, format='PNG')
|
||||
png = output.getvalue()
|
||||
if len(png) > MAX_ASSET: raise ParseError('Converted raster exceeds asset limit')
|
||||
if name in assets:
|
||||
errors.append('Ambiguous embedded filename skipped'); assets[name] = None
|
||||
else: assets[name] = 'data:image/png;base64,' + base64.b64encode(png).decode('ascii')
|
||||
except Exception as exc:
|
||||
errors.append('Embedded storage not completely decoded: ' + str(exc)[:160])
|
||||
return assets, errors
|
||||
مرجع در شماره جدید
مسدود کردن کاربر