/** * Validates the package archive. * * @param string $type upload or download package */ protected function validateArchive($type) { // try to open the archive try { // TODO: Exceptions thrown within openArchive() are discarded, resulting in // the meaningless message 'not a valid package' $this->archive->openArchive(); } catch (SystemException $e) { throw new UserInputException($type, 'noValidPackage'); } // validate php requirements $errors = PackageInstallationDispatcher::validatePHPRequirements($this->archive->getPhpRequirements()); if (!empty($errors)) { WCF::getTPL()->assign('phpRequirements', $errors); throw new UserInputException($type, 'phpRequirements'); } // check update or install support if ($this->package !== null) { if (!$this->archive->isValidUpdate()) { throw new UserInputException($type, 'noValidUpdate'); } } else { if (!$this->archive->isValidInstall()) { throw new UserInputException($type, 'noValidInstall'); } else if ($this->archive->isAlreadyInstalled()) { throw new UserInputException($type, 'uniqueAlreadyInstalled'); } } }
/** * @see \wcf\form\IForm::validate() */ public function validate() { parent::validate(); if (empty($this->source['name'])) { throw new UserInputException('source'); } if (empty($this->source['tmp_name'])) { throw new UserInputException('source', 'uploadFailed'); } try { // check if the uploaded file is a package $archive = new PackageArchive($this->source['tmp_name']); $archive->openArchive(); // check if the package is an application if ($archive->getPackageInfo('isApplication')) { throw new SystemException("Package is application"); } // check if the package includes a style $containsStyle = false; $installInstructions = $archive->getInstallInstructions(); foreach ($installInstructions as $instruction) { if ($instruction['pip'] == 'style') { $containsStyle = true; break; } } if (!$containsStyle) { throw new SystemException("Package contains no style"); } $filename = FileUtil::getTemporaryFilename('package_', preg_replace('!^.*(?=\\.(?:tar\\.gz|tgz|tar)$)!i', '', basename($this->source['name']))); if (!@move_uploaded_file($this->source['tmp_name'], $filename)) { throw new SystemException("Cannot move uploaded file"); } WCF::getSession()->register('stylePackageImportLocation', $filename); HeaderUtil::redirect(LinkHandler::getInstance()->getLink('PackageStartInstall', array('action' => 'install'))); exit; } catch (SystemException $e) { // ignore errors } try { $this->style = StyleEditor::import($this->source['tmp_name']); } catch (\Exception $e) { @unlink($this->source['tmp_name']); throw new UserInputException('source', 'importFailed'); } }
/** * Returns current package archive. * * @return \wcf\system\package\PackageArchive */ public function getArchive() { if ($this->archive === null) { $package = $this->getPackage(); // check if we're doing an iterative update of the same package if ($this->previousPackageData !== null && $this->getPackage()->package == $this->previousPackageData['package']) { if (Package::compareVersion($this->getPackage()->packageVersion, $this->previousPackageData['packageVersion'], '<')) { // fake package to simulate the package version required by current archive $this->getPackage()->setPackageVersion($this->previousPackageData['packageVersion']); } } $this->archive = new PackageArchive($this->queue->archive, $this->getPackage()); if (FileUtil::isURL($this->archive->getArchive())) { // get return value and update entry in // package_installation_queue with this value $archive = $this->archive->downloadArchive(); $queueEditor = new PackageInstallationQueueEditor($this->queue); $queueEditor->update(array('archive' => $archive)); } $this->archive->openArchive(); } return $this->archive; }
/** * Builds nodes for optional packages, whereas each package exists within * one node with the same parent node, seperated by sequence no (which does * not really matter at this point). */ protected function buildOptionalNodes() { $packages = array(); $optionalPackages = $this->installation->getArchive()->getOptionals(); foreach ($optionalPackages as $package) { // check if already installed if (Package::isAlreadyInstalled($package['name'])) { continue; } // extract package $index = $this->installation->getArchive()->getTar()->getIndexByFilename($package['file']); if ($index === false) { throw new SystemException("Unable to find required package '" . $package['file'] . "' within archive."); } $fileName = FileUtil::getTemporaryFilename('package_', preg_replace('!^.*(?=\\.(?:tar\\.gz|tgz|tar)$)!i', '', basename($package['file']))); $this->installation->getArchive()->getTar()->extract($index, $fileName); // get archive data $archive = new PackageArchive($fileName); $archive->openArchive(); // check if all requirements are met $isInstallable = true; foreach ($archive->getOpenRequirements() as $packageName => $package) { if (!isset($package['file'])) { // requirement is neither installed nor shipped, check if it is about to be installed if (!isset(self::$pendingPackages[$packageName])) { $isInstallable = false; break; } } } // check for exclusions $excludedPackages = $archive->getConflictedExcludedPackages(); if (!empty($excludedPackages)) { $isInstallable = false; } $excludingPackages = $archive->getConflictedExcludingPackages(); if (!empty($excludingPackages)) { $isInstallable = false; } $packages[] = array('archive' => $fileName, 'isInstallable' => $isInstallable, 'package' => $archive->getPackageInfo('name'), 'packageName' => $archive->getLocalizedPackageInfo('packageName'), 'packageDescription' => $archive->getLocalizedPackageInfo('packageDescription'), 'selected' => 0); self::$pendingPackages[$archive->getPackageInfo('name')] = $archive->getPackageInfo('version'); } if (!empty($packages)) { $this->parentNode = $this->node; $this->node = $this->getToken(); $this->sequenceNo = 0; $sql = "INSERT INTO\twcf" . WCF_N . "_package_installation_node\n\t\t\t\t\t\t(queueID, processNo, sequenceNo, node, parentNode, nodeType, nodeData)\n\t\t\t\tVALUES\t\t(?, ?, ?, ?, ?, ?, ?)"; $statement = WCF::getDB()->prepareStatement($sql); $statement->execute(array($this->installation->queue->queueID, $this->installation->queue->processNo, $this->sequenceNo, $this->node, $this->parentNode, 'optionalPackages', serialize($packages))); } }
/** * Installs the specified package. * * @param string $file */ private function install($file) { // PackageStartInstallForm::validateDownloadPackage() if (FileUtil::isURL($file)) { // download package $archive = new PackageArchive($file, null); try { if (VERBOSITY >= 1) { Log::info("Downloading '" . $file . "'"); } $file = $archive->downloadArchive(); } catch (SystemException $e) { $this->error('notFound', array('file' => $file)); } } else { // probably local path if (!file_exists($file)) { $this->error('notFound', array('file' => $file)); } $archive = new PackageArchive($file, null); } // PackageStartInstallForm::validateArchive() // try to open the archive try { // TODO: Exceptions thrown within openArchive() are discarded, resulting in // the meaningless message 'not a valid package' $archive->openArchive(); } catch (SystemException $e) { $this->error('noValidPackage'); } $errors = PackageInstallationDispatcher::validatePHPRequirements($archive->getPhpRequirements()); if (!empty($errors)) { // TODO: Nice output $this->error('phpRequirements', array('errors' => $errors)); } // try to find existing package $sql = "SELECT\t*\n\t\t\tFROM\twcf" . WCF_N . "_package\n\t\t\tWHERE\tpackage = ?"; $statement = CLIWCF::getDB()->prepareStatement($sql); $statement->execute(array($archive->getPackageInfo('name'))); $row = $statement->fetchArray(); $package = null; if ($row !== false) { $package = new Package(null, $row); } // check update or install support if ($package !== null) { CLIWCF::getSession()->checkPermissions(array('admin.system.package.canUpdatePackage')); $archive->setPackage($package); if (!$archive->isValidUpdate()) { $this->error('noValidUpdate'); } } else { CLIWCF::getSession()->checkPermissions(array('admin.system.package.canInstallPackage')); if (!$archive->isValidInstall()) { $this->error('noValidInstall'); } else { if ($archive->getPackageInfo('isApplication')) { // applications cannot be installed via CLI $this->error('cli.installIsApplication'); } else { if ($archive->isAlreadyInstalled()) { $this->error('uniqueAlreadyInstalled'); } else { if ($archive->getPackageInfo('isApplication') && $this->archive->hasUniqueAbbreviation()) { $this->error('noUniqueAbbrevation'); } } } } } // PackageStartInstallForm::save() $processNo = PackageInstallationQueue::getNewProcessNo(); // insert queue $queue = PackageInstallationQueueEditor::create(array('processNo' => $processNo, 'userID' => CLIWCF::getUser()->userID, 'package' => $archive->getPackageInfo('name'), 'packageName' => $archive->getLocalizedPackageInfo('packageName'), 'packageID' => $package !== null ? $package->packageID : null, 'archive' => $file, 'action' => $package !== null ? 'update' : 'install')); // PackageInstallationDispatcher::openQueue() $parentQueueID = 0; $conditions = new PreparedStatementConditionBuilder(); $conditions->add("userID = ?", array(CLIWCF::getUser()->userID)); $conditions->add("parentQueueID = ?", array($parentQueueID)); if ($processNo != 0) { $conditions->add("processNo = ?", array($processNo)); } $conditions->add("done = ?", array(0)); $sql = "SELECT\t\t*\n\t\t\tFROM\t\twcf" . WCF_N . "_package_installation_queue\n\t\t\t" . $conditions . "\n\t\t\tORDER BY\tqueueID ASC"; $statement = CLIWCF::getDB()->prepareStatement($sql); $statement->execute($conditions->getParameters()); $packageInstallation = $statement->fetchArray(); if (!isset($packageInstallation['queueID'])) { $this->error('internalOpenQueue'); return; } else { $queueID = $packageInstallation['queueID']; } // PackageInstallationConfirmPage::readParameters() $queue = new PackageInstallationQueue($queueID); if (!$queue->queueID || $queue->done) { $this->error('internalReadParameters'); return; } // PackageInstallationConfirmPage::readData() $missingPackages = 0; $packageInstallationDispatcher = new PackageInstallationDispatcher($queue); // get requirements $requirements = $packageInstallationDispatcher->getArchive()->getRequirements(); $openRequirements = $packageInstallationDispatcher->getArchive()->getOpenRequirements(); foreach ($requirements as &$requirement) { if (isset($openRequirements[$requirement['name']])) { $requirement['status'] = 'missing'; $requirement['action'] = $openRequirements[$requirement['name']]['action']; if (!isset($requirement['file'])) { if ($requirement['action'] === 'update') { $requirement['status'] = 'missingVersion'; $requirement['existingVersion'] = $openRequirements[$requirement['name']]['existingVersion']; } $missingPackages++; } else { $requirement['status'] = 'delivered'; $packageArchive = new PackageArchive($packageInstallationDispatcher->getArchive()->extractTar($requirement['file'])); $packageArchive->openArchive(); // make sure that the delivered package is correct if ($requirement['name'] != $packageArchive->getPackageInfo('name')) { $requirement['status'] = 'invalidDeliveredPackage'; $requirement['deliveredPackage'] = $packageArchive->getPackageInfo('name'); $missingPackages++; } else { if (isset($requirement['minversion'])) { // make sure that the delivered version is sufficient if (Package::compareVersion($requirement['minversion'], $packageArchive->getPackageInfo('version')) > 0) { $requirement['deliveredVersion'] = $packageArchive->getPackageInfo('version'); $requirement['status'] = 'missingVersion'; $missingPackages++; } } } } } else { $requirement['status'] = 'installed'; } } unset($requirement); // PackageInstallationConfirmPage::assignVariables/show() $excludingPackages = $packageInstallationDispatcher->getArchive()->getConflictedExcludingPackages(); $excludedPackages = $packageInstallationDispatcher->getArchive()->getConflictedExcludedPackages(); if (!($missingPackages == 0 && count($excludingPackages) == 0 && count($excludedPackages) == 0)) { $this->error('missingPackagesOrExclude', array('requirements' => $requirements, 'excludingPackages' => $excludingPackages, 'excludedPackages' => $excludedPackages)); return; } // AbstractDialogAction::readParameters() $step = 'prepare'; $queueID = $queue->queueID; $node = ''; // initialize progressbar $progressbar = new ProgressBar(new ConsoleProgressBar(array('width' => CLIWCF::getTerminal()->getWidth(), 'elements' => array(ConsoleProgressBar::ELEMENT_PERCENT, ConsoleProgressBar::ELEMENT_BAR, ConsoleProgressBar::ELEMENT_TEXT), 'textWidth' => min(floor(CLIWCF::getTerminal()->getWidth() / 2), 50)))); // InstallPackageAction::readParameters() $finished = false; while (!$finished) { $queue = new PackageInstallationQueue($queueID); if (!$queue->queueID) { // todo: what to output? echo "InstallPackageAction::readParameters()"; return; } $installation = new PackageInstallationDispatcher($queue); switch ($step) { case 'prepare': // InstallPackageAction::stepPrepare() // update package information $installation->updatePackage(); // clean-up previously created nodes $installation->nodeBuilder->purgeNodes(); // create node tree $installation->nodeBuilder->buildNodes(); $node = $installation->nodeBuilder->getNextNode(); $queueID = $installation->nodeBuilder->getQueueByNode($installation->queue->processNo, $node); $step = 'install'; $progress = 0; $currentAction = $installation->nodeBuilder->getPackageNameByQueue($queueID); break; case 'install': // InstallPackageAction::stepInstall() $step_ = $installation->install($node); $queueID = $installation->nodeBuilder->getQueueByNode($installation->queue->processNo, $step_->getNode()); if ($step_->hasDocument()) { $innerTemplate = $step_->getTemplate(); $progress = $installation->nodeBuilder->calculateProgress($node); $node = $step_->getNode(); $currentAction = $installation->nodeBuilder->getPackageNameByQueue($queueID); } else { if ($step_->getNode() == '') { // perform final actions $installation->completeSetup(); // InstallPackageAction::finalize() CacheHandler::getInstance()->flushAll(); // /InstallPackageAction::finalize() // show success $progress = 100; $currentAction = CLIWCF::getLanguage()->get('wcf.acp.package.installation.step.install.success'); $finished = true; continue; } else { // continue with next node $progress = $installation->nodeBuilder->calculateProgress($node); $node = $step_->getNode(); $currentAction = $installation->nodeBuilder->getPackageNameByQueue($queueID); } } break; } $progressbar->update($progress, $currentAction); } $progressbar->getAdapter()->finish(); }
/** * Validates this package and optionally it's delivered requirements. The set validation * mode will toggle between different checks. * * @param integer $validationMode * @return boolean */ public function validate($validationMode, $requiredVersion = '') { if ($validationMode !== PackageValidationManager::VALIDATION_EXCLUSION) { try { // try to read archive $this->archive->openArchive(); // check if package is installable or suitable for an update $this->validateInstructions($requiredVersion, $validationMode); } catch (PackageValidationException $e) { $this->exception = $e; return false; } } $package = $this->archive->getPackageInfo('name'); if ($validationMode === PackageValidationManager::VALIDATION_RECURSIVE) { try { PackageValidationManager::getInstance()->addVirtualPackage($package, $this->archive->getPackageInfo('version')); // cache excluded packages self::$excludedPackages[$package] = array(); $excludedPackages = $this->archive->getExcludedPackages(); for ($i = 0, $count = count($excludedPackages); $i < $count; $i++) { if (!isset(self::$excludedPackages[$package][$excludedPackages[$i]['name']])) { self::$excludedPackages[$package][$excludedPackages[$i]['name']] = array(); } self::$excludedPackages[$package][$excludedPackages[$i]['name']][] = $excludedPackages[$i]['version']; } // traverse open requirements foreach ($this->archive->getOpenRequirements() as $requirement) { $virtualPackageVersion = PackageValidationManager::getInstance()->getVirtualPackage($requirement['name']); if ($virtualPackageVersion === null || Package::compareVersion($virtualPackageVersion, $requirement['minversion'], '<')) { if (empty($requirement['file'])) { // check if package is known $sql = "SELECT\t*\n\t\t\t\t\t\t\t\tFROM\twcf" . WCF_N . "_package\n\t\t\t\t\t\t\t\tWHERE\tpackage = ?"; $statement = WCF::getDB()->prepareStatement($sql); $statement->execute(array($requirement['name'])); $package = $statement->fetchObject('wcf\\data\\package\\Package'); throw new PackageValidationException(PackageValidationException::MISSING_REQUIREMENT, array('package' => $package, 'packageName' => $requirement['name'], 'packageVersion' => $requirement['minversion'])); } $archive = $this->archive->extractTar($requirement['file']); $index = count($this->children); $this->children[$index] = new PackageValidationArchive($archive, $this, $this->depth + 1); if (!$this->children[$index]->validate(PackageValidationManager::VALIDATION_RECURSIVE, $requirement['minversion'])) { return false; } PackageValidationManager::getInstance()->addVirtualPackage($this->children[$index]->getArchive()->getPackageInfo('name'), $this->children[$index]->getArchive()->getPackageInfo('version')); } } } catch (PackageValidationException $e) { $this->exception = $e; return false; } } else { if ($validationMode === PackageValidationManager::VALIDATION_EXCLUSION) { try { $this->validateExclusion($package); for ($i = 0, $count = count($this->children); $i < $count; $i++) { if (!$this->children[$i]->validate(PackageValidationManager::VALIDATION_EXCLUSION)) { return false; } } } catch (PackageValidationException $e) { $this->exception = $e; return false; } } } return true; }
/** * Tries to download a package from available update servers. * * @param string $package package identifier * @param array $packageUpdateVersions package update versions * @param boolean $validateInstallInstructions * @return string tmp filename of a downloaded package */ protected function downloadPackage($package, $packageUpdateVersions, $validateInstallInstructions = false) { // get download from cache if ($filename = $this->getCachedDownload($package, $packageUpdateVersions[0]['package'])) { return $filename; } // download file foreach ($packageUpdateVersions as $packageUpdateVersion) { // get auth data $authData = $this->getAuthData($packageUpdateVersion); if ($packageUpdateVersion['filename']) { $request = new HTTPRequest($packageUpdateVersion['filename'], !empty($authData) ? array('auth' => $authData) : array(), array('apiVersion' => PackageUpdate::API_VERSION)); } else { // create request $request = new HTTPRequest($this->packageUpdateServers[$packageUpdateVersion['packageUpdateServerID']]->getDownloadURL(), !empty($authData) ? array('auth' => $authData) : array(), array('apiVersion' => PackageUpdate::API_VERSION, 'packageName' => $packageUpdateVersion['package'], 'packageVersion' => $packageUpdateVersion['packageVersion'])); } try { $request->execute(); } catch (HTTPUnauthorizedException $e) { throw new PackageUpdateUnauthorizedException($request, $this->packageUpdateServers[$packageUpdateVersion['packageUpdateServerID']], $packageUpdateVersion); } $response = $request->getReply(); // check response if ($response['statusCode'] != 200) { throw new SystemException(WCF::getLanguage()->getDynamicVariable('wcf.acp.package.error.downloadFailed', array('__downloadPackage' => $package)) . ' (' . $response['body'] . ')'); } // write content to tmp file $filename = FileUtil::getTemporaryFilename('package_'); $file = new File($filename); $file->write($response['body']); $file->close(); unset($response['body']); // test package $archive = new PackageArchive($filename); $archive->openArchive(); // check install instructions if ($validateInstallInstructions) { $installInstructions = $archive->getInstallInstructions(); if (empty($installInstructions)) { throw new SystemException("Package '" . $archive->getLocalizedPackageInfo('packageName') . "' (" . $archive->getPackageInfo('name') . ") does not contain valid installation instructions."); } } $archive->getTar()->close(); // cache download in session PackageUpdateDispatcher::getInstance()->cacheDownload($package, $packageUpdateVersion['packageVersion'], $filename); return $filename; } return false; }
/** * Builds nodes for optional packages, whereas each package exists within * one node with the same parent node, seperated by sequence no (which does * not really matter at this point). */ protected function buildOptionalNodes() { $packages = array(); $optionalPackages = $this->installation->getArchive()->getOptionals(); foreach ($optionalPackages as $package) { // extract package $index = $this->installation->getArchive()->getTar()->getIndexByFilename($package['file']); if ($index === false) { throw new SystemException("Unable to find required package '" . $package['file'] . "' within archive."); } $fileName = FileUtil::getTemporaryFilename('package_', preg_replace('!^.*(?=\\.(?:tar\\.gz|tgz|tar)$)!i', '', basename($package['file']))); $this->installation->getArchive()->getTar()->extract($index, $fileName); // get archive data $archive = new PackageArchive($fileName); $archive->openArchive(); $packages[] = array('archive' => $fileName, 'package' => $archive->getPackageInfo('name'), 'packageName' => $archive->getLocalizedPackageInfo('packageName'), 'selected' => 0); } if (!empty($packages)) { $this->parentNode = $this->node; $this->node = $this->getToken(); $this->sequenceNo = 0; $sql = "INSERT INTO\twcf" . WCF_N . "_package_installation_node\n\t\t\t\t\t\t(queueID, processNo, sequenceNo, node, parentNode, nodeType, nodeData)\n\t\t\t\tVALUES\t\t(?, ?, ?, ?, ?, ?, ?)"; $statement = WCF::getDB()->prepareStatement($sql); $statement->execute(array($this->installation->queue->queueID, $this->installation->queue->processNo, $this->sequenceNo, $this->node, $this->parentNode, 'optionalPackages', serialize($packages))); } }
/** * Gets the package name of the first application in WCFSetup.tar.gz. */ protected static function getPackageName() { // get package name $tar = new Tar(SETUP_FILE); foreach ($tar->getContentList() as $file) { if ($file['type'] != 'folder' && mb_strpos($file['filename'], 'install/packages/') === 0) { $packageFile = basename($file['filename']); $packageName = preg_replace('!\\.(tar\\.gz|tgz|tar)$!', '', $packageFile); if ($packageName != 'com.woltlab.wcf') { try { $archive = new PackageArchive(TMP_DIR . 'install/packages/' . $packageFile); $archive->openArchive(); self::$setupPackageName = $archive->getLocalizedPackageInfo('packageName'); $archive->getTar()->close(); break; } catch (SystemException $e) { } } } } $tar->close(); // assign package name WCF::getTPL()->assign(array('setupPackageName' => self::$setupPackageName)); }
/** * Tries to download a package from available update servers. * * @param string $package package identifier * @param array $packageUpdateVersions package update versions * @return string tmp filename of a downloaded package */ protected function downloadPackage($package, $packageUpdateVersions) { // get download from cache if ($filename = $this->getCachedDownload($package, $packageUpdateVersions[0]['package'])) { return $filename; } // download file $authorizationRequiredException = array(); $systemExceptions = array(); foreach ($packageUpdateVersions as $packageUpdateVersion) { try { // get auth data $authData = $this->getAuthData($packageUpdateVersion); // send request // TODO: Use HTTPRequest if (!empty($packageUpdateVersion['file'])) { $response = PackageUpdateDispatcher::getInstance()->sendRequest($packageUpdateVersion['file'], array(), $authData); } else { $response = PackageUpdateDispatcher::getInstance()->sendRequest($packageUpdateVersion['server'], array('packageName' => $packageUpdateVersion['package'], 'packageVersion' => $packageUpdateVersion['packageVersion']), $authData); } // check response // check http code if ($response['httpStatusCode'] == 401) { throw new PackageUpdateAuthorizationRequiredException($packageUpdateVersion['packageUpdateServerID'], (!empty($packageUpdateVersion['file']) ? $packageUpdateVersion['file'] : $packageUpdateVersion['server']), $response); } if ($response['httpStatusCode'] != 200) { throw new SystemException(WCF::getLanguage()->get('wcf.acp.packageUpdate.error.downloadFailed', array('$package' => $package)) . ' ('.$response['httpStatusLine'].')'); } // write content to tmp file $filename = FileUtil::getTemporaryFilename('package_'); $file = new File($filename); $file->write($response['content']); $file->close(); unset($response['content']); // test package $archive = new PackageArchive($filename); $archive->openArchive(); $archive->getTar()->close(); // cache download in session PackageUpdateDispatcher::getInstance()->cacheDownload($package, $packageUpdateVersion['packageVersion'], $filename); return $filename; } catch (PackageUpdateAuthorizationRequiredException $e) { $authorizationRequiredException[] = $e; } catch (SystemException $e) { $systemExceptions[] = $e; } } if (!empty($authorizationRequiredException)) { throw array_shift($authorizationRequiredException); } if (!empty($systemExceptions)) { throw array_shift($systemExceptions); } return false; }