private void OnAbsoluteFormTarget(Span <byte> target, Span <byte> query) { _requestTargetForm = ProtoRequestTarget.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() { ResetProto1Features(); _requestTimedOut = false; _requestTargetForm = ProtoRequestTarget.Unknown; _absoluteRequestTarget = null; _remainingRequestHeadersBytesAllowed = ServerOptions.Limits.MaxRequestHeadersTotalSize + 2; _requestCount++; MinRequestBodyDataRate = ServerOptions.Limits.MinRequestBodyDataRate; MinResponseDataRate = ServerOptions.Limits.MinResponseDataRate; }
private void OnAuthorityFormTarget(ProtoMethod method, Span <byte> target) { _requestTargetForm = ProtoRequestTarget.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 (ProtoCharacters.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 != ProtoMethod.Connect) { BadProtoRequestException.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(ProtoMethod method) { _requestTargetForm = ProtoRequestTarget.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 != ProtoMethod.Options) { BadProtoRequestException.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 Proto2Stream.TryValidatePseudoHeaders private void OnOriginFormTarget(ProtoMethod method, ProtoVersion 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 = ProtoRequestTarget.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); } }