|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Text;
namespace System.Net.Http.Headers
{
public class ViaHeaderValue : ICloneable
{
private readonly string? _protocolName;
private readonly string _protocolVersion;
private readonly string _receivedBy;
private readonly string? _comment;
public string? ProtocolName => _protocolName;
public string ProtocolVersion => _protocolVersion;
public string ReceivedBy => _receivedBy;
public string? Comment => _comment;
private ViaHeaderValue(string protocolVersion, string receivedBy, string? protocolName, string? comment, bool _)
{
#if DEBUG
// This constructor should only be used with already validated values.
new ViaHeaderValue(protocolVersion, receivedBy, protocolName, comment);
#endif
_protocolVersion = protocolVersion;
_receivedBy = receivedBy;
_protocolName = protocolName;
_comment = comment;
}
public ViaHeaderValue(string protocolVersion, string receivedBy)
: this(protocolVersion, receivedBy, null, null)
{
}
public ViaHeaderValue(string protocolVersion, string receivedBy, string? protocolName)
: this(protocolVersion, receivedBy, protocolName, null)
{
}
public ViaHeaderValue(string protocolVersion, string receivedBy, string? protocolName, string? comment)
{
HeaderUtilities.CheckValidToken(protocolVersion);
CheckReceivedBy(receivedBy);
if (!string.IsNullOrEmpty(protocolName))
{
HeaderUtilities.CheckValidToken(protocolName);
_protocolName = protocolName;
}
if (!string.IsNullOrEmpty(comment))
{
HeaderUtilities.CheckValidComment(comment);
_comment = comment;
}
_protocolVersion = protocolVersion;
_receivedBy = receivedBy;
}
private ViaHeaderValue(ViaHeaderValue source)
{
Debug.Assert(source != null);
_protocolName = source._protocolName;
_protocolVersion = source._protocolVersion;
_receivedBy = source._receivedBy;
_comment = source._comment;
}
public override string ToString()
{
var sb = new ValueStringBuilder(stackalloc char[256]);
if (!string.IsNullOrEmpty(_protocolName))
{
sb.Append(_protocolName);
sb.Append('/');
}
sb.Append(_protocolVersion);
sb.Append(' ');
sb.Append(_receivedBy);
if (!string.IsNullOrEmpty(_comment))
{
sb.Append(' ');
sb.Append(_comment);
}
return sb.ToString();
}
public override bool Equals([NotNullWhen(true)] object? obj) =>
obj is ViaHeaderValue other &&
// Note that for token and host case-insensitive comparison is used. Comments are compared using case-
// sensitive comparison.
string.Equals(_protocolVersion, other._protocolVersion, StringComparison.OrdinalIgnoreCase) &&
string.Equals(_receivedBy, other._receivedBy, StringComparison.OrdinalIgnoreCase) &&
string.Equals(_protocolName, other._protocolName, StringComparison.OrdinalIgnoreCase) &&
string.Equals(_comment, other._comment, StringComparison.Ordinal);
public override int GetHashCode() =>
HashCode.Combine(
StringComparer.OrdinalIgnoreCase.GetHashCode(_protocolVersion),
StringComparer.OrdinalIgnoreCase.GetHashCode(_receivedBy),
_protocolName is null ? 0 : StringComparer.OrdinalIgnoreCase.GetHashCode(_protocolName),
_comment);
public static ViaHeaderValue Parse(string input)
{
int index = 0;
return (ViaHeaderValue)GenericHeaderParser.SingleValueViaParser.ParseValue(input, null, ref index);
}
public static bool TryParse([NotNullWhen(true)] string? input, [NotNullWhen(true)] out ViaHeaderValue? parsedValue)
{
int index = 0;
parsedValue = null;
if (GenericHeaderParser.SingleValueViaParser.TryParseValue(input, null, ref index, out object? output))
{
parsedValue = (ViaHeaderValue)output!;
return true;
}
return false;
}
internal static int GetViaLength(string? input, int startIndex, out object? parsedValue)
{
Debug.Assert(startIndex >= 0);
parsedValue = null;
if (string.IsNullOrEmpty(input) || (startIndex >= input.Length))
{
return 0;
}
// Read <protocolName> and <protocolVersion> in '[<protocolName>/]<protocolVersion> <receivedBy> [<comment>]'
int current = GetProtocolEndIndex(input, startIndex, out string? protocolName, out string? protocolVersion);
// If we reached the end of the string after reading protocolName/Version we return (we expect at least
// <receivedBy> to follow). If reading protocolName/Version read 0 bytes, we return.
if ((current == 0) || (current == input.Length))
{
return 0;
}
Debug.Assert(protocolVersion != null);
// Read <receivedBy> in '[<protocolName>/]<protocolVersion> <receivedBy> [<comment>]'
int receivedByLength = HttpRuleParser.GetHostLength(input, current, true);
if (receivedByLength == 0)
{
return 0;
}
string receivedBy = input.Substring(current, receivedByLength);
current += receivedByLength;
current += HttpRuleParser.GetWhitespaceLength(input, current);
string? comment = null;
if ((current < input.Length) && (input[current] == '('))
{
// We have a <comment> in '[<protocolName>/]<protocolVersion> <receivedBy> [<comment>]'
if (HttpRuleParser.GetCommentLength(input, current, out int commentLength) != HttpParseResult.Parsed)
{
return 0; // We found a '(' character but it wasn't a valid comment. Abort.
}
comment = input.Substring(current, commentLength);
current += commentLength;
current += HttpRuleParser.GetWhitespaceLength(input, current);
}
parsedValue = new ViaHeaderValue(protocolVersion, receivedBy, protocolName, comment, false);
return current - startIndex;
}
private static int GetProtocolEndIndex(string input, int startIndex, out string? protocolName,
out string? protocolVersion)
{
// We have a string of the form '[<protocolName>/]<protocolVersion> <receivedBy> [<comment>]'. The first
// token may either be the protocol name or protocol version. We'll only find out after reading the token
// and by looking at the following character: If it is a '/' we just parsed the protocol name, otherwise
// the protocol version.
protocolName = null;
protocolVersion = null;
int current = startIndex;
int protocolVersionOrNameLength = HttpRuleParser.GetTokenLength(input, current);
if (protocolVersionOrNameLength == 0)
{
return 0;
}
current = startIndex + protocolVersionOrNameLength;
int whitespaceLength = HttpRuleParser.GetWhitespaceLength(input, current);
current += whitespaceLength;
if (current == input.Length)
{
return 0;
}
if (input[current] == '/')
{
// We parsed the protocol name
protocolName = input.Substring(startIndex, protocolVersionOrNameLength);
current++; // skip the '/' delimiter
current += HttpRuleParser.GetWhitespaceLength(input, current);
protocolVersionOrNameLength = HttpRuleParser.GetTokenLength(input, current);
if (protocolVersionOrNameLength == 0)
{
return 0; // We have a string "<token>/" followed by non-token chars. This is invalid.
}
protocolVersion = input.Substring(current, protocolVersionOrNameLength);
current += protocolVersionOrNameLength;
whitespaceLength = HttpRuleParser.GetWhitespaceLength(input, current);
current += whitespaceLength;
}
else
{
protocolVersion = input.Substring(startIndex, protocolVersionOrNameLength);
}
if (whitespaceLength == 0)
{
return 0; // We were able to parse [<protocolName>/]<protocolVersion> but it wasn't followed by a WS
}
return current;
}
object ICloneable.Clone()
{
return new ViaHeaderValue(this);
}
private static void CheckReceivedBy(string receivedBy)
{
ArgumentException.ThrowIfNullOrEmpty(receivedBy);
// 'receivedBy' can either be a host or a token. Since a token is a valid host, we only verify if the value
// is a valid host.;
if (HttpRuleParser.GetHostLength(receivedBy, 0, true) != receivedBy.Length)
{
throw new FormatException(SR.Format(System.Globalization.CultureInfo.InvariantCulture, SR.net_http_headers_invalid_value, receivedBy));
}
}
}
}
|