| | | 1 | | // Licensed to the .NET Foundation under one or more agreements. |
| | | 2 | | // The .NET Foundation licenses this file to you under the MIT license. |
| | | 3 | | |
| | | 4 | | using System.Collections.Generic; |
| | | 5 | | using System.IO; |
| | | 6 | | using System.Net.Http.Headers; |
| | | 7 | | using System.Text; |
| | | 8 | | using System.Threading; |
| | | 9 | | using System.Threading.Tasks; |
| | | 10 | | |
| | | 11 | | namespace System.Net.Http |
| | | 12 | | { |
| | | 13 | | public class FormUrlEncodedContent : ByteArrayContent |
| | | 14 | | { |
| | | 15 | | public FormUrlEncodedContent( |
| | | 16 | | IEnumerable<KeyValuePair< |
| | | 17 | | #nullable disable |
| | | 18 | | string, string |
| | | 19 | | #nullable restore |
| | | 20 | | >> nameValueCollection) |
| | 0 | 21 | | : base(GetContentByteArray(nameValueCollection)) |
| | 0 | 22 | | { |
| | 0 | 23 | | Headers.ContentType = new MediaTypeHeaderValue("application/x-www-form-urlencoded"); |
| | 0 | 24 | | } |
| | | 25 | | |
| | | 26 | | private static byte[] GetContentByteArray(IEnumerable<KeyValuePair<string?, string?>> nameValueCollection) |
| | 0 | 27 | | { |
| | 0 | 28 | | ArgumentNullException.ThrowIfNull(nameValueCollection); |
| | | 29 | | |
| | | 30 | | // Encode and concatenate data |
| | 0 | 31 | | var builder = new ValueStringBuilder(stackalloc char[256]); |
| | | 32 | | |
| | 0 | 33 | | foreach (KeyValuePair<string?, string?> pair in nameValueCollection) |
| | 0 | 34 | | { |
| | 0 | 35 | | if (builder.Length > 0) |
| | 0 | 36 | | { |
| | 0 | 37 | | builder.Append('&'); |
| | 0 | 38 | | } |
| | | 39 | | |
| | 0 | 40 | | Encode(ref builder, pair.Key); |
| | 0 | 41 | | builder.Append('='); |
| | 0 | 42 | | Encode(ref builder, pair.Value); |
| | 0 | 43 | | } |
| | | 44 | | |
| | | 45 | | // EscapeDataString will always return an ASCII string and DefaultHttpEncoding is Latin1, |
| | | 46 | | // so we know the output byte size will be the same as the builder length. |
| | 0 | 47 | | byte[] bytes = new byte[builder.Length]; |
| | 0 | 48 | | HttpRuleParser.DefaultHttpEncoding.GetBytes(builder.AsSpan(), bytes); |
| | 0 | 49 | | builder.Dispose(); |
| | 0 | 50 | | return bytes; |
| | 0 | 51 | | } |
| | | 52 | | |
| | | 53 | | private static void Encode(ref ValueStringBuilder builder, string? data) |
| | 0 | 54 | | { |
| | 0 | 55 | | if (!string.IsNullOrEmpty(data)) |
| | 0 | 56 | | { |
| | | 57 | | int charsWritten; |
| | 0 | 58 | | while (!Uri.TryEscapeDataString(data, builder.RawChars.Slice(builder.Length), out charsWritten)) |
| | 0 | 59 | | { |
| | 0 | 60 | | builder.EnsureCapacity(builder.Capacity + 1); |
| | 0 | 61 | | } |
| | | 62 | | |
| | | 63 | | // Escape spaces as '+'. |
| | 0 | 64 | | if (data.Contains(' ')) |
| | 0 | 65 | | { |
| | 0 | 66 | | ReadOnlySpan<char> escapedChars = builder.RawChars.Slice(builder.Length, charsWritten); |
| | | 67 | | |
| | 0 | 68 | | while (true) |
| | 0 | 69 | | { |
| | 0 | 70 | | int indexOfEscapedSpace = escapedChars.IndexOf("%20", StringComparison.Ordinal); |
| | 0 | 71 | | if (indexOfEscapedSpace < 0) |
| | 0 | 72 | | { |
| | 0 | 73 | | builder.Append(escapedChars); |
| | 0 | 74 | | break; |
| | | 75 | | } |
| | | 76 | | |
| | 0 | 77 | | builder.Append(escapedChars.Slice(0, indexOfEscapedSpace)); |
| | 0 | 78 | | builder.Append('+'); |
| | 0 | 79 | | escapedChars = escapedChars.Slice(indexOfEscapedSpace + 3); // Skip "%20" |
| | 0 | 80 | | } |
| | 0 | 81 | | } |
| | | 82 | | else |
| | 0 | 83 | | { |
| | 0 | 84 | | builder.Length += charsWritten; |
| | 0 | 85 | | } |
| | 0 | 86 | | } |
| | 0 | 87 | | } |
| | | 88 | | |
| | | 89 | | protected override Task SerializeToStreamAsync(Stream stream, TransportContext? context, CancellationToken cance |
| | | 90 | | // Only skip the original protected virtual SerializeToStreamAsync if this |
| | | 91 | | // isn't a derived type that may have overridden the behavior. |
| | 0 | 92 | | GetType() == typeof(FormUrlEncodedContent) ? SerializeToStreamAsyncCore(stream, cancellationToken) : |
| | 0 | 93 | | base.SerializeToStreamAsync(stream, context, cancellationToken); |
| | | 94 | | |
| | | 95 | | internal override Stream? TryCreateContentReadStream() => |
| | 0 | 96 | | GetType() == typeof(FormUrlEncodedContent) ? CreateMemoryStreamForByteArray() : // type check ensures we use |
| | 0 | 97 | | null; |
| | | 98 | | } |
| | | 99 | | } |