Categories
Technology

Invalid State Crashes Client?

It’s the infamous view state again. My my my. I knew there were reasons I really didn’t dig the complexity ASP.NET were trying to cover up. This time though, my sense of amazement reached new heights when the client browser crashed (yip-crashed) because the server picked up some dodgy view state. Read that again. Carefully.
Oh. And you probably might have already guessed which browser does the crashing. No prizes for getting it right, sorry. The correct and predictable, even appropriate response is: “The view state is invalid for this page and might be corrupted” which is what all the other browsers I’ve tested on so far show. On with the experiment…

We’ll start off with a simple ASPX page. Nothing complicated.

<html>
	<script language="C#" runat="server">
  public void Page_Load(Object sender, EventArgs E)
  {
  }

  public void Submit_Click(Object sender, EventArgs E)
  {
  }
	</script>
	<body>
		<form id="Form1" runat="server">
			<asp:Button ID="Submit" OnClick="Submit_Click" Text="Submit" Runat="server"></asp:Button>
		</form>
	</body>
</html>

Click on the button and woohoo- no surprises. But now we add a little extra something. We modify the contents of the page after it’s loaded. Nothing special here either really. Standard AJAX behaviour.

<asp:Button ID="Submit" OnClick="Submit_Click" Text="Submit" Runat="server"></asp:Button>
<div id="dynamic_content"></div>
<script type="text/javascript">
//<![CDATA[
  window.onload = get_dynamic_content;
  function get_dynamic_content() {
    //....
  }
//]]>
</script>

The JavaScript function get_dynamic_content() will make a request and then proceed to populate the div dynamic_content with some more HTML. The obvious solution is to do something along the lines of:

function get_dynamic_content() {
  document.getElementById('dynamic_content').innerHTML = '...some html...';
}

Incidentally, therein lies another rub in that div.innerHTML behaves differently for IE. Take a peek for yourself. And within all those comments, of this article, there lies a hack solution. You can read more about that debate there. Moving along.

So having hacked the little piece of innerHTML magic, you start populating your element with all sorts of dynamic content. Wonderful. Then during a series of development efforts, you get a buggy piece of HTML tagging along for the ride. A second form element- don’t worry, not a server-side form. More about why and why not here if you not too sure about how that fits into the scheme of things. Anyway, so you get an erroneous piece of HTML coming through- a valid bug in the development effort- and your second form contains an extra view state element. By now, you’ve also probably deciphered the design and architecture of this piece of handy work.

And i’ll recount here- this may be a valid bug, but it’s not about the bug. It’s the way the client browser handles a server-side bug.

So let’s assume that part of the payload (simplified) includes the following:

<form>
...
<input type="hidden" name="__VIEWSTATE" id="__VIEWSTATE" value="/wEWAgL9hvuOBAK8w4S2BGRm0HOf1YSq2O4B5nwrlp8eUNwR" />
...
</form>

You click on Submit. Is this what you would be expecting?

Familiar Browser Crash
Familiar Browser Crash

It’s not even bad HTML. In fact, if the dynamic payload didn’t include the extra form element, just the viewstate ala

<input type="hidden" name="__VIEWSTATE" id="__VIEWSTATE" value="/wEWAgL9hvuOBAK8w4S2BGRm0HOf1YSq2O4B5nwrlp8eUNwR" />

You get the reasonable and correct behaviour. ASP.NET complains about the view state being fudged. Good. But wrap that extra view state input element in a form tag and IE client blows up. WTF? Incidentally, the form wrapper is arguably more correct.

Here’s the full code for your enjoyment. Modify the content variable and element type of dynamic_content and test across all your browsers.

<html>
	<script language="C#" runat="server">
  public void Page_Load(Object sender, EventArgs E)
  {
  }

  public void Submit_Click(Object sender, EventArgs E)
  {
  }
	</script>
	<body>
		<form id="Form1" runat="server">
			<asp:Button ID="Submit" OnClick="Submit_Click" Text="Submit" Runat="server"></asp:Button>
			<div id="dynamic_content"></div>
      <script type="text/javascript">
      //<![CDATA[
        window.onload = get_dynamic_content;
        function get_dynamic_content() {
          var content = '<form><input type="hidden" name="__VIEWSTATE" id="__VIEWSTATE" value="/wEWAgL9hvuOBAK8w4S2BGRm0HOf1YSq2O4B5nwrlp8eUNwR" /></form>';
          //document.getElementById('dynamic_content').innerHTML = content;
          swap_content_div(document.getElementById('dynamic_content'), content);
        } 
        function swap_content_div(old_div, content) {
          someHtml = content;
          var newDiv = document.createElement(old_div.tagName);
          newDiv.id = old_div.id;
          newDiv.className = old_div.className;
          newDiv.innerHTML = someHtml;
          old_div.parentNode.replaceChild(newDiv, old_div);
        }
      //]]>
      </script>
		</form>
	</body>
</html>