5 * The installer sub-stub for extension phars
10 define("PHAREXT_PHAR", __FILE__
);
12 spl_autoload_register(function($c) {
13 return include strtr($c, "\\_", "//") . ".php";
20 class Exception
extends \Exception
22 public function __construct($message = null, $code = 0, $previous = null) {
23 if (!isset($message)) {
24 $last_error = error_get_last();
25 $message = $last_error["message"];
27 $code = $last_error["type"];
30 parent
::__construct($message, $code, $previous);
38 use pharext\Exception
;
41 * A temporary file/directory name
51 * @param string $prefix uniqid() prefix
52 * @param string $suffix e.g. file extension
54 public function __construct($prefix, $suffix = null) {
55 $temp = sys_get_temp_dir() . "/pharext-" . $this->getUser();
56 if (!is_dir($temp) && !mkdir($temp, 0700, true)) {
59 $this->name
= $temp ."/". uniqid($prefix) . $suffix;
62 private function getUser() {
63 if (extension_loaded("posix") && function_exists("posix_getpwuid")) {
64 return posix_getpwuid(posix_getuid())["name"];
66 return trim(`whoami
2>/dev
/null`
)
67 ?
: trim(`id
-nu
2>/dev
/null`
)
69 ?
: get_current_user();
75 public function __toString() {
76 return (string) $this->name
;
85 * Create a new temporary file
87 class Tempfile
extends \SplFileInfo
95 * @param string $prefix uniqid() prefix
96 * @param string $suffix e.g. file extension
97 * @throws \pharext\Exception
99 public function __construct($prefix, $suffix = ".tmp") {
103 $path = new Tempname($prefix, $suffix);
104 $this->handle
= fopen($path, "x");
105 } while (!is_resource($this->handle
) && $tries++
< 10);
108 if (!is_resource($this->handle
)) {
109 throw new Exception("Could not create temporary file");
112 parent
::__construct($path);
118 public function __destruct() {
119 if (is_file($this->getPathname())) {
120 @unlink
($this->getPathname());
127 public function closeStream() {
128 fclose($this->handle
);
132 * Retrieve the stream resource
135 public function getStream() {
136 return $this->handle
;
145 * Create a temporary directory
147 class Tempdir
extends \SplFileInfo
150 * @param string $prefix prefix to uniqid()
151 * @throws \pharext\Exception
153 public function __construct($prefix) {
154 $temp = new Tempname($prefix);
155 if (!is_dir($temp) && !mkdir($temp, 0700, true)) {
156 throw new Exception("Could not create tempdir: ".error_get_last()["message"]);
158 parent
::__construct($temp);
167 use IteratorAggregate
;
168 use RecursiveDirectoryIterator
;
171 use pharext\Exception
;
173 class Archive
implements ArrayAccess
, IteratorAggregate
175 const HALT_COMPILER
= "\137\137\150\141\154\164\137\143\157\155\160\151\154\145\162\50\51\73";
176 const SIGNED
= 0x10000;
177 const SIG_MD5
= 0x0001;
178 const SIG_SHA1
= 0x0002;
179 const SIG_SHA256
= 0x0003;
180 const SIG_SHA512
= 0x0004;
181 const SIG_OPENSSL
= 0x0010;
183 private static $siglen = [
185 self
::SIG_SHA1
=> 20,
186 self
::SIG_SHA256
=> 32,
187 self
::SIG_SHA512
=> 64,
188 self
::SIG_OPENSSL
=> 0
191 private static $sigalg = [
192 self
::SIG_MD5
=> "md5",
193 self
::SIG_SHA1
=> "sha1",
194 self
::SIG_SHA256
=> "sha256",
195 self
::SIG_SHA512
=> "sha512",
196 self
::SIG_OPENSSL
=> "openssl"
199 private static $sigtyp = [
200 self
::SIG_MD5
=> "MD5",
201 self
::SIG_SHA1
=> "SHA-1",
202 self
::SIG_SHA256
=> "SHA-256",
203 self
::SIG_SHA512
=> "SHA-512",
204 self
::SIG_OPENSSL
=> "OpenSSL",
207 const PERM_FILE_MASK
= 0x01ff;
208 const COMP_FILE_MASK
= 0xf000;
209 const COMP_GZ_FILE
= 0x1000;
210 const COMP_BZ2_FILE
= 0x2000;
212 const COMP_PHAR_MASK
= 0xf000;
213 const COMP_PHAR_GZ
= 0x1000;
214 const COMP_PHAR_BZ2
= 0x2000;
223 function __construct($file = null) {
229 function open($file) {
230 if (!$this->fd
= @fopen
($file, "r")) {
234 $this->stub
= $this->readStub();
235 $this->manifest
= $this->readManifest();
236 $this->signature
= $this->readSignature();
239 function getIterator() {
240 return new RecursiveDirectoryIterator($this->extract());
244 return $this->extracted ?
: $this->extractTo(new Tempdir("archive"));
247 function extractTo($dir) {
248 if ((string) $this->extracted
== (string) $dir) {
249 return $this->extracted
;
251 foreach ($this->manifest
["entries"] as $file => $entry) {
252 fseek($this->fd
, $this->manifest
["offset"]+
$entry["offset"]);
253 $path = "$dir/$file";
254 $copy = stream_copy_to_stream($this->fd
, $this->outFd($path, $entry["flags"]), $entry["csize"]);
255 if ($entry["osize"] != $copy) {
256 throw new Exception("Copied '$copy' of '$file', expected '{$entry["osize
"]}' from '{$entry["csize
"]}");
259 $crc = hexdec(hash_file("crc32b", $path));
260 if ($crc !== $entry["crc32"]) {
261 throw new Exception("CRC mismatch of '$file': '$crc' != '{$entry["crc32
"]}");
264 chmod($path, $entry["flags"] & self
::PERM_FILE_MASK
);
265 touch($path, $entry["stamp"]);
267 return $this->extracted
= $dir;
270 function offsetExists($o) {
271 return isset($this->entries
[$o]);
274 function offsetGet($o) {
276 return new SplFileInfo($this->extracted
."/$o");
279 function offsetSet($o, $v) {
280 throw new Exception("Archive is read-only");
283 function offsetUnset($o) {
284 throw new Exception("Archive is read-only");
287 function getSignature() {
288 /* compatible with Phar::getSignature() */
290 "hash_type" => self
::$sigtyp[$this->signature
["flags"]],
291 "hash" => strtoupper(bin2hex($this->signature
["hash"])),
296 /* compatible with Phar::getPath() */
297 return new SplFileInfo($this->file
);
300 function getMetadata($key = null) {
302 return $this->manifest
["meta"][$key];
304 return $this->manifest
["meta"];
307 private function outFd($path, $flags) {
308 $dirn = dirname($path);
309 if (!is_dir($dirn) && !@mkdir
($dirn, 0777, true)) {
312 if (!$fd = @fopen
($path, "w")) {
315 switch ($flags & self
::COMP_FILE_MASK
) {
316 case self
::COMP_GZ_FILE
:
317 if (!@stream_filter_append
($fd, "zlib.inflate")) {
321 case self
::COMP_BZ2_FILE
:
322 if (!@stream_filter_append
($fd, "bz2.decompress")) {
329 private function readVerified($fd, $len) {
330 if ($len != strlen($data = fread($fd, $len))) {
331 throw new Exception("Unexpected EOF");
336 private function readFormat($format, $fd, $len) {
337 if (false === ($data = @unpack
($format, $this->readVerified($fd, $len)))) {
343 private function readSingleFormat($format, $fd, $len) {
344 return current($this->readFormat($format, $fd, $len));
347 private function readStringBinary($fd) {
348 if (($length = $this->readSingleFormat("V", $fd, 4))) {
349 return $this->readVerified($this->fd
, $length);
354 private function readSerializedBinary($fd) {
355 if (($length = $this->readSingleFormat("V", $fd, 4))) {
356 if (false === ($data = unserialize($this->readVerified($fd, $length)))) {
364 private function readStub() {
366 while (!feof($this->fd
)) {
367 $line = fgets($this->fd
);
369 if (false !== stripos($line, self
::HALT_COMPILER
)) {
370 /* check for '?>' on a separate line */
371 if ('?>' === $this->readVerified($this->fd
, 2)) {
372 $stub .= '?>' . fgets($this->fd
);
374 fseek($this->fd
, -2, SEEK_CUR
);
382 private function readManifest() {
383 $current = ftell($this->fd
);
384 $header = $this->readFormat("Vlen/Vnum/napi/Vflags", $this->fd
, 14);
385 $alias = $this->readStringBinary($this->fd
);
386 $meta = $this->readSerializedBinary($this->fd
);
388 for ($i = 0; $i < $header["num"]; ++
$i) {
389 $this->readEntry($entries);
391 $offset = ftell($this->fd
);
392 if (($length = $offset - $current - 4) != $header["len"]) {
393 throw new Exception("Manifest length read was '$length', expected '{$header["len
"]}'");
395 return $header +
compact("alias", "meta", "entries", "offset");
398 private function readEntry(array &$entries) {
399 if (!count($entries)) {
402 $last = end($entries);
403 $offset = $last["offset"] +
$last["csize"];
405 $file = $this->readStringBinary($this->fd
);
406 if (!strlen($file)) {
407 throw new Exception("Empty file name encountered at offset '$offset'");
409 $header = $this->readFormat("Vosize/Vstamp/Vcsize/Vcrc32/Vflags", $this->fd
, 20);
410 $meta = $this->readSerializedBinary($this->fd
);
411 $entries[$file] = $header +
compact("meta", "offset");
414 private function readSignature() {
415 fseek($this->fd
, -8, SEEK_END
);
416 $sig = $this->readFormat("Vflags/Z4magic", $this->fd
, 8);
417 $end = ftell($this->fd
);
419 if ($sig["magic"] !== "GBMB") {
420 throw new Exception("Invalid signature magic value '{$sig["magic
"]}");
423 switch ($sig["flags"]) {
424 case self
::SIG_OPENSSL
:
425 fseek($this->fd
, -12, SEEK_END
);
426 if (($hash = $this->readSingleFormat("V", $this->fd
, 4))) {
428 fseek($this->fd
, -$offset, SEEK_CUR
);
429 $hash = $this->readVerified($this->fd
, $hash);
430 fseek($this->fd
, 0, SEEK_SET
);
431 $valid = openssl_verify($this->readVerified($this->fd
, $end - $offset - 8),
432 $hash, @file_get_contents
($this->file
.".pubkey")) === 1;
438 case self
::SIG_SHA256
:
439 case self
::SIG_SHA512
:
440 $offset = 8 + self
::$siglen[$sig["flags"]];
441 fseek($this->fd
, -$offset, SEEK_END
);
442 $hash = $this->readVerified($this->fd
, self
::$siglen[$sig["flags"]]);
443 $algo = hash_init(self
::$sigalg[$sig["flags"]]);
444 fseek($this->fd
, 0, SEEK_SET
);
445 hash_update_stream($algo, $this->fd
, $end - $offset);
446 $valid = hash_final($algo, true) === $hash;
450 throw new Exception("Invalid signature type '{$sig["flags
"]}");
453 return $sig +
compact("hash", "valid");
460 if (extension_loaded("Phar")) {
461 \Phar
::interceptFileFuncs();
463 $phardir = "phar://".__FILE__
;
465 $archive = new Archive(__FILE__
);
466 $phardir = $archive->extract();
469 set_include_path("$phardir:". get_include_path());
471 $installer = new Installer();
472 $installer->run($argc, $argv);
474 __HALT_COMPILER(); ?
>