/* * Autopsy Forensic Browser * * Copyright 2011-2016 Basis Technology Corp. * Contact: carrier <at> sleuthkit <dot> org * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.sleuthkit.autopsy.modules.photoreccarver; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.logging.Level; import org.openide.util.NbBundle; import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.services.FileManager; import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.MessageNotifyUtil; import org.sleuthkit.autopsy.coreutils.XMLUtil; import org.sleuthkit.autopsy.ingest.IngestJobContext; import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.CarvingResult; import org.sleuthkit.datamodel.LayoutFile; import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.TskFileRange; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.NodeList; /** * This class parses the xml output from PhotoRec, and creates a list of entries * to add back in to be processed. */ class PhotoRecCarverOutputParser { private final Path basePath; private static final Logger logger = Logger.getLogger(PhotoRecCarverFileIngestModule.class.getName()); PhotoRecCarverOutputParser(Path base) { basePath = base; } /** * Parses the given report.xml file, creating a List<LayoutFile> to return. * Uses FileManager to add all carved files that it finds to the TSK * database as $CarvedFiles under the passed-in parent id. * * @param xmlInputFile The XML file we are trying to read and parse * @param id The parent id of the unallocated space we are * parsing. * @param af The AbstractFile representing the unallocated space * we are parsing. * * @return A List<LayoutFile> containing all the files added into the * database * * @throws FileNotFoundException * @throws IOException */ List<LayoutFile> parse(File xmlInputFile, AbstractFile af, IngestJobContext context) throws FileNotFoundException, IOException { try { final Document doc = XMLUtil.loadDoc(PhotoRecCarverOutputParser.class, xmlInputFile.toString()); if (doc == null) { return new ArrayList<>(); } Element root = doc.getDocumentElement(); if (root == null) { logger.log(Level.SEVERE, "Error loading config file: invalid file format (bad root)."); //NON-NLS return new ArrayList<>(); } NodeList fileObjects = root.getElementsByTagName("fileobject"); //NON-NLS final int numberOfFiles = fileObjects.getLength(); if (numberOfFiles == 0) { return new ArrayList<>(); } String fileName; Long fileSize; NodeList fileNames; NodeList fileSizes; NodeList fileRanges; Element entry; Path filePath; FileManager fileManager = Case.getCurrentCase().getServices().getFileManager(); // create and initialize the list to put into the database List<CarvingResult.CarvedFile> carvedFiles = new ArrayList<>(); for (int fileIndex = 0; fileIndex < numberOfFiles; ++fileIndex) { if (context.fileIngestIsCancelled() == true) { // if it was cancelled by the user, result is OK logger.log(Level.INFO, "PhotoRec cancelled by user"); // NON-NLS MessageNotifyUtil.Notify.info(PhotoRecCarverIngestModuleFactory.getModuleName(), NbBundle.getMessage(PhotoRecCarverFileIngestModule.class, "PhotoRecIngestModule.cancelledByUser")); break; } entry = (Element) fileObjects.item(fileIndex); fileNames = entry.getElementsByTagName("filename"); //NON-NLS fileSizes = entry.getElementsByTagName("filesize"); //NON-NLS fileRanges = entry.getElementsByTagName("byte_run"); //NON-NLS fileSize = Long.parseLong(fileSizes.item(0).getTextContent()); fileName = fileNames.item(0).getTextContent(); filePath = Paths.get(fileName); if (filePath.startsWith(basePath)) { fileName = filePath.getFileName().toString(); } List<TskFileRange> tskRanges = new ArrayList<>(); for (int rangeIndex = 0; rangeIndex < fileRanges.getLength(); ++rangeIndex) { Long img_offset = Long.parseLong(((Element) fileRanges.item(rangeIndex)).getAttribute("img_offset")); //NON-NLS Long len = Long.parseLong(((Element) fileRanges.item(rangeIndex)).getAttribute("len")); //NON-NLS // Verify PhotoRec's output long fileByteStart = af.convertToImgOffset(img_offset); if (fileByteStart == -1) { // This better never happen... Data for this file is corrupted. Skip it. logger.log(Level.INFO, "Error while parsing PhotoRec output for file {0}", fileName); //NON-NLS continue; } // check that carved file is within unalloc block long fileByteEnd = img_offset + len; if (fileByteEnd > af.getSize()) { long overshoot = fileByteEnd - af.getSize(); if (fileSize > overshoot) { fileSize -= overshoot; } else { // This better never happen... Data for this file is corrupted. Skip it. continue; } } tskRanges.add(new TskFileRange(fileByteStart, len, rangeIndex)); } if (!tskRanges.isEmpty()) { carvedFiles.add(new CarvingResult.CarvedFile(fileName, fileSize, tskRanges)); } } return fileManager.addCarvedFiles(new CarvingResult(af, carvedFiles)); } catch (NumberFormatException | TskCoreException ex) { logger.log(Level.SEVERE, "Error parsing PhotoRec output and inserting it into the database: {0}", ex); //NON-NLS } List<LayoutFile> empty = Collections.emptyList(); return empty; } }