Skip to content

Commit d05a4e0

Browse files
Merge pull request #39 from umbraco/bugfix/entitytagheadervalue
Correctly parse ETag value, path, access conditions and use HeaderNames constants
2 parents ae44b73 + 12828cc commit d05a4e0

File tree

1 file changed

+49
-35
lines changed

1 file changed

+49
-35
lines changed

src/Umbraco.StorageProviders.AzureBlob/AzureBlobFileSystemMiddleware.cs

Lines changed: 49 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -83,13 +83,13 @@ private async Task HandleRequestAsync(HttpContext context, RequestDelegate next)
8383
var request = context.Request;
8484
var response = context.Response;
8585

86-
if (!context.Request.Path.StartsWithSegments(_rootPath, StringComparison.InvariantCultureIgnoreCase))
86+
if (!context.Request.Path.StartsWithSegments(_rootPath, StringComparison.InvariantCultureIgnoreCase, out PathString path))
8787
{
8888
await next(context).ConfigureAwait(false);
8989
return;
9090
}
9191

92-
string containerPath = $"{_containerRootPath.TrimEnd('/')}/{(request.Path.Value.Remove(0, _rootPath.Length)).TrimStart('/')}";
92+
string containerPath = $"{_containerRootPath.TrimEnd('/')}/{path.Value?.TrimStart('/')}";
9393
var blob = _fileSystemProvider.GetFileSystem(_name).GetBlobClient(containerPath);
9494

9595
var blobRequestConditions = GetAccessCondition(context.Request);
@@ -114,7 +114,7 @@ private async Task HandleRequestAsync(HttpContext context, RequestDelegate next)
114114
// a Content-Range header is needed with the new length
115115
ignoreRange = true;
116116
properties = await blob.GetPropertiesAsync().ConfigureAwait(false);
117-
response.Headers.Append("Content-Range", $"bytes */{properties.Value.ContentLength}");
117+
response.Headers.Append(HeaderNames.ContentRange, $"bytes */{properties.Value.ContentLength}");
118118
}
119119
catch (RequestFailedException ex) when (ex.Status == (int)HttpStatusCode.NotModified)
120120
{
@@ -128,15 +128,15 @@ private async Task HandleRequestAsync(HttpContext context, RequestDelegate next)
128128
// and not a request failed with status NotModified :(
129129
catch (Exception ex) when (ex.Message == "The condition specified using HTTP conditional header(s) is not met.")
130130
{
131-
if (blobRequestConditions != null
132-
&& (blobRequestConditions.IfMatch.HasValue || blobRequestConditions.IfUnmodifiedSince.HasValue))
131+
if (blobRequestConditions != null &&
132+
(blobRequestConditions.IfMatch.HasValue || blobRequestConditions.IfUnmodifiedSince.HasValue))
133133
{
134134
// If-Range or If-Unmodified-Since is not met
135135
// if the resource has been modified, we need to send the whole file back with a 200 OK
136136
// a Content-Range header is needed with the new length
137137
ignoreRange = true;
138138
properties = await blob.GetPropertiesAsync().ConfigureAwait(false);
139-
response.Headers.Append("Content-Range", $"bytes */{properties.Value.ContentLength}");
139+
response.Headers.Append(HeaderNames.ContentRange, $"bytes */{properties.Value.ContentLength}");
140140
}
141141
else
142142
{
@@ -155,22 +155,24 @@ private async Task HandleRequestAsync(HttpContext context, RequestDelegate next)
155155

156156
var responseHeaders = response.GetTypedHeaders();
157157

158-
responseHeaders.CacheControl =
159-
new CacheControlHeaderValue
160-
{
161-
Public = true,
162-
MustRevalidate = true,
163-
MaxAge = _maxAge,
164-
};
158+
responseHeaders.CacheControl = new CacheControlHeaderValue
159+
{
160+
Public = true,
161+
MustRevalidate = true,
162+
MaxAge = _maxAge,
163+
};
165164

166165
responseHeaders.LastModified = properties.Value.LastModified;
167-
responseHeaders.ETag = new EntityTagHeaderValue(properties.Value.ETag.ToString("H"));
168-
responseHeaders.Append(HeaderNames.Vary, "Accept-Encoding");
169166

170-
var requestHeaders = request.GetTypedHeaders();
167+
if (EntityTagHeaderValue.TryParse(properties.Value.ETag.ToString("H"), out EntityTagHeaderValue entityTagHeaderValue))
168+
{
169+
responseHeaders.ETag = entityTagHeaderValue;
170+
}
171171

172-
var rangeHeader = requestHeaders.Range;
172+
responseHeaders.Append(HeaderNames.Vary, HeaderNames.AcceptEncoding);
173173

174+
var requestHeaders = request.GetTypedHeaders();
175+
var rangeHeader = requestHeaders.Range;
174176
if (!ignoreRange && rangeHeader != null)
175177
{
176178
if (!ValidateRanges(rangeHeader.Ranges, properties.Value.ContentLength))
@@ -223,6 +225,7 @@ private async Task HandleRequestAsync(HttpContext context, RequestDelegate next)
223225
return;
224226
}
225227
}
228+
226229
response.StatusCode = (int)HttpStatusCode.OK;
227230
response.ContentType = properties.Value.ContentType;
228231
responseHeaders.ContentLength = properties.Value.ContentLength;
@@ -234,11 +237,11 @@ private async Task HandleRequestAsync(HttpContext context, RequestDelegate next)
234237

235238
private static BlobRequestConditions? GetAccessCondition(HttpRequest request)
236239
{
237-
var range = request.Headers["Range"];
240+
var range = request.Headers[HeaderNames.Range];
238241
if (string.IsNullOrEmpty(range))
239242
{
240243
// etag
241-
var ifNoneMatch = request.Headers["If-None-Match"];
244+
var ifNoneMatch = request.Headers[HeaderNames.IfNoneMatch];
242245
if (!string.IsNullOrEmpty(ifNoneMatch))
243246
{
244247
return new BlobRequestConditions
@@ -247,40 +250,46 @@ private async Task HandleRequestAsync(HttpContext context, RequestDelegate next)
247250
};
248251
}
249252

250-
var ifModifiedSince = request.Headers["If-Modified-Since"];
251-
if (!string.IsNullOrEmpty(ifModifiedSince))
253+
var ifModifiedSince = request.Headers[HeaderNames.IfModifiedSince];
254+
if (!string.IsNullOrEmpty(ifModifiedSince) &&
255+
DateTimeOffset.TryParse(ifModifiedSince, CultureInfo.InvariantCulture, DateTimeStyles.None, out DateTimeOffset ifModifiedSinceDate))
252256
{
253257
return new BlobRequestConditions
254258
{
255-
IfModifiedSince = DateTimeOffset.Parse(ifModifiedSince, CultureInfo.InvariantCulture)
259+
IfModifiedSince = ifModifiedSinceDate
256260
};
257261
}
258262
}
259263
else
260264
{
261265
// handle If-Range header, it can be either an etag or a date
262266
// see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/If-Range and https://tools.ietf.org/html/rfc7233#section-3.2
263-
var ifRange = request.Headers["If-Range"];
267+
var ifRange = request.Headers[HeaderNames.IfRange];
264268
if (!string.IsNullOrEmpty(ifRange))
265269
{
266-
var conditions = new BlobRequestConditions();
267-
268-
if (DateTimeOffset.TryParse(ifRange, out var date))
270+
if (DateTimeOffset.TryParse(ifRange, CultureInfo.InvariantCulture, DateTimeStyles.None, out DateTimeOffset ifRangeDate))
269271
{
270-
conditions.IfUnmodifiedSince = date;
272+
return new BlobRequestConditions()
273+
{
274+
IfUnmodifiedSince = ifRangeDate
275+
};
271276
}
272277
else
273278
{
274-
conditions.IfMatch = new ETag(ifRange);
279+
return new BlobRequestConditions()
280+
{
281+
IfMatch = new ETag(ifRange)
282+
};
275283
}
276284
}
277285

278-
var ifUnmodifiedSince = request.Headers["If-Unmodified-Since"];
279-
if (!string.IsNullOrEmpty(ifUnmodifiedSince))
286+
var ifUnmodifiedSince = request.Headers[HeaderNames.IfUnmodifiedSince];
287+
if (!string.IsNullOrEmpty(ifUnmodifiedSince) &&
288+
DateTimeOffset.TryParse(ifUnmodifiedSince, CultureInfo.InvariantCulture, DateTimeStyles.None, out DateTimeOffset ifUnmodifiedSinceDate))
280289
{
281290
return new BlobRequestConditions
282291
{
283-
IfUnmodifiedSince = DateTimeOffset.Parse(ifUnmodifiedSince, CultureInfo.InvariantCulture)
292+
IfUnmodifiedSince = ifUnmodifiedSinceDate
284293
};
285294
}
286295
}
@@ -291,14 +300,21 @@ private async Task HandleRequestAsync(HttpContext context, RequestDelegate next)
291300
private static bool ValidateRanges(ICollection<RangeItemHeaderValue> ranges, long length)
292301
{
293302
if (ranges.Count == 0)
303+
{
294304
return false;
305+
}
295306

296307
foreach (var range in ranges)
297308
{
298309
if (range.From > range.To)
310+
{
299311
return false;
312+
}
313+
300314
if (range.To >= length)
315+
{
301316
return false;
317+
}
302318
}
303319

304320
return true;
@@ -337,8 +353,7 @@ private static ContentRangeHeaderValue GetRangeHeader(BlobProperties properties,
337353
return new ContentRangeHeaderValue(from, to, properties.ContentLength);
338354
}
339355

340-
private static async Task DownloadRangeToStreamAsync(BlobClient blob, BlobProperties properties,
341-
Stream outputStream, ContentRangeHeaderValue contentRange, CancellationToken cancellationToken)
356+
private static async Task DownloadRangeToStreamAsync(BlobClient blob, BlobProperties properties, Stream outputStream, ContentRangeHeaderValue contentRange, CancellationToken cancellationToken)
342357
{
343358
var offset = contentRange.From.GetValueOrDefault(0L);
344359
var length = properties.ContentLength;
@@ -359,8 +374,7 @@ private static async Task DownloadRangeToStreamAsync(BlobClient blob, BlobProper
359374
await DownloadRangeToStreamAsync(blob, outputStream, offset, length, cancellationToken).ConfigureAwait(false);
360375
}
361376

362-
private static async Task DownloadRangeToStreamAsync(BlobClient blob, Stream outputStream,
363-
long offset, long length, CancellationToken cancellationToken)
377+
private static async Task DownloadRangeToStreamAsync(BlobClient blob, Stream outputStream, long offset, long length, CancellationToken cancellationToken)
364378
{
365379
try
366380
{

0 commit comments

Comments
 (0)