Autopsy 4.22.1
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 */
19package org.sleuthkit.autopsy.datasourceprocessors.xry;
20
21import java.io.BufferedReader;
22import java.io.IOException;
23import java.io.InputStream;
24import java.nio.charset.Charset;
25import java.nio.charset.MalformedInputException;
26import java.nio.charset.StandardCharsets;
27import java.nio.file.Files;
28import java.nio.file.LinkOption;
29import java.nio.file.Path;
30import java.nio.file.StandardOpenOption;
31import java.nio.file.attribute.BasicFileAttributes;
32import java.util.Optional;
33import java.util.logging.Level;
34import java.util.NoSuchElementException;
35import org.sleuthkit.autopsy.coreutils.Logger;
36import org.apache.commons.io.FilenameUtils;
37
48final 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-2024 Sleuth Kit Labs. Generated on:
This work is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.