fix(import): één centraal resourcebudget voor de presentatie-import (#874) #884

Merged
brenno merged 1 commit from fix/874-import-budget into main 2026-07-26 11:32:16 +00:00
Owner

De presentatie-import las een vreemd .pptx/.odp/.key met losse, hoge grenzen (2 GiB invoer, 4 GiB uitgepakt, 256/512 MiB Snappy, 50 MiB XML) over vijf bestanden verspreid, en zonder grens op het aantal onderdelen, dia's of IWA-objecten. Een geconstrueerd bestand kon zo een gewoon werkstation uitputten binnen die grenzen.

Wat er verandert

  • Eén ImportBudget (lib/services/import/utils/import_budget.dart) met realistische werkstation-grenzen, doorgevoerd op elk punt waar de bron de allocatie kon sturen: bronbytes, aantal onderdelen, per-entry en totaal uitgepakt, XML-partgrootte, Snappy blok en stream, dia's, IWA-objecten. standard in productie, forTest voor de tests.
  • Decodeer één keer. De service pakt het archief één keer uit en geeft het Archive door aan zowel de validatie (validateFormatFromArchive) als de importer (preDecoded). Het bestand werd voorheen twee keer uitgepakt — een tweede volledige kopie is precies de piekgeheugen-regressie die dit sluit.
  • Snappy zonder dubbele buffer. De stroom komt nu uit één BytesBuilder i.p.v. een groeiende List<int> die daarna nóg eens naar Uint8List werd gekopieerd.
  • Tel-caps op onderdelen, dia's en IWA-objecten, vóór de dure lus. De plafonds zijn op elkaar afgestemd (maxArchiveEntries 32768 ligt ruim boven wat een deck van maxSlides 2000 aan parts nodig heeft), zodat de dia-grens niet stil door de entry-grens wordt ingehaald.
  • Elke overschrijding eindigt gecontroleerd als ImportFailureReason.tooLarge met een leesbare {limiet} — geen half deck, geen crash. Geen nieuwe l10n-string: de bestaande tooLarge-melding wordt hergebruikt. Een niet-zip of beschadigd archief blijft een aparte reden.

Bewuste afweging (kernwaarde 2)

Door één grens te stellen weigert OciDeck voortaan een legitiem maar heel groot deck (bron >512 MiB of >2000 dia's) dat het vroeger — traag — misschien wél inlas. We kiezen robuustheid boven die randgevallen, omdat de weigering het bronbestand ongemoeid laat: de import leest alleen bytes en schrijft/verwijdert de bron nooit, dus de gebruiker raakt geen data kwijt en houdt zijn origineel bruikbaar in het oorspronkelijke programma — alleen deze specifieke, zeldzame conversie is geblokkeerd. Getoetst door de bewaker (akkoord-mits deze afweging vastligt; nu vastgelegd in import_budget.dart en de CHANGELOG).

Bewust buiten deze PR

  • Tijdbudget en annulering horen bij hetzelfde contract maar krijgen pas betekenis op een worker-isolate — dat is #875, waar ze aan ditzelfde budget-object worden toegevoegd.
  • Kleine gebruikstekst-nuance (bewaker, niet-blokkerend): de weigermelding zegt wél wát de grens is, maar nog niet dat het origineel bruikbaar blijft in het oorspronkelijke programma. Een fijnere zin raakt de tooLarge-l10n-string (31 vertalingen); apart mee te nemen om de scope hier zuiver te houden.

Geheugenbewijs

Deterministisch via een decodeer-één-keer-test (spy-importer die bevestigt dat de importer het al-uitgepakte archief krijgt), niet via een RSS-meting — die is onder de gedeelde testrunner te wisselvallig om een piek betrouwbaar te toetsen. De "meerdere volledige kopieën"-regressie ís het twee-keer-uitpakken, en dat is deterministisch afgedekt.

Tests

Adversariële fixtures: zip-bom (per-entry + totaal), zeer veel onderdelen, extreme Snappy-blok- en streamlengtes, te veel dia's (pptx + odp), te veel IWA-objecten (key), de XML-partgrens, plus de decodeer-één-keer-test en de service→tooLarge-keten. 234 import-tests groen (10 nieuw).

Poort

make check groen op de eindstaat (formatting, analyse, conventies, methodelengte, dode-code, hardgecodeerde tekst, commentaartaal, de volledige testsuite, dekkingsvloer 86,5% en per-bestand-vloer). make check-secrets en make sast gedraaid. DAST (ZAP) niet — niet ingericht in deze omgeving; deze wijziging voegt geen netwerkoppervlak toe.

Closes #874

De presentatie-import las een vreemd `.pptx`/`.odp`/`.key` met losse, hoge grenzen (2 GiB invoer, 4 GiB uitgepakt, 256/512 MiB Snappy, 50 MiB XML) over vijf bestanden verspreid, en zonder grens op het aantal onderdelen, dia's of IWA-objecten. Een geconstrueerd bestand kon zo een gewoon werkstation uitputten binnen die grenzen. ## Wat er verandert - **Eén `ImportBudget`** (`lib/services/import/utils/import_budget.dart`) met realistische werkstation-grenzen, doorgevoerd op elk punt waar de bron de allocatie kon sturen: bronbytes, aantal onderdelen, per-entry en totaal uitgepakt, XML-partgrootte, Snappy blok en stream, dia's, IWA-objecten. `standard` in productie, `forTest` voor de tests. - **Decodeer één keer.** De service pakt het archief één keer uit en geeft het `Archive` door aan zowel de validatie (`validateFormatFromArchive`) als de importer (`preDecoded`). Het bestand werd voorheen twee keer uitgepakt — een tweede volledige kopie is precies de piekgeheugen-regressie die dit sluit. - **Snappy zonder dubbele buffer.** De stroom komt nu uit één `BytesBuilder` i.p.v. een groeiende `List<int>` die daarna nóg eens naar `Uint8List` werd gekopieerd. - **Tel-caps** op onderdelen, dia's en IWA-objecten, vóór de dure lus. De plafonds zijn op elkaar afgestemd (`maxArchiveEntries` 32768 ligt ruim boven wat een deck van `maxSlides` 2000 aan parts nodig heeft), zodat de dia-grens niet stil door de entry-grens wordt ingehaald. - Elke overschrijding eindigt gecontroleerd als `ImportFailureReason.tooLarge` met een leesbare `{limiet}` — geen half deck, geen crash. Geen nieuwe l10n-string: de bestaande `tooLarge`-melding wordt hergebruikt. Een niet-zip of beschadigd archief blijft een aparte reden. ## Bewuste afweging (kernwaarde 2) Door één grens te stellen weigert OciDeck voortaan een *legitiem maar heel groot* deck (bron >512 MiB of >2000 dia's) dat het vroeger — traag — misschien wél inlas. We kiezen robuustheid boven die randgevallen, omdat de weigering het bronbestand ongemoeid laat: de import leest alleen bytes en schrijft/verwijdert de bron nooit, dus de gebruiker raakt geen data kwijt en houdt zijn origineel bruikbaar in het oorspronkelijke programma — alleen deze specifieke, zeldzame conversie is geblokkeerd. Getoetst door de bewaker (akkoord-mits deze afweging vastligt; nu vastgelegd in `import_budget.dart` en de CHANGELOG). ## Bewust buiten deze PR - **Tijdbudget en annulering** horen bij hetzelfde contract maar krijgen pas betekenis op een worker-isolate — dat is #875, waar ze aan ditzelfde budget-object worden toegevoegd. - **Kleine gebruikstekst-nuance** (bewaker, niet-blokkerend): de weigermelding zegt wél wát de grens is, maar nog niet dat het origineel bruikbaar blijft in het oorspronkelijke programma. Een fijnere zin raakt de `tooLarge`-l10n-string (31 vertalingen); apart mee te nemen om de scope hier zuiver te houden. ## Geheugenbewijs Deterministisch via een decodeer-één-keer-test (spy-importer die bevestigt dat de importer het al-uitgepakte archief krijgt), niet via een RSS-meting — die is onder de gedeelde testrunner te wisselvallig om een piek betrouwbaar te toetsen. De "meerdere volledige kopieën"-regressie ís het twee-keer-uitpakken, en dat is deterministisch afgedekt. ## Tests Adversariële fixtures: zip-bom (per-entry + totaal), zeer veel onderdelen, extreme Snappy-blok- en streamlengtes, te veel dia's (pptx + odp), te veel IWA-objecten (key), de XML-partgrens, plus de decodeer-één-keer-test en de service→tooLarge-keten. 234 import-tests groen (10 nieuw). ## Poort `make check` groen op de eindstaat (formatting, analyse, conventies, methodelengte, dode-code, hardgecodeerde tekst, commentaartaal, de volledige testsuite, dekkingsvloer 86,5% en per-bestand-vloer). `make check-secrets` en `make sast` gedraaid. DAST (ZAP) niet — niet ingericht in deze omgeving; deze wijziging voegt geen netwerkoppervlak toe. Closes #874
fix(import): één centraal resourcebudget voor de presentatie-import (#874)
All checks were successful
scans / scans (pull_request) Successful in 3m21s
ef8be02250
De import las een vreemd .pptx/.odp/.key met losse, hoge grenzen (2 GiB invoer,
4 GiB uitgepakt, 256/512 MiB Snappy, 50 MiB XML) over vijf bestanden verspreid,
en zonder grens op het aantal onderdelen, dia's of IWA-objecten. Een
geconstrueerd bestand kon zo een gewoon werkstation uitputten binnen die grenzen.

- Eén ImportBudget met realistische werkstation-grenzen, doorgevoerd op elk punt
  waar de bron de allocatie kon sturen (bronbytes, entries, per-entry en totaal
  uitgepakt, XML-part, Snappy blok/stream, dia's, IWA-objecten).
- Decodeer één keer: de service pakt uit en geeft het Archive door aan validatie
  én importer (preDecoded), zodat de piek geen tweede volledige kopie draagt.
- Snappy zonder dubbele buffer: één BytesBuilder i.p.v. een groeiende List<int>
  die daarna nog eens naar Uint8List werd gekopieerd.
- Tel-caps op entries, dia's en IWA-objecten, vóór de dure lus.
- Elke overschrijding eindigt gecontroleerd als tooLarge met een leesbare limiet;
  niet-zip/beschadigd blijven aparte redenen.

Tijdbudget en annulering horen bij hetzelfde contract maar krijgen pas betekenis
op een worker-isolate (#875) en landen daar.

Tests: zip-bom (per-entry + totaal), veel entries, extreme Snappy-lengtes, te
veel dia's (pptx + odp), te veel IWA-objecten (key), decodeer-één-keer, en de
service→tooLarge-keten.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
brenno merged commit 760c3c6d19 into main 2026-07-26 11:32:16 +00:00
Sign in to join this conversation.
No description provided.