Source for file strings.php

Documentation is available at strings.php

  1. <?php
  2.  
  3. /**
  4.  * strings.php
  5.  *
  6.  * This code provides various string manipulation functions that are
  7.  * used by the rest of the SquirrelMail code.
  8.  *
  9.  * @copyright 1999-2020 The SquirrelMail Project Team
  10.  * @license http://opensource.org/licenses/gpl-license.php GNU Public License
  11.  * @version $Id: strings.php 14840 2020-01-07 07:42:38Z pdontthink $
  12.  * @package squirrelmail
  13.  */
  14.  
  15. /**
  16.  * SquirrelMail version number -- DO NOT CHANGE
  17.  */
  18. global $version;
  19. $version '1.4.23 [SVN]';
  20.  
  21. /**
  22.  * SquirrelMail internal version number -- DO NOT CHANGE
  23.  * $sm_internal_version = array (release, major, minor)
  24.  */
  25. global $SQM_INTERNAL_VERSION;
  26. $SQM_INTERNAL_VERSION array(1423);
  27.  
  28. /**
  29.  * There can be a circular issue with includes, where the $version string is
  30.  * referenced by the include of global.php, etc. before it's defined.
  31.  * For that reason, bring in global.php AFTER we define the version strings.
  32.  */
  33. require_once(SM_PATH 'functions/global.php');
  34.  
  35. if (file_exists(SM_PATH 'plugins/compatibility/functions.php')) {
  36.     include_once(SM_PATH 'plugins/compatibility/functions.php');
  37. }
  38.  
  39. /**
  40.  * Wraps text at $wrap characters
  41.  *
  42.  * Has a problem with special HTML characters, so call this before
  43.  * you do character translation.
  44.  *
  45.  * Specifically, &#039 comes up as 5 characters instead of 1.
  46.  * This should not add newlines to the end of lines.
  47.  */
  48. function sqWordWrap(&$line$wrap$charset=null{
  49.     global $languages$squirrelmail_language;
  50.  
  51.     if (isset($languages[$squirrelmail_language]['XTRA_CODE']&&
  52.         function_exists($languages[$squirrelmail_language]['XTRA_CODE'])) {
  53.         if (mb_detect_encoding($line!= 'ASCII'{
  54.             $line $languages[$squirrelmail_language]['XTRA_CODE']('wordwrap'$line$wrap);
  55.             return;
  56.         }
  57.     }
  58.  
  59.     preg_match('/^([\t >]*)([^\t >].*)?$/'$line$regs);
  60.     $beginning_spaces $regs[1];
  61.     if (isset($regs[2])) {
  62.         $words explode(' '$regs[2]);
  63.     else {
  64.         $words array();
  65.     }
  66.  
  67.     $i 0;
  68.     $line $beginning_spaces;
  69.  
  70.     while ($i count($words)) {
  71.         /* Force one word to be on a line (minimum) */
  72.         $line .= $words[$i];
  73.         $line_len strlen($beginning_spacessq_strlen($words[$i],$charset2;
  74.         if (isset($words[$i 1]))
  75.             $line_len += sq_strlen($words[$i 1],$charset);
  76.         $i ++;
  77.  
  78.         /* Add more words (as long as they fit) */
  79.         while ($line_len $wrap && $i count($words)) {
  80.             $line .= ' ' $words[$i];
  81.             $i++;
  82.             if (isset($words[$i]))
  83.                 $line_len += sq_strlen($words[$i],$charset1;
  84.             else
  85.                 $line_len += 1;
  86.         }
  87.  
  88.         /* Skip spaces if they are the first thing on a continued line */
  89.         while (!isset($words[$i]&& $i count($words)) {
  90.             $i ++;
  91.         }
  92.  
  93.         /* Go to the next line if we have more to process */
  94.         if ($i count($words)) {
  95.             $line .= "\n";
  96.         }
  97.     }
  98. }
  99.  
  100. /**
  101.  * Does the opposite of sqWordWrap()
  102.  * @param string body the text to un-wordwrap
  103.  * @return void 
  104.  */
  105. function sqUnWordWrap(&$body{
  106.     global $squirrelmail_language;
  107.  
  108.     if ($squirrelmail_language == 'ja_JP'{
  109.         return;
  110.     }
  111.  
  112.     $lines explode("\n"$body);
  113.     $body '';
  114.     $PreviousSpaces '';
  115.     $cnt count($lines);
  116.     for ($i 0$i $cnt$i ++{
  117.         preg_match("/^([\t >]*)([^\t >].*)?$/"$lines[$i]$regs);
  118.         $CurrentSpaces $regs[1];
  119.         if (isset($regs[2])) {
  120.             $CurrentRest $regs[2];
  121.         else {
  122.             $CurrentRest '';
  123.         }
  124.  
  125.         if ($i == 0{
  126.             $PreviousSpaces $CurrentSpaces;
  127.             $body $lines[$i];
  128.         else if (($PreviousSpaces == $CurrentSpaces/* Do the beginnings match */
  129.                    && (strlen($lines[$i 1]65)    /* Over 65 characters long */
  130.                    && strlen($CurrentRest)) {          /* and there's a line to continue with */
  131.             $body .= ' ' $CurrentRest;
  132.         else {
  133.             $body .= "\n" $lines[$i];
  134.             $PreviousSpaces $CurrentSpaces;
  135.         }
  136.     }
  137.     $body .= "\n";
  138. }
  139.  
  140. /**
  141.   * Truncates the given string so that it has at
  142.   * most $max_chars characters.  NOTE that a "character"
  143.   * may be a multibyte character, or (optionally), an
  144.   * HTML entity, so this function is different than
  145.   * using substr() or mb_substr().
  146.   * 
  147.   * NOTE that if $elipses is given and used, the returned
  148.   *      number of characters will be $max_chars PLUS the
  149.   *      length of $elipses
  150.   * 
  151.   * @param string  $string    The string to truncate
  152.   * @param int     $max_chars The maximum allowable characters
  153.   * @param string  $elipses   A string that will be added to
  154.   *                            the end of the truncated string
  155.   *                            (ONLY if it is truncated) (OPTIONAL;
  156.   *                            default not used)
  157.   * @param boolean $html_entities_as_chars Whether or not to keep
  158.   *                                         HTML entities together
  159.   *                                         (OPTIONAL; default ignore
  160.   *                                         HTML entities)
  161.   *
  162.   * @return string The truncated string
  163.   *
  164.   * @since 1.4.20 and 1.5.2 (replaced truncateWithEntities())
  165.   *
  166.   */
  167. function sm_truncate_string($string$max_chars$elipses='',
  168.                             $html_entities_as_chars=FALSE)
  169. {
  170.  
  171.    // if the length of the string is less than
  172.    // the allowable number of characters, just
  173.    // return it as is (even if it contains any
  174.    // HTML entities, that would just make the
  175.    // actual length even smaller)
  176.    //
  177.    $actual_strlen sq_strlen($string'auto');
  178.    if ($max_chars <= || $actual_strlen <= $max_chars)
  179.       return $string;
  180.  
  181.  
  182.    // if needed, count the number of HTML entities in
  183.    // the string up to the maximum character limit,
  184.    // pushing that limit up for each entity found
  185.    //
  186.    $adjusted_max_chars $max_chars;
  187.    if ($html_entities_as_chars)
  188.    {
  189.  
  190.       // $loop_count is needed to prevent an endless loop
  191.       // which is caused by buggy mbstring versions that
  192.       // return 0 (zero) instead of FALSE in some rare
  193.       // cases.  Thanks, PHP.
  194.       // see: http://bugs.php.net/bug.php?id=52731
  195.       // also: tracker $3053349
  196.       //
  197.       $loop_count 0;
  198.       $entity_pos $entity_end_pos = -1;
  199.       while ($entity_end_pos $actual_strlen
  200.           && ($entity_pos sq_strpos($string'&'$entity_end_pos 1)) !== FALSE
  201.           && ($entity_end_pos sq_strpos($string';'$entity_pos)) !== FALSE
  202.           && $entity_pos <= $adjusted_max_chars
  203.           && $loop_count++ < $max_chars)
  204.       {
  205.          $adjusted_max_chars += $entity_end_pos $entity_pos;
  206.       }
  207.  
  208.  
  209.       // this isn't necessary because sq_substr() would figure this
  210.       // out anyway, but we can avoid a sq_substr() call and we
  211.       // know that we don't have to add an elipses (this is now
  212.       // an accurate comparison, since $adjusted_max_chars, like
  213.       // $actual_strlen, does not take into account HTML entities)
  214.       //
  215.       if ($actual_strlen <= $adjusted_max_chars)
  216.          return $string;
  217.  
  218.    }
  219.  
  220.  
  221.    // get the truncated string
  222.    //
  223.    $truncated_string sq_substr($string0$adjusted_max_chars);
  224.  
  225.  
  226.    // return with added elipses
  227.    //
  228.    return $truncated_string $elipses;
  229.  
  230. }
  231.  
  232. /**
  233.  * If $haystack is a full mailbox name and $needle is the mailbox
  234.  * separator character, returns the last part of the mailbox name.
  235.  *
  236.  * @param string haystack full mailbox name to search
  237.  * @param string needle the mailbox separator character
  238.  * @return string the last part of the mailbox name
  239.  */
  240. function readShortMailboxName($haystack$needle{
  241.  
  242.     if ($needle == ''{
  243.         $elem $haystack;
  244.     else {
  245.         $parts explode($needle$haystack);
  246.         $elem array_pop($parts);
  247.         while ($elem == '' && count($parts)) {
  248.             $elem array_pop($parts);
  249.         }
  250.     }
  251.     return$elem );
  252. }
  253.  
  254. /**
  255.  * php_self
  256.  *
  257.  * Attempts to determine the path and filename and any arguments
  258.  * for the currently executing script.  This is usually found in
  259.  * $_SERVER['REQUEST_URI'], but some environments may differ, so
  260.  * this function tries to standardize this value.
  261.  *
  262.  * @since 1.2.3
  263.  * @return string The path, filename and any arguments for the
  264.  *                 current script
  265.  */
  266. function php_self({
  267.  
  268.     $request_uri '';
  269.  
  270.     // first try $_SERVER['PHP_SELF'], which seems most reliable
  271.     // (albeit it usually won't include the query string)
  272.     //
  273.     $request_uri '';
  274.     if (!sqgetGlobalVar('PHP_SELF'$request_uriSQ_SERVER)
  275.      || empty($request_uri)) {
  276.  
  277.         // well, then let's try $_SERVER['REQUEST_URI']
  278.         //
  279.         $request_uri '';
  280.         if (!sqgetGlobalVar('REQUEST_URI'$request_uriSQ_SERVER)
  281.          || empty($request_uri)) {
  282.  
  283.             // TODO: anyone have any other ideas?  maybe $_SERVER['SCRIPT_NAME']???
  284.             //
  285.             return '';
  286.         }
  287.  
  288.     }
  289.  
  290.     // we may or may not have any query arguments, depending on
  291.     // which environment variable was used above, and the PHP
  292.     // version, etc., so let's check for it now
  293.     //
  294.     $query_string '';
  295.     if (strpos($request_uri'?'=== FALSE
  296.      && sqgetGlobalVar('QUERY_STRING'$query_stringSQ_SERVER)
  297.      && !empty($query_string)) {
  298.  
  299.         $request_uri .= '?' $query_string;
  300.     }
  301.  
  302.     return $request_uri;
  303.  
  304. }
  305.  
  306.  
  307. /**
  308.  * Find out where squirrelmail lives and try to be smart about it.
  309.  * The only problem would be when squirrelmail lives in directories
  310.  * called "src", "functions", or "plugins", but people who do that need
  311.  * to be beaten with a steel pipe anyway.
  312.  *
  313.  * @return string the base uri of squirrelmail installation.
  314.  */
  315. function sqm_baseuri(){
  316.     global $base_uri$PHP_SELF;
  317.     /**
  318.      * If it is in the session, just return it.
  319.      */
  320.     if (sqgetGlobalVar('base_uri',$base_uri,SQ_SESSION)){
  321.         return $base_uri;
  322.     }
  323.     $dirs array('|src/.*|''|plugins/.*|''|functions/.*|');
  324.     $repl array('''''');
  325.     $base_uri preg_replace($dirs$repl$PHP_SELF);
  326.     return $base_uri;
  327. }
  328.  
  329. /**
  330.  * get_location
  331.  *
  332.  * Determines the location to forward to, relative to your server.
  333.  * This is used in HTTP Location: redirects.
  334.  * If set, it uses $config_location_base as the first part of the URL,
  335.  * specifically, the protocol, hostname and port parts. The path is
  336.  * always autodetected.
  337.  *
  338.  * @return string the base url for this SquirrelMail installation
  339.  */
  340. function get_location ({
  341.  
  342.            $is_secure_connection$sq_ignore_http_x_forwarded_headers;
  343.  
  344.     /* Get the path, handle virtual directories */
  345.     if(strpos(php_self()'?')) {
  346.         $path substr(php_self()0strpos(php_self()'?'));
  347.     else {
  348.         $path php_self();
  349.     }
  350.     $path substr($path0strrpos($path'/'));
  351.  
  352.     // proto+host+port are already set in config:
  353.     if !empty($config_location_base) ) {
  354.         // register it in the session just in case some plugin depends on this
  355.         sqsession_register($config_location_base $path'sq_base_url');
  356.         return $config_location_base $path ;
  357.     }
  358.     // we computed it before, get it from the session:
  359.     if sqgetGlobalVar('sq_base_url'$full_urlSQ_SESSION) ) {
  360.         return $full_url $path;
  361.     }
  362.     // else: autodetect
  363.  
  364.     /* Check if this is a HTTPS or regular HTTP request. */
  365.     $proto 'http://';
  366.     if ($is_secure_connection)
  367.         $proto 'https://';
  368.  
  369.     /* Get the hostname from the Host header or server config. */
  370.     if ($sq_ignore_http_x_forwarded_headers
  371.      || !sqgetGlobalVar('HTTP_X_FORWARDED_HOST'$hostSQ_SERVER)
  372.      || empty($host)) {
  373.         if !sqgetGlobalVar('HTTP_HOST'$hostSQ_SERVER|| empty($host) ) {
  374.             if !sqgetGlobalVar('SERVER_NAME'$hostSQ_SERVER|| empty($host) ) {
  375.                 $host '';
  376.             }
  377.         }
  378.     }
  379.  
  380.     $port '';
  381.     if (strpos($host':'=== FALSE{
  382.         // Note: HTTP_X_FORWARDED_PROTO could be sent from the client and
  383.         //       therefore possibly spoofed/hackable - for now, the
  384.         //       administrator can tell SM to ignore this value by setting
  385.         //       $sq_ignore_http_x_forwarded_headers to boolean TRUE in
  386.         //       config/config_local.php, but in the future we may
  387.         //       want to default this to TRUE and make administrators
  388.         //       who use proxy systems turn it off (see 1.5.2+).
  389.         global $sq_ignore_http_x_forwarded_headers;
  390.         if ($sq_ignore_http_x_forwarded_headers
  391.          || !sqgetGlobalVar('HTTP_X_FORWARDED_PROTO'$forwarded_protoSQ_SERVER))
  392.             $forwarded_proto '';
  393.         if (sqgetGlobalVar('SERVER_PORT'$server_portSQ_SERVER)) {
  394.             if (($server_port != 80 && $proto == 'http://'||
  395.                 ($server_port != 443 && $proto == 'https://' &&
  396.                  strcasecmp($forwarded_proto'https'!== 0)) {
  397.                 $port sprintf(':%d'$server_port);
  398.             }
  399.         }
  400.     }
  401.  
  402.    /* this is a workaround for the weird macosx caching that
  403.       causes Apache to return 16080 as the port number, which causes
  404.       SM to bail */
  405.  
  406.    if ($imap_server_type == 'macosx' && $port == ':16080'{
  407.         $port '';
  408.    }
  409.  
  410.    /* Fallback is to omit the server name and use a relative */
  411.    /* URI, although this is not RFC 2616 compliant.          */
  412.    $full_url ($host $proto $host $port '');
  413.    sqsession_register($full_url'sq_base_url');
  414.    return $full_url $path;
  415. }
  416.  
  417.  
  418. /**
  419.  * Encrypts password
  420.  *
  421.  * These functions are used to encrypt the password before it is
  422.  * stored in a cookie. The encryption key is generated by
  423.  * OneTimePadCreate();
  424.  *
  425.  * @param string string the (password)string to encrypt
  426.  * @param string epad the encryption key
  427.  * @return string the base64-encoded encrypted password
  428.  */
  429. function OneTimePadEncrypt ($string$epad{
  430.     $pad base64_decode($epad);
  431.  
  432.     if (strlen($pad)>0{
  433.         // make sure that pad is longer than string
  434.         while (strlen($string)>strlen($pad)) {
  435.             $pad.=$pad;
  436.         }
  437.     else {
  438.         // FIXME: what should we do when $epad is not base64 encoded or empty.
  439.     }
  440.  
  441.     $encrypted '';
  442.     for ($i 0$i strlen ($string)$i++{
  443.         $encrypted .= chr (ord($string[$i]ord($pad[$i]));
  444.     }
  445.  
  446.     return base64_encode($encrypted);
  447. }
  448.  
  449. /**
  450.  * Decrypts a password from the cookie
  451.  *
  452.  * Decrypts a password from the cookie, encrypted by OneTimePadEncrypt.
  453.  * This uses the encryption key that is stored in the session.
  454.  *
  455.  * @param string string the string to decrypt
  456.  * @param string epad the encryption key from the session
  457.  * @return string the decrypted password
  458.  */
  459. function OneTimePadDecrypt ($string$epad{
  460.     $pad base64_decode($epad);
  461.  
  462.     if (strlen($pad)>0{
  463.         // make sure that pad is longer than string
  464.         while (strlen($string)>strlen($pad)) {
  465.             $pad.=$pad;
  466.         }
  467.     else {
  468.         // FIXME: what should we do when $epad is not base64 encoded or empty.
  469.     }
  470.  
  471.     $encrypted base64_decode ($string);
  472.     $decrypted '';
  473.     for ($i 0$i strlen ($encrypted)$i++{
  474.         $decrypted .= chr (ord($encrypted[$i]ord($pad[$i]));
  475.     }
  476.  
  477.     return $decrypted;
  478. }
  479.  
  480.  
  481. /**
  482.  * Randomizes the mt_rand() function.
  483.  *
  484.  * Toss this in strings or integers and it will seed the generator
  485.  * appropriately. With strings, it is better to get them long.
  486.  * Use md5() to lengthen smaller strings.
  487.  *
  488.  * @param mixed val a value to seed the random number generator
  489.  * @return void 
  490.  */
  491. function sq_mt_seed($Val{
  492.     /* if mt_getrandmax() does not return a 2^n - 1 number,
  493.        this might not work well.  This uses $Max as a bitmask. */
  494.     $Max mt_getrandmax();
  495.  
  496.     if (is_int($Val)) {
  497.             $Val crc32($Val);
  498.     }
  499.  
  500.     if ($Val 0{
  501.         $Val *= -1;
  502.     }
  503.  
  504.     if ($Val == 0{
  505.         return;
  506.     }
  507.  
  508.     mt_srand(($Val mt_rand(0$Max)) $Max);
  509. }
  510.  
  511.  
  512. /**
  513.  * Init random number generator
  514.  *
  515.  * This function initializes the random number generator fairly well.
  516.  * It also only initializes it once, so you don't accidentally get
  517.  * the same 'random' numbers twice in one session.
  518.  *
  519.  * @return void 
  520.  */
  521. function sq_mt_randomize({
  522.     static $randomized;
  523.  
  524.     if ($randomized{
  525.         return;
  526.     }
  527.  
  528.     /* Global. */
  529.     sqgetGlobalVar('REMOTE_PORT'$remote_portSQ_SERVER);
  530.     sqgetGlobalVar('REMOTE_ADDR'$remote_addrSQ_SERVER);
  531.     sq_mt_seed((int)((double) microtime(1000000));
  532.     sq_mt_seed(md5($remote_port $remote_addr getmypid()));
  533.  
  534.     /* getrusage */
  535.     if (function_exists('getrusage')) {
  536.         /* Avoid warnings with Win32 */
  537.         $dat @getrusage();
  538.         if (isset($dat&& is_array($dat)) {
  539.             $Str '';
  540.             foreach ($dat as $k => $v)
  541.                 {
  542.                     $Str .= $k $v;
  543.                 }
  544.             sq_mt_seed(md5($Str));
  545.         }
  546.     }
  547.  
  548.     if(sqgetGlobalVar('UNIQUE_ID'$unique_idSQ_SERVER)) {
  549.         sq_mt_seed(md5($unique_id));
  550.     }
  551.  
  552.     $randomized 1;
  553. }
  554.  
  555. /**
  556.  * Creates encryption key
  557.  *
  558.  * Creates an encryption key for encrypting the password stored in the cookie.
  559.  * The encryption key itself is stored in the session.
  560.  *
  561.  * @param int length optional, length of the string to generate
  562.  * @return string the encryption key
  563.  */
  564. function OneTimePadCreate ($length=100{
  565.     sq_mt_randomize();
  566.  
  567.     $pad '';
  568.     for ($i 0$i $length$i++{
  569.         $pad .= chr(mt_rand(0,255));
  570.     }
  571.  
  572.     return base64_encode($pad);
  573. }
  574.  
  575. /**
  576.  * Returns a string showing the size of the message/attachment.
  577.  *
  578.  * @param int bytes the filesize in bytes
  579.  * @return string the filesize in human readable format
  580.  */
  581. function show_readable_size($bytes{
  582.     $bytes /= 1024;
  583.     $type 'k';
  584.  
  585.     if ($bytes 1024 1{
  586.         $bytes /= 1024;
  587.         $type 'M';
  588.     }
  589.  
  590.     if ($bytes 10{
  591.         $bytes *= 10;
  592.         settype($bytes'integer');
  593.         $bytes /= 10;
  594.     else {
  595.         settype($bytes'integer');
  596.     }
  597.  
  598.     return $bytes '<small>&nbsp;' $type '</small>';
  599. }
  600.  
  601. /**
  602.  * Generates a random string from the caracter set you pass in
  603.  *
  604.  * @param int size the size of the string to generate
  605.  * @param string chars a string containing the characters to use
  606.  * @param int flags a flag to add a specific set to the characters to use:
  607.  *      Flags:
  608.  *        1 = add lowercase a-z to $chars
  609.  *        2 = add uppercase A-Z to $chars
  610.  *        4 = add numbers 0-9 to $chars
  611.  * @return string the random string
  612.  */
  613. function GenerateRandomString($size$chars$flags 0{
  614.     if ($flags 0x1{
  615.         $chars .= 'abcdefghijklmnopqrstuvwxyz';
  616.     }
  617.     if ($flags 0x2{
  618.         $chars .= 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
  619.     }
  620.     if ($flags 0x4{
  621.         $chars .= '0123456789';
  622.     }
  623.  
  624.     if (($size 1|| (strlen($chars1)) {
  625.         return '';
  626.     }
  627.  
  628.     sq_mt_randomize()/* Initialize the random number generator */
  629.  
  630.     $String '';
  631.     $j strlen$chars 1;
  632.     while (strlen($String$size{
  633.         $String .= $chars{mt_rand(0$j)};
  634.     }
  635.  
  636.     return $String;
  637. }
  638.  
  639. /**
  640.  * Escapes special characters for use in IMAP commands.
  641.  *
  642.  * @param string the string to escape
  643.  * @return string the escaped string
  644.  */
  645. function quoteimap($str{
  646.     // FIXME use this performance improvement (not changing because this is STABLE branch): return str_replace(array('\\', '"'), array('\\\\', '\\"'), $str);
  647.     return preg_replace("/([\"\\\\])/""\\\\$1"$str);
  648. }
  649.  
  650. /**
  651.  * Trims array
  652.  *
  653.  * Trims every element in the array, ie. remove the first char of each element
  654.  * Obsolete: will probably removed soon
  655.  * @param array array the array to trim
  656.  * @obsolete
  657.  */
  658. function TrimArray(&$array{
  659.     foreach ($array as $k => $v{
  660.         global $$k;
  661.         if (is_array($$k)) {
  662.             foreach ($$k as $k2 => $v2{
  663.                 $$k[$k2substr($v21);
  664.             }
  665.         else {
  666.             $$k substr($v1);
  667.         }
  668.  
  669.         /* Re-assign back to array. */
  670.         $array[$k= $$k;
  671.     }
  672. }
  673.  
  674. /**
  675.  * Removes slashes from every element in the array
  676.  */
  677. function RemoveSlashes(&$array{
  678.     foreach ($array as $k => $v{
  679.         global $$k;
  680.         if (is_array($$k)) {
  681.             foreach ($$k as $k2 => $v2{
  682.                 $newArray[stripslashes($k2)stripslashes($v2);
  683.             }
  684.             $$k $newArray;
  685.         else {
  686.             $$k stripslashes($v);
  687.         }
  688.  
  689.         /* Re-assign back to the array. */
  690.         $array[$k= $$k;
  691.     }
  692. }
  693.  
  694. /**
  695.  * Create compose link
  696.  *
  697.  * Returns a link to the compose-page, taking in consideration
  698.  * the compose_in_new and javascript settings.
  699.  * @param string url the URL to the compose page
  700.  * @param string text the link text, default "Compose"
  701.  * @return string a link to the compose page
  702.  */
  703. function makeComposeLink($url$text null$target='')
  704. {
  705.     global $compose_new_win,$javascript_on;
  706.  
  707.     if(!$text{
  708.         $text _("Compose");
  709.     }
  710.  
  711.  
  712.     // if not using "compose in new window", make
  713.     // regular link and be done with it
  714.     if($compose_new_win != '1'{
  715.         return makeInternalLink($url$text$target);
  716.     }
  717.  
  718.  
  719.     // build the compose in new window link...
  720.  
  721.  
  722.     // if javascript is on, use onClick event to handle it
  723.     if($javascript_on{
  724.         sqgetGlobalVar('base_uri'$base_uriSQ_SESSION);
  725.         return '<a href="javascript:void(0)" onclick="comp_in_new(\''.$base_uri.$url.'\')">'$text.'</a>';
  726.     }
  727.  
  728.  
  729.     // otherwise, just open new window using regular HTML
  730.     return makeInternalLink($url$text'_blank');
  731.  
  732. }
  733.  
  734. /**
  735.  * Print variable
  736.  *
  737.  * sm_print_r($some_variable, [$some_other_variable [, ...]]);
  738.  *
  739.  * Debugging function - does the same as print_r, but makes sure special
  740.  * characters are converted to htmlentities first.  This will allow
  741.  * values like <[email protected]> to be displayed.
  742.  * The output is wrapped in <<pre>> and <</pre>> tags.
  743.  *
  744.  * @return void 
  745.  */
  746. function sm_print_r({
  747.     ob_start();  // Buffer output
  748.     foreach(func_get_args(as $var{
  749.         print_r($var);
  750.         echo "\n";
  751.     }
  752.     $buffer ob_get_contents()// Grab the print_r output
  753.     ob_end_clean();  // Silently discard the output & stop buffering
  754.     print '<pre>';
  755.     print htmlentities($buffer);
  756.     print '</pre>';
  757. }
  758.  
  759. /**
  760.  * version of fwrite which checks for failure
  761.  */
  762. function sq_fwrite($fp$string{
  763.         // write to file
  764.         $count @fwrite($fp,$string);
  765.         // the number of bytes written should be the length of the string
  766.         if($count != strlen($string)) {
  767.                 return FALSE;
  768.         }
  769.  
  770.         return $count;
  771. }
  772. /**
  773.  * Tests if string contains 8bit symbols.
  774.  *
  775.  * If charset is not set, function defaults to default_charset.
  776.  * $default_charset global must be set correctly if $charset is
  777.  * not used.
  778.  * @param string $string tested string
  779.  * @param string $charset charset used in a string
  780.  * @return bool true if 8bit symbols are detected
  781.  * @since 1.5.1 and 1.4.4
  782.  */
  783. function sq_is8bit($string,$charset=''{
  784.     global $default_charset;
  785.  
  786.     if ($charset==''$charset=$default_charset;
  787.  
  788.     /**
  789.      * Don't use \240 in ranges. Sometimes RH 7.2 doesn't like it.
  790.      * Don't use \200-\237 for iso-8859-x charsets. This ranges
  791.      * stores control symbols in those charsets.
  792.      * Use preg_match instead of ereg in order to avoid problems
  793.      * with mbstring overloading
  794.      */
  795.     if (preg_match("/^iso-8859/i",$charset)) {
  796.         $needle='/\240|[\241-\377]/';
  797.     else {
  798.         $needle='/[\200-\237]|\240|[\241-\377]/';
  799.     }
  800.     return preg_match("$needle",$string);
  801. }
  802.  
  803. /**
  804.  * Function returns number of characters in string.
  805.  *
  806.  * Returned number might be different from number of bytes in string,
  807.  * if $charset is multibyte charset. Detection depends on mbstring
  808.  * functions. If mbstring does not support tested multibyte charset,
  809.  * vanilla string length function is used.
  810.  * @param string $str string
  811.  * @param string $charset charset
  812.  * @since 1.5.1 and 1.4.6
  813.  * @return integer number of characters in string
  814.  */
  815. function sq_strlen($string$charset=NULL){
  816.  
  817.    // NULL charset?  Just use strlen()
  818.    //
  819.    if (is_null($charset))
  820.       return strlen($string);
  821.  
  822.  
  823.    // use current character set?
  824.    //
  825.    if ($charset == 'auto')
  826.    {
  827. //FIXME: this may or may not be better as a session value instead of a global one
  828.       global $sq_string_func_auto_charset;
  829.       if (!isset($sq_string_func_auto_charset))
  830.       {
  831.          global $default_charset$squirrelmail_language;
  832.          set_my_charset();
  833.          $sq_string_func_auto_charset $default_charset;
  834.          if ($squirrelmail_language == 'ja_JP'$sq_string_func_auto_charset 'euc-jp';
  835.       }
  836.       $charset $sq_string_func_auto_charset;
  837.    }
  838.  
  839.  
  840.    // standardize character set name
  841.    //
  842.    $charset strtolower($charset);
  843.  
  844.  
  845. /* ===== 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
  846.    // only use mbstring with the following character sets
  847.    //
  848.    $sq_strlen_mb_charsets = array(
  849.       'utf-8',
  850.       'big5',
  851.       'gb2312',
  852.       'gb18030',
  853.       'euc-jp',
  854.       'euc-cn',
  855.       'euc-tw',
  856.       'euc-kr'
  857.    );
  858.  
  859.  
  860.    // now we can use mb_strlen() if needed
  861.    //
  862.    if (in_array($charset, $sq_strlen_mb_charsets)
  863.     && in_array($charset, sq_mb_list_encodings()))
  864. ===== */
  865. //FIXME: is there any reason why this cannot be a static global array used by all string wrapper functions?
  866.    if (in_array($charsetsq_mb_list_encodings()))
  867.       return mb_strlen($string$charset);
  868.  
  869.  
  870.    // else use normal strlen()
  871.    //
  872.    return strlen($string);
  873.  
  874. }
  875.  
  876. /**
  877.   * This is a replacement for PHP's strpos() that is
  878.   * multibyte-aware.
  879.   *
  880.   * @param string $haystack The string to search within
  881.   * @param string $needle   The substring to search for
  882.   * @param int    $offset   The offset from the beginning of $haystack
  883.   *                          from which to start searching
  884.   *                          (OPTIONAL; default none)
  885.   * @param string $charset  The charset of the given string.  A value of NULL
  886.   *                          here will force the use of PHP's standard strpos().
  887.   *                          (OPTIONAL; default is "auto", which indicates that
  888.   *                          the user's current charset should be used).
  889.   *
  890.   * @return mixed The integer offset of the next $needle in $haystack,
  891.   *                if found, or FALSE if not found
  892.   *
  893.   */
  894. function sq_strpos($haystack$needle$offset=0$charset='auto')
  895. {
  896.  
  897.    // NULL charset?  Just use strpos()
  898.    //
  899.    if (is_null($charset))
  900.       return strpos($haystack$needle$offset);
  901.  
  902.  
  903.    // use current character set?
  904.    //
  905.    if ($charset == 'auto')
  906.    {
  907. //FIXME: this may or may not be better as a session value instead of a global one
  908.       global $sq_string_func_auto_charset;
  909.       if (!isset($sq_string_func_auto_charset))
  910.       {
  911.          global $default_charset$squirrelmail_language;
  912.          set_my_charset();
  913.          $sq_string_func_auto_charset $default_charset;
  914.          if ($squirrelmail_language == 'ja_JP'$sq_string_func_auto_charset 'euc-jp';
  915.       }
  916.       $charset $sq_string_func_auto_charset;
  917.    }
  918.  
  919.  
  920.    // standardize character set name
  921.    //
  922.    $charset strtolower($charset);
  923.  
  924.  
  925. /* ===== 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
  926.    // only use mbstring with the following character sets
  927.    //
  928.    $sq_strpos_mb_charsets = array(
  929.       'utf-8',
  930.       'big5',
  931.       'gb2312',
  932.       'gb18030',
  933.       'euc-jp',
  934.       'euc-cn',
  935.       'euc-tw',
  936.       'euc-kr'
  937.    );
  938.  
  939.  
  940.    // now we can use mb_strpos() if needed
  941.    //
  942.    if (in_array($charset, $sq_strpos_mb_charsets)
  943.     && in_array($charset, sq_mb_list_encodings()))
  944. ===== */
  945. //FIXME: is there any reason why this cannot be a static global array used by all string wrapper functions?
  946.    if (in_array($charsetsq_mb_list_encodings()))
  947.        return mb_strpos($haystack$needle$offset$charset);
  948.  
  949.  
  950.    // else use normal strpos()
  951.    //
  952.    return strpos($haystack$needle$offset);
  953.  
  954. }
  955.  
  956. /**
  957.   * This is a replacement for PHP's substr() that is
  958.   * multibyte-aware.
  959.   *
  960.   * @param string $string  The string to operate upon
  961.   * @param int    $start   The offset at which to begin substring extraction
  962.   * @param int    $length  The number of characters after $start to return
  963.   *                         NOTE that if you need to specify a charset but
  964.   *                         want to achieve normal substr() behavior where
  965.   *                         $length is not specified, use NULL (OPTIONAL;
  966.   *                         default from $start to end of string)
  967.   * @param string $charset The charset of the given string.  A value of NULL
  968.   *                         here will force the use of PHP's standard substr().
  969.   *                         (OPTIONAL; default is "auto", which indicates that
  970.   *                         the user's current charset should be used).
  971.   *
  972.   * @return string The desired substring
  973.   *
  974.   *  Of course, you can use more advanced (e.g., negative) values
  975.   *  for $start and $length as needed - see the PHP manual for more
  976.   *  information:  http://www.php.net/manual/function.substr.php
  977.   *
  978.   */
  979. function sq_substr($string$start$length=NULL$charset='auto')
  980. {
  981.  
  982.    // if $length is NULL, use the full string length...
  983.    // we have to do this to mimick the use of substr()
  984.    // where $length is not given
  985.    //
  986.    if (is_null($length))
  987.       $length sq_strlen($length$charset);
  988.  
  989.    
  990.    // NULL charset?  Just use substr()
  991.    //
  992.    if (is_null($charset))
  993.       return substr($string$start$length);
  994.  
  995.  
  996.    // use current character set?
  997.    //
  998.    if ($charset == 'auto')
  999.    {
  1000. //FIXME: this may or may not be better as a session value instead of a global one
  1001.       global $sq_string_func_auto_charset;
  1002.       if (!isset($sq_string_func_auto_charset))
  1003.       {
  1004.          global $default_charset$squirrelmail_language;
  1005.          set_my_charset();
  1006.          $sq_string_func_auto_charset $default_charset;
  1007.          if ($squirrelmail_language == 'ja_JP'$sq_string_func_auto_charset 'euc-jp';
  1008.       }
  1009.       $charset $sq_string_func_auto_charset;
  1010.    }
  1011.  
  1012.  
  1013.    // standardize character set name
  1014.    //
  1015.    $charset strtolower($charset);
  1016.  
  1017.  
  1018. /* ===== 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
  1019.    // only use mbstring with the following character sets
  1020.    //
  1021.    $sq_substr_mb_charsets = array(
  1022.       'utf-8',
  1023.       'big5',
  1024.       'gb2312',
  1025.       'gb18030',
  1026.       'euc-jp',
  1027.       'euc-cn',
  1028.       'euc-tw',
  1029.       'euc-kr'
  1030.    );
  1031.  
  1032.  
  1033.    // now we can use mb_substr() if needed
  1034.    //
  1035.    if (in_array($charset, $sq_substr_mb_charsets)
  1036.     && in_array($charset, sq_mb_list_encodings()))
  1037. ===== */
  1038. //FIXME: is there any reason why this cannot be a global array used by all string wrapper functions?
  1039.    if (in_array($charsetsq_mb_list_encodings()))
  1040.       return mb_substr($string$start$length$charset);
  1041.  
  1042.  
  1043.    // else use normal substr()
  1044.    //
  1045.    return substr($string$start$length);
  1046.  
  1047. }
  1048.  
  1049. /**
  1050.   * This is a replacement for PHP's substr_replace() that is
  1051.   * multibyte-aware.
  1052.   *
  1053.   * @param string $string      The string to operate upon
  1054.   * @param string $replacement The string to be inserted
  1055.   * @param int    $start       The offset at which to begin substring replacement
  1056.   * @param int    $length      The number of characters after $start to remove
  1057.   *                             NOTE that if you need to specify a charset but
  1058.   *                             want to achieve normal substr_replace() behavior
  1059.   *                             where $length is not specified, use NULL (OPTIONAL;
  1060.   *                             default from $start to end of string)
  1061.   * @param string $charset     The charset of the given string.  A value of NULL
  1062.   *                             here will force the use of PHP's standard substr().
  1063.   *                             (OPTIONAL; default is "auto", which indicates that
  1064.   *                             the user's current charset should be used).
  1065.   *
  1066.   * @return string The manipulated string
  1067.   *
  1068.   *  Of course, you can use more advanced (e.g., negative) values
  1069.   *  for $start and $length as needed - see the PHP manual for more
  1070.   *  information:  http://www.php.net/manual/function.substr-replace.php
  1071.   *
  1072.   */
  1073. function sq_substr_replace($string$replacement$start$length=NULL,
  1074.                            $charset='auto')
  1075. {
  1076.  
  1077.    // NULL charset?  Just use substr_replace()
  1078.    //
  1079.    if (is_null($charset))
  1080.       return is_null($lengthsubstr_replace($string$replacement$start)
  1081.                               : substr_replace($string$replacement$start$length);
  1082.  
  1083.  
  1084.    // use current character set?
  1085.    //
  1086.    if ($charset == 'auto')
  1087.    {
  1088. //FIXME: this may or may not be better as a session value instead of a global one
  1089.       $charset $auto_charset;
  1090.       global $sq_string_func_auto_charset;
  1091.       if (!isset($sq_string_func_auto_charset))
  1092.       {
  1093.          global $default_charset$squirrelmail_language;
  1094.          set_my_charset();
  1095.          $sq_string_func_auto_charset $default_charset;
  1096.          if ($squirrelmail_language == 'ja_JP'$sq_string_func_auto_charset 'euc-jp';
  1097.       }
  1098.       $charset $sq_string_func_auto_charset;
  1099.    }
  1100.  
  1101.  
  1102.    // standardize character set name
  1103.    //
  1104.    $charset strtolower($charset);
  1105.  
  1106.  
  1107. /* ===== 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
  1108.    // only use mbstring with the following character sets
  1109.    //
  1110.    $sq_substr_replace_mb_charsets = array(
  1111.       'utf-8',
  1112.       'big5',
  1113.       'gb2312',
  1114.       'gb18030',
  1115.       'euc-jp',
  1116.       'euc-cn',
  1117.       'euc-tw',
  1118.       'euc-kr'
  1119.    );
  1120.  
  1121.  
  1122.    // now we can use our own implementation using
  1123.    // mb_substr() and mb_strlen() if needed
  1124.    //
  1125.    if (in_array($charset, $sq_substr_replace_mb_charsets)
  1126.     && in_array($charset, sq_mb_list_encodings()))
  1127. ===== */
  1128. //FIXME: is there any reason why this cannot be a global array used by all string wrapper functions?
  1129.    if (in_array($charsetsq_mb_list_encodings()))
  1130.    {
  1131.  
  1132.       $string_length mb_strlen($string$charset);
  1133.  
  1134.       if ($start 0)
  1135.          $start max(0$string_length $start);
  1136.  
  1137.       else if ($start $string_length)
  1138.          $start $string_length;
  1139.  
  1140.       if ($length 0)
  1141.          $length max(0$string_length $start $length);
  1142.  
  1143.       else if (is_null($length|| $length $string_length)
  1144.          $length $string_length;
  1145.  
  1146.       if ($start $length $string_length)
  1147.          $length $string_length $start;
  1148.  
  1149.       return mb_substr($string0$start$charset)
  1150.            . $replacement
  1151.            . mb_substr($string,
  1152.                        $start $length,
  1153.                        $string_length// FIXME: I can't see why this is needed:  - $start - $length,
  1154.                        $charset);
  1155.  
  1156.    }
  1157.  
  1158.  
  1159.    // else use normal substr_replace()
  1160.    //
  1161.    return is_null($lengthsubstr_replace($string$replacement$start)
  1162.                            : substr_replace($string$replacement$start$length);
  1163.  
  1164. }
  1165.  
  1166. /**
  1167.  * Replacement of mb_list_encodings function
  1168.  *
  1169.  * This function provides replacement for function that is available only
  1170.  * in php 5.x. Function does not test all mbstring encodings. Only the ones
  1171.  * that might be used in SM translations.
  1172.  *
  1173.  * Supported strings are stored in session in order to reduce number of
  1174.  * mb_internal_encoding function calls.
  1175.  *
  1176.  * If mb_list_encodings() function is present, code uses it. Main difference
  1177.  * from original function behaviour - array values are lowercased in order to
  1178.  * simplify use of returned array in in_array() checks.
  1179.  *
  1180.  * If you want to test all mbstring encodings - fill $list_of_encodings
  1181.  * array.
  1182.  * @return array list of encodings supported by php mbstring extension
  1183.  * @since 1.5.1 and 1.4.6
  1184.  */
  1185. function sq_mb_list_encodings({
  1186.  
  1187.     // if it's already in the session, don't need to regenerate it
  1188.     if (sqgetGlobalVar('mb_supported_encodings',$mb_supported_encodings,SQ_SESSION)
  1189.      && is_array($mb_supported_encodings))
  1190.         return $mb_supported_encodings;
  1191.  
  1192.     // check if mbstring extension is present
  1193.     if (function_exists('mb_internal_encoding')) {
  1194.         $supported_encodings array();
  1195.         sqsession_register($supported_encodings'mb_supported_encodings');
  1196.         return $supported_encodings;
  1197.     }
  1198.  
  1199.     // php 5+ function
  1200.     if (function_exists('mb_list_encodings')) {
  1201.         $supported_encodings mb_list_encodings();
  1202.         array_walk($supported_encodings'sq_lowercase_array_vals');
  1203.         sqsession_register($supported_encodings'mb_supported_encodings');
  1204.         return $supported_encodings;
  1205.     }
  1206.  
  1207.     // save original encoding
  1208.     $orig_encoding=mb_internal_encoding();
  1209.  
  1210.     $list_of_encoding=array(
  1211.         'pass',
  1212.         'auto',
  1213.         'ascii',
  1214.         'jis',
  1215.         'utf-8',
  1216.         'sjis',
  1217.         'euc-jp',
  1218.         'iso-8859-1',
  1219.         'iso-8859-2',
  1220.         'iso-8859-7',
  1221.         'iso-8859-9',
  1222.         'iso-8859-15',
  1223.         'koi8-r',
  1224.         'koi8-u',
  1225.         'big5',
  1226.         'gb2312',
  1227.         'gb18030',
  1228.         'windows-1251',
  1229.         'windows-1255',
  1230.         'windows-1256',
  1231.         'tis-620',
  1232.         'iso-2022-jp',
  1233.         'euc-cn',
  1234.         'euc-kr',
  1235.         'euc-tw',
  1236.         'uhc',
  1237.         'utf7-imap');
  1238.  
  1239.     $supported_encodings=array();
  1240.  
  1241.     foreach ($list_of_encoding as $encoding{
  1242.         // try setting encodings. suppress warning messages
  1243.         if (@mb_internal_encoding($encoding))
  1244.             $supported_encodings[]=$encoding;
  1245.     }
  1246.  
  1247.     // restore original encoding
  1248.     mb_internal_encoding($orig_encoding);
  1249.  
  1250.     // register list in session
  1251.     sqsession_register($supported_encodings'mb_supported_encodings');
  1252.  
  1253.     return $supported_encodings;
  1254. }
  1255.  
  1256. /**
  1257.  * Callback function used to lowercase array values.
  1258.  * @param string $val array value
  1259.  * @param mixed $key array key
  1260.  * @since 1.5.1 and 1.4.6
  1261.  */
  1262. function sq_lowercase_array_vals(&$val,$key{
  1263.     $val strtolower($val);
  1264. }
  1265.  
  1266. /**
  1267.  * Callback function to trim whitespace from a value, to be used in array_walk
  1268.  * @param string $value value to trim
  1269.  * @since 1.5.2 and 1.4.7
  1270.  */
  1271. function sq_trim_value &$value {
  1272.     $value trim($value);
  1273. }
  1274.  
  1275. /**
  1276.   * Gathers the list of secuirty tokens currently
  1277.   * stored in the user's preferences and optionally
  1278.   * purges old ones from the list.
  1279.   *
  1280.   * @param boolean $purge_old Indicates if old tokens
  1281.   *                            should be purged from the
  1282.   *                            list ("old" is 2 days or
  1283.   *                            older unless the administrator
  1284.   *                            overrides that value using
  1285.   *                            $max_token_age_days in
  1286.   *                            config/config_local.php)
  1287.   *                            (OPTIONAL; default is to always
  1288.   *                            purge old tokens)
  1289.   *
  1290.   * @return array The list of tokens
  1291.   *
  1292.   * @since 1.4.19 and 1.5.2
  1293.   *
  1294.   */
  1295. function sm_get_user_security_tokens($purge_old=TRUE)
  1296. {
  1297.  
  1298.    global $data_dir$username$max_token_age_days;
  1299.  
  1300.    $tokens getPref($data_dir$username'security_tokens''');
  1301.    if (($tokens unserialize($tokens)) === FALSE || !is_array($tokens))
  1302.       $tokens array();
  1303.  
  1304.    // purge old tokens if necessary
  1305.    //
  1306.    if ($purge_old)
  1307.    {
  1308.       if (empty($max_token_age_days)) $max_token_age_days 2;
  1309.       $now time();
  1310.       $discard_token_date $now ($max_token_age_days 86400);
  1311.       $cleaned_tokens array();
  1312.       foreach ($tokens as $token => $timestamp)
  1313.          if ($timestamp >= $discard_token_date)
  1314.             $cleaned_tokens[$token$timestamp;
  1315.       $tokens $cleaned_tokens;
  1316.    }
  1317.  
  1318.    return $tokens;
  1319.  
  1320. }
  1321.  
  1322. /**
  1323.   * Generates a security token that is then stored in
  1324.   * the user's preferences with a timestamp for later
  1325.   * verification/use (although session-based tokens
  1326.   * are not stored in user preferences).
  1327.   *
  1328.   * NOTE: By default SquirrelMail will use a single session-based
  1329.   *       token, but if desired, user tokens can have expiration
  1330.   *       dates associated with them and become invalid even during
  1331.   *       the same login session.  When in that mode, the note
  1332.   *       immediately below applies, otherwise it is irrelevant.
  1333.   *       To enable that mode, the administrator must add the
  1334.   *       following to config/config_local.php:
  1335.   *       $use_expiring_security_tokens = TRUE;
  1336.   *
  1337.   * NOTE: The administrator can force SquirrelMail to generate
  1338.   * a new token every time one is requested (which may increase
  1339.   * obscurity through token randomness at the cost of some
  1340.   * performance) by adding the following to
  1341.   * config/config_local.php:   $do_not_use_single_token = TRUE;
  1342.   * Otherwise, only one token will be generated per user which
  1343.   * will change only after it expires or is used outside of the
  1344.   * validity period specified when calling sm_validate_security_token()
  1345.   *
  1346.   * WARNING: If the administrator has turned the token system
  1347.   *          off by setting $disable_security_tokens to TRUE in
  1348.   *          config/config.php or the configuration tool, this
  1349.   *          function will not store tokens in the user
  1350.   *          preferences (but it will still generate and return
  1351.   *          a random string).
  1352.   *
  1353.   * @param boolean $force_generate_new When TRUE, a new token will
  1354.   *                                     always be created even if current
  1355.   *                                     configuration dictates otherwise
  1356.   *                                     (OPTIONAL; default FALSE)
  1357.   *
  1358.   * @return string A security token
  1359.   *
  1360.   * @since 1.4.19 and 1.5.2
  1361.   *
  1362.   */
  1363. function sm_generate_security_token($force_generate_new=FALSE)
  1364. {
  1365.  
  1366.    global $data_dir$username$disable_security_tokens$do_not_use_single_token,
  1367.           $use_expiring_security_tokens;
  1368.    $max_generation_tries 1000;
  1369.  
  1370.    // if we're using session-based tokens, just return
  1371.    // the same one every time (generate it if it's not there)
  1372.    //
  1373.    if (!$use_expiring_security_tokens)
  1374.    {
  1375.       if (sqgetGlobalVar('sm_security_token'$tokenSQ_SESSION))
  1376.          return $token;
  1377.  
  1378.       // create new one since there was none in session
  1379.       $token GenerateRandomString(12''7);
  1380.       sqsession_register($token'sm_security_token');
  1381.       return $token;
  1382.    }
  1383.  
  1384.    $tokens sm_get_user_security_tokens();
  1385.  
  1386.    if (!$force_generate_new && !$do_not_use_single_token && !empty($tokens))
  1387.       return key($tokens);
  1388.  
  1389.    $new_token GenerateRandomString(12''7);
  1390.    $count 0;
  1391.    while (isset($tokens[$new_token]))
  1392.    {
  1393.       $new_token GenerateRandomString(12''7);
  1394.       if (++$count $max_generation_tries)
  1395.       {
  1396.          logout_error(_("Fatal token generation error; please contact your system administrator or the SquirrelMail Team"));
  1397.          exit;
  1398.       }
  1399.    }
  1400.  
  1401.    // is the token system enabled?  CAREFUL!
  1402.    //
  1403.    if (!$disable_security_tokens)
  1404.    {
  1405.       $tokens[$new_tokentime();
  1406.       setPref($data_dir$username'security_tokens'serialize($tokens));
  1407.    }
  1408.  
  1409.    return $new_token;
  1410.  
  1411. }
  1412.  
  1413. /**
  1414.   * Validates a given security token and optionally remove it
  1415.   * from the user's preferences if it was valid.  If the token
  1416.   * is too old but otherwise valid, it will still be rejected.
  1417.   *
  1418.   * "Too old" is 2 days or older unless the administrator
  1419.   * overrides that value using $max_token_age_days in
  1420.   * config/config_local.php
  1421.   *
  1422.   * Session-based tokens of course are always reused and are
  1423.   * valid for the lifetime of the login session.
  1424.   *
  1425.   * WARNING: If the administrator has turned the token system
  1426.   *          off by setting $disable_security_tokens to TRUE in
  1427.   *          config/config.php or the configuration tool, this
  1428.   *          function will always return TRUE.
  1429.   *
  1430.   * @param string  $token           The token to validate
  1431.   * @param int     $validity_period The number of seconds tokens are valid
  1432.   *                                  for (set to zero to remove valid tokens
  1433.   *                                  after only one use; set to -1 to allow
  1434.   *                                  indefinite re-use (but still subject to
  1435.   *                                  $max_token_age_days - see elsewhere);
  1436.   *                                  use 3600 to allow tokens to be reused for
  1437.   *                                  an hour) (OPTIONAL; default is to only
  1438.   *                                  allow tokens to be used once)
  1439.   *                                  NOTE this is unrelated to $max_token_age_days
  1440.   *                                  or rather is an additional time constraint on
  1441.   *                                  tokens that allows them to be re-used (or not)
  1442.   *                                  within a more narrow timeframe
  1443.   * @param boolean $show_error      Indicates that if the token is not
  1444.   *                                  valid, this function should display
  1445.   *                                  a generic error, log the user out
  1446.   *                                  and exit - this function will never
  1447.   *                                  return in that case.
  1448.   *                                  (OPTIONAL; default FALSE)
  1449.   *
  1450.   * @return boolean TRUE if the token validated; FALSE otherwise
  1451.   *
  1452.   * @since 1.4.19 and 1.5.2
  1453.   *
  1454.   */
  1455. function sm_validate_security_token($token$validity_period=0$show_error=FALSE)
  1456. {
  1457.  
  1458.    global $data_dir$username$max_token_age_days,
  1459.           $use_expiring_security_tokens,
  1460.           $disable_security_tokens;
  1461.  
  1462.    // bypass token validation?  CAREFUL!
  1463.    //
  1464.    if ($disable_security_tokensreturn TRUE;
  1465.  
  1466.    // if we're using session-based tokens, just compare
  1467.    // the same one every time
  1468.    //
  1469.    if (!$use_expiring_security_tokens)
  1470.    {
  1471.       if (!sqgetGlobalVar('sm_security_token'$session_tokenSQ_SESSION))
  1472.       {
  1473.          if (!$show_errorreturn FALSE;
  1474.          logout_error(_("Fatal security token error; please log in again"));
  1475.          exit;
  1476.       }
  1477.       if ($token !== $session_token)
  1478.       {
  1479.          if (!$show_errorreturn FALSE;
  1480.          logout_error(_("The current page request appears to have originated from an untrusted source."));
  1481.          exit;
  1482.       }
  1483.       return TRUE;
  1484.    }
  1485.  
  1486.    // don't purge old tokens here because we already
  1487.    // do it when generating tokens
  1488.    //
  1489.    $tokens sm_get_user_security_tokens(FALSE);
  1490.  
  1491.    // token not found?
  1492.    //
  1493.    if (empty($tokens[$token]))
  1494.    {
  1495.       if (!$show_errorreturn FALSE;
  1496.       logout_error(_("This page request could not be verified and appears to have expired."));
  1497.       exit;
  1498.    }
  1499.  
  1500.    $now time();
  1501.    $timestamp $tokens[$token];
  1502.  
  1503.    // whether valid or not, we want to remove it from
  1504.    // user prefs if it's old enough (unless requested to
  1505.    // bypass this (in which case $validity_period is -1))
  1506.    //
  1507.    if ($validity_period >= 0
  1508.     && $timestamp $now $validity_period)
  1509.    {
  1510.       unset($tokens[$token]);
  1511.       setPref($data_dir$username'security_tokens'serialize($tokens));
  1512.    }
  1513.  
  1514.    // reject tokens that are too old
  1515.    //
  1516.    if (empty($max_token_age_days)) $max_token_age_days 2;
  1517.    $old_token_date $now ($max_token_age_days 86400);
  1518.    if ($timestamp $old_token_date)
  1519.    {
  1520.       if (!$show_errorreturn FALSE;
  1521.       logout_error(_("The current page request appears to have originated from an untrusted source."));
  1522.       exit;
  1523.    }
  1524.  
  1525.    // token OK!
  1526.    //
  1527.    return TRUE;
  1528.  
  1529. }
  1530.  
  1531. /**
  1532.   * Wrapper for PHP's htmlspecialchars() that
  1533.   * attempts to add the correct character encoding
  1534.   *
  1535.   * @param string $string The string to be converted
  1536.   * @param int $flags A bitmask that controls the behavior of htmlspecialchars()
  1537.   *                    (See http://php.net/manual/function.htmlspecialchars.php )
  1538.   *                    (OPTIONAL; default ENT_COMPAT, ENT_COMPAT | ENT_SUBSTITUTE for PHP >=5.4)
  1539.   * @param string $encoding The character encoding to use in the conversion
  1540.   *                          (OPTIONAL; default automatic detection)
  1541.   * @param boolean $double_encode Whether or not to convert entities that are
  1542.   *                                already in the string (only supported in
  1543.   *                                PHP 5.2.3+) (OPTIONAL; default TRUE)
  1544.   *
  1545.   * @return string The converted text
  1546.   *
  1547.   */
  1548. function sm_encode_html_special_chars($string$flags=ENT_COMPAT,
  1549.                                       $encoding=NULL$double_encode=TRUE)
  1550. {
  1551.    if (!$encoding)
  1552.    {
  1553.       global $default_charset;
  1554.       if ($default_charset == 'iso-2022-jp')
  1555.          $default_charset 'EUC-JP';
  1556.       $encoding $default_charset;
  1557.    }
  1558.  
  1559.    if (check_php_version(523)) {
  1560.       // Replace invalid characters with a symbol instead of returning
  1561.       // empty string for the entire to be encoded string.
  1562.       if (check_php_version(540&& $flags == ENT_COMPAT{
  1563.          $flags $flags ENT_SUBSTITUTE;
  1564.       }
  1565.       return htmlspecialchars($string$flags$encoding$double_encode);
  1566.    }
  1567.  
  1568.    return htmlspecialchars($string$flags$encoding);
  1569. }
  1570.  
  1571. $PHP_SELF php_self();

Documentation generated on Mon, 13 Jan 2020 04:25:23 +0100 by phpDocumentor 1.4.3