RequesterExtensions
Useful extensions for IRequester objects.
using AngleSharp.Dom;
using AngleSharp.Html;
using AngleSharp.Network;
using AngleSharp.Network.Default;
using AngleSharp.Services;
using System;
using System.IO;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
namespace AngleSharp.Extensions
{
internal static class RequesterExtensions
{
public static bool IsRedirected(this HttpStatusCode status)
{
switch (status) {
default:
return status == HttpStatusCode.MultipleChoices;
case HttpStatusCode.MovedPermanently:
case HttpStatusCode.Found:
case HttpStatusCode.SeeOther:
case HttpStatusCode.TemporaryRedirect:
return true;
}
}
public static IDownload FetchWithCors(this IResourceLoader loader, CorsRequest cors)
{
ResourceRequest request = cors.Request;
CorsSetting setting = cors.Setting;
Url target = request.Target;
if (!(request.Origin == target.Origin) && !(target.Scheme == ProtocolNames.Data) && !(target.Href == "about:blank")) {
switch (setting) {
case CorsSetting.Anonymous:
case CorsSetting.UseCredentials:
return loader.FetchFromDifferentOrigin(cors);
case CorsSetting.None:
return loader.FetchWithoutCors(request, cors.Behavior);
default:
throw new DomException(DomError.Network);
}
}
return loader.FetchFromSameOrigin(target, cors);
}
private static IDownload FetchFromSameOrigin(this IResourceLoader loader, Url url, CorsRequest cors)
{
ResourceRequest request = cors.Request;
IDownload download = loader.DownloadAsync(new ResourceRequest(request.Source, url) {
Origin = request.Origin,
IsManualRedirectDesired = true
});
return download.Wrap(delegate(IResponse response) {
if (response.IsRedirected()) {
url.Href = response.Headers.GetOrDefault(HeaderNames.Location, url.Href);
if (!request.Origin.Is(url.Origin))
return loader.FetchFromSameOrigin(url, cors);
return loader.FetchWithCors(cors.RedirectTo(url));
}
return cors.CheckIntegrity(download);
});
}
private static IDownload FetchFromDifferentOrigin(this IResourceLoader loader, CorsRequest cors)
{
ResourceRequest request = cors.Request;
request.IsCredentialOmitted = cors.IsAnonymous();
IDownload download = loader.DownloadAsync(request);
return download.Wrap(delegate(IResponse response) {
if (response == null || response.StatusCode != HttpStatusCode.OK) {
response?.Dispose();
throw new DomException(DomError.Network);
}
return cors.CheckIntegrity(download);
});
}
private static IDownload FetchWithoutCors(this IResourceLoader loader, ResourceRequest request, OriginBehavior behavior)
{
if (behavior == OriginBehavior.Fail)
throw new DomException(DomError.Network);
return loader.DownloadAsync(request);
}
private static bool IsAnonymous(this CorsRequest cors)
{
return cors.Setting == CorsSetting.Anonymous;
}
private static IDownload Wrap(this IDownload download, Func<IResponse, IDownload> callback)
{
CancellationTokenSource cts = new CancellationTokenSource();
return new Download(download.Task.Wrap(callback), cts, download.Target, download.Originator);
}
private static IDownload Wrap(this IDownload download, IResponse response)
{
CancellationTokenSource cts = new CancellationTokenSource();
return new Download(TaskEx.FromResult(response), cts, download.Target, download.Originator);
}
private static async Task<IResponse> Wrap(this Task<IResponse> task, Func<IResponse, IDownload> callback)
{
return await callback(await task.ConfigureAwait(false)).Task.ConfigureAwait(false);
}
private static bool IsRedirected(this IResponse response)
{
return (response?.StatusCode ?? HttpStatusCode.NotFound).IsRedirected();
}
private static CorsRequest RedirectTo(this CorsRequest cors, Url url)
{
ResourceRequest request = cors.Request;
return new CorsRequest(new ResourceRequest(request.Source, url) {
IsCookieBlocked = request.IsCookieBlocked,
IsSameOriginForced = request.IsSameOriginForced,
Origin = request.Origin
}) {
Setting = cors.Setting,
Behavior = cors.Behavior,
Integrity = cors.Integrity
};
}
private static IDownload CheckIntegrity(this CorsRequest cors, IDownload download)
{
IResponse result = download.Task.Result;
string text = cors.Request.Source?.GetAttribute(AttributeNames.Integrity);
IIntegrityProvider integrity = cors.Integrity;
if (!string.IsNullOrEmpty(text) && integrity != null && result != null) {
MemoryStream memoryStream = new MemoryStream();
result.Content.CopyTo(memoryStream);
memoryStream.Position = 0;
if (!integrity.IsSatisfied(memoryStream.ToArray(), text)) {
result.Dispose();
throw new DomException(DomError.Security);
}
return download.Wrap(new Response {
Address = result.Address,
Content = memoryStream,
Headers = result.Headers,
StatusCode = result.StatusCode
});
}
return download;
}
}
}