/** * Replaces any reference to one of the framework's special directories in a path with the directory's actual path * and returns the usable path. * * A framework's directory is referenced in a path by wrapping its ID into double curly braces, as in * "{{PHRED_PATH_TO_FRAMEWORK_ROOT}}", optionally with "/" after the reference. * * @param string $path The path to the file or directory (can be absolute or relative). * * @return CUStringObject The usable path. */ public static function frameworkPath($path) { assert('!isset($path) || is_cstring($path)', vs(isset($this), get_defined_vars())); if (!isset($path)) { return null; } // Replace every "{{EXAMPLE_PATH}}" in the path string with the value of "EXAMPLE_PATH" key from $GLOBALS // variable if such key exists in the variable. $modified = false; $path = CRegex::replaceWithCallback($path, "/\\{\\{\\w+\\}\\}/", function ($matches) use(&$modified) { $pathVarName = CString::substr($matches[0], 2, CString::length($matches[0]) - 4); if (isset($GLOBALS[$pathVarName])) { $modified = true; return $GLOBALS[$pathVarName] . "/"; } else { assert('false', vs(isset($this), get_defined_vars())); return $matches[0]; } }); if ($modified) { $path = CRegex::replace($path, "/\\/{2,}/", "/"); } return $path; }
/** * Returns the code point of a specified character, as a hexadecimal string. * * The returned string is always four characters in length. * * For instance, "A" would return "0041". * * @param string $char The character. * * @return string The Unicode code point of the character. */ public static function toCharCodeHex($char) { assert('is_cstring($char)', vs(isset($this), get_defined_vars())); assert('self::length($char) == 1', vs(isset($this), get_defined_vars())); return CString::substr(self::toEscString($char), 2); }
/** * Determines if the URL in a specified string is valid. * * @param string $url The URL string to be looked into. * @param bool $ignoreProtocolAbsence **OPTIONAL. Default is** `false`. Tells whether the URL in the string may * still be considered valid even if it does not indicate any protocol. * * @return bool `true` if the URL in the string is valid, `false` otherwise. */ public static function isValid($url, $ignoreProtocolAbsence = false) { assert('is_cstring($url) && is_bool($ignoreProtocolAbsence)', vs(isset($this), get_defined_vars())); $parsedUrl = parse_url($url); if (!is_cmap($parsedUrl)) { return false; } if ($ignoreProtocolAbsence && !CMap::hasKey($parsedUrl, "scheme")) { // No protocol seems to be specified, try with the default one. $url = self::DEFAULT_PROTOCOL . "://{$url}"; $parsedUrl = parse_url($url); if (!is_cmap($parsedUrl)) { return false; } if (!CMap::hasKey($parsedUrl, "scheme")) { return false; } } if (is_cstring(filter_var($url, FILTER_VALIDATE_URL))) { return true; } else { if (CMap::hasKey($parsedUrl, "host")) { // The `filter_var` function could fail to recognize an IPv6 as the URL's host (enclosed in square // brackets), so, in case of a valid IPv6 being the host, replace it with an IPv4 and give the URL another // try. $host = $parsedUrl["host"]; if (CRegex::find($host, "/^\\[.*\\]\\z/")) { $host = CString::substr($host, 1, CString::length($host) - 2); if (CIp::isValidV6($host)) { // Should not influence the validity if the string is present anywhere else. $url = CString::replace($url, "[{$host}]", "127.0.0.1"); if (is_cstring(filter_var($url, FILTER_VALIDATE_URL)) && is_cmap(parse_url($url))) { return true; } } } } } return false; }
/** * Filters a string or a collection of strings according to the expected output type(s) and returns the output * value(s). * * @param mixed $inputStringOrDecodedCollection The string to be filtered or the array or map containing the * strings to be filtered. If the parameter's value is a JSON-encoded string, the output value is going to be * either an array or map. * @param reference $success **OUTPUT.** After the method is called, the value of this parameter tells whether * the filtering was successful. * * @return mixed The output value or a collection of values of the expected type(s) after having been put through * the filter. */ public function filter($inputStringOrDecodedCollection, &$success) { assert('is_cstring($inputStringOrDecodedCollection) || is_collection($inputStringOrDecodedCollection)', vs(isset($this), get_defined_vars())); $success = true; if ($this->m_expectedType != self::CARRAY && $this->m_expectedType != self::CMAP) { // The expected output type is not a collection; the input value must be of string type. if (!is_cstring($inputStringOrDecodedCollection)) { $success = false; return oop_x($this->m_defaultValue); } $inputString = $inputStringOrDecodedCollection; if ($this->m_expectedType == self::BOOL || $this->m_expectedType == self::INT || $this->m_expectedType == self::FLOAT || $this->m_expectedType == self::EMAIL || $this->m_expectedType == self::URL || $this->m_expectedType == self::IP) { // Trim the input string on both sides from whitespace, including Unicode whitespace and control // characters. $trimmingSubjectRe = CUString::TRIMMING_AND_SPACING_NORM_SUBJECT_RE; $inputString = CRegex::remove($inputString, "/^({$trimmingSubjectRe})+|({$trimmingSubjectRe})+\\z/u"); } // Pre-process the string for integer and floating-point types. $looksLikeHex; if ($this->m_expectedType == self::INT || $this->m_expectedType == self::FLOAT) { if (CString::startsWith($inputString, "+")) { // Remove the plus sign. $inputString = CString::substr($inputString, 1); } $looksLikeHex = CRegex::find($inputString, "/^-?0x/i"); if ($this->m_allowLeadingZeros && !($this->m_expectedType == self::INT && $this->m_allowHex && $looksLikeHex)) { // Remove any leading zeros (except for special cases). $inputString = CRegex::replace($inputString, "/^(\\D*)0*(?!\\b)/", "\$1"); } if ($this->m_allowComma) { $inputString = CRegex::remove($inputString, "/,(?=\\d{3}\\b)/"); } } // Validate and sanitize the value according to its expected type. if ($this->m_expectedType == self::BOOL) { if (!CRegex::find($inputString, "/^(1|true|yes|on|0|false|no|off)\\z/i")) { $success = false; return $this->m_defaultValue; } return CString::equals($inputString, "1") || CString::equalsCi($inputString, "true") || CString::equalsCi($inputString, "yes") || CString::equalsCi($inputString, "on"); } if ($this->m_expectedType == self::INT) { $value; if (!($this->m_allowHex && $looksLikeHex)) { // Regular. if (!CRegex::find($inputString, "/^-?(?!0(?!\\b))\\d+\\z/")) { $success = false; return $this->m_defaultValue; } $value = CString::toInt($inputString); } else { // Hex. if (!CRegex::find($inputString, "/^-?0x[0-9A-F]+\\z/i")) { $success = false; return $this->m_defaultValue; } $value = CString::toIntFromHex($inputString); } if (isset($this->m_intValidMin) && $value < $this->m_intValidMin || isset($this->m_intValidMax) && $value > $this->m_intValidMax) { $success = false; return $this->m_defaultValue; } if (isset($this->m_intClampingMin) && $value < $this->m_intClampingMin) { $value = $this->m_intClampingMin; } if (isset($this->m_intClampingMax) && $value > $this->m_intClampingMax) { $value = $this->m_intClampingMax; } return $value; } if ($this->m_expectedType == self::FLOAT) { if (!CRegex::find($inputString, "/^-?(?!0(?!\\b))\\d*\\.?\\d+(e[\\-+]?\\d+)?\\z/i")) { $success = false; return $this->m_defaultValue; } $value = CString::toFloat($inputString); if (isset($this->m_floatValidMin) && $value < $this->m_floatValidMin || isset($this->m_floatValidMax) && $value > $this->m_floatValidMax) { $success = false; return $this->m_defaultValue; } if (isset($this->m_floatClampingMin) && $value < $this->m_floatClampingMin) { $value = $this->m_floatClampingMin; } if (isset($this->m_floatClampingMax) && $value > $this->m_floatClampingMax) { $value = $this->m_floatClampingMax; } return $value; } if ($this->m_expectedType == self::CSTRING) { $value = $inputString; if (!CString::isValid($value)) { $success = false; return $this->m_defaultValue; } if (!$this->m_keepAbnormalNewlines) { $value = CString::normNewlines($value); } if (!$this->m_keepNonPrintable) { if (!$this->m_keepTabsAndNewlines) { $value = CRegex::remove($value, "/[\\x00-\\x1F\\x7F-\\xFF]/"); } else { $value = CRegex::remove($value, "/[\\x00-\\x1F\\x7F-\\xFF](?<![\\x09\\x0A\\x0D])/"); } } else { if (!$this->m_keepTabsAndNewlines) { $value = CRegex::remove($value, "/[\\x09\\x0A\\x0D]/"); } } if (!$this->m_keepSideSpacing) { $value = CString::trim($value); } if (!$this->m_keepExtraSpacing) { $value = CString::normSpacing($value); } return $value; } if ($this->m_expectedType == self::CUSTRING) { $value = $inputString; if (!CUString::isValid($value)) { $success = false; return $this->m_defaultValue; } if (!$this->m_keepAbnormalNewlines) { $value = CUString::normNewlines($value); } if (!$this->m_keepNonPrintable) { if (!$this->m_keepTabsAndNewlines) { $value = CRegex::remove($value, "/\\p{C}|\\p{Zl}|\\p{Zp}/u"); } else { $value = CRegex::remove($value, "/\\p{C}(?<!\\x{0009}|\\x{000A}|\\x{000D})/u"); } } else { if (!$this->m_keepTabsAndNewlines) { $value = CRegex::remove($value, "/\\x{0009}|\\x{000A}|\\x{000D}|\\p{Zl}|\\p{Zp}/u"); } } if (!$this->m_keepSideSpacing) { $value = CUString::trim($value); } if (!$this->m_keepExtraSpacing) { $value = CUString::normSpacing($value); } return $value; } if ($this->m_expectedType == self::EMAIL) { $value = filter_var($inputString, FILTER_VALIDATE_EMAIL); if (!is_cstring($value)) { $success = false; return $this->m_defaultValue; } return $value; } if ($this->m_expectedType == self::URL) { $value = $inputString; if (!CUrl::isValid($value, $this->m_ignoreProtocolAbsence)) { $success = false; return $this->m_defaultValue; } if ($this->m_ignoreProtocolAbsence) { $value = CUrl::ensureProtocol($value); } return $value; } if ($this->m_expectedType == self::IP) { $value = $inputString; $options = CBitField::ALL_UNSET; if (!$this->m_allowPrivateRange) { $options |= CIp::DISALLOW_PRIVATE_RANGE; } if (!$this->m_allowReservedRange) { $options |= CIp::DISALLOW_RESERVED_RANGE; } $isValid; if (!$this->m_ipV6 && !$this->m_ipV4OrV6) { $isValid = CIp::isValidV4($value, $options); } else { if (!$this->m_ipV4OrV6) { $isValid = CIp::isValidV6($value, $options); } else { $isValid = CIp::isValidV4($value, $options) || CIp::isValidV6($value, $options); } } if (!$isValid) { $success = false; return $this->m_defaultValue; } return $value; } } else { if ($this->m_expectedType == self::CARRAY) { if (!is_cstring($inputStringOrDecodedCollection) && !is_carray($inputStringOrDecodedCollection)) { $success = false; return oop_x($this->m_defaultValue); } $value; if (is_cstring($inputStringOrDecodedCollection)) { // Assume JSON format for the input string. $json = new CJson($inputStringOrDecodedCollection, $this->m_jsonStrictness); $value = $json->decode($success); if (!$success) { return oop_x($this->m_defaultValue); } if (!is_carray($value)) { $success = false; return oop_x($this->m_defaultValue); } } else { $value = $inputStringOrDecodedCollection; } $value = self::recurseCollectionFiltering($value, $this->m_collectionInputFilters, $success, 0); if (!$success) { return oop_x($this->m_defaultValue); } return $value; } else { if (!is_cstring($inputStringOrDecodedCollection) && !is_cmap($inputStringOrDecodedCollection)) { $success = false; return oop_x($this->m_defaultValue); } $value; if (is_cstring($inputStringOrDecodedCollection)) { // Assume JSON format for the input string. $json = new CJson($inputStringOrDecodedCollection, $this->m_jsonStrictness); $value = $json->decode($success); if (!$success) { return oop_x($this->m_defaultValue); } if (!is_cmap($value)) { $success = false; return oop_x($this->m_defaultValue); } } else { $value = $inputStringOrDecodedCollection; } $value = self::recurseCollectionFiltering($value, $this->m_collectionInputFilters, $success, 0); if (!$success) { return oop_x($this->m_defaultValue); } return $value; } } }