Bug #49106 [Ana]: PHP incorrectly sets no_local_copy=1 on response as Apache 2 module
| From: | cmb@php.net | Date: | Wed, 27 Oct 2021 09:18:04 +0000 |
| Subject: | Bug #49106 [Ana]: PHP incorrectly sets no_local_copy=1 on response as Apache 2 module | ||
| References: | 1 | Groups: | php.bugs |
| Request: | Send a blank email to php-bugs+get-237393@lists.php.net to get a copy of this message | ||
Edit report at https://bugs.php.net/bug.php?id=49106&edit=1
ID: 49106
Updated by: cmb@php.net
Reported by: n dot sherlock at gmail dot com
Summary: PHP incorrectly sets no_local_copy=1 on response as
Apache 2 module
Status: Analyzed
Type: Bug
Package: Apache2 related
Operating System: *
PHP Version: 5.*, 6
Block user comment: N
Private report: N
New Comment:
I also think that the no_local_copy is wrong, but given the long
standing behavior, I'd be uncomfortable to remove it for any
stable version.
Previous Comments:
------------------------------------------------------------------------
[2010-01-26 21:49:10] n dot sherlock at gmail dot com
minfrin, the caching headers ETag and Last-Modified are added by Apache before PHP gets to run, and
whether mod_cache is turned on or not. They aren't mod_cache specific.
------------------------------------------------------------------------
[2010-01-26 16:56:49] rasmus@php.net
I have been looking at this code a bit this morning. It does indeed
look like the no_local_copy is not needed here since both Last-Modified
and ETag that may be present prior to PHP being executed are removed.
And to minfrin, I think PHP does need to remove any ETag or Last-
Modified headers that are generated prior to PHP execution. It simply
makes no sense for Apache to generate an ETag for a request prior to
PHP executing on that request. How could that possibly be valid?
------------------------------------------------------------------------
[2010-01-26 15:03:28] minfrin at sharp dot fm
The httpd mod_cache is designed to work as a self contained cache that bolts onto the front of the
server (or with httpd v2.3+, can be inserted anywhere in the httpd filter stack for more targeted
caching).
In theory, php shouldn't be touching any of the cache fields in request_rec at all, nor should
php be trying to obscure any HTTP headers if it detects caching has been enabled.
It should be possible for a php script to support conditional requests, in other words php should be
able to detect the If-None-Match header and respond with 200 OK or 304 Not Modified as appropriate.
Does anyone know what problem was being solved that made php want to care as to whether mod_cache
was present?
------------------------------------------------------------------------
[2009-07-30 02:40:08] n dot sherlock at gmail dot com
Description:
------------
If PHP (5.3.0) is running as an (Apache 2) module, it currently sets no_local_copy to 1 on the
response it sends to Apache (sapi/apache2handler/sapi_apache2.c:463). It looks like this flag was
set to disallow Apache from erroneously creating its own "304 Not Modified" responses
based on the ETag or Last-Modified-Date of the PHP scripts' sourcecode itself, which would
result in stale pages being served if the scripts' output changes over time.
But there's a serious side effect of setting this flag in combination with Apache's
mod_cache. If the browser makes a conditional request for a cached PHP document, but the document is
expired in the cache, mod_cache correctly passes on the conditional request to PHP. If the PHP
script responds with a "304 Not Modified" code, mod_cache should generate a 304 response
for the browser. But due to no_local_copy, Apache is denied from creating a 304 code in response to
a request for a PHP document. This forces it to resend the (still valid) body of the PHP document
from the cache with a 200 code.
But setting "no_local_copy=1" is not needed anyway. Just below the r->no_local_copy=1
line in sapi_apache2.c is a series of calls to apr_table_unset which remove any headers that Apache
might have generated based on the PHP source itself and could be using to accept conditional
requests. Starting at line 468 in php_apache_request_ctor, we have:
apr_table_unset(r->headers_out, "Content-Length");
apr_table_unset(r->headers_out, "Last-Modified");
apr_table_unset(r->headers_out, "Expires");
apr_table_unset(r->headers_out, "ETag");
It seems to me that removing the r->no_local_copy=1 will therefore not result in erroneous
"304 Not Modified" responses being sent by Apache for PHP scripts.
At the moment if you request a mod_cache'd PHP page which itself sends no special caching
directives (e.g. an empty script), the reply from the server is (trimmed to include only
cache-relevant directives):
Status=OK - 200
Date=Sun, 26 Jul 2009 10:07:58 GMT
PHP has correctly suppressed the generation of Last-Modified-Date and ETag headers based on the
source of the script itself. No conditional request is possible and you won't get a stale page.
Now, if you request a PHP document that does set "ETag", such as the attached code
"index.php", the response from the server is:
Status=OK - 200
Date=Sun, 26 Jul 2009 10:11:02 GMT
Etag="ComputedETag"
Expires=Tue, 25 Aug 2009 10:11:02 GMT
And the error log shows that the script correctly returned a 200 response code to Apache. Now if you
press "refresh" in Firefox, the browser sends this request:
If-None-Match="ComputedETag"
Cache-Control=max-age=0
This is a conditional get which will also result in Apache revalidating its cache (since max-age=0).
So Apache passes the conditional request onto PHP, and PHP sends back a 304 Not Modified response.
But due to no_local_copy, mod_cache cannot send a 304, it responds to the browser with:
Status=OK - 200
Date=Sun, 26 Jul 2009 10:11:35 GMT
Etag="ComputedETag"
Expires=Tue, 25 Aug 2009 10:11:35 GMT
So, I removed the line that sets no_local_copy in my PHP. This had no impact on the way that the
empty PHP document that sets no cache directives was served, Apache never served erroneous 304
responses because it never saw ETag or Last-Modified headers based on the PHP source. But the
ETag-conditional request for index.php by the browser now gives the correct 304 response code:
Status=Not Modified - 304
Date=Sun, 26 Jul 2009 10:16:23 GMT
Etag="ComputedETag1"
Expires=Tue, 25 Aug 2009 10:16:23 GMT
Reproduce code:
---------------
<?php
$etag="\"ComputedETag\"";
header("Etag: $etag");
//Expires ages away
header("Expires: " . gmdate("D, d M Y H:i:s", time()
+ 60 * 60 * 24 * 30) . " GMT");
if (isset($_SERVER['HTTP_IF_NONE_MATCH']) &&
$_SERVER['HTTP_IF_NONE_MATCH'] == $etag) {
/* At a users' request, the cache has been bypassed, but the
* document is still the same. Avoid costly response generation
* and waste of bandwidth by just sending not-modified.
* (it is illegal to send a response-body by the HTTP spec).
*/
header('HTTP/1.0 304 Not Modified');
error_log(date('r')." - Response: 304 Not Modified");
exit(); //Don't generate or send the body
}
error_log(date('r')." - Response: 200. Generated document.");
echo "Document body goes here";
?>
------------------------------------------------------------------------
--
Edit this bug report at https://bugs.php.net/bug.php?id=49106&edit=1