seb-win-refactoring/SafeExamBrowser.Configuration/DataResources/NetworkResourceLoader.cs

197 lines
4.9 KiB
C#

/*
* Copyright (c) 2020 ETH Zürich, Educational Development and Technology (LET)
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
using System;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Mime;
using System.Threading.Tasks;
using SafeExamBrowser.Configuration.Contracts;
using SafeExamBrowser.Configuration.Contracts.DataResources;
using SafeExamBrowser.Logging.Contracts;
namespace SafeExamBrowser.Configuration.DataResources
{
public class NetworkResourceLoader : IResourceLoader
{
private AppConfig appConfig;
private ILogger logger;
/// <remarks>
/// See https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types.
/// </remarks>
private string[] SupportedContentTypes => new[]
{
MediaTypeNames.Application.Octet,
MediaTypeNames.Text.Xml
};
private string[] SupportedSchemes => new[]
{
appConfig.SebUriScheme,
appConfig.SebUriSchemeSecure,
Uri.UriSchemeHttp,
Uri.UriSchemeHttps
};
public NetworkResourceLoader(AppConfig appConfig, ILogger logger)
{
this.appConfig = appConfig;
this.logger = logger;
}
public bool CanLoad(Uri resource)
{
var available = SupportedSchemes.Contains(resource.Scheme) && IsAvailable(resource);
if (available)
{
logger.Debug($"Can load '{resource}' as it references an existing network resource.");
}
else
{
logger.Debug($"Can't load '{resource}' since its URI scheme is not supported or the resource is unavailable.");
}
return available;
}
public LoadStatus TryLoad(Uri resource, out Stream data)
{
var uri = BuildUriFor(resource);
logger.Debug($"Sending GET request for '{uri}'...");
var request = new HttpRequestMessage(HttpMethod.Get, uri);
var response = Execute(request);
logger.Debug($"Received response '{ToString(response)}'.");
if (IsUnauthorized(response) || HasHtmlContent(response))
{
return HandleBrowserResource(response, out data);
}
logger.Debug($"Trying to extract response data...");
data = Extract(response.Content);
logger.Debug($"Created '{data}' for {data.Length / 1000.0} KB data of response body.");
return LoadStatus.Success;
}
private Uri BuildUriFor(Uri resource)
{
var scheme = GetSchemeFor(resource);
var builder = new UriBuilder(resource) { Scheme = scheme };
return builder.Uri;
}
private HttpResponseMessage Execute(HttpRequestMessage request)
{
var task = Task.Run(async () =>
{
using (var client = new HttpClient())
{
return await client.SendAsync(request);
}
});
return task.GetAwaiter().GetResult();
}
private Stream Extract(HttpContent content)
{
var task = Task.Run(async () =>
{
return await content.ReadAsStreamAsync();
});
return task.GetAwaiter().GetResult();
}
private string GetSchemeFor(Uri resource)
{
if (resource.Scheme == appConfig.SebUriScheme)
{
return Uri.UriSchemeHttp;
}
if (resource.Scheme == appConfig.SebUriSchemeSecure)
{
return Uri.UriSchemeHttps;
}
return resource.Scheme;
}
private LoadStatus HandleBrowserResource(HttpResponseMessage response, out Stream data)
{
data = default(Stream);
logger.Debug($"The {(IsUnauthorized(response) ? "resource needs authentication" : " response data is HTML")}.");
return LoadStatus.LoadWithBrowser;
}
/// <remarks>
/// See https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Type.
/// </remarks>
private bool HasHtmlContent(HttpResponseMessage response)
{
return response.Content.Headers.ContentType?.MediaType == MediaTypeNames.Text.Html;
}
private bool IsAvailable(Uri resource)
{
try
{
var uri = BuildUriFor(resource);
logger.Debug($"Sending HEAD request for '{uri}'...");
var request = new HttpRequestMessage(HttpMethod.Head, uri);
var response = Execute(request);
var isAvailable = response.IsSuccessStatusCode || IsUnauthorized(response);
logger.Debug($"Received response '{ToString(response)}'.");
if (!isAvailable)
{
logger.Debug($"HEAD request was not successful, trying GET request for '{uri}'...");
request = new HttpRequestMessage(HttpMethod.Get, uri);
response = Execute(request);
isAvailable = response.IsSuccessStatusCode || IsUnauthorized(response);
logger.Debug($"Received response '{ToString(response)}'.");
}
return isAvailable;
}
catch (Exception e)
{
logger.Error($"Failed to check availability of '{resource}'!", e);
return false;
}
}
private bool IsUnauthorized(HttpResponseMessage response)
{
return response.StatusCode == HttpStatusCode.Unauthorized;
}
private string ToString(HttpResponseMessage response)
{
return $"{(int) response.StatusCode} - {response.ReasonPhrase}";
}
}
}