Example #1
0
        public async Task Invoke(HttpContext context)
        {
            var path = context.Request.Path;

            // Delegate to the diagnostics page if it is requested
            if (diagnosticsPage.MatchesPath(path.Value))
            {
                await diagnosticsPage.Invoke(context);

                return;
            }

            // We only handle requests with an image extension, period.
            if (!PathHelpers.IsImagePath(path))
            {
                await next.Invoke(context);

                return;
            }


            var imageJobInfo = new ImageJobInfo(context, options, blobProvider);

            if (!imageJobInfo.Authorized)
            {
                await NotAuthorized(context);

                return;
            }

            // If the file is definitely missing hand to the next middleware
            // Remote providers will fail late rather than make 2 requests
            if (!imageJobInfo.PrimaryBlobMayExist())
            {
                await next.Invoke(context);

                return;
            }



            var memoryCacheEnabled      = memoryCache != null && options.AllowMemoryCaching && imageJobInfo.NeedsCaching();
            var diskCacheEnabled        = diskCache != null && options.AllowDiskCaching && imageJobInfo.NeedsCaching();
            var distributedCacheEnabled = distributedCache != null && options.AllowDistributedCaching && imageJobInfo.NeedsCaching();
            var sqliteCacheEnabled      = sqliteCache != null && options.AllowSqliteCaching && imageJobInfo.NeedsCaching();


            string cacheKey = null;

            if (memoryCacheEnabled || diskCacheEnabled || distributedCacheEnabled | sqliteCacheEnabled)
            {
                cacheKey = await imageJobInfo.GetFastCacheKey();

                if (context.Request.Headers.TryGetValue(HeaderNames.IfNoneMatch, out var etag) && cacheKey == etag)
                {
                    context.Response.StatusCode    = StatusCodes.Status304NotModified;
                    context.Response.ContentLength = 0;
                    context.Response.ContentType   = null;
                    return;
                }
            }

            try
            {
                if (sqliteCacheEnabled)
                {
                    await ProcessWithSqliteCache(context, cacheKey, imageJobInfo);
                }
                else if (diskCacheEnabled)
                {
                    await ProcessWithDiskCache(context, cacheKey, imageJobInfo);
                }
                else if (memoryCacheEnabled)
                {
                    await ProcessWithMemoryCache(context, cacheKey, imageJobInfo);

                    // ReSharper disable once ConditionIsAlwaysTrueOrFalse
                }
                else if (distributedCacheEnabled)
                {
                    await ProcessWithDistributedCache(context, cacheKey, imageJobInfo);
                }
                else
                {
                    await ProcessWithNoCache(context, imageJobInfo);
                }
            }
            catch (BlobMissingException e)
            {
                await NotFound(context, e);
            }
        }
Example #2
0
        private bool ProcessRewritesAndAuthorization(HttpContext context, ImageflowMiddlewareOptions options)
        {
            var path = context.Request.Path.Value;
            var args = new UrlEventArgs(context, context.Request.Path.Value, PathHelpers.ToQueryDictionary(context.Request.Query));

            foreach (var handler in options.PreRewriteAuthorization)
            {
                var matches = string.IsNullOrEmpty(handler.PathPrefix) ||
                              path.StartsWith(handler.PathPrefix, StringComparison.OrdinalIgnoreCase);
                if (matches && !handler.Handler(args))
                {
                    return(false);
                }
            }

            if (options.UsePresetsExclusively)
            {
                var firstKey = args.Query.FirstOrDefault().Key;

                if (args.Query.Count > 1 || (firstKey != null && firstKey != "preset"))
                {
                    return(false);
                }
            }

            // Parse and apply presets before rewriting
            if (args.Query.TryGetValue("preset", out var presetNames))
            {
                var presetNamesList = presetNames
                                      .Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
                foreach (var presetName in presetNamesList)
                {
                    if (options.Presets.TryGetValue(presetName, out var presetOptions))
                    {
                        foreach (var pair in presetOptions.pairs)
                        {
                            if (presetOptions.Priority == PresetPriority.OverrideQuery ||
                                !args.Query.ContainsKey(pair.Key))
                            {
                                args.Query[pair.Key] = pair.Value;
                            }
                        }
                    }
                    else
                    {
                        throw new InvalidOperationException($"The image preset {presetName} was referenced from the querystring but is not registered.");
                    }
                }
            }

            // Apply rewrite handlers
            foreach (var handler in options.Rewrite)
            {
                var matches = string.IsNullOrEmpty(handler.PathPrefix) ||
                              path.StartsWith(handler.PathPrefix, StringComparison.OrdinalIgnoreCase);
                if (matches)
                {
                    handler.Handler(args);
                    path = args.VirtualPath;
                }
            }

            // Set defaults if keys are missing, but at least 1 supported key is present
            if (PathHelpers.SupportedQuerystringKeys.Any(args.Query.ContainsKey))
            {
                foreach (var pair in options.CommandDefaults)
                {
                    if (!args.Query.ContainsKey(pair.Key))
                    {
                        args.Query[pair.Key] = pair.Value;
                    }
                }
            }

            // Run post-rewrite authorization
            foreach (var handler in options.PostRewriteAuthorization)
            {
                var matches = string.IsNullOrEmpty(handler.PathPrefix) ||
                              path.StartsWith(handler.PathPrefix, StringComparison.OrdinalIgnoreCase);
                if (matches && !handler.Handler(args))
                {
                    return(false);
                }
            }

            FinalVirtualPath = args.VirtualPath;
            FinalQuery       = args.Query;
            return(true);
        }
Example #3
0
 private string HashStrings(IEnumerable <string> strings)
 {
     return(PathHelpers.Base64Hash(string.Join('|', strings)));
 }
        public ImageJobInfo(HttpContext context, ImageflowMiddlewareOptions options, BlobProvider blobProvider)
        {
            this.options = options;
            Authorized   = ProcessRewritesAndAuthorization(context, options);

            if (!Authorized)
            {
                return;
            }

            HasParams = PathHelpers.SupportedQuerystringKeys.Any(FinalQuery.ContainsKey);


            var extension = Path.GetExtension(FinalVirtualPath);

            if (FinalQuery.TryGetValue("format", out var newExtension))
            {
                extension = newExtension;
            }

            EstimatedFileExtension = PathHelpers.SanitizeImageExtension(extension) ?? "jpg";

            primaryBlob = new BlobFetchCache(FinalVirtualPath, blobProvider);
            allBlobs    = new List <BlobFetchCache>(1)
            {
                primaryBlob
            };

            appliedWatermarks = new List <NamedWatermark>();

            if (HasParams)
            {
                CommandString = PathHelpers.SerializeCommandString(FinalQuery);

                // Look up watermark names
                if (FinalQuery.TryGetValue("watermark", out var watermarkValues))
                {
                    var watermarkNames = watermarkValues.Split(",").Select(s => s.Trim(' '));
                    foreach (var name in watermarkNames)
                    {
                        var watermark = options.NamedWatermarks.FirstOrDefault(w =>
                                                                               w.Name.Equals(name, StringComparison.OrdinalIgnoreCase));
                        if (watermark == null)
                        {
                            throw new InvalidOperationException(
                                      $"watermark {name} was referenced from the querystring but no watermark by that name is registered with the middleware");
                        }

                        appliedWatermarks.Add(watermark);
                    }
                }
            }

            // After we've populated the defaults, run the event handlers for custom watermarking logic
            var args = new WatermarkingEventArgs(context, FinalVirtualPath, FinalQuery, appliedWatermarks);

            foreach (var handler in options.Watermarking)
            {
                var matches = string.IsNullOrEmpty(handler.PathPrefix) ||
                              FinalVirtualPath.StartsWith(handler.PathPrefix, StringComparison.OrdinalIgnoreCase);
                if (matches)
                {
                    handler.Handler(args);
                }
            }
            appliedWatermarks = args.AppliedWatermarks;

            // Add the watermark source files
            foreach (var w in appliedWatermarks)
            {
                allBlobs.Add(new BlobFetchCache(w.VirtualPath, blobProvider));
            }

            provider = blobProvider;
        }
Example #5
0
        // ReSharper disable once UnusedMember.Global
        public async Task Invoke(HttpContext context)
        {
            // For instrumentation
            globalInfoProvider.CopyHttpContextInfo(context);

            var path = context.Request.Path;


            // Delegate to the diagnostics page if it is requested
            if (DiagnosticsPage.MatchesPath(path.Value))
            {
                await diagnosticsPage.Invoke(context);

                return;
            }
            // Delegate to licenses page if requested
            if (licensePage.MatchesPath(path.Value))
            {
                await licensePage.Invoke(context);

                return;
            }

            // Respond to /imageflow.ready
            if ("/imageflow.ready".Equals(path.Value, StringComparison.Ordinal))
            {
                options.Licensing.FireHeartbeat();
                using (new JobContext())
                {
                    await StringResponseNoCache(context, 200, "Imageflow.Server is ready to accept requests.");
                }
                return;
            }

            // Respond to /imageflow.health
            if ("/imageflow.health".Equals(path.Value, StringComparison.Ordinal))
            {
                options.Licensing.FireHeartbeat();
                await StringResponseNoCache(context, 200, "Imageflow.Server is healthy.");

                return;
            }


            // We only handle requests with an image extension or if we configured a path prefix for which to handle
            // extensionless requests

            if (!ImageJobInfo.ShouldHandleRequest(context, options))
            {
                await next.Invoke(context);

                return;
            }

            options.Licensing.FireHeartbeat();

            var imageJobInfo = new ImageJobInfo(context, options, blobProvider);

            if (!imageJobInfo.Authorized)
            {
                await NotAuthorized(context, imageJobInfo.AuthorizedMessage);

                return;
            }

            if (imageJobInfo.LicenseError)
            {
                if (options.EnforcementMethod == EnforceLicenseWith.Http422Error)
                {
                    await StringResponseNoCache(context, 422, options.Licensing.InvalidLicenseMessage);

                    return;
                }
                if (options.EnforcementMethod == EnforceLicenseWith.Http402Error)
                {
                    await StringResponseNoCache(context, 402, options.Licensing.InvalidLicenseMessage);

                    return;
                }
            }

            // If the file is definitely missing hand to the next middleware
            // Remote providers will fail late rather than make 2 requests
            if (!imageJobInfo.PrimaryBlobMayExist())
            {
                await next.Invoke(context);

                return;
            }

            string cacheKey    = null;
            var    cachingPath = imageJobInfo.NeedsCaching() ? options.ActiveCacheBackend : CacheBackend.NoCache;

            if (cachingPath != CacheBackend.NoCache)
            {
                cacheKey = await imageJobInfo.GetFastCacheKey();

                if (context.Request.Headers.TryGetValue(HeaderNames.IfNoneMatch, out var etag) && cacheKey == etag)
                {
                    GlobalPerf.Singleton.IncrementCounter("etag_hit");
                    context.Response.StatusCode    = StatusCodes.Status304NotModified;
                    context.Response.ContentLength = 0;
                    context.Response.ContentType   = null;
                    return;
                }
                GlobalPerf.Singleton.IncrementCounter("etag_miss");
            }

            try
            {
                switch (cachingPath)
                {
                case CacheBackend.ClassicDiskCache:
                    await ProcessWithDiskCache(context, cacheKey, imageJobInfo);

                    break;

                case CacheBackend.NoCache:
                    await ProcessWithNoCache(context, imageJobInfo);

                    break;

                case CacheBackend.StreamCache:
                    await ProcessWithStreamCache(context, cacheKey, imageJobInfo);

                    break;

                default:
                    throw new ArgumentOutOfRangeException();
                }

                GlobalPerf.Singleton.IncrementCounter("middleware_ok");
            }
            catch (BlobMissingException e)
            {
                await NotFound(context, e);
            }
            catch (Exception e)
            {
                var errorName    = e.GetType().Name;
                var errorCounter = "middleware_" + errorName;
                GlobalPerf.Singleton.IncrementCounter(errorCounter);
                GlobalPerf.Singleton.IncrementCounter("middleware_errors");
                throw;
            }
            finally
            {
                // Increment counter for type of file served
                var imageExtension = PathHelpers.GetImageExtensionFromContentType(context.Response.ContentType);
                if (imageExtension != null)
                {
                    GlobalPerf.Singleton.IncrementCounter("module_response_ext_" + imageExtension);
                }
            }
        }
        public ImageJobInfo(HttpContext context, ImageflowMiddlewareOptions options, BlobProvider blobProvider)
        {
            this.options = options;
            Authorized   = ProcessRewritesAndAuthorization(context, options);

            if (!Authorized)
            {
                return;
            }

            HasParams = PathHelpers.SupportedQuerystringKeys.Any(FinalQuery.ContainsKey);

            // Get the image and page domains
            ImageDomain = context.Request.Host.Host;
            var referer = context.Request.Headers["Referer"].ToString();

            if (!string.IsNullOrEmpty(referer) && Uri.TryCreate(referer, UriKind.Absolute, out var result))
            {
                PageDomain = result.DnsSafeHost;
            }

            var extension = Path.GetExtension(FinalVirtualPath);

            if (FinalQuery.TryGetValue("format", out var newExtension))
            {
                extension = newExtension;
            }

            EstimatedFileExtension = PathHelpers.SanitizeImageExtension(extension) ?? "jpg";

            primaryBlob = new BlobFetchCache(FinalVirtualPath, blobProvider);
            allBlobs    = new List <BlobFetchCache>(1)
            {
                primaryBlob
            };

            appliedWatermarks = new List <NamedWatermark>();

            if (HasParams)
            {
                if (options.Licensing.RequestNeedsEnforcementAction(context.Request))
                {
                    if (options.EnforcementMethod == EnforceLicenseWith.RedDotWatermark)
                    {
                        FinalQuery["watermark_red_dot"] = "true";
                    }
                    LicenseError = true;
                }

                CommandString = PathHelpers.SerializeCommandString(FinalQuery);

                // Look up watermark names
                if (FinalQuery.TryGetValue("watermark", out var watermarkValues))
                {
                    var watermarkNames = watermarkValues.Split(",").Select(s => s.Trim(' '));
                    foreach (var name in watermarkNames)
                    {
                        var watermark = options.NamedWatermarks.FirstOrDefault(w =>
                                                                               w.Name.Equals(name, StringComparison.OrdinalIgnoreCase));
                        if (watermark == null)
                        {
                            throw new InvalidOperationException(
                                      $"watermark {name} was referenced from the querystring but no watermark by that name is registered with the middleware");
                        }

                        appliedWatermarks.Add(watermark);
                    }
                }
            }

            // After we've populated the defaults, run the event handlers for custom watermarking logic
            var args = new WatermarkingEventArgs(context, FinalVirtualPath, FinalQuery, appliedWatermarks);

            foreach (var handler in options.Watermarking)
            {
                var matches = string.IsNullOrEmpty(handler.PathPrefix) ||
                              (FinalVirtualPath != null &&
                               FinalVirtualPath.StartsWith(handler.PathPrefix, StringComparison.OrdinalIgnoreCase));
                if (matches)
                {
                    handler.Handler(args);
                }
            }
            appliedWatermarks = args.AppliedWatermarks;
            if (appliedWatermarks.Count > 0)
            {
                HasParams = true;
            }

            // Add the watermark source files
            foreach (var w in appliedWatermarks)
            {
                allBlobs.Add(new BlobFetchCache(w.VirtualPath, blobProvider));
            }
        }