Link to home
Start Free TrialLog in
Avatar of curiouswebster
curiouswebsterFlag for United States of America

asked on

Redirecting after removing one or more query string params.

Need to Redirect after removing one or more query string params.


I am using a whitelist to remove dangerous query string params, and when done, need to redirect to whatever is left in the  query string.

I understand things may break, but am okay letting our website's existing default behavior handle it.

What is the exact command to redirect?

ActionExecutingContext filterContext is the input param of the ActionFilterAttribute

        public override void OnActionExecuting(ActionExecutingContext filterContext)

and after removing the faulty query string params from:

filterContext.HttpContext.Request.QueryString

I am ready to redirect.

                filterContext.HttpContext.Response.Redirect(filterContext.HttpContext.Request.);

Please complete the the above parameter for Redirect()

Thanks
ASKER CERTIFIED SOLUTION
Avatar of Kyle Abrahams, PMP
Kyle Abrahams, PMP
Flag of United States of America image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Avatar of curiouswebster

ASKER

Yes, but where does url come from?

I have remove faulty params of

               var value = filterContext.HttpContext.Request.QueryString[key];


using the following code

        private void RemoveParameter(NameValueCollection nameCollection, string keyToRemove)
        {
            // reflect to readonly property
            PropertyInfo isreadonly = typeof(System.Collections.Specialized.NameValueCollection).GetProperty("IsReadOnly", BindingFlags.Instance | BindingFlags.NonPublic);

            if (isreadonly != null)
            {
                // make collection editable
                isreadonly.SetValue(nameCollection, false, null);

                // remove
                nameCollection.Remove(keyToRemove);

                // make collection readonly again
                isreadonly.SetValue(nameCollection, true, null);
            }
        }

Open in new window


with the NameValueCollection  param coming from
filterContext.HttpContext.Request.QueryString
filterContext.HttpContext.Current.Request.Url or
filterContext.HttpContext.Current.Request.RawUrl

depending on what you need.
I need a string.

But there is no "Current" option available.

and both of these fail:
filterContext.HttpContext.Request.Url
filterContext.HttpContext.Request.Url.ToString()
What do you mean fail?

Are they not available?  Are they null?  

What about RawURL?
Sorry, I saw the squiggly underlining and thought it was an error. It was a warning about a null value.

It runs, but seems to be on an infinite loop, hitting breakpoints inside my Action filter forever.

Should I call the following at the end of the Action Filter?

            base.OnActionExecuting(filterContext);
Do you check if you need to redirect?  That could be the result of your loop.
I only do the redirect after I have removed a failed query param. Otherwise, not.

Is that what you're asking?

    if (badQueryStringKeys.Count > 0)
            {
                filterContext.HttpContext.Response.Redirect(filterContext.HttpContext.Request.Url.ToString()); 
            }

Open in new window

That's what I was asking.  Not sure why you would get an infinite loop then -> can you post your code?
It stopped looping infinitely.

When I removed the following...
    filterContext.Result = new RedirectResult(filterContext.HttpContext.Request.Url.ToString()); 

Open in new window


but loops two times, and crashed out with an error about appending the cookie, when I use this:

    filterContext.HttpContext.Response.Redirect(filterContext.HttpContext.Request.Url.ToString()); 

Open in new window


Here is the entire object:

 public class UrlRedirectValidationAttribute : ActionFilterAttribute
    {
        private List<string> WhiteListValues => new List<string>()
        {
            "mydomain.org"
        };
 
       public override void OnActionExecuting(ActionExecutingContext filterContext)
        {
            List<string> badQueryStringKeys = new List<string>();

            foreach (var key in filterContext.HttpContext.Request.QueryString.AllKeys)
            {
                var value = filterContext.HttpContext.Request.QueryString[key];

                if (value.IsAbsoluteUrl())
                {
                    try
                    {                      
                        var url = new Uri(value);

                        if (!url.IsUrlDomainValid(WhiteListValues))
                        {
                            badQueryStringKeys.Add(key);

                        }
                    }
                    catch (ArgumentException ex)
                    {
                        filterContext.Result = GetRedirectResult(filterContext, "Home", "Index");
                    }
                }
            }

            foreach (string badQueryStringKey in badQueryStringKeys)
            {
                RemoveParameter(filterContext.HttpContext.Request.QueryString, badQueryStringKey);
            }

            if (badQueryStringKeys.Count > 0)
            {
                //filterContext.HttpContext.Response.Redirect(filterContext.HttpContext.Request.RawUrl.Split(new[] { '?' })[0]);
                //filterContext.Result = new RedirectResult(filterContext.HttpContext.Request.Url.ToString()); LOOPS
                filterContext.HttpContext.Response.Redirect(filterContext.HttpContext.Request.Url.ToString()); 
            }

            base.OnActionExecuting(filterContext);
        }

        private void RemoveParameter(NameValueCollection nameCollection, string keyToRemove)
        {
            // reflect to readonly property
            PropertyInfo isreadonly = typeof(System.Collections.Specialized.NameValueCollection).GetProperty("IsReadOnly", BindingFlags.Instance | BindingFlags.NonPublic);

            if (isreadonly != null)
            {
                // make collection editable
                isreadonly.SetValue(nameCollection, false, null);

                // remove
                nameCollection.Remove(keyToRemove);

                // make collection readonly again
                isreadonly.SetValue(nameCollection, true, null);
            }
        }

        private ActionResult GetRedirectResult(ActionExecutingContext context, string controller, string action, string clientId = null,
            List<KeyValuePair<string, object>> additionalParameters = null)
        {
            var returnUrl = context.HttpContext.Request.RawUrl;
            var requestAccept = context.HttpContext.Request.Headers["Accept"];
            var dictionary = new RouteValueDictionary
            {
                {"controller", controller},
                {"action", action},
                {"ReturnUrl", returnUrl}
            };
            if (!string.IsNullOrEmpty(clientId))
                dictionary.Add("ClientID", clientId);
            if (additionalParameters != null)
            {
                additionalParameters.ForEach(param => dictionary.Add(param.Key, param.Value));
            }
            return new RedirectToRouteResult(dictionary);
        }
    }
}

Open in new window

Hold the phone!

The following seems to be working...

                filterContext.HttpContext.RewritePath(filterContext.HttpContext.Request.Path);
Perhaps you can comment on whether I even need that function called GetRedirectResult().

I added this so I could at least route to /Home/Index/ in the event there is some unexplained exception.

Does this make sense?
Going back to  your looping - in your code you need to pass in the sanitized url.  
           if (badQueryStringKeys.Count > 0)
            {
                //filterContext.HttpContext.Response.Redirect(filterContext.HttpContext.Request.RawUrl.Split(new[] { '?' })[0]);

                 // I believe this loops because you're passing in the same query string.  You have to redirect to the new, santizied url.              
                filterContext.Result = new RedirectResult(filterContext.HttpContext.Request.Url.ToString()); 
                return;
            }

Open in new window

Rewrite path is used to serve one page as another if that makes sense.  More info on it here: https://msdn.microsoft.com/en-us/library/sa5wkk6d(v=vs.110).aspx 

If it's working for you and you don't care then no sense going down the rabbit hole.

As far as GetRedirectResult I would rename it to GetHomeRedirect() to make it more clear.

And yes it does make sense that in an error you would want the page to go somewhere.  Though I would add some logging or send an email to yourself.

EG:  Hey, we got an error, here's the full URL so you can reproduce and see what's going on.

In theory you shouldn't need it, but theories only survive until you encounter the real world.
Can I avoid that function entirely and the /Home/Index/ redirect simply by using the following line in the Catch?

filterContext.HttpContext.Response.Redirect(filterContext.HttpContext.Request.RawUrl.Split(new[] { '?' })[0]);
filterContext.HttpContext.Response.Redirect(filterContext.HttpContext.Request.RawUrl.Split(new[] { '?' })[0]);

That will redirect to the same page without the query string.

eg:

/somepath/page?goto=XXXX

filterContext.HttpContext.Request.RawUrl.Split(new[] { '?' })[0] = /somepath/page
The home function will go to the actual home path.
> That will redirect to the same page without the query string.

Isn't that the safest thing to do?
Just depends on your logic.  I don't have enough application context to answer that.
So long as there is no exposure to a hack by using this exceptional redirect, I am okay with it. Is it safe to do?
There shouldn't be any exposure to a hack.  Only exposure would be if you allowed access based on an empty query string or if something else weird was happening.

Basically if your page handles the empty query string and you're okay with that in all context / cases (EG: user logged in / not logged in / errors, etc.) then you're fine to redirect to the same path with the empty query string.
> if your page handles the empty query string

I have been told to let the cookies fall where they may on that. We want safety first.