s3_cors_fileupload 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/.gitignore +51 -0
- data/.rspec +1 -0
- data/Gemfile +14 -0
- data/Gemfile.lock +39 -0
- data/LICENSE.txt +20 -0
- data/README.md +104 -0
- data/Rakefile +58 -0
- data/lib/generators/s3_cors_fileupload/install/USAGE +17 -0
- data/lib/generators/s3_cors_fileupload/install/install_generator.rb +51 -0
- data/lib/generators/s3_cors_fileupload/install/templates/amazon_s3.yml +17 -0
- data/lib/generators/s3_cors_fileupload/install/templates/create_source_files.rb +14 -0
- data/lib/generators/s3_cors_fileupload/install/templates/s3_uploads.js +94 -0
- data/lib/generators/s3_cors_fileupload/install/templates/s3_uploads_controller.rb +90 -0
- data/lib/generators/s3_cors_fileupload/install/templates/source_file.rb +53 -0
- data/lib/generators/s3_cors_fileupload/install/templates/views/_template_download.html.erb +29 -0
- data/lib/generators/s3_cors_fileupload/install/templates/views/_template_upload.html.erb +31 -0
- data/lib/generators/s3_cors_fileupload/install/templates/views/_template_uploaded.html.erb +25 -0
- data/lib/generators/s3_cors_fileupload/install/templates/views/index.html.erb +43 -0
- data/lib/s3_cors_fileupload.rb +2 -0
- data/lib/s3_cors_fileupload/rails.rb +8 -0
- data/lib/s3_cors_fileupload/rails/config.rb +27 -0
- data/lib/s3_cors_fileupload/rails/engine.rb +6 -0
- data/lib/s3_cors_fileupload/rails/form_helper.rb +91 -0
- data/lib/s3_cors_fileupload/rails/policy_helper.rb +48 -0
- data/lib/s3_cors_fileupload/version.rb +5 -0
- data/s3_cors_fileupload.gemspec +35 -0
- data/spec/s3_cors_fileupload/version_spec.rb +17 -0
- data/spec/s3_cors_fileupload_spec.rb +9 -0
- data/spec/spec_helper.rb +16 -0
- data/vendor/assets/images/loading.gif +0 -0
- data/vendor/assets/images/progressbar.gif +0 -0
- data/vendor/assets/javascripts/s3_cors_fileupload/index.js +6 -0
- data/vendor/assets/javascripts/s3_cors_fileupload/jquery.fileupload-ui.js +732 -0
- data/vendor/assets/javascripts/s3_cors_fileupload/jquery.fileupload.js +1106 -0
- data/vendor/assets/javascripts/s3_cors_fileupload/jquery.iframe-transport.js +172 -0
- data/vendor/assets/javascripts/s3_cors_fileupload/vendor/jquery.ui.widget.js +511 -0
- data/vendor/assets/javascripts/s3_cors_fileupload/vendor/load-image.js +122 -0
- data/vendor/assets/javascripts/s3_cors_fileupload/vendor/tmpl.js +87 -0
- data/vendor/assets/stylesheets/jquery.fileupload-ui.css.erb +85 -0
- metadata +205 -0
@@ -0,0 +1,90 @@
|
|
1
|
+
require 'base64'
|
2
|
+
require 'openssl'
|
3
|
+
require 'digest/sha1'
|
4
|
+
|
5
|
+
class S3UploadsController < ApplicationController
|
6
|
+
|
7
|
+
helper_method :s3_upload_policy_document, :s3_upload_signature
|
8
|
+
|
9
|
+
# GET /source_files
|
10
|
+
# GET /source_files.json
|
11
|
+
def index
|
12
|
+
@source_files = SourceFile.all
|
13
|
+
|
14
|
+
respond_to do |format|
|
15
|
+
format.html # index.html.erb
|
16
|
+
format.json { render json: @source_files.map{|sf| sf.to_jq_upload } }
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# POST /source_files
|
21
|
+
# POST /source_files.json
|
22
|
+
def create
|
23
|
+
@source_file = SourceFile.new(params[:source_file])
|
24
|
+
respond_to do |format|
|
25
|
+
if @source_file.save
|
26
|
+
format.html {
|
27
|
+
render :json => @source_file.to_jq_upload,
|
28
|
+
:content_type => 'text/html',
|
29
|
+
:layout => false
|
30
|
+
}
|
31
|
+
format.json { render json: @source_file.to_jq_upload, status: :created }
|
32
|
+
else
|
33
|
+
format.html { render action: "new" }
|
34
|
+
format.json { render json: @source_file.errors, status: :unprocessable_entity }
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# DELETE /source_files/1
|
40
|
+
# DELETE /source_files/1.json
|
41
|
+
def destroy
|
42
|
+
@source_file = SourceFile.find(params[:id])
|
43
|
+
@source_file.destroy
|
44
|
+
|
45
|
+
respond_to do |format|
|
46
|
+
format.html { redirect_to source_files_url }
|
47
|
+
format.json { head :no_content }
|
48
|
+
format.xml { head :no_content }
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
# used for s3_uploader
|
53
|
+
def generate_key
|
54
|
+
uid = SecureRandom.uuid.gsub(/-/,'')
|
55
|
+
|
56
|
+
render json: {
|
57
|
+
key: "uploads/#{uid}/#{params[:filename]}",
|
58
|
+
success_action_redirect: "/"
|
59
|
+
}
|
60
|
+
end
|
61
|
+
|
62
|
+
# ---- Helpers ----
|
63
|
+
# generate the policy document that amazon is expecting.
|
64
|
+
def s3_upload_policy_document
|
65
|
+
Base64.encode64(
|
66
|
+
{
|
67
|
+
expiration: 1.hour.from_now.utc.strftime('%Y-%m-%dT%H:%M:%S.000Z'),
|
68
|
+
conditions: [
|
69
|
+
{ bucket: S3CorsFileupload::Config.bucket },
|
70
|
+
{ acl: 'public-read' },
|
71
|
+
{ success_action_status: '201' },
|
72
|
+
["starts-with", "$key", ""],
|
73
|
+
["starts-with", "$Content-Type", ""]
|
74
|
+
]
|
75
|
+
}.to_json
|
76
|
+
).gsub(/\n|\r/, '')
|
77
|
+
end
|
78
|
+
|
79
|
+
# sign our request by Base64 encoding the policy document.
|
80
|
+
def s3_upload_signature
|
81
|
+
Base64.encode64(
|
82
|
+
OpenSSL::HMAC.digest(
|
83
|
+
OpenSSL::Digest::Digest.new('sha1'),
|
84
|
+
S3CorsFileupload::Config.secret_access_key,
|
85
|
+
s3_upload_policy_document
|
86
|
+
)
|
87
|
+
).gsub(/\n/, '')
|
88
|
+
end
|
89
|
+
|
90
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'aws/s3'
|
2
|
+
|
3
|
+
class SourceFile < ActiveRecord::Base
|
4
|
+
attr_accessible :url, :bucket, :key
|
5
|
+
|
6
|
+
validates_presence_of :file_name, :file_content_type, :file_size, :key, :bucket
|
7
|
+
|
8
|
+
before_validation(:on => :create) do
|
9
|
+
self.file_name = key.split('/').last if key
|
10
|
+
# for some reason, the response from AWS seems to escape the slashes in the keys, this line will unescape the slash
|
11
|
+
self.url = url.gsub(/%2F/, '/') if url
|
12
|
+
self.file_size ||= s3_object.try(:size)
|
13
|
+
self.file_content_type ||= s3_object.try(:content_type)
|
14
|
+
end
|
15
|
+
# make all attributes readonly after creating the record (not sure we need this?)
|
16
|
+
after_create { readonly! }
|
17
|
+
# cleanup; destroy corresponding file on S3
|
18
|
+
after_destroy { s3_object.try(:delete) }
|
19
|
+
|
20
|
+
def to_jq_upload
|
21
|
+
{
|
22
|
+
'id' => id,
|
23
|
+
'name' => file_name,
|
24
|
+
'size' => file_size,
|
25
|
+
'url' => url,
|
26
|
+
'image' => self.is_image?,
|
27
|
+
'delete_url' => Rails.application.routes.url_helpers.source_file_path(self)
|
28
|
+
}
|
29
|
+
end
|
30
|
+
|
31
|
+
def is_image?
|
32
|
+
!!file_content_type.try(:match, /image/)
|
33
|
+
end
|
34
|
+
|
35
|
+
#---- start S3 related methods -----
|
36
|
+
def s3_object
|
37
|
+
@s3_object ||= AWS::S3::S3Object.find(key, bucket) if self.class.open_aws && key
|
38
|
+
rescue
|
39
|
+
nil
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.open_aws
|
43
|
+
unless AWS::S3::Base.connected?
|
44
|
+
AWS::S3::Base.establish_connection!(
|
45
|
+
:access_key_id => S3CorsFileupload::Config.access_key_id,
|
46
|
+
:secret_access_key => S3CorsFileupload::Config.secret_access_key
|
47
|
+
)
|
48
|
+
end
|
49
|
+
return AWS::S3::Base.connected?
|
50
|
+
end
|
51
|
+
#---- end S3 related methods -----
|
52
|
+
|
53
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
<!-- The template to display files available for download -->
|
2
|
+
<script id="template-download" type="text/x-tmpl">
|
3
|
+
{% for (var i=0, file; file=o.files[i]; i++) { %}
|
4
|
+
<tr class="template-download fade">
|
5
|
+
{% if (file.error) { %}
|
6
|
+
<td></td>
|
7
|
+
<td class="name"><span>{%=file.name%}</span></td>
|
8
|
+
<td class="size"><span>{%=o.formatFileSize(file.size)%}</span></td>
|
9
|
+
<td class="error" colspan="2"><span class="label label-important">Error</span> {%=file.error%}</td>
|
10
|
+
{% } else { %}
|
11
|
+
<td class="preview">{% if (file.thumbnail_url) { %}
|
12
|
+
<a href="{%=file.url%}" title="{%=file.name%}" rel="gallery" download="{%=file.name%}"><img src="{%=file.thumbnail_url%}"></a>
|
13
|
+
{% } %}</td>
|
14
|
+
<td class="name">
|
15
|
+
<a href="{%=file.url%}" title="{%=file.name%}" rel="{%=file.thumbnail_url&&'gallery'%}" download="{%=file.name%}">{%=file.name%}</a>
|
16
|
+
</td>
|
17
|
+
<td class="size"><span>{%=o.formatFileSize(file.size)%}</span></td>
|
18
|
+
<td colspan="2"></td>
|
19
|
+
{% } %}
|
20
|
+
<td class="delete">
|
21
|
+
<button class="btn btn-danger" data-type="{%=file.delete_type%}" data-url="{%=file.delete_url%}">
|
22
|
+
<i class="icon-trash icon-white"></i>
|
23
|
+
<span>Delete</span>
|
24
|
+
</button>
|
25
|
+
<input type="checkbox" name="delete" value="1">
|
26
|
+
</td>
|
27
|
+
</tr>
|
28
|
+
{% } %}
|
29
|
+
</script>
|
@@ -0,0 +1,31 @@
|
|
1
|
+
<!-- The template to display files available for upload -->
|
2
|
+
<script id="template-upload" type="text/x-tmpl">
|
3
|
+
{% for (var i=0, file; file=o.files[i]; i++) { %}
|
4
|
+
<tr class="template-upload fade">
|
5
|
+
<td class="preview"><span class="fade"></span></td>
|
6
|
+
<td class="name"><span>{%=file.name%}</span></td>
|
7
|
+
<td class="size"><span>{%=o.formatFileSize(file.size)%}</span></td>
|
8
|
+
{% if (file.error) { %}
|
9
|
+
<td class="error" colspan="2"><span class="label label-important">Error</span> {%=file.error%}</td>
|
10
|
+
{% } else if (o.files.valid && !i) { %}
|
11
|
+
<td>
|
12
|
+
<div class="progress progress-success progress-striped active"><div class="bar" style="width:0%;"></div></div>
|
13
|
+
</td>
|
14
|
+
<td class="start">{% if (!o.options.autoUpload) { %}
|
15
|
+
<button class="btn btn-primary">
|
16
|
+
<i class="icon-upload icon-white"></i>
|
17
|
+
<span>Start</span>
|
18
|
+
</button>
|
19
|
+
{% } %}</td>
|
20
|
+
{% } else { %}
|
21
|
+
<td colspan="2"></td>
|
22
|
+
{% } %}
|
23
|
+
<td class="cancel">{% if (!i) { %}
|
24
|
+
<button class="btn btn-warning">
|
25
|
+
<i class="icon-ban-circle icon-white"></i>
|
26
|
+
<span>Cancel</span>
|
27
|
+
</button>
|
28
|
+
{% } %}</td>
|
29
|
+
</tr>
|
30
|
+
{% } %}
|
31
|
+
</script>
|
@@ -0,0 +1,25 @@
|
|
1
|
+
<!-- The template to display files that have been uploaded to S3 already -->
|
2
|
+
<!-- This essentially is a stand-in for the template_download partial for the s3_cors_fileupload gem's purposes -->
|
3
|
+
<script id="template-uploaded" type="text/x-tmpl">
|
4
|
+
<tr class="template-uploaded" id="source_file_{%=o.id%}">
|
5
|
+
<td class="preview">
|
6
|
+
{% if (o.image == true) { %}
|
7
|
+
<a href="{%=o.url%}" title="{%=o.name%}" rel="gallery" download="{%=o.name%}">
|
8
|
+
<image src="{%=o.url%}", style='width:80px; height:56px;'></image>
|
9
|
+
</a>
|
10
|
+
{% } %}
|
11
|
+
</td>
|
12
|
+
<td class="name">
|
13
|
+
<a href="{%=o.url%}" title="{%=o.name%}" rel="{%=o.thumbnail_url&&'gallery'%}" download="{%=o.name%}">{%=o.name%}</a>
|
14
|
+
</td>
|
15
|
+
<td class="size"><span>{%=formatFileSize(o.size)%}</span></td>
|
16
|
+
<td colspan="2"></td>
|
17
|
+
<td class="delete">
|
18
|
+
<button class="btn btn-danger" data-type="DELETE" data-url="{%=o.delete_url%}">
|
19
|
+
<i class="icon-trash icon-white"></i>
|
20
|
+
<span>Delete</span>
|
21
|
+
</button>
|
22
|
+
<input type="checkbox" name="delete" value="1">
|
23
|
+
</td>
|
24
|
+
</tr>
|
25
|
+
</script>
|
@@ -0,0 +1,43 @@
|
|
1
|
+
<%= stylesheet_link_tag '//netdna.bootstrapcdn.com/twitter-bootstrap/2.1.1/css/bootstrap-combined.min.css' %>
|
2
|
+
|
3
|
+
<div style="padding: 10px;">
|
4
|
+
<h2>Upload file</h2>
|
5
|
+
<%= s3_cors_fileupload_form :id => 'fileupload' %>
|
6
|
+
|
7
|
+
<div class="javascript-templates">
|
8
|
+
<%= render 'template_upload' %>
|
9
|
+
<%= render 'template_uploaded' %>
|
10
|
+
<%# render 'template_download' %>
|
11
|
+
</div>
|
12
|
+
</div>
|
13
|
+
|
14
|
+
<script>
|
15
|
+
//<![CDATA[
|
16
|
+
$(function () {
|
17
|
+
// Initialize the jQuery File Upload widget:
|
18
|
+
$('#fileupload').fileupload({
|
19
|
+
dataType: 'xml',
|
20
|
+
sequentialUploads: true,
|
21
|
+
downloadTemplateId: null,
|
22
|
+
downloadTemplate: null
|
23
|
+
});
|
24
|
+
|
25
|
+
// Load existing files:
|
26
|
+
$.getJSON('/source_files', function (files) {
|
27
|
+
$.each(files, function(index, value) {
|
28
|
+
$('#upload_files tbody').append(tmpl('template-uploaded', value));
|
29
|
+
});
|
30
|
+
});
|
31
|
+
});
|
32
|
+
|
33
|
+
// used by the jQuery File Upload
|
34
|
+
var fileUploadErrors = {
|
35
|
+
maxFileSize: 'File is too big',
|
36
|
+
minFileSize: 'File is too small',
|
37
|
+
acceptFileTypes: 'Filetype not allowed',
|
38
|
+
maxNumberOfFiles: 'Max number of files exceeded',
|
39
|
+
uploadedBytes: 'Uploaded bytes exceed file size',
|
40
|
+
emptyResult: 'Empty file upload result'
|
41
|
+
};
|
42
|
+
//]]>
|
43
|
+
</script>
|
@@ -0,0 +1,8 @@
|
|
1
|
+
if defined?(::Rails)
|
2
|
+
require 's3_cors_fileupload/rails/config'
|
3
|
+
require 's3_cors_fileupload/rails/engine' if ::Rails.version >= '3.1'
|
4
|
+
require 's3_cors_fileupload/rails/policy_helper'
|
5
|
+
require 's3_cors_fileupload/rails/form_helper'
|
6
|
+
|
7
|
+
ActionView::Base.send(:include, S3CorsFileupload::FormHelper) if defined?(ActionView::Base)
|
8
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module S3CorsFileupload
|
2
|
+
module Config
|
3
|
+
require 'yaml'
|
4
|
+
|
5
|
+
# this allows us to lazily instantiate the configuration by reading it in when it needs to be accessed
|
6
|
+
class << self
|
7
|
+
# if a method is called on the class, attempt to look it up in the config array
|
8
|
+
def method_missing(meth, *args, &block)
|
9
|
+
if args.empty? && block.nil?
|
10
|
+
config[meth.to_s]
|
11
|
+
else
|
12
|
+
super
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def config
|
19
|
+
@config ||= YAML.load_file(File.join(::Rails.root, 'config', 'amazon_s3.yml'))[::Rails.env]
|
20
|
+
rescue
|
21
|
+
warn('WARNING: s3_cors_fileupload gem was unable to locate a configuration file in config/amazon_s3.yml and may not ' +
|
22
|
+
'be able to function properly. Please run `rails generate s3_cors_upload:install` before proceeding.')
|
23
|
+
{}
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
module S3CorsFileupload
|
2
|
+
module FormHelper
|
3
|
+
# Options:
|
4
|
+
# :access_key_id The AWS Access Key ID of the owner of the bucket.
|
5
|
+
# Defaults to the `Config.access_key_id` (read from the yaml config file).
|
6
|
+
#
|
7
|
+
# :acl One of S3's Canned Access Control Lists, must be one of:
|
8
|
+
# 'private', 'public-read', 'public-read-write', 'authenticated-read'.
|
9
|
+
# Defaults to `'public-read'`.
|
10
|
+
#
|
11
|
+
# :max_file_size The max file size (in bytes) that you wish to allow to be uploaded.
|
12
|
+
# Defaults to `Config.max_file_size` or, if no value is set on the yaml file `524288000` (500 MB)
|
13
|
+
#
|
14
|
+
# :bucket The name of the bucket on S3 you wish for the files to be uploaded to.
|
15
|
+
# Defaults to `Config.bucket` (read from the yaml config file).
|
16
|
+
#
|
17
|
+
# Any other key creates standard HTML options for the form tag.
|
18
|
+
def s3_cors_fileupload_form(options = {}, &block)
|
19
|
+
policy_helper = PolicyHelper.new(options)
|
20
|
+
# initialize the hidden form fields
|
21
|
+
hidden_form_fields = {
|
22
|
+
:key => '',
|
23
|
+
'Content-Type' => '',
|
24
|
+
'AWSAccessKeyId' => options[:access_key_id] || Config.access_key_id,
|
25
|
+
:acl => policy_helper.options[:acl],
|
26
|
+
:policy => policy_helper.policy_document,
|
27
|
+
:signature => policy_helper.upload_signature,
|
28
|
+
:success_action_status => '201'
|
29
|
+
}
|
30
|
+
# assume that all of the non-documented keys are
|
31
|
+
_html_options = options.reject { |key, val| [:access_key_id, :acl, :max_file_size, :bucket].include?(key) }
|
32
|
+
# return the form html
|
33
|
+
construct_form_html(hidden_form_fields, policy_helper.options[:bucket], _html_options, &block)
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def build_form_options(options = {})
|
39
|
+
{ :id => 'fileupload' }.merge(options).merge(:multipart => true, :authenticity_token => false)
|
40
|
+
end
|
41
|
+
|
42
|
+
# hidden fields argument should be a hash of key value pairs (values may be blank if desired)
|
43
|
+
def construct_form_html(hidden_fields, bucket, html_options = {}, &block)
|
44
|
+
# now build the html for the form
|
45
|
+
form_text = form_tag("https://#{bucket}.s3.amazonaws.com", build_form_options(html_options)) do
|
46
|
+
hidden_fields.map do |name, value|
|
47
|
+
hidden_field_tag(name, value)
|
48
|
+
end.join.html_safe + "
|
49
|
+
<!-- The fileupload-buttonbar contains buttons to add/delete files and start/cancel the upload -->
|
50
|
+
<div class='row fileupload-buttonbar'>
|
51
|
+
<div class='span7'>
|
52
|
+
<button class='btn btn-success fileinput-button'>
|
53
|
+
<i class='icon-plus icon-white'></i>
|
54
|
+
<span>Add files...</span>
|
55
|
+
".html_safe +
|
56
|
+
file_field_tag(:file, :multiple => true) + "
|
57
|
+
</button>
|
58
|
+
<button class='btn btn-primary start' type='submit'>
|
59
|
+
<i class='icon-upload icon-white'></i>
|
60
|
+
<span>Start upload</span>
|
61
|
+
</button>
|
62
|
+
<button class='btn btn-warning cancel' type='reset'>
|
63
|
+
<i class='icon-ban-circle icon-white'></i>
|
64
|
+
<span>Cancel upload</span>
|
65
|
+
</button>
|
66
|
+
<button class='btn btn-danger delete' type='button'>
|
67
|
+
<i class='icon-trash icon-white'></i>
|
68
|
+
<span>Delete</span>
|
69
|
+
</button>
|
70
|
+
<input class='toggle' type='checkbox'></input>
|
71
|
+
</div>
|
72
|
+
<div class='span5'>
|
73
|
+
<!-- The global progress bar -->
|
74
|
+
<div class='progress progress-success progress-striped active fade'>
|
75
|
+
<div class='bar' style='width: 0%'></div>
|
76
|
+
</div>
|
77
|
+
</div>
|
78
|
+
</div>
|
79
|
+
<!-- The loading indicator is shown during image processing -->
|
80
|
+
<div class='fileupload-loading'></div>
|
81
|
+
<br>
|
82
|
+
<!-- The table listing the files available for upload/download -->
|
83
|
+
<table class='table table-striped' id='upload_files'>
|
84
|
+
<tbody class='files' data-target='#modal-gallery' data-toggle='modal-gallery'></tbody>
|
85
|
+
</table>".html_safe
|
86
|
+
end
|
87
|
+
form_text += capture(&block) if block
|
88
|
+
form_text
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'base64'
|
2
|
+
require 'openssl'
|
3
|
+
require 'digest/sha1'
|
4
|
+
require 'json'
|
5
|
+
|
6
|
+
module S3CorsFileupload
|
7
|
+
class PolicyHelper
|
8
|
+
attr_reader :options
|
9
|
+
|
10
|
+
def initialize(_options = {})
|
11
|
+
# default max_file_size to 500 MB if nothing is received
|
12
|
+
@options = {
|
13
|
+
:acl => 'public-read',
|
14
|
+
:max_file_size => Config.max_file_size || 524288000,
|
15
|
+
:bucket => Config.bucket
|
16
|
+
}.merge(_options).merge(:secret_access_key => Config.secret_access_key)
|
17
|
+
end
|
18
|
+
|
19
|
+
# generate the policy document that amazon is expecting.
|
20
|
+
def policy_document
|
21
|
+
Base64.encode64(
|
22
|
+
{
|
23
|
+
expiration: 1.hour.from_now.utc.strftime('%Y-%m-%dT%H:%M:%S.000Z'),
|
24
|
+
conditions: [
|
25
|
+
{ bucket: options[:bucket] },
|
26
|
+
{ acl: options[:acl] },
|
27
|
+
{ success_action_status: '201' },
|
28
|
+
["content-length-range", 0, options[:max_file_size]],
|
29
|
+
["starts-with", "$utf8", ""],
|
30
|
+
["starts-with", "$key", ""],
|
31
|
+
["starts-with", "$Content-Type", ""]
|
32
|
+
]
|
33
|
+
}.to_json
|
34
|
+
).gsub(/\n|\r/, '')
|
35
|
+
end
|
36
|
+
|
37
|
+
# sign our request by Base64 encoding the policy document.
|
38
|
+
def upload_signature
|
39
|
+
Base64.encode64(
|
40
|
+
OpenSSL::HMAC.digest(
|
41
|
+
OpenSSL::Digest::Digest.new('sha1'),
|
42
|
+
options[:secret_access_key],
|
43
|
+
self.policy_document
|
44
|
+
)
|
45
|
+
).gsub(/\n/, '')
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|