* This code provides various string manipulation functions that are
* used by the rest of the SquirrelMail code.
*
- * @copyright 1999-2012 The SquirrelMail Project Team
+ * @copyright 1999-2024 The SquirrelMail Project Team
* @license http://opensource.org/licenses/gpl-license.php GNU Public License
* @version $Id$
* @package squirrelmail
// (i.e. try to preserve original paragraph breaks)
// unless they occur at the very beginning of the text
if ((sq_substr($body,$pos,1) == "\n" ) && (sq_strlen($outString) != 0)) {
- $outStringLast = $outString{sq_strlen($outString) - 1};
+ $outStringLast = $outString[sq_strlen($outString) - 1];
if ($outStringLast != "\n") {
$outString .= "\n";
}
/*
$ldnspacecnt = 0;
if ($mypos == $nextNewline+1) {
- while (($mypos < $length) && ($body{$mypos} == ' ')) {
+ while (($mypos < $length) && ($body[$mypos] == ' ')) {
$ldnspacecnt++;
}
}
$firstword = sq_substr($body,$mypos,sq_strpos($body,' ',$mypos) - $mypos);
//if ($dowrap || $ldnspacecnt > 1 || ($firstword && (
if (!$smartwrap || $firstword && (
- $firstword{0} == '-' ||
- $firstword{0} == '+' ||
- $firstword{0} == '*' ||
+ $firstword[0] == '-' ||
+ $firstword[0] == '+' ||
+ $firstword[0] == '*' ||
sq_substr($firstword,0,1) == sq_strtoupper(sq_substr($firstword,0,1)) ||
strpos($firstword,':'))) {
$outString .= sq_substr($body,$pos,($lastRealChar - $pos+1));
$is_secure_connection, $sq_ignore_http_x_forwarded_headers;
/* Get the path, handle virtual directories */
- if(strpos(php_self(), '?')) {
- $path = substr(php_self(), 0, strpos(php_self(), '?'));
- } else {
- $path = php_self();
- }
- $path = substr($path, 0, strrpos($path, '/'));
+ $path = substr(php_self(FALSE), 0, strrpos(php_self(FALSE), '/'));
// proto+host+port are already set in config:
if ( !empty($config_location_base) ) {
* a more easily digested (readable) format
*
* @param int $bytes the size in bytes
+ * @param int $filesize_divisor the divisor we'll use (OPTIONAL; default 1024)
*
* @return string The size in human readable format
*
* @since 1.0
*
*/
-function show_readable_size($bytes) {
- $bytes /= 1024;
+function show_readable_size($bytes, $filesize_divisor=1024) {
+ $bytes /= $filesize_divisor;
$type = _("KiB");
- if ($bytes / 1024 > 1) {
- $bytes /= 1024;
+ if ($bytes / $filesize_divisor > 1) {
+ $bytes /= $filesize_divisor;
$type = _("MiB");
}
$String = '';
$j = strlen( $chars ) - 1;
while (strlen($String) < $size) {
- $String .= $chars{mt_rand(0, $j)};
+ $String .= $chars[mt_rand(0, $j)];
}
return $String;
function sm_get_user_security_tokens($purge_old=TRUE)
{
- global $data_dir, $username, $max_token_age_days;
+ global $data_dir, $username, $max_token_age_days,
+ $use_expiring_security_tokens;
$tokens = getPref($data_dir, $username, 'security_tokens', '');
if (($tokens = unserialize($tokens)) === FALSE || !is_array($tokens))
/**
* Generates a security token that is then stored in
* the user's preferences with a timestamp for later
- * verification/use.
+ * verification/use (although session-based tokens
+ * are not stored in user preferences).
+ *
+ * NOTE: By default SquirrelMail will use a single session-based
+ * token, but if desired, user tokens can have expiration
+ * dates associated with them and become invalid even during
+ * the same login session. When in that mode, the note
+ * immediately below applies, otherwise it is irrelevant.
+ * To enable that mode, the administrator must add the
+ * following to config/config_local.php:
+ * $use_expiring_security_tokens = TRUE;
*
* NOTE: The administrator can force SquirrelMail to generate
* a new token every time one is requested (which may increase
function sm_generate_security_token($force_generate_new=FALSE)
{
- global $data_dir, $username, $disable_security_tokens, $do_not_use_single_token;
+ global $data_dir, $username, $disable_security_tokens, $do_not_use_single_token,
+ $use_expiring_security_tokens;
$max_generation_tries = 1000;
+ // if we're using session-based tokens, just return
+ // the same one every time (generate it if it's not there)
+ //
+ if (!$use_expiring_security_tokens)
+ {
+ if (sqgetGlobalVar('sm_security_token', $token, SQ_SESSION))
+ return $token;
+
+ // create new one since there was none in session
+ $token = GenerateRandomString(12, '', 7);
+ sqsession_register($token, 'sm_security_token');
+ return $token;
+ }
+
$tokens = sm_get_user_security_tokens();
if (!$force_generate_new && !$do_not_use_single_token && !empty($tokens))
* overrides that value using $max_token_age_days in
* config/config_local.php
*
+ * Session-based tokens of course are always reused and are
+ * valid for the lifetime of the login session.
+ *
* 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
* @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; use 3600 to allow
- * tokens to be reused for an hour)
- * (OPTIONAL; default is to only allow tokens
- * to be used once)
+ * 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)
{
global $data_dir, $username, $max_token_age_days,
+ $use_expiring_security_tokens,
$disable_security_tokens;
// bypass token validation? CAREFUL!
//
if ($disable_security_tokens) return TRUE;
+ // if we're using session-based tokens, just compare
+ // the same one every time
+ //
+ if (!$use_expiring_security_tokens)
+ {
+ if (!sqgetGlobalVar('sm_security_token', $session_token, SQ_SESSION))
+ {
+ if (!$show_error) return FALSE;
+ logout_error(_("Fatal security token error; please log in again"));
+ exit;
+ }
+ if ($token !== $session_token)
+ {
+ if (!$show_error) return FALSE;
+ logout_error(_("The current page request appears to have originated from an untrusted source."));
+ exit;
+ }
+ return TRUE;
+ }
+
// don't purge old tokens here because we already
// do it when generating tokens
//
$timestamp = $tokens[$token];
// whether valid or not, we want to remove it from
- // user prefs if it's old enough
+ // user prefs if it's old enough (unless requested to
+ // bypass this (in which case $validity_period is -1))
//
- if ($timestamp < $now - $validity_period)
+ if ($validity_period >= 0
+ && $timestamp < $now - $validity_period)
{
unset($tokens[$token]);
setPref($data_dir, $username, 'security_tokens', serialize($tokens));
* 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()
+ * @param int $flags A bitmask that controls the behavior of
+ * htmlspecialchars() -- NOTE that this parameter
+ * should only be used to dictate handling of
+ * quotes; handling invalid code sequences is done
+ * using the $invalid_sequence_flag parameter below
* (See http://php.net/manual/function.htmlspecialchars.php )
* (OPTIONAL; default ENT_COMPAT)
* @param string $encoding The character encoding to use in the conversion
- * (OPTIONAL; default automatic detection)
+ * (if not one of the character sets supported
+ * by PHP's htmlspecialchars(), then $encoding
+ * will be ignored and iso-8859-1 will be used,
+ * unless a default has been specified in
+ * $default_htmlspecialchars_encoding in
+ * config_local.php) (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)
+ * @param mixed $invalid_sequence_flag A bitmask that controls how invalid
+ * code sequences should be handled;
+ * When calling htmlspecialchars(),
+ * this value will be combined with
+ * the $flags parameter above
+ * (See http://php.net/manual/function.htmlspecialchars.php )
+ * (OPTIONAL; defaults to the string
+ * "ent_substitute" that, for PHP 5.4+,
+ * is converted to the ENT_SUBSTITUTE
+ * constant, otherwise empty)
*
* @return string The converted text
*
*/
function sm_encode_html_special_chars($string, $flags=ENT_COMPAT,
- $encoding=NULL, $double_encode=TRUE)
+ $encoding=NULL, $double_encode=TRUE,
+ $invalid_sequence_flag='ent_substitute')
{
+ if ($invalid_sequence_flag === 'ent_substitute')
+ {
+ if (check_php_version(5, 4, 0))
+ $invalid_sequence_flag = ENT_SUBSTITUTE;
+ else
+ $invalid_sequence_flag = 0;
+ }
+
+
+ // charsets supported by PHP's htmlspecialchars
+ // (move this elsewhere if needed)
+ //
+ static $htmlspecialchars_charsets = array(
+ 'iso-8859-1', 'iso8859-1',
+ 'iso-8859-5', 'iso8859-5',
+ 'iso-8859-15', 'iso8859-15',
+ 'utf-8',
+ 'cp866', 'ibm866', '866',
+ 'cp1251', 'windows-1251', 'win-1251', '1251',
+ 'cp1252', 'windows-1252', '1252',
+ 'koi8-R', 'koi8-ru', 'koi8r',
+ 'big5', '950',
+ 'gb2312', '936',
+ 'big5-hkscs',
+ 'shift_jis', 'sjis', 'sjis-win', 'cp932', '932',
+ 'euc-jp', 'eucjp', 'eucjp-win',
+ 'macroman',
+ );
+
+
+ // if not given, set encoding to the charset being
+ // used by the current user interface language
+ //
if (!$encoding)
{
global $default_charset;
$encoding = $default_charset;
}
+
+ // two ways to handle encodings not supported by htmlspecialchars() -
+ // one takes less CPU cycles but can munge characters in certain
+ // translations, the other is more exact but requires more resources
+ //
+ global $html_special_chars_extended_fix;
+//FIXME: need to document that the config switch above can be enabled in config_local... but first, we need to decide if we will implement the second option here -- currently there hasn't been a need for it (munged characters seem quite rare).... see tracker #2806 for some tips https://sourceforge.net/p/squirrelmail/bugs/2806
+ if (!in_array(strtolower($encoding), $htmlspecialchars_charsets))
+ {
+ if ($html_special_chars_extended_fix)
+ {
+ // convert to utf-8 first, run htmlspecialchars() and convert
+ // back to original encoding below
+ //
+//FIXME: try conversion functions in this order: recode_string(), iconv(), mbstring (with various charset checks: sq_mb_list_encodings(), mb_check_encoding) -- oh, first check for internal charset_decode_CHARSET() function?? or just use (does this put everything into HTML entities already? shouldn't, but if it does, return right here):
+ $string = charset_decode($encoding, $string, TRUE, TRUE);
+ $string = charset_encode($string, $encoding, TRUE);
+ }
+ else
+ {
+ // simply force use of an encoding that is supported (some
+ // characters may be munged)
+ //
+ // use default from configuration if provided or hard-coded fallback
+ //
+ global $default_htmlspecialchars_encoding;
+ if (!empty($default_htmlspecialchars_encoding))
+ $encoding = $default_htmlspecialchars_encoding;
+ else
+ $encoding = 'iso-8859-1';
+ }
+ }
+
+
// TODO: Is adding this check an unnecessary performance hit?
if (check_php_version(5, 2, 3))
- return htmlspecialchars($string, $flags, $encoding, $double_encode);
+ $ret = htmlspecialchars($string, $flags | $invalid_sequence_flag,
+ $encoding, $double_encode);
+ else
+ $ret = htmlspecialchars($string, $flags | $invalid_sequence_flag,
+ $encoding);
+
+
+ // convert back to original encoding if needed (see above)
+ //
+ if ($html_special_chars_extended_fix
+ && !in_array(strtolower($encoding), $htmlspecialchars_charsets))
+ {
+//FIXME: NOT FINISHED - here, we'd convert from utf-8 back to original charset (if we obey $lossy_encoding and end up returning in utf-8 instead of original charset, does that screw up the caller?)
+ }
+
- return htmlspecialchars($string, $flags, $encoding);
+ return $ret;
}