19 package org.sleuthkit.autopsy.modules.leappanalyzers;
21 import com.fasterxml.jackson.databind.MappingIterator;
22 import com.fasterxml.jackson.dataformat.csv.CsvMapper;
23 import com.fasterxml.jackson.dataformat.csv.CsvParser;
24 import com.fasterxml.jackson.dataformat.csv.CsvSchema;
25 import com.google.common.collect.ImmutableMap;
27 import java.io.FileNotFoundException;
28 import java.io.IOException;
29 import java.io.UncheckedIOException;
30 import java.nio.file.Files;
31 import java.nio.file.Path;
32 import java.text.DateFormat;
33 import java.text.ParseException;
34 import java.text.SimpleDateFormat;
35 import java.util.List;
36 import java.util.ArrayList;
37 import java.util.Arrays;
38 import java.util.Collection;
39 import java.util.Collections;
40 import java.util.HashMap;
41 import java.util.HashSet;
42 import static java.util.Locale.US;
45 import java.util.logging.Level;
46 import java.util.stream.Collectors;
47 import java.util.stream.IntStream;
48 import java.util.stream.Stream;
49 import javax.xml.parsers.DocumentBuilder;
50 import javax.xml.parsers.DocumentBuilderFactory;
51 import javax.xml.parsers.ParserConfigurationException;
52 import org.apache.commons.collections4.CollectionUtils;
53 import org.apache.commons.collections4.MapUtils;
54 import org.apache.commons.io.FilenameUtils;
55 import org.apache.commons.lang3.StringUtils;
56 import org.openide.util.NbBundle;
57 import org.openide.util.NbBundle.Messages;
72 import org.
sleuthkit.datamodel.Blackboard.BlackboardException;
75 import org.
sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE;
80 import org.
sleuthkit.datamodel.blackboardutils.CommunicationArtifactsHelper;
81 import org.
sleuthkit.datamodel.blackboardutils.CommunicationArtifactsHelper.CallMediaType;
82 import org.
sleuthkit.datamodel.blackboardutils.CommunicationArtifactsHelper.CommunicationDirection;
83 import org.
sleuthkit.datamodel.blackboardutils.CommunicationArtifactsHelper.MessageReadStatus;
84 import org.
sleuthkit.datamodel.blackboardutils.GeoArtifactsHelper;
85 import org.
sleuthkit.datamodel.blackboardutils.attributes.GeoTrackPoints;
86 import org.
sleuthkit.datamodel.blackboardutils.attributes.GeoTrackPoints.TrackPoint;
87 import org.
sleuthkit.datamodel.blackboardutils.attributes.GeoWaypoints;
88 import org.
sleuthkit.datamodel.blackboardutils.attributes.GeoWaypoints.Waypoint;
89 import org.
sleuthkit.datamodel.blackboardutils.attributes.MessageAttachments;
90 import org.
sleuthkit.datamodel.blackboardutils.attributes.MessageAttachments.FileAttachment;
91 import org.w3c.dom.Document;
92 import org.w3c.dom.NamedNodeMap;
93 import org.w3c.dom.NodeList;
94 import org.xml.sax.SAXException;
128 BlackboardAttribute.Type getAttributeType() {
135 String getColumnName() {
142 boolean isRequired() {
158 .put(
"TSK_IP_DHCP",
"DHCP Information")
162 .put(
"zapya.tsv",
"message")
163 .put(
"sms messages.tsv",
"message")
164 .put(
"mms messages.tsv",
"message")
165 .put(
"viber - messages.tsv",
"message")
166 .put(
"viber - contacts.tsv",
"contact")
167 .put(
"viber - call logs.tsv",
"calllog")
168 .put(
"xender file transfer - messages.tsv",
"message")
169 .put(
"xender file transfer - contacts.tsv",
"contact")
170 .put(
"whatsapp - contacts.tsv",
"contact")
171 .put(
"whatsapp - group call logs.tsv",
"calllog")
172 .put(
"whatsapp - single call logs.tsv",
"calllog")
173 .put(
"whatsapp - messages logs.tsv",
"message")
174 .put(
"shareit file transfer.tsv",
"message")
175 .put(
"tangomessages messages.tsv",
"message")
176 .put(
"contacts.tsv",
"contact")
177 .put(
"imo - accountid.tsv",
"contact")
178 .put(
"imo - messages.tsv",
"message")
179 .put(
"textnow - contacts.tsv",
"contact")
180 .put(
"textnow - messages.tsv",
"message")
181 .put(
"line - messages.tsv",
"message")
182 .put(
"line - contacts.tsv",
"contact")
183 .put(
"line - calllogs.tsv",
"calllog")
184 .put(
"skype - messages logs.tsv",
"message")
185 .put(
"skype - contacts.tsv",
"contact")
186 .put(
"skype - call logs.tsv",
"calllog")
187 .put(
"facebook messenger - chats.tsv",
"message")
188 .put(
"facebook messenger - contacts.tsv",
"contact")
189 .put(
"facebook messenger - calls.tsv",
"calllog")
190 .put(
"call logs2.tsv",
"calllog")
191 .put(
"call logs.tsv",
"calllog")
192 .put(
"oruxmaps tracks.tsv",
"trackpoint")
193 .put(
"google map locations.tsv",
"route")
194 .put(
"Contacts.tsv",
"contact")
195 .put(
"sms - imessage.tsv",
"message")
196 .put(
"call history.tsv",
"calllog")
202 this.tsvFiles =
new HashMap<>();
204 this.tsvFileArtifactComments =
new HashMap<>();
205 this.tsvFileAttributes =
new HashMap<>();
219 "LeappFileProcessor.error.running.Leapp=Error running Leapp, see log file.",
220 "LeappFileProcessor.error.creating.output.dir=Error creating Leapp module output directory.",
221 "LeappFileProcessor.starting.Leapp=Starting Leapp",
222 "LeappFileProcessor.running.Leapp=Running Leapp",
223 "LeappFileProcessor.has.run=Leapp",
224 "LeappFileProcessor.Leapp.cancelled=Leapp run was canceled",
225 "LeappFileProcessor.completed=Leapp Processing Completed",
226 "LeappFileProcessor.findTsv=Finding all Leapp ouput",
227 "LeappFileProcessor.error.reading.Leapp.directory=Error reading Leapp Output Directory"
235 progress.
progress(Bundle.LeappFileProcessor_findTsv());
236 List<String> LeappTsvOutputFiles =
findTsvFiles(moduleOutputPath);
238 }
catch (IngestModuleException ex) {
239 logger.log(Level.SEVERE, String.format(
"Error trying to process Leapp output files in directory %s. ", moduleOutputPath.toString()), ex);
252 progress.
progress(Bundle.LeappFileProcessor_findTsv());
253 List<String> LeappTsvOutputFiles =
findTsvFiles(moduleOutputPath);
255 }
catch (IngestModuleException ex) {
256 logger.log(Level.SEVERE, String.format(
"Error trying to process Leapp output files in directory %s. ", moduleOutputPath.toString()), ex);
267 private List<String>
findTsvFiles(Path LeappOutputDir)
throws IngestModuleException {
268 List<String> allTsvFiles;
269 List<String> foundTsvFiles =
new ArrayList<>();
271 try (Stream<Path> walk = Files.walk(LeappOutputDir)) {
273 allTsvFiles = walk.map(x -> x.toString())
274 .filter(f -> f.toLowerCase().endsWith(
".tsv")).collect(Collectors.toList());
276 for (String tsvFile : allTsvFiles) {
277 if (tsvFiles.containsKey(FilenameUtils.getName(tsvFile.toLowerCase()))) {
278 foundTsvFiles.add(tsvFile);
282 }
catch (IOException | UncheckedIOException e) {
283 throw new IngestModuleException(Bundle.LeappFileProcessor_error_reading_Leapp_directory() + LeappOutputDir.toString(), e);
286 return foundTsvFiles;
292 logger.log(Level.INFO,
"Leapp File processing module run was cancelled");
311 "LeappFileProcessor.tsvProcessed=Processing LEAPP output file: {0}"
316 for (
int i = 0; i < LeappFilesToProcess.size(); i++) {
321 String LeappFileName = LeappFilesToProcess.get(i);
322 String fileName = FilenameUtils.getName(LeappFileName);
323 progress.
progress(Bundle.LeappFileProcessor_tsvProcessed(fileName), i);
325 File LeappFile =
new File(LeappFileName);
326 if (tsvFileAttributes.containsKey(fileName)) {
327 List<TsvColumn> attrList = tsvFileAttributes.get(fileName);
331 processFile(LeappFile, attrList, fileName, artifactType, dataSource);
332 }
catch (TskCoreException | IOException ex) {
333 logger.log(Level.SEVERE, String.format(
"Error processing file at %s", LeappFile.toString()), ex);
340 private void processFile(File LeappFile, List<TsvColumn> attrList, String fileName,
341 BlackboardArtifact.Type artifactType, Content dataSource)
throws FileNotFoundException, IOException, IngestModuleException,
344 String trackpointSegmentName = null;
345 GeoTrackPoints pointList =
new GeoTrackPoints();
346 AbstractFile geoAbstractFile = null;
348 if (LeappFile == null || !LeappFile.exists() || fileName == null) {
349 logger.log(Level.WARNING, String.format(
"Leap file: %s is null or does not exist", LeappFile != null ? LeappFile.toString() :
"<null>"));
351 }
else if (attrList == null || artifactType == null || dataSource == null) {
352 logger.log(Level.WARNING, String.format(
"attribute list, artifact type or dataSource not provided for %s", LeappFile.toString()));
356 List<BlackboardArtifact> bbartifacts =
new ArrayList<>();
359 try (MappingIterator<List<String>> iterator =
new CsvMapper()
360 .enable(CsvParser.Feature.WRAP_AS_ARRAY)
361 .readerFor(List.class)
362 .with(CsvSchema.emptySchema().withColumnSeparator(
'\t'))
363 .readValues(LeappFile)) {
365 if (iterator.hasNext()) {
366 List<String> headerItems = iterator.next();
367 Map<String, Integer> columnIndexes = IntStream.range(0, headerItems.size())
368 .mapToObj(idx -> idx)
369 .collect(Collectors.toMap(
370 idx -> headerItems.get(idx) == null ? null : headerItems.get(idx).trim().toLowerCase(),
372 (val1, val2) -> val1));
375 while (iterator.hasNext()) {
376 List<String> columnItems = iterator.next();
377 Collection<BlackboardAttribute> bbattributes =
processReadLine(columnItems, columnIndexes, attrList, fileName, lineNum);
379 if (!bbattributes.isEmpty()) {
380 switch (ACCOUNT_RELATIONSHIPS.getOrDefault(fileName.toLowerCase(),
"norelationship").toLowerCase()) {
394 geoAbstractFile =
createTrackpoint(bbattributes, dataSource, fileName, trackpointSegmentName, pointList);
398 if (bbartifact != null) {
399 bbartifacts.add(bbartifact);
411 if (ACCOUNT_RELATIONSHIPS.getOrDefault(fileName.toLowerCase(),
"norelationship").toLowerCase().equals(
"trackpoint")) {
415 throw new IngestModuleException(Bundle.LeappFileProcessor_cannot_create_message_relationship() + ex.getLocalizedMessage(), ex);
418 if (!bbartifacts.isEmpty()) {
419 postArtifacts(bbartifacts);
424 "LeappFileProcessor.cannot.create.waypoint.relationship=Cannot create TSK_WAYPOINT artifact."
426 private void createRoute(Collection<BlackboardAttribute> bbattributes, Content dataSource, String fileName)
throws IngestModuleException {
428 Double startLatitude = Double.valueOf(0);
429 Double startLongitude = Double.valueOf(0);
430 Double endLatitude = Double.valueOf(0);
431 Double endLongitude = Double.valueOf(0);
432 Double zeroValue = Double.valueOf(0);
433 String destinationName =
"";
434 String locationName =
"";
435 Long dateTime = Long.valueOf(0);
436 Collection<BlackboardAttribute> otherAttributes =
new ArrayList<>();
437 String sourceFile = null;
438 AbstractFile absFile;
442 for (BlackboardAttribute bba : bbattributes) {
443 switch (bba.getAttributeType().getTypeName()) {
444 case "TSK_GEO_LATITUDE_START":
445 startLatitude = bba.getValueDouble();
447 case "TSK_GEO_LONGITUDE_START":
448 startLongitude = bba.getValueDouble();
450 case "TSK_GEO_LATITUDE_END":
451 startLatitude = bba.getValueDouble();
453 case "TSK_GEO_LONGITUDE_END":
454 startLongitude = bba.getValueDouble();
457 dateTime = bba.getValueLong();
460 destinationName = bba.getValueString();
463 locationName = bba.getValueString();
465 case "TSK_TEXT_FILE":
466 sourceFile = bba.getValueString();
469 comment = bba.getValueString();
472 otherAttributes.add(bba);
477 if (absFile == null) {
478 absFile = (AbstractFile) dataSource;
480 GeoWaypoints waypointList =
new GeoWaypoints();
481 waypointList.addPoint(
new Waypoint(startLatitude, startLongitude, zeroValue,
""));
482 waypointList.addPoint(
new Waypoint(endLatitude, endLongitude, zeroValue, locationName));
486 throw new IngestModuleException(Bundle.LeappFileProcessor_cannot_create_waypoint_relationship() + ex.getLocalizedMessage(), ex);
492 "LeappFileProcessor.cannot.create.trackpoint.relationship=Cannot create TSK_TRACK_POINT artifact."
494 private AbstractFile
createTrackpoint(Collection<BlackboardAttribute> bbattributes, Content dataSource, String fileName, String trackpointSegmentName, GeoTrackPoints pointList)
throws IngestModuleException {
496 Double latitude = Double.valueOf(0);
497 Double longitude = Double.valueOf(0);
498 Double altitude = Double.valueOf(0);
499 Double zeroValue = Double.valueOf(0);
500 String segmentName = null;
501 Long dateTime = Long.valueOf(0);
502 Collection<BlackboardAttribute> otherAttributes =
new ArrayList<>();
503 String sourceFile = null;
504 String comment = null;
505 AbstractFile absFile = null;
508 for (BlackboardAttribute bba : bbattributes) {
509 switch (bba.getAttributeType().getTypeName()) {
510 case "TSK_GEO_LATITUDE":
511 latitude = bba.getValueDouble();
513 case "TSK_GEO_LONGITUDE":
514 longitude = bba.getValueDouble();
516 case "TSK_GEO_ALTITUDE":
517 altitude = bba.getValueDouble();
520 dateTime = bba.getValueLong();
523 segmentName = bba.getValueString();
525 case "TSK_TEXT_FILE":
526 sourceFile = bba.getValueString();
529 comment = bba.getValueString();
530 otherAttributes.add(bba);
533 otherAttributes.add(bba);
538 if (absFile == null) {
539 absFile = (AbstractFile) dataSource;
541 if ((trackpointSegmentName == null) || (trackpointSegmentName.equals(segmentName))) {
542 pointList.addPoint(
new TrackPoint(latitude, longitude, altitude, segmentName, zeroValue, zeroValue, zeroValue, dateTime));
545 pointList.addPoint(
new TrackPoint(latitude, longitude, altitude, segmentName, zeroValue, zeroValue, zeroValue, dateTime));
549 throw new IngestModuleException(Bundle.LeappFileProcessor_cannot_create_trackpoint_relationship() + ex.getLocalizedMessage(), ex);
557 "LeappFileProcessor.cannot.create.message.relationship=Cannot create TSK_MESSAGE Relationship."
559 private void createMessageRelationship(Collection<BlackboardAttribute> bbattributes, Content dataSource, String fileName)
throws IngestModuleException {
561 String messageType = null;
562 String alternateId = null;
563 CommunicationDirection communicationDirection = CommunicationDirection.UNKNOWN;
564 String senderId = null;
565 String receipentId = null;
566 String[] receipentIdList = null;
567 Long dateTime = Long.valueOf(0);
568 MessageReadStatus messageStatus = MessageReadStatus.UNKNOWN;
569 String subject = null;
570 String messageText = null;
571 String threadId = null;
572 List<BlackboardAttribute> otherAttributes =
new ArrayList<>();
573 List<FileAttachment> fileAttachments =
new ArrayList<>();
574 String sourceFile = null;
575 MessageAttachments messageAttachments;
578 for (BlackboardAttribute bba : bbattributes) {
579 switch (bba.getAttributeType().getTypeName()) {
580 case "TSK_DIRECTION":
581 if (bba.getValueString().toLowerCase().equals(
"outgoing")) {
582 communicationDirection = CommunicationDirection.OUTGOING;
583 }
else if (bba.getValueString().toLowerCase().equals(
"incoming")) {
584 communicationDirection = CommunicationDirection.INCOMING;
587 case "TSK_PHONE_NUMBER_FROM":
588 if (!bba.getValueString().isEmpty()) {
589 senderId = bba.getValueString();
592 case "TSK_PHONE_NUMBER_TO":
593 if (!bba.getValueString().isEmpty()) {
594 receipentIdList = bba.getValueString().split(
",", 0);
598 dateTime = bba.getValueLong();
601 messageType = bba.getValueString();
603 case "TSK_ATTACHMENTS":
604 if (!bba.getValueString().isEmpty()) {
608 case "TSK_TEXT_FILE":
609 sourceFile = bba.getValueString();
611 case "TSK_READ_STATUS":
612 if (bba.getValueInt() == 1) {
613 messageStatus = MessageReadStatus.READ;
615 messageStatus = MessageReadStatus.UNREAD;
619 messageText = bba.getValueString();
622 subject = bba.getValueString();
625 alternateId = bba.getValueString();
626 otherAttributes.add(bba);
629 otherAttributes.add(bba);
634 if (absFile == null) {
635 absFile = (AbstractFile) dataSource;
637 CommunicationArtifactsHelper accountHelper;
639 if (alternateId == null) {
646 BlackboardArtifact messageArtifact = accountHelper.addMessage(messageType, communicationDirection, senderId,
647 receipentId, dateTime, messageStatus, subject,
648 messageText, threadId, otherAttributes);
649 if (!fileAttachments.isEmpty()) {
650 messageAttachments =
new MessageAttachments(fileAttachments,
new ArrayList<>());
651 accountHelper.addAttachments(messageArtifact, messageAttachments);
654 throw new IngestModuleException(Bundle.LeappFileProcessor_cannot_create_message_relationship() + ex.getLocalizedMessage(), ex);
660 "LeappFileProcessor.cannot.create.contact.relationship=Cannot create TSK_CONTACT Relationship."
662 private void createContactRelationship(Collection<BlackboardAttribute> bbattributes, Content dataSource, String fileName)
throws IngestModuleException {
664 String alternateId = null;
665 String contactName = null;
666 String phoneNumber = null;
667 String homePhoneNumber = null;
668 String mobilePhoneNumber = null;
669 String emailAddr = null;
670 List<BlackboardAttribute> otherAttributes =
new ArrayList<>();
671 String sourceFile = null;
674 for (BlackboardAttribute bba : bbattributes) {
675 switch (bba.getAttributeType().getTypeName()) {
676 case "TSK_PHONE_NUMBER":
677 if (!bba.getValueString().isEmpty()) {
678 phoneNumber = bba.getValueString();
682 if (!bba.getValueString().isEmpty()) {
683 contactName = bba.getValueString();
686 case "TSK_TEXT_FILE":
687 sourceFile = bba.getValueString();
689 case "TSK_PHONE_NUMBER_HOME":
690 homePhoneNumber = bba.getValueString();
692 case "TSK_PHONE_NUMBER_MOBILE":
693 mobilePhoneNumber = bba.getValueString();
696 emailAddr = bba.getValueString();
699 alternateId = bba.getValueString();
700 otherAttributes.add(bba);
703 otherAttributes.add(bba);
708 if (absFile == null) {
709 absFile = (AbstractFile) dataSource;
712 if (accountType != null) {
714 CommunicationArtifactsHelper accountHelper;
715 if (alternateId == null) {
722 BlackboardArtifact messageArtifact = accountHelper.addContact(contactName, phoneNumber, homePhoneNumber, mobilePhoneNumber, emailAddr, otherAttributes);
725 throw new IngestModuleException(Bundle.LeappFileProcessor_cannot_create_contact_relationship() + ex.getLocalizedMessage(), ex);
730 "LeappFileProcessor.cannot.create.calllog.relationship=Cannot create TSK_CALLLOG Relationship."
732 private void createCalllogRelationship(Collection<BlackboardAttribute> bbattributes, Content dataSource, String fileName)
throws IngestModuleException {
734 String callerId = null;
735 String alternateId = null;
736 List<String> calleeId = Arrays.asList();
737 CommunicationDirection communicationDirection = CommunicationDirection.UNKNOWN;
738 Long startDateTime = Long.valueOf(0);
739 Long endDateTime = Long.valueOf(0);
740 CallMediaType mediaType = CallMediaType.UNKNOWN;
741 List<BlackboardAttribute> otherAttributes =
new ArrayList<>();
742 String sourceFile = null;
745 for (BlackboardAttribute bba : bbattributes) {
746 switch (bba.getAttributeType().getTypeName()) {
747 case "TSK_TEXT_FILE":
748 sourceFile = bba.getValueString();
750 case "TSK_DATETIME_START":
751 startDateTime = bba.getValueLong();
753 case "TSK_DATETIME_END":
754 startDateTime = bba.getValueLong();
756 case "TSK_DIRECTION":
757 if (bba.getValueString().toLowerCase().equals(
"outgoing")) {
758 communicationDirection = CommunicationDirection.OUTGOING;
759 }
else if (bba.getValueString().toLowerCase().equals(
"incoming")) {
760 communicationDirection = CommunicationDirection.INCOMING;
763 case "TSK_PHONE_NUMBER_FROM":
764 if (!bba.getValueString().isEmpty()) {
765 callerId = bba.getValueString();
768 case "TSK_PHONE_NUMBER_TO":
769 if (!bba.getValueString().isEmpty()) {
770 String[] calleeTempList = bba.getValueString().split(
",", 0);
771 calleeId = Arrays.asList(calleeTempList);
775 alternateId = bba.getValueString();
776 otherAttributes.add(bba);
779 otherAttributes.add(bba);
784 if (calleeId.isEmpty() && communicationDirection == CommunicationDirection.OUTGOING && callerId != null) {
785 String[] calleeTempList = callerId.split(
",", 0);
786 calleeId = Arrays.asList(calleeTempList);
790 if (absFile == null) {
791 absFile = (AbstractFile) dataSource;
794 CommunicationArtifactsHelper accountHelper;
795 if (accountType != null) {
802 accountHelper.addCalllog(communicationDirection, callerId, calleeId, startDateTime, endDateTime, mediaType, otherAttributes);
804 throw new IngestModuleException(Bundle.LeappFileProcessor_cannot_create_calllog_relationship() + ex.getLocalizedMessage(), ex);
810 switch (AccountTypeName.toLowerCase()) {
812 return Account.Type.ZAPYA;
813 case "sms messages.tsv":
814 return Account.Type.PHONE;
816 return Account.Type.PHONE;
817 case "imo - accountid.tsv":
818 return Account.Type.IMO;
819 case "imo - messages.tsv":
820 return Account.Type.IMO;
821 case "textnow - contacts.tsv":
822 return Account.Type.TEXTNOW;
823 case "textnow - messages.tsv":
824 return Account.Type.TEXTNOW;
825 case "mms messages.tsv":
826 return Account.Type.PHONE;
827 case "viber - call logs.tsv":
828 return Account.Type.VIBER;
829 case "viber - contacts.tsv":
830 return Account.Type.VIBER;
831 case "viber - messages.tsv":
832 return Account.Type.VIBER;
833 case "xender file transfer - messages.tsv":
834 return Account.Type.XENDER;
835 case "xender file transfer - contacts.tsv":
836 return Account.Type.XENDER;
837 case "whatsapp - single call logs.tsv":
838 return Account.Type.WHATSAPP;
839 case "whatsapp - messages logs.tsv":
840 return Account.Type.WHATSAPP;
841 case "whatsapp - group call logs.tsv":
842 return Account.Type.WHATSAPP;
843 case "whatsapp - contacts.tsv":
844 return Account.Type.WHATSAPP;
845 case "tangomessages messages.tsv":
846 return Account.Type.TANGO;
847 case "shareit file transfer.tsv":
848 return Account.Type.SHAREIT;
849 case "line - calllogs.tsv":
850 return Account.Type.LINE;
851 case "line - contacts.tsv":
852 return Account.Type.LINE;
853 case "line - messages.tsv":
854 return Account.Type.LINE;
855 case "skype - call logs.tsv":
856 return Account.Type.SKYPE;
857 case "skype - contacts.tsv":
858 return Account.Type.SKYPE;
859 case "skype - messages logs.tsv":
860 return Account.Type.SKYPE;
861 case "facebook messenger - calls.tsv":
862 return Account.Type.FACEBOOK;
863 case "facebook messenger - contacts.tsv":
864 return Account.Type.FACEBOOK;
865 case "facebook messenger - chats.tsv":
866 return Account.Type.FACEBOOK;
867 case "call logs2.tsv":
868 return Account.Type.PHONE;
869 case "call logs.tsv":
870 return Account.Type.PHONE;
871 case "sms - imessage.tsv":
872 return Account.Type.PHONE;
874 return Account.Type.PHONE;
895 private Collection<BlackboardAttribute>
processReadLine(List<String> lineValues, Map<String, Integer> columnIndexes,
896 List<TsvColumn> attrList, String fileName,
int lineNum)
throws IngestModuleException {
898 if (MapUtils.isEmpty(columnIndexes) || CollectionUtils.isEmpty(lineValues)
899 || (lineValues.size() == 1 && StringUtils.isEmpty(lineValues.get(0)))) {
900 return Collections.emptyList();
901 }
else if (lineValues.size() != columnIndexes.size()) {
902 logger.log(Level.WARNING, String.format(
903 "Row at line number %d in file %s has %d columns when %d were expected based on the header row.",
904 lineNum, fileName, lineValues.size(), columnIndexes.size()));
905 return Collections.emptyList();
908 List<BlackboardAttribute> attrsToRet =
new ArrayList<>();
910 if (colAttr.getAttributeType() == null) {
915 Integer columnIdx = columnIndexes.get(colAttr.getColumnName());
916 if (columnIdx == null) {
917 logger.log(Level.WARNING, String.format(
"No column mapping found for %s in file %s. Omitting column.", colAttr.getColumnName(), fileName));
921 String value = (columnIdx >= lineValues.size() || columnIdx < 0) ? null : lineValues.get(columnIdx);
923 logger.log(Level.WARNING, String.format(
"No value found for column %s at line %d in file %s. Omitting row.", colAttr.getColumnName(), lineNum, fileName));
924 return Collections.emptyList();
929 BlackboardAttribute attr =
getAttribute(colAttr.getAttributeType(), formattedValue, fileName);
931 logger.log(Level.WARNING, String.format(
"Blackboard attribute could not be parsed column %s at line %d in file %s. Omitting row.", colAttr.getColumnName(), lineNum, fileName));
932 return Collections.emptyList();
934 attrsToRet.add(attr);
937 if (tsvFileArtifactComments.containsKey(fileName)) {
938 attrsToRet.add(
new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_COMMENT, moduleName, tsvFileArtifactComments.get(fileName)));
954 if (colAttr.getAttributeType().getTypeName().equals(
"TSK_DOMAIN")) {
964 private static final DateFormat
TIMESTAMP_FORMAT =
new SimpleDateFormat(
"yyyy-MM-d HH:mm:ss", US);
977 private BlackboardAttribute
getAttribute(BlackboardAttribute.Type attrType, String value, String fileName) {
978 if (attrType == null || value == null) {
979 logger.log(Level.WARNING, String.format(
"Unable to parse attribute type %s for value '%s' in fileName %s",
980 attrType == null ?
"<null>" : attrType.toString(),
981 value == null ?
"<null>" : value,
982 fileName == null ?
"<null>" : fileName));
986 switch (attrType.getValueType()) {
990 (v) ->
new BlackboardAttribute(attrType, moduleName, v));
992 return parseAttrValue(value.trim(), attrType, fileName,
true,
false,
993 (v) ->
new BlackboardAttribute(attrType, moduleName, Double.valueOf(v).intValue()));
995 return parseAttrValue(value.trim(), attrType, fileName,
true,
false,
996 (v) ->
new BlackboardAttribute(attrType, moduleName, Double.valueOf(v).longValue()));
998 return parseAttrValue(value.trim(), attrType, fileName,
true,
false,
999 (v) ->
new BlackboardAttribute(attrType, moduleName, Double.valueOf(v)));
1001 return parseAttrValue(value.trim(), attrType, fileName,
true,
false,
1002 (v) ->
new BlackboardAttribute(attrType, moduleName,
new byte[]{Byte.valueOf(v)}));
1004 return parseAttrValue(value.trim(), attrType, fileName,
true,
true,
1005 (v) ->
new BlackboardAttribute(attrType, moduleName, TIMESTAMP_FORMAT.parse(v).getTime() / 1000));
1008 logger.log(Level.WARNING, String.format(
"Attribute Type %s for file %s not defined.", attrType, fileName));
1029 BlackboardAttribute
apply(String orig)
throws ParseException, NumberFormatException;
1050 String sanitizedValue = value.replaceAll(
"\\p{C}",
"");
1052 if (blankIsNull && StringUtils.isBlank(sanitizedValue)) {
1056 if (zeroIsNull && sanitizedValue.matches(
"^\\s*[0\\.]*\\s*$")) {
1061 return valueConverter.
apply(sanitizedValue);
1062 }
catch (NumberFormatException | ParseException ex) {
1063 logger.log(Level.WARNING, String.format(
"Unable to format '%s' as value type %s while converting to attributes from %s.", sanitizedValue, attrType.getValueType().getLabel(), fileName), ex);
1071 @NbBundle.Messages({
1072 "LeappFileProcessor.cannot.load.artifact.xml=Cannot load xml artifact file.",
1073 "LeappFileProcessor.cannotBuildXmlParser=Cannot buld an XML parser.",
1074 "LeappFileProcessor_cannotParseXml=Cannot Parse XML file.",
1075 "LeappFileProcessor.postartifacts_error=Error posting Blackboard Artifact",
1076 "LeappFileProcessor.error.creating.new.artifacts=Error creating new artifacts."
1082 File f =
new File(path);
1083 DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
1084 DocumentBuilder db = dbf.newDocumentBuilder();
1085 xmlinput = db.parse(f);
1087 }
catch (IOException e) {
1088 throw new IngestModuleException(Bundle.LeappFileProcessor_cannot_load_artifact_xml() + e.getLocalizedMessage(), e);
1089 }
catch (ParserConfigurationException pce) {
1090 throw new IngestModuleException(Bundle.LeappFileProcessor_cannotBuildXmlParser() + pce.getLocalizedMessage(), pce);
1091 }
catch (SAXException sxe) {
1092 throw new IngestModuleException(Bundle.LeappFileProcessor_cannotParseXml() + sxe.getLocalizedMessage(), sxe);
1103 NodeList nlist = xmlinput.getElementsByTagName(
"FileName");
1105 for (
int i = 0; i < nlist.getLength(); i++) {
1106 NamedNodeMap nnm = nlist.item(i).getAttributes();
1107 tsvFiles.put(nnm.getNamedItem(
"filename").getNodeValue().toLowerCase(), nnm.getNamedItem(
"description").getNodeValue());
1115 NodeList artifactNlist = xmlinput.getElementsByTagName(
"ArtifactName");
1116 for (
int k = 0; k < artifactNlist.getLength(); k++) {
1117 NamedNodeMap nnm = artifactNlist.item(k).getAttributes();
1118 String artifactName = nnm.getNamedItem(
"artifactname").getNodeValue();
1119 String comment = nnm.getNamedItem(
"comment").getNodeValue();
1120 String parentName = artifactNlist.item(k).getParentNode().getAttributes().getNamedItem(
"filename").getNodeValue();
1122 BlackboardArtifact.Type foundArtifactType = null;
1125 }
catch (TskCoreException ex) {
1126 logger.log(Level.SEVERE, String.format(
"There was an issue that arose while trying to fetch artifact type for %s.", artifactName), ex);
1129 if (foundArtifactType == null) {
1130 logger.log(Level.SEVERE, String.format(
"No known artifact mapping found for [artifact: %s, %s]",
1136 if (!comment.toLowerCase().matches(
"null")) {
1137 tsvFileArtifactComments.put(parentName, comment);
1144 return String.format(
"file: %s, filename: %s",
1145 this.xmlFile == null ?
"<null>" : this.xmlFile,
1146 fileName == null ?
"<null>" : fileName);
1150 return String.format(
"attribute: %s %s",
1151 attributeName == null ?
"<null>" : attributeName,
1157 NodeList attributeNlist = xmlinput.getElementsByTagName(
"AttributeName");
1158 for (
int k = 0; k < attributeNlist.getLength(); k++) {
1159 NamedNodeMap nnm = attributeNlist.item(k).getAttributes();
1160 String attributeName = nnm.getNamedItem(
"attributename").getNodeValue();
1162 if (!attributeName.toLowerCase().matches(
"null")) {
1163 String columnName = nnm.getNamedItem(
"columnName").getNodeValue();
1164 String required = nnm.getNamedItem(
"required").getNodeValue();
1165 String parentName = attributeNlist.item(k).getParentNode().getParentNode().getAttributes().getNamedItem(
"filename").getNodeValue();
1167 BlackboardAttribute.Type foundAttrType = null;
1170 }
catch (TskCoreException ex) {
1171 logger.log(Level.SEVERE, String.format(
"There was an issue that arose while trying to fetch attribute type for %s.", attributeName), ex);
1174 if (foundAttrType == null) {
1175 logger.log(Level.SEVERE, String.format(
"No known attribute mapping found for [%s]",
getXmlAttrIdentifier(parentName, attributeName)));
1178 if (required != null && required.compareToIgnoreCase(
"yes") != 0 && required.compareToIgnoreCase(
"no") != 0) {
1179 logger.log(Level.SEVERE, String.format(
"Required value %s did not match 'yes' or 'no' for [%s]",
1183 if (columnName == null) {
1184 logger.log(Level.SEVERE, String.format(
"No column name provided for [%s]",
getXmlAttrIdentifier(parentName, attributeName)));
1186 }
else if (columnName.trim().length() != columnName.length()) {
1187 logger.log(Level.SEVERE, String.format(
"Column name '%s' starts or ends with whitespace for [%s]", columnName,
getXmlAttrIdentifier(parentName, attributeName)));
1189 }
else if (columnName.matches(
"[^ \\S]")) {
1190 logger.log(Level.SEVERE, String.format(
"Column name '%s' contains invalid characters [%s]", columnName,
getXmlAttrIdentifier(parentName, attributeName)));
1196 columnName.trim().toLowerCase(),
1197 "yes".compareToIgnoreCase(required) == 0);
1199 if (tsvFileAttributes.containsKey(parentName)) {
1200 List<TsvColumn> attrList = tsvFileAttributes.get(parentName);
1201 attrList.add(thisCol);
1202 tsvFileAttributes.replace(parentName, attrList);
1204 List<TsvColumn> attrList =
new ArrayList<>();
1205 attrList.add(thisCol);
1206 tsvFileAttributes.put(parentName, attrList);
1225 private BlackboardArtifact
createArtifactWithAttributes(BlackboardArtifact.Type artType, Content dataSource, Collection<BlackboardAttribute> bbattributes) {
1227 switch (artType.getCategory()) {
1229 return dataSource.newDataArtifact(artType, bbattributes);
1230 case ANALYSIS_RESULT:
1231 return dataSource.newAnalysisResult(artType, Score.SCORE_UNKNOWN, null, null, null, bbattributes).getAnalysisResult();
1233 logger.log(Level.SEVERE, String.format(
"Unknown category type: %s", artType.getCategory().getDisplayName()));
1236 }
catch (TskException ex) {
1237 logger.log(Level.WARNING, Bundle.LeappFileProcessor_error_creating_new_artifacts(), ex);
1248 void postArtifacts(Collection<BlackboardArtifact> artifacts) {
1249 if (artifacts == null || artifacts.isEmpty()) {
1254 Case.getCurrentCase().getSleuthkitCase().getBlackboard().postArtifacts(artifacts, moduleName, context.
getJobId());
1255 }
catch (Blackboard.BlackboardException ex) {
1256 logger.log(Level.SEVERE, Bundle.LeappFileProcessor_postartifacts_error(), ex);
1279 static List<AbstractFile> findLeappFilesToProcess(Content dataSource) {
1281 List<AbstractFile> leappFiles =
new ArrayList<>();
1283 FileManager fileManager = getCurrentCase().getServices().getFileManager();
1287 leappFiles = fileManager.
findFiles(dataSource,
"%",
"/");
1288 }
catch (TskCoreException ex) {
1289 logger.log(Level.WARNING,
"No files found to process");
1293 List<AbstractFile> leappFilesToProcess =
new ArrayList<>();
1294 for (AbstractFile leappFile : leappFiles) {
1295 if (((leappFile.getLocalAbsPath() != null)
1296 && !leappFile.isVirtual())
1297 && leappFile.getNameExtension() != null
1298 && ALLOWED_EXTENSIONS.contains(leappFile.getNameExtension().toLowerCase())) {
1299 leappFilesToProcess.add(leappFile);
1303 return leappFilesToProcess;
1312 for (Map.Entry<String, String> customArtifact : CUSTOM_ARTIFACT_MAP.entrySet()) {
1313 String artifactName = customArtifact.getKey();
1314 String artifactDescription = customArtifact.getValue();
1317 BlackboardArtifact.Type customArtifactType = blkBoard.getOrAddArtifactType(artifactName, artifactDescription);
1318 }
catch (Blackboard.BlackboardException ex) {
1319 logger.log(Level.WARNING, String.format(
"Failed to create custom artifact type %s.", artifactName), ex);
1326 if (fileNamePath == null) {
1330 List<AbstractFile> files;
1332 String fileName = FilenameUtils.getName(fileNamePath);
1333 String filePath = FilenameUtils.normalize(FilenameUtils.getPath(fileNamePath),
true);
1338 files = fileManager.
findFiles(dataSource, fileName);
1340 }
catch (TskCoreException ex) {
1341 logger.log(Level.WARNING,
"Unable to find prefetch files.", ex);
1345 for (AbstractFile pFile : files) {
1347 if (pFile.getParentPath().toLowerCase().endsWith(filePath.toLowerCase())) {
void createMessageRelationship(Collection< BlackboardAttribute > bbattributes, Content dataSource, String fileName)
final IngestJobContext context
BlackboardArtifact createArtifactWithAttributes(BlackboardArtifact.Type artType, Content dataSource, Collection< BlackboardAttribute > bbattributes)
String getXmlFileIdentifier(String fileName)
final BlackboardAttribute.Type attributeType
FileManager getFileManager()
LeappFileProcessor(String xmlFile, String moduleName, IngestJobContext context)
void createContactRelationship(Collection< BlackboardAttribute > bbattributes, Content dataSource, String fileName)
static String extractDomain(String urlString)
List< String > findTsvFiles(Path LeappOutputDir)
List< AbstractFile > findFiles(String fileName)
void createCalllogRelationship(Collection< BlackboardAttribute > bbattributes, Content dataSource, String fileName)
static final Logger logger
void processFile(File LeappFile, List< TsvColumn > attrList, String fileName, BlackboardArtifact.Type artifactType, Content dataSource)
static final DateFormat TIMESTAMP_FORMAT
final Map< String, BlackboardArtifact.Type > tsvFileArtifacts
static final Set< String > ALLOWED_EXTENSIONS
void getArtifactNode(Document xmlinput)
AbstractFile findAbstractFile(Content dataSource, String fileNamePath)
void createRoute(Collection< BlackboardAttribute > bbattributes, Content dataSource, String fileName)
static final Map< String, String > CUSTOM_ARTIFACT_MAP
Collection< BlackboardAttribute > processReadLine(List< String > lineValues, Map< String, Integer > columnIndexes, List< TsvColumn > attrList, String fileName, int lineNum)
void switchToIndeterminate()
BlackboardAttribute parseAttrValue(String value, BlackboardAttribute.Type attrType, String fileName, boolean blankIsNull, boolean zeroIsNull, ParseExceptionFunction valueConverter)
void processLeappFiles(List< String > LeappFilesToProcess, Content dataSource, DataSourceIngestModuleProgress progress)
final Map< String, String > tsvFiles
static final Map< String, String > ACCOUNT_RELATIONSHIPS
SleuthkitCase getSleuthkitCase()
final Map< String, String > tsvFileArtifactComments
BlackboardAttribute getAttribute(BlackboardAttribute.Type attrType, String value, String fileName)
final Map< String, List< TsvColumn > > tsvFileAttributes
boolean dataSourceIngestIsCancelled()
ProcessResult processFileSystem(Content dataSource, Path moduleOutputPath, DataSourceIngestModuleProgress progress)
final Blackboard blkBoard
void switchToDeterminate(int workUnits)
void createCustomArtifacts(Blackboard blkBoard)
static Case getCurrentCase()
synchronized static Logger getLogger(String name)
static Case getCurrentCaseThrows()
BlackboardAttribute apply(String orig)
AbstractFile createTrackpoint(Collection< BlackboardAttribute > bbattributes, Content dataSource, String fileName, String trackpointSegmentName, GeoTrackPoints pointList)
Account.Type getAccountType(String AccountTypeName)
void getAttributeNodes(Document xmlinput)
void progress(int workUnits)
String getXmlAttrIdentifier(String fileName, String attributeName)
ProcessResult processFiles(Content dataSource, Path moduleOutputPath, AbstractFile LeappFile, DataSourceIngestModuleProgress progress)
String formatValueBasedOnAttrType(TsvColumn colAttr, String value)
void getFileNode(Document xmlinput)