diff --git a/src/RestSharp/Parameters/HeaderParameter.cs b/src/RestSharp/Parameters/HeaderParameter.cs
index 1607eaeda..7dce5df92 100644
--- a/src/RestSharp/Parameters/HeaderParameter.cs
+++ b/src/RestSharp/Parameters/HeaderParameter.cs
@@ -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 {
///
/// Instantiates a header parameter
///
- /// Parameter name
- /// Parameter value
- public HeaderParameter(string name, string value)
+ /// Header name
+ /// Header value
+ /// Set to true to encode header value according to RFC 2047. Default is false.
+ 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
}
\ No newline at end of file
diff --git a/src/RestSharp/Request/RestRequestExtensions.Headers.cs b/src/RestSharp/Request/RestRequestExtensions.Headers.cs
index 8091a1a50..d3e6b7815 100644
--- a/src/RestSharp/Request/RestRequestExtensions.Headers.cs
+++ b/src/RestSharp/Request/RestRequestExtensions.Headers.cs
@@ -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 {
@@ -39,10 +37,8 @@ public static RestRequest AddHeader(this RestRequest request, string name, strin
/// Header name
/// Header value
///
- 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));
///
/// 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(this RestRequest request, string name, T
/// Header name
/// Header value
///
- 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));
///
/// 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
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
}
\ No newline at end of file
diff --git a/test/RestSharp.Tests/RequestHeaderTests.cs b/test/RestSharp.Tests/RequestHeaderTests.cs
index 6fa6c8215..bf8bc4c4b 100644
--- a/test/RestSharp.Tests/RequestHeaderTests.cs
+++ b/test/RestSharp.Tests/RequestHeaderTests.cs
@@ -174,6 +174,12 @@ public void Should_not_allow_empty_header_name() {
var request = new RestRequest();
Assert.Throws("name", () => request.AddHeader("", "value"));
}
+
+ [Fact]
+ public void Should_not_allow_CRLF_in_header_value() {
+ var request = new RestRequest();
+ Assert.Throws(() => 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();