Index: trunk/src/com/drew/metadata/exif/ExifTiffHandler.java
===================================================================
--- trunk/src/com/drew/metadata/exif/ExifTiffHandler.java	(revision 13061)
+++ trunk/src/com/drew/metadata/exif/ExifTiffHandler.java	(revision 15217)
@@ -1,4 +1,4 @@
 /*
- * Copyright 2002-2017 Drew Noakes
+ * Copyright 2002-2019 Drew Noakes and contributors
  *
  *    Licensed under the Apache License, Version 2.0 (the "License");
@@ -30,4 +30,5 @@
 import com.drew.imaging.tiff.TiffProcessingException;
 import com.drew.imaging.tiff.TiffReader;
+import com.drew.lang.BufferBoundsException;
 import com.drew.lang.Charsets;
 import com.drew.lang.RandomAccessReader;
@@ -38,5 +39,33 @@
 import com.drew.metadata.Metadata;
 import com.drew.metadata.StringValue;
-import com.drew.metadata.exif.makernotes.*;
+import com.drew.metadata.exif.makernotes.AppleMakernoteDirectory;
+import com.drew.metadata.exif.makernotes.CanonMakernoteDirectory;
+import com.drew.metadata.exif.makernotes.CasioType1MakernoteDirectory;
+import com.drew.metadata.exif.makernotes.CasioType2MakernoteDirectory;
+import com.drew.metadata.exif.makernotes.FujifilmMakernoteDirectory;
+import com.drew.metadata.exif.makernotes.KodakMakernoteDirectory;
+import com.drew.metadata.exif.makernotes.KyoceraMakernoteDirectory;
+import com.drew.metadata.exif.makernotes.LeicaMakernoteDirectory;
+import com.drew.metadata.exif.makernotes.LeicaType5MakernoteDirectory;
+import com.drew.metadata.exif.makernotes.NikonType1MakernoteDirectory;
+import com.drew.metadata.exif.makernotes.NikonType2MakernoteDirectory;
+import com.drew.metadata.exif.makernotes.OlympusCameraSettingsMakernoteDirectory;
+import com.drew.metadata.exif.makernotes.OlympusEquipmentMakernoteDirectory;
+import com.drew.metadata.exif.makernotes.OlympusFocusInfoMakernoteDirectory;
+import com.drew.metadata.exif.makernotes.OlympusImageProcessingMakernoteDirectory;
+import com.drew.metadata.exif.makernotes.OlympusMakernoteDirectory;
+import com.drew.metadata.exif.makernotes.OlympusRawDevelopment2MakernoteDirectory;
+import com.drew.metadata.exif.makernotes.OlympusRawDevelopmentMakernoteDirectory;
+import com.drew.metadata.exif.makernotes.OlympusRawInfoMakernoteDirectory;
+import com.drew.metadata.exif.makernotes.PanasonicMakernoteDirectory;
+import com.drew.metadata.exif.makernotes.PentaxMakernoteDirectory;
+import com.drew.metadata.exif.makernotes.ReconyxHyperFireMakernoteDirectory;
+import com.drew.metadata.exif.makernotes.ReconyxUltraFireMakernoteDirectory;
+import com.drew.metadata.exif.makernotes.RicohMakernoteDirectory;
+import com.drew.metadata.exif.makernotes.SamsungType2MakernoteDirectory;
+import com.drew.metadata.exif.makernotes.SanyoMakernoteDirectory;
+import com.drew.metadata.exif.makernotes.SigmaMakernoteDirectory;
+import com.drew.metadata.exif.makernotes.SonyType1MakernoteDirectory;
+import com.drew.metadata.exif.makernotes.SonyType6MakernoteDirectory;
 import com.drew.metadata.iptc.IptcReader;
 import com.drew.metadata.tiff.DirectoryTiffHandler;
@@ -54,8 +83,5 @@
     public ExifTiffHandler(@NotNull Metadata metadata, @Nullable Directory parentDirectory)
     {
-        super(metadata);
-
-        if (parentDirectory != null)
-            _currentDirectory.setParent(parentDirectory);
+        super(metadata, parentDirectory);
     }
 
@@ -67,9 +93,8 @@
         final int panasonicRawTiffMarker = 0x0055; // for RW2 files
 
-        switch (marker)
-        {
+        switch (marker) {
             case standardTiffMarker:
-            case olympusRawTiffMarker:      // Todo: implement an IFD0, if there is one
-            case olympusRawTiffMarker2:     // Todo: implement an IFD0, if there is one
+            case olympusRawTiffMarker:      // TODO implement an IFD0, if there is one
+            case olympusRawTiffMarker2:     // TODO implement an IFD0, if there is one
                 pushDirectory(ExifIFD0Directory.class);
                 break;
@@ -172,5 +197,5 @@
 
         // an unknown (0) formatCode needs to be potentially handled later as a highly custom directory tag
-        if(formatCode == 0)
+        if (formatCode == 0)
             return 0L;
 
@@ -185,9 +210,9 @@
                                     final int byteCount) throws IOException
     {
+        assert(_currentDirectory != null);
+
         // Some 0x0000 tags have a 0 byteCount. Determine whether it's bad.
-        if (tagId == 0)
-        {
-            if (_currentDirectory.containsTag(tagId))
-            {
+        if (tagId == 0) {
+            if (_currentDirectory.containsTag(tagId)) {
                 // Let it go through for now. Some directories handle it, some don't
                 return false;
@@ -215,10 +240,10 @@
         }
 
-        if (HandlePrintIM(_currentDirectory, tagId))
+        if (handlePrintIM(_currentDirectory, tagId))
         {
             PrintIMDirectory printIMDirectory = new PrintIMDirectory();
             printIMDirectory.setParent(_currentDirectory);
             _metadata.addDirectory(printIMDirectory);
-            ProcessPrintIM(printIMDirectory, tagOffset, reader, byteCount);
+            processPrintIM(printIMDirectory, tagOffset, reader, byteCount);
             return true;
         }
@@ -226,8 +251,6 @@
         // Note: these also appear in tryEnterSubIfd because some are IFD pointers while others begin immediately
         // for the same directories
-        if(_currentDirectory instanceof OlympusMakernoteDirectory)
-        {
-            switch (tagId)
-            {
+        if (_currentDirectory instanceof OlympusMakernoteDirectory) {
+            switch (tagId) {
                 case OlympusMakernoteDirectory.TAG_EQUIPMENT:
                     pushDirectory(OlympusEquipmentMakernoteDirectory.class);
@@ -265,15 +288,13 @@
         }
 
-        if (_currentDirectory instanceof PanasonicRawIFD0Directory)
-        {
+        if (_currentDirectory instanceof PanasonicRawIFD0Directory) {
             // these contain binary data with specific offsets, and can't be processed as regular ifd's.
             // The binary data is broken into 'fake' tags and there is a pattern.
-            switch (tagId)
-            {
+            switch (tagId) {
                 case PanasonicRawIFD0Directory.TagWbInfo:
                     PanasonicRawWbInfoDirectory dirWbInfo = new PanasonicRawWbInfoDirectory();
                     dirWbInfo.setParent(_currentDirectory);
                     _metadata.addDirectory(dirWbInfo);
-                    ProcessBinary(dirWbInfo, tagOffset, reader, byteCount, false, 2);
+                    processBinary(dirWbInfo, tagOffset, reader, byteCount, false, 2);
                     return true;
                 case PanasonicRawIFD0Directory.TagWbInfo2:
@@ -281,5 +302,5 @@
                     dirWbInfo2.setParent(_currentDirectory);
                     _metadata.addDirectory(dirWbInfo2);
-                    ProcessBinary(dirWbInfo2, tagOffset, reader, byteCount, false, 3);
+                    processBinary(dirWbInfo2, tagOffset, reader, byteCount, false, 3);
                     return true;
                 case PanasonicRawIFD0Directory.TagDistortionInfo:
@@ -287,5 +308,5 @@
                     dirDistort.setParent(_currentDirectory);
                     _metadata.addDirectory(dirDistort);
-                    ProcessBinary(dirDistort, tagOffset, reader, byteCount, true, 1);
+                    processBinary(dirDistort, tagOffset, reader, byteCount, true, 1);
                     return true;
             }
@@ -293,6 +314,5 @@
 
         // Panasonic RAW sometimes contains an embedded version of the data as a JPG file.
-        if (tagId == PanasonicRawIFD0Directory.TagJpgFromRaw && _currentDirectory instanceof PanasonicRawIFD0Directory)
-        {
+        if (tagId == PanasonicRawIFD0Directory.TagJpgFromRaw && _currentDirectory instanceof PanasonicRawIFD0Directory) {
             byte[] jpegrawbytes = reader.getBytes(tagOffset, byteCount);
 
@@ -316,35 +336,27 @@
     }
 
-    private static void ProcessBinary(@NotNull final Directory directory, final int tagValueOffset, @NotNull final RandomAccessReader reader, final int byteCount, final Boolean issigned, final int arrayLength) throws IOException
+    private static void processBinary(@NotNull final Directory directory, final int tagValueOffset, @NotNull final RandomAccessReader reader, final int byteCount, final Boolean isSigned, final int arrayLength) throws IOException
     {
         // expects signed/unsigned int16 (for now)
-        //int byteSize = issigned ? sizeof(short) : sizeof(ushort);
+        //int byteSize = isSigned ? sizeof(short) : sizeof(ushort);
         int byteSize = 2;
 
         // 'directory' is assumed to contain tags that correspond to the byte position unless it's a set of bytes
-        for (int i = 0; i < byteCount; i++)
-        {
-            if (directory.hasTagName(i))
-            {
+        for (int i = 0; i < byteCount; i++) {
+            if (directory.hasTagName(i)) {
                 // only process this tag if the 'next' integral tag exists. Otherwise, it's a set of bytes
-                if (i < byteCount - 1 && directory.hasTagName(i + 1))
-                {
-                    if(issigned)
+                if (i < byteCount - 1 && directory.hasTagName(i + 1)) {
+                    if (isSigned)
                         directory.setObject(i, reader.getInt16(tagValueOffset + (i* byteSize)));
                     else
                         directory.setObject(i, reader.getUInt16(tagValueOffset + (i* byteSize)));
-                }
-                else
-                {
+                } else {
                     // the next arrayLength bytes are a multi-byte value
-                    if (issigned)
-                    {
+                    if (isSigned) {
                         short[] val = new short[arrayLength];
                         for (int j = 0; j<val.length; j++)
                             val[j] = reader.getInt16(tagValueOffset + ((i + j) * byteSize));
                         directory.setObjectArray(i, val);
-                    }
-                    else
-                    {
+                    } else {
                         int[] val = new int[arrayLength];
                         for (int j = 0; j<val.length; j++)
@@ -359,4 +371,24 @@
     }
 
+    /** Read a given number of bytes from the stream
+     *
+     * This method is employed to "suppress" attempts to read beyond end of the
+     * file as may happen at the beginning of processMakernote when we read
+     * increasingly longer camera makes.
+     *
+     * Instead of failing altogether in this context we return an empty string
+     * which will fail all sensible attempts to compare to makes while avoiding
+     * a full-on failure.
+     */
+    @NotNull
+    private static String getReaderString(final @NotNull RandomAccessReader reader, final int makernoteOffset, final int bytesRequested) throws IOException
+    {
+        try {
+            return reader.getString(makernoteOffset, bytesRequested, Charsets.UTF_8);
+        } catch(BufferBoundsException e) {
+            return "";
+        }
+    }
+
     private boolean processMakernote(final int makernoteOffset,
                                      final @NotNull Set<Integer> processedIfdOffsets,
@@ -364,4 +396,6 @@
                                      final @NotNull RandomAccessReader reader) throws IOException
     {
+        assert(_currentDirectory != null);
+
         // Determine the camera model and makernote format.
         Directory ifd0Directory = _metadata.getFirstDirectoryOfType(ExifIFD0Directory.class);
@@ -369,14 +403,14 @@
         String cameraMake = ifd0Directory == null ? null : ifd0Directory.getString(ExifIFD0Directory.TAG_MAKE);
 
-        final String firstTwoChars    = reader.getString(makernoteOffset, 2, Charsets.UTF_8);
-        final String firstThreeChars  = reader.getString(makernoteOffset, 3, Charsets.UTF_8);
-        final String firstFourChars   = reader.getString(makernoteOffset, 4, Charsets.UTF_8);
-        final String firstFiveChars   = reader.getString(makernoteOffset, 5, Charsets.UTF_8);
-        final String firstSixChars    = reader.getString(makernoteOffset, 6, Charsets.UTF_8);
-        final String firstSevenChars  = reader.getString(makernoteOffset, 7, Charsets.UTF_8);
-        final String firstEightChars  = reader.getString(makernoteOffset, 8, Charsets.UTF_8);
-        final String firstNineChars   = reader.getString(makernoteOffset, 9, Charsets.UTF_8);
-        final String firstTenChars    = reader.getString(makernoteOffset, 10, Charsets.UTF_8);
-        final String firstTwelveChars = reader.getString(makernoteOffset, 12, Charsets.UTF_8);
+        final String firstTwoChars    = getReaderString(reader, makernoteOffset, 2);
+        final String firstThreeChars  = getReaderString(reader, makernoteOffset, 3);
+        final String firstFourChars   = getReaderString(reader, makernoteOffset, 4);
+        final String firstFiveChars   = getReaderString(reader, makernoteOffset, 5);
+        final String firstSixChars    = getReaderString(reader, makernoteOffset, 6);
+        final String firstSevenChars  = getReaderString(reader, makernoteOffset, 7);
+        final String firstEightChars  = getReaderString(reader, makernoteOffset, 8);
+        final String firstNineChars   = getReaderString(reader, makernoteOffset, 9);
+        final String firstTenChars    = getReaderString(reader, makernoteOffset, 10);
+        final String firstTwelveChars = getReaderString(reader, makernoteOffset, 12);
 
         boolean byteOrderBefore = reader.isMotorolaByteOrder();
@@ -501,5 +535,5 @@
                 return false;
             }
-        } else if ("Panasonic\u0000\u0000\u0000".equals(reader.getString(makernoteOffset, 12, Charsets.UTF_8))) {
+        } else if ("Panasonic\u0000\u0000\u0000".equals(firstTwelveChars)) {
             // NON-Standard TIFF IFD Data using Panasonic Tags. There is no Next-IFD pointer after the IFD
             // Offsets are relative to the start of the TIFF header at the beginning of the EXIF segment
@@ -575,11 +609,10 @@
     }
 
-    private static Boolean HandlePrintIM(@NotNull final Directory directory, final int tagId)
+    private static boolean handlePrintIM(@NotNull final Directory directory, final int tagId)
     {
         if (tagId == ExifDirectoryBase.TAG_PRINT_IMAGE_MATCHING_INFO)
             return true;
 
-        if (tagId == 0x0E00)
-        {
+        if (tagId == 0x0E00) {
             // Tempting to say every tagid of 0x0E00 is a PIM tag, but can't be 100% sure
             if (directory instanceof CasioType2MakernoteDirectory ||
@@ -606,16 +639,14 @@
     /// lib\Image\ExifTool\PrintIM.pm
     /// </remarks>
-    private static void ProcessPrintIM(@NotNull final PrintIMDirectory directory, final int tagValueOffset, @NotNull final RandomAccessReader reader, final int byteCount) throws IOException
+    private static void processPrintIM(@NotNull final PrintIMDirectory directory, final int tagValueOffset, @NotNull final RandomAccessReader reader, final int byteCount) throws IOException
     {
         Boolean resetByteOrder = null;
 
-        if (byteCount == 0)
-        {
+        if (byteCount == 0) {
             directory.addError("Empty PrintIM data");
             return;
         }
 
-        if (byteCount <= 15)
-        {
+        if (byteCount <= 15) {
             directory.addError("Bad PrintIM data");
             return;
@@ -624,6 +655,5 @@
         String header = reader.getString(tagValueOffset, 12, Charsets.UTF_8);
 
-        if (!header.startsWith("PrintIM")) //, StringComparison.Ordinal))
-        {
+        if (!header.startsWith("PrintIM")) {
             directory.addError("Invalid PrintIM header");
             return;
@@ -632,12 +662,11 @@
         // check size of PrintIM block
         int num = reader.getUInt16(tagValueOffset + 14);
-        if (byteCount < 16 + num * 6)
-        {
+
+        if (byteCount < 16 + num * 6) {
             // size is too big, maybe byte ordering is wrong
             resetByteOrder = reader.isMotorolaByteOrder();
             reader.setMotorolaByteOrder(!reader.isMotorolaByteOrder());
             num = reader.getUInt16(tagValueOffset + 14);
-            if (byteCount < 16 + num * 6)
-            {
+            if (byteCount < 16 + num * 6) {
                 directory.addError("Bad PrintIM size");
                 return;
@@ -647,6 +676,5 @@
         directory.setObject(PrintIMDirectory.TagPrintImVersion, header.substring(8, 12));
 
-        for (int n = 0; n < num; n++)
-        {
+        for (int n = 0; n < num; n++) {
             int pos = tagValueOffset + 16 + n * 6;
             int tag = reader.getUInt16(pos);
@@ -712,10 +740,8 @@
             build = null;
         }
-        if (build != null)
-        {
+
+        if (build != null) {
             directory.setString(ReconyxHyperFireMakernoteDirectory.TAG_FIRMWARE_VERSION, String.format("%d.%d.%d.%s", major, minor, revision, build));
-        }
-        else
-        {
+        } else {
             directory.setString(ReconyxHyperFireMakernoteDirectory.TAG_FIRMWARE_VERSION, String.format("%d.%d.%d", major, minor, revision));
             directory.addError("Error processing Reconyx HyperFire makernote data: build '" + buildYearAndDate + "' is not in the expected format and will be omitted from Firmware Version.");
@@ -746,11 +772,8 @@
             (month >= 1 && month < 13) &&
             (day >= 1 && day < 32) &&
-            (year >= 1 && year <= 9999))
-        {
+            (year >= 1 && year <= 9999)) {
             directory.setString(ReconyxHyperFireMakernoteDirectory.TAG_DATE_TIME_ORIGINAL,
                     String.format("%4d:%2d:%2d %2d:%2d:%2d", year, month, day, hour, minutes, seconds));
-        }
-        else
-        {
+        } else {
             directory.addError("Error processing Reconyx HyperFire makernote data: Date/Time Original " + year + "-" + month + "-" + day + " " + hour + ":" + minutes + ":" + seconds + " is not a valid date/time.");
         }
@@ -778,6 +801,5 @@
         /*uint makernoteID = ByteConvert.FromBigEndianToNative(reader.GetUInt32(makernoteOffset + ReconyxUltraFireMakernoteDirectory.TagMakernoteID));
         directory.Set(ReconyxUltraFireMakernoteDirectory.TagMakernoteID, makernoteID);
-        if (makernoteID != ReconyxUltraFireMakernoteDirectory.MAKERNOTE_ID)
-        {
+        if (makernoteID != ReconyxUltraFireMakernoteDirectory.MAKERNOTE_ID) {
             directory.addError("Error processing Reconyx UltraFire makernote data: unknown Makernote ID 0x" + makernoteID.ToString("x8"));
             return;
@@ -786,6 +808,5 @@
         uint makernotePublicID = ByteConvert.FromBigEndianToNative(reader.GetUInt32(makernoteOffset + ReconyxUltraFireMakernoteDirectory.TagMakernotePublicID));
         directory.Set(ReconyxUltraFireMakernoteDirectory.TagMakernotePublicID, makernotePublicID);
-        if (makernotePublicID != ReconyxUltraFireMakernoteDirectory.MAKERNOTE_PUBLIC_ID)
-        {
+        if (makernotePublicID != ReconyxUltraFireMakernoteDirectory.MAKERNOTE_PUBLIC_ID) {
             directory.addError("Error processing Reconyx UltraFire makernote data: unknown Makernote Public ID 0x" + makernotePublicID.ToString("x8"));
             return;
@@ -818,10 +839,7 @@
             (month >= 1 && month < 13) &&
             (day >= 1 && day < 32) &&
-            (year >= 1 && year <= 9999))
-        {
+            (year >= 1 && year <= 9999)) {
             directory.Set(ReconyxUltraFireMakernoteDirectory.TAG_DATE_TIME_ORIGINAL, new DateTime(year, month, day, hour, minutes, seconds, DateTimeKind.Unspecified));
-        }
-        else
-        {
+        } else {
             directory.addError("Error processing Reconyx UltraFire makernote data: Date/Time Original " + year + "-" + month + "-" + day + " " + hour + ":" + minutes + ":" + seconds + " is not a valid date/time.");
         }*/
