diff --git a/src/RestSharp/Request/RequestContent.cs b/src/RestSharp/Request/RequestContent.cs index 4df106cb1..5a19f94fb 100644 --- a/src/RestSharp/Request/RequestContent.cs +++ b/src/RestSharp/Request/RequestContent.cs @@ -36,38 +36,60 @@ public RequestContent(RestClient client, RestRequest request) { _parameters = new RequestParameters(_request.Parameters.Union(_client.DefaultParameters)); } - public HttpContent BuildContent() { - AddFiles(); + public HttpContent BuildContent() + { 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(); + var postParametersExists = postParameters.Length > 0; + var bodyParametersExists = _request.TryGetBodyParameter(out var bodyParameter); + var filesExists = _request.Files.Any(); - foreach (var file in _request.Files) { - var stream = file.GetFile(); - _streams.Add(stream); - var fileContent = new StreamContent(stream); + if (filesExists) + AddFiles(); - fileContent.Headers.ContentType = file.ContentType.AsMediaTypeHeaderValue; + if (bodyParametersExists) + AddBody(postParametersExists, bodyParameter!); - 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; + if (postParametersExists) + AddPostParameters(postParameters); - mpContent.Add(fileContent); - } + AddHeaders(); - Content = mpContent; + return Content!; + } + + void AddFiles() + { + // File uploading without multipart/form-data + if (_request.AlwaysSingleFileAsContent && _request.Files.Count == 1) + { + var fileParameter = _request.Files.First(); + Content = ToStreamContent(fileParameter); + return; + } + + var mpContent = new MultipartFormDataContent(GetOrSetFormBoundary()); + + foreach (var fileParameter in _request.Files) + mpContent.Add(ToStreamContent(fileParameter)); + + Content = mpContent; + } + + StreamContent ToStreamContent(FileParameter fileParameter) + { + var stream = fileParameter.GetFile(); + _streams.Add(stream); + var streamContent = new StreamContent(stream); + + streamContent.Headers.ContentType = fileParameter.ContentType.AsMediaTypeHeaderValue; + + var dispositionHeader = fileParameter.Options.DisableFilenameEncoding + ? ContentDispositionHeaderValue.Parse($"form-data; name=\"{fileParameter.Name}\"; filename=\"{fileParameter.FileName}\"") + : new ContentDispositionHeaderValue("form-data") { Name = $"\"{fileParameter.Name}\"", FileName = $"\"{fileParameter.FileName}\"" }; + if (!fileParameter.Options.DisableFileNameStar) dispositionHeader.FileNameStar = fileParameter.FileName; + streamContent.Headers.ContentDisposition = dispositionHeader; + + return streamContent; } HttpContent Serialize(BodyParameter body) { @@ -117,9 +139,8 @@ MultipartFormDataContent CreateMultipartFormDataContent() { return mpContent; } - void AddBody(bool hasPostParameters) { - if (!_request.TryGetBodyParameter(out var bodyParameter)) return; - + void AddBody(bool hasPostParameters, BodyParameter bodyParameter) + { var bodyContent = Serialize(bodyParameter); // we need to send the body diff --git a/src/RestSharp/Request/RestRequest.cs b/src/RestSharp/Request/RestRequest.cs index bc3b68382..9748fa9cf 100644 --- a/src/RestSharp/Request/RestRequest.cs +++ b/src/RestSharp/Request/RestRequest.cs @@ -81,7 +81,12 @@ public RestRequest(Uri resource, Method method = Method.Get) /// Always send a multipart/form-data request - even when no Files are present. /// public bool AlwaysMultipartFormData { get; set; } - + + /// + /// Always send a file as request content without multipart/form-data request - even when the request contains only one file parameter + /// + public bool AlwaysSingleFileAsContent { get; set; } + /// /// When set to true, parameter values in a multipart form data requests will be enclosed in /// quotation marks. Default is false. Enable it if the remote endpoint requires parameters diff --git a/src/RestSharp/Request/RestRequestExtensions.cs b/src/RestSharp/Request/RestRequestExtensions.cs index ce33b2611..b2ff8469e 100644 --- a/src/RestSharp/Request/RestRequestExtensions.cs +++ b/src/RestSharp/Request/RestRequestExtensions.cs @@ -501,4 +501,21 @@ static void CheckAndThrowsDuplicateKeys(ICollection if (duplicateKeys.Any()) throw new ArgumentException($"Duplicate header names exist: {string.Join(", ", duplicateKeys)}"); } + + public static void ValidateParameters(this RestRequest request) { + + if (request.AlwaysSingleFileAsContent) { + var postParametersExists = request.Parameters.GetContentParameters(request.Method).Any(); + var bodyParametersExists = request.Parameters.Any(p => p.Type == ParameterType.RequestBody); + + if (request.AlwaysMultipartFormData) + throw new ArgumentException("Failed to put file as content because flag AlwaysMultipartFormData enabled"); + + if (postParametersExists) + throw new ArgumentException("Failed to put file as content because added post parameters"); + + if (bodyParametersExists) + throw new ArgumentException("Failed to put file as content because added body parameters"); + } + } } diff --git a/src/RestSharp/RestClient.Async.cs b/src/RestSharp/RestClient.Async.cs index c5da81007..d3701e221 100644 --- a/src/RestSharp/RestClient.Async.cs +++ b/src/RestSharp/RestClient.Async.cs @@ -77,6 +77,7 @@ async Task ExecuteRequestAsync(RestRequest request, CancellationTo throw new ObjectDisposedException(nameof(RestClient)); } + request.ValidateParameters(); var authenticator = request.Authenticator ?? Options.Authenticator; if (authenticator != null) await authenticator.Authenticate(this, request).ConfigureAwait(false); diff --git a/test/RestSharp.Tests/RestRequestParametersTests.cs b/test/RestSharp.Tests/RestRequestParametersTests.cs new file mode 100644 index 000000000..7370644d3 --- /dev/null +++ b/test/RestSharp.Tests/RestRequestParametersTests.cs @@ -0,0 +1,75 @@ +// 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. +// + +namespace RestSharp.Tests; + +public class RestRequestValidateParametersTests { + [Fact] + public void RestRequest_AlwaysMultipartFormData_IsAllowed() { + var request = new RestRequest { + AlwaysMultipartFormData = true + }; + + request.ValidateParameters(); + } + + [Fact] + public void RestRequest_AlwaysSingleFileAsContent_IsAllowed() { + var request = new RestRequest { + AlwaysSingleFileAsContent = true + }; + + request.ValidateParameters(); + } + + [Fact] + public void RestRequest_AlwaysSingleFileAsContent_And_AlwaysMultipartFormData_IsNotAllowed() { + var request = new RestRequest { + AlwaysSingleFileAsContent = true, + AlwaysMultipartFormData = true + }; + + Assert.Throws( + () => { request.ValidateParameters(); } + ); + } + + [Fact] + public void RestRequest_AlwaysSingleFileAsContent_And_PostParameters_IsNotAllowed() { + var request = new RestRequest { + Method = Method.Post, + AlwaysSingleFileAsContent = true, + }; + + request.AddParameter("name", "value", ParameterType.GetOrPost); + + Assert.Throws( + () => { request.ValidateParameters(); } + ); + } + + [Fact] + public void RestRequest_AlwaysSingleFileAsContent_And_BodyParameters_IsNotAllowed() { + var request = new RestRequest { + AlwaysSingleFileAsContent = true, + }; + + request.AddParameter("name", "value", ParameterType.RequestBody); + + Assert.Throws( + () => { request.ValidateParameters(); } + ); + } +} \ No newline at end of file