function read_exif_data_raw($path, $verbose) { if ($path == '' || $path == 'none') { return; } $in = @fopen($path, 'rb'); // the b is for windows machines to open in binary mode $seek = @fopen($path, 'rb'); // There may be an elegant way to do this with one file handle. $globalOffset = 0; if (!isset($verbose)) { $verbose = 0; } $result['VerboseOutput'] = $verbose; $result['Errors'] = 0; if (!$in || !$seek) { // if the path was invalid, this error will catch it $result['Errors'] = 1; $result['Error'][$result['Errors']] = gettext('The file could not be found.'); return $result; } $GLOBALS['exiferFileSize'] = filesize($path); // First 2 bytes of JPEG are 0xFFD8 $data = bin2hex(fread($in, 2)); if ($data == 'ffd8') { $result['ValidJpeg'] = 1; } else { $result['ValidJpeg'] = 0; fseek($in, 0); } $result['ValidIPTCData'] = 0; $result['ValidJFIFData'] = 0; $result['ValidEXIFData'] = 0; $result['ValidAPP2Data'] = 0; $result['ValidCOMData'] = 0; if ($result['ValidJpeg'] == 1) { // Next 2 bytes are MARKER tag (0xFFE#) $data = bin2hex(fread($in, 2)); $size = bin2hex(fread($in, 2)); // LOOP THROUGH MARKERS TILL YOU GET TO FFE1 (exif marker) $abortCount = 0; while (!feof($in) && $data != 'ffe1' && $data != 'ffc0' && $data != 'ffd9' && ++$abortCount < 200) { if ($data == 'ffe0') { // JFIF Marker $result['ValidJFIFData'] = 1; $result['JFIF']['Size'] = hexdec($size); if (hexdec($size) - 2 > 0) { $data = fread($in, 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; } else { if ($data == 'ffed') { // IPTC Marker $result['ValidIPTCData'] = 1; $result['IPTC']['Size'] = hexdec($size); if (hexdec($size) - 2 > 0) { $data = fread($in, hexdec($size) - 2); $result['IPTC']['Data'] = $data; } $globalOffset += hexdec($size) + 2; } else { if ($data == 'ffe2') { // EXIF extension Marker $result['ValidAPP2Data'] = 1; $result['APP2']['Size'] = hexdec($size); if (hexdec($size) - 2 > 0) { $data = fread($in, hexdec($size) - 2); $result['APP2']['Data'] = $data; } $globalOffset += hexdec($size) + 2; } else { if ($data == 'fffe') { // COM extension Marker $result['ValidCOMData'] = 1; $result['COM']['Size'] = hexdec($size); if (hexdec($size) - 2 > 0) { $data = fread($in, hexdec($size) - 2); $result['COM']['Data'] = $data; } $globalOffset += hexdec($size) + 2; } else { if ($data == 'ffe1') { $result['ValidEXIFData'] = 1; } } } } } $data = bin2hex(fread($in, 2)); $size = bin2hex(fread($in, 2)); } // END MARKER LOOP if ($data == 'ffe1') { $result['ValidEXIFData'] = 1; } else { fclose($in); fclose($seek); return $result; } // Size of APP1 $result['APP1Size'] = hexdec($size); // Start of APP1 block starts with 'Exif' header (6 bytes) $header = fread($in, 6); } // END IF ValidJpeg // Then theres a TIFF header with 2 bytes of endieness (II or MM) $header = fread($in, 2); if ($header === 'II') { $intel = 1; $result['Endien'] = 'Intel'; } else { if ($header === 'MM') { $intel = 0; $result['Endien'] = 'Motorola'; } else { $intel = 1; // not sure what the default should be, but this seems reasonable $result['Endien'] = 'Unknown'; } } // 2 bytes of 0x002a $tag = bin2hex(fread($in, 2)); // Then 4 bytes of offset to IFD0 (usually 8 which includes all 8 bytes of TIFF header) $offset = bin2hex(fread($in, 4)); if ($intel == 1) { $offset = intel2Moto($offset); } // Check for extremely large values here if (hexdec($offset) > 100000) { $result['ValidEXIFData'] = 0; fclose($in); fclose($seek); return $result; } if (hexdec($offset) > 8) { $unknown = fread($in, hexdec($offset) - 8); } // fixed this bug in 1.3 // add 12 to the offset to account for TIFF header if ($result['ValidJpeg'] == 1) { $globalOffset += 12; } //=========================================================== // Start of IFD0 $num = bin2hex(fread($in, 2)); if ($intel == 1) { $num = intel2Moto($num); } $num = hexdec($num); $result['IFD0NumTags'] = $num; if ($num < 1000) { // 1000 entries is too much and is probably an error. for ($i = 0; $i < $num; $i++) { read_entry($result, $in, $seek, $intel, 'IFD0', $globalOffset); } } else { $result['Errors'] = $result['Errors'] + 1; $result['Error'][$result['Errors']] = 'Illegal size for IFD0'; } // store offset to IFD1 $offset = bin2hex(fread($in, 4)); if ($intel == 1) { $offset = intel2Moto($offset); } $result['IFD1Offset'] = hexdec($offset); // Check for SubIFD if (!isset($result['IFD0']['ExifOffset']) || $result['IFD0']['ExifOffset'] == 0) { fclose($in); fclose($seek); return $result; } // seek to SubIFD (Value of ExifOffset tag) above. $ExitOffset = $result['IFD0']['ExifOffset']; $v = fseek($in, $globalOffset + $ExitOffset); if ($v == -1) { $result['Errors'] = $result['Errors'] + 1; $result['Error'][$result['Errors']] = gettext('Could not Find SubIFD'); } //=========================================================== // Start of SubIFD $num = bin2hex(fread($in, 2)); if ($intel == 1) { $num = intel2Moto($num); } $num = hexdec($num); $result['SubIFDNumTags'] = $num; if ($num < 1000) { // 1000 entries is too much and is probably an error. for ($i = 0; $i < $num; $i++) { read_entry($result, $in, $seek, $intel, 'SubIFD', $globalOffset); } } else { $result['Errors'] = $result['Errors'] + 1; $result['Error'][$result['Errors']] = gettext('Illegal size for SubIFD'); } // Add the 35mm equivalent focal length: if (isset($result['IFD0']['FocalLengthIn35mmFilm']) && !isset($result['SubIFD']['FocalLengthIn35mmFilm'])) { // found in the wrong place $result['SubIFD']['FocalLengthIn35mmFilm'] = $result['IFD0']['FocalLengthIn35mmFilm']; } if (!isset($result['SubIFD']['FocalLengthIn35mmFilm'])) { $result['SubIFD']['FocalLengthIn35mmFilm'] = get35mmEquivFocalLength($result); } // Check for IFD1 if (!isset($result['IFD1Offset']) || $result['IFD1Offset'] == 0) { fclose($in); fclose($seek); return $result; } // seek to IFD1 $v = fseek($in, $globalOffset + $result['IFD1Offset']); if ($v == -1) { $result['Errors'] = $result['Errors'] + 1; $result['Error'][$result['Errors']] = gettext('Could not Find IFD1'); } //=========================================================== // Start of IFD1 $num = bin2hex(fread($in, 2)); if ($intel == 1) { $num = intel2Moto($num); } $num = hexdec($num); $result['IFD1NumTags'] = $num; if ($num < 1000) { // 1000 entries is too much and is probably an error. for ($i = 0; $i < $num; $i++) { read_entry($result, $in, $seek, $intel, 'IFD1', $globalOffset); } } else { $result['Errors'] = $result['Errors'] + 1; $result['Error'][$result['Errors']] = gettext('Illegal size for IFD1'); } // If verbose output is on, include the thumbnail raw data... if ($result['VerboseOutput'] == 1 && $result['IFD1']['JpegIFOffset'] > 0 && $result['IFD1']['JpegIFByteCount'] > 0) { $v = fseek($seek, $globalOffset + $result['IFD1']['JpegIFOffset']); if ($v == 0) { $data = fread($seek, $result['IFD1']['JpegIFByteCount']); } else { if ($v == -1) { $result['Errors'] = $result['Errors'] + 1; } } $result['IFD1']['ThumbnailData'] = $data; } // Check for Interoperability IFD if (!isset($result['SubIFD']['ExifInteroperabilityOffset']) || $result['SubIFD']['ExifInteroperabilityOffset'] == 0) { fclose($in); fclose($seek); return $result; } // Seek to InteroperabilityIFD $v = fseek($in, $globalOffset + $result['SubIFD']['ExifInteroperabilityOffset']); if ($v == -1) { $result['Errors'] = $result['Errors'] + 1; $result['Error'][$result['Errors']] = gettext('Could not Find InteroperabilityIFD'); } //=========================================================== // Start of InteroperabilityIFD $num = bin2hex(fread($in, 2)); if ($intel == 1) { $num = intel2Moto($num); } $num = hexdec($num); $result['InteroperabilityIFDNumTags'] = $num; if ($num < 1000) { // 1000 entries is too much and is probably an error. for ($i = 0; $i < $num; $i++) { read_entry($result, $in, $seek, $intel, 'InteroperabilityIFD', $globalOffset); } } else { $result['Errors'] = $result['Errors'] + 1; $result['Error'][$result['Errors']] = gettext('Illegal size for InteroperabilityIFD'); } fclose($in); fclose($seek); return $result; }
function read_exif_data_raw($path, $verbose) { if ($path == '' || $path == 'none') { return; } $in = @fopen($path, "rb"); //the b is for windows machines to open in binary mode $seek = @fopen($path, "rb"); //There may be an elegant way to do this with one file handle. $globalOffset = 0; if (!isset($verbose)) { $verbose = 0; } $result['VerboseOutput'] = $verbose; $result['Errors'] = 0; if (!$in || !$seek) { //if the path was invalid, this error will catch it $result['Errors'] = 1; $result['Error'][$result['Errors']] = "The file could not be found."; return $result; } //First 2 bytes of JPEG are 0xFFD8 $data = bin2hex(fread($in, 2)); if ($data == "ffd8") { $result['ValidJpeg'] = 1; } else { $result['ValidJpeg'] = 0; fclose($in); fclose($seek); 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(fread($in, 2)); $size = bin2hex(fread($in, 2)); //LOOP THROUGH MARKERS TILL YOU GET TO FFE1 (exif marker) while (!feof($in) && $data != "ffe1" && $data != "ffc0" && $data != "ffd9") { if ($data == "ffe0") { //JFIF Marker $result['ValidJFIFData'] = 1; $result['JFIF']['Size'] = hexdec($size); if (hexdec($size) - 2 > 0) { $data = fread($in, 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; } else { if ($data == "ffed") { //IPTC Marker $result['ValidIPTCData'] = 1; $result['IPTC']['Size'] = hexdec($size); if (hexdec($size) - 2 > 0) { $data = fread($in, hexdec($size) - 2); $result['IPTC']['Data'] = $data; } $globalOffset += hexdec($size) + 2; } else { if ($data == "ffe2") { //EXIF extension Marker $result['ValidAPP2Data'] = 1; $result['APP2']['Size'] = hexdec($size); if (hexdec($size) - 2 > 0) { $data = fread($in, hexdec($size) - 2); $result['APP2']['Data'] = $data; } $globalOffset += hexdec($size) + 2; } else { if ($data == "fffe") { //COM extension Marker $result['ValidCOMData'] = 1; $result['COM']['Size'] = hexdec($size); if (hexdec($size) - 2 > 0) { $data = fread($in, hexdec($size) - 2); $result['COM']['Data'] = $data; } $globalOffset += hexdec($size) + 2; } else { if ($data == "ffe1") { $result['ValidEXIFData'] = 1; } } } } } $data = bin2hex(fread($in, 2)); $size = bin2hex(fread($in, 2)); } //END MARKER LOOP if ($data == "ffe1") { $result['ValidEXIFData'] = 1; } else { fclose($in); fclose($seek); return $result; } //Size of APP1 $result['APP1Size'] = hexdec($size); //Start of APP1 block starts with "Exif" header (6 bytes) $header = fread($in, 6); //Then theres a TIFF header with 2 bytes of endieness (II or MM) $header = fread($in, 2); if ($header === "II") { $intel = 1; $result['Endien'] = "Intel"; } else { if ($header === "MM") { $intel = 0; $result['Endien'] = "Motorola"; } else { $intel = 1; //not sure what the default should be, but this seems reasonable $result['Endien'] = "Unknown"; } } //2 bytes of 0x002a $tag = bin2hex(fread($in, 2)); //Then 4 bytes of offset to IFD0 (usually 8 which includes all 8 bytes of TIFF header) $offset = bin2hex(fread($in, 4)); if ($intel == 1) { $offset = intel2Moto($offset); } // Check for extremely large values here if (hexdec($offset) > 100000) { $result['ValidEXIFData'] = 0; fclose($in); fclose($seek); return $result; } if (hexdec($offset) > 8) { $unknown = fread($in, hexdec($offset) - 8); } //fixed this bug in 1.3 //add 12 to the offset to account for TIFF header $globalOffset += 12; //===========================================================Start of IFD0 $num = bin2hex(fread($in, 2)); if ($intel == 1) { $num = intel2Moto($num); } $num = hexdec($num); $result['IFD0NumTags'] = $num; if ($num < 1000) { //1000 entries is too much and is probably an error. for ($i = 0; $i < $num; $i++) { read_entry($result, $in, $seek, $intel, "IFD0", $globalOffset); } } else { $result['Errors'] = $result['Errors'] + 1; $result['Error'][$result['Errors']] = "Illegal size for IFD0"; } //store offset to IFD1 $offset = bin2hex(fread($in, 4)); if ($intel == 1) { $offset = intel2Moto($offset); } $result['IFD1Offset'] = hexdec($offset); //Check for SubIFD if (!isset($result['IFD0']['ExifOffset']) || $result['IFD0']['ExifOffset'] == 0) { fclose($in); fclose($seek); return $result; } //seek to SubIFD (Value of ExifOffset tag) above. $ExitOffset = $result['IFD0']['ExifOffset']; $v = fseek($in, $globalOffset + $ExitOffset); if ($v == -1) { $result['Errors'] = $result['Errors'] + 1; $result['Error'][$result['Errors']] = "Couldnt Find SubIFD"; } //===========================================================Start of SubIFD $num = bin2hex(fread($in, 2)); if ($intel == 1) { $num = intel2Moto($num); } $num = hexdec($num); $result['SubIFDNumTags'] = $num; if ($num < 1000) { //1000 entries is too much and is probably an error. for ($i = 0; $i < $num; $i++) { read_entry($result, $in, $seek, $intel, "SubIFD", $globalOffset); } } else { $result['Errors'] = $result['Errors'] + 1; $result['Error'][$result['Errors']] = "Illegal size for SubIFD"; } //Check for IFD1 if (!isset($result['IFD1Offset']) || $result['IFD1Offset'] == 0) { fclose($in); fclose($seek); return $result; } //seek to IFD1 $v = fseek($in, $globalOffset + $result['IFD1Offset']); if ($v == -1) { $result['Errors'] = $result['Errors'] + 1; $result['Error'][$result['Errors']] = "Couldnt Find IFD1"; } //===========================================================Start of IFD1 $num = bin2hex(fread($in, 2)); if ($intel == 1) { $num = intel2Moto($num); } $num = hexdec($num); $result['IFD1NumTags'] = $num; if ($num < 1000) { //1000 entries is too much and is probably an error. for ($i = 0; $i < $num; $i++) { read_entry($result, $in, $seek, $intel, "IFD1", $globalOffset); } } else { $result['Errors'] = $result['Errors'] + 1; $result['Error'][$result['Errors']] = "Illegal size for IFD1"; } //if verbose output is on, stick in the thumbnail raw data if ($result['VerboseOutput'] == 1 && $result['IFD1']['JpegIFOffset'] > 0 && $result['IFD1']['JpegIFByteCount'] > 0) { $v = fseek($seek, $globalOffset + $result['IFD1']['JpegIFOffset']); if ($v == 0) { $data = fread($seek, $result['IFD1']['JpegIFByteCount']); } else { if ($v == -1) { $result['Errors'] = $result['Errors'] + 1; } } $result['IFD1']["ThumbnailData"] = $data; } //Check for Interoperability IFD if (!isset($result['SubIFD']['ExifInteroperabilityOffset']) || $result['SubIFD']['ExifInteroperabilityOffset'] == 0) { fclose($in); fclose($seek); return $result; } //seek to InteroperabilityIFD $v = fseek($in, $globalOffset + $result['SubIFD']['ExifInteroperabilityOffset']); if ($v == -1) { $result['Errors'] = $result['Errors'] + 1; $result['Error'][$result['Errors']] = "Couldnt Find InteroperabilityIFD"; } //===========================================================Start of InteroperabilityIFD $num = bin2hex(fread($in, 2)); if ($intel == 1) { $num = intel2Moto($num); } $num = hexdec($num); $result['InteroperabilityIFDNumTags'] = $num; if ($num < 1000) { //1000 entries is too much and is probably an error. for ($i = 0; $i < $num; $i++) { read_entry($result, $in, $seek, $intel, "InteroperabilityIFD", $globalOffset); } } else { $result['Errors'] = $result['Errors'] + 1; $result['Error'][$result['Errors']] = "Illegal size for InteroperabilityIFD"; } fclose($in); fclose($seek); return $result; }