Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save marklagendijk/e5fd1934391a515f8c974c87024ed39c to your computer and use it in GitHub Desktop.
Save marklagendijk/e5fd1934391a515f8c974c87024ed39c to your computer and use it in GitHub Desktop.
RemoteImageProvider POC
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Extensions.Options;
namespace SixLabors.ImageSharp.Web.Providers
{
public class AllowedDomainsRemoteImageUrlValidator: IRemoteImageUrlValidator
{
private readonly List<string> _allowedDomains;
public AllowedDomainsRemoteImageUrlValidator(IOptions<AllowedDomainsRemoteImageUrlValidatorOptions> options)
{
_allowedDomains = options.Value.AllowedDomains
.Select(domain => domain.ToLower())
.ToList();
}
public bool IsValidUrl(Uri url)
{
return _allowedDomains.Contains(url.Host);
}
}
}
using System.Collections.Generic;
namespace SixLabors.ImageSharp.Web.Providers
{
public class AllowedDomainsRemoteImageUrlValidatorOptions
{
public ICollection<string> AllowedDomains { get; set; } = new List<string>();
}
}
{
"ImageSharp.Web": {
"Presets": {
"preset1": "rmode=crop&width=232&height=164",
},
"AllowedDomains": [
"example.com"
]
}
}
using System;
namespace SixLabors.ImageSharp.Web.Providers
{
public interface IRemoteImageUrlValidator
{
bool IsValidUrl(Uri url);
}
}
using System;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using SixLabors.ImageSharp.Web.Providers;
using SixLabors.ImageSharp.Web.Resolvers;
namespace SixLabors.ImageSharp.Web.Providers
{
public class RemoteImageProvider : IImageProvider
{
public ProcessingBehavior ProcessingBehavior => ProcessingBehavior.All;
public Func<HttpContext, bool> Match { get; set; } = PathStartsWithRemote;
public const string RemotePath = "/remote";
private readonly IHttpClientFactory _clientFactory;
private readonly IRemoteImageUrlValidator _urlValidator;
public RemoteImageProvider(IHttpClientFactory clientFactory, IRemoteImageUrlValidator urlValidator)
{
_clientFactory = clientFactory;
_urlValidator = urlValidator;
}
public bool IsValidRequest(HttpContext context)
{
var url = GetRemoteUrl(context);
return Uri.IsWellFormedUriString(url, UriKind.Absolute) && _urlValidator.IsValidUrl(new Uri(url));
}
public Task<IImageResolver> GetAsync(HttpContext context)
{
var url = GetRemoteUrl(context);
return Task.FromResult(new RemoteImageResolver(_clientFactory, url) as IImageResolver);
}
private static bool PathStartsWithRemote(HttpContext context)
{
return context.Request.Path.StartsWithSegments(RemotePath, StringComparison.OrdinalIgnoreCase);
}
private static string GetRemoteUrl(HttpContext context)
{
if (!context.Request.Path.HasValue || context.Request.Path.Value.Length <= RemotePath.Length)
{
return null;
}
var remoteUrl = context.Request.Path.Value?.Substring(RemotePath.Length + 1);
return remoteUrl.Replace(" ", "%20");
}
}
}
using System;
using System.IO;
using System.Net.Http;
using System.Threading.Tasks;
using SixLabors.ImageSharp.Web;
using SixLabors.ImageSharp.Web.Resolvers;
namespace SixLabors.ImageSharp.Web.Providers
{
public class RemoteImageResolver: IImageResolver
{
private readonly IHttpClientFactory _clientFactory;
private readonly string _url;
public const string HttpClientName = "RemoteImageResolver";
public RemoteImageResolver(IHttpClientFactory clientFactory, string url)
{
_clientFactory = clientFactory;
_url = url;
}
public async Task<ImageMetadata> GetMetaDataAsync()
{
var client = _clientFactory.CreateClient(HttpClientName);
var response = await client.SendAsync(new HttpRequestMessage(HttpMethod.Head, _url), HttpCompletionOption.ResponseHeadersRead);
if (!response.Content.Headers.ContentLength.HasValue)
{
throw new Exception("Required header ContentLength is missing.");
}
if (!response.Content.Headers.LastModified.HasValue || !response.Content.Headers.ContentLength.HasValue)
{
throw new Exception("Required header LastModified is missing.");
}
return new ImageMetadata(response.Content.Headers.LastModified.Value.UtcDateTime, response.Content.Headers.ContentLength.Value);
}
public async Task<Stream> OpenReadAsync()
{
var client = _clientFactory.CreateClient(HttpClientName);
var response = await client.GetAsync(_url);
return await response.Content.ReadAsStreamAsync();
}
}
}
namespace PMP.Frontend.Core.Startup
{
public class Startup
{
//...
public void ConfigureServices(IServiceCollection services)
{
//...
services.AddHttpClient(RemoteImageResolver.HttpClientName, client =>
{
client.Timeout = TimeSpan.FromSeconds(5);
client.MaxResponseContentBufferSize = 8388608; //8mb
});
services
.AddImageSharp()
.ClearProviders()
.AddProvider<RemoteImageProvider>()
.SetRequestParser<PresetRequestParser>()
.Configure<PresetRequestParserOptions>(_configuration.GetSection("ImageSharp.Web"))
.Configure<AllowedDomainsRemoteImageUrlValidatorOptions>(_configuration.GetSection("ImageSharp.Web"));
//...
}
//...
}
}
@thenexus00
Copy link

This looks good Mark. How would you use it in the views/templates ?

@marklagendijk
Copy link
Author

The RemoteImageProvider handles everything under the /remote path.
You can pass it an url to an image, but with :// replaced by /:

<img src="/remote/https/example.com/images/image.png" />
<img src="/remote/https/example.com/images/image.png?preset=MyPreset" />
<img src="/remote/https/example.com/images/image.png?width=50&height=50&rmode=stretch" />

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment