Created
May 19, 2016 19:48
-
-
Save yoya/6ac5e4181b1b0588eaaffde861b91fc2 to your computer and use it in GitHub Desktop.
Exif を PHP で分解してみた (異常系は手抜き)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
// ref) http://dsas.blog.klab.org/archives/52123322.html | |
class Exif { | |
private $data; | |
private $byteOrder; // 1:Big Endian, 2:Little Endian | |
private $IFDs = array(); | |
private $cursol = 0; | |
const IFD_OFFSET_BASE = 6; | |
function parse($data) { | |
$this->data = $data; | |
$head6 = substr($data, 0, 6); | |
if ($head6 != "Exif\0\0") { | |
throw new Exception("Unknown head 6 byte: $head6"); | |
return false; | |
} | |
$byteOrderId = substr($data, 6, 2); | |
switch ($byteOrderId) { | |
case "MM": // Big Endian | |
$this->byteOrder = 1; | |
break; | |
case "II": // Little Endian | |
$this->byteOrder = 2; | |
break; | |
default: | |
throw new Exception("Unknown byte order: $byteOrderId"); | |
} | |
//$tiffVersion = substr($data, 8, 2); | |
$this->setCursol(8); | |
$tiffVersion = $this->getUI16(); | |
if ($tiffVersion !== 0x002A) { | |
throw new Exception("Unknown tiff version:0x".dechex($tiffVersion)); | |
} | |
$IFD0thOffset = $this->getUI32(); | |
$IFD0thTable = $this->makeIFDTable(self::IFD_OFFSET_BASE + $IFD0thOffset); | |
$this->IDFs["0th"] = $IFD0thTable; | |
$IFD1thOffset = $this->getUI32(); | |
if ($IFD1thOffset > 0) { | |
$IFD1thTable = $this->makeIFDTable(self::IFD_OFFSET_BASE + $IFD1thOffset); | |
while (($nextIFDOffset = $this->getUI32()) > 0) { | |
$nextIFDTable = $this->makeIFDTable(self::IFD_OFFSET_BASE + $nextIFDOffset); | |
$IFD1thTable = $IFD1thTable + $nextIFDTable; | |
} | |
$this->IDFs["1th"] = $IFD1thTable; | |
} | |
} | |
private function setCursol($cursol) { | |
$this->cursol = $cursol; | |
} | |
private function getCursol() { | |
return $this->cursol; | |
} | |
private function incrCursol($incr) { | |
$this->cursol += $incr; | |
} | |
private function getUI8() { | |
$v = ord($this->data[$this->cursol]); | |
$this->cursol += 1; | |
return $v; | |
} | |
private function getUI16() { | |
if ($this->byteOrder === 1) { | |
$a = unpack("n", substr($this->data, $this->cursol, 4)); | |
} else { | |
$a = unpack("v", substr($this->data, $this->cursol, 4)); | |
} | |
$this->cursol += 2; | |
return $a[1]; | |
} | |
private function _getUI32() { | |
if ($this->byteOrder === 1) { | |
$a = unpack("N", substr($this->data, $this->cursol, 4)); | |
} else { | |
$a = unpack("V", substr($this->data, $this->cursol, 4)); | |
} | |
$this->cursol += 4; | |
return $a[1]; | |
} | |
private function getUI32() { | |
$value = $this->_getUI32(); | |
if (PHP_INT_SIZE > 4) { // 64bit CPU | |
return $value; | |
} | |
if ($value < 0x80000000) { | |
return $value; | |
} | |
return $value + 0x100000000; // 2-negative | |
} | |
private function getSI32() { | |
$value = $this->_getUI32(); | |
if (PHP_INT_SIZE <= 4) { // 32bit CPU | |
return $value; | |
} | |
if ($value < 0x80000000) { | |
return $value; | |
} | |
return $value - 0x100000000; // 2-negative | |
} | |
private function getData($length) { | |
$v = substr($this->data, $this->cursol, $length); | |
$this->cursol += $length; | |
return $v; | |
} | |
private function makeIFDTable($offset) { | |
$data = $this->data; | |
$this->setCursol($offset); | |
$nTags = $this->getUI16(); | |
$IFDTable = array(); | |
for ($i = 0 ; $i < $nTags ; $i++) { | |
$tagNo = $this->getUI16(); | |
$tagType = $this->getUI16(); | |
$tagCount = $this->getUI32(); | |
// echo "tag: $tagNo $tagType $tagCount\n"; | |
$value = null; | |
$nextCursol = $this->getCursol() + 4; | |
switch ($tagType) { | |
case 1: // BYTE | |
if ($tagCount > 4) { | |
$tagOffset = $this->getUI32(); | |
$this->setCursol(self::IFD_OFFSET_BASE + $tagOffset); | |
} | |
for ($j = 0 ; $j < $tagCount ; $j++) { | |
$value []= $this->getUI8(); | |
} | |
break; | |
case 2: // ASCII | |
case 7: // UNDEFINED | |
if ($tagCount > 4) { | |
$tagOffset = $this->getUI32(); | |
$this->setCursol(self::IFD_OFFSET_BASE + $tagOffset); | |
} | |
$value = $this->getData($tagCount); | |
break; | |
case 3: // SHORT | |
if ($tagCount > 2) { | |
$tagOffset = $this->getUI32(); | |
$this->setCursol(self::IFD_OFFSET_BASE + $tagOffset); | |
} | |
for ($j = 0 ; $j < $tagCount ; $j++) { | |
$value []= $this->getUI16(); | |
} | |
break; | |
case 4: // LONG | |
if ($tagCount > 1) { | |
$tagOffset = $this->getUI32(); | |
$this->setCursol(self::IFD_OFFSET_BASE + $tagOffset); | |
} | |
for ($j = 0 ; $j < $tagCount ; $j++) { | |
$value []= $this->getUI32(); | |
} | |
break; | |
case 9: // SLONG | |
if ($tagCount > 1) { | |
$tagOffset = $this->getUI32(); | |
$this->setCursol(self::IFD_OFFSET_BASE + $tagOffset); | |
} | |
for ($j = 0 ; $j < $tagCount ; $j++) { | |
$value []= $this->getSI32(); | |
} | |
break; | |
case 5: // RATIONAL | |
$tagOffset = $this->getUI32(); | |
$this->setCursol(self::IFD_OFFSET_BASE + $tagOffset); | |
for ($j = 0 ; $j < $tagCount ; $j++) { | |
$numer = $this->getUI32(); | |
$denom = $this->getUI32(); | |
$value []= [$numer, $denom]; | |
} | |
break; | |
case 10: // RATIONAL | |
$tagOffset = $this->getUI32(); | |
$this->setCursol(self::IFD_OFFSET_BASE + $tagOffset); | |
for ($j = 0 ; $j < $tagCount ; $j++) { | |
$numer = $this->getSI32(); | |
$denom = $this->getSI32(); | |
$value []= [$numer, $denom]; | |
} | |
break; | |
default: | |
throw new Exception("Unknown tagType:$tagType"); | |
} | |
$this->setCursol($nextCursol); | |
$IFDTable[$tagNo] = $value; | |
} | |
$IFDTypes = [0x8769 => "Exif", 0x8769 => "GPSInfo", 0xA005 => "Interoperability"]; | |
foreach ($IFDTypes as $offset => $name) { | |
if (isset($IFDTable[$offset])) { | |
$o = $IFDTable[$offset][0]; | |
$table = $this->makeIFDTable(self::IFD_OFFSET_BASE + $o); | |
$this->IDFs[$name] = $table; | |
} | |
} | |
return $IFDTable; | |
} | |
function listIDFs() { | |
return array_keys($this->IDFs); | |
} | |
function getIDF($key) { | |
if (isset($this->IFDs[$key])) { | |
return $this->IFDs[$key]; | |
} | |
return false; | |
} | |
} | |
$data = file_get_contents($argv[1]); | |
$exif = new Exif(); | |
$exif->parse($data); | |
var_dump($exif->IDFs); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment