diff --git a/src/Umbraco.Web/HtmlHelperRenderExtensions.cs b/src/Umbraco.Web/HtmlHelperRenderExtensions.cs index 79d6ec3f7c..1a0bee2586 100644 --- a/src/Umbraco.Web/HtmlHelperRenderExtensions.cs +++ b/src/Umbraco.Web/HtmlHelperRenderExtensions.cs @@ -188,23 +188,29 @@ namespace Umbraco.Web /// internal class UmbracoForm : MvcForm { - /// - /// Creates an UmbracoForm - /// - /// - /// - /// - /// - /// - public UmbracoForm( + + + /// + /// Creates an UmbracoForm + /// + /// + /// + /// + /// + /// + /// + public UmbracoForm( ViewContext viewContext, string surfaceController, string surfaceAction, string area, + FormMethod method, object additionalRouteVals = null) : base(viewContext) { - //need to create a params string as Base64 to put into our hidden field to use during the routes + _viewContext = viewContext; + _method = method; + //need to create a params string as Base64 to put into our hidden field to use during the routes var surfaceRouteParams = string.Format("c={0}&a={1}&ar={2}", viewContext.HttpContext.Server.UrlEncode(surfaceController), viewContext.HttpContext.Server.UrlEncode(surfaceAction), @@ -212,21 +218,19 @@ namespace Umbraco.Web var additionalRouteValsAsQuery = additionalRouteVals != null ? additionalRouteVals.ToDictionary().ToQueryString() : null; - if (!additionalRouteValsAsQuery.IsNullOrWhiteSpace()) + if (additionalRouteValsAsQuery.IsNullOrWhiteSpace() == false) surfaceRouteParams += "&" + additionalRouteValsAsQuery; - if (!string.IsNullOrWhiteSpace(surfaceRouteParams)) + if (string.IsNullOrWhiteSpace(surfaceRouteParams) == false) { _encryptedString = surfaceRouteParams.EncryptWithMachineKey(); } - - _textWriter = viewContext.Writer; } - + private readonly ViewContext _viewContext; + private readonly FormMethod _method; private bool _disposed; private readonly string _encryptedString; - private readonly TextWriter _textWriter; protected override void Dispose(bool disposing) { @@ -234,13 +238,46 @@ namespace Umbraco.Web return; this._disposed = true; - //write out the hidden surface form routes - _textWriter.Write(""); + //Need to ensure any stale GET cookies are cleared before continuing + foreach (var c in _viewContext.HttpContext.Request.Cookies.AllKeys.Where(x => x.StartsWith("ufprt_"))) + { + _viewContext.HttpContext.Request.Cookies[c].Expires = DateTime.Now.AddDays(-1); + _viewContext.HttpContext.Response.SetCookie(_viewContext.HttpContext.Request.Cookies[c]); + } + + if (_method == FormMethod.Post) + { + //write out the hidden surface form routes + _viewContext.Writer.Write(""); + } + else + { + //since we are getting and we don't want this ugly value in the query string, we'll chuck it into a cookie with an id so we can retreive + //it on the server side with the same id and then remove it. + var id = Guid.NewGuid().ToString("N"); + var cookie = new HttpCookie("ufprt_" + id, _encryptedString); + _viewContext.HttpContext.Response.SetCookie(cookie); + + _viewContext.Writer.Write(""); + } + base.Dispose(disposing); } } + /// + /// Helper method to create a new form to execute in the Umbraco request pipeline against a locally declared controller + /// + /// + /// + /// + /// + /// + public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, string controllerName, FormMethod method) + { + return html.BeginUmbracoForm(action, controllerName, null, new Dictionary(), method); + } /// /// Helper method to create a new form to execute in the Umbraco request pipeline against a locally declared controller @@ -254,6 +291,20 @@ namespace Umbraco.Web return html.BeginUmbracoForm(action, controllerName, null, new Dictionary()); } + /// + /// Helper method to create a new form to execute in the Umbraco request pipeline against a locally declared controller + /// + /// + /// + /// + /// + /// + /// + public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, string controllerName, object additionalRouteVals, FormMethod method) + { + return html.BeginUmbracoForm(action, controllerName, additionalRouteVals, new Dictionary(), method); + } + /// /// Helper method to create a new form to execute in the Umbraco request pipeline against a locally declared controller /// @@ -267,7 +318,25 @@ namespace Umbraco.Web return html.BeginUmbracoForm(action, controllerName, additionalRouteVals, new Dictionary()); } - /// + /// + /// Helper method to create a new form to execute in the Umbraco request pipeline against a locally declared controller + /// + /// + /// + /// + /// + /// + /// + /// + public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, string controllerName, + object additionalRouteVals, + object htmlAttributes, + FormMethod method) + { + return html.BeginUmbracoForm(action, controllerName, additionalRouteVals, htmlAttributes.ToDictionary(), method); + } + + /// /// Helper method to create a new form to execute in the Umbraco request pipeline against a locally declared controller /// /// @@ -283,7 +352,28 @@ namespace Umbraco.Web return html.BeginUmbracoForm(action, controllerName, additionalRouteVals, htmlAttributes.ToDictionary()); } - /// + /// + /// Helper method to create a new form to execute in the Umbraco request pipeline against a locally declared controller + /// + /// + /// + /// + /// + /// + /// + /// + public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, string controllerName, + object additionalRouteVals, + IDictionary htmlAttributes, + FormMethod method) + { + Mandate.ParameterNotNullOrEmpty(action, "action"); + Mandate.ParameterNotNullOrEmpty(controllerName, "controllerName"); + + return html.BeginUmbracoForm(action, controllerName, "", additionalRouteVals, htmlAttributes, method); + } + + /// /// Helper method to create a new form to execute in the Umbraco request pipeline against a locally declared controller /// /// @@ -302,6 +392,19 @@ namespace Umbraco.Web return html.BeginUmbracoForm(action, controllerName, "", additionalRouteVals, htmlAttributes); } + /// + /// Helper method to create a new form to execute in the Umbraco request pipeline to a surface controller plugin + /// + /// + /// + /// The surface controller to route to + /// + /// + public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, Type surfaceType, FormMethod method) + { + return html.BeginUmbracoForm(action, surfaceType, null, new Dictionary(), method); + } + /// /// Helper method to create a new form to execute in the Umbraco request pipeline to a surface controller plugin /// @@ -314,6 +417,20 @@ namespace Umbraco.Web return html.BeginUmbracoForm(action, surfaceType, null, new Dictionary()); } + /// + /// Helper method to create a new form to execute in the Umbraco request pipeline to a surface controller plugin + /// + /// + /// + /// + /// + /// + public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, FormMethod method) + where T : SurfaceController + { + return html.BeginUmbracoForm(action, typeof(T), method); + } + /// /// Helper method to create a new form to execute in the Umbraco request pipeline to a surface controller plugin /// @@ -327,6 +444,21 @@ namespace Umbraco.Web return html.BeginUmbracoForm(action, typeof(T)); } + /// + /// Helper method to create a new form to execute in the Umbraco request pipeline to a surface controller plugin + /// + /// + /// + /// The surface controller to route to + /// + /// + /// + public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, Type surfaceType, + object additionalRouteVals, FormMethod method) + { + return html.BeginUmbracoForm(action, surfaceType, additionalRouteVals, new Dictionary(), method); + } + /// /// Helper method to create a new form to execute in the Umbraco request pipeline to a surface controller plugin /// @@ -341,6 +473,21 @@ namespace Umbraco.Web return html.BeginUmbracoForm(action, surfaceType, additionalRouteVals, new Dictionary()); } + /// + /// Helper method to create a new form to execute in the Umbraco request pipeline to a surface controller plugin + /// + /// + /// + /// + /// + /// + /// + public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, object additionalRouteVals, FormMethod method) + where T : SurfaceController + { + return html.BeginUmbracoForm(action, typeof(T), additionalRouteVals, method); + } + /// /// Helper method to create a new form to execute in the Umbraco request pipeline to a surface controller plugin /// @@ -355,7 +502,25 @@ namespace Umbraco.Web return html.BeginUmbracoForm(action, typeof(T), additionalRouteVals); } - /// + /// + /// Helper method to create a new form to execute in the Umbraco request pipeline to a surface controller plugin + /// + /// + /// + /// The surface controller to route to + /// + /// + /// + /// + public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, Type surfaceType, + object additionalRouteVals, + object htmlAttributes, + FormMethod method) + { + return html.BeginUmbracoForm(action, surfaceType, additionalRouteVals, htmlAttributes.ToDictionary(), method); + } + + /// /// Helper method to create a new form to execute in the Umbraco request pipeline to a surface controller plugin /// /// @@ -371,7 +536,25 @@ namespace Umbraco.Web return html.BeginUmbracoForm(action, surfaceType, additionalRouteVals, htmlAttributes.ToDictionary()); } - /// + /// + /// Helper method to create a new form to execute in the Umbraco request pipeline to a surface controller plugin + /// + /// + /// + /// + /// + /// + /// + public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, + object additionalRouteVals, + object htmlAttributes, + FormMethod method) + where T : SurfaceController + { + return html.BeginUmbracoForm(action, typeof(T), additionalRouteVals, htmlAttributes, method); + } + + /// /// Helper method to create a new form to execute in the Umbraco request pipeline to a surface controller plugin /// /// @@ -388,7 +571,40 @@ namespace Umbraco.Web return html.BeginUmbracoForm(action, typeof(T), additionalRouteVals, htmlAttributes); } - /// + /// + /// Helper method to create a new form to execute in the Umbraco request pipeline to a surface controller plugin + /// + /// + /// + /// The surface controller to route to + /// + /// + /// + /// + public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, Type surfaceType, + object additionalRouteVals, + IDictionary htmlAttributes, + FormMethod method) + { + Mandate.ParameterNotNullOrEmpty(action, "action"); + Mandate.ParameterNotNull(surfaceType, "surfaceType"); + + var area = ""; + + var surfaceController = SurfaceControllerResolver.Current.RegisteredSurfaceControllers + .SingleOrDefault(x => x == surfaceType); + if (surfaceController == null) + throw new InvalidOperationException("Could not find the surface controller of type " + surfaceType.FullName); + var metaData = PluginController.GetMetadata(surfaceController); + if (metaData.AreaName.IsNullOrWhiteSpace() == false) + { + //set the area to the plugin area + area = metaData.AreaName; + } + return html.BeginUmbracoForm(action, metaData.ControllerName, area, additionalRouteVals, htmlAttributes, method); + } + + /// /// Helper method to create a new form to execute in the Umbraco request pipeline to a surface controller plugin /// /// @@ -400,26 +616,30 @@ namespace Umbraco.Web public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, Type surfaceType, object additionalRouteVals, IDictionary htmlAttributes) - { - Mandate.ParameterNotNullOrEmpty(action, "action"); - Mandate.ParameterNotNull(surfaceType, "surfaceType"); + { + return html.BeginUmbracoForm(action, surfaceType, additionalRouteVals, htmlAttributes, FormMethod.Post); + } - var area = ""; - - var surfaceController = SurfaceControllerResolver.Current.RegisteredSurfaceControllers - .SingleOrDefault(x => x == surfaceType); - if (surfaceController == null) - throw new InvalidOperationException("Could not find the surface controller of type " + surfaceType.FullName); - var metaData = PluginController.GetMetadata(surfaceController); - if (!metaData.AreaName.IsNullOrWhiteSpace()) - { - //set the area to the plugin area - area = metaData.AreaName; - } - return html.BeginUmbracoForm(action, metaData.ControllerName, area, additionalRouteVals, htmlAttributes); - } + /// + /// Helper method to create a new form to execute in the Umbraco request pipeline to a surface controller plugin + /// + /// + /// + /// + /// + /// + /// + /// + public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, + object additionalRouteVals, + IDictionary htmlAttributes, + FormMethod method) + where T : SurfaceController + { + return html.BeginUmbracoForm(action, typeof(T), additionalRouteVals, htmlAttributes, method); + } - /// + /// /// Helper method to create a new form to execute in the Umbraco request pipeline to a surface controller plugin /// /// @@ -436,6 +656,20 @@ namespace Umbraco.Web return html.BeginUmbracoForm(action, typeof(T), additionalRouteVals, htmlAttributes); } + /// + /// Helper method to create a new form to execute in the Umbraco request pipeline to a surface controller plugin + /// + /// + /// + /// + /// + /// + /// + public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, string controllerName, string area, FormMethod method) + { + return html.BeginUmbracoForm(action, controllerName, area, null, new Dictionary(), method); + } + /// /// Helper method to create a new form to execute in the Umbraco request pipeline to a surface controller plugin /// @@ -449,7 +683,30 @@ namespace Umbraco.Web return html.BeginUmbracoForm(action, controllerName, area, null, new Dictionary()); } - /// + /// + /// Helper method to create a new form to execute in the Umbraco request pipeline to a surface controller plugin + /// + /// + /// + /// + /// + /// + /// + /// + /// + public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, string controllerName, string area, + object additionalRouteVals, + IDictionary htmlAttributes, + FormMethod method) + { + Mandate.ParameterNotNullOrEmpty(action, "action"); + Mandate.ParameterNotNullOrEmpty(controllerName, "controllerName"); + + var formAction = UmbracoContext.Current.OriginalRequestUrl.PathAndQuery; + return html.RenderForm(formAction, method, htmlAttributes, controllerName, action, area, additionalRouteVals); + } + + /// /// Helper method to create a new form to execute in the Umbraco request pipeline to a surface controller plugin /// /// @@ -462,13 +719,9 @@ namespace Umbraco.Web public static MvcForm BeginUmbracoForm(this HtmlHelper html, string action, string controllerName, string area, object additionalRouteVals, IDictionary htmlAttributes) - { - Mandate.ParameterNotNullOrEmpty(action, "action"); - Mandate.ParameterNotNullOrEmpty(controllerName, "controllerName"); - - var formAction = UmbracoContext.Current.OriginalRequestUrl.PathAndQuery; - return html.RenderForm(formAction, FormMethod.Post, htmlAttributes, controllerName, action, area, additionalRouteVals); - } + { + return html.BeginUmbracoForm(action, controllerName, area, additionalRouteVals, htmlAttributes, FormMethod.Post); + } /// /// This renders out the form for us @@ -496,7 +749,7 @@ namespace Umbraco.Web { //ensure that the multipart/form-data is added to the html attributes - if (!htmlAttributes.ContainsKey("enctype")) + if (htmlAttributes.ContainsKey("enctype") == false) { htmlAttributes.Add("enctype", "multipart/form-data"); } @@ -507,7 +760,7 @@ namespace Umbraco.Web tagBuilder.MergeAttribute("action", formAction); // method is an explicit parameter, so it takes precedence over the htmlAttributes. tagBuilder.MergeAttribute("method", HtmlHelper.GetFormMethodString(method), true); - var traditionalJavascriptEnabled = htmlHelper.ViewContext.ClientValidationEnabled && !htmlHelper.ViewContext.UnobtrusiveJavaScriptEnabled; + var traditionalJavascriptEnabled = htmlHelper.ViewContext.ClientValidationEnabled && htmlHelper.ViewContext.UnobtrusiveJavaScriptEnabled == false; if (traditionalJavascriptEnabled) { // forms must have an ID for client validation @@ -516,7 +769,7 @@ namespace Umbraco.Web htmlHelper.ViewContext.Writer.Write(tagBuilder.ToString(TagRenderMode.StartTag)); //new UmbracoForm: - var theForm = new UmbracoForm(htmlHelper.ViewContext, surfaceController, surfaceAction, area, additionalRouteVals); + var theForm = new UmbracoForm(htmlHelper.ViewContext, surfaceController, surfaceAction, area, method, additionalRouteVals); if (traditionalJavascriptEnabled) { diff --git a/src/Umbraco.Web/Mvc/RenderRouteHandler.cs b/src/Umbraco.Web/Mvc/RenderRouteHandler.cs index 6756847980..ef894d266f 100644 --- a/src/Umbraco.Web/Mvc/RenderRouteHandler.cs +++ b/src/Umbraco.Web/Mvc/RenderRouteHandler.cs @@ -109,23 +109,52 @@ namespace Umbraco.Web.Mvc /// /// Checks the request and query strings to see if it matches the definition of having a Surface controller - /// posted value, if so, then we return a PostedDataProxyInfo object with the correct information. + /// posted/get value, if so, then we return a PostedDataProxyInfo object with the correct information. /// /// /// - private static PostedDataProxyInfo GetPostedFormInfo(RequestContext requestContext) - { - if (requestContext.HttpContext.Request.RequestType != "POST") - return null; + private static PostedDataProxyInfo GetFormInfo(RequestContext requestContext) + { + //if it is a POST/GET then a value must be in the request + if ((requestContext.HttpContext.Request.RequestType == "POST" || requestContext.HttpContext.Request.RequestType == "GET") + && requestContext.HttpContext.Request["ufprt"].IsNullOrWhiteSpace()) + { + return null; + } - //this field will contain a base64 encoded version of the surface route vals - if (requestContext.HttpContext.Request["uformpostroutevals"].IsNullOrWhiteSpace()) - return null; + string encodedVal; + + switch (requestContext.HttpContext.Request.RequestType) + { + case "POST": + //get the value from the request. + //this field will contain an encrypted version of the surface route vals. + encodedVal = requestContext.HttpContext.Request["ufprt"]; + break; + case "GET": + //get the value from the cookie based on the id sent as a query string. + var cookieId = "ufprt_" + requestContext.HttpContext.Request["ufprt"]; + if (requestContext.HttpContext.Request.Cookies[cookieId] == null || requestContext.HttpContext.Request.Cookies[cookieId].Value.IsNullOrWhiteSpace()) + { + LogHelper.Warn("Umbraco cannot process the GET form action, could not find the required cookie value for cookie name " + cookieId); + //we cannot continue if there is no cookie value + return null; + } - var encodedVal = requestContext.HttpContext.Request["uformpostroutevals"]; - var decryptedString = encodedVal.DecryptWithMachineKey(); - var parsedQueryString = HttpUtility.ParseQueryString(decryptedString); + //we need to ensure the cookie is gone + var outgoingCookie = requestContext.HttpContext.Request.Cookies[cookieId]; + outgoingCookie.Expires = DateTime.Now.AddDays(-1); + requestContext.HttpContext.Response.SetCookie(outgoingCookie); + //this field will contain an encrypted version of the surface route vals. + encodedVal = requestContext.HttpContext.Request.Cookies[cookieId].Value; + break; + default: + return null; + } + + var decryptedString = encodedVal.DecryptWithMachineKey(); + var parsedQueryString = HttpUtility.ParseQueryString(decryptedString); var decodedParts = new Dictionary(); foreach (var key in parsedQueryString.AllKeys) @@ -336,7 +365,7 @@ namespace Umbraco.Web.Mvc var routeDef = GetUmbracoRouteDefinition(requestContext, publishedContentRequest); //Need to check for a special case if there is form data being posted back to an Umbraco URL - var postedInfo = GetPostedFormInfo(requestContext); + var postedInfo = GetFormInfo(requestContext); if (postedInfo != null) { return HandlePostedValues(requestContext, postedInfo);