Skip to content

Commit 0fba5e7

Browse files
Don't allow CRLF in headers (#2258)
1 parent 7b7950b commit 0fba5e7

File tree

3 files changed

+65
-34
lines changed

3 files changed

+65
-34
lines changed

src/RestSharp/Parameters/HeaderParameter.cs

+55-6
Original file line numberDiff line numberDiff line change
@@ -13,22 +13,71 @@
1313
// limitations under the License.
1414
//
1515

16+
using System.Text;
17+
using System.Text.RegularExpressions;
18+
1619
namespace RestSharp;
1720

18-
public record HeaderParameter : Parameter {
21+
public partial record HeaderParameter : Parameter {
1922
/// <summary>
2023
/// Instantiates a header parameter
2124
/// </summary>
22-
/// <param name="name">Parameter name</param>
23-
/// <param name="value">Parameter value</param>
24-
public HeaderParameter(string name, string value)
25+
/// <param name="name">Header name</param>
26+
/// <param name="value">Header value</param>
27+
/// <param name="encode">Set to true to encode header value according to RFC 2047. Default is false.</param>
28+
public HeaderParameter(string name, string value, bool encode = false)
2529
: base(
26-
Ensure.NotEmptyString(name, nameof(name)),
27-
Ensure.NotNull(value, nameof(value)),
30+
EnsureValidHeaderString(Ensure.NotEmptyString(name, nameof(name)), "name"),
31+
EnsureValidHeaderValue(name, value, encode),
2832
ParameterType.HttpHeader,
2933
false
3034
) { }
3135

3236
public new string Name => base.Name!;
3337
public new string Value => (string)base.Value!;
38+
39+
static string EnsureValidHeaderValue(string name, string value, bool encode) {
40+
CheckAndThrowsForInvalidHost(name, value);
41+
42+
return EnsureValidHeaderString(GetValue(Ensure.NotNull(value, nameof(value)), encode), "value");
43+
}
44+
45+
static string EnsureValidHeaderString(string value, string type)
46+
=> !IsInvalidHeaderString(value) ? value : throw new ArgumentException($"Invalid character found in header {type}: {value}");
47+
48+
static string GetValue(string value, bool encode) => encode ? GetBase64EncodedHeaderValue(value) : value;
49+
50+
static string GetBase64EncodedHeaderValue(string value) => $"=?UTF-8?B?{Convert.ToBase64String(Encoding.UTF8.GetBytes(value))}?=";
51+
52+
static bool IsInvalidHeaderString(string stringValue) {
53+
// ReSharper disable once ForCanBeConvertedToForeach
54+
for (var i = 0; i < stringValue.Length; i++) {
55+
switch (stringValue[i]) {
56+
case '\t':
57+
case '\r':
58+
case '\n':
59+
return true;
60+
}
61+
}
62+
63+
return false;
64+
}
65+
66+
static readonly Regex PortSplitRegex = PartSplit();
67+
68+
static void CheckAndThrowsForInvalidHost(string name, string value) {
69+
if (name == KnownHeaders.Host && InvalidHost(value))
70+
throw new ArgumentException("The specified value is not a valid Host header string.", nameof(value));
71+
72+
return;
73+
74+
static bool InvalidHost(string host) => Uri.CheckHostName(PortSplitRegex.Split(host)[0]) == UriHostNameType.Unknown;
75+
}
76+
77+
#if NET7_0_OR_GREATER
78+
[GeneratedRegex(@":\d+")]
79+
private static partial Regex PartSplit();
80+
#else
81+
static Regex PartSplit() => new(@":\d+");
82+
#endif
3483
}

src/RestSharp/Request/RestRequestExtensions.Headers.cs

+4-28
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,6 @@
1212
// See the License for the specific language governing permissions and
1313
// limitations under the License.
1414

15-
using System.Text.RegularExpressions;
16-
1715
namespace RestSharp;
1816

1917
public static partial class RestRequestExtensions {
@@ -39,10 +37,8 @@ public static RestRequest AddHeader(this RestRequest request, string name, strin
3937
/// <param name="name">Header name</param>
4038
/// <param name="value">Header value</param>
4139
/// <returns></returns>
42-
public static RestRequest AddHeader(this RestRequest request, string name, string value) {
43-
CheckAndThrowsForInvalidHost(name, value);
44-
return request.AddParameter(new HeaderParameter(name, value));
45-
}
40+
public static RestRequest AddHeader(this RestRequest request, string name, string value)
41+
=> request.AddParameter(new HeaderParameter(name, value));
4642

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

7064
/// <summary>
7165
/// Adds or updates the request header. RestSharp will try to separate request and content headers when calling the resource.
@@ -121,22 +115,4 @@ static void CheckAndThrowsDuplicateKeys(ICollection<KeyValuePair<string, string>
121115
throw new ArgumentException($"Duplicate header names exist: {string.Join(", ", duplicateKeys)}");
122116
}
123117
}
124-
125-
static readonly Regex PortSplitRegex = PartSplit();
126-
127-
static void CheckAndThrowsForInvalidHost(string name, string value) {
128-
if (name == KnownHeaders.Host && InvalidHost(value))
129-
throw new ArgumentException("The specified value is not a valid Host header string.", nameof(value));
130-
131-
return;
132-
133-
static bool InvalidHost(string host) => Uri.CheckHostName(PortSplitRegex.Split(host)[0]) == UriHostNameType.Unknown;
134-
}
135-
136-
#if NET7_0_OR_GREATER
137-
[GeneratedRegex(@":\d+")]
138-
private static partial Regex PartSplit();
139-
#else
140-
static Regex PartSplit() => new(@":\d+");
141-
#endif
142118
}

test/RestSharp.Tests/RequestHeaderTests.cs

+6
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,12 @@ public void Should_not_allow_empty_header_name() {
174174
var request = new RestRequest();
175175
Assert.Throws<ArgumentException>("name", () => request.AddHeader("", "value"));
176176
}
177+
178+
[Fact]
179+
public void Should_not_allow_CRLF_in_header_value() {
180+
var request = new RestRequest();
181+
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"));
182+
}
177183

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

0 commit comments

Comments
 (0)