Skip to content

Commit d8cc4c9

Browse files
committed
IO-210 Create MagicNumberFileFilter
Applied patch, with following fixes: - remove additional null checks in FileFilterUtils static methods; leave the checks to the ctors - close RandomAcessFile in MagicNumberFileFilter.accept() - close streams in test cases - Javadoc fix (wrap < in {@code}) git-svn-id: https://svn.apache.org/repos/asf/commons/proper/io/trunk@919682 13f79535-47bb-0310-9956-ffa450edef68
1 parent 8ae4137 commit d8cc4c9

3 files changed

Lines changed: 470 additions & 1 deletion

File tree

src/java/org/apache/commons/io/filefilter/FileFilterUtils.java

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -279,6 +279,78 @@ public static IOFileFilter sizeRangeFileFilter(long minSizeInclusive, long maxSi
279279
IOFileFilter maximumFilter = new SizeFileFilter(maxSizeInclusive + 1L, false);
280280
return new AndFileFilter(minimumFilter, maximumFilter);
281281
}
282+
283+
/**
284+
* Returns a filter that accepts files that begin with the provided magic
285+
* number.
286+
*
287+
* @param magicNumber the magic number (byte sequence) to match at the
288+
* beginning of each file.
289+
*
290+
* @return an IOFileFilter that accepts files beginning with the provided
291+
* magic number.
292+
*
293+
* @throws IllegalArgumentException if <code>magicNumber</code> is
294+
* <code>null</code> or the empty String.
295+
*/
296+
public static IOFileFilter magicNumberFileFilter(String magicNumber) {
297+
return new MagicNumberFileFilter(magicNumber);
298+
}
299+
300+
/**
301+
* Returns a filter that accepts files that contains the provided magic
302+
* number at a specified offset within the file.
303+
*
304+
* @param magicNumber the magic number (byte sequence) to match at the
305+
* provided offset in each file.
306+
* @param offset the offset within the files to look for the magic number.
307+
*
308+
* @return an IOFileFilter that accepts files containing the magic number
309+
* at the specified offset.
310+
*
311+
* @throws IllegalArgumentException if <code>magicNumber</code> is
312+
* <code>null</code> or the empty String, or if offset is a
313+
* negative number.
314+
*/
315+
public static IOFileFilter magicNumberFileFilter(String magicNumber, long offset) {
316+
return new MagicNumberFileFilter(magicNumber, offset);
317+
}
318+
319+
/**
320+
* Returns a filter that accepts files that begin with the provided magic
321+
* number.
322+
*
323+
* @param magicNumber the magic number (byte sequence) to match at the
324+
* beginning of each file.
325+
*
326+
* @return an IOFileFilter that accepts files beginning with the provided
327+
* magic number.
328+
*
329+
* @throws IllegalArgumentException if <code>magicNumber</code> is
330+
* <code>null</code> or is of length zero.
331+
*/
332+
public static IOFileFilter magicNumberFileFilter(byte[] magicNumber) {
333+
return new MagicNumberFileFilter(magicNumber);
334+
}
335+
336+
/**
337+
* Returns a filter that accepts files that contains the provided magic
338+
* number at a specified offset within the file.
339+
*
340+
* @param magicNumber the magic number (byte sequence) to match at the
341+
* provided offset in each file.
342+
* @param offset the offset within the files to look for the magic number.
343+
*
344+
* @return an IOFileFilter that accepts files containing the magic number
345+
* at the specified offset.
346+
*
347+
* @throws IllegalArgumentException if <code>magicNumber</code> is
348+
* <code>null</code>, or contains no bytes, or <code>offset</code>
349+
* is a negative number.
350+
*/
351+
public static IOFileFilter magicNumberFileFilter(byte[] magicNumber, long offset) {
352+
return new MagicNumberFileFilter(magicNumber, offset);
353+
}
282354

283355
//-----------------------------------------------------------------------
284356
/* Constructed on demand and then cached */
Lines changed: 268 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,268 @@
1+
/*
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
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
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.
16+
*/
17+
package org.apache.commons.io.filefilter;
18+
19+
import java.io.File;
20+
import java.io.IOException;
21+
import java.io.RandomAccessFile;
22+
import java.io.Serializable;
23+
import java.util.Arrays;
24+
25+
import org.apache.commons.io.IOUtils;
26+
27+
/**
28+
* <p>
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>.
33+
* </p>
34+
*
35+
* <code><pre>
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);
43+
* }
44+
* </pre></code>
45+
*
46+
* <p>
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.
50+
* </p>
51+
*
52+
* <code><pre>
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);
59+
* }
60+
* </pre></code>
61+
*
62+
*/
63+
public class MagicNumberFileFilter extends AbstractFileFilter implements
64+
Serializable {
65+
66+
/**
67+
* The serialization version unique identifier.
68+
*/
69+
private static final long serialVersionUID = -547733176983104172L;
70+
71+
/**
72+
* The magic number to compare against the file's bytes at the provided
73+
* offset.
74+
*/
75+
private final byte[] magicNumbers;
76+
77+
/**
78+
* The offset (in bytes) within the files that the magic number's bytes
79+
* should appear.
80+
*/
81+
private final long byteOffset;
82+
83+
/**
84+
* <p>
85+
* Constructs a new MagicNumberFileFilter and associates it with the magic
86+
* number to test for in files. This constructor assumes a starting offset
87+
* of <code>0</code>.
88+
* </p>
89+
*
90+
* <p>
91+
* It is important to note that <em>the array is not cloned</em> and that
92+
* any changes to the magic number array after construction will affect the
93+
* behavior of this file filter.
94+
* </p>
95+
*
96+
* <code><pre>
97+
* MagicNumberFileFilter javaClassFileFilter =
98+
* MagicNumberFileFilter(new byte[] {(byte) 0xCA, (byte) 0xFE,
99+
* (byte) 0xBA, (byte) 0xBE});
100+
* </pre></code>
101+
*
102+
* @param magicNumber the magic number to look for in the file.
103+
*
104+
* @throws IllegalArgumentException if <code>magicNumber</code> is
105+
* <code>null</code>, or contains no bytes.
106+
*/
107+
public MagicNumberFileFilter(byte[] magicNumber) {
108+
this(magicNumber, 0);
109+
}
110+
111+
/**
112+
* <p>
113+
* Constructs a new MagicNumberFileFilter and associates it with the magic
114+
* number to test for in files. This constructor assumes a starting offset
115+
* of <code>0</code>.
116+
* </p>
117+
*
118+
* Example usage:
119+
* <pre>
120+
* {@code
121+
* MagicNumberFileFilter xmlFileFilter =
122+
* MagicNumberFileFilter("<?xml");
123+
* }
124+
* </pre>
125+
*
126+
* @param magicNumber the magic number to look for in the file.
127+
* The string is converted to bytes using the platform default charset.
128+
*
129+
* @throws IllegalArgumentException if <code>magicNumber</code> is
130+
* <code>null</code> or the empty String.
131+
*/
132+
public MagicNumberFileFilter(String magicNumber) {
133+
this(magicNumber, 0);
134+
}
135+
136+
/**
137+
* <p>
138+
* Constructs a new MagicNumberFileFilter and associates it with the magic
139+
* number to test for in files and the byte offset location in the file to
140+
* to look for that magic number.
141+
* </p>
142+
*
143+
* <code><pre>
144+
* MagicNumberFileFilter tarFileFilter =
145+
* MagicNumberFileFilter("ustar", 257);
146+
* </pre></code>
147+
*
148+
* @param magicNumber the magic number to look for in the file.
149+
* The string is converted to bytes using the platform default charset.
150+
* @param offset the byte offset in the file to start comparing bytes.
151+
*
152+
* @throws IllegalArgumentException if <code>magicNumber</code> is
153+
* <code>null</code> or the empty String, or <code>offset</code> is
154+
* a negative number.
155+
*/
156+
public MagicNumberFileFilter(String magicNumber, long offset) {
157+
if (magicNumber == null) {
158+
throw new IllegalArgumentException("The magic number cannot be null");
159+
}
160+
if (magicNumber.length() == 0) {
161+
throw new IllegalArgumentException("The magic number must contain at least one byte");
162+
}
163+
if (offset < 0) {
164+
throw new IllegalArgumentException("The offset cannot be negative");
165+
}
166+
167+
this.magicNumbers = magicNumber.getBytes(); // uses the platform default charset
168+
this.byteOffset = offset;
169+
}
170+
171+
/**
172+
* <p>
173+
* Constructs a new MagicNumberFileFilter and associates it with the magic
174+
* number to test for in files and the byte offset location in the file to
175+
* to look for that magic number.
176+
* </p>
177+
*
178+
* <p>
179+
* It is important to note that <em>the array is not cloned</em> and that
180+
* any changes to the magic number array after construction will affect the
181+
* behavior of this file filter.
182+
* </p>
183+
*
184+
* <code><pre>
185+
* MagicNumberFileFilter tarFileFilter =
186+
* MagicNumberFileFilter(new byte[] {0x75, 0x73, 0x74, 0x61, 0x72}, 257);
187+
* </pre></code>
188+
*
189+
* <code><pre>
190+
* MagicNumberFileFilter javaClassFileFilter =
191+
* MagicNumberFileFilter(new byte[] {0xCA, 0xFE, 0xBA, 0xBE}, 0);
192+
* </pre></code>
193+
*
194+
* @param magicNumber the magic number to look for in the file.
195+
* @param offset the byte offset in the file to start comparing bytes.
196+
*
197+
* @throws IllegalArgumentException if <code>magicNumber</code> is
198+
* <code>null</code>, or contains no bytes, or <code>offset</code>
199+
* is a negative number.
200+
*/
201+
public MagicNumberFileFilter(byte[] magicNumber, long offset) {
202+
if (magicNumber == null) {
203+
throw new IllegalArgumentException("The magic number cannot be null");
204+
}
205+
if (magicNumber.length == 0) {
206+
throw new IllegalArgumentException("The magic number must contain at least one byte");
207+
}
208+
if (offset < 0) {
209+
throw new IllegalArgumentException("The offset cannot be negative");
210+
}
211+
212+
this.magicNumbers = magicNumber;
213+
this.byteOffset = offset;
214+
}
215+
216+
/**
217+
* <p>
218+
* Accepts the provided file if the file contains the file filter's magic
219+
* number at the specified offset.
220+
* </p>
221+
*
222+
* <p>
223+
* If any {@link IOException}s occur while reading the file, the file will
224+
* be rejected.
225+
* </p>
226+
*
227+
* @param file the file to accept or reject.
228+
*
229+
* @return <code>true</code> if the file contains the filter's magic number
230+
* at the specified offset, <code>false</code> otherwise.
231+
*/
232+
@Override
233+
public boolean accept(File file) {
234+
if (file != null && file.isFile() && file.canRead()) {
235+
RandomAccessFile randomAccessFile = null;
236+
try {
237+
byte[] fileBytes = new byte[this.magicNumbers.length];
238+
randomAccessFile = new RandomAccessFile(file, "r");
239+
randomAccessFile.seek(byteOffset);
240+
randomAccessFile.read(fileBytes);
241+
return Arrays.equals(this.magicNumbers, fileBytes);
242+
} catch (IOException ioe) {
243+
// Do nothing, fall through and do not accept file
244+
} finally {
245+
IOUtils.closeQuietly(randomAccessFile);
246+
}
247+
}
248+
249+
return false;
250+
}
251+
252+
/**
253+
* Returns a String representation of the file filter, which includes the
254+
* magic number bytes and byte offset.
255+
*
256+
* @return a String representation of the file filter.
257+
*/
258+
@Override
259+
public String toString() {
260+
StringBuilder builder = new StringBuilder(super.toString());
261+
builder.append("(");
262+
builder.append(new String(magicNumbers));// TODO perhaps use hex if value is not printable
263+
builder.append(",");
264+
builder.append(this.byteOffset);
265+
builder.append(")");
266+
return builder.toString();
267+
}
268+
}

0 commit comments

Comments
 (0)