-
-
Notifications
You must be signed in to change notification settings - Fork 2.3k
/
RequestContent.cs
215 lines (170 loc) · 8.39 KB
/
RequestContent.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
// Copyright (c) .NET Foundation and Contributors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
using System.Net.Http.Headers;
using System.Runtime.Serialization;
using RestSharp.Extensions;
using static RestSharp.KnownHeaders;
// ReSharper disable InvertIf
// ReSharper disable SuggestBaseTypeForParameter
namespace RestSharp;
class RequestContent : IDisposable {
readonly RestClient _client;
readonly RestRequest _request;
readonly List<Stream> _streams = new();
readonly ParametersCollection _parameters;
HttpContent? Content { get; set; }
public RequestContent(RestClient client, RestRequest request) {
_client = client;
_request = request;
_parameters = new RequestParameters(_request.Parameters.Union(_client.DefaultParameters));
}
public HttpContent BuildContent() {
AddFiles();
var postParameters = _parameters.GetContentParameters(_request.Method).ToArray();
AddBody(postParameters.Length > 0);
AddPostParameters(postParameters);
AddHeaders();
return Content!;
}
void AddFiles() {
if (!_request.HasFiles() && !_request.AlwaysMultipartFormData) return;
var mpContent = CreateMultipartFormDataContent();
foreach (var file in _request.Files) {
var stream = file.GetFile();
_streams.Add(stream);
var fileContent = new StreamContent(stream);
fileContent.Headers.ContentType = file.ContentType.AsMediaTypeHeaderValue;
var dispositionHeader = file.Options.DisableFilenameEncoding
? ContentDispositionHeaderValue.Parse($"form-data; name=\"{file.Name}\"; filename=\"{file.FileName}\"")
: new ContentDispositionHeaderValue("form-data") { Name = $"\"{file.Name}\"", FileName = $"\"{file.FileName}\"" };
if (!file.Options.DisableFileNameStar) dispositionHeader.FileNameStar = file.FileName;
fileContent.Headers.ContentDisposition = dispositionHeader;
mpContent.Add(fileContent);
}
Content = mpContent;
}
HttpContent Serialize(BodyParameter body) {
return body.DataFormat switch {
DataFormat.None => new StringContent(body.Value!.ToString()!, _client.Options.Encoding, body.ContentType.Value),
DataFormat.Binary => GetBinary(),
_ => GetSerialized()
};
HttpContent GetBinary() {
var byteContent = new ByteArrayContent((body.Value as byte[])!);
byteContent.Headers.ContentType = body.ContentType.AsMediaTypeHeaderValue;
if (body.ContentEncoding != null) {
byteContent.Headers.ContentEncoding.Clear();
byteContent.Headers.ContentEncoding.Add(body.ContentEncoding);
}
return byteContent;
}
HttpContent GetSerialized() {
var serializer = _client.Serializers.GetSerializer(body.DataFormat);
var content = serializer.Serialize(body);
if (content == null) throw new SerializationException("Request body serialized to null");
var contentType = body.ContentType.Or(serializer.Serializer.ContentType);
return new StringContent(content, _client.Options.Encoding, contentType.Value);
}
}
static bool BodyShouldBeMultipartForm(BodyParameter bodyParameter) {
var bodyContentType = bodyParameter.ContentType.OrValue(bodyParameter.Name);
return bodyParameter.Name.IsNotEmpty() && bodyParameter.Name != bodyContentType;
}
string GetOrSetFormBoundary() => _request.FormBoundary ?? (_request.FormBoundary = Guid.NewGuid().ToString());
MultipartFormDataContent CreateMultipartFormDataContent() {
var boundary = GetOrSetFormBoundary();
var mpContent = new MultipartFormDataContent(boundary);
var contentType = new MediaTypeHeaderValue("multipart/form-data");
contentType.Parameters.Add(new NameValueHeaderValue(nameof(boundary), GetBoundary(boundary, _request.MultipartFormQuoteBoundary)));
mpContent.Headers.ContentType = contentType;
return mpContent;
}
void AddBody(bool hasPostParameters) {
if (!_request.TryGetBodyParameter(out var bodyParameter)) return;
var bodyContent = Serialize(bodyParameter);
// we need to send the body
if (hasPostParameters || _request.HasFiles() || BodyShouldBeMultipartForm(bodyParameter!) || _request.AlwaysMultipartFormData) {
// here we must use multipart form data
var mpContent = Content as MultipartFormDataContent ?? CreateMultipartFormDataContent();
var ct = bodyContent.Headers.ContentType?.MediaType;
var name = bodyParameter.Name.IsEmpty() ? ct : bodyParameter.Name;
if (name.IsEmpty())
mpContent.Add(bodyContent);
else
mpContent.Add(bodyContent, name);
Content = mpContent;
}
else {
// we don't have parameters, only the body
Content = bodyContent;
}
if (_client.Options.DisableCharset) {
Content.Headers.ContentType!.CharSet = "";
}
}
void AddPostParameters(GetOrPostParameter[] postParameters) {
if (postParameters.Length == 0) return;
if (Content is MultipartFormDataContent mpContent) {
// we got the multipart form already instantiated, just add parameters to it
foreach (var postParameter in postParameters) {
var parameterName = postParameter.Name!;
mpContent.Add(
new StringContent(postParameter.Value?.ToString() ?? string.Empty, _client.Options.Encoding, postParameter.ContentType.Value),
_request.MultipartFormQuoteParameters ? $"\"{parameterName}\"" : parameterName
);
}
}
else {
var encodedItems = postParameters.Select(x => $"{x.Name!.UrlEncode()}={x.Value?.ToString()?.UrlEncode() ?? string.Empty}");
var encodedContent = new StringContent(encodedItems.JoinToString("&"), _client.Options.Encoding, ContentType.FormUrlEncoded.Value);
if (_client.Options.DisableCharset) {
encodedContent.Headers.ContentType!.CharSet = "";
}
Content = encodedContent;
}
}
static string GetBoundary(string boundary, bool quote) => quote ? $"\"{boundary}\"" : boundary;
void AddHeaders() {
var contentHeaders = _parameters
.GetParameters<HeaderParameter>()
.Where(x => IsContentHeader(x.Name!))
.ToArray();
if (contentHeaders.Length > 0 && Content == null) {
// We need some content to add content headers to it, so if necessary, we'll add empty content
Content = new StringContent("");
}
contentHeaders.ForEach(AddHeader);
void AddHeader(HeaderParameter parameter) {
var parameterStringValue = parameter.Value!.ToString();
var value = parameter.Name switch {
KnownHeaders.ContentType => GetContentTypeHeader(Ensure.NotNull(parameterStringValue, nameof(parameter))),
_ => parameterStringValue
};
var pName = Ensure.NotNull(parameter.Name, nameof(parameter.Name));
ReplaceHeader(pName, value);
}
string GetContentTypeHeader(string contentType)
=> Content is MultipartFormDataContent
? $"{contentType}; boundary={GetBoundary(GetOrSetFormBoundary(), _request.MultipartFormQuoteBoundary)}"
: contentType;
}
void ReplaceHeader(string name, string? value) {
Content!.Headers.Remove(name);
Content!.Headers.TryAddWithoutValidation(name, value);
}
public void Dispose() {
_streams.ForEach(x => x.Dispose());
Content?.Dispose();
}
}