1+ package com .kd_gaming1 .copysystem ;
2+
3+ import org .slf4j .Logger ;
4+ import org .slf4j .LoggerFactory ;
5+
6+ import java .io .File ;
7+ import java .io .IOException ;
8+ import java .io .InputStream ;
9+ import java .nio .file .Files ;
10+ import java .nio .file .Path ;
11+ import java .nio .file .StandardCopyOption ;
12+ import java .util .ArrayList ;
13+ import java .util .Enumeration ;
14+ import java .util .List ;
15+ import java .util .function .Consumer ;
16+ import java .util .zip .ZipEntry ;
17+ import java .util .zip .ZipException ;
18+ import java .util .zip .ZipFile ;
19+
20+ /**
21+ * Handles the actual extraction of ZIP files with proper error handling and progress reporting.
22+ */
23+ public class ConfigExtractor {
24+ private static final Logger LOGGER = LoggerFactory .getLogger (ConfigExtractor .class );
25+
26+ /**
27+ * Extracts a ZIP file to the target directory with progress reporting
28+ */
29+ public boolean extractZipToDirectory (File zipFile , File targetDirectory , Consumer <Integer > progressCallback )
30+ throws IOException {
31+
32+ if (!zipFile .exists ()) {
33+ throw new IOException ("ZIP file does not exist: " + zipFile .getAbsolutePath ());
34+ }
35+
36+ if (!targetDirectory .exists () && !targetDirectory .mkdirs ()) {
37+ throw new IOException ("Could not create target directory: " + targetDirectory .getAbsolutePath ());
38+ }
39+
40+ try (ZipFile zip = new ZipFile (zipFile )) {
41+ List <ZipEntry > entries = collectEntries (zip );
42+
43+ if (entries .isEmpty ()) {
44+ LOGGER .warn ("ZIP file is empty: {}" , zipFile .getName ());
45+ return true ;
46+ }
47+
48+ return extractEntries (zip , entries , targetDirectory , progressCallback );
49+
50+ } catch (ZipException e ) {
51+ throw new IOException ("Invalid ZIP file: " + zipFile .getName (), e );
52+ }
53+ }
54+
55+ private List <ZipEntry > collectEntries (ZipFile zipFile ) {
56+ List <ZipEntry > entries = new ArrayList <>();
57+ Enumeration <? extends ZipEntry > enumeration = zipFile .entries ();
58+
59+ while (enumeration .hasMoreElements ()) {
60+ ZipEntry entry = enumeration .nextElement ();
61+ // Skip directory entries, we'll create them as needed
62+ if (!entry .isDirectory ()) {
63+ entries .add (entry );
64+ }
65+ }
66+
67+ return entries ;
68+ }
69+
70+ private boolean extractEntries (ZipFile zipFile , List <ZipEntry > entries , File targetDirectory ,
71+ Consumer <Integer > progressCallback ) throws IOException {
72+
73+ int totalEntries = entries .size ();
74+ int processedEntries = 0 ;
75+
76+ for (ZipEntry entry : entries ) {
77+ try {
78+ extractSingleEntry (zipFile , entry , targetDirectory );
79+ processedEntries ++;
80+
81+ // Report progress
82+ int progress = (int ) ((double ) processedEntries / totalEntries * 100 );
83+ progressCallback .accept (progress );
84+
85+ } catch (IOException e ) {
86+ LOGGER .error ("Failed to extract entry: {}" , entry .getName (), e );
87+ throw e ;
88+ }
89+ }
90+
91+ LOGGER .info ("Successfully extracted {} entries from ZIP file" , processedEntries );
92+ return true ;
93+ }
94+
95+ private void extractSingleEntry (ZipFile zipFile , ZipEntry entry , File targetDirectory ) throws IOException {
96+ // Validate entry name to prevent directory traversal attacks
97+ String entryName = validateEntryName (entry .getName ());
98+
99+ Path targetPath = new File (targetDirectory , entryName ).toPath ();
100+
101+ // Create parent directories if they don't exist
102+ Path parentPath = targetPath .getParent ();
103+ if (parentPath != null && !Files .exists (parentPath )) {
104+ Files .createDirectories (parentPath );
105+ }
106+
107+ // Extract the file
108+ try (InputStream inputStream = zipFile .getInputStream (entry )) {
109+ Files .copy (inputStream , targetPath , StandardCopyOption .REPLACE_EXISTING );
110+ }
111+
112+ LOGGER .debug ("Extracted: {}" , entryName );
113+ }
114+
115+ private String validateEntryName (String entryName ) throws IOException {
116+ // Normalize the entry name and check for directory traversal
117+ String normalizedName = entryName .replace ('\\' , '/' );
118+
119+ if (normalizedName .contains ("../" ) || normalizedName .startsWith ("/" )) {
120+ throw new IOException ("Invalid entry name (potential directory traversal): " + entryName );
121+ }
122+
123+ return normalizedName ;
124+ }
125+ }
0 commit comments