diff --git a/iped-app/resources/config/conf/CategoriesConfig.json b/iped-app/resources/config/conf/CategoriesConfig.json
index 115b36ec61..17f0336782 100644
--- a/iped-app/resources/config/conf/CategoriesConfig.json
+++ b/iped-app/resources/config/conf/CategoriesConfig.json
@@ -69,6 +69,9 @@
]}
]}
]},
+ {"name": "Apple Artifacts", "categories":[
+ {"name": "Apple Configuration Files", "mimes": ["application/x-bplist"]}
+ ]},
{"name": "Google Drive", "categories":[
{"name": "GDrive Synced Files", "mimes": ["application/x-gdrive-cloud-graph", "application/x-gdrive-snapshot"]},
{"name": "GDrive File Entries", "mimes": ["application/x-gdrive-cloud-graph-registry", "application/x-gdrive-snapshot-registry"]}
diff --git a/iped-app/resources/config/conf/CategoriesToExpand.txt b/iped-app/resources/config/conf/CategoriesToExpand.txt
index e1958cc2a1..2aa9a8c0da 100644
--- a/iped-app/resources/config/conf/CategoriesToExpand.txt
+++ b/iped-app/resources/config/conf/CategoriesToExpand.txt
@@ -19,6 +19,7 @@ OLE files
Georeferenced Files
Peer-to-peer
Chrome Cache
+Apple Configuration Files
#Event Files
# Generates registry reports:
diff --git a/iped-app/resources/config/conf/MakePreviewConfig.txt b/iped-app/resources/config/conf/MakePreviewConfig.txt
index 336846f342..91b19ba636 100644
--- a/iped-app/resources/config/conf/MakePreviewConfig.txt
+++ b/iped-app/resources/config/conf/MakePreviewConfig.txt
@@ -10,4 +10,5 @@ supportedMimes = application/x-whatsapp-db; application/x-whatsapp-db-f; applica
supportedMimes = application/x-prefetch; text/x-vcard; application/x-emule-preferences-dat; application/vnd.android.package-archive; application/x-bittorrent-settings-dat
# List of mimetypes which parsers insert links to other case items into preview
-supportedMimesWithLinks = application/x-emule; application/x-emule-part-met; application/x-ares-galaxy; application/x-shareaza-library-dat; application/x-shareaza-download; application/x-bittorrent-resume-dat; application/x-bittorrent-resume-dat-entry; application/x-bittorrent
\ No newline at end of file
+supportedMimesWithLinks = application/x-emule; application/x-emule-part-met; application/x-ares-galaxy; application/x-shareaza-library-dat; application/x-shareaza-download; application/x-bittorrent-resume-dat; application/x-bittorrent-resume-dat-entry; application/x-bittorrent
+supportedMimesWithLinks = application/x-bplist; application/x-apple-nskeyedarchiver; application/x-plist; application/x-bplist-webarchive; application/x-plist-webarchive; application/x-plist-memgraph; application/x-bplist-memgraph; application/x-bplist-itunes; application/x-plist-itunes
diff --git a/iped-app/resources/config/conf/ParserConfig.xml b/iped-app/resources/config/conf/ParserConfig.xml
index d56a231e17..ed1b4cff3e 100644
--- a/iped-app/resources/config/conf/ParserConfig.xml
+++ b/iped-app/resources/config/conf/ParserConfig.xml
@@ -23,7 +23,9 @@
-
+
+
+
diff --git a/iped-parsers/iped-parsers-impl/src/main/java/iped/parsers/plist/detector/PListDetector.java b/iped-parsers/iped-parsers-impl/src/main/java/iped/parsers/plist/detector/PListDetector.java
index 7e73ccd292..e38357b634 100644
--- a/iped-parsers/iped-parsers-impl/src/main/java/iped/parsers/plist/detector/PListDetector.java
+++ b/iped-parsers/iped-parsers-impl/src/main/java/iped/parsers/plist/detector/PListDetector.java
@@ -15,6 +15,7 @@
import com.dd.plist.NSDictionary;
import com.dd.plist.NSObject;
+import com.dd.plist.NSString;
import com.dd.plist.PropertyListFormatException;
import com.dd.plist.PropertyListParser;
@@ -32,6 +33,7 @@ public class PListDetector implements Detector {
public static MediaType BITUNES = MediaType.application("x-bplist-itunes");
public static MediaType WA_USER_PLIST = MediaType.application("x-whatsapp-user-plist");
public static MediaType THREEMA_USER_PLIST = MediaType.application("x-threema-user-plist");
+ public static MediaType NSKEYEDARCHIVER_PLIST = MediaType.application("x-apple-nskeyedarchiver");
public static MediaType detectOnKeys(Set keySet) {
if (keySet.contains("nodes") && keySet.contains("edges") && keySet.contains("graphEncodingVersion")) {
@@ -46,9 +48,24 @@ public static MediaType detectOnKeys(Set keySet) {
} else if (keySet.contains("Threema device ID")) {
return THREEMA_USER_PLIST;
}
+
return BPLIST;
}
+ public static MediaType detectOnNodes(NSDictionary rootObj, Metadata metadata) {
+ NSObject archiver = rootObj.get("$archiver");
+ if (archiver != null) {
+ if (archiver instanceof NSString) {
+ if (archiver.toString().toLowerCase().equals("nskeyedarchiver")) {
+ return NSKEYEDARCHIVER_PLIST;
+ }
+ }
+ }
+
+ return detectOnKeys(rootObj.getHashMap().keySet());
+
+ }
+
/**
* @param input
* input stream must support reset
@@ -101,7 +118,7 @@ public MediaType detect(InputStream input, Metadata metadata) throws IOException
}
if (rootObj instanceof NSDictionary) {
- return detectOnKeys(((NSDictionary) rootObj).getHashMap().keySet());
+ return detectOnNodes((NSDictionary) rootObj, metadata);
}
return BPLIST;
}
diff --git a/iped-parsers/iped-parsers-impl/src/main/java/iped/parsers/plist/parser/NSKeyedArchiverParser.java b/iped-parsers/iped-parsers-impl/src/main/java/iped/parsers/plist/parser/NSKeyedArchiverParser.java
new file mode 100644
index 0000000000..a686665ae6
--- /dev/null
+++ b/iped-parsers/iped-parsers-impl/src/main/java/iped/parsers/plist/parser/NSKeyedArchiverParser.java
@@ -0,0 +1,325 @@
+package iped.parsers.plist.parser;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.charset.Charset;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map.Entry;
+import java.util.Set;
+
+import org.apache.tika.extractor.EmbeddedDocumentExtractor;
+import org.apache.tika.metadata.Metadata;
+import org.apache.tika.mime.MediaType;
+import org.apache.tika.parser.ParseContext;
+import org.apache.tika.sax.XHTMLContentHandler;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.AttributesImpl;
+
+import com.dd.plist.NSArray;
+import com.dd.plist.NSDictionary;
+import com.dd.plist.NSNumber;
+import com.dd.plist.NSObject;
+import com.dd.plist.UID;
+
+import iped.parsers.plist.detector.PListDetector;
+import iped.utils.IOUtil;
+
+public class NSKeyedArchiverParser extends PListParser {
+ private static final Set SUPPORTED_TYPES = Collections.singleton(PListDetector.NSKEYEDARCHIVER_PLIST);
+ private static final String NSKEYEDARCHIVER_METADATA_SUFFIX = "nskeyedarchiver";
+
+ private static final Object ARCHIVERKEY = "$archiver";
+ private static final Object TOPKEY = "$top";
+ private static final Object OBJECTSKEY = "$objects";
+
+ private static final String CSS = new String(readResourceAsBytes("/iped/parsers/css/treeview.css"), Charset.forName("UTF-8"));
+ private static final String UUIDJS = new String(readResourceAsBytes("/iped/parsers/css/uuidlink.js"), Charset.forName("UTF-8"));
+
+ @Override
+ public Set getSupportedTypes(ParseContext arg0) {
+ return SUPPORTED_TYPES;
+ }
+
+ public void parseNSObject(NSObject nso, XHTMLContentHandler xhtml, Metadata metadata, String path, EmbeddedDocumentExtractor extractor, ParseContext context) throws SAXException {
+ if (nso instanceof NSDictionary) {
+ HashMap map = ((NSDictionary) nso).getHashMap();
+
+ NSObject top = map.get(TOPKEY);
+ NSObject objects = map.get(OBJECTSKEY);
+
+ if (top != null && objects != null) {
+ NSObject root = null;
+ int rootIndex = 0;
+ HashMap objectsMap = new HashMap();
+
+ NSObject rootObj = null;
+ if (top instanceof NSDictionary) {
+ if (((NSDictionary) top).size() > 0) {
+ root = ((NSDictionary) top).get("root");
+ }
+ }
+
+ if (root != null) {
+ Set alreadyVisitedObjects = new HashSet<>();// array to control already written objects and avoid infinite loops
+ parseObject("", root, ((NSArray) objects), alreadyVisitedObjects, xhtml, metadata, getBasePath(), extractor, context);
+ } else {
+ // try to parse as a PList file
+ parseAsPList(nso, xhtml, metadata, path, extractor, context);
+ }
+ } else {
+ // try to parse as a PList file
+ parseAsPList(nso, xhtml, metadata, path, extractor, context);
+ }
+ } else {
+ // try to parse as a PList file
+ parseAsPList(nso, xhtml, metadata, path, extractor, context);
+ }
+ }
+
+ public void parseAsPList(NSObject nso, XHTMLContentHandler xhtml, Metadata metadata, String path, EmbeddedDocumentExtractor extractor, ParseContext context) throws SAXException {
+ super.parseNSObject(nso, xhtml, metadata, path, extractor, context);
+ }
+
+ public int getUUIDInteger(UID uid) {
+ byte[] b = new byte[4];
+ byte[] b2 = uid.getBytes();
+ for (int i = b.length - 1, j = b2.length - 1; i >= 0 && j >= 0; i--, j--) {
+ b[i] = b2[j];
+ }
+ ByteBuffer wrapped = ByteBuffer.wrap(b);
+ return wrapped.getInt();
+ }
+
+ public boolean isPrimitive(NSObject obj) {
+ return !(obj instanceof NSDictionary) && !(obj instanceof NSArray);
+ }
+
+ public boolean isNSTime(NSObject obj) {
+ return ((obj instanceof NSDictionary) && (((NSDictionary) obj).containsKey("NS.time")));
+ }
+
+ /*
+ * Dictionary objects was found in different representations. One with the keys
+ * and objects referenced by an UID array, and the other as a common BPList
+ * dictionary (function isNSDictionary).
+ */
+ public boolean isKADictionary(NSObject obj) {
+ return ((obj instanceof NSDictionary) && (((NSDictionary) obj).containsKey("NS.keys")) && (((NSDictionary) obj).containsKey("NS.objects")));
+ }
+
+ public boolean isNSDictionary(NSObject obj) {
+ return ((obj instanceof NSDictionary) && (!((NSDictionary) obj).containsKey("NS.objects")) && !(((NSDictionary) obj).containsKey("NS.keys")));
+ }
+
+ public boolean isKAArray(NSObject obj) {
+ return ((obj instanceof NSDictionary) && ((NSDictionary) obj).containsKey("$class") && !((NSDictionary) obj).containsKey("NS.keys") && (((NSDictionary) obj).containsKey("NS.objects")));
+ }
+
+ private void parseObject(String name, NSObject object, NSArray objects, Set alreadyVisitedObjects, XHTMLContentHandler xhtml, Metadata metadata, String path,
+ EmbeddedDocumentExtractor extractor,
+ ParseContext context) throws SAXException {
+ try {
+ NSObject obj = object;
+ UID uid = null;
+ if(obj instanceof UID) {
+ uid = (UID) object;
+ obj = objects.objectAtIndex(getUUIDInteger(uid));
+ }
+
+ if (isPrimitive(obj)) {
+ parseNSPrimitiveObject(obj, xhtml, metadata, path, extractor, context);
+ } else {
+ // checks if object was already written to avoid infinite loops
+ if (alreadyVisitedObjects.contains(obj)) {
+ xhtml.startElement("details", "open", "true");
+ xhtml.startElement("summary", "class", "is-expandable");
+ xhtml.startElement("a", "onclick", "clickUID(" + getUUIDInteger(uid) + ");");
+ xhtml.characters("[Object link]");
+ xhtml.endElement("a");
+ xhtml.endElement("summary");
+ xhtml.endElement("details");
+ return;
+ } else {
+ alreadyVisitedObjects.add(obj);
+ }
+
+ AttributesImpl attr = new AttributesImpl();
+ if (uid != null) {
+ attr.addAttribute("", "treeuid", "", "", Integer.toString(getUUIDInteger(uid)));
+ attr.addAttribute("", "class", "", "", "uidref");
+ }
+
+ if (isNSTime(obj)) {
+ parseNSTime(((NSDictionary) obj).get("NS.time"), xhtml, metadata, path, extractor, context);
+ } else if (isKAArray(obj)) {
+ NSObject[] arrayObjects = ((NSArray) ((NSDictionary) obj).get("NS.objects")).getArray();
+ attr.addAttribute("", "open", "", "", "true");
+ xhtml.startElement("details", attr);
+ xhtml.startElement("summary", "class", "is-expandable");
+ xhtml.characters("[ARRAY]");
+ xhtml.endElement("summary");
+ if (arrayObjects.length > 0) {
+ String classname = getNSClassName(obj, objects);
+ for (NSObject member : arrayObjects) {
+ parseObject(classname, member, objects, alreadyVisitedObjects, xhtml, metadata, path, extractor, context);
+ }
+ } else {
+ xhtml.startElement("li");
+ xhtml.characters("[]");
+ xhtml.endElement("li");
+ }
+ xhtml.endElement("details");
+ } else if (obj instanceof NSArray) {
+ attr.addAttribute("", "open", "", "", "true");
+ xhtml.startElement("details", attr);
+ xhtml.startElement("summary", "class", "is-expandable");
+ xhtml.characters(name);
+ xhtml.endElement("summary");
+ for (NSObject member : ((NSArray) obj).getArray()) {
+ parseObject(name, member, objects, alreadyVisitedObjects, xhtml, metadata, path, extractor, context);
+ }
+ xhtml.endElement("details");
+ } else if (isNSDictionary(obj)) {
+ if (((NSDictionary) obj).size() > 0) {
+ xhtml.startElement("details", attr);
+ HashMap dictMap = ((NSDictionary) obj).getHashMap();
+ String className = getNSClassName(obj, objects);
+ xhtml.startElement("summary", "class", "is-expandable");
+ xhtml.characters(className == null ? "Dict" : className);
+ xhtml.endElement("summary");
+ for (Entry d : ((NSDictionary) obj).getHashMap().entrySet()) {
+ if (!"$class".equals(d.getKey())) {
+ xhtml.startElement("details");
+ xhtml.startElement("summary", "class", "is-expandable");
+ xhtml.characters(d.getKey());
+ xhtml.endElement("summary");
+ parseObject(d.getKey(), d.getValue(), objects, alreadyVisitedObjects, xhtml, metadata, path + ":" + className + ":" + d.getKey(), extractor,
+ context);
+ xhtml.endElement("details");
+ }
+ }
+ xhtml.endElement("details");
+ }
+ } else if (isKADictionary(obj)) {
+ parseKADictionary(name, uid, obj, objects, alreadyVisitedObjects, xhtml, metadata, path, extractor, context);
+ } else {
+ xhtml.startElement("details", attr);
+ xhtml.startElement("summary", "class", "is-expandable");
+ String classname = getNSClassName(obj, objects);
+ xhtml.characters(classname);
+ xhtml.endElement("summary");
+ for (Entry e : ((NSDictionary) obj).entrySet()) {
+ if (!"$class".equals(e.getKey())) {
+ xhtml.startElement("details", "open", "true");
+ xhtml.startElement("summary", "class", "is-expandable");
+ xhtml.characters(e.getKey());
+ xhtml.endElement("summary");
+ parseObject(e.getKey(), e.getValue(), objects, alreadyVisitedObjects, xhtml, metadata, path + ":" + classname + ":" + e.getKey(), extractor, context);
+ xhtml.endElement("details");
+ }
+ }
+ xhtml.endElement("details");
+ }
+ }
+ } catch (Exception e) {
+ System.out.println();
+ e.printStackTrace();
+ }
+ }
+
+ private void parseNSTime(NSObject nso, XHTMLContentHandler xhtml, Metadata metadata, String path, EmbeddedDocumentExtractor extractor, ParseContext context) throws SAXException {
+ Date date = new Date((((NSNumber) nso).longValue() + 978307200) * 1000);
+ parseDate(date, path, xhtml, metadata);
+ }
+
+ public void parseKADictionary(String name, UID uid, NSObject obj, NSArray objects, Set alreadyVisitedObjects, XHTMLContentHandler xhtml, Metadata metadata, String path,
+ EmbeddedDocumentExtractor extractor,
+ ParseContext context) throws SAXException {
+
+ AttributesImpl attr = new AttributesImpl();
+ if (uid != null) {
+ attr.addAttribute("", "treeuid", "", "", Integer.toString(getUUIDInteger(uid)));
+ attr.addAttribute("", "class", "", "", "uidref");
+ }
+ attr.addAttribute("", "open", "", "", "true");
+
+ xhtml.startElement("details", attr);
+ xhtml.startElement("summary", "class", "is-expandable");
+ String classname = getNSClassName(obj, objects);
+ xhtml.characters(classname);
+ xhtml.endElement("summary");
+
+ xhtml.startElement("details", "open", "true");
+ xhtml.startElement("summary", "class", "is-expandable");
+ xhtml.characters("NS.keys");
+ xhtml.endElement("summary");
+ NSArray keys = (NSArray) ((NSDictionary) obj).get("NS.keys");
+ int classIndex = -1;
+ for (NSObject member : keys.getArray()) {
+ parseObject(name, member, objects, alreadyVisitedObjects, xhtml, metadata, path, extractor, context);
+ }
+ xhtml.endElement("details");
+
+ xhtml.startElement("details", "open", "true");
+ xhtml.startElement("summary", "class", "is-expandable");
+ xhtml.characters("NS.objects");
+ xhtml.endElement("summary");
+ NSArray objectsArray = (NSArray) ((NSDictionary) obj).get("NS.objects");
+ int i=0;
+ for (NSObject member : objectsArray.getArray()) {
+ parseObject(name, member, objects, alreadyVisitedObjects, xhtml, metadata, path + ":" + classname, extractor, context);
+ i++;
+ }
+ xhtml.endElement("details");
+ xhtml.endElement("details");
+ }
+
+ public String getNSClassName(NSObject obj, NSArray objects) {
+ try {
+ NSDictionary classObj = (NSDictionary) objects.getArray()[getUUIDInteger((UID) ((NSDictionary) obj).get("$class"))];
+ return classObj.get("$classname").toString();
+ } catch (Exception e) {
+ return obj.getClass().getName();
+ }
+ }
+
+ static public boolean isNSKeyedArchiver(NSObject oc) {
+ if(oc instanceof NSDictionary) {
+ HashMap map = ((NSDictionary)oc).getHashMap();
+ NSObject archiver = map.get(ARCHIVERKEY);
+ // NSObject top = map.get(TOPKEY);
+ if (archiver != null && archiver.toString().equals("NSKeyedArchiver")) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private static byte[] readResourceAsBytes(String resource) {
+ byte[] result = null;
+ try {
+ result = IOUtil.loadInputStream(PListParser.class.getResourceAsStream(resource));
+ } catch (IOException e) {
+ e.printStackTrace();
+ } catch (Throwable e) {
+ e.printStackTrace();
+ }
+ return result;
+ }
+
+ @Override
+ public String getBasePath() {
+ return NSKEYEDARCHIVER_METADATA_SUFFIX;
+ }
+
+ @Override
+ public void parseUID(UID nso, XHTMLContentHandler xhtml, Metadata metadata, String path, EmbeddedDocumentExtractor extractor, ParseContext context) throws SAXException {
+ xhtml.startElement("li", "class", "nochild");
+ xhtml.characters(((UID) nso).getName() + "(" + getUUIDInteger(nso) + ")");
+ xhtml.endElement("li");
+ }
+
+}
diff --git a/iped-parsers/iped-parsers-impl/src/main/java/iped/parsers/plist/parser/PListParser.java b/iped-parsers/iped-parsers-impl/src/main/java/iped/parsers/plist/parser/PListParser.java
new file mode 100644
index 0000000000..e8b195304c
--- /dev/null
+++ b/iped-parsers/iped-parsers-impl/src/main/java/iped/parsers/plist/parser/PListParser.java
@@ -0,0 +1,295 @@
+package iped.parsers.plist.parser;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.Charset;
+import java.text.ParseException;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.Map.Entry;
+import java.util.Set;
+
+import javax.xml.parsers.ParserConfigurationException;
+
+import org.apache.tika.detect.apple.BPListDetector;
+import org.apache.tika.exception.TikaException;
+import org.apache.tika.extractor.EmbeddedDocumentExtractor;
+import org.apache.tika.extractor.ParsingEmbeddedDocumentExtractor;
+import org.apache.tika.io.TikaInputStream;
+import org.apache.tika.metadata.Metadata;
+import org.apache.tika.metadata.TikaCoreProperties;
+import org.apache.tika.mime.MediaType;
+import org.apache.tika.parser.AbstractParser;
+import org.apache.tika.parser.ParseContext;
+import org.apache.tika.sax.XHTMLContentHandler;
+import org.xml.sax.ContentHandler;
+import org.xml.sax.SAXException;
+
+import com.dd.plist.NSArray;
+import com.dd.plist.NSData;
+import com.dd.plist.NSDate;
+import com.dd.plist.NSDictionary;
+import com.dd.plist.NSNumber;
+import com.dd.plist.NSObject;
+import com.dd.plist.NSSet;
+import com.dd.plist.NSString;
+import com.dd.plist.PropertyListFormatException;
+import com.dd.plist.PropertyListParser;
+import com.dd.plist.UID;
+
+import iped.parsers.util.IgnoreContentHandler;
+import iped.parsers.util.MetadataUtil;
+import iped.properties.BasicProps;
+import iped.utils.DateUtil;
+import iped.utils.IOUtil;
+
+/***
+ *
+ * @author PCF Patrick Dalla Bernardina
+ *
+ * This parser formats the bplist data in a expandable html tree,
+ * extract binary data as subitens and NSDate objects as metadata.
+ */
+public class PListParser extends AbstractParser {
+
+ private static final Set SUPPORTED_TYPES = Collections
+ .unmodifiableSet(new HashSet<>(Arrays.asList(BPListDetector.BITUNES, BPListDetector.BMEMGRAPH, BPListDetector.BPLIST, BPListDetector.BWEBARCHIVE, BPListDetector.PLIST)));
+ private static final String BPLIST_METADATA_SUFFIX = "bplist";
+
+ private static final String CSS = new String(readResourceAsBytes("/iped/parsers/css/treeview.css"), Charset.forName("UTF-8"));
+ private static final String UUIDJS = new String(readResourceAsBytes("/iped/parsers/css/uuidlink.js"), Charset.forName("UTF-8"));
+
+ @Override
+ public Set getSupportedTypes(ParseContext arg0) {
+ return SUPPORTED_TYPES;
+ }
+
+ @Override
+ public void parse(InputStream is, ContentHandler handler, Metadata metadata, ParseContext context) throws IOException, SAXException, TikaException {
+ if (is instanceof TikaInputStream) {
+ NSObject oc = null;
+ try {
+ if (is instanceof TikaInputStream && ((TikaInputStream) is).hasFile()) {
+ oc = PropertyListParser.parse(((TikaInputStream) is).getFile());
+
+ } else {
+ oc = PropertyListParser.parse(is);
+ }
+
+
+ XHTMLContentHandler xhtml = new XHTMLContentHandler(handler, metadata);
+ xhtml.startDocument();
+
+ xhtml.startElement("style");
+ xhtml.characters(PListParser.CSS);
+ xhtml.endElement("style");
+
+ String contentType = metadata.get(Metadata.CONTENT_TYPE);
+ if (BPListDetector.PLIST.toString().equals(contentType)) {
+ if (oc instanceof NSDictionary) {
+ MediaType subtype = BPListDetector.detectXMLOnKeys(((NSDictionary) oc).keySet());
+ metadata.set(Metadata.CONTENT_TYPE, subtype.toString());
+ }
+ }
+
+ try {
+ EmbeddedDocumentExtractor extractor = context.get(EmbeddedDocumentExtractor.class, new ParsingEmbeddedDocumentExtractor(context));
+
+ xhtml.startElement("nav");
+
+ parseNSObject(oc, xhtml, metadata, getBasePath(), extractor, context);
+ xhtml.endElement("nav");
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+
+ xhtml.startElement("script");
+ xhtml.characters(PListParser.UUIDJS);
+ xhtml.endElement("script");
+ xhtml.endDocument();
+ } catch (IOException | PropertyListFormatException | ParseException | ParserConfigurationException | SAXException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ }
+ }
+
+ public String getBasePath() {
+ return BPLIST_METADATA_SUFFIX;
+ }
+
+ public boolean parseNSPrimitiveObject(NSObject nso, XHTMLContentHandler xhtml, Metadata metadata, String path, EmbeddedDocumentExtractor extractor, ParseContext context) throws SAXException {
+ if (nso instanceof UID) {
+ parseUID((UID) nso, xhtml, metadata, path, extractor, context);
+ return true;
+ }
+ if (nso instanceof NSNumber) {
+ NSNumber n = (NSNumber) nso;
+ Date now = new Date();
+ // converts 30 years to now and 2 years from now timestamps
+ if (n.longValue() > (now.getTime() / 1000) - 3600 * 24 * 365 * 30 && n.longValue() < (now.getTime() / 1000) + 3600 * 24 * 365 * 2) {
+ try {
+ Date date = new Date(n.longValue() * 1000);
+ // check to see if it is a date information
+ parseDate(date, path, xhtml, metadata);
+ } catch (Exception e) {
+ xhtml.startElement("li", "class", "nochild");
+ xhtml.characters(escapeEmpty(nso.toString()));
+ xhtml.endElement("li");
+ }
+ } else {
+ xhtml.startElement("li", "class", "nochild");
+ xhtml.characters(escapeEmpty(nso.toString()));
+ xhtml.endElement("li");
+ }
+ return true;
+ }
+ if (nso instanceof NSString) {
+ xhtml.startElement("li", "class", "nochild");
+ xhtml.characters(escapeEmpty(nso.toString()));
+ xhtml.endElement("li");
+ return true;
+ }
+ if (nso instanceof NSDate) {
+ parseDate(((NSDate) nso).getDate(), path, xhtml, metadata);
+ return true;
+ }
+ return false;
+ }
+
+ public void parseDate(Date date, String path, XHTMLContentHandler xhtml, Metadata metadata) throws SAXException {
+ String dateStr = DateUtil.dateToString(date);
+ xhtml.startElement("li", "class", "nochild");
+ xhtml.characters(dateStr);
+ xhtml.endElement("li");
+ String metadataName = getBasePath() + ":" + path.substring(path.lastIndexOf(":") + 1);
+ MetadataUtil.setMetadataType(metadataName, Date.class);
+ String currentValue = metadata.get(metadataName);
+ if (currentValue == null || (currentValue.length() + dateStr.length()) <= 32765) {
+ metadata.add(metadataName, dateStr);
+ }
+
+ }
+
+ public String escapeEmpty(String str) {
+ // this method was implemented to avoid empty content inside tag was causing CSS
+ // not to correctly apply
+ if (str.equals("")) {
+ return " ";
+ }
+ return str;
+ }
+
+ public void parseUID(UID nso, XHTMLContentHandler xhtml, Metadata metadata, String path, EmbeddedDocumentExtractor extractor, ParseContext context) throws SAXException {
+ xhtml.startElement("li", "class", "nochild");
+ xhtml.characters(((UID) nso).getName());
+ xhtml.endElement("li");
+ }
+
+ public void parseNSObject(NSObject nso, XHTMLContentHandler xhtml, Metadata metadata, String path, EmbeddedDocumentExtractor extractor, ParseContext context) throws SAXException {
+ if (nso instanceof NSDictionary) {
+ if (((NSDictionary) nso).size() <= 0) {
+ return;
+ }
+ }
+ if (nso instanceof NSArray) {
+ if (((NSArray) nso).getArray().length <= 0) {
+ return;
+ }
+ }
+ if (nso instanceof NSSet) {
+ if (((NSSet) nso).allObjects().length <= 0) {
+ return;
+ }
+ }
+ if (parseNSPrimitiveObject(nso, xhtml, metadata, path, extractor, context)) {
+ return;
+ }
+
+ if ((nso instanceof NSData)) {
+ try {
+ if (path.contains("MSAppCenterPastDevices")) {
+ System.out.println();
+ }
+ xhtml.startElement("li", "class", "uuidlink");
+ xhtml.characters("[Data was extracted as subitem]");
+ xhtml.endElement("li");
+
+ handleData(path, (NSData) nso, metadata, extractor);
+
+ } catch (IOException | SAXException e) {
+ e.printStackTrace();
+ }
+ return;
+ }
+
+ xhtml.startElement("details", "open", "true");
+ if (nso instanceof NSArray) {
+ if (((NSArray) nso).getArray().length > 0) {
+ xhtml.startElement("summary", "class", "is-expandable");
+ xhtml.characters("Array");
+ xhtml.endElement("summary");
+ for (NSObject ao : ((NSArray) nso).getArray()) {
+ parseNSObject(ao, xhtml, metadata, path, extractor, context);
+ }
+ }
+ }
+ if (nso instanceof NSSet) {
+ if (((NSSet) nso).allObjects().length > 0) {
+ xhtml.startElement("summary", "class", "is-expandable");
+ xhtml.characters("Set");
+ xhtml.endElement("summary");
+ for (NSObject ao : ((NSSet) nso).allObjects()) {
+ parseNSObject(ao, xhtml, metadata, path, extractor, context);
+ }
+ }
+ }
+ if (nso instanceof NSDictionary) {
+ if (((NSDictionary) nso).size() > 0) {
+ xhtml.startElement("summary", "class", "is-expandable");
+ xhtml.characters("Dict");
+ xhtml.endElement("summary");
+ for (Entry d : ((NSDictionary) nso).getHashMap().entrySet()) {
+ xhtml.startElement("details");
+ xhtml.startElement("summary", "class", "is-expandable");
+ xhtml.characters(d.getKey());
+ xhtml.endElement("summary");
+ parseNSObject(d.getValue(), xhtml, metadata, path + ":" + d.getKey(), extractor, context);
+ xhtml.endElement("details");
+
+ }
+ }
+ }
+ xhtml.endElement("details");
+ }
+
+ private void handleData(String path, NSData value, Metadata metadata, EmbeddedDocumentExtractor extractor) throws IOException, SAXException {
+ if (!extractor.shouldParseEmbedded(metadata)) {
+ return;
+ }
+
+ try (TikaInputStream tis = TikaInputStream.get(value.bytes())) {
+ Metadata m2 = new Metadata();
+ String name = path.substring(getBasePath().length() + 1);
+ m2.add(BasicProps.NAME, name);
+ m2.add(TikaCoreProperties.RESOURCE_NAME_KEY, name);
+
+ extractor.parseEmbedded(tis, new IgnoreContentHandler(), m2, true);
+ }
+ }
+
+ private static byte[] readResourceAsBytes(String resource) {
+ byte[] result = null;
+ try {
+ result = IOUtil.loadInputStream(PListParser.class.getResourceAsStream(resource));
+ } catch (IOException e) {
+ e.printStackTrace();
+ } catch (Throwable e) {
+ e.printStackTrace();
+ }
+ return result;
+ }
+
+}
diff --git a/iped-parsers/iped-parsers-impl/src/main/java/iped/parsers/util/ToXMLContentHandler.java b/iped-parsers/iped-parsers-impl/src/main/java/iped/parsers/util/ToXMLContentHandler.java
index 268ce83d0d..86594d767e 100644
--- a/iped-parsers/iped-parsers-impl/src/main/java/iped/parsers/util/ToXMLContentHandler.java
+++ b/iped-parsers/iped-parsers-impl/src/main/java/iped/parsers/util/ToXMLContentHandler.java
@@ -79,6 +79,8 @@ public String getQName(String uri, String localName) throws SAXException {
private final String encoding;
protected boolean inStartElement = false;
+ protected boolean inStyle = false;
+ protected boolean inScript = false;
protected final Map namespaces = new HashMap();
@@ -142,6 +144,18 @@ public void startElement(String uri, String localName, String qName, Attributes
currentElement = new ElementInfo(currentElement, namespaces);
+ if (localName.toLowerCase().equals("style")) {
+ inStyle = true;
+ } else {
+ inStyle = false;
+ }
+
+ if (localName.toLowerCase().equals("script")) {
+ inScript = true;
+ } else {
+ inScript = false;
+ }
+
write('<');
write(currentElement.getQName(uri, localName));
@@ -196,7 +210,11 @@ public void endElement(String uri, String localName, String qName) throws SAXExc
@Override
public void characters(char[] ch, int start, int length) throws SAXException {
lazyCloseStartElement();
- writeEscaped(ch, start, start + length, false);
+ if (inStyle || inScript) {
+ super.characters(ch, start, length);
+ } else {
+ writeEscaped(ch, start, start + length, false);
+ }
}
private void lazyCloseStartElement() throws SAXException {
diff --git a/iped-parsers/iped-parsers-impl/src/main/resources/iped/parsers/css/treeview.css b/iped-parsers/iped-parsers-impl/src/main/resources/iped/parsers/css/treeview.css
new file mode 100644
index 0000000000..2d568bb4a4
--- /dev/null
+++ b/iped-parsers/iped-parsers-impl/src/main/resources/iped/parsers/css/treeview.css
@@ -0,0 +1,41 @@
+summary {
+ cursor: pointer;
+ outline: 0;
+}
+
+
+details {
+ white-space: nowrap;
+}
+details details {
+ margin-left: 1.4rem;
+}
+details li {
+ margin-left: 1.4rem;
+}
+details a {
+ margin-left: 1.4rem;
+}
+
+details[open] > details::before {
+ transform: rotate(90deg);
+}
+.uuidlink {
+ cursor: pointer;
+}
+.uuidlink::-webkit-details-marker{
+ display: none;
+}
+.uuidlink::marker {
+ content: "";
+}
+
+summary > details {
+ padding-left: 1.4rem;
+}
+summary.nochild::-webkit-details-marker{
+ display: none;
+}
+summary.nochild::marker {
+ content: "";
+}
\ No newline at end of file
diff --git a/iped-parsers/iped-parsers-impl/src/main/resources/iped/parsers/css/uuidlink.js b/iped-parsers/iped-parsers-impl/src/main/resources/iped/parsers/css/uuidlink.js
new file mode 100644
index 0000000000..e63b77002d
--- /dev/null
+++ b/iped-parsers/iped-parsers-impl/src/main/resources/iped/parsers/css/uuidlink.js
@@ -0,0 +1,40 @@
+function clickUID(uid) {
+ var obj = document.querySelector("details[treeuid='"+uid+"']");
+ var a = obj;
+ var b=obj;
+
+ while (a) {
+ if(a instanceof HTMLElement){
+ a.removeAttribute('open');
+ }
+ b = a;
+ a = a.parentNode;
+ }
+
+ setTimeout(function(){
+ a = obj;
+ while (a) {
+ if(a instanceof HTMLElement){
+ a.open = 'true';
+ }
+ b = a;
+ a = a.parentNode;
+ }
+
+ obj.scrollIntoView();
+
+ if(!obj.highlight){
+ obj.highlight=true;
+ bcolor = obj.style.backgroundColor;
+ obj.style.backgroundColor="#AAAAAA";
+ setTimeout(function(){
+ obj.style.backgroundColor=bcolor;
+ obj.highlight=false;},1000);
+ }
+
+ },1);
+
+}
+
+window.containsNavigableTree=true;
+
diff --git a/iped-viewers/iped-viewers-impl/src/main/java/iped/viewers/HtmlViewer.java b/iped-viewers/iped-viewers-impl/src/main/java/iped/viewers/HtmlViewer.java
index 24e95d4a58..76e0774474 100644
--- a/iped-viewers/iped-viewers-impl/src/main/java/iped/viewers/HtmlViewer.java
+++ b/iped-viewers/iped-viewers-impl/src/main/java/iped/viewers/HtmlViewer.java
@@ -380,6 +380,20 @@ protected void highlightNode(Node node, boolean parentVisible) {
}
}
+ if (totalHits > 0) {
+ // expands all parent elements of each hit
+ try {
+ Boolean isNavigableTree = (Boolean) webEngine.executeScript("window.containsNavigableTree");
+ if (isNavigableTree.booleanValue()) {
+ for (int i = 0; i < totalHits; i++) {
+ webEngine.executeScript("var a = document.getElementById(\"indexerHit-" + i + "\");\n" + "var els = [];\n" + "while (a) {\n" + " a.open = 'true';\n"
+ + " a = a.parentNode;\n" + "}"); //$NON-NLS-2$
+ }
+ }
+ } catch (ClassCastException e) {
+ // ignores as the value is not of required type
+ }
+ }
}
} while (term != null);