001: /*******************************************************************************
002: * Copyright (c) 2004, 2006 IBM Corporation and others.
003: * All rights reserved. This program and the accompanying materials
004: * are made available under the terms of the Eclipse Public License v1.0
005: * which accompanies this distribution, and is available at
006: * http://www.eclipse.org/legal/epl-v10.html
007: *
008: * Contributors:
009: * IBM Corporation - initial API and implementation
010: *******************************************************************************/package org.eclipse.ui.internal.wizards.datatransfer;
011:
012: import java.io.FilterInputStream;
013: import java.io.IOException;
014: import java.io.InputStream;
015:
016: /**
017: * Input stream for reading files in ustar format (tar) compatible
018: * with the specification in IEEE Std 1003.1-2001. Also supports
019: * long filenames encoded using the GNU @LongLink extension.
020: *
021: * @since 3.1
022: */
023: public class TarInputStream extends FilterInputStream {
024: private int nextEntry = 0;
025: private int nextEOF = 0;
026: private int filepos = 0;
027: private int bytesread = 0;
028: private TarEntry firstEntry = null;
029: private String longLinkName = null;
030:
031: /**
032: * Creates a new tar input stream on the given input stream.
033: *
034: * @param in input stream
035: * @throws TarException
036: * @throws IOException
037: */
038: public TarInputStream(InputStream in) throws TarException,
039: IOException {
040: super (in);
041:
042: // Read in the first TarEntry to make sure
043: // the input is a valid tar file stream.
044: firstEntry = getNextEntry();
045: }
046:
047: /**
048: * Create a new tar input stream, skipping ahead to the given entry
049: * in the file.
050: *
051: * @param in input stream
052: * @param entry skips to this entry in the file
053: * @throws TarException
054: * @throws IOException
055: */
056: TarInputStream(InputStream in, TarEntry entry) throws TarException,
057: IOException {
058: super (in);
059: skipToEntry(entry);
060: }
061:
062: /**
063: * The checksum of a tar file header is simply the sum of the bytes in
064: * the header.
065: *
066: * @param header
067: * @return checksum
068: */
069: private long headerChecksum(byte[] header) {
070: long sum = 0;
071: for (int i = 0; i < 512; i++) {
072: sum += header[i] & 0xff;
073: }
074: return sum;
075: }
076:
077: /**
078: * Skips ahead to the position of the given entry in the file.
079: *
080: * @param entry
081: * @returns false if the entry has already been passed
082: * @throws TarException
083: * @throws IOException
084: */
085: boolean skipToEntry(TarEntry entry) throws TarException,
086: IOException {
087: int bytestoskip = entry.filepos - bytesread;
088: if (bytestoskip < 0) {
089: return false;
090: }
091: while (bytestoskip > 0) {
092: long ret = in.skip(bytestoskip);
093: if (ret < 0) {
094: throw new IOException("early end of stream"); //$NON-NLS-1$
095: }
096: bytestoskip -= ret;
097: bytesread += ret;
098: }
099: filepos = entry.filepos;
100: nextEntry = 0;
101: nextEOF = 0;
102: // Read next header to seek to file data.
103: getNextEntry();
104: return true;
105: }
106:
107: /**
108: * Returns true if the header checksum is correct.
109: *
110: * @param header
111: * @return true if this header has a valid checksum
112: */
113: private boolean isValidTarHeader(byte[] header) {
114: long fileChecksum, calculatedChecksum;
115: int pos, i;
116:
117: pos = 148;
118: StringBuffer checksumString = new StringBuffer();
119: for (i = 0; i < 8; i++) {
120: if (header[pos + i] == ' ') {
121: continue;
122: }
123: if (header[pos + i] == 0
124: || !Character.isDigit((char) header[pos + i])) {
125: break;
126: }
127: checksumString.append((char) header[pos + i]);
128: }
129: if (checksumString.length() == 0) {
130: return false;
131: }
132: if (checksumString.charAt(0) != '0') {
133: checksumString.insert(0, '0');
134: }
135: try {
136: fileChecksum = Long.decode(checksumString.toString())
137: .longValue();
138: } catch (NumberFormatException exception) {
139: //This is not valid if it cannot be parsed
140: return false;
141: }
142:
143: // Blank out the checksum.
144: for (i = 0; i < 8; i++) {
145: header[pos + i] = ' ';
146: }
147: calculatedChecksum = headerChecksum(header);
148:
149: return (fileChecksum == calculatedChecksum);
150: }
151:
152: /**
153: * Returns the next entry in the tar file. Does not handle
154: * GNU @LongLink extensions.
155: *
156: * @return the next entry in the tar file
157: * @throws TarException
158: * @throws IOException
159: */
160: TarEntry getNextEntryInternal() throws TarException, IOException {
161: byte[] header = new byte[512];
162: int pos = 0;
163: int i;
164:
165: if (firstEntry != null) {
166: TarEntry entryReturn = firstEntry;
167: firstEntry = null;
168: return entryReturn;
169: }
170:
171: while (nextEntry > 0) {
172: long ret = in.skip(nextEntry);
173: if (ret < 0) {
174: throw new IOException("early end of stream"); //$NON-NLS-1$
175: }
176: nextEntry -= ret;
177: bytesread += ret;
178: }
179:
180: int bytestoread = 512;
181: while (bytestoread > 0) {
182: int ret = super
183: .read(header, 512 - bytestoread, bytestoread);
184: if (ret < 0) {
185: throw new IOException("early end of stream"); //$NON-NLS-1$
186: }
187: bytestoread -= ret;
188: bytesread += ret;
189: }
190:
191: // If we have a header of all zeros, this marks the end of the file.
192: if (headerChecksum(header) == 0) {
193: // We are at the end of the file.
194: if (filepos > 0) {
195: return null;
196: }
197:
198: // Invalid stream.
199: throw new TarException("not in tar format"); //$NON-NLS-1$
200: }
201:
202: // Validate checksum.
203: if (!isValidTarHeader(header)) {
204: throw new TarException("not in tar format"); //$NON-NLS-1$
205: }
206:
207: while (pos < 100 && header[pos] != 0) {
208: pos++;
209: }
210: String name = new String(header, 0, pos, "UTF8"); //$NON-NLS-1$
211: // Prepend the prefix here.
212: pos = 345;
213: if (header[pos] != 0) {
214: while (pos < 500 && header[pos] != 0) {
215: pos++;
216: }
217: String prefix = new String(header, 345, pos - 345, "UTF8"); //$NON-NLS-1$
218: name = prefix + "/" + name; //$NON-NLS-1$
219: }
220:
221: TarEntry entry;
222: if (longLinkName != null) {
223: entry = new TarEntry(longLinkName, filepos);
224: longLinkName = null;
225: } else {
226: entry = new TarEntry(name, filepos);
227: }
228: if (header[156] != 0) {
229: entry.setFileType(header[156]);
230: }
231:
232: pos = 100;
233: StringBuffer mode = new StringBuffer();
234: for (i = 0; i < 8; i++) {
235: if (header[pos + i] == 0) {
236: break;
237: }
238: if (header[pos + i] == ' ') {
239: continue;
240: }
241: mode.append((char) header[pos + i]);
242: }
243: if (mode.length() > 0 && mode.charAt(0) != '0') {
244: mode.insert(0, '0');
245: }
246: try {
247: long fileMode = Long.decode(mode.toString()).longValue();
248: entry.setMode(fileMode);
249: } catch (NumberFormatException nfe) {
250: throw new TarException(
251: DataTransferMessages.TarImport_invalid_tar_format,
252: nfe);
253: }
254:
255: pos = 100 + 24;
256: StringBuffer size = new StringBuffer();
257: for (i = 0; i < 12; i++) {
258: if (header[pos + i] == 0) {
259: break;
260: }
261: if (header[pos + i] == ' ') {
262: continue;
263: }
264: size.append((char) header[pos + i]);
265: }
266: if (size.charAt(0) != '0') {
267: size.insert(0, '0');
268: }
269: int fileSize;
270: try {
271: fileSize = Integer.decode(size.toString()).intValue();
272: } catch (NumberFormatException nfe) {
273: throw new TarException(
274: DataTransferMessages.TarImport_invalid_tar_format,
275: nfe);
276: }
277:
278: entry.setSize(fileSize);
279: nextEOF = fileSize;
280: if (fileSize % 512 > 0) {
281: nextEntry = fileSize + (512 - (fileSize % 512));
282: } else {
283: nextEntry = fileSize;
284: }
285: filepos += (nextEntry + 512);
286: return entry;
287: }
288:
289: /**
290: * Moves ahead to the next file in the tar archive and returns
291: * a TarEntry object describing it.
292: *
293: * @return the next entry in the tar file
294: * @throws TarException
295: * @throws IOException
296: */
297: public TarEntry getNextEntry() throws TarException, IOException {
298: TarEntry entry = getNextEntryInternal();
299:
300: if (entry != null && entry.getName().equals("././@LongLink")) { //$NON-NLS-1$
301: // This is a GNU extension for doing long filenames.
302: // We get a file called ././@LongLink which just contains
303: // the real pathname.
304: byte[] longNameData = new byte[(int) entry.getSize()];
305: int bytesread = 0;
306: while (bytesread < longNameData.length) {
307: int cur = read(longNameData, bytesread,
308: longNameData.length - bytesread);
309: if (cur < 0) {
310: throw new IOException("early end of stream"); //$NON-NLS-1$
311: }
312: bytesread += cur;
313: }
314:
315: int pos = 0;
316: while (pos < longNameData.length && longNameData[pos] != 0) {
317: pos++;
318: }
319: longLinkName = new String(longNameData, 0, pos, "UTF8"); //$NON-NLS-1$
320: return getNextEntryInternal();
321: }
322: return entry;
323: }
324:
325: /* (non-Javadoc)
326: * @see java.io.FilterInputStream#read(byte[], int, int)
327: */
328: public int read(byte[] b, int off, int len) throws IOException {
329: if (nextEOF == 0) {
330: return -1;
331: }
332: if (len > nextEOF) {
333: len = nextEOF;
334: }
335: int size = super .read(b, off, len);
336: nextEntry -= size;
337: nextEOF -= size;
338: bytesread += size;
339: return size;
340: }
341:
342: /* (non-Javadoc)
343: * @see java.io.FilterInputStream#read()
344: */
345: public int read() throws IOException {
346: byte[] data = new byte[1];
347: int size = read(data, 0, 1);
348: if (size < 0) {
349: return size;
350: }
351: return data[0];
352: }
353: }
|