Multipart/form-data field names are encoded incorrectly

I’m using Mailgun’s REST API in a Cloudflare Worker.

The API requires a POST of multipart/form-data. Some of the API’s form fields have a colon “:” character in their name:
https://documentation.mailgun.com/en/latest/api-sending.html#sending

My code looks something like this:

const body = new FormData();
body.set('v:preheader', 'lorem ipsum bla bla');
const response = await fetch(`https://api.mailgun.net/v3/...`, { body });

In chrome the resulting request body looks something like this:

------WebKitFormBoundaryGuTQLSUYrRarMoDx
Content-Disposition: form-data; name="v:preheader"

In a worker it looks like this, note the %3A instead of a “:” character. This is incompatible with the Mailgun API.

--959c166acc39ec6ebaee689c385304e1
Content-Disposition: form-data; name="v%3Apreheader"

The spec is a bit ambiguous in this area… “:” is an ascii character so it should not be encoded per section 5.1:
https://tools.ietf.org/html/rfc7578#section-5.1

Workaround for anyone who needs it:

  // https://community.cloudflare.com/t/multipart-form-data-field-names-are-encoded-incorrectly/187890
  // Workaround:
  const brokenRequest = new Request('https://example.com', {
    method: 'POST',
    body: formData // <-- instance of FormData
  });
  // Get the content-type header (for the boundary).
  const contentType = brokenRequest.headers.get('content-type')!;
  // Get the body text.
  const bodyText = await brokenRequest.text();
  // Repair the body text.
  const body = bodyText.replace(
    /Content-Disposition: form-data; name="v%3A/g,
    'Content-Disposition: form-data; name="v:'
  );
  // Create a new request using the extracted content type and repaired body text.
  const request = new Request(url.href, {
    method: 'POST',
    headers: {
      Authorization: `Basic ${btoa(`api:${apiKey}`)}`,
      'Content-Type': contentType
    },
    body
  });
  const response = await fetch(request);

Hi @jdanyow, I think this is a bug. Our intent with percent-encoding form-data part names is to create a name that is safe for the recipient’s parser. However, that might just require us to encode quotation marks, and we should probably match Chrome’s behavior. I’ve filed a ticket to fix it, though I’m not sure when we’ll be able to do so.

1 Like

@harris sounds good, thanks!