private async Task GetPreSignedUrlAsync(DocumentKey key, bool inline, CancellationToken cancellationToken) { if (!await ObjectExistsAsync(key, cancellationToken)) return null; var request = new GetPreSignedUrlRequest { BucketName = _settings.S3.Bucket, Key = key.ToString(), Verb = HttpVerb.GET, Expires = DateTime.UtcNow.Add(DownloadUrlLifetime), ResponseHeaderOverrides = { ContentDisposition = inline ? InlineContentDisposition(key.Name.ToString()) : AttachmentContentDisposition(key.Name.ToString()), }, }; return await s3.GetPreSignedURLAsync(request); } private static string AttachmentContentDisposition(string fileName) { var header = new ContentDispositionHeaderValue("attachment"); header.SetHttpFileName(fileName); return header.ToString(); } /// /// Builds a Content-Disposition header that lets the browser render the document inline /// while preserving the file name, using RFC 5987 encoding so non ASCII file names survive. /// private static string InlineContentDisposition(string fileName) { var asciiFallback = new string(fileName.Select(c => c is >= ' ' and < (char)127 and not '"' ? c : '_').ToArray()); var encoded = Uri.EscapeDataString(fileName); return $"inline; filename=\"{asciiFallback}\"; filename*=UTF-8''{encoded}"; }