Skip to content

Commit e057f93

Browse files
Merge branch 'main' into js/taghelper
2 parents b270ca0 + 35e1ae2 commit e057f93

9 files changed

+62
-53
lines changed

src/ImageSharp.Web.Providers.AWS/AmazonS3ClientFactory.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
// Copyright (c) Six Labors.
22
// Licensed under the Six Labors Split License.
3-
#nullable disable
43

54
using Amazon;
65
using Amazon.Runtime;

src/ImageSharp.Web.Providers.AWS/Caching/AWSS3StorageCache.cs

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
// Copyright (c) Six Labors.
22
// Licensed under the Six Labors Split License.
3-
#nullable disable
43

54
using System.Globalization;
65
using Amazon.S3;
@@ -17,7 +16,7 @@ namespace SixLabors.ImageSharp.Web.Caching.AWS;
1716
public class AWSS3StorageCache : IImageCache
1817
{
1918
private readonly IAmazonS3 amazonS3Client;
20-
private readonly string bucket;
19+
private readonly string bucketName;
2120

2221
/// <summary>
2322
/// Initializes a new instance of the <see cref="AWSS3StorageCache"/> class.
@@ -27,19 +26,19 @@ public AWSS3StorageCache(IOptions<AWSS3StorageCacheOptions> cacheOptions)
2726
{
2827
Guard.NotNull(cacheOptions, nameof(cacheOptions));
2928
AWSS3StorageCacheOptions options = cacheOptions.Value;
30-
this.bucket = options.BucketName;
29+
this.bucketName = options.BucketName;
3130
this.amazonS3Client = AmazonS3ClientFactory.CreateClient(options);
3231
}
3332

3433
/// <inheritdoc/>
35-
public async Task<IImageCacheResolver> GetAsync(string key)
34+
public async Task<IImageCacheResolver?> GetAsync(string key)
3635
{
37-
GetObjectMetadataRequest request = new() { BucketName = this.bucket, Key = key };
36+
GetObjectMetadataRequest request = new() { BucketName = this.bucketName, Key = key };
3837
try
3938
{
4039
// HEAD request throws a 404 if not found.
4140
MetadataCollection metadata = (await this.amazonS3Client.GetObjectMetadataAsync(request)).Metadata;
42-
return new AWSS3StorageCacheResolver(this.amazonS3Client, this.bucket, key, metadata);
41+
return new AWSS3StorageCacheResolver(this.amazonS3Client, this.bucketName, key, metadata);
4342
}
4443
catch
4544
{
@@ -52,7 +51,7 @@ public Task SetAsync(string key, Stream stream, ImageCacheMetadata metadata)
5251
{
5352
PutObjectRequest request = new()
5453
{
55-
BucketName = this.bucket,
54+
BucketName = this.bucketName,
5655
Key = key,
5756
ContentType = metadata.ContentType,
5857
InputStream = stream,
@@ -82,12 +81,12 @@ public Task SetAsync(string key, Stream stream, ImageCacheMetadata metadata)
8281
/// If the bucket does not already exist, a <see cref="PutBucketResponse"/> describing the newly
8382
/// created bucket. If the container already exists, <see langword="null"/>.
8483
/// </returns>
85-
public static PutBucketResponse CreateIfNotExists(
84+
public static PutBucketResponse? CreateIfNotExists(
8685
AWSS3StorageCacheOptions options,
8786
S3CannedACL acl)
8887
=> AsyncHelper.RunSync(() => CreateIfNotExistsAsync(options, acl));
8988

90-
private static async Task<PutBucketResponse> CreateIfNotExistsAsync(
89+
private static async Task<PutBucketResponse?> CreateIfNotExistsAsync(
9190
AWSS3StorageCacheOptions options,
9291
S3CannedACL acl)
9392
{

src/ImageSharp.Web.Providers.AWS/Caching/AWSS3StorageCacheOptions.cs

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
// Copyright (c) Six Labors.
22
// Licensed under the Six Labors Split License.
3-
#nullable disable
43

54
namespace SixLabors.ImageSharp.Web.Caching.AWS;
65

@@ -10,19 +9,19 @@ namespace SixLabors.ImageSharp.Web.Caching.AWS;
109
public class AWSS3StorageCacheOptions : IAWSS3BucketClientOptions
1110
{
1211
/// <inheritdoc/>
13-
public string Region { get; set; }
12+
public string? Region { get; set; }
1413

1514
/// <inheritdoc/>
16-
public string BucketName { get; set; }
15+
public string BucketName { get; set; } = null!;
1716

1817
/// <inheritdoc/>
19-
public string AccessKey { get; set; }
18+
public string? AccessKey { get; set; }
2019

2120
/// <inheritdoc/>
22-
public string AccessSecret { get; set; }
21+
public string? AccessSecret { get; set; }
2322

2423
/// <inheritdoc/>
25-
public string Endpoint { get; set; }
24+
public string? Endpoint { get; set; }
2625

2726
/// <inheritdoc/>
2827
public bool UseAccelerateEndpoint { get; set; }

src/ImageSharp.Web.Providers.AWS/IAWSS3BucketClientOptions.cs

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
// Copyright (c) Six Labors.
22
// Licensed under the Six Labors Split License.
3-
#nullable disable
43

54
namespace SixLabors.ImageSharp.Web;
65

@@ -12,7 +11,7 @@ internal interface IAWSS3BucketClientOptions
1211
/// <summary>
1312
/// Gets or sets the AWS region endpoint (us-east-1/us-west-1/ap-southeast-2).
1413
/// </summary>
15-
string Region { get; set; }
14+
string? Region { get; set; }
1615

1716
/// <summary>
1817
/// Gets or sets the AWS bucket name.
@@ -24,20 +23,20 @@ internal interface IAWSS3BucketClientOptions
2423
/// If deploying inside an EC2 instance AWS keys will already be available via environment
2524
/// variables and don't need to be specified. Follow AWS best security practices on <see href="https://docs.aws.amazon.com/general/latest/gr/aws-access-keys-best-practices.html"/>.
2625
/// </summary>
27-
string AccessKey { get; set; }
26+
string? AccessKey { get; set; }
2827

2928
/// <summary>
30-
/// Gets or sets the AWS secret - Can be used to override keys provided by the environment.
29+
/// Gets or sets the AWS endpoint - used to override the default service endpoint.
3130
/// If deploying inside an EC2 instance AWS keys will already be available via environment
3231
/// variables and don't need to be specified. Follow AWS best security practices on <see href="https://docs.aws.amazon.com/general/latest/gr/aws-access-keys-best-practices.html"/>.
3332
/// </summary>
34-
string AccessSecret { get; set; }
33+
string? AccessSecret { get; set; }
3534

3635
/// <summary>
3736
/// Gets or sets the AWS endpoint - used for testing to over region endpoint allowing it
3837
/// to be set to localhost.
3938
/// </summary>
40-
string Endpoint { get; set; }
39+
string? Endpoint { get; set; }
4140

4241
/// <summary>
4342
/// Gets or sets a value indicating whether the S3 accelerate endpoint is used.

src/ImageSharp.Web.Providers.AWS/Providers/AWSS3StorageImageProvider.cs

Lines changed: 32 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
// Copyright (c) Six Labors.
22
// Licensed under the Six Labors Split License.
3-
#nullable disable
43

54
using Amazon.S3;
65
using Amazon.S3.Model;
@@ -29,7 +28,7 @@ private readonly Dictionary<string, AmazonS3Client> buckets
2928
= new();
3029

3130
private readonly AWSS3StorageImageProviderOptions storageOptions;
32-
private Func<HttpContext, bool> match;
31+
private Func<HttpContext, bool>? match;
3332

3433
/// <summary>
3534
/// Contains various helper methods based on the current configuration.
@@ -70,17 +69,23 @@ public bool IsValidRequest(HttpContext context)
7069
=> this.formatUtilities.TryGetExtensionFromUri(context.Request.GetDisplayUrl(), out _);
7170

7271
/// <inheritdoc />
73-
public async Task<IImageResolver> GetAsync(HttpContext context)
72+
public async Task<IImageResolver?> GetAsync(HttpContext context)
7473
{
7574
// Strip the leading slash and bucket name from the HTTP request path and treat
7675
// the remaining path string as the key.
7776
// Path has already been correctly parsed before here.
7877
string bucketName = string.Empty;
79-
IAmazonS3 s3Client = null;
78+
IAmazonS3? s3Client = null;
8079

8180
// We want an exact match here to ensure that bucket names starting with
8281
// the same prefix are not mixed up.
83-
string path = context.Request.Path.Value.TrimStart(SlashChars);
82+
string? path = context.Request.Path.Value?.TrimStart(SlashChars);
83+
84+
if (path is null)
85+
{
86+
return null;
87+
}
88+
8489
int index = path.IndexOfAny(SlashChars);
8590
string nameToMatch = index != -1 ? path.Substring(0, index) : path;
8691

@@ -108,19 +113,26 @@ public async Task<IImageResolver> GetAsync(HttpContext context)
108113
return null;
109114
}
110115

111-
if (!await KeyExists(s3Client, bucketName, key))
116+
KeyExistsResult keyExists = await KeyExists(s3Client, bucketName, key);
117+
if (!keyExists.Exists)
112118
{
113119
return null;
114120
}
115121

116-
return new AWSS3StorageImageResolver(s3Client, bucketName, key);
122+
return new AWSS3StorageImageResolver(s3Client, bucketName, key, keyExists.Metadata);
117123
}
118124

119125
private bool IsMatch(HttpContext context)
120126
{
121127
// Only match loosly here for performance.
122128
// Path matching conflicts should be dealt with by configuration.
123-
string path = context.Request.Path.Value.TrimStart(SlashChars);
129+
string? path = context.Request.Path.Value?.TrimStart(SlashChars);
130+
131+
if (path is null)
132+
{
133+
return false;
134+
}
135+
124136
foreach (string bucket in this.buckets.Keys)
125137
{
126138
if (path.StartsWith(bucket, StringComparison.OrdinalIgnoreCase))
@@ -133,39 +145,40 @@ private bool IsMatch(HttpContext context)
133145
}
134146

135147
// ref https://github.com/aws/aws-sdk-net/blob/master/sdk/src/Services/S3/Custom/_bcl/IO/S3FileInfo.cs#L118
136-
private static async Task<bool> KeyExists(IAmazonS3 s3Client, string bucketName, string key)
148+
private static async Task<KeyExistsResult> KeyExists(IAmazonS3 s3Client, string bucketName, string key)
137149
{
138150
try
139151
{
140-
GetObjectMetadataRequest request = new()
141-
{
142-
BucketName = bucketName,
143-
Key = key
144-
};
152+
GetObjectMetadataRequest request = new() { BucketName = bucketName, Key = key };
145153

146154
// If the object doesn't exist then a "NotFound" will be thrown
147-
await s3Client.GetObjectMetadataAsync(request);
148-
return true;
155+
GetObjectMetadataResponse metadata = await s3Client.GetObjectMetadataAsync(request);
156+
return new KeyExistsResult(metadata);
149157
}
150158
catch (AmazonS3Exception e)
151159
{
152160
if (string.Equals(e.ErrorCode, "NoSuchBucket", StringComparison.Ordinal))
153161
{
154-
return false;
162+
return default;
155163
}
156164

157165
if (string.Equals(e.ErrorCode, "NotFound", StringComparison.Ordinal))
158166
{
159-
return false;
167+
return default;
160168
}
161169

162170
// If the object exists but the client is not authorized to access it, then a "Forbidden" will be thrown.
163171
if (string.Equals(e.ErrorCode, "Forbidden", StringComparison.Ordinal))
164172
{
165-
return false;
173+
return default;
166174
}
167175

168176
throw;
169177
}
170178
}
179+
180+
private readonly record struct KeyExistsResult(GetObjectMetadataResponse Metadata)
181+
{
182+
public bool Exists => this.Metadata is not null;
183+
}
171184
}

src/ImageSharp.Web.Providers.AWS/Providers/AWSS3StorageImageProviderOptions.cs

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
// Copyright (c) Six Labors.
22
// Licensed under the Six Labors Split License.
3-
#nullable disable
43

54
namespace SixLabors.ImageSharp.Web.Providers.AWS;
65

@@ -21,19 +20,19 @@ public class AWSS3StorageImageProviderOptions
2120
public class AWSS3BucketClientOptions : IAWSS3BucketClientOptions
2221
{
2322
/// <inheritdoc/>
24-
public string Region { get; set; }
23+
public string? Region { get; set; }
2524

2625
/// <inheritdoc/>
27-
public string BucketName { get; set; }
26+
public string BucketName { get; set; } = null!;
2827

2928
/// <inheritdoc/>
30-
public string AccessKey { get; set; }
29+
public string? AccessKey { get; set; }
3130

3231
/// <inheritdoc/>
33-
public string AccessSecret { get; set; }
32+
public string? AccessSecret { get; set; }
3433

3534
/// <inheritdoc/>
36-
public string Endpoint { get; set; }
35+
public string? Endpoint { get; set; }
3736

3837
/// <inheritdoc/>
3938
public bool UseAccelerateEndpoint { get; set; }

src/ImageSharp.Web.Providers.AWS/Resolvers/AWSS3StorageCacheResolver.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
// Copyright (c) Six Labors.
22
// Licensed under the Six Labors Split License.
3-
#nullable disable
43

54
using Amazon.S3;
65
using Amazon.S3.Model;

src/ImageSharp.Web.Providers.AWS/Resolvers/AWSS3StorageImageResolver.cs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
// Copyright (c) Six Labors.
22
// Licensed under the Six Labors Split License.
3-
#nullable disable
43

54
using System.Net.Http.Headers;
65
using Amazon.S3;
@@ -16,29 +15,32 @@ public class AWSS3StorageImageResolver : IImageResolver
1615
private readonly IAmazonS3 amazonS3;
1716
private readonly string bucketName;
1817
private readonly string imagePath;
18+
private readonly GetObjectMetadataResponse? metadataResponse;
1919

2020
/// <summary>
2121
/// Initializes a new instance of the <see cref="AWSS3StorageImageResolver"/> class.
2222
/// </summary>
2323
/// <param name="amazonS3">The Amazon S3 Client</param>
2424
/// <param name="bucketName">The bucket name.</param>
2525
/// <param name="imagePath">The image path.</param>
26-
public AWSS3StorageImageResolver(IAmazonS3 amazonS3, string bucketName, string imagePath)
26+
/// <param name="metadataResponse">Optional metadata response.</param>
27+
public AWSS3StorageImageResolver(IAmazonS3 amazonS3, string bucketName, string imagePath, GetObjectMetadataResponse? metadataResponse = null)
2728
{
2829
this.amazonS3 = amazonS3;
2930
this.bucketName = bucketName;
3031
this.imagePath = imagePath;
32+
this.metadataResponse = metadataResponse;
3133
}
3234

3335
/// <inheritdoc />
3436
public async Task<ImageMetadata> GetMetaDataAsync()
3537
{
36-
GetObjectMetadataResponse metadata = await this.amazonS3.GetObjectMetadataAsync(this.bucketName, this.imagePath);
38+
GetObjectMetadataResponse metadata = this.metadataResponse ?? await this.amazonS3.GetObjectMetadataAsync(this.bucketName, this.imagePath);
3739

3840
// Try to parse the max age from the source. If it's not zero then we pass it along
3941
// to set the cache control headers for the response.
4042
TimeSpan maxAge = TimeSpan.MinValue;
41-
if (CacheControlHeaderValue.TryParse(metadata.Headers.CacheControl, out CacheControlHeaderValue cacheControl))
43+
if (CacheControlHeaderValue.TryParse(metadata.Headers.CacheControl, out CacheControlHeaderValue? cacheControl))
4244
{
4345
// Weirdly passing null to TryParse returns true.
4446
if (cacheControl?.MaxAge.HasValue == true)

0 commit comments

Comments
 (0)