Skip to content

Commit

Permalink
CookieStorage fixed cross-domain cookie duplication when used (dot) d…
Browse files Browse the repository at this point in the history
…omain. CookieStorage.ContainsKey renamed to Contains. New Cookie Filer: CookieFilters.CookieTrim filter enabled by default.

Fixed HttpResponse.ToString() issue when response body was empty.
Fixed typo in method OPTIONS.
  • Loading branch information
grandsilence committed Dec 7, 2018
1 parent de51900 commit 92e5a80
Show file tree
Hide file tree
Showing 7 changed files with 125 additions and 25 deletions.
15 changes: 15 additions & 0 deletions Leaf.xNet.Tests/~Http/HttpRequestTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,21 @@ public void FilterCookies()
Assert.AreEqual(CookieFilters.Filter(item.Key), item.Value);
}
}

/*
[TestMethod]
public void AvoidCrossdomainCookieDuplication()
{
var req = new HttpRequest("https://viastyle.org");
req.Proxy = HttpProxyClient.Parse("127.0.0.1:8888");
CookieStorage.DefaultExpireBeforeSet = true;
var resp = req.Get("/up/leaf-cookie.php");
var resp2 = req.Get("/up/leaf-cookie.php?q=q");
var resp3 = req.Get("/up/leaf-cookie.php");
}*/

/*
[TestMethod]
public void GetCookies()
Expand Down
4 changes: 2 additions & 2 deletions Leaf.xNet/Extensions/StringExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ namespace Leaf.xNet.Extensions
public static class StringExtensions
{
/// <summary>
/// Проверяет наличие слова в строке, аналогично <see cref="string.Contains"/>, но без учета реестра и региональных стандартов.
/// Проверяет наличие слова в строке, аналогично <see cref="string.Contains(string)"/>, но без учета реестра и региональных стандартов.
/// </summary>
/// <param name="str">Строка для поиска слова</param>
/// <param name="value">Слово которое должно содержаться в строке</param>
Expand All @@ -20,7 +20,7 @@ public static bool ContainsIgnoreCase(this string str, string value)
return str.IndexOf(value, StringComparison.OrdinalIgnoreCase) != -1;
}

#region Вырезание нескольких строк
#region Вырезание нескольких строк
/// <summary>
/// Вырезает несколько строк между двумя подстроками.
/// </summary>
Expand Down
8 changes: 8 additions & 0 deletions Leaf.xNet/~Http/CookieFilters.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ public static class CookieFilters
{
public static bool Enabled = true;

public static bool Trim = true;
public static bool Path = true;
public static bool CommaEndingValue = true;

Expand All @@ -18,10 +19,17 @@ public static string Filter(string rawCookie)
{
return !Enabled ? rawCookie
: rawCookie
.TrimWhitespace()
.FilterPath()
.FilterCommaEndingValue();
}

/// <summary>Убираем любые пробелы в начале и конце</summary>
private static string TrimWhitespace(this string rawCookie)
{
return !Trim ? rawCookie : rawCookie.Trim();
}

/// <summary>Заменяем все значения path на "/"</summary>
private static string FilterPath(this string rawCookie)
{
Expand Down
116 changes: 96 additions & 20 deletions Leaf.xNet/~Http/CookieStorage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,32 @@ public class CookieStorage
/// </summary>
public CookieContainer Container { get; private set; }

private static BinaryFormatter _binaryFormatter;
/// <summary>
/// Число <see cref="Cookie"/> в <see cref="CookieContainer"/> (для всех адресов).
/// </summary>
public int Count => Container.Count;

/// <summary>
/// Возвращает или задает значение, указывающие, закрыты ли куки для редактирования через ответы сервера.
/// </summary>
/// <value>Значение по умолчанию — <see langword="false"/>.</value>
public bool IsLocked { get; set; }

/// <summary>
/// Значение по умолчанию для всех экземпляров.
/// Сбрасывать старую Cookie при вызове <see cref="Set"/> если найдено совпадение по домену и имени Cookie.
/// </summary>
public static bool DefaultExpireBeforeSet { get; set; } = true;

/// <summary>
/// Сбрасывать старую Cookie при вызове <see cref="Set"/> если найдено совпадение по домену и имени Cookie.
/// </summary>
public bool ExpireBeforeSet { get; set; } = DefaultExpireBeforeSet;


private static BinaryFormatter Bf => _binaryFormatter ?? (_binaryFormatter = new BinaryFormatter());
private static BinaryFormatter _binaryFormatter;


public CookieStorage(bool isLocked = false, CookieContainer container = null)
{
Expand All @@ -23,26 +47,49 @@ public CookieStorage(bool isLocked = false, CookieContainer container = null)
}

/// <summary>
/// Возвращает или задает значение, указывающие, закрыты ли куки для редактирования через ответы сервера.
/// Добавляет Cookie в хранилище <see cref="CookieContainer"/>.
/// </summary>
/// <value>Значение по умолчанию — <see langword="false"/>.</value>
public bool IsLocked { get; set; }
/// <param name="cookie">Кука</param>
public void Add(Cookie cookie)
{
Container.Add(cookie);
}

/// <summary>
/// Добавляет или изменяет существующие Cookies в <see cref="CookieContainer"/>.
/// Добавляет коллекцию Cookies в хранилище <see cref="CookieContainer"/>.
/// </summary>
/// <param name="cookies">Коллекция Cookie</param>
public void Set(CookieCollection cookies)
public void Add(CookieCollection cookies)
{
Container.Add(cookies);
}

/// <inheritdoc cref="Set(System.Net.CookieCollection)"/>
/// <summary>
/// Добавляет или обновляет существующую Cookie в хранилище <see cref="CookieContainer"/>.
/// </summary>
/// <param name="cookie">Кука</param>
// ReSharper disable once UnusedMember.Global
public void Set(Cookie cookie)
{
Container.Add(cookie);
if (ExpireBeforeSet)
ExpireIfExists(cookie);

Add(cookie);
}

/// <summary>
/// Добавляет или обновляет существующие Cookies из коллекции в хранилище <see cref="CookieContainer"/>.
/// </summary>
/// <param name="cookies">Коллекция Cookie</param>
public void Set(CookieCollection cookies)
{
if (ExpireBeforeSet)
{
foreach (Cookie cookie in cookies)
ExpireIfExists(cookie);
}

Add(cookies);
}

/// <inheritdoc cref="Set(System.Net.CookieCollection)"/>
Expand All @@ -53,7 +100,8 @@ public void Set(Cookie cookie)
// ReSharper disable once UnusedMember.Global
public void Set(string name, string value, string domain, string path = "/")
{
Container.Add(new Cookie(name, value, path, domain));
var cookie = new Cookie(name, value, path, domain);
Set(cookie);
}

/// <inheritdoc cref="Set(System.Net.CookieCollection)"/>
Expand All @@ -62,6 +110,17 @@ public void Set(string name, string value, string domain, string path = "/")
public void Set(Uri uri, string rawCookie)
{
string filteredCookie = CookieFilters.Filter(rawCookie);

if (ExpireBeforeSet)
{
int equalIndex = filteredCookie.IndexOf('=');
if (equalIndex != -1)
{
string cookieName = filteredCookie.Substring(0, equalIndex + 1);
ExpireIfExists(uri, cookieName);
}
}

Container.SetCookies(uri, filteredCookie);
}

Expand All @@ -74,6 +133,28 @@ public void Set(string url, string rawCookie)
Set(new Uri(url), rawCookie);
}

private void ExpireIfExists(Uri uri, string cookieName)
{
var cookies = Container.GetCookies(uri);
foreach (Cookie storageCookie in cookies)
{
if (storageCookie.Name == cookieName)
storageCookie.Expired = true;
}
}

private void ExpireIfExists(Cookie cookie)
{
if (string.IsNullOrEmpty(cookie.Domain))
return;

// Fast trim: Domain.Remove is slower and much more slower variation: cookie.Domain.TrimStart('.')
string domain = cookie.Domain[0] == '.' ? cookie.Domain.Substring(1) : cookie.Domain;
var uri = new Uri($"{(cookie.Secure ? "https://" : "http://")}{domain}");

ExpireIfExists(uri, cookie.Name);
}

/// <summary>
/// Очистить <see cref="CookieContainer"/>.
/// </summary>
Expand Down Expand Up @@ -163,21 +244,21 @@ public CookieCollection GetCookies(string url)
/// Проверяет существование <see cref="Cookie"/> в <see cref="CookieContainer"/> по адресу ресурса и имени ключа куки.
/// </summary>
/// <param name="uri">URI адрес ресурса</param>
/// <param name="name">Имя-ключ куки</param>
/// <param name="cookieName">Имя-ключ куки</param>
/// <returns>Вернет <see langword="true"/> если ключ найден по запросу.</returns>
public bool ContainsKey(Uri uri, string name)
public bool Contains(Uri uri, string cookieName)
{
if (Container.Count <= 0)
return false;

var cookies = Container.GetCookies(uri);
return cookies[name] != null;
return cookies[cookieName] != null;
}

/// <inheritdoc cref="ContainsKey(System.Uri,string)"/>
public bool ContainsKey(string url, string name)
/// <inheritdoc cref="Contains(System.Uri, string)"/>
public bool Contains(string url, string cookieName)
{
return ContainsKey(new Uri(url), name);
return Contains(new Uri(url), cookieName);
}

/// <summary>
Expand Down Expand Up @@ -210,10 +291,5 @@ public static CookieStorage LoadFromFile(string filePath)
using (var fs = new FileStream(filePath, FileMode.Open))
return (CookieStorage)Bf.Deserialize(fs);
}

/// <summary>
/// Число <see cref="Cookie"/> в <see cref="CookieContainer"/> (для всех адресов).
/// </summary>
public int Count => Container.Count;
}
}
2 changes: 1 addition & 1 deletion Leaf.xNet/~Http/HttpMethod.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ public enum HttpMethod
GET,
HEAD,
DELETE,
OPTION,
OPTIONS,
POST,
PATCH,
PUT,
Expand Down
2 changes: 1 addition & 1 deletion Leaf.xNet/~Http/HttpRequest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1430,7 +1430,7 @@ public void Dispose()
// ReSharper disable once UnusedMember.Global
public bool ContainsCookie(string url, string name)
{
return !DontTrackCookies && Cookies != null && Cookies.ContainsKey(url, name);
return !DontTrackCookies && Cookies != null && Cookies.Contains(url, name);
}

#region Работа с заголовками
Expand Down
3 changes: 2 additions & 1 deletion Leaf.xNet/~Http/HttpResponse.cs
Original file line number Diff line number Diff line change
Expand Up @@ -726,7 +726,7 @@ public void None()
/// <returns>Значение <see langword="true"/>, если указанные куки содержатся, иначе значение <see langword="false"/>.</returns>
public bool ContainsCookie(string url, string name)
{
return Cookies != null && Cookies.ContainsKey(url, name);
return Cookies != null && Cookies.Contains(url, name);
}

#endregion
Expand Down Expand Up @@ -841,6 +841,7 @@ internal long LoadResponse(HttpMethod method, bool trackMiddleHeaders)
StatusCode == HttpStatusCode.NoContent ||
StatusCode == HttpStatusCode.NotModified)
{
_loadedMessageBody = string.Empty;
MessageBodyLoaded = true;
}

Expand Down

0 comments on commit 92e5a80

Please sign in to comment.