test fixups from Remi
[m6w6/ext-http] / travis / raphf-master.ext.phar
1 #!/usr/bin/env php
2 <?php
3
4 /**
5 * The installer sub-stub for extension phars
6 */
7
8 namespace pharext;
9
10 define("PHAREXT_PHAR", __FILE__);
11
12 spl_autoload_register(function($c) {
13 return include strtr($c, "\\_", "//") . ".php";
14 });
15
16
17
18 namespace pharext;
19
20 class Exception extends \Exception
21 {
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"];
26 if (!$code) {
27 $code = $last_error["type"];
28 }
29 }
30 parent::__construct($message, $code, $previous);
31 }
32 }
33
34
35
36 namespace pharext;
37
38 use pharext\Exception;
39
40 /**
41 * A temporary file/directory name
42 */
43 class Tempname
44 {
45 /**
46 * @var string
47 */
48 private $name;
49
50 /**
51 * @param string $prefix uniqid() prefix
52 * @param string $suffix e.g. file extension
53 */
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)) {
57 throw new Exception;
58 }
59 $this->name = $temp ."/". uniqid($prefix) . $suffix;
60 }
61
62 private function getUser() {
63 if (extension_loaded("posix") && function_exists("posix_getpwuid")) {
64 return posix_getpwuid(posix_getuid())["name"];
65 }
66 return trim(`whoami 2>/dev/null`)
67 ?: trim(`id -nu 2>/dev/null`)
68 ?: getenv("USER")
69 ?: get_current_user();
70 }
71
72 /**
73 * @return string
74 */
75 public function __toString() {
76 return (string) $this->name;
77 }
78 }
79
80
81
82 namespace pharext;
83
84 /**
85 * Create a new temporary file
86 */
87 class Tempfile extends \SplFileInfo
88 {
89 /**
90 * @var resource
91 */
92 private $handle;
93
94 /**
95 * @param string $prefix uniqid() prefix
96 * @param string $suffix e.g. file extension
97 * @throws \pharext\Exception
98 */
99 public function __construct($prefix, $suffix = ".tmp") {
100 $tries = 0;
101 $omask = umask(077);
102 do {
103 $path = new Tempname($prefix, $suffix);
104 $this->handle = fopen($path, "x");
105 } while (!is_resource($this->handle) && $tries++ < 10);
106 umask($omask);
107
108 if (!is_resource($this->handle)) {
109 throw new Exception("Could not create temporary file");
110 }
111
112 parent::__construct($path);
113 }
114
115 /**
116 * Unlink the file
117 */
118 public function __destruct() {
119 if (is_file($this->getPathname())) {
120 @unlink($this->getPathname());
121 }
122 }
123
124 /**
125 * Close the stream
126 */
127 public function closeStream() {
128 fclose($this->handle);
129 }
130
131 /**
132 * Retrieve the stream resource
133 * @return resource
134 */
135 public function getStream() {
136 return $this->handle;
137 }
138 }
139
140
141
142 namespace pharext;
143
144 /**
145 * Create a temporary directory
146 */
147 class Tempdir extends \SplFileInfo
148 {
149 /**
150 * @param string $prefix prefix to uniqid()
151 * @throws \pharext\Exception
152 */
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"]);
157 }
158 parent::__construct($temp);
159 }
160 }
161
162
163
164 namespace pharext;
165
166 use ArrayAccess;
167 use IteratorAggregate;
168 use RecursiveDirectoryIterator;
169 use SplFileInfo;
170
171 use pharext\Exception;
172
173 class Archive implements ArrayAccess, IteratorAggregate
174 {
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;
182
183 private static $siglen = [
184 self::SIG_MD5 => 16,
185 self::SIG_SHA1 => 20,
186 self::SIG_SHA256 => 32,
187 self::SIG_SHA512 => 64,
188 self::SIG_OPENSSL=> 0
189 ];
190
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"
197 ];
198
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",
205 ];
206
207 const PERM_FILE_MASK = 0x01ff;
208 const COMP_FILE_MASK = 0xf000;
209 const COMP_GZ_FILE = 0x1000;
210 const COMP_BZ2_FILE = 0x2000;
211
212 const COMP_PHAR_MASK= 0xf000;
213 const COMP_PHAR_GZ = 0x1000;
214 const COMP_PHAR_BZ2 = 0x2000;
215
216 private $file;
217 private $fd;
218 private $stub;
219 private $manifest;
220 private $signature;
221 private $extracted;
222
223 function __construct($file = null) {
224 if (strlen($file)) {
225 $this->open($file);
226 }
227 }
228
229 function open($file) {
230 if (!$this->fd = @fopen($file, "r")) {
231 throw new Exception;
232 }
233 $this->file = $file;
234 $this->stub = $this->readStub();
235 $this->manifest = $this->readManifest();
236 $this->signature = $this->readSignature();
237 }
238
239 function getIterator() {
240 return new RecursiveDirectoryIterator($this->extract());
241 }
242
243 function extract() {
244 return $this->extracted ?: $this->extractTo(new Tempdir("archive"));
245 }
246
247 function extractTo($dir) {
248 if ((string) $this->extracted == (string) $dir) {
249 return $this->extracted;
250 }
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"]}");
257 }
258
259 $crc = hexdec(hash_file("crc32b", $path));
260 if ($crc !== $entry["crc32"]) {
261 throw new Exception("CRC mismatch of '$file': '$crc' != '{$entry["crc32"]}");
262 }
263
264 chmod($path, $entry["flags"] & self::PERM_FILE_MASK);
265 touch($path, $entry["stamp"]);
266 }
267 return $this->extracted = $dir;
268 }
269
270 function offsetExists($o) {
271 return isset($this->entries[$o]);
272 }
273
274 function offsetGet($o) {
275 $this->extract();
276 return new SplFileInfo($this->extracted."/$o");
277 }
278
279 function offsetSet($o, $v) {
280 throw new Exception("Archive is read-only");
281 }
282
283 function offsetUnset($o) {
284 throw new Exception("Archive is read-only");
285 }
286
287 function getSignature() {
288 /* compatible with Phar::getSignature() */
289 return [
290 "hash_type" => self::$sigtyp[$this->signature["flags"]],
291 "hash" => strtoupper(bin2hex($this->signature["hash"])),
292 ];
293 }
294
295 function getPath() {
296 /* compatible with Phar::getPath() */
297 return new SplFileInfo($this->file);
298 }
299
300 function getMetadata($key = null) {
301 if (isset($key)) {
302 return $this->manifest["meta"][$key];
303 }
304 return $this->manifest["meta"];
305 }
306
307 private function outFd($path, $flags) {
308 $dirn = dirname($path);
309 if (!is_dir($dirn) && !@mkdir($dirn, 0777, true)) {
310 throw new Exception;
311 }
312 if (!$fd = @fopen($path, "w")) {
313 throw new Exception;
314 }
315 switch ($flags & self::COMP_FILE_MASK) {
316 case self::COMP_GZ_FILE:
317 if (!@stream_filter_append($fd, "zlib.inflate")) {
318 throw new Exception;
319 }
320 break;
321 case self::COMP_BZ2_FILE:
322 if (!@stream_filter_append($fd, "bz2.decompress")) {
323 throw new Exception;
324 }
325 break;
326 }
327
328 }
329 private function readVerified($fd, $len) {
330 if ($len != strlen($data = fread($fd, $len))) {
331 throw new Exception("Unexpected EOF");
332 }
333 return $data;
334 }
335
336 private function readFormat($format, $fd, $len) {
337 if (false === ($data = @unpack($format, $this->readVerified($fd, $len)))) {
338 throw new Exception;
339 }
340 return $data;
341 }
342
343 private function readSingleFormat($format, $fd, $len) {
344 return current($this->readFormat($format, $fd, $len));
345 }
346
347 private function readStringBinary($fd) {
348 if (($length = $this->readSingleFormat("V", $fd, 4))) {
349 return $this->readVerified($this->fd, $length);
350 }
351 return null;
352 }
353
354 private function readSerializedBinary($fd) {
355 if (($length = $this->readSingleFormat("V", $fd, 4))) {
356 if (false === ($data = unserialize($this->readVerified($fd, $length)))) {
357 throw new Exception;
358 }
359 return $data;
360 }
361 return null;
362 }
363
364 private function readStub() {
365 $stub = "";
366 while (!feof($this->fd)) {
367 $line = fgets($this->fd);
368 $stub .= $line;
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);
373 } else {
374 fseek($this->fd, -2, SEEK_CUR);
375 }
376 break;
377 }
378 }
379 return $stub;
380 }
381
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);
387 $entries = [];
388 for ($i = 0; $i < $header["num"]; ++$i) {
389 $this->readEntry($entries);
390 }
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"]}'");
394 }
395 return $header + compact("alias", "meta", "entries", "offset");
396 }
397
398 private function readEntry(array &$entries) {
399 if (!count($entries)) {
400 $offset = 0;
401 } else {
402 $last = end($entries);
403 $offset = $last["offset"] + $last["csize"];
404 }
405 $file = $this->readStringBinary($this->fd);
406 if (!strlen($file)) {
407 throw new Exception("Empty file name encountered at offset '$offset'");
408 }
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");
412 }
413
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);
418
419 if ($sig["magic"] !== "GBMB") {
420 throw new Exception("Invalid signature magic value '{$sig["magic"]}");
421 }
422
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))) {
427 $offset = 4 + $hash;
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;
433 }
434 break;
435
436 case self::SIG_MD5:
437 case self::SIG_SHA1:
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;
447 break;
448
449 default:
450 throw new Exception("Invalid signature type '{$sig["flags"]}");
451 }
452
453 return $sig + compact("hash", "valid");
454 }
455 }
456
457
458 namespace pharext;
459
460 if (extension_loaded("Phar")) {
461 \Phar::interceptFileFuncs();
462 \Phar::mapPhar();
463 $phardir = "phar://".__FILE__;
464 } else {
465 $archive = new Archive(__FILE__);
466 $phardir = $archive->extract();
467 }
468
469 set_include_path("$phardir:". get_include_path());
470
471 $installer = new Installer();
472 $installer->run($argc, $argv);
473
474 __HALT_COMPILER(); ?>
475 D\13