com.j256.simplemagic.ContentInfoUtil Maven / Gradle / Ivy
package com.j256.simplemagic;
import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.Closeable;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.util.Arrays;
import java.util.zip.GZIPInputStream;
import com.j256.simplemagic.entries.MagicEntries;
/**
*
* Class which reads in the magic files and determines the {@link ContentInfo} for files and byte arrays. You use the
* default constructor {@link #ContentInfoUtil()} to use the internal rules file or load in a local file from the
* file-system using {@link #ContentInfoUtil(String)}. Once the rules are loaded, you can use {@link #findMatch(String)}
* or other {@code findMatch(...)} methods to get the content-type of a file or bytes.
*
*
*
* // create a magic utility using the internal magic file
* ContentInfoUtil util = new ContentInfoUtil();
* // get the content info for this file-path or null if no match
* ContentInfo info = util.findMatch("/tmp/upload.tmp");
* // display content type information
* if (info == null) {
* System.out.println("Unknown content-type");
* } else {
* // other information in ContentInfo type
* System.out.println("Content-type is: " + info.getName());
* }
*
*
* @author graywatson
*/
public class ContentInfoUtil {
private final static String INTERNAL_MAGIC_FILE = "/magic.gz";
/**
* Number of bytes that the utility class by default reads to determine the content type information.
*/
public final static int DEFAULT_READ_SIZE = 10 * 1024;
/** internal entries loaded once if the {@link ContentInfoUtil#MagicUtil()} constructor is used. */
private static MagicEntries internalMagicEntries;
private final MagicEntries magicEntries;
private int fileReadSize = DEFAULT_READ_SIZE;
/**
* Construct a magic utility using the internal magic file built into the package.
*
* @throws IllegalStateException
* If there was a problem reading the magic entries from the internal magic file.
*/
public ContentInfoUtil() {
this((ErrorCallBack) null);
}
/**
* Construct a magic utility using the internal magic file built into the package. This also allows the caller to
* log any errors discovered in the file(s).
*
* @param errorCallBack
* Call back which shows any problems with the magic entries loaded.
* @throws IllegalStateException
* If there was a problem reading the magic entries from the internal magic file.
*/
public ContentInfoUtil(ErrorCallBack errorCallBack) {
if (internalMagicEntries == null) {
try {
internalMagicEntries = readEntriesFromResource(INTERNAL_MAGIC_FILE, errorCallBack);
} catch (IOException e) {
throw new IllegalStateException(
"Could not load entries from internal magic file: " + INTERNAL_MAGIC_FILE, e);
}
if (internalMagicEntries == null) {
throw new IllegalStateException("Internal magic file not found in class-path: " + INTERNAL_MAGIC_FILE);
}
}
this.magicEntries = internalMagicEntries;
}
/**
* Construct a magic utility using the magic files from a file or a directory of files.
*
* @param fileOrDirectoryPath
* A path which can be a magic file, or a directory of magic files, or a magic file in a resource path.
* @throws IOException
* If there was a problem reading the magic entries from the internal magic file.
*/
public ContentInfoUtil(String fileOrDirectoryPath) throws IOException {
this(new File(fileOrDirectoryPath), null);
}
/**
* Construct a magic utility using the magic files from a file or a directory of files. This also allows the caller
* to log any errors discovered in the file(s).
*
* @param fileOrDirectoryOrResourcePath
* A path which can be a magic file, or a directory of magic files, or a magic file in a resource path.
* @param errorCallBack
* Call back which shows any problems with the magic entries loaded.
* @throws IOException
* If there was a problem reading the magic entries from the internal magic file.
*/
public ContentInfoUtil(String fileOrDirectoryOrResourcePath, ErrorCallBack errorCallBack) throws IOException {
MagicEntries magicEntries = readEntriesFromResource(fileOrDirectoryOrResourcePath, errorCallBack);
if (magicEntries == null) {
File file = new File(fileOrDirectoryOrResourcePath);
magicEntries = readEntriesFromFile(file, errorCallBack);
}
if (magicEntries == null) {
throw new IllegalArgumentException(
"Magic path specified is not a file, directory, or resource: " + fileOrDirectoryOrResourcePath);
}
this.magicEntries = magicEntries;
}
/**
* Construct a magic utility using the magic files from a file or a directory of files.
*
* @param fileOrDirectory
* A path which can be a magic file, or a directory of magic files.
* @throws IOException
* If there was a problem reading the magic entries from the internal magic file.
*/
public ContentInfoUtil(File fileOrDirectory) throws IOException {
this(fileOrDirectory, null);
}
/**
* Construct a magic utility using the magic files from a file or a directory of files. This also allows the caller
* to log any errors discovered in the file(s).
*
* @param fileOrDirectory
* A path which can be a magic file, or a directory of magic files.
* @param errorCallBack
* Call back which shows any problems with the magic entries loaded.
* @throws IOException
* If there was a problem reading the magic entries from the internal magic file.
*/
public ContentInfoUtil(File fileOrDirectory, ErrorCallBack errorCallBack) throws IOException {
this.magicEntries = readEntriesFromFile(fileOrDirectory, errorCallBack);
if (this.magicEntries == null) {
throw new IllegalArgumentException(
"Magic path specified is not a file, directory, or resource: " + fileOrDirectory);
}
}
/**
* Construct a magic utility using the magic file entries from a reader.
*
* @param reader
* A reader from which we will read the magic file entries.
* @throws IOException
* If there was a problem reading the magic entries from the reader.
*/
public ContentInfoUtil(Reader reader) throws IOException {
this(reader, null);
}
/**
* Construct a magic utility using the magic file entries from a reader.
*
* @param reader
* A reader from which we will read the magic file entries.
* @param errorCallBack
* Call back which shows any problems with the magic entries loaded.
* @throws IOException
* If there was a problem reading the magic entries from the reader.
*/
public ContentInfoUtil(Reader reader, ErrorCallBack errorCallBack) throws IOException {
this.magicEntries = readEntries(reader, errorCallBack);
}
/**
* Return the content type for the file-path or null if none of the magic entries matched.
*
* @throws IOException
* If there was a problem reading from the file.
*/
public ContentInfo findMatch(String filePath) throws IOException {
return findMatch(new File(filePath));
}
/**
* Return the content type for the file or null if none of the magic entries matched.
*
* @throws IOException
* If there was a problem reading from the file.
*/
public ContentInfo findMatch(File file) throws IOException {
int readSize = fileReadSize;
if (file.length() < readSize) {
readSize = (int) file.length();
}
if (readSize == 0) {
return ContentInfo.EMPTY_INFO;
}
byte[] bytes = new byte[readSize];
FileInputStream fis = null;
try {
fis = new FileInputStream(file);
fis.read(bytes);
} finally {
closeQuietly(fis);
}
return findMatch(bytes);
}
/**
* Return the content type for the input-stream or null if none of the magic entries matched. You might want to use
* the {@link ContentInfoInputStreamWrapper} class to delegate to an input-stream and determine content information
* at the same time.
*
*
* NOTE: The caller is responsible for closing the input-stream.
*
*
* @throws IOException
* If there was a problem reading from the input-stream.
* @see ContentInfoInputStreamWrapper
*/
public ContentInfo findMatch(InputStream inputStream) throws IOException {
byte[] bytes = new byte[fileReadSize];
int numRead = inputStream.read(bytes);
if (numRead < 0) {
return null;
}
if (numRead < bytes.length) {
// move the bytes into a smaller array
bytes = Arrays.copyOf(bytes, numRead);
}
return findMatch(bytes);
}
/**
* Return the content type from the associated bytes or null if none of the magic entries matched.
*/
public ContentInfo findMatch(byte[] bytes) {
if (bytes.length == 0) {
return ContentInfo.EMPTY_INFO;
} else {
return magicEntries.findMatch(bytes);
}
}
/**
* Return the content type if the extension from the file-name matches our internal list. This can either be just
* the extension part or it will look for the last period and take the string after that as the extension.
*
* @return The matching content-info or null if no matches.
*/
public static ContentInfo findExtensionMatch(String name) {
name = name.toLowerCase();
// look up the whole name first
ContentType type = ContentType.fromFileExtension(name);
if (type != ContentType.OTHER) {
return new ContentInfo(type);
}
// now find the .ext part, if any
int index = name.lastIndexOf('.');
if (index < 0 || index == name.length() - 1) {
return null;
}
type = ContentType.fromFileExtension(name.substring(index + 1));
if (type == ContentType.OTHER) {
return null;
} else {
return new ContentInfo(type);
}
}
/**
* Return the content type if the mime-type matches our internal list.
*
* @return The matching content-info or null if no matches.
*/
public static ContentInfo findMimeTypeMatch(String mimeType) {
ContentType type = ContentType.fromMimeType(mimeType.toLowerCase());
if (type == ContentType.OTHER) {
return null;
} else {
return new ContentInfo(type);
}
}
/**
* Set the default size that will be read if we are getting the content from a file.
*
* @see #DEFAULT_READ_SIZE
*/
public void setFileReadSize(int fileReadSize) {
this.fileReadSize = fileReadSize;
}
/**
* @deprecated Not used since it is only passed into the constructor.
*/
@Deprecated
public void setErrorCallBack(ErrorCallBack errorCallBack) {
// no op
}
private MagicEntries readEntriesFromFile(File fileOrDirectory, ErrorCallBack errorCallBack)
throws FileNotFoundException, IOException {
if (fileOrDirectory.isFile()) {
FileReader reader = new FileReader(fileOrDirectory);
try {
return readEntries(reader, errorCallBack);
} finally {
closeQuietly(reader);
}
} else if (fileOrDirectory.isDirectory()) {
MagicEntries entries = new MagicEntries();
for (File subFile : fileOrDirectory.listFiles()) {
FileReader fr = new FileReader(subFile);
try {
readEntries(entries, fr, errorCallBack);
} catch (IOException e) {
// ignore the file
} finally {
closeQuietly(fr);
}
}
entries.optimizeFirstBytes();
return entries;
} else {
return null;
}
}
private MagicEntries readEntriesFromResource(String resource, ErrorCallBack errorCallBack) throws IOException {
InputStream stream = getClass().getResourceAsStream(resource);
if (stream == null) {
return null;
}
Reader reader = null;
try {
// this suffix test is here for testing purposes so we can generate a simple magic file
if (resource.endsWith(".gz")) {
reader = new InputStreamReader(new GZIPInputStream(new BufferedInputStream(stream)));
} else {
reader = new InputStreamReader(new BufferedInputStream(stream));
}
stream = null;
return readEntries(reader, errorCallBack);
} finally {
closeQuietly(reader);
closeQuietly(stream);
}
}
private MagicEntries readEntries(Reader reader, ErrorCallBack errorCallBack) throws IOException {
MagicEntries entries = new MagicEntries();
readEntries(entries, reader, errorCallBack);
entries.optimizeFirstBytes();
return entries;
}
private void readEntries(MagicEntries entries, Reader reader, ErrorCallBack errorCallBack) throws IOException {
BufferedReader lineReader = new BufferedReader(reader);
try {
entries.readEntries(lineReader, errorCallBack);
} finally {
closeQuietly(lineReader);
}
}
private void closeQuietly(Closeable closeable) {
if (closeable != null) {
try {
closeable.close();
} catch (IOException e) {
// ignored
}
}
}
/**
* Optional call-back which will be made whenever we discover an error while parsing the magic configuration files.
* There are usually tons of badly formed lines and other errors.
*/
public interface ErrorCallBack {
/**
* An error was generated while processing the line.
*
* @param line
* Line where the error happened.
* @param details
* Specific information about the error.
* @param e
* Exception that was thrown trying to parse the line or null if none.
*/
public void error(String line, String details, Exception e);
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy