2
* Licensed to the Apache Software Foundation (ASF) under one or more
3
* contributor license agreements. See the NOTICE file distributed with
4
* this work for additional information regarding copyright ownership.
5
* The ASF licenses this file to You under the Apache License, Version 2.0
6
* (the "License"); you may not use this file except in compliance with
7
* the License. You may obtain a copy of the License at
9
* http://www.apache.org/licenses/LICENSE-2.0
11
* Unless required by applicable law or agreed to in writing, software
12
* distributed under the License is distributed on an "AS IS" BASIS,
13
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
* See the License for the specific language governing permissions and
15
* limitations under the License.
17
package org.apache.commons.io.filefilter;
20
import java.io.IOException;
21
import java.io.RandomAccessFile;
22
import java.io.Serializable;
23
import java.util.Arrays;
25
import org.apache.commons.io.IOUtils;
29
* File filter for matching files containing a "magic number". A magic number
30
* is a unique series of bytes common to all files of a specific file format.
31
* For instance, all Java class files begin with the bytes
32
* <code>0xCAFEBABE</code>.
36
* File dir = new File(".");
37
* MagicNumberFileFilter javaClassFileFilter =
38
* MagicNumberFileFilter(new byte[] {(byte) 0xCA, (byte) 0xFE,
39
* (byte) 0xBA, (byte) 0xBE});
40
* String[] javaClassFiles = dir.list(javaClassFileFilter);
41
* for (String javaClassFile : javaClassFiles) {
42
* System.out.println(javaClassFile);
47
* Sometimes, such as in the case of TAR files, the
48
* magic number will be offset by a certain number of bytes in the file. In the
49
* case of TAR archive files, this offset is 257 bytes.
53
* File dir = new File(".");
54
* MagicNumberFileFilter tarFileFilter =
55
* MagicNumberFileFilter("ustar", 257);
56
* String[] tarFiles = dir.list(tarFileFilter);
57
* for (String tarFile : tarFiles) {
58
* System.out.println(tarFile);
62
* @see FileFilterUtils#magicNumberFileFilter(byte[])
63
* @see FileFilterUtils#magicNumberFileFilter(String)
64
* @see FileFilterUtils#magicNumberFileFilter(byte[], long)
65
* @see FileFilterUtils#magicNumberFileFilter(String, long)
67
public class MagicNumberFileFilter extends AbstractFileFilter implements
71
* The serialization version unique identifier.
73
private static final long serialVersionUID = -547733176983104172L;
76
* The magic number to compare against the file's bytes at the provided
79
private final byte[] magicNumbers;
82
* The offset (in bytes) within the files that the magic number's bytes
85
private final long byteOffset;
89
* Constructs a new MagicNumberFileFilter and associates it with the magic
90
* number to test for in files. This constructor assumes a starting offset
95
* It is important to note that <em>the array is not cloned</em> and that
96
* any changes to the magic number array after construction will affect the
97
* behavior of this file filter.
101
* MagicNumberFileFilter javaClassFileFilter =
102
* MagicNumberFileFilter(new byte[] {(byte) 0xCA, (byte) 0xFE,
103
* (byte) 0xBA, (byte) 0xBE});
106
* @param magicNumber the magic number to look for in the file.
108
* @throws IllegalArgumentException if <code>magicNumber</code> is
109
* {@code null}, or contains no bytes.
111
public MagicNumberFileFilter(byte[] magicNumber) {
112
this(magicNumber, 0);
117
* Constructs a new MagicNumberFileFilter and associates it with the magic
118
* number to test for in files. This constructor assumes a starting offset
125
* MagicNumberFileFilter xmlFileFilter =
126
* MagicNumberFileFilter("<?xml");
130
* @param magicNumber the magic number to look for in the file.
131
* The string is converted to bytes using the platform default charset.
133
* @throws IllegalArgumentException if <code>magicNumber</code> is
134
* {@code null} or the empty String.
136
public MagicNumberFileFilter(String magicNumber) {
137
this(magicNumber, 0);
142
* Constructs a new MagicNumberFileFilter and associates it with the magic
143
* number to test for in files and the byte offset location in the file to
144
* to look for that magic number.
148
* MagicNumberFileFilter tarFileFilter =
149
* MagicNumberFileFilter("ustar", 257);
152
* @param magicNumber the magic number to look for in the file.
153
* The string is converted to bytes using the platform default charset.
154
* @param offset the byte offset in the file to start comparing bytes.
156
* @throws IllegalArgumentException if <code>magicNumber</code> is
157
* {@code null} or the empty String, or <code>offset</code> is
160
public MagicNumberFileFilter(String magicNumber, long offset) {
161
if (magicNumber == null) {
162
throw new IllegalArgumentException("The magic number cannot be null");
164
if (magicNumber.length() == 0) {
165
throw new IllegalArgumentException("The magic number must contain at least one byte");
168
throw new IllegalArgumentException("The offset cannot be negative");
171
this.magicNumbers = magicNumber.getBytes(); // uses the platform default charset
172
this.byteOffset = offset;
177
* Constructs a new MagicNumberFileFilter and associates it with the magic
178
* number to test for in files and the byte offset location in the file to
179
* to look for that magic number.
183
* It is important to note that <em>the array is not cloned</em> and that
184
* any changes to the magic number array after construction will affect the
185
* behavior of this file filter.
189
* MagicNumberFileFilter tarFileFilter =
190
* MagicNumberFileFilter(new byte[] {0x75, 0x73, 0x74, 0x61, 0x72}, 257);
194
* MagicNumberFileFilter javaClassFileFilter =
195
* MagicNumberFileFilter(new byte[] {0xCA, 0xFE, 0xBA, 0xBE}, 0);
198
* @param magicNumber the magic number to look for in the file.
199
* @param offset the byte offset in the file to start comparing bytes.
201
* @throws IllegalArgumentException if <code>magicNumber</code> is
202
* {@code null}, or contains no bytes, or <code>offset</code>
203
* is a negative number.
205
public MagicNumberFileFilter(byte[] magicNumber, long offset) {
206
if (magicNumber == null) {
207
throw new IllegalArgumentException("The magic number cannot be null");
209
if (magicNumber.length == 0) {
210
throw new IllegalArgumentException("The magic number must contain at least one byte");
213
throw new IllegalArgumentException("The offset cannot be negative");
216
this.magicNumbers = new byte[magicNumber.length];
217
System.arraycopy(magicNumber, 0, this.magicNumbers, 0, magicNumber.length);
218
this.byteOffset = offset;
223
* Accepts the provided file if the file contains the file filter's magic
224
* number at the specified offset.
228
* If any {@link IOException}s occur while reading the file, the file will
232
* @param file the file to accept or reject.
234
* @return {@code true} if the file contains the filter's magic number
235
* at the specified offset, {@code false} otherwise.
238
public boolean accept(File file) {
239
if (file != null && file.isFile() && file.canRead()) {
240
RandomAccessFile randomAccessFile = null;
242
byte[] fileBytes = new byte[this.magicNumbers.length];
243
randomAccessFile = new RandomAccessFile(file, "r");
244
randomAccessFile.seek(byteOffset);
245
int read = randomAccessFile.read(fileBytes);
246
if (read != magicNumbers.length) {
249
return Arrays.equals(this.magicNumbers, fileBytes);
250
} catch (IOException ioe) {
251
// Do nothing, fall through and do not accept file
253
IOUtils.closeQuietly(randomAccessFile);
261
* Returns a String representation of the file filter, which includes the
262
* magic number bytes and byte offset.
264
* @return a String representation of the file filter.
267
public String toString() {
268
StringBuilder builder = new StringBuilder(super.toString());
270
builder.append(new String(magicNumbers));// TODO perhaps use hex if value is not printable
272
builder.append(this.byteOffset);
274
return builder.toString();