/// <summary> /// Post processes the image. /// </summary> /// <param name="context">The current context.</param> /// <param name="stream">The source image stream.</param> /// <param name="extension">The image extension.</param> /// <returns> /// The <see cref="MemoryStream" />. /// </returns> public static async Task <MemoryStream> PostProcessImageAsync(HttpContext context, MemoryStream stream, string extension) { if (!PostProcessorBootstrapper.Instance.IsInstalled) { return(stream); } string sourceFile = null, destinationFile = null; var timeout = PostProcessorBootstrapper.Instance.Timout; try { // Save source file length var length = stream.Length; // Create a temporary source file with the correct extension var tempSourceFile = Path.GetTempFileName(); sourceFile = Path.ChangeExtension(tempSourceFile, extension); File.Move(tempSourceFile, sourceFile); // Give our destination file a unique name destinationFile = sourceFile.Replace(extension, "-out" + extension); // Get processes to start var processStartInfos = GetProcessStartInfos(extension, length, sourceFile, destinationFile).ToList(); if (processStartInfos.Count > 0) { // Save the input stream to our source temp file for post processing using (var fileStream = File.Create(sourceFile)) { await stream.CopyToAsync(fileStream).ConfigureAwait(false); } // Create cancellation token with timeout using (var cancellationTokenSource = new CancellationTokenSource(timeout)) { foreach (var processStartInfo in processStartInfos) { // Use destination file as new source (if previous process created one). if (File.Exists(destinationFile)) { File.Copy(destinationFile, sourceFile, true); } // Set default properties processStartInfo.FileName = Path.Combine(PostProcessorBootstrapper.Instance.WorkingPath, processStartInfo.FileName); processStartInfo.CreateNoWindow = true; processStartInfo.WindowStyle = ProcessWindowStyle.Hidden; // Run process using (var processResults = await ProcessEx.RunAsync(processStartInfo, cancellationTokenSource.Token).ConfigureAwait(false)) { if (processResults.ExitCode == 1) { ImageProcessorBootstrapper.Instance.Logger.Log(typeof(PostProcessor), $"Unable to post process image for request {context.Request.Unvalidated.Url}, {processStartInfo.FileName} {processStartInfo.Arguments} exited with error code 1. Original image returned."); break; } } } } // Save result if (!File.Exists(destinationFile)) { File.Copy(sourceFile, destinationFile, true); } var result = new PostProcessingResultEventArgs(destinationFile, length); if (result.ResultFileSize > 0 && result.Saving > 0) { using (var fileStream = File.OpenRead(destinationFile)) { stream.SetLength(0); await fileStream.CopyToAsync(stream).ConfigureAwait(false); } } } } catch (OperationCanceledException) { ImageProcessorBootstrapper.Instance.Logger.Log(typeof(PostProcessor), $"Unable to post process image for request {context.Request.Unvalidated.Url} within {timeout}ms. Original image returned."); } catch (Exception ex) { // Some security policies don't allow execution of programs in this way ImageProcessorBootstrapper.Instance.Logger.Log(typeof(PostProcessor), ex.Message); } finally { // Always cleanup files try { // Ensure files exist, are not read only, and delete if (sourceFile != null && File.Exists(sourceFile)) { File.SetAttributes(sourceFile, FileAttributes.Normal); File.Delete(sourceFile); } if (destinationFile != null && File.Exists(destinationFile)) { File.SetAttributes(destinationFile, FileAttributes.Normal); File.Delete(destinationFile); } } catch { // Normally a no no, but logging would be excessive + temp files get cleaned up eventually. } } // ALways return stream (even if it's not optimized) stream.Position = 0; return(stream); }