Bug #51749 [Com]: header("Location:") changing HTTP status

2010-05-29 Thread theimp at iinet dot net dot au
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:

The patch no-http-status-code-overwrite-on-location-header (last
revision 2010-05-29 20:00 UTC) is a patch to main/SAPI.c from 5.3.2 (04
Mar 2010).



The relevant changes are lines 661 to 665 (661 to 668 in the original),
and 675 to 678 (678 in the original).



I deliberately chose the simplest method I could; it simply checks to
see if the status is currently 200, and if so, then it updates the
status code as before. Otherwise, it does not.



As mentioned, the behavior that is the subject of this bug has been a
non-problem for a very long time. I am more and more inclined to agree
with mike at php dot net that "fixing" it just might not be worth it,
when compared against the possibility of failures related to
backwards-compatibility expectations. But aharvey at php dot net does
have a valid point too.



A method I considered, but rejected as too inefficient (considering the
benefit), involved creating a new variable to store the status when set,
and only write it just before the headers are sent.



Another similar method that I considered, which was more efficient, but
that I rejected for being too dangerous and fault-prone, was making the
default HTTP response value 0 (or any other invalid value) and, again,
updating it to 200 if it was still set to 0 (meaning that it hadn't been
updated) before the headers were about to be sent.



These changes involve much more significant modification for very little
additional benefit. The only case where they would improve the patch
code is if you set the HTTP code to some number, and then set the HTTP
code back to 200, and then sent a Location or similar header; you might
expect it not to change (because you've explicitly set it), but it would
change (because it's currently 200, and that's the only condition that
is checked).



(Also, the code on lines 665-666 of the original is redundant in the
current code because it would always be performed again at 752).



Please note that I am not necessarily saying that I think this patch
should be applied. In fact, after reading the source code, I think that
- if anything - my complaint really should simply be a documentation bug
(ie; that setting a Status header, last, is the preferred method of
setting the response code, and clarifications related to the correct use
of http_response_code).



(Should have read the source code first, I guess. For example, it's now
painfully clear that PHP follows RFC 3875 as closely as possible).



Thank you for your consideration.


Previous Comments:

[2010-05-20 09:17:43] m...@php.net

Heh, you're quite patient either ;)



I think the most SAPI-portable way to set the HTTP status is:



header("Status: NNN", true, NNN);



Still, special headers like Location and WWW-Authenticate might override
it again, so it's best to set the HTTP status at last.



If you want to know more about the "hack", visit sapi_header_op() in
main/SAPI.c



Cheers



PS: patches are always welcome, even if the just cause a discussion
without following changes

------------------------
[2010-05-20 08:51:06] theimp at iinet dot net dot au

> header("HTTP/1.1 XXX") is just a hack



I did not realize this. So does that mean that, per RFC 3875, we're only
supposed to set the Status header and hope that the server does what we
expect?



The documentation specifically lists this "hack" as the correct way to
supply the Status-Line and, therefore, the Response Code. But I'm not
going to argue with you about this.



> I see no hard reason to introduce other hacks to support a hack in the
first place.



Well, that's fair enough.



> You are writing *pages*



I apologize. I tend to use far more words than I have to, in
anticipation of explaining myself poorly otherwise. I will try to be
more concise. Many of the details I felt were essential for
understanding how the fix SHOULD be handled, distinct from my personal
preferences.



> about code that's *years* old and worked that way for a long long
time, and there's very little chance to become that changed.



I understand from this, that you do not want to fix this in the way I
have suggested, which is fair enough; it doesn't seem to bother anyone
else and has trivial workarounds, and its very status as a bug is more
an matter of opinion than fact.



I'm bad at interpreting subtlety, though, and so

Bug #51749 [Com]: header("Location:") changing HTTP status

2010-05-19 Thread theimp at iinet dot net dot au
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:

> header("HTTP/1.1 XXX") is just a hack



I did not realize this. So does that mean that, per RFC 3875, we're only
supposed to set the Status header and hope that the server does what we
expect?



The documentation specifically lists this "hack" as the correct way to
supply the Status-Line and, therefore, the Response Code. But I'm not
going to argue with you about this.



> I see no hard reason to introduce other hacks to support a hack in the
first place.



Well, that's fair enough.



> You are writing *pages*



I apologize. I tend to use far more words than I have to, in
anticipation of explaining myself poorly otherwise. I will try to be
more concise. Many of the details I felt were essential for
understanding how the fix SHOULD be handled, distinct from my personal
preferences.



> about code that's *years* old and worked that way for a long long
time, and there's very little chance to become that changed.



I understand from this, that you do not want to fix this in the way I
have suggested, which is fair enough; it doesn't seem to bother anyone
else and has trivial workarounds, and its very status as a bug is more
an matter of opinion than fact.



I'm bad at interpreting subtlety, though, and so I am not clear if I
should also conclude that:



1. This bug is closed, we've got more important things to fix, and this
is technically not even broken. Stop bothering me!



or:



2. If you want it fixed so bad, then submit a patch yourself and we'll
see if it's not too convoluted to keep.



I am not trying to be offensive, and understand that you weren't,
either.



Thank you for your patience.


Previous Comments:

[2010-05-18 09:47:14] m...@php.net

Maybe, but header("HTTP/1.1 XXX") is just a hack and I see no hard
reason to introduce other hacks to support a hack in the first place.



You are writing *pages* about code that's *years* old and worked that
way for a long long time, and there's very little chance to become that
changed. You know, PHP is an acronym for BC, or was it the other way
round...

----
[2010-05-12 14:19:48] theimp at iinet dot net dot au

@ 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; practica

Bug #51749 [Com]: header("Location:") changing HTTP status

2010-05-12 Thread theimp at iinet dot net dot au
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
alre

Bug #51749 [Opn]: header("Location:") changing HTTP status

2010-05-06 Thread theimp at iinet dot net dot au
Edit report at http://bugs.php.net/bug.php?id=51749&edit=1

 ID:   51749
 User updated 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:

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 bu

[PHP-BUG] Bug #51749 [NEW]: header("Location:") changing HTTP status

2010-05-05 Thread theimp at iinet dot net dot au
From: 
Operating system: Debian Lenny
PHP version:  5.3.2
Package:  HTTP related
Bug Type: Bug
Bug description:header("Location:") changing HTTP status

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 bug report at http://bugs.php.net/bug.php?id=51749&edit=1
-- 
Try a snapshot (PHP 5.2):
http://bugs.php.net/fix.php?id=51749&r=trysnapshot52
Try a snapshot (PHP 5.3):
http://bugs.php.net/fix.php?id=51749&r=trysnapshot53
Try a snapshot (PHP 6.0):
http://bugs.php.net/fix.php?id=51749&r=trysnapshot60
Fixed in SVN:
http://bugs.php.net/fix.php?id=51749&r=fixed
Fixed in SVN and need be documented: 
http://bugs.php.net/fix.php?id=51749&r=needdocs
Fixed in release:
http://bugs.php.net/fix.php?id=51749&r=alreadyfixed
Need backtrace:  
http://bugs.php.net/fix.php?id=51749&r=needtrace
Need Reproduce Script:   
http://bugs.php.net/fix.php?id=51749&r=needscript
Try newer version:   
http://bugs.php.net/fix.php?id=51749&r=oldversion
Not developer issue: 
http://bugs.php.net/fix.php?id=51749&r=support
Expected behavior:   
http://bugs.php.net/fix.php?id=51749&r=notwrong
Not enough info: 
http://bugs.php.net/fix.php?id=51749&r=notenoughinfo
Submitted twice: 
http://bugs.php.net/fix.php?id=51749&r=submittedtwice
register_globals:
http://bugs.php.net/fix.php?id=51749&r=globals
PHP 4 support discontinued:  http://bugs.php.net/fix.php?id=51749&r=php4
Daylight Savings:http://bugs.php.net/fix.php?id=51749&r=dst
IIS Stability:   
http://bugs.php.net/fix.php?id=51749&r=isapi
Install GNU Sed: 
http://bugs.php.net/fix.php?id=51749&r=gnused
Floating point limitations:  
http://bugs.php.net/fix.php?id=51749&r=float
No Zend Extensions:  
http://bugs.php.net/fix.php?id=51749&r=nozend
MySQL Configuration Error:   
http://bugs.php.net/fix.php?id=51749&r=mysqlcfg