Problem/Motivation
This is rather new: we see increasing website exceptions created by simple anonymous GET calls.
Everything in this Drupal install (and a testing web I used to reproduce) is up to date (Drupal 8.6.10). None of both uses any module with REST or json functionality.
See original issue summary for more.
This is still reproducible in the latest versions of Drupal (10.1.x, currently).
Steps to reproduce
- Install a fresh Drupal site.
- Create a new node.
- Navigate to the node view.
- Add the query parameter
?_format=hal_jsonto the node url (e.g.,{base_url}/node/1?_format=hal_json). - Observe
406 Not Acceptableresponse with (plaintext) messageThe website encountered an unexpected error. Please try again later.The website encountered an unexpected error. Please try again later.<br />
Proposed resolution
Handle all 4xx errors that aren't caught in other exception subscribers and provide a more useful message so we don't fall back on the "The website encountered an unexpected error." message.
Remaining tasks
Review and commit!
User interface changes
None.
API changes
None.
Data model changes
None.
Release notes snippet
None.
Original issue summary
This is rather new: we see increasing website exceptions created by simple anonymous GET calls.
Everything in this Drupal install (and a testing web I used to reproduce) is up to date (Drupal 8.6.10). None of both uses any module with REST or json functionality.
All one gets is if trying a GET like that in a Browser is: "The website encountered an unexpected error. Please try again later. "
In the apache logs this is just a 404, if trying to visit the related watchdog detail, another error is thrown.
I hope, it's OK if I post here, how to reproduce:
wget 'http://your-testing.tld/node/1?_format=hal_json'
Formats that don't even exist should be ignored.
Formats that aren't in use at all (module not installed) probably should be ignored, too.
| Comment | File | Size | Author |
|---|---|---|---|
| #72 | interdiff-70-72.txt | 1.41 KB | wells |
| #72 | 3035589-72.patch | 4.03 KB | wells |
| #62 | 3035589-62.patch | 4.03 KB | paulocs |
| #62 | interdiff-60-62.txt | 652 bytes | paulocs |
Comments
Comment #2
indigoxela commentedComment #3
indigoxela commentedUPDATE: if the node/[ID] exists, the outcome is slightly different: "406 Not Acceptable" in Browser/wget
Symfony\Component\HttpKernel\Exception\NotAcceptableHttpException: Not acceptable format: hal_json in Drupal\Core\EventSubscriber\RenderArrayNonHtmlSubscriber->onRespond() (line 30 of ...core/lib/Drupal/Core/EventSubscriber/RenderArrayNonHtmlSubscriber.php).But still an exception if trying to see the watchdog detail. Above is the title of the watchdog entry link.
Comment #4
indigoxela commentedSome more digging: it really seems, 2827766 is related, at least partly.
And I see two base problems, which seem to be unrelated to each other:
1) using query string "?_format=" always causes "406 Not Acceptable", which is obviously wrong. "?_format=1234" for instance should be ignored instead of throwing "No route found for the specified format 1234".
2) a wrong referer string should never cause watchdog to throw an exception. If causing a 404 (hence put into the dblog) with a wrong referer string, watchdog produces an exception, when viewing that detail page. Uncaught PHP Exception InvalidArgumentException: "The URI 'notreally.tld' is invalid." (Referers aren't anything reliable out in the web.)
Comment #5
indigoxela commentedThe second problem described in #4 (watchdog exception on detail page with invalid urls) is already covered by another issue Refactor how dblog module is rendering links in event details.
The first problem described in #4 ("406 Not Acceptable" on nonsense GET param) is partly covered in the related issue PathValidator can get NotAcceptableHttpException
But there's an important question not covered yet: Do we really want to throw exceptions on nonsense GET params?
Example: /?foo=bar&baz
That's completely ignored as foo and baz have no meaning at all.
But: /?_format=1234
Throws an exception although format 1234 is obviously just rubbish.
I think, we should ignore that, too.
Comment #6
indigoxela commentedUpdated summary.
Comment #7
mcannon commentedI did some tests on a clean install and it seems like this specific issue is tied to the exact parameter of "_format". I tried it without the leading underscore and it works as expected. I also tried "?foo=bar&baz" as well and no issue.
Is there an unknown reason why the "_format" parameter cause such an error? It might be intentional, not a bug.
Comment #8
indigoxela commented"_format" is a parameter known to Drupal, but only useful, if one of the implementing modules is installed. "format" without underscore is like "baz" and "foo" - totally ignored.
The question I'm asking is:
1) Shall we really throw a warning using the unimplemented format, if none of the modules implementing "_format" is enabled at all? html is the only known format then.
Right now Drupal shows a warning in the format (xml/json) "No route found for the specified format hal_json. Supported formats: html."
In how far does this make sense at all?
2) Shall we really throw exceptions, if the value is obvious rubbish.
"?_format=1234" shouldn't cause an exception, but does (reproducible).
"The website encountered an unexpected error. Please try again later."
Wouldn't it be better to check against a list of plausible formats, check if the one being asked for is implemented and possibly show a Drupal message if not. Or even totally ignore, if the format is either not active or it's a nonsense format.
Comment #9
slip commentedI agree that this is a bug. We should catch this exception and show the proper, HTML-formatted error page. Attached is a patch for consideration.
With this patch:
http://localhost:8080/user/1/?_format=json - Shows json error message as before
http://localhost:8080/user/1/?_format=NOPE - Shows an HTML 406 error page saying "A client error happened"
http://localhost:8080/user/1/?_format=html - Shows HTML user page
http://localhost:8080/user/1 - Shows HTML user page
Comment #11
slip commentedReroll
Comment #13
indigoxela commented@slip many thanks for your patch.
It's a lot better already, tested it on a fresh Drupal standard install. No more website exceptions with unknown/nonsense formats.
I guess, it's by intention that even if none of the modules (Serialization, HAL, REST...) is active and the only available format is html, still http error is "406".
dblog still shows: Symfony\Component\HttpKernel\Exception\NotAcceptableHttpException: Not acceptable format: ...
But that's probably a job for the related issue. Is that correct? Or should both problems get fixed at once? The related issue (PathValidator can get NotAcceptableHttpException) has stalled a bit.
Comment #14
indigoxela commentedAnd: wow, as the test is failing now, someone really thought, throwing exceptions for nonsense GET request is a good idea...
I don't get the concept.
Comment #15
slip commentedHere's another patch which fixes @indigoxela point about the exception still being logged, and it also reduces the scope of the fix to the problem at hand.
Comment #16
slip commentedComment #17
indigoxela commentedPatch #16 really makes sense.
Tested with and without rest module enabled (but without any actual format other than html in use). It would be cool, if someone who is actually using formats (services), takes a look.
No more website exceptions on unknown/nonsense formats. And a useful message in the dblog ("client error").
Besides the fixed exceptions, the behavior is the same as without patch (error 406).
We probably can't accomplish more without bigger changes.
Comment #18
indigoxela commentedI do have one concern: shouldn't there be some escaping for the request?
The original test checked for escaped markup, but your test accepts the script tag directly.
Comment #19
slip commentedIt's text/plain so that risk is mitigated. You definitely do have a point tho. Updated the patch.
Comment #20
indigoxela commented@slip many thanks for your patch!
From my point of view, this looks good for a commit.
But as mentioned before, I don't use any of the other formats/services.
It's not that I'm really worried about side effects, but you never know.
Comment #22
slip commentedMoving to "Reviewed & tested by the community" per comment #20 now that the tests are passing.
Comment #23
alexpott@slip thanks you so much for working on this. It is community policy to not rtbc your own patches and @indigoxela's comment in #20 says that whilst they think the code is good they don't feel they can rtbc.
Comment #24
alexpottI think the test needs to assert that the error is a 406. Maybe an explicit 406 test ala \Drupal\KernelTests\Core\Routing\ExceptionHandlingTest::test405().
This is a good comment. I think we should consider setting this to -250 to be even closer to the -256 of the final subscriber.
Can we not log something more specific?
Comment #25
indigoxela commentedThe question is: what could be helpful for admins reading the message?
To me it already makes sense.
For example: "client error" and "/node/1?_format=xml" when there's no such format available.
Possibly "No such format available." could make sense, or "Drupal can't handle format %format for that type of entity."
Not sure.
Comment #26
slip commentedFor logging I was mimicking what the other loggers do in that class for access denied and page not found. In this patch I'm adding all the information I think it makes sense to add.
The test is also added and I lowered the priority as suggested.
@alexpott thanks a lot for the feedback!
Comment #27
alexpottSo... I was wondering do we actually want to log these? I'm not sure it is necessary to bring them into the Drupal logs. It's not quite the same as access denied or 404.
Imo we could drop this code. It's not necessary to fix the bug.
Comment #28
indigoxela commentedWait... not so fast. ;)
Let's say, something went totally wrong with an actual service. For sure it would be helpful for admins to find something in the logs. To my opinion a 406 isn't less interesting than a 404 or 403.
Comment #29
slip commented@alexpott I get what you're saying. I personally think if we're logging 404s we should also log these. Something's going on with their request and they're not getting what they're asking for.
Additionally, if we remove this on406, an exception would get logged, which is part of the original problem and something I think we definitely don't want.
So I think we should keep this logging message or, if not, would you prefer to swallow the error with an empty function:
public function on406(GetResponseForExceptionEvent $event) {}Comment #30
slip commentedComment #31
slip commentedThis is ready for more feedback.
Comment #32
indigoxela commented@slip many thanks for your patience.
As we had some feedback by alexpott, I'd set this issue to rtbc now.
Comment #33
alexpott406 doesn't mean client error. It has a specific HTTP meaning. Imo it's fine to remove this. Yes that means we'll get the standard message by onError but that's no change. What the user sees is fixed. We should open a follow-up to log 400s differently as the message
$this->logger->get('php')->log($error['severity_level'], '%type: @message in %function (line %line of %file).', $error);doesn't work for 405s either (for example).Comment #34
alexpottHmmm thinking about this even more let's make this more generic and do
And then we can fix logging to be more generic for 400s as well.
Comment #35
slip commentedUpdates made. This is now a generic handler.
I also added support for cacheable responses like
ExceptionJsonSubscriberMoved logging changes to https://www.drupal.org/project/drupal/issues/3039266
Comment #36
slip commentedComment #37
alexpottNot sure that we are specific to serialization failures - however there's no harm in including an example here - so we could mention 406s generated when handling unsupported formats.
Needs a new name.
Needs a new name.
If we're outputting plain text I think we should strip HTML. We can use
\Drupal\Component\Render\PlainTextOutput::renderFromHtml()A single subscriber can register more than one listener so we could do something like:
Comment #38
slip commentedAll valid comments. New patch attached.
Comment #39
krzysztof domańskiDevelopments changes should now be targeted the 8.8.x-dev branch.
Allowed changes during the Drupal 8 release cycle
Drupal 8 minor version schedule
Comment #40
alexpott@Krzysztof Domański - yeah but this is a bug fix so should target 8.7.x
Comment #41
alexpottThere's a space after the * on the blank line.
This comment needs to wrap at 80 chars.
The website encountered an unexpected error. Please try again later.. What is actually wrong with that?Comment #42
slip commentedStyling fixed.
This original issue was definitely a bug. Site's are showing that error (which is basically Drupal's version of a fatal error) and exceptions are being logged. The logging part is perhaps most severe issue but we split that off. Still I'd like to finish this issue before dedicating time to that one.
I do take issue with the message displayed. The Drupal message "The website encountered an unexpected error. Please try again later." makes me think an unrecoverable fatal error happened. Additionally, If you have your site set up to show exceptions, one will be printed. This seems like overkill for something easily reproduced on every D8 site.
For messaging we could use the messaging in Http4xxController. At one point I had those simple messages being output.
Another point is that the json subscriber is actually doing something very similar to us and is outputting exception messages. for example (logged out):
https://www.site.tld/admin/?_format=json
If exception messages shouldn't be shown, that page shouldn't show them either.
If you disagree we can close this issue and tackle the logging issue. Otherwise please let me know what you think and I'll take it from there.
Comment #43
indigoxela commentedI do have a problem here with patch #42 applied.
My test path: /node/1?_format=htmlccc (rubbish)
I get an almost empty page with "Not acceptable format: htmlccc" as only content. Nothing else, no markup at all. Is this intended?
The http code is 406 as expected.
Drupal dblog detail page renders normally, the message is:
Drupal Version is 8.6.13.
Comment #44
slip commented@indigoxela yes, that's intended. We scaled back this patch significantly and since the user is requesting a format that isn't html, it didn't seem to make sense to return HTML, although that is easy enough to do. Additionally, the logging fix was moved to a different issue. That's why the logging is still an issue even with this patch.
Comment #45
alexpott@slip I think using the messages in \Drupal\system\Controller\Http4xxController is a very good idea. Those messages are indeed way better than
The website encountered an unexpected error. Please try again later.It would be neat if somehow the messages could be shared between Http4xxController and FinalExceptionSubscriber.
Another thought is that maybe if you are getting these on your site it might be helpful to get the additional backtrace info added by \Drupal\Core\EventSubscriber\FinalExceptionSubscriber::onException() so perhaps we should roll this content change into that method.
Comment #47
wim leersThis needs to document the reasoning for this particular priority. Why -250 compared to -256?
Also: the comment above this line belongs with the -256 event.
Comment #49
berdirReroll for D9.
Added a comment, but there's not too much to say. It has to run before the final exception handler, that's pretty much all there is to it I think.
Not sure what to do about #45.
I think it would be nice to finally resolve this, there are still bots out there looking for vulnerable sites for the hal_json security issue and it's filling up logs.
Comment #50
wellsAdding a reroll for D8.9.x.
Comment #52
berdirAnother reroll, and I can already see this conflicting again because that assertEqual() line is going to change again :-/.
Comment #54
wellsUpgrading an 8.9 site to 9.x today and the reroll in #52 still applies and resolves the issue. Marking RTBC as #47 has been addressed and its not clear if #45 if necessary. @alexpott or someone else can revert back to needs work if necessary.
Comment #55
berdirIt does apply, but it has coding style issues that need to be resolved sadly.
Comment #56
neslee canil pintoUpdating #52 to pass drupalCI.
Comment #57
neslee canil pintoHow can we handle this
/var/www/html/core/tests/Drupal/KernelTests/Core/Routing/ExceptionHandlingTest.php:215:59 - Unknown word (jsonalert)here -$this->assertStringStartsWith('Not acceptable format: jsonalert(123);', $response->getContent());Should we use it as we did it before inside em tag
Comment #58
berdirYeah, I'm unsure what to do about that. The reason that we end up with jsonalert is that this is all that's left over of
json<script>alert(123);</script>, this is is specifically testing that we're escaping that format.Except now, we just strip out the HTML and don't escape it, due to `$message = PlainTextOutput::renderFromHtml($exception->getMessage());`. Similar cases of that are in \Drupal\jsonapi\Normalizer\UnprocessableHttpEntityExceptionNormalizer::buildErrorObjects and \Drupal\rest\Plugin\rest\resource\EntityResourceValidationTrait::validate().
Should we just add that word to the exception list? Or somehow handle it differently? We could change the test to add a space in front of
We should then also update the comment above that change, because that talks about escaped HTML that is no longer there.
Just a basic reroll for now.
Comment #59
wellsJust cleaning up the default displayed patches to the working D8 and D9 versions. #52 and #56 no longer apply to 9.1.10 -- #58 applies and continues to work.
Comment #60
berdirAnother reroll for 9.3, question in #58 remains.
Comment #61
alexpottAdd a comment
before the assertion and then this spelling error will only be ignored here.
Comment #62
paulocsAddressing comment #61.
Comment #63
weseze commentedThe patch fixes the fatal error issue.
I am however wondering why we are not simply ignoring unknown query parameters/values?
In several places in core where the "_format" parameter is handled there is comment indicating that the "html" value should be the default.
With that information I would assume it would be more logical to fallback to HTML when an unknown value is encountered?
Or to just completely ignore unknown query parameter/values, that would then also fallback to html.
What is the advantage of showing a custom error page instead?
Comment #65
mfbThis patch has logic to potentially make it a cacheable response, but I'm wondering what a site would have to do to make it actually cacheable, as it wasn't in my testing, i.e. adding _format=something is a reliable way to bypass the page cache and proxy cache.
Comment #66
berdirI think the logic for that is just copied. For the 406 errors to be cacheable, \Drupal\Core\Routing\RequestFormatRouteFilter::filter() would need to throw a cacheable http exception, which feels like a different issue.
Comment #67
mfbok, and for posterity, looks like there are a couple other places where a cacheable http exception would need to be thrown - \Drupal\Core\EventSubscriber\RenderArrayNonHtmlSubscriber::onRespond() and \Drupal\Core\Routing\Enhancer\ParamConversionEnhancer::onException()
Comment #70
berdirReroll for D10.
Re #63: That too is a different issue. This isn't about whether or not core should throw such errors, it's how to handle them if it happens, and they are a valid HTTP response. As for _format throwing an exception on invalid values, that kind of makes sense as the client would expect a certain format and not returning in that would likely cause the client to fail.
Comment #71
catchCan this be str_starts_with() now we require PHP 8.1?
Should this say "Tests a route"?
Overall looks good to me though.
Comment #72
wellsAttaching #70 patch with updates from #71 review.
Comment #73
smustgrave commentedCan the issue summary be updated please? Mentions Drupal 8 are the steps still the same for D10?
What was the proposed solution?
Any remaining tasks?
etc.
Comment #74
wellsI have updated the issue description with the standard template. Hope that helps!
Comment #75
smustgrave commentedSo if the node exists I get
Not acceptable format: hal_json
If the node doesn't exist I get
The "node" parameter was not converted for the path "/node/{node}" (route name: "entity.node.canonical")
Is that expected?
Comment #76
wellsYes. See #45 from @alexpott --
Comment #77
smustgrave commentedAh thanks for the follow up!
Then I don't see any issue on this.
Comment #79
catchCommitted 92fcdbf and pushed to 10.1.x. Thanks!
Debating whether to backport this due to the new event subscriber, it should not break anything and seems unlikely someone would have registered a competing exception subscriber, but since this is just improving an error message, going to leave it in 10.1.x - however if you've got strong objections re-open and we can probably backport with a change record.