5

I am POST-ing an image with HttpClient and it works well for files with Latin names, but as soon as a name contains any non-ASCII characters it gets transformed to a sequence of question marks. If I create an html form and use a browser to post the file, the file name is sent in UTF8 and the target server perfectly accepts it.

using (var client = new HttpClient())
{
    var streamContent = new StreamContent(someImageFileStream);
    streamContent.Headers.Add(
        "Content-Disposition",
        "form-data; name=\"image\"; filename=\"Тест.jpg\"");

    var content = new MultipartFormDataContent();
    content.Add(streamContent);

    await client.PostAsync("http://localhost.fiddler/", content);
}

This produces the following request:

POST http://localhost/ HTTP/1.1
Content-Type: multipart/form-data; boundary="e6fe89be-e652-4fe3-8859-8c7a339c5550"
Host: localhost
Content-Length: 10556

--e6fe89be-e652-4fe3-8859-8c7a339c5550
Content-Disposition: form-data; name="image"; filename="????.jpg"

...here goes the contents of the file...

I understand that HttpClient might work according to some standard, but anyway, is there any workaround?

UPDATE: The external API doesn't want to accept the format filename*=utf-8''Тест.jpg, it expects filename="Тест.jpg".

Monsignor
  • 2,671
  • 1
  • 36
  • 34

4 Answers4

8

This is another way to workaround the limitation of HttpClient without tampering with internal fields. Inspired by this answer.

using (var client = new HttpClient())
{
    var streamContent = new StreamContent(someImageFileStream);
    streamContent.Headers.Add("Content-Disposition",
        new string(Encoding.UTF8.GetBytes("form-data; name=\"image\"; filename=\"Тест.jpg\"").
        Select(b => (char)b).ToArray()));

    var content = new MultipartFormDataContent();
    content.Add(streamContent);
    await client.PostAsync("http://localhost.fiddler/", content);
}

I confirm that even .net core 2.2 doesn't have proper support for uploading files whose names contain non-ASCII characters. HttpClient does work according to some standard but Java servers don't care about that standard and expect UTF-8 formatted headers.

idilov
  • 495
  • 6
  • 13
  • 3
    "even .net core 2.2 doesn't have proper support" MultipartFormDataContent\StreamContent seems to works 100% by the spec (RFC's) afaics. The problem is that no one else seems to, not even Microsofts old stuff like HttpRequest\HttpPostedFile. I think Microsoft should have given IETF the finger on this one and just encoded filename as plain utf8. – osexpert Jul 08 '19 at 19:37
  • I've banged my head for a whole day on this problem (with .Net 6). My understanding of the problem is somewhat limited, but HttpClient's default behaviour of transforming the file's name to RFC 1342 (=?utf-8?B?...) isn't handled by the external API my app is calling. Your solution works perfectly. Thanks a lot! – TheMSG Sep 24 '22 at 15:20
2

OK, I've found a way to force MultipartFormDataContent to forget the ancient RFCs and use UTF8 instead. The trick is to use reflection to overwrite the DefaultHttpEncoding defined in the internal static class HttpRuleParser.

typeof(HttpClient)
  .Assembly
  .GetType("System.Net.Http.HttpRuleParser")
  .GetField("DefaultHttpEncoding", BindingFlags.Static | BindingFlags.NonPublic)
  .SetValue(null, System.Text.Encoding.UTF8);

Not sure which bad consequences that might cause, but I suppose there are none.

Monsignor
  • 2,671
  • 1
  • 36
  • 34
1

Instead of adding a header that you built yourself, use the .NET library:

streamContent.Headers.ContentDisposition = 
    new System.Net.Http.Headers.ContentDispositionHeaderValue("form-data") { 
        Name = "image", 
        FileName = "Тест.jpg" };

That creates the header per the web docs and RFC5987.

Content-Disposition: form-data; name=image; filename="=?utf-8?B?0KLQtdGB0YIuanBn?="
Community
  • 1
  • 1
Tom Blodget
  • 20,260
  • 3
  • 39
  • 72
1

If it helps, you can also remove the "filename*"

//It deletes filename* parametr
foreach (var content in multipartContent) {
   var headerContent = content.Headers.ContentDisposition.Parameters.Where(x => x.Name == "filename*").SingleOrDefault();
   if(headerContent != null)
      content.Headers.ContentDisposition.Parameters.Remove(headerContent);
}