@@ -19,6 +19,7 @@ use crate::tlog::{
1919 node_hash, split_stored_hash_index, stored_hash_index, sub_tree_index, Hash , HashReader ,
2020 TlogError , HASH_SIZE ,
2121} ;
22+ use std:: cmp:: max;
2223use std:: collections:: HashMap ;
2324use std:: fmt;
2425use std:: str:: FromStr ;
@@ -204,11 +205,7 @@ impl Tile {
204205 }
205206
206207 /// Path returns a tile coordinate path describing t.
207- pub fn path ( & self ) -> String {
208- self . path_internal ( true )
209- }
210-
211- fn path_internal ( & self , with_height : bool ) -> String {
208+ pub fn path ( & self , with_height : bool ) -> String {
212209 let mut n = self . n ;
213210 let h_str = if with_height {
214211 & format ! ( "/{}" , self . h)
@@ -255,8 +252,7 @@ impl Tile {
255252 Some ( t)
256253 }
257254
258- /// Parses a tile coordinate path. This currently supports only paths with
259- /// height element and with the data path element "data".
255+ /// Parses a tile coordinate path.
260256 ///
261257 /// # Errors
262258 ///
@@ -265,36 +261,57 @@ impl Tile {
265261 /// # Panics
266262 ///
267263 /// Panics if there are internal math errors.
268- pub fn from_path ( path : & str ) -> Result < Self , BadPathError > {
269- // This library bounds N to u64::MAX, which means we can limit the path size here.
270- if path. len ( ) > 52 {
264+ pub fn from_path (
265+ path : & str ,
266+ with_height : bool ,
267+ data_path : PathElem ,
268+ ) -> Result < Self , BadPathError > {
269+ // Calculate based on max supported values.
270+ let max_path_len = "tile/30" . len ( )
271+ + max ( data_path. to_string ( ) . len ( ) + 1 , "/63" . len ( ) )
272+ + "/x018/x446/x744/x073/x709/x551/615.p/1073741823" . len ( ) ;
273+ if path. len ( ) > max_path_len {
271274 return Err ( BadPathError ( path. into ( ) ) ) ;
272275 }
273276
277+ let min_path_elems = if with_height { 3 } else { 2 } ;
278+
274279 let mut components: Vec < & str > = path. split ( '/' ) . collect ( ) ;
275280 let len = components. len ( ) ;
276281
277- if len < 4 || components[ 0 ] != "tile" {
282+ if len < min_path_elems || components[ 0 ] != "tile" {
278283 return Err ( BadPathError ( path. into ( ) ) ) ;
279284 }
280285
281- let h = u8:: from_str ( components[ 1 ] ) . map_err ( |_| BadPathError ( path. into ( ) ) ) ?;
282- let ( l, data_path_opt) = if components[ 2 ] == "data" {
283- ( 0 , Some ( PathElem :: Data ) )
286+ let mut next_idx = 1 ;
287+ let h = if with_height {
288+ next_idx += 1 ;
289+ u8:: from_str ( components[ 1 ] ) . map_err ( |_| BadPathError ( path. into ( ) ) ) ?
290+ } else {
291+ TlogTile :: HEIGHT
292+ } ;
293+
294+ let ( l, data_path_opt) = if components[ next_idx] == data_path. to_string ( ) {
295+ ( 0 , Some ( data_path) )
284296 } else {
285297 (
286- u8:: from_str ( components[ 2 ] ) . map_err ( |_| BadPathError ( path. into ( ) ) ) ?,
298+ u8:: from_str ( components[ next_idx ] ) . map_err ( |_| BadPathError ( path. into ( ) ) ) ?,
287299 None ,
288300 )
289301 } ;
302+ if l > 63 {
303+ return Err ( BadPathError ( path. into ( ) ) ) ;
304+ }
305+
306+ next_idx += 1 ;
290307
291308 if !( 1 ..=30 ) . contains ( & h) {
292309 return Err ( BadPathError ( path. into ( ) ) ) ;
293310 }
294311
295312 let mut w = 1 << h;
296313 #[ allow( clippy:: case_sensitive_file_extension_comparisons) ]
297- if len >= 5 && components[ len - 2 ] . ends_with ( ".p" ) {
314+ if len > min_path_elems && components[ len - 2 ] . ends_with ( ".p" ) {
298315 let ww = u32:: from_str ( components[ len - 1 ] ) . map_err ( |_| BadPathError ( path. into ( ) ) ) ?;
299316 if !( 0 ..w) . contains ( & ww) {
300317 return Err ( BadPathError ( path. into ( ) ) ) ;
@@ -304,7 +321,7 @@ impl Tile {
304321 components. pop ( ) ;
305322 }
306323
307- components = components[ 3 ..] . to_vec ( ) ;
324+ components = components[ next_idx ..] . to_vec ( ) ;
308325
309326 let mut n = 0_u64 ;
310327 for s in components {
@@ -327,7 +344,7 @@ impl Tile {
327344
328345 let tile = Self :: new ( h, l, n, w, data_path_opt) ;
329346
330- if path != tile. path ( ) {
347+ if path != tile. path ( with_height ) {
331348 return Err ( BadPathError ( path. into ( ) ) ) ;
332349 }
333350
@@ -446,7 +463,7 @@ impl TlogTile {
446463 /// parameter should be `"entries"` for tlog-tiles, and `"data"` for
447464 /// static-ct-api.
448465 pub fn path ( & self ) -> String {
449- self . 0 . path_internal ( false )
466+ self . 0 . path ( false )
450467 }
451468
452469 /// Returns the tile's `k`'th tile parent in the tiles for a tree of size `n`. If there is no such
@@ -675,17 +692,126 @@ mod tests {
675692 }
676693
677694 #[ test]
678- fn test_tile_from_path ( ) -> Result < ( ) , BadPathError > {
679- // Valid tile path with all parameters at the maximum supported by this library.
680- let _ = Tile :: from_path ( "tile/30/data/x018/x446/x744/x073/x709/x551/615.p/255" ) ?;
681- // Valid tile path.
682- let _ = Tile :: from_path ( "tile/3/5/x13/x4/5/x3/6/07/5" ) ;
683- // Same as above, but tile path is not in canonical form.
684- let _ = Tile :: from_path ( "tile/3/5/x013/x004/x005/x003/x006/x007/005" ) ?;
685- // Triggers integer overflow.
686- let _ = Tile :: from_path ( "tile/30/data/x018/x446/x744/x073/x709/x551/616.p/255" ) ;
687- // Path too long.
688- let _ = Tile :: from_path ( "tile/30/data/x018/x446/x744/x073/x709/x551/616.p/2555" ) ;
689- Ok ( ( ) )
695+ fn test_tile_from_path ( ) {
696+ let tests = [
697+ // Valid: minimum params support by library, without height
698+ ( "tile/0/000" , false , PathElem :: Custom ( "" ) , true ) ,
699+ // Invalid: N not in canonical form
700+ ( "tile/0/00" , false , PathElem :: Custom ( "" ) , false ) ,
701+ // Invalid: H = 0
702+ ( "tile/0/0/000" , true , PathElem :: Custom ( "" ) , false ) ,
703+ // Valid: minimum params support by library, with height
704+ ( "tile/1/0/000" , true , PathElem :: Custom ( "" ) , true ) ,
705+ // Valid: all parameters at max supported by library
706+ (
707+ "tile/30/63/x018/x446/x744/x073/x709/x551/615.p/1073741823" ,
708+ true ,
709+ PathElem :: Data ,
710+ true ,
711+ ) ,
712+ // Valid: same as above, with data path
713+ (
714+ "tile/30/data/x018/x446/x744/x073/x709/x551/615.p/1073741823" ,
715+ true ,
716+ PathElem :: Data ,
717+ true ,
718+ ) ,
719+ // Invalid: total path too long (also data path element mismatch)
720+ (
721+ "tile/too_long/63/x018/x446/x744/x073/x709/x551/615.p/1073741823" ,
722+ true ,
723+ PathElem :: Data ,
724+ false ,
725+ ) ,
726+ // Invalid: non-canonical trailing zero path elements
727+ ( "tile/30/63/x000/615.p/1" , true , PathElem :: Data , false ) ,
728+ // Invalid: N > u64::MAX
729+ (
730+ "tile/30/data/x018/x446/x744/x073/x709/x551/616.p/1073741823" ,
731+ true ,
732+ PathElem :: Data ,
733+ false ,
734+ ) ,
735+ // Invalid: W > 2**H
736+ (
737+ "tile/30/data/x018/x446/x744/x073/x709/x551/615.p/1073741824" ,
738+ true ,
739+ PathElem :: Data ,
740+ false ,
741+ ) ,
742+ // Invalid: H > 30
743+ (
744+ "tile/31/data/x018/x446/x744/x073/x709/x551/615.p/1073741824" ,
745+ true ,
746+ PathElem :: Data ,
747+ false ,
748+ ) ,
749+ // Invalid: L > 63
750+ (
751+ "tile/30/64/x018/x446/x744/x073/x709/x551/615.p/1073741824" ,
752+ true ,
753+ PathElem :: Data ,
754+ false ,
755+ ) ,
756+ // Valid
757+ (
758+ "tile/3/5/x013/x004/x005/x003/x006/x007/005" ,
759+ true ,
760+ PathElem :: Data ,
761+ true ,
762+ ) ,
763+ // Invalid: same as above, but not in canonical form
764+ ( "tile/3/5/x13/x4/5/x3/6/07/5" , true , PathElem :: Data , false ) ,
765+ // Invalid: mismatched data path element
766+ (
767+ "tile/3/data/x013/x004/x005/x003/x006/x007/005" ,
768+ true ,
769+ PathElem :: Entries ,
770+ false ,
771+ ) ,
772+ ] ;
773+
774+ for ( path, with_height, path_elem, valid) in tests {
775+ let result = Tile :: from_path ( path, with_height, path_elem) ;
776+ if valid {
777+ result. unwrap ( ) ;
778+ } else {
779+ result. unwrap_err ( ) ;
780+ }
781+ }
782+ }
783+
784+ #[ test]
785+ fn test_tile_path ( ) {
786+ let tile_paths = vec ! [
787+ ( "tile/4/0/001" , Some ( Tile :: new( 4 , 0 , 1 , 16 , None ) ) ) ,
788+ ( "tile/4/0/001.p/5" , Some ( Tile :: new( 4 , 0 , 1 , 5 , None ) ) ) ,
789+ (
790+ "tile/3/5/x123/x456/078" ,
791+ Some ( Tile :: new( 3 , 5 , 123_456_078 , 8 , None ) ) ,
792+ ) ,
793+ (
794+ "tile/3/5/x123/x456/078.p/2" ,
795+ Some ( Tile :: new( 3 , 5 , 123_456_078 , 2 , None ) ) ,
796+ ) ,
797+ (
798+ "tile/1/0/x003/x057/500" ,
799+ Some ( Tile :: new( 1 , 0 , 3_057_500 , 2 , None ) ) ,
800+ ) ,
801+ ( "tile/3/5/123/456/078" , None ) ,
802+ ( "tile/3/-1/123/456/078" , None ) ,
803+ (
804+ "tile/1/data/x003/x057/500" ,
805+ Some ( Tile :: new( 1 , 0 , 3_057_500 , 2 , Some ( PathElem :: Data ) ) ) ,
806+ ) ,
807+ ] ;
808+
809+ for ( path, want) in tile_paths {
810+ let got = Tile :: from_path ( path, true , PathElem :: Data ) . ok ( ) ;
811+ assert_eq ! ( want, got) ;
812+ if let Some ( t) = want {
813+ assert_eq ! ( t. path( true ) , path) ;
814+ }
815+ }
690816 }
691817}
0 commit comments