refile 0.5.5 → 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/refile.rb +252 -27
- data/lib/refile/app.rb +55 -14
- data/lib/refile/attacher.rb +39 -40
- data/lib/refile/attachment.rb +28 -13
- data/lib/refile/attachment/active_record.rb +90 -1
- data/lib/refile/attachment_definition.rb +47 -0
- data/lib/refile/backend/s3.rb +1 -147
- data/lib/refile/backend_macros.rb +13 -5
- data/lib/refile/custom_logger.rb +3 -1
- data/lib/refile/file.rb +9 -0
- data/lib/refile/image_processing.rb +1 -143
- data/lib/refile/rails.rb +30 -0
- data/lib/refile/rails/attachment_helper.rb +27 -16
- data/lib/refile/signature.rb +5 -0
- data/lib/refile/simple_form.rb +17 -0
- data/lib/refile/version.rb +1 -1
- data/spec/refile/active_record_helper.rb +11 -0
- data/spec/refile/app_spec.rb +197 -20
- data/spec/refile/attachment/active_record_spec.rb +298 -1
- data/spec/refile/attachment_helper_spec.rb +39 -0
- data/spec/refile/attachment_spec.rb +53 -5
- data/spec/refile/backend_examples.rb +13 -2
- data/spec/refile/backend_macros_spec.rb +27 -6
- data/spec/refile/custom_logger_spec.rb +2 -3
- data/spec/refile/features/direct_upload_spec.rb +18 -0
- data/spec/refile/features/multiple_upload_spec.rb +122 -0
- data/spec/refile/features/normal_upload_spec.rb +5 -3
- data/spec/refile/features/presigned_upload_spec.rb +4 -0
- data/spec/refile/features/simple_form_spec.rb +8 -0
- data/spec/refile/fixtures/monkey.txt +1 -0
- data/spec/refile/fixtures/world.txt +1 -0
- data/spec/refile/spec_helper.rb +21 -11
- data/spec/refile_spec.rb +253 -24
- metadata +12 -303
- data/.gitignore +0 -27
- data/.rspec +0 -2
- data/.rubocop.yml +0 -68
- data/.travis.yml +0 -21
- data/.yardopts +0 -1
- data/CONTRIBUTING.md +0 -33
- data/Gemfile +0 -3
- data/History.md +0 -96
- data/LICENSE.txt +0 -22
- data/README.md +0 -651
- data/Rakefile +0 -19
- data/app/assets/javascripts/refile.js +0 -63
- data/config.ru +0 -8
- data/config/locales/en.yml +0 -8
- data/config/routes.rb +0 -5
- data/refile.gemspec +0 -42
- data/spec/refile/backend/s3_spec.rb +0 -11
- data/spec/refile/test_app.rb +0 -65
- data/spec/refile/test_app/app/assets/javascripts/application.js +0 -42
- data/spec/refile/test_app/app/controllers/application_controller.rb +0 -2
- data/spec/refile/test_app/app/controllers/direct_posts_controller.rb +0 -15
- data/spec/refile/test_app/app/controllers/home_controller.rb +0 -4
- data/spec/refile/test_app/app/controllers/normal_posts_controller.rb +0 -48
- data/spec/refile/test_app/app/controllers/presigned_posts_controller.rb +0 -31
- data/spec/refile/test_app/app/models/post.rb +0 -5
- data/spec/refile/test_app/app/views/direct_posts/new.html.erb +0 -20
- data/spec/refile/test_app/app/views/home/index.html.erb +0 -1
- data/spec/refile/test_app/app/views/layouts/application.html.erb +0 -14
- data/spec/refile/test_app/app/views/normal_posts/_form.html.erb +0 -28
- data/spec/refile/test_app/app/views/normal_posts/edit.html.erb +0 -1
- data/spec/refile/test_app/app/views/normal_posts/index.html +0 -5
- data/spec/refile/test_app/app/views/normal_posts/new.html.erb +0 -1
- data/spec/refile/test_app/app/views/normal_posts/show.html.erb +0 -19
- data/spec/refile/test_app/app/views/presigned_posts/new.html.erb +0 -16
- data/spec/refile/test_app/config/database.yml +0 -7
- data/spec/refile/test_app/config/routes.rb +0 -17
- data/spec/refile/test_app/public/favicon.ico +0 -0
@@ -6,8 +6,8 @@ module Refile
|
|
6
6
|
def verify_id(method)
|
7
7
|
mod = Module.new do
|
8
8
|
define_method(method) do |id|
|
9
|
-
id = id
|
10
|
-
if id
|
9
|
+
id = self.class.decode_id(id)
|
10
|
+
if self.class.valid_id?(id)
|
11
11
|
super(id)
|
12
12
|
else
|
13
13
|
raise Refile::InvalidID
|
@@ -20,18 +20,26 @@ module Refile
|
|
20
20
|
def verify_uploadable(method)
|
21
21
|
mod = Module.new do
|
22
22
|
define_method(method) do |uploadable|
|
23
|
-
[:size, :read, :eof?, :close].each do |m|
|
23
|
+
[:size, :read, :eof?, :rewind, :close].each do |m|
|
24
24
|
unless uploadable.respond_to?(m)
|
25
|
-
raise
|
25
|
+
raise Refile::InvalidFile, "does not respond to `#{m}`."
|
26
26
|
end
|
27
27
|
end
|
28
28
|
if max_size and uploadable.size > max_size
|
29
|
-
raise Refile::
|
29
|
+
raise Refile::InvalidMaxSize, "#{uploadable.inspect} is too large"
|
30
30
|
end
|
31
31
|
super(uploadable)
|
32
32
|
end
|
33
33
|
end
|
34
34
|
prepend mod
|
35
35
|
end
|
36
|
+
|
37
|
+
def valid_id?(id)
|
38
|
+
id =~ /\A[a-z0-9]+\z/i
|
39
|
+
end
|
40
|
+
|
41
|
+
def decode_id(id)
|
42
|
+
id.to_s
|
43
|
+
end
|
36
44
|
end
|
37
45
|
end
|
data/lib/refile/custom_logger.rb
CHANGED
data/lib/refile/file.rb
CHANGED
@@ -71,9 +71,18 @@ module Refile
|
|
71
71
|
|
72
72
|
Tempfile.new(id, binmode: true).tap do |tempfile|
|
73
73
|
IO.copy_stream(io, tempfile)
|
74
|
+
tempfile.rewind
|
75
|
+
tempfile.fsync
|
74
76
|
end
|
75
77
|
end
|
76
78
|
|
79
|
+
# Rewind to beginning of file.
|
80
|
+
#
|
81
|
+
# @return [nil]
|
82
|
+
def rewind
|
83
|
+
@io = nil
|
84
|
+
end
|
85
|
+
|
77
86
|
private
|
78
87
|
|
79
88
|
def io
|
@@ -1,143 +1 @@
|
|
1
|
-
|
2
|
-
require "mini_magick"
|
3
|
-
|
4
|
-
module Refile
|
5
|
-
# Processes images via MiniMagick, resizing cropping and padding them.
|
6
|
-
class ImageProcessor
|
7
|
-
# @param [Symbol] method The method to invoke on {#call}
|
8
|
-
def initialize(method)
|
9
|
-
@method = method
|
10
|
-
end
|
11
|
-
|
12
|
-
# Changes the image encoding format to the given format
|
13
|
-
#
|
14
|
-
# @see http://www.imagemagick.org/script/command-line-options.php#format
|
15
|
-
# @param [MiniMagick::Image] img the image to convert
|
16
|
-
# @param [String] format the format to convert to
|
17
|
-
# @return [void]
|
18
|
-
def convert(img, format)
|
19
|
-
img.format(format.to_s.downcase, nil)
|
20
|
-
end
|
21
|
-
|
22
|
-
# Resize the image to fit within the specified dimensions while retaining
|
23
|
-
# the original aspect ratio. Will only resize the image if it is larger
|
24
|
-
# than the specified dimensions. The resulting image may be shorter or
|
25
|
-
# narrower than specified in either dimension but will not be larger than
|
26
|
-
# the specified values.
|
27
|
-
#
|
28
|
-
# @param [MiniMagick::Image] img the image to convert
|
29
|
-
# @param [#to_s] width the maximum width
|
30
|
-
# @param [#to_s] height the maximum height
|
31
|
-
# @return [void]
|
32
|
-
def limit(img, width, height)
|
33
|
-
img.resize "#{width}x#{height}>"
|
34
|
-
end
|
35
|
-
|
36
|
-
# Resize the image to fit within the specified dimensions while retaining
|
37
|
-
# the original aspect ratio. The image may be shorter or narrower than
|
38
|
-
# specified in the smaller dimension but will not be larger than the
|
39
|
-
# specified values.
|
40
|
-
#
|
41
|
-
# @param [MiniMagick::Image] img the image to convert
|
42
|
-
# @param [#to_s] width the width to fit into
|
43
|
-
# @param [#to_s] height the height to fit into
|
44
|
-
# @return [void]
|
45
|
-
def fit(img, width, height)
|
46
|
-
img.resize "#{width}x#{height}"
|
47
|
-
end
|
48
|
-
|
49
|
-
# Resize the image so that it is at least as large in both dimensions as
|
50
|
-
# specified, then crops any excess outside the specified dimensions.
|
51
|
-
#
|
52
|
-
# The resulting image will always be exactly as large as the specified
|
53
|
-
# dimensions.
|
54
|
-
#
|
55
|
-
# By default, the center part of the image is kept, and the remainder
|
56
|
-
# cropped off, but this can be changed via the `gravity` option.
|
57
|
-
#
|
58
|
-
# @param [MiniMagick::Image] img the image to convert
|
59
|
-
# @param [#to_s] width the width to fill out
|
60
|
-
# @param [#to_s] height the height to fill out
|
61
|
-
# @param [String] gravity which part of the image to focus on
|
62
|
-
# @return [void]
|
63
|
-
# @see http://www.imagemagick.org/script/command-line-options.php#gravity
|
64
|
-
def fill(img, width, height, gravity = "Center")
|
65
|
-
# FIXME: test and rewrite to simpler implementation!
|
66
|
-
width = width.to_i
|
67
|
-
height = height.to_i
|
68
|
-
cols, rows = img[:dimensions]
|
69
|
-
img.combine_options do |cmd|
|
70
|
-
if width != cols || height != rows
|
71
|
-
scale_x = width / cols.to_f
|
72
|
-
scale_y = height / rows.to_f
|
73
|
-
if scale_x >= scale_y
|
74
|
-
cols = (scale_x * (cols + 0.5)).round
|
75
|
-
rows = (scale_x * (rows + 0.5)).round
|
76
|
-
cmd.resize "#{cols}"
|
77
|
-
else
|
78
|
-
cols = (scale_y * (cols + 0.5)).round
|
79
|
-
rows = (scale_y * (rows + 0.5)).round
|
80
|
-
cmd.resize "x#{rows}"
|
81
|
-
end
|
82
|
-
end
|
83
|
-
cmd.gravity gravity
|
84
|
-
cmd.background "rgba(255,255,255,0.0)"
|
85
|
-
cmd.extent "#{width}x#{height}" if cols != width || rows != height
|
86
|
-
end
|
87
|
-
end
|
88
|
-
|
89
|
-
# resize the image to fit within the specified dimensions while retaining
|
90
|
-
# the original aspect ratio in the same way as {#fill}. unlike {#fill} it
|
91
|
-
# will, if necessary, pad the remaining area with the given color, which
|
92
|
-
# defaults to transparent where supported by the image format and white
|
93
|
-
# otherwise.
|
94
|
-
#
|
95
|
-
# the resulting image will always be exactly as large as the specified
|
96
|
-
# dimensions.
|
97
|
-
#
|
98
|
-
# by default, the image will be placed in the center but this can be
|
99
|
-
# changed via the `gravity` option.
|
100
|
-
#
|
101
|
-
# @param [minimagick::image] img the image to convert
|
102
|
-
# @param [#to_s] width the width to fill out
|
103
|
-
# @param [#to_s] height the height to fill out
|
104
|
-
# @param [string] background the color to use as a background
|
105
|
-
# @param [string] gravity which part of the image to focus on
|
106
|
-
# @return [void]
|
107
|
-
# @see http://www.imagemagick.org/script/color.php
|
108
|
-
# @see http://www.imagemagick.org/script/command-line-options.php#gravity
|
109
|
-
def pad(img, width, height, background = "transparent", gravity = "Center")
|
110
|
-
img.combine_options do |cmd|
|
111
|
-
cmd.thumbnail "#{width}x#{height}>"
|
112
|
-
if background == "transparent"
|
113
|
-
cmd.background "rgba(255, 255, 255, 0.0)"
|
114
|
-
else
|
115
|
-
cmd.background background
|
116
|
-
end
|
117
|
-
cmd.gravity gravity
|
118
|
-
cmd.extent "#{width}x#{height}"
|
119
|
-
end
|
120
|
-
end
|
121
|
-
|
122
|
-
# Process the given file. The file will be processed via one of the
|
123
|
-
# instance methods of this class, depending on the `method` argument passed
|
124
|
-
# to the constructor on initialization.
|
125
|
-
#
|
126
|
-
# If the format is given it will convert the image to the given file format.
|
127
|
-
#
|
128
|
-
# @param [Tempfile] file the file to manipulate
|
129
|
-
# @param [String] format the file format to convert to
|
130
|
-
# @return [File] the processed file
|
131
|
-
def call(file, *args, format: nil)
|
132
|
-
img = ::MiniMagick::Image.new(file.path)
|
133
|
-
img.format(format.to_s.downcase, nil) if format
|
134
|
-
send(@method, img, *args)
|
135
|
-
|
136
|
-
::File.open(img.path, "rb")
|
137
|
-
end
|
138
|
-
end
|
139
|
-
end
|
140
|
-
|
141
|
-
[:fill, :fit, :limit, :pad, :convert].each do |name|
|
142
|
-
Refile.processor(name, Refile::ImageProcessor.new(name))
|
143
|
-
end
|
1
|
+
raise "[Refile] image processing has been extracted into a separate gem, see https://github.com/refile/refile-mini_magick"
|
data/lib/refile/rails.rb
CHANGED
@@ -5,6 +5,11 @@ module Refile
|
|
5
5
|
# @api private
|
6
6
|
class Engine < Rails::Engine
|
7
7
|
initializer "refile.setup", before: :load_environment_config do
|
8
|
+
if RUBY_PLATFORM == "java"
|
9
|
+
# Work around a bug in JRuby, see: https://github.com/jruby/jruby/issues/2779
|
10
|
+
Encoding.default_internal = nil
|
11
|
+
end
|
12
|
+
|
8
13
|
Refile.store ||= Refile::Backend::FileSystem.new(Rails.root.join("tmp/uploads/store").to_s)
|
9
14
|
Refile.cache ||= Refile::Backend::FileSystem.new(Rails.root.join("tmp/uploads/cache").to_s)
|
10
15
|
|
@@ -20,5 +25,30 @@ module Refile
|
|
20
25
|
Refile.logger = Rails.logger
|
21
26
|
Refile.app = Refile::App.new
|
22
27
|
end
|
28
|
+
|
29
|
+
initializer "refile.secret_key" do |app|
|
30
|
+
Refile.secret_key ||= if app.respond_to?(:secrets)
|
31
|
+
app.secrets.secret_key_base
|
32
|
+
elsif app.config.respond_to?(:secret_key_base)
|
33
|
+
app.config.secret_key_base
|
34
|
+
elsif app.config.respond_to?(:secret_token)
|
35
|
+
app.config.secret_token
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# Add in missing methods for file uploads in Rails < 4
|
42
|
+
ActionDispatch::Http::UploadedFile.class_eval do
|
43
|
+
unless instance_methods.include?(:eof?)
|
44
|
+
def eof?
|
45
|
+
@tempfile.eof?
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
unless instance_methods.include?(:close)
|
50
|
+
def close
|
51
|
+
@tempfile.close
|
52
|
+
end
|
23
53
|
end
|
24
54
|
end
|
@@ -19,11 +19,17 @@ module Refile
|
|
19
19
|
# @param [Refile::Attachment] object Instance of a class which has an attached file
|
20
20
|
# @param [Symbol] name The name of the attachment column
|
21
21
|
# @param [String, nil] filename The filename to be appended to the URL
|
22
|
+
# @param [String, nil] fallback The path to an asset to be used as a fallback
|
22
23
|
# @param [String, nil] format A file extension to be appended to the URL
|
23
24
|
# @param [String, nil] host Override the host
|
24
25
|
# @return [String, nil] The generated URL
|
25
|
-
def attachment_url(record, name, *args, **opts)
|
26
|
-
|
26
|
+
def attachment_url(record, name, *args, fallback: nil, **opts)
|
27
|
+
file = record && record.public_send(name)
|
28
|
+
if file
|
29
|
+
Refile.attachment_url(record, name, *args, **opts)
|
30
|
+
elsif fallback
|
31
|
+
asset_url(fallback)
|
32
|
+
end
|
27
33
|
end
|
28
34
|
|
29
35
|
# Generates an image tag for the given attachment, adding appropriate
|
@@ -36,12 +42,12 @@ module Refile
|
|
36
42
|
# @param [Hash] options Additional options for the image tag
|
37
43
|
# @see #attachment_url
|
38
44
|
# @return [ActiveSupport::SafeBuffer, nil] The generated image tag
|
39
|
-
def attachment_image_tag(record, name, *args, fallback: nil,
|
40
|
-
file = record.
|
41
|
-
classes = ["attachment", record.class.model_name.singular, name, *options[:class]]
|
45
|
+
def attachment_image_tag(record, name, *args, fallback: nil, host: nil, prefix: nil, format: nil, **options)
|
46
|
+
file = record && record.public_send(name)
|
47
|
+
classes = ["attachment", (record.class.model_name.singular if record), name, *options[:class]]
|
42
48
|
|
43
49
|
if file
|
44
|
-
image_tag(attachment_url(record, name, *args,
|
50
|
+
image_tag(attachment_url(record, name, *args, host: host, prefix: prefix, format: format), options.merge(class: classes))
|
45
51
|
elsif fallback
|
46
52
|
classes << "fallback"
|
47
53
|
image_tag(fallback, options.merge(class: classes))
|
@@ -63,23 +69,28 @@ module Refile
|
|
63
69
|
def attachment_field(object_name, method, object:, **options)
|
64
70
|
options[:data] ||= {}
|
65
71
|
|
66
|
-
|
67
|
-
options[:accept] =
|
72
|
+
definition = object.send(:"#{method}_attachment_definition")
|
73
|
+
options[:accept] = definition.accept
|
68
74
|
|
69
75
|
if options[:direct]
|
70
|
-
|
71
|
-
backend_name = Refile.backends.key(attacher.cache)
|
72
|
-
|
73
|
-
url = ::File.join(host, main_app.refile_app_path, backend_name)
|
76
|
+
url = Refile.attachment_upload_url(object, method, host: options[:host], prefix: options[:prefix])
|
74
77
|
options[:data].merge!(direct: true, as: "file", url: url)
|
75
78
|
end
|
76
79
|
|
77
|
-
if options[:presigned] and
|
78
|
-
options[:
|
80
|
+
if options[:presigned] and definition.cache.respond_to?(:presign)
|
81
|
+
url = Refile.attachment_presign_url(object, method, host: options[:host], prefix: options[:prefix])
|
82
|
+
options[:data].merge!(direct: true, presigned: true, url: url)
|
79
83
|
end
|
80
84
|
|
81
|
-
|
82
|
-
|
85
|
+
options[:data][:reference] = SecureRandom.hex
|
86
|
+
|
87
|
+
hidden_field(object_name, method,
|
88
|
+
multiple: options[:multiple],
|
89
|
+
value: object.send("#{method}_data").try(:to_json),
|
90
|
+
object: object,
|
91
|
+
id: nil,
|
92
|
+
data: { reference: options[:data][:reference] }
|
93
|
+
) + file_field(object_name, method, options)
|
83
94
|
end
|
84
95
|
end
|
85
96
|
end
|
data/lib/refile/signature.rb
CHANGED
@@ -0,0 +1,17 @@
|
|
1
|
+
# make attachment_field behave like a normal input type so we get nice wrapper and labels
|
2
|
+
# <%= f.input :cover_image, as: :attachment, direct: true, presigned: true %>
|
3
|
+
module SimpleForm
|
4
|
+
module Inputs
|
5
|
+
class AttachmentInput < Base
|
6
|
+
def input(wrapper_options = nil)
|
7
|
+
refile_options = [:presigned, :direct, :multiple]
|
8
|
+
merged_input_options = merge_wrapper_options(input_options.slice(*refile_options).merge(input_html_options), wrapper_options)
|
9
|
+
@builder.attachment_field(attribute_name, merged_input_options)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
SimpleForm::FormBuilder.class_eval do
|
16
|
+
map_type :attachment, to: SimpleForm::Inputs::AttachmentInput
|
17
|
+
end
|
data/lib/refile/version.rb
CHANGED
@@ -11,6 +11,7 @@ ActiveRecord::Base.establish_connection(
|
|
11
11
|
class TestMigration < ActiveRecord::Migration
|
12
12
|
def self.up
|
13
13
|
create_table :posts, force: true do |t|
|
14
|
+
t.integer :user_id
|
14
15
|
t.column :title, :string
|
15
16
|
t.column :image_id, :string
|
16
17
|
t.column :document_id, :string
|
@@ -18,6 +19,16 @@ class TestMigration < ActiveRecord::Migration
|
|
18
19
|
t.column :document_content_type, :string
|
19
20
|
t.column :document_size, :integer
|
20
21
|
end
|
22
|
+
|
23
|
+
create_table :users, force: true
|
24
|
+
|
25
|
+
create_table :documents, force: true do |t|
|
26
|
+
t.belongs_to :post, null: false
|
27
|
+
t.column :file_id, :string, null: false
|
28
|
+
t.column :file_filename, :string
|
29
|
+
t.column :file_content_type, :string
|
30
|
+
t.column :file_size, :integer, null: false
|
31
|
+
end
|
21
32
|
end
|
22
33
|
end
|
23
34
|
|
data/spec/refile/app_spec.rb
CHANGED
@@ -7,11 +7,15 @@ describe Refile::App do
|
|
7
7
|
Refile::App.new
|
8
8
|
end
|
9
9
|
|
10
|
+
before do
|
11
|
+
allow(Refile).to receive(:token).and_return("token")
|
12
|
+
end
|
13
|
+
|
10
14
|
describe "GET /:backend/:id/:filename" do
|
11
15
|
it "returns a stored file" do
|
12
16
|
file = Refile.store.upload(StringIO.new("hello"))
|
13
17
|
|
14
|
-
get "/store/#{file.id}/hello"
|
18
|
+
get "/token/store/#{file.id}/hello"
|
15
19
|
|
16
20
|
expect(last_response.status).to eq(200)
|
17
21
|
expect(last_response.body).to eq("hello")
|
@@ -20,7 +24,7 @@ describe Refile::App do
|
|
20
24
|
it "sets appropriate content type from extension" do
|
21
25
|
file = Refile.store.upload(StringIO.new("hello"))
|
22
26
|
|
23
|
-
get "/store/#{file.id}/hello.html"
|
27
|
+
get "/token/store/#{file.id}/hello.html"
|
24
28
|
|
25
29
|
expect(last_response.status).to eq(200)
|
26
30
|
expect(last_response.body).to eq("hello")
|
@@ -30,7 +34,7 @@ describe Refile::App do
|
|
30
34
|
it "returns a 404 if the file doesn't exist" do
|
31
35
|
Refile.store.upload(StringIO.new("hello"))
|
32
36
|
|
33
|
-
get "/store/doesnotexist/hello"
|
37
|
+
get "/token/store/doesnotexist/hello"
|
34
38
|
|
35
39
|
expect(last_response.status).to eq(404)
|
36
40
|
expect(last_response.content_type).to eq("text/plain;charset=utf-8")
|
@@ -40,7 +44,7 @@ describe Refile::App do
|
|
40
44
|
it "returns a 404 if the backend doesn't exist" do
|
41
45
|
file = Refile.store.upload(StringIO.new("hello"))
|
42
46
|
|
43
|
-
get "/doesnotexist/#{file.id}/hello"
|
47
|
+
get "/token/doesnotexist/#{file.id}/hello"
|
44
48
|
|
45
49
|
expect(last_response.status).to eq(404)
|
46
50
|
expect(last_response.content_type).to eq("text/plain;charset=utf-8")
|
@@ -55,7 +59,7 @@ describe Refile::App do
|
|
55
59
|
it "sets CORS header" do
|
56
60
|
file = Refile.store.upload(StringIO.new("hello"))
|
57
61
|
|
58
|
-
get "/store/#{file.id}/hello"
|
62
|
+
get "/token/store/#{file.id}/hello"
|
59
63
|
|
60
64
|
expect(last_response.status).to eq(200)
|
61
65
|
expect(last_response.body).to eq("hello")
|
@@ -66,7 +70,7 @@ describe Refile::App do
|
|
66
70
|
it "returns a 200 for head requests" do
|
67
71
|
file = Refile.store.upload(StringIO.new("hello"))
|
68
72
|
|
69
|
-
head "/store/#{file.id}/hello"
|
73
|
+
head "/token/store/#{file.id}/hello"
|
70
74
|
|
71
75
|
expect(last_response.status).to eq(200)
|
72
76
|
expect(last_response.body).to be_empty
|
@@ -75,7 +79,7 @@ describe Refile::App do
|
|
75
79
|
it "returns a 404 for head requests if the file doesn't exist" do
|
76
80
|
Refile.store.upload(StringIO.new("hello"))
|
77
81
|
|
78
|
-
head "/store/doesnotexist/hello"
|
82
|
+
head "/token/store/doesnotexist/hello"
|
79
83
|
|
80
84
|
expect(last_response.status).to eq(404)
|
81
85
|
expect(last_response.body).to be_empty
|
@@ -84,19 +88,84 @@ describe Refile::App do
|
|
84
88
|
it "returns a 404 for non get requests" do
|
85
89
|
file = Refile.store.upload(StringIO.new("hello"))
|
86
90
|
|
87
|
-
post "/store/#{file.id}/hello"
|
91
|
+
post "/token/store/#{file.id}/hello"
|
88
92
|
|
89
93
|
expect(last_response.status).to eq(404)
|
90
94
|
expect(last_response.content_type).to eq("text/plain;charset=utf-8")
|
91
95
|
expect(last_response.body).to eq("not found")
|
92
96
|
end
|
97
|
+
|
98
|
+
context "verification" do
|
99
|
+
before do
|
100
|
+
allow(Refile).to receive(:token).and_call_original
|
101
|
+
end
|
102
|
+
|
103
|
+
it "accepts valid token" do
|
104
|
+
file = Refile.store.upload(StringIO.new("hello"))
|
105
|
+
token = Refile.token("/store/#{file.id}/hello")
|
106
|
+
|
107
|
+
get "/#{token}/store/#{file.id}/hello"
|
108
|
+
|
109
|
+
expect(last_response.status).to eq(200)
|
110
|
+
expect(last_response.body).to eq("hello")
|
111
|
+
end
|
112
|
+
|
113
|
+
it "returns a 403 for unsigned get requests" do
|
114
|
+
file = Refile.store.upload(StringIO.new("hello"))
|
115
|
+
|
116
|
+
get "/eviltoken/store/#{file.id}/hello"
|
117
|
+
|
118
|
+
expect(last_response.status).to eq(403)
|
119
|
+
expect(last_response.body).to eq("forbidden")
|
120
|
+
end
|
121
|
+
|
122
|
+
it "does not retrieve nor process files for unauthenticated requests" do
|
123
|
+
file = Refile.store.upload(StringIO.new("hello"))
|
124
|
+
|
125
|
+
expect(Refile.store).not_to receive(:get)
|
126
|
+
get "/eviltoken/store/#{file.id}/hello"
|
127
|
+
|
128
|
+
expect(last_response.status).to eq(403)
|
129
|
+
expect(last_response.body).to eq("forbidden")
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
context "when unrestricted" do
|
134
|
+
before do
|
135
|
+
allow(Refile).to receive(:allow_downloads_from).and_return(:all)
|
136
|
+
end
|
137
|
+
|
138
|
+
it "gets signatures from all backends" do
|
139
|
+
file = Refile.store.upload(StringIO.new("hello"))
|
140
|
+
get "/token/store/#{file.id}/test.txt"
|
141
|
+
expect(last_response.status).to eq(200)
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
context "when restricted" do
|
146
|
+
before do
|
147
|
+
allow(Refile).to receive(:allow_downloads_from).and_return(["store"])
|
148
|
+
end
|
149
|
+
|
150
|
+
it "gets signatures from allowed backend" do
|
151
|
+
file = Refile.store.upload(StringIO.new("hello"))
|
152
|
+
get "/token/store/#{file.id}/test.txt"
|
153
|
+
expect(last_response.status).to eq(200)
|
154
|
+
end
|
155
|
+
|
156
|
+
it "returns 404 if backend is not allowed" do
|
157
|
+
file = Refile.store.upload(StringIO.new("hello"))
|
158
|
+
get "/token/cache/#{file.id}/test.txt"
|
159
|
+
expect(last_response.status).to eq(404)
|
160
|
+
end
|
161
|
+
end
|
93
162
|
end
|
94
163
|
|
95
164
|
describe "GET /:backend/:processor/:id/:filename" do
|
96
165
|
it "returns 404 if processor does not exist" do
|
97
166
|
file = Refile.store.upload(StringIO.new("hello"))
|
98
167
|
|
99
|
-
get "/store/doesnotexist/#{file.id}/hello"
|
168
|
+
get "/token/store/doesnotexist/#{file.id}/hello"
|
100
169
|
|
101
170
|
expect(last_response.status).to eq(404)
|
102
171
|
expect(last_response.content_type).to eq("text/plain;charset=utf-8")
|
@@ -106,7 +175,7 @@ describe Refile::App do
|
|
106
175
|
it "applies block processor to file" do
|
107
176
|
file = Refile.store.upload(StringIO.new("hello"))
|
108
177
|
|
109
|
-
get "/store/reverse/#{file.id}/hello"
|
178
|
+
get "/token/store/reverse/#{file.id}/hello"
|
110
179
|
|
111
180
|
expect(last_response.status).to eq(200)
|
112
181
|
expect(last_response.body).to eq("olleh")
|
@@ -115,7 +184,7 @@ describe Refile::App do
|
|
115
184
|
it "applies object processor to file" do
|
116
185
|
file = Refile.store.upload(StringIO.new("hello"))
|
117
186
|
|
118
|
-
get "/store/upcase/#{file.id}/hello"
|
187
|
+
get "/token/store/upcase/#{file.id}/hello"
|
119
188
|
|
120
189
|
expect(last_response.status).to eq(200)
|
121
190
|
expect(last_response.body).to eq("HELLO")
|
@@ -124,7 +193,7 @@ describe Refile::App do
|
|
124
193
|
it "applies processor with arguments" do
|
125
194
|
file = Refile.store.upload(StringIO.new("hello"))
|
126
195
|
|
127
|
-
get "/store/concat/foo/bar/baz/#{file.id}/hello"
|
196
|
+
get "/token/store/concat/foo/bar/baz/#{file.id}/hello"
|
128
197
|
|
129
198
|
expect(last_response.status).to eq(200)
|
130
199
|
expect(last_response.body).to eq("hellofoobarbaz")
|
@@ -133,34 +202,142 @@ describe Refile::App do
|
|
133
202
|
it "applies processor with format" do
|
134
203
|
file = Refile.store.upload(StringIO.new("hello"))
|
135
204
|
|
136
|
-
get "/store/convert_case/#{file.id}/hello.up"
|
205
|
+
get "/token/store/convert_case/#{file.id}/hello.up"
|
137
206
|
|
138
207
|
expect(last_response.status).to eq(200)
|
139
208
|
expect(last_response.body).to eq("HELLO")
|
140
209
|
end
|
210
|
+
|
211
|
+
it "returns a 403 for unsigned request" do
|
212
|
+
file = Refile.store.upload(StringIO.new("hello"))
|
213
|
+
|
214
|
+
get "/eviltoken/store/reverse/#{file.id}/hello"
|
215
|
+
|
216
|
+
expect(last_response.status).to eq(403)
|
217
|
+
expect(last_response.body).to eq("forbidden")
|
218
|
+
end
|
141
219
|
end
|
142
220
|
|
143
221
|
describe "POST /:backend" do
|
144
|
-
it "
|
222
|
+
it "uploads a file for direct upload backends" do
|
145
223
|
file = Rack::Test::UploadedFile.new(path("hello.txt"))
|
146
|
-
post "/
|
224
|
+
post "/cache", file: file
|
147
225
|
|
148
|
-
expect(last_response.status).to eq(
|
149
|
-
expect(last_response.
|
150
|
-
expect(last_response.body).to eq("not found")
|
226
|
+
expect(last_response.status).to eq(200)
|
227
|
+
expect(JSON.parse(last_response.body)["id"]).not_to be_empty
|
151
228
|
end
|
152
229
|
|
153
|
-
it "
|
230
|
+
it "does not require signed request param to upload" do
|
231
|
+
allow(Refile).to receive(:secret_key).and_return("abcd1234")
|
232
|
+
|
154
233
|
file = Rack::Test::UploadedFile.new(path("hello.txt"))
|
155
234
|
post "/cache", file: file
|
156
235
|
|
157
236
|
expect(last_response.status).to eq(200)
|
158
237
|
expect(JSON.parse(last_response.body)["id"]).not_to be_empty
|
159
238
|
end
|
239
|
+
|
240
|
+
context "when unrestricted" do
|
241
|
+
before do
|
242
|
+
allow(Refile).to receive(:allow_uploads_to).and_return(:all)
|
243
|
+
end
|
244
|
+
|
245
|
+
it "allows uploads to all backends" do
|
246
|
+
post "/store", file: Rack::Test::UploadedFile.new(path("hello.txt"))
|
247
|
+
expect(last_response.status).to eq(200)
|
248
|
+
end
|
249
|
+
end
|
250
|
+
|
251
|
+
context "when restricted" do
|
252
|
+
before do
|
253
|
+
allow(Refile).to receive(:allow_uploads_to).and_return(["cache"])
|
254
|
+
end
|
255
|
+
|
256
|
+
it "allows uploads to allowed backends" do
|
257
|
+
post "/cache", file: Rack::Test::UploadedFile.new(path("hello.txt"))
|
258
|
+
expect(last_response.status).to eq(200)
|
259
|
+
end
|
260
|
+
|
261
|
+
it "returns 404 if backend is not allowed" do
|
262
|
+
post "/store", file: Rack::Test::UploadedFile.new(path("hello.txt"))
|
263
|
+
expect(last_response.status).to eq(404)
|
264
|
+
end
|
265
|
+
end
|
266
|
+
|
267
|
+
context "when file is invalid" do
|
268
|
+
before do
|
269
|
+
allow(Refile).to receive(:allow_uploads_to).and_return(:all)
|
270
|
+
end
|
271
|
+
|
272
|
+
context "when file is too big" do
|
273
|
+
before do
|
274
|
+
backend = double
|
275
|
+
allow(backend).to receive(:upload).with(anything).and_raise(Refile::InvalidMaxSize)
|
276
|
+
allow_any_instance_of(Refile::App).to receive(:backend).and_return(backend)
|
277
|
+
end
|
278
|
+
|
279
|
+
it "returns 413 if file is too big" do
|
280
|
+
post "/store_max_size", file: Rack::Test::UploadedFile.new(path("hello.txt"))
|
281
|
+
expect(last_response.status).to eq(413)
|
282
|
+
end
|
283
|
+
end
|
284
|
+
|
285
|
+
context "when other unexpected exception happens" do
|
286
|
+
before do
|
287
|
+
backend = double
|
288
|
+
allow(backend).to receive(:upload).with(anything).and_raise(Refile::InvalidFile)
|
289
|
+
allow_any_instance_of(Refile::App).to receive(:backend).and_return(backend)
|
290
|
+
end
|
291
|
+
|
292
|
+
it "returns 400 if file is too big" do
|
293
|
+
post "/store_max_size", file: Rack::Test::UploadedFile.new(path("hello.txt"))
|
294
|
+
expect(last_response.status).to eq(400)
|
295
|
+
end
|
296
|
+
end
|
297
|
+
end
|
298
|
+
end
|
299
|
+
|
300
|
+
describe "GET /:backend/presign" do
|
301
|
+
it "returns presign signature" do
|
302
|
+
get "/limited_cache/presign"
|
303
|
+
|
304
|
+
expect(last_response.status).to eq(200)
|
305
|
+
result = JSON.parse(last_response.body)
|
306
|
+
expect(result["id"]).not_to be_empty
|
307
|
+
expect(result["url"]).to eq("/presigned/posts/upload")
|
308
|
+
expect(result["as"]).to eq("file")
|
309
|
+
end
|
310
|
+
|
311
|
+
context "when unrestricted" do
|
312
|
+
before do
|
313
|
+
allow(Refile).to receive(:allow_uploads_to).and_return(:all)
|
314
|
+
end
|
315
|
+
|
316
|
+
it "gets signatures from all backends" do
|
317
|
+
get "/limited_cache/presign"
|
318
|
+
expect(last_response.status).to eq(200)
|
319
|
+
end
|
320
|
+
end
|
321
|
+
|
322
|
+
context "when restricted" do
|
323
|
+
before do
|
324
|
+
allow(Refile).to receive(:allow_uploads_to).and_return(["limited_cache"])
|
325
|
+
end
|
326
|
+
|
327
|
+
it "gets signatures from allowed backend" do
|
328
|
+
get "/limited_cache/presign"
|
329
|
+
expect(last_response.status).to eq(200)
|
330
|
+
end
|
331
|
+
|
332
|
+
it "returns 404 if backend is not allowed" do
|
333
|
+
get "/store/presign"
|
334
|
+
expect(last_response.status).to eq(404)
|
335
|
+
end
|
336
|
+
end
|
160
337
|
end
|
161
338
|
|
162
339
|
it "returns a 404 if id not given" do
|
163
|
-
get "/store"
|
340
|
+
get "/token/store"
|
164
341
|
|
165
342
|
expect(last_response.status).to eq(404)
|
166
343
|
expect(last_response.content_type).to eq("text/plain;charset=utf-8")
|