Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Don't allow CRLF in headers #2258

Merged
merged 1 commit into from
Aug 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 55 additions & 6 deletions src/RestSharp/Parameters/HeaderParameter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,22 +13,71 @@
// limitations under the License.
//

using System.Text;
using System.Text.RegularExpressions;

namespace RestSharp;

public record HeaderParameter : Parameter {
public partial record HeaderParameter : Parameter {
/// <summary>
/// Instantiates a header parameter
/// </summary>
/// <param name="name">Parameter name</param>
/// <param name="value">Parameter value</param>
public HeaderParameter(string name, string value)
/// <param name="name">Header name</param>
/// <param name="value">Header value</param>
/// <param name="encode">Set to true to encode header value according to RFC 2047. Default is false.</param>
public HeaderParameter(string name, string value, bool encode = false)
: base(
Ensure.NotEmptyString(name, nameof(name)),
Ensure.NotNull(value, nameof(value)),
EnsureValidHeaderString(Ensure.NotEmptyString(name, nameof(name)), "name"),
EnsureValidHeaderValue(name, value, encode),
ParameterType.HttpHeader,
false
) { }

public new string Name => base.Name!;
public new string Value => (string)base.Value!;

static string EnsureValidHeaderValue(string name, string value, bool encode) {
CheckAndThrowsForInvalidHost(name, value);

return EnsureValidHeaderString(GetValue(Ensure.NotNull(value, nameof(value)), encode), "value");
}

static string EnsureValidHeaderString(string value, string type)
=> !IsInvalidHeaderString(value) ? value : throw new ArgumentException($"Invalid character found in header {type}: {value}");

static string GetValue(string value, bool encode) => encode ? GetBase64EncodedHeaderValue(value) : value;

static string GetBase64EncodedHeaderValue(string value) => $"=?UTF-8?B?{Convert.ToBase64String(Encoding.UTF8.GetBytes(value))}?=";

static bool IsInvalidHeaderString(string stringValue) {
// ReSharper disable once ForCanBeConvertedToForeach
for (var i = 0; i < stringValue.Length; i++) {
switch (stringValue[i]) {
case '\t':
case '\r':
case '\n':
return true;
}
}

return false;
}

static readonly Regex PortSplitRegex = PartSplit();

static void CheckAndThrowsForInvalidHost(string name, string value) {
if (name == KnownHeaders.Host && InvalidHost(value))
throw new ArgumentException("The specified value is not a valid Host header string.", nameof(value));

return;

static bool InvalidHost(string host) => Uri.CheckHostName(PortSplitRegex.Split(host)[0]) == UriHostNameType.Unknown;
}

#if NET7_0_OR_GREATER
[GeneratedRegex(@":\d+")]
private static partial Regex PartSplit();
#else
static Regex PartSplit() => new(@":\d+");
#endif
}
32 changes: 4 additions & 28 deletions src/RestSharp/Request/RestRequestExtensions.Headers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,6 @@
// See the License for the specific language governing permissions and
// limitations under the License.

using System.Text.RegularExpressions;

namespace RestSharp;

public static partial class RestRequestExtensions {
Expand All @@ -39,10 +37,8 @@ public static RestRequest AddHeader(this RestRequest request, string name, strin
/// <param name="name">Header name</param>
/// <param name="value">Header value</param>
/// <returns></returns>
public static RestRequest AddHeader(this RestRequest request, string name, string value) {
CheckAndThrowsForInvalidHost(name, value);
return request.AddParameter(new HeaderParameter(name, value));
}
public static RestRequest AddHeader(this RestRequest request, string name, string value)
=> request.AddParameter(new HeaderParameter(name, value));

/// <summary>
/// Adds a header to the request. RestSharp will try to separate request and content headers when calling the resource.
Expand All @@ -62,10 +58,8 @@ public static RestRequest AddHeader<T>(this RestRequest request, string name, T
/// <param name="name">Header name</param>
/// <param name="value">Header value</param>
/// <returns></returns>
public static RestRequest AddOrUpdateHeader(this RestRequest request, string name, string value) {
CheckAndThrowsForInvalidHost(name, value);
return request.AddOrUpdateParameter(new HeaderParameter(name, value));
}
public static RestRequest AddOrUpdateHeader(this RestRequest request, string name, string value)
=> request.AddOrUpdateParameter(new HeaderParameter(name, value));

/// <summary>
/// Adds or updates the request header. RestSharp will try to separate request and content headers when calling the resource.
Expand Down Expand Up @@ -121,22 +115,4 @@ static void CheckAndThrowsDuplicateKeys(ICollection<KeyValuePair<string, string>
throw new ArgumentException($"Duplicate header names exist: {string.Join(", ", duplicateKeys)}");
}
}

static readonly Regex PortSplitRegex = PartSplit();

static void CheckAndThrowsForInvalidHost(string name, string value) {
if (name == KnownHeaders.Host && InvalidHost(value))
throw new ArgumentException("The specified value is not a valid Host header string.", nameof(value));

return;

static bool InvalidHost(string host) => Uri.CheckHostName(PortSplitRegex.Split(host)[0]) == UriHostNameType.Unknown;
}

#if NET7_0_OR_GREATER
[GeneratedRegex(@":\d+")]
private static partial Regex PartSplit();
#else
static Regex PartSplit() => new(@":\d+");
#endif
}
6 changes: 6 additions & 0 deletions test/RestSharp.Tests/RequestHeaderTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,12 @@ public void Should_not_allow_empty_header_name() {
var request = new RestRequest();
Assert.Throws<ArgumentException>("name", () => request.AddHeader("", "value"));
}

[Fact]
public void Should_not_allow_CRLF_in_header_value() {
var request = new RestRequest();
Assert.Throws<ArgumentException>(() => request.AddHeader("name", "test\r\nUser-Agent: injected header!\r\n\r\nGET /smuggled HTTP/1.1\r\nHost: insert.some.site.here"));
}

static Parameter[] GetHeaders(RestRequest request) => request.Parameters.Where(x => x.Type == ParameterType.HttpHeader).ToArray();

Expand Down
Loading