Skip to content

Commit

Permalink
Don't allow CRLF in headers (#2258)
Browse files Browse the repository at this point in the history
  • Loading branch information
alexeyzimarev committed Aug 29, 2024
1 parent 7b7950b commit 0fba5e7
Show file tree
Hide file tree
Showing 3 changed files with 65 additions and 34 deletions.
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

0 comments on commit 0fba5e7

Please sign in to comment.