PageRenderTime 34ms CodeModel.GetById 2ms app.highlight 23ms RepoModel.GetById 1ms app.codeStats 1ms

/tags/rel-1_4_21/functions/strings.php

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