#!/usr/bin/env php 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(); ?> 8*a:7:{s:7:"version";s:5:"4.1.1";s:6:"header";s:49:"pharext v4.1.1 (c) Michael Wallner ";s:4:"date";s:10:"2015-09-28";s:4:"name";s:6:"propro";s:7:"release";s:6:"master";s:7:"license";s:1345:"Copyright (c) 2013, Michael Wallner . All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ";s:4:"type";s:9:"extension";}pharext/Archive.php l V4-Ipharext/Cli/Args/Help.php l V gX'pharext/Cli/Args.php l V?npharext/Cli/Command.phpk l Vk dapharext/Command.php l Vm`Ͷpharext/Exception.phpc l VcU{pharext/ExecCmd.php l Vlʶpharext/Installer.php& l V&d&pharext/License.php l VEpharext/Metadata.php l Vڐpharext/Openssl/PrivateKey.php l V&Ppharext/Packager.php! l V!0<pharext/SourceDir/Basic.phpz l Vz+pharext/SourceDir/Git.phpZ l VZ\pharext/SourceDir/Pecl.php l Vжpharext/SourceDir.php l V3#pharext/Task/Activate.php l V Ipharext/Task/Askpass.phpU l VU* pharext/Task/BundleGenerator.php} l V} `Ypharext/Task/Cleanup.php l VIBpharext/Task/Configure.phpT l VT}pharext/Task/Extract.phpp l Vp[̶pharext/Task/GitClone.phpm l Vmy@pharext/Task/Make.php l V6 pharext/Task/PaxFixup.php l Vypharext/Task/PeclFixup.php l Vetpharext/Task/PharBuild.php l Vζ0ɶpharext/Task/PharCompress.phpc l Vc϶pharext/Task/PharRename.php l V[˶pharext/Task/PharSign.php l Vۺipharext/Task/PharStub.php l VY|pharext/Task/Phpize.php l V 2Ѷpharext/Task/StreamFetch.php l Vs\pharext/Task.phpw l Vw IǶpharext/Tempdir.php l V,pharext/Tempfile.php l Vpharext/Tempname.phpt l Vtn<pharext/Updater.php l Vvpharext_installer.php l Vqpharext_packager.phpb l VbV϶pharext_updater.phph l Vh j.gitattributes2 l V2Nt .gitignore l VwgCREDITS l VuBζDoxyfilea* l Va*dLICENSEA l VAJ README.md l V-)u config.m4 l V k" config.w32 l V5 package.xml l Vjg php_propro.c08 l V08^ php_propro.hO l VOG+php_propro_api.h l V #tests/001.phpt9 l V92սAtests/002.phpt8 l V8Ltests/003.phpt l VU8 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"); } } prog = $prog; $this->args = $args; } function __toString() { $usage = "Usage:\n\n \$ "; $usage .= $this->prog; list($flags, $required, $optional, $positional) = $this->listSpec(); if ($flags) { $usage .= $this->dumpFlags($flags); } if ($required) { $usage .= $this->dumpRequired($required); } if ($optional) { $usage .= $this->dumpOptional($optional); } if ($positional) { $usage .= $this->dumpPositional($positional); } $help = $this->dumpHelp($positional); return $usage . "\n\n" . $help . "\n"; } function listSpec() { $flags = []; $required = []; $optional = []; $positional = []; foreach ($this->args->getSpec() as $spec) { if (is_numeric($spec[0])) { $positional[] = $spec; } elseif ($spec[3] & Args::REQUIRED) { $required[] = $spec; } elseif ($spec[3] & (Args::OPTARG|Args::REQARG)) { $optional[] = $spec; } else { $flags[] = $spec; } } return [$flags, $required, $optional, $positional] + compact("flags", "required", "optional", "positional"); } function dumpFlags(array $flags) { return sprintf(" [-%s]", implode("", array_column($flags, 0))); } function dumpRequired(array $required) { $dump = ""; foreach ($required as $req) { $dump .= sprintf(" -%s <%s>", $req[0], $req[1]); } return $dump; } function dumpOptional(array $optional) { $req = array_filter($optional, function($a) { return $a[3] & Args::REQARG; }); $opt = array_filter($optional, function($a) { return $a[3] & Args::OPTARG; }); $dump = ""; if ($req) { $dump .= sprintf(" [-%s ]", implode("|-", array_column($req, 0))); } if ($opt) { $dump .= sprintf(" [-%s []]", implode("|-", array_column($opt, 0))); } return $dump; } function dumpPositional(array $positional) { $dump = " [--]"; foreach ($positional as $pos) { if ($pos[3] & Args::REQUIRED) { $dump .= sprintf(" <%s>", $pos[1]); } else { $dump .= sprintf(" [<%s>]", $pos[1]); } if ($pos[3] & Args::MULTI) { $dump .= sprintf(" [<%s>]...", $pos[1]); } } return $dump; } function calcMaxLen() { $spc = $this->args->getSpec(); $max = max(array_map("strlen", array_column($spc, 1))); $max += $max % 8 + 2; return $max; } function dumpHelp() { $max = $this->calcMaxLen(); $dump = ""; foreach ($this->args->getSpec() as $spec) { $dump .= " "; if (is_numeric($spec[0])) { $dump .= sprintf("-- %s ", $spec[1]); } elseif (isset($spec[0])) { $dump .= sprintf("-%s|", $spec[0]); } if (!is_numeric($spec[0])) { $dump .= sprintf("--%s ", $spec[1]); } if ($spec[3] & Args::REQARG) { $dump .= " "; } elseif ($spec[3] & Args::OPTARG) { $dump .= "[]"; } else { $dump .= " "; } $dump .= str_repeat(" ", $max-strlen($spec[1])+3*!isset($spec[0])); $dump .= $spec[2]; if ($spec[3] & Args::REQUIRED) { $dump .= " (REQUIRED)"; } if ($spec[3] & Args::MULTI) { $dump .= " (MULTIPLE)"; } if (isset($spec[4])) { $dump .= sprintf(" [%s]", $spec[4]); } $dump .= "\n"; } return $dump; } } compile($spec); } } /** * Compile the original spec * @param array|Traversable $spec * @return pharext\Cli\Args self */ public function compile($spec) { foreach ($spec as $arg) { if (isset($arg[0]) && is_numeric($arg[0])) { $arg[3] &= ~0xf00; $this->spec["--".$arg[0]] = $arg; } elseif (isset($arg[0])) { $this->spec["-".$arg[0]] = $arg; $this->spec["--".$arg[1]] = $arg; } else { $this->spec["--".$arg[1]] = $arg; } $this->orig[] = $arg; } return $this; } /** * Get original spec * @return array */ public function getSpec() { return $this->orig; } /** * Get compiled spec * @return array */ public function getCompiledSpec() { return $this->spec; } /** * Parse command line arguments according to the compiled spec. * * The Generator yields any parsing errors. * Parsing will stop when all arguments are processed or the first option * flagged Cli\Args::HALT was encountered. * * @param int $argc * @param array $argv * @return Generator */ public function parse($argc, array $argv) { for ($f = false, $p = 0, $i = 0; $i < $argc; ++$i) { $o = $argv[$i]; if ($o{0} === "-" && strlen($o) > 2 && $o{1} !== "-") { // multiple short opts, e.g. -vps $argc += strlen($o) - 2; array_splice($argv, $i, 1, array_map(function($s) { return "-$s"; }, str_split(substr($o, 1)))); $o = $argv[$i]; } elseif ($o{0} === "-" && strlen($o) > 2 && $o{1} === "-" && 0 < ($eq = strpos($o, "="))) { // long opt with argument, e.g. --foo=bar $argc++; array_splice($argv, $i, 1, [ substr($o, 0, $eq++), substr($o, $eq) ]); $o = $argv[$i]; } elseif ($o === "--") { // only positional args following $f = true; continue; } if ($f || !isset($this->spec[$o])) { if ($o{0} !== "-" && isset($this->spec["--$p"])) { $this[$p] = $o; if (!$this->optIsMulti($p)) { ++$p; } } else { yield sprintf("Unknown option %s", $o); } } elseif (!$this->optAcceptsArg($o)) { $this[$o] = true; } elseif ($i+1 < $argc && !isset($this->spec[$argv[$i+1]])) { $this[$o] = $argv[++$i]; } elseif ($this->optRequiresArg($o)) { yield sprintf("Option --%s requires an argument", $this->optLongName($o)); } else { // OPTARG $this[$o] = $this->optDefaultArg($o); } if ($this->optHalts($o)) { return; } } } /** * Validate that all required options were given. * * The Generator yields any validation errors. * * @return Generator */ public function validate() { $required = array_filter($this->orig, function($spec) { return $spec[3] & self::REQUIRED; }); foreach ($required as $req) { if ($req[3] & self::MULTI) { if (is_array($this[$req[0]])) { continue; } } elseif (strlen($this[$req[0]])) { continue; } if (is_numeric($req[0])) { yield sprintf("Argument <%s> is required", $req[1]); } else { yield sprintf("Option --%s is required", $req[1]); } } } public function toArray() { $args = []; foreach ($this->spec as $spec) { $opt = $this->opt($spec[1]); $args[$opt] = $this[$opt]; } return $args; } /** * Retreive the default argument of an option * @param string $o * @return mixed */ private function optDefaultArg($o) { $o = $this->opt($o); if (isset($this->spec[$o][4])) { return $this->spec[$o][4]; } return null; } /** * Retrieve the help message of an option * @param string $o * @return string */ private function optHelp($o) { $o = $this->opt($o); if (isset($this->spec[$o][2])) { return $this->spec[$o][2]; } return ""; } /** * Retrieve option's flags * @param string $o * @return int */ private function optFlags($o) { $o = $this->opt($o); if (isset($this->spec[$o])) { return $this->spec[$o][3]; } return null; } /** * Check whether an option is flagged for halting argument processing * @param string $o * @return boolean */ private function optHalts($o) { return $this->optFlags($o) & self::HALT; } /** * Check whether an option needs an argument * @param string $o * @return boolean */ private function optRequiresArg($o) { return $this->optFlags($o) & self::REQARG; } /** * Check wether an option accepts any argument * @param string $o * @return boolean */ private function optAcceptsArg($o) { return $this->optFlags($o) & 0xf00; } /** * Check whether an option can be used more than once * @param string $o * @return boolean */ private function optIsMulti($o) { return $this->optFlags($o) & self::MULTI; } /** * Retreive the long name of an option * @param string $o * @return string */ private function optLongName($o) { $o = $this->opt($o); return is_numeric($this->spec[$o][0]) ? $this->spec[$o][0] : $this->spec[$o][1]; } /** * Retreive the short name of an option * @param string $o * @return string */ private function optShortName($o) { $o = $this->opt($o); return is_numeric($this->spec[$o][0]) ? null : $this->spec[$o][0]; } /** * Retreive the canonical name (--long-name) of an option * @param string $o * @return string */ private function opt($o) { if (is_numeric($o)) { return "--$o"; } if ($o{0} !== '-') { if (strlen($o) > 1) { $o = "-$o"; } $o = "-$o"; } return $o; } /**@+ * Implements ArrayAccess and virtual properties */ function offsetExists($o) { $o = $this->opt($o); return isset($this->args[$o]); } function __isset($o) { return $this->offsetExists($o); } function offsetGet($o) { $o = $this->opt($o); if (isset($this->args[$o])) { return $this->args[$o]; } return $this->optDefaultArg($o); } function __get($o) { return $this->offsetGet($o); } function offsetSet($o, $v) { $osn = $this->optShortName($o); $oln = $this->optLongName($o); if ($this->optIsMulti($o)) { if (isset($osn)) { $this->args["-$osn"][] = $v; } $this->args["--$oln"][] = $v; } else { if (isset($osn)) { $this->args["-$osn"] = $v; } $this->args["--$oln"] = $v; } } function __set($o, $v) { $this->offsetSet($o, $v); } function offsetUnset($o) { unset($this->args["-".$this->optShortName($o)]); unset($this->args["--".$this->optLongName($o)]); } function __unset($o) { $this->offsetUnset($o); } /**@-*/ } args; } /** * Retrieve metadata of the currently running phar * @param string $key * @return mixed */ public function metadata($key = null) { if (extension_loaded("Phar")) { $running = new Phar(Phar::running(false)); } else { $running = new Archive(PHAREXT_PHAR); } if ($key === "signature") { $sig = $running->getSignature(); return sprintf("%s signature of %s\n%s", $sig["hash_type"], $this->metadata("name"), chunk_split($sig["hash"], 64, "\n")); } $metadata = $running->getMetadata(); if (isset($key)) { return $metadata[$key]; } return $metadata; } /** * Output pharext vX.Y.Z header */ public function header() { if (!headers_sent()) { /* only display header, if we didn't generate any output yet */ printf("%s\n\n", $this->metadata("header")); } } /** * @inheritdoc * @see \pharext\Command::debug() */ public function debug($fmt) { if ($this->args->verbose) { vprintf($fmt, array_slice(func_get_args(), 1)); } } /** * @inheritdoc * @see \pharext\Command::info() */ public function info($fmt) { if (!$this->args->quiet) { vprintf($fmt, array_slice(func_get_args(), 1)); } } /** * @inheritdoc * @see \pharext\Command::warn() */ public function warn($fmt) { if (!$this->args->quiet) { if (!isset($fmt)) { $fmt = "%s\n"; $arg = error_get_last()["message"]; } else { $arg = array_slice(func_get_args(), 1); } vfprintf(STDERR, "Warning: $fmt", $arg); } } /** * @inheritdoc * @see \pharext\Command::error() */ public function error($fmt) { if (!isset($fmt)) { $fmt = "%s\n"; $arg = error_get_last()["message"]; } else { $arg = array_slice(func_get_args(), 1); } vfprintf(STDERR, "ERROR: $fmt", $arg); } /** * Output command line help message * @param string $prog */ public function help($prog) { print new Args\Help($prog, $this->args); } /** * Verbosity * @return boolean */ public function verbosity() { if ($this->args->verbose) { return true; } elseif ($this->args->quiet) { return false; } else { return null; } } } command = $command; $this->verbose = $verbose; } /** * (Re-)set sudo command * @param string $sudo */ public function setSu($sudo = false) { $this->sudo = $sudo; } /** * Execute a program with escalated privileges handling interactive password prompt * @param string $command * @param bool $verbose * @return int exit status */ private function suExec($command, $verbose = null) { if (!($proc = proc_open($command, [STDIN,["pipe","w"],["pipe","w"]], $pipes))) { $this->status = -1; throw new Exception("Failed to run {$command}"); } $stdout = $pipes[1]; $passwd = 0; $checks = 10; while (!feof($stdout)) { $R = [$stdout]; $W = []; $E = []; if (!stream_select($R, $W, $E, null)) { continue; } $data = fread($stdout, 0x1000); /* only check a few times */ if ($passwd < $checks) { $passwd++; if (stristr($data, "password")) { $passwd = $checks + 1; printf("\n%s", $data); continue; } } elseif ($passwd > $checks) { /* new line after pw entry */ printf("\n"); $passwd = $checks; } if ($verbose === null) { print $this->progress($data, 0); } else { if ($verbose) { printf("%s", $data); } $this->output .= $data; } } if ($verbose === null) { $this->progress("", PHP_OUTPUT_HANDLER_FINAL); } return $this->status = proc_close($proc); } /** * Output handler that displays some progress while soaking output * @param string $string * @param int $flags * @return string */ private function progress($string, $flags) { static $counter = 0; static $symbols = ["\\","|","/","-"]; $this->output .= $string; if (false !== strpos($string, "\n")) { ++$counter; } return $flags & PHP_OUTPUT_HANDLER_FINAL ? " \r" : sprintf(" %s\r", $symbols[$counter % 4]); } /** * Run the command * @param array $args * @return \pharext\ExecCmd self * @throws \pharext\Exception */ public function run(array $args = null) { $exec = escapeshellcmd($this->command); if ($args) { $exec .= " ". implode(" ", array_map("escapeshellarg", (array) $args)); } if ($this->sudo) { $this->suExec(sprintf($this->sudo." 2>&1", $exec), $this->verbose); } elseif ($this->verbose) { ob_start(function($s) { $this->output .= $s; return $s; }, 1); passthru($exec, $this->status); ob_end_flush(); } elseif ($this->verbose !== false /* !quiet */) { ob_start([$this, "progress"], 1); passthru($exec . " 2>&1", $this->status); ob_end_flush(); } else { exec($exec ." 2>&1", $output, $this->status); $this->output = implode("\n", $output); } if ($this->status) { throw new Exception("Command {$exec} failed ({$this->status})"); } return $this; } /** * Retrieve exit code of cmd run * @return int */ public function getStatus() { return $this->status; } /** * Retrieve output of cmd run * @return string */ public function getOutput() { return $this->output; } } args = new Cli\Args([ ["h", "help", "Display help", Cli\Args::OPTIONAL|Cli\Args::SINGLE|Cli\Args::NOARG|Cli\Args::HALT], ["v", "verbose", "More output", Cli\Args::OPTIONAL|Cli\Args::SINGLE|Cli\Args::NOARG], ["q", "quiet", "Less output", Cli\Args::OPTIONAL|Cli\Args::SINGLE|Cli\Args::NOARG], ["p", "prefix", "PHP installation prefix if phpize is not in \$PATH, e.g. /opt/php7", Cli\Args::OPTIONAL|Cli\Args::SINGLE|Cli\Args::REQARG], ["n", "common-name", "PHP common program name, e.g. php5 or zts-php", Cli\Args::OPTIONAL|Cli\Args::SINGLE|Cli\Args::REQARG, "php"], ["c", "configure", "Additional extension configure flags, e.g. -c --with-flag", Cli\Args::OPTIONAL|Cli\Args::MULTI|Cli\Args::REQARG], ["s", "sudo", "Installation might need increased privileges", Cli\Args::OPTIONAL|Cli\Args::SINGLE|Cli\Args::OPTARG, "sudo -S %s"], ["i", "ini", "Activate in this php.ini instead of loaded default php.ini", Cli\Args::OPTIONAL|Cli\Args::SINGLE|Cli\Args::REQARG], [null, "signature", "Show package signature", Cli\Args::OPTIONAL|Cli\Args::SINGLE|Cli\Args::NOARG|Cli\Args::HALT], [null, "license", "Show package license", Cli\Args::OPTIONAL|Cli\Args::SINGLE|Cli\Args::NOARG|Cli\Args::HALT], [null, "name", "Show package name", Cli\Args::OPTIONAL|Cli\Args::SINGLE|Cli\Args::NOARG|Cli\Args::HALT], [null, "date", "Show package release date", Cli\Args::OPTIONAL|Cli\Args::SINGLE|Cli\Args::NOARG|Cli\Args::HALT], [null, "release", "Show package release version", Cli\Args::OPTIONAL|Cli\Args::SINGLE|Cli\Args::NOARG|Cli\Args::HALT], [null, "version", "Show pharext version", Cli\Args::OPTIONAL|Cli\Args::SINGLE|Cli\Args::NOARG|Cli\Args::HALT], ]); } /** * Perform cleaniup */ function __destruct() { foreach ($this->cleanup as $cleanup) { $cleanup->run(); } } private function extract($phar) { $temp = (new Task\Extract($phar))->run($this->verbosity()); $this->cleanup[] = new Task\Cleanup($temp); return $temp; } private function hooks(SplObjectStorage $phars) { $hook = []; foreach ($phars as $phar) { if (isset($phar["pharext_package.php"])) { $sdir = include $phar["pharext_package.php"]; if ($sdir instanceof SourceDir) { $this->args->compile($sdir->getArgs()); $hook[] = $sdir; } } } return $hook; } private function load() { $list = new SplObjectStorage(); $phar = extension_loaded("Phar") ? new Phar(Phar::running(false)) : new Archive(PHAREXT_PHAR); $temp = $this->extract($phar); foreach ($phar as $entry) { $dep_file = $entry->getBaseName(); if (fnmatch("*.ext.phar*", $dep_file)) { $dep_phar = extension_loaded("Phar") ? new Phar("$temp/$dep_file") : new Archive("$temp/$dep_file"); $list[$dep_phar] = $this->extract($dep_phar); } } /* the actual ext.phar at last */ $list[$phar] = $temp; return $list; } /** * @inheritdoc * @see \pharext\Command::run() */ public function run($argc, array $argv) { try { /* load the phar(s) */ $list = $this->load(); /* installer hooks */ $hook = $this->hooks($list); } catch (\Exception $e) { $this->error("%s\n", $e->getMessage()); exit(self::EEXTRACT); } /* standard arg stuff */ $errs = []; $prog = array_shift($argv); foreach ($this->args->parse(--$argc, $argv) as $error) { $errs[] = $error; } if ($this->args["help"]) { $this->header(); $this->help($prog); exit; } try { foreach (["signature", "name", "date", "license", "release", "version"] as $opt) { if ($this->args[$opt]) { printf("%s\n", $this->metadata($opt)); exit; } } } catch (\Exception $e) { $this->error("%s\n", $e->getMessage()); exit(self::EARGS); } foreach ($this->args->validate() as $error) { $errs[] = $error; } if ($errs) { if (!$this->args["quiet"]) { $this->header(); } foreach ($errs as $err) { $this->error("%s\n", $err); } if (!$this->args["quiet"]) { $this->help($prog); } exit(self::EARGS); } try { /* post process hooks */ foreach ($hook as $sdir) { $sdir->setArgs($this->args); } } catch (\Exception $e) { $this->error("%s\n", $e->getMessage()); exit(self::EARGS); } /* install packages */ try { foreach ($list as $phar) { $this->info("Installing %s ...\n", basename($phar->getPath())); $this->install($list[$phar]); $this->activate($list[$phar]); $this->info("Successfully installed %s!\n", basename($phar->getPath())); } } catch (\Exception $e) { $this->error("%s\n", $e->getMessage()); exit(self::EINSTALL); } } /** * Phpize + trinity */ private function install($temp) { // phpize $phpize = new Task\Phpize($temp, $this->args->prefix, $this->args->{"common-name"}); $phpize->run($this->verbosity()); // configure $configure = new Task\Configure($temp, $this->args->configure, $this->args->prefix, $this->args->{"common-name"}); $configure->run($this->verbosity()); // make $make = new Task\Make($temp); $make->run($this->verbosity()); // install $sudo = isset($this->args->sudo) ? $this->args->sudo : null; $install = new Task\Make($temp, ["install"], $sudo); $install->run($this->verbosity()); } private function activate($temp) { if ($this->args->ini) { $files = [$this->args->ini]; } else { $files = array_filter(array_map("trim", explode(",", php_ini_scanned_files()))); $files[] = php_ini_loaded_file(); } $sudo = isset($this->args->sudo) ? $this->args->sudo : null; $type = $this->metadata("type") ?: "extension"; $activate = new Task\Activate($temp, $files, $type, $this->args->prefix, $this->args{"common-name"}, $sudo); if (!$activate->run($this->verbosity())) { $this->info("Extension already activated ...\n"); } } } mergeLicensePattern($name, strtolower($name)); } $exts = []; foreach (["t{,e}xt", "rst", "asc{,i,ii}", "m{,ark}d{,own}", "htm{,l}"] as $ext) { $exts[] = $this->mergeLicensePattern(strtoupper($ext), $ext); } $pattern = "{". implode(",", $names) ."}{,.{". implode(",", $exts) ."}}"; if (($glob = glob("$dir/$pattern", GLOB_BRACE))) { return current($glob); } } private function mergeLicensePattern($upper, $lower) { $pattern = ""; $length = strlen($upper); for ($i = 0; $i < $length; ++$i) { if ($lower{$i} === $upper{$i}) { $pattern .= $upper{$i}; } else { $pattern .= "[" . $upper{$i} . $lower{$i} . "]"; } } return $pattern; } public function readLicense($file) { $text = file_get_contents($file); switch (strtolower(pathinfo($file, PATHINFO_EXTENSION))) { case "htm": case "html": $text = strip_tags($text); break; } return $text; } } ", self::version()); } static function date() { return gmdate("Y-m-d"); } static function all() { return [ "version" => self::version(), "header" => self::header(), "date" => self::date(), ]; } } key); $this->pub = openssl_pkey_get_details($key)["key"]; } /** * Sign the PHAR * @param \Phar $package */ function sign(\Phar $package) { $package->setSignatureAlgorithm(\Phar::OPENSSL, $this->key); } /** * Export the public key to a file * @param string $file * @throws \pharext\Exception */ function exportPublicKey($file) { if (!file_put_contents("$file.tmp", $this->pub) || !rename("$file.tmp", $file)) { throw new Exception; } } } args = new Cli\Args([ ["h", "help", "Display this help", Cli\Args::OPTIONAL|Cli\Args::SINGLE|Cli\Args::NOARG|Cli\Args::HALT], ["v", "verbose", "More output", Cli\Args::OPTIONAL|Cli\Args::SINGLE|Cli\Args::NOARG], ["q", "quiet", "Less output", Cli\Args::OPTIONAL|Cli\Args::SINGLE|Cli\Args::NOARG], ["n", "name", "Extension name", Cli\Args::REQUIRED|Cli\Args::SINGLE|Cli\Args::REQARG], ["r", "release", "Extension release version", Cli\Args::REQUIRED|Cli\Args::SINGLE|Cli\Args::REQARG], ["s", "source", "Extension source directory", Cli\Args::REQUIRED|Cli\Args::SINGLE|Cli\Args::REQARG], ["g", "git", "Use `git ls-tree` to determine file list", Cli\Args::OPTIONAL|Cli\Args::SINGLE|Cli\Args::NOARG], ["b", "branch", "Checkout this tag/branch", Cli\Args::OPTIONAL|Cli\Args::SINGLE|Cli\Args::REQARG], ["p", "pecl", "Use PECL package.xml to determine file list, name and release", Cli\Args::OPTIONAL|Cli\Args::SINGLE|Cli\Args::NOARG], ["d", "dest", "Destination directory", Cli\Args::OPTIONAL|Cli\Args::SINGLE|Cli\Args::REQARG, "."], ["z", "gzip", "Create additional PHAR compressed with gzip", Cli\Args::OPTIONAL|Cli\Args::SINGLE|Cli\Args::NOARG], ["Z", "bzip", "Create additional PHAR compressed with bzip", Cli\Args::OPTIONAL|Cli\Args::SINGLE|Cli\Args::NOARG], ["S", "sign", "Sign the PHAR with a private key", Cli\Args::OPTIONAL|Cli\Args::SINGLE|Cli\Args::REQARG], ["E", "zend", "Mark as Zend Extension", Cli\Args::OPTIONAL|Cli\Args::SINGLE|Cli\Args::NOARG], [null, "signature", "Show pharext signature", Cli\Args::OPTIONAL|Cli\Args::SINGLE|Cli\Args::NOARG|Cli\Args::HALT], [null, "license", "Show pharext license", Cli\Args::OPTIONAL|Cli\Args::SINGLE|Cli\Args::NOARG|Cli\Args::HALT], [null, "version", "Show pharext version", Cli\Args::OPTIONAL|Cli\Args::SINGLE|Cli\Args::NOARG|Cli\Args::HALT], ]); } /** * Perform cleaniup */ function __destruct() { foreach ($this->cleanup as $cleanup) { $cleanup->run(); } } /** * @inheritdoc * @see \pharext\Command::run() */ public function run($argc, array $argv) { $errs = []; $prog = array_shift($argv); foreach ($this->args->parse(--$argc, $argv) as $error) { $errs[] = $error; } if ($this->args["help"]) { $this->header(); $this->help($prog); exit; } try { foreach (["signature", "license", "version"] as $opt) { if ($this->args[$opt]) { printf("%s\n", $this->metadata($opt)); exit; } } } catch (\Exception $e) { $this->error("%s\n", $e->getMessage()); exit(self::EARGS); } try { /* source needs to be evaluated before Cli\Args validation, * so e.g. name and version can be overriden and Cli\Args * does not complain about missing arguments */ $this->loadSource(); } catch (\Exception $e) { $errs[] = $e->getMessage(); } foreach ($this->args->validate() as $error) { $errs[] = $error; } if ($errs) { if (!$this->args["quiet"]) { $this->header(); } foreach ($errs as $err) { $this->error("%s\n", $err); } printf("\n"); if (!$this->args["quiet"]) { $this->help($prog); } exit(self::EARGS); } $this->createPackage(); } /** * Download remote source * @param string $source * @return string local source */ private function download($source) { if ($this->args->git) { $task = new Task\GitClone($source, $this->args->branch); } else { /* print newline only once */ $done = false; $task = new Task\StreamFetch($source, function($bytes_pct) use(&$done) { if (!$done) { $this->info(" %3d%% [%s>%s] \r", floor($bytes_pct*100), str_repeat("=", round(50*$bytes_pct)), str_repeat(" ", round(50*(1-$bytes_pct))) ); if ($bytes_pct == 1) { $done = true; $this->info("\n"); } } }); } $local = $task->run($this->verbosity()); $this->cleanup[] = new Task\Cleanup($local); return $local; } /** * Extract local archive * @param stirng $source * @return string extracted directory */ private function extract($source) { try { $task = new Task\Extract($source); $dest = $task->run($this->verbosity()); } catch (\Exception $e) { if (false === strpos($e->getMessage(), "checksum mismatch")) { throw $e; } $dest = (new Task\PaxFixup($source))->run($this->verbosity()); } $this->cleanup[] = new Task\Cleanup($dest); return $dest; } /** * Localize a possibly remote source * @param string $source * @return string local source directory */ private function localize($source) { if (!stream_is_local($source) || ($this->args->git && isset($this->args->branch))) { $source = $this->download($source); $this->cleanup[] = new Task\Cleanup($source); } $source = realpath($source); if (!is_dir($source)) { $source = $this->extract($source); $this->cleanup[] = new Task\Cleanup($source); if (!$this->args->git) { $source = (new Task\PeclFixup($source))->run($this->verbosity()); } } return $source; } /** * Load the source dir * @throws \pharext\Exception */ private function loadSource(){ if ($this->args["source"]) { $source = $this->localize($this->args["source"]); if ($this->args["pecl"]) { $this->source = new SourceDir\Pecl($source); } elseif ($this->args["git"]) { $this->source = new SourceDir\Git($source); } elseif (is_file("$source/pharext_package.php")) { $this->source = include "$source/pharext_package.php"; } else { $this->source = new SourceDir\Basic($source); } if (!$this->source instanceof SourceDir) { throw new Exception("Unknown source dir $source"); } foreach ($this->source->getPackageInfo() as $key => $val) { $this->args->$key = $val; } } } /** * Creates the extension phar */ private function createPackage() { try { $meta = array_merge(Metadata::all(), [ "name" => $this->args->name, "release" => $this->args->release, "license" => $this->source->getLicense(), "type" => $this->args->zend ? "zend_extension" : "extension", ]); $file = (new Task\PharBuild($this->source, __DIR__."/../pharext_installer.php", $meta))->run($this->verbosity()); } catch (\Exception $e) { $this->error("%s\n", $e->getMessage()); exit(self::EBUILD); } try { if ($this->args->sign) { $this->info("Using private key to sign phar ...\n"); $pass = (new Task\Askpass)->run($this->verbosity()); $sign = new Task\PharSign($file, $this->args->sign, $pass); $pkey = $sign->run($this->verbosity()); } } catch (\Exception $e) { $this->error("%s\n", $e->getMessage()); exit(self::ESIGN); } if ($this->args->gzip) { try { $gzip = (new Task\PharCompress($file, Phar::GZ))->run(); $move = new Task\PharRename($gzip, $this->args->dest, $this->args->name ."-". $this->args->release); $name = $move->run($this->verbosity()); $this->info("Created gzipped phar %s\n", $name); if ($this->args->sign) { $sign = new Task\PharSign($name, $this->args->sign, $pass); $sign->run($this->verbosity())->exportPublicKey($name.".pubkey"); } } catch (\Exception $e) { $this->warn("%s\n", $e->getMessage()); } } if ($this->args->bzip) { try { $bzip = (new Task\PharCompress($file, Phar::BZ2))->run(); $move = new Task\PharRename($bzip, $this->args->dest, $this->args->name ."-". $this->args->release); $name = $move->run($this->verbosity()); $this->info("Created bzipped phar %s\n", $name); if ($this->args->sign) { $sign = new Task\PharSign($name, $this->args->sign, $pass); $sign->run($this->verbosity())->exportPublicKey($name.".pubkey"); } } catch (\Exception $e) { $this->warn("%s\n", $e->getMessage()); } } try { $move = new Task\PharRename($file, $this->args->dest, $this->args->name ."-". $this->args->release); $name = $move->run($this->verbosity()); $this->info("Created executable phar %s\n", $name); if (isset($pkey)) { $pkey->exportPublicKey($name.".pubkey"); } } catch (\Exception $e) { $this->error("%s\n", $e->getMessage()); exit(self::EBUILD); } } } path = $path; } public function getBaseDir() { return $this->path; } public function getPackageInfo() { return []; } public function getLicense() { if (($file = $this->findLicense($this->getBaseDir()))) { return $this->readLicense($file); } return "UNKNOWN"; } public function getArgs() { return []; } public function setArgs(Args $args) { } public function filter($current, $key, $iterator) { $sub = $current->getSubPath(); if ($sub === ".git" || $sub === ".hg" || $sub === ".svn") { return false; } return true; } public function getIterator() { $rdi = new RecursiveDirectoryIterator($this->path, FilesystemIterator::CURRENT_AS_SELF | // needed for 5.5 FilesystemIterator::KEY_AS_PATHNAME | FilesystemIterator::SKIP_DOTS); $rci = new RecursiveCallbackFilterIterator($rdi, [$this, "filter"]); $rii = new RecursiveIteratorIterator($rci); foreach ($rii as $path => $child) { if (!$child->isDir()) { yield realpath($path); } } } } path = $path; } /** * @inheritdoc * @see \pharext\SourceDir::getBaseDir() */ public function getBaseDir() { return $this->path; } /** * @inheritdoc * @return array */ public function getPackageInfo() { return []; } /** * @inheritdoc * @return string */ public function getLicense() { if (($file = $this->findLicense($this->getBaseDir()))) { return $this->readLicense($file); } return "UNKNOWN"; } /** * @inheritdoc * @return array */ public function getArgs() { return []; } /** * @inheritdoc */ public function setArgs(Args $args) { } /** * Generate a list of files by `git ls-files` * @return Generator */ private function generateFiles() { $pwd = getcwd(); chdir($this->path); if (($pipe = popen("git ls-tree -r --name-only HEAD", "r"))) { $path = realpath($this->path); while (!feof($pipe)) { if (strlen($file = trim(fgets($pipe)))) { /* there may be symlinks, so no realpath here */ yield "$path/$file"; } } pclose($pipe); } chdir($pwd); } /** * Implements IteratorAggregate * @see IteratorAggregate::getIterator() */ public function getIterator() { return $this->generateFiles(); } } file = "$path/package2.xml"); } elseif (is_file("$path/package.xml")) { $sxe = simplexml_load_file($this->file = "$path/package.xml"); } else { throw new Exception("Missing package.xml in $path"); } $sxe->registerXPathNamespace("pecl", $sxe->getDocNamespaces()[""]); $this->sxe = $sxe; $this->path = realpath($path); } /** * @inheritdoc * @see \pharext\SourceDir::getBaseDir() */ public function getBaseDir() { return $this->path; } /** * Retrieve gathered package info * @return Generator */ public function getPackageInfo() { if (($name = $this->sxe->xpath("/pecl:package/pecl:name"))) { yield "name" => (string) $name[0]; } if (($release = $this->sxe->xpath("/pecl:package/pecl:version/pecl:release"))) { yield "release" => (string) $release[0]; } if ($this->sxe->xpath("/pecl:package/pecl:zendextsrcrelease")) { yield "zend" => true; } } /** * @inheritdoc * @return string */ public function getLicense() { if (($license = $this->sxe->xpath("/pecl:package/pecl:license"))) { if (($file = $this->findLicense($this->getBaseDir(), $license[0]["filesource"]))) { return $this->readLicense($file); } } if (($file = $this->findLicense($this->getBaseDir()))) { return $this->readLicense($file); } if ($license) { return $license[0] ." ". $license[0]["uri"]; } return "UNKNOWN"; } /** * @inheritdoc * @see \pharext\SourceDir::getArgs() */ public function getArgs() { $configure = $this->sxe->xpath("/pecl:package/pecl:extsrcrelease/pecl:configureoption"); foreach ($configure as $cfg) { yield [null, $cfg["name"], ucfirst($cfg["prompt"]), Args::OPTARG, strlen($cfg["default"]) ? $cfg["default"] : null]; } $configure = $this->sxe->xpath("/pecl:package/pecl:zendextsrcrelease/pecl:configureoption"); foreach ($configure as $cfg) { yield [null, $cfg["name"], ucfirst($cfg["prompt"]), Args::OPTARG, strlen($cfg["default"]) ? $cfg["default"] : null]; } } /** * @inheritdoc * @see \pharext\SourceDir::setArgs() */ public function setArgs(Args $args) { $configure = $this->sxe->xpath("/pecl:package/pecl:extsrcrelease/pecl:configureoption"); foreach ($configure as $cfg) { if (isset($args[$cfg["name"]])) { $args->configure = "--{$cfg["name"]}={$args[$cfg["name"]]}"; } } $configure = $this->sxe->xpath("/pecl:package/pecl:zendextsrcrelease/pecl:configureoption"); foreach ($configure as $cfg) { if (isset($args[$cfg["name"]])) { $args->configure = "--{$cfg["name"]}={$args[$cfg["name"]]}"; } } } /** * Compute the path of a file by parent dir nodes * @param \SimpleXMLElement $ele * @return string */ private function dirOf($ele) { $path = ""; while (($ele = current($ele->xpath(".."))) && $ele->getName() == "dir") { $path = trim($ele["name"], "/") ."/". $path ; } return trim($path, "/"); } /** * Generate a list of files from the package.xml * @return Generator */ private function generateFiles() { /* hook */ $temp = tmpfile(); fprintf($temp, " $temp; /* deps */ $dependencies = $this->sxe->xpath("/pecl:package/pecl:dependencies/pecl:required/pecl:package"); foreach ($dependencies as $key => $dep) { if (($glob = glob("{$this->path}/{$dep->name}-*.ext.phar*"))) { usort($glob, function($a, $b) { return version_compare( substr($a, strpos(".ext.phar", $a)), substr($b, strpos(".ext.phar", $b)) ); }); yield end($glob); } } /* files */ yield realpath($this->file); foreach ($this->sxe->xpath("//pecl:file") as $file) { yield realpath($this->path ."/". $this->dirOf($file) ."/". $file["name"]); } } /** * Implements IteratorAggregate * @see IteratorAggregate::getIterator() */ public function getIterator() { return $this->generateFiles(); } } cwd = $cwd; $this->type = $type; $this->sudo = $sudo; if (!$this->inis = $inis) { throw new Exception("No PHP INIs given"); } $cmd = $common_name . "-config"; if (isset($prefix)) { $cmd = $prefix . "/bin/" . $cmd; } $this->php_config = $cmd; } /** * @param bool $verbose * @return boolean false, if extension was already activated */ public function run($verbose = false) { if ($verbose !== false) { printf("Running INI activation ...\n"); } $extension = basename(current(glob("{$this->cwd}/modules/*.so"))); if ($this->type === "zend_extension") { $pattern = preg_quote((new ExecCmd($this->php_config))->run(["--extension-dir"])->getOutput() . "/$extension", "/"); } else { $pattern = preg_quote($extension, "/"); } foreach ($this->inis as $file) { if ($verbose) { printf("Checking %s ...\n", $file); } if (!file_exists($file)) { throw new Exception(sprintf("INI file '%s' does not exist", $file)); } $temp = new Tempfile("phpini"); foreach (file($file) as $line) { if (preg_match("/^\s*{$this->type}\s*=\s*[\"']?{$pattern}[\"']?\s*(;.*)?\$/", $line)) { return false; } fwrite($temp->getStream(), $line); } } /* not found; append to last processed file, which is the main by default */ if ($verbose) { printf("Activating in %s ...\n", $file); } fprintf($temp->getStream(), $this->type . "=%s\n", $extension); $temp->closeStream(); $path = $temp->getPathname(); $stat = stat($file); // owner transfer $ugid = sprintf("%d:%d", $stat["uid"], $stat["gid"]); $cmd = new ExecCmd("chown", $verbose); if (isset($this->sudo)) { $cmd->setSu($this->sudo); } $cmd->run([$ugid, $path]); // permission transfer $perm = decoct($stat["mode"] & 0777); $cmd = new ExecCmd("chmod", $verbose); if (isset($this->sudo)) { $cmd->setSu($this->sudo); } $cmd->run([$perm, $path]); // rename $cmd = new ExecCmd("mv", $verbose); if (isset($this->sudo)) { $cmd->setSu($this->sudo); } $cmd->run([$path, $file]); if ($verbose) { printf("Replaced %s ...\n", $file); } return true; } } prompt = $prompt; } /** * @param bool $verbose * @return string */ public function run($verbose = false) { system("stty -echo"); printf("%s ", $this->prompt); $pass = fgets(STDIN, 1024); printf("\n"); system("stty echo"); if (substr($pass, -1) == "\n") { $pass = substr($pass, 0, -1); } return $pass; } } rewind(); $rii->valid(); $rii->next()) { if (!$rii->isDot()) { yield $rii->getSubPathname() => $rii->key(); } } } } rm = $rm; } /** * @param bool $verbose */ public function run($verbose = false) { if ($verbose) { printf("Cleaning up %s ...\n", $this->rm); } if ($this->rm instanceof Tempfile) { unset($this->rm); } elseif (is_dir($this->rm)) { $rdi = new RecursiveDirectoryIterator($this->rm, FilesystemIterator::CURRENT_AS_SELF | // needed for 5.5 FilesystemIterator::KEY_AS_PATHNAME | FilesystemIterator::SKIP_DOTS); $rii = new RecursiveIteratorIterator($rdi, RecursiveIteratorIterator::CHILD_FIRST); foreach ($rii as $path => $child) { if ($child->isDir()) { @rmdir($path); } else { @unlink($path); } } @rmdir($this->rm); } elseif (file_exists($this->rm)) { @unlink($this->rm); } } } cwd = $cwd; $cmd = $common_name . "-config"; if (isset($prefix)) { $cmd = $prefix . "/bin/" . $cmd; } $this->args = ["--with-php-config=$cmd"]; if ($args) { $this->args = array_merge($this->args, $args); } } public function run($verbose = false) { if ($verbose !== false) { printf("Running ./configure ...\n"); } $pwd = getcwd(); if (!chdir($this->cwd)) { throw new Exception; } try { $cmd = new ExecCmd("./configure", $verbose); $cmd->run($this->args); } finally { chdir($pwd); } } } source = $source; } else { $this->source = new PharData($source); } } /** * @param bool $verbose * @return \pharext\Tempdir */ public function run($verbose = false) { if ($verbose) { printf("Extracting %s ...\n", basename($this->source->getPath())); } if ($this->source instanceof Archive) { return $this->source->extract(); } $dest = new Tempdir("extract"); $this->source->extractTo($dest); return $dest; } } source = $source; $this->branch = $branch; } /** * @param bool $verbose * @return \pharext\Tempdir */ public function run($verbose = false) { if ($verbose !== false) { printf("Fetching %s ...\n", $this->source); } $local = new Tempdir("gitclone"); $cmd = new ExecCmd("git", $verbose); if (strlen($this->branch)) { $cmd->run(["clone", "--depth", 1, "--branch", $this->branch, $this->source, $local]); } else { $cmd->run(["clone", $this->source, $local]); } return $local; } } cwd = $cwd; $this->sudo = $sudo; $this->args = $args; } /** * * @param bool $verbose * @throws \pharext\Exception */ public function run($verbose = false) { if ($verbose !== false) { printf("Running make"); if ($this->args) { foreach ($this->args as $arg) { printf(" %s", $arg); } } printf(" ...\n"); } $pwd = getcwd(); if (!chdir($this->cwd)) { throw new Exception; } try { $cmd = new ExecCmd("make", $verbose); if (isset($this->sudo)) { $cmd->setSu($this->sudo); } $args = $this->args; if (!$verbose) { $args = array_merge((array) $args, ["-s"]); } $cmd->run($args); } finally { chdir($pwd); } } } source = $source; } private function openArchive($source) { $hdr = file_get_contents($source, false, null, 0, 3); if ($hdr === "\x1f\x8b\x08") { $fd = fopen("compress.zlib://$source", "r"); } elseif ($hdr === "BZh") { $fd = fopen("compress.bzip2://$source", "r"); } else { $fd = fopen($source, "r"); } if (!is_resource($fd)) { throw new Exception; } return $fd; } public function run($verbose = false) { if ($verbose !== false) { printf("Fixing up a tarball with global pax header ...\n"); } $temp = new Tempfile("paxfix"); stream_copy_to_stream($this->openArchive($this->source), $temp->getStream(), -1, 1024); $temp->closeStream(); return (new Extract((string) $temp))->run($verbose); } }source = $source; } /** * @param bool $verbose * @return string sanitized source location * @throws \pahrext\Exception */ public function run($verbose = false) { if ($verbose !== false) { printf("Sanitizing PECL dir ...\n"); } $dirs = glob("{$this->source}/*", GLOB_ONLYDIR); $files = array_diff(glob("{$this->source}/*"), $dirs); $check = array_reduce($files, function($r, $v) { return $v && fnmatch("package*.xml", basename($v)); }, true); if (count($dirs) !== 1 || !$check) { throw new Exception("Does not look like an extracted PECL dir: {$this->source}"); } $dest = current($dirs); foreach ($files as $file) { if ($verbose) { printf("Moving %s into %s ...\n", basename($file), basename($dest)); } if (!rename($file, "$dest/" . basename($file))) { throw new Exception; } } return $dest; } } source = $source; $this->stub = $stub; $this->meta = $meta; $this->readonly = $readonly; } /** * @param bool $verbose * @return \pharext\Tempname * @throws \pharext\Exception */ public function run($verbose = false) { /* Phar::compress() and ::convert*() use strtok("."), ugh! * so, be sure to not use any other dots in the filename * except for .phar */ $temp = new Tempname("", "-pharext.phar"); $phar = new Phar($temp); $phar->startBuffering(); if ($this->meta) { $phar->setMetadata($this->meta); } if ($this->stub) { (new PharStub($phar, $this->stub))->run($verbose); } $phar->buildFromIterator((new Task\BundleGenerator)->run()); if ($this->source) { if ($verbose) { $bdir = $this->source->getBaseDir(); $blen = strlen($bdir); foreach ($this->source as $index => $file) { if (is_resource($file)) { printf("Packaging %s ...\n", $index); $phar[$index] = $file; } else { printf("Packaging %s ...\n", $index = trim(substr($file, $blen), "/")); $phar->addFile($file, $index); } } } else { $phar->buildFromIterator($this->source, $this->source->getBaseDir()); } } $phar->stopBuffering(); if (!chmod($temp, fileperms($temp) | 0111)) { throw new Exception; } return $temp; } }file = $file; $this->package = new Phar($file); $this->encoding = $encoding; switch ($encoding) { case Phar::GZ: $this->extension = ".gz"; break; case Phar::BZ2: $this->extension = ".bz2"; break; } } /** * @param bool $verbose * @return string */ public function run($verbose = false) { if ($verbose) { printf("Compressing %s ...\n", basename($this->package->getPath())); } /* stop shebang */ $stub = $this->package->getStub(); $phar = $this->package->compress($this->encoding); $phar->setStub(substr($stub, strpos($stub, "\n")+1)); return $this->file . $this->extension; } } phar = $phar; $this->dest = $dest; $this->name = $name; } /** * @param bool $verbose * @return string path to renamed phar * @throws \pharext\Exception */ public function run($verbose = false) { $extension = substr(strstr($this->phar, "-pharext.phar"), 8); $name = sprintf("%s/%s.ext%s", $this->dest, $this->name, $extension); if ($verbose) { printf("Renaming %s to %s ...\n", basename($this->phar), basename($name)); } if (!rename($this->phar, $name)) { throw new Exception; } return $name; } } phar = $phar; } else { $this->phar = new Phar($phar); } $this->pkey = new Openssl\PrivateKey($pkey, $pass); } /** * @param bool $verbose * @return \pharext\Openssl\PrivateKey */ public function run($verbose = false) { if ($verbose) { printf("Signing %s ...\n", basename($this->phar->getPath())); } $this->pkey->sign($this->phar); return $this->pkey; } } phar = $phar; if (!file_exists($this->stub = $stub)) { throw new Exception("File '$stub' does not exist"); } } /** * @param bool $verbose */ function run($verbose = false) { if ($verbose) { printf("Using stub '%s'...\n", basename($this->stub)); } $stub = preg_replace_callback('/^#include <([^>]+)>/m', function($includes) { return file_get_contents($includes[1], true, null, 5); }, file_get_contents($this->stub)); if ($this->phar->isCompressed() && substr($stub, 0, 2) === "#!") { $stub = substr($stub, strpos($stub, "\n")+1); } $this->phar->setStub($stub); } } cwd = $cwd; $cmd = $common_name . "ize"; if (isset($prefix)) { $cmd = $prefix . "/bin/" . $cmd; } $this->phpize = $cmd; } /** * @param bool $verbose * @throws \pharext\Exception */ public function run($verbose = false) { if ($verbose !== false) { printf("Running %s ...\n", $this->phpize); } $pwd = getcwd(); if (!chdir($this->cwd)) { throw new Exception; } try { $cmd = new ExecCmd($this->phpize, $verbose); $cmd->run(); } finally { chdir($pwd); } } } source = $source; $this->progress = $progress; } private function createStreamContext() { $progress = $this->progress; /* avoid bytes_max bug of older PHP versions */ $maxbytes = 0; return stream_context_create([],["notification" => function($notification, $severity, $message, $code, $bytes_cur, $bytes_max) use($progress, &$maxbytes) { if ($bytes_max > $maxbytes) { $maxbytes = $bytes_max; } switch ($notification) { case STREAM_NOTIFY_CONNECT: $progress(0); break; case STREAM_NOTIFY_PROGRESS: $progress($maxbytes > 0 ? $bytes_cur/$maxbytes : .5); break; case STREAM_NOTIFY_COMPLETED: /* this is sometimes not generated, why? */ $progress(1); break; } }]); } /** * @param bool $verbose * @return \pharext\Task\Tempfile * @throws \pharext\Exception */ public function run($verbose = false) { if ($verbose !== false) { printf("Fetching %s ...\n", $this->source); } $context = $this->createStreamContext(); if (!$remote = fopen($this->source, "r", false, $context)) { throw new Exception; } $local = new Tempfile("remote"); if (!stream_copy_to_stream($remote, $local->getStream())) { throw new Exception; } $local->closeStream(); /* STREAM_NOTIFY_COMPLETED is not generated, see above */ call_user_func($this->progress, 1); return $local; } } 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; } } 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; } } args = new Cli\Args([ ["h", "help", "Display this help", Cli\Args::OPTIONAL|Cli\Args::SINGLE|Cli\Args::NOARG|Cli\Args::HALT], ["v", "verbose", "More output", Cli\Args::OPTIONAL|Cli\Args::SINGLE|Cli\Args::NOARG], ["q", "quiet", "Less output", Cli\Args::OPTIONAL|Cli\Args::SINGLE|Cli\Args::NOARG], [null, "signature", "Show pharext signature", Cli\Args::OPTIONAL|Cli\Args::SINGLE|Cli\Args::NOARG|Cli\Args::HALT], [null, "license", "Show pharext license", Cli\Args::OPTIONAL|Cli\Args::SINGLE|Cli\Args::NOARG|Cli\Args::HALT], [null, "version", "Show pharext version", Cli\Args::OPTIONAL|Cli\Args::SINGLE|Cli\Args::NOARG|Cli\Args::HALT], [0, "path", "Path to .ext.phar to update", Cli\Args::REQUIRED|Cli\Args::MULTI], ]); } /** * @inheritdoc * @see \pharext\Command::run() */ public function run($argc, array $argv) { $errs = []; $prog = array_shift($argv); foreach ($this->args->parse(--$argc, $argv) as $error) { $errs[] = $error; } if ($this->args["help"]) { $this->header(); $this->help($prog); exit; } try { foreach (["signature", "license", "version"] as $opt) { if ($this->args[$opt]) { printf("%s\n", $this->metadata($opt)); exit; } } } catch (\Exception $e) { $this->error("%s\n", $e->getMessage()); exit(self::EARGS); } foreach ($this->args->validate() as $error) { $errs[] = $error; } if ($errs) { if (!$this->args["quiet"]) { $this->header(); } foreach ($errs as $err) { $this->error("%s\n", $err); } printf("\n"); if (!$this->args["quiet"]) { $this->help($prog); } exit(self::EARGS); } foreach ($this->args[0] as $file) { $info = new SplFileInfo($file); while ($info->isLink()) { $info = new SplFileInfo($info->getLinkTarget()); } if ($info->isFile()) { if (!$this->updatePackage($info)) { $this->warn("Cannot upgrade pre-v3 packages\n"); } } else { $this->error("File '%s' does not exist\n", $file); exit(self::EARGS); } } } /** * Replace the pharext core in an .ext.phar package * @param string $temp path to temp phar * @return boolean FALSE if the package is too old (pre-v3) to upgrade */ private function replacePharext($temp) { $phar = new Phar($temp, Phar::CURRENT_AS_SELF); $phar->startBuffering(); if (!$meta = $phar->getMetadata()) { // don't upgrade pre-v3 packages return false; } // replace current pharext files $core = (new Task\BundleGenerator)->run($this->verbosity()); $phar->buildFromIterator($core); $stub = __DIR__."/../pharext_installer.php"; (new Task\PharStub($phar, $stub))->run($this->verbosity()); // check dependencies foreach ($phar as $info) { if (fnmatch("*.ext.phar*", $info->getBasename())) { $this->updatePackage($info, $phar); } } $phar->stopBuffering(); $phar->setMetadata([ "version" => Metadata::version(), "header" => Metadata::header(), ] + $meta); $this->info("Updated pharext version from '%s' to '%s'\n", isset($meta["version"]) ? $meta["version"] : "(unknown)", $phar->getMetadata()["version"]); return true; } /** * Update an .ext.phar package to the current pharext version * @param SplFileInfo $file * @param Phar $phar the parent phar containing $file as dependency * @return boolean FALSE if the package is too old (pre-v3) to upgrade * @throws Exception */ private function updatePackage(SplFileInfo $file, Phar $phar = null) { $this->info("Updating pharext core in '%s'...\n", basename($file)); $temp = new Tempname("update", substr(strstr($file, ".ext.phar"), 4)); if (!copy($file->getPathname(), $temp)) { throw new Exception; } if (!chmod($temp, $file->getPerms())) { throw new Exception; } if (!$this->replacePharext($temp)) { return false; } if ($phar) { $phar->addFile($temp, $file); } elseif (!rename($temp, $file->getPathname())) { throw new Exception; } return true; } } #!/usr/bin/env php #include #include #include #include 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(); #!/usr/bin/php -dphar.readonly=0 run($argc, $argv); __HALT_COMPILER(); #!/usr/bin/php -dphar.readonly=0 run($argc, $argv); __HALT_COMPILER(); package.xml merge=touch php_propro.h merge=touch # / *~ /*.tgz /.deps /*.lo /*.la /config.[^wm]* /configure* /lib* /ac*.m4 /ltmain.sh /install-sh /Make* /mk* /missing /.libs /build /include /modules /autom4te* /.dep.inc run-tests.php propro# Doxyfile 1.8.5 #--------------------------------------------------------------------------- # Project related configuration options #--------------------------------------------------------------------------- DOXYFILE_ENCODING = UTF-8 PROJECT_NAME = "Property proxy API" PROJECT_NUMBER = PROJECT_BRIEF = "A facility to manage extension object properties tied to C-struct members" PROJECT_LOGO = OUTPUT_DIRECTORY = CREATE_SUBDIRS = NO OUTPUT_LANGUAGE = English BRIEF_MEMBER_DESC = YES REPEAT_BRIEF = YES ABBREVIATE_BRIEF = ALWAYS_DETAILED_SEC = NO INLINE_INHERITED_MEMB = NO FULL_PATH_NAMES = YES STRIP_FROM_PATH = STRIP_FROM_INC_PATH = SHORT_NAMES = NO JAVADOC_AUTOBRIEF = YES QT_AUTOBRIEF = NO MULTILINE_CPP_IS_BRIEF = NO INHERIT_DOCS = YES SEPARATE_MEMBER_PAGES = NO TAB_SIZE = 4 ALIASES = TCL_SUBST = OPTIMIZE_OUTPUT_FOR_C = YES OPTIMIZE_OUTPUT_JAVA = NO OPTIMIZE_FOR_FORTRAN = NO OPTIMIZE_OUTPUT_VHDL = NO EXTENSION_MAPPING = MARKDOWN_SUPPORT = YES AUTOLINK_SUPPORT = YES BUILTIN_STL_SUPPORT = NO CPP_CLI_SUPPORT = NO SIP_SUPPORT = NO IDL_PROPERTY_SUPPORT = YES DISTRIBUTE_GROUP_DOC = NO SUBGROUPING = YES INLINE_GROUPED_CLASSES = NO INLINE_SIMPLE_STRUCTS = YES TYPEDEF_HIDES_STRUCT = NO LOOKUP_CACHE_SIZE = 0 #--------------------------------------------------------------------------- # Build related configuration options #--------------------------------------------------------------------------- EXTRACT_ALL = YES EXTRACT_PRIVATE = NO EXTRACT_PACKAGE = NO EXTRACT_STATIC = NO EXTRACT_LOCAL_CLASSES = NO EXTRACT_LOCAL_METHODS = NO EXTRACT_ANON_NSPACES = NO HIDE_UNDOC_MEMBERS = NO HIDE_UNDOC_CLASSES = NO HIDE_FRIEND_COMPOUNDS = NO HIDE_IN_BODY_DOCS = NO INTERNAL_DOCS = NO CASE_SENSE_NAMES = YES HIDE_SCOPE_NAMES = NO SHOW_INCLUDE_FILES = YES FORCE_LOCAL_INCLUDES = NO INLINE_INFO = YES SORT_MEMBER_DOCS = YES SORT_BRIEF_DOCS = NO SORT_MEMBERS_CTORS_1ST = NO SORT_GROUP_NAMES = NO SORT_BY_SCOPE_NAME = NO STRICT_PROTO_MATCHING = NO GENERATE_TODOLIST = YES GENERATE_TESTLIST = YES GENERATE_BUGLIST = YES GENERATE_DEPRECATEDLIST= YES ENABLED_SECTIONS = MAX_INITIALIZER_LINES = 30 SHOW_USED_FILES = YES SHOW_FILES = YES SHOW_NAMESPACES = YES FILE_VERSION_FILTER = LAYOUT_FILE = CITE_BIB_FILES = #--------------------------------------------------------------------------- # Configuration options related to warning and progress messages #--------------------------------------------------------------------------- QUIET = NO WARNINGS = YES WARN_IF_UNDOCUMENTED = YES WARN_IF_DOC_ERROR = YES WARN_NO_PARAMDOC = NO WARN_FORMAT = "$file:$line: $text" WARN_LOGFILE = #--------------------------------------------------------------------------- # Configuration options related to the input files #--------------------------------------------------------------------------- INPUT = php_propro.h INPUT_ENCODING = UTF-8 FILE_PATTERNS = RECURSIVE = NO EXCLUDE = EXCLUDE_SYMLINKS = NO EXCLUDE_PATTERNS = EXCLUDE_SYMBOLS = EXAMPLE_PATH = EXAMPLE_PATTERNS = EXAMPLE_RECURSIVE = NO IMAGE_PATH = INPUT_FILTER = FILTER_PATTERNS = FILTER_SOURCE_FILES = NO FILTER_SOURCE_PATTERNS = USE_MDFILE_AS_MAINPAGE = #--------------------------------------------------------------------------- # Configuration options related to source browsing #--------------------------------------------------------------------------- SOURCE_BROWSER = NO INLINE_SOURCES = NO STRIP_CODE_COMMENTS = YES REFERENCED_BY_RELATION = YES REFERENCES_RELATION = NO REFERENCES_LINK_SOURCE = YES SOURCE_TOOLTIPS = YES USE_HTAGS = NO VERBATIM_HEADERS = YES #--------------------------------------------------------------------------- # Configuration options related to the alphabetical class index #--------------------------------------------------------------------------- ALPHABETICAL_INDEX = YES COLS_IN_ALPHA_INDEX = 5 IGNORE_PREFIX = #--------------------------------------------------------------------------- # Configuration options related to the HTML output #--------------------------------------------------------------------------- GENERATE_HTML = YES HTML_OUTPUT = html HTML_FILE_EXTENSION = .html HTML_HEADER = HTML_FOOTER = HTML_STYLESHEET = HTML_EXTRA_STYLESHEET = HTML_EXTRA_FILES = HTML_COLORSTYLE_HUE = 220 HTML_COLORSTYLE_SAT = 100 HTML_COLORSTYLE_GAMMA = 80 HTML_TIMESTAMP = NO HTML_DYNAMIC_SECTIONS = NO HTML_INDEX_NUM_ENTRIES = 100 GENERATE_DOCSET = NO DOCSET_FEEDNAME = "Doxygen generated docs" DOCSET_BUNDLE_ID = org.doxygen.Project DOCSET_PUBLISHER_ID = org.doxygen.Publisher DOCSET_PUBLISHER_NAME = Publisher GENERATE_HTMLHELP = NO CHM_FILE = HHC_LOCATION = GENERATE_CHI = NO CHM_INDEX_ENCODING = BINARY_TOC = NO TOC_EXPAND = NO GENERATE_QHP = NO QCH_FILE = QHP_NAMESPACE = org.doxygen.Project QHP_VIRTUAL_FOLDER = doc QHP_CUST_FILTER_NAME = QHP_CUST_FILTER_ATTRS = QHP_SECT_FILTER_ATTRS = QHG_LOCATION = GENERATE_ECLIPSEHELP = NO ECLIPSE_DOC_ID = org.doxygen.Project DISABLE_INDEX = NO GENERATE_TREEVIEW = YES ENUM_VALUES_PER_LINE = 4 TREEVIEW_WIDTH = 250 EXT_LINKS_IN_WINDOW = NO FORMULA_FONTSIZE = 10 FORMULA_TRANSPARENT = YES USE_MATHJAX = NO MATHJAX_FORMAT = HTML-CSS MATHJAX_RELPATH = http://cdn.mathjax.org/mathjax/latest MATHJAX_EXTENSIONS = MATHJAX_CODEFILE = SEARCHENGINE = YES SERVER_BASED_SEARCH = NO EXTERNAL_SEARCH = NO SEARCHENGINE_URL = SEARCHDATA_FILE = searchdata.xml EXTERNAL_SEARCH_ID = EXTRA_SEARCH_MAPPINGS = #--------------------------------------------------------------------------- # Configuration options related to the LaTeX output #--------------------------------------------------------------------------- GENERATE_LATEX = NO LATEX_OUTPUT = latex LATEX_CMD_NAME = latex MAKEINDEX_CMD_NAME = makeindex COMPACT_LATEX = NO PAPER_TYPE = a4 EXTRA_PACKAGES = LATEX_HEADER = LATEX_FOOTER = LATEX_EXTRA_FILES = PDF_HYPERLINKS = YES USE_PDFLATEX = YES LATEX_BATCHMODE = NO LATEX_HIDE_INDICES = NO LATEX_SOURCE_CODE = NO LATEX_BIB_STYLE = plain #--------------------------------------------------------------------------- # Configuration options related to the RTF output #--------------------------------------------------------------------------- GENERATE_RTF = NO RTF_OUTPUT = rtf COMPACT_RTF = NO RTF_HYPERLINKS = NO RTF_STYLESHEET_FILE = RTF_EXTENSIONS_FILE = #--------------------------------------------------------------------------- # Configuration options related to the man page output #--------------------------------------------------------------------------- GENERATE_MAN = NO MAN_OUTPUT = man MAN_EXTENSION = .3 MAN_LINKS = NO #--------------------------------------------------------------------------- # Configuration options related to the XML output #--------------------------------------------------------------------------- GENERATE_XML = NO XML_OUTPUT = xml XML_SCHEMA = XML_DTD = XML_PROGRAMLISTING = YES #--------------------------------------------------------------------------- # Configuration options related to the DOCBOOK output #--------------------------------------------------------------------------- GENERATE_DOCBOOK = NO DOCBOOK_OUTPUT = docbook #--------------------------------------------------------------------------- # Configuration options for the AutoGen Definitions output #--------------------------------------------------------------------------- GENERATE_AUTOGEN_DEF = NO #--------------------------------------------------------------------------- # Configuration options related to the Perl module output #--------------------------------------------------------------------------- GENERATE_PERLMOD = NO PERLMOD_LATEX = NO PERLMOD_PRETTY = YES PERLMOD_MAKEVAR_PREFIX = #--------------------------------------------------------------------------- # Configuration options related to the preprocessor #--------------------------------------------------------------------------- ENABLE_PREPROCESSING = YES MACRO_EXPANSION = YES EXPAND_ONLY_PREDEF = NO SEARCH_INCLUDES = YES INCLUDE_PATH = INCLUDE_FILE_PATTERNS = PREDEFINED = DOXYGEN TSRMLS_C= TSRMLS_D= TSRMLS_CC= TSRMLS_DC= PHP_PROPRO_API= EXPAND_AS_DEFINED = SKIP_FUNCTION_MACROS = YES #--------------------------------------------------------------------------- # Configuration options related to external references #--------------------------------------------------------------------------- TAGFILES = GENERATE_TAGFILE = ALLEXTERNALS = NO EXTERNAL_GROUPS = YES EXTERNAL_PAGES = YES PERL_PATH = /usr/bin/perl #--------------------------------------------------------------------------- # Configuration options related to the dot tool #--------------------------------------------------------------------------- CLASS_DIAGRAMS = YES MSCGEN_PATH = HIDE_UNDOC_RELATIONS = YES HAVE_DOT = YES DOT_NUM_THREADS = 0 DOT_FONTNAME = Helvetica DOT_FONTSIZE = 10 DOT_FONTPATH = CLASS_GRAPH = NO COLLABORATION_GRAPH = YES GROUP_GRAPHS = YES UML_LOOK = NO UML_LIMIT_NUM_FIELDS = 10 TEMPLATE_RELATIONS = NO INCLUDE_GRAPH = YES INCLUDED_BY_GRAPH = YES CALL_GRAPH = YES CALLER_GRAPH = YES GRAPHICAL_HIERARCHY = YES DIRECTORY_GRAPH = YES DOT_IMAGE_FORMAT = png INTERACTIVE_SVG = NO DOT_PATH = DOTFILE_DIRS = MSCFILE_DIRS = DOT_GRAPH_MAX_NODES = 50 MAX_DOT_GRAPH_DEPTH = 0 DOT_TRANSPARENT = NO DOT_MULTI_TARGETS = NO GENERATE_LEGEND = YES DOT_CLEANUP = YES Copyright (c) 2013, Michael Wallner . All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # pecl/propro ## About: The "Property Proxy" extension provides a fairly transparent proxy for internal object properties hidden in custom non-zval implementations. ## Installation: This extension is hosted at [PECL](http://pecl.php.net) and can be installed with [PEAR](http://pear.php.net)'s pecl command: # pecl install propro Also, watch out for self-installing [pharext](https://github.com/m6w6/pharext) packages attached to [releases](https://github.com/m6w6/ext-propro/releases). ## Internals: > ***NOTE:*** This extension mostly only provides infrastructure for other extensions. See the [API docs here](http://m6w6.github.io/ext-propro/). ## Documentation Userland documentation can be found at https://mdref.m6w6.name/propro PHP_ARG_ENABLE(propro, whether to enable property proxy support, [ --enable-propro Enable property proxy support]) if test "$PHP_PROPRO" != "no"; then PHP_INSTALL_HEADERS(ext/propro, php_propro.h php_propro_api.h) PHP_NEW_EXTENSION(propro, php_propro.c, $ext_shared) fi ARG_ENABLE("propro", "for propro support", "no"); if (PHP_PROPRO == "yes") { EXTENSION("propro", "php_propro.c"); AC_DEFINE("HAVE_PROPRO", 1); PHP_INSTALL_HEADERS("ext/propro", "php_propro.h"); } propro pecl.php.net Property proxy A reusable split-off of pecl_http's property proxy API. Michael Wallner mike mike@php.net yes 2013-12-05 2.0.0-dev 2.0.0 stable stable BSD, revised 7.0.0 1.4.0 propro /* +--------------------------------------------------------------------+ | PECL :: propro | +--------------------------------------------------------------------+ | Redistribution and use in source and binary forms, with or without | | modification, are permitted provided that the conditions mentioned | | in the accompanying LICENSE file are met. | +--------------------------------------------------------------------+ | Copyright (c) 2013 Michael Wallner | +--------------------------------------------------------------------+ */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #include #include #include "php_propro_api.h" #define DEBUG_PROPRO 0 static inline zval *get_referenced_zval(zval *ref) { while (Z_ISREF_P(ref)) { ref = Z_REFVAL_P(ref); } return ref; } php_property_proxy_t *php_property_proxy_init(zval *container, zend_string *member) { php_property_proxy_t *proxy = ecalloc(1, sizeof(*proxy)); ZVAL_COPY(&proxy->container, get_referenced_zval(container)); proxy->member = zend_string_copy(member); return proxy; } void php_property_proxy_free(php_property_proxy_t **proxy) { if (*proxy) { zval_ptr_dtor(&(*proxy)->container); zend_string_release((*proxy)->member); efree(*proxy); *proxy = NULL; } } static zend_class_entry *php_property_proxy_class_entry; static zend_object_handlers php_property_proxy_object_handlers; zend_class_entry *php_property_proxy_get_class_entry(void) { return php_property_proxy_class_entry; } static inline php_property_proxy_object_t *get_propro(zval *object); static zval *get_parent_proxied_value(zval *object, zval *return_value); static zval *get_proxied_value(zval *object, zval *return_value); static zval *read_dimension(zval *object, zval *offset, int type, zval *return_value); static ZEND_RESULT_CODE cast_proxied_value(zval *object, zval *return_value, int type); static void write_dimension(zval *object, zval *offset, zval *value); static void set_proxied_value(zval *object, zval *value); #if DEBUG_PROPRO /* we do not really care about TS when debugging */ static int level = 1; static const char space[] = " "; static const char *inoutstr[] = {"< return","="," > enter"}; static void _walk(php_property_proxy_object_t *obj) { if (obj) { if (!Z_ISUNDEF(obj->parent)) { _walk(get_propro(&obj->parent)); } if (obj->proxy) { fprintf(stderr, ".%s", obj->proxy->member->val); } } } static void debug_propro(int inout, const char *f, php_property_proxy_object_t *obj, zval *offset, zval *value TSRMLS_DC) { fprintf(stderr, "#PP %p %s %s %s ", obj, &space[sizeof(space)-level], inoutstr[inout+1], f); level += inout; _walk(obj); if (*f++=='d' && *f++=='i' && *f++=='m' ) { char *offset_str = "[]"; zval *o = offset; if (o) { convert_to_string_ex(o); offset_str = Z_STRVAL_P(o); } fprintf(stderr, ".%s", offset_str); if (o && o != offset) { zval_ptr_dtor(o); } } if (value && !Z_ISUNDEF_P(value)) { const char *t[] = { "UNDEF", "NULL", "FALSE", "TRUE", "int", "float", "string", "Array", "Object", "resource", "reference", "constant", "constant AST", "_BOOL", "callable", "indirect", "---", "pointer" }; fprintf(stderr, " = (%s) ", t[Z_TYPE_P(value)&0xf]); if (!Z_ISUNDEF_P(value) && Z_TYPE_P(value) != IS_INDIRECT) { zend_print_flat_zval_r(value TSRMLS_CC); } } fprintf(stderr, "\n"); } #else #define debug_propro(l, f, obj, off, val) #endif php_property_proxy_object_t *php_property_proxy_object_new_ex( zend_class_entry *ce, php_property_proxy_t *proxy) { php_property_proxy_object_t *o; if (!ce) { ce = php_property_proxy_class_entry; } o = ecalloc(1, sizeof(*o) + sizeof(zval) * (ce->default_properties_count - 1)); zend_object_std_init(&o->zo, ce); object_properties_init(&o->zo, ce); o->proxy = proxy; o->zo.handlers = &php_property_proxy_object_handlers; debug_propro(0, "init", o, NULL, NULL); return o; } zend_object *php_property_proxy_object_new(zend_class_entry *ce) { return &php_property_proxy_object_new_ex(ce, NULL)->zo; } static void destroy_obj(zend_object *object) { php_property_proxy_object_t *o = PHP_PROPRO_PTR(object); debug_propro(0, "dtor", o, NULL, NULL); if (o->proxy) { php_property_proxy_free(&o->proxy); } if (!Z_ISUNDEF(o->parent)) { zval_ptr_dtor(&o->parent); ZVAL_UNDEF(&o->parent); } zend_object_std_dtor(object); } static inline php_property_proxy_object_t *get_propro(zval *object) { object = get_referenced_zval(object); switch (Z_TYPE_P(object)) { case IS_OBJECT: break; EMPTY_SWITCH_DEFAULT_CASE(); } return PHP_PROPRO_PTR(Z_OBJ_P(object)); } static inline zend_bool got_value(zval *container, zval *value) { zval identical; if (!Z_ISUNDEF_P(value)) { if (SUCCESS == is_identical_function(&identical, value, container)) { if (Z_TYPE(identical) != IS_TRUE) { return 1; } } } return 0; } static zval *get_parent_proxied_value(zval *object, zval *return_value) { php_property_proxy_object_t *obj; obj = get_propro(object); debug_propro(1, "parent_get", obj, NULL, NULL); if (obj->proxy) { if (!Z_ISUNDEF(obj->parent)) { get_proxied_value(&obj->parent, return_value); } } debug_propro(-1, "parent_get", obj, NULL, return_value); return return_value; } static zval *get_proxied_value(zval *object, zval *return_value) { zval *hash_value, *ref, prop_tmp; php_property_proxy_object_t *obj; obj = get_propro(object); debug_propro(1, "get", obj, NULL, NULL); if (obj->proxy) { if (!Z_ISUNDEF(obj->parent)) { zval parent_value; ZVAL_UNDEF(&parent_value); get_parent_proxied_value(object, &parent_value); if (got_value(&obj->proxy->container, &parent_value)) { zval_ptr_dtor(&obj->proxy->container); ZVAL_COPY(&obj->proxy->container, &parent_value); } } ref = get_referenced_zval(&obj->proxy->container); switch (Z_TYPE_P(ref)) { case IS_OBJECT: RETVAL_ZVAL(zend_read_property(Z_OBJCE_P(ref), ref, obj->proxy->member->val, obj->proxy->member->len, 0, &prop_tmp), 0, 0); break; case IS_ARRAY: hash_value = zend_symtable_find(Z_ARRVAL_P(ref), obj->proxy->member); if (hash_value) { RETVAL_ZVAL(hash_value, 0, 0); } break; } } debug_propro(-1, "get", obj, NULL, return_value); return return_value; } static ZEND_RESULT_CODE cast_proxied_value(zval *object, zval *return_value, int type) { get_proxied_value(object, return_value); debug_propro(0, "cast", get_propro(object), NULL, return_value); if (!Z_ISUNDEF_P(return_value)) { convert_to_explicit_type_ex(return_value, type); return SUCCESS; } return FAILURE; } static void set_proxied_value(zval *object, zval *value) { php_property_proxy_object_t *obj; zval *ref; obj = get_propro(object); debug_propro(1, "set", obj, NULL, value TSRMLS_CC); if (obj->proxy) { if (!Z_ISUNDEF(obj->parent)) { zval parent_value; ZVAL_UNDEF(&parent_value); get_parent_proxied_value(object, &parent_value); if (got_value(&obj->proxy->container, &parent_value)) { zval_ptr_dtor(&obj->proxy->container); ZVAL_COPY(&obj->proxy->container, &parent_value); } } ref = get_referenced_zval(&obj->proxy->container); switch (Z_TYPE_P(ref)) { case IS_OBJECT: zend_update_property(Z_OBJCE_P(ref), ref, obj->proxy->member->val, obj->proxy->member->len, value); break; default: convert_to_array(ref); /* no break */ case IS_ARRAY: Z_TRY_ADDREF_P(value); zend_symtable_update(Z_ARRVAL_P(ref), obj->proxy->member, value); break; } if (!Z_ISUNDEF(obj->parent)) { set_proxied_value(&obj->parent, &obj->proxy->container); } } debug_propro(-1, "set", obj, NULL, NULL); } static zval *read_dimension(zval *object, zval *offset, int type, zval *return_value) { zval proxied_value; zend_string *member = offset ? zval_get_string(offset) : NULL; debug_propro(1, type == BP_VAR_R ? "dim_read" : "dim_read_ref", get_propro(object), offset, NULL); ZVAL_UNDEF(&proxied_value); get_proxied_value(object, &proxied_value); if (BP_VAR_R == type && member && !Z_ISUNDEF(proxied_value)) { if (Z_TYPE(proxied_value) == IS_ARRAY) { zval *hash_value = zend_symtable_find(Z_ARRVAL(proxied_value), member); if (hash_value) { RETVAL_ZVAL(hash_value, 1, 0); } } } else { php_property_proxy_t *proxy; php_property_proxy_object_t *proxy_obj; if (!Z_ISUNDEF(proxied_value)) { convert_to_array(&proxied_value); Z_ADDREF(proxied_value); } else { array_init(&proxied_value); set_proxied_value(object, &proxied_value); } if (!member) { member = zend_long_to_str(zend_hash_next_free_element( Z_ARRVAL(proxied_value))); } proxy = php_property_proxy_init(&proxied_value, member); zval_ptr_dtor(&proxied_value); proxy_obj = php_property_proxy_object_new_ex(NULL, proxy); ZVAL_COPY(&proxy_obj->parent, object); RETVAL_OBJ(&proxy_obj->zo); } if (member) { zend_string_release(member); } debug_propro(-1, type == BP_VAR_R ? "dim_read" : "dim_read_ref", get_propro(object), offset, return_value); return return_value; } static int has_dimension(zval *object, zval *offset, int check_empty) { zval proxied_value; int exists = 0; debug_propro(1, "dim_exists", get_propro(object), offset, NULL); ZVAL_UNDEF(&proxied_value); get_proxied_value(object, &proxied_value); if (Z_ISUNDEF(proxied_value)) { exists = 0; } else { zend_string *zs = zval_get_string(offset); if (Z_TYPE(proxied_value) == IS_ARRAY) { zval *zentry = zend_symtable_find(Z_ARRVAL(proxied_value), zs); if (!zentry) { exists = 0; } else { if (check_empty) { exists = !Z_ISNULL_P(zentry); } else { exists = 1; } } } zend_string_release(zs); } debug_propro(-1, "dim_exists", get_propro(object), offset, NULL); return exists; } static void write_dimension(zval *object, zval *offset, zval *value) { zval proxied_value; debug_propro(1, "dim_write", get_propro(object), offset, value); ZVAL_UNDEF(&proxied_value); get_proxied_value(object, &proxied_value); if (!Z_ISUNDEF(proxied_value)) { if (Z_TYPE(proxied_value) == IS_ARRAY) { Z_ADDREF(proxied_value); } else { convert_to_array(&proxied_value); } } else { array_init(&proxied_value); } SEPARATE_ZVAL(value); Z_TRY_ADDREF_P(value); if (offset) { zend_string *zs = zval_get_string(offset); zend_symtable_update(Z_ARRVAL(proxied_value), zs, value); zend_string_release(zs); } else { zend_hash_next_index_insert(Z_ARRVAL(proxied_value), value); } set_proxied_value(object, &proxied_value); debug_propro(-1, "dim_write", get_propro(object), offset, &proxied_value); zval_ptr_dtor(&proxied_value); } static void unset_dimension(zval *object, zval *offset) { zval proxied_value; debug_propro(1, "dim_unset", get_propro(object), offset, NULL); ZVAL_UNDEF(&proxied_value); get_proxied_value(object, &proxied_value); if (Z_TYPE(proxied_value) == IS_ARRAY) { zval *o = offset; ZEND_RESULT_CODE rv; convert_to_string_ex(o); rv = zend_symtable_del(Z_ARRVAL(proxied_value), Z_STR_P(o)); if (SUCCESS == rv) { set_proxied_value(object, &proxied_value); } if (o != offset) { zval_ptr_dtor(o); } } debug_propro(-1, "dim_unset", get_propro(object), offset, &proxied_value); } ZEND_BEGIN_ARG_INFO_EX(ai_propro_construct, 0, 0, 2) ZEND_ARG_INFO(1, object) ZEND_ARG_INFO(0, member) ZEND_ARG_OBJ_INFO(0, parent, php\\PropertyProxy, 1) ZEND_END_ARG_INFO(); static PHP_METHOD(propro, __construct) { zend_error_handling zeh; zval *container, *parent = NULL; zend_string *member; zend_replace_error_handling(EH_THROW, NULL, &zeh); if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS(), "zS|O!", &container, &member, &parent, php_property_proxy_class_entry)) { php_property_proxy_object_t *obj; zval *ref = get_referenced_zval(container); switch (Z_TYPE_P(ref)) { case IS_OBJECT: case IS_ARRAY: break; default: convert_to_array(ref); } obj = get_propro(getThis()); obj->proxy = php_property_proxy_init(container, member); if (parent) { ZVAL_COPY(&obj->parent, parent); } } zend_restore_error_handling(&zeh); } static const zend_function_entry php_property_proxy_method_entry[] = { PHP_ME(propro, __construct, ai_propro_construct, ZEND_ACC_PUBLIC) {0} }; static PHP_MINIT_FUNCTION(propro) { zend_class_entry ce = {0}; INIT_NS_CLASS_ENTRY(ce, "php", "PropertyProxy", php_property_proxy_method_entry); php_property_proxy_class_entry = zend_register_internal_class(&ce); php_property_proxy_class_entry->create_object = php_property_proxy_object_new; php_property_proxy_class_entry->ce_flags |= ZEND_ACC_FINAL; memcpy(&php_property_proxy_object_handlers, zend_get_std_object_handlers(), sizeof(zend_object_handlers)); php_property_proxy_object_handlers.offset = XtOffsetOf(php_property_proxy_object_t, zo); php_property_proxy_object_handlers.free_obj = destroy_obj; php_property_proxy_object_handlers.set = set_proxied_value; php_property_proxy_object_handlers.get = get_proxied_value; php_property_proxy_object_handlers.cast_object = cast_proxied_value; php_property_proxy_object_handlers.read_dimension = read_dimension; php_property_proxy_object_handlers.write_dimension = write_dimension; php_property_proxy_object_handlers.has_dimension = has_dimension; php_property_proxy_object_handlers.unset_dimension = unset_dimension; return SUCCESS; } PHP_MINFO_FUNCTION(propro) { php_info_print_table_start(); php_info_print_table_header(2, "Property proxy support", "enabled"); php_info_print_table_row(2, "Extension version", PHP_PROPRO_VERSION); php_info_print_table_end(); } static const zend_function_entry propro_functions[] = { {0} }; zend_module_entry propro_module_entry = { STANDARD_MODULE_HEADER, "propro", propro_functions, PHP_MINIT(propro), NULL, NULL, NULL, PHP_MINFO(propro), PHP_PROPRO_VERSION, STANDARD_MODULE_PROPERTIES }; #ifdef COMPILE_DL_PROPRO ZEND_GET_MODULE(propro) #endif /* * Local variables: * tab-width: 4 * c-basic-offset: 4 * End: * vim600: noet sw=4 ts=4 fdm=marker * vim<600: noet sw=4 ts=4 */ /* +--------------------------------------------------------------------+ | PECL :: propro | +--------------------------------------------------------------------+ | Redistribution and use in source and binary forms, with or without | | modification, are permitted provided that the conditions mentioned | | in the accompanying LICENSE file are met. | +--------------------------------------------------------------------+ | Copyright (c) 2013 Michael Wallner | +--------------------------------------------------------------------+ */ #ifndef PHP_PROPRO_H #define PHP_PROPRO_H extern zend_module_entry propro_module_entry; #define phpext_propro_ptr &propro_module_entry #define PHP_PROPRO_VERSION "2.0.0dev" #ifdef PHP_WIN32 # define PHP_PROPRO_API __declspec(dllexport) #elif defined(__GNUC__) && __GNUC__ >= 4 # define PHP_PROPRO_API extern __attribute__ ((visibility("default"))) #else # define PHP_PROPRO_API extern #endif #ifdef ZTS # include #endif #define PHP_PROPRO_PTR(zo) (void*)(((char*)(zo))-(zo)->handlers->offset) #endif /* PHP_PROPRO_H */ /* * Local variables: * tab-width: 4 * c-basic-offset: 4 * End: * vim600: noet sw=4 ts=4 fdm=marker * vim<600: noet sw=4 ts=4 */ /* +--------------------------------------------------------------------+ | PECL :: propro | +--------------------------------------------------------------------+ | Redistribution and use in source and binary forms, with or without | | modification, are permitted provided that the conditions mentioned | | in the accompanying LICENSE file are met. | +--------------------------------------------------------------------+ | Copyright (c) 2013 Michael Wallner | +--------------------------------------------------------------------+ */ #ifndef PHP_PROPRO_API_H #define PHP_PROPRO_API_H #include "php_propro.h" /** * The internal property proxy. * * Container for the object/array holding the proxied property. */ struct php_property_proxy { /** The container holding the property */ zval container; /** The name of the proxied property */ zend_string *member; }; typedef struct php_property_proxy php_property_proxy_t; /** * The userland object. * * Return an object instance of php\\PropertyProxy to make your C-struct * member accessible by reference from PHP userland. * * Example: * ~~~~~~~~~~{.c} * static zval *my_read_prop(zval *object, zval *member, int type, void **cache_slot, zval *tmp) * { * zval *return_value; * zend_string *member_name = zval_get_string(member); * my_prophandler_t *handler = my_get_prophandler(member_name); * * if (!handler || type == BP_VAR_R || type == BP_VAR_IS) { * return_value = zend_get_std_object_handlers()->read_property(object, member, type, cache_slot, tmp); * * if (handler) { * handler->read(object, tmp); * * zval_ptr_dtor(return_value); * ZVAL_COPY_VALUE(return_value, tmp); * } * } else { * return_value = php_property_proxy_zval(object, member_name); * } * * zend_string_release(member_name); * * return return_value; * } * ~~~~~~~~~~ */ struct php_property_proxy_object { /** The actual property proxy */ php_property_proxy_t *proxy; /** Any parent property proxy object */ zval parent; /** The std zend_object */ zend_object zo; }; typedef struct php_property_proxy_object php_property_proxy_object_t; /** * Create a property proxy * * The property proxy will forward reads and writes to itself to the * proxied property with name \a member_str of \a container. * * @param container the container holding the property * @param member the name of the proxied property * @return a new property proxy */ PHP_PROPRO_API php_property_proxy_t *php_property_proxy_init(zval *container, zend_string *member); /** * Destroy and free a property proxy. * * The destruction of the property proxy object calls this. * * @param proxy a pointer to the allocated property proxy */ PHP_PROPRO_API void php_property_proxy_free(php_property_proxy_t **proxy); /** * Get the zend_class_entry of php\\PropertyProxy * @return the class entry pointer */ PHP_PROPRO_API zend_class_entry *php_property_proxy_get_class_entry(void); /** * Instantiate a new php\\PropertyProxy * @param ce the property proxy or derived class entry * @return the zend object */ PHP_PROPRO_API zend_object *php_property_proxy_object_new(zend_class_entry *ce); /** * Instantiate a new php\\PropertyProxy with \a proxy * @param ce the property proxy or derived class entry * @param proxy the internal property proxy * @return the property proxy */ PHP_PROPRO_API php_property_proxy_object_t *php_property_proxy_object_new_ex( zend_class_entry *ce, php_property_proxy_t *proxy); #endif /* PHP_PROPRO_API_H */ /* * Local variables: * tab-width: 4 * c-basic-offset: 4 * End: * vim600: noet sw=4 ts=4 fdm=marker * vim<600: noet sw=4 ts=4 */ --TEST-- property proxy --SKIPIF-- --FILE-- prop; $a = $c->anon; var_dump($c); echo "set\n"; $a = 123; echo "get\n"; echo $a,"\n"; $p["foo"] = 123; $p["bar"]["baz"]["a"]["b"]=987; var_dump($c); ?> DONE --EXPECTF-- Test object(c)#%d (2) { ["prop":"c":private]=> NULL ["anon":"c":private]=> NULL } set get 123 object(c)#%d (2) { ["prop":"c":private]=> array(2) { ["foo"]=> int(123) ["bar"]=> array(1) { ["baz"]=> array(1) { ["a"]=> array(1) { ["b"]=> int(987) } } } } ["anon":"c":private]=> int(123) } DONE --TEST-- property proxy --SKIPIF-- --FILE-- storage, $p); } function __set($p, $v) { $this->storage[$p] = $v; } } $c = new c; $c->data["foo"] = 1; var_dump( isset($c->data["foo"]), isset($c->data["bar"]) ); var_dump($c); $c->data[] = 1; $c->data[] = 2; $c->data[] = 3; $c->data["bar"][] = 123; $c->data["bar"][] = 456; var_dump($c); unset($c->data["bar"][0]); var_dump($c); ?> DONE --EXPECTF-- Test bool(true) bool(false) object(c)#%d (1) { ["storage":"c":private]=> array(1) { ["data"]=> array(1) { ["foo"]=> int(1) } } } object(c)#%d (1) { ["storage":"c":private]=> array(1) { ["data"]=> array(5) { ["foo"]=> int(1) [0]=> int(1) [1]=> int(2) [2]=> int(3) ["bar"]=> array(2) { [0]=> int(123) [1]=> int(456) } } } } object(c)#%d (1) { ["storage":"c":private]=> array(1) { ["data"]=> array(5) { ["foo"]=> int(1) [0]=> int(1) [1]=> int(2) [2]=> int(3) ["bar"]=> array(1) { [1]=> int(456) } } } } DONE --TEST-- property proxy --SKIPIF-- --FILE-- ref; $r = 1; var_dump($t); $t->ref[] = 2; var_dump($t); ?> ===DONE=== --EXPECTF-- Test object(t)#%d (1) { ["ref":"t":private]=> int(1) } object(t)#%d (1) { ["ref":"t":private]=> array(2) { [0]=> int(1) [1]=> int(2) } } ===DONE===hz%ݠd R`ǕGBMB