.NetCF HttpWebRequest and HttpWebResponse Have No Cookie Support

By Joshua Freeman at April 25, 2009 00:58
Filed Under: .Net Compact Framework

For space saving reasons some parts of the .Net Framework are left out of the .Net Compact Framework. Unfortunately, (at least for me), one of the things not making the cut is the cookie support methods for the HttpWebRequest and HttpWebResponse classes. What this means is developers are left to handle cookies in their own code.

Cookies play an important role in web applications and sometimes your application will be required to read and/or set them. One of my applications Chronobis falls into this category. In order to authenticate to OWA servers using FORM based authentication, you must accept, store, and send cookies. The full version of the .Net Framework handles this almost seamlessly for you. However, in the Compact Framework you are on your own.

I decided to write an encapsulated CookieManager class to solve this problem for Chronobis, which I am providing here as an example of how to handle cookies in your .NET Compact Framework application.

Warning: This code assumes that every cookie stored will be sent. This means that I effectively ignore the cookie attribute values (Comment, Domain, Max-Age, Path, Secure, and Version). If your situation warrants, you might need to extend this code to account for them.

using System;
using System.Collections.Generic;
using System.Text;
using System.Net;

namespace net.frejos.http
{
    public class CookieManager
    {
        private Dictionary cookieValues;

        public Dictionary CookieValues {
            get {
                if (this.cookieValues == null) {
                    this.cookieValues = new Dictionary();
                }

                return this.cookieValues;
            }
        }

        public void PublishCookies(HttpWebRequest webRequest)
        {
            StringBuilder sb = new StringBuilder();
            sb.Append("Cookie: ");
            foreach (string key in this.CookieValues.Keys) {
                sb.Append(key);
                sb.Append("=");
                sb.Append(this.CookieValues[key]);
                sb.Append("; ");
                sb.Append("$Path=\"/\"; ");               
            }

            webRequest.Headers.Add(sb.ToString());
            sb = null;
            webRequest = null;
        }

        public void StoreCookies(HttpWebResponse webResponse)
        {
            for (int x=0; x < webResponse.Headers.Count; x++) {
                if (webResponse.Headers.Keys[x].ToLower().Equals("set-cookie")) {
                    this.AddRawCookie( webResponse.Headers[x] );
                }
            }

            webResponse = null;
        }

        private void AddRawCookie(string rawCookieData)
        {
            string key = null;
            string value = null;

            string[] entries = null;

            if (rawCookieData.IndexOf(",") > 0)
            {
                entries = rawCookieData.Split(',');
            }
            else { 
                entries = new string[] { rawCookieData };
            }

            foreach (string entry in entries) {
                string cookieData = entry.Trim();

                if (cookieData.IndexOf(';') > 0)
                {
                    string[] temp = cookieData.Split(';');
                    cookieData = temp[0];
                }

                int index = cookieData.IndexOf('=');
                if (index > 0)
                {
                    key = cookieData.Substring(0, index);
                    value = cookieData.Substring(index + 1);
                }

                if (key != null && value != null)
                {
                    this.CookieValues[key] = value;
                }

                cookieData = null;
            }

            rawCookieData = null;
            entries = null;
            key = null;
            value = null;
        }

        public override string ToString()
        {
            StringBuilder sb = new StringBuilder();
            sb.Append("[");
            foreach (string key in this.CookieValues.Keys) {
                sb.Append("{");
                sb.Append(key);
                sb.Append(",");
                sb.Append(this.CookieValues[key]);
                sb.Append("}, ");
            }
            if (this.CookieValues.Keys.Count > 0)
            {
                sb.Remove(sb.Length - 2, 2);
            }
            sb.Append("]");

            return sb.ToString();
        }
    }
}

A typical usage of this class would look something like this:

CookieManager cookieManager = new CookieManager();
// Set a cookie value
cookieManager.CookieValues["FavoriteCookie"] = "Chocolate Chip";

HttpWebRequest webRequest = (HttpWebRequest)WebRequest.Create(url);
// Publish the cookies to the request before asking for the response
cookieManager.PublishCookies(webRequest);

HttpWebResponse webResponse = (HttpWebResponse)webRequest.GetResponse();
// Store any cookies returned from the response
cookieManager.StoreCookies(webResponse);

// Get the value of a cookie
string session = cookieManager.CookieValues["SESSIONID"];

webRequest = (HttpWebRequest)WebRequest.Create(url2);
cookieManager.PublishCookies(webRequest);

webResponse = (HttpWebResponse)webRequest.GetResponse();

Just in case you need a cookie RFC refresher, or need to add the missing functionality, you can find the RFC here.

The example source code contained within this post is provided under the terms of the MIT License. Copyright© 2008-2009 Joshua Freeman.

CookieManager.cs (3.39 kb)

Comments

2/25/2010 4:20:37 PM #

bigal

i found 1 small and 1 big problem with this code Smile

the first is $path= is added to the end of each cookie. if you look at the raw headers that are sent you will see this. tbh i didnt test it enough to see if this  is a real problem, but it causes the headers to not match what would be sent if you were using the real CookieCollection object (ie not in .netcf).

the second is that the POST request will sometimes have an auto redirect and the redirected url checks for the cookies received in the original POST. eg i have made an app which logs into a website via a form with a username and password -

GET /home.aspx, store cookies from webrequest
create new web request
publish cookies to webrequest
POST home.aspx
remote server sends redirects to
GET home.aspx (but without cookies being republished)
login fails.

but, there is a property on the webrequest object AllowAutoRedirect. you should set this to false and use the following logic -

1. GET /home.aspx, store cookies from webrequest
create new web request, set AllowAutoRedirect = false
publish cookies to webrequest
2. POST home.aspx
get url of redirect (usually same as original POST).
store cookies from 2.
create new webrequest, publish cookies to request.
3. POST home.aspx
4. profit!!!

login should be ok this time.

took me a while to figure that out.

ps it's a complete pain in the ass that MS didnt add cookies.


bigal

2/25/2010 6:03:32 PM #

Joshua Freeman

Hey thanks for the comment. You are correct on both points.

I did point out in the post that,

Warning: This code assumes that every cookie stored will be sent. This means that I effectively ignore the cookie attribute values (Comment, Domain, Max-Age, Path, Secure, and Version). If your situation warrants, you might need to extend this code to account for them.

In the case that a redirect is sent from the server, you must either handle that yourself, by checking the response code and performing the GET request (publishing the cookies again on the new request), or as you suggested use the AllowAutoRedirect property to let the framework do it for you.

Joshua Freeman

About the Author

I'm Joshua Freeman a Senior Software Developer who has been developing software for the past 14 years, not counting all of those Comadore64 programs as a child. Developing software has always been a passion of mine. I've had the opportunity to use many different technologies over those years, including things like: HTML, JavaScript, Perl, ASP, JSP, SQL. The things that I use most often now are: JBoss, Tomcat, Java, .Net Framework, .Net Compact Framework, Windows Mobile, ASP.Net, JavaServer Faces to name a few.

I finally decided to start a blog in order to share and discuss some of the tools tips and other obscure findings that I've found over the years. So I present circumdev, which is a word that I've created that literally means "around development". (One of the great things about creating words is that their domains are usually still available.)

I hope you find the information that I post here useful in your software development journey, the way that I have found so many others helpful in mine.

Month List

Disclaimer

The opinions expressed within my blog entries, comments, and any other content of this site are those of my own. They do not represent the views of my employer, or those of anyone else in any way.