Added prototype of a proper package archive validation
authorAlexander Ebert <ebert@woltlab.com>
Tue, 8 Apr 2014 14:43:26 +0000 (16:43 +0200)
committerAlexander Ebert <ebert@woltlab.com>
Tue, 8 Apr 2014 14:43:26 +0000 (16:43 +0200)
Ignore this, these classes are just lying around doing nothing.

wcfsetup/install/files/lib/system/package/PackageArchive.class.php
wcfsetup/install/files/lib/system/package/validation/PackageValidationArchive.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/package/validation/PackageValidationException.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/package/validation/PackageValidationManager.class.php [new file with mode: 0644]

index 898c7c5d5dc4045208b986b14c6d944a95f528b1..4342867d6cc84eb8181232c55781e603e9f75fb9 100644 (file)
@@ -2,7 +2,7 @@
 namespace wcf\system\package;
 use wcf\data\package\Package;
 use wcf\system\database\util\PreparedStatementConditionBuilder;
-use wcf\system\exception\SystemException;
+use wcf\system\package\validation\PackageValidationException;
 use wcf\system\io\Tar;
 use wcf\system\WCF;
 use wcf\util\DateUtil;
@@ -134,7 +134,7 @@ class PackageArchive {
        public function openArchive() {
                // check whether archive exists and is a TAR archive
                if (!file_exists($this->archive)) {
-                       throw new SystemException("unable to find package file '".$this->archive."'");
+                       throw new PackageValidationException(PackageValidationException::FILE_NOT_FOUND, array('archive' => $this->archive));
                }
                
                // open archive and read package information
@@ -149,7 +149,7 @@ class PackageArchive {
                // search package.xml in package archive
                // throw error message if not found
                if ($this->tar->getIndexByFilename(self::INFO_FILE) === false) {
-                       throw new SystemException("package information file '".(self::INFO_FILE)."' not found in '".$this->archive."'");
+                       throw new PackageValidationException(PackageValidationException::MISSING_PACKAGE_XML, array('archive' => $this->archive));
                }
                
                // extract package.xml, parse XML
@@ -170,7 +170,7 @@ class PackageArchive {
                $packageName = $package->getAttribute('name');
                if (!Package::isValidPackageName($packageName)) {
                        // package name is not a valid package identifier
-                       throw new SystemException("'".$packageName."' is not a valid package name.");
+                       throw new PackageValidationException(PackageValidationException::INVALID_PACKAGE_NAME, array('packageName' => $packageName));
                }
                
                $this->packageInfo['name'] = $packageName;
@@ -209,7 +209,7 @@ class PackageArchive {
                                
                                case 'version':
                                        if (!Package::isValidVersion($element->nodeValue)) {
-                                               throw new SystemException("package version '".$element->nodeValue."' is invalid");
+                                               throw new PackageValidationException(PackageValidationException::INVALID_PACKAGE_VERSION, array('packageVersion' => $element->nodeValue));
                                        }
                                        
                                        $this->packageInfo['version'] = $element->nodeValue;
@@ -235,7 +235,7 @@ class PackageArchive {
                $elements = $xpath->query('child::ns:requiredpackages/ns:requiredpackage', $package);
                foreach ($elements as $element) {
                        if (!Package::isValidPackageName($element->nodeValue)) {
-                               throw new SystemException("'".$element->nodeValue."' is not a valid package name.");
+                               throw new PackageValidationException(PackageValidationException::INVALID_PACKAGE_NAME, array('packageName' => $element->nodeValue));
                        }
                        
                        // read attributes
@@ -252,7 +252,7 @@ class PackageArchive {
                $elements = $xpath->query('child::ns:optionalpackages/ns:optionalpackage', $package);
                foreach ($elements as $element) {
                        if (!Package::isValidPackageName($element->nodeValue)) {
-                               throw new SystemException("'".$element->nodeValue."' is not a valid package name.");
+                               throw new PackageValidationException(PackageValidationException::INVALID_PACKAGE_NAME, array('packageName' => $element->nodeValue));
                        }
                        
                        // read attributes
@@ -269,7 +269,7 @@ class PackageArchive {
                $elements = $xpath->query('child::ns:excludedpackages/ns:excludedpackage', $package);
                foreach ($elements as $element) {
                        if (!Package::isValidPackageName($element->nodeValue)) {
-                               throw new SystemException("'".$element->nodeValue."' is not a valid package name.");
+                               throw new PackageValidationException(PackageValidationException::INVALID_PACKAGE_NAME, array('packageName' => $element->nodeValue));
                        }
                        
                        // read attributes
@@ -738,7 +738,10 @@ class PackageArchive {
                // search the requested tar archive in our package archive.
                // throw error message if not found.
                if (($fileIndex = $this->tar->getIndexByFilename($filename)) === false) {
-                       throw new SystemException("tar archive '".$filename."' not found in '".$this->archive."'.");
+                       throw new PackageValidationException(PackageValidationException::FILE_NOT_FOUND, array(
+                               'archive' => $this->archive,
+                               'targetArchive' => $filename
+                       ));
                }
                
                // requested tar archive was found
diff --git a/wcfsetup/install/files/lib/system/package/validation/PackageValidationArchive.class.php b/wcfsetup/install/files/lib/system/package/validation/PackageValidationArchive.class.php
new file mode 100644 (file)
index 0000000..763a64e
--- /dev/null
@@ -0,0 +1,152 @@
+<?php
+namespace wcf\system\package\validation;
+use wcf\system\package\PackageArchive;
+use wcf\system\WCF;
+
+/**
+ * Recursively validates the package archive and it's delivered requirements.
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2014 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf
+ * @subpackage system.package.validation
+ * @category   Community Framework
+ */
+class PackageValidationArchive implements \RecursiveIterator {
+       /**
+        * package archive object
+        * @var \wcf\system\package\PackageArchive
+        */
+       protected $archive = null;
+       
+       /**
+        * list of direct requirements delivered by this package
+        * @var array<\wcf\system\package\validation\PackageValidationArchive>
+        */
+       protected $children = array();
+       
+       /**
+        * exception occured during validation
+        * @var \Exception
+        */
+       protected $exception = null;
+       
+       /**
+        * children pointer
+        * @var integer
+        */
+       private $position = 0;
+       
+       /**
+        * Creates a new package validation archive instance.
+        * 
+        * @param       string          $archive
+        */
+       public function __construct($archive) {
+               $this->archive = new PackageArchive($archive);
+       }
+       
+       /**
+        * Validates this package and it's delivered requirements.
+        * 
+        * @return      boolean
+        */
+       public function validate() {
+               //
+               // step 1) try to read archive
+               //
+               try {
+                       $this->archive->openArchive();
+               }
+               catch (\Exception $e) {
+                       $this->exception = $e;
+               }
+               
+               //
+               // step 2) traverse requirements
+               //
+               die("<pre>".print_r($this->archive->getOpenRequirements(), true));
+               
+               //
+               // step 3) check requirements against virtual package table
+               //
+               
+               /* TODO: do something */
+               
+               //
+               // step 4) check exclusions
+               //
+               
+               /* TODO: do something */
+               
+               return true;
+               
+       }
+       
+       /**
+        * Returns the exception message.
+        * 
+        * @return      string
+        */
+       public function getExceptionMessage() {
+               if ($this->exception === null) {
+                       return '';
+               }
+               
+               if ($this->exception instanceof PackageValidationException) {
+                       return WCF::getLanguage()->getDynamicVariable('wcf.package.validation.errorCode.' . $this->exception->getCode(), $this->exception->getDetails());
+               }
+               
+               return $this->exception->getMessage();
+       }
+       
+       /**
+        * @see \Iterator::rewind()
+        */
+       public function rewind() {
+               $this->position = 0;
+       }
+       
+       /**
+        * @see \Iterator::valid()
+        */
+       public function valid() {
+               return isset($this->children[$this->position]);
+       }
+       
+       /**
+        * @see \Iterator::next()
+        */
+       public function next() {
+               $this->position++;
+       }
+       
+       /**
+        * @see \Iterator::current()
+        */
+       public function current() {
+               return $this->children[$this->position];
+       }
+       
+       /**
+        * @see \Iterator::key()
+        */
+       public function key() {
+               return $this->position;
+       }
+       
+       /**
+        * @see \RecursiveIterator::getChildren()
+        */
+       public function getChildren() {
+               return $this->children[$this->position];
+       }
+       
+       /**
+        * @see \RecursiveIterator::hasChildren()
+        */
+       public function hasChildren() {
+               return count($this->children) > 0;
+       }
+}
diff --git a/wcfsetup/install/files/lib/system/package/validation/PackageValidationException.class.php b/wcfsetup/install/files/lib/system/package/validation/PackageValidationException.class.php
new file mode 100644 (file)
index 0000000..554e0c1
--- /dev/null
@@ -0,0 +1,103 @@
+<?php
+namespace wcf\system\package\validation;
+use wcf\system\exception\SystemException;
+
+/**
+ * Represents exceptions occured during validation of a package archive. This exception
+ * does not cause the details to be logged.
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2014 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf
+ * @subpackage system.package.validation
+ * @category   Community Framework
+ */
+class PackageValidationException extends SystemException {
+       /**
+        * list of additional details for each subtype
+        * @var array<string>
+        */
+       protected $details = array();
+       
+       /**
+        * missing archive, expects the detail 'archive' and optionally 'targetArchive' (extracting archive from the archive)
+        * @var integer
+        */
+       const FILE_NOT_FOUND = 1;
+       
+       /**
+        * missing package.xml, expects the detail 'archive'
+        * @var integer
+        */
+       const MISSING_PACKAGE_XML = 2;
+       
+       /**
+        * package name violates WCF's schema, expects the detail 'packageName'
+        * @var integer
+        */
+       const INVALID_PACKAGE_NAME = 3;
+       
+       /**
+        * package version violates WCF's schema, expects the detail 'packageVersion'
+        * @var integer
+        */
+       const INVALID_PACKAGE_VERSION = 4;
+       
+       /**
+        * Creates a new PackageArchiveValidationException.
+        * 
+        * @param       integer         $code
+        * @param       array<string>   $details
+        */
+       public function __construct($code, array $details = array()) {
+               parent::__construct($this->getLegacyMessage(), $code);
+               
+               $this->details = $details;
+       }
+       
+       /**
+        * Returns exception details.
+        * 
+        * @return      array<string>
+        */
+       public function getDetails() {
+               return $this->details;
+       }
+       
+       /**
+        * Returns legacy error messages to mimic WCF 2.0.x PackageArchive's exceptions.
+        * 
+        * @return      string
+        */
+       protected function getLegacyMessage() {
+               switch ($this->getCode()) {
+                       case self::FILE_NOT_FOUND:
+                               if (isset($this->details['targetArchive'])) {
+                                       return "tar archive '".$this->details['targetArchive']."' not found in '".$this->details['archive']."'.";
+                               }
+                               
+                               return "unable to find package file '".$this->details['archive']."'";
+                       break;
+                       
+                       case self::MISSING_PACKAGE_XML:
+                               return "package information file '".PackageArchive::INFO_FILE."' not found in '".$this->details['archive']."'";
+                       break;
+                       
+                       case self::INVALID_PACKAGE_NAME:
+                               return "'".$this->details['packageName']."' is not a valid package name.";
+                       break;
+                       
+                       case self::INVALID_PACKAGE_VERSION:
+                               return "package version '".$this->details['packageVersion']."' is invalid";
+                       break;
+               }
+       }
+       
+       /**
+        * @see \wcf\system\exception\LoggedException::logError()
+        */
+       protected function logError() {
+               // do not log errors
+       }
+}
diff --git a/wcfsetup/install/files/lib/system/package/validation/PackageValidationManager.class.php b/wcfsetup/install/files/lib/system/package/validation/PackageValidationManager.class.php
new file mode 100644 (file)
index 0000000..c9c1045
--- /dev/null
@@ -0,0 +1,84 @@
+<?php
+namespace wcf\system\package\validation;
+use wcf\system\SingletonFactory;
+use wcf\data\package\Package;
+
+/**
+ * Manages recursive validation of package archives.
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2014 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    com.woltlab.wcf
+ * @subpackage system.package.validation
+ * @category   Community Framework
+ */
+class PackageValidationManager extends SingletonFactory {
+       /**
+        * package validation archive object
+        * @var \wcf\system\package\validation\PackageValidationArchive
+        */
+       protected $packageValidationArchive = null;
+       
+       /**
+        * virtual package list containing package => packageVersion
+        * @var array<string>
+        */
+       protected $virtualPackageList = array();
+       
+       /**
+        * Validates given archive for existance and ability to be installed/updated
+        * 
+        * @param       string          $archive
+        * @return      boolean
+        */
+       public function validate($archive) {
+               $this->virtualPackageList = array();
+               $this->packageValidationArchive = new PackageValidationArchive($archive);
+               
+               return $this->packageValidationArchive->validate();
+       }
+       
+       /**
+        * Returns package validation archive object.
+        * 
+        * @return      \wcf\system\package\validation\PackageValidationArchive
+        */
+       public function getPackageValidationArchive() {
+               return $this->packageValidationArchive;
+       }
+       
+       /**
+        * Adds a virtual package with the corresponding version, if the package is already known,
+        * the higher version number will be stored.
+        * 
+        * @param       string          $package
+        * @param       string          $packageVersion
+        * @return      boolean
+        */
+       public function addVirtualPackage($package, $packageVersion) {
+               if (isset($this->virtualPackageList[$package])) {
+                       if (Package::compareVersion($packageVersion, $this->virtualPackageList[$package], '<')) {
+                               return false;
+                       }
+               }
+               
+               $this->virtualPackageList[$package] = $packageVersion;
+               
+               return true;
+       }
+       
+       /**
+        * Returns the version number of a virtual package or null if it doesn't exist.
+        * 
+        * @param       string          $package
+        * @return      string
+        */
+       public function geVirtualPackageVersion($package) {
+               if (isset($this->virtualPackageList[$package])) {
+                       return $this->virtualPackageList[$package];
+               }
+               
+               return null;
+       }
+}