openai/openai-dotnet

Public

mirrored from https://github.com/openai/openai-dotnetAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
OpenAI_2.0.0-beta.1

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

src/Utility/MultipartFormDataBinaryContent.cs

184lines · modeblame

9f9f2936Jose Arriaga Maldonado2 years ago1using System;
2using System.ClientModel;
3using System.Diagnostics;
4using System.Globalization;
5using System.IO;
6using System.Net.Http;
7using System.Net.Http.Headers;
8using System.Threading;
9using System.Threading.Tasks;
10
11namespace OpenAI;
12
13internal class MultipartFormDataBinaryContent : BinaryContent
14{
15private readonly MultipartFormDataContent _multipartContent;
16
17private static Random _random = new();
18private static readonly char[] _boundaryValues = "0123456789=ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz".ToCharArray();
19
20public MultipartFormDataBinaryContent()
21{
22_multipartContent = new MultipartFormDataContent(CreateBoundary());
23}
24
25public string ContentType
26{
27get
28{
29Debug.Assert(_multipartContent.Headers.ContentType is not null);
30
31return _multipartContent.Headers.ContentType!.ToString();
32}
33}
34
35internal HttpContent HttpContent => _multipartContent;
36
37public void Add(Stream content, string name, string fileName = default, string contentType = null)
38{
39Argument.AssertNotNull(content, nameof(content));
40Argument.AssertNotNullOrEmpty(name, nameof(name));
41
42Add(new StreamContent(content), name, fileName, contentType);
43}
44
45//public void Add(Stream stream, string name, string fileName = default)
46//{
47// Add(new StreamContent(stream), name, fileName);
48//}
49
50public void Add(string content, string name, string fileName = default)
51{
52Add(new StringContent(content), name, fileName);
53}
54
55public void Add(int content, string name, string fileName = default)
56{
57// https://learn.microsoft.com/en-us/dotnet/standard/base-types/standard-numeric-format-strings#GFormatString
58string value = content.ToString("G", CultureInfo.InvariantCulture);
59Add(new StringContent(value), name, fileName);
60}
61
62public void Add(double content, string name, string fileName = default)
63{
64// https://learn.microsoft.com/en-us/dotnet/standard/base-types/standard-numeric-format-strings#GFormatString
65string value = content.ToString("G", CultureInfo.InvariantCulture);
66Add(new StringContent(value), name, fileName);
67}
68
69public void Add(byte[] content, string name, string fileName = default)
70{
71Add(new ByteArrayContent(content), name, fileName);
72}
73
74public void Add(BinaryData content, string name, string fileName = default)
75{
76Add(new ByteArrayContent(content.ToArray()), name, fileName);
77}
78
79private void Add(HttpContent content, string name, string filename, string contentType)
80{
81if (filename != null)
82{
83Argument.AssertNotNullOrEmpty(filename, nameof(filename));
84AddFileNameHeader(content, name, filename);
85}
86if (contentType != null)
87{
88Argument.AssertNotNullOrEmpty(contentType, nameof(contentType));
89AddContentTypeHeader(content, contentType);
90}
91_multipartContent.Add(content, name);
92}
93
94private void Add(HttpContent content, string name, string fileName)
95{
96if (fileName is not null)
97{
98AddFileNameHeader(content, name, fileName);
99}
100
101_multipartContent.Add(content, name);
102}
103
104private static void AddFileNameHeader(HttpContent content, string name, string filename)
105{
106// Add the content header manually because the default implementation
107// adds a `filename*` parameter to the header, which RFC 7578 says not
108// to do. We are following up with the BCL team per correctness.
109ContentDispositionHeaderValue header = new("form-data")
110{
111Name = name,
112FileName = filename
113};
114content.Headers.ContentDisposition = header;
115}
116
117public static void AddContentTypeHeader(HttpContent content, string contentType)
118{
119MediaTypeHeaderValue header = new MediaTypeHeaderValue(contentType);
120content.Headers.ContentType = header;
121}
122
123private static string CreateBoundary()
124{
125Span<char> chars = new char[70];
126
127byte[] random = new byte[70];
128_random.NextBytes(random);
129
130// The following will sample evenly from the possible values.
131// This is important to ensuring that the odds of creating a boundary
132// that occurs in any content part are astronomically small.
133int mask = 255 >> 2;
134
135Debug.Assert(_boundaryValues.Length - 1 == mask);
136
137for (int i = 0; i < 70; i++)
138{
139chars[i] = _boundaryValues[random[i] & mask];
140}
141
142return chars.ToString();
143}
144
145public override bool TryComputeLength(out long length)
146{
147// We can't call the protected method on HttpContent
148
149if (_multipartContent.Headers.ContentLength is long contentLength)
150{
151length = contentLength;
152return true;
153}
154
155length = 0;
156return false;
157}
158
159public override void WriteTo(Stream stream, CancellationToken cancellationToken = default)
160{
161// TODO: polyfill sync-over-async for netstandard2.0 for Azure clients.
162// Tracked by https://github.com/Azure/azure-sdk-for-net/issues/42674
163
164#if NET6_0_OR_GREATER
165_multipartContent.CopyTo(stream, default, cancellationToken);
166#else
167_multipartContent.CopyToAsync(stream).GetAwaiter().GetResult();
168#endif
169}
170
171public override async Task WriteToAsync(Stream stream, CancellationToken cancellationToken = default)
172{
173#if NET6_0_OR_GREATER
174await _multipartContent.CopyToAsync(stream, cancellationToken).ConfigureAwait(false);
175#else
176await _multipartContent.CopyToAsync(stream).ConfigureAwait(false);
177#endif
178}
179
180public override void Dispose()
181{
182_multipartContent.Dispose();
183}
184}