private async Task ResizeSourceImage(byte[] sourceBytes, UrlParserResult urlParserResult) { string preProcessedFile = null; string postProcessorCommand = GetPostProcessorCommand(urlParserResult); if (!string.IsNullOrEmpty(postProcessorCommand)) { preProcessedFile = Regex.Replace(urlParserResult.Destination, @"\.(gif|jpe?g|png)$", new MatchEvaluator((match) => { return(".tmp" + match.Value); }), RegexOptions.IgnoreCase); } ImageFit.IEncoder encoder = GetEncoder(urlParserResult); Action <byte[], string, int, int, ImageFit.IEncoder> action; ResizeMethod resizeMethod = urlParserResult.ResizeMethod ?? ResizeMethod.ScaleDown; switch (resizeMethod) { case ResizeMethod.Contain: action = ImageFit.Image.Contain; break; case ResizeMethod.Cover: action = ImageFit.Image.Cover; break; case ResizeMethod.Fill: action = ImageFit.Image.Fill; break; case ResizeMethod.ScaleDown: action = ImageFit.Image.ScaleDown; break; default: throw new NotSupportedException(); } action(sourceBytes, preProcessedFile ?? urlParserResult.Destination, urlParserResult.Width, urlParserResult.Height, encoder); if (!string.IsNullOrEmpty(postProcessorCommand)) { string command = postProcessorCommand .Replace("{inputfile}", preProcessedFile) .Replace("{outputfile}", urlParserResult.Destination); SimpleShell.ICommandResult commandResult = await SimpleShell.Command.Run(command); File.Delete(preProcessedFile); File.Delete(urlParserResult.Destination + ".bak"); // OptiPng likes creating .bak files... if (!string.IsNullOrEmpty(commandResult.StandardOutput)) { _logger.LogInfo(commandResult.StandardOutput); } if (!string.IsNullOrEmpty(commandResult.StandardError)) { _logger.LogInfo(commandResult.StandardError); } if (commandResult.ErrorType != SimpleShell.CommandErrorType.None) { File.Delete(urlParserResult.Destination); throw new Exception($"Error running post-processor command: {postProcessorCommand}"); } } }
public async Task <IRequestHandlerResult> HandleRequest(Uri url, string ipAddress) { if (_rateLimiter.IsBlacklisted(ipAddress)) { _logger.LogInfo($"Denied request from blacklisted ip: {ipAddress}"); return(new RequestHandlerResult(ResultStatus.Forbidden)); } if (_rateLimiter.IsLimitExceeded(ipAddress)) { _logger.LogInfo($"Denied request because limit was exceeded for ip: {ipAddress}"); return(new RequestHandlerResult(ResultStatus.TooManyRequests)); } try { UrlParserResult urlParserResult = _urlParser.Parse(url); byte[] sourceBytes = await GetSourceImage(urlParserResult.Source); _rateLimiter.RegisterRequest(ipAddress, sourceBytes.LongLength); await FileAccessor.RunAction(urlParserResult.Destination, () => ResizeSourceImage(sourceBytes, urlParserResult)); _logger.LogInfo($"Created image at: {urlParserResult.Destination}"); string contentType = "image/" + urlParserResult.ImageType.ToString().ToLower(); FileInfo fileInfo = new FileInfo(urlParserResult.Destination); return(new RequestHandlerResult(ResultStatus.OK, new ImageInfo(fileInfo.Length, contentType, urlParserResult.Destination))); } catch (UrlParserException) { _logger.LogWarning($"Could not parse url: {url}"); return(new RequestHandlerResult(ResultStatus.BadRequest)); } catch (FileNotFoundException e) { _logger.LogWarning($"Could not find source: {e.FileName}"); return(new RequestHandlerResult(ResultStatus.FileNotFound)); } catch (HttpRequestException e) { _logger.LogError($"Exception was thrown trying to fetch remote source: {e.ToString()}"); return(new RequestHandlerResult(ResultStatus.BadGateway)); } catch (Exception e) { _logger.LogError($"An unhandled exception occurred: {e.ToString()}"); return(new RequestHandlerResult(ResultStatus.ServerError)); } }
private string GetPostProcessorCommand(UrlParserResult urlParserResult) { bool forcedOn = urlParserResult.PostProcess.HasValue && urlParserResult.PostProcess.Value == true; bool forcedOff = urlParserResult.PostProcess.HasValue && urlParserResult.PostProcess.Value == false; Func <IPostProcessorSettings, string> GetCommand = (settings) => { string cmd = string.Empty; if (forcedOn || (settings.PostProcessorEnabled && !forcedOff)) { cmd = settings.PostProcessorCommand; if (string.IsNullOrEmpty(cmd)) { _logger.LogWarning($"Post-processing requested but no post-processor command specified."); } } return(cmd); }; string command = string.Empty; if (!forcedOff) { switch (urlParserResult.ImageType) { case ImageType.Gif: command = GetCommand(_settings.GifSettings); break; case ImageType.Jpeg: command = GetCommand(_settings.JpegSettings); break; case ImageType.Png: command = GetCommand(_settings.PngSettings); break; } } return(command); }
private ImageFit.IEncoder GetEncoder(UrlParserResult urlParserResult) { switch (urlParserResult.ImageType) { case ImageType.Gif: return(new ImageFit.GifEncoder()); case ImageType.Jpeg: int quality = urlParserResult.Quality.HasValue ? urlParserResult.Quality.Value : _settings.JpegSettings.Quality; return(new ImageFit.JpegEncoder(quality)); case ImageType.Png: int compressionLevel = urlParserResult.Compression.HasValue ? urlParserResult.Compression.Value : _settings.PngSettings.CompressionLevel; return(new ImageFit.PngEncoder(compressionLevel)); default: throw new NotSupportedException(); } }
public UrlParserResult Parse(Uri url) { string pattern = @"^ (?<location>.*) / (?<modifier> (?<width>\d+)x(?<height>\d+) ( -(?<resize>contain|cover|fill|scaledown) | -(q(?<quality>\d+)) | -(c(?<compression>\d+)) | -(?<postprocess>n?pp) )* ) / (?<file>[^/]+\.(?<extension>gif|jpe?g|png)) $"; Match match = Regex.Match(url.GetLeftPart(UriPartial.Path), pattern, RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace); if (!match.Success) { throw new UrlParserException($"The URL {url.GetLeftPart(UriPartial.Path)} is not a valid."); } string location = match.Groups["location"].Value; string modifier = match.Groups["modifier"].Value; string file = match.Groups["file"].Value; string extension = match.Groups["extension"].Value; string root = _destinationRoot.AbsoluteUrl.OriginalString; string dirs = string.Empty; if (location.StartsWith(root, StringComparison.InvariantCultureIgnoreCase)) { dirs = location.Replace(root, "").Trim('/'); } else { throw new UrlParserException($"The requested location {location} is not below the root location {root}."); } UrlParserResult result = new UrlParserResult() { Source = GetSource(dirs, file), Destination = GetDestination(dirs, modifier, file), ImageType = GetImageType(extension), Width = Convert.ToInt32(match.Groups["width"].Value), Height = Convert.ToInt32(match.Groups["height"].Value), ResizeMethod = GetValueOrNull <ResizeMethod>(match.Groups["resize"].Value), Quality = GetValueOrNull <int>(match.Groups["quality"].Value), Compression = GetValueOrNull <int>(match.Groups["compression"].Value) }; string postProcessValue = match.Groups["postprocess"].Value; if (!string.IsNullOrEmpty(postProcessValue)) { result.PostProcess = postProcessValue == "pp"; } return(result); }