* This code provides various string manipulation functions that are
* used by the rest of the SquirrelMail code.
*
- * @copyright © 1999-2006 The SquirrelMail Project Team
+ * @copyright 1999-2017 The SquirrelMail Project Team
* @license http://opensource.org/licenses/gpl-license.php GNU Public License
* @version $Id$
* @package squirrelmail
}
}
- ereg("^([\t >]*)([^\t >].*)?$", $line, $regs);
+ preg_match('/^([\t >]*)([^\t >].*)?$/', $line, $regs);
$beginning_spaces = $regs[1];
if (isset($regs[2])) {
$words = explode(' ', $regs[2]);
} else {
- $words = '';
+ $words = array();
}
$i = 0;
*/
function get_location () {
- global $imap_server_type, $config_location_base;
+ global $imap_server_type, $config_location_base,
+ $is_secure_connection, $sq_ignore_http_x_forwarded_headers;
/* Get the path, handle virtual directories */
if(strpos(php_self(), '?')) {
/* Check if this is a HTTPS or regular HTTP request. */
$proto = 'http://';
-
- /*
- * If you have 'SSLOptions +StdEnvVars' in your apache config
- * OR if you have HTTPS=on in your HTTP_SERVER_VARS
- * OR if you are on port 443
- */
- $getEnvVar = getenv('HTTPS');
- if ((isset($getEnvVar) && strcasecmp($getEnvVar, 'on') === 0) ||
- (sqgetGlobalVar('HTTPS', $https_on, SQ_SERVER) && strcasecmp($https_on, 'on') === 0) ||
- (sqgetGlobalVar('SERVER_PORT', $server_port, SQ_SERVER) && $server_port == 443)) {
+ if ($is_secure_connection)
$proto = 'https://';
- }
/* Get the hostname from the Host header or server config. */
- if ( !sqgetGlobalVar('HTTP_X_FORWARDED_HOST', $host, SQ_SERVER) || empty($host) ) {
+ if ($sq_ignore_http_x_forwarded_headers
+ || !sqgetGlobalVar('HTTP_X_FORWARDED_HOST', $host, SQ_SERVER)
+ || empty($host)) {
if ( !sqgetGlobalVar('HTTP_HOST', $host, SQ_SERVER) || empty($host) ) {
if ( !sqgetGlobalVar('SERVER_NAME', $host, SQ_SERVER) || empty($host) ) {
$host = '';
$port = '';
if (! strstr($host, ':')) {
+ // Note: HTTP_X_FORWARDED_PROTO could be sent from the client and
+ // therefore possibly spoofed/hackable. Thus, SquirrelMail
+ // ignores such headers by default. The administrator
+ // can tell SM to use such header values by setting
+ // $sq_ignore_http_x_forwarded_headers to boolean FALSE
+ // in config/config.php or by using config/conf.pl.
+ global $sq_ignore_http_x_forwarded_headers;
+ if ($sq_ignore_http_x_forwarded_headers
+ || !sqgetGlobalVar('HTTP_X_FORWARDED_PROTO', $forwarded_proto, SQ_SERVER))
+ $forwarded_proto = '';
if (sqgetGlobalVar('SERVER_PORT', $server_port, SQ_SERVER)) {
if (($server_port != 80 && $proto == 'http://') ||
- ($server_port != 443 && $proto == 'https://')) {
+ ($server_port != 443 && $proto == 'https://' &&
+ strcasecmp($forwarded_proto, 'https') !== 0)) {
$port = sprintf(':%d', $server_port);
}
}
}
+/**
+ * Get Message List URI
+ *
+ * @param string $mailbox Current mailbox name (unencoded/raw)
+ * @param string $startMessage The mailbox page offset
+ * @param string $what Any current search parameters (OPTIONAL;
+ * default empty string)
+ *
+ * @return string The message list URI
+ *
+ * @since 1.5.2
+ *
+ */
+function get_message_list_uri($mailbox, $startMessage, $what='') {
+
+ global $base_uri;
+
+ $urlMailbox = urlencode($mailbox);
+
+ $list_xtra = "?where=read_body.php&what=$what&mailbox=" . $urlMailbox.
+ "&startMessage=$startMessage";
+
+ return $base_uri .'src/right_main.php'. $list_xtra;
+}
+
+
/**
* Encrypts password
*
return $decrypted;
}
-
-/**
- * Randomizes the mt_rand() function.
- *
- * Toss this in strings or integers and it will seed the generator
- * appropriately. With strings, it is better to get them long.
- * Use md5() to lengthen smaller strings.
- *
- * @param mixed $val a value to seed the random number generator. mixed = integer or string.
- * @return void
- * @since 1.0
- */
-function sq_mt_seed($Val) {
- /* if mt_getrandmax() does not return a 2^n - 1 number,
- this might not work well. This uses $Max as a bitmask. */
- $Max = mt_getrandmax();
-
- if (! is_int($Val)) {
- $Val = crc32($Val);
- }
-
- if ($Val < 0) {
- $Val *= -1;
- }
-
- if ($Val == 0) {
- return;
- }
-
- mt_srand(($Val ^ mt_rand(0, $Max)) & $Max);
-}
-
-
-/**
- * Init random number generator
- *
- * This function initializes the random number generator fairly well.
- * It also only initializes it once, so you don't accidentally get
- * the same 'random' numbers twice in one session.
- *
- * @return void
- * @since 1.0
- */
-function sq_mt_randomize() {
- static $randomized;
-
- if ($randomized) {
- return;
- }
-
- /* Global. */
- sqgetGlobalVar('REMOTE_PORT', $remote_port, SQ_SERVER);
- sqgetGlobalVar('REMOTE_ADDR', $remote_addr, SQ_SERVER);
- sq_mt_seed((int)((double) microtime() * 1000000));
- sq_mt_seed(md5($remote_port . $remote_addr . getmypid()));
-
- /* getrusage */
- if (function_exists('getrusage')) {
- /* Avoid warnings with Win32 */
- $dat = @getrusage();
- if (isset($dat) && is_array($dat)) {
- $Str = '';
- foreach ($dat as $k => $v)
- {
- $Str .= $k . $v;
- }
- sq_mt_seed(md5($Str));
- }
- }
-
- if(sqgetGlobalVar('UNIQUE_ID', $unique_id, SQ_SERVER)) {
- sq_mt_seed(md5($unique_id));
- }
-
- $randomized = 1;
-}
-
/**
* Creates encryption key
*
* @since 1.0
*/
function OneTimePadCreate ($length=100) {
- sq_mt_randomize();
-
$pad = '';
for ($i = 0; $i < $length; $i++) {
$pad .= chr(mt_rand(0,255));
}
/**
- * Returns a string showing the size of the message/attachment.
- *
- * @param int $bytes the filesize in bytes
- * @return string the filesize in human readable format
- * @since 1.0
- */
+ * Returns a string showing a byte size figure in
+ * a more easily digested (readable) format
+ *
+ * @param int $bytes the size in bytes
+ *
+ * @return string The size in human readable format
+ *
+ * @since 1.0
+ *
+ */
function show_readable_size($bytes) {
$bytes /= 1024;
- $type = 'KiB';
+ $type = _("KiB");
if ($bytes / 1024 > 1) {
$bytes /= 1024;
- $type = 'MiB';
+ $type = _("MiB");
}
if ($bytes < 10) {
settype($bytes, 'integer');
}
- return $bytes . ' ' . $type;
+ global $nbsp;
+ return $bytes . $nbsp . $type;
}
/**
return '';
}
- sq_mt_randomize(); /* Initialize the random number generator */
-
$String = '';
$j = strlen( $chars ) - 1;
while (strlen($String) < $size) {
* @since 1.0.3
*/
function quoteimap($str) {
- return preg_replace("/([\"\\\\])/", "\\\\$1", $str);
+ return str_replace(array('\\', '"'), array('\\\\', '\\"'), $str);
}
/**
*
* Returns a link to the compose-page, taking in consideration
* the compose_in_new and javascript settings.
- * @param string $url the URL to the compose page
- * @param string $text the link text, default "Compose"
- * @param string $target (since 1.4.3) url target
+ *
+ * @param string $url The URL to the compose page
+ * @param string $text The link text, default "Compose"
+ * @param string $target URL target, if any (since 1.4.3)
+ * @param string $accesskey The access key to be used, if any
+ *
* @return string a link to the compose page
+ *
* @since 1.4.2
*/
-function makeComposeLink($url, $text = null, $target='') {
- global $compose_new_win, $javascript_on, $compose_width,
+function makeComposeLink($url, $text = null, $target='', $accesskey='NONE') {
+ global $compose_new_win, $compose_width,
$compose_height, $oTemplate;
if(!$text) {
// if not using "compose in new window", make
// regular link and be done with it
if($compose_new_win != '1') {
- return makeInternalLink($url, $text, $target);
+ return makeInternalLink($url, $text, $target, $accesskey);
}
// build the compose in new window link...
// if javascript is on, use onclick event to handle it
- if($javascript_on) {
+ if(checkForJavascript()) {
sqgetGlobalVar('base_uri', $base_uri, SQ_SESSION);
$compuri = SM_BASE_URI.$url;
- $oTemplate->assign('uri', 'javascript:void(0)');
- $oTemplate->assign('text', $text);
- $oTemplate->assign('onclick', "comp_in_new('$compuri','$compose_width','$compose_height')");
- return $oTemplate->fetch('hyperlink.tpl');
+
+ return create_hyperlink('javascript:void(0)', $text, '',
+ "comp_in_new('$compuri','$compose_width','$compose_height')",
+ '', '', '',
+ ($accesskey == 'NONE'
+ ? array()
+ : array('accesskey' => $accesskey)));
}
// otherwise, just open new window using regular HTML
- return makeInternalLink($url, $text, '_blank');
+ return makeInternalLink($url, $text, '_blank', $accesskey);
}
/**
* @link http://www.php.net/substr
* @link http://www.php.net/mb_substr
*/
-function sq_substr($string,$start,$length,$charset='auto') {
+function sq_substr($string,$start,$length=NULL,$charset='auto') {
+
+ // if $length is NULL, use the full string length...
+ // we have to do this to mimick the use of substr()
+ // where $length is not given
+ //
+ if (is_null($length))
+ $length = sq_strlen($length);
+
+
// use automatic charset detection, if function call asks for it
static $charset_auto, $bUse_mb;
return substr($string,$start,$length);
}
+/**
+ * This is a replacement for PHP's substr_replace() that is
+ * multibyte-aware.
+ *
+ * @param string $string The string to operate upon
+ * @param string $replacement The string to be inserted
+ * @param int $start The offset at which to begin substring replacement
+ * @param int $length The number of characters after $start to remove
+ * NOTE that if you need to specify a charset but
+ * want to achieve normal substr_replace() behavior
+ * where $length is not specified, use NULL (OPTIONAL;
+ * default from $start to end of string)
+ * @param string $charset The charset of the given string. A value of NULL
+ * here will force the use of PHP's standard substr().
+ * (OPTIONAL; default is "auto", which indicates that
+ * the user's current charset should be used).
+ *
+ * @return string The manipulated string
+ *
+ * Of course, you can use more advanced (e.g., negative) values
+ * for $start and $length as needed - see the PHP manual for more
+ * information: http://www.php.net/manual/function.substr-replace.php
+ *
+ */
+function sq_substr_replace($string, $replacement, $start, $length=NULL,
+ $charset='auto')
+{
+
+ // NULL charset? Just use substr_replace()
+ //
+ if (is_null($charset))
+ return is_null($length) ? substr_replace($string, $replacement, $start)
+ : substr_replace($string, $replacement, $start, $length);
+
+
+ // use current character set?
+ //
+ if ($charset == 'auto')
+ {
+//FIXME: is there any reason why this cannot be a global flag used by all string wrapper functions?
+ static $auto_charset;
+ if (!isset($auto_charset))
+ {
+ global $default_charset;
+//FIXME - do we need this?
+global $squirrelmail_language;
+ set_my_charset();
+ $auto_charset = $default_charset;
+//FIXME - do we need this?
+if ($squirrelmail_language == 'ja_JP') $auto_charset = 'euc-jp';
+ }
+ $charset = $auto_charset;
+ }
+
+
+ // standardize character set name
+ //
+ $charset = strtolower($charset);
+
+
+/* ===== FIXME: this list is not used in 1.5.x, but if we need it, unless this differs between all our string function wrappers, we should store this info in the session
+ // only use mbstring with the following character sets
+ //
+ $sq_substr_replace_mb_charsets = array(
+ 'utf-8',
+ 'big5',
+ 'gb2312',
+ 'gb18030',
+ 'euc-jp',
+ 'euc-cn',
+ 'euc-tw',
+ 'euc-kr'
+ );
+
+
+ // now we can use our own implementation using
+ // mb_substr() and mb_strlen() if needed
+ //
+ if (in_array($charset, $sq_substr_replace_mb_charsets)
+ && in_array($charset, sq_mb_list_encodings()))
+===== */
+//FIXME: is there any reason why this cannot be a global array used by all string wrapper functions?
+ if (in_array($charset, sq_mb_list_encodings()))
+ {
+
+ $string_length = mb_strlen($string, $charset);
+
+ if ($start < 0)
+ $start = max(0, $string_length + $start);
+
+ else if ($start > $string_length)
+ $start = $string_length;
+
+ if ($length < 0)
+ $length = max(0, $string_length - $start + $length);
+
+ else if (is_null($length) || $length > $string_length)
+ $length = $string_length;
+
+ if ($start + $length > $string_length)
+ $length = $string_length - $start;
+
+ return mb_substr($string, 0, $start, $charset)
+ . $replacement
+ . mb_substr($string,
+ $start + $length,
+ $string_length, // FIXME: I can't see why this is needed: - $start - $length,
+ $charset);
+
+ }
+
+
+ // else use normal substr_replace()
+ //
+ return is_null($length) ? substr_replace($string, $replacement, $start)
+ : substr_replace($string, $replacement, $start, $length);
+
+}
+
/**
* Wrapper that is used to switch between vanilla and multibyte strpos
* functions.
function sq_trim_value ( &$value ) {
$value = trim($value);
}
+
+/**
+ * Truncates the given string so that it has at
+ * most $max_chars characters. NOTE that a "character"
+ * may be a multibyte character, or (optionally), an
+ * HTML entity , so this function is different than
+ * using substr() or mb_substr().
+ *
+ * NOTE that if $elipses is given and used, the returned
+ * number of characters will be $max_chars PLUS the
+ * length of $elipses
+ *
+ * @param string $string The string to truncate
+ * @param int $max_chars The maximum allowable characters
+ * @param string $elipses A string that will be added to
+ * the end of the truncated string
+ * (ONLY if it is truncated) (OPTIONAL;
+ * default not used)
+ * @param boolean $html_entities_as_chars Whether or not to keep
+ * HTML entities together
+ * (OPTIONAL; default ignore
+ * HTML entities)
+ *
+ * @return string The truncated string
+ *
+ * @since 1.4.20 and 1.5.2 (replaced truncateWithEntities())
+ *
+ */
+function sm_truncate_string($string, $max_chars, $elipses='',
+ $html_entities_as_chars=FALSE)
+{
+
+ // if the length of the string is less than
+ // the allowable number of characters, just
+ // return it as is (even if it contains any
+ // HTML entities, that would just make the
+ // actual length even smaller)
+ //
+ $actual_strlen = sq_strlen($string, 'auto');
+ if ($max_chars <= 0 || $actual_strlen <= $max_chars)
+ return $string;
+
+
+ // if needed, count the number of HTML entities in
+ // the string up to the maximum character limit,
+ // pushing that limit up for each entity found
+ //
+ $adjusted_max_chars = $max_chars;
+ if ($html_entities_as_chars)
+ {
+
+ // $loop_count is needed to prevent an endless loop
+ // which is caused by buggy mbstring versions that
+ // return 0 (zero) instead of FALSE in some rare
+ // cases. Thanks, PHP.
+ // see: http://bugs.php.net/bug.php?id=52731
+ // also: tracker $3053349
+ //
+ $loop_count = 0;
+ $entity_pos = $entity_end_pos = -1;
+ while ($entity_end_pos + 1 < $actual_strlen
+ && ($entity_pos = sq_strpos($string, '&', $entity_end_pos + 1)) !== FALSE
+ && ($entity_end_pos = sq_strpos($string, ';', $entity_pos)) !== FALSE
+ && $entity_pos <= $adjusted_max_chars
+ && $loop_count++ < $max_chars)
+ {
+ $adjusted_max_chars += $entity_end_pos - $entity_pos;
+ }
+
+
+ // this isn't necessary because sq_substr() would figure this
+ // out anyway, but we can avoid a sq_substr() call and we
+ // know that we don't have to add an elipses (this is now
+ // an accurate comparison, since $adjusted_max_chars, like
+ // $actual_strlen, does not take into account HTML entities)
+ //
+ if ($actual_strlen <= $adjusted_max_chars)
+ return $string;
+
+ }
+
+
+ // get the truncated string
+ //
+ $truncated_string = sq_substr($string, 0, $adjusted_max_chars);
+
+
+ // return with added elipses
+ //
+ return $truncated_string . $elipses;
+
+}
+
+/**
+ * Gathers the list of secuirty tokens currently
+ * stored in the user's preferences and optionally
+ * purges old ones from the list.
+ *
+ * @param boolean $purge_old Indicates if old tokens
+ * should be purged from the
+ * list ("old" is 2 days or
+ * older unless the administrator
+ * overrides that value using
+ * $max_token_age_days in
+ * config/config_local.php)
+ * (OPTIONAL; default is to always
+ * purge old tokens)
+ *
+ * @return array The list of tokens
+ *
+ * @since 1.4.19 and 1.5.2
+ *
+ */
+function sm_get_user_security_tokens($purge_old=TRUE)
+{
+
+ global $data_dir, $username, $max_token_age_days;
+
+ $tokens = getPref($data_dir, $username, 'security_tokens', '');
+ if (($tokens = unserialize($tokens)) === FALSE || !is_array($tokens))
+ $tokens = array();
+
+ // purge old tokens if necessary
+ //
+ if ($purge_old)
+ {
+ if (empty($max_token_age_days)) $max_token_age_days = 2;
+ $now = time();
+ $discard_token_date = $now - ($max_token_age_days * 86400);
+ $cleaned_tokens = array();
+ foreach ($tokens as $token => $timestamp)
+ if ($timestamp >= $discard_token_date)
+ $cleaned_tokens[$token] = $timestamp;
+ $tokens = $cleaned_tokens;
+ }
+
+ return $tokens;
+
+}
+
+/**
+ * Generates a security token that is then stored in
+ * the user's preferences with a timestamp for later
+ * verification/use.
+ *
+ * NOTE: The administrator can force SquirrelMail to generate
+ * a new token every time one is requested (which may increase
+ * obscurity through token randomness at the cost of some
+ * performance) by adding the following to
+ * config/config_local.php: $do_not_use_single_token = TRUE;
+ * Otherwise, only one token will be generated per user which
+ * will change only after it expires or is used outside of the
+ * validity period specified when calling sm_validate_security_token()
+ *
+ * WARNING: If the administrator has turned the token system
+ * off by setting $disable_security_tokens to TRUE in
+ * config/config.php or the configuration tool, this
+ * function will not store tokens in the user
+ * preferences (but it will still generate and return
+ * a random string).
+ *
+ * @param boolean $force_generate_new When TRUE, a new token will
+ * always be created even if current
+ * configuration dictates otherwise
+ * (OPTIONAL; default FALSE)
+ *
+ * @return string A security token
+ *
+ * @since 1.4.19 and 1.5.2
+ *
+ */
+function sm_generate_security_token($force_generate_new=FALSE)
+{
+
+ global $data_dir, $username, $disable_security_tokens, $do_not_use_single_token;
+ $max_generation_tries = 1000;
+
+ $tokens = sm_get_user_security_tokens();
+
+ if (!$force_generate_new && !$do_not_use_single_token && !empty($tokens))
+ return key($tokens);
+
+ $new_token = GenerateRandomString(12, '', 7);
+ $count = 0;
+ while (isset($tokens[$new_token]))
+ {
+ $new_token = GenerateRandomString(12, '', 7);
+ if (++$count > $max_generation_tries)
+ {
+ logout_error(_("Fatal token generation error; please contact your system administrator or the SquirrelMail Team"));
+ exit;
+ }
+ }
+
+ // is the token system enabled? CAREFUL!
+ //
+ if (!$disable_security_tokens)
+ {
+ $tokens[$new_token] = time();
+ setPref($data_dir, $username, 'security_tokens', serialize($tokens));
+ }
+
+ return $new_token;
+
+}
+
+/**
+ * Validates a given security token and optionally remove it
+ * from the user's preferences if it was valid. If the token
+ * is too old but otherwise valid, it will still be rejected.
+ *
+ * "Too old" is 2 days or older unless the administrator
+ * overrides that value using $max_token_age_days in
+ * config/config_local.php
+ *
+ * WARNING: If the administrator has turned the token system
+ * off by setting $disable_security_tokens to TRUE in
+ * config/config.php or the configuration tool, this
+ * function will always return TRUE.
+ *
+ * @param string $token The token to validate
+ * @param int $validity_period The number of seconds tokens are valid
+ * for (set to zero to remove valid tokens
+ * after only one use; set to -1 to allow
+ * indefinite re-use (but still subject to
+ * $max_token_age_days - see elsewhere);
+ * use 3600 to allow tokens to be reused for
+ * an hour) (OPTIONAL; default is to only
+ * allow tokens to be used once)
+ * NOTE this is unrelated to $max_token_age_days
+ * or rather is an additional time constraint on
+ * tokens that allows them to be re-used (or not)
+ * within a more narrow timeframe
+ * @param boolean $show_error Indicates that if the token is not
+ * valid, this function should display
+ * a generic error, log the user out
+ * and exit - this function will never
+ * return in that case.
+ * (OPTIONAL; default FALSE)
+ *
+ * @return boolean TRUE if the token validated; FALSE otherwise
+ *
+ * @since 1.4.19 and 1.5.2
+ *
+ */
+function sm_validate_security_token($token, $validity_period=0, $show_error=FALSE)
+{
+
+ global $data_dir, $username, $max_token_age_days,
+ $disable_security_tokens;
+
+ // bypass token validation? CAREFUL!
+ //
+ if ($disable_security_tokens) return TRUE;
+
+ // don't purge old tokens here because we already
+ // do it when generating tokens
+ //
+ $tokens = sm_get_user_security_tokens(FALSE);
+
+ // token not found?
+ //
+ if (empty($tokens[$token]))
+ {
+ if (!$show_error) return FALSE;
+ logout_error(_("This page request could not be verified and appears to have expired."));
+ exit;
+ }
+
+ $now = time();
+ $timestamp = $tokens[$token];
+
+ // whether valid or not, we want to remove it from
+ // user prefs if it's old enough (unless requested to
+ // bypass this (in which case $validity_period is -1))
+ //
+ if ($validity_period >= 0
+ && $timestamp < $now - $validity_period)
+ {
+ unset($tokens[$token]);
+ setPref($data_dir, $username, 'security_tokens', serialize($tokens));
+ }
+
+ // reject tokens that are too old
+ //
+ if (empty($max_token_age_days)) $max_token_age_days = 2;
+ $old_token_date = $now - ($max_token_age_days * 86400);
+ if ($timestamp < $old_token_date)
+ {
+ if (!$show_error) return FALSE;
+ logout_error(_("The current page request appears to have originated from an untrusted source."));
+ exit;
+ }
+
+ // token OK!
+ //
+ return TRUE;
+
+}
+
+/**
+ * Wrapper for PHP's htmlspecialchars() that
+ * attempts to add the correct character encoding
+ *
+ * @param string $string The string to be converted
+ * @param int $flags A bitmask that controls the behavior of htmlspecialchars()
+ * (See http://php.net/manual/function.htmlspecialchars.php )
+ * (OPTIONAL; default ENT_COMPAT, ENT_COMPAT | ENT_SUBSTITUTE for PHP >=5.4)
+ * @param string $encoding The character encoding to use in the conversion
+ * (OPTIONAL; default automatic detection)
+ * @param boolean $double_encode Whether or not to convert entities that are
+ * already in the string (only supported in
+ * PHP 5.2.3+) (OPTIONAL; default TRUE)
+ *
+ * @return string The converted text
+ *
+ */
+function sm_encode_html_special_chars($string, $flags=ENT_COMPAT,
+ $encoding=NULL, $double_encode=TRUE)
+{
+ if (!$encoding)
+ {
+ global $default_charset;
+ if ($default_charset == 'iso-2022-jp')
+ $default_charset = 'EUC-JP';
+ $encoding = $default_charset;
+ }
+
+ if (check_php_version(5, 2, 3)) {
+ // Replace invalid characters with a symbol instead of returning
+ // empty string for the entire to be encoded string.
+ if (check_php_version(5, 4, 0) && $flags == ENT_COMPAT) {
+ $flags = $flags | ENT_SUBSTITUTE;
+ }
+ return htmlspecialchars($string, $flags, $encoding, $double_encode);
+ }
+
+ return htmlspecialchars($string, $flags, $encoding);
+}
+