-
-
Notifications
You must be signed in to change notification settings - Fork 9.6k
[HttpFoundation] [Request] Throw dedicated exceptions. #20389
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
[HttpFoundation] [Request] Throw dedicated exceptions. #20389
Conversation
* file that was distributed with this source code. | ||
*/ | ||
|
||
namespace Symfony\Component\HttpFoundation\Exception; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In other components, exceptions implement an interface specific to the component.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Which interface would be appropriate in this case?
For example currently the class throws exceptions of the class ConflictingHeadersException but that doesn't implement any interface.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was only one exception but if the component contains more exceptions, they should implement an ExceptionInterface.
8b34ded
to
9807f06
Compare
reworked, @Ener-Getick please have a look :) |
@@ -1220,7 +1226,7 @@ public function getHost() | |||
// check that it does not contain forbidden characters (see RFC 952 and RFC 2181) | |||
// use preg_replace() instead of preg_match() to prevent DoS attacks with long host names | |||
if ($host && '' !== preg_replace('/(?:^\[)?[a-zA-Z0-9-:\]_]+\.?/', '', $host)) { | |||
throw new \UnexpectedValueException(sprintf('Invalid Host "%s"', $host)); | |||
throw new InvalidHostException($host, sprintf('Invalid host "%s".', $host)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Couldn't we just pass the message as it contains the host, such as done for ?UntrustedHostException
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would like to get the host
that triggered the exception to be thrown when I'm dealing with the exception. Relying on the message (format) of an exception is never a good idea IMHO.
*/ | ||
public static function getTrustedHeaderName($key) | ||
{ | ||
if (!array_key_exists($key, self::$trustedHeaders)) { | ||
throw new \InvalidArgumentException(sprintf('Unable to get the trusted header name for key "%s".', $key)); | ||
throw new InvalidTrustedHeaderException($key, null, sprintf('Unable to get the trusted header name for key "%s".', $key)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Isn't an InvalidArgumentException
localized in the component enough and more appropriate?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Happy to revert :)
@@ -1936,7 +1937,7 @@ public function testHostValidity($host, $isValid, $expectedHost = null, $expecte | |||
$this->assertSame($expectedPort, $request->getPort()); | |||
} | |||
} else { | |||
$this->setExpectedException('UnexpectedValueException', 'Invalid Host'); | |||
$this->setExpectedException('Symfony\Component\HttpFoundation\Exception\InvalidHostException', 'Invalid host'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can use the short syntax here Exception::class
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ah SF upped requirements to PHP 5.5, good point will update
* | ||
* @author SpacePossum | ||
*/ | ||
final class InvalidHostException extends AbstractHostException |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this one should be internal.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍 agreed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
On second thought, I don't think so, people want to deal with this exception by catching it. Marking it as internal will cause IDE's to complain about using an internal class.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Indeed sorry I didn't commented on the right class... I was thinking about AbstractHostException
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍 I've updated that class.
* @param string $host | ||
* @param string $message | ||
*/ | ||
public function __construct($host, $message) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not making $message
optional (if null, default to Invalid host "'.$host.'"
), so you can simply throw new InvalidHostException($host)
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Making it null
able will break with the design of the base exception class itself, which is IIRC something people don't want in the SF project.
Besides there is no way this class can generate a useful/correct message based on the value of $host
alone, the logic that figured out the $host
value is incorrect and throws the exception knows what is wrong and can therefor set a meaningful message only.
* @param string|null $value | ||
* @param string $message | ||
*/ | ||
public function __construct($header, $value, $message) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same.. make message optional?
/** | ||
* @var string | ||
*/ | ||
private $value; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Having getHeader() / getValue()
would be nice? Right now these are basically useless.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
thank, indeed, however as opt here I will remove the class all together.
abstract class AbstractHostException extends \UnexpectedValueException implements ExceptionInterface | ||
{ | ||
/** | ||
* @var string |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe unnecessary docs can be removed.. same for the getter.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
these do no harm and will become of use when doing SCA, refactoring and/or updating to PHP 7 when the PHPDocs can be used to generate the type hints on method signatures.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
They are useless, they can be inferred from constructor's docs and most tools support this.
If this PR is accepted, it will only go into Symfony 4.0 only for BC reasons I suppose. You should also trigger some deprecation errors before every new exception types is thrown. |
@hhamon : Why ? There is no BC break, as the new exceptions extends |
} catch (\UnexpectedValueException $e) { | ||
$this->assertEquals('Untrusted Host "evil.com"', $e->getMessage()); | ||
} catch (UntrustedHostException $e) { | ||
$this->assertEquals('Untrusted host "evil.com".', $e->getMessage()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think host
was capitalized referencing the Host header. I'm not sure this has to be changed (same for other occurrences).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
alright 👍 updated back to Host
I agree with @ogizanagi here, there is no real BC break. The only way this can break is people rely on the exception being an instance of
no, the logic of the exception being thrown is not deprecated and so it should not trigger such warnings. |
👍 |
@dunglas I've removed the PHPDoc's as requested, anything else you want me to change to get the PR into the status that is ready and marked as reviewed? |
It's ok for me, you now need to wait that at least another core team member review it and agree to merge it. Status: reviewed |
👍 for me (but for 3.3) |
@@ -1940,7 +1945,12 @@ private static function createRequestFromFactory(array $query = array(), array $ | |||
$request = call_user_func(self::$requestFactory, $query, $request, $attributes, $cookies, $files, $server, $content); | |||
|
|||
if (!$request instanceof self) { | |||
throw new \LogicException('The Request factory must return an instance of Symfony\Component\HttpFoundation\Request.'); | |||
throw new \UnexpectedValueException( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
BC break :) although this is the better exception :(
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
reverted, I don't care to much about that one anyway so ;)
does this mean I've to retarget this PR? |
Closing in favor of #20962 |
This PR was merged into the 3.3-dev branch. Discussion ---------- Request exceptions | Q | A | ------------- | --- | Branch? | master | Bug fix? | yes | New feature? | yes | BC breaks? | no | Deprecations? | no | Tests pass? | yes | Fixed tickets | #20389, #20615, #20662 | License | MIT | Doc PR | n/a Replaces #20389 and #20662. The idea is to generically manage 400 responses when an exception implements `RequestExceptionInterface`. The "weird" caches on the request for the host and the clients IPs allows to correctly manage exceptions in an exception listener/controller (as we are duplicating the request there, but we don't want to throw an exception there). Commits ------- 32ec288 [HttpFoundation] refactored Request exceptions d876809 Return a 400 response for suspicious operations
I'm trying to deal with invalid requests being sent to one of my sites.
While this is a Silex site I think this PR will help dealing with such requests and will be useful for SF developers as well.
The problem is that sometimes a request is send with host data like "my.test.com/".
The trailing slash will cause the Request class to throw an exception when asking for the host (for example through
getHost()
).In order to deal with these kind of errors I setup something like:
Dealing with the exception at these points is hard because the Request class throws an SPL
\UnexpectedValueException
exception.At the point of handling it is hard to see this exception comes from the Request class and not from somewhere else in the system (for example a controller dealing with the Request object).
It is not possible to deal with exception within the controllers and/or other part of the application on user level.
This is because the exception is thrown before the Request is given to a controller.
Example trace:
It will not only be a lot of work modifying all components dealing with the host of a Request (either by
getHost
orgetHttpHost
) before the Request is routed to a controller, it will also be hard to make sure when new components are added (or existing modified) deal with the exception correctly.Therefor I propose to let the Request class throw exceptions of meaningful (/dedicated) classes. That way people can deal with these exception as they want.