Seditio Source
Root |
./othercms/xenForo 2.2.8/src/XF/InputFilterer.php
<?php

namespace XF;

use function
array_key_exists, floatval, intval, is_array, is_scalar, is_string, strval;

class
InputFilterer
{
    protected
$stringCleaning = [
       
// strip a bunch of control characters
       
"\x00" => '', // null
       
"\x01" => '', // start of heading
       
"\x02" => '', // start of text
       
"\x03" => '', // end of text
       
"\x04" => '', // end of transmission
       
"\x05" => '', // enquiry
       
"\x06" => '', // ack
       
"\x07" => '', // bell
       
"\x08" => '', // backspace
       
"\x0B" => '', // vertical tab
       
"\x0C" => '', // form feed
       
"\x0D" => '', // carriage returns, because jQuery does so in .val()
       
"\x0E" => '', // shift out
       
"\x0F" => '', // shift in
       
"\x10" => '', // data link escape
       
"\x11" => '', // device ctrl 1
       
"\x12" => '', // device ctrl 2
       
"\x13" => '', // device ctrl 3
       
"\x14" => '', // device ctrl 4
       
"\x15" => '', // negative ack
       
"\x16" => '', // sync idle
       
"\x17" => '', // end of transmission block
       
"\x18" => '', // cancel
       
"\x19" => '', // end of medium
       
"\x1A" => '', // substitute
       
"\x1B" => '', // escape
       
"\x1C" => '', // file sep
       
"\x1D" => '', // group sep
       
"\x1E" => '', // record sep
       
"\x1F" => '', // unit sep
       
"\x7F" => '', // delete

        // more UTF-8 control characters
       
"\xC2\x80" => '',
       
"\xC2\x81" => '',
       
"\xC2\x82" => '',
       
"\xC2\x83" => '',
       
"\xC2\x84" => '',
       
"\xC2\x85" => '',
       
"\xC2\x86" => '',
       
"\xC2\x87" => '',
       
"\xC2\x88" => '',
       
"\xC2\x89" => '',
       
"\xC2\x8A" => '',
       
"\xC2\x8B" => '',
       
"\xC2\x8C" => '',
       
"\xC2\x8D" => '',
       
"\xC2\x8E" => '',
       
"\xC2\x8F" => '',
       
"\xC2\x90" => '',
       
"\xC2\x91" => '',
       
"\xC2\x92" => '',
       
"\xC2\x93" => '',
       
"\xC2\x94" => '',
       
"\xC2\x95" => '',
       
"\xC2\x96" => '',
       
"\xC2\x97" => '',
       
"\xC2\x98" => '',
       
"\xC2\x99" => '',
       
"\xC2\x9A" => '',
       
"\xC2\x9B" => '',
       
"\xC2\x9C" => '',
       
"\xC2\x9D" => '',
       
"\xC2\x9E" => '',
       
"\xC2\x9F" => '',

       
"\xC2\xA0" => ' ', // nbsp
       
"\xC2\xAD" => '', // soft hyphen
       
"\xE2\x80\x8B" => '', // zero width space
       
"\xEF\xBB\xBF" => '' // zero width nbsp
   
];

    protected
$fullUnicode = false;

    public function
__construct($fullUnicode = false)
    {
       
$this->fullUnicode = $fullUnicode;
    }

    public function
filterArray(array $array, array $filters)
    {
       
$output = [];

        foreach (
$filters AS $key => $type)
        {
           
$value = array_key_exists($key, $array) ? $array[$key] : null;

            if (
is_array($type))
            {
                if (!
is_array($value))
                {
                   
$value = [];
                }
               
$output[$key] = $this->filterArray($value, $type);
            }
            else
            {
               
$output[$key] = $this->filter($value, $type);
            }
        }

        return
$output;
    }

    public function
filter($value, $type, array $options = null)
    {
        if (!
is_array($options))
        {
           
$optionParts = explode(',', $type);
           
$type = array_shift($optionParts);
           
$options = [];

            foreach (
$optionParts AS $part)
            {
               
$option = explode(':', trim($part), 2);
                if (!isset(
$option[1]))
                {
                   
$option[1] = true;
                }
                else
                {
                   
$option[1] = trim($option[1]);
                }
               
$options[trim($option[0])] = $option[1];
            }
        }

       
$type = trim(strtolower($type));

        if (
$type && $type[0] === '?')
        {
           
$nullable = true;
           
$type = substr($type, 1);
        }
        else
        {
           
$nullable = false;
        }

        if (!
$type)
        {
            throw new \
LogicException("No filter type provided");
        }

        if (
$nullable && $value === null)
        {
            return
null;
        }

        return
$this->cleanInternal($value, $type, $options);
    }

    protected function
cleanInternal($value, $type, array $options)
    {
        switch (
$type)
        {
            case
'str':
            case
'string':
                if (
is_scalar($value))
                {
                   
$value = str_replace("\r\n", "\n", strval($value));
                    if (!
preg_match('/^./us', $value))
                    {
                       
$value = '';
                    }
                }
                else
                {
                   
$value = '';
                }

                if (empty(
$options['no-clean']))
                {
                   
$value = $this->cleanString($value, false);
                }

                if (empty(
$options['no-trim']))
                {
                   
$value = trim($value);
                }
                break;

            case
'num':
                if (
is_scalar($value))
                {
                   
$value = $this->normalizeDecimalSeparator($value);
                   
$value = strval(floatval($value)) + 0;
                }
                else
                {
                   
$value = 0;
                }
                break;

            case
'unum':
                if (
is_scalar($value))
                {
                   
$value = $this->normalizeDecimalSeparator($value);
                   
$value = strval(floatval($value)) + 0;
                    if (
$value < 0)
                    {
                       
$value = 0;
                    }
                }
                else
                {
                   
$value = 0;
                }
                break;

            case
'int':
            case
'integer':
                if (
is_scalar($value))
                {
                   
$value = intval($value);
                }
                else
                {
                   
$value = 0;
                }
                break;

            case
'uint':
            case
'unsigned':
                if (
is_scalar($value))
                {
                   
$value = intval($value);
                    if (
$value < 0)
                    {
                       
$value = 0;
                    }
                }
                else
                {
                   
$value = 0;
                }
                break;

            case
'posint':
            case
'positive-integer':
                if (
is_scalar($value))
                {
                   
$value = intval($value);
                    if (
$value < 1)
                    {
                       
$value = 1;
                    }
                }
                else
                {
                   
$value = 1;
                }
                break;

            case
'float':
                if (
is_scalar($value))
                {
                   
$value = $this->normalizeDecimalSeparator($value);
                   
$value = floatval($value);
                }
                else
                {
                   
$value = 0;
                }
                break;

            case
'bool':
            case
'boolean':
               
$value = (bool)$value;
                break;

            case
'array':
                if (!
is_array($value))
                {
                   
$value = [];
                }

                if (empty(
$options['no-clean']))
                {
                   
$value = $this->cleanArrayStrings($value);
                }
                break;

            case
'json-array':
                if (
is_string($value))
                {
                   
$value = json_decode($value, true);
                    if (!
is_array($value))
                    {
                       
$value = [];
                    }
                }
                else if (!
is_array($value))
                {
                   
$value = [];
                }

                if (empty(
$options['no-clean']))
                {
                   
$value = $this->cleanArrayStrings($value);
                }
                break;

            case
'timeoffset':
                if (
is_array($value) && isset($value['amount']) && isset($value['unit']))
                {
                   
$amount = is_scalar($value['amount']) ? intval($value['amount']) : 0;
                   
$unit = is_scalar($value['unit']) ? strtolower($value['unit']) : '';

                    switch (
$unit)
                    {
                        case
'seconds':
                        case
'minutes':
                        case
'hours':
                        case
'days':
                        case
'weeks':
                        case
'months':
                        case
'years':
                           
$valid = true;
                            break;

                        default:
                           
$valid = false;
                    }

                    if (
$valid && $amount)
                    {
                        if (
$amount > 0)
                        {
                           
$amount = "+$amount";
                        }
                       
$value = strtotime("$amount $unit", 0);
                    }
                    else
                    {
                       
$value = 0;
                    }
                }
                else
                {
                   
$value = 0;
                }
                break;

            case
'datetime':
                if (!empty(
$options['tz']))
                {
                   
// $options['tz'] accepts either a timezone string, or a DateTimeZone object
                   
$tz = $options['tz'] instanceof \DateTimeZone ? $options['tz'] : new \DateTimeZone($options['tz']);
                }
                else
                {
                   
$tz = \XF::language()->getTimeZone();
                }
               
$useDtObject = !empty($options['obj']);

                if (
is_scalar($value) && $value)
                {
                   
$value = trim(strval($value));
                    if (!
$value || is_numeric($value))
                    {
                       
$value = intval($value);
                    }
                    else
                    {
                        try
                        {
                           
$dt = new \DateTime($value, $tz);
                            if (!empty(
$options['end']))
                            {
                               
$dt->setTime(23, 59, 59);
                            }

                           
$value = $useDtObject ? $dt : intval($dt->format('U'));
                        }
                        catch (\
Exception $e)
                        {
                           
// probably a formatting issue, ignore
                           
$value = empty($options['obj']) ? 0 : null;
                        }
                    }
                }
                else
                {
                   
$value = empty($options['obj']) ? 0 : null;
                }

                if (
$useDtObject)
                {
                    if (!
$value)
                    {
                       
$value = null;
                    }
                    else if (!(
$value instanceof \DateTime))
                    {
                       
$value = new \DateTime('@' . $value);
                    }
                }
                break;

            default:
                if (
preg_match('/^array-(.*)$/', $type, $match))
                {
                    if (!
is_array($value))
                    {
                       
$value = [];
                    }
                    else
                    {
                        foreach (
$value AS &$innerValue)
                        {
                           
$innerValue = $this->filter($innerValue, $match[1], $options);
                        }
                    }
                }
                else
                {
                    throw new \
InvalidArgumentException("Unknown filter type $type");
                }
        }

        return
$value;
    }

    public function
normalizeDecimalSeparator($value)
    {
       
$decimalSep = \XF::language()['decimal_point'];

        if (
strpos($value, $decimalSep) !== false && $decimalSep !== '.')
        {
           
$value = str_replace($decimalSep, '.', $value);
        }

        return
$value;
    }

    public function
cleanString($string, $trim = true)
    {
        if (!
$this->fullUnicode)
        {
           
// only cover the BMP as MySQL only supports that unless opting into utf8mb4
           
$string = preg_replace('/[\xF0-\xF7].../', '', $string);
        }

       
$string = strtr(strval($string), $this->stringCleaning);
        if (
$trim)
        {
           
$string = trim($string);
        }

        return
$string;
    }

    public function
cleanArrayStrings(array $input, $trim = true)
    {
        foreach (
$input AS &$v)
        {
            if (
is_string($v))
            {
               
$v = str_replace("\r\n", "\n", $v);
                if (!
preg_match('/^./us', $v))
                {
                   
$v = '';
                }
               
$v = $this->cleanString($v, $trim);
            }
            else if (
is_array($v))
            {
               
$v = $this->cleanArrayStrings($v, $trim);
            }
        }

        return
$input;
    }

    public function
getFullUnicode()
    {
        return
$this->fullUnicode;
    }

    public function
setFullUnicode($fullUnicode)
    {
       
$this->fullUnicode = $fullUnicode;
    }

    public function
getNewArrayFilterer(array $input = [])
    {
        return new
InputFiltererArray($this, $input);
    }
}