public void RemoveHeader_RemovesProxyRequestHeader(string names, string removedHeader, string expected)
    {
        var httpContext  = new DefaultHttpContext();
        var proxyRequest = new HttpRequestMessage()
        {
            Content = new EmptyHttpContent()
        };

        foreach (var name in names.Split("; "))
        {
            httpContext.Request.Headers.Add(name, "value0");
            RequestUtilities.AddHeader(proxyRequest, name, "value1");
        }

        RequestTransform.RemoveHeader(new RequestTransformContext()
        {
            HttpContext  = httpContext,
            ProxyRequest = proxyRequest
        }, removedHeader);

        foreach (var name in names.Split("; "))
        {
            Assert.True(httpContext.Request.Headers.TryGetValue(name, out var value));
            Assert.Equal("value0", value);
        }

        var expectedHeaders  = expected.Split("; ", System.StringSplitOptions.RemoveEmptyEntries).OrderBy(h => h);
        var remainingHeaders = proxyRequest.Headers.Union(proxyRequest.Content.Headers).OrderBy(h => h.Key);

        Assert.Equal(expectedHeaders, remainingHeaders.Select(h => h.Key));
        Assert.All(remainingHeaders, h => Assert.Equal("value1", Assert.Single(h.Value)));
    }
Example #2
0
        private void CopyRequestHeaders(HttpContext context, HttpRequestMessage proxyRequest)
        {
            // Transforms that were run in the first pass.
            HashSet <string> transformsRun = null;

            if (ShouldCopyRequestHeaders.GetValueOrDefault(true))
            {
                foreach (var header in context.Request.Headers)
                {
                    var headerName  = header.Key;
                    var headerValue = header.Value;
                    if (StringValues.IsNullOrEmpty(headerValue))
                    {
                        continue;
                    }

                    // Filter out HTTP/2 pseudo headers like ":method" and ":path", those go into other fields.
                    if (headerName.Length > 0 && headerName[0] == ':')
                    {
                        continue;
                    }

                    if (RequestHeaderTransforms.TryGetValue(headerName, out var transform))
                    {
                        (transformsRun ??= new HashSet <string>(StringComparer.OrdinalIgnoreCase)).Add(headerName);
                        headerValue = transform.Apply(context, proxyRequest, headerValue);
                        if (StringValues.IsNullOrEmpty(headerValue))
                        {
                            continue;
                        }
                    }

                    RequestUtilities.AddHeader(proxyRequest, headerName, headerValue);
                }
            }

            transformsRun ??= EmptyHash;

            // Run any transforms that weren't run yet.
            foreach (var(headerName, transform) in RequestHeaderTransforms)
            {
                if (!transformsRun.Contains(headerName))
                {
                    var headerValue = transform.Apply(context, proxyRequest, StringValues.Empty);
                    if (!StringValues.IsNullOrEmpty(headerValue))
                    {
                        RequestUtilities.AddHeader(proxyRequest, headerName, headerValue);
                    }
                }
            }
        }
        /// <summary>
        /// Adds the given header to the HttpRequestMessage or HttpContent where applicable.
        /// </summary>
        public static void AddHeader(RequestTransformContext context, string headerName, StringValues values)
        {
            if (context is null)
            {
                throw new System.ArgumentNullException(nameof(context));
            }

            if (string.IsNullOrEmpty(headerName))
            {
                throw new System.ArgumentException($"'{nameof(headerName)}' cannot be null or empty.", nameof(headerName));
            }

            RequestUtilities.AddHeader(context.ProxyRequest, headerName, values);
        }
Example #4
0
    /// <summary>
    /// A callback that is invoked prior to sending the proxied request. All HttpRequestMessage fields are
    /// initialized except RequestUri, which will be initialized after the callback if no value is provided.
    /// See <see cref="RequestUtilities.MakeDestinationAddress(string, PathString, QueryString)"/> for constructing a custom request Uri.
    /// The string parameter represents the destination URI prefix that should be used when constructing the RequestUri.
    /// The headers are copied by the base implementation, excluding some protocol headers like HTTP/2 pseudo headers (":authority").
    /// </summary>
    /// <param name="httpContext">The incoming request.</param>
    /// <param name="proxyRequest">The outgoing proxy request.</param>
    /// <param name="destinationPrefix">The uri prefix for the selected destination server which can be used to create the RequestUri.</param>
    public virtual ValueTask TransformRequestAsync(HttpContext httpContext, HttpRequestMessage proxyRequest, string destinationPrefix)
    {
        foreach (var header in httpContext.Request.Headers)
        {
            var headerName  = header.Key;
            var headerValue = header.Value;
            if (RequestUtilities.ShouldSkipRequestHeader(headerName))
            {
                continue;
            }

            RequestUtilities.AddHeader(proxyRequest, headerName, headerValue);
        }

        // https://datatracker.ietf.org/doc/html/rfc7230#section-3.3.3
        // If a message is received with both a Transfer-Encoding and a
        // Content-Length header field, the Transfer-Encoding overrides the
        // Content-Length.  Such a message might indicate an attempt to
        // perform request smuggling (Section 9.5) or response splitting
        // (Section 9.4) and ought to be handled as an error.  A sender MUST
        // remove the received Content-Length field prior to forwarding such
        // a message downstream.
        if (httpContext.Request.Headers.ContainsKey(HeaderNames.TransferEncoding) &&
            httpContext.Request.Headers.ContainsKey(HeaderNames.ContentLength))
        {
            proxyRequest.Content?.Headers.Remove(HeaderNames.ContentLength);
        }

        // https://datatracker.ietf.org/doc/html/rfc7540#section-8.1.2.2
        // The only exception to this is the TE header field, which MAY be
        // present in an HTTP/2 request; when it is, it MUST NOT contain any
        // value other than "trailers".
        if (ProtocolHelper.IsHttp2OrGreater(httpContext.Request.Protocol))
        {
            var te = httpContext.Request.Headers.GetCommaSeparatedValues(HeaderNames.TE);
            if (te != null)
            {
                for (var i = 0; i < te.Length; i++)
                {
                    if (string.Equals(te[i], "trailers", StringComparison.OrdinalIgnoreCase))
                    {
                        var added = proxyRequest.Headers.TryAddWithoutValidation(HeaderNames.TE, te[i]);
                        Debug.Assert(added);
                        break;
                    }
                }
            }
        }

        return(default);
        /// <summary>
        /// A callback that is invoked prior to sending the proxied request. All HttpRequestMessage fields are
        /// initialized except RequestUri, which will be initialized after the callback if no value is provided.
        /// See <see cref="RequestUtilities.MakeDestinationAddress(string, PathString, QueryString)"/> for constructing a custom request Uri.
        /// The string parameter represents the destination URI prefix that should be used when constructing the RequestUri.
        /// The headers are copied by the base implementation, excluding some protocol headers like HTTP/2 pseudo headers (":authority").
        /// </summary>
        /// <param name="httpContext">The incoming request.</param>
        /// <param name="proxyRequest">The outgoing proxy request.</param>
        /// <param name="destinationPrefix">The uri prefix for the selected destination server which can be used to create the RequestUri.</param>
        public virtual ValueTask TransformRequestAsync(HttpContext httpContext, HttpRequestMessage proxyRequest, string destinationPrefix)
        {
            foreach (var header in httpContext.Request.Headers)
            {
                var headerName  = header.Key;
                var headerValue = header.Value;
                if (StringValues.IsNullOrEmpty(headerValue))
                {
                    continue;
                }

                // Filter out HTTP/2 pseudo headers like ":method" and ":path", those go into other fields.
                if (headerName.Length > 0 && headerName[0] == ':')
                {
                    continue;
                }

                RequestUtilities.AddHeader(proxyRequest, headerName, headerValue);
            }

            return(default);
Example #6
0
 /// <summary>
 /// Adds the given header to the HttpRequestMessage or HttpContent where applicable.
 /// </summary>
 public static void AddHeader(RequestTransformContext context, string headerName, StringValues values)
 {
     RequestUtilities.AddHeader(context.ProxyRequest, headerName, values);
 }