Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Modernize CookieContainer domain-matching #112604

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 2 additions & 5 deletions src/libraries/Common/src/System/Net/CookieComparer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,8 @@ internal static bool Equals(Cookie left, Cookie right)
}

internal static bool EqualDomains(ReadOnlySpan<char> left, ReadOnlySpan<char> right)
{
if (left.StartsWith('.')) left = left.Slice(1);
if (right.StartsWith('.')) right = right.Slice(1);
=> StripLeadingDot(left).Equals(StripLeadingDot(right), StringComparison.OrdinalIgnoreCase);

return left.Equals(right, StringComparison.OrdinalIgnoreCase);
}
internal static ReadOnlySpan<char> StripLeadingDot(ReadOnlySpan<char> s) => s.StartsWith('.') ? s[1..] : s;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,6 @@
Link="Common\System\Net\SocketAddressExtensions.cs" />
<Compile Include="$(CommonPath)System\Net\NegotiationInfoClass.cs"
Link="Common\System\Net\NegotiationInfoClass.cs" />
<Compile Include="$(CommonPath)System\Net\NetworkInformation\HostInformation.cs"
Link="Common\System\Net\NetworkInformation\HostInformation.cs" />
<Compile Include="$(CommonPath)System\Text\StringBuilderCache.cs"
Link="Common\System\Text\StringBuilderCache.cs" />
<Compile Include="$(CommonPath)System\HexConverter.cs"
Expand Down
215 changes: 72 additions & 143 deletions src/libraries/System.Net.Primitives/src/System/Net/Cookie.cs

Large diffs are not rendered by default.

248 changes: 43 additions & 205 deletions src/libraries/System.Net.Primitives/src/System/Net/CookieContainer.cs

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,8 @@ public static void Equals_Compare_Success()
Cookie c14 = new Cookie("name", "value", "path", "domain") { Version = 5 };
Cookie c15 = new Cookie("name", "value", "path", "domain") { Version = 100 };

Cookie c9dot = new Cookie("name", "value", "path", ".domain");

Assert.False(c2.Equals(null));
Assert.False(c2.Equals(""));

Expand Down Expand Up @@ -329,6 +331,9 @@ public static void Equals_Compare_Success()
Assert.NotEqual(c13, c15);
Assert.Equal(c13.GetHashCode(), c14.GetHashCode());
Assert.NotEqual(c13.GetHashCode(), c15.GetHashCode());

Assert.Equal(c9, c9dot);
Assert.Equal(c9.GetHashCode(), c9dot.GetHashCode());
}

[Fact]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,39 +93,6 @@ public void GetCookies_AddCookieVersion0WithExplicitDomain_CookieReturnedForDoma
Assert.Equal(1, cookies.Count);
}

[Fact]
public void GetCookies_AddCookieVersion1WithExplicitDomain_CookieReturnedForDomainAndOneLevelSubDomain()
{
const string SchemePrefix = "http://";
const string OriginalDomain = "contoso.com";
const string OriginalDomainWithLeadingDot = "." + OriginalDomain;

var container = new CookieContainer();
var cookie1 = new Cookie(CookieName1, CookieValue1) { Domain = OriginalDomainWithLeadingDot, Version = 1 };
container.Add(new Uri(SchemePrefix + OriginalDomain), cookie1);

var uri = new Uri(SchemePrefix + OriginalDomain);
var cookies = container.GetCookies(uri);
Assert.Equal(1, cookies.Count);
Assert.Equal(OriginalDomainWithLeadingDot, cookies[CookieName1].Domain);

uri = new Uri(SchemePrefix + "www." + OriginalDomain);
cookies = container.GetCookies(uri);
Assert.Equal(1, cookies.Count);

uri = new Uri(SchemePrefix + "x.www." + OriginalDomain);
cookies = container.GetCookies(uri);
Assert.Equal(0, cookies.Count);

uri = new Uri(SchemePrefix + "y.x.www." + OriginalDomain);
cookies = container.GetCookies(uri);
Assert.Equal(0, cookies.Count);

uri = new Uri(SchemePrefix + "z.y.x.www." + OriginalDomain);
cookies = container.GetCookies(uri);
Assert.Equal(0, cookies.Count);
}

[Fact]
public void GetAllCookies_Empty_ReturnsEmptyCollection()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -467,39 +467,6 @@ public void GetCookies_AddCookieVersion0WithExplicitDomain_CookieReturnedForDoma
Assert.Equal(1, cookies.Count);
}

[Fact]
public void GetCookies_AddCookieVersion1WithExplicitDomain_CookieReturnedForDomainAndOneLevelSubDomain()
{
const string SchemePrefix = "http://";
const string OriginalDomain = "contoso.com";
const string OriginalDomainWithLeadingDot = "." + OriginalDomain;

var container = new CookieContainer();
var cookie1 = new Cookie(CookieName1, CookieValue1) { Domain = OriginalDomainWithLeadingDot, Version = 1 };
container.Add(new Uri(SchemePrefix + OriginalDomain), cookie1);

var uri = new Uri(SchemePrefix + OriginalDomain);
CookieCollection cookies = container.GetCookies(uri);
Assert.Equal(1, cookies.Count);
Assert.Equal(OriginalDomainWithLeadingDot, cookies[CookieName1].Domain);

uri = new Uri(SchemePrefix + "www." + OriginalDomain);
cookies = container.GetCookies(uri);
Assert.Equal(1, cookies.Count);

uri = new Uri(SchemePrefix + "x.www." + OriginalDomain);
cookies = container.GetCookies(uri);
Assert.Equal(0, cookies.Count);

uri = new Uri(SchemePrefix + "y.x.www." + OriginalDomain);
cookies = container.GetCookies(uri);
Assert.Equal(0, cookies.Count);

uri = new Uri(SchemePrefix + "z.y.x.www." + OriginalDomain);
cookies = container.GetCookies(uri);
Assert.Equal(0, cookies.Count);
}

[Fact]
public void Ctor_Capacity_Success()
{
Expand Down Expand Up @@ -590,13 +557,20 @@ public void Add_SameCookieDifferentVairants_OverridesOlderVariant()
{
Uri uri = new Uri("http://domain.com");

Cookie c0 = new Cookie("name1", "value", "", "domain.com");
Cookie c1 = new Cookie("name1", "value", "", ".domain.com"); // Variant = Plain
Cookie c2 = new Cookie("name1", "value", "", ".domain.com") { Port = "\"80\"" }; // Variant = RFC2965 (should override)
Cookie c3 = new Cookie("name1", "value", "", ".domain.com") { Port = "\"80, 90\"" }; // Variant = RFC2965 (should override)
Cookie c4 = new Cookie("name1", "value", "", ".domain.com") { Version = 1 }; // Variant = RFC2109 (should be rejected)

CookieContainer cc = new CookieContainer();

cc.Add(c0);
Assert.Equal("domain.com", cc.GetCookies(uri)[0].Domain);

cc.Add(c1);
Assert.Equal(1, cc.Count);
Assert.Equal(".domain.com", cc.GetCookies(uri)[0].Domain);

// Adding a newer variant should override an older one
cc.Add(c2);
Expand All @@ -614,6 +588,24 @@ public void Add_SameCookieDifferentVairants_OverridesOlderVariant()
Assert.Equal(1, cc.Count);
}

[Fact]
public static void Add_SetCookies_SameCookieDifferentVairants_OverridesOlderVariant()
{
Uri uri = new Uri("http://domain.com");
CookieContainer cc = new();
Cookie a = new()
{
Domain = "domain.com",
Name = "lol",
Value = "0"
};
cc.Add(uri, a);
cc.SetCookies(uri, "lol=42");

Assert.Equal(1, cc.Count);
Assert.Equal("42", cc.GetCookies(uri).Single().Value);
}

[Fact]
public void Add_Cookie_Invalid()
{
Expand Down Expand Up @@ -781,10 +773,10 @@ private void VerifyGetCookies(CookieContainer cc1, Uri uri, Cookie[] expected)

for (int i = 0; i < expected.Length; i++)
{
Cookie c1 = expected[i];
Cookie c2 = cc2[i];
Assert.Equal(c1.Name, c2.Name); // Primitive check for equality
Assert.Equal(c1.Value, c2.Value);
Cookie c1 = expected.Single(c => c.Name == c2.Name);

Assert.Equal(c1.Value, c2.Value); // Primitive check for equality
}
}

Expand Down Expand Up @@ -983,5 +975,100 @@ public void GetCookies_PathMatchingFollowsRfc6265(bool useDefaultPath, string[]
CookieCollection collection = container.GetCookies(requestUri);
Assert.Equal(expectedMatches, collection.Count);
}

[Fact]
public void Add_ImplicitDomainOfIPv6Hostname_Success()
{
CookieContainer container = new CookieContainer();
Cookie cookie = new Cookie("lol", "haha");
Uri uri = new Uri("https://[::FFFF:192.168.0.1]/test");
container.Add(uri, cookie);
Assert.Equal(uri.Host, container.GetCookies(uri).Single().Domain);
}

public static TheoryData<string, string> DomainMatching_WhenMatches_Success_Data = new TheoryData<string, string>()
{
{ "https://q", "q" },
{ "https://localhost/", "localhost" },
{ "https://test.com", "test.com" },
{ "https://test.COM", "tEsT.com" },
{ "https://test.com", ".test.com" },
{ "https://yay.test.com", "yay.test.com" },
{ "https://yay.test.com", ".yay.test.com" },
{ "https://yay.test.com", ".test.com" },
{ "https://42.aaaa.bbb.cc.d.test.com", "d.test.com" },
{ "https://yay.test.com/foo/bar", "test.com" },
{ "https://127.0.1.1", "127.0.1.1" },
{ "https://42.42.100.100", "42.42.100.100" },
};

[Theory]
[MemberData(nameof(DomainMatching_WhenMatches_Success_Data))]
public void DomainMatching_WhenMatches_Success(string uriString, string domain)
{
CookieContainer container = new CookieContainer();
Cookie cookie = new Cookie("lol", "haha")
{
Domain = domain
};

Uri uri = new Uri(uriString);
container.Add(uri, cookie);
Assert.Equal(1, container.Count);

CookieCollection collection = container.GetCookies(uri);
Assert.Equal(1, collection.Count);
}

[Fact]
public void DomainMatching_ExplicitDomain_MatchesMultiple()
{
CookieContainer container = new CookieContainer();
Cookie a = new Cookie("a", "aa", null, "a.com");
Cookie b = new Cookie("b", "bb", null, "b.a.com");
Cookie c = new Cookie("c", "cc", null, "c.b.a.com");

container.Add(new Uri("http://a.com"), a);
container.Add(new Uri("http://b.a.com"), b);
container.Add(new Uri("http://c.b.a.com"), c);

CookieCollection matches = container.GetCookies(new Uri("http://c.b.a.com"));
Assert.Equal(3, matches.Count);
}

public static TheoryData<string, string> DomainMatching_WhenDoesNotMatch_ThrowsCookieException_Data = new TheoryData<string, string>()
{
{ "https://test.com", "test.co" }, // Domain is not a suffix
{ "https://test.com", "x.test.com" }, // Domain is not a suffix (extra chars at start)
{ "https://test.com", "ttest.com" }, // Suffix but not separated by dot
{ "https://test.com", "test.com." }, // Trailing dot
{ "https://test.com", "..test.com" }, // 2 leading dots
{ "https://foo.test.com", "yay.test.com" }, // subdomain mismatch
{ "https://42.42.100.100", "41.42.100.100" }, // different IP

// Single label domain without a full match.
{ "https://test.com", ".com" },
{ "https://test.com", "com" },

// If Host is an IP address, it should be equal to the domain.
// See https://issues.chromium.org/issues/40126142 and the last condition in
// https://datatracker.ietf.org/doc/html/rfc6265#section-5.1.3
{ "https://1.2.3.4", ".2.3.4" }
};


[Theory]
[MemberData(nameof(DomainMatching_WhenDoesNotMatch_ThrowsCookieException_Data))]
public void DomainMatching_WhenDoesNotMatch_ThrowsCookieException(string uriString, string domain)
{
CookieContainer container = new CookieContainer();
Cookie cookie = new Cookie("lol", "haha")
{
Domain = domain
};

Uri uri = new Uri(uriString);
Assert.Throws<CookieException>(() => container.Add(uri, cookie));
}
}
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<NoWarn>$(NoWarn);169;649</NoWarn>
Expand Down Expand Up @@ -28,6 +28,10 @@
Link="ProductionCode\System\Net\Sockets\SocketError.cs" />
<Compile Include="..\..\src\System\Net\IPAddress.cs"
Link="ProductionCode\System\Net\IPAddress.cs" />
<Compile Include="$(CommonPath)System\Net\IPv4AddressHelper.Common.cs"
Link="ProductionCode\Common\System\Net\IPv4AddressHelper.Common.cs" />
<Compile Include="$(CommonPath)System\Net\IPv6AddressHelper.Common.cs"
Link="ProductionCode\Common\System\Net\IPv6AddressHelper.Common.cs" />
<Compile Include="..\..\src\System\Net\EndPoint.cs"
Link="ProductionCode\System\Net\EndPoint.cs" />
<Compile Include="..\..\src\System\Net\Sockets\AddressFamily.cs"
Expand Down Expand Up @@ -58,9 +62,6 @@
<ItemGroup>
<Compile Include="CookieCollectionTest.cs" />
<Compile Include="Fakes\CookieException.cs" />
<Compile Include="Fakes\HostInformation.cs" />
<Compile Include="Fakes\IPv4AddressHelper.cs" />
<Compile Include="Fakes\IPv6AddressHelper.cs" />
<Compile Include="$(CommonPath)System\Net\HttpKnownHeaderNames.cs"
Link="ProductionCode\Common\System\Net\HttpKnownHeaderNames.cs" />
<Compile Include="$(CommonPath)System\Net\IPAddressParserStatics.cs"
Expand Down
Loading