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();
            }
        }
예제 #2
0
        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();
            }
        }
예제 #3
0
        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);
        }
예제 #5
0
        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");
            }
        }
예제 #7
0
    /// <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");
            }
        }
예제 #10
0
 public void FormatNonNegativeInt64_Throws_ForNegativeValues(long value)
 {
     Assert.Throws <ArgumentOutOfRangeException>(() => HeaderUtilities.FormatNonNegativeInt64(value));
 }
예제 #11
0
 public void FormatNonNegativeInt64_MatchesToString(long value)
 {
     Assert.Equal(value.ToString(CultureInfo.InvariantCulture), HeaderUtilities.FormatNonNegativeInt64(value));
 }
예제 #12
0
 private static string FormatInt64(long input)
 {
     return(HeaderUtilities.FormatNonNegativeInt64(input));
 }
예제 #13
0
    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);
    }
예제 #14
0
    // 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);
            }
        }));
    }