/** * 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; }