17

I'm confused how CookieContainer handles domain, so I create this test. This test shows cookieContainer doesn't return any cookie for "example.com" but according to RFC it should return at least 2 cookies.

Isn't it a bug?

How make it to work?

Here is a discussion about this bug:

http://social.msdn.microsoft.com/Forums/en-US/ncl/thread/c4edc965-2dc2-4724-8f08-68815cf1dce6

<%@ Page Language="C#" %>

<%@ Import Namespace="System.Net" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<script runat="server">
    CookieContainer getContainer()
    {
        CookieContainer result = new CookieContainer();

        Uri uri = new Uri("http://sub.example.com");
        string cookieH = @"Test1=val; domain=sub.example.com; path=/";
        result.SetCookies(uri, cookieH);

        cookieH = @"Test2=val; domain=.example.com; path=/";
        result.SetCookies(uri, cookieH);

        cookieH = @"Test3=val; domain=example.com; path=/";
        result.SetCookies(uri, cookieH);

        return result;
    }

    void Test()
    {
        CookieContainer cookie = getContainer();
        lblResult.Text += "<br>Total cookies count: " + cookie.Count + " &nbsp;&nbsp; expected: 3";

        Uri uri = new Uri("http://sub.example.com");
        CookieCollection coll = cookie.GetCookies(uri);
        lblResult.Text += "<br>For " + uri + " Cookie count: " + coll.Count + " &nbsp;&nbsp; expected: 2";

        uri = new Uri("http://other.example.com");
        coll = cookie.GetCookies(uri);
        lblResult.Text += "<br>For " + uri + " Cookie count: " + coll.Count + " &nbsp;&nbsp; expected: 2";

        uri = new Uri("http://example.com");
        coll = cookie.GetCookies(uri);
        lblResult.Text += "<br>For " + uri + " Cookie count: " + coll.Count + " &nbsp;&nbsp; expected: 2";

    }

    protected void Page_Load(object sender, EventArgs e)
    {
        Test();
    }
</script>

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title>CookieContainer Test Page</title>
</head>
<body>
    <form id="frmTest" runat="server">
    <asp:Label ID="lblResult" EnableViewState="false" runat="server"></asp:Label>
    </form>
</body>
</html>
jdphenix
  • 15,022
  • 3
  • 41
  • 74
Salar
  • 495
  • 3
  • 6
  • 14
  • I have tried this many times before as well. I ended up reading the cookie header myself and storing it somewhere else. – Nippysaurus Jun 26 '09 at 06:58
  • I have to use CookieContainer because it is the only way to send cookies to HttpWebRequest. – Salar Jun 26 '09 at 08:08
  • Can't believe I finally had a scenario where changing the framework from 4.0 to 3.5 (I wasn't using 4.0 stuff) broke my program. Took me some time to figure out, why the session cookies for authentication suddenly were missing. They fixed this issue in 4.0, so changing the framework introduced a bug into my program :-) – VVS Sep 07 '10 at 13:17

6 Answers6

26

I just found the fix for this bug and discussed here: http://dot-net-expertise.blogspot.com/2009/10/cookiecontainer-domain-handling-bug-fix.html

Here is the solution:

  1. Don't use .Add(Cookie), Use only .Add(Uri, Cookie) method.
  2. Call BugFix_CookieDomain each time you add a cookie to the container or before you use .GetCookie or before system use the container.

    private void BugFix_CookieDomain(CookieContainer cookieContainer)
    {
        System.Type _ContainerType = typeof(CookieContainer);
        Hashtable table = (Hashtable)_ContainerType.InvokeMember("m_domainTable",
                                   System.Reflection.BindingFlags.NonPublic |
                                   System.Reflection.BindingFlags.GetField |
                                   System.Reflection.BindingFlags.Instance,
                                   null,
                                   cookieContainer,
                                   new object[] { });
        ArrayList keys = new ArrayList(table.Keys);
        foreach (string keyObj in keys)
        {
            string key = (keyObj as string);
            if (key[0] == '.')
            {
                string newKey = key.Remove(0, 1);
                table[newKey] = table[keyObj];
            }
        }
    }
    
John Saunders
  • 160,644
  • 26
  • 247
  • 397
CallMeLaNN
  • 8,328
  • 7
  • 59
  • 74
  • Thank you so much! I spent 2 days debugging this, thinking a newbie like me should learn to figure libraries on my own. But it was a bug! – PRINCESS FLUFF Feb 07 '10 at 22:23
  • Argh... where does _ContainerType come from? My compiler won't find it! – PRINCESS FLUFF Feb 11 '10 at 10:35
  • Ah! I found it... You need to replace _ContainerType by cookieContainer.GetType() – PRINCESS FLUFF Feb 11 '10 at 10:46
  • Just edit it to add System.Type _ContainerType = typeof(CookieContainer); Note that use this for faster performance. Using cookieContainer.GetType() each time will consume more time. – CallMeLaNN Mar 14 '10 at 10:50
  • It's this weird stuff that I hate microsoft for, though normally I don't mind them so much. It can happen when getting results form some domains, my.opera.com being one of them. – Henry B Jul 26 '10 at 17:07
1

I've created a fix for this problem that works on Windows 10 / UWP / .NET Core apps. The issue is that the internals for CookieContainer are different, but just as crappy, as they are in the .NET Framework proper. So the accepted solution does not work anymore.

But instead of "fixing" the CookieContainer, I just wrote a version of GetCookies() that gets all the cookies for a particular domain with a string, regardless of their "secure" state or if they are prefixed with a dot. Feel free to modify it as you see fit for your needs, and I'll see about getting a version of it implemented in a future .NET Core release.

using System.Collections.Generic;
using System.Reflection;

namespace System.Net
{

    /// <summary>
    /// Contains extensions for the <see cref="CookieContaner"/> class.
    /// </summary>
    public static class CookieContainerExtensions
    {

        /// <summary>
        /// Uses Reflection to get ALL of the <see cref="Cookie">Cookies</see> where <see cref="Cookie.Domain"/> 
        /// contains part of the specified string. Will return cookies for any subdomain, as well as dotted-prefix cookies. 
        /// </summary>
        /// <param name="cookieContainer">The <see cref="CookieContainer"/> to extract the <see cref="Cookie">Cookies</see> from.</param>
        /// <param name="domain">The string that contains part of the domain you want to extract cookies for.</param>
        /// <returns></returns>
        public static IEnumerable<Cookie> GetCookies(this CookieContainer cookieContainer, string domain)
        {
            var domainTable = GetFieldValue<dynamic>(cookieContainer, "_domainTable");
            foreach (var entry in domainTable)
            {
                string key = GetPropertyValue<string>(entry, "Key");

                if (key.Contains(domain))
                {
                    var value = GetPropertyValue<dynamic>(entry, "Value");

                    var internalList = GetFieldValue<SortedList<string, CookieCollection>>(value, "_list");
                    foreach (var li in internalList)
                    {
                        foreach (Cookie cookie in li.Value)
                        {
                            yield return cookie;
                        }
                    }
                }
            }
        }

        /// <summary>
        /// Gets the value of a Field for a given object instance.
        /// </summary>
        /// <typeparam name="T">The <see cref="Type"/> you want the value to be converted to when returned.</typeparam>
        /// <param name="instance">The Type instance to extract the Field's data from.</param>
        /// <param name="fieldName">The name of the Field to extract the data from.</param>
        /// <returns></returns>
        internal static T GetFieldValue<T>(object instance, string fieldName)
        {
            BindingFlags bindFlags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static;
            FieldInfo fi = instance.GetType().GetField(fieldName, bindFlags);
            return (T)fi.GetValue(instance);
        }

        /// <summary>
        /// Gets the value of a Property for a given object instance.
        /// </summary>
        /// <typeparam name="T">The <see cref="Type"/> you want the value to be converted to when returned.</typeparam>
        /// <param name="instance">The Type instance to extract the Property's data from.</param>
        /// <param name="propertyName">The name of the Property to extract the data from.</param>
        /// <returns></returns>
        internal static T GetPropertyValue<T>(object instance, string propertyName)
        {
            var pi = instance.GetType().GetProperty(propertyName);
            return (T)pi.GetValue(instance, null);
        }

    }

}
Robert McLaws
  • 2,258
  • 2
  • 19
  • 22
  • Shouldn't `key.Contains(domain)` actually be `key == domain || key[0] == '.' && domain.Contains(key.Substring(1))`, since otherwise you are excluding subdomains, which is the main point of the period-prefix? – paulie4 Oct 29 '20 at 14:22
  • Contains() != Equals(), it means that the partial string exists inside the string you are checking. So you don't need to substring for the dot and then check that, because the following code is correct: `".test.domain.com".Contains("domain.com") == true`. That properly accounts for any subdomain and any period *without* needing string manipulation. – Robert McLaws Oct 30 '20 at 15:40
  • I'm pretty sure you have it backwards. The `key` value does not include the subdomain. MDN says, "if `Domain=mozilla.org` is set, then cookies are available on subdomains like `developer.mozilla.org`", so in that example, if someone is trying to `GetCookies()` for `developer.mozilla.org`, the `key` would be `.mozilla.org`. – paulie4 Nov 02 '20 at 04:00
  • This is a simple explanation of what is going on, why the CookieContainer has a problem, and why my fix solves the problem as succinctly as possible: https://dotnetfiddle.net/ohJVgc – Robert McLaws Nov 03 '20 at 05:14
  • Yes, I know your fix works with that specific case, but I am saying that your fix does not support subdomains. Here is what I am saying (NOTE: my first comment should have said `EndsWIth()` not `Contains()`): https://dotnetfiddle.net/fqOrXT – paulie4 Nov 05 '20 at 13:58
0
//bug fix, exists only in 3.5 FW, please wrap it with defines
//http://dot-net-expertise.blogspot.com/2009/10/cookiecontainer-domain-handling-bug-fix.html
if(!value.Contains("://www.")) //we are going to hit the bug
{
    string urlWWW = value.Replace("://", "://www.");
    Uri uriWWW = new Uri(urlWWW);
    foreach (Cookie c in _cookieContainer.GetCookies(uriWWW))
        if (c.Domain.StartsWith("."))
            request.Headers["Cookies"] += c.Name + "=" + c.Value + ";"; //manually add the cookies
}
//~bug fix
Josh Lee
  • 171,072
  • 38
  • 269
  • 275
Dmitry
  • 792
  • 3
  • 17
0

Lost my day with this issue. CallMeLaNN's response didn't help me (I'm using .Net 4.5). In my case the problem was in order of setting body of request and settings cookies.

In this case cookies will not be sent to server:

            var response = (HttpWebRequest)WebRequest.Create("http://localhost:4433/");

            using (var requestStream = response.GetRequestStream())
            {
               using (var streamWriter = new StreamWriter(requestStream))
               {
                    requestStream.Write(RequestContent);
               }
            }

            response.CookieContainer.Add(new Cookie("Name", "Value"));
            await response.GetResponseAsync();

To make it work change the order is required:

            var response = (HttpWebRequest)WebRequest.Create("http://localhost:4433/");

            response.CookieContainer.Add(new Cookie("Name", "Value"));
            await response.GetResponseAsync();

            using (var requestStream = response.GetRequestStream())
            {
               using (var streamWriter = new StreamWriter(requestStream))
               {
                    requestStream.Write(RequestContent);
               }
            }
Yaroslav
  • 163
  • 8
-1

Here is a hack to get around this bug: http://social.microsoft.com/Forums/en-US/netfxnetcom/thread/1297afc1-12d4-4d75-8d3f-7563222d234c It uses reflection.

Salar
  • 495
  • 3
  • 6
  • 14
-1

At last they gonna fix it: https://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=478521

Salar
  • 495
  • 3
  • 6
  • 14