From c7d1e720cf83b10d686f9a1e6ad4dfa7f8a1a2df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 6 Nov 2024 11:08:05 +0100 Subject: [PATCH] Add better support for adding form file parameters to web requests Something I ended up needing in the course of BSS work. The previous implementation would treat files as a straight dictionary, as in you could have only one file with a given parameter name in a request. This isn't how `multipart/form-data` really works; you're allowed to send multiple files in scope of a single parameter. Additionally, the request did not permit specifying a filename, which is yet another thing I need for BSS purposes. I don't believe this method has any real consumers, yet anyhow: Breaking changes ---------------- `WebRequest.AddFile()` has been altered to provide an API closer to what the HTTP specs intended, and to allow sending multiple files for a given request parameter. To that end: - The signature of the method has changed from ```csharp public void AddFile(string name, byte[] data); ``` to ```csharp public void AddFile(string paramName, byte[] data, string filename = "blob"); ``` - Calling `.AddFile()` with the same first parameter (formerly called `name`, now called `paramName`) twice will result in two files being sent in the request, rather than the second call overwriting the file added by the first one. - If the `filename` parameter is not explicitly specified in the method call, the filename given in the `Content-Disposition` header of the multipart request has changed from being equal to the parameter name to `blob`. This is consistent with JavaScript `FormData` API behaviour. --- osu.Framework/IO/Network/WebRequest.cs | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/osu.Framework/IO/Network/WebRequest.cs b/osu.Framework/IO/Network/WebRequest.cs index 4fff194c84..e3d5a91b48 100644 --- a/osu.Framework/IO/Network/WebRequest.cs +++ b/osu.Framework/IO/Network/WebRequest.cs @@ -119,7 +119,7 @@ public class WebRequest : IDisposable /// /// FILE parameters. /// - private readonly IDictionary files = new Dictionary(); + private readonly List files = new List(); /// /// The request headers. @@ -349,9 +349,9 @@ private async Task internalPerform(CancellationToken cancellationToken = default foreach (var p in files) { - var byteContent = new ByteArrayContent(p.Value); + var byteContent = new ByteArrayContent(p.Content); byteContent.Headers.Add("Content-Type", "application/octet-stream"); - formData.Add(byteContent, p.Key, p.Key); + formData.Add(byteContent, p.ParamName, p.Filename); } postContent = await formData.ReadAsStreamAsync(linkedToken.Token).ConfigureAwait(false); @@ -662,17 +662,21 @@ public void AddRaw(Stream stream) } /// - /// Add a new FILE parameter to this request. Replaces any existing file with the same name. + /// Add a new FILE parameter to this request. /// This may not be used in conjunction with . GET requests may not contain files. /// - /// The name of the file. This becomes the name of the file in a multi-part form POST content. + /// The name of the form parameter of the request that the file relates to. /// The file data. - public void AddFile(string name, byte[] data) + /// + /// The filename of the file to be sent to be reported to the server in the Content-Disposition header. + /// blob is used by default if omitted, to mirror browser behaviour. + /// + public void AddFile(string paramName, byte[] data, string filename = "blob") { - ArgumentNullException.ThrowIfNull(name); + ArgumentNullException.ThrowIfNull(paramName); ArgumentNullException.ThrowIfNull(data); - files[name] = data; + files.Add(new FormFile(paramName, data, filename)); } /// @@ -931,5 +935,7 @@ protected override void Dispose(bool disposing) baseStream.Dispose(); } } + + private record struct FormFile(string ParamName, byte[] Content, string Filename); } }