CloudinaryDotNet icon indicating copy to clipboard operation
CloudinaryDotNet copied to clipboard

Cloudinary.Upload() throws a FormatException

Open galenmolk opened this issue 4 years ago • 2 comments

Hi, hope you're doing well.

I have the Cloudinary .NET SDK integrated with a Unity project. I've placed the CloudinaryDotNet DLL, as well as all of its DLL dependencies, inside my project. I'm able to access all the Cloudinary namespaces/classes/etc. from my code, which is fantastic.

Unfortunately, when I attempt a simple implementation of Cloudinary.Upload, I get a FormatException. Here's the problem code:

public void UploadImage()
{
      ImageUploadParams uploadParams = new ImageUploadParams
      {
            File = new FileDescription("https://www.fnordware.com/superpng/pnggrad16rgb.png"), // Just a simple gradient image for testing
      };

      cloudinary.Upload(uploadParams); // FormatException is traced back to this line
}

Before the above method is called in my program, I'm setting my configuration parameters as outlined in the documentation, like this (with my actual info substituted):

Account account = new Account(
    "my_cloud_name",
    "my_api_key",
    "my_api_secret");

Cloudinary cloudinary = new Cloudinary(account);

Here are the package versions I'm using:

  • Cloudinary .NET SDK: 1.15.2
  • Microsoft.AspNetCore.Html.Abstractions: 2.2.0
  • Microsoft.AspNetCore.Http.Abstractions: 2.2.0
  • Microsoft.AspNetCore.Http.Features: 2.2.0
  • Microsoft.Extensions.DependencyInjection.Abstractions: 5.0.0
  • Microsoft.Extensions.Options: 5.0.0
  • Microsoft.Extensions.Primitives: 5.0.1
  • Microsoft.Extensions.WebEncoders: 5.0.7
  • System.Buffers: 4.5.1
  • System.IO.Pipelines: 5.0.1
  • System.Memory: 4.5.4
  • System.Numerics.Vectors: 4.5.0
  • System.Runtime.CompilerServices.Unsafe: 5.0.0
  • System.Text.Encodings.Web: 5.0.1
  • System.Threading.Tasks.Extensions: 4.5.4

Below is the FormatException that I'm getting:

FormatException: One of the identified items was in an invalid format.

System.Net.Http.Headers.HttpHeaders.AddInternal (System.String name, System.Collections.Generic.IEnumerable`1[T] values, System.Net.Http.Headers.HeaderInfo headerInfo, System.Boolean ignoreInvalid) (at <efff4cb93af94c0c950db61b78368b54>:0)
System.Net.Http.Headers.HttpHeaders.Add (System.String name, System.Collections.Generic.IEnumerable`1[T] values) (at <efff4cb93af94c0c950db61b78368b54>:0)
System.Net.Http.Headers.HttpHeaders.Add (System.String name, System.String value) (at <efff4cb93af94c0c950db61b78368b54>:0)
CloudinaryDotNet.ApiShared.PrePrepareRequestBody (System.Net.Http.HttpRequestMessage request, CloudinaryDotNet.HttpMethod method, System.Collections.Generic.Dictionary`2[TKey,TValue] extraHeaders) (at <0c3884533d824ad98e55170b6aba31df>:0)
CloudinaryDotNet.ApiShared+<PrepareRequestBodyAsync>d__75.MoveNext () (at <0c3884533d824ad98e55170b6aba31df>:0)

--- End of stack trace from previous location where exception was thrown ---

System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw () (at <695d1cc93cca45069c528c15c9fdd749>:0)
System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess (System.Threading.Tasks.Task task) (at <695d1cc93cca45069c528c15c9fdd749>:0)
System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification (System.Threading.Tasks.Task task) (at <695d1cc93cca45069c528c15c9fdd749>:0)
System.Runtime.CompilerServices.TaskAwaiter.ValidateEnd (System.Threading.Tasks.Task task) (at <695d1cc93cca45069c528c15c9fdd749>:0)
System.Runtime.CompilerServices.ConfiguredTaskAwaitable`1+ConfiguredTaskAwaiter[TResult].GetResult () (at <695d1cc93cca45069c528c15c9fdd749>:0)
CloudinaryDotNet.ApiShared+<CallAsync>d__58.MoveNext () (at <0c3884533d824ad98e55170b6aba31df>:0)

--- End of stack trace from previous location where exception was thrown ---

System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw () (at <695d1cc93cca45069c528c15c9fdd749>:0)
System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess (System.Threading.Tasks.Task task) (at <695d1cc93cca45069c528c15c9fdd749>:0)
System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification (System.Threading.Tasks.Task task) (at <695d1cc93cca45069c528c15c9fdd749>:0)
System.Runtime.CompilerServices.TaskAwaiter.ValidateEnd (System.Threading.Tasks.Task task) (at <695d1cc93cca45069c528c15c9fdd749>:0)
System.Runtime.CompilerServices.ConfiguredTaskAwaitable`1+ConfiguredTaskAwaiter[TResult].GetResult () (at <695d1cc93cca45069c528c15c9fdd749>:0)
CloudinaryDotNet.ApiShared+<CallAndParseAsync>d__56`1[T].MoveNext () (at <0c3884533d824ad98e55170b6aba31df>:0)

--- End of stack trace from previous location where exception was thrown ---

System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw () (at <695d1cc93cca45069c528c15c9fdd749>:0)
System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess (System.Threading.Tasks.Task task) (at <695d1cc93cca45069c528c15c9fdd749>:0)
System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification (System.Threading.Tasks.Task task) (at <695d1cc93cca45069c528c15c9fdd749>:0)
System.Runtime.CompilerServices.TaskAwaiter.ValidateEnd (System.Threading.Tasks.Task task) (at <695d1cc93cca45069c528c15c9fdd749>:0)
System.Runtime.CompilerServices.TaskAwaiter`1[TResult].GetResult () (at <695d1cc93cca45069c528c15c9fdd749>:0)
CloudinaryDotNet.Cloudinary.Upload[T,TP] (TP parameters) (at <0c3884533d824ad98e55170b6aba31df>:0)
CloudinaryDotNet.Cloudinary.Upload (CloudinaryDotNet.Actions.ImageUploadParams parameters) (at <0c3884533d824ad98e55170b6aba31df>:0)
CloudinaryUploader.UploadImage () (at Assets/Scripts/_general/Cloudinary/CloudinaryUploader.cs:28)

I'm working on a Mac inside Unity 2021.1.3f1, which can use either .NET Standard 2.0 or .NET 4.x. Both APIs produce the above error.

Please let me know if there's any additional information I can provide.

Thank you so much. I would really appreciate any insight anyone might have.

All the best, Galen

galenmolk avatar Jun 17 '21 21:06 galenmolk

In case anyone else working inside Unity comes across this same problem, I'll try to detail what I did to fix this here.

The FormatException error was being thrown from the method PrePrepareRequestBody, on line 450 of ApiShared.Internal.cs:

request.Headers.Add("User-Agent", userPlatform);

The issue was the userPlatform parameter. This is created from the static string ApiInternal.USER_AGENT. USER_AGENT is built on line 809 of ApiShared.cs in BuildUserAgent. And this in turn uses System.Runtime.InteropServices.RuntimeInformation.FrameworkDescription to get the name of your .NET installation.

When working inside Unity, FrameworkDescription returns the following string:

Mono 5.11.0 ((HEAD/768f1b247c6)

For some incredibly unhelpful reason, there is a closing parentheses missing from the end of that string, which was making HTTP reject the Header parameter.

I fixed this by adding an additional closing parentheses on the end of the string returned inside ApiInternal.BuildUserAgent. So that method now looks like:

private static string BuildUserAgent()
{
    return $"CloudinaryDotNet/{CloudinaryVersion.Full} ({RuntimeInformation.FrameworkDescription}))";
}

Hopefully this sort of direct meddling with the User-Agent string doesn't negatively impact me in some other way. If anyone has any cautionary advice on this, I'd love to hear your thoughts.

Thanks everyone.

galenmolk avatar Jun 20 '21 12:06 galenmolk

@galenmolk , thank you for reporting this issue! It is a bit weird bug. We'll fix it by encoding a header value, so it will be safe regardless of the amount of parenthesis or other unsafe characters.

const-cloudinary avatar Jun 20 '21 14:06 const-cloudinary