"""Bounded static XLSX inventory. Never evaluates formulas or opens links.""" import io,json,posixpath,re,sys,zipfile,xml.etree.ElementTree as ET NS={'s':'http://schemas.openxmlformats.org/spreadsheetml/2006/main'} REL='{http://schemas.openxmlformats.org/officeDocument/2006/relationships}id' def inventory(data): if len(data)>150*1024:raise ValueError('Compressed workbook exceeds 150 KiB') with zipfile.ZipFile(io.BytesIO(data)) as z: files=z.infolist();names=[f.filename for f in files] if len(files)>100 or len(set(names))!=len(names):raise ValueError('Member bound or duplicate ZIP name') if sum(f.file_size for f in files)>2*1024*1024 or any(f.file_size>256*1024 for f in files):raise ValueError('Expanded member bound exceeded') if any(f.flag_bits&1 for f in files):raise ValueError('Encrypted ZIP is outside scope') if any(n.startswith('/') or '\\' in n or '..' in n.split('/') for n in names):raise ValueError('Noncanonical ZIP member') if any(x in n.lower() for n in names for x in ['vbaproject','externallinks','embeddings','activex','connections']):raise ValueError('Active or external content outside scope') def xml(name): b=z.read(name) # Reject declarations even for UTF-16 XML; no entities are needed here. probe=b.replace(b'\x00',b'').upper() if b'3:raise ValueError('More than three worksheets') rels={r.get('Id'):r for r in xml('xl/_rels/workbook.xml.rels')} shared=[] if 'xl/sharedStrings.xml' in names: shared=[''.join(t.text or '' for t in si.iter('{'+NS['s']+'}t')) for si in xml('xl/sharedStrings.xml')] result=[];cell_count=0;formula_count=0 for sheet in sheets: relation=rels.get(sheet.get(REL)) if relation is None or not relation.get('Type','').endswith('/worksheet'):raise ValueError('Worksheet relation missing') target=relation.get('Target','') path=target.lstrip('/') if target.startswith('/') else posixpath.normpath('xl/'+target) if not path.startswith('xl/worksheets/') or path not in names:raise ValueError('Worksheet target outside scope') cells=[];seen=set() for cell in xml(path).findall('s:sheetData/s:row/s:c',NS): ref=cell.get('r','') if not re.fullmatch(r'[A-Z]{1,3}[1-9][0-9]{0,6}',ref) or ref in seen:raise ValueError('Invalid or duplicate cell identity') seen.add(ref);cell_count+=1 f=cell.find('s:f',NS);v=cell.find('s:v',NS) if f is not None: formula_count+=1 if f.attrib:raise ValueError('Shared/array/data-table formulas outside scope') formula='='+(f.text or '') if len(formula)>2000:raise ValueError('Formula text bound exceeded') cells.append({'cell':ref,'formula':formula,'cached_value_unverified':v.text if v is not None else None}) elif cell.get('t')=='s': if v is None:raise ValueError('Shared string index missing') index=int(v.text) if index<0 or index>=len(shared):raise ValueError('Shared string index invalid') cells.append({'cell':ref,'value_text':shared[index]}) elif cell.get('t')=='inlineStr':cells.append({'cell':ref,'value_text':''.join(t.text or '' for t in cell.iter('{'+NS['s']+'}t'))}) elif v is not None:cells.append({'cell':ref,'value_text':v.text,'type':cell.get('t','n')}) result.append({'sheet':sheet.get('name'),'cells':cells}) if cell_count>2000 or formula_count>50:raise ValueError('Cell or formula bound exceeded') return {'sheets':result,'cell_count':cell_count,'formula_count':formula_count,'formulas_evaluated':False,'cached_values_verified':False,'external_resources_opened':False} if __name__=='__main__': from pathlib import Path with Path(sys.argv[1]).open('rb') as f: data=f.read(150*1024+1) print(json.dumps(inventory(data),indent=2))