DXR is a code search and navigation tool aimed at making sense of large projects. It supports full-text and regex searches as well as structural queries.

Header

Mercurial (5350524bb654)

VCS Links

Line Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* 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"

namespace mozilla {
namespace 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 EXIFTag
{
  OrientationTag = 0x112,
};

// 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;

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

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

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

  JumpTo(offsetIFD);

  Orientation orientation;
  if (!ParseIFD0(orientation)) {
    return EXIFData();
  }

  // We only care about orientation at this point, so we don't bother with the
  // other IFDs. If we got this far we're done.
  return EXIFData(orientation);
}

/////////////////////////////////////////////////////////
// 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 + EXIFHeaderLength;
  return true;
}

/////////////////////////////////////////////////////////
// Parse the entries in IFD0. (Section 4.6.2)
/////////////////////////////////////////////////////////
bool
EXIFParser::ParseIFD0(Orientation& aOrientationOut)
{
  uint16_t entryCount;
  if (!ReadUInt16(entryCount)) {
    return false;
  }

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

    // Right now, we only care about orientation, so we immediately skip to the
    // next entry if we find anything else.
    if (tag != OrientationTag) {
      Advance(10);
      continue;
    }

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

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

    // We should have an orientation value here; go ahead and parse it.
    if (!ParseOrientation(type, count, aOrientationOut)) {
      return false;
    }

    // Since the orientation is all we care about, we're done.
    return true;
  }

  // We didn't find an orientation field in the IFD. That's OK; we assume the
  // default orientation in that case.
  aOrientationOut = Orientation();
  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:
      NS_NOTREACHED("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:
      NS_NOTREACHED("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:
      NS_NOTREACHED("Should know the byte order by now");
      matched = false;
  }

  if (matched) {
    Advance(4);
  }

  return matched;
}

} // namespace image
} // namespace mozilla