/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "EXIF.h"

#include "mozilla/EndianUtils.h"
#include "mozilla/StaticPrefs_image.h"

namespace mozilla::image {

// Section references in this file refer to the EXIF v2.3 standard, also known
// as CIPA DC-008-Translation-2010.

// See Section 4.6.4, Table 4.
// Typesafe enums are intentionally not used here since we're comparing to raw
// integers produced by parsing.
enum class EXIFTag : uint16_t {
  Orientation = 0x112,
  XResolution = 0x11a,
  YResolution = 0x11b,
  PixelXDimension = 0xa002,
  PixelYDimension = 0xa003,
  ResolutionUnit = 0x128,
  IFDPointer = 0x8769,
};

// See Section 4.6.2.
enum EXIFType {
  ByteType = 1,
  ASCIIType = 2,
  ShortType = 3,
  LongType = 4,
  RationalType = 5,
  UndefinedType = 7,
  SignedLongType = 9,
  SignedRational = 10,
};

static const char* EXIFHeader = "Exif\0\0";
static const uint32_t EXIFHeaderLength = 6;

uint32_t EXIFParser::TIFFHeaderStart() const {
  return mExpectExifIdCode ? EXIFHeaderLength : 0;
}

struct ParsedEXIFData {
  Orientation orientation;
  Maybe<float> resolutionX;
  Maybe<float> resolutionY;
  Maybe<uint32_t> pixelXDimension;
  Maybe<uint32_t> pixelYDimension;
  Maybe<ResolutionUnit> resolutionUnit;
};

static float ToDppx(float aResolution, ResolutionUnit aUnit) {
  constexpr float kPointsPerInch = 72.0f;
  constexpr float kPointsPerCm = 1.0f / 2.54f;
  switch (aUnit) {
    case ResolutionUnit::Dpi:
      return aResolution / kPointsPerInch;
    case ResolutionUnit::Dpcm:
      return aResolution / kPointsPerCm;
  }
  MOZ_CRASH("Unknown resolution unit?");
}

static Resolution ResolutionFromParsedData(const ParsedEXIFData& aData,
                                           const gfx::IntSize& aRealImageSize) {
  if (!aData.resolutionUnit || !aData.resolutionX || !aData.resolutionY) {
    return {};
  }

  Resolution resolution{ToDppx(*aData.resolutionX, *aData.resolutionUnit),
                        ToDppx(*aData.resolutionY, *aData.resolutionUnit)};

  if (StaticPrefs::image_exif_density_correction_sanity_check_enabled()) {
    if (!aData.pixelXDimension || !aData.pixelYDimension) {
      return {};
    }

    const gfx::IntSize exifSize(*aData.pixelXDimension, *aData.pixelYDimension);

    gfx::IntSize scaledSize = aRealImageSize;
    resolution.ApplyTo(scaledSize.width, scaledSize.height);

    if (exifSize != scaledSize) {
      return {};
    }
  }

  return resolution;
}

/////////////////////////////////////////////////////////////
// Parse EXIF data, typically found in a JPEG's APP1 segment.
/////////////////////////////////////////////////////////////
EXIFData EXIFParser::ParseEXIF(const uint8_t* aData, const uint32_t aLength,
                               const gfx::IntSize& aRealImageSize) {
  if (!Initialize(aData, aLength)) {
    return EXIFData();
  }

  if (mExpectExifIdCode) {
    if (!ParseEXIFHeader()) {
      return EXIFData();
    }
  }

  uint32_t offsetIFD;
  if (!ParseTIFFHeader(offsetIFD)) {
    return EXIFData();
  }

  JumpTo(offsetIFD);

  ParsedEXIFData data;
  ParseIFD(data);

  return EXIFData{data.orientation,
                  ResolutionFromParsedData(data, aRealImageSize)};
}

/////////////////////////////////////////////////////////
// Parse the EXIF header. (Section 4.7.2, Figure 30)
/////////////////////////////////////////////////////////
bool EXIFParser::ParseEXIFHeader() {
  return MatchString(EXIFHeader, EXIFHeaderLength);
}

/////////////////////////////////////////////////////////
// Parse the TIFF header. (Section 4.5.2, Table 1)
/////////////////////////////////////////////////////////
bool EXIFParser::ParseTIFFHeader(uint32_t& aIFD0OffsetOut) {
  // Determine byte order.
  if (MatchString("MM\0*", 4)) {
    mByteOrder = ByteOrder::BigEndian;
  } else if (MatchString("II*\0", 4)) {
    mByteOrder = ByteOrder::LittleEndian;
  } else {
    return false;
  }

  // Determine offset of the 0th IFD. (It shouldn't be greater than 64k, which
  // is the maximum size of the entry APP1 segment.)
  uint32_t ifd0Offset;
  if (!ReadUInt32(ifd0Offset) || ifd0Offset > 64 * 1024) {
    return false;
  }

  // The IFD offset is relative to the beginning of the TIFF header, which
  // begins after the EXIF header, so we need to increase the offset
  // appropriately.
  aIFD0OffsetOut = ifd0Offset + TIFFHeaderStart();
  return true;
}

// An arbitrary limit on the amount of pointers that we'll chase, to prevent bad
// inputs getting us stuck.
constexpr uint32_t kMaxEXIFDepth = 16;

/////////////////////////////////////////////////////////
// Parse the entries in IFD0. (Section 4.6.2)
/////////////////////////////////////////////////////////
void EXIFParser::ParseIFD(ParsedEXIFData& aData, uint32_t aDepth) {
  if (NS_WARN_IF(aDepth > kMaxEXIFDepth)) {
    return;
  }

  uint16_t entryCount;
  if (!ReadUInt16(entryCount)) {
    return;
  }

  for (uint16_t entry = 0; entry < entryCount; ++entry) {
    // Read the fields of the 12-byte entry.
    uint16_t tag;
    if (!ReadUInt16(tag)) {
      return;
    }

    uint16_t type;
    if (!ReadUInt16(type)) {
      return;
    }

    uint32_t count;
    if (!ReadUInt32(count)) {
      return;
    }

    switch (EXIFTag(tag)) {
      case EXIFTag::Orientation:
        // We should have an orientation value here; go ahead and parse it.
        if (!ParseOrientation(type, count, aData.orientation)) {
          return;
        }
        break;
      case EXIFTag::ResolutionUnit:
        if (!ParseResolutionUnit(type, count, aData.resolutionUnit)) {
          return;
        }
        break;
      case EXIFTag::XResolution:
        if (!ParseResolution(type, count, aData.resolutionX)) {
          return;
        }
        break;
      case EXIFTag::YResolution:
        if (!ParseResolution(type, count, aData.resolutionY)) {
          return;
        }
        break;
      case EXIFTag::PixelXDimension:
        if (!ParseDimension(type, count, aData.pixelXDimension)) {
          return;
        }
        break;
      case EXIFTag::PixelYDimension:
        if (!ParseDimension(type, count, aData.pixelYDimension)) {
          return;
        }
        break;
      case EXIFTag::IFDPointer: {
        uint32_t offset;
        if (!ReadUInt32(offset)) {
          return;
        }

        ScopedJump jump(*this, offset + TIFFHeaderStart());
        ParseIFD(aData, aDepth + 1);
        break;
      }

      default:
        Advance(4);
        break;
    }
  }
}

bool EXIFParser::ReadRational(float& aOut) {
  // Values larger than 4 bytes (like rationals) are specified as an offset into
  // the TIFF header.
  uint32_t valueOffset;
  if (!ReadUInt32(valueOffset)) {
    return false;
  }
  ScopedJump jumpToHeader(*this, valueOffset + TIFFHeaderStart());
  uint32_t numerator;
  if (!ReadUInt32(numerator)) {
    return false;
  }
  uint32_t denominator;
  if (!ReadUInt32(denominator)) {
    return false;
  }
  if (denominator == 0) {
    return false;
  }
  aOut = float(numerator) / float(denominator);
  return true;
}

bool EXIFParser::ParseResolution(uint16_t aType, uint32_t aCount,
                                 Maybe<float>& aOut) {
  if (aType != RationalType || aCount != 1) {
    return false;
  }
  float value;
  if (!ReadRational(value)) {
    return false;
  }
  if (value == 0.0f) {
    return false;
  }
  aOut = Some(value);
  return true;
}

bool EXIFParser::ParseDimension(uint16_t aType, uint32_t aCount,
                                Maybe<uint32_t>& aOut) {
  if (aCount != 1) {
    return false;
  }

  switch (aType) {
    case ShortType: {
      uint16_t value;
      if (!ReadUInt16(value)) {
        return false;
      }
      aOut = Some(value);
      Advance(2);
      break;
    }
    case LongType: {
      uint32_t value;
      if (!ReadUInt32(value)) {
        return false;
      }
      aOut = Some(value);
      break;
    }
    default:
      return false;
  }
  return true;
}

bool EXIFParser::ParseResolutionUnit(uint16_t aType, uint32_t aCount,
                                     Maybe<ResolutionUnit>& aOut) {
  if (aType != ShortType || aCount != 1) {
    return false;
  }
  uint16_t value;
  if (!ReadUInt16(value)) {
    return false;
  }
  switch (value) {
    case 2:
      aOut = Some(ResolutionUnit::Dpi);
      break;
    case 3:
      aOut = Some(ResolutionUnit::Dpcm);
      break;
    default:
      return false;
  }

  // This is a 32-bit field, but the unit value only occupies the first 16 bits.
  // We need to advance another 16 bits to consume the entire field.
  Advance(2);
  return true;
}

bool EXIFParser::ParseOrientation(uint16_t aType, uint32_t aCount,
                                  Orientation& aOut) {
  // Sanity check the type and count.
  if (aType != ShortType || aCount != 1) {
    return false;
  }

  uint16_t value;
  if (!ReadUInt16(value)) {
    return false;
  }

  switch (value) {
    case 1:
      aOut = Orientation(Angle::D0, Flip::Unflipped);
      break;
    case 2:
      aOut = Orientation(Angle::D0, Flip::Horizontal);
      break;
    case 3:
      aOut = Orientation(Angle::D180, Flip::Unflipped);
      break;
    case 4:
      aOut = Orientation(Angle::D180, Flip::Horizontal);
      break;
    case 5:
      aOut = Orientation(Angle::D90, Flip::Horizontal);
      break;
    case 6:
      aOut = Orientation(Angle::D90, Flip::Unflipped);
      break;
    case 7:
      aOut = Orientation(Angle::D270, Flip::Horizontal);
      break;
    case 8:
      aOut = Orientation(Angle::D270, Flip::Unflipped);
      break;
    default:
      return false;
  }

  // This is a 32-bit field, but the orientation value only occupies the first
  // 16 bits. We need to advance another 16 bits to consume the entire field.
  Advance(2);
  return true;
}

bool EXIFParser::Initialize(const uint8_t* aData, const uint32_t aLength) {
  if (aData == nullptr) {
    return false;
  }

  // An APP1 segment larger than 64k violates the JPEG standard.
  if (aLength > 64 * 1024) {
    return false;
  }

  mStart = mCurrent = aData;
  mLength = mRemainingLength = aLength;
  mByteOrder = ByteOrder::Unknown;
  return true;
}

void EXIFParser::Advance(const uint32_t aDistance) {
  if (mRemainingLength >= aDistance) {
    mCurrent += aDistance;
    mRemainingLength -= aDistance;
  } else {
    mCurrent = mStart;
    mRemainingLength = 0;
  }
}

void EXIFParser::JumpTo(const uint32_t aOffset) {
  if (mLength >= aOffset) {
    mCurrent = mStart + aOffset;
    mRemainingLength = mLength - aOffset;
  } else {
    mCurrent = mStart;
    mRemainingLength = 0;
  }
}

bool EXIFParser::MatchString(const char* aString, const uint32_t aLength) {
  if (mRemainingLength < aLength) {
    return false;
  }

  for (uint32_t i = 0; i < aLength; ++i) {
    if (mCurrent[i] != aString[i]) {
      return false;
    }
  }

  Advance(aLength);
  return true;
}

bool EXIFParser::MatchUInt16(const uint16_t aValue) {
  if (mRemainingLength < 2) {
    return false;
  }

  bool matched;
  switch (mByteOrder) {
    case ByteOrder::LittleEndian:
      matched = LittleEndian::readUint16(mCurrent) == aValue;
      break;
    case ByteOrder::BigEndian:
      matched = BigEndian::readUint16(mCurrent) == aValue;
      break;
    default:
      MOZ_ASSERT_UNREACHABLE("Should know the byte order by now");
      matched = false;
  }

  if (matched) {
    Advance(2);
  }

  return matched;
}

bool EXIFParser::ReadUInt16(uint16_t& aValue) {
  if (mRemainingLength < 2) {
    return false;
  }

  bool matched = true;
  switch (mByteOrder) {
    case ByteOrder::LittleEndian:
      aValue = LittleEndian::readUint16(mCurrent);
      break;
    case ByteOrder::BigEndian:
      aValue = BigEndian::readUint16(mCurrent);
      break;
    default:
      MOZ_ASSERT_UNREACHABLE("Should know the byte order by now");
      matched = false;
  }

  if (matched) {
    Advance(2);
  }

  return matched;
}

bool EXIFParser::ReadUInt32(uint32_t& aValue) {
  if (mRemainingLength < 4) {
    return false;
  }

  bool matched = true;
  switch (mByteOrder) {
    case ByteOrder::LittleEndian:
      aValue = LittleEndian::readUint32(mCurrent);
      break;
    case ByteOrder::BigEndian:
      aValue = BigEndian::readUint32(mCurrent);
      break;
    default:
      MOZ_ASSERT_UNREACHABLE("Should know the byte order by now");
      matched = false;
  }

  if (matched) {
    Advance(4);
  }

  return matched;
}

}  // namespace mozilla::image
