/*******************************************************************************
* Copyright (c) MOBAC developers
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
******************************************************************************/
package mobac.utilities.tar;
import java.io.File;
import java.io.UnsupportedEncodingException;
public class TarHeader {
// private static Logger log = Logger.getLogger(TarHeader.class);
private File baseFilePath;
private int fileNameLength;
private final char[] fileName = new char[100];
private final char[] fileMode = new char[8];
private final char[] fileOwnerUserID = new char[8];
private final char[] fileOwnerGroupID = new char[8];
private final char[] fileSize = new char[12];
private final char[] lastModificationTime = new char[12];
private final char[] linkIndicator = new char[1];
private final char[] nameOfLinkedFile = new char[100];
private static final char[] padding = new char[255];
public TarHeader() {
}
public TarHeader(File theFile, File theBaseFilePath) {
this();
baseFilePath = theBaseFilePath;
this.setFileName(theFile, baseFilePath);
this.setFileMode();
this.setFileOwnerUserID();
this.setFileOwnerGroupID();
this.setFileSize(theFile);
this.setLastModificationTime(theFile);
this.setLinkIndicator(theFile);
}
public TarHeader(String fileName, int fileSize, boolean isDirectory) {
this();
this.setFileName(fileName);
this.setFileMode();
this.setFileOwnerUserID();
this.setFileOwnerGroupID();
this.setFileSize(fileSize);
this.setLastModificationTime(System.currentTimeMillis());
this.setLinkIndicator(isDirectory);
}
public void read(byte[] buffer) {
String fn = new String(buffer, 0, 512);
fn.getChars(0, 100, fileName, 0);
fileNameLength = fn.indexOf((char) 0);
fn.getChars(100, 108, fileMode, 0);
fn.getChars(108, 116, fileOwnerUserID, 0);
fn.getChars(116, 124, fileOwnerGroupID, 0);
fn.getChars(124, 136, fileSize, 0);
fn.getChars(136, 148, lastModificationTime, 0);
// fn.getChars(148, 156, checksum, 0); we ignore the checksum
fn.getChars(156, 157, linkIndicator, 0);
fn.getChars(157, 257, nameOfLinkedFile, 0);
}
// S E T - Methods
public void setFileName(File theFile, File theBaseFilePath) {
String filePath = theFile.getAbsolutePath();
String basePath = theBaseFilePath.getAbsolutePath();
if (!filePath.startsWith(basePath))
throw new RuntimeException("File \"" + filePath
+ "\" is outside of archive base path \"" + basePath + "\"!");
String tarFileName = filePath.substring(basePath.length(), filePath.length());
tarFileName = tarFileName.replace('\\', '/');
if (tarFileName.startsWith("/"))
tarFileName = tarFileName.substring(1, tarFileName.length());
if (theFile.isDirectory())
tarFileName = tarFileName + "/";
setFileName(tarFileName);
}
public void setFileName(String newFileName) {
char[] theFileName = newFileName.toCharArray();
fileNameLength = newFileName.length();
for (int i = 0; i < fileName.length; i++) {
if (i < theFileName.length) {
fileName[i] = theFileName[i];
} else {
fileName[i] = 0;
}
}
}
public void setFileMode() {
" 777 ".getChars(0, 7, fileMode, 0);
}
public void setFileOwnerUserID() {
" 0 ".getChars(0, 7, fileOwnerUserID, 0);
}
public void setFileOwnerGroupID() {
" 0 ".getChars(0, 7, fileOwnerGroupID, 0);
}
public void setFileSize(File theFile) {
long fileSizeLong = 0;
if (!theFile.isDirectory()) {
fileSizeLong = theFile.length();
}
setFileSize(fileSizeLong);
}
public void setFileSize(long fileSize) {
char[] fileSizeCharArray = Long.toString(fileSize, 8).toCharArray();
int offset = 11 - fileSizeCharArray.length;
for (int i = 0; i < 12; i++) {
if (i < offset) {
this.fileSize[i] = ' ';
} else if (i == 11) {
this.fileSize[i] = ' ';
} else {
this.fileSize[i] = fileSizeCharArray[i - offset];
}
}
}
public void setLastModificationTime(File theFile) {
setLastModificationTime(theFile.lastModified());
}
public void setLastModificationTime(long lastModifiedTime) {
lastModifiedTime /= 1000;
char[] fileLastModifiedTimeCharArray = Long.toString(lastModifiedTime, 8).toCharArray();
for (int i = 0; i < fileLastModifiedTimeCharArray.length; i++) {
lastModificationTime[i] = fileLastModifiedTimeCharArray[i];
}
if (fileLastModifiedTimeCharArray.length < 12) {
for (int i = fileLastModifiedTimeCharArray.length; i < 12; i++) {
lastModificationTime[i] = ' ';
}
}
}
public void setLinkIndicator(File theFile) {
setLinkIndicator(theFile.isDirectory());
}
public void setLinkIndicator(boolean isDirectory) {
if (isDirectory) {
linkIndicator[0] = '5';
} else {
linkIndicator[0] = '0';
}
}
// G E T - Methods
public String getFileName() {
return new String(fileName, 0, fileNameLength);
}
public int getFileNameLength() {
return fileNameLength;
}
public char[] getFileMode() {
return fileMode;
}
public char[] getFileOwnerUserID() {
return fileOwnerUserID;
}
public char[] getFileOwnerGroupID() {
return fileOwnerGroupID;
}
public char[] getFileSize() {
return fileSize;
}
public int getFileSizeInt() {
return Integer.parseInt(new String(fileSize).trim(), 8);
}
public char[] getLastModificationTime() {
return lastModificationTime;
}
public char[] getLinkIndicator() {
return linkIndicator;
}
public char[] getNameOfLinkedFile() {
return nameOfLinkedFile;
}
public char[] getPadding() {
return padding;
}
/**
* <p>
* Checksum field content:<br>
* Header checksum, stored as an octal number in ASCII. To compute the
* checksum, set the checksum field to all spaces, then sum all bytes in the
* header using unsigned arithmetic. This field should be stored as six
* octal digits followed by a null and a space character. Note that many
* early implementations of tar used signed arithmetic for the checksum
* field, which can cause inter- operability problems when transferring
* archives between systems. Modern robust readers compute the checksum both
* ways and accept the header if either computation matches.<br>
* <a href="http://www.freebsd.org/cgi/man.cgi?query=tar&sektion=5&manpath=FreeBSD+8-current"
* >definition source</a>
* </p>
*
* @param header
* array containing a tar header at offset 0 (512 bytes of size)
* with prepared checksum field (filled with spaces)
*/
public void correctCheckSum(byte[] header) {
// Compute the checksum
// theoretical max = 512 bytes * 255 = 130560 = o377000
int checksum = 0;
for (int i = 0; i < 512; i++) {
// compute the checksum with unsigned arithmetic
checksum = checksum + (header[i] & 0xFF);
}
String s = Integer.toOctalString(checksum);
while (s.length() < 6)
s = '0' + s;
byte[] checksumBin = (s).getBytes();
System.arraycopy(checksumBin, 0, header, 148, 6);
header[154] = 0;
}
public byte[] getBytes() {
StringBuffer sb = new StringBuffer(512);
sb.append(fileName);
sb.append(fileMode);
sb.append(fileOwnerUserID);
sb.append(fileOwnerGroupID);
sb.append(fileSize);
sb.append(lastModificationTime);
sb.append(" "); // empty/prepared checksum
sb.append(linkIndicator);
sb.append(nameOfLinkedFile);
sb.append(padding);
byte[] result;
try {
result = sb.toString().getBytes("US-ASCII");
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e); // should never happen
}
if (result.length != 512)
throw new RuntimeException("Invalid tar header size: " + result.length);
correctCheckSum(result);
return result;
}
}