Set-Cookie header broken in WebHeaderCollection

J

Jason Collins

There seems to be an inconsistency (bug?) in the way the Set-Cookie header
is handled by the WebHeaderCollection. That is, the values of Set-Cookie,
when an Expires is specified, contain the "," character. This seems to be
incorrectly parsed during GetValues().

A simple example shows it best (there are 2 .aspx pages and an output):

AddHeaders.aspx:

<%@ Page language="c#" %>
<html>
<body>
<script language=C# runat=server>
private void Page_Load(object sender, System.EventArgs e)
{
Response.AddHeader("No-comma", "value1");
Response.AddHeader("No-comma", "value2");

Response.AddHeader("Comma", "a,b");
Response.AddHeader("Comma", "c,d");

HttpCookie c1 = new HttpCookie("n1", "v1");
c1.Expires = DateTime.Now.AddHours(4);
Response.Cookies.Add(c1);

HttpCookie c2 = new HttpCookie("n2", "v2");
c2.Expires = DateTime.Now.AddHours(4);
Response.Cookies.Add(c2);
}
</script>
</body>
</html>


GetHeaders.aspx:

<%@ Page language="c#" %>
<HTML>
<body>
<asp:Label id="Label1" runat="server">Label</asp:Label>
<script language="C#" runat="server">
private void Page_Load(object sender, System.EventArgs e)
{
// make a resquest for the other page (AddHeaders.aspx)
System.Net.HttpWebRequest proxyRequest = (System.Net.HttpWebRequest)
System.Net.WebRequest.Create("http://localhost/WebRequestTest/AddHeaders.asp
x");

System.Net.HttpWebResponse proxyResponse = (System.Net.HttpWebResponse)
proxyRequest.GetResponse();

System.Text.StringBuilder sb = new System.Text.StringBuilder();
foreach (string key in proxyResponse.Headers.Keys)
{
string[] values = proxyResponse.Headers.GetValues(key);
for (int i=0; i<values.Length; i++)
sb.AppendFormat("header {0}={1}<br>", key, values);
}
Label1.Text = sb.ToString();
}
</script>
</body>
</HTML>


This yields the output:

header Server=Microsoft-IIS/5.1
header Date=Mon, 10 May 2004 16:29:06 GMT
header X-AspNet-Version=1.1.4322
header No-comma=value1
header No-comma=value2
header Comma=a,b
header Comma=c,d
header Set-Cookie=ASP.NET_SessionId=pyum1s45wwc5wdzhndrzyeqh; path=/
header Set-Cookie=n1=v1; expires=Mon
header Set-Cookie=10-May-2004 20:29:06 GMT; path=/
header Set-Cookie=n2=v2; expires=Mon
header Set-Cookie=10-May-2004 20:29:06 GMT; path=/
header Cache-Control=private
header Content-Type=text/html; charset=utf-8
header Content-Length=42

Note how the Set-Cookie multi-value headers have been incorrectly broken up.
They should look like this:

header Set-Cookie=n1=v1; expires=Mon, 10-May-2004 20:29:06 GMT; path=/
header Set-Cookie=n2=v2; expires=Mon, 10-May-2004 20:29:06 GMT; path=/

I know that I can use the CookieContainer to correctly parse out these
values, but I'd rather not (for efficiency); I just want to deal with raw
headers.

Is this a bug, or am I using the framework (1.1) incorrectly?

j
 
F

Feroze [MSFT]

WebHeaderCollection is a general purpose class for header manipulation. It
recognizes multivalued headers from the commas delimiting them. It does not
do context sensitive parsing. Calling GetValues() will give you unexpected
results for some headers which have commas in them (eg: Cookie,
WWW-Authenticate etc).

If the server is sending cookies, then on the client you should use
CookieContainer/CookieCollection etc classes (from System.Net namespace).
These apis' will do the parsing for you and you can query the expires etc
properties from that class.

hope this helps.

--
feroze
http://weblogs.asp.net/feroze_daud
============

Remove "user" from the email address to reply to the author.

This posting is provided "AS IS" with no warranties, and confers no rights

Use of included script samples are subject to the terms specified at
http://www.microsoft.com/info/cpyright.htm




Jason Collins said:
There seems to be an inconsistency (bug?) in the way the Set-Cookie header
is handled by the WebHeaderCollection. That is, the values of Set-Cookie,
when an Expires is specified, contain the "," character. This seems to be
incorrectly parsed during GetValues().

A simple example shows it best (there are 2 .aspx pages and an output):

AddHeaders.aspx:

<%@ Page language="c#" %>
<html>
<body>
<script language=C# runat=server>
private void Page_Load(object sender, System.EventArgs e)
{
Response.AddHeader("No-comma", "value1");
Response.AddHeader("No-comma", "value2");

Response.AddHeader("Comma", "a,b");
Response.AddHeader("Comma", "c,d");

HttpCookie c1 = new HttpCookie("n1", "v1");
c1.Expires = DateTime.Now.AddHours(4);
Response.Cookies.Add(c1);

HttpCookie c2 = new HttpCookie("n2", "v2");
c2.Expires = DateTime.Now.AddHours(4);
Response.Cookies.Add(c2);
}
</script>
</body>
</html>


GetHeaders.aspx:

<%@ Page language="c#" %>
<HTML>
<body>
<asp:Label id="Label1" runat="server">Label</asp:Label>
<script language="C#" runat="server">
private void Page_Load(object sender, System.EventArgs e)
{
// make a resquest for the other page (AddHeaders.aspx)
System.Net.HttpWebRequest proxyRequest = (System.Net.HttpWebRequest)
System.Net.WebRequest.Create("http://localhost/WebRequestTest/AddHeaders.asp
x");

System.Net.HttpWebResponse proxyResponse = (System.Net.HttpWebResponse)
proxyRequest.GetResponse();

System.Text.StringBuilder sb = new System.Text.StringBuilder();
foreach (string key in proxyResponse.Headers.Keys)
{
string[] values = proxyResponse.Headers.GetValues(key);
for (int i=0; i<values.Length; i++)
sb.AppendFormat("header {0}={1}<br>", key, values);
}
Label1.Text = sb.ToString();
}
</script>
</body>
</HTML>


This yields the output:

header Server=Microsoft-IIS/5.1
header Date=Mon, 10 May 2004 16:29:06 GMT
header X-AspNet-Version=1.1.4322
header No-comma=value1
header No-comma=value2
header Comma=a,b
header Comma=c,d
header Set-Cookie=ASP.NET_SessionId=pyum1s45wwc5wdzhndrzyeqh; path=/
header Set-Cookie=n1=v1; expires=Mon
header Set-Cookie=10-May-2004 20:29:06 GMT; path=/
header Set-Cookie=n2=v2; expires=Mon
header Set-Cookie=10-May-2004 20:29:06 GMT; path=/
header Cache-Control=private
header Content-Type=text/html; charset=utf-8
header Content-Length=42

Note how the Set-Cookie multi-value headers have been incorrectly broken up.
They should look like this:

header Set-Cookie=n1=v1; expires=Mon, 10-May-2004 20:29:06 GMT; path=/
header Set-Cookie=n2=v2; expires=Mon, 10-May-2004 20:29:06 GMT; path=/

I know that I can use the CookieContainer to correctly parse out these
values, but I'd rather not (for efficiency); I just want to deal with raw
headers.

Is this a bug, or am I using the framework (1.1) incorrectly?

j
 
J

Jason Collins

Yeah, I'm currently using the CookieContainer. It's just that I don't
actually want/need to interact with the header values; I am building an HTTP
proxy and would just like to transfer the headers in the most lightweight
way possible.

I can likely live with the CookieContainer performance hit.

This just seemed like it might be a bug and wanted it brought to someone's
attention.

j

Feroze said:
WebHeaderCollection is a general purpose class for header manipulation. It
recognizes multivalued headers from the commas delimiting them. It does not
do context sensitive parsing. Calling GetValues() will give you unexpected
results for some headers which have commas in them (eg: Cookie,
WWW-Authenticate etc).

If the server is sending cookies, then on the client you should use
CookieContainer/CookieCollection etc classes (from System.Net namespace).
These apis' will do the parsing for you and you can query the expires etc
properties from that class.

hope this helps.

--
feroze
http://weblogs.asp.net/feroze_daud
============

Remove "user" from the email address to reply to the author.

This posting is provided "AS IS" with no warranties, and confers no rights

Use of included script samples are subject to the terms specified at
http://www.microsoft.com/info/cpyright.htm




Jason Collins said:
There seems to be an inconsistency (bug?) in the way the Set-Cookie header
is handled by the WebHeaderCollection. That is, the values of Set-Cookie,
when an Expires is specified, contain the "," character. This seems to be
incorrectly parsed during GetValues().

A simple example shows it best (there are 2 .aspx pages and an output):

AddHeaders.aspx:

<%@ Page language="c#" %>
<html>
<body>
<script language=C# runat=server>
private void Page_Load(object sender, System.EventArgs e)
{
Response.AddHeader("No-comma", "value1");
Response.AddHeader("No-comma", "value2");

Response.AddHeader("Comma", "a,b");
Response.AddHeader("Comma", "c,d");

HttpCookie c1 = new HttpCookie("n1", "v1");
c1.Expires = DateTime.Now.AddHours(4);
Response.Cookies.Add(c1);

HttpCookie c2 = new HttpCookie("n2", "v2");
c2.Expires = DateTime.Now.AddHours(4);
Response.Cookies.Add(c2);
}
</script>
</body>
</html>


GetHeaders.aspx:

<%@ Page language="c#" %>
<HTML>
<body>
<asp:Label id="Label1" runat="server">Label</asp:Label>
<script language="C#" runat="server">
private void Page_Load(object sender, System.EventArgs e)
{
// make a resquest for the other page (AddHeaders.aspx)
System.Net.HttpWebRequest proxyRequest = (System.Net.HttpWebRequest)
System.Net.WebRequest.Create("http://localhost/WebRequestTest/AddHeaders.asp
x");

System.Net.HttpWebResponse proxyResponse = (System.Net.HttpWebResponse)
proxyRequest.GetResponse();

System.Text.StringBuilder sb = new System.Text.StringBuilder();
foreach (string key in proxyResponse.Headers.Keys)
{
string[] values = proxyResponse.Headers.GetValues(key);
for (int i=0; i<values.Length; i++)
sb.AppendFormat("header {0}={1}<br>", key, values);
}
Label1.Text = sb.ToString();
}
</script>
</body>
</HTML>


This yields the output:

header Server=Microsoft-IIS/5.1
header Date=Mon, 10 May 2004 16:29:06 GMT
header X-AspNet-Version=1.1.4322
header No-comma=value1
header No-comma=value2
header Comma=a,b
header Comma=c,d
header Set-Cookie=ASP.NET_SessionId=pyum1s45wwc5wdzhndrzyeqh; path=/
header Set-Cookie=n1=v1; expires=Mon
header Set-Cookie=10-May-2004 20:29:06 GMT; path=/
header Set-Cookie=n2=v2; expires=Mon
header Set-Cookie=10-May-2004 20:29:06 GMT; path=/
header Cache-Control=private
header Content-Type=text/html; charset=utf-8
header Content-Length=42

Note how the Set-Cookie multi-value headers have been incorrectly broken up.
They should look like this:

header Set-Cookie=n1=v1; expires=Mon, 10-May-2004 20:29:06 GMT; path=/
header Set-Cookie=n2=v2; expires=Mon, 10-May-2004 20:29:06 GMT; path=/

I know that I can use the CookieContainer to correctly parse out these
values, but I'd rather not (for efficiency); I just want to deal with raw
headers.

Is this a bug, or am I using the framework (1.1) incorrectly?

j

 
F

Feroze [MSFT]

If you just want to forward headers, why dont you just query them & set them
on the outgoing request ?

foreach(string s in headers.Keys) {
Console.WriteLine(s + ": " + headers);
}

This will give you all the header lines in the header collection.

--
feroze
http://weblogs.asp.net/feroze_daud
============

Remove "user" from the email address to reply to the author.

This posting is provided "AS IS" with no warranties, and confers no rights

Use of included script samples are subject to the terms specified at
http://www.microsoft.com/info/cpyright.htm




Jason Collins said:
Yeah, I'm currently using the CookieContainer. It's just that I don't
actually want/need to interact with the header values; I am building an HTTP
proxy and would just like to transfer the headers in the most lightweight
way possible.

I can likely live with the CookieContainer performance hit.

This just seemed like it might be a bug and wanted it brought to someone's
attention.

j

Feroze said:
WebHeaderCollection is a general purpose class for header manipulation. It
recognizes multivalued headers from the commas delimiting them. It does not
do context sensitive parsing. Calling GetValues() will give you unexpected
results for some headers which have commas in them (eg: Cookie,
WWW-Authenticate etc).

If the server is sending cookies, then on the client you should use
CookieContainer/CookieCollection etc classes (from System.Net namespace).
These apis' will do the parsing for you and you can query the expires etc
properties from that class.

hope this helps.

--
feroze
http://weblogs.asp.net/feroze_daud
============

Remove "user" from the email address to reply to the author.

This posting is provided "AS IS" with no warranties, and confers no rights

Use of included script samples are subject to the terms specified at
http://www.microsoft.com/info/cpyright.htm
System.Net.WebRequest.Create("http://localhost/WebRequestTest/AddHeaders.asp
x");

System.Net.HttpWebResponse proxyResponse = (System.Net.HttpWebResponse)
proxyRequest.GetResponse();

System.Text.StringBuilder sb = new System.Text.StringBuilder();
foreach (string key in proxyResponse.Headers.Keys)
{
string[] values = proxyResponse.Headers.GetValues(key);
for (int i=0; i<values.Length; i++)
sb.AppendFormat("header {0}={1}<br>", key, values);
}
Label1.Text = sb.ToString();
}
</script>
</body>
</HTML>


This yields the output:

header Server=Microsoft-IIS/5.1
header Date=Mon, 10 May 2004 16:29:06 GMT
header X-AspNet-Version=1.1.4322
header No-comma=value1
header No-comma=value2
header Comma=a,b
header Comma=c,d
header Set-Cookie=ASP.NET_SessionId=pyum1s45wwc5wdzhndrzyeqh; path=/
header Set-Cookie=n1=v1; expires=Mon
header Set-Cookie=10-May-2004 20:29:06 GMT; path=/
header Set-Cookie=n2=v2; expires=Mon
header Set-Cookie=10-May-2004 20:29:06 GMT; path=/
header Cache-Control=private
header Content-Type=text/html; charset=utf-8
header Content-Length=42

Note how the Set-Cookie multi-value headers have been incorrectly
broken
up.
They should look like this:

header Set-Cookie=n1=v1; expires=Mon, 10-May-2004 20:29:06 GMT; path=/
header Set-Cookie=n2=v2; expires=Mon, 10-May-2004 20:29:06 GMT; path=/

I know that I can use the CookieContainer to correctly parse out these
values, but I'd rather not (for efficiency); I just want to deal with raw
headers.

Is this a bug, or am I using the framework (1.1) incorrectly?

j

 
J

Jason Collins

If I do this, all the Set-Cookie headers are munged into a single,
comma-separated Set-Cookie header like this:

Set-Cookie=n1=v1; expires=Mon, 10-May-2004 20:29:06 GMT; path=/, n2=v2;
expires=Mon, 10-May-2004 20:29:06 GMT; path=/

Internet Explorer 6 (the only one I've tested) does not react well to this
header. That is, it only sets the first cookie in the list, the others are
ignored.

So I need to preserve the multiple Set-Cookie headers. Grabbing headers
munges them together.

j

Feroze said:
If you just want to forward headers, why dont you just query them & set them
on the outgoing request ?

foreach(string s in headers.Keys) {
Console.WriteLine(s + ": " + headers);
}

This will give you all the header lines in the header collection.

--
feroze
http://weblogs.asp.net/feroze_daud
============

Remove "user" from the email address to reply to the author.

This posting is provided "AS IS" with no warranties, and confers no rights

Use of included script samples are subject to the terms specified at
http://www.microsoft.com/info/cpyright.htm




Jason Collins said:
Yeah, I'm currently using the CookieContainer. It's just that I don't
actually want/need to interact with the header values; I am building an HTTP
proxy and would just like to transfer the headers in the most lightweight
way possible.

I can likely live with the CookieContainer performance hit.

This just seemed like it might be a bug and wanted it brought to someone's
attention.

j
manipulation.
It does
not to
be
System.Net.WebRequest.Create("http://localhost/WebRequestTest/AddHeaders.asp
x");

System.Net.HttpWebResponse proxyResponse =
(System.Net.HttpWebResponse)
proxyRequest.GetResponse();

System.Text.StringBuilder sb = new System.Text.StringBuilder();
foreach (string key in proxyResponse.Headers.Keys)
{
string[] values = proxyResponse.Headers.GetValues(key);
for (int i=0; i<values.Length; i++)
sb.AppendFormat("header {0}={1}<br>", key, values);
}
Label1.Text = sb.ToString();
}
</script>
</body>
</HTML>


This yields the output:

header Server=Microsoft-IIS/5.1
header Date=Mon, 10 May 2004 16:29:06 GMT
header X-AspNet-Version=1.1.4322
header No-comma=value1
header No-comma=value2
header Comma=a,b
header Comma=c,d
header Set-Cookie=ASP.NET_SessionId=pyum1s45wwc5wdzhndrzyeqh; path=/
header Set-Cookie=n1=v1; expires=Mon
header Set-Cookie=10-May-2004 20:29:06 GMT; path=/
header Set-Cookie=n2=v2; expires=Mon
header Set-Cookie=10-May-2004 20:29:06 GMT; path=/
header Cache-Control=private
header Content-Type=text/html; charset=utf-8
header Content-Length=42

Note how the Set-Cookie multi-value headers have been incorrectly broken
up.
They should look like this:

header Set-Cookie=n1=v1; expires=Mon, 10-May-2004 20:29:06 GMT; path=/
header Set-Cookie=n2=v2; expires=Mon, 10-May-2004 20:29:06 GMT; path=/

I know that I can use the CookieContainer to correctly parse out these
values, but I'd rather not (for efficiency); I just want to deal
with
raw
headers.

Is this a bug, or am I using the framework (1.1) incorrectly?

j

 
S

Steven Cheng[MSFT]

Hi Jason,

I think Feroze's suggestion are correct. The HttpWebRequest.Headers
property is infact a normal NameValueCollection which is not particularly
deinfed for any specified Items such as cookies. And the
"GetValues" method will treat the whole string value of the certain key as
a normal text and split it via comma, that's why if you use GetValues, when
encountering the cookie value, the cookie value will be broken(because it
contains comma between cookie value). I think this is a apparent behavior
of a NameValueCollection.

So if we want to let the collection automaticaly do such checking
operations for us, we'd better use the CookieCollection instead. Otherwise,
we have to manually do the string spliting. Do you thinks so?

If you still have anyother concerns, please feel free to post here. Thanks.

Regards,

Steven Cheng
Microsoft Online Support

Get Secure! www.microsoft.com/security
(This posting is provided "AS IS", with no warranties, and confers no
rights.)

Get Preview at ASP.NET whidbey
http://msdn.microsoft.com/asp.net/whidbey/default.aspx
 
J

Jason Collins

Like I said a couple of posts back, I suppose I will just live with the
overhead of CookieContainer.

Note, though, that WebHeaderCollection overrides NameValueCollection's
GetValues() and uses a parser that is particular to Set-Cookie
(System.Net.HeaderInfoTable.MultiParser). An alternative parser could be
developed (e.g., in Whidbey) that would better handle this situation.

j
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top