-
-
Notifications
You must be signed in to change notification settings - Fork 3.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
JApplicationWeb::setHeader/sendHeaders behavior #9836
Conversation
setHeader $replace previously did nothing other than delete a value - even when false it allowed a duplicate to be created which effectively replaces the original. This modification searches for an existing header with the same name and only inserts it if it doesn't already exist, or if $replace is true.
you need to fix travis CI errors |
Fix Travis CI error regarding invalid foreach
Reading the JApplicationWebTest - it's actually testing for the issue this PR is meant to resolve. The failure is on line 1321 of the test, which is within testSetHeader:
Notice, the 2nd time the header is set, a duplicate header (foo) exists. The way headers are set, by name, means that only the 2nd header will be added to the resulting output - effectively replacing the original entry without using $replace=true! This is a bad test, it was written to support an error. The second setHeader should fail because a duplicate name exists without $replace=true. |
@mbabker can you help here? |
Anyway, you also have a lot of code style errors. |
Formatting changes
Formatting issues
OK, code style errors resolved (including one that was present in the existing code) |
ok. seems fine. BTW i think the unit test failing is this: |
That's what I said earlier...
In JApplicationWeb::header, when headers are set - they're set with $replace=true 4 out of 5 times (the default) with the exception of the status header. So it becomes even more important that $replace be enforced in setHeader, as the only other place it gets enforced isn't ever being used beyond the status code. JApplicationWebTest needs repair before this issue can be resolved. |
working on that now....Do I reference the commit here? These must be fixed in correct order. |
in line 1314 ��"$this->class->setHeader('foo', 'car');", the method is called without the $replace argument, meaning it uses the default of "false". The way headers are set, allowing a duplicate here means the header is replaced in JApplicationWeb::header. If $replace isn't enforced in setHeader then duplicates mean the header gets replaced anyway. The attempted entry is removed from the assertEquals test. This change is in conjunction with a JApplicationWeb PR I have waiting joomla#9836
just directly edit the file in this link https://github.com/stutteringp0et/joomla-cms/blob/patch-1/tests/unit/suites/libraries/joomla/application/JApplicationWebTest.php#L1299 If you edit in this link it will automatically changed in this PR |
no, don't do another PR for that |
oops...sorry |
|
no problem just close the other PR |
in line 1314 ��"$this->class->setHeader('foo', 'car');", the method is called without the $replace argument, meaning it uses the default of "false". The way headers are set, allowing a duplicate here means the header is replaced in JApplicationWeb::header. If $replace isn't enforced in setHeader then duplicates mean the header gets replaced anyway. The attempted entry is removed from the assertEquals test.
OK, fixed it |
i have some questions regarding this.
|
Line 955 of JApplicationWeb defines the header() method, which defaults to $replace=true. The only time the header method is used where $replace is altered (set to null - line 691) is for the status header, but header() only responds to FALSE, so null doesn't do anything on line 691. So any duplicated header gets replaced anyway. |
Sorry, yes you're right about the test. Install and run the plugin and check the Expires header. Alter JApplicationWeb and check the header again. |
So the test can be just add to protostar index.php ... JFactory::getApplication()->setHeader('Test', 'aaa');
JFactory::getApplication()->setHeader('Test', 'bbb');
JFactory::getApplication()->setHeader('Test 2', 'ccc');
JFactory::getApplication()->setHeader('Test 2', 'ddd', true); Result: Before patch
Affter patch
For what i understand, should be
I'm not 100% sure it should be like this tought. |
@brianteeman please remove the "Unit/System Tests" label since now it doesn't make changes in the unit tests |
@andrepereiradasilva yes, I updated the instructions in the initial post. |
@stutteringp0et one thing how do i unset an header that has already been setted before. Is there a way? |
for instance something like this doesn't work, right? $app->setHeader('Test-Header', null, true); |
To more clear this is waht i'm trying to do to make tests easier $app = JFactory::getApplication();
$app->allowCache(true); // if not set, the Expires header cannot be changed from a 2005 date
$testResults = array();
$datePlusOneHour = gmdate('D, d M Y H:i:s', time() + 3600) . ' GMT';
$datePlusTenMinutes = gmdate('D, d M Y H:i:s', time() + 600) . ' GMT';
// Do tests
$app->setHeader('Expires', $datePlusOneHour, true); // 1 hour
$app->setHeader('Expires', $datePlusTenMinutes); // 10 minutes
$testResults['test1'] = $app->getHeaders();
$app->setHeader('Expires', null, true); // reset
$app->setHeader('Expires', $datePlusOneHour); // 1 hour
$app->setHeader('Expires', $datePlusTenMinutes, true); // 10 minutes
$testResults['test2'] = $app->getHeaders();
$app->setHeader('Expires', null, true); // reset
$app->setHeader('Expires', $datePlusOneHour); // 1 hour
$app->setHeader('Expires', $datePlusTenMinutes); // 10 minutes
$testResults['test3'] = $app->getHeaders();
$app->setHeader('Expires', null, true); // reset
$app->setHeader('Test-Header', 'aaa');
$app->setHeader('Test-Header', 'bbb');
$app->setHeader('Test-Header', 'ccc');
$testResults['test4'] = $app->getHeaders();
$app->setHeader('Test-Header', null, true); // reset
$app->setHeader('Test-Header', 'aaa');
$app->setHeader('Test-Header', 'bbb', true);
$app->setHeader('Test-Header', 'ccc');
$testResults['test5'] = $app->getHeaders();
$app->setHeader('Test-Header', null, true); // reset
$app->setHeader('Test-Header', 'aaa');
$app->setHeader('Test-Header', 'bbb');
$app->setHeader('Test-Header', 'ccc', true);
$testResults['test6'] = $app->getHeaders();
$app->setHeader('Test-Header', null, true); // reset
// Show test results
print_r($testResults); |
I don't believe there ever was a facility to remove a header once set - they could only be added or replaced. It's not hard to accomplish, and I'm not opposed to adding it if you think it's necessary - but it isn't something that's being done now. Edit: just tested pre-patch and setHeader('name',null,true) just creates an empty header (header with no value), so the method never removed on a null value. I can make that happen if you think it's necessary. |
Remove on null was really easy to accomplish, and I've got that ready to add if you think it's necessary. |
actually i think it would be a good feature allowing to remove an already setted header. If you could do it, it would be great. |
Adding ability to unset (remove) an existing header using $replace=true and $value=null setHeader('name', null, true) now removes the header entirely, where previous behavior would have sent the header with an empty value.
Done, waiting on Travis |
DARN YOU FORMATTING ERRORS - DARN YOU ALL TO HECK! |
formatting fix
OK, it's done now, passed tests and your test now works as expected on my test server |
ok will test. |
this is my result Before patchServer date: 17:34:30 GMT Array
(
[test1] => Array
(
[0] => Array
(
[name] => Expires
[value] => Thu, 14 Apr 2016 18:34:30 GMT
)
[1] => Array
(
[name] => Expires
[value] => Thu, 14 Apr 2016 17:44:30 GMT
)
)
[test2] => Array
(
[0] => Array
(
[name] => Expires
[value] => Thu, 14 Apr 2016 17:44:30 GMT
)
)
[test3] => Array
(
[0] => Array
(
[name] => Expires
[value] =>
)
[1] => Array
(
[name] => Expires
[value] => Thu, 14 Apr 2016 18:34:30 GMT
)
[2] => Array
(
[name] => Expires
[value] => Thu, 14 Apr 2016 17:44:30 GMT
)
)
[test4] => Array
(
[0] => Array
(
[name] => Expires
[value] =>
)
[1] => Array
(
[name] => Test-Header
[value] => aaa
)
[2] => Array
(
[name] => Test-Header
[value] => bbb
)
[3] => Array
(
[name] => Test-Header
[value] => ccc
)
)
[test5] => Array
(
[0] => Array
(
[name] => Expires
[value] =>
)
[1] => Array
(
[name] => Test-Header
[value] => bbb
)
[2] => Array
(
[name] => Test-Header
[value] => ccc
)
)
[test6] => Array
(
[0] => Array
(
[name] => Expires
[value] =>
)
[1] => Array
(
[name] => Test-Header
[value] => ccc
)
)
) After patchServer date: 17:37:40 GMT Array
(
[test1] => Array
(
[0] => Array
(
[name] => Expires
[value] => Thu, 14 Apr 2016 18:37:40 GMT
)
)
[test2] => Array
(
[0] => Array
(
[name] => Expires
[value] => Thu, 14 Apr 2016 17:47:40 GMT
)
)
[test3] => Array
(
[0] => Array
(
[name] => Expires
[value] => Thu, 14 Apr 2016 18:37:40 GMT
)
)
[test4] => Array
(
[0] => Array
(
[name] => Test-Header
[value] => aaa
)
[1] => Array
(
[name] => Test-Header
[value] => bbb
)
[2] => Array
(
[name] => Test-Header
[value] => ccc
)
)
[test5] => Array
(
[0] => Array
(
[name] => Test-Header
[value] => bbb
)
[1] => Array
(
[name] => Test-Header
[value] => ccc
)
)
[test6] => Array
(
[0] => Array
(
[name] => Test-Header
[value] => ccc
)
)
) |
That looks right to me |
I have tested this item ✅ successfully on 3ee123a This comment was created with the J!Tracker Application at issues.joomla.org/joomla-cms/9836. |
This needs another human test to be completed, correct? |
yes |
What can I do to get someone else to test this? |
@stutteringp0et I am going to see if I can find some time to test this. This comment was created with the J!Tracker Application at issues.joomla.org/tracker/joomla-cms/9836. |
Thank you @roland-d |
I have tested this item ✅ successfully on 3ee123a
All tests are successful. This comment was created with the J!Tracker Application at issues.joomla.org/tracker/joomla-cms/9836. |
Set to RTC as we have 2 successful tests This comment was created with the J!Tracker Application at issues.joomla.org/tracker/joomla-cms/9836. |
Pull Request for Issue #9819 .
Summary of Changes
setHeader $replace previously did nothing other than delete a value - even when false it allowed a duplicate to be created which effectively replaces the original. $replace didn't matter, you could have $replace=false and the item would still be replaced when the headers were sent.
This backwards-compatible modification repairs issues with setHeader and sendHeaders to allow for multiple values to be sent for applicable headers per RFC 7230-7, and to appropriately handle header replacements where the original created duplicates which became replacements later.
When a header can contain only a single value:
setHeader($name,$value) will insert when no previous value is set
setHeader($name,$value,true) will replace, removing the previous value
When a header can contain multiple values:
setHeader($name,$value) will insert
setHeader($name,$value,true) will replace, removing the previous value(s)
If multiple values are present for a header name when sendHeaders is called, the multiple values are imploded on ', '.
Testing Instructions
I'm starting this off with an $app variable to bring my test line lengths down to something that won't wrap in this description.
$app = JFactory::getApplication();
First test shows that setting a duplicate single value header like Expires without $replace does not replace the value as was the previous behavior.
Result: Expires 1 hour future from the Date header.
(previous behavior would have the 15 minute future header, and this is the reason I made this change - because JApplicationWeb::respond sets it to now+900 seconds without $replace=true, replacing any previous value)
Now, let's reverse the $replace value
Result: Expires 10 minutes future from the Date header.
(previous behavior would have the 15 minute future header, and this is the reason I made this change - because JApplicationWeb::respond sets it to now+900 seconds without $replace=true, replacing any previous value)
Next we repeat the last test with no $replace value
Result: Expires 1 hour future from the Date header.
(previous behavior would have the 15 minute future header, and this is the reason I made this change - because JApplicationWeb::respond sets it to now+900 seconds without $replace=true, replacing any previous value)
Our next tests relate to a made-up header. All headers not in the single value list are considered to allow multiple values
Result: Test-Header value is "aaa, bbb, ccc"
(previous behavior would have a value of "ccc")
Next test issues $replace = true in the middle, losing one of the values
Result: Test-Header value is "bbb, ccc"
(previous behavior would have a value of "ccc")
Next test issues $replace = true on the last value, losing the other entries
Result: Test-Header value is "ccc"
(previous behavior would have a value of "ccc")