CookieContainer bug?
Asked Answered
E

6

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>
Ezzell answered 26/6, 2009 at 6:41 Comment(3)
I have tried this many times before as well. I ended up reading the cookie header myself and storing it somewhere else.Lubberly
I have to use CookieContainer because it is the only way to send cookies to HttpWebRequest.Ezzell
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 :-)Balkanize
A
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];
            }
        }
    }
    
Atlantic answered 8/10, 2009 at 12:24 Comment(5)
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!Shayneshays
Argh... where does _ContainerType come from? My compiler won't find it!Shayneshays
Ah! I found it... You need to replace _ContainerType by cookieContainer.GetType()Shayneshays
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.Atlantic
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.Pretty
B
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);
        }

    }

}
Batfowl answered 16/9, 2016 at 17:5 Comment(5)
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?Obregon
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.Batfowl
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.Obregon
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: dotnetfiddle.net/ohJVgcBatfowl
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()): dotnetfiddle.net/fqOrXTObregon
A
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
Aubigny answered 8/7, 2013 at 15:58 Comment(0)
L
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);
               }
            }
Luddite answered 3/2, 2016 at 12:46 Comment(0)
E
-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.

Ezzell answered 26/6, 2009 at 19:50 Comment(0)
E
-1

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

Ezzell answered 31/7, 2009 at 19:23 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.