using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
-using System.IO;
using System.Text;
namespace System.Net.Http.Headers
}
// Returns input for decoding failures, as the content might not be encoded.
- private string EncodeAndQuoteMime(string input)
+ private static string EncodeAndQuoteMime(string input)
{
string result = input;
bool needsQuotes = false;
throw new ArgumentException(SR.Format(CultureInfo.InvariantCulture,
SR.net_http_headers_invalid_value, input));
}
- else if (RequiresEncoding(result))
+ else if (HeaderUtilities.ContainsNonAscii(result))
{
needsQuotes = true; // Encoded data must always be quoted, the equals signs are invalid in tokens.
result = EncodeMime(result); // =?utf-8?B?asdfasdfaesdf?=
}
// Returns true if the value starts and ends with a quote.
- private bool IsQuoted(ReadOnlySpan<char> value)
+ private static bool IsQuoted(ReadOnlySpan<char> value)
{
return
value.Length > 1 &&
value[value.Length - 1] == '"';
}
- // tspecials are required to be in a quoted string. Only non-ascii needs to be encoded.
- private bool RequiresEncoding(string input)
- {
- Debug.Assert(input != null);
-
- foreach (char c in input)
- {
- if ((int)c > 0x7f)
- {
- return true;
- }
- }
- return false;
- }
-
// Encode using MIME encoding.
- private string EncodeMime(string input)
+ private static string EncodeMime(string input)
{
byte[] buffer = Encoding.UTF8.GetBytes(input);
string encodedName = Convert.ToBase64String(buffer);
}
// Attempt to decode MIME encoded strings.
- private bool TryDecodeMime(string input, out string output)
+ private static bool TryDecodeMime(string input, out string output)
{
Debug.Assert(input != null);
// Attempt to decode using RFC 5987 encoding.
// encoding'language'my%20string
- private bool TryDecode5987(string input, out string output)
+ private static bool TryDecode5987(string input, out string output)
{
output = null;
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
+using System.Buffers;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
}
}
- // Encode a string using RFC 5987 encoding.
- // encoding'lang'PercentEncodedSpecials
- internal static string Encode5987(string input)
+ internal static bool ContainsNonAscii(string input)
{
- string output;
- IsInputEncoded5987(input, out output);
+ Debug.Assert(input != null);
- return output;
+ foreach (char c in input)
+ {
+ if ((int)c > 0x7f)
+ {
+ return true;
+ }
+ }
+ return false;
}
- internal static bool IsInputEncoded5987(string input, out string output)
+ // Encode a string using RFC 5987 encoding.
+ // encoding'lang'PercentEncodedSpecials
+ internal static string Encode5987(string input)
{
// Encode a string using RFC 5987 encoding.
// encoding'lang'PercentEncodedSpecials
- bool wasEncoded = false;
StringBuilder builder = StringBuilderCache.Acquire();
+ byte[] utf8bytes = ArrayPool<byte>.Shared.Rent(Encoding.UTF8.GetMaxByteCount(input.Length));
+ int utf8length = Encoding.UTF8.GetBytes(input, 0, input.Length, utf8bytes, 0);
+
builder.Append("utf-8\'\'");
- foreach (char c in input)
+ for (int i = 0; i < utf8length; i++)
{
+ byte utf8byte = utf8bytes[i];
+
// attr-char = ALPHA / DIGIT / "!" / "#" / "$" / "&" / "+" / "-" / "." / "^" / "_" / "`" / "|" / "~"
// ; token except ( "*" / "'" / "%" )
- if (c > 0x7F) // Encodes as multiple utf-8 bytes
+ if (utf8byte > 0x7F) // Encodes as multiple utf-8 bytes
{
- byte[] bytes = Encoding.UTF8.GetBytes(c.ToString());
- foreach (byte b in bytes)
- {
- AddHexEscaped((char)b, builder);
- wasEncoded = true;
- }
+ AddHexEscaped(utf8byte, builder);
}
- else if (!HttpRuleParser.IsTokenChar(c) || c == '*' || c == '\'' || c == '%')
+ else if (!HttpRuleParser.IsTokenChar((char)utf8byte) || utf8byte == '*' || utf8byte == '\'' || utf8byte == '%')
{
// ASCII - Only one encoded byte.
- AddHexEscaped(c, builder);
- wasEncoded = true;
+ AddHexEscaped(utf8byte, builder);
}
else
{
- builder.Append(c);
+ builder.Append((char)utf8byte);
}
}
- output = StringBuilderCache.GetStringAndRelease(builder);
- return wasEncoded;
+ Array.Clear(utf8bytes, 0, utf8length);
+ ArrayPool<byte>.Shared.Return(utf8bytes);
+
+ return StringBuilderCache.GetStringAndRelease(builder);
}
/// <summary>Transforms an ASCII character into its hexadecimal representation, adding the characters to a StringBuilder.</summary>
- private static void AddHexEscaped(char c, StringBuilder destination)
+ private static void AddHexEscaped(byte c, StringBuilder destination)
{
Debug.Assert(destination != null);
- Debug.Assert(c <= 0xFF);
destination.Append('%');
destination.Append(s_hexUpperChars[(c & 0xf0) >> 4]);
}
else
{
- string usernameStar;
- if (HeaderUtilities.IsInputEncoded5987(credential.UserName, out usernameStar))
+ if (HeaderUtilities.ContainsNonAscii(credential.UserName))
{
+ string usernameStar = HeaderUtilities.Encode5987(credential.UserName);
sb.AppendKeyValue(UsernameStar, usernameStar, includeQuotes: false);
}
else
internal static class StringBuilderExtensions
{
+ // Characters that require escaping in quoted string
+ private static readonly char[] SpecialCharacters = new[] { '"', '\\' };
+
public static void AppendKeyValue(this StringBuilder sb, string key, string value, bool includeQuotes = true, bool includeComma = true)
{
sb.Append(key);
if (includeQuotes)
{
sb.Append('"');
+ int lastSpecialIndex = 0;
+ int specialIndex;
+ while (true)
+ {
+ specialIndex = value.IndexOfAny(SpecialCharacters, lastSpecialIndex);
+ if (specialIndex >= 0)
+ {
+ sb.Append(value, lastSpecialIndex, specialIndex - lastSpecialIndex);
+ sb.Append('\\');
+ sb.Append(value[specialIndex]);
+ lastSpecialIndex = specialIndex + 1;
+ }
+ else
+ {
+ sb.Append(value, lastSpecialIndex, value.Length - lastSpecialIndex);
+ break;
+ }
+ }
+ sb.Append('"');
}
-
- sb.Append(value);
- if (includeQuotes)
+ else
{
- sb.Append('"');
+ sb.Append(value);
}
if (includeComma)
Assert.Equal(expectedResult, parameter != null);
}
+
+ [Theory]
+ [InlineData("test", "username=\"test\"")]
+ [InlineData("test@example.org", "username=\"test@example.org\"")]
+ [InlineData("test\"example.org", "username=\"test\\\"example.org\"")]
+ [InlineData("t\u00E6st", "username*=utf-8''t%C3%A6st")]
+ [InlineData("\uD834\uDD1E", "username*=utf-8''%F0%9D%84%9E")]
+ public async void DigestResponse_UserName_Encoding(string username, string encodedUserName)
+ {
+ NetworkCredential credential = new NetworkCredential(username, "bar");
+ AuthenticationHelper.DigestResponse digestResponse = new AuthenticationHelper.DigestResponse("realm=\"NetCore\", nonce=\"qMRqWgAAAAAQMjIABgAAAFwEiEwAAAAA\"");
+ HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, "http://microsoft.com/");
+ string parameter = await AuthenticationHelper.GetDigestTokenForCredential(credential, request, digestResponse).ConfigureAwait(false);
+ Assert.StartsWith(encodedUserName, parameter);
+ }
}
}