8 class Archive
implements ArrayAccess
10 const HALT_COMPILER
= "\137\137\150\141\154\164\137\143\157\155\160\151\154\145\162\50\51\73";
11 const SIGNED
= 0x10000;
12 const SIG_MD5
= 0x0001;
13 const SIG_SHA1
= 0x0002;
14 const SIG_SHA256
= 0x0003;
15 const SIG_SHA512
= 0x0004;
16 const SIG_OPENSSL
= 0x0010;
18 private static $siglen = [
21 self
::SIG_SHA256
=> 32,
22 self
::SIG_SHA512
=> 64,
26 private static $sigalg = [
27 self
::SIG_MD5
=> "md5",
28 self
::SIG_SHA1
=> "sha1",
29 self
::SIG_SHA256
=> "sha256",
30 self
::SIG_SHA512
=> "sha512",
31 self
::SIG_OPENSSL
=> "openssl"
34 private static $sigtyp = [
35 self
::SIG_MD5
=> "MD5",
36 self
::SIG_SHA1
=> "SHA-1",
37 self
::SIG_SHA256
=> "SHA-256",
38 self
::SIG_SHA512
=> "SHA-512",
39 self
::SIG_OPENSSL
=> "OpenSSL",
42 const PERM_FILE_MASK
= 0x01ff;
43 const COMP_FILE_MASK
= 0xf000;
44 const COMP_GZ_FILE
= 0x1000;
45 const COMP_BZ2_FILE
= 0x2000;
47 const COMP_PHAR_MASK
= 0xf000;
48 const COMP_PHAR_GZ
= 0x1000;
49 const COMP_PHAR_BZ2
= 0x2000;
58 function __construct($file = null) {
64 function open($file) {
65 if (!$this->fd
= @fopen
($this->file
= $file, "r")) {
68 $this->stub
= $this->readStub();
69 $this->manifest
= $this->readManifest();
70 $this->signature
= $this->readSignature();
74 return $this->extracted ?
: $this->extractTo(new Tempdir("archive"));
77 function extractTo($dir) {
78 if ((string) $this->extracted
== (string) $dir) {
79 return $this->extracted
;
81 foreach ($this->manifest
["entries"] as $file => $entry) {
82 fseek($this->fd
, $this->manifest
["offset"]+
$entry["offset"]);
83 $path = $dir."/$file";
84 $dirn = dirname($path);
85 if (!is_dir($dirn) && !@mkdir
($dirn, 0777, true)) {
88 if (!$fd = @fopen
($path, "w")) {
91 switch ($entry["flags"] & self
::COMP_FILE_MASK
) {
92 case self
::COMP_GZ_FILE
:
93 if (!@stream_filter_append
($fd, "zlib.inflate")) {
97 case self
::COMP_BZ2_FILE
:
98 if (!@stream_filter_append
($fd, "bz2.decompress")) {
103 if ($entry["osize"] != ($copied = stream_copy_to_stream($this->fd
, $fd, $entry["csize"]))) {
104 throw new Exception("Copied '$copied' of '$file', expected '{$entry["osize
"]}' from '{$entry["csize
"]}");
108 $crc = hexdec(hash_file("crc32b", $path));
109 if ($crc !== $entry["crc32"]) {
110 throw new Exception("CRC mismatch of '$file': '$crc' != '{$entry["crc32
"]}");
113 chmod($path, $entry["flags"] & self
::PERM_FILE_MASK
);
114 touch($path, $entry["stamp"]);
116 return $this->extracted
= $dir;
119 function offsetExists($o) {
120 return isset($this->entries
[$o]);
123 function offsetGet($o) {
125 return new \
SplFileInfo($this->extracted
."/$o");
128 function offsetSet($o, $v) {
129 throw new Exception("Archive is read-only");
132 function offsetUnset($o) {
133 throw new Exception("Archive is read-only");
136 function getSignature() {
137 /* compatible with Phar::getSignature() */
139 "hash_type" => self
::$sigtyp[$this->signature
["flags"]],
140 "hash" => strtoupper(bin2hex($this->signature
["hash"])),
145 /* compatible with Phar::getPath() */
146 return new \
SplFileInfo($this->file
);
149 function getMetadata($key = null) {
151 return $this->manifest
["meta"][$key];
153 return $this->manifest
["meta"];
156 private function readStub() {
158 while (!feof($this->fd
)) {
159 $line = fgets($this->fd
);
161 if (false !== stripos($line, self
::HALT_COMPILER
)) {
162 /* check for '?>' on a separate line */
163 if ('?>' === fread($this->fd
, 2)) {
164 $stub .= '?>' . fgets($this->fd
);
166 fseek($this->fd
, -2, SEEK_CUR
);
174 private function readManifest() {
175 $current = ftell($this->fd
);
176 $header = unpack("Vlen/Vnum/napi/Vflags", fread($this->fd
, 14));
177 if (($alias = current(unpack("V", fread($this->fd
, 4))))) {
178 $alias = fread($this->fd
, $alias);
180 if (($meta = current(unpack("V", fread($this->fd
, 4))))) {
181 $meta = unserialize(fread($this->fd
, $meta));
184 for ($i = 0; $i < $header["num"]; ++
$i) {
185 $this->readEntry($entries);
187 $offset = ftell($this->fd
);
188 if (($length = $offset - $current - 4) != $header["len"]) {
189 throw new Exception("Manifest length read was '$length', expected '{$header["len
"]}'");
191 return $header +
compact("alias", "meta", "entries", "offset");
194 private function readEntry(array &$entries) {
195 if (!count($entries)) {
198 $last = end($entries);
199 $offset = $last["offset"] +
$last["csize"];
201 if (($file = current(unpack("V", fread($this->fd
, 4))))) {
202 $file = fread($this->fd
, $file);
204 if ($file === 0 ||
!strlen($file)) {
205 throw new Exception("Empty file name encountered at offset '$offset'");
207 $header = unpack("Vosize/Vstamp/Vcsize/Vcrc32/Vflags", fread($this->fd
, 20));
208 if (($meta = current(unpack("V", fread($this->fd
, 4))))) {
209 $meta = unserialize(fread($this->fd
, $meta));
213 $entries[$file] = $header +
compact("meta", "offset");
216 private function readSignature() {
217 fseek($this->fd
, -8, SEEK_END
);
218 $sig = unpack("Vflags/Z4magic", fread($this->fd
, 8));
219 $end = ftell($this->fd
);
221 if ($sig["magic"] !== "GBMB") {
222 throw new Exception("Invalid signature magic value '{$sig["magic
"]}");
225 switch ($sig["flags"]) {
226 case self
::SIG_OPENSSL
:
227 fseek($this->fd
, -12, SEEK_END
);
228 if (($hash = current(unpack("V", fread($this->fd
, 4))))) {
230 fseek($this->fd
, -$offset, SEEK_CUR
);
231 $hash = fread($this->fd
, $hash);
232 fseek($this->fd
, 0, SEEK_SET
);
233 $valid = openssl_verify(fread($this->fd
, $end - $offset - 8),
234 $hash, file_get_contents($this->file
.".pubkey")) === 1;
240 case self
::SIG_SHA256
:
241 case self
::SIG_SHA512
:
242 $offset = 8 + self
::$siglen[$sig["flags"]];
243 fseek($this->fd
, -$offset, SEEK_END
);
244 $hash = fread($this->fd
, self
::$siglen[$sig["flags"]]);
245 $algo = hash_init(self
::$sigalg[$sig["flags"]]);
246 fseek($this->fd
, 0, SEEK_SET
);
247 hash_update_stream($algo, $this->fd
, $end - $offset);
248 $valid = hash_final($algo, true) === $hash;
252 throw new Exception("Invalid signature type '{$sig["flags
"]}");
255 return $sig +
compact("hash", "valid");