internal async Task FinalizeCacheBodyAsync(ResponseCachingContext context) { if (context.ShouldCacheResponse && context.ResponseCachingStream.BufferingEnabled) { var contentLength = context.HttpContext.Response.ContentLength; var bufferStream = context.ResponseCachingStream.GetBufferStream(); if (!contentLength.HasValue || contentLength == bufferStream.Length) { var response = context.HttpContext.Response; // Add a content-length if required if (!response.ContentLength.HasValue && StringValues.IsNullOrEmpty(response.Headers[HeaderNames.TransferEncoding])) { context.CachedResponse.Headers[HeaderNames.ContentLength] = HeaderUtilities.FormatNonNegativeInt64(bufferStream.Length); } context.CachedResponse.Body = bufferStream; _logger.LogResponseCached(); await _cache.SetAsync(context.StorageVaryKey ?? context.BaseKey, context.CachedResponse, context.CachedResponseValidFor); } else { _logger.LogResponseContentLengthMismatchNotCached(); } } else { _logger.LogResponseNotCached(); } }
internal void FinalizeCacheBody(ResponseCachingContext context) { if (context.ShouldCacheResponse && context.ResponseCachingStream.BufferingEnabled) { var contentLength = context.HttpContext.Response.ContentLength; var cachedResponseBody = context.ResponseCachingStream.GetCachedResponseBody(); if (!contentLength.HasValue || contentLength == cachedResponseBody.Length || (cachedResponseBody.Length == 0 && HttpMethods.IsHead(context.HttpContext.Request.Method))) { var response = context.HttpContext.Response; // Add a content-length if required if (!response.ContentLength.HasValue && StringValues.IsNullOrEmpty(response.Headers[HeaderNames.TransferEncoding])) { context.CachedResponse.Headers[HeaderNames.ContentLength] = HeaderUtilities.FormatNonNegativeInt64(cachedResponseBody.Length); } context.CachedResponse.Body = cachedResponseBody; _logger.ResponseCached(); _cache.Set(context.StorageVaryKey ?? context.BaseKey, context.CachedResponse, context.CachedResponseValidFor); } else { _logger.ResponseContentLengthMismatchNotCached(); } } else { _logger.LogResponseNotCached(); } }
private static void ValidContentLengthsAcceptedImpl(HttpHeaders httpHeaders) { IDictionary <string, StringValues> headers = httpHeaders; StringValues value; Assert.False(headers.TryGetValue("Content-Length", out value)); Assert.Null(httpHeaders.ContentLength); Assert.False(httpHeaders.ContentLength.HasValue); httpHeaders.ContentLength = 1; Assert.True(headers.TryGetValue("Content-Length", out value)); Assert.Equal("1", value[0]); Assert.Equal(1, httpHeaders.ContentLength); Assert.True(httpHeaders.ContentLength.HasValue); httpHeaders.ContentLength = long.MaxValue; Assert.True(headers.TryGetValue("Content-Length", out value)); Assert.Equal(HeaderUtilities.FormatNonNegativeInt64(long.MaxValue), value[0]); Assert.Equal(long.MaxValue, httpHeaders.ContentLength); Assert.True(httpHeaders.ContentLength.HasValue); httpHeaders.ContentLength = null; Assert.False(headers.TryGetValue("Content-Length", out value)); Assert.Null(httpHeaders.ContentLength); Assert.False(httpHeaders.ContentLength.HasValue); }
internal async Task <bool> TryServeCachedResponseAsync(ResponseCachingContext context, IResponseCacheEntry cacheEntry) { var cachedResponse = cacheEntry as CachedResponse; if (cachedResponse == null) { return(false); } context.CachedResponse = cachedResponse; context.CachedResponseHeaders = cachedResponse.Headers; context.ResponseTime = _options.SystemClock.UtcNow; var cachedEntryAge = context.ResponseTime.Value - context.CachedResponse.Created; context.CachedEntryAge = cachedEntryAge > TimeSpan.Zero ? cachedEntryAge : TimeSpan.Zero; if (_policyProvider.IsCachedEntryFresh(context)) { // Check conditional request rules if (ContentIsNotModified(context)) { _logger.LogNotModifiedServed(); context.HttpContext.Response.StatusCode = StatusCodes.Status304NotModified; } else { var response = context.HttpContext.Response; // Copy the cached status code and response headers response.StatusCode = context.CachedResponse.StatusCode; foreach (var header in context.CachedResponse.Headers) { response.Headers[header.Key] = header.Value; } // Note: int64 division truncates result and errors may be up to 1 second. This reduction in // accuracy of age calculation is considered appropriate since it is small compared to clock // skews and the "Age" header is an estimate of the real age of cached content. response.Headers[HeaderNames.Age] = HeaderUtilities.FormatNonNegativeInt64(context.CachedEntryAge.Value.Ticks / TimeSpan.TicksPerSecond); // Copy the cached response body var body = context.CachedResponse.Body; if (body.Length > 0) { try { await body.CopyToAsync(response.Body, StreamUtilities.BodySegmentSize, context.HttpContext.RequestAborted); } catch (OperationCanceledException) { context.HttpContext.Abort(); } } _logger.LogCachedResponseServed(); } return(true); } return(false); }
private static string FormatInt64(long input) { #if NETSTANDARD2_0 return(HeaderUtilities.FormatNonNegativeInt64(input)); #else return(HeaderUtilities.FormatInt64(input)); #endif }
public async Task DecompressionDeflateTest() { // Arrange using (var server = new TestServer(this.CreateDecompressionBuilder(options => { options.MinimumCompressionThreshold = 0; }))) { // Act byte[] compressedBytes; using (var dataStream = new MemoryStream()) { using (var zipStream = new DeflateStream(dataStream, CompressionMode.Compress)) { using (var writer = new StreamWriter(zipStream)) { writer.Write(Helpers.ResponseText); } } compressedBytes = dataStream.ToArray(); } HttpResponseMessage response; string responseText; using (HttpClient client = server.CreateClient()) { client.DefaultRequestHeaders.Add(HeaderNames.AcceptEncoding, "gzip"); var content = new ByteArrayContent(compressedBytes); content.Headers.Add(HeaderNames.ContentEncoding, "deflate"); content.Headers.Add(HeaderNames.ContentLength, HeaderUtilities.FormatNonNegativeInt64(compressedBytes.Length)); response = await client.PutAsync("/", content); // Assert response.EnsureSuccessStatusCode(); Stream stream = await response.Content.ReadAsStreamAsync(); using (var decompression = new GZipStream(stream, CompressionMode.Decompress)) { using (var ms = new MemoryStream()) { await decompression.CopyToAsync(ms); responseText = Encoding.UTF8.GetString(ms.ToArray()); } } } Assert.AreEqual(HttpStatusCode.OK, response.StatusCode, "StatusCode != OK"); Assert.AreEqual(Helpers.ResponseText, responseText, "Response Text not equal"); Assert.AreEqual(85, response.Content.Headers.ContentLength, "Content-Length != 85"); Assert.AreEqual(true, response.Content.Headers.ContentEncoding.Any(), "Content-Encoding == null"); Assert.AreEqual("gzip", response.Content.Headers.ContentEncoding.ToString(), "Content-Encoding != gzip"); } }
/// <summary> /// Append string representation of this <see cref="SetCookieHeaderValue"/> to given /// <paramref name="builder"/>. /// </summary> /// <param name="builder"> /// The <see cref="StringBuilder"/> to receive the string representation of this /// <see cref="SetCookieHeaderValue"/>. /// </param> public void AppendToStringBuilder(StringBuilder builder) { builder.Append(_name.AsSpan()); builder.Append('='); builder.Append(_value.AsSpan()); if (Expires.HasValue) { AppendSegment(builder, ExpiresToken, HeaderUtilities.FormatDate(Expires.GetValueOrDefault())); } if (MaxAge.HasValue) { AppendSegment(builder, MaxAgeToken, HeaderUtilities.FormatNonNegativeInt64((long)MaxAge.GetValueOrDefault().TotalSeconds)); } if (Domain != null) { AppendSegment(builder, DomainToken, Domain); } if (Path != null) { AppendSegment(builder, PathToken, Path); } if (Secure) { AppendSegment(builder, SecureToken, null); } // Allow for Unspecified (-1) to skip SameSite if (SameSite == SameSiteMode.None) { AppendSegment(builder, SameSiteToken, SameSiteNoneToken); } else if (SameSite == SameSiteMode.Lax) { AppendSegment(builder, SameSiteToken, SameSiteLaxToken); } else if (SameSite == SameSiteMode.Strict) { AppendSegment(builder, SameSiteToken, SameSiteStrictToken); } if (HttpOnly) { AppendSegment(builder, HttpOnlyToken, null); } foreach (var extension in Extensions) { AppendSegment(builder, extension, null); } }
public async Task DecompressionGZipNoCompressionTest() { // Arrange using (var server = new TestServer(this.CreateDecompressionBuilder())) { // Act byte[] compressedBytes; using (var dataStream = new MemoryStream()) { using (var zipStream = new GZipStream(dataStream, CompressionMode.Compress)) { using (var writer = new StreamWriter(zipStream)) { writer.Write(Helpers.ResponseText); } } compressedBytes = dataStream.ToArray(); } HttpResponseMessage response; string responseText; using (HttpClient client = server.CreateClient()) { client.DefaultRequestHeaders.Add(HeaderNames.AcceptEncoding, "gzip"); var content = new ByteArrayContent(compressedBytes); content.Headers.Add(HeaderNames.ContentEncoding, "gzip"); content.Headers.Add(HeaderNames.ContentLength, HeaderUtilities.FormatNonNegativeInt64(compressedBytes.Length)); response = await client.PutAsync("/", content); // Assert response.EnsureSuccessStatusCode(); responseText = await response.Content.ReadAsStringAsync(); } Assert.AreEqual(HttpStatusCode.OK, response.StatusCode, "StatusCode != OK"); Assert.AreEqual(Helpers.ResponseText, responseText, "Response Text not equal"); Assert.AreEqual(124, response.Content.Headers.ContentLength, "Content-Length != 124"); Assert.AreEqual(false, response.Content.Headers.ContentEncoding.Any(), "Content-Encoding != null"); } }
public async Task NoDecompressionGZipTest() { // Arrange IWebHostBuilder builder = new WebHostBuilder() .ConfigureServices(s => s.AddCompression()) .Configure( app => { app.UseCompression(); app.Run( async c => { using (var ms = new MemoryStream()) { await c.Request.Body.CopyToAsync(ms); c.Response.ContentType = "text/plain"; c.Response.ContentLength = ms.Length; ms.Seek(0, SeekOrigin.Begin); await ms.CopyToAsync(c.Response.Body); } }); }); using (var server = new TestServer(builder)) { // Act byte[] compressedBytes; using (var dataStream = new MemoryStream()) { using (var zipStream = new GZipStream(dataStream, CompressionMode.Compress)) { using (var writer = new StreamWriter(zipStream)) { writer.Write(Helpers.ResponseText); } } compressedBytes = dataStream.ToArray(); } HttpResponseMessage response; byte[] responseBytes; using (HttpClient client = server.CreateClient()) { client.DefaultRequestHeaders.Add(HeaderNames.AcceptEncoding, "gzip"); var content = new ByteArrayContent(compressedBytes); content.Headers.Add(HeaderNames.ContentLength, HeaderUtilities.FormatNonNegativeInt64(compressedBytes.Length)); response = await client.PutAsync("/", content); // Assert response.EnsureSuccessStatusCode(); responseBytes = await response.Content.ReadAsByteArrayAsync(); } Assert.AreEqual(HttpStatusCode.OK, response.StatusCode, "StatusCode != OK"); Assert.AreEqual(true, compressedBytes.SequenceEqual(responseBytes), "Response bytes not equal"); Assert.AreEqual(compressedBytes.Length, response.Content.Headers.ContentLength, $"Content-Length != {compressedBytes.Length}"); Assert.AreEqual(true, !response.Content.Headers.ContentEncoding.Any(), "Content-Encoding != null"); } }
public void FormatNonNegativeInt64_Throws_ForNegativeValues(long value) { Assert.Throws <ArgumentOutOfRangeException>(() => HeaderUtilities.FormatNonNegativeInt64(value)); }
public void FormatNonNegativeInt64_MatchesToString(long value) { Assert.Equal(value.ToString(CultureInfo.InvariantCulture), HeaderUtilities.FormatNonNegativeInt64(value)); }
private static string FormatInt64(long input) { return(HeaderUtilities.FormatNonNegativeInt64(input)); }
internal async Task <bool> TryServeCachedResponseAsync(OutputCacheContext context, OutputCacheEntry?cacheEntry, IReadOnlyList <IOutputCachePolicy> policies) { if (cacheEntry == null) { return(false); } context.CachedResponse = cacheEntry; context.ResponseTime = _options.SystemClock.UtcNow; var cacheEntryAge = context.ResponseTime.Value - context.CachedResponse.Created; context.CachedEntryAge = cacheEntryAge > TimeSpan.Zero ? cacheEntryAge : TimeSpan.Zero; foreach (var policy in policies) { await policy.ServeFromCacheAsync(context, context.HttpContext.RequestAborted); } context.IsCacheEntryFresh = true; // Validate expiration if (context.CachedEntryAge <= TimeSpan.Zero) { _logger.ExpirationExpiresExceeded(context.ResponseTime !.Value); context.IsCacheEntryFresh = false; } if (context.IsCacheEntryFresh) { var cachedResponseHeaders = context.CachedResponse.Headers; // Check conditional request rules if (ContentIsNotModified(context)) { _logger.NotModifiedServed(); context.HttpContext.Response.StatusCode = StatusCodes.Status304NotModified; if (cachedResponseHeaders != null) { foreach (var key in HeadersToIncludeIn304) { if (cachedResponseHeaders.TryGetValue(key, out var values)) { context.HttpContext.Response.Headers[key] = values; } } } } else { var response = context.HttpContext.Response; // Copy the cached status code and response headers response.StatusCode = context.CachedResponse.StatusCode; foreach (var header in context.CachedResponse.Headers) { response.Headers[header.Key] = header.Value; } // Note: int64 division truncates result and errors may be up to 1 second. This reduction in // accuracy of age calculation is considered appropriate since it is small compared to clock // skews and the "Age" header is an estimate of the real age of cached content. response.Headers.Age = HeaderUtilities.FormatNonNegativeInt64(context.CachedEntryAge.Ticks / TimeSpan.TicksPerSecond); // Copy the cached response body var body = context.CachedResponse.Body; if (body.Length > 0) { try { await body.CopyToAsync(response.BodyWriter, context.HttpContext.RequestAborted); } catch (OperationCanceledException) { context.HttpContext.Abort(); } } _logger.CachedResponseServed(); } return(true); } return(false); }
// name="value"; expires=Sun, 06 Nov 1994 08:49:37 GMT; max-age=86400; domain=domain1; path=path1; secure; samesite={strict|lax|none}; httponly /// <inheritdoc /> public override string ToString() { var length = _name.Length + EqualsToken.Length + _value.Length; string?maxAge = null; string?sameSite = null; if (Expires.HasValue) { length += SeparatorToken.Length + ExpiresToken.Length + EqualsToken.Length + ExpiresDateLength; } if (MaxAge.HasValue) { maxAge = HeaderUtilities.FormatNonNegativeInt64((long)MaxAge.GetValueOrDefault().TotalSeconds); length += SeparatorToken.Length + MaxAgeToken.Length + EqualsToken.Length + maxAge.Length; } if (Domain != null) { length += SeparatorToken.Length + DomainToken.Length + EqualsToken.Length + Domain.Length; } if (Path != null) { length += SeparatorToken.Length + PathToken.Length + EqualsToken.Length + Path.Length; } if (Secure) { length += SeparatorToken.Length + SecureToken.Length; } // Allow for Unspecified (-1) to skip SameSite if (SameSite == SameSiteMode.None) { sameSite = SameSiteNoneToken; length += SeparatorToken.Length + SameSiteToken.Length + EqualsToken.Length + sameSite.Length; } else if (SameSite == SameSiteMode.Lax) { sameSite = SameSiteLaxToken; length += SeparatorToken.Length + SameSiteToken.Length + EqualsToken.Length + sameSite.Length; } else if (SameSite == SameSiteMode.Strict) { sameSite = SameSiteStrictToken; length += SeparatorToken.Length + SameSiteToken.Length + EqualsToken.Length + sameSite.Length; } if (HttpOnly) { length += SeparatorToken.Length + HttpOnlyToken.Length; } foreach (var extension in Extensions) { length += SeparatorToken.Length + extension.Length; } return(string.Create(length, (this, maxAge, sameSite), (span, tuple) => { var(headerValue, maxAgeValue, sameSite) = tuple; Append(ref span, headerValue._name); Append(ref span, EqualsToken); Append(ref span, headerValue._value); if (headerValue.Expires is DateTimeOffset expiresValue) { Append(ref span, SeparatorToken); Append(ref span, ExpiresToken); Append(ref span, EqualsToken); var formatted = expiresValue.TryFormat(span, out var charsWritten, ExpiresDateFormat); span = span.Slice(charsWritten); Debug.Assert(formatted); } if (maxAgeValue != null) { AppendSegment(ref span, MaxAgeToken, maxAgeValue); } if (headerValue.Domain != null) { AppendSegment(ref span, DomainToken, headerValue.Domain); } if (headerValue.Path != null) { AppendSegment(ref span, PathToken, headerValue.Path); } if (headerValue.Secure) { AppendSegment(ref span, SecureToken, null); } if (sameSite != null) { AppendSegment(ref span, SameSiteToken, sameSite); } if (headerValue.HttpOnly) { AppendSegment(ref span, HttpOnlyToken, null); } foreach (var extension in Extensions) { AppendSegment(ref span, extension, null); } })); }