initial commit of SVN release repo

This commit is contained in:
Ryan Prather
2018-05-07 10:51:08 -04:00
committed by Ryan Prather
parent 2c25d5e577
commit 8c38a6cdb9
4369 changed files with 728576 additions and 0 deletions

24
inc/vendor/pacificsec/cpe/README.md vendored Normal file
View File

@ -0,0 +1,24 @@
Common Platform Enumeration for PHP
--------------------------------------
*CPE* (this code) is a MIT licensed PHP package, implementing the
CPE standards.
About the CPE standard
----------------------
Common Platform Enumeration (CPE) is a standardized method of describing
and identifying classes of applications, operating systems, and hardware
devices present among an enterprise's computing assets.
For more information, please visit the official website of CPE,
developed by [MITRE](http://cpe.mitre.org/) and maintained by [NIST](http://nvd.nist.gov/cpe.cfm).
Features
--------
- CPE rich comparison.
- CPE Language parsing and evaluation.
- MIT Licensed.

View File

@ -0,0 +1,42 @@
<?php
namespace PacificSec\CPE\Common;
/**
* This class represents a Logical Value. It is based on Java version
* implemented by JKRAUNELIS <jkraunelis@mitre.org>.
*
* @see <a href="http://cpe.mitre.org">cpe.mitre.org</a> for more information.
* @author Antonio Franco
* @email antonio.franco@pacificsec.com
*/
class LogicalValue {
private $any = false;
private $na = false;
// Object must be constructed with the string "ANY" or "NA".
public function __construct($type) {
if ($type == "ANY") {
$this->any = true;
} else if ($type == "NA") {
$this->na = true;
} else {
throw new Exception("LogicalValue must be ANY or NA");
}
}
public function isANY(){
return $this->any;
}
public function isNA(){
return $this->na;
}
public function __toString(){
if ($this->any){
return "ANY";
}
return "NA";
}
}

View File

@ -0,0 +1,167 @@
<?php
namespace PacificSec\CPE\Common;
use \Exception;
/**
* A collection of utility functions for use with the matching and
* naming namespaces. It is based on Java version implemented by
* Joshua Kraunelis <jkraunelis@mitre.org>.
*
* @see <a href="http://cpe.mitre.org">cpe.mitre.org</a> for more information.
* @author Antonio Franco
* @email antonio.franco@pacificsec.com
*/
class Utilities {
/**
* Searches string for special characters * and ?
* @param $string to be searched
* @return true if string contains wildcard, false otherwise
*/
public static function containsWildcards($string) {
if (strpos($string, "*") !== false || strpos($string, "?") !== false) {
if (!(strpos($string, "\\") !== false)) {
return true;
}
return false;
}
return false;
}
/**
* Checks if given number is even or not
* @param $num number to check
* @return true if number is even, false if not
*/
public static function isEvenNumber($num) {
return (is_int($num) && $num % 2 == 0);
}
/**
* Counts the number of escape characters in the string beginning and ending
* at the given indices
* @param $str string to search
* @param $start beginning index
* @param $end ending index
* @return number of escape characters in string
* @todo fix the use of $str. The Java version is also not using this variable.
*/
public static function countEscapeCharacters($str, $start, $end) {
$result = 0;
$active = false;
$i = 0;
while ($i < $end) {
if ($active && ($i >= $start)) {
$result = $result + 1;
}
$i = $i + 1;
}
return $result;
}
/**
* Searches a string for the first unescaped colon and returns the index of
* that colon
* @param $str string to search
* @return index of first unescaped colon, or 0 if not found
*/
public static function getUnescapedColonIndex($str) {
$found = false;
$colon_idx = 0;
$start_idx = 0;
// Find the first non-escaped colon.
while (!$found) {
$colon_idx = strpos($str, ":", $start_idx + 1);
// If no colon is found, return 0.
if ($colon_idx === false) {
return 0;
}
// Peek at character before colon.
if (substr($str, $colon_idx-1, 1) == "\\") {
// If colon is escaped, keep looking.
$start_idx = $colon_idx;
} else {
$found = true;
}
}
return $colon_idx;
}
/**
* Returns true if the string contains only
* alphanumeric characters or the underscore character,
* false otherwise.
* @param $c the string in question
* @return true if $c is alphanumeric or underscore, false if not
*/
public static function isAlphanum($c) {
return (preg_match("/^[a-zA-Z0-9]$/", $c) || $c == "_");
}
/**
* This function is not part of the reference implementation pseudo code
* found in the CPE 2.3 specification. It enforces two rules in the
* specification:
* URI must start with the characters "cpe:/"
* A URI may not contain more than 7 components
* If either rule is violated, a Exception is thrown.
* @param $in string with URI to be validated
*/
public static function validateURI($in) {
// make sure uri starts with cpe:/
if (strpos(strtolower($in), "cpe:/") !== 0) {
throw new Exception("Error: URI must start with 'cpe:/'. Given: " . $in, 0);
}
// make sure uri doesn't contain more than 7 colons
$count = sizeof(explode(":", $in));
if ($count > 8) {
throw new Exception("Error parsing URI. Found " . ($count - 8) . " extra components in: " . $in, 0);
}
}
/**
* This function is not part of the reference implementation pseudo code
* found in the CPE 2.3 specification. It enforces three rules found in the
* specification:
* Formatted string must start with the characters "cpe:2.3:"
* A formatted string must contain 11 components
* A formatted string must not contain empty components
* If any rule is violated, a ParseException is thrown.
* @param $in string with FS to be validated
*/
public static function validateFS($in) {
if (strpos(strtolower($in), "cpe:2.3:") !== 0) {
throw new Exception("Error: Formatted String must start with \"cpe:2.3\". Given: " . $in, 0);
}
$count = 0;
for ($i = 0; $i != strlen($in); $i++){
if (substr($in, $i, 1) == ":"){
if (substr($in, $i - 1, 1) != "\\"){
$count++;
}
if (($i+1) < strlen($in) && substr($in, $i+1, 1) == ":"){
throw new Exception("Error parsing formatted string. Found empty component", 0);
}
}
}
if ($count > 12){
$extra = $count - 12;
$s = "Error parsing formatted string. Found " . $extra . " extra component";
if ($extra > 1){
$s = $s . "s";
}
$s = $s . " in: " . $in;
throw new Exception($s, 0);
}
if ($count < 12){
$missing = 12 - $count;
$s = "Error parsing formatted string. Missing " . $missing . " component";
if ($missing > 1){
$s = $s . "s";
}
throw new Exception($s, 0);
}
}
}

View File

@ -0,0 +1,210 @@
<?php
namespace PacificSec\CPE\Common;
use \Exception;
/**
* The WellFormedName class represents a Well Formed Name, as defined
* in the CPE Specification version 2.3. It is based on Java version
* implemented by jkraunelis <jkraunelis@mitre.org>.
*
* @see <a href="http://cpe.mitre.org">cpe.mitre.org</a> for details.
* @author Antonio Franco
* @email antonio.franco@pacificsec.com
*/
class WellFormedName {
// Underlying wfn representation.
private $wfn = null;
// All permissible WFN attributes as defined by specification.
const ATTRIBUTES = array("part", "vendor", "product", "version",
"update", "edition", "language", "sw_edition", "target_sw",
"target_hw", "other");
/**
* Constructs a new WellFormedName object, setting each component to the
* given parameter value. If a parameter is null, the component is set to
* the default value "ANY".
* @param $part string representing the part component
* @param $vendor string representing the vendor component
* @param $product string representing the product component
* @param $version string representing the version component
* @param $update string representing the update component
* @param $edition string representing the edition component
* @param $language string representing the language component
* @param $sw_edition string representing the sw_edition component
* @param $target_sw string representing the target_sw component
* @param $target_hw string representing the target_hw component
* @param $other string representing the other component
*/
public function __construct($part = null, $vendor = null, $product = null, $version = null,
$update = null, $edition = null, $language = null, $sw_edition = null, $target_sw = null,
$target_hw = null, $other = null) {
$this->wfn = array();
// Constructs a new WellFormedName object, with all components set to the default value "ANY".
if ($part === null && $vendor === null && $product === null && $version === null &&
$update === null && $edition === null && $language === null && $sw_edition === null && $target_sw === null &&
$target_hw === null && $other === null){
foreach (WellFormedName::ATTRIBUTES as $a){
if ($a != "part"){
$this->set($a, new LogicalValue("ANY"));
}
}
return;
}
$this->set("part", $part);
$this->set("vendor", $vendor);
$this->set("product", $product);
$this->set("version", $version);
$this->set("update", $update);
$this->set("edition", $edition);
$this->set("language", $language);
$this->set("sw_edition", $sw_edition);
$this->set("target_sw", $target_sw);
$this->set("target_hw", $target_hw);
$this->set("other", $other);
}
/**
* @param $attribute string representing the component value to get
* @return the string value of the given component, or default value "ANY"
* if the component does not exist
*/
public function get($attribute){
if (array_key_exists($attribute, $this->wfn))
return $this->wfn[$attribute];
else
return new LogicalValue("ANY");
}
/**
* Sets the given attribute to value, if the attribute is in the list of
* permissible components
* @param $attribute string representing the component to set
* @param $value object or string representing the value of the given component
*/
public final function set($attribute, $value){
// Iterate over permissible attributes.
foreach (WellFormedName::ATTRIBUTES as $a){
// If the argument is a valid attribute, set that attribute's value.
if ($attribute == $a) {
// check to see if we're setting a LogicalValue ANY or NA
if ($value instanceof LogicalValue){
// don't allow logical values in part component
if ($attribute == "part"){
var_dump($value); echo "<br>\n";
var_dump($a); echo "<br>\n";
var_dump($attribute); echo "<br>\n";
throw new Exception("Error! part component cannot be a logical value");
}
// put the Object in the ht and break
$this->wfn[$attribute] = $value;
break;
}
if ($value == null || $value == ""){
// if value is null or blank, set attribute to default logical ANY
$this->wfn[$attribute] = new LogicalValue("ANY");
break;
}
$svalue = $value;
// Reg exs
// check for printable characters - no control characters
if (!preg_match("/^[[:print:]]*$/", $svalue)){
throw new Exception("Error! encountered non printable character in: " . $svalue, 0);
}
// svalue has whitespace
if (preg_match("/^.*\\s+.*$/", $svalue)){
throw new Exception("Error! component cannot contain whitespace: " . $svalue, 0);
}
// svalue has more than one unquoted star
if (preg_match("/^\\*{2,}.*$/", $svalue) || preg_match("/^.*\\*{2,}$/", $svalue)){
throw new Exception("Error! component cannot contain more than one * in sequence: " . $svalue, 0);
}
// svalue has unquoted punctuation embedded
if (preg_match("/^.*(?<!\\\\)[\\!\\\"\\#\\$\\%\\&\\'\\(\\)\\+\\,\\.\\/\\:\\;\\<\\=\\>\\@\\[\\]\\^\\`\\{\\|\\}\\~\\-].*$/", $svalue)) {
throw new Exception("Error! component cannot contain unquoted punctuation: " . $svalue, 0);
}
// svalue has an unquoted *
if (preg_match("/^.+(?<!\\\\)[\\*].+$/", $svalue)) {
throw new Exception("Error! component cannot contain embedded *: " . $svalue, 0);
}
// svalue has embedded unquoted ?
// this will catch a single unquoted ?, so make sure we deal with that
if (strpos($svalue, "?") !== false) {
if ($svalue == "?") {
// single ? is valid
$this->wfn[$attribute] = $svalue;
break;
}
// remove leading and trailing ?s
$v = $svalue;
while (strpos($v, "?") === 0) {
// remove all leading ?'s
$v = substr($v, 1);
}
$v = strrev($v);
while (strpos($v, "?") === 0) {
// remove all trailing ?'s (string has been reversed)
$v = substr($v, 1);
}
// back to normal
$v = strrev($v);
// after leading and trailing ?s are removed, check if value
// contains unquoted ?s
if (preg_match("/^.+(?<!\\\\)[\\?].+$/", $v)) {
throw new Exception("Error! component cannot contain embedded ?: " . $svalue, 0);
}
}
// single asterisk is not allowed
if ($svalue == "*") {
throw new Exception("Error! component cannot be a single *: " . $svalue, 0);
}
// quoted hyphen not allowed by itself
if ($svalue == "-") {
throw new Exception("Error! component cannot be quoted hyphen: " . $svalue, 0);
}
// part must be a, o, or h
if ($attribute == "part") {
if ($svalue != "a" && $svalue != "o" && $svalue != "h") {
throw new Exception("Error! part component must be one of the following: 'a', 'o', 'h': " . $svalue, 0);
}
}
// should be good to go
$this->wfn[$attribute] = $svalue;
break;
}
}
}
/**
*
* @return string representation of the WellFormedName
*/
public function __toString() {
$str = "wfn:[";
foreach (WellFormedName::ATTRIBUTES as $attr) {
$str = $str . $attr;
$str = $str . "=";
$o = $this->wfn[$attr];
if ($o instanceof LogicalValue) {
$str = $str . $o;
$str = $str . ", ";
} else {
$str = $str . "\"";
$str = $str . $o;
$str = $str . "\", ";
}
}
$str = substr($str, 0, strlen($str)-1);
$str = substr($str, 0, strlen($str)-1);
$str = $str . "]";
return $str;
}
}

View File

@ -0,0 +1,287 @@
<?php
namespace PacificSec\CPE\Matching;
use PacificSec\CPE\Common\WellFormedName;
use PacificSec\CPE\Common\Utilities;
use PacificSec\CPE\Common\LogicalValue;
use PacificSec\CPE\Naming\CPENameBinder;
use PacificSec\CPE\Naming\CPENameUnbinder;
/**
* The CPENameMatcher is an implementation of the CPE Matching algorithm,
* as specified in the CPE Matching Standard version 2.3. It is based on
* Java version implemented by Joshua Kraunelis <jkraunelis@mitre.org>.
*
* @see <a href="http://cpe.mitre.org">cpe.mitre.org</a> for more information.
* @author Antonio Franco
* @email antonio.franco@pacificsec.com
*/
class CPENameMatcher {
/**
* Tests two Well Formed Names for disjointness.
* @param $source WellFormedName Source WFN
* @param $target WellFormedName Target WFN
* @return true if the names are disjoint, false otherwise
*/
public function isDisjoint(WellFormedName $source, WellFormedName $target) {
// if any pairwise comparison is disjoint, the names are disjoint.
$resultList = $this->compareWFNs($source, $target);
foreach ($resultList as $result){
if ($result == Relation::DISJOINT)
return true;
}
return false;
}
/**
* Tests two Well Formed Names for equality.
* @param $source WellFormedName Source WFN
* @param $target WellFormedName Target WFN
* @return true if the names are equal, false otherwise
*/
public function isEqual(WellFormedName $source, WellFormedName $target) {
// if every pairwise comparison is equal, the names are equal.
$resultList = $this->compareWFNs($source, $target);
foreach ($resultList as $result){
if ($result != Relation::EQUAL){
return false;
}
}
return true;
}
/**
* Tests if the target Well Formed Name is a subset of the source Well Formed
* Name.
* @param $source WellFormedName Source WFN
* @param $target WellFormedName Target WFN
* @return true if the target is a subset of the source, false otherwise
*/
public function isSubset(WellFormedName $source, WellFormedName $target) {
// if any comparison is anything other than subset or equal, then target is
// not a subset of source.
$resultList = $this->compareWFNs($source, $target);
foreach ($resultList as $result){
if ($result != Relation::SUBSET && $result != Relation::EQUAL) {
return false;
}
}
return true;
}
/**
* Tests if the target Well Formed name is a superset of the source Well Formed
* Name.
* @param $source WellFormedName Source WFN
* @param $target WellFormedName Target WFN
* @return true if the target is a superset of the source, false otherwise
*/
public function isSuperset(WellFormedName $source, WellFormedName $target) {
// if any comparison is anything other than superset or equal, then target is not
// a superset of source.
$resultList = $this->compareWFNs($source, $target);
foreach ($resultList as $result){
if ($result != Relation::SUPERSET && $result != Relation::EQUAL) {
return false;
}
}
return true;
}
/**
* Compares each attribute value pair in two Well Formed Names.
* @param $source WellFormedName Source WFN
* @param $target WellFormedName Target WFN
* @return A array mapping attribute string to attribute value Relation
*/
public function compareWFNs(WellFormedName $source, WellFormedName $target) {
$result = array();
$result["part"] = $this->compare($source->get("part"), $target->get("part"));
$result["vendor"] = $this->compare($source->get("vendor"), $target->get("vendor"));
$result["product"] = $this->compare($source->get("product"), $target->get("product"));
$result["version"] = $this->compare($source->get("version"), $target->get("version"));
$result["update"] = $this->compare($source->get("update"), $target->get("update"));
$result["edition"] = $this->compare($source->get("edition"), $target->get("edition"));
$result["language"] = $this->compare($source->get("language"), $target->get("language"));
$result["sw_edition"] = $this->compare($source->get("sw_edition"), $target->get("sw_edition"));
$result["target_sw"] = $this->compare($source->get("target_sw"), $target->get("target_sw"));
$result["target_hw"] = $this->compare($source->get("target_hw"), $target->get("target_hw"));
$result["other"] = $this->compare($source->get("other"), $target->get("other"));
return $result;
}
/**
* Compares an attribute value pair.
* @param $source Source attribute value.
* @param $target Target attribute value.
* @return The relation between the two attribute values.
*/
private function compare($source, $target) {
// matching is case insensitive, convert strings to lowercase.
if ($this->isString($source)) {
$source = strtolower($source);
}
if ($this->isString($target)) {
$target = strtolower($target);
}
// Unquoted wildcard characters yield an undefined result.
if ($this->isString($target) && Utilities::containsWildcards($target)) {
return Relation::UNDEFINED;
}
// If source and target values are equal, then result is equal.
if ($source == $target) {
return Relation::EQUAL;
}
// Check to see if source or target are Logical Values.
$lvSource = null;
$lvTarget = null;
if ($source instanceof LogicalValue) {
$lvSource = $source;
}
if ($target instanceof LogicalValue) {
$lvTarget = $target;
}
if ($lvSource != null && $lvTarget != null) {
// If Logical Values are equal, result is equal.
if ($lvSource->isANY() == $lvTarget->isANY() || $lvSource->isNA() == $lvTarget->isNA()) {
return Relation::EQUAL;
}
}
// If source value is ANY, result is a superset.
if ($lvSource != null) {
if ($lvSource->isANY()) {
return Relation::SUPERSET;
}
}
// If target value is ANY, result is a subset.
if ($lvTarget != null) {
if ($lvTarget->isANY()) {
return Relation::SUBSET;
}
}
// If source or target is NA, result is disjoint.
if ($lvSource != null) {
if ($lvSource->isNA()) {
return Relation::DISJOINT;
}
}
if ($lvTarget != null) {
if ($lvTarget->isNA()) {
return Relation::DISJOINT;
}
}
// only Strings will get to this point, not LogicalValues
return $this->compareStrings($source, $target);
}
/**
* Compares a source string to a target string, and addresses the condition
* in which the source string includes unquoted special characters. It
* performs a simple regular expression match, with the assumption that
* (as required) unquoted special characters appear only at the beginning
* and/or the end of the source string. It also properly differentiates
* between unquoted and quoted special characters.
*
* @param $source string Source attribute value.
* @param $target string Target attribute value.
* @return Relation between source and target Strings.
*/
private function compareStrings($source, $target) {
$start = 0;
$end = strlen($source);
$begins = 0;
$ends = 0;
$index = 0; $leftover = 0; $escapes = 0;
if (substr($source, 0, 1) == "*") {
$start = 1;
$begins = -1;
} else {
while (($start < strlen($source)) && (substr($source, $start, 1) == "?")) {
$start = $start + 1;
$begins = $begins + 1;
}
}
if ((substr($source, $end - 1, 1) == "*") && ($this->isEvenWildcards($source, $end - 1))) { //TODO
$end = $end - 1;
$ends = -1;
} else {
while (($end > 0) && substr($source, $end - 1, 1) == "?" && ($this->isEvenWildcards($source, $end - 1))) { //TODO
$end = $end - 1;
$ends = $ends + 1;
}
}
$source = substr($source, $start, $end-$start);
$index = -1;
$leftover = strlen($target);
while ($leftover > 0) {
$index = strpos($target, $source, $index + 1);
if ($index === false) {
break;
}
$escapes = Utilities::countEscapeCharacters($target, 0, $index);
if (($index > 0) && ($begins != -1) && ($begins < ($index - $escapes))) {
break;
}
$escapes = Utilities::countEscapeCharacters($target, $index + 1, strlen($target));
$leftover = strlen($target) - $index - $escapes - strlen($source);
if (($leftover > 0) && (($ends != -1) && ($leftover > $ends))) {
continue;
}
return Relation::SUPERSET;
}
return Relation::DISJOINT;
}
/**
* Searches a string for the backslash character
* @param $str string to search in
* @param $idx end index
* @return true if the number of backslash characters is even, false if odd
*/
private function isEvenWildcards($str, $idx) {
$result = 0;
while (($idx > 0) && (strpos($str, "\\", $idx - 1)) !== false) {
$idx = $idx - 1;
$result = $result + 1;
}
return Utilities::isEvenNumber($result);
}
/**
* Tests if an Object is an instance of the String class
* @param $arg the var to test
* @return true if arg is a string, false if not
*/
private function isString($arg) {
is_string($arg);
}
/*
* Static method to demonstrate this class.
*/
public static function test() {
// Examples.
$wfn = new WellFormedName("a", "microsoft", "internet_explorer", "8\\.0\\.6001", "beta", new LogicalValue("ANY"), "sp2", null, null, null, null);
$wfn2 = new WellFormedName("a", "microsoft", "internet_explorer", new LogicalValue("ANY"), new LogicalValue("ANY"), new LogicalValue("ANY"), new LogicalValue("ANY"), new LogicalValue("ANY"), new LogicalValue("ANY"), new LogicalValue("ANY"), new LogicalValue("ANY"));
$cpenm = new CPENameMatcher();
$cpenu = new CPENameUnbinder();
$cpenb = new CPENameBinder();
$wfn = $cpenu->unbindURI($cpenb->bindToURI($wfn));
$wfn2 = $cpenu->unbindFS($cpenb->bindToFS($wfn2));
var_dump($cpenm->isDisjoint($wfn, $wfn2)); // false
var_dump($cpenm->isEqual($wfn, $wfn2)); // false
var_dump($cpenm->isSubset($wfn, $wfn2)); // true, $wfn2 is a subset of wfn
var_dump($cpenm->isSuperset($wfn, $wfn2)); // false
$wfn = $cpenu->unbindFS("cpe:2.3:a:adobe:*:9.*:*:PalmOS:*:*:*:*:*");
$wfn2 = $cpenu->unbindURI("cpe:/a::Reader:9.3.2:-:-");
var_dump($cpenm->isDisjoint($wfn, $wfn2)); // true, $wfn2 and wfn are disjoint
var_dump($cpenm->isEqual($wfn, $wfn2)); // false
var_dump($cpenm->isSubset($wfn, $wfn2)); // false
var_dump($cpenm->isSuperset($wfn, $wfn2)); // false
}
}

View File

@ -0,0 +1,17 @@
<?php
namespace PacificSec\CPE\Matching;
/**
* Class for relational values. It is based on Java version implemented by
* Joshua Kraunelis <jkraunelis@mitre.org>.
*
* @author Antonio Franco
* @email antonio.franco@pacificsec.com
*/
class Relation {
const DISJOINT = 1;
const SUBSET = 2;
const SUPERSET = 3;
const EQUAL = 4;
const UNDEFINED = 5;
}

View File

@ -0,0 +1,370 @@
<?php
namespace PacificSec\CPE\Naming;
use PacificSec\CPE\Common\WellFormedName;
use PacificSec\CPE\Common\Utilities;
use PacificSec\CPE\Common\LogicalValue;
/**
* The CPENameBinder class is a simple implementation
* of the CPE Name binding algorithm, as specified in the
* CPE Naming Standard version 2.3. It is based on Java version
* implemented by Joshua Kraunelis <jkraunelis@mitre.org>.
*
* @see <a href="http://cpe.mitre.org">cpe.mitre.org</a> for more information.
* @author Antonio Franco
* @email antonio.franco@pacificsec.com
*/
class CPENameBinder {
/**
* Binds a {@link WellFormedName} object to a URI.
* @param $w WellFormedName to be bound to URI
* @return URI binding of WFN
*/
public function bindToURI(WellFormedName $w) {
// Initialize the output with the CPE v2.2 URI prefix.
$uri = "cpe:/";
// Define the attributes that correspond to the seven components in a v2.2. CPE.
$attributes = array("part", "vendor", "product", "version", "update", "edition", "language");
// Iterate over the well formed name
foreach ($attributes as $a) {
$v = "";
if ($a == "edition") {
// Call the pack() helper function to compute the proper
// binding for the edition element.
$ed = $this->bindValueForURI($w->get("edition"));
$sw_ed = $this->bindValueForURI($w->get("sw_edition"));
$t_sw = $this->bindValueForURI($w->get("target_sw"));
$t_hw = $this->bindValueForURI($w->get("target_hw"));
$oth = $this->bindValueForURI($w->get("other"));
$v = $this->pack($ed, $sw_ed, $t_sw, $t_hw, $oth);
} else {
// Get the value for a in w, then bind to a string
// for inclusion in the URI.
$v = $this->bindValueForURI($w->get($a));
}
// Append v to the URI then add a colon.
$uri = $uri . $v . ":";
}
// Return the URI string, with trailing colons trimmed.
return $this->trim($uri);
}
/**
* Top-level function used to bind WFN w to formatted string.
* @param $w WellFormedName to bind
* @return Formatted String
*/
public function bindToFS(WellFormedName $w) {
// Initialize the output with the CPE v2.3 string prefix.
$fs = "cpe:2.3:";
foreach (array("part", "vendor", "product", "version",
"update", "edition", "language", "sw_edition", "target_sw",
"target_hw", "other") as $a) {
$v = $this->bindValueForFS($w->get($a));
$fs = $fs . $v;
// add a colon except at the very end
if (strpos($a, "other") === false) {
$fs = $fs . ":";
}
}
return $fs;
}
/**
* Convert the value v to its proper string representation for insertion to
* formatted string.
* @param $v value to convert
* @return Formatted value
*/
private function bindValueForFS($v) {
if ($v instanceof LogicalValue) {
$l = $v;
// The value NA binds to a blank.
if ($l->isANY()) {
return "*";
}
// The value NA binds to a single hyphen.
if ($l->isNA()) {
return "-";
}
}
return $this->processQuotedChars($v);
}
/**
* Inspect each character in string s. Certain nonalpha characters pass
* thru without escaping into the result, but most retain escaping.
* @param $s
* @return
*/
private function processQuotedChars($s) {
$result = "";
$idx = 0;
while ($idx < strlen($s)) {
$c = substr($s, $idx, 1);
if ($c != "\\") {
// unquoted characters pass thru unharmed.
$result = $result . $c;
} else {
// escaped characters are examined.
$nextchr = substr($s, $idx + 1, 1);
// the period, hyphen and underscore pass unharmed.
if ($nextchr == "." || $nextchr == "-" || $nextchr == "_") {
$result = $result . $nextchr;
$idx = $idx + 2;
continue;
} else {
// all others retain escaping.
$result = $result . "\\" . $nextchr;
$idx = $idx + 2;
continue;
}
}
$idx = $idx + 1;
}
return $result;
}
/**
* Converts a string to the proper string for including in
* a CPE v2.2-conformant URI. The logical value ANY binds
* to the blank in the 2.2-conformant URI.
* @param $s string to be converted
* @return converted string
*/
private function bindValueForURI($s) {
if ($s instanceof LogicalValue) {
$l = $s;
// The value NA binds to a blank.
if ($l->isANY()) {
return "";
}
// The value NA binds to a single hyphen.
if ($l->isNA()) {
return "-";
}
}
// If we get here, we're dealing with a string value.
return $this->transformForURI($s);
}
/**
* Scans an input string and performs the following transformations:
* - Pass alphanumeric characters thru untouched
* - Percent-encode quoted non-alphanumerics as needed
* - Unquoted special characters are mapped to their special forms
* @param $s string to be transformed
* @return transformed string
*/
private function transformForURI($s) {
$result = "";
$idx = 0;
while ($idx < strlen($s)) {
// Get the idx'th character of s.
$thischar = substr($s, $idx, 1);
// Alphanumerics (incl. underscore) pass untouched.
if (Utilities::isAlphanum($thischar)) {
$result = $result . $thischar;
$idx = $idx + 1;
continue;
}
// Check for escape character.
if ($thischar == "\\") {
$idx = $idx + 1;
$nxtchar = substr($s, $idx, 1);
$result = $result . $this->pctEncode($nxtchar);
$idx = $idx + 1;
continue;
}
// Bind the unquoted '?' special character to "%01".
if ($thischar == "?") {
$result = $result . "%01";
}
// Bind the unquoted '*' special character to "%02".
if ($thischar == "*") {
$result = $result . "%02";
}
$idx = $idx + 1;
}
return $result;
}
/**
* Returns the appropriate percent-encoding of character c.
* Certain characters are returned without encoding.
* @param $c the single character string to be encoded
* @return the percent encoded string
*/
private function pctEncode($c) {
if ($c == "!") {
return "%21";
}
if ($c == "\"") {
return "%22";
}
if ($c == "#") {
return "%23";
}
if ($c == "$") {
return "%24";
}
if ($c == "%") {
return "%25";
}
if ($c == "&") {
return "%26";
}
if ($c == "'") {
return "%27";
}
if ($c == "(") {
return "%28";
}
if ($c == ")") {
return "%29";
}
if ($c == "*") {
return "%2a";
}
if ($c == "+") {
return "%2b";
}
if ($c == ",") {
return "%2c";
}
// bound without encoding.
if ($c == "-") {
return $c;
}
// bound without encoding.
if ($c == ".") {
return $c;
}
if ($c == "/") {
return "%2f";
}
if ($c == ":") {
return "%3a";
}
if ($c == ";") {
return "%3b";
}
if ($c == "<") {
return "%3c";
}
if ($c == "=") {
return "%3d";
}
if ($c == ">") {
return "%3e";
}
if ($c == "?") {
return "%3f";
}
if ($c == "@") {
return "%40";
}
if ($c == "[") {
return "%5b";
}
if ($c == "\\") {
return "%5c";
}
if ($c == "]") {
return "%5d";
}
if ($c == "^") {
return "%5e";
}
if ($c == "`") {
return "%60";
}
if ($c == "{") {
return "%7b";
}
if ($c == "|") {
return "%7c";
}
if ($c == "}") {
return "%7d";
}
if ($c == "~") {
return "%7d";
}
// Shouldn't reach here, return original character
return $c;
}
/**
* Packs the values of the five arguments into the single
* edition component. If all the values are blank, the
* function returns a blank.
* @param $ed edition string
* @param $sw_ed software edition string
* @param $t_sw target software string
* @param $t_hw target hardware string
* @param $oth other edition information string
* @return the packed string, or blank
*/
private function pack($ed, $sw_ed, $t_sw, $t_hw, $oth) {
if ($sw_ed == "" && $t_sw == "" && $t_hw == "" && $oth == "") {
// All the extended attributes are blank, so don't do
// any packing, just return ed.
return $ed;
}
// Otherwise, pack the five values into a single string
// prefixed and internally delimited with the tilde.
return "~" . $ed . "~" . $sw_ed . "~" . $t_sw . "~" . $t_hw . "~" . $oth;
}
/**
* Removes trailing colons from the URI.
* @param $s the string to be trimmed
* @return the trimmed string
*/
private function trim($s) {
$s1 = strrev($s);
$idx = 0;
for ($i = 0; $i != strlen($s1); $i++) {
if (substr($s1, $i, 1) == ":") {
$idx = $idx + 1;
} else {
break;
}
}
// Return the substring after all trailing colons,
// reversed back to its original character order.
return strrev(substr($s1, $idx, strlen($s1)-$idx));
}
/*
* Static method to demonstrate this class.
*/
public static function test() {
// A few examples.
echo "Testing CPENamingBind...<br>\n";
$wfn = new WellFormedName("a", "microsoft", "internet_explorer", "8\\.0\\.6001",
"beta", new LogicalValue("ANY"), "sp2", null, null, null, null);
$wfn2 = new WellFormedName();
$wfn2->set("part", "a");
$wfn2->set("vendor", "foo\\\$bar");
$wfn2->set("product", "insight");
$wfn2->set("version", "7\\.4\\.0\\.1570");
$wfn2->set("target_sw", "win2003");
$wfn2->set("update", new LogicalValue("NA"));
$wfn2->set("sw_edition", "online");
$wfn2->set("target_hw", "x64");
$cpenb = new CPENameBinder();
echo $cpenb->bindToURI($wfn) . "<br>\n";
echo $cpenb->bindToFS($wfn2) . "<br>\n";
}
}

View File

@ -0,0 +1,466 @@
<?php
namespace PacificSec\CPE\Naming;
use PacificSec\CPE\Common\WellFormedName;
use PacificSec\CPE\Common\Utilities;
use PacificSec\CPE\Common\LogicalValue;
use \Exception;
/**
* The CPENameUnBinder class is a simple implementation
* of the CPE Name unbinding algorithm, as specified in the
* CPE Naming Standard version 2.3. It is based on Java version
* implemented by Joshua Kraunelis <jkraunelis@mitre.org>.
*
* @see <a href="http://cpe.mitre.org">cpe.mitre.org</a> for more information.
* @author Antonio Franco
* @email antonio.franco@pacificsec.com
*/
class CPENameUnbinder {
/**
* Top level function used to unbind a URI to a WFN.
* @param $uri string representing the URI to be unbound.
* @return WellFormedName representing the unbound URI.
* @throws Exception representing parsing errors.
*/
public function unbindURI($uri) {
// Validate the URI
Utilities::validateURI($uri);
// Initialize the empty WFN.
$result = new WellFormedName();
for ($i = 0; $i != 8; $i++) {
// get the i'th component of uri
$v = $this->getCompURI($uri, $i);
switch ($i) {
case 1:
$result->set("part", $this->decode($v));
break;
case 2:
$result->set("vendor", $this->decode($v));
break;
case 3:
$result->set("product", $this->decode($v));
break;
case 4:
$result->set("version", $this->decode($v));
break;
case 5:
$result->set("update", $this->decode($v));
break;
case 6:
// Special handling for edition component.
// Unpack edition if needed.
if ($v == "" || $v == "-"
|| substr($v, 0, 1) != "~") {
// Just a logical value or a non-packed value.
// So unbind to legacy edition, leaving other
// extended attributes unspecified.
$result->set("edition", $this->decode($v));
} else {
// We have five values packed together here.
$this->unpack($v, $result);
}
break;
case 7:
$result->set("language", $this->decode($v));
break;
}
}
return $result;
}
/**
* Top level function to unbind a formatted string to WFN.
* @param $fs Formatted string to unbind
* @return WellFormedName
* @throws Exception representing parsing error
*/
public function unbindFS($fs) {
// Validate the formatted string
Utilities::validateFS($fs);
// Initialize empty WFN
$result = new WellFormedName();
// The cpe scheme is the 0th component, the cpe version is the 1st.
// So we start parsing at the 2nd component.
for ($a = 2; $a != 13; $a++) {
// Get the a'th string field.
$v = $this->getCompFS($fs, $a);
// Unbind the string.
$v = $this->unbindValueFS($v);
// Set the value of the corresponding attribute.
switch ($a) {
case 2:
$result->set("part", $v);
break;
case 3:
$result->set("vendor", $v);
break;
case 4:
$result->set("product", $v);
break;
case 5:
$result->set("version", $v);
break;
case 6:
$result->set("update", $v);
break;
case 7:
$result->set("edition", $v);
break;
case 8:
$result->set("language", $v);
break;
case 9:
$result->set("sw_edition", $v);
break;
case 10:
$result->set("target_sw", $v);
break;
case 11:
$result->set("target_hw", $v);
break;
case 12:
$result->set("other", $v);
break;
}
}
return $result;
}
/**
* Returns the i'th field of the formatted string. The colon is the field
* delimiter unless prefixed by a backslash.
* @param $fs formatted string to retrieve from
* @param $i index of field to retrieve from fs.
* @return value of index of formatted string
*/
private function getCompFS($fs, $i) {
if ($i == 0) {
// return the substring from index 0 to the first occurence of an
// unescaped colon
$colon_idx = Utilities::getUnescapedColonIndex($fs);
// If no colon is found, we are at the end of the formatted string,
// so just return what's left.
if ($colon_idx == 0) {
return $fs;
}
return substr($fs, 0, $colon_idx);
} else {
$substrStart = Utilities::getUnescapedColonIndex($fs) + 1;
$substrLength = strlen($fs) - $substrStart;
return $this->getCompFS(substr($fs, $substrStart, $substrLength), $i - 1);
}
}
/**
* Takes a string value and returns the appropriate logical value if string
* is the bound form of a logical value. If string is some general value
* string, add quoting of non-alphanumerics as needed.
* @param $s value to be unbound
* @return logical value or quoted string
* @throws Exception representing parsing errors
*/
private function unbindValueFS($s) {
if ($s == "*") {
return new LogicalValue("ANY");
}
if ($s == "-") {
return new LogicalValue("NA");
}
return $this->addQuoting($s);
}
/**
* Inspect each character in a string, copying quoted characters, with
* their escaping, into the result. Look for unquoted non alphanumerics
* and if not "*" or "?", add escaping.
* @param $s
* @return
* @throws Exception representing parsing errors.
*/
private function addQuoting($s) {
$result = "";
$idx = 0;
$embedded = false;
while ($idx < strlen($s)) {
$c = substr($s, $idx, 1);
if (Utilities::isAlphanum($c) || $c == "_") {
// Alphanumeric characters pass untouched.
$result = $result . $c;
$idx = $idx + 1;
$embedded = true;
continue;
}
if ($c == "\\") {
// Anything quoted in the bound string stays quoted in the
// unbound string.
$result = $result . substr($s, $idx, 2);
$idx = $idx + 2;
$embedded = true;
continue;
}
if ($c == "*") {
// An unquoted asterisk must appear at the beginning or the end
// of the string.
if ($idx == 0 || $idx == strlen($s) - 1) {
$result = $result . $c;
$idx = $idx + 1;
$embedded = true;
continue;
} else {
throw new Exception("Error! cannot have unquoted * embedded in formatted string.", 0);
}
}
if ($c == "?") {
// An unquoted question mark must appear at the beginning or
// end of the string, or in a leading or trailing sequence.
if ( // ? legal at beginning or end
(($idx == 0) || ($idx == (strlen($s) - 1)))
// embedded is false, so must be preceded by ?
|| (!$embedded && (substr($s, $idx - 1, 1) == "?"))
// embedded is true, so must be followed by ?
|| ($embedded && (substr($s, $idx + 1, 1) == "?"))) {
$result = $result . $c;
$idx = $idx + 1;
$embedded = false;
continue;
} else {
throw new Exception("Error! cannot have unquoted ? embedded in formatted string.", 0);
}
}
// All other characters must be quoted.
$result = $result . "\\" . $c;
$idx = $idx + 1;
$embedded = true;
}
return $result;
}
/**
* Return the i'th component of the URI.
* @param $uri string representation of URI to retrieve components from.
* @param $i Index of component to return.
* @return If i = 0, returns the URI scheme. Otherwise, returns the i'th
* component of uri.
*/
private function getCompURI($uri, $i) {
if ($i == 0) {
return substr($uri, $i, strpos($uri, "/"));
}
$sa = explode(":", $uri);
// If requested component exceeds the number
// of components in URI, return blank
if ($i >= sizeof($sa)) {
return "";
}
if ($i === 1) {
return substr($sa[$i], 1, strlen($sa[$i])-1);
}
return $sa[$i];
}
/**
* Scans a string and returns a copy with all percent-encoded characters
* decoded. This function is the inverse of pctEncode() defined in the
* CPENameBinder class. Only legal percent-encoded forms are decoded.
* Others raise a ParseException.
* @param $s string to be decoded
* @return decoded string
* @throws Exception representing parsing errors
* @see CPENameBinder#pctEncode
*/
private function decode($s) {
if ($s == "") {
return new LogicalValue("ANY");
}
if ($s == "-") {
return new LogicalValue("NA");
}
// Start the scanning loop.
// Normalize: convert all uppercase letters to lowercase first.
$s = strtolower($s);
$result = "";
$idx = 0;
$embedded = false;
while ($idx < strlen($s)) {
// Get the idx'th character of s.
$c = substr($s, $idx, 1);
// Deal with dot, hyphen, and tilde: decode with quoting.
if ($c == "." || $c == "-" || $c == "~") {
$result = $result . "\\" . $c;
$idx = $idx + 1;
// a non-%01 encountered.
$embedded = true;
continue;
}
if ($c != "%") {
$result = $result . $c;
$idx = $idx + 1;
// a non-%01 encountered.
$embedded = true;
continue;
}
// We get here if we have a substring starting w/ '%'.
$form = substr($s, $idx, 3);
if ($form == "%01") {
if (($idx == 0)
|| ($idx == strlen($s) - 3)
|| (!$embedded && substr($s, $idx - 3, 2) == "%01")
|| ($embedded && (strlen($s) >= $idx + 6))
&& (substr($s, $idx + 3, 3) == "%01")) {
$result = $result . "?";
$idx = $idx + 3;
continue;
} else {
throw new Exception("Error decoding string", 0);
}
} else if ($form == "%02") {
if (($idx == 0) || ($idx == (strlen($s) - 3))) {
$result = $result . "*";
} else {
throw new Exception("Error decoding string", 0);
}
} else if ($form == "%21") {
$result = $result . "\\!";
} else if ($form == "%22") {
$result = $result . "\\\"";
} else if ($form == "%23") {
$result = $result . "\\#";
} else if ($form == "%24") {
$result = $result . "\\$";
} else if ($form == "%25") {
$result = $result . "\\%";
} else if ($form == "%26") {
$result = $result . "\\&";
} else if ($form == "%27") {
$result = $result . "\\'";
} else if ($form == "%28") {
$result = $result . "\\(";
} else if ($form == "%29") {
$result = $result . "\\)";
} else if ($form == "%2a") {
$result = $result . "\\*";
} else if ($form == "%2b") {
$result = $result . "\\+";
} else if ($form == "%2c") {
$result = $result . "\\,";
} else if ($form == "%2f") {
$result = $result . "\\/";
} else if ($form == "%3a") {
$result = $result . "\\))";
} else if ($form == "%3b") {
$result = $result . "\\;";
} else if ($form == "%3c") {
$result = $result . "\\<";
} else if ($form == "%3d") {
$result = $result . "\\=";
} else if ($form == "%3e") {
$result = $result . "\\>";
} else if ($form == "%3f") {
$result = $result . "\\?";
} else if ($form == "%40") {
$result = $result . "\\@";
} else if ($form == "%5b") {
$result = $result . "\\[";
} else if ($form == "%5c") {
$result = $result . "\\\\";
} else if ($form == "%5d") {
$result = $result . "\\]";
} else if ($form == "%5e") {
$result = $result . "\\^";
} else if ($form == "%60") {
$result = $result . "\\`";
} else if ($form == "%7b") {
$result = $result . "\\{";
} else if ($form == "%7c") {
$result = $result . "\\|";
} else if ($form == "%7d") {
$result = $result . "\\}";
} else if ($form == "%7e") {
$result = $result . "\\~";
} else {
throw new Exception("Unknown form: " . $form, 0);
}
$idx = $idx + 3;
$embedded = true;
}
return $result;
}
/**
* Unpacks the elements in s and sets the attributes in the given
* WellFormedName accordingly.
* @param $s packed string
* @param $wfn WellFormedName
* @return The augmented WellFormedName.
*/
private function unpack($s, WellFormedName $wfn) {
// Parse out the five elements.
$start = 1;
$ed = ""; $sw_edition = ""; $t_sw = ""; $t_hw = ""; $oth = "";
$end = strpos($s, "~", $start);
if ($start == $end) {
$ed = "";
} else {
$ed = substr($s, $start, $end-$start);
}
$start = $end + 1;
$end = strpos($s, "~", $start);
if ($start == $end) {
$sw_edition = "";
} else {
$sw_edition = substr($s, $start, $end-$start);
}
$start = $end + 1;
$end = strpos($s, "~", $start);
if ($start == $end) {
$t_sw = "";
} else {
$t_sw = substr($s, $start, $end-$start);
}
$start = $end + 1;
$end = strpos($s, "~", $start);
if ($start == $end) {
$t_hw = "";
} else {
$t_hw = substr($s, $start, $end-$start);
}
$start = $end + 1;
if ($start >= strlen($s)) {
$oth = "";
} else {
$oth = substr($s, $start, strlen($s) - 1 - $start);
}
// Set each component in the WFN.
try {
$wfn->set("edition", $this->decode($ed));
$wfn->set("sw_edition", $this->decode($sw_edition));
$wfn->set("target_sw", $this->decode($t_sw));
$wfn->set("target_hw", $this->decode($t_hw));
$wfn->set("other", $this->decode($oth));
} catch (Exception $e) {
echo $e->getMessage() . "\n";
}
return $wfn;
}
/*
* Static method to demonstrate this class.
*/
public static function test() {
// A few examples.
echo "Testing CPENamingUnbind...<br>\n";
$cpenu = new CPENameUnbinder();
$wfn = $cpenu->unbindURI("cpe:/a:microsoft:internet_explorer%01%01%01%01:?:beta");
echo $wfn . "<br>\n";
$wfn = $cpenu->unbindURI("cpe:/a:microsoft:internet_explorer:8.%2a:sp%3f");
echo $wfn . "<br>\n";
$wfn = $cpenu->unbindURI("cpe:/a:microsoft:internet_explorer:8.%02:sp%01");
echo $wfn . "<br>\n";
$wfn = $cpenu->unbindURI("cpe:/a:hp:insight_diagnostics:7.4.0.1570::~~online~win2003~x64~");
echo $wfn . "<br>\n";
echo $cpenu->unbindFS("cpe:2.3:a:micr\\?osoft:internet_explorer:8.0.6001:beta:*:*:*:*:*:*") . "<br>\n";
}
}