image_style_deliver uses module_invoke_all to call hook_file_download, and module_invoke_all use array_merge_recursive to merge the results. This will be a problem if there is more than one hook_file_download returning the same header.
Example.
If two modules provide the header 'Content-Type' => 'image/jpeg', image_style_deliver will get an headers array looking like this.
array(
'Content-Type' => array(
'image/jpeg',
'image/jpeg',
),
)
And the result from that is that drupal_add_http_header will throw a notice (array to string).
I think image_style_deliver should behave like file_download does. So I created a patch to fix this... *phu* I hope anyone can read and understand this ;)
Comments
Comment #1
logaritmisk commentedChanged status, there is a patch to test.
Comment #2
mariancalinro commentedIt works for me.
Comment #3
almc commentedThe patch didn't apply automatically for Drupal 7.26 so I'll try to trigger re-test here, however after I applied it manually in the core image.module, the issue with 'array to string conversion' notices I had with File Entity is gone now. Thank you for the patch.
Comment #4
almc commentedimage_style_deliver_shouldnt_use_module_invoke_all-0.patch queued for re-testing.
Comment #6
logaritmisk commentedRe-roll patch against dev
Comment #7
mariancalinro commentedI will rtbc again, I have this live on some sites, and it's fixing the issue.
Comment #8
David_Rothstein commentedThis would need to go into Drupal 8 first.
It could also use a code comment explaining why it's done this way, and I'm wondering if there are other places in the codebase that invoke hook_file_download() that have the same problem?
Comment #9
thtas commentedThe error that brought me here was:
For future googlers.
Patch #6 solved the problem
Comment #10
joelpittetD7:
TLDR; this is an over explanation of the fact that I should have read the issue summary, it would have saved me time.
I had a private file being rendered as a download in a custom entity.
following hooks were called:
Both returned the same array.
Within module_invoke_all($hook) we are doing a
array_merge_recursiveResults:
Ok all that being said, maybe a test is needed to show the problem.
Comment #11
seanenroute commentedHi, this error is still happening in 7.8. Will the patch be added to core fairly soon, I'm not keen on patching a core module.
Comment #12
kyuubi commentedHi,
I am having the same issue.
Patch fixed it for me.
When can we expect to see this commited?
Thanks,
Comment #13
kyuubi commentedI just noticed this was introduced with the latest core update to 7.31.
I can see from the doing a diff that the code this patch adds was in core but got removed?
Comment #14
berte commentedAlso present in core 7.34.
Patch in #6 worked for me.
Comment #15
pslcbs commentedSame issue on 7.35.
Applied #6 and works great too.
Thanks!
Comment #16
StefanPr commentedI've added a patch for D7.36
Comment #17
Johnny vd Laar commentedComment #18
mpotter commentedMarking this Needs Review to get #16 tested in latest D7. But then I know somebody will just change it to 8.0.x again. But we need this patch in Open Atrium to fix the send_header errors we are seeing.
Comment #19
marcvangendIf we want it fixed in D7, it should at least be fixed in 7.x-dev.
Comment #21
eiriksmOK, let's get this in. I really need this for a handful of sites.
Probably needs tests. So here is a test-only patch and patch with added test.
Comment #24
eiriksmOops. Made this in a repo where drupal was not root folder :)
Comment #25
eiriksmHm, I guess the testbot does not consider notices as errors in tests. Either that or it does not consider "Array to string conversion" as a notice (should be added in php 5.4, right?)
For the record, I checked if this bug exists in d8, it does not.
Not sure how to adjust the test to force it to fail on the testbot, through. image_style_deliver calls drupal_exit, so that does not work. Anyway, leaving as needs review for now.
Comment #27
andrew@oscailte.org commentedThis patch works for me in 7.38
Comment #28
eiriksmIf it works and you think the patch code looks ok, feel free to set the status to "Reviewed & tested by the community" to try to get this commited. :)
Comment #29
mariancalinro commentedAbout the tests, I did a little digging around, and the only idea I found is this.
Maybe that would work for making tests fail, even if it's ugly.
Comment #30
eiriksmHm, really strange that this test-only patch is not failing. Tested it on different platforms too. Trying once more...
Comment #31
mariancalinro commentedThe test will not fail because notices do not trigger fails in tests. For a failing test, you need to intercept the notice, and trigger a fail. To intercept the notice, you can use a custom error handler, and in there you do a string match to see if the error message is "Array to string conversion". If the string matches, you trigger a fail, otherwise at the end of the test you trigger a pass.
You can find a link to an example in #29.
Comment #32
eiriksmOn my computer, across 3 versions of php and in both GUI and CLI versions of running the tests, and across 2 platforms (both on my Mac and in a virtual ubuntu machine) they are always being marked as failing, when there is a notice. According to someone on IRC, this should also be the case for the testbots.
I'll see if I can make it fail in another way though. And worst case, look at that link. Seems a little elaborate, though :)
Comment #33
eiriksmHow about this, then...
Comment #34
eiriksmHm, maybe try to log in the user to make sure it is not a caching issue. And also assert server response. This is so strange...
Comment #36
mariancalinro commentedThe tests in #34 failed because the login failed, $this->admin_user is not existing.
What if we throw an exception in drupal_add_http_header() if the $name is not a string, as the function assumes that the $name argument is a string? That would make the tests a lot easier to write, and would make possibly bad code in contrib easier to identify.
Comment #37
eiriksmYeah, i saw that, went a bit fast there. :)
So, I finally got this to not fail for me too. And throwing an exception would not fix it, because the test just never triggered the notice (hence it was not passing an array as a header value). Hopefully this will get the test failing.
Comment #38
eiriksmTaking another stab in the dark here. Maybe the test image is getting cached somehow, since it re-uses the same file as another test.
Comment #39
eiriksmOK, let's try something else. This will not be the actual test diff, of course, but this makes the test also fail for me. Let's hope the same is the case for the testbot
Comment #40
eiriksmOK, so this is literally obligated to fail :)
Comment #41
eiriksmSeriously? Just uploading this in pure spite, to test the testbot.
Comment #42
eiriksmOK, then I guess all my effort has always been wasted :). The testbot does not recognise a notice at all, so I guess we have to go with an exception? This one is not really a test-only patch, and I am a bit uncertain if it is a good idea to add this (one could consider this a breaking change, in some weird way).
Comment #44
eiriksmOK then, we allow numbers as well, I guess.
Comment #45
eiriksmComment #47
eiriksmAh, so I finally am pretty sure I have figured it out.
On php 5.3, the notice will not be generated, but the string will just become "Array". I was convinced these tests run on 5.4 (since that is what it says on the testbot project page), but I assume it has to be 5.3.
Attached is a patch that fails on both 5.3 and above. So we can finally go back to the simple solution and not throw an exception, probably. Less disruptive this way.
Comment #48
eiriksmWell, thats a lot of back and forth.
Anyway, to summarize:
Last comment, when I thought I had made a test that would fail on php 5.3, I only switched CLI php version, so the web server was still running 5.4. So that was why it was failing locally for me.
So it seems it is impossible to get this to fail on php 5.3. So I'll just reupload the patch from #21. This test would have failed if the test was running on 5.4 >=.
image_style_deliver can end up calling drupal_add_header with an array, but then a couple of lines later, file_transfer also adds the content-type header, thus overriding the array with a string again. So no way to cleanly test this as far as I can see.
Anyway, this patch still fixes the issue (which appearently only is an issue on 5.4 >=), so please test and review.
Comment #49
sam152 commentedThis comment should explain why the headers are discarded not that they ARE discarded.
Does discarding the headers for a -1 result break the access denied later on? -1 will never appear in the array and thus MENU_ACCESS_DENIED will never be returned?
Comment #50
eiriksmThis is a copy-paste from the file_dowload function, which has the exact same code. We throw away the headers, because someone has denied access, and thus no headers should be sent. It does not directly relate to throwing away headers to avoid duplicate headers.
No. When a -1 is encountered, the headers are "emptied" (as explained above), the loop is "stopped", and the next if will check for an empty array of headers, which will in turn return MENU_ACCESS_DENIED. Actually I think we can remove the "if (in_array(-1, $headers)" part, because headers will always be empty when there is a -1 result. I'll just try that here, to see if all tests still pass.
Comment #51
mstrelan commentedCan we RTBC this?
Comment #52
joelpittet@mstrelan if you've reviewed and tested it, then sure you can.
Comment #53
joelpittetThe tests only patch in #47 really should be failing if it's testing the problem we are solving and this is really a bug in D7. So i'd keep it say this needs-work for that.
I'm re-posting those so we can get the new CI testbot to try it out with different versions of PHP.
Comment #54
joelpittetComment #55
eiriksmThis is not failing because the test is run on the old CI with php 5.3 where this does not generate a notice. Do you know when we can start to test d7 on the new testbot? Then this would fail easily and prove the bug.
Comment #56
MixologicI fired up two ad-hoc tests for this issue to test #47 and #50.
The tests only results are (will be) here:
https://dispatcher.drupalci.org/job/DCI_XML/161/
And the patch is here:
https://dispatcher.drupalci.org/job/DCI_XML/162/
Comment #57
stefan.r commentedIn #56 the test-only patch is red with:
and #50 fixes it, so re-uploading that.
Anyone else care to give this a final review?
Comment #58
PatchRanger commentedAlso had this problem, #57 fixed it.
Drupal 7.41, PHP 5.6.14.
Comment #59
David_Rothstein commentedSee my comment in #8... I think all of that still applies?
And related to that, for Drupal 7 couldn't we just call file_download_headers() here rather than duplicating its code? (Drupal 8 doesn't have that function, although probably it should be added as part of this issue so it can be reused in the Drupal 8 patch also.)
Comment #60
eiriksmAs stated in #25 I have tested this on D8 and the bug does not exist there. Granted, this might as well be stated in the IS, it is not so easy to just find randomly in one of the comments.
To be 100% sure, I just tested it again today. But don't take my word for it, here is a test-only patch that should pass on D8. :)
Regarding the other points in #8 and #59:
- We could absolutely comment it better. And use file_download_headers. Definitely. I'll write a new patch with that. (after this d8 patch).
- There is only one other call to module_invoke_all('file_download') in the codebase, and it is also in image.module. But the problem would not apply here, as it does not try to add these headers afterwards. The call is here: https://api.drupal.org/api/drupal/modules!image!image.module/function/im...
Comment #61
stefan.r commentedBack to 7.x
Comment #62
eiriksmFor d7, with more comments and using file_download_headers
Did not do an interdiff since the patch is so small (and the change so apparent) :)
Comment #63
David_Rothstein commentedAh, it looks like Drupal 8 never uses array_merge_recursive() when invoking any hook, that's why.
So since we're calling file_download_headers() I don't think we need this code comment - the reasoning is already documented inside that function and we don't need to explain why we're using the correct API :) (Sorry for the confusion - in my comment from long ago I was probably thinking we needed a comment here because file_download_headers didn't even exist then so calling it wasn't an option.)
Agreed. That one should probably be using file_download_headers() anyway, but isn't needed to fix a bug so doesn't need to be part of this issue.
Comment #64
eiriksmI agree. So here is one with no comments :)
Comment #65
eiriksmComment #66
David_Rothstein commentedYup, that looks good to me, and the patch works as expected.
Comment #68
doitDave commented*Bump*
I would _really_ like to see this issue not becoming legacy with Drupal 7. Can we have the patch in the very next core release pretty soon, please? Really annoying thing if you work with a lot of private file image styles.
Can I assist in accelerating the solution? Please let me know. Thanks!
Comment #69
doitDave commentedaccidentally changed issue state (re-rolling)
Comment #70
David_Rothstein commentedYup, the plan was always to get it into the next release - I just left it for a while in case there were additional reviews.
Committed to 7.x - thanks!
Removing backport tag since it turned out this did not need a Drupal 8 fix. If someone wants to forward-port the tests to Drupal 8 I guess it would be nice to have a followup issue for that - it could be created and then linked back here.
Comment #72
joelpittetThanks @David_Rothstein. I've created a stub issue for those D8 forward port tests #2660564: Forward port tests for image_style_deliver can create invalid headers
Comment #73
doitDave commentedGreat, thanks from here, too!