/**
  * Is there a session ID in the request?
  * @return bool
  */
 public static function request_contains_session_id()
 {
     $secure = Director::is_https() && Config::inst()->get('SilverStripe\\Control\\Session', 'cookie_secure');
     $name = $secure ? 'SECSESSID' : session_name();
     return isset($_COOKIE[$name]) || isset($_REQUEST[$name]);
 }
 /**
  * Add the appropriate caching headers to the response, including If-Modified-Since / 304 handling.
  * Note that setting HTTP::$cache_age will overrule any cache headers set by PHP's
  * session_cache_limiter functionality. It is your responsibility to ensure only cacheable data
  * is in fact cached, and HTTP::$cache_age isn't set when the HTTP body contains session-specific
  * content.
  *
  * Omitting the $body argument or passing a string is deprecated; in these cases, the headers are
  * output directly.
  *
  * @param HTTPResponse $body
  */
 public static function add_cache_headers($body = null)
 {
     $cacheAge = self::$cache_age;
     // Validate argument
     if ($body && !$body instanceof HTTPResponse) {
         user_error("HTTP::add_cache_headers() must be passed an HTTPResponse object", E_USER_WARNING);
         $body = null;
     }
     // Development sites have frequently changing templates; this can get stuffed up by the code
     // below.
     if (Director::isDev()) {
         $cacheAge = 0;
     }
     // The headers have been sent and we don't have an HTTPResponse object to attach things to; no point in
     // us trying.
     if (headers_sent() && !$body) {
         return;
     }
     // Populate $responseHeaders with all the headers that we want to build
     $responseHeaders = array();
     $cacheControlHeaders = Config::inst()->get(__CLASS__, 'cache_control');
     // currently using a config setting to cancel this, seems to be so that the CMS caches ajax requests
     if (function_exists('apache_request_headers') && Config::inst()->get(__CLASS__, 'cache_ajax_requests')) {
         $requestHeaders = array_change_key_case(apache_request_headers(), CASE_LOWER);
         if (isset($requestHeaders['x-requested-with']) && $requestHeaders['x-requested-with'] == 'XMLHttpRequest') {
             $cacheAge = 0;
         }
     }
     if ($cacheAge > 0) {
         $cacheControlHeaders['max-age'] = self::$cache_age;
         // Set empty pragma to avoid PHP's session_cache_limiter adding conflicting caching information,
         // defaulting to "nocache" on most PHP configurations (see http://php.net/session_cache_limiter).
         // Since it's a deprecated HTTP 1.0 option, all modern HTTP clients and proxies should
         // prefer the caching information indicated through the "Cache-Control" header.
         $responseHeaders["Pragma"] = "";
         // To do: User-Agent should only be added in situations where you *are* actually
         // varying according to user-agent.
         $vary = Config::inst()->get(__CLASS__, 'vary');
         if ($vary && strlen($vary)) {
             $responseHeaders['Vary'] = $vary;
         }
     } else {
         $contentDisposition = null;
         if ($body) {
             // Grab header for checking. Unfortunately HTTPRequest uses a mistyped variant.
             $contentDisposition = $body->getHeader('Content-disposition');
             if (!$contentDisposition) {
                 $contentDisposition = $body->getHeader('Content-Disposition');
             }
         }
         if ($body && Director::is_https() && isset($_SERVER['HTTP_USER_AGENT']) && strstr($_SERVER['HTTP_USER_AGENT'], 'MSIE') == true && strstr($contentDisposition, 'attachment;') == true) {
             // IE6-IE8 have problems saving files when https and no-cache are used
             // (http://support.microsoft.com/kb/323308)
             // Note: this is also fixable by ticking "Do not save encrypted pages to disk" in advanced options.
             $cacheControlHeaders['max-age'] = 3;
             // Set empty pragma to avoid PHP's session_cache_limiter adding conflicting caching information,
             // defaulting to "nocache" on most PHP configurations (see http://php.net/session_cache_limiter).
             // Since it's a deprecated HTTP 1.0 option, all modern HTTP clients and proxies should
             // prefer the caching information indicated through the "Cache-Control" header.
             $responseHeaders["Pragma"] = "";
         } else {
             $cacheControlHeaders['no-cache'] = "true";
             $cacheControlHeaders['no-store'] = "true";
         }
     }
     foreach ($cacheControlHeaders as $header => $value) {
         if (is_null($value)) {
             unset($cacheControlHeaders[$header]);
         } elseif (is_bool($value) && $value || $value === "true") {
             $cacheControlHeaders[$header] = $header;
         } else {
             $cacheControlHeaders[$header] = $header . "=" . $value;
         }
     }
     $responseHeaders['Cache-Control'] = implode(', ', $cacheControlHeaders);
     unset($cacheControlHeaders, $header, $value);
     if (self::$modification_date && $cacheAge > 0) {
         $responseHeaders["Last-Modified"] = self::gmt_date(self::$modification_date);
         // Chrome ignores Varies when redirecting back (http://code.google.com/p/chromium/issues/detail?id=79758)
         // which means that if you log out, you get redirected back to a page which Chrome then checks against
         // last-modified (which passes, getting a 304)
         // when it shouldn't be trying to use that page at all because it's the "logged in" version.
         // By also using and etag that includes both the modification date and all the varies
         // values which we also check against we can catch this and not return a 304
         $etagParts = array(self::$modification_date, serialize($_COOKIE));
         $etagParts[] = Director::is_https() ? 'https' : 'http';
         if (isset($_SERVER['HTTP_USER_AGENT'])) {
             $etagParts[] = $_SERVER['HTTP_USER_AGENT'];
         }
         if (isset($_SERVER['HTTP_ACCEPT'])) {
             $etagParts[] = $_SERVER['HTTP_ACCEPT'];
         }
         $etag = sha1(implode(':', $etagParts));
         $responseHeaders["ETag"] = $etag;
         // 304 response detection
         if (isset($_SERVER['HTTP_IF_MODIFIED_SINCE'])) {
             $ifModifiedSince = strtotime(stripslashes($_SERVER['HTTP_IF_MODIFIED_SINCE']));
             // As above, only 304 if the last request had all the same varies values
             // (or the etag isn't passed as part of the request - but with chrome it always is)
             $matchesEtag = !isset($_SERVER['HTTP_IF_NONE_MATCH']) || $_SERVER['HTTP_IF_NONE_MATCH'] == $etag;
             if ($ifModifiedSince >= self::$modification_date && $matchesEtag) {
                 if ($body) {
                     $body->setStatusCode(304);
                     $body->setBody('');
                 } else {
                     header('HTTP/1.0 304 Not Modified');
                     die;
                 }
             }
         }
         $expires = time() + $cacheAge;
         $responseHeaders["Expires"] = self::gmt_date($expires);
     }
     if (self::$etag) {
         $responseHeaders['ETag'] = self::$etag;
     }
     // etag needs to be a quoted string according to HTTP spec
     if (!empty($responseHeaders['ETag']) && 0 !== strpos($responseHeaders['ETag'], '"')) {
         $responseHeaders['ETag'] = sprintf('"%s"', $responseHeaders['ETag']);
     }
     // Now that we've generated them, either output them or attach them to the HTTPResponse as appropriate
     foreach ($responseHeaders as $k => $v) {
         if ($body) {
             // Set the header now if it's not already set.
             if ($body->getHeader($k) === null) {
                 $body->addHeader($k, $v);
             }
         } elseif (!headers_sent()) {
             header("{$k}: {$v}");
         }
     }
 }
 public function testIsHttps()
 {
     if (!TRUSTED_PROXY) {
         $this->markTestSkipped('Test cannot be run without trusted proxy');
     }
     // nothing available
     $headers = array('HTTP_X_FORWARDED_PROTOCOL', 'HTTPS', 'SSL');
     $origServer = $_SERVER;
     foreach ($headers as $header) {
         if (isset($_SERVER[$header])) {
             unset($_SERVER['HTTP_X_FORWARDED_PROTOCOL']);
         }
     }
     $this->assertFalse(Director::is_https());
     $_SERVER['HTTP_X_FORWARDED_PROTOCOL'] = 'https';
     $this->assertTrue(Director::is_https());
     $_SERVER['HTTP_X_FORWARDED_PROTOCOL'] = 'http';
     $this->assertFalse(Director::is_https());
     $_SERVER['HTTP_X_FORWARDED_PROTOCOL'] = 'ftp';
     $this->assertFalse(Director::is_https());
     $_SERVER['HTTP_X_FORWARDED_PROTO'] = 'https';
     $this->assertTrue(Director::is_https());
     $_SERVER['HTTP_X_FORWARDED_PROTO'] = 'http';
     $this->assertFalse(Director::is_https());
     $_SERVER['HTTP_X_FORWARDED_PROTO'] = 'ftp';
     $this->assertFalse(Director::is_https());
     $_SERVER['HTTP_FRONT_END_HTTPS'] = 'On';
     $this->assertTrue(Director::is_https());
     $_SERVER['HTTP_FRONT_END_HTTPS'] = 'Off';
     $this->assertFalse(Director::is_https());
     // https via HTTPS
     $_SERVER['HTTPS'] = 'true';
     $this->assertTrue(Director::is_https());
     $_SERVER['HTTPS'] = '1';
     $this->assertTrue(Director::is_https());
     $_SERVER['HTTPS'] = 'off';
     $this->assertFalse(Director::is_https());
     // https via SSL
     $_SERVER['SSL'] = '';
     $this->assertTrue(Director::is_https());
     $_SERVER = $origServer;
 }