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