The Wayback Machine - https://web.archive.org/web/20220525010455/https://github.com/dotnet/aspnetcore/issues/11544
Skip to content
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

Provide an option on FromRouteAttribute that allows decoding the value being bound #11544

Open
SchroterQuentin opened this issue Jun 25, 2019 · 19 comments
Labels
affected-medium area-web-frameworks enhancement feature-Model-Binding severity-minor

Comments

@SchroterQuentin
Copy link

@SchroterQuentin SchroterQuentin commented Jun 25, 2019

The rights of my users are at /api/admin/users/{userId}/rights

And my controller is as simple as

[Route("/api/admin/users/{userId}/rights")]
public async Task<IActionResult> GetRights([FromRoute]string userId)
{
    var rights = await _someService.GetRights(userId);
    return Ok(rights);
}

or

[HttpPut("{userId}")]
public async Task<IActionResult> Update(string userId, [FromBody]UpdateUserViewModel parameters)
{
    var user = await _employeService.Update(userId, parameters);
    return Ok(user);
}

The problem I have is that, the userIds of my users may contains a / which is encoded to %2F in the Uri. But userId doesn't decode %2F so my string contains %2F. It's fine for me, I can deal with that.

But the userIds of my users may contains a + which is encoded to %2B in the Uri. And now, the userId decode the %2B to + 😲

Currently, I can't use WebUtility.UrlDecode(userId) because userId may contains a + which would be send as %2B decoded as + and finally to . My only actual solution is to replace %2F to / which is ugly and does not solve all the possibility : %252F

I saw a recommandation to use [FromQuery] instead of [FromRoute] but it's obvious that if both exist, it's because they have semantic difference.

It seems that's not the first time the problem appears : aspnet/Mvc#4599, #2655, #4445 and I'd like to know if it's on the roadmap to change this behavior or not.

Could you please this time consider this bug ? I'd be happy to help.

@anurse anurse added the area-mvc label Jun 25, 2019
@mkArtakMSFT
Copy link
Contributor

@mkArtakMSFT mkArtakMSFT commented Jun 28, 2019

Thanks for contacting us, @SchroterQuentin.
We'll provide an option so that FromRoute attribute decodes specific (by you) parameters.

@mkArtakMSFT mkArtakMSFT added this to the 3.1.0 milestone Jun 28, 2019
@mkArtakMSFT mkArtakMSFT added enhancement PRI: 1 - Required labels Jun 28, 2019
@pranavkm pranavkm changed the title DataBinding from Uri path - string decode Provide an option on FromRouteAttribute that allows decoding the value being bound Jun 28, 2019
@SchroterQuentin
Copy link
Author

@SchroterQuentin SchroterQuentin commented Jul 1, 2019

The problem exist on [HttpGet] [HttpPost] [HttpPut] etc. Is there a way to handle all this problems in one area ? Or do we have to add this options on all this attributes ?

I'm actually not understanding how would work this option ?

@rynowak rynowak removed their assignment Jul 4, 2019
@bachratyg
Copy link

@bachratyg bachratyg commented Jul 10, 2019

Also see aspnet/Mvc#6388

The hosts partially decodes the percent encoding and explicitly skips decoding %2F here:

Microsoft.AspNetCore.Server.IIS > Microsoft.AspNetCore.HttpSys.Internal.RequestUriBuilder.UnescapePercentEncoding
Microsoft.AspNetCore.Server.HttpSys > Microsoft.AspNetCore.HttpSys.Internal.RequestUriBuilder.UnescapePercentEncoding
Microsoft.AspNetCore.Server.Kestrel.Core > Microsoft.AspNetCore.Internal.UrlDecoder.UnescapePercentEncoding

The main problem is Routing or MVC model binding cannot reliably decode the partially decoded segment. Ideally the server should not do any decoding especially not partial. (Full decoding is not possible since it would introduce new segments.)

If the server behavior is not allowed to change (even behind a compatibility flag) then the router middleware could be modified to use the raw request url from IHttpRequestFetaure.RawTarget (which is not yet decoded) to get proper encoded tokens then decode as needed.

A possible workaround: inject a middleware before routing takes place that restores the original undecoded path from IHttpRequestFeature.RawTarget then in another middleware after routing decode the RouteData tokens.

@pranavkm
Copy link
Contributor

@pranavkm pranavkm commented Aug 26, 2019

As part of doing this, ensure binding to non-string values that require decoded values work. See #11134

@mkArtakMSFT mkArtakMSFT removed this from the 3.1.0 milestone Aug 27, 2019
@mkArtakMSFT mkArtakMSFT added this to the 5.0.0-preview1 milestone Aug 27, 2019
@mkArtakMSFT mkArtakMSFT removed this from the 5.0.0-preview1 milestone Jan 10, 2020
@mkArtakMSFT mkArtakMSFT added this to the next sprint planning milestone Jan 10, 2020
@ArthurHNL
Copy link

@ArthurHNL ArthurHNL commented Mar 31, 2020

Can we get an update on this? I just spent half an hour debugging my API before I found out that Route parameters are not URL decoded by default.

@DanielBryars
Copy link

@DanielBryars DanielBryars commented Apr 1, 2020

Yeah, this behaviour is pretty weird.

If you can't fix it then this FromRouteAttribute option would be a good compromise for me. Or perhaps provide an example implementation of the middleware suggested by https://github.com/Eilon in aspnet/Mvc#6388

@stefanluchian
Copy link

@stefanluchian stefanluchian commented Apr 23, 2020

I see that this bug lives since at least 2017 and there is not even a workaround about it.
In .Net Core you don't allow us to do WCF anymore, so we are forced to do REST.
In REST we need to send parameters via Route, not via Query.
So we need to use [FromRoute], which should happen short before Model-Binding, but definitely after Routing.
So there is no worry about decoding %2B into /.
Then why is nothing happening here?
You closed any other Issues (at least 4 peaces counted) and now you let this open for loooong time.
How many developers do you need to comment on this issue, before you consider it worthy to look upon?

@pranavkm
Copy link
Contributor

@pranavkm pranavkm commented Apr 23, 2020

Can we get an update on this?

We will consider fixing this in the upcoming 5.0 release. That said, the plan is to not have this decoding on by default - you explicitly would have to opt-in to this behavior. For current releases, you should be able to unblock yourselves by explicitly url decoding values in your action that you know require to be decoded.

@mkArtakMSFT
Copy link
Contributor

@mkArtakMSFT mkArtakMSFT commented Jun 9, 2020

We've moved this issue to the Backlog milestone. This means that it is not going to be worked on for the coming release. We will reassess the backlog following the current release and consider this item at that time. To learn more about our issue management process and to have better expectation regarding different types of issues you can read our Triage Process.

@skovalyova
Copy link

@skovalyova skovalyova commented Aug 3, 2021

I face the same issue when passing encoded URI as part of route. In this case, : is decoded automatically, but // remain encoded (http:%2F%2F). I have to use HttpUtility.UrlDecode as a workaround or use query parameters instead.

@talshloman
Copy link

@talshloman talshloman commented Sep 9, 2021

I faced the same issue when passing encoded URI as query parameters.
I had to Get a request and I excepted to get a model. one of the props was string and we send encoded string due to we want to pass '+'.

I tried every solution I found on google, but no one work. so I started to try myself and I found a solution. I explicit the get set.
Now all work as expected.

In you example
`[Route("/api/admin/users/{userId}/rights")]
public async Task GetRights([FromRoute]GetRightsRequest userId)
{
var rights = await _someService.GetRights(userId);
return Ok(rights);
}

public class GetRightsRequest
{
private string _userId
public string UserId{get{return _userId;} set{_userId = value;}}
}`

In my case instead [FromRoute] I use [FromQuery] but I think it will work.
In addition, I don't really know why this work, and when I use automatic get set ({get; set;}) it didn't work.

@mkArtakMSFT mkArtakMSFT added area-web-frameworks and removed area-mvc labels Oct 14, 2021
@ItayBenNer
Copy link

@ItayBenNer ItayBenNer commented Oct 17, 2021

Hi devs, please advise if not dealing the route cause of this issue, please provide a proper workaround this is not that small of a thing, the DOTNET core FW should be working according to REST standards this partial escaping is really a wired behavior :

@celluj34
Copy link

@celluj34 celluj34 commented Nov 2, 2021

A workaround is to provide your own model binder and provider:

// Startup.cs : ConfigureServices

            services.AddMvcCore(options =>
                    {
                        options.ModelBinderProviders.Insert(0, new DecodePathStringsBinderProvider());
                    });


    public class DecodePathStringsBinderProvider : IModelBinderProvider
    {
        public IModelBinder? GetBinder(ModelBinderProviderContext context)
        {
            return context.Metadata.ModelType == typeof(string) && context.BindingInfo.BindingSource == BindingSource.Path /* or Route, or whatever you want */ ? new BinderTypeModelBinder(typeof(DecodePathStringsBinder)) : null;
        }
    }

    public class DecodePathStringsBinder : IModelBinder
    {
        public Task BindModelAsync(ModelBindingContext bindingContext)
        {
            if (bindingContext == null)
            {
                throw new ArgumentNullException(nameof(bindingContext));
            }

            var modelName = bindingContext.ModelName;

            // Try to fetch the value of the argument by name
            var valueProviderResult = bindingContext.ValueProvider.GetValue(modelName);
            if (valueProviderResult == ValueProviderResult.None)
            {
                return Task.CompletedTask;
            }

            var value = valueProviderResult.FirstValue;
            var urlDecode = HttpUtility.UrlDecode(value);

            bindingContext.ModelState.SetModelValue(modelName, urlDecode, value);
            bindingContext.Result = ModelBindingResult.Success(urlDecode);

            return Task.CompletedTask;
        }
    }

@rafikiassumani-msft rafikiassumani-msft removed this from the Backlog milestone Dec 28, 2021
@rafikiassumani-msft rafikiassumani-msft added this to the .NET 7 Planning milestone Dec 28, 2021
@msftbot
Copy link
Contributor

@msftbot msftbot bot commented Dec 28, 2021

Thanks for contacting us.

We're moving this issue to the .NET 7 Planning milestone for future evaluation / consideration. We would like to keep this around to collect more feedback, which can help us with prioritizing this work. We will re-evaluate this issue, during our next planning meeting(s).
If we later determine, that the issue has no community involvement, or it's very rare and low-impact issue, we will close it - so that the team can focus on more important and high impact issues.
To learn more about what to expect next and how this issue will be handled you can read more about our triage process here.

@Nils-Berghs
Copy link

@Nils-Berghs Nils-Berghs commented Feb 16, 2022

What am I missing? I can't understand that this isn't fixable in 6 years. It is very easy:

  • Evaluate the route
  • Decode route params
  • Pass them to the controller action/page/...

A solution is even given in the workaround described by @celluj34 The soltution described by @celluj34 does NOT always work. It works for the / but if there are other 'escaped' characters in the route param they are already decoded, and decoding them again may cause issues.

Examples (input string in unencoded form):

  • 'ab/cd' => decodes ok
  • 'ab%cd' => decodes ok
  • 'ab%12' => ERROR, in the model binder the % character is already encoded and decoding again will decode %12

THERE IS NO SOLUTION FOR THIS PROBLEM POSSIBLE
An idea would be to replace %2f by /, but there is no way to know if the percentage was already decoded or not!

Is there any reason why route parameters should not be fully URL decoded, by default? I can think of none. And if there is, is it as devastating as the problem above? In short route parameter can not be used in asp .net core (unless you know all possible values in advance....)

@asprna
Copy link

@asprna asprna commented Apr 13, 2022

For me, this only happens when the controller inherits from ControllerBase class. The workaround for me is that instead of ControllerBase, I inherit the controller from the Controller class.

@Nils-Berghs
Copy link

@Nils-Berghs Nils-Berghs commented Apr 13, 2022

@asprna I can not confirm this, for me it happens both with Controller and ControllerBase as base class. (Maybe this depends on the .net core version, after all this bug could have been fixed in 3.1)

@asprna
Copy link

@asprna asprna commented Apr 16, 2022

@Nils-Berghs I am currently using .net core 3.1

@asprna
Copy link

@asprna asprna commented Apr 16, 2022

@Nils-Berghs Actually you are right. The actual workaround is to get the value from the query string.

[ApiController] [Route("api/v1/[controller]")] public class TestController : ControllerBase { [HttpGet("Successful")] public async Task<IActionResult> Successful([FromQuery] string token) { return Ok(token); } }

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
affected-medium area-web-frameworks enhancement feature-Model-Binding severity-minor
Projects
None yet
Development

No branches or pull requests