Ошибка CookieContainer?

Я запутался, как CookieContainer обрабатывает домен, поэтому я создаю этот тест. Этот тест показывает, что cookieContainer не возвращает куки для "example.com", но согласно RFC он должен вернуть как минимум 2 куки.

Разве это не ошибка?

Как заставить это работать?

Вот обсуждение этой ошибки:

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>

6 ответов

Решение

Я только что нашел исправление для этой ошибки и обсудил здесь: http://dot-net-expertise.blogspot.com/2009/10/cookiecontainer-domain-handling-bug-fix.html

Вот решение:

  1. Не используйте метод.Add(Cookie), используйте только метод.Add(Uri, Cookie).
  2. Вызывайте BugFix_CookieDomain каждый раз, когда вы добавляете файл cookie в контейнер или перед использованием.GetCookie, или перед тем, как система использует контейнер.

    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];
            }
        }
    }
    

Я создал исправление для этой проблемы, которое работает в приложениях Windows 10 / UWP / .NET Core. Проблема в том, что внутренности для CookieContainer они разные, но такие же дрянные, как и в собственно.NET Framework. Таким образом, принятое решение больше не работает.

Но вместо "исправления" CookieContainerЯ просто написал версию GetCookies() он получает все файлы cookie для определенного домена со строкой, независимо от их "безопасного" состояния или с префиксом точки. Не стесняйтесь изменять его так, как считаете нужным, и я посмотрю, как будет реализована его версия в будущем выпуске.NET Core.

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);
        }

    }

}

Потерял мой день с этим вопросом. Ответ CallMeLaNN мне не помог (я использую.Net 4.5). В моем случае проблема была в порядке установки тела запроса и настроек куки.

В этом случае куки не будут отправлены на сервер:

            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();

Для того, чтобы это сработало, необходимо изменить порядок:

            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);
               }
            }
//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

Вот как можно обойти эту ошибку: http://social.microsoft.com/Forums/en-US/netfxnetcom/thread/1297afc1-12d4-4d75-8d3f-7563222d234c Он использует отражение.

Наконец они собираются это исправить: https://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=478521

Другие вопросы по тегам