Uploading file with content and metadata to Google Docs returns 400 Invalid/Bad Request

Go To StackoverFlow.com

1

I am attempting to upload a file from an HTML5 file dialog to my Google Docs account.

I am using the Google Documents List API version 3.0 for this request, and the instructions at this link.

I gather the correct resumable-create-link URL from the Documents List JSON after a query, and POST an XML entity to that link to obtain the location URL. I get a 200 OK response and the location URL.

After setting the headers, I make a PUT request to the location URL to upload the file. For the purposes of this question, the file is smaller than 512kb, and does not need to be chunked.

After submitting this PUT request, I receive a 400 Bad Request (sometimes 400 Invalid Request) response and the file is not uploaded.

Interestingly, if I omit the Content-Range header, I can upload a small file, but files other than text files are corrupted.

I have done a binary compare of the file loaded in memory and the local file. It appears that they match.

To access the API, I am using Javascript inside of a Google Gadget with http://www.google.com/jsapi.

I GET, POST, and PUT data using gadgets.io.makeRequest. My authorization header and token seem correct given that I am able to query for a list of documents with no issue.

What steps should I take to resolve this issue?

Sample Output

PUT /feeds/upload/create-session/default/private/full?v=3.0&convert=false&upload_id=[id]

Host: docs.google.com
X-Shindig-AuthType: oauth
Authorization: OAuth oauth_body_hash="x", opensocial_owner_id="x", opensocial_viewer_id="x", opensocial_app_id="x", opensocial_app_url="x", xoauth_signature_publickey="x", xoauth_public_key="x", oauth_version="1.0", oauth_timestamp="x", oauth_nonce="x", opensocial_container="x", oauth_token="x", oauth_consumer_key="x", oauth_signature_method="RSA-SHA1", oauth_signature="x"
Content-Length: 433
Content-Range: bytes 0-433/433
Content-Type: application/x-javascript
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.79 Safari/535.11,gzip(gfe)
X-Forwarded-For: 74.203.139.139
X-shindig-dos: on
X-Upload-Content-Length: 433
X-Upload-Content-Type: application/x-javascript

[bytes 0-433]

==== Received response 1:
HTTP/1.1 400



Cache-Control: no-cache, no-store, must-revalidate

Content-Length: 15

Content-Type: text/html; charset=UTF-8

Date: Thu, 05 Apr 2012 19:49:48 GMT

Expires: Fri, 01 Jan 1990 00:00:00 GMT

Pragma: no-cache

Server: HTTP Upload Server Built on Apr 2 2012 11:47:06 (1333392426)

Via: HTTP/1.1 GWA

X-Google-Cache-Control: remote-fetch



Invalid Request


====
  errors: 400 Error

Edit

Binary code handling data:

    // -- File Upload functions -- 
    function constructContent(docTitle) {
        var atom = ['<?xml version=\"1.0\" encoding=\"UTF-8"?>',
                    '<entry xmlns=\"http://www.w3.org/2005/Atom\" xmlns:docs=\"http://schemas.google.com/docs/2007\">',
                    '<title>' + docTitle + '</title>',
                    '</entry>'].join(''); 

        return atom;    
    }

    function writeFile(file, under512) {
        var body = constructContent(file.name.toString()); 
        var params = {};

        console.log(body);

        params[gadgets.io.RequestParameters.AUTHORIZATION] = gadgets.io.AuthorizationType.OAUTH;
        params[gadgets.io.RequestParameters.OAUTH_SERVICE_NAME] = "google";
        params[gadgets.io.RequestParameters.OAUTH_USE_TOKEN] = "always";
        params[gadgets.io.RequestParameters.METHOD] = gadgets.io.MethodType.POST;
        params[gadgets.io.RequestParameters.POST_DATA] = body; 
        params[gadgets.io.RequestParameters.HEADERS] = { 
            "Content-Type" : "application/atom+xml", 
            "Content-Length" : "359",
            "X-Upload-Content-Type" : file.type.toString(),
            "X-Upload-Content-Length" : file.size.toString()
        };

        var data; 
        submitRequest(resumableLink + '&convert=false', params, function(requestSuceeded, data) { 
            if ( data.rc == 200 ) {
                console.log(data);
                console.log(data.headers.location[0].toString());

                if ( under512 == true ) { 
                    continueFile(data.headers.location[0].toString(), file, ('0-' + file.size + '/' + file.size).toString(), under512, params);
                }

                else {
                    continueFile(data.headers.location[0].toString(), file, ('0-524287/' + file.size).toString(), under512, params);
                }
            }
        }); 
    }

    // recursive
    function continueFile(location, file, contentRange, under512) { 
        console.log('location: ' + location);

        var contentLength = "0"; 
        var reader = new FileReader(); 
        var blob; 

        var start = contentRange.split('-')[0].toString();
        var stop = contentRange.split('-')[1].split('/')[0].toString(); 

        console.log(file.size);
        console.log(file.type);

        console.log('start: ' + start);
        console.log('stop: ' + stop);

        console.log(("bytes " + contentRange).toString());

        console.log(contentRange);

        ( under512 == true ) ? contentLength = contentRange.split('/')[1].toString() : contentLength = "524288"; 
        if ( file.webkitSlice ) {
            blob = file.webkitSlice(start, stop+1);
        }

        else {
            blob = file.mozSlice(start, stop+1); 
        }

        reader.readAsBinaryString(blob); 


        reader.onloadend = function(evt) {
            if ( evt.target.readyState == FileReader.DONE ) { 
                console.log(evt.target.result);

                var b = showResult(reader);
                // console.log('binary: ' + b); 

                var params = {}; 

                params[gadgets.io.RequestParameters.AUTHORIZATION] = gadgets.io.AuthorizationType.OAUTH;
                params[gadgets.io.RequestParameters.OAUTH_SERVICE_NAME] = "google";
                params[gadgets.io.RequestParameters.OAUTH_USE_TOKEN] = "always";
                params[gadgets.io.RequestParameters.METHOD] = gadgets.io.MethodType.PUT; 
                params[gadgets.io.RequestParameters.POST_DATA] = evt.target.result; 
                params[gadgets.io.RequestParameters.HEADERS] = {
                    "Content-Length" : (contentLength).toString(),
                    "Content-Type" : file.type.toString(),
                    "Content-Range" : ("bytes " + contentRange).toString().trim()
                    // "GData-Version" : "3.0"
                }; 


                submitRequest(location.toString().trim(), params, function(requestSucceeded, data) { 
                    if ( data.rc == 308 ) { 
                        var newStart = start + 524288;  
                        var newEnd; 
                        ( end + 524288 > file.size ) ? newEnd = file.size : newEnd = end + 524288; 
                        var range = (newStart + '-' + newEnd + '/' + file.size).toString().trim(); 
                        continueFile(data.headers.location.toString().trim(), file, range, under512); 
                    }

                    else if ( data.rc == 201 ) { 
                        console.log('done!'); 
                    }

                    else {
                        console.log('terrible error.');
                        console.log(data); 
                        writeObj(data);
                    }
                });
            }
        }; 


    }

    function uploadFile() {
        var file = document.getElementById('uploads').files[0];
        var under512 = false; 
        ( file.size < 524287 ) ? under512 = true : under512 = false; 
        writeFile(file, under512); 
    }
2012-04-05 20:28
by Paul McLain
Can you show your code handling the binary data - Ali Afshar 2012-04-05 20:58
Code added. I am using the HTML5 File API to obtain the raw data - Paul McLain 2012-04-05 21:06
You may as well add all the code - Ali Afshar 2012-04-05 21:10
What are you missing - Paul McLain 2012-04-05 21:12
Why is // "GData-Version" : "3.0" commented out - Ali Afshar 2012-04-06 12:46
Because I'm getting a URL with the v3 link already - Paul McLain 2012-04-13 14:46
I would always suggest using the header if you can - Ali Afshar 2012-04-16 20:14
Did you finally find a solution - ashishb 2014-10-01 00:07


0

As described in the docs the continuation requests don't have the X-Upload-Content-* headers, and should look more like:

PUT [next location]
Content-Length: 524288
Content-Type: application/pdf
Content-Range: bytes 0-524287/1073741824

Additionally, in your log 0-433 is 434 bytes, not 433.

2012-04-05 20:57
by Ali Afshar
Removing the X-Upload-Content-* headers did not seem to help; I get the same 400 Bad Request response. Thank you, though - Paul McLain 2012-04-05 21:03
Updated answer, can you check your lengths - Ali Afshar 2012-04-05 21:13
Just checked that. A text file under 512kb returned a 308, while an image file under 512kb returned a 400 - Paul McLain 2012-04-05 21:21
Ads