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.io.InputStream;
32 import java.util.ArrayList;
33 import java.util.Collection;
34 import java.util.Collections;
35 import java.util.Date;
36 import java.util.HashSet;
37 import java.util.List;
38 import java.util.TimeZone;
39 import java.util.concurrent.atomic.AtomicInteger;
40 import java.util.logging.Level;
41 import org.apache.commons.lang3.StringUtils;
42 import org.openide.util.NbBundle;
43 import org.openide.util.NbBundle.Messages;
58 import org.
sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE;
62 import org.
sleuthkit.datamodel.ReadContentInputStream.ReadContentInputStreamException;
66 import org.
sleuthkit.datamodel.TskData.TSK_DB_FILES_TYPE_ENUM;
74 "CannotRunFileTypeDetection=Cannot run file type detection."
80 private final AtomicInteger filesProcessed =
new AtomicInteger(0);
84 private final HashSet<String> supportedMimeTypes =
new HashSet<>();
85 private TimeZone timeZone = null;
90 supportedMimeTypes.add(
"audio/x-wav");
91 supportedMimeTypes.add(
"image/jpeg");
92 supportedMimeTypes.add(
"image/tiff");
97 jobId = context.getJobId();
112 logger.log(Level.INFO,
"Exception while getting open case.", ex);
116 if ((content.getType().equals(TSK_DB_FILES_TYPE_ENUM.UNALLOC_BLOCKS)
117 || (content.getType().equals(TSK_DB_FILES_TYPE_ENUM.SLACK)))) {
121 if (content.isFile() ==
false) {
126 if (content.getKnown().equals(TskData.FileKnown.KNOWN)) {
131 if (!parsableFormat(content)) {
135 return processFile(content);
138 @Messages({
"ExifParserFileIngestModule.indexError.message=Failed to index EXIF Metadata artifact for keyword search."})
139 ProcessResult processFile(AbstractFile file) {
140 InputStream in = null;
141 BufferedInputStream bin = null;
144 in =
new ReadContentInputStream(file);
145 bin =
new BufferedInputStream(in);
147 Collection<BlackboardAttribute> attributes =
new ArrayList<>();
148 Metadata metadata = ImageMetadataReader.readMetadata(bin);
151 ExifSubIFDDirectory exifDir = metadata.getFirstDirectoryOfType(ExifSubIFDDirectory.class);
152 if (exifDir != null) {
155 if (timeZone == null) {
157 Content dataSource = file.getDataSource();
158 if ((dataSource != null) && (dataSource instanceof Image)) {
159 Image image = (Image) dataSource;
160 timeZone = TimeZone.getTimeZone(image.getTimeZone());
162 }
catch (TskCoreException ex) {
163 logger.log(Level.INFO,
"Error getting time zones", ex);
166 Date date = exifDir.getDate(ExifSubIFDDirectory.TAG_DATETIME_ORIGINAL, timeZone);
168 attributes.add(
new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DATETIME_CREATED, ExifParserModuleFactory.getModuleName(), date.getTime() / 1000));
173 GpsDirectory gpsDir = metadata.getFirstDirectoryOfType(GpsDirectory.class);
174 if (gpsDir != null) {
175 GeoLocation loc = gpsDir.getGeoLocation();
177 double latitude = loc.getLatitude();
178 double longitude = loc.getLongitude();
179 attributes.add(
new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_GEO_LATITUDE, ExifParserModuleFactory.getModuleName(), latitude));
180 attributes.add(
new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE, ExifParserModuleFactory.getModuleName(), longitude));
183 Rational altitude = gpsDir.getRational(GpsDirectory.TAG_ALTITUDE);
184 if (altitude != null) {
185 attributes.add(
new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_GEO_ALTITUDE, ExifParserModuleFactory.getModuleName(), altitude.doubleValue()));
190 ExifIFD0Directory devDir = metadata.getFirstDirectoryOfType(ExifIFD0Directory.class);
191 if (devDir != null) {
192 String model = devDir.getString(ExifIFD0Directory.TAG_MODEL);
193 if (StringUtils.isNotBlank(model)) {
194 attributes.add(
new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DEVICE_MODEL, ExifParserModuleFactory.getModuleName(), model));
197 String make = devDir.getString(ExifIFD0Directory.TAG_MAKE);
198 if (StringUtils.isNotBlank(make)) {
199 attributes.add(
new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DEVICE_MAKE, ExifParserModuleFactory.getModuleName(), make));
204 if (!attributes.isEmpty()) {
206 org.
sleuthkit.datamodel.Blackboard tskBlackboard = tskCase.getBlackboard();
208 if (!tskBlackboard.artifactExists(file, BlackboardArtifact.ARTIFACT_TYPE.TSK_METADATA_EXIF, attributes)) {
209 BlackboardArtifact bba = file.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_METADATA_EXIF);
210 bba.addAttributes(attributes);
215 }
catch (Blackboard.BlackboardException ex) {
216 logger.log(Level.SEVERE,
"Unable to index blackboard artifact " + bba.getArtifactID(), ex);
217 MessageNotifyUtil.Notify.error(
218 Bundle.ExifParserFileIngestModule_indexError_message(), bba.getDisplayName());
222 BlackboardArtifact.ARTIFACT_TYPE.TSK_METADATA_EXIF,
223 Collections.singletonList(bba)));
227 return ProcessResult.OK;
228 }
catch (TskCoreException ex) {
229 logger.log(Level.WARNING,
"Failed to create blackboard artifact for exif metadata ({0}).", ex.getLocalizedMessage());
230 return ProcessResult.ERROR;
231 }
catch (ImageProcessingException ex) {
232 logger.log(Level.WARNING, String.format(
"Failed to process the image file '%s/%s' (id=%d).", file.getParentPath(), file.getName(), file.getId()), ex);
233 return ProcessResult.ERROR;
234 }
catch (ReadContentInputStreamException ex) {
235 logger.log(Level.WARNING, String.format(
"Error while trying to read image file '%s/%s' (id=%d).", file.getParentPath(), file.getName(), file.getId()), ex);
236 return ProcessResult.ERROR;
237 }
catch (IOException ex) {
238 logger.log(Level.WARNING, String.format(
"IOException when parsing image file '%s/%s' (id=%d).", file.getParentPath(), file.getName(), file.getId()), ex);
239 return ProcessResult.ERROR;
248 }
catch (IOException ex) {
249 logger.log(Level.WARNING,
"Failed to close InputStream.", ex);
250 return ProcessResult.ERROR;
265 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)
void fireModuleDataEvent(ModuleDataEvent moduleDataEvent)
SleuthkitCase getSleuthkitCase()
Blackboard getBlackboard()
synchronized void indexArtifact(BlackboardArtifact artifact)
synchronized static Logger getLogger(String name)
static Case getCurrentCaseThrows()
FileTypeDetector fileTypeDetector
boolean parsableFormat(AbstractFile f)
static synchronized IngestServices getInstance()