mirror of https://github.com/SixLabors/ImageSharp
7 changed files with 1060 additions and 193 deletions
@ -0,0 +1,64 @@ |
|||
// License: CPOL at http://www.codeproject.com/info/cpol10.aspx
|
|||
using System.Collections.Generic; |
|||
using System.Net; |
|||
using System.Threading; |
|||
using System.Threading.Tasks; |
|||
|
|||
namespace System.IO |
|||
{ |
|||
public static class AsyncIoExtensions |
|||
{ |
|||
public static Task<Stream> GetRequestStreamAsync(this WebRequest webRequest) |
|||
{ |
|||
return Task.Factory.FromAsync( |
|||
webRequest.BeginGetRequestStream, |
|||
ar => webRequest.EndGetRequestStream(ar), |
|||
null); |
|||
} |
|||
|
|||
public static Task<WebResponse> GetResponseAsync(this WebRequest webRequest) |
|||
{ |
|||
return Task.Factory.FromAsync( |
|||
webRequest.BeginGetResponse, |
|||
ar => webRequest.EndGetResponse(ar), |
|||
null); |
|||
} |
|||
|
|||
public static Task<int> ReadAsync(this Stream input, Byte[] buffer, int offset, int count) |
|||
{ |
|||
return Task.Factory.FromAsync( |
|||
input.BeginRead, |
|||
(Func<IAsyncResult, int>)input.EndRead, |
|||
buffer, offset, count, |
|||
null); |
|||
} |
|||
|
|||
public static Task WriteAsync(this Stream input, Byte[] buffer, int offset, int count) |
|||
{ |
|||
return Task.Factory.FromAsync( |
|||
input.BeginWrite, |
|||
input.EndWrite, |
|||
buffer, offset, count, |
|||
null); |
|||
} |
|||
|
|||
public static /*async*/ Task CopyToAsync(this Stream input, Stream output, CancellationToken cancellationToken = default(CancellationToken)) |
|||
{ |
|||
return CopyToAsyncTasks(input, output, cancellationToken).ToTask(); |
|||
} |
|||
private static IEnumerable<Task> CopyToAsyncTasks(Stream input, Stream output, CancellationToken cancellationToken) |
|||
{ |
|||
byte[] buffer = new byte[0x1000]; // 4 KiB
|
|||
while (true) |
|||
{ |
|||
cancellationToken.ThrowIfCancellationRequested(); |
|||
var readTask = input.ReadAsync(buffer, 0, buffer.Length); |
|||
yield return readTask; |
|||
if (readTask.Result == 0) break; |
|||
|
|||
cancellationToken.ThrowIfCancellationRequested(); |
|||
yield return output.WriteAsync(buffer, 0, readTask.Result); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,347 @@ |
|||
// -----------------------------------------------------------------------
|
|||
// <copyright file="RemoteFile.cs" company="James South">
|
|||
// Copyright (c) James South.
|
|||
// Dual licensed under the MIT or GPL Version 2 licenses.
|
|||
// </copyright>
|
|||
// -----------------------------------------------------------------------
|
|||
|
|||
namespace ImageProcessor.Web.Helpers |
|||
{ |
|||
#region Using
|
|||
using System; |
|||
using System.Diagnostics.Contracts; |
|||
using System.Globalization; |
|||
using System.IO; |
|||
using System.Linq; |
|||
using System.Net; |
|||
using System.Security; |
|||
using System.Text; |
|||
using ImageProcessor.Web.Config; |
|||
#endregion
|
|||
|
|||
/// <summary>
|
|||
/// Encapsulates methods used to download files from a website address.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// <para>
|
|||
/// The purpose of this class is so there's one core way of downloading remote files with url[s] that are from
|
|||
/// outside users. There's various areas in application where an attacker could supply an external url to the server
|
|||
/// and tie up resources.
|
|||
/// </para>
|
|||
/// For example, the ImageProcessingModule accepts off-server addresses as a path. An attacker could, for instance, pass the url
|
|||
/// to a file that's a few gigs in size, causing the server to get out-of-memory exceptions or some other errors. An attacker
|
|||
/// could also use this same method to use one application instance to hammer another site by, again, passing an off-server
|
|||
/// address of the victims site to the ImageProcessingModule.
|
|||
/// This class will not throw an exception if the Uri supplied points to a resource local to the running application instance.
|
|||
/// <para>
|
|||
/// There shouldn't be any security issues there, as the internal WebRequest instance is still calling it remotely.
|
|||
/// Any local files that shouldn't be accessed by this won't be allowed by the remote call.
|
|||
/// </para>
|
|||
/// Adapted from <see cref="http://blogengine.codeplex.com">BlogEngine.Net</see>
|
|||
/// </remarks>
|
|||
internal sealed class RemoteFile |
|||
{ |
|||
#region Fields
|
|||
/// <summary>
|
|||
/// The white-list of url[s] from which to download remote files.
|
|||
/// </summary>
|
|||
private static readonly Uri[] RemoteFileWhiteList = ImageProcessorConfig.Instance.RemoteFileWhiteList; |
|||
|
|||
/// <summary>
|
|||
/// The length of time, in milliseconds, that a remote file download attempt can last before timing out.
|
|||
/// </summary>
|
|||
private static readonly int TimeoutMilliseconds = ImageProcessorConfig.Instance.Timeout; |
|||
|
|||
/// <summary>
|
|||
/// The maximum size, in bytes, that a remote file download attempt can download.
|
|||
/// </summary>
|
|||
private static readonly int MaxBytes = ImageProcessorConfig.Instance.MaxBytes; |
|||
|
|||
/// <summary>
|
|||
/// Whether to allow remote downloads.
|
|||
/// </summary>
|
|||
private static readonly bool AllowRemoteDownloads = ImageProcessorConfig.Instance.AllowRemoteDownloads; |
|||
|
|||
/// <summary>
|
|||
/// Whether this RemoteFile instance is ignoring remote download rules set in the current application
|
|||
/// instance.
|
|||
/// </summary>
|
|||
private readonly bool ignoreRemoteDownloadSettings; |
|||
|
|||
/// <summary>
|
|||
/// The <see cref="T:System.Uri">Uri</see> of the remote file being downloaded.
|
|||
/// </summary>
|
|||
private readonly Uri url; |
|||
|
|||
/// <summary>
|
|||
/// The maximum allowable download size in bytes.
|
|||
/// </summary>
|
|||
private readonly int maxDownloadSize; |
|||
|
|||
/// <summary>
|
|||
/// The length of time, in milliseconds, that a remote file download attempt can last before timing out.
|
|||
/// </summary>
|
|||
private int timeoutLength; |
|||
|
|||
/// <summary>
|
|||
/// The <see cref="T:System.Net.WebResponse">WebResponse</see> object used internally for this RemoteFile instance.
|
|||
/// </summary>
|
|||
private WebRequest webRequest; |
|||
#endregion
|
|||
|
|||
#region Constructors
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="T:ImageProcessor.Web.Helpers.RemoteFile">RemoteFile</see> class.
|
|||
/// </summary>
|
|||
/// <param name="filePath">The url of the file to be downloaded.</param>
|
|||
/// <param name="ignoreRemoteDownloadSettings">
|
|||
/// If set to <see langword="true"/>, then RemoteFile should ignore the current the applications instance's remote download settings; otherwise,<see langword="false"/>.
|
|||
/// </param>
|
|||
internal RemoteFile(Uri filePath, bool ignoreRemoteDownloadSettings) |
|||
{ |
|||
Contract.Requires(filePath != null); |
|||
|
|||
this.url = filePath; |
|||
this.ignoreRemoteDownloadSettings = ignoreRemoteDownloadSettings; |
|||
this.timeoutLength = TimeoutMilliseconds; |
|||
this.maxDownloadSize = MaxBytes; |
|||
} |
|||
#endregion
|
|||
|
|||
#region Properties
|
|||
/// <summary>
|
|||
/// Gets a value indicating whether this RemoteFile instance is ignoring remote download rules set in the
|
|||
/// current application instance.
|
|||
/// <remarks>
|
|||
/// This should only be set to true if the supplied url is a verified resource. Use at your own risk.
|
|||
/// </remarks>
|
|||
/// </summary>
|
|||
/// <value>
|
|||
/// <see langword="true"/> if this RemoteFile instance is ignoring remote download rules set in the current
|
|||
/// application instance; otherwise, <see langword="false"/>.
|
|||
/// </value>
|
|||
public bool IgnoreRemoteDownloadSettings |
|||
{ |
|||
get |
|||
{ |
|||
return this.ignoreRemoteDownloadSettings; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the Uri of the remote file being downloaded.
|
|||
/// </summary>
|
|||
public Uri Uri |
|||
{ |
|||
get |
|||
{ |
|||
return this.url; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the length of time, in milliseconds, that a remote file download attempt can
|
|||
/// last before timing out.
|
|||
/// <remarks>
|
|||
/// <para>
|
|||
/// This value can only be set if the instance is supposed to ignore the remote download settings set
|
|||
/// in the current application instance.
|
|||
/// </para>
|
|||
/// <para>
|
|||
/// Set this value to 0 if there should be no timeout.
|
|||
/// </para>
|
|||
/// </remarks>
|
|||
/// </summary>
|
|||
public int TimeoutLength |
|||
{ |
|||
get |
|||
{ |
|||
return this.IgnoreRemoteDownloadSettings ? this.timeoutLength : TimeoutMilliseconds; |
|||
} |
|||
|
|||
set |
|||
{ |
|||
if (!this.IgnoreRemoteDownloadSettings) |
|||
{ |
|||
throw new SecurityException("Timeout length can not be adjusted on remote files that are abiding by remote download rules"); |
|||
} |
|||
|
|||
if (value < 0) |
|||
{ |
|||
throw new ArgumentOutOfRangeException("TimeoutLength"); |
|||
} |
|||
|
|||
this.timeoutLength = value; |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the maximum download size, in bytes, that a remote file download attempt can be.
|
|||
/// <remarks>
|
|||
/// <para>
|
|||
/// This value can only be set if the instance is supposed to ignore the remote download settings set
|
|||
/// in the current application instance.
|
|||
/// </para>
|
|||
/// <para>
|
|||
/// Set this value to 0 if there should be no timeout.
|
|||
/// </para>
|
|||
/// </remarks>
|
|||
/// </summary>
|
|||
public int MaxDownloadSize |
|||
{ |
|||
get |
|||
{ |
|||
return this.IgnoreRemoteDownloadSettings ? this.maxDownloadSize : MaxBytes; |
|||
} |
|||
|
|||
set |
|||
{ |
|||
if (!this.IgnoreRemoteDownloadSettings) |
|||
{ |
|||
throw new SecurityException("Max Download Size can not be adjusted on remote files that are abiding by remote download rules"); |
|||
} |
|||
|
|||
if (value < 0) |
|||
{ |
|||
throw new ArgumentOutOfRangeException("MaxDownloadSize"); |
|||
} |
|||
|
|||
this.timeoutLength = value; |
|||
} |
|||
} |
|||
#endregion
|
|||
|
|||
#region Methods
|
|||
#region Public
|
|||
/// <summary>
|
|||
/// Returns the <see cref="T:System.Net.WebResponse">WebResponse</see> used to download this file.
|
|||
/// <remarks>
|
|||
/// <para>
|
|||
/// This method is meant for outside users who need specific access to the WebResponse this class
|
|||
/// generates. They're responsible for disposing of it.
|
|||
/// </para>
|
|||
/// </remarks>
|
|||
/// </summary>
|
|||
/// <returns>The <see cref="T:System.Net.WebResponse">WebResponse</see> used to download this file.</returns>
|
|||
public WebResponse GetWebResponse() |
|||
{ |
|||
WebResponse response = this.GetWebRequest().GetResponse(); |
|||
|
|||
long contentLength = response.ContentLength; |
|||
|
|||
// WebResponse.ContentLength doesn't always know the value, it returns -1 in this case.
|
|||
if (contentLength == -1) |
|||
{ |
|||
// Response headers may still have the Content-Length inside of it.
|
|||
string headerContentLength = response.Headers["Content-Length"]; |
|||
|
|||
if (!string.IsNullOrWhiteSpace(headerContentLength)) |
|||
{ |
|||
contentLength = long.Parse(headerContentLength, CultureInfo.InvariantCulture); |
|||
} |
|||
} |
|||
|
|||
// We don't need to check the url here since any external urls are available only from the web.config.
|
|||
if ((this.MaxDownloadSize > 0) && (contentLength > this.MaxDownloadSize)) |
|||
{ |
|||
response.Close(); |
|||
throw new SecurityException("An attempt to download a remote file has been halted because the file is larger than allowed."); |
|||
} |
|||
|
|||
return response; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Returns the remote file as a String.
|
|||
/// <remarks>
|
|||
/// This returns the resulting stream as a string as passed through a StreamReader.
|
|||
/// </remarks>
|
|||
/// </summary>
|
|||
/// <returns>The remote file as a String.</returns>
|
|||
public string GetFileAsString() |
|||
{ |
|||
using (WebResponse response = this.GetWebResponse()) |
|||
{ |
|||
Stream responseStream = response.GetResponseStream(); |
|||
|
|||
if (responseStream != null) |
|||
{ |
|||
// Pipe the stream to a stream reader with the required encoding format.
|
|||
using (StreamReader reader = new StreamReader(responseStream, Encoding.UTF8)) |
|||
{ |
|||
return reader.ReadToEnd(); |
|||
} |
|||
} |
|||
|
|||
return string.Empty; |
|||
} |
|||
} |
|||
#endregion
|
|||
|
|||
#region Private
|
|||
/// <summary>
|
|||
/// Performs a check to see whether the application is able to download remote files.
|
|||
/// </summary>
|
|||
private void CheckCanDownload() |
|||
{ |
|||
if (!this.IgnoreRemoteDownloadSettings && !AllowRemoteDownloads) |
|||
{ |
|||
throw new SecurityException("application is not configured to allow remote file downloads."); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Creates the WebRequest object used internally for this RemoteFile instance.
|
|||
/// </summary>
|
|||
/// <returns>
|
|||
/// <para>
|
|||
/// The WebRequest should not be passed outside of this instance, as it will allow tampering. Anyone
|
|||
/// that needs more fine control over the downloading process should probably be using the WebRequest
|
|||
/// class on its own.
|
|||
/// </para>
|
|||
/// </returns>
|
|||
private WebRequest GetWebRequest() |
|||
{ |
|||
// Check downloads are allowed.
|
|||
this.CheckCanDownload(); |
|||
|
|||
// Check the url is from a whitelisted location.
|
|||
this.CheckSafeUrlLocation(); |
|||
|
|||
if (this.webRequest == null) |
|||
{ |
|||
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(this.Uri); |
|||
request.Headers["Accept-Encoding"] = "gzip"; |
|||
request.Headers["Accept-Language"] = "en-us"; |
|||
request.Credentials = CredentialCache.DefaultNetworkCredentials; |
|||
request.AutomaticDecompression = DecompressionMethods.GZip; |
|||
|
|||
if (this.TimeoutLength > 0) |
|||
{ |
|||
request.Timeout = this.TimeoutLength; |
|||
} |
|||
|
|||
this.webRequest = request; |
|||
} |
|||
|
|||
return this.webRequest; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Returns a value indicating whether the current url is in a list of safe download locations.
|
|||
/// </summary>
|
|||
private void CheckSafeUrlLocation() |
|||
{ |
|||
bool validUrl = RemoteFileWhiteList.Any(item => item.Host.ToUpperInvariant().Equals(this.url.Host.ToUpperInvariant())); |
|||
|
|||
if (!validUrl) |
|||
{ |
|||
throw new SecurityException("application is not configured to allow remote file downloads from this domain."); |
|||
} |
|||
} |
|||
#endregion
|
|||
#endregion
|
|||
} |
|||
} |
|||
|
|||
|
|||
@ -0,0 +1,342 @@ |
|||
// -----------------------------------------------------------------------
|
|||
// <copyright file="ImageProcessingModule.cs" company="James South">
|
|||
// Copyright (c) James South.
|
|||
// Dual licensed under the MIT or GPL Version 2 licenses.
|
|||
// </copyright>
|
|||
// -----------------------------------------------------------------------
|
|||
|
|||
namespace ImageProcessor.Web.HttpModules |
|||
{ |
|||
#region Using
|
|||
using System; |
|||
using System.IO; |
|||
using System.Net; |
|||
using System.Reflection; |
|||
using System.Web; |
|||
using System.Web.Hosting; |
|||
using ImageProcessor.Helpers.Extensions; |
|||
using ImageProcessor.Imaging; |
|||
using ImageProcessor.Web.Caching; |
|||
using ImageProcessor.Web.Config; |
|||
using ImageProcessor.Web.Helpers; |
|||
#endregion
|
|||
|
|||
/// <summary>
|
|||
/// Processes any image requests within the web application.
|
|||
/// </summary>
|
|||
public class ImageProcessingModule : IHttpModule |
|||
{ |
|||
#region Fields
|
|||
/// <summary>
|
|||
/// The key for storing the response type of the current image.
|
|||
/// </summary>
|
|||
private const string CachedResponseTypeKey = "CACHED_IMAGE_RESPONSE_TYPE"; |
|||
|
|||
/// <summary>
|
|||
/// The value to prefix any remote image requests with to ensure they get captured.
|
|||
/// </summary>
|
|||
private static readonly string RemotePrefix = ImageProcessorConfig.Instance.RemotePrefix; |
|||
|
|||
/// <summary>
|
|||
/// The object to lock against.
|
|||
/// </summary>
|
|||
private static readonly object SyncRoot = new object(); |
|||
|
|||
/// <summary>
|
|||
/// The assembly version.
|
|||
/// </summary>
|
|||
private static readonly string AssemblyVersion = Assembly.GetExecutingAssembly().GetName().Version.ToString(); |
|||
|
|||
/// <summary>
|
|||
/// A value indicating whether the application has started.
|
|||
/// </summary>
|
|||
private static bool hasModuleInitialized; |
|||
#endregion
|
|||
|
|||
/// <summary>
|
|||
/// The delegate void representing the ProcessImage method.
|
|||
/// </summary>
|
|||
/// <param name="context">
|
|||
/// the <see cref="T:System.Web.HttpContext">HttpContext</see> object that provides
|
|||
/// references to the intrinsic server objects
|
|||
/// </param>
|
|||
private delegate void ProcessImageDelegate(HttpContext context); |
|||
|
|||
#region IHttpModule Members
|
|||
/// <summary>
|
|||
/// Initializes a module and prepares it to handle requests.
|
|||
/// </summary>
|
|||
/// <param name="context">
|
|||
/// An <see cref="T:System.Web.HttpApplication"/> that provides
|
|||
/// access to the methods, properties, and events common to all
|
|||
/// application objects within an ASP.NET application
|
|||
/// </param>
|
|||
public void Init(HttpApplication context) |
|||
{ |
|||
if (!hasModuleInitialized) |
|||
{ |
|||
lock (SyncRoot) |
|||
{ |
|||
if (!hasModuleInitialized) |
|||
{ |
|||
DiskCache.CreateCacheDirectories(); |
|||
hasModuleInitialized = true; |
|||
} |
|||
} |
|||
} |
|||
|
|||
context.AddOnBeginRequestAsync(this.OnBeginAsync, this.OnEndAsync); |
|||
context.PreSendRequestHeaders += this.ContextPreSendRequestHeaders; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Disposes of the resources (other than memory) used by the module that implements <see cref="T:System.Web.IHttpModule"/>.
|
|||
/// </summary>
|
|||
public void Dispose() |
|||
{ |
|||
// Nothing to dispose.
|
|||
} |
|||
#endregion
|
|||
|
|||
/// <summary>
|
|||
/// The <see cref="T:System.Web.BeginEventHandler"/> that starts asynchronous processing
|
|||
/// of the <see cref="System.Web.HttpApplication.BeginRequest"/>.
|
|||
/// </summary>
|
|||
/// <param name="sender">The source of the event.</param>
|
|||
/// <param name="e">
|
|||
/// An <see cref="T:System.EventArgs">EventArgs</see> that contains
|
|||
/// the event data.
|
|||
/// </param>
|
|||
/// <param name="callBack">
|
|||
/// The delegate to call when the asynchronous method call is complete.
|
|||
/// If the callback is null, the delegate is not called.
|
|||
/// </param>
|
|||
/// <param name="state">
|
|||
/// Any additional data needed to process the request.
|
|||
/// </param>
|
|||
/// <returns>
|
|||
/// The status of the asynchronous operation.
|
|||
/// </returns>
|
|||
private IAsyncResult OnBeginAsync(object sender, EventArgs e, AsyncCallback callBack, object state) |
|||
{ |
|||
HttpContext context = ((HttpApplication)sender).Context; |
|||
|
|||
ProcessImageDelegate processImage = this.ProcessImage; |
|||
|
|||
return processImage.BeginInvoke(context, callBack, state); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// The method that handles asynchronous events such as application events.
|
|||
/// </summary>
|
|||
/// <param name="result">
|
|||
/// The <see cref="T:System.IAsyncResult"/> that is the result of the
|
|||
/// <see cref="T:System.Web.BeginEventHandler"/> operation.
|
|||
/// </param>
|
|||
private void OnEndAsync(IAsyncResult result) |
|||
{ |
|||
// Ensure our ProcessImage has completed in the background.
|
|||
while (!result.IsCompleted) |
|||
{ |
|||
System.Threading.Thread.Sleep(1); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Occurs just before ASP.NET send HttpHeaders to the client.
|
|||
/// </summary>
|
|||
/// <param name="sender">The source of the event.</param>
|
|||
/// <param name="e">An <see cref="T:System.EventArgs">EventArgs</see> that contains the event data.</param>
|
|||
private void ContextPreSendRequestHeaders(object sender, EventArgs e) |
|||
{ |
|||
HttpContext context = ((HttpApplication)sender).Context; |
|||
|
|||
object responseTypeObject = context.Items[CachedResponseTypeKey]; |
|||
|
|||
if (responseTypeObject != null) |
|||
{ |
|||
string responseType = (string)responseTypeObject; |
|||
|
|||
// Set the headers
|
|||
this.SetHeaders(context, responseType); |
|||
|
|||
context.Items[CachedResponseTypeKey] = null; |
|||
} |
|||
} |
|||
|
|||
#region Private
|
|||
/// <summary>
|
|||
/// Processes the image.
|
|||
/// </summary>
|
|||
/// <param name="context">
|
|||
/// the <see cref="T:System.Web.HttpContext">HttpContext</see> object that provides
|
|||
/// references to the intrinsic server objects
|
|||
/// </param>
|
|||
private void ProcessImage(HttpContext context) |
|||
{ |
|||
// Is this a remote file.
|
|||
bool isRemote = context.Request.Path.Equals(RemotePrefix, StringComparison.OrdinalIgnoreCase); |
|||
string path = string.Empty; |
|||
string queryString = string.Empty; |
|||
|
|||
if (isRemote) |
|||
{ |
|||
// We need to split the querystring to get the actual values we want.
|
|||
string urlDecode = HttpUtility.UrlDecode(context.Request.QueryString.ToString()); |
|||
|
|||
if (urlDecode != null) |
|||
{ |
|||
string[] paths = urlDecode.Split('?'); |
|||
|
|||
path = paths[0]; |
|||
|
|||
if (paths.Length > 1) |
|||
{ |
|||
queryString = paths[1]; |
|||
} |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
path = HostingEnvironment.MapPath(context.Request.Path); |
|||
queryString = HttpUtility.UrlDecode(context.Request.QueryString.ToString()); |
|||
} |
|||
|
|||
// Only process requests that pass our sanitizing filter.
|
|||
if (ImageUtils.IsValidImageExtension(path) && !string.IsNullOrWhiteSpace(queryString)) |
|||
{ |
|||
if (this.FileExists(path, isRemote)) |
|||
{ |
|||
string fullPath = string.Format("{0}?{1}", path, queryString); |
|||
string imageName = Path.GetFileName(path); |
|||
string cachedPath = DiskCache.GetCachePath(fullPath, imageName); |
|||
bool isUpdated = DiskCache.IsUpdatedFile(path, cachedPath, isRemote); |
|||
|
|||
// Only process if the file has been updated.
|
|||
if (isUpdated) |
|||
{ |
|||
// Process the image.
|
|||
using (ImageFactory imageFactory = new ImageFactory()) |
|||
{ |
|||
if (isRemote) |
|||
{ |
|||
Uri uri = new Uri(path); |
|||
RemoteFile remoteFile = new RemoteFile(uri, false); |
|||
|
|||
using (MemoryStream memoryStream = new MemoryStream()) |
|||
{ |
|||
using (Stream responseStream = remoteFile.GetWebResponse().GetResponseStream()) |
|||
{ |
|||
if (responseStream != null) |
|||
{ |
|||
//lock (SyncRoot)
|
|||
//{
|
|||
// Trim the cache.
|
|||
DiskCache.TrimCachedFolders(); |
|||
|
|||
responseStream.CopyTo(memoryStream); |
|||
|
|||
imageFactory.Load(memoryStream) |
|||
.AddQueryString(queryString) |
|||
.Format(ImageUtils.GetImageFormat(imageName)) |
|||
.AutoProcess().Save(cachedPath); |
|||
|
|||
// Ensure that the LastWriteTime property of the source and cached file match.
|
|||
DateTime dateTime = DiskCache.SetCachedLastWriteTime(path, cachedPath, true); |
|||
|
|||
// Add to the cache.
|
|||
DiskCache.AddImageToCache(cachedPath, dateTime); |
|||
//}
|
|||
} |
|||
} |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
//lock (SyncRoot)
|
|||
//{
|
|||
// Trim the cache.
|
|||
DiskCache.TrimCachedFolders(); |
|||
|
|||
imageFactory.Load(fullPath).AutoProcess().Save(cachedPath); |
|||
|
|||
// Ensure that the LastWriteTime property of the source and cached file match.
|
|||
DateTime dateTime = DiskCache.SetCachedLastWriteTime(path, cachedPath, false); |
|||
|
|||
// Add to the cache.
|
|||
DiskCache.AddImageToCache(cachedPath, dateTime); |
|||
//}
|
|||
} |
|||
} |
|||
} |
|||
|
|||
context.Items[CachedResponseTypeKey] = ImageUtils.GetResponseType(imageName).ToDescription(); |
|||
|
|||
// The cached file is valid so just rewrite the path.
|
|||
context.RewritePath(DiskCache.GetVirtualPath(cachedPath, context.Request), false); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// returns a value indicating whether a file exists.
|
|||
/// </summary>
|
|||
/// <param name="path">The path to the file to check.</param>
|
|||
/// <param name="isRemote">Whether the file is remote.</param>
|
|||
/// <returns>True if the file exists, otherwise false.</returns>
|
|||
/// <remarks>If the file is remote the method will always return true.</remarks>
|
|||
private bool FileExists(string path, bool isRemote) |
|||
{ |
|||
if (isRemote) |
|||
{ |
|||
return true; |
|||
} |
|||
|
|||
FileInfo fileInfo = new FileInfo(path); |
|||
return fileInfo.Exists; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// This will make the browser and server keep the output
|
|||
/// in its cache and thereby improve performance.
|
|||
/// See http://en.wikipedia.org/wiki/HTTP_ETag
|
|||
/// </summary>
|
|||
/// <param name="context">
|
|||
/// the <see cref="T:System.Web.HttpContext">HttpContext</see> object that provides
|
|||
/// references to the intrinsic server objects
|
|||
/// </param>
|
|||
/// <param name="responseType">The HTTP MIME type to to send.</param>
|
|||
private void SetHeaders(HttpContext context, string responseType) |
|||
{ |
|||
HttpResponse response = context.Response; |
|||
|
|||
response.ContentType = responseType; |
|||
|
|||
response.AddHeader("Image-Served-By", "ImageProcessor/" + AssemblyVersion); |
|||
|
|||
HttpCachePolicy cache = response.Cache; |
|||
|
|||
cache.VaryByHeaders["Accept-Encoding"] = true; |
|||
|
|||
int maxDays = DiskCache.MaxFileCachedDuration; |
|||
|
|||
cache.SetExpires(DateTime.Now.ToUniversalTime().AddDays(maxDays)); |
|||
cache.SetMaxAge(new TimeSpan(maxDays, 0, 0, 0)); |
|||
cache.SetRevalidation(HttpCacheRevalidation.AllCaches); |
|||
|
|||
string incomingEtag = context.Request.Headers["If-None-Match"]; |
|||
|
|||
cache.SetCacheability(HttpCacheability.Public); |
|||
|
|||
if (incomingEtag == null) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
response.Clear(); |
|||
response.StatusCode = (int)HttpStatusCode.NotModified; |
|||
response.SuppressContent = true; |
|||
} |
|||
#endregion
|
|||
} |
|||
} |
|||
Loading…
Reference in new issue