@@ -234,30 +234,87 @@ def parse_github_url(self, url: str):
234234 def unzip_file (self , zip_path : str , target_dir : str ) -> None :
235235 """解压缩文件, 并将压缩包内**第一个**文件夹内的文件移动到 target_dir"""
236236 ensure_dir (target_dir )
237- update_dir = ""
238237 with zipfile .ZipFile (zip_path , "r" ) as z :
239- update_dir = z .namelist ()[ 0 ]
238+ update_dir = self . _resolve_archive_root_dir ( z .namelist ())
240239 z .extractall (target_dir )
241240 logger .debug (f"解压文件完成: { zip_path } " )
242241
243- files = os .listdir (os .path .join (target_dir , update_dir ))
242+ self ._finalize_extracted_archive (zip_path , target_dir , update_dir )
243+
244+ @staticmethod
245+ def _resolve_archive_root_dir (entries : list [str ]) -> str :
246+ normalized_entries = [os .path .normpath (entry ) for entry in entries ]
247+ portable_entries = [entry .replace ("\\ " , "/" ) for entry in normalized_entries ]
248+ root_candidates : list [str ] = []
249+
250+ for raw_entry , normalized_entry , portable_entry in zip (
251+ entries , normalized_entries , portable_entries
252+ ):
253+ if normalized_entry == "." :
254+ continue
255+
256+ has_children = any (
257+ other_entry != portable_entry
258+ and other_entry .startswith (f"{ portable_entry } /" )
259+ for other_entry in portable_entries
260+ )
261+ if raw_entry .endswith (("/" , "\\ " )) or has_children :
262+ root_candidates .append (normalized_entry )
263+ continue
264+
265+ parent_portable , _ , _ = portable_entry .rpartition ("/" )
266+ if not parent_portable :
267+ return ""
268+ root_candidates .append (parent_portable .replace ("/" , os .sep ))
269+
270+ if not root_candidates :
271+ return ""
272+ return os .path .commonpath (root_candidates )
273+
274+ def _finalize_extracted_archive (
275+ self ,
276+ zip_path : str ,
277+ target_dir : str ,
278+ update_dir : str ,
279+ ) -> None :
280+ target_root_path = os .path .normpath (target_dir )
281+
282+ def _join_under_root (root : str , * parts : str ) -> str :
283+ path = os .path .normpath (os .path .join (root , * parts ))
284+ try :
285+ if os .path .commonpath ([root , path ]) != root :
286+ raise ValueError ("path escapes root directory" )
287+ except ValueError as exc :
288+ raise ValueError ("path escapes root directory" ) from exc
289+ return path
290+
291+ if not update_dir :
292+ try :
293+ os .remove (zip_path )
294+ except Exception :
295+ logger .warning (f"删除更新文件失败,可以手动删除 { zip_path } " )
296+ return
297+
298+ update_root_path = _join_under_root (target_root_path , update_dir )
299+
300+ files = os .listdir (update_root_path )
244301 for f in files :
245- if os .path .isdir (os .path .join (target_dir , update_dir , f )):
246- if os .path .exists (os .path .join (target_dir , f )):
247- shutil .rmtree (os .path .join (target_dir , f ), onerror = on_error )
248- elif os .path .exists (os .path .join (target_dir , f )):
249- os .remove (os .path .join (target_dir , f ))
250- shutil .move (os .path .join (target_dir , update_dir , f ), target_dir )
302+ update_item_path = _join_under_root (update_root_path , f )
303+ target_item_path = _join_under_root (target_root_path , f )
304+ if os .path .isdir (update_item_path ):
305+ if os .path .exists (target_item_path ):
306+ shutil .rmtree (target_item_path , onerror = on_error )
307+ elif os .path .exists (target_item_path ):
308+ os .remove (target_item_path )
309+ shutil .move (update_item_path , target_root_path )
251310
252311 try :
253- logger .debug (
254- f"删除临时更新文件: { zip_path } 和 { os .path .join (target_dir , update_dir )} " ,
255- )
256- shutil .rmtree (os .path .join (target_dir , update_dir ), onerror = on_error )
312+ logger .debug (f"删除临时更新文件: { zip_path } 和 { update_root_path } " )
313+ shutil .rmtree (update_root_path , onerror = on_error )
257314 os .remove (zip_path )
258- except BaseException :
315+ except Exception :
259316 logger .warning (
260- f"删除更新文件失败,可以手动删除 { zip_path } 和 { os . path . join ( target_dir , update_dir ) } " ,
317+ f"删除更新文件失败,可以手动删除 { zip_path } 和 { update_root_path } "
261318 )
262319
263320 def format_name (self , name : str ) -> str :
0 commit comments