Skip to content

Instantly share code, notes, and snippets.

@yoya
Created May 19, 2016 19:48
Show Gist options
  • Save yoya/6ac5e4181b1b0588eaaffde861b91fc2 to your computer and use it in GitHub Desktop.
Save yoya/6ac5e4181b1b0588eaaffde861b91fc2 to your computer and use it in GitHub Desktop.
Exif を PHP で分解してみた (異常系は手抜き)
<?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