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; }
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(); }
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; } }
protected override void OnReset() { FastFeatureSet(typeof(IHttpUpgradeFeature), this); _requestTimedOut = false; _requestTargetForm = HttpRequestTarget.Unknown; _absoluteRequestTarget = null; _remainingRequestHeadersBytesAllowed = ServerOptions.Limits.MaxRequestHeadersTotalSize + 2; _requestCount++; }
protected override void OnReset() { ResetHttp1Features(); _requestTimedOut = false; _requestTargetForm = HttpRequestTarget.Unknown; _absoluteRequestTarget = null; _remainingRequestHeadersBytesAllowed = ServerOptions.Limits.MaxRequestHeadersTotalSize + 2; _requestCount++; MinResponseDataRate = ServerOptions.Limits.MinResponseDataRate; }
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; }
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; }
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; }
// 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); } }
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); }
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; }
// 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); } }