refile 0.5.5 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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")
|