Skip to content

Instantly share code, notes, and snippets.

@elieux
Last active April 2, 2017 14:40
Show Gist options
  • Save elieux/85ea9477256f417530cc to your computer and use it in GitHub Desktop.
Save elieux/85ea9477256f417530cc to your computer and use it in GitHub Desktop.
PHP Unserializer
<?php
/*
* Custom PHP unserializer with better error reporting
*
* Does not support objects.
*/
class Unserializer {
private $serialized;
private $len;
private $i;
public function __construct($serialized) {
$this->serialized = $serialized;
$this->len = strlen($this->serialized);
$this->i = 0;
}
private function read() {
if ($this->i >= $this->len) {
$this->err("Unexpected end");
}
$b = substr($this->serialized, $this->i, 1);
$this->i++;
return $b;
}
private function err($msg) {
$offset = $this->i - 1;
throw new Exception("{$msg} at {$offset}");
}
private function arr() {
$len = $this->num(true);
$this->byte(':');
$this->byte('{');
$result = array();
while ($len-- > 0) {
$key = $this->any();
if (!is_scalar($key)) {
$this->err("Non-scalar key");
}
if (isset($result[$key])) {
$this->err("Duplicate key '{$key}'");
}
$value = $this->any();
$result[$key] = $value;
}
$this->byte('}');
return $result;
}
private function num($int_only = false) {
$result = 0;
$b = $this->byte('0123456789');
$result *= 10;
$result += $b;
while (true) {
$b = $this->byte($int_only ? '0123456789' : '0123456789.', false);
if ($b === null) {
return $result;
}
if ($b === '.') {
break;
}
$result *= 10;
$result += $b;
}
$denom = 1;
while (true) {
$b = $this->byte('0123456789', false);
if ($b === null) {
return $result;
}
$denom *= 10;
$result += $b / $denom;
}
}
private function bool() {
$b = $this->byte('01');
if ($b == '1') {
$result = true;
} else if ($b == '0') {
$result = false;
}
$this->byte(';');
return $result;
}
private function str() {
$len = $this->num(true);
$this->byte(':');
$this->byte('"');
$result = '';
while ($len-- > 0) {
$result .= $this->read();
}
$this->byte('"');
$this->byte(';');
return $result;
}
private function byte($exp, $err = true) {
$exp = str_split($exp);
$b = $this->read();
if (!in_array($b, $exp, true)) {
if ($err) {
$exp = implode(' or ', array_map(function($c) { return "'{$c}'"; }, $exp));
$this->err("Unexpected '{$b}' (expecting {$exp})");
} else {
$this->i--;
return null;
}
}
return $b;
}
private function any() {
$b = $this->byte('bidsaN');
if (in_array($b, array('b', 'i', 'd', 's', 'a'))) {
$this->byte(':');
if ($b === 'b') {
$result = $this->bool();
} else if ($b === 'i' || $b === 'd') {
$result = $this->num();
$this->byte(';');
} else if ($b === 's') {
$result = $this->str();
} else if ($b === 'a') {
$result = $this->arr();
}
} else if ($b === 'N') {
$result = null;
$this->byte(';');
}
return $result;
}
public function unserialize() {
$result = $this->any();
if ($this->len !== $this->i) {
$this->err("Unexpected more bytes");
}
return $result;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment