@@ -28,14 +28,24 @@ pub fn cmd_import(vfs: &Path, args: &ImportArgs) -> Result<()> {
2828
2929 let meta_raw = read_meta_raw ( & mut archive) ?;
3030 let meta = Meta :: decode ( & meta_raw) . context ( "parse meta" ) ?;
31+ if !id_matches ( & args. path , & meta) {
32+ bail ! (
33+ "app ID ({}.{}) doesn't match the expected ID" ,
34+ meta. author_id,
35+ meta. app_id
36+ ) ;
37+ }
38+ if ( meta. launcher || meta. sudo ) && meta. author_id != "sys" {
39+ println ! ( "⚠️ The app uses privileged system access. Make sure you trust the author." ) ;
40+ }
3141 let rom_path = vfs. join ( "roms" ) . join ( meta. author_id ) . join ( meta. app_id ) ;
3242
3343 init_vfs ( vfs) . context ( "init VFS" ) ?;
3444 _ = fs:: remove_dir_all ( & rom_path) ;
3545 create_dir_all ( & rom_path) . context ( "create ROM dir" ) ?;
3646 archive. extract ( & rom_path) . context ( "extract archive" ) ?;
37- if let Err ( err) = verify ( & rom_path) {
38- println ! ( "⚠️ verification failed: {err}" ) ;
47+ if let Err ( err) = verify_hash ( & rom_path) {
48+ println ! ( "⚠️ hash verification failed: {err}" ) ;
3949 }
4050 create_data_dir ( & meta, vfs) . context ( "create app data directory" ) ?;
4151 write_stats ( & meta, vfs) . context ( "create app stats file" ) ?;
@@ -47,14 +57,35 @@ pub fn cmd_import(vfs: &Path, args: &ImportArgs) -> Result<()> {
4757 Ok ( ( ) )
4858}
4959
60+ /// Check if the ID from the ID/path/URL that the user provided matches the app ID in meta.
61+ ///
62+ /// Currently verifies ID only if the app source is the catalog.
63+ /// For installation from URL/file we let the URL/file to have any name.
64+ fn id_matches ( given : & str , meta : & Meta < ' _ > ) -> bool {
65+ let is_catalog = !given. ends_with ( ".zip" ) ;
66+ if !is_catalog {
67+ return true ;
68+ }
69+ if given == "launcher" {
70+ return meta. author_id == "sys" && meta. app_id == "launcher" ;
71+ }
72+ let full_id = format ! ( "{}.{}" , meta. author_id, meta. app_id) ;
73+ given == full_id
74+ }
75+
76+ /// Fetch the given app archive as a file.
77+ ///
78+ /// * If file path is given, this path will be returned without any file modification.
79+ /// * If URL is given, the file will be downloaded.
80+ /// * If app ID is given, try downloading the app from the catalog.
5081fn fetch_archive ( path : & str ) -> Result < PathBuf > {
51- let mut path = path. to_string ( ) ;
82+ let mut path = path;
5283 if path == "launcher" {
53- path = "https://github.com/firefly-zero/firefly-launcher/releases/latest/download/sys.launcher.zip" . to_string ( ) ;
84+ path = "https://github.com/firefly-zero/firefly-launcher/releases/latest/download/sys.launcher.zip" ;
5485 }
86+ let mut path = path. to_string ( ) ;
5587
5688 // App ID is given. Fetch download URL from the catalog.
57- #[ expect( clippy:: case_sensitive_file_extension_comparisons) ]
5889 if !path. ends_with ( ".zip" ) {
5990 let Some ( ( author_id, app_id) ) = path. split_once ( '.' ) else {
6091 bail ! ( "app ID must contain dot" ) ;
@@ -90,6 +121,7 @@ fn fetch_archive(path: &str) -> Result<PathBuf> {
90121 Ok ( out_path)
91122}
92123
124+ /// Read and parse app metadata from the app archive.
93125fn read_meta_raw ( archive : & mut ZipArchive < File > ) -> Result < Vec < u8 > > {
94126 let mut meta_raw = Vec :: new ( ) ;
95127 let mut meta_file = if archive. index_for_name ( META ) . is_some ( ) {
@@ -134,14 +166,14 @@ fn reset_launcher_cache(vfs_path: &Path) -> anyhow::Result<()> {
134166}
135167
136168/// Verify SHA256 hash.
137- fn verify ( rom_path : & Path ) -> anyhow:: Result < ( ) > {
169+ fn verify_hash ( rom_path : & Path ) -> anyhow:: Result < ( ) > {
138170 let hash_path = rom_path. join ( HASH ) ;
139171 let hash_expected: & [ u8 ] = & fs:: read ( hash_path) . context ( "read hash file" ) ?;
140172 let hash_actual: & [ u8 ] = & hash_dir ( rom_path) . context ( "calculate hash" ) ?[ ..] ;
141173 if hash_actual != hash_expected {
142174 let exp = HEXLOWER . encode ( hash_expected) ;
143175 let act = HEXLOWER . encode ( hash_actual) ;
144- bail ! ( "invalid hash: \n expected: {exp}\n got: {act}" ) ;
176+ bail ! ( "expected: {exp}, got: {act}" ) ;
145177 }
146178 Ok ( ( ) )
147179}
@@ -319,4 +351,27 @@ mod tests {
319351 dirs_eq ( & vfs. join ( "roms" ) , & vfs2. join ( "roms" ) ) ;
320352 dirs_eq ( & vfs. join ( "data" ) , & vfs2. join ( "data" ) ) ;
321353 }
354+
355+ #[ test]
356+ fn test_id_matches ( ) {
357+ let meta = Meta {
358+ author_id : "sys" ,
359+ app_id : "launcher" ,
360+
361+ app_name : "" ,
362+ author_name : "" ,
363+ launcher : true ,
364+ sudo : true ,
365+ version : 1 ,
366+ } ;
367+ assert ! ( id_matches( "launcher" , & meta) ) ;
368+ assert ! ( id_matches( "sys.launcher" , & meta) ) ;
369+ assert ! ( id_matches( "sys.launcher.zip" , & meta) ) ;
370+ assert ! ( id_matches( "/tmp/sys.launcher.zip" , & meta) ) ;
371+ let url = "https://github.com/firefly-zero/firefly-launcher/releases/latest/download/sys.launcher.zip" ;
372+ assert ! ( id_matches( url, & meta) ) ;
373+
374+ assert ! ( !id_matches( "lux.snek" , & meta) ) ;
375+ assert ! ( !id_matches( "snek" , & meta) ) ;
376+ }
322377}
0 commit comments