@@ -215,25 +215,109 @@ mod _lzma {
215215 spec : & PyObject ,
216216 vm : & VirtualMachine ,
217217 ) -> PyResult < backend:: FilterSpec > {
218- let mut parsed = filter_spec_with_id ( spec, vm) ?;
219- match parsed. id {
220- FILTER_LZMA1 => {
221- parsed. preset = get_dict_opt_u32 ( spec, "preset" , vm) ?;
222- parsed. lc = get_dict_opt_u32 ( spec, "lc" , vm) ?;
223- parsed. lp = get_dict_opt_u32 ( spec, "lp" , vm) ?;
224- parsed. pb = get_dict_opt_u32 ( spec, "pb" , vm) ?;
225- parsed. dict_size = get_dict_opt_u32 ( spec, "dict_size" , vm) ?;
218+ fn take_keyword (
219+ dict : & PyDict ,
220+ key : & str ,
221+ remaining : & mut usize ,
222+ error : & str ,
223+ vm : & VirtualMachine ,
224+ ) -> PyResult < Option < PyObjectRef > > {
225+ if * remaining == 0 {
226+ return Ok ( None ) ;
226227 }
227- FILTER_LZMA2 => {
228+ let value = dict
229+ . inner_getitem_opt ( key, vm)
230+ . map_err ( |_| vm. new_value_error ( error) ) ?;
231+ if value. is_some ( ) {
232+ * remaining -= 1 ;
233+ }
234+ Ok ( value)
235+ }
236+
237+ fn field (
238+ dict : & PyDict ,
239+ key : & str ,
240+ remaining : & mut usize ,
241+ error : & str ,
242+ vm : & VirtualMachine ,
243+ ) -> PyResult < Option < u32 > > {
244+ take_keyword ( dict, key, remaining, error, vm) ?
245+ . map ( |value| {
246+ value
247+ . try_into_value :: < u32 > ( vm)
248+ . map_err ( |_| vm. new_value_error ( error) )
249+ } )
250+ . transpose ( )
251+ }
252+
253+ let mut parsed = filter_spec_with_id ( spec, vm) ?;
254+ let dict = spec
255+ . downcast_ref :: < PyDict > ( )
256+ . expect ( "filter ID was read from a dict" ) ;
257+ let ( max, error) = match parsed. id {
258+ FILTER_LZMA1 | FILTER_LZMA2 => {
228259 parsed. preset = get_dict_opt_u32 ( spec, "preset" , vm) ?;
229- parsed. dict_size = get_dict_opt_u32 ( spec, "dict_size" , vm) ?;
260+ if let Some ( preset) = parsed. preset
261+ && preset & !PRESET_EXTREME > 9
262+ {
263+ return Err ( new_lzma_error (
264+ format ! ( "Invalid compression preset: {preset}" ) ,
265+ vm,
266+ ) ) ;
267+ }
268+ ( 10 , "Invalid filter specifier for LZMA filter" )
230269 }
231- FILTER_DELTA => parsed . dist = get_dict_opt_u32 ( spec , "dist" , vm ) ? ,
270+ FILTER_DELTA => ( 2 , "Invalid filter specifier for delta filter" ) ,
232271 FILTER_X86 | FILTER_POWERPC | FILTER_IA64 | FILTER_ARM | FILTER_ARMTHUMB
233- | FILTER_SPARC => {
234- parsed. start_offset = get_dict_opt_u32 ( spec, "start_offset" , vm) ?;
272+ | FILTER_SPARC => ( 2 , "Invalid filter specifier for BCJ filter" ) ,
273+ _ => return Ok ( parsed) ,
274+ } ;
275+ // PyArg_ParseTupleAndKeywords snapshots the keyword count and stops
276+ // looking up optional fields when all of those keywords are consumed.
277+ let mut remaining = dict. __len__ ( ) ;
278+ if remaining > max {
279+ return Err ( vm. new_value_error ( error) ) ;
280+ }
281+ let _ = take_keyword ( dict, "id" , & mut remaining, error, vm) ?;
282+ if matches ! ( parsed. id, FILTER_LZMA1 | FILTER_LZMA2 ) {
283+ let _ = take_keyword ( dict, "preset" , & mut remaining, error, vm) ?;
284+ parsed. dict_size = field ( dict, "dict_size" , & mut remaining, error, vm) ?;
285+ parsed. lc = field ( dict, "lc" , & mut remaining, error, vm) ?;
286+ parsed. lp = field ( dict, "lp" , & mut remaining, error, vm) ?;
287+ parsed. pb = field ( dict, "pb" , & mut remaining, error, vm) ?;
288+ parsed. mode = field ( dict, "mode" , & mut remaining, error, vm) ?;
289+ parsed. nice_len = field ( dict, "nice_len" , & mut remaining, error, vm) ?;
290+ parsed. mf = field ( dict, "mf" , & mut remaining, error, vm) ?;
291+ parsed. depth = field ( dict, "depth" , & mut remaining, error, vm) ?;
292+ if remaining > 0 {
293+ check_filter_spec_keys (
294+ spec,
295+ & [
296+ "id" ,
297+ "preset" ,
298+ "dict_size" ,
299+ "lc" ,
300+ "lp" ,
301+ "pb" ,
302+ "mode" ,
303+ "nice_len" ,
304+ "mf" ,
305+ "depth" ,
306+ ] ,
307+ error,
308+ vm,
309+ ) ?;
310+ }
311+ } else if parsed. id == FILTER_DELTA {
312+ parsed. dist = field ( dict, "dist" , & mut remaining, error, vm) ?;
313+ if remaining > 0 {
314+ check_filter_spec_keys ( spec, & [ "id" , "dist" ] , error, vm) ?;
315+ }
316+ } else {
317+ parsed. start_offset = field ( dict, "start_offset" , & mut remaining, error, vm) ?;
318+ if remaining > 0 {
319+ check_filter_spec_keys ( spec, & [ "id" , "start_offset" ] , error, vm) ?;
235320 }
236- _ => { }
237321 }
238322 Ok ( parsed)
239323 }
@@ -444,16 +528,10 @@ mod _lzma {
444528
445529 fn py_new ( _cls : & Py < PyType > , args : Self :: Args , vm : & VirtualMachine ) -> PyResult < Self > {
446530 if args. format != FORMAT_XZ && args. check != -1 && args. check != CHECK_NONE {
447- return Err ( new_lzma_error (
448- "Integrity checks are only supported by FORMAT_XZ" ,
449- vm,
450- ) ) ;
531+ return Err ( vm. new_value_error ( "Integrity checks are only supported by FORMAT_XZ" ) ) ;
451532 }
452533 if args. preset . is_some ( ) && args. filters . is_some ( ) {
453- return Err ( new_lzma_error (
454- "Cannot specify both preset and filter chain" ,
455- vm,
456- ) ) ;
534+ return Err ( vm. new_value_error ( "Cannot specify both preset and filter chain" ) ) ;
457535 }
458536 let preset = match args. preset {
459537 Some ( preset) => preset. try_into_value :: < u32 > ( vm) ?,
0 commit comments