to be displayed.
+ * The output is wrapped in <> and <
> tags.
+ *
+ * @return void
+ */
+function sm_print_r() {
+ ob_start(); // Buffer output
+ foreach(func_get_args() as $var) {
+ print_r($var);
+ echo "\n";
+ }
+ $buffer = ob_get_contents(); // Grab the print_r output
+ ob_end_clean(); // Silently discard the output & stop buffering
+ print '';
+ print htmlentities($buffer);
+ print '
';
+}
+
+/**
+ * version of fwrite which checks for failure
+ */
+function sq_fwrite($fp, $string) {
+ // write to file
+ $count = @fwrite($fp,$string);
+ // the number of bytes written should be the length of the string
+ if($count != strlen($string)) {
+ return FALSE;
+ }
+
+ return $count;
+}
+/**
+ * Tests if string contains 8bit symbols.
+ *
+ * If charset is not set, function defaults to default_charset.
+ * $default_charset global must be set correctly if $charset is
+ * not used.
+ * @param string $string tested string
+ * @param string $charset charset used in a string
+ * @return bool true if 8bit symbols are detected
+ * @since 1.5.1 and 1.4.4
+ */
+function sq_is8bit($string,$charset='') {
+ global $default_charset;
+
+ if ($charset=='') $charset=$default_charset;
+
+ /**
+ * Don't use \240 in ranges. Sometimes RH 7.2 doesn't like it.
+ * Don't use \200-\237 for iso-8859-x charsets. This ranges
+ * stores control symbols in those charsets.
+ * Use preg_match instead of ereg in order to avoid problems
+ * with mbstring overloading
+ */
+ if (preg_match("/^iso-8859/i",$charset)) {
+ $needle='/\240|[\241-\377]/';
+ } else {
+ $needle='/[\200-\237]|\240|[\241-\377]/';
+ }
+ return preg_match("$needle",$string);
+}
+
+/**
+ * Function returns number of characters in string.
+ *
+ * Returned number might be different from number of bytes in string,
+ * if $charset is multibyte charset. Detection depends on mbstring
+ * functions. If mbstring does not support tested multibyte charset,
+ * vanilla string length function is used.
+ * @param string $str string
+ * @param string $charset charset
+ * @since 1.5.1 and 1.4.6
+ * @return integer number of characters in string
+ */
+function sq_strlen($string, $charset=NULL){
+
+ // NULL charset? Just use strlen()
+ //
+ if (is_null($charset))
+ return strlen($string);
+
+
+ // use current character set?
+ //
+ if ($charset == 'auto')
+ {
+//FIXME: this may or may not be better as a session value instead of a global one
+ global $sq_string_func_auto_charset;
+ if (!isset($sq_string_func_auto_charset))
+ {
+ global $default_charset, $squirrelmail_language;
+ set_my_charset();
+ $sq_string_func_auto_charset = $default_charset;
+ if ($squirrelmail_language == 'ja_JP') $sq_string_func_auto_charset = 'euc-jp';
+ }
+ $charset = $sq_string_func_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_strlen_mb_charsets = array(
+ 'utf-8',
+ 'big5',
+ 'gb2312',
+ 'gb18030',
+ 'euc-jp',
+ 'euc-cn',
+ 'euc-tw',
+ 'euc-kr'
+ );
+
+
+ // now we can use mb_strlen() if needed
+ //
+ if (in_array($charset, $sq_strlen_mb_charsets)
+ && in_array($charset, sq_mb_list_encodings()))
+===== */
+//FIXME: is there any reason why this cannot be a static global array used by all string wrapper functions?
+ if (in_array($charset, sq_mb_list_encodings()))
+ return mb_strlen($string, $charset);
+
+
+ // else use normal strlen()
+ //
+ return strlen($string);
+
+}
+
+/**
+ * This is a replacement for PHP's strpos() that is
+ * multibyte-aware.
+ *
+ * @param string $haystack The string to search within
+ * @param string $needle The substring to search for
+ * @param int $offset The offset from the beginning of $haystack
+ * from which to start searching
+ * (OPTIONAL; default none)
+ * @param string $charset The charset of the given string. A value of NULL
+ * here will force the use of PHP's standard strpos().
+ * (OPTIONAL; default is "auto", which indicates that
+ * the user's current charset should be used).
+ *
+ * @return mixed The integer offset of the next $needle in $haystack,
+ * if found, or FALSE if not found
+ *
+ */
+function sq_strpos($haystack, $needle, $offset=0, $charset='auto')
+{
+
+ // NULL charset? Just use strpos()
+ //
+ if (is_null($charset))
+ return strpos($haystack, $needle, $offset);
+
+
+ // use current character set?
+ //
+ if ($charset == 'auto')
+ {
+//FIXME: this may or may not be better as a session value instead of a global one
+ global $sq_string_func_auto_charset;
+ if (!isset($sq_string_func_auto_charset))
+ {
+ global $default_charset, $squirrelmail_language;
+ set_my_charset();
+ $sq_string_func_auto_charset = $default_charset;
+ if ($squirrelmail_language == 'ja_JP') $sq_string_func_auto_charset = 'euc-jp';
+ }
+ $charset = $sq_string_func_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_strpos_mb_charsets = array(
+ 'utf-8',
+ 'big5',
+ 'gb2312',
+ 'gb18030',
+ 'euc-jp',
+ 'euc-cn',
+ 'euc-tw',
+ 'euc-kr'
+ );
+
+
+ // now we can use mb_strpos() if needed
+ //
+ if (in_array($charset, $sq_strpos_mb_charsets)
+ && in_array($charset, sq_mb_list_encodings()))
+===== */
+//FIXME: is there any reason why this cannot be a static global array used by all string wrapper functions?
+ if (in_array($charset, sq_mb_list_encodings()))
+ return mb_strpos($haystack, $needle, $offset, $charset);
+
+
+ // else use normal strpos()
+ //
+ return strpos($haystack, $needle, $offset);
+
+}
+
+/**
+ * This is a replacement for PHP's substr() that is
+ * multibyte-aware.
+ *
+ * @param string $string The string to operate upon
+ * @param int $start The offset at which to begin substring extraction
+ * @param int $length The number of characters after $start to return
+ * NOTE that if you need to specify a charset but
+ * want to achieve normal substr() 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 desired substring
+ *
+ * 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.php
+ *
+ */
+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, $charset);
+
+
+ // NULL charset? Just use substr()
+ //
+ if (is_null($charset))
+ return substr($string, $start, $length);
+
+
+ // use current character set?
+ //
+ if ($charset == 'auto')
+ {
+//FIXME: this may or may not be better as a session value instead of a global one
+ global $sq_string_func_auto_charset;
+ if (!isset($sq_string_func_auto_charset))
+ {
+ global $default_charset, $squirrelmail_language;
+ set_my_charset();
+ $sq_string_func_auto_charset = $default_charset;
+ if ($squirrelmail_language == 'ja_JP') $sq_string_func_auto_charset = 'euc-jp';
+ }
+ $charset = $sq_string_func_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_mb_charsets = array(
+ 'utf-8',
+ 'big5',
+ 'gb2312',
+ 'gb18030',
+ 'euc-jp',
+ 'euc-cn',
+ 'euc-tw',
+ 'euc-kr'
+ );
+
+
+ // now we can use mb_substr() if needed
+ //
+ if (in_array($charset, $sq_substr_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()))
+ return mb_substr($string, $start, $length, $charset);
+
+
+ // else use normal substr()
+ //
+ 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: this may or may not be better as a session value instead of a global one
+ $charset = $auto_charset;
+ global $sq_string_func_auto_charset;
+ if (!isset($sq_string_func_auto_charset))
+ {
+ global $default_charset, $squirrelmail_language;
+ set_my_charset();
+ $sq_string_func_auto_charset = $default_charset;
+ if ($squirrelmail_language == 'ja_JP') $sq_string_func_auto_charset = 'euc-jp';
+ }
+ $charset = $sq_string_func_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);
+
+}
+
+/**
+ * Replacement of mb_list_encodings function
+ *
+ * This function provides replacement for function that is available only
+ * in php 5.x. Function does not test all mbstring encodings. Only the ones
+ * that might be used in SM translations.
+ *
+ * Supported strings are stored in session in order to reduce number of
+ * mb_internal_encoding function calls.
+ *
+ * If mb_list_encodings() function is present, code uses it. Main difference
+ * from original function behaviour - array values are lowercased in order to
+ * simplify use of returned array in in_array() checks.
+ *
+ * If you want to test all mbstring encodings - fill $list_of_encodings
+ * array.
+ * @return array list of encodings supported by php mbstring extension
+ * @since 1.5.1 and 1.4.6
+ */
+function sq_mb_list_encodings() {
+
+ // if it's already in the session, don't need to regenerate it
+ if (sqgetGlobalVar('mb_supported_encodings',$mb_supported_encodings,SQ_SESSION)
+ && is_array($mb_supported_encodings))
+ return $mb_supported_encodings;
+
+ // check if mbstring extension is present
+ if (! function_exists('mb_internal_encoding')) {
+ $supported_encodings = array();
+ sqsession_register($supported_encodings, 'mb_supported_encodings');
+ return $supported_encodings;
+ }
+
+ // php 5+ function
+ if (function_exists('mb_list_encodings')) {
+ $supported_encodings = mb_list_encodings();
+ array_walk($supported_encodings, 'sq_lowercase_array_vals');
+ sqsession_register($supported_encodings, 'mb_supported_encodings');
+ return $supported_encodings;
+ }
+
+ // save original encoding
+ $orig_encoding=mb_internal_encoding();
+
+ $list_of_encoding=array(
+ 'pass',
+ 'auto',
+ 'ascii',
+ 'jis',
+ 'utf-8',
+ 'sjis',
+ 'euc-jp',
+ 'iso-8859-1',
+ 'iso-8859-2',
+ 'iso-8859-7',
+ 'iso-8859-9',
+ 'iso-8859-15',
+ 'koi8-r',
+ 'koi8-u',
+ 'big5',
+ 'gb2312',
+ 'gb18030',
+ 'windows-1251',
+ 'windows-1255',
+ 'windows-1256',
+ 'tis-620',
+ 'iso-2022-jp',
+ 'euc-cn',
+ 'euc-kr',
+ 'euc-tw',
+ 'uhc',
+ 'utf7-imap');
+
+ $supported_encodings=array();
+
+ foreach ($list_of_encoding as $encoding) {
+ // try setting encodings. suppress warning messages
+ if (@mb_internal_encoding($encoding))
+ $supported_encodings[]=$encoding;
+ }
+
+ // restore original encoding
+ mb_internal_encoding($orig_encoding);
+
+ // register list in session
+ sqsession_register($supported_encodings, 'mb_supported_encodings');
+
+ return $supported_encodings;
+}
+
+/**
+ * Callback function used to lowercase array values.
+ * @param string $val array value
+ * @param mixed $key array key
+ * @since 1.5.1 and 1.4.6
+ */
+function sq_lowercase_array_vals(&$val,$key) {
+ $val = strtolower($val);
+}
+
+/**
+ * Callback function to trim whitespace from a value, to be used in array_walk
+ * @param string $value value to trim
+ * @since 1.5.2 and 1.4.7
+ */
+function sq_trim_value ( &$value ) {
+ $value = trim($value);
+}
+
+/**
+ * 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; 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
+ //
+ if ($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)
+ * @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;
+ }
+
+ $string = iconv($encoding, 'UTF-8', $string);
+ if (check_php_version(5, 2, 3))
+ $ret = htmlspecialchars($string, $flags, 'UTF-8', $double_encode);
+ else
+ $ret = htmlspecialchars($string, $flags, 'UTF-8');
+
+ return iconv('UTF-8', $encoding, $ret);
+}
+
+$PHP_SELF = php_self();