Search code examples
cordovaphonegap-buildios8.4

Cordova FileUploadOptions params not posted on iOS


Using an app built using build.phonegap.com I'm finding that the FileUploadOptions.params values aren't being posted - iOS (8.4.1) only posts the file multipart data. This isn't a server side error as I've watched the POST using tcpdump/Wireshark and netcat.

Using:

  • PhoneGap v.3.7.0
  • Ionic Framework v.1.0.1
  • AngularJS v.1.3.13

This is the code that is used to upload a test file. It generates a temporary file and attempts to upload it. The question is, have I mucked something up, or is this a bona fide bug?

  $scope.doUploadTest = function() {
    var q = $q.defer();
    var promise = q.promise;

    var text = "Test file content: " + new Date();

    $window.requestFileSystem(LocalFileSystem.TEMPORARY, text.length, function(fs) {
      q.resolve(fs);
    }, function(err) {
      q.reject(err);
    });

    promise
    .then(function(fs) {
      var qFile = $q.defer();

      fs.root.getFile("upload.tmp", {create: true, exclusive: false}, function(entry) {
        qFile.resolve(entry);
      }, function(err) {
        qFile.reject(err);
      });

      return qFile.promise;
    })
    .then(function(entry) {
      console.log("got file: " + entry.nativeURL);

      var qWriter = $q.defer();

      entry.createWriter(function(writer) {
        qWriter.resolve({writer: writer, entry: entry});
      }, function(err) {
        qWriter.reject(err);
      });

      return qWriter.promise;
    })
    .then(function(response) {
      var qWrite = $q.defer();

      response.writer.onwriteend = function(evt) {
        qWrite.resolve(response.entry);
      };

      response.writer.write(text);

      return qWrite.promise;
    })
    .then(function(entry) {
      $scope.error = entry;

      var qUpload = $q.defer();

      var options = new FileUploadOptions();
      options.params = {
        Value: {
          list: [1, 2, 3],
          text: "foo bar baz",
          number: 1234
        }
      };
      options.fileKey = "thefile";
      options.fileName = entry.name;
      options.mimeType = "text/plain";
      options.chunkedMode = false;
      options.headers = {
        "X-Upload": (new Date()).toString()
      };


      var ft = new FileTransfer();
      ft.upload(entry.nativeURL, "http://192.168.0.30:9100/", function(success) {
        entry.remove();
        qUpload.resolve(success);
      }, function(err) {
        entry.remove();
        qUpload.reject(err);
      }, options);

      // Abort after 3 seconds
      $timeout(function() {
        console.log("Aborting upload - timed out");
        ft.abort();
      }, 3000);

      return qUpload.promise;
    })
    .then(function(response) {
      console.log("Upload succeeded");
      $scope.error = response;
    })

    .catch(function(err) {
      console.log("Upload failed");
      $scope.error = err;
    });
    ;


  };

The request made on an iPhone 6 (iOS v.8.4.1) is as follows:


    POST / HTTP/1.1
    Host: 192.168.0.30:9100
    Content-Type: multipart/form-data; boundary=+++++org.apache.cordova.formBoundary
    Accept-Encoding: gzip, deflate
    X-Upload: Tue Sep 01 2015 12:12:49 GMT+0100 (BST)
    Connection: keep-alive
    Accept: */*
    User-Agent: Mozilla/5.0 (iPhone; CPU iPhone OS 8_4_1 like Mac OS X) AppleWebKit/600.1.4 (KHTML, like Gecko) Mobile/12H321 (5348809648)
    Content-Length: 261
    Accept-Language: en-us
    X-Requested-With: XMLHttpRequest

    --+++++org.apache.cordova.formBoundary
    Content-Disposition: form-data; name="thefile"; filename="upload.tmp"
    Content-Type: text/plain
    Content-Length: 58

    Test file content: Tue Sep 01 2015 12:12:49 GMT+0100 (BST)
    --+++++org.apache.cordova.formBoundary--

On a Motorola Moto X (2nd gen, Android 5.1) the request is this:


    POST / HTTP/1.1
    Content-Type: multipart/form-data; boundary=+++++
    X-Upload: Tue Sep 01 2015 12:52:24 GMT+0100 (BST)
    User-Agent: Dalvik/2.1.0 (Linux; U; Android 5.1; XT1092 Build/LPE23.32-25.1)
    Host: 192.168.0.30:9100
    Connection: Keep-Alive
    Accept-Encoding: gzip
    Content-Length: 289

    --+++++
    Content-Disposition: form-data; name="Value"

    {"list":[1,2,3],"text":"foo bar baz","number":1234}
    --+++++
    Content-Disposition: form-data; name="thefile"; filename="upload.tmp"
    Content-Type: text/plain

    Test file content: Tue Sep 01 2015 12:52:23 GMT+0100 (BST)
    --+++++--


Solution

  • It turns out that the params object can only contain literal properties, and not objects. The documentation only uses string values, but doesn't explicitly say that objects are unsupported.

    This is further complicated by the fact that on Android the plugin serialises the object as a JSON string, whereas on iOS the plugin silently dumps it.

    So, the solution is simply to serialise the data first:

      var options = new FileUploadOptions();
      options.params = {
        Value: JSON.stringify({
          list: [1, 2, 3],
          text: "foo bar baz",
          number: 1234
        })
      };