Skip to content

Commit 2facbeb

Browse files
Support file uploading without multipart/form-data (#2068)
* Support file uploading without multipart/form-data * AlwaysSingleFileAsContent flag * ValidateParameters method * ValidateParameters tests
1 parent 71c73f8 commit 2facbeb

File tree

5 files changed

+149
-30
lines changed

5 files changed

+149
-30
lines changed

src/RestSharp/Request/RequestContent.cs

Lines changed: 50 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -36,38 +36,60 @@ public RequestContent(RestClient client, RestRequest request) {
3636
_parameters = new RequestParameters(_request.Parameters.Union(_client.DefaultParameters));
3737
}
3838

39-
public HttpContent BuildContent() {
40-
AddFiles();
39+
public HttpContent BuildContent()
40+
{
4141
var postParameters = _parameters.GetContentParameters(_request.Method).ToArray();
42-
AddBody(postParameters.Length > 0);
43-
AddPostParameters(postParameters);
44-
AddHeaders();
45-
46-
return Content!;
47-
}
48-
49-
void AddFiles() {
50-
if (!_request.HasFiles() && !_request.AlwaysMultipartFormData) return;
51-
52-
var mpContent = CreateMultipartFormDataContent();
42+
var postParametersExists = postParameters.Length > 0;
43+
var bodyParametersExists = _request.TryGetBodyParameter(out var bodyParameter);
44+
var filesExists = _request.Files.Any();
5345

54-
foreach (var file in _request.Files) {
55-
var stream = file.GetFile();
56-
_streams.Add(stream);
57-
var fileContent = new StreamContent(stream);
46+
if (filesExists)
47+
AddFiles();
5848

59-
fileContent.Headers.ContentType = file.ContentType.AsMediaTypeHeaderValue;
49+
if (bodyParametersExists)
50+
AddBody(postParametersExists, bodyParameter!);
6051

61-
var dispositionHeader = file.Options.DisableFilenameEncoding
62-
? ContentDispositionHeaderValue.Parse($"form-data; name=\"{file.Name}\"; filename=\"{file.FileName}\"")
63-
: new ContentDispositionHeaderValue("form-data") { Name = $"\"{file.Name}\"", FileName = $"\"{file.FileName}\"" };
64-
if (!file.Options.DisableFileNameStar) dispositionHeader.FileNameStar = file.FileName;
65-
fileContent.Headers.ContentDisposition = dispositionHeader;
52+
if (postParametersExists)
53+
AddPostParameters(postParameters);
6654

67-
mpContent.Add(fileContent);
68-
}
55+
AddHeaders();
6956

70-
Content = mpContent;
57+
return Content!;
58+
}
59+
60+
void AddFiles()
61+
{
62+
// File uploading without multipart/form-data
63+
if (_request.AlwaysSingleFileAsContent && _request.Files.Count == 1)
64+
{
65+
var fileParameter = _request.Files.First();
66+
Content = ToStreamContent(fileParameter);
67+
return;
68+
}
69+
70+
var mpContent = new MultipartFormDataContent(GetOrSetFormBoundary());
71+
72+
foreach (var fileParameter in _request.Files)
73+
mpContent.Add(ToStreamContent(fileParameter));
74+
75+
Content = mpContent;
76+
}
77+
78+
StreamContent ToStreamContent(FileParameter fileParameter)
79+
{
80+
var stream = fileParameter.GetFile();
81+
_streams.Add(stream);
82+
var streamContent = new StreamContent(stream);
83+
84+
streamContent.Headers.ContentType = fileParameter.ContentType.AsMediaTypeHeaderValue;
85+
86+
var dispositionHeader = fileParameter.Options.DisableFilenameEncoding
87+
? ContentDispositionHeaderValue.Parse($"form-data; name=\"{fileParameter.Name}\"; filename=\"{fileParameter.FileName}\"")
88+
: new ContentDispositionHeaderValue("form-data") { Name = $"\"{fileParameter.Name}\"", FileName = $"\"{fileParameter.FileName}\"" };
89+
if (!fileParameter.Options.DisableFileNameStar) dispositionHeader.FileNameStar = fileParameter.FileName;
90+
streamContent.Headers.ContentDisposition = dispositionHeader;
91+
92+
return streamContent;
7193
}
7294

7395
HttpContent Serialize(BodyParameter body) {
@@ -117,9 +139,8 @@ MultipartFormDataContent CreateMultipartFormDataContent() {
117139
return mpContent;
118140
}
119141

120-
void AddBody(bool hasPostParameters) {
121-
if (!_request.TryGetBodyParameter(out var bodyParameter)) return;
122-
142+
void AddBody(bool hasPostParameters, BodyParameter bodyParameter)
143+
{
123144
var bodyContent = Serialize(bodyParameter);
124145

125146
// we need to send the body

src/RestSharp/Request/RestRequest.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,12 @@ public RestRequest(Uri resource, Method method = Method.Get)
8181
/// Always send a multipart/form-data request - even when no Files are present.
8282
/// </summary>
8383
public bool AlwaysMultipartFormData { get; set; }
84-
84+
85+
/// <summary>
86+
/// Always send a file as request content without multipart/form-data request - even when the request contains only one file parameter
87+
/// </summary>
88+
public bool AlwaysSingleFileAsContent { get; set; }
89+
8590
/// <summary>
8691
/// When set to true, parameter values in a multipart form data requests will be enclosed in
8792
/// quotation marks. Default is false. Enable it if the remote endpoint requires parameters

src/RestSharp/Request/RestRequestExtensions.cs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -501,4 +501,21 @@ static void CheckAndThrowsDuplicateKeys(ICollection<KeyValuePair<string, string>
501501

502502
if (duplicateKeys.Any()) throw new ArgumentException($"Duplicate header names exist: {string.Join(", ", duplicateKeys)}");
503503
}
504+
505+
public static void ValidateParameters(this RestRequest request) {
506+
507+
if (request.AlwaysSingleFileAsContent) {
508+
var postParametersExists = request.Parameters.GetContentParameters(request.Method).Any();
509+
var bodyParametersExists = request.Parameters.Any(p => p.Type == ParameterType.RequestBody);
510+
511+
if (request.AlwaysMultipartFormData)
512+
throw new ArgumentException("Failed to put file as content because flag AlwaysMultipartFormData enabled");
513+
514+
if (postParametersExists)
515+
throw new ArgumentException("Failed to put file as content because added post parameters");
516+
517+
if (bodyParametersExists)
518+
throw new ArgumentException("Failed to put file as content because added body parameters");
519+
}
520+
}
504521
}

src/RestSharp/RestClient.Async.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ async Task<HttpResponse> ExecuteRequestAsync(RestRequest request, CancellationTo
7777
throw new ObjectDisposedException(nameof(RestClient));
7878
}
7979

80+
request.ValidateParameters();
8081
var authenticator = request.Authenticator ?? Options.Authenticator;
8182
if (authenticator != null) await authenticator.Authenticate(this, request).ConfigureAwait(false);
8283

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
// Copyright (c) .NET Foundation and Contributors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
//
15+
16+
namespace RestSharp.Tests;
17+
18+
public class RestRequestValidateParametersTests {
19+
[Fact]
20+
public void RestRequest_AlwaysMultipartFormData_IsAllowed() {
21+
var request = new RestRequest {
22+
AlwaysMultipartFormData = true
23+
};
24+
25+
request.ValidateParameters();
26+
}
27+
28+
[Fact]
29+
public void RestRequest_AlwaysSingleFileAsContent_IsAllowed() {
30+
var request = new RestRequest {
31+
AlwaysSingleFileAsContent = true
32+
};
33+
34+
request.ValidateParameters();
35+
}
36+
37+
[Fact]
38+
public void RestRequest_AlwaysSingleFileAsContent_And_AlwaysMultipartFormData_IsNotAllowed() {
39+
var request = new RestRequest {
40+
AlwaysSingleFileAsContent = true,
41+
AlwaysMultipartFormData = true
42+
};
43+
44+
Assert.Throws<ArgumentException>(
45+
() => { request.ValidateParameters(); }
46+
);
47+
}
48+
49+
[Fact]
50+
public void RestRequest_AlwaysSingleFileAsContent_And_PostParameters_IsNotAllowed() {
51+
var request = new RestRequest {
52+
Method = Method.Post,
53+
AlwaysSingleFileAsContent = true,
54+
};
55+
56+
request.AddParameter("name", "value", ParameterType.GetOrPost);
57+
58+
Assert.Throws<ArgumentException>(
59+
() => { request.ValidateParameters(); }
60+
);
61+
}
62+
63+
[Fact]
64+
public void RestRequest_AlwaysSingleFileAsContent_And_BodyParameters_IsNotAllowed() {
65+
var request = new RestRequest {
66+
AlwaysSingleFileAsContent = true,
67+
};
68+
69+
request.AddParameter("name", "value", ParameterType.RequestBody);
70+
71+
Assert.Throws<ArgumentException>(
72+
() => { request.ValidateParameters(); }
73+
);
74+
}
75+
}

0 commit comments

Comments
 (0)