1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
//! Shared scan-budget constants for a bounded `quick-xml` event loop over a remote,
//! registry-generated XML response (#698).
//!
//! `deps-gradle::license`'s POM-parsing loop already bounds itself with a private
//! entry-count + `buffer_position()` pair (issue #690/#694); this module exists so a
//! second remote-XML consumer — `deps-maven::registry::parse_metadata_xml` — shares the
//! same budget shape instead of inventing its own. It deliberately does not wrap
//! `quick_xml::Reader` itself: the workspace's XML consumers split between
//! `Reader::from_str` + `read_event()` and `Reader::from_reader` + `read_event_into(&mut
//! buf)`, and at least one (`deps-nuget::parser`) relies on the zero-copy borrow that
//! `from_str` gives deliberately — a shared wrapper would have to fight that for no
//! security gain. Each consumer keeps its own reader setup and calls [`exhausted`] as a
//! loop-top guard.
/// Entry-count threshold at which a bounded XML scan is rejected as exhausted.
///
/// Once `<version>` (or equivalent per-entry) elements retained by the scan reach this
/// count, [`exhausted`] returns `true` and the caller returns `Err` — so the largest
/// document actually *accepted* holds `MAX_METADATA_VERSIONS - 1` entries, not this count
/// itself. Real Maven Central artifacts top out around 3-4k versions for even the
/// longest-lived groups, so this leaves generous headroom above any legitimate document.
pub const MAX_METADATA_VERSIONS: usize = 8192;
/// Hard cap, in bytes of `reader.buffer_position()`, on how far into a document a bounded XML scan will read.
///
/// A real `maven-metadata.xml` with a few thousand versions is on the order of 100 KB, so
/// this leaves generous headroom while still bounding a hostile or pathological response.
pub const MAX_METADATA_BYTES_SCANNED: usize = 8 * 1024 * 1024;
/// Whether a bounded XML scan has exceeded its budget and must stop.
///
/// Checked at the top of the read loop, before processing the next event — mirrors the
/// shape-independent guard `deps-gradle::license::parse_pom_licenses` already uses: it
/// advances on every event regardless of what it is, so it cannot be starved by a
/// document that simply omits the element type a narrower, shape-keyed counter would be
/// watching for.
///
/// Thin wrapper around [`exhausted_with`] using this module's own
/// [`MAX_METADATA_VERSIONS`]/[`MAX_METADATA_BYTES_SCANNED`] budget — use `exhausted_with`
/// directly for a consumer with its own, differently-sized budget (e.g.
/// `deps-gradle::license`'s POM scan, issue #725).
///
/// # Examples
///
/// ```
/// use deps_core::xml_bounds::{exhausted, MAX_METADATA_VERSIONS};
///
/// assert!(!exhausted(0, 0));
/// assert!(exhausted(MAX_METADATA_VERSIONS, 0));
/// ```
/// Whether a bounded XML scan has exceeded a caller-supplied entry/byte budget.
///
/// Same shape-independent check as [`exhausted`], parameterized so a consumer with its
/// own budget (e.g. a POM's license-entry cap, distinct from a `maven-metadata.xml`
/// version-list cap) can share this conversion instead of keeping a private, lossy `as
/// usize` copy of it (issue #725). The `usize::try_from(..).unwrap_or(usize::MAX)`
/// fail-closed path only differs from a lossy `as usize` cast on a target where `usize`
/// is narrower than `u64` — i.e. a 32-bit target; every target this workspace's CI
/// actually builds for (see `.github/workflows/ci.yml`'s `cross-check` matrix) is
/// 64-bit, where the two are equivalent. This is 32-bit-target hardening and DRY
/// consolidation of the budget check, not a fix for a bug reachable on a supported
/// target today.
///
/// # Examples
///
/// ```
/// use deps_core::xml_bounds::exhausted_with;
///
/// assert!(!exhausted_with(0, 0, 64, 1024));
/// assert!(exhausted_with(64, 0, 64, 1024));
/// assert!(exhausted_with(0, 2048, 64, 1024));
/// ```