http icon indicating copy to clipboard operation
http copied to clipboard

RequestFactory: invalid byte sequences in $_SERVER['REQUEST_URI']

Open JanTvrdik opened this issue 10 years ago • 26 comments

The goal is to not simply ignore invalid byte sequences in URL, but throw Exception or return NULL. Current Nette behavior is unwanted, e.g. these URLs should not be accepted at all:

http://doc.nette.org/en/2.2/%C0%C0%C0%C0%C0templating
https://www.rohlik.cz/stranka/%C0kontakt

How would you prefer to handle it? Exception or by returning NULL. Both break the RequestFactory API. I would prefer the exception, e. g. Nette\Http\InvalidRequestException. Do you like the name?

cc @fprochazka

JanTvrdik avatar Dec 16 '14 11:12 JanTvrdik

Exception is fine with me. But the main problem here is how to present the error to the user. If I visit such URL I definitely shouldn't see error page generated by Tracy. It should fall to error presenter - I don't wanna have log full of bluescreens - I want to have one string line in access.log. But that is a problem, because you can access the request in bootstrap, custom scripts or just simply somewhere else, before the app hits Application::processRequest().

One thing that crosses my mind is to return instance of Http\InvalidRequest that would provide backwards compatibility, the control characters could be removed from the url in this request and when such Http\InvalidRequest hits Application, it could directly forward to ErrorPresenter, without even hiting the router.

fprochazka avatar Dec 16 '14 16:12 fprochazka

you can access the request in bootstrap, custom scripts or just simply somewhere else

Then it should be your job to handle it. But it is obviously a nasty BC break.

return instance of Http\InvalidRequest that would provide backwards compatibility

:-1: That does not feel right.


Another think to consider – if you choose to change the API and throw exception, we may also reconsider how we handle control characters (note: that is something different than invalid encoding which is what the exception is primary for). Currently we ignore keys with control characters and discard values with control characters.

JanTvrdik avatar Dec 16 '14 16:12 JanTvrdik

The whole problem is that the usage of Http\Request in Nette\Application (the api) is really bad.

The api should look like this

$app = $container->getByType(Nette\Application\Application::class);
$app->handle($container->getByType(Nette\Http\RequestFactory::class)->create());

Then, you have perfect control of when the request is created and what you do with it.

$app = $container->getByType(Nette\Application\Application::class);
try {
    $app->handle($container->getByType(Nette\Http\RequestFactory::class)->create());
} catch (Nette\Http\InvalidRequestException $e) {
    $app->handleInvalidRequest($e); // this could for example open the error presenter
}

In the end, the entire thing could be hidden in $app->run()

public function run()
{
    try {
        $this->handle($this->httpFactory->create());
    } catch (Nette\Http\InvalidRequestException $e) {
        $this->handleInvalidRequest($e); // this could for example open the error presenter
    }
}

At this point, you're gaining the possibility to handle the exception created by invalid request in appropriate level of abstraction. It should either be rejected by the http server (nginx), or it should be handled by the front controller as bad request and error presenter should be shown.

Having Http\Request as a service is bad IMHO.

The service Http\Request should be replaced with Http\RequestStack, with BC compatible methods and it should always return the values of the last Http\Request. But the point is, you get a "singleton" container, just like the User is for Identity and the Http\Request can be changed how you please. This would also provide possibility of better handling requests in applications that are controlled by react.php


Symfony has much better front controller API ... We should investigate how Symfony and other frameworks handle this situation and then decide what is best for us.

fprochazka avatar Dec 16 '14 18:12 fprochazka

So Http\RequestStack would be (for most practical purposes) something like Nullable<Http\Request> in C#?

JanTvrdik avatar Dec 16 '14 18:12 JanTvrdik

I was thinking something like

class RequestStack implements IRequest
{
    private $requests = [];
    private $lastRequest;

    public function append(IRequestFactory $factory)
    {
        $this->lastRequest = $factory->create();
        $this->requests[] = $this->lastRequest;
        return $this->lastRequest;
    }

    public function getUrl()
    {
        return $this->lastRequest->getUrl();
    }

    // ...
}

How will the stack be handled is a detail, it could even be container only for the lastRequest. The point is, that we must have a container for the Request object, it should have never been a service (just like Identity is also not a service).

fprochazka avatar Dec 16 '14 19:12 fprochazka

+1 Only thing which bothers me is not logging this exception. Don't worry about making the bc break. Can't imagine how somehow would create theses malformed urls intentionaly.

hrach avatar Dec 17 '14 14:12 hrach

Related – I would also like to change how we handle invalid Host header. Currently it is simply ignored.

JanTvrdik avatar Dec 17 '14 21:12 JanTvrdik

@fprochazka I look at the RequestStack in Symfony. It was introduced in Symfony 2.4 (a year ago). Explanation of the motivation for that decision is available in a blogpost and documentation.

It seems to be the best solution to the problem. Thoughts? cc @dg


Note: Nette needs the RequestStack much less than Symfony because we already have Application\Request with Application\Request's stack managed by Application\Application.


Maybe – just maybe – we may 1) remove Http\Request from DIC 2) Move Application\Application::$requests to Application\RequestStack and put that in the DIC. 3) Add optional $httpRequest parameter to Application\Request. Is that stupid? I think that (1) and (2) are fine, not really sure about (3) though.

JanTvrdik avatar Dec 19 '14 14:12 JanTvrdik

@JanTvrdik well, beeing the one suggesting it, I hardly can disagree :)

fprochazka avatar Dec 20 '14 02:12 fprochazka

RequestStack is not suitable for Nette, because it solves a different problem (as @JanTvrdik said, Nette has stack of Application\Request). When you need to create Http\Request inside Application, Nette has native solution: interface & generated factory.

dg avatar Dec 20 '14 21:12 dg

@dg I'm not sure you understand the problem. The problem is that Http\Request is a value object not a service. Having it as a service is wrong and it's causing problems. That's why new service that will contain the Request object should be introduced. Where is it created and how it's handled is a detail.

fprochazka avatar Dec 20 '14 21:12 fprochazka

@fprochazka I understand.

(btw, value object can be service.)

dg avatar Dec 20 '14 22:12 dg

Well obviousely, but that doesn't make it right.

Identity is not a service.

fprochazka avatar Dec 20 '14 22:12 fprochazka

"Identity is not a service → value object as a service is wrong" is fallacy of the converse.

dg avatar Dec 20 '14 22:12 dg

The Http\Request is an immutable value object (FYI it should probably clone the url it returns from getUrl, it might be changed otherwise). We have now here two ways to solve this problem

  1. The DIC in Nette handles scopes and can drop instances of services, that can change when next request occurs. This also means it knows the entire graph of services that has to be dropped when the Http\Request service is changed - this concept is known to all advanced DIC containers in all advanced languages, like C# and If I'm not mistaken, even Symfony has now scopes in DIC
  2. You introduce container object for the value object, that will then be service instead - which is obviousely much simpler solution

Because Nette has no scopes in DIC, having value objects as a service is wrong. Simple as that.

fprochazka avatar Dec 21 '14 01:12 fprochazka

@fprochazka in czech. Prove that for each value object is wrong to have it as a service.

dg avatar Dec 21 '14 02:12 dg

I don't want to say "there isn't a case where having value object as a service is a good thing", but I can't think of any.

I also don't have to prove it (at least not about Http\Request), because the symfony community has already proven that having request VO as a service was a bad decision by practise and therefore I'm applying that findings on Nette. It's exactly the same case. You prove them (and every other high level framework in other languages that came to the same conclusion) wrong :)

In other case (there are two, the specific one with Http\Request and my general statement about VO as service beeing wrong, which is my opinion not a fact), you prove me wrong by counter example (or other weapon of your choise) - where is it a good thing to have a VO directly as a service in DIC? I've never stated "for every" but if you wanna generalize, let me correct myself:

Because Nette has no scopes mechanizm in DIC, having MOST value objects as services is wrong.

Also about the Identity example, that you like to repeat instead of continuing in the discussion, I haven't stated that there is an implication, it was just a very good example, because it's the most obviouse one.

fprochazka avatar Dec 21 '14 08:12 fprochazka

Basically, DIC should almost always hold only services, not the entities, crates, etc. This is the general rule, which also aplies here.

hrach avatar Dec 21 '14 09:12 hrach

Regarding Http\RequestStack – could anyone describe a real-world use-case where it would hold more than a single Http\Request instance? I was originally thinking about React.PHP but even there it makes no sense, because you would actually need a queue not a stack.

JanTvrdik avatar Dec 21 '14 10:12 JanTvrdik

Stack makes sense with subrequests in symfony. They don't have snippets and controls. They have subrequests (recursive calling of controllers and their output is then composed into a single template), which can be converted to proxy-level subrequests, which makes caching them a reall bliss.

I don't think that makes much sense with Nette, implementing this would be probably hard. Stack might make sense when you have forwarded requests. But I'm not sure.

fprochazka avatar Dec 21 '14 11:12 fprochazka

@fprochazka You said that the value object cannot be a service, which is a strong & general statement, but you cannot prove it (the fact that some value objects cannot be used as services is really not an evidence, see fallacy of the converse).

I would prefer to end debate about VO.

dg avatar Dec 21 '14 12:12 dg

Would you preffer to end it at this issue (because it's a little bit off topic), or end it all together? Because I don't think the debate is over, because you haven't conviced me and neither have I convinced you.

fprochazka avatar Dec 21 '14 12:12 fprochazka

Here. It is off topic.

dg avatar Dec 21 '14 12:12 dg

Another point regarding Http\RequestStack – even though Nette itself does not have a use-case for Http\RequestStack, applications using nette/http and not using nette/application may have one (e.g. if they use Symfony inspired architecture).

JanTvrdik avatar Dec 27 '14 12:12 JanTvrdik

Self note – Symfony deprecated scopes in 3.0, however I still think that scopes (*) can provide a good solution to this problem. Not sure though how much complexity would that bring.

(*) in general you have a directed acyclic graph of containers – services from container A can depend on services from container B only if there exist path from container A to container B. To handle the request problem you would have a root container and a request container. The request container would contain Http\Request instance and services depending on this instance (e.g. routers or link generator).

This makes e.g. testing presenters depending on Http\Request easier as you would no longer need to rebuild the entire container for each Presenter::run call, only the much smaller request container would need to be thrown away and recreated.

Another use case for scopes may be database connection or ORM identity map (useful mostly in tests)

JanTvrdik avatar Nov 09 '15 20:11 JanTvrdik

Related https://github.com/nette/routing/issues/6

dg avatar Feb 06 '21 00:02 dg