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)) {