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 · modecode

1using 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{
15 private readonly MultipartFormDataContent _multipartContent;
16
17 private static Random _random = new();
18 private static readonly char[] _boundaryValues = "0123456789=ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz".ToCharArray();
19
20 public MultipartFormDataBinaryContent()
21 {
22 _multipartContent = new MultipartFormDataContent(CreateBoundary());
23 }
24
25 public string ContentType
26 {
27 get
28 {
29 Debug.Assert(_multipartContent.Headers.ContentType is not null);
30
31 return _multipartContent.Headers.ContentType!.ToString();
32 }
33 }
34
35 internal HttpContent HttpContent => _multipartContent;
36
37 public void Add(Stream content, string name, string fileName = default, string contentType = null)
38 {
39 Argument.AssertNotNull(content, nameof(content));
40 Argument.AssertNotNullOrEmpty(name, nameof(name));
41
42 Add(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
50 public void Add(string content, string name, string fileName = default)
51 {
52 Add(new StringContent(content), name, fileName);
53 }
54
55 public 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
58 string value = content.ToString("G", CultureInfo.InvariantCulture);
59 Add(new StringContent(value), name, fileName);
60 }
61
62 public 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
65 string value = content.ToString("G", CultureInfo.InvariantCulture);
66 Add(new StringContent(value), name, fileName);
67 }
68
69 public void Add(byte[] content, string name, string fileName = default)
70 {
71 Add(new ByteArrayContent(content), name, fileName);
72 }
73
74 public void Add(BinaryData content, string name, string fileName = default)
75 {
76 Add(new ByteArrayContent(content.ToArray()), name, fileName);
77 }
78
79 private void Add(HttpContent content, string name, string filename, string contentType)
80 {
81 if (filename != null)
82 {
83 Argument.AssertNotNullOrEmpty(filename, nameof(filename));
84 AddFileNameHeader(content, name, filename);
85 }
86 if (contentType != null)
87 {
88 Argument.AssertNotNullOrEmpty(contentType, nameof(contentType));
89 AddContentTypeHeader(content, contentType);
90 }
91 _multipartContent.Add(content, name);
92 }
93
94 private void Add(HttpContent content, string name, string fileName)
95 {
96 if (fileName is not null)
97 {
98 AddFileNameHeader(content, name, fileName);
99 }
100
101 _multipartContent.Add(content, name);
102 }
103
104 private 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.
109 ContentDispositionHeaderValue header = new("form-data")
110 {
111 Name = name,
112 FileName = filename
113 };
114 content.Headers.ContentDisposition = header;
115 }
116
117 public static void AddContentTypeHeader(HttpContent content, string contentType)
118 {
119 MediaTypeHeaderValue header = new MediaTypeHeaderValue(contentType);
120 content.Headers.ContentType = header;
121 }
122
123 private static string CreateBoundary()
124 {
125 Span<char> chars = new char[70];
126
127 byte[] 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.
133 int mask = 255 >> 2;
134
135 Debug.Assert(_boundaryValues.Length - 1 == mask);
136
137 for (int i = 0; i < 70; i++)
138 {
139 chars[i] = _boundaryValues[random[i] & mask];
140 }
141
142 return chars.ToString();
143 }
144
145 public override bool TryComputeLength(out long length)
146 {
147 // We can't call the protected method on HttpContent
148
149 if (_multipartContent.Headers.ContentLength is long contentLength)
150 {
151 length = contentLength;
152 return true;
153 }
154
155 length = 0;
156 return false;
157 }
158
159 public 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
171 public override async Task WriteToAsync(Stream stream, CancellationToken cancellationToken = default)
172 {
173#if NET6_0_OR_GREATER
174 await _multipartContent.CopyToAsync(stream, cancellationToken).ConfigureAwait(false);
175#else
176 await _multipartContent.CopyToAsync(stream).ConfigureAwait(false);
177#endif
178 }
179
180 public override void Dispose()
181 {
182 _multipartContent.Dispose();
183 }
184}
185