mirror of
https://github.com/danog/ext-pq.git
synced 2024-12-02 09:18:02 +01:00
6405 lines
155 KiB
Plaintext
6405 lines
155 KiB
Plaintext
|
#!/usr/bin/env php
|
|||
|
<?php
|
|||
|
|
|||
|
/**
|
|||
|
* The installer sub-stub for extension phars
|
|||
|
*/
|
|||
|
|
|||
|
namespace pharext;
|
|||
|
|
|||
|
define("PHAREXT_PHAR", __FILE__);
|
|||
|
|
|||
|
spl_autoload_register(function($c) {
|
|||
|
return include strtr($c, "\\_", "//") . ".php";
|
|||
|
});
|
|||
|
|
|||
|
|
|||
|
|
|||
|
namespace pharext;
|
|||
|
|
|||
|
class Exception extends \Exception
|
|||
|
{
|
|||
|
public function __construct($message = null, $code = 0, $previous = null) {
|
|||
|
if (!isset($message)) {
|
|||
|
$last_error = error_get_last();
|
|||
|
$message = $last_error["message"];
|
|||
|
if (!$code) {
|
|||
|
$code = $last_error["type"];
|
|||
|
}
|
|||
|
}
|
|||
|
parent::__construct($message, $code, $previous);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
|
|||
|
namespace pharext;
|
|||
|
|
|||
|
use pharext\Exception;
|
|||
|
|
|||
|
/**
|
|||
|
* A temporary file/directory name
|
|||
|
*/
|
|||
|
class Tempname
|
|||
|
{
|
|||
|
/**
|
|||
|
* @var string
|
|||
|
*/
|
|||
|
private $name;
|
|||
|
|
|||
|
/**
|
|||
|
* @param string $prefix uniqid() prefix
|
|||
|
* @param string $suffix e.g. file extension
|
|||
|
*/
|
|||
|
public function __construct($prefix, $suffix = null) {
|
|||
|
$temp = sys_get_temp_dir() . "/pharext-" . $this->getUser();
|
|||
|
if (!is_dir($temp) && !mkdir($temp, 0700, true)) {
|
|||
|
throw new Exception;
|
|||
|
}
|
|||
|
$this->name = $temp ."/". uniqid($prefix) . $suffix;
|
|||
|
}
|
|||
|
|
|||
|
private function getUser() {
|
|||
|
if (extension_loaded("posix") && function_exists("posix_getpwuid")) {
|
|||
|
return posix_getpwuid(posix_getuid())["name"];
|
|||
|
}
|
|||
|
return trim(`whoami 2>/dev/null`)
|
|||
|
?: trim(`id -nu 2>/dev/null`)
|
|||
|
?: getenv("USER")
|
|||
|
?: get_current_user();
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* @return string
|
|||
|
*/
|
|||
|
public function __toString() {
|
|||
|
return (string) $this->name;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
|
|||
|
namespace pharext;
|
|||
|
|
|||
|
/**
|
|||
|
* Create a new temporary file
|
|||
|
*/
|
|||
|
class Tempfile extends \SplFileInfo
|
|||
|
{
|
|||
|
/**
|
|||
|
* @var resource
|
|||
|
*/
|
|||
|
private $handle;
|
|||
|
|
|||
|
/**
|
|||
|
* @param string $prefix uniqid() prefix
|
|||
|
* @param string $suffix e.g. file extension
|
|||
|
* @throws \pharext\Exception
|
|||
|
*/
|
|||
|
public function __construct($prefix, $suffix = ".tmp") {
|
|||
|
$tries = 0;
|
|||
|
$omask = umask(077);
|
|||
|
do {
|
|||
|
$path = new Tempname($prefix, $suffix);
|
|||
|
$this->handle = fopen($path, "x");
|
|||
|
} while (!is_resource($this->handle) && $tries++ < 10);
|
|||
|
umask($omask);
|
|||
|
|
|||
|
if (!is_resource($this->handle)) {
|
|||
|
throw new Exception("Could not create temporary file");
|
|||
|
}
|
|||
|
|
|||
|
parent::__construct($path);
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Unlink the file
|
|||
|
*/
|
|||
|
public function __destruct() {
|
|||
|
if (is_file($this->getPathname())) {
|
|||
|
@unlink($this->getPathname());
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Close the stream
|
|||
|
*/
|
|||
|
public function closeStream() {
|
|||
|
fclose($this->handle);
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Retrieve the stream resource
|
|||
|
* @return resource
|
|||
|
*/
|
|||
|
public function getStream() {
|
|||
|
return $this->handle;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
|
|||
|
namespace pharext;
|
|||
|
|
|||
|
/**
|
|||
|
* Create a temporary directory
|
|||
|
*/
|
|||
|
class Tempdir extends \SplFileInfo
|
|||
|
{
|
|||
|
/**
|
|||
|
* @param string $prefix prefix to uniqid()
|
|||
|
* @throws \pharext\Exception
|
|||
|
*/
|
|||
|
public function __construct($prefix) {
|
|||
|
$temp = new Tempname($prefix);
|
|||
|
if (!is_dir($temp) && !mkdir($temp, 0700, true)) {
|
|||
|
throw new Exception("Could not create tempdir: ".error_get_last()["message"]);
|
|||
|
}
|
|||
|
parent::__construct($temp);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
|
|||
|
namespace pharext;
|
|||
|
|
|||
|
use ArrayAccess;
|
|||
|
use IteratorAggregate;
|
|||
|
use RecursiveDirectoryIterator;
|
|||
|
use SplFileInfo;
|
|||
|
|
|||
|
use pharext\Exception;
|
|||
|
|
|||
|
class Archive implements ArrayAccess, IteratorAggregate
|
|||
|
{
|
|||
|
const HALT_COMPILER = "\137\137\150\141\154\164\137\143\157\155\160\151\154\145\162\50\51\73";
|
|||
|
const SIGNED = 0x10000;
|
|||
|
const SIG_MD5 = 0x0001;
|
|||
|
const SIG_SHA1 = 0x0002;
|
|||
|
const SIG_SHA256 = 0x0003;
|
|||
|
const SIG_SHA512 = 0x0004;
|
|||
|
const SIG_OPENSSL= 0x0010;
|
|||
|
|
|||
|
private static $siglen = [
|
|||
|
self::SIG_MD5 => 16,
|
|||
|
self::SIG_SHA1 => 20,
|
|||
|
self::SIG_SHA256 => 32,
|
|||
|
self::SIG_SHA512 => 64,
|
|||
|
self::SIG_OPENSSL=> 0
|
|||
|
];
|
|||
|
|
|||
|
private static $sigalg = [
|
|||
|
self::SIG_MD5 => "md5",
|
|||
|
self::SIG_SHA1 => "sha1",
|
|||
|
self::SIG_SHA256 => "sha256",
|
|||
|
self::SIG_SHA512 => "sha512",
|
|||
|
self::SIG_OPENSSL=> "openssl"
|
|||
|
];
|
|||
|
|
|||
|
private static $sigtyp = [
|
|||
|
self::SIG_MD5 => "MD5",
|
|||
|
self::SIG_SHA1 => "SHA-1",
|
|||
|
self::SIG_SHA256 => "SHA-256",
|
|||
|
self::SIG_SHA512 => "SHA-512",
|
|||
|
self::SIG_OPENSSL=> "OpenSSL",
|
|||
|
];
|
|||
|
|
|||
|
const PERM_FILE_MASK = 0x01ff;
|
|||
|
const COMP_FILE_MASK = 0xf000;
|
|||
|
const COMP_GZ_FILE = 0x1000;
|
|||
|
const COMP_BZ2_FILE = 0x2000;
|
|||
|
|
|||
|
const COMP_PHAR_MASK= 0xf000;
|
|||
|
const COMP_PHAR_GZ = 0x1000;
|
|||
|
const COMP_PHAR_BZ2 = 0x2000;
|
|||
|
|
|||
|
private $file;
|
|||
|
private $fd;
|
|||
|
private $stub;
|
|||
|
private $manifest;
|
|||
|
private $signature;
|
|||
|
private $extracted;
|
|||
|
|
|||
|
function __construct($file = null) {
|
|||
|
if (strlen($file)) {
|
|||
|
$this->open($file);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
function open($file) {
|
|||
|
if (!$this->fd = @fopen($file, "r")) {
|
|||
|
throw new Exception;
|
|||
|
}
|
|||
|
$this->file = $file;
|
|||
|
$this->stub = $this->readStub();
|
|||
|
$this->manifest = $this->readManifest();
|
|||
|
$this->signature = $this->readSignature();
|
|||
|
}
|
|||
|
|
|||
|
function getIterator() {
|
|||
|
return new RecursiveDirectoryIterator($this->extract());
|
|||
|
}
|
|||
|
|
|||
|
function extract() {
|
|||
|
return $this->extracted ?: $this->extractTo(new Tempdir("archive"));
|
|||
|
}
|
|||
|
|
|||
|
function extractTo($dir) {
|
|||
|
if ((string) $this->extracted == (string) $dir) {
|
|||
|
return $this->extracted;
|
|||
|
}
|
|||
|
foreach ($this->manifest["entries"] as $file => $entry) {
|
|||
|
fseek($this->fd, $this->manifest["offset"]+$entry["offset"]);
|
|||
|
$path = "$dir/$file";
|
|||
|
$copy = stream_copy_to_stream($this->fd, $this->outFd($path, $entry["flags"]), $entry["csize"]);
|
|||
|
if ($entry["osize"] != $copy) {
|
|||
|
throw new Exception("Copied '$copy' of '$file', expected '{$entry["osize"]}' from '{$entry["csize"]}");
|
|||
|
}
|
|||
|
|
|||
|
$crc = hexdec(hash_file("crc32b", $path));
|
|||
|
if ($crc !== $entry["crc32"]) {
|
|||
|
throw new Exception("CRC mismatch of '$file': '$crc' != '{$entry["crc32"]}");
|
|||
|
}
|
|||
|
|
|||
|
chmod($path, $entry["flags"] & self::PERM_FILE_MASK);
|
|||
|
touch($path, $entry["stamp"]);
|
|||
|
}
|
|||
|
return $this->extracted = $dir;
|
|||
|
}
|
|||
|
|
|||
|
function offsetExists($o) {
|
|||
|
return isset($this->entries[$o]);
|
|||
|
}
|
|||
|
|
|||
|
function offsetGet($o) {
|
|||
|
$this->extract();
|
|||
|
return new SplFileInfo($this->extracted."/$o");
|
|||
|
}
|
|||
|
|
|||
|
function offsetSet($o, $v) {
|
|||
|
throw new Exception("Archive is read-only");
|
|||
|
}
|
|||
|
|
|||
|
function offsetUnset($o) {
|
|||
|
throw new Exception("Archive is read-only");
|
|||
|
}
|
|||
|
|
|||
|
function getSignature() {
|
|||
|
/* compatible with Phar::getSignature() */
|
|||
|
return [
|
|||
|
"hash_type" => self::$sigtyp[$this->signature["flags"]],
|
|||
|
"hash" => strtoupper(bin2hex($this->signature["hash"])),
|
|||
|
];
|
|||
|
}
|
|||
|
|
|||
|
function getPath() {
|
|||
|
/* compatible with Phar::getPath() */
|
|||
|
return new SplFileInfo($this->file);
|
|||
|
}
|
|||
|
|
|||
|
function getMetadata($key = null) {
|
|||
|
if (isset($key)) {
|
|||
|
return $this->manifest["meta"][$key];
|
|||
|
}
|
|||
|
return $this->manifest["meta"];
|
|||
|
}
|
|||
|
|
|||
|
private function outFd($path, $flags) {
|
|||
|
$dirn = dirname($path);
|
|||
|
if (!is_dir($dirn) && !@mkdir($dirn, 0777, true)) {
|
|||
|
throw new Exception;
|
|||
|
}
|
|||
|
if (!$fd = @fopen($path, "w")) {
|
|||
|
throw new Exception;
|
|||
|
}
|
|||
|
switch ($flags & self::COMP_FILE_MASK) {
|
|||
|
case self::COMP_GZ_FILE:
|
|||
|
if (!@stream_filter_append($fd, "zlib.inflate")) {
|
|||
|
throw new Exception;
|
|||
|
}
|
|||
|
break;
|
|||
|
case self::COMP_BZ2_FILE:
|
|||
|
if (!@stream_filter_append($fd, "bz2.decompress")) {
|
|||
|
throw new Exception;
|
|||
|
}
|
|||
|
break;
|
|||
|
}
|
|||
|
|
|||
|
}
|
|||
|
private function readVerified($fd, $len) {
|
|||
|
if ($len != strlen($data = fread($fd, $len))) {
|
|||
|
throw new Exception("Unexpected EOF");
|
|||
|
}
|
|||
|
return $data;
|
|||
|
}
|
|||
|
|
|||
|
private function readFormat($format, $fd, $len) {
|
|||
|
if (false === ($data = @unpack($format, $this->readVerified($fd, $len)))) {
|
|||
|
throw new Exception;
|
|||
|
}
|
|||
|
return $data;
|
|||
|
}
|
|||
|
|
|||
|
private function readSingleFormat($format, $fd, $len) {
|
|||
|
return current($this->readFormat($format, $fd, $len));
|
|||
|
}
|
|||
|
|
|||
|
private function readStringBinary($fd) {
|
|||
|
if (($length = $this->readSingleFormat("V", $fd, 4))) {
|
|||
|
return $this->readVerified($this->fd, $length);
|
|||
|
}
|
|||
|
return null;
|
|||
|
}
|
|||
|
|
|||
|
private function readSerializedBinary($fd) {
|
|||
|
if (($length = $this->readSingleFormat("V", $fd, 4))) {
|
|||
|
if (false === ($data = unserialize($this->readVerified($fd, $length)))) {
|
|||
|
throw new Exception;
|
|||
|
}
|
|||
|
return $data;
|
|||
|
}
|
|||
|
return null;
|
|||
|
}
|
|||
|
|
|||
|
private function readStub() {
|
|||
|
$stub = "";
|
|||
|
while (!feof($this->fd)) {
|
|||
|
$line = fgets($this->fd);
|
|||
|
$stub .= $line;
|
|||
|
if (false !== stripos($line, self::HALT_COMPILER)) {
|
|||
|
/* check for '?>' on a separate line */
|
|||
|
if ('?>' === $this->readVerified($this->fd, 2)) {
|
|||
|
$stub .= '?>' . fgets($this->fd);
|
|||
|
} else {
|
|||
|
fseek($this->fd, -2, SEEK_CUR);
|
|||
|
}
|
|||
|
break;
|
|||
|
}
|
|||
|
}
|
|||
|
return $stub;
|
|||
|
}
|
|||
|
|
|||
|
private function readManifest() {
|
|||
|
$current = ftell($this->fd);
|
|||
|
$header = $this->readFormat("Vlen/Vnum/napi/Vflags", $this->fd, 14);
|
|||
|
$alias = $this->readStringBinary($this->fd);
|
|||
|
$meta = $this->readSerializedBinary($this->fd);
|
|||
|
$entries = [];
|
|||
|
for ($i = 0; $i < $header["num"]; ++$i) {
|
|||
|
$this->readEntry($entries);
|
|||
|
}
|
|||
|
$offset = ftell($this->fd);
|
|||
|
if (($length = $offset - $current - 4) != $header["len"]) {
|
|||
|
throw new Exception("Manifest length read was '$length', expected '{$header["len"]}'");
|
|||
|
}
|
|||
|
return $header + compact("alias", "meta", "entries", "offset");
|
|||
|
}
|
|||
|
|
|||
|
private function readEntry(array &$entries) {
|
|||
|
if (!count($entries)) {
|
|||
|
$offset = 0;
|
|||
|
} else {
|
|||
|
$last = end($entries);
|
|||
|
$offset = $last["offset"] + $last["csize"];
|
|||
|
}
|
|||
|
$file = $this->readStringBinary($this->fd);
|
|||
|
if (!strlen($file)) {
|
|||
|
throw new Exception("Empty file name encountered at offset '$offset'");
|
|||
|
}
|
|||
|
$header = $this->readFormat("Vosize/Vstamp/Vcsize/Vcrc32/Vflags", $this->fd, 20);
|
|||
|
$meta = $this->readSerializedBinary($this->fd);
|
|||
|
$entries[$file] = $header + compact("meta", "offset");
|
|||
|
}
|
|||
|
|
|||
|
private function readSignature() {
|
|||
|
fseek($this->fd, -8, SEEK_END);
|
|||
|
$sig = $this->readFormat("Vflags/Z4magic", $this->fd, 8);
|
|||
|
$end = ftell($this->fd);
|
|||
|
|
|||
|
if ($sig["magic"] !== "GBMB") {
|
|||
|
throw new Exception("Invalid signature magic value '{$sig["magic"]}");
|
|||
|
}
|
|||
|
|
|||
|
switch ($sig["flags"]) {
|
|||
|
case self::SIG_OPENSSL:
|
|||
|
fseek($this->fd, -12, SEEK_END);
|
|||
|
if (($hash = $this->readSingleFormat("V", $this->fd, 4))) {
|
|||
|
$offset = 4 + $hash;
|
|||
|
fseek($this->fd, -$offset, SEEK_CUR);
|
|||
|
$hash = $this->readVerified($this->fd, $hash);
|
|||
|
fseek($this->fd, 0, SEEK_SET);
|
|||
|
$valid = openssl_verify($this->readVerified($this->fd, $end - $offset - 8),
|
|||
|
$hash, @file_get_contents($this->file.".pubkey")) === 1;
|
|||
|
}
|
|||
|
break;
|
|||
|
|
|||
|
case self::SIG_MD5:
|
|||
|
case self::SIG_SHA1:
|
|||
|
case self::SIG_SHA256:
|
|||
|
case self::SIG_SHA512:
|
|||
|
$offset = 8 + self::$siglen[$sig["flags"]];
|
|||
|
fseek($this->fd, -$offset, SEEK_END);
|
|||
|
$hash = $this->readVerified($this->fd, self::$siglen[$sig["flags"]]);
|
|||
|
$algo = hash_init(self::$sigalg[$sig["flags"]]);
|
|||
|
fseek($this->fd, 0, SEEK_SET);
|
|||
|
hash_update_stream($algo, $this->fd, $end - $offset);
|
|||
|
$valid = hash_final($algo, true) === $hash;
|
|||
|
break;
|
|||
|
|
|||
|
default:
|
|||
|
throw new Exception("Invalid signature type '{$sig["flags"]}");
|
|||
|
}
|
|||
|
|
|||
|
return $sig + compact("hash", "valid");
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
namespace pharext;
|
|||
|
|
|||
|
if (extension_loaded("Phar")) {
|
|||
|
\Phar::interceptFileFuncs();
|
|||
|
\Phar::mapPhar();
|
|||
|
$phardir = "phar://".__FILE__;
|
|||
|
} else {
|
|||
|
$archive = new Archive(__FILE__);
|
|||
|
$phardir = $archive->extract();
|
|||
|
}
|
|||
|
|
|||
|
set_include_path("$phardir:". get_include_path());
|
|||
|
|
|||
|
$installer = new Installer();
|
|||
|
$installer->run($argc, $argv);
|
|||
|
|
|||
|
__HALT_COMPILER(); ?>
|
|||
|
|