@@ -16,7 +16,7 @@ class Unzip
1616 /**
1717 * @var array
1818 */
19- static $ statusStrings = array (
19+ private static $ statusStrings = array (
2020 \ZipArchive::ER_OK => 'No error ' ,
2121 \ZipArchive::ER_MULTIDISK => 'Multi-disk zip archives not supported ' ,
2222 \ZipArchive::ER_RENAME => 'Renaming temporary file failed ' ,
@@ -43,6 +43,39 @@ class Unzip
4343 \ZipArchive::ER_DELETED => 'Entry has been deleted ' ,
4444 );
4545
46+ /**
47+ * @var boolean
48+ */
49+ private $ continueOnError ;
50+
51+ /**
52+ * Extract zip file to target path
53+ *
54+ * @param string $zipFile Path of .zip file
55+ * @param string $targetPath Extract to this target (destination) path
56+ * @param boolean $continueOnError Continue extracting files on error
57+ *
58+ * @return mixed Array of filenames corresponding to the extracted files
59+ *
60+ * @throw \Exception
61+ */
62+ public function extract ($ zipFile , $ targetPath , $ continueOnError = false )
63+ {
64+ $ this ->continueOnError = $ continueOnError ;
65+
66+ $ zipArchive = $ this ->openZipFile ($ zipFile );
67+ $ targetPath = $ this ->fixPath ($ targetPath );
68+ $ filenames = $ this ->extractFilenames ($ zipArchive );
69+
70+ if ($ zipArchive ->extractTo ($ targetPath , $ filenames ) === false ) {
71+ throw new \Exception ($ this ->getStatusAsText ($ zipArchive ->status ));
72+ }
73+
74+ $ zipArchive ->close ();
75+
76+ return $ filenames ;
77+ }
78+
4679 /**
4780 * Make sure target path ends in '/'
4881 *
@@ -65,13 +98,16 @@ private function fixPath($path)
6598 * @param string $zipFile
6699 *
67100 * @return \ZipArchive
101+ *
102+ * @throw \Exception
68103 */
69104 private function openZipFile ($ zipFile )
70105 {
71106 $ zipArchive = new \ZipArchive ;
107+ $ status = $ zipArchive ->open ($ zipFile );
72108
73- if ($ zipArchive -> open ( $ zipFile ) !== true ) {
74- throw new \Exception (' Error opening ' . $ zipFile );
109+ if ($ status !== true ) {
110+ throw new \Exception ($ this -> getStatusAsText ( $ status ) . " : $ zipFile" );
75111 }
76112
77113 return $ zipArchive ;
@@ -90,7 +126,9 @@ private function extractFilenames(\ZipArchive $zipArchive)
90126 $ fileCount = $ zipArchive ->numFiles ;
91127
92128 for ($ i = 0 ; $ i < $ fileCount ; $ i ++) {
93- if (($ filename = $ this ->extractFilename ($ zipArchive , $ i )) !== false ) {
129+ $ filename = $ this ->extractFilename ($ zipArchive , $ i );
130+
131+ if ($ filename !== false ) {
94132 $ filenames [] = $ filename ;
95133 }
96134 }
@@ -101,21 +139,22 @@ private function extractFilenames(\ZipArchive $zipArchive)
101139 /**
102140 * Test for valid filename path
103141 *
104- * The .zip file is untrusted input. We check for absolute path (i.e., leading slash),
142+ * The .zip file is untrusted input. We check for absolute path (i.e., leading slash),
105143 * possible directory traversal attack (i.e., '..'), and use of PHP wrappers (i.e., ':').
144+ * Subclass and override this method at your own risk!
106145 *
107146 * @param string $path
108147 *
109148 * @return boolean
110149 */
111- private function isValidPath ($ path )
150+ protected function isValidPath ($ path )
112151 {
113152 $ pathParts = explode ('/ ' , $ path );
114153
115- if (! strncmp ($ path , '/ ' , 1 ) ||
154+ if (strncmp ($ path , '/ ' , 1 ) === 0 ||
116155 array_search ('.. ' , $ pathParts ) !== false ||
117- strpos ($ path , ': ' ) !== false )
118- {
156+ strpos ($ path , ': ' ) !== false
157+ ) {
119158 return false ;
120159 }
121160
@@ -129,59 +168,44 @@ private function isValidPath($path)
129168 * @param integer $fileIndex File index
130169 *
131170 * @return string
171+ *
172+ * @throw \Exception
132173 */
133174 private function extractFilename (\ZipArchive $ zipArchive , $ fileIndex )
134175 {
135176 $ entry = $ zipArchive ->statIndex ($ fileIndex );
136177
137178 // convert Windows directory separator to Unix style
138- $ filename = str_replace ('\\' , '/ ' , $ entry ['name ' ]);
179+ $ filename = str_replace ('\\' , '/ ' , $ entry ['name ' ]);
139180
140181 if ($ this ->isValidPath ($ filename )) {
141182 return $ filename ;
142183 }
143184
144- throw new \Exception ('Invalid filename path in zip archive ' );
185+ $ statusText = "Invalid filename path in zip archive: $ filename " ;
186+
187+ if ($ this ->continueOnError ) {
188+ trigger_error ($ statusText );
189+
190+ return false ;
191+ }
192+
193+ throw new \Exception ($ statusText );
145194 }
146195
147196 /**
148- * Get error
197+ * Get status as text string
149198 *
150199 * @param integer $status ZipArchive status
151200 *
152201 * @return string
153202 */
154- private function getError ($ status )
203+ private function getStatusAsText ($ status )
155204 {
156205 $ statusString = isset ($ this ->statusStrings [$ status ])
157206 ? $ this ->statusStrings [$ status ]
158- :'Unknown status ' ;
207+ : 'Unknown status ' ;
159208
160209 return $ statusString . '( ' . $ status . ') ' ;
161210 }
162-
163- /**
164- * Extract zip file to target path
165- *
166- * @param string $zipFile Path of .zip file
167- * @param string $targetPath Extract to this target (destination) path
168- *
169- * @return mixed Array of filenames corresponding to the extracted files
170- *
171- * @throw \Exception
172- */
173- public function extract ($ zipFile , $ targetPath )
174- {
175- $ zipArchive = $ this ->openZipFile ($ zipFile );
176- $ targetPath = $ this ->fixPath ($ targetPath );
177- $ filenames = $ this ->extractFilenames ($ zipArchive );
178-
179- if ($ zipArchive ->extractTo ($ targetPath , $ filenames ) === false ) {
180- throw new \Exception ($ this ->getError ($ zipArchive ->status ));
181- }
182-
183- $ zipArchive ->close ();
184-
185- return $ filenames ;
186- }
187211}
0 commit comments