Skip to content

Commit 8c49ee4

Browse files
cmaglieFederico Fissore
authored and
Federico Fissore
committed
Added class ArchiveExtractor
1 parent 7d5d7a8 commit 8c49ee4

File tree

1 file changed

+273
-0
lines changed

1 file changed

+273
-0
lines changed
Lines changed: 273 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,273 @@
1+
/*
2+
* This file is part of Arduino.
3+
*
4+
* Copyright 2014 Arduino LLC (http://www.arduino.cc/)
5+
*
6+
* Arduino is free software; you can redistribute it and/or modify
7+
* it under the terms of the GNU General Public License as published by
8+
* the Free Software Foundation; either version 2 of the License, or
9+
* (at your option) any later version.
10+
*
11+
* This program is distributed in the hope that it will be useful,
12+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+
* GNU General Public License for more details.
15+
*
16+
* You should have received a copy of the GNU General Public License
17+
* along with this program; if not, write to the Free Software
18+
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
19+
*
20+
* As a special exception, you may use this file as part of a free software
21+
* library without restriction. Specifically, if other files instantiate
22+
* templates or use macros or inline functions from this file, or you compile
23+
* this file and link it with other files to produce an executable, this
24+
* file does not by itself cause the resulting executable to be covered by
25+
* the GNU General Public License. This exception does not however
26+
* invalidate any other reasons why the executable file might be covered by
27+
* the GNU General Public License.
28+
*/
29+
package cc.arduino.utils;
30+
31+
import java.io.File;
32+
import java.io.FileInputStream;
33+
import java.io.FileNotFoundException;
34+
import java.io.FileOutputStream;
35+
import java.io.IOException;
36+
import java.io.InputStream;
37+
import java.util.HashMap;
38+
import java.util.Map;
39+
40+
import org.apache.commons.compress.archivers.ArchiveEntry;
41+
import org.apache.commons.compress.archivers.ArchiveInputStream;
42+
import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
43+
import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
44+
import org.apache.commons.compress.archivers.zip.ZipArchiveInputStream;
45+
import org.apache.commons.compress.compressors.bzip2.BZip2CompressorInputStream;
46+
import org.apache.commons.compress.compressors.gzip.GzipCompressorInputStream;
47+
48+
import cc.arduino.os.FileNativeUtils;
49+
50+
public class ArchiveExtractor {
51+
52+
/**
53+
* Extract <b>source</b> into <b>destFolder</b>. <b>source</b> file archive
54+
* format is autodetected from file extension.
55+
*
56+
* @param archiveFile
57+
* @param destFolder
58+
* @throws IOException
59+
*/
60+
public static void extract(File archiveFile, File destFolder)
61+
throws IOException {
62+
extract(archiveFile, destFolder, 0);
63+
}
64+
65+
/**
66+
* Extract <b>source</b> into <b>destFolder</b>. <b>source</b> file archive
67+
* format is autodetected from file extension.
68+
*
69+
* @param archiveFile
70+
* Archive file to extract
71+
* @param destFolder
72+
* Destination folder
73+
* @param stripPath
74+
* Number of path elements to strip from the paths contained in the
75+
* archived files
76+
* @throws IOException
77+
*/
78+
public static void extract(File archiveFile, File destFolder, int stripPath)
79+
throws IOException {
80+
81+
// Folders timestamps must be set at the end of archive extraction
82+
// (because creating a file in a folder alters the folder's timestamp)
83+
Map<File, Long> foldersTimestamps = new HashMap<File, Long>();
84+
85+
ArchiveInputStream in = null;
86+
try {
87+
88+
// Create an ArchiveInputStream with the correct archiving algorithm
89+
if (archiveFile.getName().endsWith("tar.bz2")) {
90+
InputStream fin = new FileInputStream(archiveFile);
91+
fin = new BZip2CompressorInputStream(fin);
92+
in = new TarArchiveInputStream(fin);
93+
} else if (archiveFile.getName().endsWith("zip")) {
94+
InputStream fin = new FileInputStream(archiveFile);
95+
in = new ZipArchiveInputStream(fin);
96+
} else if (archiveFile.getName().endsWith("tar.gz")) {
97+
InputStream fin = new FileInputStream(archiveFile);
98+
fin = new GzipCompressorInputStream(fin);
99+
in = new TarArchiveInputStream(fin);
100+
} else if (archiveFile.getName().endsWith("tar")) {
101+
InputStream fin = new FileInputStream(archiveFile);
102+
in = new TarArchiveInputStream(fin);
103+
} else {
104+
throw new IOException("Archive format not supported.");
105+
}
106+
107+
String pathPrefix = "";
108+
109+
// Cycle through all the archive entries
110+
while (true) {
111+
ArchiveEntry entry = in.getNextEntry();
112+
if (entry == null)
113+
break;
114+
115+
// Extract entry info
116+
long size = entry.getSize();
117+
String name = entry.getName();
118+
boolean isDirectory = entry.isDirectory();
119+
boolean isLink = false;
120+
boolean isSymLink = false;
121+
String linkName = null;
122+
Integer mode = null;
123+
long modifiedTime = entry.getLastModifiedDate().getTime();
124+
125+
{
126+
// Skip MacOSX metadata
127+
// http://superuser.com/questions/61185/why-do-i-get-files-like-foo-in-my-tarball-on-os-x
128+
int slash = name.lastIndexOf('/');
129+
if (slash == -1) {
130+
if (name.startsWith("._"))
131+
continue;
132+
} else {
133+
if (name.substring(slash + 1).startsWith("._"))
134+
continue;
135+
}
136+
}
137+
138+
// Skip git metadata
139+
// http://www.unix.com/unix-for-dummies-questions-and-answers/124958-file-pax_global_header-means-what.html
140+
if (name.contains("pax_global_header"))
141+
continue;
142+
143+
if (entry instanceof TarArchiveEntry) {
144+
TarArchiveEntry tarEntry = (TarArchiveEntry) entry;
145+
mode = tarEntry.getMode();
146+
isLink = tarEntry.isLink();
147+
isSymLink = tarEntry.isSymbolicLink();
148+
linkName = tarEntry.getLinkName();
149+
}
150+
151+
// On the first archive entry, if requested, detect the common path
152+
// prefix to be stripped from filenames
153+
if (stripPath > 0 && pathPrefix.isEmpty()) {
154+
int slash = 0;
155+
while (stripPath > 0) {
156+
slash = name.indexOf("/", slash);
157+
if (slash == -1)
158+
throw new IOException(
159+
"Invalid archive: it must contains a single root folder");
160+
slash++;
161+
stripPath--;
162+
}
163+
pathPrefix = name.substring(0, slash);
164+
}
165+
166+
// Strip the common path prefix when requested
167+
if (!name.startsWith(pathPrefix))
168+
throw new IOException(
169+
"Invalid archive: it must contains a single root folder while file "
170+
+ name + " is outside " + pathPrefix);
171+
name = name.substring(pathPrefix.length());
172+
if (name.isEmpty())
173+
continue;
174+
File outputFile = new File(destFolder, name);
175+
176+
File outputLinkFile = null;
177+
if (isLink) {
178+
if (!linkName.startsWith(pathPrefix)) {
179+
throw new IOException(
180+
"Invalid archive: it must contains a single root folder while file "
181+
+ linkName + " is outside " + pathPrefix);
182+
}
183+
linkName = linkName.substring(pathPrefix.length());
184+
outputLinkFile = new File(destFolder, linkName);
185+
}
186+
if (isSymLink) {
187+
// Symbolic links are referenced with relative paths
188+
outputLinkFile = new File(linkName);
189+
if (outputLinkFile.isAbsolute())
190+
throw new IOException(
191+
"Invalid archive: it contains a symbolic link with absolute path '"
192+
+ outputLinkFile + "'");
193+
}
194+
195+
// Safety check
196+
if (isDirectory) {
197+
if (outputFile.isFile())
198+
throw new IOException("Can't create folder " + outputFile
199+
+ ", a file with the same name exists!");
200+
} else {
201+
// - isLink
202+
// - isSymLink
203+
// - anything else
204+
if (outputFile.exists())
205+
throw new IOException("Can't extract file " + outputFile
206+
+ ", file already exists!");
207+
}
208+
209+
// Extract the entry
210+
if (isDirectory) {
211+
if (!outputFile.exists())
212+
if (!outputFile.mkdirs())
213+
throw new IOException("Could not create folder: " + outputFile);
214+
foldersTimestamps.put(outputFile, modifiedTime);
215+
} else if (isLink) {
216+
FileNativeUtils.link(outputLinkFile, outputFile);
217+
} else if (isSymLink) {
218+
FileNativeUtils.symlink(outputLinkFile, outputFile);
219+
outputFile.setLastModified(modifiedTime);
220+
} else {
221+
// Create the containing folder if not exists
222+
if (!outputFile.getParentFile().isDirectory())
223+
outputFile.getParentFile().mkdirs();
224+
copyStreamToFile(in, size, outputFile);
225+
outputFile.setLastModified(modifiedTime);
226+
}
227+
228+
// Set file/folder permission
229+
if (mode != null && !isSymLink)
230+
FileNativeUtils.chmod(outputFile, mode);
231+
}
232+
} finally {
233+
if (in != null)
234+
in.close();
235+
}
236+
237+
// Set folders timestamps
238+
for (File folder : foldersTimestamps.keySet()) {
239+
folder.setLastModified(foldersTimestamps.get(folder));
240+
}
241+
}
242+
243+
private static void copyStreamToFile(InputStream in, long size,
244+
File outputFile)
245+
throws FileNotFoundException, IOException {
246+
FileOutputStream fos = new FileOutputStream(outputFile);
247+
try {
248+
// if size is not available, copy until EOF...
249+
if (size == -1) {
250+
byte buffer[] = new byte[4096];
251+
int l;
252+
while ((l = in.read(buffer)) != -1) {
253+
fos.write(buffer, 0, l);
254+
}
255+
return;
256+
}
257+
258+
// ...else copy just the needed amount of bytes
259+
byte buffer[] = new byte[4096];
260+
while (size > 0) {
261+
int l = in.read(buffer);
262+
if (l <= 0)
263+
throw new IOException("Error while extracting file "
264+
+ outputFile.getAbsolutePath());
265+
fos.write(buffer, 0, l);
266+
size -= l;
267+
}
268+
} finally {
269+
fos.close();
270+
}
271+
}
272+
273+
}

0 commit comments

Comments
 (0)