Ejemplo n.º 1
0
        /// <summary>
        /// Loads the image to process. Always call this method first.
        /// </summary>
        /// <param name="stream">
        /// The <see cref="T:System.IO.Stream"/> containing the image information.
        /// </param>
        /// <returns>
        /// The current instance of the <see cref="T:ImageProcessor.ImageFactory"/> class.
        /// </returns>
        public ImageFactory Load(Stream stream)
        {
            ISupportedImageFormat format = FormatUtilities.GetFormat(stream);

            if (format == null)
            {
                throw new ImageFormatException("Input stream is not a supported format.");
            }

            MemoryStream memoryStream = new MemoryStream();

            // Copy the stream. Disposal of the input stream is the responsibility
            // of the user.
            stream.CopyTo(memoryStream);

            // Set the position to 0 afterwards.
            if (stream.CanSeek)
            {
                stream.Position = 0;
            }

            memoryStream.Position = 0;

            // Set our image as the memory stream value.
            this.Image = format.Load(memoryStream);

            // Store the stream so we can dispose of it later.
            this.InputStream = memoryStream;

            // Set the other properties.
            format.Quality   = DefaultQuality;
            format.IsIndexed = FormatUtilities.IsIndexed(this.Image);

            IQuantizableImageFormat imageFormat = format as IQuantizableImageFormat;

            if (imageFormat != null)
            {
                imageFormat.ColorCount = FormatUtilities.GetColorCount(this.Image);
            }

            this.backupFormat       = format;
            this.CurrentImageFormat = format;

            // Always load the data.
            // TODO. Some custom data doesn't seem to get copied by default methods.
            foreach (int id in this.Image.PropertyIdList)
            {
                this.ExifPropertyItems[id] = this.Image.GetPropertyItem(id);
            }

            this.ShouldProcess = true;

            // Normalize the gamma component of the image.
            if (this.FixGamma)
            {
                this.Gamma(2.2F);
            }

            return(this);
        }
Ejemplo n.º 2
0
        /// <summary>
        /// Loads the image to process. Always call this method first.
        /// </summary>
        /// <param name="imagePath">The absolute path to the image to load.</param>
        /// <returns>
        /// The current instance of the <see cref="T:ImageProcessor.ImageFactory"/> class.
        /// </returns>
        public ImageFactory Load(string imagePath)
        {
            FileInfo fileInfo = new FileInfo(imagePath);

            if (fileInfo.Exists)
            {
                this.ImagePath = imagePath;

                // Open a file stream to prevent the need for lock.
                using (FileStream fileStream = new FileStream(imagePath, FileMode.Open, FileAccess.Read))
                {
                    ISupportedImageFormat format = FormatUtilities.GetFormat(fileStream);

                    if (format == null)
                    {
                        throw new ImageFormatException("Input stream is not a supported format.");
                    }

                    MemoryStream memoryStream = new MemoryStream();

                    // Copy the stream.
                    fileStream.CopyTo(memoryStream);

                    // Set the position to 0 afterwards.
                    fileStream.Position = memoryStream.Position = 0;

                    // Set our image as the memory stream value.
                    this.Image = format.Load(memoryStream);

                    // Store the stream so we can dispose of it later.
                    this.InputStream = memoryStream;

                    // Set the other properties.
                    format.Quality          = DefaultQuality;
                    format.IsIndexed        = FormatUtilities.IsIndexed(this.Image);
                    this.backupFormat       = format;
                    this.CurrentImageFormat = format;

                    // Always load the data.
                    foreach (PropertyItem propertyItem in this.Image.PropertyItems)
                    {
                        this.ExifPropertyItems[propertyItem.Id] = propertyItem;
                    }

                    this.ShouldProcess = true;
                }
            }
            else
            {
                throw new FileNotFoundException(imagePath);
            }

            return(this);
        }
Ejemplo n.º 3
0
        /// <summary>
        /// Loads the image to process. Always call this method first.
        /// </summary>
        /// <param name="stream">
        /// The <see cref="T:System.IO.Stream"/> containing the image information.
        /// </param>
        /// <returns>
        /// The current instance of the <see cref="T:ImageProcessor.ImageFactory"/> class.
        /// </returns>
        public ImageFactory Load(Stream stream)
        {
            MemoryStream memoryStream = new MemoryStream();

            // Copy the stream. Disposal of the input stream is the responsibility
            // of the user.
            stream.CopyTo(memoryStream);

            // Set the position to 0 afterwards.
            if (stream.CanSeek)
            {
                stream.Position = 0;
            }

            ISupportedImageFormat format = FormatUtilities.GetFormat(memoryStream);

            if (format == null)
            {
                throw new ImageFormatException("Input stream is not a supported format.");
            }

            // Set our image as the memory stream value.
            this.Image = format.Load(memoryStream);

            // Store the stream so we can dispose of it later.
            this.InputStream = memoryStream;

            // Set the other properties.
            format.Quality   = DefaultQuality;
            format.IsIndexed = FormatUtilities.IsIndexed(this.Image);

            this.backupFormat       = format;
            this.CurrentImageFormat = format;

            // Always load the data.
            // TODO. Some custom data doesn't seem to get copied by default methods.
            foreach (int id in this.Image.PropertyIdList)
            {
                this.ExifPropertyItems[id] = this.Image.GetPropertyItem(id);
            }

            this.backupExifPropertyItems = this.ExifPropertyItems;

            // Ensure the image is in the most efficient format.
            Image formatted = this.Image.Copy();

            this.Image.Dispose();
            this.Image = formatted;

            this.ShouldProcess = true;

            return(this);
        }
Ejemplo n.º 4
0
        /// <summary>
        /// Get the correct mime-type for the given string input.
        /// </summary>
        /// <param name="path">
        /// The path to the cached image.
        /// </param>
        /// <returns>
        /// The <see cref="string"/> matching the correct mime-type.
        /// </returns>
        public static string GetMimeType(string path)
        {
            using (FileStream file = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read, 4096, false))
            {
                ISupportedImageFormat format = FormatUtilities.GetFormat(file);

                if (format != null)
                {
                    return(format.MimeType);
                }
            }

            return(string.Empty);
        }
Ejemplo n.º 5
0
        /// <summary>
        /// Loads the image to process from an array of bytes. Always call this method first.
        /// </summary>
        /// <param name="bytes">
        /// The <see cref="T:System.Byte"/> containing the image information.
        /// </param>
        /// <returns>
        /// The current instance of the <see cref="T:ImageProcessor.ImageFactory"/> class.
        /// </returns>
        public ImageFactory Load(byte[] bytes)
        {
            MemoryStream memoryStream = new MemoryStream(bytes);

            ISupportedImageFormat format = FormatUtilities.GetFormat(memoryStream);

            if (format == null)
            {
                throw new ImageFormatException("Input stream is not a supported format.");
            }

            // Set our image as the memory stream value.
            this.Image = format.Load(memoryStream);

            // Store the stream so we can dispose of it later.
            this.InputStream = memoryStream;

            // Set the other properties.
            format.Quality   = DefaultQuality;
            format.IsIndexed = FormatUtilities.IsIndexed(this.Image);

            IQuantizableImageFormat imageFormat = format as IQuantizableImageFormat;

            if (imageFormat != null)
            {
                imageFormat.ColorCount = FormatUtilities.GetColorCount(this.Image);
            }

            this.backupFormat       = format;
            this.CurrentImageFormat = format;

            // Always load the data.
            foreach (int id in this.Image.PropertyIdList)
            {
                this.ExifPropertyItems[id] = this.Image.GetPropertyItem(id);
            }

            this.ShouldProcess = true;

            // Normalize the gamma component of the image.
            if (this.FixGamma)
            {
                this.Gamma(2.2F);
            }

            return(this);
        }
Ejemplo n.º 6
0
        /// <summary>
        /// Loads the image to process from an array of bytes. Always call this method first.
        /// </summary>
        /// <param name="bytes">
        /// The <see cref="T:System.Byte"/> containing the image information.
        /// </param>
        /// <returns>
        /// The current instance of the <see cref="T:ImageProcessor.ImageFactory"/> class.
        /// </returns>
        public ImageFactory Load(byte[] bytes)
        {
            MemoryStream memoryStream = new MemoryStream(bytes);

            ISupportedImageFormat format = FormatUtilities.GetFormat(memoryStream);

            if (format == null)
            {
                throw new ImageFormatException("Input stream is not a supported format.");
            }

            // Set our image as the memory stream value.
            this.Image = format.Load(memoryStream);

            // Store the stream so we can dispose of it later.
            this.InputStream = memoryStream;

            // Set the other properties.
            format.Quality   = DefaultQuality;
            format.IsIndexed = FormatUtilities.IsIndexed(this.Image);

            this.backupFormat       = format;
            this.CurrentImageFormat = format;

            // Always load the data.
            foreach (int id in this.Image.PropertyIdList)
            {
                this.ExifPropertyItems[id] = this.Image.GetPropertyItem(id);
            }

            // Ensure the image is in the most efficient format.
            Image formatted = this.Image.Copy();

            this.Image.Dispose();
            this.Image = formatted;

            this.ShouldProcess = true;

            return(this);
        }
        /// <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>
        /// <returns>
        /// The <see cref="T:System.Threading.Tasks.Task"/>.
        /// </returns>
        private async Task ProcessImageAsync(HttpContext context)
        {
            HttpRequest request = context.Request;
            string      rawUrl  = this.GetRequestUrl(request);

            // Should we ignore this request?
            if (string.IsNullOrWhiteSpace(rawUrl) || rawUrl.IndexOf("IPIGNORE=TRUE", StringComparison.InvariantCultureIgnoreCase) >= 0)
            {
                return;
            }

            // Sometimes the request is url encoded
            // See https://github.com/JimBobSquarePants/ImageProcessor/issues/478
            // This causes a bit of a nightmare as the incoming request is corrupted and cannot be used for splitting
            // out each url part. This becomes a manual job.
            string url             = rawUrl;
            string applicationPath = request.ApplicationPath;

            IImageService currentService = this.GetImageServiceForRequest(url, applicationPath);

            if (currentService == null)
            {
                return;
            }

            // Parse url
            UrlParser.ParseUrl(url, currentService.Prefix, out string requestPath, out string queryString);
            string originalQueryString = queryString;

            // Replace any presets in the querystring with the actual value.
            queryString = this.ReplacePresetsInQueryString(queryString);

            var httpContextBase = new HttpContextWrapper(context);

            // Execute the handler which can change the querystring
            var validatingArgs = new ValidatingRequestEventArgs(httpContextBase, queryString);

            this.OnValidatingRequest(validatingArgs);

            // If the validation has failed based on events, return
            if (validatingArgs.Cancel)
            {
                ImageProcessorBootstrapper.Instance.Logger.Log <ImageProcessingModule>($"Image processing for {url} has been cancelled by an event");
                return;
            }

            // Re-assign based on event handlers
            queryString = validatingArgs.QueryString;
            if (string.IsNullOrWhiteSpace(originalQueryString) && !string.IsNullOrWhiteSpace(queryString))
            {
                url = url + "?" + queryString;
            }
            else if (!string.IsNullOrWhiteSpace(queryString))
            {
                url = Regex.Replace(url, originalQueryString, queryString, RegexOptions.IgnoreCase);
            }

            // Map the request path if file local.
            bool isFileLocal = currentService.IsFileLocalService;

            if (currentService.IsFileLocalService)
            {
                requestPath = HostingEnvironment.MapPath(requestPath);
            }

            if (string.IsNullOrWhiteSpace(requestPath))
            {
                return;
            }

            // Parse any protocol values from settings if no protocol is present.
            if (currentService.Settings.ContainsKey("Protocol") && (ProtocolRegex.Matches(requestPath).Count == 0 || ProtocolRegex.Matches(requestPath)[0].Index > 0))
            {
                // ReSharper disable once PossibleNullReferenceException
                requestPath = currentService.Settings["Protocol"] + "://" + requestPath.TrimStart('/');
            }

            // Break out if we don't meet critera.
            // First check that the request path is valid and whether we are intercepting all requests or the querystring is valid.
            bool interceptAll = interceptAllRequests != null && interceptAllRequests.Value;

            if (string.IsNullOrWhiteSpace(requestPath) || (!interceptAll && string.IsNullOrWhiteSpace(queryString)))
            {
                return;
            }

            // Check whether the path is valid for other requests.
            // We've already checked the unprefixed requests in GetImageServiceForRequest().
            if (!string.IsNullOrWhiteSpace(currentService.Prefix) && !currentService.IsValidRequest(requestPath))
            {
                return;
            }

            bool   isNewOrUpdated = false;
            string cachedPath     = string.Empty;
            bool   processing     = false;

            IWebGraphicsProcessor[] processors = null;
            AnimationProcessMode    mode       = AnimationProcessMode.First;

            using (await Locker.ReaderLockAsync(rawUrl).ConfigureAwait(false))
            {
                // Parse the url to see whether we should be doing any work.
                // If we're not intercepting all requests and we don't have valid instructions we shoul break here.
                if (!string.IsNullOrWhiteSpace(queryString))
                {
                    // Attempt to match querystring and processors.
                    processors = ImageFactoryExtensions.GetMatchingProcessors(queryString);

                    // Animation is not a processor but can be a specific request so we should allow it.
                    mode = this.ParseAnimationMode(queryString, out bool processAnimation);

                    // Are we processing or cache busting?
                    processing = processors != null && (processors.Length > 0 || processAnimation);
                    bool cacheBusting = ParseCacheBuster(queryString);
                    if (!processing && !cacheBusting)
                    {
                        // No? Someone is either attacking the server or hasn't read the instructions.
                        string message = $"The request {request.Unvalidated.RawUrl} could not be understood by the server due to malformed syntax.";
                        ImageProcessorBootstrapper.Instance.Logger.Log <ImageProcessingModule>(message);
                        return;
                    }
                }

                // Create a new cache to help process and cache the request.
                this.imageCache = (IImageCache)ImageProcessorConfiguration.Instance
                                  .ImageCache.GetInstance(requestPath, url, queryString);

                // Is the file new or updated?
                isNewOrUpdated = await this.imageCache.IsNewOrUpdatedAsync().ConfigureAwait(false);

                cachedPath = this.imageCache.CachedPath;

                if (!isNewOrUpdated)
                {
                    // The cached file is valid so just rewrite the path.
                    this.imageCache.RewritePath(context);

                    // Redirect if not a locally store file.
                    if (!new Uri(cachedPath).IsFile)
                    {
                        context.ApplicationInstance.CompleteRequest();
                    }

                    return;
                }
            }

            // Only process if the file has been updated.
            using (await Locker.WriterLockAsync(rawUrl).ConfigureAwait(false))
            {
                // Ok let's get the image
                byte[] imageBuffer = null;
                string mimeType;

                try
                {
                    if (currentService is IImageService2 imageService2)
                    {
                        imageBuffer = await imageService2.GetImage(requestPath, context).ConfigureAwait(false);
                    }
                    else
                    {
                        imageBuffer = await currentService.GetImage(requestPath).ConfigureAwait(false);
                    }
                }
                catch (HttpException ex)
                {
                    // We want 404's to be handled by IIS so that other handlers/modules can still run.
                    if (ex.GetHttpCode() == (int)HttpStatusCode.NotFound)
                    {
                        ImageProcessorBootstrapper.Instance.Logger.Log <ImageProcessingModule>(ex.Message);
                        return;
                    }
                }

                if (imageBuffer == null)
                {
                    return;
                }

                // Using recyclable streams here should dramatically reduce the overhead required
                using (MemoryStream inStream = MemoryStreamPool.Shared.GetStream("inStream", imageBuffer, 0, imageBuffer.Length))
                {
                    // Process the Image. Use a recyclable stream here to reduce the allocations
                    MemoryStream outStream = MemoryStreamPool.Shared.GetStream();

                    if (!string.IsNullOrWhiteSpace(queryString))
                    {
                        if (processing)
                        {
                            // Process the image.
                            bool         exif     = preserveExifMetaData != null && preserveExifMetaData.Value;
                            MetaDataMode metaMode = !exif ? MetaDataMode.None : metaDataMode.Value;
                            bool         gamma    = fixGamma != null && fixGamma.Value;

                            try
                            {
                                using (var imageFactory = new ImageFactory(metaMode, gamma)
                                {
                                    AnimationProcessMode = mode
                                })
                                {
                                    imageFactory.Load(inStream).AutoProcess(processors).Save(outStream);
                                    mimeType = imageFactory.CurrentImageFormat.MimeType;
                                }
                            }
                            catch (ImageFormatException)
                            {
                                ImageProcessorBootstrapper.Instance.Logger.Log <ImageProcessingModule>($"Request {url} is not a valid image.");
                                return;
                            }
                        }
                        else
                        {
                            // We're cache-busting. Allow the value to be cached
                            await inStream.CopyToAsync(outStream).ConfigureAwait(false);

                            mimeType = FormatUtilities.GetFormat(outStream).MimeType;
                        }
                    }
                    else
                    {
                        // We're capturing all requests.
                        await inStream.CopyToAsync(outStream).ConfigureAwait(false);

                        mimeType = FormatUtilities.GetFormat(outStream).MimeType;
                    }

                    // Fire the post processing event.
                    EventHandler <PostProcessingEventArgs> handler = OnPostProcessing;
                    if (handler != null)
                    {
                        string extension = Path.GetExtension(cachedPath);
                        var    args      = new PostProcessingEventArgs
                        {
                            Context        = context,
                            ImageStream    = outStream,
                            ImageExtension = extension
                        };

                        handler(this, args);
                        outStream = args.ImageStream;
                    }

                    // Add to the cache.
                    await this.imageCache.AddImageToCacheAsync(outStream, mimeType).ConfigureAwait(false);

                    // Cleanup
                    outStream.Dispose();
                }

                // Store the response type and cache dependency in the context for later retrieval.
                context.Items[CachedResponseTypeKey] = mimeType;
                bool isFileCached = new Uri(cachedPath).IsFile;

                if (isFileLocal)
                {
                    if (isFileCached)
                    {
                        // Some services might only provide filename so we can't monitor for the browser.
                        context.Items[CachedResponseFileDependency] = Path.GetFileName(requestPath) == requestPath
                            ? new[] { cachedPath }
                            : new[] { requestPath, cachedPath };
                    }
                    else
                    {
                        context.Items[CachedResponseFileDependency] = Path.GetFileName(requestPath) == requestPath
                            ? null
                            : new[] { requestPath };
                    }
                }
                else if (isFileCached)
                {
                    context.Items[CachedResponseFileDependency] = new[] { cachedPath };
                }

                // The cached file has been saved so now rewrite the path.
                this.imageCache.RewritePath(context);

                // Redirect if not a locally store file.
                if (!new Uri(cachedPath).IsFile)
                {
                    context.ApplicationInstance.CompleteRequest();
                }

                // Trim the cache.
                await this.imageCache.TrimCacheAsync().ConfigureAwait(false);
            }
        }
Ejemplo n.º 8
0
        /// <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>
        /// <returns>
        /// The <see cref="T:System.Threading.Tasks.Task"/>.
        /// </returns>
        private async Task ProcessImageAsync(HttpContext context)
        {
            HttpRequest request = context.Request;

            // Should we ignore this request?
            if (request.Unvalidated.RawUrl.ToUpperInvariant().Contains("IPIGNORE=TRUE"))
            {
                return;
            }

            IImageService currentService = this.GetImageServiceForRequest(request);

            if (currentService != null)
            {
                bool   isFileLocal    = currentService.IsFileLocalService;
                string url            = request.Url.ToString();
                bool   isLegacy       = ProtocolRegex.Matches(url).Count > 1;
                bool   hasMultiParams = url.Count(f => f == '?') > 1;
                string requestPath;
                string queryString   = string.Empty;
                string urlParameters = string.Empty;

                // Legacy support. I'd like to remove this asap.
                if (isLegacy && hasMultiParams)
                {
                    // We need to split the querystring to get the actual values we want.
                    string[] paths = url.Split('?');
                    requestPath = paths[1];

                    // Handle extension-less urls.
                    if (paths.Length > 3)
                    {
                        queryString   = paths[3];
                        urlParameters = paths[2];
                    }
                    else if (paths.Length > 1)
                    {
                        queryString = paths[2];
                    }
                }
                else
                {
                    if (string.IsNullOrWhiteSpace(currentService.Prefix))
                    {
                        requestPath = currentService.IsFileLocalService
                            ? HostingEnvironment.MapPath(request.Path)
                            : request.Path;
                        queryString = request.QueryString.ToString();
                    }
                    else
                    {
                        // Parse any protocol values from settings.
                        string protocol = currentService.Settings.ContainsKey("Protocol")
                                              ? currentService.Settings["Protocol"] + "://"
                                              : currentService.GetType() == typeof(RemoteImageService) ? request.Url.Scheme + "://" : string.Empty;

                        // Handle requests that require parameters.
                        if (hasMultiParams)
                        {
                            string[] paths = url.Split('?');
                            requestPath = protocol
                                          + request.Path.TrimStart('/').Remove(0, currentService.Prefix.Length).TrimStart('/')
                                          + "?" + paths[1];
                            queryString = paths[2];
                        }
                        else
                        {
                            requestPath = protocol + request.Path.TrimStart('/').Remove(0, currentService.Prefix.Length).TrimStart('/');
                            queryString = request.QueryString.ToString();
                        }
                    }
                }

                // Replace any presets in the querystring with the actual value.
                queryString = this.ReplacePresetsInQueryString(queryString);

                // Execute the handler which can change the querystring
                queryString = this.CheckQuerystringHandler(context, queryString, request.Unvalidated.RawUrl);

                // Break out if we don't meet critera.
                bool interceptAll = interceptAllRequests != null && interceptAllRequests.Value;
                if (string.IsNullOrWhiteSpace(requestPath) || (!interceptAll && string.IsNullOrWhiteSpace(queryString)))
                {
                    return;
                }

                string parts    = !string.IsNullOrWhiteSpace(urlParameters) ? "?" + urlParameters : string.Empty;
                string fullPath = string.Format("{0}{1}?{2}", requestPath, parts, queryString);
                object resourcePath;

                // More legacy support code.
                if (hasMultiParams)
                {
                    resourcePath = string.IsNullOrWhiteSpace(urlParameters)
                        ? new Uri(requestPath, UriKind.RelativeOrAbsolute)
                        : new Uri(requestPath + "?" + urlParameters, UriKind.RelativeOrAbsolute);
                }
                else
                {
                    resourcePath = requestPath;
                }

                // Check whether the path is valid for other requests.
                if (!currentService.IsValidRequest(resourcePath.ToString()))
                {
                    return;
                }

                string combined = requestPath + fullPath + queryString;
                using (await Locker.LockAsync(combined))
                {
                    // Create a new cache to help process and cache the request.
                    this.imageCache = (IImageCache)ImageProcessorConfiguration.Instance
                                      .ImageCache.GetInstance(requestPath, fullPath, queryString);

                    // Is the file new or updated?
                    bool isNewOrUpdated = await this.imageCache.IsNewOrUpdatedAsync();

                    string cachedPath = this.imageCache.CachedPath;

                    // Only process if the file has been updated.
                    if (isNewOrUpdated)
                    {
                        // Process the image.
                        bool exif  = preserveExifMetaData != null && preserveExifMetaData.Value;
                        bool gamma = fixGamma != null && fixGamma.Value;
                        AnimationProcessMode mode = this.ParseAnimationMode(queryString);

                        using (ImageFactory imageFactory = new ImageFactory(exif, gamma)
                        {
                            AnimationProcessMode = mode
                        })
                        {
                            byte[] imageBuffer = null;
                            string mimeType;

                            try
                            {
                                imageBuffer = await currentService.GetImage(resourcePath);
                            }
                            catch (HttpException ex)
                            {
                                // We want 404's to be handled by IIS so that other handlers/modules can still run.
                                if (ex.GetHttpCode() == (int)HttpStatusCode.NotFound)
                                {
                                    return;
                                }
                            }

                            if (imageBuffer == null)
                            {
                                return;
                            }

                            using (MemoryStream inStream = new MemoryStream(imageBuffer))
                            {
                                // Process the Image
                                MemoryStream outStream = new MemoryStream();

                                if (!string.IsNullOrWhiteSpace(queryString))
                                {
                                    imageFactory.Load(inStream).AutoProcess(queryString).Save(outStream);
                                    mimeType = imageFactory.CurrentImageFormat.MimeType;
                                }
                                else
                                {
                                    await inStream.CopyToAsync(outStream);

                                    mimeType = FormatUtilities.GetFormat(outStream).MimeType;
                                }

                                // Fire the post processing event.
                                EventHandler <PostProcessingEventArgs> handler = OnPostProcessing;
                                if (handler != null)
                                {
                                    string extension             = Path.GetExtension(cachedPath);
                                    PostProcessingEventArgs args = new PostProcessingEventArgs
                                    {
                                        Context        = context,
                                        ImageStream    = outStream,
                                        ImageExtension = extension
                                    };

                                    handler(this, args);
                                    outStream = args.ImageStream;
                                }

                                // Add to the cache.
                                await this.imageCache.AddImageToCacheAsync(outStream, mimeType);

                                // Cleanup
                                outStream.Dispose();
                            }

                            // Store the response type and cache dependency in the context for later retrieval.
                            context.Items[CachedResponseTypeKey] = mimeType;
                            bool isFileCached = new Uri(cachedPath).IsFile;

                            if (isFileLocal)
                            {
                                if (isFileCached)
                                {
                                    // Some services might only provide filename so we can't monitor for the browser.
                                    context.Items[CachedResponseFileDependency] = Path.GetFileName(requestPath) == requestPath
                                        ? new List <string> {
                                        cachedPath
                                    }
                                        : new List <string> {
                                        requestPath, cachedPath
                                    };
                                }
                                else
                                {
                                    context.Items[CachedResponseFileDependency] = Path.GetFileName(requestPath) == requestPath
                                        ? null
                                        : new List <string> {
                                        requestPath
                                    };
                                }
                            }
                            else if (isFileCached)
                            {
                                context.Items[CachedResponseFileDependency] = new List <string> {
                                    cachedPath
                                };
                            }
                        }
                    }

                    // The cached file is valid so just rewrite the path.
                    this.imageCache.RewritePath(context);

                    // Redirect if not a locally store file.
                    if (!new Uri(cachedPath).IsFile)
                    {
                        context.ApplicationInstance.CompleteRequest();
                    }
                }
            }
        }
Ejemplo n.º 9
0
        /// <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>
        /// <returns>
        /// The <see cref="T:System.Threading.Tasks.Task"/>.
        /// </returns>
        private async Task ProcessImageAsync(HttpContext context)
        {
            HttpRequest request = context.Request;
            string      rawUrl  = request.Unvalidated.RawUrl;

            // Should we ignore this request?
            if (string.IsNullOrWhiteSpace(rawUrl) || rawUrl.ToUpperInvariant().Contains("IPIGNORE=TRUE"))
            {
                return;
            }

            // Sometimes the request is url encoded so we have to decode.
            // See https://github.com/JimBobSquarePants/ImageProcessor/issues/478
            // This causes a bit of a nightmare as the incoming request is corrupted and cannot be used for splitting
            // out each url part. This becomes a manual job.
            string url             = this.DecodeUrlString(rawUrl);
            string applicationPath = request.ApplicationPath;

            IImageService currentService = this.GetImageServiceForRequest(url, applicationPath);

            if (currentService != null)
            {
                // Remove any service identifier prefixes from the url.
                string prefix = currentService.Prefix;
                if (!string.IsNullOrWhiteSpace(prefix))
                {
                    url = url.Split(new[] { prefix }, StringSplitOptions.None)[1].TrimStart("?");
                }

                // Identify each part of the incoming request.
                int      queryCount     = url.Count(f => f == '?');
                bool     hasParams      = queryCount > 0;
                bool     hasMultiParams = queryCount > 1;
                string[] splitPath      = url.Split('?');

                // Ensure we include any relevent querystring parameters into our request path for third party requests.
                string requestPath = hasMultiParams ? string.Join("?", splitPath.Take(splitPath.Length - 1)) : splitPath[0];
                string queryString = hasParams ? splitPath[splitPath.Length - 1] : string.Empty;

                // Map the request path if file local.
                bool isFileLocal = currentService.IsFileLocalService;
                if (currentService.IsFileLocalService)
                {
                    requestPath = HostingEnvironment.MapPath(requestPath);
                }

                // Parse any protocol values from settings if no protocol is present.
                if (currentService.Settings.ContainsKey("Protocol") && (ProtocolRegex.Matches(url).Count == 0 || ProtocolRegex.Matches(url)[0].Index > 0))
                {
                    // ReSharper disable once PossibleNullReferenceException
                    requestPath = currentService.Settings["Protocol"] + "://" + requestPath.TrimStart('/');
                }

                // Replace any presets in the querystring with the actual value.
                queryString = this.ReplacePresetsInQueryString(queryString);

                HttpContextWrapper httpContextBase = new HttpContextWrapper(context);

                // Execute the handler which can change the querystring
                //  LEGACY:
#pragma warning disable 618
                queryString = this.CheckQuerystringHandler(context, queryString, rawUrl);
#pragma warning restore 618

                // NEW WAY:
                ValidatingRequestEventArgs validatingArgs = new ValidatingRequestEventArgs(httpContextBase, queryString);
                this.OnValidatingRequest(validatingArgs);

                // If the validation has failed based on events, return
                if (validatingArgs.Cancel)
                {
                    ImageProcessorBootstrapper.Instance.Logger.Log <ImageProcessingModule>("Image processing has been cancelled by an event");
                    return;
                }

                // Re-assign based on event handlers
                queryString = validatingArgs.QueryString;

                // Break out if we don't meet critera.
                bool interceptAll = interceptAllRequests != null && interceptAllRequests.Value;
                if (string.IsNullOrWhiteSpace(requestPath) || (!interceptAll && string.IsNullOrWhiteSpace(queryString)))
                {
                    return;
                }

                // Check whether the path is valid for other requests.
                // We've already checked the unprefixed requests in GetImageServiceForRequest().
                if (!string.IsNullOrWhiteSpace(prefix) && !currentService.IsValidRequest(requestPath))
                {
                    return;
                }

                using (await Locker.LockAsync(rawUrl))
                {
                    // Create a new cache to help process and cache the request.
                    this.imageCache = (IImageCache)ImageProcessorConfiguration.Instance
                                      .ImageCache.GetInstance(requestPath, url, queryString);

                    // Is the file new or updated?
                    bool isNewOrUpdated = await this.imageCache.IsNewOrUpdatedAsync();

                    string cachedPath = this.imageCache.CachedPath;

                    // Only process if the file has been updated.
                    if (isNewOrUpdated)
                    {
                        byte[] imageBuffer = null;
                        string mimeType;

                        try
                        {
                            imageBuffer = await currentService.GetImage(requestPath);
                        }
                        catch (HttpException ex)
                        {
                            // We want 404's to be handled by IIS so that other handlers/modules can still run.
                            if (ex.GetHttpCode() == (int)HttpStatusCode.NotFound)
                            {
                                ImageProcessorBootstrapper.Instance.Logger.Log <ImageProcessingModule>(ex.Message);
                                return;
                            }
                        }

                        if (imageBuffer == null)
                        {
                            return;
                        }

                        using (MemoryStream inStream = new MemoryStream(imageBuffer))
                        {
                            // Process the Image
                            MemoryStream outStream = new MemoryStream();

                            if (!string.IsNullOrWhiteSpace(queryString))
                            {
                                // Animation is not a processor but can be a specific request so we should allow it.
                                bool processAnimation;
                                AnimationProcessMode mode = this.ParseAnimationMode(queryString, out processAnimation);

                                // Attempt to match querystring and processors.
                                IWebGraphicsProcessor[] processors = ImageFactoryExtensions.GetMatchingProcessors(queryString);
                                if (processors.Any() || processAnimation)
                                {
                                    // Process the image.
                                    bool exif  = preserveExifMetaData != null && preserveExifMetaData.Value;
                                    bool gamma = fixGamma != null && fixGamma.Value;

                                    using (ImageFactory imageFactory = new ImageFactory(exif, gamma)
                                    {
                                        AnimationProcessMode = mode
                                    })
                                    {
                                        imageFactory.Load(inStream).AutoProcess(processors).Save(outStream);
                                        mimeType = imageFactory.CurrentImageFormat.MimeType;
                                    }
                                }
                                else if (this.ParseCacheBuster(queryString))
                                {
                                    // We're cachebustng. Allow the value to be cached
                                    await inStream.CopyToAsync(outStream);

                                    mimeType = FormatUtilities.GetFormat(outStream).MimeType;
                                }
                                else
                                {
                                    // No match? Someone is either attacking the server or hasn't read the instructions.
                                    // Either way throw an exception to prevent caching.
                                    string message = $"The request {request.Unvalidated.RawUrl} could not be understood by the server due to malformed syntax.";
                                    ImageProcessorBootstrapper.Instance.Logger.Log <ImageProcessingModule>(message);
                                    throw new HttpException((int)HttpStatusCode.BadRequest, message);
                                }
                            }
                            else
                            {
                                // We're capturing all requests.
                                await inStream.CopyToAsync(outStream);

                                mimeType = FormatUtilities.GetFormat(outStream).MimeType;
                            }

                            // Fire the post processing event.
                            EventHandler <PostProcessingEventArgs> handler = OnPostProcessing;
                            if (handler != null)
                            {
                                string extension             = Path.GetExtension(cachedPath);
                                PostProcessingEventArgs args = new PostProcessingEventArgs
                                {
                                    Context        = context,
                                    ImageStream    = outStream,
                                    ImageExtension = extension
                                };

                                handler(this, args);
                                outStream = args.ImageStream;
                            }

                            // Add to the cache.
                            await this.imageCache.AddImageToCacheAsync(outStream, mimeType);

                            // Cleanup
                            outStream.Dispose();
                        }

                        // Store the response type and cache dependency in the context for later retrieval.
                        context.Items[CachedResponseTypeKey] = mimeType;
                        bool isFileCached = new Uri(cachedPath).IsFile;

                        if (isFileLocal)
                        {
                            if (isFileCached)
                            {
                                // Some services might only provide filename so we can't monitor for the browser.
                                context.Items[CachedResponseFileDependency] = Path.GetFileName(requestPath) == requestPath
                                    ? new[] { cachedPath }
                                    : new[] { requestPath, cachedPath };
                            }
                            else
                            {
                                context.Items[CachedResponseFileDependency] = Path.GetFileName(requestPath) == requestPath
                                    ? null
                                    : new[] { requestPath };
                            }
                        }
                        else if (isFileCached)
                        {
                            context.Items[CachedResponseFileDependency] = new[] { cachedPath };
                        }
                    }

                    // The cached file is valid so just rewrite the path.
                    this.imageCache.RewritePath(context);

                    // Redirect if not a locally store file.
                    if (!new Uri(cachedPath).IsFile)
                    {
                        context.ApplicationInstance.CompleteRequest();
                    }
                }
            }
        }