Skip to content

Commit

Permalink
404 status header fix; see #197
Browse files Browse the repository at this point in the history
  • Loading branch information
JasWSInc committed Jun 18, 2014
1 parent 3d5b4cb commit 60fa420
Show file tree
Hide file tree
Showing 2 changed files with 108 additions and 26 deletions.
14 changes: 7 additions & 7 deletions quick-cache/includes/advanced-cache.tpl.php
Original file line number Diff line number Diff line change
Expand Up @@ -701,9 +701,9 @@ public function maybe_start_output_buffering()
{
list($headers, $cache) = explode('<!--headers-->', file_get_contents($this->cache_file), 2);

$headers_list = headers_list(); // Headers already sent (or ready to be sent).
$headers_list = $this->headers_list(); // Headers already sent (or ready to be sent).
foreach(unserialize($headers) as $_header) // Preserves original headers sent with this file.
if(!in_array($_header, $headers_list) && stripos($_header, 'Last-Modified:') !== 0) header($_header);
if(!in_array($_header, $headers_list, TRUE) && stripos($_header, 'Last-Modified:') !== 0) header($_header);
unset($_header); // Just a little housekeeping.

if(QUICK_CACHE_DEBUGGING_ENABLE && $this->is_html_xml_doc($cache)) // Only if HTML comments are possible.
Expand Down Expand Up @@ -865,15 +865,15 @@ public function output_buffer_callback_handler($buffer, $phase)
return (boolean)$this->maybe_set_debug_info($this::NC_DEBUG_IS_LOGGED_IN_USER);

if($this->is_like_user_logged_in()) // Commenters, password-protected access, or actually logged-in.
return (boolean)$this->maybe_set_debug_info($this::NC_DEBUG_IS_LIKE_LOGGED_IN_USER); // This uses a separate debug notice.
return (boolean)$this->maybe_set_debug_info($this::NC_DEBUG_IS_LIKE_LOGGED_IN_USER); // Separate debug notice.

if($this->is_404 && !QUICK_CACHE_CACHE_404_REQUESTS) // Not caching 404 errors.
return (boolean)$this->maybe_set_debug_info($this::NC_DEBUG_404_REQUEST);

if(strpos($cache, '<body id="error-page">') !== FALSE)
return (boolean)$this->maybe_set_debug_info($this::NC_DEBUG_WP_ERROR_PAGE);

if(!function_exists('http_response_code') && stripos($cache, '<title>database error</title>') !== FALSE)
if(!$this->function_is_possible('http_response_code') && stripos($cache, '<title>database error</title>') !== FALSE)
return (boolean)$this->maybe_set_debug_info($this::NC_DEBUG_WP_ERROR_PAGE);

if(!$this->has_a_cacheable_content_type()) // Exclude non-HTML/XML content types.
Expand All @@ -885,7 +885,7 @@ public function output_buffer_callback_handler($buffer, $phase)
if($this->is_maintenance) // <http://wordpress.org/extend/plugins/maintenance-mode>
return (boolean)$this->maybe_set_debug_info($this::NC_DEBUG_MAINTENANCE_PLUGIN);

if(function_exists('zlib_get_coding_type') && zlib_get_coding_type()
if($this->function_is_possible('zlib_get_coding_type') && zlib_get_coding_type()
&& (!($zlib_oc = ini_get('zlib.output_compression')) || !filter_var($zlib_oc, FILTER_VALIDATE_BOOLEAN))
) return (boolean)$this->maybe_set_debug_info($this::NC_DEBUG_OB_ZLIB_CODING_TYPE);

Expand Down Expand Up @@ -921,12 +921,12 @@ public function output_buffer_callback_handler($buffer, $phase)
*/
if($this->is_404) // This is a 404; let's create 404 cache file and symlink to it.
{
if(file_put_contents($cache_file_tmp, serialize(headers_list()).'<!--headers-->'.$cache) && rename($cache_file_tmp, $this->cache_file_404))
if(file_put_contents($cache_file_tmp, serialize($this->headers_list()).'<!--headers-->'.$cache) && rename($cache_file_tmp, $this->cache_file_404))
if(symlink($this->cache_file_404, $this->cache_file)) // If this fails an exception will be thrown down below.
return $cache; // Return the newly built cache; with possible debug information also.

} // NOT a 404; let's write a new cache file.
else if(file_put_contents($cache_file_tmp, serialize(headers_list()).'<!--headers-->'.$cache) && rename($cache_file_tmp, $this->cache_file))
else if(file_put_contents($cache_file_tmp, serialize($this->headers_list()).'<!--headers-->'.$cache) && rename($cache_file_tmp, $this->cache_file))
return $cache; // Return the newly built cache; with possible debug information also.

@unlink($cache_file_tmp); // Clean this up (if it exists); and throw an exception with information for the site owner.
Expand Down
120 changes: 101 additions & 19 deletions quick-cache/includes/share.php
Original file line number Diff line number Diff line change
Expand Up @@ -433,6 +433,8 @@ public function host_base_dir_tokens($dashify = FALSE)
* @note This routine may trigger a flag which indicates that the current user was logged-in at some point,
* but now the login cookie can no longer be validated by WordPress; i.e. they are NOT actually logged in any longer.
* See {@link $user_login_cookie_expired_or_invalid}
*
* @warning Do NOT call upon this method until WordPress reaches it's cache postload phase.
*/
public function user_token() // When/if possible.
{
Expand Down Expand Up @@ -664,13 +666,15 @@ public function is_html_xml_doc($doc)
* @return boolean `TRUE` if the current request has a cacheable content type.
*
* @note The return value of this function is cached to reduce overhead on repeat calls.
*
* @warning Do NOT call upon this method until the end of a script execution.
*/
public function has_a_cacheable_content_type()
{
if(isset(static::$static[__FUNCTION__]))
return static::$static[__FUNCTION__];

foreach(headers_list() as $_header)
foreach($this->headers_list() as $_header)
if(stripos($_header, 'Content-Type:') === 0)
$content_type = $_header; // Last one.
unset($_header); // Just a little housekeeping.
Expand All @@ -689,34 +693,22 @@ public function has_a_cacheable_content_type()
* @return boolean `TRUE` if the current request has a cacheable HTTP status code.
*
* @note The return value of this function is cached to reduce overhead on repeat calls.
*
* @warning Do NOT call upon this method until the end of a script execution.
*/
public function has_a_cacheable_status()
{
if(isset(static::$static[__FUNCTION__]))
return static::$static[__FUNCTION__];

$http_status = 0; // Initialize the HTTP status code; as a string here.
if($this->function_is_possible('http_response_code') && ($http_response_code = http_response_code()))
{
$http_status = (integer)$http_response_code;
if(property_exists($this, 'http_status')) // Update {@link advanced_cache} property?
$this->{'http_status'} = $http_status; // Prefer over {@link status_header()} filter value.
}
$http_status = (string)$http_status; // Need a string type here.
if(isset($http_status[0]) && $http_status[0] !== '2' && $http_status !== '404')
if(($http_status = (string)$this->http_status()) && $http_status[0] !== '2' && $http_status !== '404')
return (static::$static[__FUNCTION__] = FALSE); // A non-2xx & non-404 status code.
/*
* PHP's `headers_list()` currently does NOT include `HTTP/` headers.
* This means the following routine will never catch a status sent by `status_header()`.
* However, I'm leaving this check in place in case a future version of PHP adds support for this.
*
* For now, we monitor `status_header()` via {@link maybe_filter_status_header_postload()} so that will suffice.
*/
foreach(headers_list() as $_header)

foreach($this->headers_list() as $_header)
if(preg_match('/^(?:Retry\-After\:\s+(?P<retry>.+)|Status\:\s+(?P<status>[0-9]+)|HTTP\/[0-9]+\.[0-9]+\s+(?P<http_status>[0-9]+))/i', $_header, $_m))
if(!empty($_m['retry']) || (!empty($_m['status']) && $_m['status'][0] !== '2' && $_m['status'] !== '404')
|| (!empty($_m['http_status']) && $_m['http_status'][0] !== '2' && $_m['http_status'] !== '404')
) return (static::$static[__FUNCTION__] = FALSE); // NOT a 2xx or 404 status.
) return (static::$static[__FUNCTION__] = FALSE); // Not a cacheable status.
unset($_header); // Just a little housekeeping.

return (static::$static[__FUNCTION__] = TRUE); // Assume that it is by default.
Expand Down Expand Up @@ -787,6 +779,94 @@ public function function_is_possible($function)
return (static::$static[__FUNCTION__][$function] = $possible);
}

/* --------------------------------------------------------------------------------------
* HTTP protocol/status utility methods.
-------------------------------------------------------------------------------------- */

/**
* Current HTTP protocol; i.e. `HTTP/1.0` or `HTTP/1.1`.
*
* @since 14xxxx Correcting 404 cache response status code.
*
* @return string Current HTTP protocol; i.e. `HTTP/1.0` or `HTTP/1.1`.
*/
public function http_protocol()
{
if(isset(static::$static[__FUNCTION__]))
return static::$static[__FUNCTION__];

$protocol = !empty($_SERVER['SERVER_PROTOCOL'])
? strtoupper((string)$_SERVER['SERVER_PROTOCOL']) : 'HTTP/1.0';

if($protocol !== 'HTTP/1.1' && $protocol !== 'HTTP/1.0')
$protocol = 'HTTP/1.0'; // Default value.

return (static::$static[__FUNCTION__] = $protocol);
}

/**
* An array of all headers sent via PHP; and the current HTTP status header too.
*
* @since 14xxxx Correcting 404 cache response status code.
*
* @return array PHP {@link headers_list()} supplemented with
* HTTP status code when possible.
*
* @warning Do NOT call upon this method until the end of a script execution.
*/
public function headers_list()
{
if(isset(static::$static[__FUNCTION__]))
return static::$static[__FUNCTION__];

$headers_list = headers_list(); // Lacks HTTP status header.

if(($http_status = (string)$this->http_status()))
$headers_list[] = $this->http_protocol().' '.$http_status;

return (static::$static[__FUNCTION__] = $headers_list);
}

/**
* HTTP status code if at all possible.
*
* @since 14xxxx Correcting 404 cache response status code.
*
* @return integer HTTP status code if at all possible; else `0`.
*
* @warning Do NOT call upon this method until the end of a script execution.
*/
public function http_status()
{
if(isset(static::$static[__FUNCTION__]))
return static::$static[__FUNCTION__];

$http_status = 0; // Initialize.
$has_property__is_404 = property_exists($this, 'is_404');
$has_property__http_status = property_exists($this, 'http_status');

// Determine current HTTP status code.

if($has_property__is_404 && $this->{'is_404'})
$http_status = 404; // WordPress said so.

else if($this->function_is_possible('http_response_code') && ($http_response_code = (integer)http_response_code()))
$http_status = $http_response_code; // {@link \http_response_code()} available since PHP v5.4.

else if($has_property__http_status && (integer)$this->{'http_status'})
$http_status = (integer)$this->{'http_status'}; // {@link \status_header()} filter.

// Dynamically update class property flags related to the HTTP status code.

if($http_status && $has_property__http_status) // Update {@link $http_status}?
$this->{'http_status'} = $http_status; // Prefer over {@link status_header()}.

if($http_status === 404 && $has_property__is_404) // Update {@link $is_404}?
$this->{'is_404'} = TRUE; // Prefer over {@link is_404()}.

return (static::$static[__FUNCTION__] = $http_status);
}

/* --------------------------------------------------------------------------------------
* Misc. utility methods.
-------------------------------------------------------------------------------------- */
Expand Down Expand Up @@ -960,6 +1040,8 @@ public function remove_filter() // Simple `remove_hook()` alias.
* Runs any callables attached to an action.
*
* @since 140422 First documented version.
*
* @param string $hook The name of an action hook.
*/
public function do_action($hook)
{
Expand Down

0 comments on commit 60fa420

Please sign in to comment.