예제 #1
0
        private void OnAuthorityFormTarget(HttpMethod method, Span <byte> target)
        {
            _requestTargetForm = HttpRequestTarget.AuthorityForm;

            // This is not complete validation. It is just a quick scan for invalid characters
            // but doesn't check that the target fully matches the URI spec.
            for (var i = 0; i < target.Length; i++)
            {
                var ch = target[i];
                if (!UriUtilities.IsValidAuthorityCharacter(ch))
                {
                    ThrowRequestTargetRejected(target);
                }
            }

            // The authority-form of request-target is only used for CONNECT
            // requests (https://tools.ietf.org/html/rfc7231#section-4.3.6).
            if (method != HttpMethod.Connect)
            {
                ThrowRequestRejected(RequestRejectionReason.ConnectMethodRequired);
            }

            // When making a CONNECT request to establish a tunnel through one or
            // more proxies, a client MUST send only the target URI's authority
            // component (excluding any userinfo and its "@" delimiter) as the
            // request-target.For example,
            //
            //  CONNECT www.example.com:80 HTTP/1.1
            //
            // Allowed characters in the 'host + port' section of authority.
            // See https://tools.ietf.org/html/rfc3986#section-3.2
            RawTarget   = target.GetAsciiStringNonNullCharacters();
            Path        = string.Empty;
            QueryString = string.Empty;
        }
예제 #2
0
        private void OnAbsoluteFormTarget(Span <byte> target, Span <byte> query)
        {
            _requestTargetForm = HttpRequestTarget.AbsoluteForm;

            // absolute-form
            // https://tools.ietf.org/html/rfc7230#section-5.3.2

            // This code should be the edge-case.

            // From the spec:
            //    a server MUST accept the absolute-form in requests, even though
            //    HTTP/1.1 clients will only send them in requests to proxies.

            RawTarget = target.GetAsciiStringNonNullCharacters();

            // Validation of absolute URIs is slow, but clients
            // should not be sending this form anyways, so perf optimization
            // not high priority

            if (!Uri.TryCreate(RawTarget, UriKind.Absolute, out var uri))
            {
                ThrowRequestTargetRejected(target);
            }

            _absoluteRequestTarget = uri;
            Path = uri.LocalPath;
            // don't use uri.Query because we need the unescaped version
            QueryString = query.GetAsciiStringNonNullCharacters();
        }
예제 #3
0
        private void OnAbsoluteFormTarget(Span <byte> target, Span <byte> query)
        {
            _requestTargetForm = HttpRequestTarget.AbsoluteForm;

            // absolute-form
            // https://tools.ietf.org/html/rfc7230#section-5.3.2

            // This code should be the edge-case.

            // From the spec:
            //    a server MUST accept the absolute-form in requests, even though
            //    HTTP/1.1 clients will only send them in requests to proxies.

            var disableStringReuse = ServerOptions.DisableStringReuse;
            var previousValue      = _parsedRawTarget;

            if (disableStringReuse ||
                previousValue == null || previousValue.Length != target.Length ||
                !StringUtilities.BytesOrdinalEqualsStringAndAscii(previousValue, target))
            {
                // The previous string does not match what the bytes would convert to,
                // so we will need to generate a new string.
                RawTarget = _parsedRawTarget = target.GetAsciiStringNonNullCharacters();

                // Validation of absolute URIs is slow, but clients
                // should not be sending this form anyways, so perf optimization
                // not high priority

                if (!Uri.TryCreate(RawTarget, UriKind.Absolute, out var uri))
                {
                    ThrowRequestTargetRejected(target);
                }

                _absoluteRequestTarget = uri;
                Path = _parsedPath = uri.LocalPath;
                // don't use uri.Query because we need the unescaped version
                previousValue = _parsedQueryString;
                if (disableStringReuse ||
                    previousValue == null || previousValue.Length != query.Length ||
                    !StringUtilities.BytesOrdinalEqualsStringAndAscii(previousValue, query))
                {
                    // The previous string does not match what the bytes would convert to,
                    // so we will need to generate a new string.
                    QueryString = _parsedQueryString = query.GetAsciiStringNonNullCharacters();
                }
                else
                {
                    QueryString = _parsedQueryString;
                }
            }
            else
            {
                // As RawTarget is the same we can reuse the previous values.
                RawTarget   = _parsedRawTarget;
                Path        = _parsedPath;
                QueryString = _parsedQueryString;
            }
        }
예제 #4
0
        protected override void OnReset()
        {
            FastFeatureSet(typeof(IHttpUpgradeFeature), this);

            _requestTimedOut       = false;
            _requestTargetForm     = HttpRequestTarget.Unknown;
            _absoluteRequestTarget = null;
            _remainingRequestHeadersBytesAllowed = ServerOptions.Limits.MaxRequestHeadersTotalSize + 2;
            _requestCount++;
        }
예제 #5
0
        protected override void OnReset()
        {
            ResetHttp1Features();

            _requestTimedOut       = false;
            _requestTargetForm     = HttpRequestTarget.Unknown;
            _absoluteRequestTarget = null;
            _remainingRequestHeadersBytesAllowed = ServerOptions.Limits.MaxRequestHeadersTotalSize + 2;
            _requestCount++;

            MinResponseDataRate = ServerOptions.Limits.MinResponseDataRate;
        }
예제 #6
0
        private void OnAuthorityFormTarget(HttpMethod method, Span <byte> target)
        {
            _requestTargetForm = HttpRequestTarget.AuthorityForm;

            // This is not complete validation. It is just a quick scan for invalid characters
            // but doesn't check that the target fully matches the URI spec.
            if (HttpCharacters.ContainsInvalidAuthorityChar(target))
            {
                ThrowRequestTargetRejected(target);
            }

            // The authority-form of request-target is only used for CONNECT
            // requests (https://tools.ietf.org/html/rfc7231#section-4.3.6).
            if (method != HttpMethod.Connect)
            {
                BadHttpRequestException.Throw(RequestRejectionReason.ConnectMethodRequired);
            }

            // When making a CONNECT request to establish a tunnel through one or
            // more proxies, a client MUST send only the target URI's authority
            // component (excluding any userinfo and its "@" delimiter) as the
            // request-target.For example,
            //
            //  CONNECT www.example.com:80 HTTP/1.1
            //
            // Allowed characters in the 'host + port' section of authority.
            // See https://tools.ietf.org/html/rfc3986#section-3.2

            var previousValue = _parsedRawTarget;

            if (ServerOptions.DisableStringReuse ||
                previousValue == null || previousValue.Length != target.Length ||
                !StringUtilities.BytesOrdinalEqualsStringAndAscii(previousValue, target))
            {
                // The previous string does not match what the bytes would convert to,
                // so we will need to generate a new string.
                RawTarget = _parsedRawTarget = target.GetAsciiStringNonNullCharacters();
            }
            else
            {
                // Reuse previous value
                RawTarget = _parsedRawTarget;
            }

            Path        = string.Empty;
            QueryString = string.Empty;
            // Clear parsedData for path and queryString as we won't check it if we come via this path again,
            // an setting to null is fast as it doesn't need to use a GC write barrier.
            _parsedPath = _parsedQueryString = null;
        }
예제 #7
0
        private void OnAsteriskFormTarget(HttpMethod method)
        {
            _requestTargetForm = HttpRequestTarget.AsteriskForm;

            // The asterisk-form of request-target is only used for a server-wide
            // OPTIONS request (https://tools.ietf.org/html/rfc7231#section-4.3.7).
            if (method != HttpMethod.Options)
            {
                ThrowRequestRejected(RequestRejectionReason.OptionsMethodRequired);
            }

            RawTarget   = Asterisk;
            Path        = string.Empty;
            QueryString = string.Empty;
        }
예제 #8
0
        private void OnAsteriskFormTarget(HttpMethod method)
        {
            _requestTargetForm = HttpRequestTarget.AsteriskForm;

            // The asterisk-form of request-target is only used for a server-wide
            // OPTIONS request (https://tools.ietf.org/html/rfc7231#section-4.3.7).
            if (method != HttpMethod.Options)
            {
                BadHttpRequestException.Throw(RequestRejectionReason.OptionsMethodRequired);
            }

            RawTarget   = Asterisk;
            Path        = string.Empty;
            QueryString = string.Empty;
            // Clear parsedData as we won't check it if we come via this path again,
            // an setting to null is fast as it doesn't need to use a GC write barrier.
            _parsedRawTarget = _parsedPath = _parsedQueryString = null;
        }
예제 #9
0
        // Compare with Http2Stream.TryValidatePseudoHeaders
        private void OnOriginFormTarget(HttpMethod method, HttpVersion version, Span <byte> target, Span <byte> path, Span <byte> query, Span <byte> customMethod, bool pathEncoded)
        {
            Debug.Assert(target[0] == ByteForwardSlash, "Should only be called when path starts with /");

            _requestTargetForm = HttpRequestTarget.OriginForm;

            // URIs are always encoded/escaped to ASCII https://tools.ietf.org/html/rfc3986#page-11
            // Multibyte Internationalized Resource Identifiers (IRIs) are first converted to utf8;
            // then encoded/escaped to ASCII  https://www.ietf.org/rfc/rfc3987.txt "Mapping of IRIs to URIs"

            try
            {
                // Read raw target before mutating memory.
                RawTarget   = target.GetAsciiStringNonNullCharacters();
                QueryString = query.GetAsciiStringNonNullCharacters();
                Path        = PathNormalizer.DecodePath(path, pathEncoded, RawTarget, query.Length);
            }
            catch (InvalidOperationException)
            {
                ThrowRequestTargetRejected(target);
            }
        }
예제 #10
0
        public void HttpTokenValidationTest()
        {
            var privateKey  = TEST_RSA_PrivateKey_256.ReadAsRSAKey();
            var cert        = new X509Certificate2(Convert.FromBase64String(TEST_X509_PublicKey_2048));
            var securityKey = new RsaSecurityKey(privateKey)
            {
                KeyId = cert.GetSubjectKeyIdentifier()
            };
            var signingCredentials = new SigningCredentials(securityKey, SecurityAlgorithms.RsaSha256Signature);
            var payload            = @"{""amount"":123.9,""date"":""2019-06-21T12:05:40.111Z""}";
            var requestId          = "ed67e7c4-9985-45a9-8f1c-7ce7d9c007fe";
            var requestDate        = DateTime.UtcNow;
            var requestTarget      = new HttpRequestTarget("POST", "/payment");
            var token            = new HttpSignatureSecurityToken(signingCredentials, Encoding.UTF8.GetBytes(payload), requestId, requestDate, requestTarget);
            var digestHeader     = token.Digest.ToString();
            var signatureHeader  = token.Signature.ToString();
            var validationKey    = new X509SecurityKey(cert);
            var validatedToken   = new HttpSignatureSecurityToken(digestHeader, signatureHeader);
            var disgestIsValid   = validatedToken.Digest.Validate(Encoding.UTF8.GetBytes(payload));
            var signatureIsValid = validatedToken.Signature.Validate(validationKey, digestHeader, requestId, requestDate, requestTarget);

            Assert.True(disgestIsValid);
            Assert.True(signatureIsValid);
        }
예제 #11
0
        private void OnOriginFormTarget(HttpMethod method, HttpVersion version, Span <byte> target, Span <byte> path, Span <byte> query, Span <byte> customMethod, bool pathEncoded)
        {
            Debug.Assert(target[0] == ByteForwardSlash, "Should only be called when path starts with /");

            _requestTargetForm = HttpRequestTarget.OriginForm;

            // URIs are always encoded/escaped to ASCII https://tools.ietf.org/html/rfc3986#page-11
            // Multibyte Internationalized Resource Identifiers (IRIs) are first converted to utf8;
            // then encoded/escaped to ASCII  https://www.ietf.org/rfc/rfc3987.txt "Mapping of IRIs to URIs"
            string requestUrlPath = null;
            string rawTarget      = null;

            try
            {
                // Read raw target before mutating memory.
                rawTarget = target.GetAsciiStringNonNullCharacters();

                if (pathEncoded)
                {
                    // URI was encoded, unescape and then parse as UTF-8
                    // Disabling warning temporary
#pragma warning disable 618
                    var pathLength = UrlEncoder.Decode(path, path);
#pragma warning restore 618

                    // Removing dot segments must be done after unescaping. From RFC 3986:
                    //
                    // URI producing applications should percent-encode data octets that
                    // correspond to characters in the reserved set unless these characters
                    // are specifically allowed by the URI scheme to represent data in that
                    // component.  If a reserved character is found in a URI component and
                    // no delimiting role is known for that character, then it must be
                    // interpreted as representing the data octet corresponding to that
                    // character's encoding in US-ASCII.
                    //
                    // https://tools.ietf.org/html/rfc3986#section-2.2
                    pathLength = PathNormalizer.RemoveDotSegments(path.Slice(0, pathLength));

                    requestUrlPath = GetUtf8String(path.Slice(0, pathLength));
                }
                else
                {
                    var pathLength = PathNormalizer.RemoveDotSegments(path);

                    if (path.Length == pathLength && query.Length == 0)
                    {
                        // If no decoding was required, no dot segments were removed and
                        // there is no query, the request path is the same as the raw target
                        requestUrlPath = rawTarget;
                    }
                    else
                    {
                        requestUrlPath = path.Slice(0, pathLength).GetAsciiStringNonNullCharacters();
                    }
                }
            }
            catch (InvalidOperationException)
            {
                ThrowRequestTargetRejected(target);
            }

            QueryString = query.GetAsciiStringNonNullCharacters();
            RawTarget   = rawTarget;
            Path        = requestUrlPath;
        }
예제 #12
0
        // Compare with Http2Stream.TryValidatePseudoHeaders
        private void OnOriginFormTarget(HttpMethod method, HttpVersion version, Span <byte> target, Span <byte> path, Span <byte> query, Span <byte> customMethod, bool pathEncoded)
        {
            Debug.Assert(target[0] == ByteForwardSlash, "Should only be called when path starts with /");

            _requestTargetForm = HttpRequestTarget.OriginForm;

            if (target.Length == 1)
            {
                // If target.Length == 1 it can only be a forward slash (e.g. home page)
                // and we know RawTarget and Path are the same and QueryString is Empty
                RawTarget   = ForwardSlash;
                Path        = ForwardSlash;
                QueryString = string.Empty;
                // Clear parsedData as we won't check it if we come via this path again,
                // an setting to null is fast as it doesn't need to use a GC write barrier.
                _parsedRawTarget = _parsedPath = _parsedQueryString = null;
                return;
            }

            // URIs are always encoded/escaped to ASCII https://tools.ietf.org/html/rfc3986#page-11
            // Multibyte Internationalized Resource Identifiers (IRIs) are first converted to utf8;
            // then encoded/escaped to ASCII  https://www.ietf.org/rfc/rfc3987.txt "Mapping of IRIs to URIs"

            try
            {
                var disableStringReuse = ServerOptions.DisableStringReuse;
                // Read raw target before mutating memory.
                var previousValue = _parsedRawTarget;
                if (disableStringReuse ||
                    previousValue == null || previousValue.Length != target.Length ||
                    !StringUtilities.BytesOrdinalEqualsStringAndAscii(previousValue, target))
                {
                    // The previous string does not match what the bytes would convert to,
                    // so we will need to generate a new string.
                    RawTarget = _parsedRawTarget = target.GetAsciiStringNonNullCharacters();

                    previousValue = _parsedQueryString;
                    if (disableStringReuse ||
                        previousValue == null || previousValue.Length != query.Length ||
                        !StringUtilities.BytesOrdinalEqualsStringAndAscii(previousValue, query))
                    {
                        // The previous string does not match what the bytes would convert to,
                        // so we will need to generate a new string.
                        QueryString = _parsedQueryString = query.GetAsciiStringNonNullCharacters();
                    }
                    else
                    {
                        // Same as previous
                        QueryString = _parsedQueryString;
                    }

                    if (path.Length == 1)
                    {
                        // If path.Length == 1 it can only be a forward slash (e.g. home page)
                        Path = _parsedPath = ForwardSlash;
                    }
                    else
                    {
                        Path = _parsedPath = PathNormalizer.DecodePath(path, pathEncoded, RawTarget, query.Length);
                    }
                }
                else
                {
                    // As RawTarget is the same we can reuse the previous parsed values.
                    RawTarget   = _parsedRawTarget;
                    Path        = _parsedPath;
                    QueryString = _parsedQueryString;
                }
            }
            catch (InvalidOperationException)
            {
                ThrowRequestTargetRejected(target);
            }
        }