Skip to content

Commit

Permalink
Use encoded filename for FileNameStar (#2117) (#2123)
Browse files Browse the repository at this point in the history
* Use encoded filename for FileNameStar (#2117)

* Remove the "fix" and add a test that shows it working

* Update the docs

* Fix the CsvHelper new version
  • Loading branch information
alexeyzimarev committed Aug 14, 2023
1 parent 6197f31 commit 1c9caba
Show file tree
Hide file tree
Showing 13 changed files with 85 additions and 57 deletions.
Expand Up @@ -9,7 +9,7 @@

<ItemGroup>
<PackageReference Include="AutoFixture" Version="4.18.0" />
<PackageReference Include="BenchmarkDotNet" Version="0.13.5" />
<PackageReference Include="BenchmarkDotNet" Version="0.13.7" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.6.0" />
</ItemGroup>
<ItemGroup>
Expand Down
16 changes: 16 additions & 0 deletions docs/usage.md
Expand Up @@ -561,6 +561,22 @@ AddFile(parameterName, getFile, fileName, contentType);

Remember that `AddFile` will set all the necessary headers, so please don't try to set content headers manually.

You can also provide file upload options to the `AddFile` call. The options are:
- `DisableFilenameEncoding` (default `false`): if set to `true`, RestSharp will not encode the file name in the `Content-Disposition` header
- `DisableFilenameStar` (default `true`): if set to `true`, RestSharp will not add the `filename*` parameter to the `Content-Disposition` header

Example of using the options:

```csharp
var options = new FileParameterOptions {
DisableFilenameEncoding = true,
DisableFilenameStar = false
};
request.AddFile("file", filePath, options: options);
```

The options specified in the snippet above usually help when you upload files with non-ASCII characters in their names.

### Downloading binary data

There are two functions that allow you to download binary data from the remote API.
Expand Down
3 changes: 3 additions & 0 deletions props/Common.props
Expand Up @@ -7,4 +7,7 @@
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<Using Include="System.Net.Http"/>
</ItemGroup>
</Project>
2 changes: 1 addition & 1 deletion src/Directory.Build.props
Expand Up @@ -21,7 +21,7 @@
<ItemGroup>
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1" PrivateAssets="All"/>
<PackageReference Include="MinVer" Version="4.3.0" PrivateAssets="All"/>
<PackageReference Include="JetBrains.Annotations" Version="2022.3.1" PrivateAssets="All"/>
<PackageReference Include="JetBrains.Annotations" Version="2023.2.0" PrivateAssets="All"/>
</ItemGroup>
<ItemGroup>
<None Include="$(RepoRoot)\restsharp.png" Pack="true" PackagePath="\"/>
Expand Down
14 changes: 1 addition & 13 deletions src/RestSharp.Serializers.CsvHelper/CsvHelperSerializer.cs
Expand Up @@ -84,19 +84,7 @@ public class CsvHelperSerializer : IDeserializer, IRestSerializer, ISerializer {
using var csvWriter = new CsvWriter(stringWriter, _configuration);

if (obj is IEnumerable records) {
// ReSharper disable once PossibleMultipleEnumeration
var enumerator = records.GetEnumerator();

if (enumerator.MoveNext() && enumerator.Current != null) {
csvWriter.WriteHeader(enumerator.Current.GetType());
csvWriter.NextRecord();
// ReSharper disable once PossibleMultipleEnumeration
csvWriter.WriteRecords(records);
}

if (enumerator is IDisposable disposable) {
disposable.Dispose();
}
csvWriter.WriteRecords(records);
}
else {
csvWriter.WriteHeader(obj.GetType());
Expand Down
@@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<ItemGroup>
<PackageReference Include="CsvHelper" Version="28.0.1"/>
<PackageReference Include="CsvHelper" Version="30.0.1" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\RestSharp\RestSharp.csproj"/>
Expand Down
7 changes: 6 additions & 1 deletion src/RestSharp/Parameters/FileParameter.cs
Expand Up @@ -113,6 +113,11 @@ public record FileParameter {

[PublicAPI]
public class FileParameterOptions {
public bool DisableFileNameStar { get; set; } = true;
[Obsolete("Use DisableFilenameStar instead")]
public bool DisableFileNameStar {
get => DisableFilenameStar;
set => DisableFilenameStar = value;
}
public bool DisableFilenameStar { get; set; } = true;

Check warning on line 121 in src/RestSharp/Parameters/FileParameter.cs

View workflow job for this annotation

GitHub Actions / nuget

Identifier 'FileParameterOptions.DisableFilenameStar' differing only in case is not CLS-compliant

Check warning on line 121 in src/RestSharp/Parameters/FileParameter.cs

View workflow job for this annotation

GitHub Actions / nuget

Identifier 'FileParameterOptions.DisableFilenameStar.get' differing only in case is not CLS-compliant

Check warning on line 121 in src/RestSharp/Parameters/FileParameter.cs

View workflow job for this annotation

GitHub Actions / nuget

Identifier 'FileParameterOptions.DisableFilenameStar.set' differing only in case is not CLS-compliant

Check warning on line 121 in src/RestSharp/Parameters/FileParameter.cs

View workflow job for this annotation

GitHub Actions / nuget

Identifier 'FileParameterOptions.DisableFilenameStar' differing only in case is not CLS-compliant

Check warning on line 121 in src/RestSharp/Parameters/FileParameter.cs

View workflow job for this annotation

GitHub Actions / nuget

Identifier 'FileParameterOptions.DisableFilenameStar.get' differing only in case is not CLS-compliant

Check warning on line 121 in src/RestSharp/Parameters/FileParameter.cs

View workflow job for this annotation

GitHub Actions / nuget

Identifier 'FileParameterOptions.DisableFilenameStar.set' differing only in case is not CLS-compliant

Check warning on line 121 in src/RestSharp/Parameters/FileParameter.cs

View workflow job for this annotation

GitHub Actions / nuget

Identifier 'FileParameterOptions.DisableFilenameStar' differing only in case is not CLS-compliant

Check warning on line 121 in src/RestSharp/Parameters/FileParameter.cs

View workflow job for this annotation

GitHub Actions / nuget

Identifier 'FileParameterOptions.DisableFilenameStar.get' differing only in case is not CLS-compliant
public bool DisableFilenameEncoding { get; set; }
}
8 changes: 4 additions & 4 deletions test/Directory.Build.props
Expand Up @@ -9,14 +9,14 @@
</PropertyGroup>

<ItemGroup Condition="$(IsTestProject) == 'true'">
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.6.3"/>
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5" PrivateAssets="All"/>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.7.0"/>
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.0" PrivateAssets="All"/>
<PackageReference Include="coverlet.collector" Version="6.0.0"/>
</ItemGroup>
<ItemGroup>
<PackageReference Include="xunit" Version="2.4.2"/>
<PackageReference Include="xunit" Version="2.5.0"/>
<PackageReference Include="AutoFixture" Version="4.18.0"/>
<PackageReference Include="FluentAssertions" Version="6.10.0"/>
<PackageReference Include="FluentAssertions" Version="6.11.0"/>
</ItemGroup>
<ItemGroup Condition="$(TargetFramework) == 'net472'">
<PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies.net472" Version="1.0.3" PrivateAssets="All"/>
Expand Down
Expand Up @@ -16,7 +16,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="HttpTracer" Version="2.1.1" />
<PackageReference Include="Microsoft.AspNetCore.TestHost" Version="6.0.19" />
<PackageReference Include="Microsoft.AspNetCore.TestHost" Version="6.0.21" />
<PackageReference Include="Polly" Version="7.2.4" />
<PackageReference Include="Xunit.Extensions.Logging" Version="1.1.0" />
</ItemGroup>
Expand Down
20 changes: 15 additions & 5 deletions test/RestSharp.Tests.Integrated/Server/Handlers/FileHandlers.cs
Expand Up @@ -10,17 +10,27 @@ public class UploadController : ControllerBase {
[HttpPost]
[Route("upload")]
[SuppressMessage("Performance", "CA1822:Mark members as static")]
public async Task<UploadResponse> Upload([FromForm] FormFile formFile) {
public async Task<IActionResult> Upload([FromForm] FormFile formFile, [FromQuery] bool checkFile = true) {
var file = formFile.File;

if (!checkFile) {
return Ok(new UploadResponse(file.FileName, file.Length, true));
}

var assetPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Assets");
var file = formFile.File;

await using var stream = file.OpenReadStream();

var received = await stream.ReadAsBytes(default);
var expected = await System.IO.File.ReadAllBytesAsync(Path.Combine(assetPath, file.FileName));

var response = new UploadResponse(file.FileName, file.Length, received.SequenceEqual(expected));
return response;
try {
var expected = await System.IO.File.ReadAllBytesAsync(Path.Combine(assetPath, file.FileName));
var response = new UploadResponse(file.FileName, file.Length, received.SequenceEqual(expected));
return Ok(response);
}
catch (Exception e) {
return BadRequest(new { Message = e.Message, Filename = file.FileName });
}
}
}

Expand Down
53 changes: 29 additions & 24 deletions test/RestSharp.Tests.Integrated/UploadFileTests.cs
Expand Up @@ -7,58 +7,63 @@ namespace RestSharp.Tests.Integrated;
public class UploadFileTests {
readonly ITestOutputHelper _output;
readonly RestClient _client;
readonly string _path = AppDomain.CurrentDomain.BaseDirectory;
readonly string _basePath = AppDomain.CurrentDomain.BaseDirectory;
readonly string _path;
readonly UploadResponse _expected;

const string Filename = "Koala.jpg";

public UploadFileTests(TestServerFixture fixture, ITestOutputHelper output) {
_output = output;
_client = new RestClient(new RestClientOptions(fixture.Server.Url) { ThrowOnAnyError = true });
// _client = new RestClient(new RestClientOptions(fixture.Server.Url) { ThrowOnAnyError = true });
_client = new RestClient(new RestClientOptions(fixture.Server.Url) { ThrowOnAnyError = false });
_path = Path.Combine(_basePath, "Assets", Filename);
_expected = new UploadResponse(Filename, new FileInfo(_path).Length, true);
}

[Fact]
public async Task Should_upload_from_file() {
const string filename = "Koala.jpg";

var path = Path.Combine(_path, "Assets", filename);

var request = new RestRequest("upload").AddFile("file", path);
var request = new RestRequest("upload").AddFile("file", _path);
var response = await _client.ExecutePostAsync<UploadResponse>(request);

response.StatusCode.Should().Be(HttpStatusCode.OK);

var expected = new UploadResponse(filename, new FileInfo(path).Length, true);

_output.WriteLine(response.Content);
response.Data.Should().BeEquivalentTo(expected);
response.Data.Should().BeEquivalentTo(_expected);
}

[Fact]
public async Task Should_upload_from_bytes() {
const string filename = "Koala.jpg";
var bytes = await File.ReadAllBytesAsync(_path);

var path = Path.Combine(_path, "Assets", filename);
var bytes = await File.ReadAllBytesAsync(path);

var request = new RestRequest("upload").AddFile("file", bytes, filename);
var request = new RestRequest("upload").AddFile("file", bytes, Filename);
var response = await _client.ExecutePostAsync<UploadResponse>(request);

var expected = new UploadResponse(filename, new FileInfo(path).Length, true);

_output.WriteLine(response.Content);
response.Data.Should().BeEquivalentTo(expected);
response.Data.Should().BeEquivalentTo(_expected);
}

[Fact]
public async Task Should_upload_from_stream() {
const string filename = "Koala.jpg";
var request = new RestRequest("upload").AddFile("file", () => File.OpenRead(_path), Filename);
var response = await _client.ExecutePostAsync<UploadResponse>(request);

var path = Path.Combine(_path, "Assets", filename);
_output.WriteLine(response.Content);
response.Data.Should().BeEquivalentTo(_expected);
}

var request = new RestRequest("upload").AddFile("file", () => File.OpenRead(path), filename);
var response = await _client.ExecutePostAsync<UploadResponse>(request);
[Fact]
public async Task Should_upload_from_stream_non_ascii() {
const string nonAsciiFilename = "Präsentation_Export.zip";

var options = new FileParameterOptions { DisableFilenameEncoding = true, DisableFilenameStar = false};

var expected = new UploadResponse(filename, new FileInfo(path).Length, true);
var request = new RestRequest("upload")
.AddFile("file", () => File.OpenRead(_path), nonAsciiFilename, options: options)
.AddQueryParameter("checkFile", "false");
var response = await _client.ExecutePostAsync<UploadResponse>(request);

_output.WriteLine(response.Content);
response.Data.Should().BeEquivalentTo(expected);
response.Data.Should().BeEquivalentTo(new UploadResponse(nonAsciiFilename, new FileInfo(_path).Length, true));
}
}
11 changes: 6 additions & 5 deletions test/RestSharp.Tests.Serializers.Csv/CsvHelperTests.cs
Expand Up @@ -137,11 +137,12 @@ public class CsvHelperTests {
DateTimeValue = new DateTime(2024, 1, 20)
};

serializer.Serialize(item)
.Should()
.Be(
"StringValue,Int32Value,DecimalValue,DoubleValue,SingleValue,DateTimeValue,TimeSpanValue;hello,32,0,0,16.5,01/20/2024 00:00:00,00:10:00;"
);
var actual = serializer.Serialize(item);

const string expected =
"StringValue,Int32Value,DecimalValue,DoubleValue,SingleValue,DateTimeValue,TimeSpanValue;hello,32,0,0,16.5,01/20/2024 00:00:00,00:10:00;";

actual.Should().Be(expected);
}

[Fact]
Expand Down
2 changes: 1 addition & 1 deletion test/RestSharp.Tests/RestSharp.Tests.csproj
@@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<ItemGroup>
<PackageReference Include="Moq" Version="4.18.4" />
<PackageReference Include="Moq" Version="4.20.2" />
<PackageReference Include="RichardSzalay.MockHttp" Version="6.0.0" />
<PackageReference Include="System.Net.Http.Json" Version="7.0.1" />
</ItemGroup>
Expand Down

0 comments on commit 1c9caba

Please sign in to comment.