5179

Indy 10 Multipart upload to OneDrive Error

Question:

I am trying to do <a href="https://dev.onedrive.com/items/upload_post.htm" rel="nofollow">Multipart upload to OneDrive using POST</a> and get 'HTTP/1.1 400 Bad Request'. IdLogFile:

Stat Connected. Sent 10.02.2017 12:50:08: POST /v1.0/drive/root::/children HTTP/1.0`<EOL>`Content-Type: multipart/related; boundary="Boundary"`<EOL>`Content-Length: 254`<EOL>`Authorization: Bearer EwA...%3d`<EOL>`Host: api.onedrive.com`<EOL>`Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8`<EOL>`Accept-Encoding: identity`<EOL>`User-Agent: Mozilla/3.0 (compatible; Indy Library)`<EOL><EOL>` Sent 10.02.2017 12:50:08: --Boundary`<EOL>`Content-ID: <metadata>`<EOL>`Content-Type: application/json`<EOL>`{"name":"Dest.txt", "file":{}, "@content.sourceUrl":"cid:content"}`<EOL>`--Boundary`<EOL>`Content-ID: <content><EOL>Content-Type: application/octet-stream<SourceContent>--Boundary-- Recv 10.02.2017 12:50:08: H Recv 10.02.2017 12:50:08: TTP/1.1 400 Bad Request`<EOL>`Via: 1.1 DM5SCH102210409 (wls-colorado)`<EOL>`Content-Length: 60`<EOL>`Content-Type: application/json`<EOL>`Server: Microsoft-IIS/8.5`<EOL>`P3P: CP="BUS CUR CONo FIN IVDo ONL OUR PHY SAMo TELo"`<EOL>`X-WLSPROXY: DM5SCH102210409`<EOL>`X-MSNSERVER: DM5SCH102231823`<EOL>`Strict-Transport-Security: max-age=31536000; includeSubDomains`<EOL>`X-QosStats: {"ApiId":0,"ResultType":2,"SourcePropertyId":0,"TargetPropertyId":42}`<EOL>`X-ThrowSite: 1479.b891`<EOL>`X-AsmVersion: UNKNOWN; 16.0.0.0`<EOL>`X-MSEdge-Ref: Ref A: A9918FA26FAF469EB3797E9DAEA3172E Ref B: FRAEDGE0409 Ref C: Fri Feb 10 01:50:09 2017 PST`<EOL>`Date: Fri, 10 Feb 2017 09:50:09 GMT`<EOL>`Connection: close`<EOL><EOL>`{"error":{"code":"invalidRequest","message":"Bad Argument"}} Stat Disconnected.

The code:

procedure TSaveFilter.UploadTest; const URL = 'https://api.onedrive.com/v1.0/drive/root::/children'; Boundary = 'Boundary'; var IdHTTP: TIdHTTP; MemoryStream: TMemoryStream; FileStream: TFileStream; procedure WriteLnString(str: AnsiString; CRLF: Boolean = True); begin if CRLF then str := str + #13#10; MemoryStream.Write(str[1], Length(str)); end; 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; IdHTTP.Request.ContentType := Format('multipart/related; boundary="%s"', [Boundary]); MemoryStream := TMemoryStream.Create; try WriteLnString('--' + Boundary); WriteLnString('Content-ID: <metadata>'); WriteLnString('Content-Type: application/json'); WriteLnString('{"name":"Dest.txt", "file":{}, "@content.sourceUrl":"cid:content"}'); WriteLnString('--' + Boundary); WriteLnString('Content-ID: <content>'); WriteLnString('Content-Type: application/octet-stream', False); FileStream := TFileStream.Create('Source.txt', fmOpenRead); try MemoryStream.CopyFrom(FileStream, FileStream.Size); finally FileStream.Free; end; WriteLnString('--' + Boundary + '--', False); IdHTTP.Post(URL, MemoryStream); finally MemoryStream.Free; end; finally IdHTTP.Free; end; end;

What am I doing wrong?

This is a working request (List children):

Stat Connected. Sent 10.02.2017 20:52:42: GET /v1.0/drive/root::/children?select=name,folder,file HTTP/1.1`<EOL>`Authorization: Bearer EwA...%3d`<EOL>`Host: api.onedrive.com`<EOL>`Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8`<EOL>`Accept-Encoding: identity`<EOL>`User-Agent: Mozilla/3.0 (compatible; Indy Library)`<EOL>``<EOL>` Recv 10.02.2017 20:52:43: H Recv 10.02.2017 20:52:43: TTP/1.1 200 OK`<EOL>`Via: 1.1 BN2BAP4ED8CB55D (wls-colorado)`<EOL>`Content-Length: 213`<EOL>`Content-Type: application/json; odata.metadata=minimal`<EOL>`Server: Microsoft-IIS/8.5`<EOL>`P3P: CP="BUS CUR CONo FIN IVDo ONL OUR PHY SAMo TELo"`<EOL>`X-WLSPROXY: BN2BAP4ED8CB55D``X-MSNSERVER: DM5SCH102231619`<EOL>`Strict-Transport-Security: max-age=31536000; includeSubDomains`<EOL>`OData-Version: 4.0`<EOL>`X-AsmVersion: UNKNOWN; 16.0.0.0`<EOL>`X-MSEdge-Ref: Ref A: ECB06A4BE05B478AB36611C892C36CC7 Ref B: AM1EDGE0419 Ref C: Fri Feb 10 09:52:43 2017 PST``Date: Fri, 10 Feb 2017 17:52:43 GMT`<EOL>``<EOL>`{"@odata.context":"https://api.onedrive.com/v1.0/$metadata#drives('me')/items('root')/children(name,folder,file)","value":[{"name":"AB","folder":{"childCount":7}},{"name":"ArecaBackup","folder":{"childCount":6}}]} Stat Disconnected.

Answer1:

Your MIME data is malformed, that is why the server is rejecting it.

This is the request you are sending:

POST /v1.0/drive/root::/children HTTP/1.0 Content-Type: multipart/related; boundary="Boundary" Content-Length: 254 Authorization: Bearer EwA...%3d Host: api.onedrive.com Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Encoding: identity User-Agent: Mozilla/3.0 (compatible; Indy Library) --Boundary Content-ID: <metadata> Content-Type: application/json {"name":"Dest.txt", "file":{}, "@content.sourceUrl":"cid:content"} --Boundary Content-ID: <content> Content-Type: application/octet-stream<SourceContent>--Boundary--

As you can see, the MIME data is all messed up. Specifically, each MIME field is missing some required CRLFs. Just like with the HTTP headers and body, MIME headers and body are separated by a <CRLF><CRLF> sequence, and there needs to be a CRLF between the TFileStream data and the MIME boundary that follows it.

The request needs to look more like this instead:

POST /v1.0/drive/root::/children HTTP/1.0 Content-Type: multipart/related; boundary="Boundary" Content-Length: 260 Authorization: Bearer EwA...%3d Host: api.onedrive.com Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Encoding: identity User-Agent: Mozilla/3.0 (compatible; Indy Library) --Boundary Content-ID: <metadata> Content-Type: application/json {"name":"Dest.txt", "file":{}, "@content.sourceUrl":"cid:content"} --Boundary Content-ID: <content> Content-Type: application/octet-stream <SourceContent> --Boundary--

Try this code to populate the TMemoryStream:

MemoryStream := TMemoryStream.Create; try WriteLnString('--' + Boundary); WriteLnString('Content-ID: <metadata>'); WriteLnString('Content-Type: application/json'); WriteLnString(''); // <-- ADD THIS!!! WriteLnString('{"name":"Dest.txt", "file":{}, "@content.sourceUrl":"cid:content"}'); WriteLnString('--' + Boundary); WriteLnString('Content-ID: <content>'); WriteLnString('Content-Type: application/octet-stream'); // <-- REMOVE THE FALSE!!! WriteLnString(''); // <-- ADD THIS!!! FileStream := TFileStream.Create('Source.txt', fmOpenRead); try MemoryStream.CopyFrom(FileStream, 0); finally FileStream.Free; end; WriteLnString(''); // <!-- ADD THIS!!! WriteLnString('--' + Boundary + '--', False); ... finally MemoryStream.Free; end;

That being said, Indy has a TIdMultipartFormDataStream class that is typically used with TIdHTTP when sending multipart/form-data posts. OneDrive does not support multipart/form-data, but what is odd is that OneDrive's documentation clearly specifies the following, which only applies to multipart/form-data and not to multipart/related:

<blockquote>

The request will be rejected if more than two parts are included. <strong>Each part must specify a name value in the Content-Disposition header that indicates which part it is</strong>. Parts can be in either order, but should specify the metadata part first.

</blockquote>

However, the example given in the same documentation is using multipart/related, just like your code is. There are discussions in Microsoft/OneDrive forums and various blogs regarding whether to use multipart/form-data or multipart/related when uploading to OneDrive. One OneDrive employee did confirm this issue needs some work on their end.

Just in case OneDrive ever supports multipart/form-data, here is a example using TIdMultipartFormDataStream:

procedure TSaveFilter.UploadTest; const URL = 'https://api.onedrive.com/v1.0/drive/root::/children'; var IdHTTP: TIdHTTP; PostData: 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; PostData := TIdMultipartFormDataStream.Create; try PostData.AddFormField('metadata', '{"name":"Dest.txt", "file":{}}', 'utf-8', 'application/json'); PostData.AddFile('content', 'Source.txt', 'application/octet-stream').FileName := ''; IdHTTP.Post(URL, PostData); finally PostData.Free; end; finally IdHTTP.Free; end; end;

Recommend

  • Set Mechanize to accept cookies
  • Collation conflict SQL Server 2008
  • Hide Flask-Admin route
  • Any reason why an std::ofstream object won't close properly?
  • TASM Print characters of string
  • Why does my stored procedure throw error 'No data - zero rows fetched, selected, or processed&#
  • Restoring Mysql dump from java: why does it hang the process?
  • plot phylogenetic logistic regression with binary response variable
  • How to export MS Access table into a csv file in Python using e.g. pypyodbc
  • How to convert single element tuple into string?
  • Remote debugging of a Java application launched as a Windows service
  • How can I do a 301 redirect from http to https in Wildfly 8.2?
  • Exclusive access established by another Thread Java smartcardio
  • How to prevent cross domain issues by proxying in IIS?
  • problem with cross-domain ajax calls
  • Show HTML user input, security issue
  • GWT Toolkit: preprocessing files on client side
  • CHAR vs VARCHAR for password security
  • .NET Core 2.0 RSA PlatformNotSupportedException
  • Success handler not working after Symfony2 login
  • how to automatically enter password when using ssh?
  • DotNetOpenAuth - how to uniquely identify Google users?
  • Installing apk from within application in android
  • Paramiko SSHException Channel Closed
  • Unique SMS sender id?
  • 3.0.0.M1: SSL - Invalid keystore format
  • Convert RSA pem key String to der byte[]
  • How secure are apple APNS push notifications?
  • File loader changed image file name but not the file name in HTML file
  • Local Development, Apache vs Developer - file permissions
  • Embedded Glassfish JPA Datasource connection fail
  • HttpListener.IsSupported is false on XP SP3
  • Enabling DTD support in Sql Server
  • GAE: Way to get reference to an HttpSession from its ID?
  • Redirect STDERR in OPEN pipe comand. Perl Linux
  • Does Mobilefirst provide a provision to access web services directly?
  • MongoError: Incorrect arguments
  • Read a local file using javascript
  • What is the “return” in scheme?
  • What do the 'size' numbers mean in the windbg !heap output?