Export to GitHub

jquery-html5-upload - issue #12

Enhancement: Amazon S3 Compatibility


Posted on Sep 7, 2010 by Grumpy Monkey

What steps will reproduce the problem? 1. host html & script in a S3 bucket with global write 2. pick file(s) & proceed to upload

What is the expected output? What do you see instead? Observed: Error XML return Desired: the file(s) would upload to the bucket.

Changes required: xhr.open: need option for method (put rather than post) and pass the number variable to options.url (I don't you HAVE to have the full url here, you could probably use the bucket name alone, but it looks better when auditing code to have the full path available) xhr.setRequestHeader: need to be able to pass the path/filename as "key". I'm using: xhr.setRequestHeader("key", options.url(number)); Content-Disposition: form-data; name="file" <-- S3 demands the file be uploaded with this name

I could submit a patch that moves the setRequestHeader set and the method into options, or put an S3 flag into options and modify the behavior that way.

Please let me know if you'd like me to submit, Paul

Comment #1

Posted on Sep 7, 2010 by Grumpy Monkey

Comment deleted

Comment #2

Posted on Sep 7, 2010 by Grumpy Monkey

S3 can be enabled by changing the method from POST to PUT and assuring the the url can be set to reflect the desired URL of the file (accomplished here by passing the number to options.url)

Attachments

Comment #3

Posted on Sep 20, 2010 by Swift Panda

Thank you, version with your patch was commited. Sorry for long answer time.

Comment #4

Posted on Sep 20, 2010 by Swift Panda

Please say if it doesn't work.

Comment #5

Posted on Nov 3, 2010 by Massive Ox

Did you manage upload directly to S3 ?

I'll be thankful if you can post example for that.

Cheers,

Dom

Comment #6

Posted on Nov 5, 2010 by Grumpy Monkey

DMagon: yes, I have it working on S3, you can see a sample at http://upload.attik.com/html5_upload/example.html If you look at the source, you can see how I've set things to work, including setting the method to PUT and sendBoundary to false (if you send boundaries they will be in the file). S3 settings: you need to put these files in a bucket that has public-write access. Of course if you set both public write and public read, anyone can use your S3 bucket for their own file storage, so I would discourage that. NOTE: I plan to submit a patch to allow more flexibility in the headers sent. I have added a header at line 124 (x-amz-acl: bucket-owner-full-control) that gives the bucket owner full control over the file. Sometimes AWS can lock you out of doing anything with the file but deleting it if you don't do this.

Attachments

Comment #7

Posted on Nov 5, 2010 by Grumpy Monkey

New patch to address the headers issue in my last comment. This patch moves the headers to an object within the options, allowing run-time adjustments. attik.html is my personalized version that includes a demonstration of how the content-type metadata can be parsed so that the file type is set correctly when uploaded to AWS (this functionality should probably be delegated to a full jQuery plugin). Functional test available at: http://upload.attik.com/html5_upload_test/attik.html

Attachments

Comment #8

Posted on Mar 11, 2011 by Quick Bear

Hi!~

Totally fascinated with this s3 solution.

Questions: Do you have any idea how to integrate with PLUpload for a slick front end? I found this solution when I got frustrated with lack of HTML5 for plupload direct to s3 support.

Does this support the multi-part resumption where if a large file is interrupted it will resume at last good chunk?

Thanks so much!

Cary

Comment #9

Posted on Mar 11, 2011 by Quick Bear

Comment deleted

Comment #10

Posted on Mar 12, 2011 by Quick Bear

Any ideas about drag & drop functionality? :))

Comment #11

Posted on Mar 12, 2011 by Grumpy Elephant

Well, we have done for one of our projects a Flash/Flex based uploader with drag&drop, which uploads directly to Amazon S3, is secure (generates AWS signature for every upload session and you don't need global write on your bucket), it is customizable, and it supports uploads of files with size upto multiple gigabytes via chunking. Resume is also possible, even when you close a browser. If you are interested then just contact me please.

Comment #12

Posted on Mar 12, 2011 by Quick Bear

I cannot figure out how to contact you I am afraid as clicking on the "klo...@gmail.com" link does not get me to any type of contact page. I can be emailed at caryabramoff@yahoo.com

Comment #13

Posted on Mar 12, 2011 by Quick Bear

Is this secure? In other words is the S3 ACL, to only allow authenticated users write access sufficient. If someone were creating an offline file storage solution for multiple users could this page be used to create other http requests that could delete files and so forth?

Comment #14

Posted on Mar 14, 2011 by Swift Panda

Ok, now I am back in the project:) As I understand, the problem with AWS is solved? If so, I'll close this issue. I created 2 new issues for discussing about drag&drop and chunking.

Comment #15

Posted on Mar 14, 2011 by Grumpy Monkey

Thanks, Mihail, for rolling this functionality in.

Cary, drag/drop had very spotty support the last time I looked, but as Mihail said that is probably best in a new issue. No clue on how to support chunking with browser-based uploads, the generally stateless nature of browsers would seem to preclude the ability to seek to a point in the file.

As for the security issue, the solution I have submitted here assumes that anyone can write to the bucket, but only the owner will be able to read/download. I have developed server-side solutions for generating the needed signatures, but I personally don't feel that approach is in the jQuery "spirit" of things as it would create a bunch of server-side dependencies that have nothing to do with the plugin. I would suggest adding to the success callback and triggering a server-side action that moves the uploaded file to a secure bucket if that's what you need.

As for the Flash apps that securely upload to private buckets, remember that both the key and secret must be compiled into the app for that to work (unless there is server-side code to generate signatures). While I've been told that decompiling a Flash binary is very difficult, it certainly isn't impossible, and creates the possibility that someone could get full access to your S3 account.

Comment #16

Posted on Mar 23, 2011 by Quick Bear

Thanks Mihail & Thanks Paul!

I appreciate you guys taking the time to reply. I am sorry if my JQuery posts are in the wrong spot. I love JQuery but many things including netiquette are still confusing to me.

For the time being I am now concentrating solely on trying to create thumbnail version of uploaded images using the new html5 features. I figure I can perfect security a bit later as I have an Amazon Bronze Support account and they have given me some feedback on that topic so combined with yours Paul, I will revisit this when I can give it the proper attention. BTW Flajaxian pulls secret key from the asp.net web.config, i.e. it is signed on the server and therefore secure. It is not in the Flash code, but yes, Flash is nasty to program... :)

Anyway I have tried to use the code in the JQuery plugin to create thumbnails and I have gotten as far as creating a canvas, writing the image to a dataURL but now I simply cannot how to configure the xhr stuff to make a valid request.

I am pasting my code in the hopes that I have done most of the work and configuring the s3 xhr call will be easy for someone more experienced than I. I am terrible understanding raw html requests try though I might... :))

Anyway I have this code commented in my html page as I just cannot get the code to run right and I am sure I have done some seriously ignorant modifications to it but .... well apologies in advance :))

PS- I was trying to integrate some drag and drop stuff and ended up putting the plugin source code directly in my html page rather than using a tag to link to it just so there's no confusion there...

function upload() { // debugger; files = this.files; if (krafto != undefined || krafto != null) {

                    // alert('You have dropped: '+krafto.length+' file(s)');
                    files = krafto;
                }


                var total = files.length;
                var $this = $(this);
                if (!$this.triggerHandler('html5_upload.onStart', [total])) {
                    return false;
                }
                this.disabled = true;
                var uploaded = 0;
                var xhr = this.html5_upload['xhr'];
                this.html5_upload['continue_after_abort'] = true;
                function upload_file(number) {

                    if (number == total) {
                        $this.trigger('html5_upload.onFinish', [total]);
                        options.setStatus(options.genStatus(1, true));
                        $this.attr("disabled", false);
                        if (options.autoclear) {
                            $this.val("");
                        }
                        return;
                    }

                    var file = files[number];
                    //////////////////////
                     //if image let's thumb it
                    var imageT = /image/;

                    if (file.type.match(imageT)) {
                        // LET'S make a thumb & let's also allow user to mellow phone files


                        var canvas =document.createElement("canvas");
                        var img = document.createElement("img");


                        if(!isFF())
                        {
                            try {
                                img.src = window.webkitURL.createObjectURL(file);
                            } catch (e) {
                                alert('CreateObjectURL: '+e);
                            }
                        }
                        else
                        {
                            var reader = new FileReader();  
                            reader.onload = function(e) {img.src = e.target.result}
                            reader.readAsDataURL(file)
                        }



                        var ctx = canvas.getContext("2d");

                        ctx.drawImage(img, 175, 125, 175, 125);
                        var dataurl;
                        if(!isFF())
                        {
                            dataurl = canvas.toDataURL("image/png");
                        }
                        else
                        {
                            dataurl = canvas.toDataURL("image/png");

                        }
                        debugger;
                        try {
                            ///////////////////////////////////////////////////////////////////////////////////////////

                    /////////////////////////////////////////////////////////////////
                            options.setStatus(options.genStatus(0));
                            var thumbski="thumb_"+file.fileName;
                            options.setName(options.genName(thumbski, number, total));
                            options.setProgress(options.genProgress(0, file.fileSize));
                            xhr.upload['onprogress'] = function (rpe) {
                                $this.trigger('html5_upload.onProgress', [rpe.loaded / rpe.total, thumbski, number, total]);
                                options.setStatus(options.genStatus(rpe.loaded / rpe.total));
                                options.setProgress(options.genProgress(rpe.loaded, rpe.total));
                            };
                            xhr.onload = function (load) {
                                $this.trigger('html5_upload.onFinishOne', [xhr.responseText,thumbski, number, total]);
                                options.setStatus(options.genStatus(1, true));
                                options.setProgress(options.genProgress(file.fileSize, file.fileSize));
                                upload_file(number + 1);
                            };
                            xhr.onabort = function () {
                                if ($this[0].html5_upload['continue_after_abort']) {
                                    upload_file(number + 1);
                                }
                                else {
                                    $this.attr("disabled", false);
                                    if (options.autoclear) {
                                        $this.val("");
                                    }
                                }
                            };
                            xhr.onerror = function (e) {
                                $this.trigger('html5_upload.onError', [thumbski, e]);
                                if (!options.stopOnFirstError) {
                                    upload_file(number + 1);
                                }
                            };
                            xhr.open(options.method, typeof (options.url) == "function" ? options.url(number) : options.url, true);
                            $.each(options.headers, function (key, val) {
                                val = typeof (val) == "function" ? val(file) : val; // resolve value
                                if (val === false) return true; // if resolved value is boolean false, do not send this header
                                xhr.setRequestHeader(key, val);
                            });

                            if (1==1) {

                                debugger;

                                // the Content-Type header that had been here has been moved to the headers object in options
                                if (1==2) {//Many thanks to scottt.tw

                                }
                                else  {//Thanks to jm.schelcher


                                    dataurl = dataurl.substr(dataurl.indexOf("base64,", 0) + 7);

                                    ///////////////////////////////////////////////////////////////////////////////////////////////////
                                    var boundary = '------multipartformboundary' + (new Date).getTime();
                                    var dashdash = '--';
                                    var crlf = '\r\n';

                                    /* Build RFC2388 string. */
                                    var builder = '';

                                    builder += dashdash;
                                    builder += boundary;
                                    builder += crlf;

                                    builder += "Content-Disposition: form-data; name=\"fileupload\"";
                                    builder += '; filename="' + thumbski + '"';
                                    builder += "Content-Transfer-Encoding: base64" + crlf + crlf
                                    builder += dataurl + crlf;
                                    builder += crlf;
                                    builder += 'Content-Type: image/jpeg';
                                    builder += crlf;
                                    builder += crlf;

                                    /* Write boundary. */
                                    builder += dashdash;
                                    builder += boundary;
                                    builder += dashdash;
                                    builder += crlf;

                                    xhr.setRequestHeader('content-type', 'multipart/form-data; boundary=' + boundary);
                                    xhr.sendAsBinary(builder);
                                }

                            }
                       ////////////////////////// end last call for alcohol

                        } catch (e) {
                                alert('fubar city: '+e);
                        }



                    }

                    ////////// regularly scheduled jquery-html5-upload code continues here.... /////////////

PPS- I based my butchered request code on, I noticed the similarity with all the boundary stuff and I tried my best to massage it but I am sure my code reflects my ignorance. Any assist would be awesome! Thanks again gentlemen! Cary :))

http://code.google.com/p/imageshackapi/issues/detail?id=20

var boundaryString = '------------------------------'; xhr.open('POST','http://www.imageshack.us/upload_api.php'); //xhr.open('POST','http://localhost:8080/Upload/'); xhr.setRequestHeader("Content-Type", "multipart/form-data; boundary="+boundaryString); xhr.onreadystatechange = function() { if (xhr.readyState == 4) { if ((xhr.status >= 200 && xhr.status <= 200) || xhr.status == 304) { if (xhr.responseText != "") { alert(xhr.responseText); // display response. } } }; } dataUrl = dataUrl.substr(dataUrl.indexOf("base64,",0)+7);

    var cr = "\r\n"; 
    var boundary = "--"+ boundaryString;
    var body = boundary + cr;
    body+="Content-Disposition: form-data; name=\"key\""+cr+cr;
    body+="key"+cr;
    body+=boundary+cr;
    body+="Content-Disposition: form-data; name=\"fileupload\"; filename=\"gprFoto.jpeg\""+cr;
    body+="Content-Type: image/jpeg"+cr;
    body+="Content-Transfer-Encoding: base64"+cr+cr
    body+=dataUrl+ cr;
    body+=boundary+"--"+cr;
    xhr.send(body);

Comment #17

Posted on Mar 23, 2011 by Quick Bear

Oh, one last thing if this post is not totally misplaced &/or not really part of the jquery-html5-upload mandate.... The xhr.sendAsBinary(builder) was throwing errors in chrome as I guess it is not supported and had originally fallen into the window.form (Many thanks to scottt.tw)..... In chrome it basically ended up swallowing the thumbnail errors and uploading normally regular files and in Firefox it just decided to go into a deep freeze. I am out of my depths here. Thanks again!

Cary

Comment #18

Posted on Mar 25, 2011 by Quick Bear

Mihail & whoever else may have contributed to this plugin... I just want to say after spending at least 100 hours in past two weeks working with the guts of the code. It is beautiful. Took a while to understand how it works but now I am able to modify it to suit my needs and I am grateful for this plugin. This is the deepest I have ever gotten into how plugins work, and after this experience I will be creating my own plugins wherever I can. Thanks & Spasiba! Cary :))

Comment #19

Posted on Jan 28, 2012 by Swift Panda

As I understand, this issue is fixed:)

Status: Fixed

Labels:
Type-Defect Priority-Medium