19 package org.sleuthkit.autopsy.modules.exif;
21 import com.drew.imaging.ImageMetadataReader;
22 import com.drew.imaging.ImageProcessingException;
23 import com.drew.lang.GeoLocation;
24 import com.drew.lang.Rational;
25 import com.drew.metadata.Metadata;
26 import com.drew.metadata.exif.ExifIFD0Directory;
27 import com.drew.metadata.exif.ExifSubIFDDirectory;
28 import com.drew.metadata.exif.GpsDirectory;
29 import java.io.BufferedInputStream;
30 import java.io.IOException;
31 import java.util.ArrayList;
32 import java.util.Collection;
33 import java.util.Date;
34 import java.util.HashSet;
35 import java.util.TimeZone;
36 import java.util.logging.Level;
37 import org.apache.commons.lang3.StringUtils;
38 import org.openide.util.NbBundle;
39 import org.openide.util.NbBundle.Messages;
51 import static org.
sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_METADATA_EXIF;
52 import static org.
sleuthkit.datamodel.BlackboardArtifact.ARTIFACT_TYPE.TSK_USER_CONTENT_SUSPECTED;
54 import static org.
sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DATETIME_CREATED;
55 import static org.
sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DEVICE_MAKE;
56 import static org.
sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_DEVICE_MODEL;
57 import static org.
sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_ALTITUDE;
58 import static org.
sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LATITUDE;
59 import static org.
sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE;
63 import org.
sleuthkit.datamodel.ReadContentInputStream.ReadContentInputStreamException;
66 import org.
sleuthkit.datamodel.TskData.TSK_DB_FILES_TYPE_ENUM;
73 @NbBundle.Messages({
"CannotRunFileTypeDetection=Cannot run file type detection."})
81 private final HashSet<String> supportedMimeTypes =
new HashSet<>();
82 private TimeZone timeZone = null;
86 supportedMimeTypes.add(
"audio/x-wav");
87 supportedMimeTypes.add(
"image/jpeg");
88 supportedMimeTypes.add(
"image/tiff");
93 jobId = context.getJobId();
102 @Messages({
"ExifParserFileIngestModule.indexError.message=Failed to post EXIF Metadata artifact(s)."})
108 logger.log(Level.INFO,
"Exception while getting open case.", ex);
112 if ((content.getType().equals(TSK_DB_FILES_TYPE_ENUM.UNALLOC_BLOCKS)
113 || (content.getType().equals(TSK_DB_FILES_TYPE_ENUM.SLACK)))) {
117 if (content.isFile() ==
false) {
122 if (content.getKnown().equals(TskData.FileKnown.KNOWN)) {
127 if (!parsableFormat(content)) {
131 return processFile(content);
134 @Messages({
"ExifParserFileIngestModule.userContent.description=EXIF metadata exists for this file."})
137 try (BufferedInputStream bin =
new BufferedInputStream(
new ReadContentInputStream(file));) {
139 Collection<BlackboardAttribute> attributes =
new ArrayList<>();
140 Metadata metadata = ImageMetadataReader.readMetadata(bin);
143 ExifSubIFDDirectory exifDir = metadata.getFirstDirectoryOfType(ExifSubIFDDirectory.class);
144 if (exifDir != null) {
147 if (timeZone == null) {
149 Content dataSource = file.getDataSource();
150 if ((dataSource != null) && (dataSource instanceof Image)) {
151 Image image = (Image) dataSource;
152 timeZone = TimeZone.getTimeZone(image.getTimeZone());
154 }
catch (TskCoreException ex) {
155 logger.log(Level.INFO,
"Error getting time zones", ex);
158 Date date = exifDir.getDate(ExifSubIFDDirectory.TAG_DATETIME_ORIGINAL, timeZone);
160 attributes.add(
new BlackboardAttribute(TSK_DATETIME_CREATED, MODULE_NAME, date.getTime() / 1000));
165 GpsDirectory gpsDir = metadata.getFirstDirectoryOfType(GpsDirectory.class);
166 if (gpsDir != null) {
167 GeoLocation loc = gpsDir.getGeoLocation();
169 attributes.add(
new BlackboardAttribute(TSK_GEO_LATITUDE, MODULE_NAME, loc.getLatitude()));
170 attributes.add(
new BlackboardAttribute(TSK_GEO_LONGITUDE, MODULE_NAME, loc.getLongitude()));
173 Rational altitude = gpsDir.getRational(GpsDirectory.TAG_ALTITUDE);
174 if (altitude != null) {
175 attributes.add(
new BlackboardAttribute(TSK_GEO_ALTITUDE, MODULE_NAME, altitude.doubleValue()));
180 ExifIFD0Directory devDir = metadata.getFirstDirectoryOfType(ExifIFD0Directory.class);
181 if (devDir != null) {
182 String model = devDir.getString(ExifIFD0Directory.TAG_MODEL);
183 if (StringUtils.isNotBlank(model)) {
184 attributes.add(
new BlackboardAttribute(TSK_DEVICE_MODEL, MODULE_NAME, model));
187 String make = devDir.getString(ExifIFD0Directory.TAG_MAKE);
188 if (StringUtils.isNotBlank(make)) {
189 attributes.add(
new BlackboardAttribute(TSK_DEVICE_MAKE, MODULE_NAME, make));
194 if (!attributes.isEmpty()) {
196 if (!blackboard.artifactExists(file, TSK_METADATA_EXIF, attributes)) {
197 BlackboardArtifact bba = file.newArtifact(TSK_METADATA_EXIF);
198 BlackboardArtifact bba2 = file.newArtifact(TSK_USER_CONTENT_SUSPECTED);
199 bba.addAttributes(attributes);
200 bba2.addAttribute(
new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_COMMENT, MODULE_NAME, Bundle.ExifParserFileIngestModule_userContent_description()));
203 blackboard.postArtifact(bba, MODULE_NAME);
204 blackboard.postArtifact(bba2, MODULE_NAME);
205 }
catch (Blackboard.BlackboardException ex) {
206 logger.log(Level.SEVERE,
"Unable to index blackboard artifact " + bba.getArtifactID(), ex);
208 Bundle.ExifParserFileIngestModule_indexError_message(), bba.getDisplayName());
214 }
catch (TskCoreException ex) {
215 logger.log(Level.WARNING,
"Failed to create blackboard artifact for exif metadata ({0}).", ex.getLocalizedMessage());
217 }
catch (ImageProcessingException ex) {
218 logger.log(Level.WARNING, String.format(
"Failed to process the image file '%s/%s' (id=%d).", file.getParentPath(), file.getName(), file.getId()), ex);
220 }
catch (ReadContentInputStreamException ex) {
221 logger.log(Level.WARNING, String.format(
"Error while trying to read image file '%s/%s' (id=%d).", file.getParentPath(), file.getName(), file.getId()), ex);
223 }
catch (IOException ex) {
224 logger.log(Level.WARNING, String.format(
"IOException when parsing image file '%s/%s' (id=%d).", file.getParentPath(), file.getName(), file.getId()), ex);
239 return supportedMimeTypes.contains(mimeType);
synchronized long decrementAndGet(long jobId)
void startUp(IngestJobContext context)
ProcessResult process(AbstractFile content)
synchronized long incrementAndGet(long jobId)
String getMIMEType(AbstractFile file)
SleuthkitCase getSleuthkitCase()
ProcessResult processFile(AbstractFile file)
static String getModuleName()
static void error(String title, String message)
synchronized static Logger getLogger(String name)
static Case getCurrentCaseThrows()
FileTypeDetector fileTypeDetector
boolean parsableFormat(AbstractFile f)