pptxboss 2.2.0__tar.gz → 2.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {pptxboss-2.2.0 → pptxboss-2.3.0}/Cargo.lock +6 -6
- {pptxboss-2.2.0 → pptxboss-2.3.0}/Cargo.toml +4 -4
- {pptxboss-2.2.0 → pptxboss-2.3.0}/PKG-INFO +6 -4
- {pptxboss-2.2.0/crates/pptxboss-check → pptxboss-2.3.0}/README.md +5 -3
- {pptxboss-2.2.0/crates/pptxboss-testkit → pptxboss-2.3.0/crates/pptxboss-check}/README.md +5 -3
- {pptxboss-2.2.0 → pptxboss-2.3.0}/crates/pptxboss-check/src/container.rs +1 -1
- {pptxboss-2.2.0 → pptxboss-2.3.0}/crates/pptxboss-core/README.md +5 -3
- {pptxboss-2.2.0 → pptxboss-2.3.0}/crates/pptxboss-core/src/inflate.rs +212 -19
- {pptxboss-2.2.0 → pptxboss-2.3.0}/crates/pptxboss-core/src/package.rs +121 -17
- {pptxboss-2.2.0 → pptxboss-2.3.0}/crates/pptxboss-core/src/ppt.rs +2 -1
- {pptxboss-2.2.0 → pptxboss-2.3.0}/crates/pptxboss-core/src/zip.rs +18 -1
- {pptxboss-2.2.0 → pptxboss-2.3.0}/crates/pptxboss-core/tests/document.rs +64 -0
- {pptxboss-2.2.0 → pptxboss-2.3.0}/crates/pptxboss-py/src/write.rs +100 -5
- {pptxboss-2.2.0 → pptxboss-2.3.0/crates/pptxboss-testkit}/README.md +5 -3
- pptxboss-2.3.0/crates/pptxboss-write/README.md +338 -0
- {pptxboss-2.2.0 → pptxboss-2.3.0}/crates/pptxboss-write/examples/placed.rs +18 -3
- {pptxboss-2.2.0 → pptxboss-2.3.0}/crates/pptxboss-write/src/layout.rs +280 -8
- {pptxboss-2.2.0 → pptxboss-2.3.0}/crates/pptxboss-write/src/lib.rs +66 -0
- {pptxboss-2.2.0 → pptxboss-2.3.0}/crates/pptxboss-write/src/parts.rs +146 -16
- {pptxboss-2.2.0 → pptxboss-2.3.0}/crates/pptxboss-write/tests/roundtrip.rs +83 -2
- {pptxboss-2.2.0 → pptxboss-2.3.0}/pyproject.toml +1 -1
- {pptxboss-2.2.0 → pptxboss-2.3.0}/python/pptxboss/_pptxboss.pyi +29 -3
- {pptxboss-2.2.0 → pptxboss-2.3.0}/python/pptxboss/write.py +4 -2
- pptxboss-2.2.0/crates/pptxboss-write/README.md +0 -336
- {pptxboss-2.2.0 → pptxboss-2.3.0}/LICENSE-APACHE +0 -0
- {pptxboss-2.2.0 → pptxboss-2.3.0}/LICENSE-MIT +0 -0
- {pptxboss-2.2.0 → pptxboss-2.3.0}/crates/pptxboss-check/Cargo.toml +0 -0
- {pptxboss-2.2.0 → pptxboss-2.3.0}/crates/pptxboss-check/src/content_types.rs +0 -0
- {pptxboss-2.2.0 → pptxboss-2.3.0}/crates/pptxboss-check/src/core_props.rs +0 -0
- {pptxboss-2.2.0 → pptxboss-2.3.0}/crates/pptxboss-check/src/lib.rs +0 -0
- {pptxboss-2.2.0 → pptxboss-2.3.0}/crates/pptxboss-check/src/pml.rs +0 -0
- {pptxboss-2.2.0 → pptxboss-2.3.0}/crates/pptxboss-check/src/relationships.rs +0 -0
- {pptxboss-2.2.0 → pptxboss-2.3.0}/crates/pptxboss-check/src/xml_parts.rs +0 -0
- {pptxboss-2.2.0 → pptxboss-2.3.0}/crates/pptxboss-check/tests/rules.rs +0 -0
- {pptxboss-2.2.0 → pptxboss-2.3.0}/crates/pptxboss-core/Cargo.toml +0 -0
- {pptxboss-2.2.0 → pptxboss-2.3.0}/crates/pptxboss-core/benches/read.rs +0 -0
- {pptxboss-2.2.0 → pptxboss-2.3.0}/crates/pptxboss-core/examples/cfb_ls.rs +0 -0
- {pptxboss-2.2.0 → pptxboss-2.3.0}/crates/pptxboss-core/examples/inflate_ab.rs +0 -0
- {pptxboss-2.2.0 → pptxboss-2.3.0}/crates/pptxboss-core/examples/inflate_check.rs +0 -0
- {pptxboss-2.2.0 → pptxboss-2.3.0}/crates/pptxboss-core/examples/open_loop.rs +0 -0
- {pptxboss-2.2.0 → pptxboss-2.3.0}/crates/pptxboss-core/examples/open_phases.rs +0 -0
- {pptxboss-2.2.0 → pptxboss-2.3.0}/crates/pptxboss-core/examples/part_costs.rs +0 -0
- {pptxboss-2.2.0 → pptxboss-2.3.0}/crates/pptxboss-core/examples/ppt_dump.rs +0 -0
- {pptxboss-2.2.0 → pptxboss-2.3.0}/crates/pptxboss-core/examples/ppt_text.rs +0 -0
- {pptxboss-2.2.0 → pptxboss-2.3.0}/crates/pptxboss-core/examples/ppt_walk.rs +0 -0
- {pptxboss-2.2.0 → pptxboss-2.3.0}/crates/pptxboss-core/examples/read_counts.rs +0 -0
- {pptxboss-2.2.0 → pptxboss-2.3.0}/crates/pptxboss-core/examples/time_file.rs +0 -0
- {pptxboss-2.2.0 → pptxboss-2.3.0}/crates/pptxboss-core/src/cfb.rs +0 -0
- {pptxboss-2.2.0 → pptxboss-2.3.0}/crates/pptxboss-core/src/chart.rs +0 -0
- {pptxboss-2.2.0 → pptxboss-2.3.0}/crates/pptxboss-core/src/comments.rs +0 -0
- {pptxboss-2.2.0 → pptxboss-2.3.0}/crates/pptxboss-core/src/crc32.rs +0 -0
- {pptxboss-2.2.0 → pptxboss-2.3.0}/crates/pptxboss-core/src/diagram.rs +0 -0
- {pptxboss-2.2.0 → pptxboss-2.3.0}/crates/pptxboss-core/src/document.rs +0 -0
- {pptxboss-2.2.0 → pptxboss-2.3.0}/crates/pptxboss-core/src/encoding.rs +0 -0
- {pptxboss-2.2.0 → pptxboss-2.3.0}/crates/pptxboss-core/src/error.rs +0 -0
- {pptxboss-2.2.0 → pptxboss-2.3.0}/crates/pptxboss-core/src/hash.rs +0 -0
- {pptxboss-2.2.0 → pptxboss-2.3.0}/crates/pptxboss-core/src/lib.rs +0 -0
- {pptxboss-2.2.0 → pptxboss-2.3.0}/crates/pptxboss-core/src/markdown.rs +0 -0
- {pptxboss-2.2.0 → pptxboss-2.3.0}/crates/pptxboss-core/src/mce.rs +0 -0
- {pptxboss-2.2.0 → pptxboss-2.3.0}/crates/pptxboss-core/src/model.rs +0 -0
- {pptxboss-2.2.0 → pptxboss-2.3.0}/crates/pptxboss-core/src/opc.rs +0 -0
- {pptxboss-2.2.0 → pptxboss-2.3.0}/crates/pptxboss-core/src/pml.rs +0 -0
- {pptxboss-2.2.0 → pptxboss-2.3.0}/crates/pptxboss-core/src/presentation.rs +0 -0
- {pptxboss-2.2.0 → pptxboss-2.3.0}/crates/pptxboss-core/src/properties.rs +0 -0
- {pptxboss-2.2.0 → pptxboss-2.3.0}/crates/pptxboss-core/src/slide.rs +0 -0
- {pptxboss-2.2.0 → pptxboss-2.3.0}/crates/pptxboss-core/src/text.rs +0 -0
- {pptxboss-2.2.0 → pptxboss-2.3.0}/crates/pptxboss-core/src/xml.rs +0 -0
- {pptxboss-2.2.0 → pptxboss-2.3.0}/crates/pptxboss-py/Cargo.toml +0 -0
- {pptxboss-2.2.0 → pptxboss-2.3.0}/crates/pptxboss-py/src/lib.rs +0 -0
- {pptxboss-2.2.0 → pptxboss-2.3.0}/crates/pptxboss-py/src/model.rs +0 -0
- {pptxboss-2.2.0 → pptxboss-2.3.0}/crates/pptxboss-py/src/package.rs +0 -0
- {pptxboss-2.2.0 → pptxboss-2.3.0}/crates/pptxboss-testkit/Cargo.toml +0 -0
- {pptxboss-2.2.0 → pptxboss-2.3.0}/crates/pptxboss-testkit/examples/gen_fixtures.rs +0 -0
- {pptxboss-2.2.0 → pptxboss-2.3.0}/crates/pptxboss-testkit/src/deck.rs +0 -0
- {pptxboss-2.2.0 → pptxboss-2.3.0}/crates/pptxboss-testkit/src/legacy.rs +0 -0
- {pptxboss-2.2.0 → pptxboss-2.3.0}/crates/pptxboss-testkit/src/lib.rs +0 -0
- {pptxboss-2.2.0 → pptxboss-2.3.0}/crates/pptxboss-write/Cargo.toml +0 -0
- {pptxboss-2.2.0 → pptxboss-2.3.0}/crates/pptxboss-write/examples/styled.rs +0 -0
- {pptxboss-2.2.0 → pptxboss-2.3.0}/crates/pptxboss-write/src/fonts.rs +0 -0
- {pptxboss-2.2.0 → pptxboss-2.3.0}/crates/pptxboss-write/src/image.rs +0 -0
- {pptxboss-2.2.0 → pptxboss-2.3.0}/crates/pptxboss-write/src/lzcomp.rs +0 -0
- {pptxboss-2.2.0 → pptxboss-2.3.0}/crates/pptxboss-write/src/markdown.rs +0 -0
- {pptxboss-2.2.0 → pptxboss-2.3.0}/crates/pptxboss-write/src/metrics.rs +0 -0
- {pptxboss-2.2.0 → pptxboss-2.3.0}/crates/pptxboss-write/src/metrics_data.rs +0 -0
- {pptxboss-2.2.0 → pptxboss-2.3.0}/crates/pptxboss-write/src/mtx.rs +0 -0
- {pptxboss-2.2.0 → pptxboss-2.3.0}/crates/pptxboss-write/src/style.rs +0 -0
- {pptxboss-2.2.0 → pptxboss-2.3.0}/crates/pptxboss-write/src/xml.rs +0 -0
- {pptxboss-2.2.0 → pptxboss-2.3.0}/crates/pptxboss-write/src/zipw.rs +0 -0
- {pptxboss-2.2.0 → pptxboss-2.3.0}/crates/pptxboss-write/tests/data/boxy-bold.ttf +0 -0
- {pptxboss-2.2.0 → pptxboss-2.3.0}/crates/pptxboss-write/tests/data/boxy-hinted.ttf +0 -0
- {pptxboss-2.2.0 → pptxboss-2.3.0}/crates/pptxboss-write/tests/data/boxy-regular.ttf +0 -0
- {pptxboss-2.2.0 → pptxboss-2.3.0}/python/pptxboss/__init__.py +0 -0
- {pptxboss-2.2.0 → pptxboss-2.3.0}/python/pptxboss/py.typed +0 -0
|
@@ -355,7 +355,7 @@ checksum = "05c8b63e8d9609db387f0324918f81d68fe27748f084ef092fb35954d0539a85"
|
|
|
355
355
|
|
|
356
356
|
[[package]]
|
|
357
357
|
name = "pptxboss-check"
|
|
358
|
-
version = "2.
|
|
358
|
+
version = "2.3.0"
|
|
359
359
|
dependencies = [
|
|
360
360
|
"pptxboss-core",
|
|
361
361
|
"pptxboss-testkit",
|
|
@@ -364,7 +364,7 @@ dependencies = [
|
|
|
364
364
|
|
|
365
365
|
[[package]]
|
|
366
366
|
name = "pptxboss-cli"
|
|
367
|
-
version = "2.
|
|
367
|
+
version = "2.3.0"
|
|
368
368
|
dependencies = [
|
|
369
369
|
"clap",
|
|
370
370
|
"pptxboss-check",
|
|
@@ -377,7 +377,7 @@ dependencies = [
|
|
|
377
377
|
|
|
378
378
|
[[package]]
|
|
379
379
|
name = "pptxboss-core"
|
|
380
|
-
version = "2.
|
|
380
|
+
version = "2.3.0"
|
|
381
381
|
dependencies = [
|
|
382
382
|
"criterion",
|
|
383
383
|
"flate2",
|
|
@@ -388,7 +388,7 @@ dependencies = [
|
|
|
388
388
|
|
|
389
389
|
[[package]]
|
|
390
390
|
name = "pptxboss-py"
|
|
391
|
-
version = "2.
|
|
391
|
+
version = "2.3.0"
|
|
392
392
|
dependencies = [
|
|
393
393
|
"pptxboss-check",
|
|
394
394
|
"pptxboss-core",
|
|
@@ -398,14 +398,14 @@ dependencies = [
|
|
|
398
398
|
|
|
399
399
|
[[package]]
|
|
400
400
|
name = "pptxboss-testkit"
|
|
401
|
-
version = "2.
|
|
401
|
+
version = "2.3.0"
|
|
402
402
|
dependencies = [
|
|
403
403
|
"flate2",
|
|
404
404
|
]
|
|
405
405
|
|
|
406
406
|
[[package]]
|
|
407
407
|
name = "pptxboss-write"
|
|
408
|
-
version = "2.
|
|
408
|
+
version = "2.3.0"
|
|
409
409
|
dependencies = [
|
|
410
410
|
"flate2",
|
|
411
411
|
"pptxboss-check",
|
|
@@ -3,7 +3,7 @@ resolver = "2"
|
|
|
3
3
|
members = ["crates/pptxboss-core", "crates/pptxboss-check", "crates/pptxboss-write", "crates/pptxboss-py", "crates/pptxboss-testkit"]
|
|
4
4
|
|
|
5
5
|
[workspace.package]
|
|
6
|
-
version = "2.
|
|
6
|
+
version = "2.3.0" # x-release-please-version
|
|
7
7
|
edition = "2021"
|
|
8
8
|
license = "MIT OR Apache-2.0"
|
|
9
9
|
repository = "https://github.com/4thel00z/pptxboss"
|
|
@@ -16,9 +16,9 @@ keywords = ["pptx", "powerpoint", "ecma-376", "ooxml"]
|
|
|
16
16
|
# Inter-crate dependencies carry an explicit `version` (required by
|
|
17
17
|
# `cargo publish`) alongside the local `path`. The markers keep them in
|
|
18
18
|
# lockstep with the workspace version at release time.
|
|
19
|
-
pptxboss-core = { path = "crates/pptxboss-core", version = "2.
|
|
20
|
-
pptxboss-check = { path = "crates/pptxboss-check", version = "2.
|
|
21
|
-
pptxboss-write = { path = "crates/pptxboss-write", version = "2.
|
|
19
|
+
pptxboss-core = { path = "crates/pptxboss-core", version = "2.3.0" } # x-release-please-version
|
|
20
|
+
pptxboss-check = { path = "crates/pptxboss-check", version = "2.3.0" } # x-release-please-version
|
|
21
|
+
pptxboss-write = { path = "crates/pptxboss-write", version = "2.3.0" } # x-release-please-version
|
|
22
22
|
criterion = { version = "0.5", default-features = false, features = ["cargo_bench_support"] }
|
|
23
23
|
|
|
24
24
|
# Release builds trade compile time for cross-crate inlining and tighter
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pptxboss
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.3.0
|
|
4
4
|
Classifier: Development Status :: 3 - Alpha
|
|
5
5
|
Classifier: Intended Audience :: Developers
|
|
6
6
|
Classifier: Programming Language :: Rust
|
|
@@ -210,9 +210,11 @@ deck.write_to("review.pptx")?;
|
|
|
210
210
|
```
|
|
211
211
|
|
|
212
212
|
Content without coordinates is placed by the layout engine: it measures
|
|
213
|
-
text with embedded font metrics, stacks blocks
|
|
214
|
-
|
|
215
|
-
|
|
213
|
+
text with embedded font metrics, stacks blocks (text, pictures, tables,
|
|
214
|
+
key numbers, pull quotes) or sets them in columns, shrinks body text to
|
|
215
|
+
the theme's minimum when a slide is full, and continues what is left on
|
|
216
|
+
the next slide. Section slides and a footer band with the slide number
|
|
217
|
+
come from `Slide::section` and `Theme::footer`. `Theme::embed_font` stores a
|
|
216
218
|
TrueType or OpenType file in the deck, compressed the way PowerPoint
|
|
217
219
|
stores its own embedded fonts, so it renders in that font on machines
|
|
218
220
|
that lack it. Output is deterministic (fixed timestamps, fixed
|
|
@@ -187,9 +187,11 @@ deck.write_to("review.pptx")?;
|
|
|
187
187
|
```
|
|
188
188
|
|
|
189
189
|
Content without coordinates is placed by the layout engine: it measures
|
|
190
|
-
text with embedded font metrics, stacks blocks
|
|
191
|
-
|
|
192
|
-
|
|
190
|
+
text with embedded font metrics, stacks blocks (text, pictures, tables,
|
|
191
|
+
key numbers, pull quotes) or sets them in columns, shrinks body text to
|
|
192
|
+
the theme's minimum when a slide is full, and continues what is left on
|
|
193
|
+
the next slide. Section slides and a footer band with the slide number
|
|
194
|
+
come from `Slide::section` and `Theme::footer`. `Theme::embed_font` stores a
|
|
193
195
|
TrueType or OpenType file in the deck, compressed the way PowerPoint
|
|
194
196
|
stores its own embedded fonts, so it renders in that font on machines
|
|
195
197
|
that lack it. Output is deterministic (fixed timestamps, fixed
|
|
@@ -187,9 +187,11 @@ deck.write_to("review.pptx")?;
|
|
|
187
187
|
```
|
|
188
188
|
|
|
189
189
|
Content without coordinates is placed by the layout engine: it measures
|
|
190
|
-
text with embedded font metrics, stacks blocks
|
|
191
|
-
|
|
192
|
-
|
|
190
|
+
text with embedded font metrics, stacks blocks (text, pictures, tables,
|
|
191
|
+
key numbers, pull quotes) or sets them in columns, shrinks body text to
|
|
192
|
+
the theme's minimum when a slide is full, and continues what is left on
|
|
193
|
+
the next slide. Section slides and a footer band with the slide number
|
|
194
|
+
come from `Slide::section` and `Theme::footer`. `Theme::embed_font` stores a
|
|
193
195
|
TrueType or OpenType file in the deck, compressed the way PowerPoint
|
|
194
196
|
stores its own embedded fonts, so it renders in that font on machines
|
|
195
197
|
that lack it. Output is deterministic (fixed timestamps, fixed
|
|
@@ -297,7 +297,7 @@ pub fn run(ctx: &Context<'_>, sink: &mut Sink<'_>) {
|
|
|
297
297
|
let Some(entry) = archive.get(index) else {
|
|
298
298
|
continue;
|
|
299
299
|
};
|
|
300
|
-
if let Err(err) = archive.
|
|
300
|
+
if let Err(err) = archive.read_parsed(entry, &mut data) {
|
|
301
301
|
sink.push(
|
|
302
302
|
Finding::new(&ZIP008, format!("part could not be read: {err}"))
|
|
303
303
|
.in_part(&part.name),
|
|
@@ -187,9 +187,11 @@ deck.write_to("review.pptx")?;
|
|
|
187
187
|
```
|
|
188
188
|
|
|
189
189
|
Content without coordinates is placed by the layout engine: it measures
|
|
190
|
-
text with embedded font metrics, stacks blocks
|
|
191
|
-
|
|
192
|
-
|
|
190
|
+
text with embedded font metrics, stacks blocks (text, pictures, tables,
|
|
191
|
+
key numbers, pull quotes) or sets them in columns, shrinks body text to
|
|
192
|
+
the theme's minimum when a slide is full, and continues what is left on
|
|
193
|
+
the next slide. Section slides and a footer band with the slide number
|
|
194
|
+
come from `Slide::section` and `Theme::footer`. `Theme::embed_font` stores a
|
|
193
195
|
TrueType or OpenType file in the deck, compressed the way PowerPoint
|
|
194
196
|
stores its own embedded fonts, so it renders in that font on machines
|
|
195
197
|
that lack it. Output is deterministic (fixed timestamps, fixed
|
|
@@ -37,6 +37,29 @@ const DIST_EXTRA: [u8; 30] = [
|
|
|
37
37
|
/// DEFLATE expands at most this much (a 258-byte match per 2-bit code),
|
|
38
38
|
/// which bounds the reservation a corrupt size hint can ask for.
|
|
39
39
|
const MAX_RATIO: usize = 1032;
|
|
40
|
+
/// The most one part may inflate to.
|
|
41
|
+
pub const MAX_PART_BYTES: usize = 128 << 20;
|
|
42
|
+
/// How far past its compressed size a parsed part may inflate.
|
|
43
|
+
pub const MAX_INFLATE_RATIO: usize = 100;
|
|
44
|
+
/// Output up to this size is exempt from the ratio, and counted in the deck limit.
|
|
45
|
+
pub const RATIO_EXEMPT_BYTES: usize = 1 << 20;
|
|
46
|
+
/// The most a deck's parsed parts may inflate to in all.
|
|
47
|
+
pub const MAX_DECK_BYTES: usize = 512 << 20;
|
|
48
|
+
|
|
49
|
+
/// The output limit for a part parsed as XML: `MAX_INFLATE_RATIO` times its
|
|
50
|
+
/// compressed size, within `RATIO_EXEMPT_BYTES` and `MAX_PART_BYTES`.
|
|
51
|
+
pub fn ratio_limit(compressed: usize) -> usize {
|
|
52
|
+
compressed
|
|
53
|
+
.saturating_mul(MAX_INFLATE_RATIO)
|
|
54
|
+
.clamp(RATIO_EXEMPT_BYTES, MAX_PART_BYTES)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/// The output limit for all of a deck's parsed parts together: the ratio times
|
|
58
|
+
/// the file size, within `RATIO_EXEMPT_BYTES` and `MAX_DECK_BYTES`.
|
|
59
|
+
pub fn deck_limit(file: usize) -> usize {
|
|
60
|
+
file.saturating_mul(MAX_INFLATE_RATIO)
|
|
61
|
+
.clamp(RATIO_EXEMPT_BYTES, MAX_DECK_BYTES)
|
|
62
|
+
}
|
|
40
63
|
/// The order in which code length code lengths are transmitted (3.2.7).
|
|
41
64
|
const CODE_LENGTH_ORDER: [usize; 19] = [
|
|
42
65
|
16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15,
|
|
@@ -50,6 +73,10 @@ fn invalid(msg: &str) -> Error {
|
|
|
50
73
|
Error::Inflate(msg.into())
|
|
51
74
|
}
|
|
52
75
|
|
|
76
|
+
fn past_limit(limit: usize) -> Error {
|
|
77
|
+
Error::Inflate(format!("output passes the inflate limit of {limit} bytes"))
|
|
78
|
+
}
|
|
79
|
+
|
|
53
80
|
/// A little-endian bit reader over the whole input. Failures are recorded
|
|
54
81
|
/// in `failure` and checked once per symbol, keeping `Result` out of the
|
|
55
82
|
/// decode loop.
|
|
@@ -315,34 +342,60 @@ struct Out<'a> {
|
|
|
315
342
|
base: usize,
|
|
316
343
|
/// Next byte to write.
|
|
317
344
|
at: usize,
|
|
345
|
+
/// The most this stream may write.
|
|
346
|
+
limit: usize,
|
|
318
347
|
}
|
|
319
348
|
|
|
320
349
|
impl Out<'_> {
|
|
350
|
+
/// Room for `extra` more bytes; false when the buffer cannot hold them
|
|
351
|
+
/// within the limit.
|
|
321
352
|
#[inline(always)]
|
|
322
|
-
fn ensure(&mut self, extra: usize) {
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
353
|
+
fn ensure(&mut self, extra: usize) -> bool {
|
|
354
|
+
self.at + extra <= self.buf.len() || self.grow(extra)
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
/// The end of the buffer at its fullest: the limit plus the 16 bytes a
|
|
358
|
+
/// chunked copy overshoots into.
|
|
359
|
+
fn ceiling(&self) -> usize {
|
|
360
|
+
self.base.saturating_add(self.limit).saturating_add(16)
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
/// Grows the buffer for `extra` more bytes, never past `ceiling`. Every
|
|
364
|
+
/// write comes here once the buffer is full, so the buffer itself holds
|
|
365
|
+
/// the limit for literals, matches and stored blocks alike, and costs
|
|
366
|
+
/// nothing while it has room.
|
|
367
|
+
#[cold]
|
|
368
|
+
#[inline(never)]
|
|
369
|
+
fn grow(&mut self, extra: usize) -> bool {
|
|
370
|
+
let ceiling = self.ceiling();
|
|
371
|
+
if self.at + extra > ceiling {
|
|
372
|
+
return false;
|
|
326
373
|
}
|
|
374
|
+
let grown = (self.buf.len() * 2).max(self.at + extra + 256).min(ceiling);
|
|
375
|
+
self.buf.resize(grown, 0);
|
|
376
|
+
true
|
|
327
377
|
}
|
|
328
378
|
|
|
379
|
+
/// Appends `byte`; false when that passes the limit.
|
|
329
380
|
#[inline(always)]
|
|
330
|
-
fn literal(&mut self, byte: u8) {
|
|
331
|
-
self.ensure(1)
|
|
381
|
+
fn literal(&mut self, byte: u8) -> bool {
|
|
382
|
+
if !self.ensure(1) {
|
|
383
|
+
return false;
|
|
384
|
+
}
|
|
332
385
|
self.buf[self.at] = byte;
|
|
333
386
|
self.at += 1;
|
|
387
|
+
true
|
|
334
388
|
}
|
|
335
389
|
|
|
336
|
-
/// Appends `length` bytes copied from `distance` bytes back
|
|
337
|
-
/// the
|
|
338
|
-
/// or 8-byte chunks that may overshoot `length`; the overshoot
|
|
339
|
-
/// slack that later output overwrites.
|
|
390
|
+
/// Appends `length` bytes copied from `distance` bytes back, a distance
|
|
391
|
+
/// the caller has checked; false when that passes the limit. Copies run
|
|
392
|
+
/// in 16- or 8-byte chunks that may overshoot `length`; the overshoot
|
|
393
|
+
/// lands in slack that later output overwrites.
|
|
340
394
|
#[inline(always)]
|
|
341
395
|
fn copy_match(&mut self, distance: usize, length: usize) -> bool {
|
|
342
|
-
if
|
|
396
|
+
if !self.ensure(length + 16) {
|
|
343
397
|
return false;
|
|
344
398
|
}
|
|
345
|
-
self.ensure(length + 16);
|
|
346
399
|
let start = self.at - distance;
|
|
347
400
|
if distance >= 16 {
|
|
348
401
|
let mut offset = 0;
|
|
@@ -386,18 +439,36 @@ impl Out<'_> {
|
|
|
386
439
|
}
|
|
387
440
|
|
|
388
441
|
/// Inflates a raw DEFLATE stream into `out` (appended); `expected` sizes the
|
|
389
|
-
/// reservation. Trailing bytes after the final block are ignored.
|
|
442
|
+
/// reservation. Trailing bytes after the final block are ignored. The output
|
|
443
|
+
/// is held to `MAX_PART_BYTES` by what the stream actually produces, never by
|
|
444
|
+
/// a declared size.
|
|
390
445
|
pub fn inflate(input: &[u8], expected: usize, out: &mut Vec<u8>) -> Result<()> {
|
|
446
|
+
inflate_within(input, expected, MAX_PART_BYTES, out)
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
/// `inflate` held to `limit` output bytes.
|
|
450
|
+
pub fn inflate_within(
|
|
451
|
+
input: &[u8],
|
|
452
|
+
expected: usize,
|
|
453
|
+
limit: usize,
|
|
454
|
+
out: &mut Vec<u8>,
|
|
455
|
+
) -> Result<()> {
|
|
391
456
|
let base = out.len();
|
|
392
457
|
let most = input.len().saturating_mul(MAX_RATIO).saturating_add(64);
|
|
393
|
-
out.resize(base + expected.min(most).max(64) + 64, 0);
|
|
394
458
|
let mut out = Out {
|
|
395
459
|
buf: out,
|
|
396
460
|
base,
|
|
397
461
|
at: base,
|
|
462
|
+
limit,
|
|
398
463
|
};
|
|
464
|
+
let reserved = (expected.min(most).min(limit).max(64) + 64).min(out.ceiling() - base);
|
|
465
|
+
out.buf.resize(base + reserved, 0);
|
|
399
466
|
let mut bits = Bits::new(input);
|
|
400
|
-
let result =
|
|
467
|
+
let result =
|
|
468
|
+
inflate_blocks(&mut bits, &mut out).and_then(|()| match out.at - out.base > limit {
|
|
469
|
+
true => Err(past_limit(limit)),
|
|
470
|
+
false => Ok(()),
|
|
471
|
+
});
|
|
401
472
|
let written = out.at;
|
|
402
473
|
out.buf.truncate(written);
|
|
403
474
|
result
|
|
@@ -445,7 +516,9 @@ fn stored_block(bits: &mut Bits<'_>, out: &mut Out<'_>) -> Result<()> {
|
|
|
445
516
|
if end > data.len() {
|
|
446
517
|
return Err(truncated());
|
|
447
518
|
}
|
|
448
|
-
out.ensure(len)
|
|
519
|
+
if !out.ensure(len) {
|
|
520
|
+
return Err(past_limit(out.limit));
|
|
521
|
+
}
|
|
449
522
|
out.buf[out.at..out.at + len].copy_from_slice(&data[start..end]);
|
|
450
523
|
out.at += len;
|
|
451
524
|
bits.pos = end;
|
|
@@ -524,7 +597,9 @@ fn decode_block(
|
|
|
524
597
|
}
|
|
525
598
|
let symbol = literals.decode(bits);
|
|
526
599
|
if symbol < END_OF_BLOCK {
|
|
527
|
-
out.literal(symbol as u8)
|
|
600
|
+
if !out.literal(symbol as u8) {
|
|
601
|
+
return Err(past_limit(out.limit));
|
|
602
|
+
}
|
|
528
603
|
continue;
|
|
529
604
|
}
|
|
530
605
|
if symbol == END_OF_BLOCK {
|
|
@@ -545,9 +620,12 @@ fn decode_block(
|
|
|
545
620
|
let distance =
|
|
546
621
|
usize::from(DIST_BASE[distance_code]) + bits.take(u32::from(DIST_EXTRA[distance_code]));
|
|
547
622
|
bits.check()?;
|
|
548
|
-
if
|
|
623
|
+
if distance > out.at - out.base {
|
|
549
624
|
return Err(invalid("distance reaches before the start of output"));
|
|
550
625
|
}
|
|
626
|
+
if !out.copy_match(distance, length) {
|
|
627
|
+
return Err(past_limit(out.limit));
|
|
628
|
+
}
|
|
551
629
|
}
|
|
552
630
|
}
|
|
553
631
|
|
|
@@ -642,9 +720,10 @@ mod tests {
|
|
|
642
720
|
buf: &mut vec![0u8; 64],
|
|
643
721
|
base: 0,
|
|
644
722
|
at: 0,
|
|
723
|
+
limit: usize::MAX,
|
|
645
724
|
};
|
|
646
725
|
for byte in b"abcdefghijklm" {
|
|
647
|
-
out.literal(*byte);
|
|
726
|
+
assert!(out.literal(*byte));
|
|
648
727
|
}
|
|
649
728
|
assert!(out.copy_match(10, 12));
|
|
650
729
|
assert_eq!(&out.buf[..25], b"abcdefghijklmdefghijklmde");
|
|
@@ -711,4 +790,118 @@ mod tests {
|
|
|
711
790
|
assert!(code.build(&[1, 1], DISTANCE_BITS).is_ok());
|
|
712
791
|
assert!(code.build(&[1], DISTANCE_BITS).is_ok());
|
|
713
792
|
}
|
|
793
|
+
|
|
794
|
+
#[test]
|
|
795
|
+
fn a_stream_inflating_past_the_ratio_is_refused() {
|
|
796
|
+
// 32 MiB of zeros deflates about 1000:1, far past any real part.
|
|
797
|
+
let stream = deflate(&vec![0u8; 32 << 20], Compression::default());
|
|
798
|
+
let mut out = Vec::new();
|
|
799
|
+
let error = inflate_within(&stream, 0, ratio_limit(stream.len()), &mut out).unwrap_err();
|
|
800
|
+
assert!(error.to_string().contains("inflate limit"), "{error}");
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
#[test]
|
|
804
|
+
fn a_stream_inflating_past_the_part_limit_is_refused() {
|
|
805
|
+
let stream = deflate(&noise(4096, 7).repeat(64), Compression::default());
|
|
806
|
+
let mut out = Vec::new();
|
|
807
|
+
let error = inflate_within(&stream, 0, 4096, &mut out).unwrap_err();
|
|
808
|
+
assert!(error.to_string().contains("inflate limit"), "{error}");
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
#[test]
|
|
812
|
+
fn a_small_part_is_exempt_from_the_ratio() {
|
|
813
|
+
// 512 KiB of zeros inflates well past the ratio but under the exemption.
|
|
814
|
+
let stream = deflate(&vec![0u8; 512 << 10], Compression::default());
|
|
815
|
+
let mut out = Vec::new();
|
|
816
|
+
inflate_within(&stream, 0, ratio_limit(stream.len()), &mut out).unwrap();
|
|
817
|
+
assert_eq!(out, vec![0u8; 512 << 10]);
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
/// Bits written least significant first, as DEFLATE reads them.
|
|
821
|
+
#[derive(Default)]
|
|
822
|
+
struct BitWriter {
|
|
823
|
+
bytes: Vec<u8>,
|
|
824
|
+
pending: u64,
|
|
825
|
+
used: u32,
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
impl BitWriter {
|
|
829
|
+
fn put(&mut self, value: u64, width: u32) {
|
|
830
|
+
self.pending |= value << self.used;
|
|
831
|
+
self.used += width;
|
|
832
|
+
while self.used >= 8 {
|
|
833
|
+
self.bytes.push(self.pending as u8);
|
|
834
|
+
self.pending >>= 8;
|
|
835
|
+
self.used -= 8;
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
/// A run of `count` zero code lengths, as code length symbols 18
|
|
840
|
+
/// (runs of 11 to 138).
|
|
841
|
+
fn zero_lengths(&mut self, mut count: u64) {
|
|
842
|
+
while count > 0 {
|
|
843
|
+
let run = count.min(138);
|
|
844
|
+
self.put(1, 1);
|
|
845
|
+
self.put(run - 11, 7);
|
|
846
|
+
count -= run;
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
fn finish(mut self) -> Vec<u8> {
|
|
851
|
+
if self.used > 0 {
|
|
852
|
+
self.bytes.push(self.pending as u8);
|
|
853
|
+
}
|
|
854
|
+
self.bytes
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
/// One dynamic block whose only codes are `a` and end-of-block, one bit
|
|
859
|
+
/// each: `count` literals in `count` bits, with no match at all.
|
|
860
|
+
fn literal_stream(count: usize) -> Vec<u8> {
|
|
861
|
+
let mut bits = BitWriter::default();
|
|
862
|
+
const FINAL_BLOCK: u64 = 1;
|
|
863
|
+
const DYNAMIC_CODES: u64 = 2;
|
|
864
|
+
const MINIMUM_LITERAL_CODES: u64 = 0;
|
|
865
|
+
const MINIMUM_DISTANCE_CODES: u64 = 0;
|
|
866
|
+
const CODE_LENGTH_CODES_SENT: u64 = 14;
|
|
867
|
+
bits.put(FINAL_BLOCK, 1);
|
|
868
|
+
bits.put(DYNAMIC_CODES, 2);
|
|
869
|
+
bits.put(MINIMUM_LITERAL_CODES, 5);
|
|
870
|
+
bits.put(MINIMUM_DISTANCE_CODES, 5);
|
|
871
|
+
bits.put(CODE_LENGTH_CODES_SENT, 4);
|
|
872
|
+
for symbol in &CODE_LENGTH_ORDER[..18] {
|
|
873
|
+
bits.put(u64::from(*symbol == 1 || *symbol == 18), 3);
|
|
874
|
+
}
|
|
875
|
+
bits.zero_lengths(u64::from(b'a'));
|
|
876
|
+
bits.put(0, 1);
|
|
877
|
+
bits.zero_lengths(u64::from(END_OF_BLOCK) - u64::from(b'a') - 1);
|
|
878
|
+
bits.put(0, 1);
|
|
879
|
+
bits.put(0, 1);
|
|
880
|
+
for _ in 0..count {
|
|
881
|
+
bits.put(0, 1);
|
|
882
|
+
}
|
|
883
|
+
bits.put(1, 1);
|
|
884
|
+
bits.finish()
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
#[test]
|
|
888
|
+
fn a_stream_of_literals_alone_is_held_to_the_limit() {
|
|
889
|
+
// One-bit literal codes inflate 8x without a single match.
|
|
890
|
+
let stream = literal_stream(1 << 16);
|
|
891
|
+
let mut out = Vec::new();
|
|
892
|
+
inflate_within(&stream, 0, usize::MAX, &mut out).unwrap();
|
|
893
|
+
assert_eq!(out, vec![b'a'; 1 << 16]);
|
|
894
|
+
out.clear();
|
|
895
|
+
let error = inflate_within(&stream, 0, 4096, &mut out).unwrap_err();
|
|
896
|
+
assert!(error.to_string().contains("inflate limit"), "{error}");
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
#[test]
|
|
900
|
+
fn the_limit_is_exact_for_literals() {
|
|
901
|
+
for (count, limit, accepted) in [(4096, 4096, true), (4097, 4096, false), (8, 4, false)] {
|
|
902
|
+
let mut out = Vec::new();
|
|
903
|
+
let result = inflate_within(&literal_stream(count), 0, limit, &mut out);
|
|
904
|
+
assert_eq!(result.is_ok(), accepted, "{count} literals, limit {limit}");
|
|
905
|
+
}
|
|
906
|
+
}
|
|
714
907
|
}
|