Skip to content

Commit

Permalink
Fixed cookie parsing exception when domains starts with dot. (1 level).
Browse files Browse the repository at this point in the history
Net Core 2.2 support.
HttpRequest.DontTrackCookies renamed to UseCookies (by default is true).
Debug only: don't call ToString while debugging.
Refactoring.
  • Loading branch information
grandsilence committed Dec 27, 2018
1 parent 92e5a80 commit 3add884
Show file tree
Hide file tree
Showing 11 changed files with 110 additions and 78 deletions.
2 changes: 1 addition & 1 deletion Leaf.xNet.Tests/Leaf.xNet.Tests.csproj
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFrameworks>net452;net462;net471;net472;netcoreapp2.0;netcoreapp2.1</TargetFrameworks>
<TargetFrameworks>net452;net462;net471;net472;netcoreapp2.0;netcoreapp2.1;netcoreapp2.2</TargetFrameworks>

<IsPackable>false</IsPackable>

Expand Down
6 changes: 3 additions & 3 deletions Leaf.xNet/Leaf.xNet.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>Leaf.xNet</RootNamespace>
<AssemblyName>Leaf.xNet</AssemblyName>
<TargetFrameworks>net452;net462;net471;net472;netcoreapp2.0;netcoreapp2.1</TargetFrameworks>
<TargetFrameworks>net452;net462;net471;net472;netcoreapp2.0;netcoreapp2.1;netcoreapp2.2</TargetFrameworks>
<FileAlignment>512</FileAlignment>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<Authors>Grand Silence</Authors>
Expand Down Expand Up @@ -45,10 +45,10 @@
<RepositoryType>Git</RepositoryType>
<PackageProjectUrl>https://github.com/csharp-leaf</PackageProjectUrl>
<Copyright>© 2018 Developed by Grand Silence — Kelog Studio</Copyright>
<PackageTags>net,http,socks,proxy,cloudflare</PackageTags>
<PackageTags>net,http,socks,proxy,cloudflare,xnet,https,stormwall,useragent,parsing,bot,web,crowling</PackageTags>
<PackageIconUrl>https://raw.githubusercontent.com/csharp-leaf/Leaf.Core/master/Icons/icon-300.png</PackageIconUrl>
<NoWarn>1591</NoWarn>
<Description>Improved xNet library for .NET Framework 4.5.2, 4.6.2, 4.7.1, 4.7.2 &amp; .NET Core 2.0-2.1</Description> <!-- Ignore warinings abound undocumented code -->
<Description>Improved xNet library for .NET Framework 4.5.2, 4.6.2, 4.7.1, 4.7.2 &amp; .NET Core 2.0-2.2</Description> <!-- Ignore warinings abound undocumented code -->
</PropertyGroup>

<!-- :: // Generic configurations -->
Expand Down
7 changes: 4 additions & 3 deletions Leaf.xNet/Services/Cloudflare/CloudflareBypass.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@
namespace Leaf.xNet.Services.Cloudflare
{
/// <summary>
/// Cloudflare Anti-DDoS bypass extension for HttpRequest.
/// CloudFlare Anti-DDoS bypass extension for HttpRequest.
/// </summary>
/// <remarks>
/// Only the JavaScript challenge can be handled. CAPTCHA and IP address blocking cannot be bypassed.
/// </remarks>
// ReSharper disable once UnusedMember.Global
public static class CloudflareBypass
{
/// <summary>
Expand Down Expand Up @@ -69,7 +70,7 @@ public static HttpResponse GetThroughCloudflare(this HttpRequest request, string
DLog log = null,
CancellationToken cancellationToken = default(CancellationToken))
{
if (request.DontTrackCookies)
if (!request.UseCookies)
throw new HttpException("В свойствах HTTP запроса отключена обработка Cookies через свойство DontTrackCookies. Для CloudFlare куки обязательны.");

// User-Agent is required
Expand All @@ -87,7 +88,7 @@ public static HttpResponse GetThroughCloudflare(this HttpRequest request, string
var response = ManualGet(request, url);
if (!response.IsCloudflared())
{
log?.Invoke("УСПЕХ: Cloudflare не обнаружен, работаем дальше: " + url);
log?.Invoke("УСПЕХ: CloudFlare не обнаружен, работаем дальше: " + url);
return response;
}

Expand Down
1 change: 1 addition & 0 deletions Leaf.xNet/Services/StormWall/StormWallBypass.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ namespace Leaf.xNet.Services.StormWall
/// <summary>
/// Класс-расширение для обхода AntiDDoS защиты StormWall.
/// </summary>
// ReSharper disable once UnusedMember.Global
public static class StormWallBypass
{
#region Solver Singleton
Expand Down
5 changes: 5 additions & 0 deletions Leaf.xNet/~Http/CookieFilters.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@ public static string Filter(string rawCookie)
.FilterCommaEndingValue();
}

public static string FilterDomain(string domain)
{
return string.IsNullOrWhiteSpace(domain) ? null : domain.Trim('.', '\t', '\n', '\r', ' ');
}

/// <summary>Убираем любые пробелы в начале и конце</summary>
private static string TrimWhitespace(this string rawCookie)
{
Expand Down
61 changes: 57 additions & 4 deletions Leaf.xNet/~Http/CookieStorage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,16 +26,15 @@ public class CookieStorage

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

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


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

Expand Down Expand Up @@ -133,6 +132,60 @@ public void Set(string url, string rawCookie)
Set(new Uri(url), rawCookie);
}


public void SetFromHeader(string headerValue)
{
// Отделяем Cross-domain cookie - если не делать, будет исключение.
// Разделяем все key=value
var arguments = headerValue.Split(new[] {';'}, StringSplitOptions.RemoveEmptyEntries);
if (arguments.Length == 0)
return;

// Получаем ключ и значение самой Cookie
var keyValue = arguments[0].Split(new[] {'='}, 2);
var cookie = new Cookie(keyValue[0], keyValue.Length < 2 ? string.Empty : keyValue[1]);

// Обрабатываем дополнительные ключи Cookie
for (int i = 1; i < arguments.Length; i++)
{
keyValue = arguments[i].Split(new[] {'='}, 2);

// Обрабатываем ключи регистронезависимо
string key = keyValue[0].Trim().ToLower();
string value = keyValue.Length < 2 ? null : keyValue[1].Trim();

// ReSharper disable once SwitchStatementMissingSomeCases
switch (key)
{
case "expires":
if (!DateTime.TryParse(value, out var expires))
continue;

cookie.Expires = expires;
break;

case "path":
cookie.Path = value;
break;
case "domain":
string domain = CookieFilters.FilterDomain(value);
if (domain == null)
return;

cookie.Domain = domain;
break;
case "secure":
cookie.Secure = true;
break;
case "httponly":
cookie.HttpOnly = true;
break;
}
}

Set(cookie);
}

private void ExpireIfExists(Uri uri, string cookieName)
{
var cookies = Container.GetCookies(uri);
Expand Down
2 changes: 1 addition & 1 deletion Leaf.xNet/~Http/HttpException.cs
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ public override void GetObjectData(SerializationInfo serializationInfo, Streamin

serializationInfo.AddValue("Status", (int)Status);
serializationInfo.AddValue("HttpStatusCode", (int)HttpStatusCode);
serializationInfo.AddValue("EmptyMessageBody", (bool)EmptyMessageBody);
serializationInfo.AddValue("EmptyMessageBody", EmptyMessageBody);
}
}
}
16 changes: 9 additions & 7 deletions Leaf.xNet/~Http/HttpRequest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -553,17 +553,19 @@ public string Authorization

/// <summary>
/// Возвращает или задает куки, связанные с запросом.
/// Создается автоматически, если задано свойство <see cref="UseCookies"/> в значении <see langword="true"/>.
/// </summary>
/// <value>Значение по умолчанию — <see langword="null"/>.</value>
/// <value>Значение по умолчанию: если <see cref="UseCookies"/> установлено в <see langword="true"/>, то вернется коллекция.
/// Если <see langword="false"/>, то вернется <see langword="null"/>.</value>
/// <remarks>Куки могут изменяться ответом от HTTP-сервера. Чтобы не допустить этого, нужно установить свойство <see cref="Leaf.xNet.CookieStorage.IsLocked"/> равным <see langword="true"/>.</remarks>
public CookieStorage Cookies { get; set; }

/// <summary>
/// Позволяет отключить автоматическое создание <see cref="CookieStorage"/> в свойстве Cookies когда получены куки от сервера.
/// Запрос не будет отправлять заголовок с куками. Ответ не будет обрабатывать заголовки Set-Cookie.
/// Позволяет задать автоматическое создание <see cref="CookieStorage"/> в свойстве Cookies когда получены куки от сервера.
/// Если установить значение в <see langword="false"/> - заголовки с куками не будут отправляться и не будут сохраняться из ответа (заголовок Set-Cookie).
/// </summary>
/// <value>Значение по умолчанию — <see langword="false"/>.</value>
public bool DontTrackCookies { get; set; }
/// <value>Значение по умолчанию — <see langword="true"/>.</value>
public bool UseCookies { get; set; } = true;

#endregion

Expand Down Expand Up @@ -1430,7 +1432,7 @@ public void Dispose()
// ReSharper disable once UnusedMember.Global
public bool ContainsCookie(string url, string name)
{
return !DontTrackCookies && Cookies != null && Cookies.Contains(url, name);
return UseCookies && Cookies != null && Cookies.Contains(url, name);
}

#region Работа с заголовками
Expand Down Expand Up @@ -2029,7 +2031,7 @@ private string GenerateHeaders(Uri uri, HttpMethod method, long contentLength =
MergeHeaders(headers, _temporaryHeaders);

// Disabled cookies
if (DontTrackCookies)
if (!UseCookies)
return ToHeadersString(headers);

// Cookies isn't set now
Expand Down
81 changes: 24 additions & 57 deletions Leaf.xNet/~Http/HttpResponse.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,23 @@
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Net;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using Leaf.xNet.Extensions;

#if DEBUG
using System.Diagnostics;
#endif

namespace Leaf.xNet
{
/// <summary>
/// Представляет класс, предназначенный для загрузки ответа от HTTP-сервера.
/// </summary>
#if DEBUG
[DebuggerDisplay("ToString() disabled in debug mode")]
#endif
public sealed class HttpResponse
{
#region Классы (закрытые)
Expand Down Expand Up @@ -799,7 +805,7 @@ internal long LoadResponse(HttpMethod method, bool trackMiddleHeaders)
}
_headers.Clear();

if (!_request.DontTrackCookies)
if (_request.UseCookies)
{
Cookies = _request.Cookies != null && !_request.Cookies.IsLocked
? _request.Cookies
Expand Down Expand Up @@ -918,65 +924,26 @@ private void ReceiveHeaders()
string headerValue = header.Substring(separatorPos + 1).Trim(' ', '\t', '\r', '\n');

if (headerName.Equals("Set-Cookie", StringComparison.OrdinalIgnoreCase))
{
if (_request.DontTrackCookies)
continue;
ParseCookieFromHeader(headerValue);
else
_headers[headerName] = headerValue;
}
}

// Отделяем Cross-domain cookie - если не делать, будет исключение.
// Родной парсинг raw-cookie плохо работает.
if (!headerValue.ContainsIgnoreCase("domain="))
Cookies.Set(_request.Address, headerValue);
else
{
// Разделяем все key=value
var arguments = headerValue.Split(new [] {';'}, StringSplitOptions.RemoveEmptyEntries);
if (arguments.Length == 0)
continue;
#endregion

// Получаем ключ и значение самой Cookie
var keyValue = arguments[0].Split(new[] {'='}, 2);
var cookie = new Cookie(keyValue[0], keyValue.Length < 2 ? string.Empty : keyValue[1]);
#region Ручной разбор Cookie с расширенными атрибутами

// Обрабатываем дополнительные ключи Cookie
for (int i = 1; i < arguments.Length; i++)
{
keyValue = arguments[i].Split(new[] {'='}, 2);

// Обрабатываем ключи регистронезависимо
string key = keyValue[0].Trim().ToLower();
string value = keyValue.Length < 2 ? null : keyValue[1].Trim();

// ReSharper disable once SwitchStatementMissingSomeCases
switch (key)
{
case "expires":
if (!DateTime.TryParse(value, out var expires))
continue;

cookie.Expires = expires;
break;

case "path":
cookie.Path = value;
break;
case "domain":
cookie.Domain = value;
break;
case "secure":
cookie.Secure = true;
break;
case "httponly":
cookie.HttpOnly = true;
break;
}
}
private void ParseCookieFromHeader(string headerValue)
{
if (!_request.UseCookies)
return;

Cookies.Set(cookie);
}
}
else
_headers[headerName] = headerValue;
}
// Обычная Cookie, без указания домена
if (!headerValue.ContainsIgnoreCase("domain="))
Cookies.Set(_request.Address, headerValue);
else
Cookies.SetFromHeader(headerValue);
}

#endregion
Expand Down
3 changes: 2 additions & 1 deletion Leaf.xNet/~Http/~Content/HttpContent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,9 @@ public string ContentType
/// <param name="stream">Поток, куда будут записаны данные тела запроса.</param>
public abstract void WriteTo(Stream stream);

/// <inheritdoc />
/// <summary>
/// Освобождает все ресурсы, используемые текущим экземпляром класса <see cref="HttpContent"/>.
/// Освобождает все ресурсы, используемые текущим экземпляром класса <see cref="T:Leaf.xNet.HttpContent" />.
/// </summary>
public void Dispose()
{
Expand Down
4 changes: 3 additions & 1 deletion appveyor.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ artifacts:
name: netcoreapp2.0
- path: Leaf.xNet\bin\Release\netcoreapp2.1
name: netcoreapp2.1
- path: Leaf.xNet\bin\Release\netcoreapp2.2
name: netcoreapp2.2
deploy:
- provider: NuGet
api_key:
Expand All @@ -52,7 +54,7 @@ deploy:
release: Leaf.xNet v$(appveyor_build_version)
auth_token:
secure: NQtMOO3yB309cDK/pFWRiQ==
artifact: net452;net462;net471;net472;netcoreapp2.0;netcoreapp2.1
artifact: net452;net462;net471;net472;netcoreapp2.0;netcoreapp2.1;netcoreapp2.2
on:
branch: master
only_commits:
Expand Down

0 comments on commit 3add884

Please sign in to comment.