/** * Creates a parsed URL from a URL string. * * The URL string is expected to be by all means valid, with characters being percent-encoded where it is required * by the URL standard and without any leading or trailing whitespace. It is only when the URL string does not * indicate any protocol that the URL may still be considered valid and the default protocol is assigned to the * URL, which is "http". * * @param string $url The URL string. */ public function __construct($url) { assert('is_cstring($url)', vs(isset($this), get_defined_vars())); assert('self::isValid($url, true)', vs(isset($this), get_defined_vars())); $this->m_url = $url; $parsedUrl = parse_url($url); assert('is_cmap($parsedUrl)', vs(isset($this), get_defined_vars())); // should not rise for a valid URL // Protocol (scheme). if (CMap::hasKey($parsedUrl, "scheme")) { $this->m_hasProtocol = true; $this->m_protocol = $parsedUrl["scheme"]; // Normalize by converting to lowercase. $this->m_normProtocol = CString::toLowerCase($this->m_protocol); } else { $this->m_hasProtocol = false; $this->m_normProtocol = self::DEFAULT_PROTOCOL; if (!CMap::hasKey($parsedUrl, "host")) { // Most likely, `parse_url` function has not parsed the host because the protocol (scheme) is absent // and there are no "//" in the front, so try parsing the host with the default protocol in the URL. $parsedUrl = parse_url(self::ensureProtocol($url)); assert('is_cmap($parsedUrl)', vs(isset($this), get_defined_vars())); CMap::remove($parsedUrl, "scheme"); } } // Host (domain). $this->m_hostIsInBrackets = false; if (CMap::hasKey($parsedUrl, "host")) { $this->m_host = $parsedUrl["host"]; if (CRegex::find($this->m_host, "/^\\[.*\\]\\z/")) { // Most likely, an IPv6 enclosed in "[]". $this->m_hostIsInBrackets = true; $this->m_host = CString::substr($this->m_host, 1, CString::length($this->m_host) - 2); } // Normalize by converting to lowercase. $this->m_normHost = CString::toLowerCase($this->m_host); } else { // Same as invalid. assert('false', vs(isset($this), get_defined_vars())); } // Port. if (CMap::hasKey($parsedUrl, "port")) { $this->m_hasPort = true; $this->m_port = $parsedUrl["port"]; // Should be `int`, but look into the type just in case. if (is_cstring($this->m_port)) { $this->m_port = CString::toInt($this->m_port); } } else { $this->m_hasPort = false; } // Path. if (CMap::hasKey($parsedUrl, "path")) { $this->m_hasPath = true; $this->m_path = $parsedUrl["path"]; // Normalize by replacing percent-encoded bytes of unreserved characters with their literal equivalents and // ensuring that all percent-encoded parts are in uppercase. $pathDelimitersReEsc = CRegex::enterTd(self::$ms_delimiters); $this->m_normPath = CRegex::replaceWithCallback($this->m_path, "/[^{$pathDelimitersReEsc}]+/", function ($matches) { return CUrl::enterTdNew(CUrl::leaveTdNew($matches[0])); }); } else { $this->m_hasPath = false; $this->m_normPath = "/"; } $this->m_urlPath = new CUrlPath($this->m_normPath); // Query string. $this->m_hasQuery = false; if (CMap::hasKey($parsedUrl, "query")) { $this->m_hasQuery = true; $this->m_queryString = $parsedUrl["query"]; $parsingWasFruitful; $this->m_urlQuery = new CUrlQuery($this->m_queryString, $parsingWasFruitful); if ($parsingWasFruitful) { $this->m_hasQuery = true; $this->m_normQueryString = $this->m_urlQuery->queryString(true); } } // Fragment ID. if (CMap::hasKey($parsedUrl, "fragment")) { $this->m_hasFragmentId = true; $this->m_fragmentId = $parsedUrl["fragment"]; // Normalize by replacing percent-encoded bytes of unreserved characters with their literal equivalents and // ensuring that all percent-encoded parts are in uppercase. $fiDelimitersReEsc = CRegex::enterTd(self::$ms_delimiters); $this->m_normFragmentId = CRegex::replaceWithCallback($this->m_fragmentId, "/[^{$fiDelimitersReEsc}]+/", function ($matches) { // Use the newer flavor of percent-encoding. return CUrl::enterTdNew(CUrl::leaveTdNew($matches[0])); }); } else { $this->m_hasFragmentId = false; } // User. if (CMap::hasKey($parsedUrl, "user")) { $this->m_hasUser = true; $this->m_user = $parsedUrl["user"]; } else { $this->m_hasUser = false; } // Password. if (CMap::hasKey($parsedUrl, "pass")) { $this->m_hasPassword = true; $this->m_password = $parsedUrl["pass"]; } else { $this->m_hasPassword = false; } // Compose the normalized URL string. $this->m_normUrl = ""; $this->m_normUrl .= $this->m_normProtocol . "://"; if ($this->m_hasUser) { $this->m_normUrl .= $this->m_user; } if ($this->m_hasPassword) { $this->m_normUrl .= ":" . $this->m_password; } if ($this->m_hasUser || $this->m_hasPassword) { $this->m_normUrl .= "@"; } if (!$this->m_hostIsInBrackets) { $this->m_normUrl .= $this->m_normHost; } else { $this->m_normUrl .= "[" . $this->m_normHost . "]"; } if ($this->m_hasPort) { // Normalize by skipping port indication if the port is the default one for the protocol. if (!(CMap::hasKey(self::$ms_knownProtocolToDefaultPort, $this->m_normProtocol) && self::$ms_knownProtocolToDefaultPort[$this->m_normProtocol] == $this->m_port)) { $this->m_normUrl .= ":" . $this->m_port; } } $this->m_normUrl .= $this->m_normPath; if ($this->m_hasQuery) { $this->m_normUrl .= "?" . $this->m_normQueryString; } $this->m_normUrlWithoutFragmentId = $this->m_normUrl; if ($this->m_hasFragmentId) { $this->m_normUrl .= "#" . $this->m_normFragmentId; } }
protected static function timeToComponentsInTimeZone(CTime $time, $timeZone, &$year, &$month, &$day, &$hour, &$minute, &$second, &$millisecond, &$dayOfWeek) { $FTime = $time->FTime(); $UTime = $time->UTime(); $MTime = $time->MTime(); $negMsCase = false; if ($FTime < 0.0 && $MTime != 0) { $negMsCase = true; $UTime--; } $time = self::timeToStringInTimeZone(new self($UTime), "Y,m,d,H,i,s,w", $timeZone); $components = CString::split($time, ","); $year = CString::toInt($components[0]); $month = CString::toInt($components[1]); $day = CString::toInt($components[2]); $hour = CString::toInt($components[3]); $minute = CString::toInt($components[4]); $second = CString::toInt($components[5]); $dayOfWeek = CString::toInt($components[6]); if (!$negMsCase) { $millisecond = $MTime; } else { $millisecond = $MTime + 1000; } }
/** * @ignore */ public static function maybeUpdateThirdParty() { if (!self::isInCliMode()) { // This method can be run in CLI mode only. assert('false', vs(isset($this), get_defined_vars())); return false; } $updates = CConfiguration::option("updates"); $updatesAreEnabled = $updates["enable"]; if ($updatesAreEnabled) { $minTimeBetweenDoUpdatesDays = $updates["minTimeBetweenDoUpdatesDays"]; $components = $updates["components"]; assert('is_int($minTimeBetweenDoUpdatesDays)', vs(isset($this), get_defined_vars())); // Logging. $logging = $updates["logging"]; $loggingIsEnabled = $logging["enable"]; $logFp = $logging["logFilePath"]; if ($loggingIsEnabled) { assert('!CString::isEmpty($logFp)', vs(isset($this), get_defined_vars())); $logFp = CFilePath::frameworkPath($logFp); CShell::setLogging($logFp); } // Mailing. $mailing = $updates["mailing"]; $mailingIsEnabled = $mailing["enable"]; if ($mailingIsEnabled) { $adminMail = CConfiguration::option("admin.mail"); $to = $adminMail["to"]; $from = $adminMail["from"]; $transport = $adminMail["transport"]; assert('!CString::isEmpty($to) && !CString::isEmpty($from) && !CString::isEmpty($transport)', vs(isset($this), get_defined_vars())); $mail; if (CString::equalsCi($transport, "smtp")) { $smtpOutgoingServer = $adminMail["smtpOutgoingServer"]; $smtpUsername = $adminMail["smtpUsername"]; $smtpPassword = $adminMail["smtpPassword"]; assert('!CString::isEmpty($smtpOutgoingServer) && !CString::isEmpty($smtpUsername) && ' . '!CString::isEmpty($smtpPassword)', vs(isset($this), get_defined_vars())); $mail = CMail::makeSmtp($smtpOutgoingServer, $smtpUsername, $smtpPassword, $from, $to); } else { if (CString::equalsCi($transport, "system")) { $mail = CMail::makeSystem($from, $to); } else { assert('false', vs(isset($this), get_defined_vars())); } } CShell::setMailing($mail); } $thirdPartyDp = $GLOBALS["PHRED_PATH_TO_THIRD_PARTY"]; $lastUpdateTimeFp = CFilePath::add($thirdPartyDp, self::$ms_thirdPartyLastUpdateTimeFn); // Read the file containing the Unix seconds of the last update time stamp (if exists) and compare that // time with the current time. $numDaysSinceLastUpdate; if (CFile::exists($lastUpdateTimeFp)) { $lastUpdateTime = new CTime(CString::toInt(CFile::read($lastUpdateTimeFp))); $currTime = CTime::now(); if ($lastUpdateTime->isBefore($currTime)) { $numDaysSinceLastUpdate = $currTime->diffInDays($lastUpdateTime); if ($numDaysSinceLastUpdate < $minTimeBetweenDoUpdatesDays) { // It is too early for updates yet. return false; } } else { assert('false', vs(isset($this), get_defined_vars())); } } $date = CShell::currentDate(); CShell::say("Started on {$date}."); if (isset($numDaysSinceLastUpdate)) { CShell::say("It has been {$numDaysSinceLastUpdate} day(s) since last successful update."); } $concurrLockFp = CFilePath::add($thirdPartyDp, self::$ms_thirdPartyConcurrLockFn); // Try locking the operation. if (!self::setLock($concurrLockFp, false)) { assert('false', vs(isset($this), get_defined_vars())); CShell::onError(false, "Could not obtain a lock on the operation."); CShell::writeToLog("\n"); return false; } $phpConfigNeedsReload = false; $totalNumComponents = CMap::length($components); $numComponentsUpdated = 0; // The Browser Capabilities Project (BrowsCap). if (CMap::hasKey($components, "browsCap")) { $browsCap = $components["browsCap"]; $skip = $browsCap["skip"]; if (!$skip) { CShell::say("Updating the Browser Capabilities Project (BrowsCap) ..."); $lookupFileUrl = $browsCap["lookupFileUrl"]; assert('!CString::isEmpty($lookupFileUrl)', vs(isset($this), get_defined_vars())); // Component-related constants. static $s_configOptName = "browscap"; static $s_lookupFileDownloadTimeoutSeconds = 120; if (self::hasConfigOption($s_configOptName)) { $browsCapLookupFp = CString::trim(self::configOption($s_configOptName)); if (!CString::isEmpty($browsCapLookupFp)) { $browsCapDp = CFilePath::directory($browsCapLookupFp); CShell::say("Downloading a BrowsCap lookup file from '{$lookupFileUrl}' ..."); $temporaryFp = CFile::createTemporary($browsCapDp); $downloadRes = CInetRequest::downloadFile($lookupFileUrl, $temporaryFp, $s_lookupFileDownloadTimeoutSeconds); if ($downloadRes) { // After the file is downloaded into a temporary one, move it to the destination, // safely replacing the existing file, if any. CFile::move($temporaryFp, $browsCapLookupFp); $numComponentsUpdated++; $phpConfigNeedsReload = true; $downloadedFileSizeKB = CUUnit::convertStoragef((double) CFile::size($browsCapLookupFp), CUUnit::BYTE, CUUnit::KILOBYTE); $downloadedFileSizeKB = CMathf::round($downloadedFileSizeKB, 2); CShell::say("Done. The downloaded file is {$downloadedFileSizeKB} KB in size."); } else { CShell::onError(false, "Could not download a BrowsCap lookup file from '{$lookupFileUrl}'."); } // Just in case, check for any temporary files that could have been left by any previous // operations in the directory. $leftoverFiles = CFile::findFiles(CFilePath::add($browsCapDp, CFile::DEFAULT_TEMPORARY_FILE_PREFIX . "*")); if (!CArray::isEmpty($leftoverFiles)) { // Cleanup the directory from the temporary files. $len = CArray::length($leftoverFiles); for ($i = 0; $i < $len; $i++) { CFile::delete($leftoverFiles[$i]); } } } else { CShell::onError(false, "Could not read the value of '{$s_configOptName}' option " . "in the PHP CLI configuration file."); } } else { CShell::onError(false, "Could not find '{$s_configOptName}' option in the PHP CLI configuration file."); } } else { CShell::say("Skipping the Browser Capabilities Project (BrowsCap)."); } } // All the components have been processed. Unlock the operation. self::unsetLock($concurrLockFp); $date = CShell::currentDate(); if ($numComponentsUpdated != 0) { // One or more third-party components have been updated. Put a time stamp on the directory where the // components are located. CFile::write($lastUpdateTimeFp, CString::fromInt(CTime::currentUTime())); if ($numComponentsUpdated == $totalNumComponents) { CShell::speak("Success. All {$totalNumComponents} third-party component(s)"); } else { CShell::speak("Partial success. {$numComponentsUpdated} out of {$totalNumComponents} third-party component(s)"); } CShell::say("have been updated. Completed on {$date}."); } else { CShell::say("No third-party components have been updated. Completed on {$date}."); } return $phpConfigNeedsReload; } else { return false; } }
/** * Returns the code point of a specified character, as an integer. * * @param string $char The character. * * @return int The Unicode code point of the character. */ public static function toCharCode($char) { assert('is_cstring($char)', vs(isset($this), get_defined_vars())); assert('self::length($char) == 1', vs(isset($this), get_defined_vars())); return CString::toInt(CString::hexToDec(CString::substr(self::toEscString($char), 2))); }
/** * Returns the value of a specified long option with which the script was run, as an integer value. * * @param string $optionName The name of the option, excluding "-". * * @return int The value of the option specified. */ public static function valueForLongOptionInt($optionName) { assert('is_cstring($optionName)', vs(isset($this), get_defined_vars())); return CString::toInt(self::valueForLongOption($optionName)); }
/** * Returns the number of redirections that took place while processing the request. * * This method can be called for HTTP requests only. * * @return int The number of redirections that took place. */ public function requestNumRedirections() { assert('$this->isHttp() && $this->m_done && !$this->m_hasError', vs(isset($this), get_defined_vars())); $key = "redirect_count"; if (CMap::hasKey($this->m_requestSummary, $key)) { $value = $this->m_requestSummary[$key]; return is_int($value) || is_float($value) ? (int) $value : CString::toInt($value); } else { return 0; } }
/** * 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; } } }