1 //------------------------------------------------------------------------------
2 // <copyright file="HtmlForm.cs" company="Microsoft">
3 //     Copyright (c) Microsoft Corporation.  All rights reserved.
4 // </copyright>
5 //------------------------------------------------------------------------------
6 
7 // HtmlForm.cs
8 //
9 
10 namespace System.Web.UI.HtmlControls {
11     using System.ComponentModel;
12     using System;
13     using System.Collections;
14     using System.Globalization;
15     using System.IO;
16     using System.Text;
17     using System.Web.Configuration;
18     using System.Web.Util;
19     using System.Web.UI;
20     using System.Web.UI.WebControls;
21     using System.Web.Security;
22     using System.Security.Permissions;
23 
24 
25     /// <devdoc>
26     ///    <para>
27     ///       The <see langword='HtmlForm'/> class defines the methods, properties, and
28     ///       events for the HtmlForm control. This class provides programmatic access to the
29     ///       HTML &lt;form&gt; element on the server.
30     ///    </para>
31     /// </devdoc>
32     public class HtmlForm : HtmlContainerControl {
33         private string _defaultFocus;
34         private string _defaultButton;
35         private bool _submitDisabledControls;
36         private const string _aspnetFormID = "aspnetForm";
37 
38 
39         /// <devdoc>
40         /// </devdoc>
HtmlForm()41         public HtmlForm()
42             : base("form") {
43         }
44 
45         [
46         WebCategory("Behavior"),
47         DefaultValue(""),
48         DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)
49         ]
50         public string Action {
51             get {
52                 string s = Attributes["action"];
53                 return ((s != null) ? s : String.Empty);
54             }
55             set {
56                 Attributes["action"] = MapStringAttributeToString(value);
57             }
58         }
59 
60         /// <devdoc>
61         ///     Gets or sets default button for the form
62         /// </devdoc>
63         [
64         WebCategory("Behavior"),
65         DefaultValue(""),
66         ]
67         public string DefaultButton {
68             get {
69                 if (_defaultButton == null) {
70                     return String.Empty;
71                 }
72                 return _defaultButton;
73             }
74             set {
75                 _defaultButton = value;
76             }
77         }
78 
79 
80         /// <devdoc>
81         ///     Gets or sets default focused control for the form
82         /// </devdoc>
83         [
84         WebCategory("Behavior"),
85         DefaultValue(""),
86         ]
87         public string DefaultFocus {
88             get {
89                 if (_defaultFocus == null) {
90                     return String.Empty;
91                 }
92                 return _defaultFocus;
93             }
94             set {
95                 _defaultFocus = value;
96             }
97         }
98 
99         /*
100          * Encode Type property.
101          */
102 
103         /// <devdoc>
104         ///    <para>
105         ///       Gets or sets the Enctype attribute of the form. This is
106         ///       the encoding type that browsers
107         ///       use when posting the form's data to the server.
108         ///    </para>
109         /// </devdoc>
110         [
111         WebCategory("Behavior"),
112         DefaultValue(""),
113         DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)
114         ]
115         public string Enctype {
116             get {
117                 string s = Attributes["enctype"];
118                 return ((s != null) ? s : String.Empty);
119             }
120             set {
121                 Attributes["enctype"] = MapStringAttributeToString(value);
122             }
123         }
124 
125         /*
126          * Method property.
127          */
128 
129         /// <devdoc>
130         ///    <para>
131         ///       Gets or sets the Method attribute for the form. This defines how a browser
132         ///       posts form data to the server for processing. The two common methods supported
133         ///       by all browsers are GET and POST.
134         ///    </para>
135         /// </devdoc>
136         [
137         WebCategory("Behavior"),
138         DefaultValue(""),
139         DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)
140         ]
141         public string Method {
142             get {
143                 string s = Attributes["method"];
144                 return ((s != null) ? s : "post");
145             }
146             set {
147                 Attributes["method"] = MapStringAttributeToString(value);
148             }
149         }
150 
151         /*
152          * Name property.
153          */
154 
155         /// <devdoc>
156         ///    <para>
157         ///       Gets the value of the HTML Name attribute that will be rendered to the
158         ///       browser.
159         ///    </para>
160         /// </devdoc>
161         [
162         WebCategory("Appearance"),
163         DefaultValue(""),
164         DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)
165         ]
166         public virtual string Name {
167             get {
168                 return UniqueID;
169             }
170             set {
171                 // no-op setter to prevent the name from being set
172             }
173         }
174 
175 
176         /// <devdov>
177         /// If true, forces controls disabled on the client to submit their values (thus preserving their previous postback state)
178         /// </devdoc>
179         [
180         WebCategory("Behavior"),
181         DefaultValue(false)
182         ]
183         public virtual bool SubmitDisabledControls {
184             get {
185                 return _submitDisabledControls;
186             }
187             set {
188                 _submitDisabledControls = value;
189             }
190         }
191 
192         /*
193          * Target property.
194          */
195 
196         /// <devdoc>
197         ///    <para>
198         ///       Gets or sets the Uri of the frame or window to render the results of a Form
199         ///       POST request. Developers can use this property to redirect these results to
200         ///       another browser window or frame.
201         ///    </para>
202         /// </devdoc>
203         [
204         WebCategory("Behavior"),
205         DefaultValue(""),
206         DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)
207         ]
208         public string Target {
209             get {
210                 string s = Attributes["target"];
211                 return ((s != null) ? s : String.Empty);
212             }
213 
214             set {
215                 Attributes["target"] = MapStringAttributeToString(value);
216             }
217         }
218 
219 
220         /// <devdoc>
221         /// Overridden to return a constant value or tack the ID onto the same constant value.
222         /// This fixes a bug in PocketPC which doesn't allow the name and ID of a form to be different
223         /// </devdoc>
224         public override string UniqueID {
225             get {
226                 if (NamingContainer == Page) {
227                     return base.UniqueID;
228                 }
229                 else if (this.EffectiveClientIDMode != ClientIDMode.AutoID) {
230                     return ID ?? _aspnetFormID;
231                 }
232 
233                 return _aspnetFormID;
234             }
235         }
236 
237         public override string ClientID {
238             get {
239                 if (this.EffectiveClientIDMode != ClientIDMode.AutoID) {
240                     return ID;
241                 }
242                 return base.ClientID;
243             }
244         }
245 
Render(HtmlTextWriter output)246         protected internal override void Render(HtmlTextWriter output) {
247             Page p = Page;
248             if (p == null)
249                 throw new HttpException(SR.GetString(SR.Form_Needs_Page));
250 
251 #pragma warning disable 0618    // To avoid deprecation warning
252             if (p.SmartNavigation) {
253 #pragma warning restore 0618
254                 ((IAttributeAccessor)this).SetAttribute("__smartNavEnabled", "true");
255 
256                 // Output the IFrame
257                 StringBuilder sb = new StringBuilder("<IFRAME id=\"__hifSmartNav\" name=\"__hifSmartNav\" style=\"display:none\" src=\"");
258                 sb.Append(HttpEncoderUtility.UrlEncodeSpaces(HttpUtility.HtmlAttributeEncode(Page.ClientScript.GetWebResourceUrl(typeof(HtmlForm), "SmartNav.htm"))));
259                 sb.Append("\"></IFRAME>");
260                 output.WriteLine(sb.ToString());
261             }
262 
263             base.Render(output);
264         }
265 
GetActionAttribute()266         private string GetActionAttribute() {
267             // If the Action property is nonempty, we use it instead of the current page.  This allows the developer
268             // to support scenarios like PathInfo, UrlMapping, etc. (DevDiv Bugs 164390)
269             string actionProperty = Action;
270             if (!String.IsNullOrEmpty(actionProperty)) {
271                 return actionProperty;
272             }
273 
274             string action;
275             VirtualPath clientFilePath = Context.Request.ClientFilePath;
276 
277             // ASURT 15075/11054/59970: always set the action to the current page.
278             // DevDiv Servicing 215795/Dev10 567580: The IIS URL Rewrite module and other rewrite
279             // scenarios need the postback action to be the original URL.  Note however, if Server.Transfer/Execute
280             // is used, the action will be set to the transferred/executed page, that is, the value of
281             // CurrentExecutionFilePathObject.  This is because of ASURT 59970 and the document attached to
282             // that bug, which indirectly states that things should behave this way when Transfer/Execute is used.
283             if (Context.ServerExecuteDepth == 0) {
284                 // There hasn't been any Server.Transfer or RewritePath.
285                 // ASURT 15979: need to use a relative path, not absolute
286                 action = clientFilePath.VirtualPathString;
287                 int iPos = action.LastIndexOf('/');
288                 if (iPos >= 0) {
289                     // Ensure the segment is always a relative path, so prepend a dot-segment
290                     // (RFC section 4.2 Relative Reference)
291                     action = "./" + action.Substring(iPos + 1);
292                 }
293             }
294             else {
295                 VirtualPath currentFilePath = Context.Request.CurrentExecutionFilePathObject;
296                 // Server.Transfer or RewritePath case.  We need to make the form action relative
297                 // to the original ClientFilePath (since that's where the browser thinks we are).
298                 currentFilePath = clientFilePath.MakeRelative(currentFilePath);
299                 action = currentFilePath.VirtualPathString;
300             }
301 
302             // VSWhidbey 202380: If cookieless is on, we need to add the app path modifier to the form action
303             bool cookieless = CookielessHelperClass.UseCookieless(Context, false, FormsAuthentication.CookieMode);
304             if (cookieless && Context.Request != null && Context.Response != null) {
305                 action = Context.Response.ApplyAppPathModifier(action);
306             }
307 
308             // Dev11 406986: <form> elements must have non-empty 'action' attributes to pass W3 validation.
309             // The only time this might happen is that the current file path is "", which meant that the
310             // incoming URL ended in a slash, so we can just point 'action' back to the current directory.
311             if (String.IsNullOrEmpty(action) && RenderingCompatibility >= VersionUtil.Framework45) {
312                 action = "./";
313             }
314 
315             // Dev11 177096: The action may be empty if the RawUrl does not point to a file (for e.g. http://localhost:8080/) but is never null.
316             // Empty action values work fine since the form does not emit the action attribute.
317             Debug.Assert(action != null);
318 
319             string queryString = Page.ClientQueryString;
320             // ASURT 15355: Don't lose the query string if there is one.
321             // In scriptless mobile HTML, we prepend __EVENTTARGET, et. al. to the query string.  These have to be
322             // removed from the form action.  Use new HttpValueCollection to leverage ToString(bool encoded).
323             if (!String.IsNullOrEmpty(queryString)) {
324                 action += "?" + queryString;
325             }
326 
327             return action;
328         }
329 
330 
331         /// <internalonly/>
332         /// <devdoc>
333         ///    <para> Call RegisterViewStateHandler().</para>
334         /// </devdoc>
OnInit(EventArgs e)335         protected internal override void OnInit(EventArgs e) {
336             base.OnInit(e);
337 
338             Page.SetForm(this);
339 
340             // Make sure view state is calculated (see ASURT 73020)
341             Page.RegisterViewStateHandler();
342         }
343 
344 
345         /// <devdoc>
346         /// Overridden to handle focus stuff
347         /// </devdoc>
OnPreRender(EventArgs e)348         protected internal override void OnPreRender(EventArgs e) {
349             base.OnPreRender(e);
350 
351 #pragma warning disable 0618    // To avoid deprecation warning
352             if (Page.SmartNavigation) {
353 #pragma warning restore 0618
354                 // Register the smartnav script file reference so it gets rendered
355                 Page.ClientScript.RegisterClientScriptResource(typeof(HtmlForm), "SmartNav.js");
356             }
357         }
358 
359 
360         /// <internalonly/>
361         /// <devdoc>
362         /// </devdoc>
RenderAttributes(HtmlTextWriter writer)363         protected override void RenderAttributes(HtmlTextWriter writer) {
364             ArrayList invalidAttributes = new ArrayList();
365             foreach (String key in Attributes.Keys) {
366                 if (!writer.IsValidFormAttribute(key)) {
367                     invalidAttributes.Add(key);
368                 }
369             }
370 
371             foreach (String key in invalidAttributes) {
372                 Attributes.Remove(key);
373             }
374 
375             bool enableLegacyRendering = EnableLegacyRendering;
376 
377             Page page = Page;
378             if (writer.IsValidFormAttribute("name")) {
379                 // DevDiv 27328 Do not render name attribute for uplevel browser
380                 if (page != null && page.RequestInternal != null &&
381                     RenderingCompatibility < VersionUtil.Framework40 &&
382                     (page.RequestInternal.Browser.W3CDomVersion.Major == 0 ||
383                      page.XhtmlConformanceMode != XhtmlConformanceMode.Strict)) {
384                     writer.WriteAttribute("name", Name);
385                 }
386                 Attributes.Remove("name");
387             }
388 
389             writer.WriteAttribute("method", Method);
390             Attributes.Remove("method");
391 
392             // Encode the action attribute - ASURT 66784
393             writer.WriteAttribute("action", GetActionAttribute(), true /*encode*/);
394             Attributes.Remove("action");
395 
396             // see if the page has a submit event
397             if (page != null) {
398                 string onSubmit = page.ClientOnSubmitEvent;
399                 if (!String.IsNullOrEmpty(onSubmit)) {
400                     if (Attributes["onsubmit"] != null) {
401                         // If there was an onsubmit on the form, register it as an onsubmit statement and remove it from the attribute collection
402                         string formOnSubmit = Attributes["onsubmit"];
403                         if (formOnSubmit.Length > 0) {
404                             if (!StringUtil.StringEndsWith(formOnSubmit, ';')) {
405                                 formOnSubmit += ";";
406                             }
407                             if (page.ClientSupportsJavaScript || !formOnSubmit.ToLower(CultureInfo.CurrentCulture).Contains("javascript")) {
408                                 page.ClientScript.RegisterOnSubmitStatement(typeof(HtmlForm), "OnSubmitScript", formOnSubmit);
409                             }
410                             Attributes.Remove("onsubmit");
411                         }
412                     }
413 
414                     // Don't render the on submit if it contains javascript and the page doesn't support it
415                     if (page.ClientSupportsJavaScript || !onSubmit.ToLower(CultureInfo.CurrentCulture).Contains("javascript")) {
416                         if (enableLegacyRendering) {
417                             writer.WriteAttribute("language", "javascript", false);
418                         }
419                         writer.WriteAttribute("onsubmit", onSubmit);
420                     }
421                 }
422 
423                 if ((page.RequestInternal != null) &&
424                     (page.RequestInternal.Browser.EcmaScriptVersion.Major > 0) &&
425                     (page.RequestInternal.Browser.W3CDomVersion.Major > 0)) {
426                     if (DefaultButton.Length > 0) {
427                         // Find control from the page if it's a hierarchical ID.
428                         // Dev11 bug 19915
429                         Control c = FindControlFromPageIfNecessary(DefaultButton);
430 
431                         if (c is IButtonControl) {
432                             page.ClientScript.RegisterDefaultButtonScript(c, writer, false /* UseAddAttribute */);
433                         }
434                         else {
435                             throw new InvalidOperationException(SR.GetString(SR.HtmlForm_OnlyIButtonControlCanBeDefaultButton, ID));
436                         }
437                     }
438                 }
439             }
440 
441 
442             // We always want the form to have an id on the client
443             // base.RenderAttributes takes care of actually rendering it.
444             EnsureID();
445 
446             base.RenderAttributes(writer);
447         }
448 
449 
450         /// <internalonly/>
451         /// <devdoc>
452         /// </devdoc>
RenderChildren(HtmlTextWriter writer)453         protected internal override void RenderChildren(HtmlTextWriter writer) {
454             // We need to register the script here since other controls might register
455             // for focus during PreRender
456             Page page = Page;
457             if (page != null) {
458                 page.OnFormRender();
459                 page.BeginFormRender(writer, UniqueID);
460             }
461 
462             // DevDiv Bugs 154630: move custom hidden fields to the begining of the form
463             HttpWriter httpWriter = writer.InnerWriter as HttpWriter;
464             if (page != null && httpWriter != null && RuntimeConfig.GetConfig(Context).Pages.RenderAllHiddenFieldsAtTopOfForm) {
465                 // If the response is flushed or cleared during render, we won't be able
466                 // to move the hidden fields.  Set HasBeenClearedRecently to false and
467                 // then check again when we're ready to move the fields.
468                 httpWriter.HasBeenClearedRecently = false;
469 
470                 // Remember the index where the form begins
471                 int formBeginIndex = httpWriter.GetResponseBufferCountAfterFlush();
472 
473                 base.RenderChildren(writer);
474 
475                 // Remember the index where the custom hidden fields begin
476                 int fieldsBeginIndex = httpWriter.GetResponseBufferCountAfterFlush();
477 
478                 page.EndFormRenderHiddenFields(writer, UniqueID);
479 
480                 // we can only move the hidden fields if the response has not been flushed or cleared
481                 if (!httpWriter.HasBeenClearedRecently) {
482                     int fieldsEndIndex = httpWriter.GetResponseBufferCountAfterFlush();
483                     httpWriter.MoveResponseBufferRangeForward(fieldsBeginIndex, fieldsEndIndex - fieldsBeginIndex, formBeginIndex);
484                 }
485 
486                 page.EndFormRenderArrayAndExpandoAttribute(writer, UniqueID);
487                 page.EndFormRenderPostBackAndWebFormsScript(writer, UniqueID);
488                 page.OnFormPostRender(writer);
489             }
490             else {
491                 base.RenderChildren(writer);
492 
493                 if (page != null) {
494                     page.EndFormRender(writer, UniqueID);
495                     page.OnFormPostRender(writer);
496                 }
497             }
498         }
499 
RenderControl(HtmlTextWriter writer)500         public override void RenderControl(HtmlTextWriter writer) {
501             if (DesignMode) {
502                 // User Control Designer scenario
503                 base.RenderChildren(writer);
504             }
505             else {
506                 base.RenderControl(writer);
507             }
508         }
509 
CreateControlCollection()510         protected override ControlCollection CreateControlCollection() {
511             return new ControlCollection(this, 100, 2);
512         }
513     }
514 }
515