1

using my chrome extension i would like to grab a file(and maybe change it) before it gets uploaded to a website. Particularly from file-inputs. Put another way if i select a file with an input[type=file] i want my chrome extension to interrupt any submit and take the file. Then my extension applies its magic to the file and after that the file may be submitted/uploaded. How can i approach this?

On problem occurs with the file path. If i grab the vaule of a file-input it always changes the actual path to " C:\fakepath\" due to HTML5 standard and compatibility issues. How can i access the file then? Maybe it is saved temporarily in some chrome storage?

EDIT: Well, i managed to read the file selected in the file input like this:

var file;
$('input[type=file]').change(function(e){
    file = e.target.files[0];
    var reader = new FileReader();      
    reader.onload = function (event) {
        console.log(event.target.result);  //contains the file data
        //maybe change data and use filewriter  

    };      
    reader.readAsDataURL(file);
};

Now i would like to use a FileWriter to write to e.target.files[0]

I followed this tutorial: http://www.html5rocks.com/en/tutorials/file/filesystem/ but i am not able to create a proper FileWriter.

It is not necessary to write to the original file on the disk - might not even possible - but i need to change the data that is used to upload in the corresponding file-input form field.

Merion
  • 721
  • 2
  • 13
  • 24

1 Answers1

5

There's no defined API for a Chrome extension to intercept the payload of a request (work in progress).

That being said, you can use a Content script to add an event listener which:

  1. Cancels the normal form submission.
  2. Reads the file as an ArrayBuffer using the FileReader API.
  3. Modify the resulting ArrayBuffer after wrapping it in a view.
  4. Create a Blob from the modified data.
  5. Reconstruct the form by creating a FormData instance, then use the .append(name, value[, filename]) method.
    Note: In the example below, I did not include the form reconstruction method. I only included the file upload part. There are two approaches to reconstruct the form:

    1. Write a method which is specific to your form.
    2. Write a general form-parsing method, which takes care of nameless/disabled/unchecked/... elements. If you want to take this route, have a look at the W3 specification for the Form submission algorithm.
  6. Submit the data using XMLHttpRequest. Note: If you're running this code in the context of your Chrome extension, don't forget to explicitly whitelist the URL at the permissions section in the manifest file.

Here's an example of the implementation:

// Reference to the form:
var form = document.forms["uploadForm"];
form.addEventListener('submit', function(ev) {
    ev.preventDefault(); // Cancel submission

    var fileInput = form.querySelector('input[type="file"]');
    var file = fileInput.files[0];
    if (!file) return; // No file selected

    var fileReader = new FileReader();
    fileReader.onload = function() {
        var arraybuffer = fileReader.result;
        // To manipulate an arraybuffer, wrap it in a view:
        var view = new Uint8Array(arraybuffer);
        view[0] = 0; // For example, change the first byte to a NULL-byte

        // Create an object which is suitable for use with FormData
        var blob = new Blob([view], {type: file.type});

        // Now, the form reconstruction + upload part:
        var formData = new FormData();
        formData.append(fileInput.name, blob, file.name);
        // ... handle remainder of the form ...

        // Now, submit the form
        var xhr = new XMLHttpRequest();
        xhr.open('POST', form.action);
        xhr.onload = function() {
            // Do something. For example:
            alert(xhr.responseText);
        };
        xhr.onerror = function() {
            console.log(xhr); // Aw. Error. Log xhr object for debugging
        }
        xhr.send(formData);
    };
    fileReader.readAsArrayBuffer(file);
});
Rob W
  • 341,306
  • 83
  • 791
  • 678
  • If i use "form.addEventListener('submit'..." in my extension and use FormData+xhr to send the form, will that overwrite or corrupt submit handlers on the website (e.g. for form validation)? – Merion Jul 26 '12 at 13:34
  • @user1547097 No, `addEventListener` **adds** an event listener. It does not overwrite previously defined event listeners (inline: `
    `, expando (JS): `form.onsubmit = ...`, previously added event listeners: `form.addEventListener('submit', ...)`).
    – Rob W Jul 26 '12 at 14:51
  • Well yes, but how to handle this on a website?: `function foonsubmit (that) { $('[name="foo"]').val(2); that.submit(); }` .....`
    – Merion Jul 26 '12 at 15:22
  • @user1547097 If you *really* want to get rid off the inline event, use `form.remoteAttribute('onsubmit');` or `form.onsubmit = null`: http://jsfiddle.net/VfU3q/. – Rob W Jul 26 '12 at 16:06
  • That's the point. I do NOT want to get rid of those event since my extension should not corrupt a website(form validation etc.) .. Again: i "only" want to edit a fileupload on the fly without changing anything in the rest of the form. – Merion Jul 27 '12 at 07:41
  • @user1547097 Another option is to void the `submit` method of the form (of course, after storing a reference to the original handler): http://jsfiddle.net/VfU3q/1/. Then, `addEventListener('submit', ...)` will run as expected. Note: The form itself is **not** submitted, **you reconstruct the form** in the event listener, then **submit that form using XHR2**. The page won't unload, but if you wish, you *can* change the URL using `history.pushState` and replace the document by DOM manipulation (which is even easier when `xhr.responseType='document'` is available). – Rob W Jul 27 '12 at 08:14
  • Well in that case there will occur problems if the website handles the form submission in that submit method and not using a submit button.... – Merion Jul 27 '12 at 08:22
  • @user1547097 The event bound using `addEventListener` are sequentially executed (order depends on the time of definition). Since your extension binds the event in the end, all of the page's "form-modifying/validating" stuff have already been executed. When the form is invalid, the page's script ought call `event.stopPropagation();`. To get back at your comment: The `onsubmit` event is still fired. But calls to the direct `submit()` are ignored. – Rob W Jul 27 '12 at 08:29
  • Is there no way to e.g. read the selected file then copy it into html5 storage with write permission then edit it(possible up to here) and after that _change the file input (of the existing form) to point its filehandler to the file in html5 storage_? – Merion Jul 27 '12 at 08:29
  • @user1547097 No, the `files` property of the `` element is **read-only**. – Rob W Jul 27 '12 at 08:31
  • Is it possible to remove the old file-in put and create a new one in JS with same attributes but with my modified blob? – Merion Jul 27 '12 at 08:46
  • Well seems as if my content script is not able to modify the .submit ... `$('form')[0].submit = function(){};` works in the website and disables any submit, but executed in the content script it has no effect on the websites submit... – Merion Jul 27 '12 at 09:00
  • @user1547097 Possibly caused by the fact that content scripts only have access to the *DOM* of a page. The solution is thoroughly explained at [this answer](http://stackoverflow.com/questions/9515704/building-a-chrome-extension-inject-code-in-a-page-using-a-content-script/9517879#9517879). – Rob W Jul 27 '12 at 09:19
  • Why not simply use the existing form? var newFormData = new FormData(someFormElement); Then append the file; no need to rebuild the entire form first. – BrianFreud Aug 17 '12 at 02:39
  • @BrianFreud The `formData` object has an `.append` method, but no `.remove` option. The goal is to replace `type=file` elements. One can move the input type=file element, then use the `FormData` constructor. That may result in side effects though (such as a broken lay-out). One can also remove the `name` attribute of the element. And don't even try to use `someFormElement.cloneNode(true);`, because the user-defined values are not cloned. – Rob W Aug 17 '12 at 09:41