2

If I dynamically insert a form object into a page, submit and remove the form and it works fine.

Here is an example of the form code:

<form target="_blank" enctype="multipart/form-data" 
      action="https://www.example.com/" method="POST">
  <input value="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==" name="image_content" type="hidden">
  <input value="" name="filename" type="hidden">
  <input value="" name="image_url" type="hidden">
</form>

When I try to do the same process with loadOneTab(), result POST is not exactly the same and therefore the result is not the same as above.
On checking the headers, "some value" is not sent fully (gets cropped) and it sets Content-Length: 0.
I must be missing something.

let postStream = Components.classes['@mozilla.org/network/mime-input-stream;1']
                   .createInstance(Components.interfaces.nsIMIMEInputStream);
postStream.addHeader('Content-Type', 'multipart/form-data');
postStream.addHeader('filename', '');
postStream.addHeader('image_url', '');
postStream.addHeader('image_content', 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==');
postStream.addContentLength = true;
window.gBrowser.loadOneTab('https://www.example.com/',
    {inBackground: false, postData: postStream});

Note: image_content value is 'data:image/png;base64' Data URI
NoScript causes issues with sending form and XSS and I prefer to use loadOneTab for the inBackground

erosman
  • 7,094
  • 7
  • 27
  • 46

1 Answers1

3

Normally, one would use FormData to compose the postData of a request, but unfortunately we cannot do so here, as there is currently no way to get the stream (and other information) from a FormData instance (nsIXHRSendable is not scriptable, unfortunately), so we'll have to create a multipart/form-data stream ourselves.

Since it is likely you'll want to post some file data as well, I added file uploads as well. ;)

function encodeFormData(data, charset) {
  let encoder = Cc["@mozilla.org/intl/saveascharset;1"].
                createInstance(Ci.nsISaveAsCharset);
  encoder.Init(charset || "utf-8",
               Ci.nsISaveAsCharset.attr_EntityAfterCharsetConv + 
               Ci.nsISaveAsCharset.attr_FallbackDecimalNCR,
               0);
  let encode = function(val, header) {
    val = encoder.Convert(val);
    if (header) {
      val = val.replace(/\r\n/g, " ").replace(/"/g, "\\\"");
    }
    return val;
  }

  let boundary = "----boundary--" + Date.now();
  let mpis = Cc['@mozilla.org/io/multiplex-input-stream;1'].
             createInstance(Ci.nsIMultiplexInputStream);
  let item = "";
  for (let k of Object.keys(data)) {
    item += "--" + boundary + "\r\n";
    let v = data[k];
    if (v instanceof Ci.nsIFile) {
      let fstream = Cc["@mozilla.org/network/file-input-stream;1"].
                    createInstance(Ci.nsIFileInputStream); 
      fstream.init(v, -1, -1, Ci.nsIFileInputStream.DEFER_OPEN);
      item += "Content-Disposition: form-data; name=\"" + encode(k, true) + "\";" +
              " filename=\"" + encode(v.leafName, true) + "\"\r\n";
      let ctype = "application/octet-stream";
      try {
        let mime = Cc["@mozilla.org/mime;1"].getService(Ci.nsIMIMEService);
        ctype = mime.getTypeFromFile(v) || ctype;
      }
      catch (ex) {
        console.warn("failed to get type", ex);
      }
      item += "Content-Type: " + ctype + "\r\n\r\n";
      let ss = Cc["@mozilla.org/io/string-input-stream;1"].
               createInstance(Ci.nsIStringInputStream);
      ss.data = item;
      mpis.appendStream(ss);
      mpis.appendStream(fstream);
      item = "";
    }
    else {
      item += "Content-Disposition: form-data; name=\"" + encode(k, true) + "\"\r\n\r\n";
      item += encode(v);
    }
    item += "\r\n";
  }
  item += "--" + boundary + "--\r\n";
  let ss = Cc["@mozilla.org/io/string-input-stream;1"].
           createInstance(Ci.nsIStringInputStream);
  ss.data = item;
  mpis.appendStream(ss);
  let postStream = Cc["@mozilla.org/network/mime-input-stream;1"].
                   createInstance(Ci.nsIMIMEInputStream);
  postStream.addHeader("Content-Type",
                       "multipart/form-data; boundary=" + boundary);
  postStream.setData(mpis);
  postStream.addContentLength = true;
  return postStream;
}

(You could use additional nsIMIMEInputStream instead of string-concatenating stuff together, but this would perform worse and has no real merit).

Which can then be used like e.g.:

let file = Services.dirsvc.get("Desk", Ci.nsIFile);
file.append("australis-xp hällow, wörld.png");

let postData = encodeFormData({
  "filename": "",
  "image_url": "",
  "image_content": "--somne value ---",
  "contents": file
}, "iso8859-1");

gBrowser.loadOneTab("http://www.example.org/", {
  inBackground: false,
  postData: postData
});
Noitidart
  • 35,443
  • 37
  • 154
  • 323
nmaier
  • 32,336
  • 5
  • 63
  • 78
  • Tnx Nils. I was using the first one to send base64 image data. As you guessed, my next step would have been file upload. It looks like a lot more work than inserting a `Form` into document. BTW, I am now part of #amo-editors (I haven't seen you logged in the IRC) – erosman Jul 29 '14 at 17:15
  • @erosman and nmaier how what is the value of `image_content`? Is it data url obtained from canvas so like `'image_content': document.getElementById("canvas").toDataURL()`? – Noitidart Jul 30 '14 at 07:34
  • @Noitidart In my example, `'image_content'` was base64/Image src Data:URL, direct from img src (not from canvas) example: (the cat) http://dopiaza.org/tools/datauri/examples/index.php – erosman Jul 30 '14 at 09:10
  • Oh I see @erosman so you like XHR load the image and then base64 encode that and pass that – Noitidart Jul 30 '14 at 09:52
  • @Noitidart .. not at all ... `img` `src` is already `data:image/png;base64` and I want to `POST` this `src` to another page – erosman Jul 30 '14 at 11:47
  • Nils: How can I pass `formData` to `postData`? – erosman Jul 30 '14 at 13:21
  • If you mean `FormData`, then you cannot. I covered that in the intro paragraph of my answer. – nmaier Jul 30 '14 at 13:28
  • Man I'm getting a blob from a canvas, with toBlob, then I use FileReader to get array buffer, and I try to attach it in, for small images it works, but for big images (like > 200px x 200px) something is going wrong. This was what I was doing - can you please take a look - https://discourse.mozilla-community.org/t/post-arraybuffer-to-loadonetab/6224 – Noitidart Dec 25 '15 at 08:53