@@ -81,7 +81,7 @@ use crate::{
8181/// let limits = Limits::default();
8282///
8383/// // Customize limits
84- /// let strict_limits = Limits {
84+ /// let limits = Limits {
8585/// max_metadata_size: 64 * 1024,
8686/// // Set to libc::PATH_MAX when extracting to disk
8787/// max_path_len: Some(4096),
@@ -133,13 +133,6 @@ pub struct Limits {
133133 ///
134134 /// Default: 10000.
135135 pub max_sparse_entries : usize ,
136-
137- /// When true, PAX extension values that fail to parse (invalid UTF-8,
138- /// invalid integer for numeric fields like `uid`, `gid`, `size`, `mtime`)
139- /// cause errors instead of being silently ignored.
140- ///
141- /// Default: `true`.
142- pub strict : bool ,
143136}
144137
145138impl Default for Limits {
@@ -149,7 +142,6 @@ impl Default for Limits {
149142 max_path_len : None ,
150143 max_pending_entries : 16 ,
151144 max_sparse_entries : 10_000 ,
152- strict : true ,
153145 }
154146 }
155147}
@@ -172,22 +164,6 @@ impl Limits {
172164 max_path_len : None ,
173165 max_pending_entries : usize:: MAX ,
174166 max_sparse_entries : 1_000_000 ,
175- strict : false ,
176- }
177- }
178-
179- /// Create strict limits suitable for untrusted archives.
180- ///
181- /// This sets conservative limits to minimize resource consumption
182- /// from potentially malicious archives.
183- #[ must_use]
184- pub fn strict ( ) -> Self {
185- Self {
186- max_metadata_size : 64 * 1024 , // 64 KiB
187- max_path_len : Some ( 4096 ) ,
188- max_pending_entries : 8 ,
189- max_sparse_entries : 1000 ,
190- strict : true ,
191167 }
192168 }
193169
@@ -292,7 +268,7 @@ pub enum ParseError {
292268 #[ error( "invalid PAX sparse map: {0}" ) ]
293269 InvalidPaxSparseMap ( Cow < ' static , str > ) ,
294270
295- /// A PAX extension value failed to parse in strict mode .
271+ /// A PAX extension value failed to parse.
296272 #[ error( "invalid PAX {key} value: {value:?}" ) ]
297273 InvalidPaxValue {
298274 /// The PAX key (e.g. "uid", "size").
@@ -605,23 +581,23 @@ impl PendingMetadata<'_> {
605581///
606582/// Returns `Some((major, minor))` if `GNU.sparse.major` and
607583/// `GNU.sparse.minor` are both present and parseable, `None` if
608- /// the keys are absent. In strict mode , malformed values produce
609- /// errors instead of being silently ignored .
610- fn pax_sparse_version ( pax : & [ u8 ] , strict : bool ) -> Result < Option < ( u64 , u64 ) > > {
584+ /// the keys are absent. When `ignore_errors` is true , malformed values
585+ /// are silently skipped instead of producing errors .
586+ fn pax_sparse_version ( pax : & [ u8 ] , ignore_errors : bool ) -> Result < Option < ( u64 , u64 ) > > {
611587 let mut major = None ;
612588 let mut minor = None ;
613589 for ext in PaxExtensions :: new ( pax) {
614590 let ext = ext?;
615591 let key = match ext. key ( ) {
616592 Ok ( k) => k,
617- Err ( _) if !strict => continue ,
593+ Err ( _) if ignore_errors => continue ,
618594 Err ( e) => return Err ( ParseError :: from ( e) ) ,
619595 } ;
620596 match key {
621597 PAX_GNU_SPARSE_MAJOR => {
622598 let s = match ext. value ( ) {
623599 Ok ( s) => s,
624- Err ( _) if !strict => continue ,
600+ Err ( _) if ignore_errors => continue ,
625601 Err ( _) => {
626602 return Err ( ParseError :: InvalidPaxValue {
627603 key : PAX_GNU_SPARSE_MAJOR ,
@@ -631,7 +607,7 @@ fn pax_sparse_version(pax: &[u8], strict: bool) -> Result<Option<(u64, u64)>> {
631607 } ;
632608 match s. parse :: < u64 > ( ) {
633609 Ok ( v) => major = Some ( v) ,
634- Err ( _) if !strict => { }
610+ Err ( _) if ignore_errors => { }
635611 Err ( _) => {
636612 return Err ( ParseError :: InvalidPaxValue {
637613 key : PAX_GNU_SPARSE_MAJOR ,
@@ -643,7 +619,7 @@ fn pax_sparse_version(pax: &[u8], strict: bool) -> Result<Option<(u64, u64)>> {
643619 PAX_GNU_SPARSE_MINOR => {
644620 let s = match ext. value ( ) {
645621 Ok ( s) => s,
646- Err ( _) if !strict => continue ,
622+ Err ( _) if ignore_errors => continue ,
647623 Err ( _) => {
648624 return Err ( ParseError :: InvalidPaxValue {
649625 key : PAX_GNU_SPARSE_MINOR ,
@@ -653,7 +629,7 @@ fn pax_sparse_version(pax: &[u8], strict: bool) -> Result<Option<(u64, u64)>> {
653629 } ;
654630 match s. parse :: < u64 > ( ) {
655631 Ok ( v) => minor = Some ( v) ,
656- Err ( _) if !strict => { }
632+ Err ( _) if ignore_errors => { }
657633 Err ( _) => {
658634 return Err ( ParseError :: InvalidPaxValue {
659635 key : PAX_GNU_SPARSE_MINOR ,
@@ -726,6 +702,13 @@ pub struct Parser {
726702 ///
727703 /// Default: `true`.
728704 verify_checksums : bool ,
705+ /// When true, malformed PAX extension values (invalid UTF-8, unparseable
706+ /// integers for uid/gid/size/mtime) are silently skipped instead of
707+ /// producing errors. This matches the behavior of many real-world tar
708+ /// implementations.
709+ ///
710+ /// Default: `false`.
711+ ignore_pax_errors : bool ,
729712}
730713
731714impl Parser {
@@ -737,6 +720,7 @@ impl Parser {
737720 state : State :: ReadHeader ,
738721 allow_empty_path : false ,
739722 verify_checksums : true ,
723+ ignore_pax_errors : false ,
740724 }
741725 }
742726
@@ -759,6 +743,18 @@ impl Parser {
759743 self . verify_checksums = verify;
760744 }
761745
746+ /// Control whether malformed PAX extension values are silently ignored.
747+ ///
748+ /// When set to `true`, PAX values that fail to parse (invalid UTF-8,
749+ /// unparseable integers for `uid`, `gid`, `size`, `mtime`) are skipped
750+ /// instead of producing [`ParseError::InvalidPaxValue`] errors. This
751+ /// matches the lenient behavior of many real-world tar implementations.
752+ ///
753+ /// Default: `false` (malformed values produce errors).
754+ pub fn set_ignore_pax_errors ( & mut self , ignore : bool ) {
755+ self . ignore_pax_errors = ignore;
756+ }
757+
762758 /// Create a new parser with default limits.
763759 #[ must_use]
764760 pub fn with_defaults ( ) -> Self {
@@ -919,7 +915,7 @@ impl Parser {
919915 // Check for PAX v1.0 sparse before emitting — it requires
920916 // reading the sparse map from the data stream.
921917 let sparse_version = if let Some ( pax) = slices. pax_extensions {
922- pax_sparse_version ( pax, self . limits . strict ) ?
918+ pax_sparse_version ( pax, self . ignore_pax_errors ) ?
923919 } else {
924920 None
925921 } ;
@@ -1040,21 +1036,21 @@ impl Parser {
10401036 "missing PAX extensions" ,
10411037 ) ) ) ?;
10421038
1043- let strict = self . limits . strict ;
1039+ let ignore_errors = self . ignore_pax_errors ;
10441040 let mut real_size = None ;
10451041 let mut sparse_name = None ;
10461042 for ext in PaxExtensions :: new ( pax) {
10471043 let ext = ext?;
10481044 let key = match ext. key ( ) {
10491045 Ok ( k) => k,
1050- Err ( _) if !strict => continue ,
1046+ Err ( _) if ignore_errors => continue ,
10511047 Err ( e) => return Err ( ParseError :: from ( e) ) ,
10521048 } ;
10531049 match key {
10541050 PAX_GNU_SPARSE_REALSIZE | PAX_GNU_SPARSE_SIZE => {
10551051 let s = match ext. value ( ) {
10561052 Ok ( s) => s,
1057- Err ( _) if !strict => continue ,
1053+ Err ( _) if ignore_errors => continue ,
10581054 Err ( _) => {
10591055 return Err ( ParseError :: InvalidPaxValue {
10601056 key : PAX_GNU_SPARSE_REALSIZE ,
@@ -1064,7 +1060,7 @@ impl Parser {
10641060 } ;
10651061 match s. parse :: < u64 > ( ) {
10661062 Ok ( v) => real_size = Some ( v) ,
1067- Err ( _) if !strict => { }
1063+ Err ( _) if ignore_errors => { }
10681064 Err ( _) => {
10691065 return Err ( ParseError :: InvalidPaxValue {
10701066 key : PAX_GNU_SPARSE_REALSIZE ,
@@ -1346,16 +1342,16 @@ impl Parser {
13461342 let mut pax_sparse_pending_offset: Option < u64 > = None ;
13471343
13481344 if let Some ( pax) = raw_pax {
1349- let strict = self . limits . strict ;
1345+ let ignore_errors = self . ignore_pax_errors ;
13501346 let extensions = PaxExtensions :: new ( pax) ;
13511347
1352- // Helper: parse a PAX numeric value, returning Err in strict mode
1353- // or Ok(None) in lenient mode when the value is unparseable.
1348+ // Helper: parse a PAX numeric value, returning Ok(None) when
1349+ // ignore_pax_errors is set and the value is unparseable.
13541350 let parse_pax_u64 =
13551351 |ext : & crate :: PaxExtension < ' _ > , key : & ' static str | -> Result < Option < u64 > > {
13561352 let s = match ext. value ( ) {
13571353 Ok ( s) => s,
1358- Err ( _) if !strict => return Ok ( None ) ,
1354+ Err ( _) if ignore_errors => return Ok ( None ) ,
13591355 Err ( _) => {
13601356 return Err ( ParseError :: InvalidPaxValue {
13611357 key,
@@ -1365,7 +1361,7 @@ impl Parser {
13651361 } ;
13661362 match s. parse :: < u64 > ( ) {
13671363 Ok ( v) => Ok ( Some ( v) ) ,
1368- Err ( _) if !strict => Ok ( None ) ,
1364+ Err ( _) if ignore_errors => Ok ( None ) ,
13691365 Err ( _) => Err ( ParseError :: InvalidPaxValue {
13701366 key,
13711367 value : s. to_owned ( ) . into ( ) ,
@@ -1407,7 +1403,7 @@ impl Parser {
14071403 // parse only the integer part.
14081404 let s = match ext. value ( ) {
14091405 Ok ( s) => s,
1410- Err ( _) if !strict => continue ,
1406+ Err ( _) if ignore_errors => continue ,
14111407 Err ( _) => {
14121408 return Err ( ParseError :: InvalidPaxValue {
14131409 key : PAX_MTIME ,
@@ -1418,7 +1414,7 @@ impl Parser {
14181414 let int_part = s. split ( '.' ) . next ( ) . unwrap_or ( s) ;
14191415 match int_part. parse :: < u64 > ( ) {
14201416 Ok ( v) => mtime = v,
1421- Err ( _) if !strict => { }
1417+ Err ( _) if ignore_errors => { }
14221418 Err ( _) => {
14231419 return Err ( ParseError :: InvalidPaxValue {
14241420 key : PAX_MTIME ,
@@ -1459,7 +1455,7 @@ impl Parser {
14591455 PAX_GNU_SPARSE_MAP => {
14601456 let s = match ext. value ( ) {
14611457 Ok ( s) => s,
1462- Err ( _) if !strict => continue ,
1458+ Err ( _) if ignore_errors => continue ,
14631459 Err ( _) => {
14641460 return Err ( ParseError :: InvalidPaxSparseMap ( Cow :: Borrowed (
14651461 "non-UTF8 sparse map" ,
@@ -1614,10 +1610,10 @@ mod tests {
16141610 }
16151611
16161612 #[ test]
1617- fn test_strict_limits ( ) {
1618- let limits = Limits :: strict ( ) ;
1619- assert_eq ! ( limits. max_path_len , Some ( 4096 ) ) ;
1620- assert ! ( limits. max_metadata_size < Limits :: default ( ) . max_metadata_size ) ;
1613+ fn test_permissive_limits_relaxed ( ) {
1614+ let limits = Limits :: permissive ( ) ;
1615+ assert ! ( limits. max_metadata_size > Limits :: default ( ) . max_metadata_size ) ;
1616+ assert ! ( limits. max_pending_entries > Limits :: default ( ) . max_pending_entries ) ;
16211617 }
16221618
16231619 #[ test]
@@ -2861,11 +2857,8 @@ mod tests {
28612857 #[ test]
28622858 fn test_lenient_ignores_invalid_pax_uid ( ) {
28632859 let archive = make_archive_with_pax ( "uid" , b"notanumber" ) ;
2864- let limits = Limits {
2865- strict : false ,
2866- ..Default :: default ( )
2867- } ;
2868- let mut parser = Parser :: new ( limits) ;
2860+ let mut parser = Parser :: new ( Limits :: default ( ) ) ;
2861+ parser. set_ignore_pax_errors ( true ) ;
28692862 let event = parser. parse ( & archive) . unwrap ( ) ;
28702863 match event {
28712864 ParseEvent :: Entry { entry, .. } => {
@@ -2879,11 +2872,8 @@ mod tests {
28792872 #[ test]
28802873 fn test_lenient_ignores_invalid_pax_size ( ) {
28812874 let archive = make_archive_with_pax ( "size" , b"xyz" ) ;
2882- let limits = Limits {
2883- strict : false ,
2884- ..Default :: default ( )
2885- } ;
2886- let mut parser = Parser :: new ( limits) ;
2875+ let mut parser = Parser :: new ( Limits :: default ( ) ) ;
2876+ parser. set_ignore_pax_errors ( true ) ;
28872877 let event = parser. parse ( & archive) . unwrap ( ) ;
28882878 match event {
28892879 ParseEvent :: Entry { entry, .. } => {
0 commit comments