Version: current

      1 <?php
      2 /**
      3  * A class with HTTP-related helpers.
      4  * Like Debug, this is more a bundle of methods than a class ;-)
      5  *
      6  * @package sapphire
      7  * @subpackage misc
      8  */
      9 class HTTP {
     10
     11     static $userName;
     12     static $password;
     13
     14     /**
     15      * Turns a local system filename into a URL by comparing it to the script filename
     16      */
     17     static function filename2url($filename) {
     18         $slashPos = -1;
     19         while(($slashPos = strpos($filename, "/", $slashPos+1)) !== false) {
     20             if(substr($filename, 0, $slashPos) == substr($_SERVER['SCRIPT_FILENAME'],0,$slashPos)) {
     21                 $commonLength = $slashPos;
     22             } else {
     23                 break;
     24             }
     25         }
     26
     27         $urlBase = substr($_SERVER['PHP_SELF'], 0, -(strlen($_SERVER['SCRIPT_FILENAME']) - $commonLength));
     28         $url = $urlBase . substr($filename, $commonLength);
     29         $protocol = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] && $_SERVER['HTTPS'] != 'off') ? "https" : "http";
     30         return "$protocol://". $_SERVER['HTTP_HOST'] . $url;
     31
     32         // Count the number of extra folders the script is in.
     33         // $prefix = str_repeat("../", substr_count(substr($_SERVER[SCRIPT_FILENAME],$commonBaseLength)));
     34     }
     35
     36     /**
     37      * Turn all relative URLs in the content to absolute URLs
     38      */
     39     static function absoluteURLs($html) {
     40         $html = str_replace('$CurrentPageURL', $_SERVER['REQUEST_URI'], $html);
     41         return HTTP::urlRewriter($html, '(substr($URL,0,1) == "/") ? ( Director::protocolAndHost() . $URL ) : ( (ereg("^[A-Za-z]+:", $URL)) ? $URL : Director::absoluteBaseURL() . $URL )' );
     42     }
     43
     44     /*
     45      * Rewrite all the URLs in the given content, evaluating the given string as PHP code
     46      *
     47      * Put $URL where you want the URL to appear, however, you can't embed $URL in strings
     48      * Some example code:
     49      *  '"../../" . $URL'
     50      *  'myRewriter($URL)'
     51      *  '(substr($URL,0,1)=="/") ? "../" . substr($URL,1) : $URL'
     52      */
     53     static function urlRewriter($content, $code) {
     54         $attribs = array("src","background","a" => "href","link" => "href", "base" => "href");
     55         foreach($attribs as $tag => $attrib) {
     56             if(!is_numeric($tag)) $tagPrefix = "$tag ";
     57             else $tagPrefix = "";
     58
     59             $regExps[] = "/(<{$tagPrefix}[^>]*$attrib *= *\")([^\"]*)(\")/ie";
     60             $regExps[] = "/(<{$tagPrefix}[^>]*$attrib *= *')([^']*)(')/ie";
     61             $regExps[] = "/(<{$tagPrefix}[^>]*$attrib *= *)([^\"' ]*)( )/ie";
     62         }
     63         $regExps[] = '/(background-image:[^;]*url *\()([^)]+)(\))/ie';
     64         $regExps[] = '/(background:[^;]*url *\()([^)]+)(\))/ie';
     65
     66         // Make
     67         $code = 'stripslashes("$1") . (' . str_replace('$URL', 'stripslashes("$2")', $code) . ') . stripslashes("$3")';
     68
     69         foreach($regExps as $regExp) {
     70             $content = preg_replace($regExp, $code, $content);
     71         }
     72
     73         return $content;
     74     }
     75
     76     /**
     77      * Will try to include a GET parameter for an existing URL,
     78      * preserving existing parameters and fragments.
     79      * If no URL is given, falls back to $_SERVER['REQUEST_URI'].
     80      * Uses parse_url() to dissect the URL, and http_build_query() to reconstruct it
     81      * with the additional parameter. Converts any '&' (ampersand)
     82      * URL parameter separators to the more XHTML compliant '&amp;'.
     83      *
     84      * CAUTION: If the URL is determined to be relative,
     85      * it is prepended with Director::absoluteBaseURL().
     86      * This method will always return an absolute URL because
     87      * Director::makeRelative() can lead to inconsistent results.
     88      *
     89      * @param String $varname
     90      * @param String $varvalue
     91      * @param String $currentURL Relative or absolute URL (Optional).
     92      * @param String $separator Separator for http_build_query(). (Optional).
     93      * @return String Absolute URL
     94      */
     95     public static function setGetVar($varname, $varvalue, $currentURL = null, $separator = '&amp;') {
     96         $uri = $currentURL ? $currentURL : Director::makeRelative($_SERVER['REQUEST_URI']);
     97
     98         $isRelative = false;
     99         // We need absolute URLs for parse_url()
    100         if(Director::is_relative_url($uri)) {
    101             $uri = Director::absoluteURL(Director::baseURL()) . $uri;
    102             $isRelative = true;
    103         }
    104
    105         // try to parse uri
    106         $parts = parse_url($uri);
    107         if(!$parts) {
    108             throw new InvalidArgumentException("Can't parse URL: " . $uri);
    109         }
    110
    111         // Parse params and add new variable
    112         $params = array();
    113         if(isset($parts['query'])) parse_str($parts['query'], $params);
    114         $params[$varname] = $varvalue;
    115
    116         // Generate URI segments and formatting
    117         $scheme = (isset($parts['scheme'])) ? $parts['scheme'] : 'http';
    118         $user = (isset($parts['user']) && $parts['user'] != '')  ? $parts['user'] : '';
    119
    120         if($user != '') {
    121             // format in either user:pass@host.com or user@host.com
    122             $user .= (isset($parts['pass']) && $parts['pass'] != '') ? ':' . $parts['pass'] . '@' : '@';
    123         }
    124
    125         $host = (isset($parts['host'])) ? $parts['host'] : '';
    126         $port = (isset($parts['port']) && $parts['port'] != '') ? ':'.$parts['port'] : '';
    127         $path = (isset($parts['path']) && $parts['path'] != '') ? $parts['path'] : '';
    128
    129         // handle URL params which are existing / new
    130         $params = ($params) ?  '?' . http_build_query($params, null, $separator) : '';
    131         if ($params == '?') $params = '';
    132
    133         // keep fragments (anchors) intact.
    134         $fragment = (isset($parts['fragment']) && $parts['fragment'] != '') ?  '#'.$parts['fragment'] : '';
    135
    136         // Recompile URI segments
    137         $newUri =  $scheme . '://' . $user . $host . $port . $path . $params . $fragment;
    138
    139         if($isRelative) return Director::makeRelative($newUri);
    140
    141         return $newUri;
    142     }
    143
    144     static function RAW_setGetVar($varname, $varvalue, $currentURL = null) {
    145         $url = self::setGetVar($varname, $varvalue, $currentURL);
    146         return Convert::xml2raw($url);
    147     }
    148
    149     /**
    150      * Search for all tags with a specific attribute, then return the value of that attribute in a flat array.
    151      *
    152      * @param string $content
    153      * @param array $attributes an array of tags to attributes, for example "[a] => 'href', [div] => 'id'"
    154      * @return array
    155      */
    156     public static function findByTagAndAttribute($content, $attributes) {
    157         $regexes = array();
    158
    159         foreach($attributes as $tag => $attribute) {
    160             $regexes[] = "/<{$tag} [^>]*$attribute *= *([\"'])(.*?)\\1[^>]*>/i";
    161             $regexes[] = "/<{$tag} [^>]*$attribute *= *([^ \"'>]+)/i";
    162         }
    163
    164         $result = array();
    165
    166         if($regexes) foreach($regexes as $regex) {
    167             if(preg_match_all($regex, $content, $matches)) {
    168                 $result = array_merge_recursive($result, (isset($matches[2]) ? $matches[2] : $matches[1]));
    169             }
    170         }
    171
    172         return count($result) ? $result : null;
    173     }
    174
    175     static function getLinksIn($content) {
    176         return self::findByTagAndAttribute($content, array("a" => "href"));
    177     }
    178
    179     static function getImagesIn($content) {
    180         return self::findByTagAndAttribute($content, array("img" => "src"));
    181     }
    182
    183     /*
    184      * Get mime type based on extension
    185      */
    186     static function getMimeType($filename) {
    187         global $global_mimetypes;
    188         if(!$global_mimetypes) self::loadMimeTypes();
    189         $ext = strtolower(substr($filename,strrpos($filename,'.')+1));
    190         if(isset($global_mimetypes[$ext])) return $global_mimetypes[$ext];
    191     }
    192
    193     /*
    194      * Load the mime-type data from the system file
    195      */
    196     static function loadMimeTypes() {
    197         if(@file_exists('/etc/mime.types')) {
    198             $mimeTypes = file('/etc/mime.types');
    199             foreach($mimeTypes as $typeSpec) {
    200                 if(($typeSpec = trim($typeSpec)) && substr($typeSpec,0,1) != "#") {
    201                     $parts = preg_split("/[ \t\r\n]+/", $typeSpec);
    202                     if(sizeof($parts) > 1) {
    203                         $mimeType = array_shift($parts);
    204                         foreach($parts as $ext) {
    205                             $ext = strtolower($ext);
    206                             $mimeData[$ext] = $mimeType;
    207                         }
    208                     }
    209                 }
    210             }
    211
    212         // Fail-over for if people don't have /etc/mime.types on their server.  it's unclear how important this actually is
    213         } else {
    214             $mimeData = array(
    215                 "doc" => "application/msword",
    216                 "xls" => "application/vnd.ms-excel",
    217                 "rtf" => "application/rtf",
    218             );
    219         }
    220
    221         global $global_mimetypes;
    222         $global_mimetypes = $mimeData;
    223         return $mimeData;
    224     }
    225
    226     /**
    227      * Send an HTTP request to the host.
    228      *
    229      * @return String Response text
    230      */
    231     static function sendRequest( $host, $path, $query, $port = 80 ) {
    232         $socket = fsockopen( $host, $port, $errno, $error );
    233
    234         if( !$socket )
    235             return $error;
    236
    237         if( $query )
    238             $query = '?' . $query;
    239
    240         if( self::$userName && self::$password ) {
    241             $auth = "Authorization: Basic " . base64_encode( self::$userName . ':' . self::$password ) . "\r\n";
    242         } else {
    243             $auth = '';
    244         }
    245
    246         $request = "GET {$path}{$query} HTTP/1.1\r\nHost: $host\r\nConnection: Close\r\n{$auth}\r\n";
    247
    248         fwrite( $socket, $request );
    249         $response = stream_get_contents( $socket );
    250
    251         return $response;
    252     }
    253
    254     /**
    255      * Send a HTTP POST request through fsockopen().
    256      *
    257      * @param string $host Absolute URI without path, e.g. http://silverstripe.com
    258      * @param string $path Path with leading slash
    259      * @param array|string $data Payload for the request
    260      * @param string $name Parametername for the payload (only if passed as a string)
    261      * @param string $query
    262      * @param string $port
    263      * @return string Raw HTTP-result including headers
    264      */
    265     static function sendPostRequest($host, $path, $data, $name = null, $query = '', $port = 80, $getResponse = true) {
    266         $socket = fsockopen($host, $port, $errno, $error);
    267
    268         if(!$socket)
    269             return $error;
    270
    271         if(self::$userName && self::$password)
    272             $auth = "Authorization: Basic " . base64_encode(self::$userName . ':' . self::$password) . "\r\n";
    273         else
    274             $auth = '';
    275
    276         if($query)
    277             $query = '?' . $query;
    278
    279         $dataStr = (is_array($data)) ? http_build_query($data) : $name . '=' . urlencode($data);
    280         $request = "POST {$path}{$query} HTTP/1.1\r\nHost: $host\r\n{$auth}Content-Type: application/x-www-form-urlencoded\r\nContent-Length: " . strlen($dataStr) . "\r\n\r\n";
    281         $request .= $dataStr . "\r\n\r\n";
    282
    283         fwrite($socket, $request);
    284
    285         if($getResponse){
    286             $response = stream_get_contents($socket);
    287             return $response;
    288         }
    289
    290     }
    291
    292     protected static $cache_age = 0, $modification_date = null;
    293     protected static $etag = null;
    294
    295     /**
    296      * Set the maximum age of this page in web caches, in seconds
    297      */
    298     static function set_cache_age($age) {
    299         self::$cache_age = $age;
    300     }
    301
    302     static function register_modification_date($dateString) {
    303         $timestamp = strtotime($dateString);
    304         if($timestamp > self::$modification_date)
    305             self::$modification_date = $timestamp;
    306     }
    307
    308     static function register_modification_timestamp($timestamp) {
    309         if($timestamp > self::$modification_date)
    310             self::$modification_date = $timestamp;
    311     }
    312
    313     static function register_etag($etag) {
    314         self::$etag = $etag;
    315     }
    316
    317     /**
    318      * Add the appropriate caching headers to the response, including If-Modified-Since / 304 handling.
    319      *
    320      * @param SS_HTTPResponse The SS_HTTPResponse object to augment.  Omitted the argument or passing a string is deprecated; in these
    321      * cases, the headers are output directly.
    322      */
    323     static function add_cache_headers($body = null) {
    324         // Validate argument
    325         if($body && !($body instanceof SS_HTTPResponse)) {
    326             user_error("HTTP::add_cache_headers() must be passed an SS_HTTPResponse object", E_USER_WARNING);
    327             $body = null;
    328         }
    329
    330         // Development sites have frequently changing templates; this can get stuffed up by the code
    331         // below.
    332         if(Director::isDev()) {
    333             if(!headers_sent()) header("Cache-Control: no-cache, max-age=0, must-revalidate");
    334             return;
    335         }
    336
    337         // The headers have been sent and we don't have an SS_HTTPResponse object to attach things to; no point in us trying.
    338         if(headers_sent() && !$body) return;
    339
    340         // Popuplate $responseHeaders with all the headers that we want to build
    341         $responseHeaders = array();
    342         if(function_exists('apache_request_headers')) {
    343             $requestHeaders = apache_request_headers();
    344             if(isset($requestHeaders['X-Requested-With']) && $requestHeaders['X-Requested-With'] == 'XMLHttpRequest') self::$cache_age = 0;
    345             // bdc: now we must check for DUMB IE6:
    346             if(isset($requestHeaders['x-requested-with']) && $requestHeaders['x-requested-with'] == 'XMLHttpRequest') self::$cache_age = 0;
    347         }
    348
    349         if(self::$cache_age > 0) {
    350             $responseHeaders["Cache-Control"] = "max-age=" . self::$cache_age . ", must-revalidate";
    351             $responseHeaders["Pragma"] = "";
    352         } else {
    353             $responseHeaders["Cache-Control"] = "no-cache, max-age=0, must-revalidate";
    354         }
    355
    356         if(self::$modification_date && self::$cache_age > 0) {
    357             $responseHeaders["Last-Modified"] =self::gmt_date(self::$modification_date);
    358
    359             // 304 response detection
    360             if(isset($_SERVER['HTTP_IF_MODIFIED_SINCE'])) {
    361                 $ifModifiedSince = strtotime(stripslashes($_SERVER['HTTP_IF_MODIFIED_SINCE']));
    362                 if($ifModifiedSince >= self::$modification_date) {
    363                     if($body) {
    364                         $body->setStatusCode(304);
    365                         $body->setBody('');
    366                     } else {
    367                         header('HTTP/1.0 304 Not Modified');
    368                         die();
    369                     }
    370                 }
    371             }
    372
    373             $expires = time() + self::$cache_age;
    374             $responseHeaders["Expires"] = self::gmt_date($expires);
    375         }
    376
    377         if(self::$etag) {
    378             $responseHeaders['ETag'] = self::$etag;
    379         }
    380
    381         // Now that we've generated them, either output them or attach them to the SS_HTTPResponse as appropriate
    382         foreach($responseHeaders as $k => $v) {
    383             if($body) $body->addHeader($k, $v);
    384             else if(!headers_sent()) header("$k: $v");
    385         }
    386     }
    387
    388
    389     /**
    390      * Return an {@link http://www.faqs.org/rfcs/rfc2822 RFC 2822} date in the
    391      * GMT timezone (a timestamp is always in GMT: the number of seconds
    392      * since January 1 1970 00:00:00 GMT)
    393      */
    394     static function gmt_date($timestamp) {
    395         return gmdate('D, d M Y H:i:s', $timestamp) . ' GMT';
    396     }
    397
    398     /*
    399      * Return static variable cache_age in second
    400      */
    401     static function get_cache_age() {
    402         return self::$cache_age;
    403     }
    404
    405 }
    406
    407 ?>
    408

[Raise a SilverStripe Framework issue/bug](https://github.com/silverstripe/silverstripe-framework/issues/new)
- [Raise a SilverStripe CMS issue/bug](https://github.com/silverstripe/silverstripe-cms/issues/new)
- Please use the Silverstripe Forums to ask development related questions. -