Edit report at http://bugs.php.net/bug.php?id=51749&edit=1
ID: 51749 Comment by: theimp at iinet dot net dot au Reported by: theimp at iinet dot net dot au Summary: header("Location:") changing HTTP status Status: Open Type: Bug Package: HTTP related Operating System: Debian Lenny PHP Version: 5.3.2 New Comment: @ mike at php dot net I did more testing, and yes, if you use the additional parameters on the occasion that you send the location header, the special handling of the Location header does not override the explicit behavior. So: header("HTTP/1.1 503 Service Unavailable", true, 503); header("Location: http://www.php.net/"); Does not work; but: header("HTTP/1.1 503 Service Unavailable"); header("Location: http://www.php.net/", true, 503); Does work. Obvious, in hindsight. But very confusing at first. The documentation for http_response_code simply says: "Forces the HTTP response code to the specified value"; I read that as forcing the response code irrespective of any other later action other than another http_response_code. It's not quite a documentation bug, since it's not strictly wrong, but it should probably be changed, because it is easy to read other than as intended. I would accept changing this bug to a documentation bug. @ aharvey at php dot net The functionality I expected exists; I simply did not understand it. But I do agree with what you say also; it is questionable whether it should behave the way that it does even aside from other functionality. To really know how this should be treated requires, I think, consideration of the points I have previously mentioned. Presumably, this specific behavior was put into PHP for a reason, and it was not changed much when the opportunity last arose. I do not know the specific goals of the PHP project in this respect. I would not have written this bug report if I had realized the correct way to use the http_response_code parameter. Also, the workaround mentioned in bug #25044 is still possible. Finally, I had not properly considered RFC 3875 when I first created this bug report. If I had, I would probably have decided that the behavior is deliberate and was not expected to be fixed. The http_response_code is confusing, since it can be set on unrelated headers, making it difficult to track down the code that sets it since it could be a place other than where you set the Response Line itself (in principle, any header; practically, any Location header in addition to the Response Line header). Also, the HTTP Response Code that you want to set must be known at the time you want to set the Location header, which might not be known at that time. It might have already been set in another function; there is no way to retrieve the response code that you have set, so you have to remember it yourself by assigning it to a variable and re-using that variable at the time you set the Location header. For example: ... if ($_SERVER["HTTPS"] != "on") { setstatus(426); setlocation("https"); } ... function setstatus($status) { switch ($status) { ... case 426: header("HTTP/1.1 426 Upgrade Required"); break; ... } } ... function setlocation($scheme) { switch ($scheme) { ... case "https": header('Location: $scheme://$url'); break; ... } } A better solution may have been, rather than to add the http_response_code parameter, to have added a force_response() function to optionally set the HTTP Response Code, which would not be overwritten. But we are long past the point that it makes sense to add such a function; it would add no new functionality and would deprecate existing uses of unrelated code. While I do think that PHP should not set the Response Code after a Location header, there are still reasons to consider this behavior appropriate (standards compliance and backwards-compatibility), which I have already discussed at length. On the other hand; fixing this "bug" in a way similar to the one I suggested would make PHP more robust and make it much more likely that people get the behavior that they expect after they read all of the relevant documentation. It may also help with bug-finding in the case of incorrect header output, and simplify some functions, depending on how they have been designed. Thank you all for taking the time to consider this bug. Previous Comments: ------------------------------------------------------------------------ [2010-05-12 09:49:04] ahar...@php.net That's not actually the point of the bug: the point is that if you've already changed the HTTP status code, you probably don't want a Location header silently changing it later to a 302. I do think the bug's a valid one, FWIW. ------------------------------------------------------------------------ [2010-05-12 09:46:56] m...@php.net header("Location: http://www.example.com/", true, 503) works fine here. ------------------------------------------------------------------------ [2010-05-07 02:14:50] theimp at iinet dot net dot au Just to ensure that any solution is properly considered, I have noticed a counterpoint to my original argument, and likely the reason for the specific current behavior. RFC 3875 (The Common Gateway Interface (CGI) Version 1.1) says that "[If the script returns a Location header field for a Client Redirect Response] The Status header field MUST be supplied and MUST contain a status value of 302 'Found', or it MAY contain an extension-code, that is, another valid status code that means client redirection." Critical in deciding the scope of this requirement is understanding what is a "valid status code that means client redirection". Though it could be assumed that it would be any HTTP 3xx code (the wording "extension-code" would normally mean exactly that), the spec is not unambiguous and the 201 response already proves to be an exception. It's not just web browsers with interactive interfaces that use HTTP; knowing what to do next can be useful for any failure code, and as 201 proves, even some success codes. (Although RFC 3875 is "Informational" by comparison to HTTP, etc., and attempts at standardizing CGI any further have been stagnant, I would nevertheless agree that compliance with RFC 3875 is very important where possible, even above what I consider to be correct handling of the bug that was supplied). On the other hand, RFC 3875 also mentions related behaviors that are not followed as specified. For example, PHP does not unset script-set headers (whether set before or after the Location header) if the script finishes without outputting a message body, as required. Nor does it implement a mechanism for a Local Redirect Response at all (because it doesn't make a lot of sense, given the misconception of the roles "server" and "script" that RFC 3875 has). Actually, much of considering the behavior of PHP with respect to RFC 3875 also requires deciding if php is the "script" or the "server". Though it seems obvious that PHP is the script per the definitions of RFC 3875, large chunks of functionality that are the direct responsibility of the "server" according to RFC 3875 are performed, and can only realistically be performed, by PHP. Now that I look, I can find practically no documentation on PHP regarding which of the two (if not both) PHP considers itself to be; or in fact much of anything related to RFC 3875, including how PHP interprets it or indeed even if it agrees to follow it. To be honest, I would favor a standards-compliant response, if at all sensible, even if it was onerous. On the other hand, as someone who has tried very hard to comply with this and other standards, I find RFC 3875 to be maddeningly deficient, primarily by its confusion between what is the responsibility (and thus functionality) of the script, and what is the responsibility of the server. Ultimately, I would conclude that the original bug as I reported it should be fixed in the way that I suggested, on the grounds that the *intent* of RFC 3875 is being followed. I arrive at this assertion by virtue of the fact that the intent of the Location header according to Section 6.2 is clearly to ensure that the script author is obeyed and thus the client actually gets redirected when they should; to prevent a scenario where the script author intends to redirect the client and sends no message body to this effect, but interaction with the server or other software results in a non-redirect response being generated and the user being unable to understand the result. It is far more important to obey the intent of the script author when they have taken the care to consider and provide the correct response code for their script, as by all accounts the response code is the single most important piece of information returned and, when explicitly set, the intent of the script author cannot be realized if this cannot be reliably communicated. Also, I think that permitting the Location header other than for 3xx codes reflects that, as per the HTML and related specifications, it is not necessarily required or even desired for the client (web browser or otherwise) to obey a Location header and follow it without user confirmation (despite common practice today); that the client will always follow a Location directive seems to be a key assumption of Section of 6.2/6.3. Sorry for the essay; I'll summarize: I think this is a bug. Others (including the PHP programmers) may not. Whether it is or is not a bug is a matter of opinion, probably according to interpretation of RFC 3875. As a resolution to this bug I propose that the PHP engine not change an explicitly set response code when a Location header is set. This may violate the letter of RFC 3875 (but then again, it may not, as interpretation is ambiguous; and full compliance may be impossible in any case). For (imperfect) backwards compatibility, PHP could change the response code to "302 Found" only if it has not yet been changed (ie; is only implicitly set to "200 OK"). This bug is probably not a security issue. This bug is probably of low importance. ------------------------------------------------------------------------ [2010-05-05 16:12:19] theimp at iinet dot net dot au Description: ------------ Please see bug #25044 (http://bugs.php.net/bug.php?id=25044), where this issue has previously been addressed to some extent. When this previous bug was fixed, the fix simply involved adding the exact mentioned codes to an exception list for status response codes that are not overwritten upon sending a Location header. Now, 201, 301, 303, 305, and 307 do not overwrite the Response code. Nevertheless, all others still do. A more permanent fix would be not setting the status for ANY response code (very similar to the actual fix originally suggested for #25044). For backwards compatibility, you could set the response code if it has not already been set at the time that the Location header is set; but it should never be overwritten if it already has been set. HTTP Responses 503 and 426 come immediately to mind as additional reasonable cases for adding a Location header; but in fact, neither RFC 1945 (HTTP/1.0), RFC 2616 (HTTP/1.1), RFC 2817 (Upgrading to TLS Within HTTP/1.1), nor any other IETF or other relevant standard limits the Location header to any particular response, other than to recommend ("SHOULD") it for 301, 302, 303, 305 (could be read as "MUST"), 307, and suggest it for 201 (and "intentionally undefined" by RFC 4918 (HTTP Extensions for Web Distributed Authoring and Versioning (WebDAV)) for 207). So preventing any status code from having a Location header is undesirable (however silly it may be for some certain responses). This would future-proof the code in question against any future changes that do not involve a mandatory or forbidden Location: field (for which the current code would most likely require patching anyway). (To be fair, this is documented behavior, even if it is non-standards-compliant. Mind you, the documentation is contradictory; http_response_code apparently "Forces the HTTP response code to the specified value.", but at the same time, "The second special case is the "Location:" header. Not only does it send this header back to the browser, but it also returns a REDIRECT (302) status code to the browser unless the 201 or a 3xx status code has already been set". It is also true that most current clients will ignore a Location header for most non-3xx responses, but that is unimportant.) I would not consider this issue to have particular security concerns. Test script: --------------- header("HTTP/1.1 503 Service Unavailable"); header("Location: http://www.php.net/"); Expected result: ---------------- HTTP Response: HTTP/1.1 503 Service Unavailable Location: http://www.php.net/ Actual result: -------------- HTTP Response: HTTP/1.1 302 Found Location: http://www.php.net/ ------------------------------------------------------------------------ -- Edit this bug report at http://bugs.php.net/bug.php?id=51749&edit=1