<commit_before>/*
 * Autopsy Forensic Browser
 *
 * Copyright 2011 Basis Technology Corp.
 * Contact: carrier <at> sleuthkit <dot> org
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.sleuthkit.autopsy.exifparser;

import com.drew.imaging.ImageMetadataReader;
import com.drew.imaging.ImageProcessingException;
import com.drew.metadata.Metadata;
import com.drew.metadata.exif.ExifIFD0Directory;
import com.drew.metadata.exif.ExifSubIFDDirectory;
import com.drew.metadata.exif.GpsDirectory;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.sleuthkit.autopsy.ingest.IngestServices;
import org.sleuthkit.autopsy.ingest.IngestMessage;
import org.sleuthkit.autopsy.ingest.IngestMessage.MessageType;
import org.sleuthkit.autopsy.ingest.IngestModuleAbstract;
import org.sleuthkit.autopsy.ingest.IngestModuleAbstractFile;
import org.sleuthkit.autopsy.ingest.IngestModuleInit;
import org.sleuthkit.datamodel.AbstractFile;
import org.sleuthkit.datamodel.BlackboardArtifact;
import org.sleuthkit.datamodel.BlackboardAttribute;
import org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE;
import org.sleuthkit.datamodel.FsContent;
import org.sleuthkit.datamodel.ReadContentInputStream;
import org.sleuthkit.datamodel.TskCoreException;
import org.sleuthkit.datamodel.TskData.TSK_DB_FILES_TYPE_ENUM;

/**
 * Ingest module to parse image Exif metadata. Currently only supports JPEG files.
 * Ingests an image file and, if available, adds it's date, latitude, longitude,
 * altitude, device model, and device make to a blackboard artifact.
 */
public final class ExifParserFileIngestModule implements IngestModuleAbstractFile {

    private IngestServices services;
    
    final String MODULE_NAME = "Exif Parser";
    private static final Logger logger = Logger.getLogger(ExifParserFileIngestModule.class.getName());
    private static ExifParserFileIngestModule defaultInstance = null;
    private static int messageId = 0;

    //file ingest modules require a private constructor
    //to ensure singleton instances
    private ExifParserFileIngestModule() {
       
    }

    //default instance used for module registration
    public static synchronized ExifParserFileIngestModule getDefault() {
        if (defaultInstance == null) {
            defaultInstance = new ExifParserFileIngestModule();
        }
        return defaultInstance;
    }

    @Override
    public IngestModuleAbstractFile.ProcessResult process(AbstractFile content) {
        if(content.getType().equals(TSK_DB_FILES_TYPE_ENUM.FS)) {
            FsContent fsContent = (FsContent) content;
            if(fsContent.isFile()) {
                if(parsableFormat(fsContent)) {
                    return processFile(fsContent);
                }
            }
        }
        
        return IngestModuleAbstractFile.ProcessResult.UNKNOWN;
    }
    
    public IngestModuleAbstractFile.ProcessResult processFile(FsContent f) {
        InputStream in = null;
        BufferedInputStream bin = null;
        
        try {
            in = new ReadContentInputStream(f);
            bin = new BufferedInputStream(in);
            
            Collection<BlackboardAttribute> attributes = new ArrayList<BlackboardAttribute>();
            Metadata metadata = ImageMetadataReader.readMetadata(bin, true);
            
            // Date
            ExifSubIFDDirectory exifDir = metadata.getDirectory(ExifSubIFDDirectory.class);
            if(exifDir != null) {
                Date date = exifDir.getDate(ExifSubIFDDirectory.TAG_DATETIME_ORIGINAL);
                
                if(date != null) {
                    attributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DATETIME.getTypeID(), MODULE_NAME, date.toString()));
                }
            }
            
            // GPS Stuff
            GpsDirectory gpsDir = metadata.getDirectory(GpsDirectory.class);
            if(gpsDir != null) {
                String latitude = gpsDir.getString(GpsDirectory.TAG_GPS_LATITUDE);
                String latRef = gpsDir.getString(GpsDirectory.TAG_GPS_LATITUDE_REF);
                String longitude = gpsDir.getString(GpsDirectory.TAG_GPS_LONGITUDE);
                String longRef = gpsDir.getString(GpsDirectory.TAG_GPS_LONGITUDE_REF);
                String altitude = gpsDir.getString(GpsDirectory.TAG_GPS_ALTITUDE);
                
                if(latitude!= null && latRef!=null) {
                    attributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_GEO_LATITUDE.getTypeID(), MODULE_NAME, latitude + " " +  latRef));
                } if(longitude!=null && longRef!=null) {
                    attributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE.getTypeID(), MODULE_NAME, longitude + " " + longRef));
                } if(altitude!=null) {
                    attributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_GEO_ALTITUDE.getTypeID(), MODULE_NAME, altitude));
                }
            }
            
            // Device info
            ExifIFD0Directory devDir = metadata.getDirectory(ExifIFD0Directory.class);
            if(devDir != null) {
                String model = devDir.getString(ExifIFD0Directory.TAG_MODEL);
                String make = devDir.getString(ExifIFD0Directory.TAG_MAKE);
                
                if(model!=null) {
                    attributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DEVICE_MODEL.getTypeID(), MODULE_NAME, model));
                } if(make!=null) {
                    attributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DEVICE_MAKE.getTypeID(), MODULE_NAME, make));
                }
            }
            
            // Add the attributes, if there are any, to a new artifact
            if(!attributes.isEmpty()) {
                BlackboardArtifact bba = f.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_METADATA_EXIF);
                bba.addAttributes(attributes);
            }
            
            return IngestModuleAbstractFile.ProcessResult.OK;
            
        } catch (TskCoreException ex) {
            Logger.getLogger(ExifParserFileIngestModule.class.getName()).log(Level.SEVERE, null, ex);
        } catch (ImageProcessingException ex) {
            logger.log(Level.WARNING, "Failed to process the image.", ex);
        } catch (IOException ex) {
            logger.log(Level.WARNING, "IOException when parsing image file.", ex);
        } finally {
            try {
                if(in!=null) { in.close(); }
                if(bin!=null) { bin.close(); }
            } catch (IOException ex) {
                logger.log(Level.WARNING, "Failed to close InputStream.", ex);
            }
        }
        
        // If we got here, there was an error
        return IngestModuleAbstractFile.ProcessResult.ERROR;
    }
    
    private boolean parsableFormat(FsContent f) {
        // Get the name, extension
        String name = f.getName();
        int dotIndex = name.lastIndexOf(".");
        if (dotIndex == -1) {
            return false;
        }
        String ext = name.substring(dotIndex).toLowerCase();
        if(ext.equals(".jpeg") || ext.equals(".jpg")) {
            return true;
        }
        
        return false;
    }
<commit_msg>Updated ExifParser logging.<commit_after>/*
 * Autopsy Forensic Browser
 *
 * Copyright 2011 Basis Technology Corp.
 * Contact: carrier <at> sleuthkit <dot> org
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.sleuthkit.autopsy.exifparser;

import com.drew.imaging.ImageMetadataReader;
import com.drew.imaging.ImageProcessingException;
import com.drew.imaging.jpeg.JpegProcessingException;
import com.drew.metadata.Metadata;
import com.drew.metadata.exif.ExifIFD0Directory;
import com.drew.metadata.exif.ExifSubIFDDirectory;
import com.drew.metadata.exif.GpsDirectory;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.sleuthkit.autopsy.ingest.IngestServices;
import org.sleuthkit.autopsy.ingest.IngestMessage;
import org.sleuthkit.autopsy.ingest.IngestMessage.MessageType;
import org.sleuthkit.autopsy.ingest.IngestModuleAbstract;
import org.sleuthkit.autopsy.ingest.IngestModuleAbstractFile;
import org.sleuthkit.autopsy.ingest.IngestModuleInit;
import org.sleuthkit.datamodel.AbstractFile;
import org.sleuthkit.datamodel.BlackboardArtifact;
import org.sleuthkit.datamodel.BlackboardAttribute;
import org.sleuthkit.datamodel.BlackboardAttribute.ATTRIBUTE_TYPE;
import org.sleuthkit.datamodel.FsContent;
import org.sleuthkit.datamodel.ReadContentInputStream;
import org.sleuthkit.datamodel.TskCoreException;
import org.sleuthkit.datamodel.TskData.TSK_DB_FILES_TYPE_ENUM;

/**
 * Ingest module to parse image Exif metadata. Currently only supports JPEG files.
 * Ingests an image file and, if available, adds it's date, latitude, longitude,
 * altitude, device model, and device make to a blackboard artifact.
 */
public final class ExifParserFileIngestModule implements IngestModuleAbstractFile {

    private IngestServices services;
    
    final String MODULE_NAME = "Exif Parser";
    private static final Logger logger = Logger.getLogger(ExifParserFileIngestModule.class.getName());
    private static ExifParserFileIngestModule defaultInstance = null;
    private static int messageId = 0;

    //file ingest modules require a private constructor
    //to ensure singleton instances
    private ExifParserFileIngestModule() {
       
    }

    //default instance used for module registration
    public static synchronized ExifParserFileIngestModule getDefault() {
        if (defaultInstance == null) {
            defaultInstance = new ExifParserFileIngestModule();
        }
        return defaultInstance;
    }

    @Override
    public IngestModuleAbstractFile.ProcessResult process(AbstractFile content) {
        if(content.getType().equals(TSK_DB_FILES_TYPE_ENUM.FS)) {
            FsContent fsContent = (FsContent) content;
            if(fsContent.isFile()) {
                if(parsableFormat(fsContent)) {
                    return processFile(fsContent);
                }
            }
        }
        
        return IngestModuleAbstractFile.ProcessResult.UNKNOWN;
    }
    
    public IngestModuleAbstractFile.ProcessResult processFile(FsContent f) {
        InputStream in = null;
        BufferedInputStream bin = null;
        
        try {
            in = new ReadContentInputStream(f);
            bin = new BufferedInputStream(in);
            
            Collection<BlackboardAttribute> attributes = new ArrayList<BlackboardAttribute>();
            Metadata metadata = ImageMetadataReader.readMetadata(bin, true);
            
            // Date
            ExifSubIFDDirectory exifDir = metadata.getDirectory(ExifSubIFDDirectory.class);
            if(exifDir != null) {
                Date date = exifDir.getDate(ExifSubIFDDirectory.TAG_DATETIME_ORIGINAL);
                
                if(date != null) {
                    attributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DATETIME.getTypeID(), MODULE_NAME, date.toString()));
                }
            }
            
            // GPS Stuff
            GpsDirectory gpsDir = metadata.getDirectory(GpsDirectory.class);
            if(gpsDir != null) {
                String latitude = gpsDir.getString(GpsDirectory.TAG_GPS_LATITUDE);
                String latRef = gpsDir.getString(GpsDirectory.TAG_GPS_LATITUDE_REF);
                String longitude = gpsDir.getString(GpsDirectory.TAG_GPS_LONGITUDE);
                String longRef = gpsDir.getString(GpsDirectory.TAG_GPS_LONGITUDE_REF);
                String altitude = gpsDir.getString(GpsDirectory.TAG_GPS_ALTITUDE);
                
                if(latitude!= null && latRef!=null) {
                    attributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_GEO_LATITUDE.getTypeID(), MODULE_NAME, latitude + " " +  latRef));
                } if(longitude!=null && longRef!=null) {
                    attributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_GEO_LONGITUDE.getTypeID(), MODULE_NAME, longitude + " " + longRef));
                } if(altitude!=null) {
                    attributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_GEO_ALTITUDE.getTypeID(), MODULE_NAME, altitude));
                }
            }
            
            // Device info
            ExifIFD0Directory devDir = metadata.getDirectory(ExifIFD0Directory.class);
            if(devDir != null) {
                String model = devDir.getString(ExifIFD0Directory.TAG_MODEL);
                String make = devDir.getString(ExifIFD0Directory.TAG_MAKE);
                
                if(model!=null) {
                    attributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DEVICE_MODEL.getTypeID(), MODULE_NAME, model));
                } if(make!=null) {
                    attributes.add(new BlackboardAttribute(ATTRIBUTE_TYPE.TSK_DEVICE_MAKE.getTypeID(), MODULE_NAME, make));
                }
            }
            
            // Add the attributes, if there are any, to a new artifact
            if(!attributes.isEmpty()) {
                BlackboardArtifact bba = f.newArtifact(BlackboardArtifact.ARTIFACT_TYPE.TSK_METADATA_EXIF);
                bba.addAttributes(attributes);
            }
            
            return IngestModuleAbstractFile.ProcessResult.OK;
            
        } catch (TskCoreException ex) {
            Logger.getLogger(ExifParserFileIngestModule.class.getName()).log(Level.SEVERE, null, ex);
        } catch (ImageProcessingException ex) {
            logger.log(Level.WARNING, "Failed to process the image.");
        } catch (IOException ex) {
            logger.log(Level.WARNING, "IOException when parsing image file.", ex);
        } finally {
            try {
                if(in!=null) { in.close(); }
                if(bin!=null) { bin.close(); }
            } catch (IOException ex) {
                logger.log(Level.WARNING, "Failed to close InputStream.", ex);
            }
        }
        
        // If we got here, there was an error
        return IngestModuleAbstractFile.ProcessResult.ERROR;
    }
    
    private boolean parsableFormat(FsContent f) {
        // Get the name, extension
        String name = f.getName();
        int dotIndex = name.lastIndexOf(".");
        if (dotIndex == -1) {
            return false;
        }
        String ext = name.substring(dotIndex).toLowerCase();
        if(ext.equals(".jpeg") || ext.equals(".jpg")) {
            return true;
        }
        
        return false;
    }

{'fraction_non_alphanumeric': 0.08823160010047727, 'fraction_numerical': 0.0018839487565938207, 'mean_word_length': 2.5570694661603754, 'pattern_counts': {'":': 0, '<': 11, '<?xml version=': 0, '>': 11, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3526189', 'n_tokens_mistral': 4521, 'n_tokens_neox': 4363, 'n_words': 933}