From e8b430f9204ae6795d7bca9466390e88751b385b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Berenguel?= Date: Tue, 24 Feb 2026 15:32:58 -0300 Subject: [PATCH 1/2] #2803: better logging for UFDR evidences with protection enabled --- .../iped/engine/datasource/UfedXmlReader.java | 107 +++++++++++++----- 1 file changed, 77 insertions(+), 30 deletions(-) diff --git a/iped-engine/src/main/java/iped/engine/datasource/UfedXmlReader.java b/iped-engine/src/main/java/iped/engine/datasource/UfedXmlReader.java index 7ea2749fa2..70c51b577a 100644 --- a/iped-engine/src/main/java/iped/engine/datasource/UfedXmlReader.java +++ b/iped-engine/src/main/java/iped/engine/datasource/UfedXmlReader.java @@ -6,7 +6,6 @@ import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; -import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.nio.charset.StandardCharsets; import java.nio.file.Files; @@ -39,6 +38,8 @@ import javax.xml.parsers.SAXParser; import javax.xml.parsers.SAXParserFactory; +import org.apache.commons.io.FileUtils; +import org.apache.commons.io.FilenameUtils; import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.Level; import org.apache.logging.log4j.LogManager; @@ -99,6 +100,12 @@ public class UfedXmlReader extends DataSourceReader { private final Level CONSOLE = Level.getLevel("MSG"); //$NON-NLS-1$ private static final String[] HEADER_STRINGS = { "project id", "extractionType", "sourceExtractions" }; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + private static final byte[] UFDR_PROTECTION_NO_PASSWORD_REPORT_XML_INITIAL_BYTES = new byte[] { 0x2D, 0x06, 0x52, 0x6D }; + private static final int UFDR_REPORT_XML_INITIAL_BYTES_TO_READ = 1024; + + private static final String UFDR_EXTENSION = "ufdr"; + private static final String XML_REPORT_EXTENSION = "xml"; + private static final String AVATAR_PATH_META = ExtraProperties.UFED_META_PREFIX + "contactphoto_extracted_path"; //$NON-NLS-1$ private static final String ATTACH_PATH_META = ExtraProperties.UFED_META_PREFIX + "attachment_extracted_path"; //$NON-NLS-1$ @@ -152,41 +159,40 @@ public UfedXmlReader(ICaseData caseData, File output, boolean listOnly) { @Override public boolean isSupported(File datasource) { - if (datasource.getName().toLowerCase().endsWith(".ufdr")) { + if (FilenameUtils.isExtension(datasource.getName(), UFDR_EXTENSION)) { return true; } - InputStream xmlReport = lookUpXmlReportInputStream(datasource); - IOUtil.closeQuietly(xmlReport); - - if (xmlReport != null) - return true; - - return false; + // supports any folder with valid XML report inside + try (InputStream xmlReport = lookUpXmlReportInputStream(datasource)) { + return xmlReport != null; + } catch (Exception e) { + return false; + } } private InputStream getXmlInputStream(File file) { - if (file.getName().toLowerCase().endsWith(".xml")) { //$NON-NLS-1$ - try (InputStreamReader reader = new InputStreamReader(new FileInputStream(file), "UTF-8")) { //$NON-NLS-1$ - char[] cbuf = new char[1024]; - int off = 0, i = 0; - while (off < cbuf.length && (i = reader.read(cbuf, off, cbuf.length - off)) != -1) - off += i; - String header = new String(cbuf, 0, off); - for (String str : HEADER_STRINGS) - if (!header.contains(str)) - return null; + if (FilenameUtils.isExtension(file.getName(), XML_REPORT_EXTENSION)) { + try (InputStream fis = new FileInputStream(file)) { + byte[] initialBytes = fis.readNBytes(UFDR_REPORT_XML_INITIAL_BYTES_TO_READ); + if (!containsHeaderStrings(initialBytes)) { + return null; + } + return new FileInputStream(file); } catch (IOException e) { throw new RuntimeException(e); } - } else if (file.getName().toLowerCase().endsWith(".ufdr")) { + } else if (FilenameUtils.isExtension(file.getName(), UFDR_EXTENSION)) { try { ufdrFile = file; String xml = "report.xml"; if (!getUISF().entryExists(xml)) { xml = "Report.xml"; + if (!getUISF().entryExists(xml)) { + return null; + } } return getUISF().getSeekableInputStream(xml); @@ -197,6 +203,21 @@ private InputStream getXmlInputStream(File file) { return null; } + private boolean isReportXmlProtected(byte[] initialBytes) { + int len = UFDR_PROTECTION_NO_PASSWORD_REPORT_XML_INITIAL_BYTES.length; + return Arrays.equals(initialBytes, 0, len, UFDR_PROTECTION_NO_PASSWORD_REPORT_XML_INITIAL_BYTES, 0, len); + } + + private boolean containsHeaderStrings(byte[] initialBytes) { + String header = new String(initialBytes, StandardCharsets.UTF_8); + for (String str : HEADER_STRINGS) + if (header.contains(str)) + return true; + + return false; + + } + private boolean entryExists(String entryPath) { if (ufdrFile != null) { return getUISF().entryExists(entryPath); @@ -233,16 +254,13 @@ private FileInputStreamFactory getFISF() { private InputStream lookUpXmlReportInputStream(File root) { if (root.isFile()) return getXmlInputStream(root); - File[] files = root.listFiles(); - if (files != null) { - for (File file : files) { - if (file.getName().toLowerCase().endsWith(".xml")) { - InputStream is = getXmlInputStream(file); - if (is != null) - return is; - } - } + + for (File file : FileUtils.listFiles(root, new String[]{XML_REPORT_EXTENSION}, false)) { + InputStream is = getXmlInputStream(file); + if (is != null) + return is; } + return null; } @@ -261,6 +279,8 @@ public void read(File root, Item parent) throws Exception { try { xmlStream = lookUpXmlReportInputStream(root); + validateXmlStream(xmlStream); + configureParsers(); SAXParserFactory spf = SAXParserFactory.newInstance(); @@ -275,6 +295,33 @@ public void read(File root, Item parent) throws Exception { } } + private void validateXmlStream(InputStream xmlStream) throws IOException { + if (xmlStream == null) { + if (root.isFile()) { + throw new RuntimeException("Invalid UFDR file: XML report not found. File: " + root.getAbsolutePath()); + } else { + throw new RuntimeException("Invalid UFDR folder: No XML report has been found. Folder: " + root.getAbsolutePath()); + } + } + + if (root.isFile()) { + xmlStream.mark(0); + byte[] initialBytes = xmlStream.readNBytes(UFDR_REPORT_XML_INITIAL_BYTES_TO_READ); + + if (isReportXmlProtected(initialBytes)) { + throw new RuntimeException( + "Unsupported UFDR file: protection is enabled. Generate the UFDR file again with protection disabled. File: " + + root.getAbsolutePath()); + } + + if (!containsHeaderStrings(initialBytes)) { + throw new RuntimeException("Invalid UFDR file: XML report is not valid. " + + "Protection may be enabled. Generate the UFDR file again with protection disabled. File: " + root.getAbsolutePath()); + } + xmlStream.reset(); + } + } + private void configureParsers() { configureParsers(false); } @@ -317,7 +364,7 @@ private void addRootItem(IItem parent) throws InterruptedException { rootItem.setDataSource(evidenceSource); rootItem.setIdInDataSource(""); rootItem.setHasChildren(true); - if (root.getName().endsWith(".ufdr")) { + if (FilenameUtils.isExtension(root.getName(), UFDR_EXTENSION)) { rootItem.setLength(root.length()); rootItem.setSumVolume(false); } From 2ce36bda88bc5cbc31e7a3408a80a99d5bc1edc9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Berenguel?= Date: Tue, 24 Feb 2026 15:39:08 -0300 Subject: [PATCH 2/2] #2803: don't handle exceptions in UfedXmlReader.isSupported() --- .../main/java/iped/engine/datasource/UfedXmlReader.java | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/iped-engine/src/main/java/iped/engine/datasource/UfedXmlReader.java b/iped-engine/src/main/java/iped/engine/datasource/UfedXmlReader.java index 70c51b577a..fdbc5cf8df 100644 --- a/iped-engine/src/main/java/iped/engine/datasource/UfedXmlReader.java +++ b/iped-engine/src/main/java/iped/engine/datasource/UfedXmlReader.java @@ -164,11 +164,9 @@ public boolean isSupported(File datasource) { } // supports any folder with valid XML report inside - try (InputStream xmlReport = lookUpXmlReportInputStream(datasource)) { - return xmlReport != null; - } catch (Exception e) { - return false; - } + InputStream xmlReport = lookUpXmlReportInputStream(datasource); + IOUtil.closeQuietly(xmlReport); + return xmlReport != null; } private InputStream getXmlInputStream(File file) {