/** * * @param mixed $path Filename -or- an open PHP stream. * * @return array */ protected function _readData($path) { if (is_resource($path)) { $in = new Horde_Stream_Existing(array('stream' => $path)); $in->rewind(); } else { $in = new Horde_Stream_Existing(array('stream' => @fopen($path, 'rb'))); } $globalOffset = 0; $result = array('Errors' => 0); // if the path was invalid, this error will catch it if (!$in) { $result['Errors'] = 1; $result['Error'][$result['Errors']] = Horde_Image_Translation::t("The file could not be opened."); return $result; } // First 2 bytes of JPEG are 0xFFD8 $data = bin2hex($in->substring(0, 2)); if ($data == 'ffd8') { $result['ValidJpeg'] = 1; } else { $result['ValidJpeg'] = 0; if (!is_resource($path)) { $in->close(); } return $result; } $result['ValidIPTCData'] = 0; $result['ValidJFIFData'] = 0; $result['ValidEXIFData'] = 0; $result['ValidAPP2Data'] = 0; $result['ValidCOMData'] = 0; // Next 2 bytes are marker tag (0xFFE#) $data = bin2hex($in->substring(0, 2)); $size = bin2hex($in->substring(0, 2)); // Loop through markers till you get to FFE1 (Exif marker) while (!$in->eof() && $data != 'ffe1' && $data != 'ffc0' && $data != 'ffd9') { switch ($data) { case 'ffe0': // JFIF Marker $result['ValidJFIFData'] = 1; $result['JFIF']['Size'] = hexdec($size); if (hexdec($size) - 2 > 0) { $data = $in->substring(0, hexdec($size) - 2); $result['JFIF']['Data'] = $data; } $result['JFIF']['Identifier'] = substr($data, 0, 5); $result['JFIF']['ExtensionCode'] = bin2hex(substr($data, 6, 1)); $globalOffset += hexdec($size) + 2; break; case 'ffed': // IPTC Marker $result['ValidIPTCData'] = 1; $result['IPTC']['Size'] = hexdec($size); if (hexdec($size) - 2 > 0) { $data = $in->substring(0, hexdec($size) - 2); $result['IPTC']['Data'] = $data; } $globalOffset += hexdec($size) + 2; break; case 'ffe2': // EXIF extension Marker $result['ValidAPP2Data'] = 1; $result['APP2']['Size'] = hexdec($size); if (hexdec($size) - 2 > 0) { $data = $in->substring(0, hexdec($size) - 2); $result['APP2']['Data'] = $data; } $globalOffset += hexdec($size) + 2; break; case 'fffe': // COM extension Marker $result['ValidCOMData'] = 1; $result['COM']['Size'] = hexdec($size); if (hexdec($size) - 2 > 0) { $data = $in->substring(0, hexdec($size) - 2); $result['COM']['Data'] = $data; } $globalOffset += hexdec($size) + 2; break; case 'ffe1': $result['ValidEXIFData'] = 1; break; } $data = bin2hex($in->substring(0, 2)); $size = bin2hex($in->substring(0, 2)); } if ($data != 'ffe1') { if (!is_resource($path)) { $in->close(); } return $result; } $result['ValidEXIFData'] = 1; // Size of APP1 $result['APP1Size'] = hexdec($size); // Start of APP1 block starts with 'Exif' header (6 bytes) $header = $in->substring(0, 6); // Then theres a TIFF header with 2 bytes of endieness (II or MM) $header = $in->substring(0, 2); switch ($header) { case 'II': $intel = 1; $result['Endien'] = 'Intel'; break; case 'MM': $intel = 0; $result['Endien'] = 'Motorola'; break; default: // not sure what the default should be, but this seems reasonable $intel = 1; $result['Endien'] = 'Unknown'; break; } // 2 bytes of 0x002a if (bin2hex($in->substring(0, 2)) != '002a') { $result['Errors'] = $result['Errors'] + 1; $result['Error'][$result['Errors']] = 'Unexpected value.'; return $result; } // Then 4 bytes of offset to IFD0 (usually 8 which includes all 8 bytes // of TIFF header) $offset = bin2hex($in->substring(0, 4)); if ($intel == 1) { $offset = Horde_Image_Exif::intel2Moto($offset); } // Check for extremely large values here if (hexdec($offset) > 100000) { $result['ValidEXIFData'] = 0; if (!is_resource($path)) { $in->close(); } return $result; } if (hexdec($offset) > 8) { $unknown = $in->substring(0, hexdec($offset) - 8); } // add 12 to the offset to account for TIFF header $globalOffset += 12; //=========================================================== // Start of IFD0 $num = bin2hex($in->substring(0, 2)); if ($intel == 1) { $num = Horde_Image_Exif::intel2Moto($num); } $num = hexdec($num); $result['IFD0NumTags'] = $num; // 1000 entries is too much and is probably an error. if ($num < 1000) { for ($i = 0; $i < $num; $i++) { $this->_readEntry($result, $in, $intel, 'IFD0', $globalOffset); } } else { $result['Errors'] = $result['Errors'] + 1; $result['Error'][$result['Errors']] = 'Illegal size for IFD0'; } // store offset to IFD1 $offset = bin2hex($in->substring(0, 4)); if ($intel == 1) { $offset = Horde_Image_Exif::intel2Moto($offset); } $result['IFD1Offset'] = hexdec($offset); // Check for SubIFD if (!isset($result['IFD0']['ExifOffset']) || $result['IFD0']['ExifOffset'] == 0) { if (!is_resource($path)) { $in->close(); } return $result; } // seek to SubIFD (Value of ExifOffset tag) above. $ExifOffset = $result['IFD0']['ExifOffset']; if (!$in->seek($globalOffset + $ExifOffset, false)) { $result['Errors'] = $result['Errors'] + 1; $result['Error'][$result['Errors']] = Horde_Image_Translation::t("Couldnt Find SubIFD"); } //=========================================================== // Start of SubIFD $num = bin2hex($in->substring(0, 2)); if ($intel == 1) { $num = Horde_Image_Exif::intel2Moto($num); } $num = hexdec($num); $result['SubIFDNumTags'] = $num; // 1000 entries is too much and is probably an error. if ($num < 1000) { for ($i = 0; $i < $num; $i++) { $this->_readEntry($result, $in, $intel, 'SubIFD', $globalOffset); } } else { $result['Errors'] = $result['Errors'] + 1; $result['Error'][$result['Errors']] = Horde_Image_Translation::t("Illegal size for SubIFD"); } // Add the 35mm equivalent focal length: // Now properly get this using the FocalLength35mmFilm tag //$result['SubIFD']['FocalLength35mmEquiv'] = get35mmEquivFocalLength($result); // Check for IFD1 if (!isset($result['IFD1Offset']) || $result['IFD1Offset'] == 0) { if (!is_resource($path)) { $in->close(); } return $result; } // seek to IFD1 if (!$in->seek($globalOffset + $result['IFD1Offset'], false)) { $result['Errors'] = $result['Errors'] + 1; $result['Error'][$result['Errors']] = Horde_Image_Translation::t("Couldnt Find IFD1"); } //=========================================================== // Start of IFD1 $num = bin2hex($in->substring(0, 2)); if ($intel == 1) { $num = Horde_Image_Exif::intel2Moto($num); } $num = hexdec($num); $result['IFD1NumTags'] = $num; // 1000 entries is too much and is probably an error. if ($num < 1000) { for ($i = 0; $i < $num; $i++) { $this->_readEntry($result, $in, $intel, 'IFD1', $globalOffset); } } else { $result['Errors'] = $result['Errors'] + 1; $result['Error'][$result['Errors']] = Horde_Image_Translation::t("Illegal size for IFD1"); } // include the thumbnail raw data... if ($result['IFD1']['JpegIFOffset'] > 0 && $result['IFD1']['JpegIFByteCount'] > 0) { $cpos = $in->pos(); if ($in->seek($globalOffset + $result['IFD1']['JpegIFOffset'], false)) { $data = $in->substring(0, $result['IFD1']['JpegIFByteCount']); } else { $result['Errors'] = $result['Errors'] + 1; } $result['IFD1']['ThumbnailData'] = $data; } // Check for Interoperability IFD if (!isset($result['SubIFD']['ExifInteroperabilityOffset']) || $result['SubIFD']['ExifInteroperabilityOffset'] == 0) { if (!is_resource($path)) { $in->close(); } return $result; } // Seek to InteroperabilityIFD if (!$in->seek($globalOffset + $result['SubIFD']['ExifInteroperabilityOffset'], false)) { $result['Errors'] = $result['Errors'] + 1; $result['Error'][$result['Errors']] = Horde_Image_Translation::t("Couldnt Find InteroperabilityIFD"); } //=========================================================== // Start of InteroperabilityIFD $num = bin2hex($in->substring(0, 2)); if ($intel == 1) { $num = Horde_Image_Exif::intel2Moto($num); } $num = hexdec($num); $result['InteroperabilityIFDNumTags'] = $num; // 1000 entries is too much and is probably an error. if ($num < 1000) { for ($i = 0; $i < $num; $i++) { $this->_readEntry($result, $in, $intel, 'InteroperabilityIFD', $globalOffset); } } else { $result['Errors'] = $result['Errors'] + 1; $result['Error'][$result['Errors']] = Horde_Image_Translation::t("Illegal size for InteroperabilityIFD"); } if (!is_resource($path)) { $in->close(); } return $result; }