php.net |  support |  documentation |  report a bug |  advanced search |  search howto |  statistics |  random bug |  login
Bug #70273 header(): Setting the Location header will set a status code of 202 to 302.
Submitted: 2015-08-14 19:45 UTC Modified: 2015-08-20 15:14 UTC
From: jeff at nd4c dot com Assigned:
Status: Not a bug Package: HTTP related
PHP Version: Irrelevant OS: Any
Private report: No CVE-ID: None
View Developer Edit
Welcome! If you don't have a Git account, you can't do anything here.
If you reported this bug, you can edit this bug over here.
(description)
Block user comment
Status: Assign to:
Package:
Bug Type:
Summary:
From: jeff at nd4c dot com
New email:
PHP Version: OS:

 

 [2015-08-14 19:45 UTC] jeff at nd4c dot com
Description:
------------
Setting the Location header will set a status code of 202 to 302.

From the online documentation:
(http://php.net/manual/en/function.header.php)
...
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.
...

The auto redirect ignores status codes: 201 and 3xx, but it SHOULD also ignore status code: 202.  Using the Location header for a 202 response is now the standard for a RESTful api.

According to: 
http://restcookbook.com/Resources/asynchroneous-operations/
...you can then issue at 202 (Accepted) response code. This informs the client that the request has been accepted and understood by the server, but the resource is not (yet) created. Send the temporary resource inside the Location header.

Request:
POST /blogs HTTP/1.1
<xml>
    blogdata
</xml>

Response:
HTTP/1.1 202 Accepted
Location: /queue/12345
...



Test script:
---------------
header("HTTP/1.1 202 Accepted");
header('Location: /some/arbitrary/uri');

// This will change the status code to 302, but it shouldn't

header('Location: /some/arbitrary/uri');
header("HTTP/1.1 202 Accepted", true, 202);

// This works, always forcing the status code prevents the defaults redirect handling.



Expected result:
----------------
Status code 202 should remain as 202 after a Location header is set.


Patches

Pull Requests

History

AllCommentsChangesGit/SVN commitsRelated reports
 [2015-08-14 21:42 UTC] cmb@php.net
RFC 7231, section 7.1.2 states[1]:

| For 201 (Created) responses, the Location value refers to the
| primary resource created by the request. For 3xx (Redirection)
| responses, the Location value refers to the preferred target
| resource for automatically redirecting the request.

No mention of 202 responses in this section.

IMHO, this is not a bug.

[1] <http://tools.ietf.org/html/rfc7231#section-7.1.2>
 [2015-08-14 22:23 UTC] jeff at nd4c dot com
Agreed.  After some more research, it appears that the use of the Location header for a 202 response is non-standard. There is nothing in any RFCs that I could find on the subject that would suggest using a Location header for a 202 response. On the other hand, none of them, in any way, suggest that it should not be used. There are several forum replies and the "http://restcookbook.com/" recommends it, but I don't at this time think this is a bug. Sorry for any time wasted or headaches about this.
 [2015-08-14 23:32 UTC] requinix@php.net
Actually, I think this is alright.

RFC 2616 says <http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.2.3>
> The entity returned with this response SHOULD include an indication of the
> request's current status and either a pointer to a status monitor or some
> estimate of when the user can expect the request to be fulfilled.
I could reasonably interpret "pointer to a status monitor" to include a Location header.

But what do the major browsers do? Do they follow the redirect?
 [2015-08-17 16:35 UTC] jeff at nd4c dot com
After some more thought and research, I've again changed my mind.  I do believe that there is a real bug with header() function.  But the bug description is now different...

header(): Setting the Location header will set the status code to 302 even if the status code has been passed as the third parameter with any previous header call.

Test scripts:

// This format would only 
// require knowing if we want to force the status code
// Passing status code 202 as a "forced" status code
header("HTTP/1.1 202 Accepted", true, 202);

// This will still set the status code to 302,
// even though the 202 was forced.
header('Location: /some/arbitrary/uri');


// This format would require knowing both:
// if we want to force the status code AND
// also knowing when we are passing a header
// that might change the status code
// Passing status code 202 as a "forced" status code
header("HTTP/1.1 202 Accepted", true, 202);

// This will work as expected with a forced 202 status code.
header('Location: /some/arbitrary/uri', false, 202);


// Current solution: To force the status code as the last header.

// This format would only require
// knowing if we want to force the status code
// This will still set the status code to 302
header('Location: /some/arbitrary/uri');

// Passing status code 202 as a "forced" status code as the last header
header("HTTP/1.1 202 Accepted", true, 202);

I believe that since settings header should be allowed in any order since the RFC on headers does not set any requirements on the order of setting headers.

I also believe that passing in a Status Code as a forced status code, it should remain 'forced' and not be overwritten by any following header calls unless a status code is passed in as a new forced header.

I also question the benefit of having a Location header automatically setting the status code to a 302.  The setting of a Location header would always be done in a context where the program would know whether or not to set the status code.  There is no need or even desire for a 'magic' side effect.  These are typically frowned upon!!

So, at this point, I now consider this a bug.

I hope that helps.
 [2015-08-18 12:10 UTC] cmb@php.net
According to bug #51749 this ticket is not a bug either (or maybe
"won't fix").
 [2015-08-18 16:07 UTC] jeff at nd4c dot com
It's definitely a bug. Whether it will be fixed or not is another question.

1. https://bugs.php.net/bug.php?id=25044 was fixed, but IMO not correctly.  The proper fix would have been to remove the 'magic' side-effect of setting a Location header. 

Quote from bug report 25044:
It may even be appropriate to not change the status for any value other than 200 (the default).  As a result, if a user explicitly sets the status to something other than 200, then setting the Location header should not change that value again (even if the combination of the status and header do not make sense).  The reasoning here is that HTTP status codes are extensible, meaning that new specifications can add new status codes that may use the Location header.  If only testing for the above list, new codes would be overlooked and therefore automatically changed.

2. https://bugs.php.net/bug.php?id=51749 turned into a flame war with the developers - obviously it was rejected as not a bug. ;-)  The developer stated the following:
 [2010-05-18 09:47 UTC] mike@php.net
...
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...

I find that a little disheartening.  I'm guessing that quote's been pasted up in many closed source companies' boardrooms! ;-)

I have not found any documentation, RFC or the like to infer that whenever a Location header is set it MUST have a status of 302.  I have not found anything to suggest that whenever a Location header is set it SHOULD have a status of 302.  It definitely COULD, but it COULD also have ANY OTHER status code. There is no documentation to state otherwise.  It's true that a status code of 302 SHOULD (or even MUST) have a Location header, but that does not logically infer the reverse.

So why does the PHP header function force a 302 as a 'magic' side-effect?
 [2015-08-19 10:34 UTC] ab@php.net
-Status: Open +Status: Not a bug
 [2015-08-19 10:34 UTC] ab@php.net
@jeff, 

http://www.ietf.org/rfc/rfc2119.txt 

"3. SHOULD   This word, or the adjective "RECOMMENDED", mean that there
   may exist valid reasons in particular circumstances to ignore a
   particular item, but the full implications must be understood and
   carefully weighed before choosing a different course."

Taking in account also the to paragraphs quoted by @cmb and @requinix

- some status should be set, in the PHP implementation it's set 302 by default. In PHP implementation the "particular item" is not ignored
- there is no single definition about status 202 and Location header combination
- the status can be overridden in PHP implementation, which gives the full freedom to set it to whatever a programmer wants

Further - the REST specification has chosen to set the status 202+Location under some circumstances. That is perfectly valid with the HTTP specification too, because the Location header is not bound to any specific status. 

However where your position is not valid - the PHP implementation is a generic one, it is not dedicated specifically to REST, AJAX, anything else. But, the PHP implementation makes possible to implement whatever a programmer needs. It were valid if you were filing a ticket about a REST specific implementation, maybe. Or if there were no possibility to override the status. Given this and also with the regard to the backward compatibility it doesn't look like a bug. There are many REST frameworks which could be probably more of use for you, if you don't want to implement it yourself.

Thanks.
 [2015-08-19 15:04 UTC] jeff at nd4c dot com
@cmb


7.1.2.  Location

   The "Location" header field is used in some responses to refer to a
   specific resource in relation to the response.  The type of
   relationship is defined by the combination of request method and
   status code semantics.

Neither is there mention of 302.  The references to 201 and 3xx are explaining how the Location header would typically be used IF the 201 and 3xx status codes are used.

Also, to be clear, 3xx does NOT mean 302 specifically.  So why force the status code to 302?  Where does it say that?

I repeat:

I have not found any documentation, RFC or the like to infer that whenever a Location header is set it MUST have a status of 302.  I have not found anything to suggest that whenever a Location header is set it SHOULD have a status of 302.  It definitely COULD, but it COULD also have ANY OTHER status code. There is no documentation to state otherwise.  It's true that a status code of 302 SHOULD (or even MUST) have a Location header, but that does not logically infer the reverse.

So why does the PHP header function force a 302 as a 'magic' side-effect?
 [2015-08-19 15:21 UTC] jeff at nd4c dot com
@ab

I fully agree with your arguments, but not with your deduction.

The reference to:

http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.2.3

Does not state anywhere that a Location header MUST NOT be used.  It also states, as requinix pointed out, that it SHOULD have some indication of its current status and a pointer (Location?) to a status monitor.

http://tools.ietf.org/html/rfc7231#section-7.1.2

This description of the Location header does not even insinuate that only a 302 status code must be used.  It clearly indicates that the Location header is used for several different status codes.  There is also nothing to suggest that, as you said, "some status should be set" - it does not say that anywhere!

Your statement, "there is no single definition about status 202" is mute.  This is about the Location header and any status code.  The 202 was only used a valid and current example of usage.

Your statement, "the status can be overridden in PHP implementation, which gives the full freedom to set it to whatever a programmer wants" is flawed.  It would be more accurate if written: "the status HAS TO BE overridden in PHP implementation, which REMOVES the full freedom to set it to whatever a programmer wants"

The PHP implementation is definitely NOT a generic one.  It is dedicated to forcing a 302 status code for a Location header, contradicting all current RFC and similar documentation.  A generic one would simply let the programmer decide.

https://en.wikipedia.org/wiki/Software_bug

Wikipedia states: "A software bug is an error, flaw, failure, or fault in a computer program or system that causes it to produce an incorrect or unexpected result, or to behave in unintended ways."

The current implementation of the header() function produces an incorrect and unexpected result when a Location header is set.  It's definitely a bug.
 [2015-08-20 13:54 UTC] ab@php.net
@jeff, thanks for the follow up.

It's hard to see how the PHP implementation doesn't comply with the HTTP spec. The PHP implementation is focused on the generic use case serving HTTP redirect. At the same time

- it doesn't prevent using any other status
- changing it to set no HTTP status is an incommensurable BC break to the gain it would bring for the async operations in REST

Thanks.
 [2015-08-20 15:14 UTC] jeff at nd4c dot com
@ab

As far as the "generic use case serving HTTP redirect", I'm not fully convinced that setting the Location header signifies a generic use case redirect.  It's very clear in the documentation (expressed in previous posts) that the Location header is used for many distinct cases that do not involve a redirect.  Being that 302 is the most likely I admit that it's close to a generic use case, so I guess it's just my dislike for micro-management.  Surprising since I am a programmer.

However, in the interests of BC and because there is a sufficiently simple workaround, I would agree that this is best left alone.

Sorry, for the bother.  I will now have to concentrate on a framework 'bug-fix' to make sure the workaround is available and that there are no incorrect assumptions on how the header() function handles Location headers.
 
PHP Copyright © 2001-2024 The PHP Group
All rights reserved.
Last updated: Sat Dec 21 14:01:32 2024 UTC