s3upload 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,3 @@
1
+ 0.1.0
2
+
3
+ - Initial public release
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License
2
+
3
+ Copyright (c) 2010 Robert Sköld
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1,13 @@
1
+ CHANGELOG
2
+ LICENSE
3
+ Manifest
4
+ README.md
5
+ Rakefile
6
+ bin/jquery.s3upload.min.js
7
+ bin/s3upload.swf
8
+ lib/s3upload.rb
9
+ s3upload.gemspec
10
+ src/build.hxml
11
+ src/hx/MimeTypes.hx
12
+ src/hx/S3Upload.hx
13
+ src/js/jquery.s3upload.js
@@ -0,0 +1,97 @@
1
+ S3Upload
2
+ ========
3
+
4
+ A jQuery plugin for direct upload to an Amazon S3 bucket.
5
+
6
+ It works by replacing a file input with a html element overlaid with a transparent SWF. The same way [Flickr](http://www.flickr.com/photos/upload/) does it.
7
+
8
+ By signing the request server side we also avoid the security issue of showing the Amazon AWS Access Id Key and Secure Key in plain text. A library for signing the request in Ruby is included, but the logic should be very easy to replicate in other languages like PHP or Python.
9
+
10
+ The Javascript API also allows these callback functions:
11
+
12
+ * onselect(info) - Called when a user has selected a file.
13
+ * oncancel(info) - Called if the user decides to abort the file browsing.
14
+ * onstart(info) - Called after the request has been signed and the file upload to S3 is starting.
15
+ * onprogress(progress,info) - Called while uploading, "progress" being a float between 0 and 1 of the current upload progress.
16
+ * oncomplete(info) - Called when the upload has finished successfully.
17
+ * onerror(msg,info) - Called if there's been a problem with a message saying what failed.
18
+ * onenabled() - Called when the SWF has been enabled. Usually when swf.enable() has been called. Called first thing when the SWF is finished initializing.
19
+ * ondisabled() - Called when the SWF has been disabled. Usually when swf.disable() has been called.
20
+
21
+ _info_ is an object containing "name", "size" and "type" of the selected file.
22
+
23
+ And these mouse callbacks:
24
+
25
+ * onmouseover(x,y)
26
+ * onmouseout(x,y)
27
+ * onmousedown(x,y)
28
+ * onmouseup(x,y)
29
+ * onmousemove(x,y)
30
+ * onclick(x,y)
31
+ * onrollover(x,y)
32
+ * onrollout(x,y)
33
+ * ondoubleclick(x,y)
34
+
35
+ The mouse events are also triggered as regular jQuery events (i.e. `$('#input_replaced').rollover(function(){alert('over!')});` should work just fine as well).
36
+
37
+ Every callback is scoped to the DOM element which has replaced the previous input (i.e. "this" in the callbacks points to the html element). Also by returning `true` in a callback function the default callback will be used.
38
+
39
+ Which file types that can be selected may be defined with the _file\_types_ option, see the "Usage Example" below for more info. If none is defined all files are acceptable.
40
+
41
+
42
+ Requirements
43
+ -------------
44
+
45
+ * jQuery 1.3+
46
+ * SWFObject 2.1+
47
+
48
+ Both available from Google AJAX APIs (recommended as it likely speeds things up).
49
+
50
+
51
+ Example Usage
52
+ -------------
53
+
54
+ The HTML/JS part:
55
+
56
+ <script type="text/javascript" charset="utf-8" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4/jquery.min.js"></script>
57
+ <script type="text/javascript" charset="utf-8" src="http://ajax.googleapis.com/ajax/libs/swfobject/2.2/swfobject.js"></script>
58
+ <script type="text/javascript" charset="utf-8" src="jquery.s3upload.js"></script>
59
+
60
+ <script type="text/javascript" charset="utf-8">
61
+ $(function(){
62
+ var max_file_size = 2 * 1024 * 1024; // = 2Mb
63
+ $("form").s3upload({
64
+ prefix: "s3upload/",
65
+ required: true,
66
+ onselect: function(info) {
67
+ if( parseInt( info.size ) < max_file_size )
68
+ return true; // Default is to show the filename in the element.
69
+ else
70
+ $(this).html("Too big file! Must be smaller than " + max_file_size + " (was "+info.size+")");
71
+ },
72
+ file_types: [
73
+ [ "Images" , "*.png;*.jpg;*.bmp"],
74
+ [ "Documents" , "*.pdf;*.doc;*.txt"]
75
+ ]
76
+ });
77
+ });
78
+ </script>
79
+
80
+ <form action="/media/new" method="post" accept-charset="utf-8" enctype="multipart/form-data">
81
+ <label for="media_title">Title</label>
82
+ <input type="text" name="media[title]" value="" id="media_title" />
83
+ <label for="media_video">Video</label>
84
+ <input type="file" name="media[video]" value="" id="media_video" />
85
+ <label for="media_thumbnail">Thumbnail</label>
86
+ <input type="file" name="media[thumbnail]" value="" id="media_thumbnail" />
87
+ <input type="submit" value="Upload" />
88
+ </form>
89
+
90
+
91
+ The Sinatra part (assumes the _s3upload_ gem is installed):
92
+
93
+ require "s3upload"
94
+ get "/s3upload" do
95
+ up = S3::Upload.new( options.s3_upload_access_key_id , options.s3_upload_secret_key , options.s3_upload_bucket )
96
+ up.to_xml( params[:key] , params[:contentType] )
97
+ end
@@ -0,0 +1,8 @@
1
+ require 'echoe'
2
+
3
+ Echoe.new("s3upload","0.1.0") do |p|
4
+ p.author = "Robert Sköld"
5
+ p.summary = "A jQuery plugin for direct upload to an Amazon S3 bucket."
6
+ p.url = "http://github.com/slaskis/s3upload"
7
+ p.runtime_dependencies = []
8
+ end
@@ -0,0 +1,32 @@
1
+ /* S3Upload 0.1.0 <http://github.com/slaskis/s3upload>
2
+ is released under the MIT License <http://www.opensource.org/licenses/mit-license.php>
3
+ */
4
+
5
+ (function($){$.fn.s3upload=function(settings){var config={text:"<a href='#basic'>Select file...</a>",path:"/s3upload.swf",prefix:"",signature_url:"/s3upload",required:false,submit_on_all_complete:true,error_class:"s3_error",file_types:[]};if(settings)$.extend(config,settings);var elements=this.map(function(){if($(this).is("form"))
6
+ return $(this).find(":file").toArray();return $(this);});$.fn.s3upload.instances=$.fn.s3upload.instances||0;var started=0;var completed=0;return elements.each(function(){var form=$(this).closest("form");var id=$(this).attr("id")||"s3upload_"+$.fn.s3upload.instances;var name=$(this).attr("name");$(this).replaceWith("<div id='"+id+"'></div>");var el=$("#"+id);el.attr("class",$(this).attr("class"));var flash_div=$("<div id='s3upload_"+id+"'><div id='flash_"+id+"'></div></div>").appendTo("body");var filters=$.map(config.file_types,function(f,i){return f.join("#");}).join("|");var fv={id:"flash_"+id,prefix:config.prefix,signatureURL:config.signature_url,filters:filters};var params={wmode:"transparent",menu:false};swfobject.embedSWF(config.path,fv.id,"100%","100%","9.0.0",false,fv,params,false,function(e){if(e.success){var swf=e.ref;el.html(config.text);swf.update_interval=setInterval(function(){var pos=el.offset();flash_div.css({width:el.width(),height:el.height(),position:"absolute",top:pos.top,left:pos.left});},100);var formSubmit=function(){if(config.required&&!swf.info)
7
+ swf.onerror("Error: No file selected.");else
8
+ swf.upload();return false;}
9
+ form.submit(formSubmit);swf.onmouseevent=function(type,x,y){var def=true;if($.isFunction(config["on"+type]))
10
+ def=config["on"+type].call(el,x,y);if(def){el.trigger(type);}}
11
+ swf.onselect=function(name,size,type){swf.info={name:name,size:size,type:type};var def=true;if($.isFunction(config.onselect))
12
+ def=config.onselect.call(el,swf.info);if(def){el.html(name);}}
13
+ swf.oncancel=function(){var def=true;if($.isFunction(config.oncancel))
14
+ def=config.oncancel.call(el,swf.info);if(def){el.html(config.text);}
15
+ swf.info=null;}
16
+ swf.onstart=function(){var def=true;if($.isFunction(config.onstart))
17
+ def=config.onstart.call(el,swf.info);if(def){}
18
+ started++;swf.disable();}
19
+ swf.onprogress=function(p){var def=true;if($.isFunction(config.onprogress))
20
+ def=config.onprogress.call(el,p,swf.info);if(def){el.html(Math.ceil(p*100)+"%");}}
21
+ swf.onerror=function(msg){var def=true;if($.isFunction(config.onerror))
22
+ def=config.onerror.call(el,msg,swf.info);if(def){el.html("<span class='"+config.error_class+"'>"+msg+"</span>");}
23
+ swf.enable();}
24
+ swf.oncomplete=function(key){swf.info.key=key;var def=true;if($.isFunction(config.oncomplete))
25
+ def=config.oncomplete.call(el,swf.info);if(def){}
26
+ if(el.nextAll("input[name^="+name+"]").length==0)
27
+ for(var k in swf.info)
28
+ el.after("<input type='hidden' name='"+name+"["+k+"]' value='"+swf.info[k]+"' />");else
29
+ for(var k in swf.info)
30
+ el.siblings("input[name="+name+"["+k+"]]").val(swf.info[k]);completed++;form.unbind("submit",formSubmit);if(!config.submit_on_all_complete)
31
+ swf.enable();else if(started==completed)
32
+ form.submit();}}});$.fn.s3upload.instances++;});};})(jQuery);
Binary file
@@ -0,0 +1,68 @@
1
+ require 'openssl'
2
+ require 'digest/sha1'
3
+ require "base64"
4
+
5
+ module S3
6
+ class Upload
7
+
8
+ attr_accessor :bucket, :expires, :secret_key, :access_key_id, :acl
9
+
10
+ def initialize( access_key_id , secret_key , bucket , acl="public-read" , expires=nil)
11
+ @access_key_id = access_key_id
12
+ @secret_key = secret_key
13
+ @bucket = bucket
14
+ @acl = acl
15
+
16
+ # default to one hour from now
17
+ @expires = expires || (Time.now + 3600)
18
+
19
+ end
20
+
21
+ def to_xml( key , content_type )
22
+ @key = key
23
+ @content_type = content_type
24
+
25
+ props = {
26
+ :accessKeyId => access_key_id,
27
+ :acl => acl,
28
+ :bucket => bucket,
29
+ :contentType => @content_type,
30
+ :expires => expiration_str,
31
+ :key => @key,
32
+ :secure => false,
33
+ :signature => signature,
34
+ :policy => policy
35
+ }
36
+
37
+ # Create xml of the properties
38
+ xml = "<s3>"
39
+ props.each {|k,v| xml << "<#{k}>#{v}</#{k}>"}
40
+ xml << "</s3>"
41
+ end
42
+
43
+ private
44
+
45
+ def expiration_str
46
+ @expires.utc.strftime('%Y-%m-%dT%H:%M:%S.000Z')
47
+ end
48
+
49
+ def policy
50
+ @policy ||= Base64.encode64( "{
51
+ 'expiration': '#{expiration_str}',
52
+ 'conditions': [
53
+ {'bucket': '#{bucket}'},
54
+ {'key': '#{@key}'},
55
+ {'acl': '#{acl}'},
56
+ {'Content-Type': '#{@content_type}'},
57
+ ['starts-with', '$Filename', ''],
58
+ ['eq', '$success_action_status', '201']
59
+ ]
60
+ }").gsub(/\n|\r/, '')
61
+ end
62
+
63
+ def signature
64
+ [OpenSSL::HMAC.digest(OpenSSL::Digest::Digest.new('sha1'), secret_key, policy)].pack("m").strip
65
+ end
66
+
67
+ end
68
+ end
@@ -0,0 +1,31 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = %q{s3upload}
5
+ s.version = "0.1.0"
6
+
7
+ s.required_rubygems_version = Gem::Requirement.new(">= 1.2") if s.respond_to? :required_rubygems_version=
8
+ s.authors = ["Robert Sk\303\266ld"]
9
+ s.date = %q{2010-01-25}
10
+ s.description = %q{A jQuery plugin for direct upload to an Amazon S3 bucket.}
11
+ s.email = %q{}
12
+ s.executables = ["jquery.s3upload.min.js", "s3upload.swf"]
13
+ s.extra_rdoc_files = ["CHANGELOG", "LICENSE", "README.md", "bin/jquery.s3upload.min.js", "bin/s3upload.swf", "lib/s3upload.rb"]
14
+ s.files = ["CHANGELOG", "LICENSE", "Manifest", "README.md", "Rakefile", "bin/jquery.s3upload.min.js", "bin/s3upload.swf", "lib/s3upload.rb", "s3upload.gemspec", "src/build.hxml", "src/hx/MimeTypes.hx", "src/hx/S3Upload.hx", "src/js/jquery.s3upload.js"]
15
+ s.homepage = %q{http://github.com/slaskis/s3upload}
16
+ s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "S3upload", "--main", "README.md"]
17
+ s.require_paths = ["lib"]
18
+ s.rubyforge_project = %q{s3upload}
19
+ s.rubygems_version = %q{1.3.5}
20
+ s.summary = %q{A jQuery plugin for direct upload to an Amazon S3 bucket.}
21
+
22
+ if s.respond_to? :specification_version then
23
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
24
+ s.specification_version = 3
25
+
26
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
27
+ else
28
+ end
29
+ else
30
+ end
31
+ end
@@ -0,0 +1,5 @@
1
+ -cp hx
2
+ -main S3Upload
3
+ -swf ../bin/s3upload.swf
4
+ -swf-version 9
5
+ #-debug
@@ -0,0 +1,158 @@
1
+ class MimeTypes {
2
+ static var TYPES = [
3
+ ["application/andrew-inset","ez"],
4
+ ["application/atom+xml","atom"],
5
+ ["application/mac-binhex40","hqx"],
6
+ ["application/mac-compactpro","cpt"],
7
+ ["application/mathml+xml","mathml"],
8
+ ["application/msword","doc"],
9
+ ["application/octet-stream","bin","dms","lha","lzh","exe","class","so","dll","dmg"],
10
+ ["application/oda","oda"],
11
+ ["application/ogg","ogg"],
12
+ ["application/pdf","pdf"],
13
+ ["application/postscript","ai","eps","ps"],
14
+ ["application/rdf+xml","rdf"],
15
+ ["application/smil","smi","smil"],
16
+ ["application/srgs","gram"],
17
+ ["application/srgs+xml","grxml"],
18
+ ["application/vnd.adobe.apollo-application-installer-package+zip","air"],
19
+ ["application/vnd.mif","mif"],
20
+ ["application/vnd.mozilla.xul+xml","xul"],
21
+ ["application/vnd.ms-excel","xls"],
22
+ ["application/vnd.ms-powerpoint","ppt"],
23
+ ["application/vnd.rn-realmedia","rm"],
24
+ ["application/vnd.wap.wbxml","wbxml"],
25
+ ["application/vnd.wap.wmlc","wmlc"],
26
+ ["application/vnd.wap.wmlscriptc","wmlsc"],
27
+ ["application/voicexml+xml","vxml"],
28
+ ["application/x-bcpio","bcpio"],
29
+ ["application/x-cdlink","vcd"],
30
+ ["application/x-chess-pgn","pgn"],
31
+ ["application/x-cpio","cpio"],
32
+ ["application/x-csh","csh"],
33
+ ["application/x-director","dcr","dir","dxr"],
34
+ ["application/x-dvi","dvi"],
35
+ ["application/x-futuresplash","spl"],
36
+ ["application/x-gtar","gtar"],
37
+ ["application/x-hdf","hdf"],
38
+ ["application/x-javascript","js"],
39
+ ["application/x-koan","skp","skd","skt","skm"],
40
+ ["application/x-latex","latex"],
41
+ ["application/x-netcdf","nc","cdf"],
42
+ ["application/x-photoshop","psd"],
43
+ ["application/x-sh","sh"],
44
+ ["application/x-shar","shar"],
45
+ ["application/x-shockwave-flash","swf"],
46
+ ["application/x-stuffit","sit"],
47
+ ["application/x-sv4cpio","sv4cpio"],
48
+ ["application/x-sv4crc","sv4crc"],
49
+ ["application/x-tar","tar"],
50
+ ["application/x-tcl","tcl"],
51
+ ["application/x-tex","tex"],
52
+ ["application/x-texinfo","texinfo","texi"],
53
+ ["application/x-troff","t","tr","roff"],
54
+ ["application/x-troff-man","man"],
55
+ ["application/x-troff-me","me"],
56
+ ["application/x-troff-ms","ms"],
57
+ ["application/x-ustar","ustar"],
58
+ ["application/x-wais-source","src"],
59
+ ["application/xhtml+xml","xhtml","xht"],
60
+ ["application/xml","xml","xsl"],
61
+ ["application/xml-dtd","dtd"],
62
+ ["application/xslt+xml","xslt"],
63
+ ["application/zip","zip"],
64
+ ["audio/basic","au","snd"],
65
+ ["audio/midi","mid","midi","kar"],
66
+ ["audio/mpeg","mp3","mpga","mp2"],
67
+ ["audio/x-aiff","aif","aiff","aifc"],
68
+ ["audio/x-mpegurl","m3u"],
69
+ ["audio/x-pn-realaudio","ram","ra"],
70
+ ["audio/x-wav","wav"],
71
+ ["chemical/x-pdb","pdb"],
72
+ ["chemical/x-xyz","xyz"],
73
+ ["image/bmp","bmp"],
74
+ ["image/cgm","cgm"],
75
+ ["image/gif","gif"],
76
+ ["image/ief","ief"],
77
+ ["image/jpeg","jpg","jpeg","jpe"],
78
+ ["image/png","png"],
79
+ ["image/svg+xml","svg"],
80
+ ["image/tiff","tiff","tif"],
81
+ ["image/vnd.djvu","djvu","djv"],
82
+ ["image/vnd.wap.wbmp","wbmp"],
83
+ ["image/x-cmu-raster","ras"],
84
+ ["image/x-icon","ico"],
85
+ ["image/x-portable-anymap","pnm"],
86
+ ["image/x-portable-bitmap","pbm"],
87
+ ["image/x-portable-graymap","pgm"],
88
+ ["image/x-portable-pixmap","ppm"],
89
+ ["image/x-rgb","rgb"],
90
+ ["image/x-xbitmap","xbm"],
91
+ ["image/x-xpixmap","xpm"],
92
+ ["image/x-xwindowdump","xwd"],
93
+ ["model/iges","igs","iges"],
94
+ ["model/mesh","msh","mesh","silo"],
95
+ ["model/vrml","wrl","vrml"],
96
+ ["text/calendar","ics","ifb"],
97
+ ["text/css","css"],
98
+ ["text/html","html","htm"],
99
+ ["text/plain","txt","asc"],
100
+ ["text/richtext","rtx"],
101
+ ["text/rtf","rtf"],
102
+ ["text/sgml","sgml","sgm"],
103
+ ["text/tab-separated-values","tsv"],
104
+ ["text/vnd.wap.wml","wml"],
105
+ ["text/vnd.wap.wmlscript","wmls"],
106
+ ["text/x-setext","etx"],
107
+ ["video/mpeg","mpg","mpeg","mpe"],
108
+ ["video/quicktime","mov","qt"],
109
+ ["video/vnd.mpegurl","m4u","mxu"],
110
+ ["video/x-flv","flv"],
111
+ ["video/x-msvideo","avi"],
112
+ ["video/x-sgi-movie","movie"],
113
+ ["x-conference/x-cooltalk","ice"]
114
+ ];
115
+
116
+ var types : Array<Array<String>>;
117
+
118
+ public function new() types = TYPES.copy()
119
+
120
+ /**
121
+ * Returns the mimetype for the given extension.
122
+ */
123
+ public function getMimeType(extension:String):String {
124
+ extension = extension.toLowerCase();
125
+ for( a in types ) {
126
+ for( b in a ) {
127
+ if (b == extension) {
128
+ return a[0];
129
+ }
130
+ }
131
+ }
132
+ return null;
133
+ }
134
+
135
+ /**
136
+ * Returns the prefered extension for the given mimetype.
137
+ */
138
+ public function getExtension(mimetype:String):String {
139
+ mimetype = mimetype.toLowerCase();
140
+ for( a in types ) {
141
+ if(a[0] == mimetype) {
142
+ return a[1];
143
+ }
144
+ }
145
+ return null;
146
+ }
147
+
148
+ /**
149
+ * Adds a mimetype to the map. The order of the extensions matters. The most preferred should come first.
150
+ */
151
+ public function addMimeType(mimetype:String, extensions:Array<String>) {
152
+ var newType = [mimetype];
153
+ for( a in extensions ) {
154
+ newType.push(a);
155
+ }
156
+ types.push(newType);
157
+ }
158
+ }
@@ -0,0 +1,314 @@
1
+ class S3Upload extends flash.display.Sprite {
2
+
3
+ static var _id : String;
4
+
5
+ var _signatureURL : String;
6
+ var _prefix : String;
7
+ var _fr : flash.net.FileReference;
8
+ var _filters : Array<flash.net.FileFilter>;
9
+
10
+ public function new() super()
11
+
12
+ public function init() {
13
+ _id = stage.loaderInfo.parameters.id;
14
+ _signatureURL = stage.loaderInfo.parameters.signatureURL;
15
+ _prefix = stage.loaderInfo.parameters.prefix;
16
+ _filters = [];
17
+ if( stage.loaderInfo.parameters.filters != null && stage.loaderInfo.parameters.filters != "" ) {
18
+ for( filter in stage.loaderInfo.parameters.filters.split("|") ) {
19
+ var f = filter.split("#");
20
+ _filters.push( new flash.net.FileFilter( f[0] , f[1] ) );
21
+ }
22
+ }
23
+
24
+ if( flash.external.ExternalInterface.available ) {
25
+ flash.external.ExternalInterface.addCallback( "disable" , disable );
26
+ flash.external.ExternalInterface.addCallback( "enable" , enable );
27
+ flash.external.ExternalInterface.addCallback( "upload" , upload );
28
+ }
29
+ stage.addEventListener( "resize" , onStageResize );
30
+ onStageResize();
31
+ enable();
32
+ }
33
+
34
+ function onBrowse( e ) {
35
+ var fr = new flash.net.FileReference();
36
+ fr.addEventListener( "cancel" , function(e) { call( e.type , [] ); } );
37
+ fr.addEventListener( "select" , function(e) { call( e.type , [fr.name,fr.size,extractType(fr)]); } );
38
+ if( _filters.length > 0 )
39
+ fr.browse( _filters );
40
+ else
41
+ fr.browse();
42
+ _fr = fr;
43
+ }
44
+
45
+ function enable() {
46
+ buttonMode = true;
47
+ doubleClickEnabled = true;
48
+ addEventListener( "click" , onBrowse );
49
+ addEventListener( "click" , onMouseEvent );
50
+ addEventListener( "rollOver" , onMouseEvent );
51
+ addEventListener( "rollOut" , onMouseEvent );
52
+ addEventListener( "mouseMove" , onMouseEvent );
53
+ addEventListener( "mouseDown" , onMouseEvent );
54
+ addEventListener( "mouseUp" , onMouseEvent );
55
+ addEventListener( "mouseOver" , onMouseEvent );
56
+ addEventListener( "mouseOut" , onMouseEvent );
57
+ addEventListener( "doubleClick" , onMouseEvent );
58
+ call("enabled");
59
+ }
60
+
61
+ function disable() {
62
+ buttonMode = false;
63
+ doubleClickEnabled = false;
64
+ removeEventListener( "click" , onBrowse );
65
+ removeEventListener( "click" , onMouseEvent );
66
+ removeEventListener( "rollOver" , onMouseEvent );
67
+ removeEventListener( "rollOut" , onMouseEvent );
68
+ removeEventListener( "mouseMove" , onMouseEvent );
69
+ removeEventListener( "mouseDown" , onMouseEvent );
70
+ removeEventListener( "mouseUp" , onMouseEvent );
71
+ removeEventListener( "mouseOver" , onMouseEvent );
72
+ removeEventListener( "mouseOut" , onMouseEvent );
73
+ removeEventListener( "doubleClick" , onMouseEvent );
74
+ call("disabled");
75
+ }
76
+
77
+ function onMouseEvent(e) {
78
+ call( "mouseevent" , [e.type.toLowerCase(),e.stageX,e.stageY] );
79
+ }
80
+
81
+ function upload() {
82
+ // No browse has been called
83
+ if( _fr == null )
84
+ return;
85
+
86
+ // Fetch a signature and other good things from the backend
87
+ var vars = new flash.net.URLVariables();
88
+ vars.fileName = _fr.name;
89
+ vars.fileSize = _fr.size;
90
+ vars.contentType = extractType( _fr );
91
+ vars.key = _prefix + _fr.name;
92
+
93
+ var req = new flash.net.URLRequest(_signatureURL);
94
+ req.method = flash.net.URLRequestMethod.GET;
95
+ req.data = vars;
96
+
97
+ var load = new flash.net.URLLoader();
98
+ load.dataFormat = flash.net.URLLoaderDataFormat.TEXT;
99
+ load.addEventListener( "complete" , onSignatureComplete );
100
+ load.addEventListener( "securityError" , onSignatureError );
101
+ load.addEventListener( "ioError" , onSignatureError );
102
+ load.load( req );
103
+ }
104
+
105
+ static function extractType( fr : flash.net.FileReference ) {
106
+ if( fr.type == null || fr.type.indexOf( "/" ) == -1 ) {
107
+ var ext = fr.name.split(".").pop();
108
+ var mime = new MimeTypes().getMimeType( ext );
109
+ if( mime == null )
110
+ return "application/octet-stream";
111
+ else
112
+ return mime;
113
+ }
114
+ return fr.type;
115
+ }
116
+
117
+ function onSignatureError(e) {
118
+ call( "error" , ["Could not get signature because: " + e.text] );
119
+ }
120
+
121
+ function onSignatureComplete(e) {
122
+ // Now that we have the signature we can send the file to S3.
123
+
124
+ var load = cast( e.target , flash.net.URLLoader );
125
+ var sign = new haxe.xml.Fast( Xml.parse( load.data ).firstElement() );
126
+
127
+ if( sign.has.error ) {
128
+ call( "error" , ["There was an error while making the signature: " + sign.node.error.innerData] );
129
+ return;
130
+ }
131
+
132
+ // Create an S3Options object from the signature xml
133
+ var opts = {
134
+ accessKeyId: sign.node.accessKeyId.innerData,
135
+ acl: sign.node.acl.innerData,
136
+ bucket: sign.node.bucket.innerData,
137
+ contentType: sign.node.contentType.innerData,
138
+ expires: sign.node.expires.innerData,
139
+ key: sign.node.key.innerData,
140
+ secure: sign.node.secure.innerData == "true",
141
+ signature: sign.node.signature.innerData,
142
+ policy: sign.node.policy.innerData
143
+ };
144
+
145
+ var fr = _fr;
146
+
147
+ var req = new S3Request( opts );
148
+ req.onError = function(msg) { call( "error" , [msg] ); }
149
+ req.onProgress = function(p) { call( "progress" , [p] ); }
150
+ req.onComplete = function() { call( "complete" , [opts.key] ); }
151
+ req.upload( _fr );
152
+ call( "start" , [] );
153
+ }
154
+
155
+ static function call( eventType , args : Array<Dynamic> = null ) {
156
+ if( args == null )
157
+ args = [];
158
+ var method = "on"+eventType;
159
+ if( _id != null && flash.external.ExternalInterface.available ) {
160
+ var c = "function(){
161
+ var swf = document.getElementById('"+_id+"');
162
+ if( swf )
163
+ swf['"+method+"'].apply(swf,['"+args.join("','")+"']);
164
+ }()";
165
+ flash.external.ExternalInterface.call( c , [] );
166
+ }
167
+ }
168
+
169
+ function onStageResize(e=null) {
170
+ graphics.clear();
171
+ graphics.beginFill( 0 , 0 );
172
+ graphics.drawRect( 0 , 0 , stage.stageWidth , stage.stageHeight );
173
+ }
174
+
175
+ public static function main() {
176
+ flash.Lib.current.stage.align = flash.display.StageAlign.TOP_LEFT;
177
+ flash.Lib.current.stage.scaleMode = flash.display.StageScaleMode.NO_SCALE;
178
+
179
+ var s = new S3Upload();
180
+ flash.Lib.current.addChild( s );
181
+ s.init();
182
+ }
183
+
184
+
185
+ }
186
+
187
+ typedef S3Options = {
188
+ var accessKeyId : String;
189
+ var acl : String;
190
+ var bucket : String;
191
+ var contentType : String;
192
+ var expires : String;
193
+ var key : String;
194
+ var secure : Bool;
195
+ var signature : String;
196
+ var policy : String;
197
+ }
198
+
199
+ class S3Request {
200
+
201
+ static inline var AMAZON_BASE_URL = "s3.amazonaws.com";
202
+
203
+ var _opts : S3Options;
204
+ var _httpStatus : Bool;
205
+
206
+ public var onComplete : Void -> Void;
207
+ public var onProgress : Float -> Void;
208
+ public var onError : String -> Void;
209
+
210
+ public function new( opts : S3Options ) {
211
+ _opts = opts;
212
+ _httpStatus = false;
213
+ }
214
+
215
+ function getUrl() {
216
+ var vanity = canUseVanityStyle();
217
+
218
+ if( _opts.secure && vanity && _opts.bucket.indexOf( "." ) > -1 )
219
+ throw new flash.errors.IllegalOperationError( "Cannot use SSL with bucket name containing '.': " + _opts.bucket );
220
+
221
+ var url = "http" + ( _opts.secure ? "s" : "" ) + "://";
222
+
223
+ if( vanity )
224
+ url += _opts.bucket + "." + AMAZON_BASE_URL;
225
+ else
226
+ url += AMAZON_BASE_URL + "/" + _opts.bucket;
227
+
228
+ return url;
229
+ }
230
+
231
+ function getVars() {
232
+ var vars = new flash.net.URLVariables();
233
+ vars.key = _opts.key;
234
+ vars.acl = _opts.acl;
235
+ vars.AWSAccessKeyId = _opts.accessKeyId;
236
+ vars.signature = _opts.signature;
237
+ Reflect.setField( vars , "Content-Type" , _opts.contentType );
238
+ vars.policy = _opts.policy;
239
+ vars.success_action_status = "201";
240
+ return vars;
241
+ }
242
+
243
+ function canUseVanityStyle() {
244
+ if( _opts.bucket.length < 3 || _opts.bucket.length > 63 )
245
+ return false;
246
+
247
+ var periodPosition = _opts.bucket.indexOf( "." );
248
+ if( periodPosition == 0 && periodPosition == _opts.bucket.length - 1 )
249
+ return false;
250
+
251
+ if( ~/^[0-9]|+\.[0-9]|+\.[0-9]|+\.[0-9]|+$/.match( _opts.bucket ) )
252
+ return false;
253
+
254
+ if( _opts.bucket.toLowerCase() != _opts.bucket )
255
+ return false;
256
+
257
+ return true;
258
+ }
259
+
260
+ public function upload( fr : flash.net.FileReference ) {
261
+ var url = getUrl();
262
+ flash.system.Security.loadPolicyFile(url + "/crossdomain.xml");
263
+
264
+ var req = new flash.net.URLRequest( url );
265
+ req.method = flash.net.URLRequestMethod.POST;
266
+ req.data = getVars();
267
+
268
+ fr.addEventListener( "uploadCompleteData" , onUploadComplete );
269
+ fr.addEventListener( "securityError" , onUploadError );
270
+ fr.addEventListener( "ioError" , onUploadError );
271
+ fr.addEventListener( "progress" , onUploadProgress);
272
+ fr.addEventListener( "open" , onUploadOpen);
273
+ fr.addEventListener( "httpStatus", onUploadHttpStatus);
274
+
275
+ fr.upload(req, "file", false);
276
+ }
277
+
278
+ function onUploadComplete( e ) {
279
+ if( isError( e.data ) )
280
+ onError( "Amazon S3 returned an error: " + e.data );
281
+ else {
282
+ onProgress( 1 );
283
+ onComplete();
284
+ }
285
+ }
286
+
287
+ function onUploadHttpStatus( e ) {
288
+ _httpStatus = true;
289
+ if( e.status >= 200 && e.status < 300 )
290
+ onComplete();
291
+ else
292
+ onError( "Amazon S3 returned an error: " + e.status );
293
+ }
294
+
295
+ function onUploadOpen( e ) {
296
+ onProgress( 0 );
297
+ }
298
+
299
+ function onUploadProgress( e ) {
300
+ onProgress( e.bytesLoaded / e.bytesTotal );
301
+ }
302
+
303
+ function onUploadError( e ) {
304
+ if( !_httpStatus ) // ignore io errors if we already had a valid http status
305
+ onError( "Amazon S3 returned an error: " + e.message );
306
+ }
307
+
308
+ function isError(responseText:String):Bool {
309
+ var xml = Xml.parse(responseText);
310
+ var root = xml.firstChild();
311
+ return root != null && root.nodeName == "Error";
312
+ }
313
+
314
+ }
@@ -0,0 +1,227 @@
1
+ (function($){
2
+ $.fn.s3upload = function( settings ) {
3
+ var config = {
4
+ text: null, // Defaults to the previous value of content of the element
5
+ path: "/s3upload.swf",
6
+ prefix: "",
7
+ element: "<input type='text' />",
8
+ signature_url: "/s3upload",
9
+ required: false,
10
+ submit_on_all_complete: true,
11
+ error_class: "s3_error",
12
+ file_types: []
13
+ };
14
+ if( settings ) $.extend( config , settings );
15
+
16
+ // TODO Define settings for the default visuals (like progress percentage or bar)
17
+
18
+ // if any element is a form find all :file inputs inside instead.
19
+ var elements = this.map( function() {
20
+ if( $(this).is("form") )
21
+ return $(this).find(":file").toArray();
22
+ return $(this);
23
+ } );
24
+
25
+ $.fn.s3upload.instances = $.fn.s3upload.instances || [];
26
+ var started = 0;
27
+ var completed = 0;
28
+
29
+ return elements.each( function() {
30
+ var form = $(this).closest("form");
31
+ var id = $(this).attr("id") || "s3upload_" + $.fn.s3upload.instances.length;
32
+ var name = $(this).attr("name");
33
+
34
+ // Ignore already created instances
35
+ if( $.inArray( id , $.fn.s3upload.instances ) != -1 )
36
+ return;
37
+
38
+ // replace the file input
39
+ var el = $(config.element);
40
+ el.attr( {
41
+ id: id,
42
+ "class": $(this).attr("class")
43
+ });
44
+ $(this).replaceWith(el);
45
+
46
+ // Find the value of the original to use (useful for when a previous "upload" exists)
47
+ var val = config.text;
48
+ if( !val ) {
49
+ if( $(this).is(":file") )
50
+ val = $(this).attr("defaultValue");
51
+ else if( $(this).is("input") )
52
+ val = $(this).val();
53
+ else
54
+ val = $(this).html();
55
+ }
56
+
57
+ // A helper method to set the value, even if it's an input element
58
+ var setValue = function(v) {
59
+ if( el.is("input") )
60
+ el.val(v);
61
+ else
62
+ el.html(v);
63
+ }
64
+
65
+ // create a div for the transparent flash button and absolute position it above the created element.
66
+ var flash_div = $( "<div id='s3upload_"+id+"'><div id='flash_"+id+"'></div></div>" ).appendTo( "body" );
67
+
68
+ // "Serialize" the filters
69
+ var filters = $.map( config.file_types , function(f,i) { return f.join("#"); }).join("|");
70
+
71
+ // Instantiate a swfobject in that div with js callbacks for selected, start, cancel, progress, error and complete
72
+ var fv = {
73
+ id: "flash_"+id,
74
+ prefix: config.prefix,
75
+ signatureURL: config.signature_url,
76
+ filters: filters
77
+ };
78
+
79
+ var params = { wmode: "transparent" , menu: false };
80
+ swfobject.embedSWF(config.path, fv.id, "100%", "100%", "9.0.0" , false, fv, params, false, function(e){
81
+ if( e.success ) {
82
+ var swf = e.ref;
83
+
84
+ // Now that the flash has been initialized, show the initial text
85
+ setValue(val);
86
+
87
+ // Position the swf on top of the element (and keep it positioned)
88
+ swf.update_interval = setInterval( function(){
89
+ var pos = el.offset();
90
+ flash_div.css({
91
+ width: el.outerWidth(),
92
+ height: el.outerHeight(),
93
+ position: "absolute",
94
+ top: pos.top,
95
+ left: pos.left
96
+ });
97
+ } , 100 );
98
+
99
+ // add a submit listener to the elements form which starts the upload in the flash instances.
100
+ var formSubmit = function(e){
101
+ if( config.required && !swf.info )
102
+ swf.onerror( "Error: No file selected." );
103
+ else if( swf.info )
104
+ swf.upload();
105
+ return false;
106
+ }
107
+
108
+ // Create SWF-JS callbacks
109
+ swf.onenabled = function() {
110
+ if( $.isFunction( config.onenabled ) )
111
+ config.onenabled.call(el);
112
+ if( !$.isFunction( swf.upload ) ) {
113
+ swf.onerror( "S3UploadError: Callbacks could not be initialized. Try enabling the browser cache." );
114
+ $(swf).parent().remove();
115
+ }
116
+ }
117
+ swf.ondisabled = function() {
118
+ if( $.isFunction( config.ondisabled ) )
119
+ config.ondisabled.call(el);
120
+ form.unbind("submit",formSubmit);
121
+ }
122
+ swf.onmouseevent = function(type,x,y) {
123
+ var def = true;
124
+ if( $.isFunction( config["on"+type] ) )
125
+ def = config["on"+type].call(el,x,y);
126
+ if( def ) {
127
+ // Do default stuff: Run the regular dom events on "el"?
128
+ el.trigger(type);
129
+ }
130
+ }
131
+ swf.onselect = function(name,size,type) {
132
+ swf.info = {name: name, size: size, type: type};
133
+ var def = true;
134
+ if( $.isFunction( config.onselect ) )
135
+ def = config.onselect.call(el,swf.info);
136
+ if( def ) {
137
+ // Do default stuff: Replace the text of the div with the filename?
138
+ setValue(config.prefix + name);
139
+ }
140
+ form.data("s3_selected",(form.data("s3_selected")||0) + 1 );
141
+ form.submit(formSubmit);
142
+ }
143
+ swf.oncancel = function() {
144
+ var def = true;
145
+ if( $.isFunction( config.oncancel ) )
146
+ def = config.oncancel.call(el,swf.info);
147
+ if( def ) {
148
+ // Do default stuff: Show a message? Go back to "Select file..." text?
149
+ setValue(val);
150
+ }
151
+ swf.info = null;
152
+ }
153
+ swf.onstart = function() {
154
+ var def = true;
155
+ if( $.isFunction( config.onstart ) )
156
+ def = config.onstart.call(el,swf.info);
157
+ if( def ) {
158
+ // Do default stuff: Replace the text of the div?
159
+ }
160
+ if( $.isFunction( swf.disable ) )
161
+ swf.disable();
162
+ }
163
+ swf.onprogress = function(p) {
164
+ var def = true;
165
+ if( $.isFunction( config.onprogress ) )
166
+ def = config.onprogress.call(el,p,swf.info);
167
+ if( def ) {
168
+ // Do default stuff: Fill the background of the div?
169
+ setValue( Math.ceil( p * 100 ) + "%" );
170
+ }
171
+ }
172
+ swf.onerror = function(msg) {
173
+ var def = true;
174
+ if( $.isFunction( config.onerror ) )
175
+ def = config.onerror.call(el,msg,swf.info);
176
+ if( def ) {
177
+ // Do default stuff: Replace the text with the error message?
178
+ if( !el.is("input") )
179
+ setValue( "<span class='"+config.error_class+"'>" + msg + "</span>" );
180
+ else
181
+ setValue( msg );
182
+ }
183
+ if( $.isFunction( swf.enable ) )
184
+ swf.enable();
185
+ }
186
+ swf.oncomplete = function(key) {
187
+ // Add the key to the info object
188
+ swf.info.key = key;
189
+
190
+ var def = true;
191
+ if( $.isFunction( config.oncomplete ) )
192
+ def = config.oncomplete.call(el,swf.info);
193
+ if( def ) {
194
+ // Do default stuff...
195
+ }
196
+
197
+ // Create/Update the hidden inputs
198
+ if( el.nextAll("input[name^="+name+"]").length == 0 )
199
+ for( var k in swf.info )
200
+ el.after( "<input type='hidden' name='"+name+"["+k+"]' value='"+swf.info[k]+"' />" );
201
+ else
202
+ for( var k in swf.info )
203
+ el.siblings( "input[name="+name+"["+k+"]]" ).val( swf.info[k] );
204
+
205
+ // Add to the total upload complete counter, and if it matches the number of uploads we submit the form.
206
+ form.data("s3_completed",(form.data("s3_completed")||0) + 1 );
207
+
208
+ var done = form.data("s3_completed") / form.data("s3_selected");
209
+
210
+ // All Done! Do a regular form submit or just re-enable the flash.
211
+ if( done == 1 ) {
212
+ if( !config.submit_on_all_complete ) {
213
+ if( $.isFunction( swf.enable ) )
214
+ swf.enable();
215
+ } else {
216
+ form.unbind();
217
+ form.submit();
218
+ }
219
+ }
220
+ }
221
+
222
+ }
223
+ });
224
+ $.fn.s3upload.instances.push( id );
225
+ } );
226
+ };
227
+ })(jQuery);
metadata ADDED
@@ -0,0 +1,78 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: s3upload
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - "Robert Sk\xC3\xB6ld"
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2010-01-25 00:00:00 +01:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description: A jQuery plugin for direct upload to an Amazon S3 bucket.
17
+ email: ""
18
+ executables:
19
+ - jquery.s3upload.min.js
20
+ - s3upload.swf
21
+ extensions: []
22
+
23
+ extra_rdoc_files:
24
+ - CHANGELOG
25
+ - LICENSE
26
+ - README.md
27
+ - bin/jquery.s3upload.min.js
28
+ - bin/s3upload.swf
29
+ - lib/s3upload.rb
30
+ files:
31
+ - CHANGELOG
32
+ - LICENSE
33
+ - Manifest
34
+ - README.md
35
+ - Rakefile
36
+ - bin/jquery.s3upload.min.js
37
+ - bin/s3upload.swf
38
+ - lib/s3upload.rb
39
+ - s3upload.gemspec
40
+ - src/build.hxml
41
+ - src/hx/MimeTypes.hx
42
+ - src/hx/S3Upload.hx
43
+ - src/js/jquery.s3upload.js
44
+ has_rdoc: true
45
+ homepage: http://github.com/slaskis/s3upload
46
+ licenses: []
47
+
48
+ post_install_message:
49
+ rdoc_options:
50
+ - --line-numbers
51
+ - --inline-source
52
+ - --title
53
+ - S3upload
54
+ - --main
55
+ - README.md
56
+ require_paths:
57
+ - lib
58
+ required_ruby_version: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - ">="
61
+ - !ruby/object:Gem::Version
62
+ version: "0"
63
+ version:
64
+ required_rubygems_version: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: "1.2"
69
+ version:
70
+ requirements: []
71
+
72
+ rubyforge_project: s3upload
73
+ rubygems_version: 1.3.5
74
+ signing_key:
75
+ specification_version: 3
76
+ summary: A jQuery plugin for direct upload to an Amazon S3 bucket.
77
+ test_files: []
78
+