Autopsy  4.19.3
Graphical digital forensics platform for The Sleuth Kit and other tools.
XRYFileReader.java
Go to the documentation of this file.
1 /*
2  * Autopsy Forensic Browser
3  *
4  * Copyright 2019 Basis Technology Corp.
5  * Contact: carrier <at> sleuthkit <dot> org
6  *
7  * Licensed under the Apache License, Version 2.0 (the "License");
8  * you may not use this file except in compliance with the License.
9  * You may obtain a copy of the License at
10  *
11  * http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing, software
14  * distributed under the License is distributed on an "AS IS" BASIS,
15  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16  * See the License for the specific language governing permissions and
17  * limitations under the License.
18  */
19 package org.sleuthkit.autopsy.datasourceprocessors.xry;
20 
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;
37 
48 final class XRYFileReader implements AutoCloseable {
49 
50  private static final Logger logger = Logger.getLogger(XRYFileReader.class.getName());
51 
52  //Assume UTF_16LE
53  private static final Charset CHARSET = StandardCharsets.UTF_16LE;
54 
55  //Assume the header begins with 'xry export'.
56  private static final String START_OF_HEADER = "xry export";
57 
58  //Assume all XRY reports have the type on the 3rd line
59  //relative to the start of the header.
60  private static final int LINE_WITH_REPORT_TYPE = 3;
61 
62  //Assume all headers are 5 lines in length.
63  private static final int HEADER_LENGTH_IN_LINES = 5;
64 
65  //Assume TXT extension
66  private static final String EXTENSION = "txt";
67 
68  //Assume 0xFFFE is the BOM
69  private static final int[] BOM = {0xFF, 0xFE};
70 
71  //Entity to be consumed during file iteration.
72  private final StringBuilder xryEntity;
73 
74  //Underlying reader for the xry file.
75  private final BufferedReader reader;
76 
77  //Reference to the original xry file.
78  private final Path xryFilePath;
79 
94  public XRYFileReader(Path xryFile) throws IOException {
95  reader = Files.newBufferedReader(xryFile, CHARSET);
96  xryFilePath = xryFile;
97 
98  //Advance the reader to the start of the header.
99  advanceToHeader(reader);
100 
101  //Advance the reader past the header to the start
102  //of the first XRY entity.
103  for (int i = 1; i < HEADER_LENGTH_IN_LINES; i++) {
104  reader.readLine();
105  }
106 
107  xryEntity = new StringBuilder();
108  }
109 
119  public String getReportType() throws IOException {
120  Optional<String> reportType = getType(xryFilePath);
121  if (reportType.isPresent()) {
122  return reportType.get();
123  }
124 
125  throw new IllegalArgumentException(xryFilePath.toString() + " does not "
126  + "have a report type.");
127  }
128 
135  public Path getReportPath() throws IOException {
136  return xryFilePath;
137  }
138 
147  public boolean hasNextEntity() throws IOException {
148  //Entity has yet to be consumed.
149  if (xryEntity.length() > 0) {
150  return true;
151  }
152 
153  String line;
154  while ((line = reader.readLine()) != null) {
155  if (marksEndOfEntity(line)) {
156  if (xryEntity.length() > 0) {
157  //Found a non empty XRY entity.
158  return true;
159  }
160  } else {
161  xryEntity.append(line).append('\n');
162  }
163  }
164 
165  //Check if EOF was hit before an entity delimiter was found.
166  return xryEntity.length() > 0;
167  }
168 
178  public String nextEntity() throws IOException {
179  if (hasNextEntity()) {
180  String returnVal = xryEntity.toString();
181  xryEntity.setLength(0);
182  return returnVal;
183  } else {
184  throw new NoSuchElementException();
185  }
186  }
187 
197  public String peek() throws IOException {
198  if (hasNextEntity()) {
199  return xryEntity.toString();
200  } else {
201  throw new NoSuchElementException();
202  }
203  }
204 
210  @Override
211  public void close() throws IOException {
212  reader.close();
213  }
214 
222  private boolean marksEndOfEntity(String line) {
223  return line.isEmpty();
224  }
225 
244  public static boolean isXRYFile(Path file) throws IOException {
245  String parsedExtension = FilenameUtils.getExtension(file.toString());
246 
247  //A XRY file should have a txt extension.
248  if (!EXTENSION.equals(parsedExtension)) {
249  return false;
250  }
251 
252  BasicFileAttributes attr = Files.readAttributes(file,
253  BasicFileAttributes.class, LinkOption.NOFOLLOW_LINKS);
254 
255  //Do not follow symbolic links. XRY files cannot be a directory.
256  if (attr.isSymbolicLink() || attr.isDirectory()) {
257  return false;
258  }
259 
260  //Check 0xFFFE BOM
261  if (!isXRYBOM(file)) {
262  return false;
263  }
264 
265  try {
266  Optional<String> reportType = getType(file);
267  //All valid XRY reports should have a type.
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);
272  return false;
273  }
274  }
275 
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) {
290  return false;
291  }
292  }
293  }
294 
295  return true;
296  }
297 
307  private static Optional<String> getType(Path file) throws IOException {
308  try (BufferedReader reader = Files.newBufferedReader(file, CHARSET)) {
309  //Header may not start at the beginning of the file.
310  advanceToHeader(reader);
311 
312  //Advance the reader to the line before the report type.
313  for (int i = 1; i < LINE_WITH_REPORT_TYPE - 1; i++) {
314  reader.readLine();
315  }
316 
317  String reportTypeLine = reader.readLine();
318  if (reportTypeLine != null && !reportTypeLine.isEmpty()) {
319  return Optional.of(reportTypeLine);
320  }
321  return Optional.empty();
322  }
323  }
324 
336  private static void advanceToHeader(BufferedReader reader) throws IOException {
337  String line;
338  if((line = reader.readLine()) == null) {
339  return;
340  }
341 
342  String normalizedLine = line.trim().toLowerCase();
343  if (normalizedLine.equals(START_OF_HEADER)) {
344  return;
345  }
346 
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)) {
357  return;
358  }
359  }
360 
364  while ((line = reader.readLine()) != null) {
365  normalizedLine = line.trim().toLowerCase();
366  if (normalizedLine.equals(START_OF_HEADER)) {
367  return;
368  }
369  }
370  }
371 }

Copyright © 2012-2022 Basis Technology. Generated on: Tue Jun 27 2023
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.