19 package org.sleuthkit.autopsy.datasourceprocessors.xry;
21 import java.io.BufferedReader;
22 import java.io.IOException;
23 import java.io.InputStream;
24 import java.nio.charset.Charset;
25 import java.nio.charset.MalformedInputException;
26 import java.nio.charset.StandardCharsets;
27 import java.nio.file.Files;
28 import java.nio.file.LinkOption;
29 import java.nio.file.Path;
30 import java.nio.file.StandardOpenOption;
31 import java.nio.file.attribute.BasicFileAttributes;
32 import java.util.Optional;
33 import java.util.logging.Level;
34 import java.util.NoSuchElementException;
36 import org.apache.commons.io.FilenameUtils;
48 final class XRYFileReader
implements AutoCloseable {
50 private static final Logger logger = Logger.getLogger(XRYFileReader.class.getName());
53 private static final Charset CHARSET = StandardCharsets.UTF_16LE;
56 private static final String START_OF_HEADER =
"xry export";
60 private static final int LINE_WITH_REPORT_TYPE = 3;
63 private static final int HEADER_LENGTH_IN_LINES = 5;
66 private static final String EXTENSION =
"txt";
69 private static final int[] BOM = {0xFF, 0xFE};
72 private final StringBuilder xryEntity;
75 private final BufferedReader reader;
78 private final Path xryFilePath;
94 public XRYFileReader(Path xryFile)
throws IOException {
95 reader = Files.newBufferedReader(xryFile, CHARSET);
96 xryFilePath = xryFile;
99 advanceToHeader(reader);
103 for (
int i = 1; i < HEADER_LENGTH_IN_LINES; i++) {
107 xryEntity =
new StringBuilder();
119 public String getReportType() throws IOException {
120 Optional<String> reportType = getType(xryFilePath);
121 if (reportType.isPresent()) {
122 return reportType.get();
125 throw new IllegalArgumentException(xryFilePath.toString() +
" does not "
126 +
"have a report type.");
135 public Path getReportPath() throws IOException {
147 public boolean hasNextEntity() throws IOException {
149 if (xryEntity.length() > 0) {
154 while ((line = reader.readLine()) != null) {
155 if (marksEndOfEntity(line)) {
156 if (xryEntity.length() > 0) {
161 xryEntity.append(line).append(
'\n');
166 return xryEntity.length() > 0;
178 public String nextEntity() throws IOException {
179 if (hasNextEntity()) {
180 String returnVal = xryEntity.toString();
181 xryEntity.setLength(0);
184 throw new NoSuchElementException();
197 public String peek() throws IOException {
198 if (hasNextEntity()) {
199 return xryEntity.toString();
201 throw new NoSuchElementException();
211 public void close() throws IOException {
222 private boolean marksEndOfEntity(String line) {
223 return line.isEmpty();
244 public static boolean isXRYFile(Path file)
throws IOException {
245 String parsedExtension = FilenameUtils.getExtension(file.toString());
248 if (!EXTENSION.equals(parsedExtension)) {
252 BasicFileAttributes attr = Files.readAttributes(file,
253 BasicFileAttributes.class, LinkOption.NOFOLLOW_LINKS);
256 if (attr.isSymbolicLink() || attr.isDirectory()) {
261 if (!isXRYBOM(file)) {
266 Optional<String> reportType = getType(file);
268 return reportType.isPresent();
269 }
catch (MalformedInputException ex) {
270 logger.log(Level.WARNING, String.format(
"File at path [%s] had "
271 +
"0xFFFE BOM but was not encoded in UTF-16LE.", file.toString()), ex);
286 private static boolean isXRYBOM(Path file)
throws IOException {
287 try (InputStream in = Files.newInputStream(file, StandardOpenOption.READ)) {
288 for (
int bomByte : BOM) {
289 if (in.read() != bomByte) {
307 private static Optional<String> getType(Path file)
throws IOException {
308 try (BufferedReader reader = Files.newBufferedReader(file, CHARSET)) {
310 advanceToHeader(reader);
313 for (
int i = 1; i < LINE_WITH_REPORT_TYPE - 1; i++) {
317 String reportTypeLine = reader.readLine();
318 if (reportTypeLine != null && !reportTypeLine.isEmpty()) {
319 return Optional.of(reportTypeLine);
321 return Optional.empty();
336 private static void advanceToHeader(BufferedReader reader)
throws IOException {
338 if((line = reader.readLine()) == null) {
342 String normalizedLine = line.trim().toLowerCase();
343 if (normalizedLine.equals(START_OF_HEADER)) {
352 byte[] normalizedBytes = normalizedLine.getBytes(CHARSET);
353 if (normalizedBytes.length > 2) {
354 normalizedLine =
new String(normalizedBytes, 2,
355 normalizedBytes.length - 2, CHARSET);
356 if (normalizedLine.equals(START_OF_HEADER)) {
364 while ((line = reader.readLine()) != null) {
365 normalizedLine = line.trim().toLowerCase();
366 if (normalizedLine.equals(START_OF_HEADER)) {