So, this is the way I finally got it working. It's done in a Chrome extension. I don't think it's possible in a normal browser script, since the canvas.toDataURL function is used and will throw a security exception if the downloaded file is cross-origin. Anyway, here's a simplified version of how I did it. Luckily the files I'm downloading and uploading are images, so I can use the Image() class.
// load the image var img = new Image(); img.src = "url to image"; // loaded img.onload = function() { // convert the image into a data url var dataUrl = getImageDataUrl(this); var blob = dataUrlToBlob(dataUrl); // FormData will encapsulate the form, and understands blobs var formData = new FormData(); // "image_name.png" can be whatever you want; it's what the // host will see as the filename for this image (the host server // that receives the posted form). it doesn't have to match the // original filename, and can be arbitrary or not provided at all. formData.append("file", blob, "image_name.png"); var request = new XMLHttpRequest(); // upload request.open("POST", "url to form"); request.send(formData); }; // converts an image into a dataurl function getImageDataUrl(img) { // Create an empty canvas element var canvas = document.createElement("canvas"); canvas.width = img.width; canvas.height = img.height; // Copy the image contents to the canvas var ctx = canvas.getContext("2d"); ctx.drawImage(img, 0, 0); // NOTE: this will throw a cross-origin security exception // if not done in an extension and if the image is cross-origin return canvas.toDataURL("image/png"); } // converts a dataurl into a blob function dataUrlToBlob(dataUrl) { var binary = atob(dataUrl.split(',')[1]); var array = []; for(var i = 0; i < binary.length; i++) { array.push(binary.charCodeAt(i)); } return new Blob([new Uint8Array(array)], {type: 'image/png'}); }