diff options
Diffstat (limited to 'www/wiki/extensions/AbuseFilter/includes/parser/AFPData.php')
-rw-r--r-- | www/wiki/extensions/AbuseFilter/includes/parser/AFPData.php | 497 |
1 files changed, 497 insertions, 0 deletions
diff --git a/www/wiki/extensions/AbuseFilter/includes/parser/AFPData.php b/www/wiki/extensions/AbuseFilter/includes/parser/AFPData.php new file mode 100644 index 00000000..ff1faa98 --- /dev/null +++ b/www/wiki/extensions/AbuseFilter/includes/parser/AFPData.php @@ -0,0 +1,497 @@ +<?php + +class AFPData { + // Datatypes + const DINT = 'int'; + const DSTRING = 'string'; + const DNULL = 'null'; + const DBOOL = 'bool'; + const DFLOAT = 'float'; + const DLIST = 'list'; + + // Translation table mapping shell-style wildcards to PCRE equivalents. + // Derived from <http://www.php.net/manual/en/function.fnmatch.php#100207> + private static $wildcardMap = [ + '\*' => '.*', + '\+' => '\+', + '\-' => '\-', + '\.' => '\.', + '\?' => '.', + '\[' => '[', + '\[\!' => '[^', + '\\' => '\\\\', + '\]' => ']', + ]; + + public $type; + public $data; + + /** + * @param string $type + * @param null $val + */ + public function __construct( $type = self::DNULL, $val = null ) { + $this->type = $type; + $this->data = $val; + } + + /** + * @param mixed $var + * @return AFPData + * @throws AFPException + */ + public static function newFromPHPVar( $var ) { + if ( is_string( $var ) ) { + return new AFPData( self::DSTRING, $var ); + } elseif ( is_int( $var ) ) { + return new AFPData( self::DINT, $var ); + } elseif ( is_float( $var ) ) { + return new AFPData( self::DFLOAT, $var ); + } elseif ( is_bool( $var ) ) { + return new AFPData( self::DBOOL, $var ); + } elseif ( is_array( $var ) ) { + $result = []; + foreach ( $var as $item ) { + $result[] = self::newFromPHPVar( $item ); + } + + return new AFPData( self::DLIST, $result ); + } elseif ( is_null( $var ) ) { + return new AFPData(); + } else { + throw new AFPException( + 'Data type ' . gettype( $var ) . ' is not supported by AbuseFilter' + ); + } + } + + /** + * @return AFPData + */ + public function dup() { + return new AFPData( $this->type, $this->data ); + } + + /** + * @param AFPData $orig + * @param string $target + * @return AFPData + */ + public static function castTypes( $orig, $target ) { + if ( $orig->type == $target ) { + return $orig->dup(); + } + if ( $target == self::DNULL ) { + return new AFPData(); + } + + if ( $orig->type == self::DLIST ) { + if ( $target == self::DBOOL ) { + return new AFPData( self::DBOOL, (bool)count( $orig->data ) ); + } + if ( $target == self::DFLOAT ) { + return new AFPData( self::DFLOAT, floatval( count( $orig->data ) ) ); + } + if ( $target == self::DINT ) { + return new AFPData( self::DINT, intval( count( $orig->data ) ) ); + } + if ( $target == self::DSTRING ) { + $s = ''; + foreach ( $orig->data as $item ) { + $s .= $item->toString() . "\n"; + } + + return new AFPData( self::DSTRING, $s ); + } + } + + if ( $target == self::DBOOL ) { + return new AFPData( self::DBOOL, (bool)$orig->data ); + } + if ( $target == self::DFLOAT ) { + return new AFPData( self::DFLOAT, floatval( $orig->data ) ); + } + if ( $target == self::DINT ) { + return new AFPData( self::DINT, intval( $orig->data ) ); + } + if ( $target == self::DSTRING ) { + return new AFPData( self::DSTRING, strval( $orig->data ) ); + } + if ( $target == self::DLIST ) { + return new AFPData( self::DLIST, [ $orig ] ); + } + } + + /** + * @param AFPData $value + * @return AFPData + */ + public static function boolInvert( $value ) { + return new AFPData( self::DBOOL, !$value->toBool() ); + } + + /** + * @param AFPData $base + * @param AFPData $exponent + * @return AFPData + */ + public static function pow( $base, $exponent ) { + $res = pow( $base->toNumber(), $exponent->toNumber() ); + if ( $res === (int)$res ) { + return new AFPData( self::DINT, $res ); + } else { + return new AFPData( self::DFLOAT, $res ); + } + } + + /** + * @param AFPData $a + * @param AFPData $b + * @return AFPData + */ + public static function keywordIn( $a, $b ) { + $a = $a->toString(); + $b = $b->toString(); + + if ( $a == '' || $b == '' ) { + return new AFPData( self::DBOOL, false ); + } + + return new AFPData( self::DBOOL, strpos( $b, $a ) !== false ); + } + + /** + * @param AFPData $a + * @param AFPData $b + * @return AFPData + */ + public static function keywordContains( $a, $b ) { + $a = $a->toString(); + $b = $b->toString(); + + if ( $a == '' || $b == '' ) { + return new AFPData( self::DBOOL, false ); + } + + return new AFPData( self::DBOOL, strpos( $a, $b ) !== false ); + } + + /** + * @param string $value + * @param mixed $list + * @return bool + */ + public static function listContains( $value, $list ) { + // Should use built-in PHP function somehow + foreach ( $list->data as $item ) { + if ( self::equals( $value, $item ) ) { + return true; + } + } + + return false; + } + + /** + * @ToDo Should we also build a proper system to compare arrays with different types? + * @param AFPData $d1 + * @param AFPData $d2 + * @param bool $strict whether to also check types + * @return bool + */ + public static function equals( $d1, $d2, $strict = false ) { + if ( $d1->type != self::DLIST && $d2->type != self::DLIST ) { + $typecheck = $d1->type == $d2->type || !$strict; + return $typecheck && $d1->toString() === $d2->toString(); + } elseif ( $d1->type == self::DLIST && $d2->type == self::DLIST ) { + $data1 = $d1->data; + $data2 = $d2->data; + if ( count( $data1 ) !== count( $data2 ) ) { + return false; + } + $length = count( $data1 ); + for ( $i = 0; $i < $length; $i++ ) { + $result = self::equals( $data1[$i], $data2[$i], $strict ); + if ( $result === false ) { + return false; + } + } + return true; + } else { + // Trying to compare an array to something else + return false; + } + } + + /** + * @param AFPData $str + * @param AFPData $pattern + * @return AFPData + */ + public static function keywordLike( $str, $pattern ) { + $str = $str->toString(); + $pattern = '#^' . strtr( preg_quote( $pattern->toString(), '#' ), self::$wildcardMap ) . '$#u'; + Wikimedia\suppressWarnings(); + $result = preg_match( $pattern, $str ); + Wikimedia\restoreWarnings(); + + return new AFPData( self::DBOOL, (bool)$result ); + } + + /** + * @param AFPData $str + * @param AFPData $regex + * @param int $pos + * @param bool $insensitive + * @return AFPData + * @throws Exception + */ + public static function keywordRegex( $str, $regex, $pos, $insensitive = false ) { + $str = $str->toString(); + $pattern = $regex->toString(); + + $pattern = preg_replace( '!(\\\\\\\\)*(\\\\)?/!', '$1\/', $pattern ); + $pattern = "/$pattern/u"; + + if ( $insensitive ) { + $pattern .= 'i'; + } + + Wikimedia\suppressWarnings(); + $result = preg_match( $pattern, $str ); + Wikimedia\restoreWarnings(); + if ( $result === false ) { + throw new AFPUserVisibleException( + 'regexfailure', + $pos, + [ 'unspecified error in preg_match()', $pattern ] + ); + } + + return new AFPData( self::DBOOL, (bool)$result ); + } + + /** + * @param string $str + * @param string $regex + * @param int $pos + * @return AFPData + */ + public static function keywordRegexInsensitive( $str, $regex, $pos ) { + return self::keywordRegex( $str, $regex, $pos, true ); + } + + /** + * @param AFPData $data + * @return AFPData + */ + public static function unaryMinus( $data ) { + if ( $data->type == self::DINT ) { + return new AFPData( $data->type, -$data->toInt() ); + } else { + return new AFPData( $data->type, -$data->toFloat() ); + } + } + + /** + * @param AFPData $a + * @param AFPData $b + * @param string $op + * @return AFPData + * @throws AFPException + */ + public static function boolOp( $a, $b, $op ) { + $a = $a->toBool(); + $b = $b->toBool(); + if ( $op == '|' ) { + return new AFPData( self::DBOOL, $a || $b ); + } + if ( $op == '&' ) { + return new AFPData( self::DBOOL, $a && $b ); + } + if ( $op == '^' ) { + return new AFPData( self::DBOOL, $a xor $b ); + } + throw new AFPException( "Invalid boolean operation: {$op}" ); // Should never happen. + } + + /** + * @param AFPData $a + * @param AFPData $b + * @param string $op + * @return AFPData + * @throws AFPException + */ + public static function compareOp( $a, $b, $op ) { + if ( $op == '==' || $op == '=' ) { + return new AFPData( self::DBOOL, self::equals( $a, $b ) ); + } + if ( $op == '!=' ) { + return new AFPData( self::DBOOL, !self::equals( $a, $b ) ); + } + if ( $op == '===' ) { + return new AFPData( self::DBOOL, self::equals( $a, $b, true ) ); + } + if ( $op == '!==' ) { + return new AFPData( self::DBOOL, !self::equals( $a, $b, true ) ); + } + $a = $a->toString(); + $b = $b->toString(); + if ( $op == '>' ) { + return new AFPData( self::DBOOL, $a > $b ); + } + if ( $op == '<' ) { + return new AFPData( self::DBOOL, $a < $b ); + } + if ( $op == '>=' ) { + return new AFPData( self::DBOOL, $a >= $b ); + } + if ( $op == '<=' ) { + return new AFPData( self::DBOOL, $a <= $b ); + } + throw new AFPException( "Invalid comparison operation: {$op}" ); // Should never happen + } + + /** + * @param AFPData $a + * @param AFPData $b + * @param string $op + * @param int $pos + * @return AFPData + * @throws AFPUserVisibleException + * @throws AFPException + */ + public static function mulRel( $a, $b, $op, $pos ) { + $a = $a->toNumber(); + $b = $b->toNumber(); + + if ( $op != '*' && $b == 0 ) { + throw new AFPUserVisibleException( 'dividebyzero', $pos, [ $a ] ); + } + + if ( $op == '*' ) { + $data = $a * $b; + } elseif ( $op == '/' ) { + $data = $a / $b; + } elseif ( $op == '%' ) { + $data = $a % $b; + } else { + // Should never happen + throw new AFPException( "Invalid multiplication-related operation: {$op}" ); + } + + if ( $data === (int)$data ) { + $data = intval( $data ); + $type = self::DINT; + } else { + $data = floatval( $data ); + $type = self::DFLOAT; + } + + return new AFPData( $type, $data ); + } + + /** + * @param AFPData $a + * @param AFPData $b + * @return AFPData + */ + public static function sum( $a, $b ) { + if ( $a->type == self::DSTRING || $b->type == self::DSTRING ) { + return new AFPData( self::DSTRING, $a->toString() . $b->toString() ); + } elseif ( $a->type == self::DLIST && $b->type == self::DLIST ) { + return new AFPData( self::DLIST, array_merge( $a->toList(), $b->toList() ) ); + } else { + $res = $a->toNumber() + $b->toNumber(); + if ( $res === (int)$res ) { + return new AFPData( self::DINT, $res ); + } else { + return new AFPData( self::DFLOAT, $res ); + } + } + } + + /** + * @param AFPData $a + * @param AFPData $b + * @return AFPData + */ + public static function sub( $a, $b ) { + $res = $a->toNumber() - $b->toNumber(); + if ( $res === (int)$res ) { + return new AFPData( self::DINT, $res ); + } else { + return new AFPData( self::DFLOAT, $res ); + } + } + + /** Convert shorteners */ + + /** + * @throws MWException + * @return mixed + */ + public function toNative() { + switch ( $this->type ) { + case self::DBOOL: + return $this->toBool(); + case self::DSTRING: + return $this->toString(); + case self::DFLOAT: + return $this->toFloat(); + case self::DINT: + return $this->toInt(); + case self::DLIST: + $input = $this->toList(); + $output = []; + foreach ( $input as $item ) { + $output[] = $item->toNative(); + } + + return $output; + case self::DNULL: + return null; + default: + throw new MWException( "Unknown type" ); + } + } + + /** + * @return bool + */ + public function toBool() { + return self::castTypes( $this, self::DBOOL )->data; + } + + /** + * @return string + */ + public function toString() { + return self::castTypes( $this, self::DSTRING )->data; + } + + /** + * @return float + */ + public function toFloat() { + return self::castTypes( $this, self::DFLOAT )->data; + } + + /** + * @return int + */ + public function toInt() { + return self::castTypes( $this, self::DINT )->data; + } + + /** + * @return int|float + */ + public function toNumber() { + return $this->type == self::DINT ? $this->toInt() : $this->toFloat(); + } + + public function toList() { + return self::castTypes( $this, self::DLIST )->data; + } +} |