Azure / azure-functions-host

The host/runtime that powers Azure Functions
https://functions.azure.com
MIT License
1.93k stars 441 forks source link

Routing fails if parameters contains slash #9290

Open russgove opened 5 years ago

russgove commented 5 years ago

Hi, I see this was reported a while back and resolved, but i cannot find the resolution.

I need to be able to pass slashes in the final segment of my route:

i.e for a department called 'A/P'for i want to be able

https://somefunction/api/departments/A%2FP

I set up my route like this: Route = "departments/{dept}"

it works fine if there are no slashes (encoded as %2F) in the department, but if there is a slash it returns a 404.

How can I fix this?

ColbyTresness commented 5 years ago

@mattchenderson to redirect

umair8794 commented 5 years ago

@ColbyTresness I am facing the same issue. @mattchenderson can you please add something here?

magyarb commented 5 years ago

The question did not specify whether its a proxy or a function, nor the programming language, but with a js function, this function.json works:

{
  "bindings": [
    {
      "authLevel": "anonymous",
      "type": "httpTrigger",
      "direction": "in",
      "name": "req",
      "route": "departments/{*segments}",
      "methods": [
        "get"
      ]
    },
    {
      "type": "http",
      "direction": "out",
      "name": "res"
    }
  ]
}

Then, in the function, you can access the segment in req.params.segments. This is a string, and it can contain forward slashes (not sure about back slashes).

anilpras commented 4 years ago

The workaround suggested by engineering- try modifying the rule like /code/{*code}

Modify the client to send requests as https://.azurewebsites.net/code/fsdfsfdasfdsbk/asfdsfsfsdfdsfsa Instead of https://< functionapp>.azurewebsites.net/code=sdfsfdasfdsbk/asfdsfsfsdfdsfsa

Crossbow78 commented 4 years ago

We're deliberately url-encoding the slashes in order to not affect the request path, so I'm trying to understand why the %2F is decoded before route matching.

With a function route /get-resources/{keyVaultUri} a request like /get-resources/https%3A%2F%2Fsomeurl.net works fine when debugging on a local running environment (eg. IIS-express), but will result in status code 404 on a deployed Azure Web App. Why that difference?

The * wildcard suggestion seems useful, but only if you have a single url-encoded string parameter and can control it to the end of the url. But even then, in my case (using an Azure Function V3) with the above example, I receive an argument value https:/someurl.net (so it swallowed one of the slashes). And even with a request /get-resources/https%3A%2F%2F%2F%2F%2F%2Fsomeurl.net it gets trimmed down to a single slash / 😞

MatsAnd commented 3 years ago

This has to be a bug in the Azure functions runtime, as it's behaving different in debug locally and when deployed to Azure..

Anyways, I faced this issue today and figured out that it's possible to double encode the values that's sent. When doing this, the slashes in the encoded string are surviving the routing algorithm going on in the background.

Node.js example:

const encode = (id) => encodeURIComponent(encodeURIComponent(id))
const decode = (id) => decodeURIComponent(decodeURIComponent(id))

// Client side:
const id = 'TEST/ABC123'
fetch(`azure-functions-api.com/groups/${encode(id)}/members`)
  .then(console.log)
  .catch(console.error)

// Azure Functions side:
module.exports = (context, req) => {
  const { id: rawId } = req.params
  const id = decode(rawId)

  // do stuff..
}
oc002 commented 3 years ago

We are facing the same issue. Our http triggered functions are connected to Azure API Management and used by our customers. Recently one of our customers noticed that they could not use the identifier they wanted when using the APIs we expose.

After debugging we found that this was tied to the %2F issue others are facing.

Since the APIs we expose are used by our customers we cannot ask them to "double encode" their parameters before using the API.

Aslo, as noted before, the option of using the ${*param} bindining will not work when there are more than one parameter in question and perhaps said parameter is not the trailing parameter of the url.

As noted by @MatsAnd, this seems only to be the case when running the function in the Azure cloud and not while developing locally.

To solve this issue now, we might need to do the double encoding ourselves in the <inbound> rules of the API Management.

I have tried with forcing the parameter AZURE_FUNCTION_PROXY_BACKEND_URL_DECODE_SLASHES to false even though the documentation says that false is the default value. No success there.

This issue has been dormant since 2018 and seems to be linked to this https://github.com/Azure/azure-functions-host/issues/2249.

russgove commented 3 years ago

hey @ingenfakir , that sounds like a great idea. hadn’t thought of that since i hadn’t used Apim. I have forgotten the context i asked my question in, but could you please let me know if this fix works for you? thanks russell

oc002 commented 3 years ago

Does not seem possible to rewrite the uri in a generic way to support "double encoding" of /. There is a directive <find-and-replace /> but that only works for the body otherwise it could have been used as <find-and-replace from="%2F" to="%252F" />. Theres also the <rewrite-uri template="uri template" copy-unmatched-params="true | false" /> but that does not support the usecase.

I've resorted to creating a support ticket from our Tenant to Azure support. Will keep this ticket updated with the answers I receive from them.

oc002 commented 3 years ago

After many exchanges back and forth between me and the support team and the product team the solution was:

For resolution, the PG team says that simply, don’t pass a / in the URL part of the HTTP address. You might setup a URL Rewrite rule to look for this and then change it before it gets to the application.

Which means that there's nothing we can do but to prohibit the usage of url-encoded / characters in our APIs or figure out a way to double encode the parameter.

We will go with the former and ask our customers to avoid encoding / characters in the URL for now.

IanKemp commented 3 years ago

The only option here is to perform additional encoding on the URL before you call it, then perform the corresponding decoding on the receiving end. We used Base64 for this.

oc002 commented 3 years ago

Yes @IanKemp that is true, and the issue we have is that our APIs are consumed by our customers and we don't feel like communicating to them that "For all path variables please perform two encodings since our API platform does not support single encoded path parameters".

If the consumers of the APIs were internal, this would be fine. But ours are external.

IanKemp commented 3 years ago

@drownintheloch Yeah, I was just giving a possible solution for anyone who is fortunate enough to not be publishing an external API.

The fact this has been an issue since 2018 and never got any attention is just bizarre to me, but sadly par for the course for Azure Functions. Microsoft honestly doesn't seem to care, the result is that using AF as a web API has gone from "not recommended" in my books to "strongly discouraged".

jamesburtonqubithealth commented 3 years ago

I've lost an afternoon to this working on dev then breaking on Azure, before finding this slash strip/replace parameter mangling. Double encoding is a potential solution for me, but it will clearly be labelled with // HACK: Remove when Microsoft fixes Azure

laurentbel commented 2 years ago

Same issue here. Locally everything works fine, but when deploying (over a linux container), I'm getting the bug and need to double encode :-(

alphonso77 commented 2 years ago

Has anyone observed this same behavior with a NodeJS app hosted in Azure App Services?

alphonso77 commented 2 years ago

As a follow up - I ran across this same issue in an Azure-hosted NodeJS container. We had some routes that had parameters with encoded slashes (like: http://our-api.com/first-param/second-param/some%2Fname). The route worked fine running locally on a NodeJS Windows 11 x64 architecture. But it 404'd in the target Azure environment. I changed the last parameter to an encoded Query String parameter and it worked fine.

sebps commented 1 year ago

solved it as follow :

  1. set up a GET /{*path} wildcard operation for current api
  2. add the following policy to the operation inbound processing: <rewrite-uri template="{path}" copy-unmatched-params="true" />

image (2) image image (1)

mirzaciri commented 1 year ago

2. dd the following polic

This did not work for me:

image

Harshit-Shah-Ext-Volansys commented 1 year ago

It works for me without adding <rewrite-uri template="{path}" copy-unmatched-params="true" /> just need to add {*path}. Thanks @sebps

despian commented 1 year ago

It's nearly 5 years since this was opened now, and still with no direct response from Microsoft. Kinda frustrating ...

It seems @mattchenderson who was mentioned when the issue was created is still active in some MS repos. I wonder if he could shed any light on whether Microsoft will ever address this issue.

It's classified in the "Active Questions" milestone but should be a bug IMHO. It's clearly an unexpected behaviour of the cloud-hosted runtime which differs from that of running locally.

It seems likely the issue was introduced by PR Azure/azure-functions-host#2732 as mentioned earlier in this thread.

mattchenderson commented 1 year ago

Hi all - agreed that this is a valid issue, and we'll look into it. I'll double check during the triage discussion, but we may end up moving this to the host repo, just as a heads up.

Thank you all for continuing to engage on the thread here. As part of the discussion around this issue we'll also be discussing process improvements to make sure issues like this one are reviewed and responded to.

MitchBodmer commented 1 year ago

Hey @kshyju, can you share any information about the status/progress of this issue with us? Thanks!

kshyju commented 1 year ago

The current behavior is caused by the App service front end decoding the encoded request path value before forwarding the request to the application (in this case functions host instance). When doing this, the front end stores the raw request path value it received and add it as a special request header.

Below are some relevant comments from other thread where this issue was discussed before.

crosenblatt commented on Oct 14, 2022 Hi @ccromer - This behavior is present in App Service for backwards compatibility reasons. In order to get the raw bytes of the url exactly as they are received on the wire, you can use the header "X-Waws-Unencoded-Url".

and

bradygaster commented on Mar 30, 2022 After corresponding with the service team, I learned that this is By Design: IIS FE's URL parse the request URL during proxying, so the URI will be modified (canonicalized and normalized).

The work around is for the app to use the HTTP_X_WAWS_UNENCODED_URL server variable (a.k.a. X-WAWS-Unencoded-URL: header). This header contains the exact bytes as they were received from the wire.

This issue is not specific to functions. You will run into same issue when you deploy an aspnet core web api with similar route patterns as discussed above.

The fix here is to use the X-Waws-Unencoded-Url header value which has the raw request path (undecoded) and udpate the request path with that so that the routing middleware can use that and route to the correct endpoint.

I am working on a fix on the host side with the above solution(using a middleware to restore the original request path) and will share an update in this thread when that is available. Please stay tuned.

kshyju commented 8 months ago

We got confirmation from Antares team that this can be fixed on the platform level.

https://msazure.visualstudio.com/Antares/_workitems/edit/25263505 is the (internal) work item tracking the fix.

Ikaer commented 5 months ago

I've used this solution to fix the problem:

 public class UseOriginalPathMiddleware
 {
     private const string OriginalPathHeader = "X-Original-URL";
     private readonly RequestDelegate _next;

     public UseOriginalPathMiddleware(RequestDelegate next)
     {
         _next = next;
     }

     public async Task Invoke(HttpContext context)
     {
         if (context.Request.Headers.TryGetValue(OriginalPathHeader, out Microsoft.Extensions.Primitives.StringValues value))
         {
             var originalPath = value.ToString();
             context.Request.Path = originalPath;
         }

         await _next(context);
     }
 }

and then in startup before routing:

   app.UseMiddleware<UseOriginalPathMiddleware>();

  app.UseRouting();

it fix the problem of this thread, but be aware that there is another problem on asp.net core side coming after that: https://github.com/dotnet/aspnetcore/issues/11544

The encoded slash is not decoded when getting the value in route method arguments where other encoded characters are correctly decoded.

for example: path/to/my/route/{string} path/to/my/route/AAA%2fBBB%2bCCC will ended up with a string argument value "AAA%2fBBB+CCC" where %2b is decoded but not %2f

So you will have to come with a solution to decode properly the slash in the argument value, but you will not have a 404 anymore on production.

j9850s commented 1 week ago

Be aware that de X-Waws-Unencoded-Url contains the PathAndQuery When using a Uri that contains querystring your solution fails. Fix:

    public class RestoreRawRequestPathMiddleware
    {
        private const string UnencodedUrlHeaderName = "X-Waws-Unencoded-Url";
        private readonly RequestDelegate _next;

        public RestoreRawRequestPathMiddleware(RequestDelegate next)
        {
            _next = next;
        }

        public async Task Invoke(HttpContext context)
        {
            if (context.Request.Headers.TryGetValue(UnencodedUrlHeaderName, out var unencodedUrlValue) && unencodedUrlValue.Any())
            {
                if (Uri.TryCreate($"https://fake.uri{unencodedUrlValue.First()}", UriKind.Absolute, out var uri))
                {
                    context.Request.Path = new PathString(uri.AbsolutePath);
                }
            }
            await _next(context);
        }
    }