src/main/java/de/uniks/networkparser/ext/io/TarUtils.java
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package de.uniks.networkparser.ext.io;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import de.uniks.networkparser.SimpleException;
public class TarUtils {
/* CONST */
/** Default record size */
public static final int DEFAULT_RCDSIZE = 512;
/** Default block size */
public static final int DEFAULT_BLKSIZE = DEFAULT_RCDSIZE * 20;
/** GNU format as per before tar 1.12. */
public static final int FORMAT_OLDGNU = 2;
/** Pure Posix format. */
public static final int FORMAT_POSIX = 3;
/** xstar format used by Joerg Schilling's star. */
public static final int FORMAT_XSTAR = 4;
/** Offset inside the header for the xstar magic bytes. */
public static final int XSTAR_MAGIC_OFFSET = 508;
/** Length of the XSTAR magic. */
public static final int XSTAR_MAGIC_LEN = 4;
/** Length of the prefix field in xstar archives. */
public static final int PREFIXLEN_XSTAR = 131;
/** Offset of start of magic field within header record */
public static final int VERSION_OFFSET = 263;
/**
* Previously this was regarded as part of "magic" field, but it is separate.
*/
public static final int VERSIONLEN = 2;
/** The length of the user id field in a header buffer. */
public static final int UIDLEN = 8;
/** The length of the group id field in a header buffer. */
public static final int GIDLEN = 8;
/** Offset of the checksum field within header record. */
public static final int CHKSUM_OFFSET = 148;
/**
* The length of the size field in a header buffer. Includes the trailing space
* or NUL.
*/
public static final int SIZELEN = 12;
/** The length of the user name field in a header buffer. */
public static final int UNAMELEN = 32;
/** The length of the group name field in a header buffer. */
public static final int GNAMELEN = 32;
/**
* The maximum size of a file in a tar archive which can be expressed in octal
* char notation (that's 11 sevens, octal).
*/
public static final long MAXSIZE = 077777777777L;
/** Offset of start of magic field within header record */
public static final int MAGIC_OFFSET = 257;
/** The length of the magic field in a header buffer. */
public static final int MAGICLEN = 6;
/**
* The magix string used in the last four bytes of the header to identify the
* xstar format.
*/
public static final String MAGIC_XSTAR = "tar\0";
/** The magic tag representing an Ant tar archive. */
public static final String MAGIC_ANT = "ustar\0";
/** The "version" representing an Ant tar archive. */
public static final String VERSION_ANT = "\0\0";
/** The length of the checksum field in a header buffer. */
public static final int CHKSUMLEN = 8;
/**
* The maximum value of gid/uid in a tar archive which can be expressed in octal
* char notation (that's 7 sevens, octal).
*/
public static final long MAXID = 07777777L;
/** The sum of the length of all sparse headers in a sparse header buffer. */
public static final int SPARSELEN_GNU_SPARSE = 504;
/**
* The magic tag representing a POSIX tar archive.
*/
public static final String MAGIC_POSIX = "ustar\0";
public static final String VERSION_POSIX = "00";
/**
* LF_ constants represent the "link flag" of an entry, or more commonly, the
* "entry type". This is the "old way" of indicating a normal file.
*/
public static final byte LF_OLDNORM = 0;
/** Normal file type. */
public static final byte LF_NORMAL = (byte) '0';
/** Link file type. */
public static final byte LF_LINK = (byte) '1';
/** Symbolic link file type. */
public static final byte LF_SYMLINK = (byte) '2';
/** Character device file type. */
public static final byte LF_CHR = (byte) '3';
/** Block device file type. */
public static final byte LF_BLK = (byte) '4';
/** Directory file type. */
public static final byte LF_DIR = (byte) '5';
/** FIFO (pipe) file type. */
public static final byte LF_FIFO = (byte) '6';
/** The length of the name field in a header buffer. */
public static final int NAMELEN = 100;
/** The length of the mode field in a header buffer. */
public static final int MODELEN = 8;
/** The length of the access time field in an old GNU header buffer. */
public static final int ATIMELEN_GNU = 12;
/** The length of the created time field in an old GNU header buffer. */
public static final int CTIMELEN_GNU = 12;
/**
* The length of the multivolume start offset field in an old GNU header buffer.
*/
public static final int OFFSETLEN_GNU = 12;
/** The length of the long names field in an old GNU header buffer. */
public static final int LONGNAMESLEN_GNU = 4;
/** The length of the padding field in an old GNU header buffer. */
public static final int PAD2LEN_GNU = 1;
/** The sum of the length of all sparse headers in an old GNU header buffer. */
public static final int SPARSELEN_GNU = 96;
/**
* The length of each of the device fields (major and minor) in a header buffer.
*/
public static final int DEVLEN = 8;
/** Identifies the entry as a Pax extended header. */
public static final byte LF_PAX_EXTENDED_HEADER_LC = (byte) 'x';
/** Identifies the entry as a Pax extended header (SunOS tar -E). */
public static final byte LF_PAX_EXTENDED_HEADER_UC = (byte) 'X';
/** Identifies the entry as a Pax global extended header. */
public static final byte LF_PAX_GLOBAL_EXTENDED_HEADER = (byte) 'g';
/** Identifies the *next* file on the tape as having a long linkname. */
public static final byte LF_GNUTYPE_LONGLINK = (byte) 'K';
/** Identifies the *next* file on the tape as having a long name. */
public static final byte LF_GNUTYPE_LONGNAME = (byte) 'L';
public static final byte LF_GNUTYPE_SPARSE = (byte) 'S';
public static final String MAGIC_GNU = "ustar ";
public static final String VERSION_GNU_SPACE = " \0";
public static final String VERSION_GNU_ZERO = "0\0";
/** The length of the is extension field in an old GNU header buffer. */
public static final int ISEXTENDEDLEN_GNU = 1;
/** The length of the real size field in an old GNU header buffer. */
public static final int REALSIZELEN_GNU = 12;
/** The length of the modification time field in a header buffer. */
public static final int MODTIMELEN = 12;
/** Length of the prefix field. */
public static final int PREFIXLEN = 155;
private static final int BYTE_MASK = 255;
static final NioZipEncoding DEFAULT_ENCODING = TarUtils.getZipEncoding(null);
/**
* Encapsulates the algorithms used up to Commons Compress 1.3 as ZipEncoding.
*/
static final NioZipEncoding FALLBACK_ENCODING = new NioZipEncoding();
/**
* Parse an octal string from a buffer.
*
* <p>
* Leading spaces are ignored. The buffer must contain a trailing space or NUL,
* and may contain an additional trailing space or NUL.
* </p>
*
* <p>
* The input buffer is allowed to contain all NULs, in which case the method
* returns 0L (this allows for missing fields).
* </p>
*
* <p>
* To work-around some tar implementations that insert a leading NUL this method
* returns 0 if it detects a leading NUL since Commons Compress 1.4.
* </p>
*
* @param buffer The buffer from which to parse.
* @param offset The offset into the buffer from which to parse.
* @param length The maximum number of bytes to parse - must be at least 2
* bytes.
* @return The long value of the octal string.
* @throws SimpleException if the trailing space/NUL is missing or if a invalid
* byte is detected.
*/
public static long parseOctal(byte[] buffer, int offset, int length) {
long result = 0;
int end = offset + length;
int start = offset;
if (length < 2 || buffer == null || buffer.length < start) {
throw new SimpleException("Length " + length + " must be at least 2");
}
if (buffer[start] == 0) {
return 0L;
}
/* Skip leading spaces */
while (start < end) {
if (buffer[start] == ' ') {
start++;
} else {
break;
}
}
/*
* Trim all trailing NULs and spaces. The ustar and POSIX tar specs require a
* trailing NUL or space but some implementations use the extra digit for big
* sizes/uids/gids ...
*/
byte trailer = buffer[end - 1];
while (start < end && (trailer == 0 || trailer == ' ')) {
end--;
trailer = buffer[end - 1];
}
for (; start < end; start++) {
final byte currentByte = buffer[start];
/* CheckStyle:MagicNumber OFF */
if (currentByte < '0' || currentByte > '7') {
String string = new String(buffer, offset, length);
string = string.replaceAll("\0", "{NUL}"); /* Replace NULs to allow string to be printed */
String msg = "Invalid byte " + currentByte + " at offset " + (start - offset) + " in '" + string
+ "' len=" + length;
throw new SimpleException(msg);
}
result = (result << 3) + (currentByte - '0'); /* convert from ASCII */
/* CheckStyle:MagicNumber ON */
}
return result;
}
/**
* Compute the value contained in a byte buffer. If the most significant bit of
* the first byte in the buffer is set, this bit is ignored and the rest of the
* buffer is interpreted as a binary number. Otherwise, the buffer is
* interpreted as an octal number as per the parseOctal function above.
*
* @param buffer The buffer from which to parse.
* @param offset The offset into the buffer from which to parse.
* @param length The maximum number of bytes to parse.
* @return The long value of the octal or binary string.
* @throws SimpleException if the trailing space/NUL is missing or an invalid
* byte is detected in an octal number, or if a binary
* number would exceed the size of a signed long 64-bit
* integer.
* @since 1.4
*/
public static long parseOctalOrBinary(byte[] buffer, int offset, int length) {
if (buffer == null || buffer.length < offset) {
return -1;
}
if ((buffer[offset] & 0x80) == 0) {
return parseOctal(buffer, offset, length);
}
final boolean negative = buffer[offset] == (byte) 0xff;
if (length < 9) {
return parseBinaryLong(buffer, offset, length, negative);
}
return parseBinaryBigInteger(buffer, offset, length, negative);
}
private static long parseBinaryLong(byte[] buffer, int offset, int length, boolean negative) {
if (length >= 9) {
throw new SimpleException("At offset " + offset + ", " + length + " byte binary number"
+ " exceeds maximum signed long" + " value");
}
long val = 0;
for (int i = 1; i < length; i++) {
val = (val << 8) + (buffer[offset + i] & 0xff);
}
if (negative) {
/* 2's complement */
val--;
val ^= (long) Math.pow(2.0, (length - 1) * 8.0) - 1;
}
return negative ? -val : val;
}
private static long parseBinaryBigInteger(byte[] buffer, int offset, int length, boolean negative) {
if(buffer == null || offset>buffer.length) {
return -1;
}
final byte[] remainder = new byte[length - 1];
System.arraycopy(buffer, offset + 1, remainder, 0, length - 1);
BigInteger val = new BigInteger(remainder);
if (negative) {
/* 2's complement */
val = val.add(BigInteger.valueOf(-1)).not();
}
if (val.bitLength() > 63) {
throw new SimpleException("At offset " + offset + ", " + length + " byte binary number"
+ " exceeds maximum signed long" + " value");
}
return negative ? -val.longValue() : val.longValue();
}
/**
* Parse a boolean byte from a buffer. Leading spaces and NUL are ignored. The
* buffer may contain trailing spaces or NULs.
*
* @param buffer The buffer from which to parse.
* @param offset The offset into the buffer from which to parse.
* @return The boolean value of the bytes.
*/
public static boolean parseBoolean(byte[] buffer, int offset) {
if(buffer != null && offset<buffer.length) {
return buffer[offset] == 1;
}
return false;
}
/**
* Parse an entry name from a buffer. Parsing stops when a NUL is found or the
* buffer length is reached.
*
* @param buffer The buffer from which to parse.
* @param offset The offset into the buffer from which to parse.
* @param length The maximum number of bytes to parse.
* @return The entry name.
*/
public static String parseName(byte[] buffer, int offset, int length) {
String result = parseName(buffer, offset, length, DEFAULT_ENCODING);
if (result != null) {
return result;
}
return parseName(buffer, offset, length, FALLBACK_ENCODING);
}
/**
* Parse an entry name from a buffer. Parsing stops when a NUL is found or the
* buffer length is reached.
*
* @param buffer The buffer from which to parse.
* @param offset The offset into the buffer from which to parse.
* @param length The maximum number of bytes to parse.
* @param encoding name of the encoding to use for file names
* @return The entry name.
*/
public static String parseName(byte[] buffer, int offset, int length, NioZipEncoding encoding) {
if (buffer == null) {
return null;
}
int len = 0;
for (int i = offset; len < length && i<buffer.length && buffer[i] != 0; i++) {
len++;
}
if (len > 0) {
final byte[] b = new byte[len];
System.arraycopy(buffer, offset, b, 0, len);
return encoding.decode(b);
}
return "";
}
/**
* Copy a name into a buffer. Copies characters from the name into the buffer
* starting at the specified offset. If the buffer is longer than the name, the
* buffer is filled with trailing NULs. If the name is longer than the buffer,
* the output is truncated.
*
* @param name The header name from which to copy the characters.
* @param buf The buffer where the name is to be stored.
* @param offset The starting offset into the buffer
* @param length The maximum number of header bytes to copy.
* @return The updated offset, i.e. offset + length
*/
public static int formatNameBytes(String name, byte[] buf, int offset, int length) {
return formatNameBytes(name, buf, offset, length, DEFAULT_ENCODING);
}
/**
* Copy a name into a buffer. Copies characters from the name into the buffer
* starting at the specified offset. If the buffer is longer than the name, the
* buffer is filled with trailing NULs. If the name is longer than the buffer,
* the output is truncated.
*
* @param name The header name from which to copy the characters.
* @param buf The buffer where the name is to be stored.
* @param offset The starting offset into the buffer
* @param length The maximum number of header bytes to copy.
* @param encoding name of the encoding to use for file names
* @return The updated offset, i.e. offset + length
*/
public static int formatNameBytes(String name, byte[] buf, int offset, int length, NioZipEncoding encoding) {
if (buf == null || name == null) {
return -1;
}
int len = name.length();
ByteBuffer b = encoding.encode(name);
while (b.limit() > length && len > 0) {
b = encoding.encode(name.substring(0, --len));
}
final int limit = b.limit() - b.position();
if(buf.length<limit) {
return -1;
}
System.arraycopy(b.array(), b.arrayOffset(), buf, offset, limit);
/* Pad any remaining output bytes with NUL */
for (int i = limit; i < length; ++i) {
buf[offset + i] = 0;
}
return offset + length;
}
/**
* Fill buffer with unsigned octal number, padded with leading zeroes.
*
* @param value number to convert to octal - treated as unsigned
* @param buffer destination buffer
* @param offset starting offset in buffer
* @param length length of buffer to fill
* @return If Parameter valid
*/
public static boolean formatUnsignedOctalString(long value, byte[] buffer, int offset, int length) {
int remaining = length;
remaining--;
if (buffer == null || buffer.length < offset) {
return false;
}
if (value == 0) {
if(buffer.length>=offset + remaining) {
buffer[offset + remaining--] = (byte) '0';
}else {
return false;
}
} else {
long val = value;
for (; remaining >= 0 && val != 0; --remaining) {
/* CheckStyle:MagicNumber OFF */
buffer[offset + remaining] = (byte) ((byte) '0' + (byte) (val & 7));
val = val >>> 3;
/* CheckStyle:MagicNumber ON */
}
if (val != 0) {
return false;
}
}
for (; remaining >= 0; --remaining) { /* leading zeros */
buffer[offset + remaining] = (byte) '0';
}
return true;
}
/**
* Write an octal integer into a buffer.
*
* Uses {@link #formatUnsignedOctalString} to format the value as an octal
* string with leading zeros. The converted number is followed by space and NUL
*
* @param value The value to write
* @param buf The buffer to receive the output
* @param offset The starting offset into the buffer
* @param length The size of the output buffer
* @return The updated offset, i.e offset+length
*/
public static int formatOctalBytes(long value, byte[] buf, int offset, int length) {
int idx = length - 2; /* For space and trailing null */
if(buf == null || offset+idx>buf.length) {
return -1;
}
formatUnsignedOctalString(value, buf, offset, idx);
buf[offset + idx++] = (byte) ' '; /* Trailing space */
buf[offset + idx] = 0; /* Trailing null */
return offset + length;
}
/**
* Write an octal long integer into a buffer.
*
* Uses {@link #formatUnsignedOctalString} to format the value as an octal
* string with leading zeros. The converted number is followed by a space.
*
* @param value The value to write as octal
* @param buf The destinationbuffer.
* @param offset The starting offset into the buffer.
* @param length The length of the buffer
* @return The updated offset
*/
public static int formatLongOctalBytes(long value, byte[] buf, int offset, int length) {
if (buf == null || buf.length < offset) {
return -1;
}
final int idx = length - 1; /* For space */
if(offset+idx>buf.length) {
return -1;
}
formatUnsignedOctalString(value, buf, offset, idx);
buf[offset + idx] = (byte) ' '; /* Trailing space */
return offset + length;
}
/**
* Write an long integer into a buffer as an octal string if this will fit, or
* as a binary number otherwise.
*
* Uses {@link #formatUnsignedOctalString} to format the value as an octal
* string with leading zeros. The converted number is followed by a space.
*
* @param value The value to write into the buffer.
* @param buf The destination buffer.
* @param offset The starting offset into the buffer.
* @param length The length of the buffer.
* @return The updated offset.
*/
public static int formatLongOctalOrBinaryBytes(long value, byte[] buf, int offset, int length) {
if(offset<0) {
return -1;
}
/* Check whether we are dealing with UID/GID or SIZE field */
final long maxAsOctalChar = length == UIDLEN ? MAXID : MAXSIZE;
final boolean negative = value < 0;
if (negative == false && value <= maxAsOctalChar) { /* OK to store as octal chars */
return formatLongOctalBytes(value, buf, offset, length);
}
if (length < 9) {
formatLongBinary(value, buf, offset, length, negative);
} else {
formatBigIntegerBinary(value, buf, offset, length, negative);
}
buf[offset] = (byte) (negative ? 0xff : 0x80);
return offset + length;
}
private static boolean formatLongBinary(long value, byte[] buf, int offset, int length, boolean negative) {
if(buf == null || length>buf.length) {
return false;
}
final int bits = (length - 1) * 8;
final long max = 1L << bits;
long val = Math.abs(value); /* Long.MIN_VALUE stays Long.MIN_VALUE */
if (val < 0 || val >= max|| offset<0) {
return false;
}
if (negative) {
val ^= max - 1;
val++;
val |= 0xffL << bits;
}
for (int i = offset + length - 1; i >= offset; i--) {
buf[i] = (byte) val;
val >>= 8;
}
return true;
}
private static boolean formatBigIntegerBinary(long value, byte[] buf, int offset, int length, boolean negative) {
if(buf == null) {
return false;
}
final BigInteger val = BigInteger.valueOf(value);
final byte[] b = val.toByteArray();
final int len = b.length;
if (len > length - 1) {
return false;
}
final int off = offset + length - len;
if(off>buf.length) {
return false;
}
System.arraycopy(b, 0, buf, off, len);
final byte fill = (byte) (negative ? 0xff : 0);
for (int i = offset + 1; i < off; i++) {
buf[i] = fill;
}
return true;
}
/**
* Writes an octal value into a buffer.
*
* Uses {@link #formatUnsignedOctalString} to format the value as an octal
* string with leading zeros. The converted number is followed by NUL and then
* space.
*
* @param value The value to convert
* @param buf The destination buffer
* @param offset The starting offset into the buffer.
* @param length The size of the buffer.
* @return The updated value of offset, i.e. offset+length
*/
public static int formatCheckSumOctalBytes(long value, byte[] buf, int offset, int length) {
if(buf == null) {
return -1;
}
int idx = length - 2; /* for NUL and space */
formatUnsignedOctalString(value, buf, offset, idx);
if(offset + idx>buf.length) {
return -1;
}
buf[offset + idx++] = 0; /* Trailing null */
buf[offset + idx] = (byte) ' '; /* Trailing space */
return offset + length;
}
/**
* Compute the checksum of a tar entry header.
*
* @param buf The tar entry's header buffer.
* @return The computed checksum.
*/
public static long computeCheckSum(byte[] buf) {
long sum = 0;
if(buf == null) {
return sum;
}
for (final byte element : buf) {
sum += BYTE_MASK & element;
}
return sum;
}
/**
* Wikipedia <a href=
* "https://en.wikipedia.org/wiki/Tar_(file_format)#File_header">says</a>:
* <blockquote> The checksum is calculated by taking the sum of the unsigned
* byte values of the header block with the eight checksum bytes taken to be
* ascii spaces (decimal value 32). It is stored as a six digit octal number
* with leading zeroes followed by a NUL and then a space. Various
* implementations do not adhere to this format. For better compatibility,
* ignore leading and trailing whitespace, and get the first six digits. In
* addition, some historic tar implementations treated bytes as signed.
* Implementations typically calculate the checksum both ways, and treat it as
* good if either the signed or unsigned sum matches the included checksum.
* </blockquote>
* <p>
* The return value of this method should be treated as a best-effort heuristic
* rather than an absolute and final truth. The checksum verification logic may
* well evolve over time as more special cases are encountered.
*
* @param header tar header
* @return whether the checksum is reasonably good
* @see <a href=
* "https://issues.apache.org/jira/browse/COMPRESS-191">COMPRESS-191</a>
* @since 1.5
*/
public static boolean verifyCheckSum(byte[] header) {
final long storedSum = parseOctal(header, CHKSUM_OFFSET, CHKSUMLEN);
long unsignedSum = 0;
long signedSum = 0;
for (int i = 0; i < header.length; i++) {
byte b = header[i];
if (CHKSUM_OFFSET <= i && i < CHKSUM_OFFSET + CHKSUMLEN) {
b = ' ';
}
unsignedSum += 0xff & b;
signedSum += b;
}
return storedSum == unsignedSum || storedSum == signedSum;
}
/**
* Check if buffer contents matches Ascii String.
*
* @param expected expected string
* @param buffer the buffer
* @param offset offset to read from
* @param length length of the buffer
* @return if buffer is the same as the expected string
*/
public static boolean matchAsciiBuffer(String expected, byte[] buffer, int offset, int length) {
if(expected == null) {
return false;
}
byte[] buffer1;
try {
buffer1 = expected.getBytes(Charset.forName("US-ASCII"));
} catch (final Exception e) {
return false;
}
return isEqual(buffer1, 0, buffer1.length, buffer, offset, length, false);
}
/**
* Compare byte buffers, optionally ignoring trailing nulls
*
* @param buffer1 first buffer
* @param offset1 first offset
* @param length1 first length
* @param buffer2 second buffer
* @param offset2 second offset
* @param length2 second length
* @param ignoreTrailingNulls whether to ignore trailing nulls
* @return if buffer1 and buffer2 have same contents, having regard to trailing
* nulls
*/
public static boolean isEqual(byte[] buffer1, int offset1, int length1, byte[] buffer2, int offset2, int length2,
boolean ignoreTrailingNulls) {
if(buffer1 == null || buffer2 == null) {
return false;
}
final int minLen = length1 < length2 ? length1 : length2;
if(offset1+minLen>buffer1.length || offset2+minLen>buffer2.length) {
return false;
}
for (int i = 0; i < minLen; i++) {
if (buffer1[offset1 + i] != buffer2[offset2 + i]) {
return false;
}
}
if (length1 == length2) {
return true;
}
if (ignoreTrailingNulls) {
if (length1 > length2) {
for (int i = length2; i < length1; i++) {
if (buffer1[offset1 + i] != 0) {
return false;
}
}
} else {
for (int i = length1; i < length2; i++) {
if (buffer2[offset2 + i] != 0) {
return false;
}
}
}
return true;
}
return false;
}
/**
* Returns true if the first N bytes of an array are all zero
*
* @param a The array to check
* @param size The number of characters to check (not the size of the array)
* @return true if the first N bytes are zero
*/
public static boolean isArrayZero(byte[] a, int size) {
if(a== null || size>a.length) {
return true;
}
for (int i = 0; i < size; i++) {
if (a[i] != 0) {
return false;
}
}
return true;
}
public static ByteBuffer growBufferBy(ByteBuffer buffer, int increment) {
if(buffer == null) {
return ByteBuffer.allocate(0);
}
buffer.limit(buffer.position());
buffer.rewind();
final ByteBuffer on = ByteBuffer.allocate(buffer.capacity() + increment);
on.put(buffer);
return on;
}
/**
* Instantiates a zip encoding. An NIO based character set encoder/decoder will
* be returned. As a special case, if the character set is UTF-8, the nio
* encoder will be configured replace malformed and unmappable characters with
* '?'. This matches existing behavior from the older fallback encoder.
* <p>
* If the requested characer set cannot be found, the platform default will be
* used instead.
* </p>
*
* @param name The name of the zip encoding. Specify {@code null} for the
* platform's default encoding.
* @return A zip encoding for the given encoding name.
*/
public static NioZipEncoding getZipEncoding(String name) {
Charset cs = Charset.defaultCharset();
if (name != null) {
try {
cs = Charset.forName(name);
} catch (Exception e) { /* NOSONAR we use the default encoding instead */
}
}
boolean useReplacement = isUTF8(cs.name());
return new NioZipEncoding(cs, useReplacement);
}
/**
* Returns whether a given encoding is UTF-8. If the given name is null, then
* check the platform's default encoding.
*
* @param charsetName If the given name is null, then check the platform's
* default encoding.
* @return isUTF8
*/
static boolean isUTF8(String charsetName) {
if (charsetName == null) {
/* check platform's default encoding */
charsetName = Charset.defaultCharset().name();
}
if (StandardCharsets.UTF_8.name().equalsIgnoreCase(charsetName)) {
return true;
}
for (final String alias : StandardCharsets.UTF_8.aliases()) {
if (alias.equalsIgnoreCase(charsetName)) {
return true;
}
}
return false;
}
}