Auth_HTTP
[ class tree: Auth_HTTP ] [ index: Auth_HTTP ] [ all elements ]

Source for file Auth_HTTP.php

Documentation is available at Auth_HTTP.php

  1. <?php
  2. //
  3. // +----------------------------------------------------------------------+
  4. // | PHP Version 4                                                        |
  5. // +----------------------------------------------------------------------+
  6. // | Copyright (c) 1997-2004 The PHP Group                                |
  7. // +----------------------------------------------------------------------+
  8. // | This source file is subject to version 2.02 of the PHP license,      |
  9. // | that is bundled with this package in the file LICENSE, and is        |
  10. // | available at through the world-wide-web at                           |
  11. // | http://www.php.net/license/2_02.txt.                                 |
  12. // | If you did not receive a copy of the PHP license and are unable to   |
  13. // | obtain it through the world-wide-web, please send a note to          |
  14. // | license@php.net so we can mail you a copy immediately.               |
  15. // +----------------------------------------------------------------------+
  16. // | Authors: Martin Jansen <mj@php.net>                                  |
  17. // |          Rui Hirokawa <hirokawa@php.net>                             |
  18. // |          David Costa  <gurugeek@php.net>                             |
  19. // +----------------------------------------------------------------------+
  20. //
  21. //  $Id: Auth_HTTP.php,v 1.18 2004/06/19 04:55:02 hirokawa Exp $ 
  22. //
  23.  
  24. require_once "Auth/Auth.php";
  25.  
  26. define('AUTH_HTTP_NONCE_TIME_LEN'16);
  27. define('AUTH_HTTP_NONCE_HASH_LEN'32);
  28.  
  29. // {{{ class Auth_HTTP
  30.  
  31. /**
  32.  * PEAR::Auth_HTTP
  33.  *
  34.  * The PEAR::Auth_HTTP class provides methods for creating an
  35.  * HTTP authentication system based on RFC-2617 using PHP.
  36.  *
  37.  * Instead of generating an HTML driven form like PEAR::Auth
  38.  * does, this class sends header commands to the clients which
  39.  * cause them to present a login box like they are e.g. used
  40.  * in Apache's .htaccess mechanism.
  41.  *
  42.  * This class requires the PEAR::Auth package.
  43.  *
  44.  * @notes The HTTP Diegest Authentication part is based on
  45.  *   authentication class written by Tom Pike <tom.pike@xiven.com>
  46.  *
  47.  * @author  Martin Jansen <mj@php.net>
  48.  * @author  Rui Hirokawa <hirokawa@php.net>
  49.  * @author  David Costa <gurugeek@php.net>
  50.  * @package Auth_HTTP
  51.  * @extends Auth
  52.  * @version $Revision: 1.18 $
  53.  */
  54. class Auth_HTTP extends Auth
  55. {
  56.    
  57.     // {{{ properties
  58.  
  59.     /**
  60.      * Authorization method: 'basic' or 'digest'
  61.      *
  62.      * @access public
  63.      * @var    string 
  64.      */
  65.     var $authType = 'basic';
  66.  
  67.     /**
  68.      * Name of the realm for Basic Authentication
  69.      *
  70.      * @access public
  71.      * @var    string 
  72.      * @see    drawLogin()
  73.      */
  74.     var $realm = "protected area";
  75.  
  76.     /**
  77.      * Text to send if user hits cancel button
  78.      *
  79.      * @access public
  80.      * @var    string 
  81.      * @see    drawLogin()
  82.      */
  83.     var $CancelText = "Error 401 - Access denied";
  84.  
  85.     /**
  86.      * option array
  87.      *
  88.      * @access public
  89.      * @var    array 
  90.      */
  91.     var $options = array();
  92.  
  93.     /**
  94.      * flag to indicate the nonce was stale.
  95.      *
  96.      * @access public
  97.      * @var    bool 
  98.      */
  99.     var $stale = false;
  100.  
  101.     /**
  102.      * opaque string for digest authentication
  103.      *
  104.      * @access public
  105.      * @var    string 
  106.      */
  107.     var $opaque = 'dummy';
  108.  
  109.     /**
  110.      * digest URI
  111.      *
  112.      * @access public
  113.      * @var    string 
  114.      */
  115.     var $uri = '';
  116.  
  117.     /**
  118.      * authorization info returned by the client
  119.      *
  120.      * @access public
  121.      * @var    array 
  122.      */
  123.     var $auth = array();
  124.  
  125.     /**
  126.      * next nonce value
  127.      *
  128.      * @access public
  129.      * @var    string 
  130.      */
  131.     var $nextNonce = '';
  132.  
  133.     /**
  134.      * nonce value
  135.      *
  136.      * @access public
  137.      * @var    string 
  138.      */
  139.     var $nonce = '';
  140.  
  141.     // }}}
  142.     // {{{ Constructor
  143.  
  144.     /**
  145.      * Constructor
  146.      *
  147.      * @param string    Type of the storage driver
  148.      * @param mixed     Additional options for the storage driver
  149.      *                   (example: if you are using DB as the storage
  150.      *                    driver, you have to pass the dsn string here)
  151.      *
  152.      * @return void 
  153.      */
  154.     function Auth_HTTP($storageDriver$options ''
  155.     {
  156.         /* set default values for options */
  157.         $this->options = array('cryptType' => 'md5',
  158.                    'algorithm' => 'MD5',
  159.                                'qop' => 'auth-int,auth',
  160.                                'opaquekey' => 'moo',
  161.                                'noncekey' => 'moo',
  162.                                'digestRealm' => 'protected area',
  163.                                'forceDigestOnly' => false,
  164.                                'nonceLife' => 300,
  165.                                'sessionSharing' => true,
  166.                                );
  167.         
  168.         if (!empty($options['authType'])) {
  169.             $this->authType = strtolower($options['authType']);
  170.         }
  171.         
  172.         foreach($options as $key => $value{
  173.             if (array_key_exists$key$this->options)) {
  174.                 $this->options[$key$value;
  175.             }
  176.         }
  177.         
  178.         if (!empty($this->options['opaquekey'])) {
  179.             $this->opaque = md5($this->options['opaquekey']);
  180.         }
  181.         
  182.         $this->Auth($storageDriver$options);
  183.     }
  184.     
  185.     // }}}
  186.     // {{{ assignData()
  187.  
  188.     /**
  189.      * Assign values from $PHP_AUTH_USER and $PHP_AUTH_PW or 'Authorization' header
  190.      * to internal variables and sets the session id based
  191.      * on them
  192.      *
  193.      * @return void 
  194.      */
  195.     function assignData()
  196.     {
  197.         $server &$this->_importGlobalVariable('server');
  198.  
  199.         if ($this->authType == 'basic'{
  200.             if (!empty($server['PHP_AUTH_USER'])) {
  201.                 $this->username $server['PHP_AUTH_USER'];
  202.             }
  203.             
  204.             if (!empty($server['PHP_AUTH_PW'])) {
  205.                 $this->password $server['PHP_AUTH_PW'];
  206.             }
  207.             
  208.             /**
  209.              * Try to get authentication information from IIS
  210.              */
  211.             if  (empty($this->username&& empty($this->password)) {
  212.                 if (!empty($server['HTTP_AUTHORIZATION'])) {
  213.                     list($this->username$this->password
  214.                         explode(':'base64_decode(substr($server['HTTP_AUTHORIZATION']6)));
  215.                 }
  216.             }
  217.         elseif ($this->authType == 'digest'{
  218.          $this->username '';
  219.          $this->password '';
  220.  
  221.             $headers = getallheaders();
  222.             if(isset($headers['Authorization']&& !empty($headers['Authorization'])) {
  223.                 $authtemp explode(','substr($headers['Authorization'],
  224.                                                 strpos($headers['Authorization'],' ')+1));
  225.                 $auth = array();
  226.                 foreach($authtemp as $key => $value{
  227.                     $value trim($value);
  228.                     if(strpos($value,'='!== false{
  229.                         $lhs substr($value,0,strpos($value,'='));
  230.                         $rhs substr($value,strpos($value,'=')+1);
  231.                         if(substr($rhs,0,1== '"' && substr($rhs,-1,1== '"'{
  232.                             $rhs substr($rhs,1,-1);
  233.                         }
  234.                         $auth[$lhs$rhs;
  235.                     }
  236.                 }
  237.             }
  238.             if (!isset($auth['uri']|| !isset($auth['realm'])) {
  239.                 return;
  240.             }
  241.             
  242.             if ($this->selfURI(== $auth['uri']{
  243.                 $this->uri = $auth['uri'];
  244.                 if (substr($headers['Authorization'],0,7== 'Digest '{
  245.                     
  246.                     $this->authType = 'digest';
  247.  
  248.                     if (!isset($auth['nonce']|| !isset($auth['username']|| 
  249.                   !isset($auth['response']|| !isset($auth['qop']|| 
  250.                   !isset($auth['nc']|| !isset($auth['cnonce'])){
  251.                         return;
  252.                     }
  253.  
  254.                if ($auth['qop'!= 'auth' && $auth['qop'!= 'auth-int'{
  255.                         return;
  256.                }
  257.                     
  258.                     $this->stale = $this->_judgeStale($auth['nonce']);
  259.  
  260.                if ($this->nextNonce == false{
  261.                   return;
  262.                }
  263.  
  264.                     $this->username $auth['username'];
  265.                     $this->password $auth['response'];
  266.                     $this->auth['nonce'$auth['nonce'];
  267.                     
  268.                $this->auth['qop'$auth['qop'];
  269.                $this->auth['nc'$auth['nc'];
  270.                $this->auth['cnonce'$auth['cnonce'];
  271.  
  272.                     if (isset($auth['opaque'])) {
  273.                         $this->auth['opaque'$auth['opaque'];
  274.                     }
  275.                     
  276.                 elseif (substr($headers['Authorization'],0,6== 'Basic '{
  277.                     if ($this->options['forceDigestOnly']{
  278.                         return// Basic authentication is not allowed.
  279.                     }
  280.                     
  281.                     $this->authType = 'basic';
  282.                     list($username$password
  283.                         explode(':',base64_decode(substr($headers['Authorization'],6)));
  284.                     $this->username $username;
  285.                     $this->password $password;
  286.                 }
  287.             }
  288.         else {
  289.             return PEAR::raiseError('authType is invalid.');
  290.         }
  291.  
  292.         if ($this->options['sessionSharing'&& 
  293.             isset($this->username&& isset($this->password)) {
  294.             session_id(md5('Auth_HTTP' $this->username $this->password));
  295.         }
  296.         
  297.         /**
  298.          * set sessionName for AUTH, so that the sessionName is different 
  299.          * for distinct realms 
  300.          */
  301.          $this->_sessionName "_authhttp".md5($this->realm);
  302.     }
  303.  
  304.     // }}}
  305.     // {{{ login()
  306.  
  307.     /**
  308.      * Login function
  309.      *
  310.      * @access private
  311.      * @return void 
  312.      */
  313.     function login(
  314.     {
  315.         $login_ok = false;
  316.  
  317.         /**
  318.          * When the user has already entered a username,
  319.          * we have to validate it.
  320.          */
  321.         if (!empty($this->username&& !empty($this->password)) {
  322.             if ($this->authType == 'basic' && !$this->options['forceDigestOnly']{
  323.                 if (true === $this->storage->fetchData($this->username$this->password)) {
  324.                     $login_ok = true;
  325.                 }
  326.             else /* digest authentication */
  327.  
  328.             if (!$this->getAuth(|| $this->getAuthData('a1'== null{
  329.                /* 
  330.                 * note:
  331.                 *  - only PEAR::DB is supported as container.
  332.                 *  - password should be stored in container as plain-text 
  333.                 *    (if $options['cryptType'] == 'none') or 
  334.                 *     A1 hashed form (md5('username:realm:password')) 
  335.                 *    (if $options['cryptType'] == 'md5')
  336.                 */
  337.                $dbs $this->storage;
  338.                if (!DB::isConnection($dbs->db)) {
  339.                   $dbs->_connect($dbs->options['dsn']);
  340.                }
  341.                
  342.                $query 'SELECT '.$dbs->options['passwordcol']." FROM ".$dbs->options['table'].
  343.                   ' WHERE '.$dbs->options['usernamecol']." = '".
  344.                   $dbs->db->quoteString($this->username)."' ";
  345.                
  346.                $pwd $dbs->db->getOne($query)// password stored in container.
  347.                
  348.                if (DB::isError($pwd)) {
  349.                   return PEAR::raiseError($pwd->getMessage()$pwd->getCode());
  350.                }
  351.                
  352.                if ($this->options['cryptType'== 'none'{
  353.                   $a1 md5($this->username.':'.$this->options['digestRealm'].':'.$pwd);
  354.                else {
  355.                   $a1 $pwd;
  356.                }
  357.  
  358.                $this->setAuthData('a1'$a1true);
  359.             else {
  360.                $a1 $this->getAuthData('a1');
  361.             }
  362.  
  363.                 $login_ok $this->validateDigest($this->password$a1);
  364.             if ($this->nextNonce == false{
  365.                $login_ok = false;
  366.             }
  367.             }
  368.  
  369.             if (!$login_ok && is_callable($this->loginFailedCallback)) {
  370.                 call_user_func($this->loginFailedCallback,$this->username$this);
  371.             }
  372.         }
  373.         
  374.         if (!empty($this->username&& $login_ok{
  375.             $this->setAuth($this->username);
  376.             if (is_callable($this->loginCallback)) {
  377.                 call_user_func($this->loginCallback,$this->username$this);
  378.             }
  379.         }
  380.         
  381.         /**
  382.          * If the login failed or the user entered no username,
  383.          * output the login screen again.
  384.          */
  385.         if (!empty($this->username&& !$login_ok{
  386.             $this->status = AUTH_WRONG_LOGIN;
  387.         }
  388.         
  389.         if ((empty($this->username|| !$login_ok&& $this->showLogin{
  390.             $this->drawLogin($this->storage->activeUser);
  391.             return;
  392.         }
  393.  
  394.       if (!empty($this->username&& $login_ok && $this->authType == 'digest'
  395.          && $this->auth['qop'== 'auth'
  396.          $this->authenticationInfo();
  397.       }
  398.     }
  399.     
  400.     // }}}
  401.     // {{{ drawLogin()
  402.  
  403.     /**
  404.      * Launch the login box
  405.      *
  406.      * @param  string $username  Username
  407.      * @return void 
  408.      */
  409.     function drawLogin($username "")
  410.     {
  411.         /**
  412.          * Send the header commands
  413.          */
  414.         if ($this->authType == 'basic'{
  415.             header("WWW-Authenticate: Basic realm=\"".$this->realm."\"");
  416.             header('HTTP/1.0 401 Unauthorized');            
  417.         else if ($this->authType == 'digest'{
  418.             $this->nonce = $this->_getNonce();
  419.  
  420.             $wwwauth 'WWW-Authenticate: Digest ';
  421.             $wwwauth .= 'qop="'.$this->options['qop'].'", ';
  422.             $wwwauth .= 'algorithm='.$this->options['algorithm'].', ';
  423.             $wwwauth .= 'realm="'.$this->options['digestRealm'].'", ';
  424.             $wwwauth .= 'nonce="'.$this->nonce.'", ';
  425.             if ($this->stale{
  426.                 $wwwauth .= 'stale=true, ';
  427.             }
  428.             if (!empty($this->opaque)) {
  429.                 $wwwauth .= 'opaque="'.$this->opaque.'"' ;
  430.             }
  431.             $wwwauth .= "\r\n";
  432.             if (!$this->options['forceDigestOnly']{
  433.                 $wwwauth .= 'WWW-Authenticate: Basic realm="'.$this->realm.'"';
  434.             }
  435.             header($wwwauth);
  436.             header('HTTP/1.0 401 Unauthorized');            
  437.         }
  438.  
  439.         /**
  440.          * This code is only executed if the user hits the cancel
  441.          * button or if he enters wrong data 3 times.
  442.          */
  443.         if ($this->stale{
  444.             echo 'Stale nonce value, please re-authenticate.';
  445.         else {
  446.             echo $this->CancelText;
  447.         }
  448.         exit;
  449.     }
  450.  
  451.     // }}}
  452.     // {{{ setRealm()
  453.  
  454.     /**
  455.      * Set name of the current realm
  456.      *
  457.      * @access public
  458.      * @param  string $realm  Name of the realm
  459.      * @param  string $digestRealm  Name of the realm for digest authentication
  460.      * @return void 
  461.      */
  462.     function setRealm($realm$digestRealm '')
  463.     {
  464.         $this->realm = $realm;
  465.         if (!empty($digestRealm)) {
  466.             $this->options['digestRealm'$digestRealm;
  467.         }
  468.     }
  469.  
  470.     // }}}
  471.     // {{{ setCancelText()
  472.  
  473.     /**
  474.      * Set the text to send if user hits the cancel button
  475.      *
  476.      * @access public
  477.      * @param  string $text  Text to send
  478.      * @return void 
  479.      */
  480.     function setCancelText($text)
  481.     {
  482.         $this->CancelText = $text;
  483.     }
  484.  
  485.     // }}}
  486.     // {{{ validateDigest()
  487.     
  488.     /**
  489.      * judge if the client response is valid.
  490.      *
  491.      * @access public
  492.      * @param  string $response  client response
  493.      * @param  string $a1 password or hashed password stored in container
  494.      * @return bool true if success, false otherwise
  495.      */
  496.     function validateDigest($response$a1)    
  497.     {
  498.         $server &$this->_importGlobalVariable('server');
  499.  
  500.         $a2unhashed $server['REQUEST_METHOD'].":".$this->selfURI();
  501.         if($this->auth['qop'== 'auth-int'{
  502.             if(isset($GLOBALS["HTTP_RAW_POST_DATA"])) {
  503.                 // In PHP < 4.3 get raw POST data from this variable
  504.                 $body $GLOBALS["HTTP_RAW_POST_DATA"];
  505.             else if($lines @file('php://input')) {
  506.                 // In PHP >= 4.3 get raw POST data from this file
  507.                 $body implode("\n"$lines);
  508.             else {
  509.                 $post &$this->_importGlobalVariable('post');
  510.                 $body '';
  511.                 foreach($post as $key => $value{
  512.                     if($body != ''$body .= '&';
  513.                     $body .= rawurlencode($key'=' rawurlencode($value);
  514.                 }
  515.             }
  516.  
  517.             $a2unhashed .= ':'.md5($body);
  518.         }
  519.         
  520.         $a2 md5($a2unhashed);
  521.         $combined $a1.':'.
  522.             $this->auth['nonce'].':'.
  523.             $this->auth['nc'].':'.
  524.             $this->auth['cnonce'].':'.
  525.             $this->auth['qop'].':'.
  526.             $a2;
  527.         $expectedResponse md5($combined);
  528.         
  529.         if(!isset($this->auth['opaque']|| $this->auth['opaque'== $this->opaque{
  530.             if($response == $expectedResponse// password is valid
  531.                 if(!$this->stale{
  532.                     return true;
  533.                 else {
  534.                     $this->drawLogin();
  535.                 }
  536.             }
  537.         }
  538.         
  539.         return false;
  540.     }
  541.     
  542.     // }}}
  543.     // {{{ _judgeStale()
  544.     
  545.     /**
  546.      * judge if nonce from client is stale.
  547.      *
  548.      * @access private
  549.      * @param  string $nonce  nonce value from client
  550.      * @return bool stale
  551.      */
  552.     function _judgeStale($nonce
  553.     {
  554.         $stale = false;
  555.         
  556.         if(!$this->_decodeNonce($nonce$time$hash_cli)) {
  557.          $this->nextNonce = false;
  558.          $stale = true;
  559.             return $stale;
  560.         }
  561.  
  562.         if ($time time($this->options['nonceLife']{
  563.          $this->nextNonce = $this->_getNonce();
  564.             $stale = true;
  565.         else {
  566.          $this->nextNonce = $nonce;
  567.       }
  568.  
  569.         return $stale;
  570.     }
  571.     
  572.     // }}}
  573.     // {{{ _nonceDecode()
  574.     
  575.     /**
  576.      * decode nonce string
  577.      *
  578.      * @access private
  579.      * @param  string $nonce nonce value from client
  580.      * @param  string $time decoded time
  581.      * @param  string $hash decoded hash
  582.      * @return bool false if nonce is invalid
  583.      */
  584.     function _decodeNonce($nonce&$time&$hash
  585.     {
  586.         $server &$this->_importGlobalVariable('server');
  587.  
  588.         if (strlen($nonce!= AUTH_HTTP_NONCE_TIME_LEN + AUTH_HTTP_NONCE_HASH_LEN{
  589.             return false;
  590.         }
  591.  
  592.         $time =  base64_decode(substr($nonce0AUTH_HTTP_NONCE_TIME_LEN));
  593.         $hash_cli substr($nonceAUTH_HTTP_NONCE_TIME_LENAUTH_HTTP_NONCE_HASH_LEN);
  594.  
  595.         $hash md5($time $server['HTTP_USER_AGENT'$this->options['noncekey']);
  596.  
  597.         if ($hash_cli != $hash{
  598.             return false;
  599.         }
  600.         
  601.         return true;
  602.     }
  603.  
  604.     // }}}
  605.     // {{{ _getNonce()
  606.     
  607.     /**
  608.      * return nonce to detect timeout
  609.      *
  610.      * @access private
  611.      * @return string nonce value
  612.      */
  613.     function _getNonce(
  614.     {
  615.         $server &$this->_importGlobalVariable('server');
  616.  
  617.         $time time();
  618.         $hash md5($time $server['HTTP_USER_AGENT'$this->options['noncekey']);
  619.  
  620.         return base64_encode($time$hash;  
  621.     }
  622.  
  623.     // }}}
  624.     // {{{ authenticationInfo()
  625.     
  626.     /**
  627.      * output HTTP Authentication-Info header
  628.      *
  629.      * @notes md5 hash of contents is required if 'qop' is 'auth-int'
  630.      *
  631.      * @access public
  632.      * @param string MD5 hash of content
  633.      */
  634.     function authenticationInfo($contentMD5 ''{
  635.         
  636.         if($this->getAuth(&& ($this->getAuthData('a1'!= null)) {
  637.             $server &$this->_importGlobalVariable('server');
  638.             $a1 $this->getAuthData('a1');
  639.  
  640.             // Work out authorisation response
  641.             $a2unhashed ":".$this->selfURI();
  642.             if($this->auth['qop'== 'auth-int'{
  643.                 $a2unhashed .= ':'.$contentMD5;
  644.             }
  645.             $a2 md5($a2unhashed);
  646.             $combined $a1.':'.
  647.                         $this->nonce.':'.
  648.                         $this->auth['nc'].':'.
  649.                         $this->auth['cnonce'].':'.
  650.                         $this->auth['qop'].':'.
  651.                         $a2;
  652.             
  653.             // Send authentication info
  654.             $wwwauth 'Authentication-Info: ';
  655.             if($this->nonce != $this->nextNonce{
  656.                 $wwwauth .= 'nextnonce="'.$this->nextNonce.'", ';
  657.             }
  658.             $wwwauth .= 'qop='.$this->auth['qop'].', ';
  659.             $wwwauth .= 'rspauth="'.md5($combined).'", ';
  660.             $wwwauth .= 'cnonce="'.$this->auth['cnonce'].'", ';
  661.             $wwwauth .= 'nc='.$this->auth['nc'].'';
  662.             header($wwwauth);
  663.         }
  664.     }
  665.     // }}}
  666.     // {{{ setOption()
  667.     /**
  668.      * set authentication option
  669.      *
  670.      * @access public
  671.      * @param mixed $name key of option
  672.      * @param mixed $value value of option
  673.      * @return void 
  674.      */
  675.     function setOption($name$value = null
  676.     {
  677.         if (is_array($name)) {
  678.             foreach($name as $key => $value{
  679.                 if (array_key_exists$key$this->options)) {
  680.                     $this->options[$key$value;
  681.                 }
  682.             }
  683.         else {
  684.             if (array_key_exists$name$this->options)) {
  685.                     $this->options[$name$value;
  686.             }
  687.         }
  688.     }
  689.  
  690.     // }}}
  691.     // {{{ getOption()
  692.     /**
  693.      * get authentication option
  694.      *
  695.      * @access public
  696.      * @param string $name key of option
  697.      * @return mixed option value
  698.      */
  699.     function getOption($name
  700.     {
  701.         if (array_key_exists$name$this->options)) {
  702.             return $this->options[$name];
  703.         }
  704.         if ($name == 'CancelText'{
  705.             return $this->CancelText;
  706.         }
  707.         if ($name == 'Realm'{
  708.             return $this->realm;
  709.         }
  710.         return false;
  711.     }
  712.  
  713.     // }}}
  714.     // {{{ selfURI()
  715.     /**
  716.      * get self URI
  717.      *
  718.      * @access public
  719.      * @return string self URI
  720.      */
  721.     function selfURI(
  722.     {
  723.         $server &$this->_importGlobalVariable('server');
  724.  
  725.         if (preg_match("/MSIE/",$server['HTTP_USER_AGENT'])) {
  726.             // query string should be removed for MSIE
  727.             $uri preg_replace("/^(.*)\?/","\\1",$server['REQUEST_URI']);
  728.         else {
  729.             $uri $server['REQUEST_URI'];
  730.         }
  731.         return $uri;
  732.     }
  733.  
  734.     // }}}
  735.  
  736. }
  737.  
  738. // }}}
  739.  
  740. /*
  741.  * Local variables:
  742.  * tab-width: 4
  743.  * c-basic-offset: 4
  744.  * End:
  745.  */
  746. ?>

Documentation generated on Mon, 11 Mar 2019 13:52:55 -0400 by phpDocumentor 1.4.4. PEAR Logo Copyright © PHP Group 2004.