URL Rewrite v2.1

The IIS team just released URL Rewrite v2.1. The blog post below details the changes introduced in this release. You can download the latest version from https://www.iis.net/downloads/microsoft/url-rewrite or from WebPI.

Control response cacheability of URL Rewrite Rules

URL Rewrite v7.1.1909 removed `HTTP_HOST` from the set of server variables that are cacheable. This meant that any URL Rewrite rule that referred to `HTTP_HOST` in the condition or whose action is a rewrite/redirect AND set the `HTTP_HOST` as part of its action was no longer kernel cacheable. The objective of this fix was prevent customers from being stuck in rewrite loops due to cacheing as there was no way for URL Rewrite to detect loops. However, this update removed the ability for customers to allow their responses to be kernel cacheable if they knew they didn't have any redirect loops.

Introduction of a responseCacheDirective

URL Rewrite rules can be explicitily marked as cacheable by the introduction of
a new directive on the rule element- responseCacheDirective.

The responseCacheDirective accepts four possible values:

1. Always:  The response is always cacheable.
2. Never: The response is never cacheable
3. NotIfRuleMatched: The response is not cacheable if the rule matched.
4. Auto(default): URL rewrite determines the cache friendliness of the rule based on the server variables used in the rule.

The risk of entering a redirect loop has not been mitigated and hence setting responseCacheDirective to always should only used when you can verify there are no redirect loops.

What happens when you define multiple rules with different responseCacheDirective?

URL rewrite tries to match an incoming URL to a set of rules sequentially. Each of the rules has three possible results as it is applied to an incoming URL: Unmatched, URL Matched, and Rule Matched in increasing degrees of matching. Rule Matched differs from URL Matched when the rule conditions are met in addition to URL being a match.

The cacheability of the response is reconsidered with each rule, the initial state being a neutral state where URL rewrite will not instruct the kernel cache either way. If the current state changes to not cacheable, further rules are not considered for determining cacheability. In other words, a single rule among all the rules executed is enough to make the entire response uncacheable. This makes the ordering of the rules important in cases where the processing is stopped when a "Rule Matched" occurs. Consider the case where there is at least one rule that evaluates to "URL Matched" and the rule is set either as "Never" or "Auto" with cache unfriendly servers. If this rule is sequentially before the Rule that would be "Rule Matched", then it would cause the kernel cache to be disabled. If on the other hand the rule is skipped because it is after the "Rule Matched" rule, it would have no effect on the cacheability.

For a rule to have an effect on cacheability, the rule should at the minimum be "URL Matched". If "NotIfRuleMatched" is selected for the responseCacheDirective for a given rule, kernel caching for that response will be disabled if the entire rule is matched with URL and the conditions. Keep in mind that "NotIfRuleMatched" does not take into account the cache unfriendly server variables. This is also true for Never and Always, leaving Auto to be the only value where the existence of cache unfriendly server variables cause the kernel caching to be disabled.

Preserve original URL encoding

In URL Rewrite versions prior to v7.1.1980, when one tries to use UNENCODED_URL, URL Rewrite will encode it which may lead to double encoding if the original URL was already encoded This is in violation of section 2.4 of RFC3986, which says "Implementations must not percent-encode or decode the same string more than once, as decoding an already decoded string might lead to misinterpreting a percent data octet as the beginning of a percent-encoding, or vice versa in the case of percent-encoding an already percent-encoded string." It also made the use of UNENCODED_URL impractical, especially in reverse forwarder scenarios with ARR where the backend servers expect the URL to be passed unmodified.

In v7.1.1980, we are adding a feature flag, useOriginalURLEncoding that allows you to turn off this non-compliant URL Encoding when set to true. The default behavior will remain unchanged (useOriginalURLEncoding is true by default).

To further explain this, let's look at the example below where the incoming URL is https://contoso.com/ab%2fde/. In this example, the cooked representation of the URL IIS receives from HTTP.SYS is the URL once decoded /ab/de/.

When Original URL Encoding is preserved (useOriginalURLEncoding == true), the UNENCODED_URL server variable is computed by encoding the incoming URL, which leads to the double encoding ab%252f. After turning off the non-compliant behavior (useOriginalURLEncoding = false), the UNENCODED_URL is now just the incoming URL.

A more common way of using URL Rewrite is with Back-References where {R:0} represents the entire part of the URL that was matched for a rule and {R:n} represents the parts of the URL that matched to a specific part of the regular expression which is enclosed in parentheses. If there are more than one part of the RE that was enclosed in parentheses, n denotes the order within where 1<=n<=# of parentheses pairs used in the RE.

A resulting back-reference is computed by encoding the corresponding part of the cooked URL. However, since we are encoding the cooked URL, it is impossible to ascertain if the “/  was present in the original URL or it was an artifact of the first decode and hence, we will not attempt to encode it. After setting useOriginalURLEncoding to false, the back reference is now just the cooked URL.

Original URL: https://contoso.com/ab%2fde/

Rewrite rule contains useOriginalURLEncoding=true useOriginalEncoding=false
BACK_REFERENCE /ab/de/ /ab/de/
UNENCODED_URL /ab%252fde/ /ab%2fde/

Let's look at another example where the incoming URL is already double-encoded: http://contoso.com/ab%2520de/. In this example, the *cooked* representation of the URL IIS receives from HTTP.SYS is the URL once decoded /ab%20de/.

When Original URL Encoding is preserved, the UNENCODED_URL server variable is again computed by encoding the cooked URL. After turning off non-compliant behavior, the UNENCODED_URL is still just the original URL.

The back-reference is computed by encoding the cooked URL. After setting useOriginalURLEncoding to false, the URL server variable is now just the cooked URL.

Rewrite rule contains useOriginalURLEncoding=true useOriginalURLEncoding=false
BACK_REFERENCE /ab%2520de/ /ab%20de/
UNENCODED_URL /ab%252520de/ /ab%2520de/

In both examples, the useOriginalURLEncoding=false provides a way to pass the original URL unmodified by using UNENCODED_URL. This is usually the desired outcome in a reverse-proxy scenario. It also eliminates any double encoding that would have been otherwise performed by the URL Rewrite Module.

7 Comments

  • Nice to see that IIS team not sleep and working on URL Rewrite module. For more new inprovements and features, you can be inspired by UrlRewrite.Net module: https://github.com/Bikeman868/UrlRewrite.Net

  • I have implemented ARR to access SAP fiori portal through it, I have successfully open the page and login but after login an error occurred "unable to load group". I have open a case with SAP as per them.

    Cause

    You are using third-party proxy such as Microsoft IIS, Microsoft UAG, Apache, etc.

    The reverse proxy automatically decodes the entire URL before forwarding the request to the backend. '%2F' is

    decoded to '/'.

    Resolution
    Configure your proxy server to make sure it will pass the request URL not decoded.

    Note: I followed your instruction and upgrade rewirte 2.0 and configure condition {encode_url} pattern (.*) but no positive result so Please assist how can I configure proxy server to pass URL not decoded. your prompt response is requested

  • Logs from FailedReqLog file:

    0 1 0 1 0x0 RPROXY {80000019-0001-FC00-B63F-84710C7967BB} 2 fiori 1610612752 0 https://fiori1.ffbl.com:443/sap/opu/odata/UI2/INTEROP/FeedbackLegalTexts('1') GET GENERAL_REQUEST_START {D42CF7EF-DE92-473E-8B6C-621EA663113A} 0 1 4 1 0x400 RPROXY {80000019-0001-FC00-B63F-84710C7967BB} /sap/opu/odata/UI2/INTEROP/FeedbackLegalTexts('1') 0 0 URL_REWRITE_START Rewrite Global Inbound {0469ABFA-1BB2-466A-B645-E3E15A02F38B} 0 1 5 3 0x400 RPROXY {80000019-0001-FC00-B63F-84710C7967BB} ARR_FFBL_FIORI_loadbalance sap/opu/odata/UI2/INTEROP/FeedbackLegalTexts('1') 0 true / RULE_EVALUATION_START Rewrite Regex {0469ABFA-1BB2-466A-B645-E3E15A02F38B} 0 1 5 10 0x400 RPROXY {80000019-0001-FC00-B63F-84710C7967BB} .* sap/opu/odata/UI2/INTEROP/FeedbackLegalTexts('1') false true PATTERN_MATCH Rewrite {0469ABFA-1BB2-466A-B645-E3E15A02F38B} 0 1 5 5 0x400 RPROXY {80000019-0001-FC00-B63F-84710C7967BB} 0 CONDITIONS_EVALUATION_START Rewrite MatchAll {0469ABFA-1BB2-466A-B645-E3E15A02F38B} 0 1 5 11 0x400 RPROXY {80000019-0001-FC00-B63F-84710C7967BB} {HTTPS} on 0 on false true CONDITION_EVALUATION Rewrite Pattern {0469ABFA-1BB2-466A-B645-E3E15A02F38B} 0 1 5 11 0x400 RPROXY {80000019-0001-FC00-B63F-84710C7967BB} {HTTP_HOST} fiori1.ffbl.com 0 fiori1.* false true CONDITION_EVALUATION Rewrite Pattern {0469ABFA-1BB2-466A-B645-E3E15A02F38B} 0 1 5 11 0x400 RPROXY {80000019-0001-FC00-B63F-84710C7967BB} {UNENCODED_URL} /sap/opu/odata/UI2/INTEROP/FeedbackLegalTexts('1') 0 (.*) false true CONDITION_EVALUATION Rewrite Pattern {0469ABFA-1BB2-466A-B645-E3E15A02F38B} 0 1 5 6 0x400 RPROXY {80000019-0001-FC00-B63F-84710C7967BB} true CONDITIONS_EVALUATION_END Rewrite {0469ABFA-1BB2-466A-B645-E3E15A02F38B} 0 1 5 12 0x400 RPROXY {80000019-0001-FC00-B63F-84710C7967BB} http://FFBL_FIORI/{R:0} http://FFBL_FIORI/sap/opu/odata/UI2/INTEROP/FeedbackLegalTexts('1') true false REWRITE_ACTION Rewrite {0469ABFA-1BB2-466A-B645-E3E15A02F38B} 0 1 5 4 0x400 RPROXY {80000019-0001-FC00-B63F-84710C7967BB} ARR_FFBL_FIORI_loadbalance http://FFBL_FIORI/sap/opu/odata/UI2/INTEROP/FeedbackLegalTexts('1') true true RULE_EVALUATION_END Rewrite {0469ABFA-1BB2-466A-B645-E3E15A02F38B} 0 1 3 19 0x400 RPROXY {80000019-0001-FC00-B63F-84710C7967BB} REWRITE_DISABLED_KERNEL_CACHE Rewrite {0469ABFA-1BB2-466A-B645-E3E15A02F38B} 0 1 5 53 0x0 RPROXY {80000019-0001-FC00-B63F-84710C7967BB} X-Original-URL /sap/opu/odata/UI2/INTEROP/FeedbackLegalTexts('1') true GENERAL_SET_REQUEST_HEADER {D42CF7EF-DE92-473E-8B6C-621EA663113A} 0 1 4 42 0x0 RPROXY {80000019-0001-FC00-B63F-84710C7967BB} /sap/opu/odata/UI2/INTEROP/FeedbackLegalTexts('1') http://FFBL_FIORI/sap/opu/odata/UI2/INTEROP/FeedbackLegalTexts('1') URL_CHANGED {D42CF7EF-DE92-473E-8B6C-621EA663113A} 0 1 4 2 0x400 RPROXY {80000019-0001-FC00-B63F-84710C7967BB} http://FFBL_FIORI/sap/opu/odata/UI2/INTEROP/FeedbackLegalTexts('1') URL_REWRITE_END Rewrite {0469ABFA-1BB2-466A-B645-E3E15A02F38B} 0 1 4 55 0x0 RPROXY {80000019-0001-FC00-B63F-84710C7967BB} 202.125.137.133 49299 202.125.137.133 443 GENERAL_ENDPOINT_INFORMATION {D42CF7EF-DE92-473E-8B6C-621EA663113A} 0 1 4 50 0x0 RPROXY {80000019-0001-FC00-B63F-84710C7967BB} Connection: Keep-Alive Accept: application/json Accept-Encoding: gzip, deflate Accept-Language: en Cookie: sap-usercontext=sap-language=EN&sap-client=950; SAP_SESSIONID_GWA_950=xB0yA-wrScKXERgtrqXQpCqmSUNlPBHnkkYADCmrLiA%3d; MYSAPSSO2=AjQxMDMBABhBAE4AQQBXAEEAWgAxACAAIAAgACAAIAACAAY5ADUAMAADABBHAFcAQQAgACAAIAAgACAABAAYMgAwADEANwAwADcAMQAwADAANgA1ADEABQAEAAAACAYAAlgACQACRQD%2fAP0wgfoGCSqGSIb3DQEHAqCB7DCB6QIBATELMAkGBSsOAwIaBQAwCwYJKoZIhvcNAQcBMYHJMIHGAgEBMBowDjEMMAoGA1UEAxMDR1dBAggKIBcCIQkxATAJBgUrDgMCGgUAoF0wGAYJKoZIhvcNAQkDMQsGCSqGSIb3DQEHATAcBgkqhkiG9w0BCQUxDxcNMTcwNzEwMDY1MTE0WjAjBgkqhkiG9w0BCQQxFgQU6F%21vVnNheWWqgduc4Hcpg1GoZYwwCQYHKoZIzjgEAwQwMC4CFQDaNBvIRRjJIjGJBoPA9InPJvfAFQIVAOLJIEgwRRuXq0xyScxgpHFOyOkq Host: fiori1.ffbl.com Referer: https://fiori1.ffbl.com/fiori/shells/abap/FioriLaunchpad.html User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64; Trident/7.0; rv:11.0) like Gecko X-CSRF-Token: Fetch sap-language: EN MaxDataServiceVersion: 3.0 X-XHR-Logon: accept="iframe" X-Requested-With: XMLHttpRequest X-Original-URL: /sap/opu/odata/UI2/INTEROP/FeedbackLegalTexts('1') GENERAL_REQUEST_HEADERS {D42CF7EF-DE92-473E-8B6C-621EA663113A} 0 1 4 30 0x0 RPROXY {80000019-0001-FC00-B63F-84710C7967BB} 553 GENERAL_GET_URL_METADATA {D42CF7EF-DE92-473E-8B6C-621EA663113A} 0 1 4 44 0x0 RPROXY {80000019-0001-FC00-B63F-84710C7967BB} true USER_SET {D42CF7EF-DE92-473E-8B6C-621EA663113A} 0 1 4 1 0x800 RPROXY {80000019-0001-FC00-B63F-84710C7967BB} FFBL_FIORI 0 ARR_WEBFARM_ROUTED RequestRouting WeightedRoundRobin {53AE50FA-81DF-47B1-8161-71F0A1C55A48} 0 1 4 43 0x0 RPROXY {80000019-0001-FC00-B63F-84710C7967BB} ApplicationRequestRoutingHandler ApplicationRequestRouting HANDLER_CHANGED {D42CF7EF-DE92-473E-8B6C-621EA663113A} 0 1 4 2 0x800 RPROXY {80000019-0001-FC00-B63F-84710C7967BB} 3 SAPGWA.FFBL.COM 0 78 0 12 82356 222659 57 ARR_SERVER_ROUTED RequestRouting LoadBalancing Active {53AE50FA-81DF-47B1-8161-71F0A1C55A48} 0 1 5 53 0x0 RPROXY {80000019-0001-FC00-B63F-84710C7967BB} Max-Forwards 10 true GENERAL_SET_REQUEST_HEADER {D42CF7EF-DE92-473E-8B6C-621EA663113A} 0 1 5 53 0x0 RPROXY {80000019-0001-FC00-B63F-84710C7967BB} X-Forwarded-For 202.125.137.133:49299 true GENERAL_SET_REQUEST_HEADER {D42CF7EF-DE92-473E-8B6C-621EA663113A} 0 1 5 53 0x0 RPROXY {80000019-0001-FC00-B63F-84710C7967BB} X-ARR-SSL 2048|256|C=GB, S=Greater Manchester, L=Salford, O=COMODO CA Limited, CN=COMODO RSA Domain Validation Secure Server CA|OU=Domain Control Validated, OU=COMODO SSL Wildcard, CN=*.ffbl.com true GENERAL_SET_REQUEST_HEADER {D42CF7EF-DE92-473E-8B6C-621EA663113A} 0 1 5 53 0x0 RPROXY {80000019-0001-FC00-B63F-84710C7967BB} X-ARR-ClientCert true GENERAL_SET_REQUEST_HEADER {D42CF7EF-DE92-473E-8B6C-621EA663113A} 0 1 5 53 0x0 RPROXY {80000019-0001-FC00-B63F-84710C7967BB} X-ARR-LOG-ID 0ecc110c-98f1-4a81-894e-efe71f1e13c3 true GENERAL_SET_REQUEST_HEADER {D42CF7EF-DE92-473E-8B6C-621EA663113A} 0 1 5 53 0x0 RPROXY {80000019-0001-FC00-B63F-84710C7967BB} Connection true GENERAL_SET_REQUEST_HEADER {D42CF7EF-DE92-473E-8B6C-621EA663113A} 0 1 4 42 0x0 RPROXY {80000019-0001-FC00-B63F-84710C7967BB} http://FFBL_FIORI/sap/opu/odata/UI2/INTEROP/FeedbackLegalTexts('1') /sap/opu/odata/UI2/INTEROP/FeedbackLegalTexts('1') URL_CHANGED {D42CF7EF-DE92-473E-8B6C-621EA663113A} 0 1 4 3 0x800 RPROXY {80000019-0001-FC00-B63F-84710C7967BB} ARR_REQUEST_HEADERS_START RequestRouting {53AE50FA-81DF-47B1-8161-71F0A1C55A48} 0 1 4 4 0x800 RPROXY {80000019-0001-FC00-B63F-84710C7967BB} ARR_REQUEST_HEADERS_END RequestRouting {53AE50FA-81DF-47B1-8161-71F0A1C55A48} 0 1 4 7 0x800 RPROXY {80000019-0001-FC00-B63F-84710C7967BB} ARR_RESPONSE_HEADERS_START RequestRouting {53AE50FA-81DF-47B1-8161-71F0A1C55A48} 0 1 4 8 0x800 RPROXY {80000019-0001-FC00-B63F-84710C7967BB} ARR_RESPONSE_HEADERS_END RequestRouting {53AE50FA-81DF-47B1-8161-71F0A1C55A48} 0 1 4 56 0x0 RPROXY {80000019-0001-FC00-B63F-84710C7967BB} Cache-Control no-cache true GENERAL_SET_RESPONSE_HEADER {D42CF7EF-DE92-473E-8B6C-621EA663113A} 0 1 4 56 0x0 RPROXY {80000019-0001-FC00-B63F-84710C7967BB} Pragma no-cache true GENERAL_SET_RESPONSE_HEADER {D42CF7EF-DE92-473E-8B6C-621EA663113A} 0 1 4 56 0x0 RPROXY {80000019-0001-FC00-B63F-84710C7967BB} Content-Length 253 true GENERAL_SET_RESPONSE_HEADER {D42CF7EF-DE92-473E-8B6C-621EA663113A} 0 1 4 56 0x0 RPROXY {80000019-0001-FC00-B63F-84710C7967BB} Content-Type application/json; charset=utf-8 true GENERAL_SET_RESPONSE_HEADER {D42CF7EF-DE92-473E-8B6C-621EA663113A} 0 1 4 56 0x0 RPROXY {80000019-0001-FC00-B63F-84710C7967BB} Expires -1 true GENERAL_SET_RESPONSE_HEADER {D42CF7EF-DE92-473E-8B6C-621EA663113A} 0 1 4 56 0x0 RPROXY {80000019-0001-FC00-B63F-84710C7967BB} x-csrf-token 8FlI4766D14LenIO07E0Lw== false GENERAL_SET_RESPONSE_HEADER {D42CF7EF-DE92-473E-8B6C-621EA663113A} 0 1 4 56 0x0 RPROXY {80000019-0001-FC00-B63F-84710C7967BB} dataserviceversion 2.0 false GENERAL_SET_RESPONSE_HEADER {D42CF7EF-DE92-473E-8B6C-621EA663113A} 0 1 4 56 0x0 RPROXY {80000019-0001-FC00-B63F-84710C7967BB} sap-metadata-last-modified Wed, 12 Aug 2015 13:56:15 GMT false GENERAL_SET_RESPONSE_HEADER {D42CF7EF-DE92-473E-8B6C-621EA663113A} 0 1 4 56 0x0 RPROXY {80000019-0001-FC00-B63F-84710C7967BB} X-Powered-By ARR/3.0 false GENERAL_SET_RESPONSE_HEADER {D42CF7EF-DE92-473E-8B6C-621EA663113A} 0 1 5 9 0x800 RPROXY {80000019-0001-FC00-B63F-84710C7967BB} ARR_RESPONSE_ENTITY_START RequestRouting {53AE50FA-81DF-47B1-8161-71F0A1C55A48} 0 1 5 10 0x800 RPROXY {80000019-0001-FC00-B63F-84710C7967BB} 253 ARR_RESPONSE_ENTITY_END RequestRouting {53AE50FA-81DF-47B1-8161-71F0A1C55A48} 0 1 5 52 0x0 RPROXY {80000019-0001-FC00-B63F-84710C7967BB} 1 GENERAL_NOT_SEND_CUSTOM_ERROR SETSTATUS_SUCCESS {D42CF7EF-DE92-473E-8B6C-621EA663113A} 0 1 4 35 0x0 RPROXY {80000019-0001-FC00-B63F-84710C7967BB} GENERAL_FLUSH_RESPONSE_START {D42CF7EF-DE92-473E-8B6C-621EA663113A} 0 1 4 47 0x0 RPROXY {80000019-0001-FC00-B63F-84710C7967BB} Cache-Control: no-cache Pragma: no-cache Content-Length: 253 Content-Type: application/json; charset=utf-8 Expires: -1 x-csrf-token: 8FlI4766D14LenIO07E0Lw== dataserviceversion: 2.0 sap-metadata-last-modified: Wed, 12 Aug 2015 13:56:15 GMT X-Powered-By: ARR/3.0 GENERAL_RESPONSE_HEADERS {D42CF7EF-DE92-473E-8B6C-621EA663113A} 0 1 5 49 0x0 RPROXY {80000019-0001-FC00-B63F-84710C7967BB} {"d":{"__metadata":{"id":"http://fiori1.ffbl.com/sap/opu/odata/UI2/INTEROP/FeedbackLegalTexts('-1')","uri":"http://fiori1.ffbl.com/sap/opu/odata/UI2/INTEROP/FeedbackLegalTexts('-1')","type":"INTEROP.FeedbackLegalText"},"internalId":"-1","legalText":""}} GENERAL_RESPONSE_ENTITY_BUFFER {D42CF7EF-DE92-473E-8B6C-621EA663113A} 0 1 4 36 0x0 RPROXY {80000019-0001-FC00-B63F-84710C7967BB} 580 0 GENERAL_FLUSH_RESPONSE_END The operation completed successfully. (0x0) {D42CF7EF-DE92-473E-8B6C-621EA663113A} 0 1 0 2 0x0 RPROXY {80000019-0001-FC00-B63F-84710C7967BB} 580 1100 200 0 GENERAL_REQUEST_END {D42CF7EF-DE92-473E-8B6C-621EA663113A}

  • When we will have recursive search enabled in URLrewrite ? e.g i want rules to be executed from child web.config and root web.config just have reference pointing to child web.config ..

    Thanks
    Naveed

  • I don’t understand

  • Where do I set "useOriginalURLEncoding = false" in order to preserve the original URL encoding?

  • Where do I set "useOriginalURLEncoding = false" in order to preserve the original URL encoding?

Comments have been disabled for this content.