#!/usr/bin/php -dphar.readonly=0 2, 'c' => 'text/plain', 'cc' => 'text/plain', 'cpp' => 'text/plain', 'c++' => 'text/plain', 'dtd' => 'text/plain', 'h' => 'text/plain', 'log' => 'text/plain', 'rng' => 'text/plain', 'txt' => 'text/plain', 'xsd' => 'text/plain', 'php' => 1, 'inc' => 1, 'avi' => 'video/avi', 'bmp' => 'image/bmp', 'css' => 'text/css', 'gif' => 'image/gif', 'htm' => 'text/html', 'html' => 'text/html', 'htmls' => 'text/html', 'ico' => 'image/x-ico', 'jpe' => 'image/jpeg', 'jpg' => 'image/jpeg', 'jpeg' => 'image/jpeg', 'js' => 'application/x-javascript', 'midi' => 'audio/midi', 'mid' => 'audio/midi', 'mod' => 'audio/mod', 'mov' => 'movie/quicktime', 'mp3' => 'audio/mp3', 'mpg' => 'video/mpeg', 'mpeg' => 'video/mpeg', 'pdf' => 'application/pdf', 'png' => 'image/png', 'swf' => 'application/shockwave-flash', 'tif' => 'image/tiff', 'tiff' => 'image/tiff', 'wav' => 'audio/wav', 'xbm' => 'image/xbm', 'xml' => 'text/xml', ); header("Cache-Control: no-cache, must-revalidate"); header("Pragma: no-cache"); $basename = basename(__FILE__); if (!strpos($_SERVER['REQUEST_URI'], $basename)) { chdir(Extract_Phar::$temp); include $web; return; } $pt = substr($_SERVER['REQUEST_URI'], strpos($_SERVER['REQUEST_URI'], $basename) + strlen($basename)); if (!$pt || $pt == '/') { $pt = $web; header('HTTP/1.1 301 Moved Permanently'); header('Location: ' . $_SERVER['REQUEST_URI'] . '/' . $pt); exit; } $a = realpath(Extract_Phar::$temp . DIRECTORY_SEPARATOR . $pt); if (!$a || strlen(dirname($a)) < strlen(Extract_Phar::$temp)) { header('HTTP/1.0 404 Not Found'); echo "\n \n File Not Found<title>\n </head>\n <body>\n <h1>404 - File ", $pt, " Not Found</h1>\n </body>\n</html>"; exit; } $b = pathinfo($a); if (!isset($b['extension'])) { header('Content-Type: text/plain'); header('Content-Length: ' . filesize($a)); readfile($a); exit; } if (isset($mimes[$b['extension']])) { if ($mimes[$b['extension']] === 1) { include $a; exit; } if ($mimes[$b['extension']] === 2) { highlight_file($a); exit; } header('Content-Type: ' .$mimes[$b['extension']]); header('Content-Length: ' . filesize($a)); readfile($a); exit; } } class Extract_Phar { static $temp; static $origdir; const GZ = 0x1000; const BZ2 = 0x2000; const MASK = 0x3000; const START = 'pharext_packager.php'; const LEN = 6696; static function go($return = false) { $fp = fopen(__FILE__, 'rb'); fseek($fp, self::LEN); $L = unpack('V', $a = (binary)fread($fp, 4)); $m = (binary)''; do { $read = 8192; if ($L[1] - strlen($m) < 8192) { $read = $L[1] - strlen($m); } $last = (binary)fread($fp, $read); $m .= $last; } while (strlen($last) && strlen($m) < $L[1]); if (strlen($m) < $L[1]) { die('ERROR: manifest length read was "' . strlen($m) .'" should be "' . $L[1] . '"'); } $info = self::_unpack($m); $f = $info['c']; if ($f & self::GZ) { if (!function_exists('gzinflate')) { die('Error: zlib extension is not enabled -' . ' gzinflate() function needed for zlib-compressed .phars'); } } if ($f & self::BZ2) { if (!function_exists('bzdecompress')) { die('Error: bzip2 extension is not enabled -' . ' bzdecompress() function needed for bz2-compressed .phars'); } } $temp = self::tmpdir(); if (!$temp || !is_writable($temp)) { $sessionpath = session_save_path(); if (strpos ($sessionpath, ";") !== false) $sessionpath = substr ($sessionpath, strpos ($sessionpath, ";")+1); if (!file_exists($sessionpath) || !is_dir($sessionpath)) { die('Could not locate temporary directory to extract phar'); } $temp = $sessionpath; } $temp .= '/pharextract/'.basename(__FILE__, '.phar'); self::$temp = $temp; self::$origdir = getcwd(); @mkdir($temp, 0777, true); $temp = realpath($temp); if (!file_exists($temp . DIRECTORY_SEPARATOR . md5_file(__FILE__))) { self::_removeTmpFiles($temp, getcwd()); @mkdir($temp, 0777, true); @file_put_contents($temp . '/' . md5_file(__FILE__), ''); foreach ($info['m'] as $path => $file) { $a = !file_exists(dirname($temp . '/' . $path)); @mkdir(dirname($temp . '/' . $path), 0777, true); clearstatcache(); if ($path[strlen($path) - 1] == '/') { @mkdir($temp . '/' . $path, 0777); } else { file_put_contents($temp . '/' . $path, self::extractFile($path, $file, $fp)); @chmod($temp . '/' . $path, 0666); } } } chdir($temp); if (!$return) { include self::START; } } static function tmpdir() { if (strpos(PHP_OS, 'WIN') !== false) { if ($var = getenv('TMP') ? getenv('TMP') : getenv('TEMP')) { return $var; } if (is_dir('/temp') || mkdir('/temp')) { return realpath('/temp'); } return false; } if ($var = getenv('TMPDIR')) { return $var; } return realpath('/tmp'); } static function _unpack($m) { $info = unpack('V', substr($m, 0, 4)); $l = unpack('V', substr($m, 10, 4)); $m = substr($m, 14 + $l[1]); $s = unpack('V', substr($m, 0, 4)); $o = 0; $start = 4 + $s[1]; $ret['c'] = 0; for ($i = 0; $i < $info[1]; $i++) { $len = unpack('V', substr($m, $start, 4)); $start += 4; $savepath = substr($m, $start, $len[1]); $start += $len[1]; $ret['m'][$savepath] = array_values(unpack('Va/Vb/Vc/Vd/Ve/Vf', substr($m, $start, 24))); $ret['m'][$savepath][3] = sprintf('%u', $ret['m'][$savepath][3] & 0xffffffff); $ret['m'][$savepath][7] = $o; $o += $ret['m'][$savepath][2]; $start += 24 + $ret['m'][$savepath][5]; $ret['c'] |= $ret['m'][$savepath][4] & self::MASK; } return $ret; } static function extractFile($path, $entry, $fp) { $data = ''; $c = $entry[2]; while ($c) { if ($c < 8192) { $data .= @fread($fp, $c); $c = 0; } else { $c -= 8192; $data .= @fread($fp, 8192); } } if ($entry[4] & self::GZ) { $data = gzinflate($data); } elseif ($entry[4] & self::BZ2) { $data = bzdecompress($data); } if (strlen($data) != $entry[0]) { die("Invalid internal .phar file (size error " . strlen($data) . " != " . $stat[7] . ")"); } if ($entry[3] != sprintf("%u", crc32((binary)$data) & 0xffffffff)) { die("Invalid internal .phar file (checksum error)"); } return $data; } static function _removeTmpFiles($temp, $origdir) { chdir($temp); foreach (glob('*') as $f) { if (file_exists($f)) { is_dir($f) ? @rmdir($f) : @unlink($f); if (file_exists($f) && is_dir($f)) { self::_removeTmpFiles($f, getcwd()); } } } @rmdir($temp); clearstatcache(); chdir($origdir); } } Extract_Phar::go(); __HALT_COMPILER(); ?> q��������� ���pharext.phar�������pharext/Cli/Args.php,��ÿwU,��˯a϶���������pharext/Cli/Command.phpS ��ÿwUS ��ÙAÆv¶���������pharext/Command.php¯��ÿwU¯��JÊŶ���������pharext/ExecCmd.php ��ÿwU ��-ð*L¶���������pharext/Installer.phpÊ��ÿwUÊ��ƒs_¶���������pharext/Openssl/PrivateKey.php¸��ÿwU¸��-v™¶���������pharext/Packager.php›%��ÿwU›%��þŠ–¶���������pharext/SourceDir/Git.php¸��ÿwU¸��šß;à¶���������pharext/SourceDir/Pecl.phpÖ��ÿwUÖ��W/ƒ4¶���������pharext/SourceDir/Pharext.phpu��ÿwUu��úâ<ܶ���������pharext/SourceDir.phpù��ÿwUù��¶Íßø¶���������pharext/Tempdir.phpu��ÿwUu��rÎì¶���������pharext/Tempfile.phpÐ��ÿwUÐ��¸’%¶���������pharext/Version.php@���ÿwU@���ÆC뵶���������pharext_install.tpl.php��ÿwU��ûïL†¶���������pharext_installer.phpÝ���ÿwUÝ���‹pDZ¶���������pharext_packager.phpÔ���ÿwUÔ���Ñ1÷¶������<?php namespace pharext\Cli; /** * Command line arguments */ class Args implements \ArrayAccess { /** * Optional option */ const OPTIONAL = 0x000; /** * Required Option */ const REQUIRED = 0x001; /** * Only one value, even when used multiple times */ const SINGLE = 0x000; /** * Aggregate an array, when used multiple times */ const MULTI = 0x010; /** * Option takes no argument */ const NOARG = 0x000; /** * Option requires an argument */ const REQARG = 0x100; /** * Option takes an optional argument */ const OPTARG = 0x200; /** * Option halts processing */ const HALT = 0x10000000; /** * Original option spec * @var array */ private $orig = []; /** * Compiled spec * @var array */ private $spec = []; /** * Parsed args * @var array */ private $args = []; /** * Compile the original spec * @param array $spec */ public function __construct(array $spec = null) { $this->compile($spec); } /** * Compile the original spec * @param array $spec * @return pharext\CliArgs self */ public function compile(array $spec = null) { $this->orig = array_merge($this->orig, (array) $spec); foreach ((array) $spec as $arg) { if (isset($arg[0])) { $this->spec["-".$arg[0]] = $arg; } $this->spec["--".$arg[1]] = $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 CliArgs::HALT was encountered. * * @param int $argc * @param array $argv * @return Generator */ public function parse($argc, array $argv) { for ($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, "="))) { $argc++; array_splice($argv, $i, 1, [ substr($o, 0, $eq++), substr($o, $eq) ]); $o = $argv[$i]; } if (!isset($this->spec[$o])) { 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 (!isset($this[$req[0]])) { yield sprintf("Option --%s is required", $req[1]); } } } /** * 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 $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 $this->spec[$o][0]; } /** * Retreive the canonical name (--long-name) of an option * @param string $o * @return string */ private function opt($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); } /**@-*/ } <?php namespace pharext\Cli; use pharext\Cli\Args as CliArgs; require_once "pharext/Version.php"; trait Command { /** * Command line arguments * @var pharext\CliArgs */ private $args; /** * @inheritdoc * @see \pharext\Command::getArgs() */ public function getArgs() { return $this->args; } /** * Output pharext vX.Y.Z header */ function header() { printf("pharext v%s (c) Michael Wallner <mike@php.net>\n\n", \pharext\VERSION); } /** * @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::error() */ public function error($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, "ERROR: $fmt", $arg); } } /** * Output command line help message * @param string $prog */ public function help($prog) { printf("Usage:\n\n \$ %s", $prog); $flags = []; $required = []; $optional = []; foreach ($this->args->getSpec() as $spec) { if ($spec[3] & CliArgs::REQARG) { if ($spec[3] & CliArgs::REQUIRED) { $required[] = $spec; } else { $optional[] = $spec; } } else { $flags[] = $spec; } } if ($flags) { printf(" [-%s]", implode("", array_column($flags, 0))); } foreach ($required as $req) { printf(" -%s <arg>", $req[0]); } if ($optional) { printf(" [-%s <arg>]", implode("|-", array_column($optional, 0))); } printf("\n\n"); $spc = $this->args->getSpec(); $max = max(array_map("strlen", array_column($spc, 1))); $max += $max % 8 + 2; foreach ($spc as $spec) { if (isset($spec[0])) { printf(" -%s|", $spec[0]); } else { printf(" "); } printf("--%s ", $spec[1]); if ($spec[3] & CliArgs::REQARG) { printf("<arg> "); } elseif ($spec[3] & CliArgs::OPTARG) { printf("[<arg>]"); } else { printf(" "); } printf("%s%s", str_repeat(" ", $max-strlen($spec[1])+3*!isset($spec[0])), $spec[2]); if ($spec[3] & CliArgs::REQUIRED) { printf(" (REQUIRED)"); } if (isset($spec[4])) { printf(" [%s]", $spec[4]); } printf("\n"); } printf("\n"); } /** * rm -r * @param string $dir */ private function rm($dir) { foreach (scandir($dir) as $entry) { if ($entry === "." || $entry === "..") { continue; } elseif (is_dir("$dir/$entry")) { $this->rm("$dir/$entry"); } elseif (!unlink("$dir/$entry")) { $this->error(null); } } if (!rmdir($dir)) { $this->error(null); } } } <?php namespace pharext; /** * Command interface */ interface Command { /** * Retrieve command line arguments * @return pharext\CliArgs */ public function getArgs(); /** * Print debug message * @param string $fmt * @param string ...$args */ public function debug($fmt); /** * Print info * @param string $fmt * @param string ...$args */ public function info($fmt); /** * Print error * @param string $fmt * @param string ...$args */ public function error($fmt); /** * Execute the command * @param int $argc command line argument count * @param array $argv command line argument list */ public function run($argc, array $argv); } <?php namespace pharext; class ExecCmd { /** * Sudo command, if the cmd needs escalated privileges * @var string */ private $sudo; /** * Executable of the cmd * @var string */ private $command; /** * Passthrough cmd output * @var bool */ private $verbose; /** * Output of cmd run * @var string */ private $output; /** * Return code of cmd run * @var int */ private $status; /** * @param string $command * @param bool verbose */ public function __construct($command, $verbose = false) { $this->command = $command; $this->verbose = $verbose; /* interrupt output stream */ if ($verbose) { printf("\n"); } } /** * (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 string $output * @param int $status */ private function suExec($command, &$output, &$status) { if (!($proc = proc_open($command, [STDIN,["pipe","w"],["pipe","w"]], $pipes))) { $status = -1; throw new \Exception("Failed to run {$command}"); } $stdout = $pipes[1]; $passwd = 0; 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++ < 10) { if (stristr($data, "password")) { printf("\n%s", $data); } } $output .= $data; } $status = proc_close($proc); } /** * Run the command * @param array $args * @throws \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->output, $this->status); } elseif ($this->verbose) { ob_start(function($s) { $this->output .= $s; return $s; }, 1); passthru($exec, $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 {$this->command} failed ({$this->status})"); } } /** * Retrieve exit code of cmd run * @return int */ public function getStatus() { return $status; } /** * Retrieve output of cmd run * @return string */ public function getOutput() { return $this->output; } } <?php namespace pharext; use Phar; use pharext\Cli\Args as CliArgs; use pharext\Cli\Command as CliCommand; /** * The extension install command executed by the extension phar */ class Installer implements Command { use CliCommand; /** * The temporary directory we should operate in * @var string */ private $tmp; /** * The directory we came from * @var string */ private $cwd; /** * Create the command */ public function __construct() { $this->args = new CliArgs([ ["h", "help", "Display help", CliArgs::OPTIONAL|CliArgs::SINGLE|CliArgs::NOARG|CliArgs::HALT], ["v", "verbose", "More output", CliArgs::OPTIONAL|CliArgs::SINGLE|CliArgs::NOARG], ["q", "quiet", "Less output", CliArgs::OPTIONAL|CliArgs::SINGLE|CliArgs::NOARG], ["p", "prefix", "PHP installation prefix if phpize is not in \$PATH, e.g. /opt/php7", CliArgs::OPTIONAL|CliArgs::SINGLE|CliArgs::REQARG], ["n", "common-name", "PHP common program name, e.g. php5 or zts-php", CliArgs::OPTIONAL|CliArgs::SINGLE|CliArgs::REQARG, "php"], ["c", "configure", "Additional extension configure flags, e.g. -c --with-flag", CliArgs::OPTIONAL|CliArgs::MULTI|CliArgs::REQARG], ["s", "sudo", "Installation might need increased privileges", CliArgs::OPTIONAL|CliArgs::SINGLE|CliArgs::OPTARG, "sudo -S %s"], ["i", "ini", "Activate in this php.ini instead of loaded default php.ini", CliArgs::OPTIONAL|CliArgs::SINGLE|CliArgs::REQARG], ]); } /** * Cleanup temp directory */ public function __destruct() { $this->cleanup(); } /** * @inheritdoc * @see \pharext\Command::run() */ public function run($argc, array $argv) { $this->cwd = getcwd(); $this->tmp = $this->tempname(basename(Phar::running(false))); $phar = new Phar(Phar::running(false)); foreach ($phar as $entry) { if (fnmatch("*.ext.phar*", $entry->getBaseName())) { $temp = new Tempdir($entry->getBaseName()); $phar->extractTo($temp, $entry->getFilename(), true); $phars[$temp] = new Phar($temp."/".$entry->getFilename()); } } $phars[$this->tmp] = $phar; foreach ($phars as $phar) { if (isset($phar["pharext_install.php"])) { $callable = include $phar["pharext_install.php"]; if (is_callable($callable)) { $recv[] = $callable($this); } } } $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; } 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(1); } if (isset($recv)) { foreach ($recv as $r) { $r($this); } } foreach ($phars as $temp => $phar) { $this->installPackage($phar, $temp); } } /** * Prepares, configures, builds and installs the extension */ private function installPackage(Phar $phar, $temp) { $this->info("Installing %s ... \n", basename($phar->getAlias())); try { $phar->extractTo($temp, null, true); } catch (\Exception $e) { $this->error("%s\n", $e->getMessage()); exit(3); } if (!chdir($temp)) { $this->error(null); exit(4); } $this->build(); $this->activate(); $this->cleanup($temp); } /** * Phpize + trinity */ private function build() { try { // phpize $this->info("Runnin phpize ... "); $cmd = new ExecCmd($this->php("ize"), $this->args->verbose); $cmd->run(); $this->info("OK\n"); // configure $this->info("Running configure ... "); $args = ["--with-php-config=". $this->php("-config")]; if ($this->args->configure) { $args = array_merge($args, $this->args->configure); } $cmd = new ExecCmd("./configure", $this->args->verbose); $cmd->run($args); $this->info("OK\n"); // make $this->info("Running make ... "); $cmd = new ExecCmd("make", $this->args->verbose); if ($this->args->verbose) { $cmd->run(["-j3"]); } else { $cmd->run(["-j3", "-s"]); } $this->info("OK\n"); // install $this->info("Running make install ... "); if (isset($this->args->sudo)) { $cmd->setSu($this->args->sudo); } if ($this->args->verbose) { $cmd->run(["install"]); } else { $cmd->run(["install", "-s"]); } $this->info("OK\n"); } catch (\Exception $e) { $this->error("%s\n", $e->getMessage()); $this->error("%s\n", $cmd->getOutput()); } } /** * Perform any cleanups */ private function cleanup($temp = null) { if (!isset($temp)) { $temp = $this->tmp; } if (is_dir($temp)) { chdir($this->cwd); $this->info("Cleaning up %s ...\n", $temp); $this->rm($temp); } } /** * Construct a command from prefix common-name and suffix * @param type $suffix * @return string */ private function php($suffix) { $cmd = $this->args["common-name"] . $suffix; if (isset($this->args->prefix)) { $cmd = $this->args->prefix . "/bin/" . $cmd; } return $cmd; } /** * Activate extension in php.ini */ private function activate() { if ($this->args->ini) { $files = [realpath($this->args->ini)]; } else { $files = array_filter(array_map("trim", explode(",", php_ini_scanned_files()))); $files[] = php_ini_loaded_file(); } $extension = basename(current(glob("modules/*.so"))); $pattern = preg_quote($extension); foreach ($files as $index => $file) { $temp = new Tempfile("phpini"); foreach (file($file) as $line) { if (preg_match("/^\s*extension\s*=\s*[\"']?{$pattern}[\"']?\s*(;.*)?\$/", $line)) { // already there $this->info("Extension already activated\n"); return; } fwrite($temp->getStream(), $line); } } // not found, add extension line to the last process file if (isset($temp, $file)) { fprintf($temp->getStream(), "extension=%s\n", $extension); $temp->closeStream(); $path = $temp->getPathname(); $stat = stat($file); try { $this->info("Running INI owner transfer ... "); $ugid = sprintf("%d:%d", $stat["uid"], $stat["gid"]); $cmd = new ExecCmd("chown", $this->args->verbose); if (isset($this->args->sudo)) { $cmd->setSu($this->args->sudo); } $cmd->run([$ugid, $path]); $this->info("OK\n"); $this->info("Running INI permission transfer ... "); $perm = decoct($stat["mode"] & 0777); $cmd = new ExecCmd("chmod", $this->args->verbose); if (isset($this->args->sudo)) { $cmd->setSu($this->args->sudo); } $cmd->run([$perm, $path]); $this->info("OK\n"); $this->info("Running INI activation ... "); $cmd = new ExecCmd("mv", $this->args->verbose); if (isset($this->args->sudo)) { $cmd->setSu($this->args->sudo); } $cmd->run([$path, $file]); $this->info("OK\n"); } catch (\Exception $e) { $this->error("%s\n", $e->getMessage()); $this->error("%s\n", $cmd->getOutput()); exit(5); } } } } <?php namespace pharext\Openssl; class PrivateKey { /** * Private key * @var string */ private $key; /** * Public key * @var string */ private $pub; /** * Read a private key * @param string $file * @param string $password * @throws \Exception */ function __construct($file, $password) { /* there appears to be a bug with refcount handling of this * resource; when the resource is stored as property, it cannot be * "coerced to a private key" on openssl_sign() alter in another method */ $key = openssl_pkey_get_private("file://$file", $password); if (!is_resource($key)) { throw new \Exception("Could not load private key"); } openssl_pkey_export($key, $this->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 \Exception */ function exportPublicKey($file) { if (!file_put_contents("$file.tmp", $this->pub) || !rename("$file.tmp", $file)) { throw new \Exception(error_get_last()["message"]); } } } <?php namespace pharext; use Phar; use PharData; use pharext\Cli\Args as CliArgs; use pharext\Cli\Command as CliCommand; /** * The extension packaging command executed by bin/pharext */ class Packager implements Command { use CliCommand; /** * Extension source directory * @var pharext\SourceDir */ private $source; /** * Cleanups * @var array */ private $cleanup = []; /** * Create the command */ public function __construct() { $this->args = new CliArgs([ ["h", "help", "Display this help", CliArgs::OPTIONAL|CliArgs::SINGLE|CliArgs::NOARG|CliArgs::HALT], ["v", "verbose", "More output", CliArgs::OPTIONAL|CliArgs::SINGLE|CliArgs::NOARG], ["q", "quiet", "Less output", CliArgs::OPTIONAL|CliArgs::SINGLE|CliArgs::NOARG], ["n", "name", "Extension name", CliArgs::REQUIRED|CliArgs::SINGLE|CliArgs::REQARG], ["r", "release", "Extension release version", CliArgs::REQUIRED|CliArgs::SINGLE|CliArgs::REQARG], ["s", "source", "Extension source directory", CliArgs::REQUIRED|CliArgs::SINGLE|CliArgs::REQARG], ["g", "git", "Use `git ls-tree` to determine file list", CliArgs::OPTIONAL|CliArgs::SINGLE|CliArgs::NOARG], ["p", "pecl", "Use PECL package.xml to determine file list, name and release", CliArgs::OPTIONAL|CliArgs::SINGLE|CliArgs::NOARG], ["d", "dest", "Destination directory", CliArgs::OPTIONAL|CliArgs::SINGLE|CliArgs::REQARG, "."], ["z", "gzip", "Create additional PHAR compressed with gzip", CliArgs::OPTIONAL|CliArgs::SINGLE|CliArgs::NOARG], ["Z", "bzip", "Create additional PHAR compressed with bzip", CliArgs::OPTIONAL|CliArgs::SINGLE|CliArgs::NOARG], ["S", "sign", "Sign the PHAR with a private key", CliArgs::OPTIONAL|CliArgs::SINGLE|CliArgs::REQARG], [null, "signature", "Dump signature", CliArgs::OPTIONAL|CliArgs::SINGLE|CliArgs::NOARG|CliArgs::HALT], ]); } /** * Perform cleaniup */ function __destruct() { foreach ($this->cleanup as $cleanup) { if (is_dir($cleanup)) { $this->rm($cleanup); } else { unlink($cleanup); } } } /** * @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; } if ($this->args["signature"]) { exit($this->signature($prog)); } try { /* source needs to be evaluated before CliArgs validation, * so e.g. name and version can be overriden and CliArgs * does not complain about missing arguments */ if ($this->args["source"]) { $source = $this->localize($this->args["source"]); if ($this->args["pecl"]) { $this->source = new SourceDir\Pecl($this, $source); } elseif ($this->args["git"]) { $this->source = new SourceDir\Git($this, $source); } else { $this->source = new SourceDir\Pharext($this, $source); } } } catch (\Exception $e) { $errs[] = $e->getMessage(); } foreach ($this->args->validate() as $error) { $errs[] = $error; } if ($errs) { if (!$this->args["quiet"]) { if (!headers_sent()) { /* only display header, if we didn't generate any output yet */ $this->header(); } } foreach ($errs as $err) { $this->error("%s\n", $err); } printf("\n"); if (!$this->args["quiet"]) { $this->help($prog); } exit(1); } $this->createPackage(); } /** * Dump program signature * @param string $prog * @return int exit code */ function signature($prog) { try { $sig = (new Phar(Phar::running(false)))->getSignature(); printf("%s signature of %s\n%s", $sig["hash_type"], $prog, chunk_split($sig["hash"], 64, "\n")); return 0; } catch (\Exception $e) { $this->error("%s\n", $e->getMessage()); return 2; } } /** * Download remote source * @param string $source * @return string local source */ private function download($source) { $this->info("Fetching remote source %s ... ", $source); if ($this->args["git"]) { $local = new Tempdir("gitclone"); $cmd = new ExecCmd("git", $this->args->verbose); $cmd->run(["clone", $source, $local]); if (!$this->args->verbose) { $this->info("OK\n"); } } else { $context = stream_context_create([],["notification" => function($notification, $severity, $message, $code, $bytes_cur, $bytes_max) { switch ($notification) { case STREAM_NOTIFY_CONNECT: $this->debug("\n"); break; case STREAM_NOTIFY_PROGRESS: if ($bytes_max) { $bytes_pct = $bytes_cur/$bytes_max; $this->debug("\r %3d%% [%s>%s] ", $bytes_pct*100, str_repeat("=", round(70*$bytes_pct)), str_repeat(" ", round(70*(1-$bytes_pct))) ); } break; case STREAM_NOTIFY_COMPLETED: /* this is not generated, why? */ break; } }]); if (!$remote = fopen($source, "r", false, $context)) { $this->error(null); exit(2); } $local = new Tempfile("remote"); if (!stream_copy_to_stream($remote, $local->getStream())) { $this->error(null); exit(2); } $local->closeStream(); $this->info("OK\n"); } $this->cleanup[] = $local; return $local; } /** * Extract local archive * @param stirng $source * @return string extracted directory */ private function extract($source) { $dest = new Tempdir("local"); $this->debug("Extracting %s to %s ... ", $source, $dest); $archive = new PharData($source); $archive->extractTo($dest); $this->debug("OK\n"); $this->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)) { $source = $this->download($source); } if (!is_dir($source)) { $source = $this->extract($source); if ($this->args["pecl"]) { $this->debug("Sanitizing PECL dir ... "); $dirs = glob("$source/*", GLOB_ONLYDIR); $files = array_diff(glob("$source/*"), $dirs); $source = current($dirs); foreach ($files as $file) { rename($file, "$source/" . basename($file)); } $this->debug("OK\n"); } } return $source; } /** * Traverses all pharext source files to bundle * @return Generator */ private function bundle() { $rdi = new \RecursiveDirectoryIterator(__DIR__); $rii = new \RecursiveIteratorIterator($rdi); for ($rii->rewind(); $rii->valid(); $rii->next()) { yield "pharext/". $rii->getSubPathname() => $rii->key(); } } /** * Ask for password on the console * @param string $prompt * @return string password */ private function askpass($prompt = "Password:") { system("stty -echo", $retval); if ($retval) { $this->error("Could not disable echo on the terminal\n"); } printf("%s ", $prompt); $pass = fgets(STDIN, 1024); system("stty echo"); if (substr($pass, -1) == "\n") { $pass = substr($pass, 0, -1); } return $pass; } /** * Creates the extension phar */ private function createPackage() { $pkguniq = uniqid(); $pkgtemp = sprintf("%s/%s.phar", sys_get_temp_dir(), $pkguniq); $pkgdesc = "{$this->args->name}-{$this->args->release}"; $this->info("Creating phar %s ...%s", $pkgtemp, $this->args->verbose ? "\n" : " "); try { $package = new Phar($pkgtemp); if ($this->args->sign) { $this->info("\nUsing private key to sign phar ... \n"); $privkey = new Openssl\PrivateKey(realpath($this->args->sign), $this->askpass()); $privkey->sign($package); } $package->startBuffering(); $package->buildFromIterator($this->source, $this->source->getBaseDir()); $package->buildFromIterator($this->bundle(__DIR__)); $package->addFile(__DIR__."/../pharext_installer.php", "pharext_installer.php"); $package->setDefaultStub("pharext_installer.php"); $package->setStub("#!/usr/bin/php -dphar.readonly=1\n".$package->getStub()); $package->stopBuffering(); if (!chmod($pkgtemp, 0777)) { $this->error(null); } elseif ($this->args->verbose) { $this->debug("Created executable phar %s\n", $pkgtemp); } else { $this->info("OK\n"); } if ($this->args->gzip) { $this->info("Compressing with gzip ... "); try { $package->compress(Phar::GZ) ->setDefaultStub("pharext_installer.php"); $this->info("OK\n"); } catch (\Exception $e) { $this->error("%s\n", $e->getMessage()); } } if ($this->args->bzip) { $this->info("Compressing with bzip ... "); try { $package->compress(Phar::BZ2) ->setDefaultStub("pharext_installer.php"); $this->info("OK\n"); } catch (\Exception $e) { $this->error("%s\n", $e->getMessage()); } } unset($package); } catch (\Exception $e) { $this->error("%s\n", $e->getMessage()); exit(4); } foreach (glob($pkgtemp."*") as $pkgtemp) { $pkgfile = str_replace($pkguniq, "{$pkgdesc}.ext", $pkgtemp); $pkgname = $this->args->dest ."/". basename($pkgfile); $this->info("Finalizing %s ... ", $pkgname); if (!rename($pkgtemp, $pkgname)) { $this->error(null); exit(5); } $this->info("OK\n"); if ($this->args->sign && isset($privkey)) { $keyname = $this->args->dest ."/". basename($pkgfile) . ".pubkey"; $this->info("Public Key %s ... ", $keyname); try { $privkey->exportPublicKey($keyname); $this->info("OK\n"); } catch (\Exception $e) { $this->error("%s", $e->getMessage()); } } } } } <?php namespace pharext\SourceDir; use pharext\Command; use pharext\SourceDir; /** * Extension source directory which is a git repo */ class Git implements \IteratorAggregate, SourceDir { /** * The Packager command * @var pharext\Command */ private $cmd; /** * Base directory * @var string */ private $path; /** * @inheritdoc * @see \pharext\SourceDir::__construct() */ public function __construct(Command $cmd, $path) { $this->cmd = $cmd; $this->path = $path; } /** * @inheritdoc * @see \pharext\SourceDir::getBaseDir() */ public function getBaseDir() { return $this->path; } /** * 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)))) { if ($this->cmd->getArgs()->verbose) { $this->cmd->info("Packaging %s\n", $file); } /* there may be symlinks, so no realpath here */ if (!file_exists("$path/$file")) { $this->cmd->error("File %s does not exist in %s\n", $file, $path); } yield "$path/$file"; } } pclose($pipe); } chdir($pwd); } /** * Implements IteratorAggregate * @see IteratorAggregate::getIterator() */ public function getIterator() { return $this->generateFiles(); } } <?php namespace pharext\SourceDir; use pharext\Command; use pharext\SourceDir; /** * A PECL extension source directory containing a v2 package.xml */ class Pecl implements \IteratorAggregate, SourceDir { /** * The Packager command * @var pharext\Packager */ private $cmd; /** * The package.xml * @var SimpleXmlElement */ private $sxe; /** * The base directory * @var string */ private $path; /** * @inheritdoc * @see \pharext\SourceDir::__construct() */ public function __construct(Command $cmd, $path) { if (realpath("$path/package2.xml")) { $sxe = simplexml_load_file("$path/package2.xml"); } elseif (realpath("$path/package.xml")) { $sxe = simplexml_load_file("$path/package.xml"); } else { throw new \Exception("Missing package.xml in $path"); } $sxe->registerXPathNamespace("pecl", $sxe->getDocNamespaces()[""]); $args = $cmd->getArgs(); if (!isset($args->name)) { $name = (string) $sxe->xpath("/pecl:package/pecl:name")[0]; foreach ($args->parse(2, ["--name", $name]) as $error) { $cmd->error("%s\n", $error); } } if (!isset($args->release)) { $release = (string) $sxe->xpath("/pecl:package/pecl:version/pecl:release")[0]; foreach ($args->parse(2, ["--release", $release]) as $error) { $cmd->error("%s\n", $error); } } $this->cmd = $cmd; $this->sxe = $sxe; $this->path = $path; } /** * @inheritdoc * @see \pharext\SourceDir::getBaseDir() */ public function getBaseDir() { return $this->path; } /** * 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, "/"); } /** * Render installer hook * @param array $configure * @return string */ private static function loadHook($configure, $dependencies) { return include __DIR__."/../../pharext_install.tpl.php"; } /** * Create installer hook * @return \Generator */ private function generateHooks() { $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 realpath($this->path."/".end($glob)); } else { unset($dependencies[$key]); } } $configure = $this->sxe->xpath("/pecl:package/pecl:extsrcrelease/pecl:configureoption"); if ($configure) { $fd = tmpfile(); ob_start(function($s) use($fd){ fwrite($fd, $s); return null; }); self::loadHook($configure, $dependencies); ob_end_flush(); rewind($fd); yield "pharext_install.php" => $fd; } } /** * Generate a list of files from the package.xml * @return Generator */ private function generateFiles() { foreach ($this->generateHooks() as $file => $hook) { if ($this->cmd->getArgs()->verbose) { $this->cmd->info("Packaging %s\n", is_string($hook) ? $hook : $file); } yield $file => $hook; } foreach ($this->sxe->xpath("//pecl:file") as $file) { $path = $this->path ."/". $this->dirOf($file) ."/". $file["name"]; if ($this->cmd->getArgs()->verbose) { $this->cmd->info("Packaging %s\n", $path); } if (!($realpath = realpath($path))) { $this->cmd->error("File %s does not exist", $path); } yield $realpath; } } /** * Implements IteratorAggregate * @see IteratorAggregate::getIterator() */ public function getIterator() { return $this->generateFiles(); } } <?php namespace pharext\SourceDir; use pharext\Command; use pharext\SourceDir; /** * A source directory containing pharext_package.php and eventually pharext_install.php */ class Pharext implements \IteratorAggregate, SourceDir { /** * @var pharext\Command */ private $cmd; /** * @var string */ private $path; /** * @var callable */ private $iter; /** * @inheritdoc * @see \pharext\SourceDir::__construct() */ public function __construct(Command $cmd, $path) { $this->cmd = $cmd; $this->path = $path; $callable = include "$path/pharext_package.php"; if (!is_callable($callable)) { throw new \Exception("Package hook did not return a callable"); } $this->iter = $callable($cmd, $path); } /** * @inheritdoc * @see \pharext\SourceDir::getBaseDir() */ public function getBaseDir() { return $this->path; } /** * Implements IteratorAggregate * @see IteratorAggregate::getIterator() */ public function getIterator() { if (!is_callable($this->iter)) { return new Git($this->cmd, $this->path); } return call_user_func($this->iter, $this->cmd, $this->path); } } <?php namespace pharext; /** * Source directory interface */ interface SourceDir extends \Traversable { /** * Read the source directory * * Note: Best practices are for others, but if you want to follow them, do * not put constructors in interfaces. Keep your complaints, I warned you. * * @param Command $cmd * @param string $path */ public function __construct(Command $cmd, $path); /** * Retrieve the base directory * @return string */ public function getBaseDir(); } <?php namespace pharext; class Tempdir extends \SplFileInfo { private $dir; public function __construct($prefix) { $temp = sprintf("%s/%s", sys_get_temp_dir(), uniqid($prefix)); if (!is_dir($temp)) { if (!mkdir($temp, 0700, true)) { throw new Exception("Could not create tempdir: ".error_get_last()["message"]); } } parent::__construct($temp); } } <?php namespace pharext; class Tempfile extends \SplFileInfo { private $handle; function __construct($prefix) { $tries = 0; /* PharData needs a dot in the filename, sure */ $temp = sys_get_temp_dir() . "/"; $omask = umask(077); do { $path = $temp.uniqid($prefix).".tmp"; $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); } function __destruct() { @unlink($this->getPathname()); } function closeStream() { fclose($this->handle); } function getStream() { return $this->handle; } } <?php namespace pharext; const VERSION = "@PHAREXT_VERSION@"; <?='<?php'?> /** * Generated by pharext v<?=pharext\VERSION?> at <?=date("Y-m-d H:i:i T")?>. */ namespace pharext; use pharext\Cli\Args as CliArgs; return function(Installer $installer) { $args = $installer->getArgs(); <?php foreach ($configure as $cfg) : ?> $args->compile([[ null, "<?=$cfg["name"]?>", "<?=ucfirst($cfg["prompt"])?>", CliArgs::OPTARG, <?php if (strlen($cfg["default"])) : ?> "<?=$cfg["default"]?>" <?php else : ?> NULL <?php endif; ?> ]]); <?php endforeach; ?> return function(Installer $installer) { $args = $installer->getArgs(); <?php foreach ($configure as $cfg) : ?> if (isset($args["<?=$cfg["name"]?>"])) { $args->configure = "--<?=$cfg["name"]?>=".$args["<?=$cfg["name"]?>"]; } <?php endforeach; ?> }; }; <?php /** * The installer sub-stub for extension phars */ spl_autoload_register(function($c) { return include strtr($c, "\\_", "//") . ".php"; }); $installer = new pharext\Installer(); $installer->run($argc, $argv); <?php /** * The packager sub-stub for bin/pharext */ spl_autoload_register(function($c) { return include strtr($c, "\\_", "//") . ".php"; }); $packager = new pharext\Packager(); $packager->run($argc, $argv); X¼É»D®ÏÅCkD{Àß“–åë1º���GBMB