I have a self-hosted WCF service with an endpoint set up with WebHttpBinding listening for HTTP requests. I need to access cookies passed with these requests. I can get the Cookie:
header value, but I am stuck with actual parsing. My implementation attempt was using CookieContainer
:
var container = new CookieContainer();
var uri = new Uri("http://localhost/");
container.SetCookies(uri, cookieHeader);
var cookies = container.GetCookies(uri).OfType<Cookie>();
foreach (var cookie in cookies)
{
Console.WriteLine("{0} = {1}", cookie.Name, cookie.Value);
}
Issue with this code is that CookieContainer
expects cookies to be separated by comma (cookieHeader="c1=v1,c2=v2"
), while browsers tend to use semicolon (cookieHeader="c1=v1;c2=v2"
) for that. Since RFC 6265 permits only semicolon to be used as separator (older RFC's permit both, though) I am a bit puzzled why CookieContainer
only supports comma. Now I am struggling for finding an alternative solution for the problem that seems to be quite typical. How do I parse cookies properly? ASP.NET should be able to do it, does it expose any publicly usable classes for that?
###Why does it not work?
As I understand it, the separators vary for a Set-Cookie:
and the Cookie:
headers. Set-Cookie:
is normally duplicated if multiple headers exist, whereas Cookie:
has multiple cookies in a single header. From RFC6265:
== Server -> User Agent ==
Set-Cookie: SID=31d4d96e407aad42; Path=/; Secure; HttpOnly
Set-Cookie: lang=en-US; Path=/; Domain=example.com
== User Agent -> Server ==
Cookie: SID=31d4d96e407aad42; lang=en-US
The Set-Cookie:
header can be combined into a single string by appending each header separated by a comma:
Set-Cookie: SID=31d4d96e407aad42; Path=/; Secure; HttpOnly,lang=en-US; Path=/; Domain=example.com
CookieContainer
is designed to be used on the client side (by a user-agent), so the SetCookies(Uri, string)
method only parses the syntax used by Set-Cookie:
.
###How does ASP.Net do it
ASP.Net uses an internal method to parse the Cookie:
header, and it does not appear to be exposed by any public methods. See HttpListenerRequest.Cookies
:
private CookieCollection ParseCookies(Uri uri, string setCookieHeader) {
GlobalLog.Print("HttpListenerRequest#" + ValidationHelper.HashString(this) + "::ParseCookies() uri:" + uri + " setCookieHeader:" + setCookieHeader);
CookieCollection cookies = new CookieCollection();
CookieParser parser = new CookieParser(setCookieHeader);
for (;;) {
Cookie cookie = parser.GetServer();
GlobalLog.Print("HttpListenerRequest#" + ValidationHelper.HashString(this) + "::ParseCookies() CookieParser returned cookie:" + ValidationHelper.ToString(cookie));
if (cookie==null) {
// EOF, done.
break;
}
if (cookie.Name.Length==0) {
continue;
}
cookies.InternalAdd(cookie, true);
}
return cookies;
}
###How can I parse the header, then?
Nancy has a very concise parser that could be used if licensing permits:
private IDictionary<string, string> GetCookieData()
{
var cookieDictionary = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
if (!this.Headers.Cookie.Any())
{
return cookieDictionary;
}
var values = this.Headers["cookie"].First().TrimEnd(';').Split(';');
foreach (var parts in values.Select(c => c.Split(new[] { '=' }, 2)))
{
var cookieName = parts[0].Trim();
string cookieValue;
if (parts.Length == 1)
{
//Cookie attribute
cookieValue = string.Empty;
}
else
{
cookieValue = parts[1];
}
cookieDictionary[cookieName] = cookieValue;
}
return cookieDictionary;
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With