Search code examples
delphiindydelphi-2009boxindy10

Indy 10 + XE8 multipart upload with long filename


I am trying to do multipart upload to Box.com using TIdMultipartFormDataStream. While file name is up to '\u0424\u042B\u0412\u0410\u041F.txt' it works OK, but when it longer ('\u0424\u042B\u0412\u0410\u041F\u0420.txt') it causes 'HTTP/1.1 400 Bad Request'.

Is there some limitation for FormField.FFieldValue length? If so, is there any way to bypass it?

procedure TBoxComSaveFilter.UploadTest;
const
  URL = 'https://upload.box.com/api/2.0/files/content';
var
  IdHTTP: TIdHTTP;
  MD: TIdMultipartFormDataStream;
begin
  IdHTTP := TIdHTTP.Create(nil);
  try
    IdHTTP.HandleRedirects := True;
    IdHTTP.IOHandler := TIdSSLIOHandlerSocketOpenSSL.Create(IdHTTP);
    IdHTTP.Request.BasicAuthentication := False;
    IdHTTP.Request.CustomHeaders.Values['Authorization'] := 'Bearer ' + FAccessToken;
    MD := TIdMultipartFormDataStream.Create;
    try
      MD.AddFormField('metadata', '{"name": "' +
        '\u0424\u042B\u0412\u0410\u041F.txt' +       // => OK
//        '\u0424\u042B\u0412\u0410\u041F\u0420.txt' + // => 400 Bad Request
        '", "parent": {"id": "0"}}', '', 'application/json');

      MD.AddFile('content', 'source.txt', 'application/octet-stream');
      IdHTTP.Post(URL, MD);
    finally
      MD.Free;
    end;
  finally
    IdHTTP.Free;
  end;
end;

A partial log for short filename:

Sent 20.02.2017 21:16:26: ----------022017211625520
Content-Disposition: form-data; name="metadata"
Content-Type: application/json
Content-Transfer-Encoding: quoted-printable

{"name": "\u0424\u042B\u0412\u0410\u041F.txt", "parent": {"id": "0"}}
----------022017211625520

The same part for long filename:

Sent 20.02.2017 21:17:48: ----------022017211748412
Content-Disposition: form-data; name="metadata"
Content-Type: application/json
Content-Transfer-Encoding: quoted-printable

{"name": "\u0424\u042B\u0412\u0410\u041F\u0420.txt", "parent": {"id": =
"0"}}
----------022017211748412

As I can see the data split after 70 byte by "=CRLF".


Solution

  • The default transfer encoding for text fields is MIME's quoted-printable format. It would seem Box does not like that format.

    AddFormField() returns a TIdFormDataField, which has a ContentTransfer property you can set it to 8bit or binary to send the JSON text as-is (after it is charset encoded to bytes, that is):

    MD.AddFormField('metadata', '{"name": "ФЫВАПР.txt", "parent": {"id": "0"}}', 'utf-8', 'application/json').ContentTransfer := '8bit';
    

    Alternatively, put your JSON in a TStream, like TStringStream or TMemoryStream, and then use the TStream overload of AddFormField(), which sets the ContentTransfer to binary by default:

    JsonStream := TStringStream.Create('{"name": "ФЫВАПР.txt", "parent": {"id": "0"}}', TEncoding.UTF8);
    try
      MD.AddFormField('metadata', 'application/json', 'utf-8', JsonStream);
      // send the post...
    finally
      JsonStream.Free;
    end;