activestorage 0.1 → 5.2.0.beta1
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of activestorage might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CHANGELOG.md +5 -0
- data/README.md +94 -25
- data/app/assets/javascripts/activestorage.js +1 -0
- data/app/controllers/active_storage/blobs_controller.rb +16 -0
- data/app/controllers/active_storage/direct_uploads_controller.rb +23 -0
- data/app/controllers/active_storage/disk_controller.rb +51 -0
- data/app/controllers/active_storage/previews_controller.rb +12 -0
- data/app/controllers/active_storage/variants_controller.rb +16 -0
- data/app/javascript/activestorage/blob_record.js +54 -0
- data/app/javascript/activestorage/blob_upload.js +35 -0
- data/app/javascript/activestorage/direct_upload.js +42 -0
- data/app/javascript/activestorage/direct_upload_controller.js +67 -0
- data/app/javascript/activestorage/direct_uploads_controller.js +50 -0
- data/app/javascript/activestorage/file_checksum.js +53 -0
- data/app/javascript/activestorage/helpers.js +42 -0
- data/app/javascript/activestorage/index.js +11 -0
- data/app/javascript/activestorage/ujs.js +75 -0
- data/app/jobs/active_storage/analyze_job.rb +8 -0
- data/app/jobs/active_storage/base_job.rb +5 -0
- data/app/jobs/active_storage/purge_job.rb +11 -0
- data/app/models/active_storage/attachment.rb +35 -0
- data/app/models/active_storage/blob.rb +313 -0
- data/app/models/active_storage/filename.rb +73 -0
- data/app/models/active_storage/filename/parameters.rb +36 -0
- data/app/models/active_storage/preview.rb +90 -0
- data/app/models/active_storage/variant.rb +86 -0
- data/app/models/active_storage/variation.rb +67 -0
- data/config/routes.rb +43 -0
- data/lib/active_storage.rb +37 -2
- data/lib/active_storage/analyzer.rb +33 -0
- data/lib/active_storage/analyzer/image_analyzer.rb +36 -0
- data/lib/active_storage/analyzer/null_analyzer.rb +13 -0
- data/lib/active_storage/analyzer/video_analyzer.rb +79 -0
- data/lib/active_storage/attached.rb +28 -22
- data/lib/active_storage/attached/macros.rb +89 -16
- data/lib/active_storage/attached/many.rb +53 -21
- data/lib/active_storage/attached/one.rb +74 -20
- data/lib/active_storage/downloading.rb +26 -0
- data/lib/active_storage/engine.rb +72 -0
- data/lib/active_storage/gem_version.rb +17 -0
- data/lib/active_storage/log_subscriber.rb +52 -0
- data/lib/active_storage/previewer.rb +58 -0
- data/lib/active_storage/previewer/pdf_previewer.rb +17 -0
- data/lib/active_storage/previewer/video_previewer.rb +23 -0
- data/lib/active_storage/service.rb +112 -24
- data/lib/active_storage/service/azure_storage_service.rb +124 -0
- data/lib/active_storage/service/configurator.rb +32 -0
- data/lib/active_storage/service/disk_service.rb +103 -44
- data/lib/active_storage/service/gcs_service.rb +87 -29
- data/lib/active_storage/service/mirror_service.rb +38 -22
- data/lib/active_storage/service/s3_service.rb +83 -38
- data/lib/active_storage/version.rb +10 -0
- data/lib/tasks/activestorage.rake +4 -15
- metadata +64 -108
- data/.gitignore +0 -1
- data/Gemfile +0 -11
- data/Gemfile.lock +0 -235
- data/Rakefile +0 -11
- data/activestorage.gemspec +0 -21
- data/lib/active_storage/attachment.rb +0 -30
- data/lib/active_storage/blob.rb +0 -80
- data/lib/active_storage/disk_controller.rb +0 -28
- data/lib/active_storage/download.rb +0 -90
- data/lib/active_storage/filename.rb +0 -31
- data/lib/active_storage/migration.rb +0 -28
- data/lib/active_storage/purge_job.rb +0 -10
- data/lib/active_storage/railtie.rb +0 -56
- data/lib/active_storage/storage_services.yml +0 -27
- data/lib/active_storage/verified_key_with_expiration.rb +0 -24
- data/test/attachments_test.rb +0 -95
- data/test/blob_test.rb +0 -28
- data/test/database/create_users_migration.rb +0 -7
- data/test/database/setup.rb +0 -6
- data/test/disk_controller_test.rb +0 -34
- data/test/filename_test.rb +0 -36
- data/test/service/.gitignore +0 -1
- data/test/service/configurations-example.yml +0 -11
- data/test/service/disk_service_test.rb +0 -8
- data/test/service/gcs_service_test.rb +0 -20
- data/test/service/mirror_service_test.rb +0 -50
- data/test/service/s3_service_test.rb +0 -11
- data/test/service/shared_service_tests.rb +0 -68
- data/test/test_helper.rb +0 -28
- data/test/verified_key_with_expiration_test.rb +0 -19
data/Rakefile
DELETED
data/activestorage.gemspec
DELETED
@@ -1,21 +0,0 @@
|
|
1
|
-
Gem::Specification.new do |s|
|
2
|
-
s.name = "activestorage"
|
3
|
-
s.version = "0.1"
|
4
|
-
s.authors = "David Heinemeier Hansson"
|
5
|
-
s.email = "david@basecamp.com"
|
6
|
-
s.summary = "Attach cloud and local files in Rails applications"
|
7
|
-
s.homepage = "https://github.com/rails/activestorage"
|
8
|
-
s.license = "MIT"
|
9
|
-
|
10
|
-
s.required_ruby_version = ">= 2.3.0"
|
11
|
-
|
12
|
-
s.add_dependency "activesupport", ">= 5.1"
|
13
|
-
s.add_dependency "activerecord", ">= 5.1"
|
14
|
-
s.add_dependency "actionpack", ">= 5.1"
|
15
|
-
s.add_dependency "activejob", ">= 5.1"
|
16
|
-
|
17
|
-
s.add_development_dependency "bundler", "~> 1.15"
|
18
|
-
|
19
|
-
s.files = `git ls-files`.split("\n")
|
20
|
-
s.test_files = `git ls-files -- test/*`.split("\n")
|
21
|
-
end
|
@@ -1,30 +0,0 @@
|
|
1
|
-
require "active_storage/blob"
|
2
|
-
require "global_id"
|
3
|
-
require "active_support/core_ext/module/delegation"
|
4
|
-
|
5
|
-
# Schema: id, record_gid, blob_id, created_at
|
6
|
-
class ActiveStorage::Attachment < ActiveRecord::Base
|
7
|
-
self.table_name = "active_storage_attachments"
|
8
|
-
|
9
|
-
belongs_to :blob, class_name: "ActiveStorage::Blob"
|
10
|
-
|
11
|
-
delegate_missing_to :blob
|
12
|
-
|
13
|
-
def record
|
14
|
-
@record ||= GlobalID::Locator.locate(record_gid)
|
15
|
-
end
|
16
|
-
|
17
|
-
def record=(record)
|
18
|
-
@record = record
|
19
|
-
self.record_gid = record&.to_gid
|
20
|
-
end
|
21
|
-
|
22
|
-
def purge
|
23
|
-
blob.purge
|
24
|
-
destroy
|
25
|
-
end
|
26
|
-
|
27
|
-
def purge_later
|
28
|
-
ActiveStorage::PurgeJob.perform_later(self)
|
29
|
-
end
|
30
|
-
end
|
data/lib/active_storage/blob.rb
DELETED
@@ -1,80 +0,0 @@
|
|
1
|
-
require "active_storage/service"
|
2
|
-
require "active_storage/filename"
|
3
|
-
require "active_storage/purge_job"
|
4
|
-
|
5
|
-
# Schema: id, key, filename, content_type, metadata, byte_size, checksum, created_at
|
6
|
-
class ActiveStorage::Blob < ActiveRecord::Base
|
7
|
-
self.table_name = "active_storage_blobs"
|
8
|
-
|
9
|
-
has_secure_token :key
|
10
|
-
store :metadata, coder: JSON
|
11
|
-
|
12
|
-
class_attribute :service
|
13
|
-
|
14
|
-
class << self
|
15
|
-
def build_after_upload(io:, filename:, content_type: nil, metadata: nil)
|
16
|
-
new.tap do |blob|
|
17
|
-
blob.filename = filename
|
18
|
-
blob.content_type = content_type
|
19
|
-
blob.metadata = metadata
|
20
|
-
|
21
|
-
blob.upload io
|
22
|
-
end
|
23
|
-
end
|
24
|
-
|
25
|
-
def create_after_upload!(io:, filename:, content_type: nil, metadata: nil)
|
26
|
-
build_after_upload(io: io, filename: filename, content_type: content_type, metadata: metadata).tap(&:save!)
|
27
|
-
end
|
28
|
-
end
|
29
|
-
|
30
|
-
# We can't wait until the record is first saved to have a key for it
|
31
|
-
def key
|
32
|
-
self[:key] ||= self.class.generate_unique_secure_token
|
33
|
-
end
|
34
|
-
|
35
|
-
def filename
|
36
|
-
ActiveStorage::Filename.new(self[:filename])
|
37
|
-
end
|
38
|
-
|
39
|
-
def url(expires_in: 5.minutes, disposition: :inline)
|
40
|
-
service.url key, expires_in: expires_in, disposition: disposition, filename: filename
|
41
|
-
end
|
42
|
-
|
43
|
-
|
44
|
-
def upload(io)
|
45
|
-
self.checksum = compute_checksum_in_chunks(io)
|
46
|
-
self.byte_size = io.size
|
47
|
-
|
48
|
-
service.upload(key, io, checksum: checksum)
|
49
|
-
end
|
50
|
-
|
51
|
-
def download
|
52
|
-
service.download key
|
53
|
-
end
|
54
|
-
|
55
|
-
|
56
|
-
def delete
|
57
|
-
service.delete key
|
58
|
-
end
|
59
|
-
|
60
|
-
def purge
|
61
|
-
delete
|
62
|
-
destroy
|
63
|
-
end
|
64
|
-
|
65
|
-
def purge_later
|
66
|
-
ActiveStorage::PurgeJob.perform_later(self)
|
67
|
-
end
|
68
|
-
|
69
|
-
|
70
|
-
private
|
71
|
-
def compute_checksum_in_chunks(io)
|
72
|
-
Digest::MD5.new.tap do |checksum|
|
73
|
-
while chunk = io.read(5.megabytes)
|
74
|
-
checksum << chunk
|
75
|
-
end
|
76
|
-
|
77
|
-
io.rewind
|
78
|
-
end.base64digest
|
79
|
-
end
|
80
|
-
end
|
@@ -1,28 +0,0 @@
|
|
1
|
-
require "action_controller"
|
2
|
-
require "active_storage/blob"
|
3
|
-
require "active_storage/verified_key_with_expiration"
|
4
|
-
|
5
|
-
require "active_support/core_ext/object/inclusion"
|
6
|
-
|
7
|
-
class ActiveStorage::DiskController < ActionController::Base
|
8
|
-
def show
|
9
|
-
if key = decode_verified_key
|
10
|
-
blob = ActiveStorage::Blob.find_by!(key: key)
|
11
|
-
|
12
|
-
if stale?(etag: blob.checksum)
|
13
|
-
send_data blob.download, filename: blob.filename, type: blob.content_type, disposition: disposition_param
|
14
|
-
end
|
15
|
-
else
|
16
|
-
head :not_found
|
17
|
-
end
|
18
|
-
end
|
19
|
-
|
20
|
-
private
|
21
|
-
def decode_verified_key
|
22
|
-
ActiveStorage::VerifiedKeyWithExpiration.decode(params[:encoded_key])
|
23
|
-
end
|
24
|
-
|
25
|
-
def disposition_param
|
26
|
-
params[:disposition].presence_in(%w( inline attachment )) || 'inline'
|
27
|
-
end
|
28
|
-
end
|
@@ -1,90 +0,0 @@
|
|
1
|
-
class ActiveStorage::Download
|
2
|
-
# Sending .ai files as application/postscript to Safari opens them in a blank, grey screen.
|
3
|
-
# Downloading .ai as application/postscript files in Safari appends .ps to the extension.
|
4
|
-
# Sending HTML, SVG, XML and SWF files as binary closes XSS vulnerabilities.
|
5
|
-
# Sending JS files as binary avoids InvalidCrossOriginRequest without compromising security.
|
6
|
-
CONTENT_TYPES_TO_RENDER_AS_BINARY = %w(
|
7
|
-
text/html
|
8
|
-
text/javascript
|
9
|
-
image/svg+xml
|
10
|
-
application/postscript
|
11
|
-
application/x-shockwave-flash
|
12
|
-
text/xml
|
13
|
-
application/xml
|
14
|
-
application/xhtml+xml
|
15
|
-
)
|
16
|
-
|
17
|
-
BINARY_CONTENT_TYPE = 'application/octet-stream'
|
18
|
-
|
19
|
-
def initialize(stored_file)
|
20
|
-
@stored_file = stored_file
|
21
|
-
end
|
22
|
-
|
23
|
-
def headers(force_attachment: false)
|
24
|
-
{
|
25
|
-
x_accel_redirect: '/reproxy',
|
26
|
-
x_reproxy_url: reproxy_url,
|
27
|
-
content_type: content_type,
|
28
|
-
content_disposition: content_disposition(force_attachment),
|
29
|
-
x_frame_options: 'SAMEORIGIN'
|
30
|
-
}
|
31
|
-
end
|
32
|
-
|
33
|
-
private
|
34
|
-
def reproxy_url
|
35
|
-
@stored_file.depot_location.paths.first
|
36
|
-
end
|
37
|
-
|
38
|
-
def content_type
|
39
|
-
if @stored_file.content_type.in? CONTENT_TYPES_TO_RENDER_AS_BINARY
|
40
|
-
BINARY_CONTENT_TYPE
|
41
|
-
else
|
42
|
-
@stored_file.content_type
|
43
|
-
end
|
44
|
-
end
|
45
|
-
|
46
|
-
def content_disposition(force_attachment = false)
|
47
|
-
if force_attachment || content_type == BINARY_CONTENT_TYPE
|
48
|
-
"attachment; #{escaped_filename}"
|
49
|
-
else
|
50
|
-
"inline; #{escaped_filename}"
|
51
|
-
end
|
52
|
-
end
|
53
|
-
|
54
|
-
# RFC2231 encoding for UTF-8 filenames, with an ASCII fallback
|
55
|
-
# first for unsupported browsers (IE < 9, perhaps others?).
|
56
|
-
# http://greenbytes.de/tech/tc2231/#encoding-2231-fb
|
57
|
-
def escaped_filename
|
58
|
-
filename = @stored_file.filename.sanitized
|
59
|
-
ascii_filename = encode_ascii_filename(filename)
|
60
|
-
utf8_filename = encode_utf8_filename(filename)
|
61
|
-
"#{ascii_filename}; #{utf8_filename}"
|
62
|
-
end
|
63
|
-
|
64
|
-
TRADITIONAL_PARAMETER_ESCAPED_CHAR = /[^ A-Za-z0-9!#$+.^_`|~-]/
|
65
|
-
|
66
|
-
def encode_ascii_filename(filename)
|
67
|
-
# There is no reliable way to escape special or non-Latin characters
|
68
|
-
# in a traditionally quoted Content-Disposition filename parameter.
|
69
|
-
# Settle for transliterating to ASCII, then percent-escaping special
|
70
|
-
# characters, excluding spaces.
|
71
|
-
filename = I18n.transliterate(filename)
|
72
|
-
filename = percent_escape(filename, TRADITIONAL_PARAMETER_ESCAPED_CHAR)
|
73
|
-
%(filename="#{filename}")
|
74
|
-
end
|
75
|
-
|
76
|
-
RFC5987_PARAMETER_ESCAPED_CHAR = /[^A-Za-z0-9!#$&+.^_`|~-]/
|
77
|
-
|
78
|
-
def encode_utf8_filename(filename)
|
79
|
-
# RFC2231 filename parameters can simply be percent-escaped according
|
80
|
-
# to RFC5987.
|
81
|
-
filename = percent_escape(filename, RFC5987_PARAMETER_ESCAPED_CHAR)
|
82
|
-
%(filename*=UTF-8''#{filename})
|
83
|
-
end
|
84
|
-
|
85
|
-
def percent_escape(string, pattern)
|
86
|
-
string.gsub(pattern) do |char|
|
87
|
-
char.bytes.map { |byte| "%%%02X" % byte }.join("")
|
88
|
-
end
|
89
|
-
end
|
90
|
-
end
|
@@ -1,31 +0,0 @@
|
|
1
|
-
class ActiveStorage::Filename
|
2
|
-
include Comparable
|
3
|
-
|
4
|
-
def initialize(filename)
|
5
|
-
@filename = filename
|
6
|
-
end
|
7
|
-
|
8
|
-
def extname
|
9
|
-
File.extname(@filename)
|
10
|
-
end
|
11
|
-
|
12
|
-
def extension
|
13
|
-
extname.from(1)
|
14
|
-
end
|
15
|
-
|
16
|
-
def base
|
17
|
-
File.basename(@filename, extname)
|
18
|
-
end
|
19
|
-
|
20
|
-
def sanitized
|
21
|
-
@filename.encode(Encoding::UTF_8, invalid: :replace, undef: :replace, replace: "�").strip.tr("\u{202E}%$|:;/\t\r\n\\", "-")
|
22
|
-
end
|
23
|
-
|
24
|
-
def to_s
|
25
|
-
sanitized.to_s
|
26
|
-
end
|
27
|
-
|
28
|
-
def <=>(other)
|
29
|
-
to_s.downcase <=> other.to_s.downcase
|
30
|
-
end
|
31
|
-
end
|
@@ -1,28 +0,0 @@
|
|
1
|
-
class ActiveStorage::CreateTables < ActiveRecord::Migration[5.1]
|
2
|
-
def change
|
3
|
-
create_table :active_storage_blobs do |t|
|
4
|
-
t.string :key
|
5
|
-
t.string :filename
|
6
|
-
t.string :content_type
|
7
|
-
t.text :metadata
|
8
|
-
t.integer :byte_size
|
9
|
-
t.string :checksum
|
10
|
-
t.time :created_at
|
11
|
-
|
12
|
-
t.index [ :key ], unique: true
|
13
|
-
end
|
14
|
-
|
15
|
-
create_table :active_storage_attachments do |t|
|
16
|
-
t.string :name
|
17
|
-
t.string :record_gid
|
18
|
-
t.integer :blob_id
|
19
|
-
|
20
|
-
t.time :created_at
|
21
|
-
|
22
|
-
t.index :record_gid
|
23
|
-
t.index :blob_id
|
24
|
-
t.index [ :record_gid, :name ]
|
25
|
-
t.index [ :record_gid, :blob_id ], unique: true
|
26
|
-
end
|
27
|
-
end
|
28
|
-
end
|
@@ -1,56 +0,0 @@
|
|
1
|
-
require "rails/railtie"
|
2
|
-
|
3
|
-
module ActiveStorage
|
4
|
-
class Engine < Rails::Engine # :nodoc:
|
5
|
-
config.active_storage = ActiveSupport::OrderedOptions.new
|
6
|
-
|
7
|
-
config.eager_load_namespaces << ActiveStorage
|
8
|
-
|
9
|
-
initializer "active_storage.routes" do
|
10
|
-
require "active_storage/disk_controller"
|
11
|
-
|
12
|
-
config.after_initialize do |app|
|
13
|
-
app.routes.prepend do
|
14
|
-
get "/rails/blobs/:encoded_key" => "active_storage/disk#show", as: :rails_disk_blob
|
15
|
-
end
|
16
|
-
end
|
17
|
-
end
|
18
|
-
|
19
|
-
initializer "active_storage.attached" do
|
20
|
-
require "active_storage/attached"
|
21
|
-
|
22
|
-
ActiveSupport.on_load(:active_record) do
|
23
|
-
extend ActiveStorage::Attached::Macros
|
24
|
-
end
|
25
|
-
end
|
26
|
-
|
27
|
-
config.after_initialize do |app|
|
28
|
-
config_choice = app.config.active_storage.service
|
29
|
-
config_file = Pathname.new(Rails.root.join("config/storage_services.yml"))
|
30
|
-
|
31
|
-
if config_choice
|
32
|
-
raise("Couldn't find Active Storage configuration in #{config_file}") unless config_file.exist?
|
33
|
-
|
34
|
-
begin
|
35
|
-
require "yaml"
|
36
|
-
require "erb"
|
37
|
-
configs = YAML.load(ERB.new(config_file.read).result) || {}
|
38
|
-
|
39
|
-
if service_configuration = configs[config_choice.to_s].symbolize_keys
|
40
|
-
service_name = service_configuration.delete(:service)
|
41
|
-
|
42
|
-
ActiveStorage::Blob.service = ActiveStorage::Service.configure(service_name, service_configuration)
|
43
|
-
else
|
44
|
-
raise "Couldn't configure Active Storage as #{config_choice} was not found in #{config_file}"
|
45
|
-
end
|
46
|
-
rescue Psych::SyntaxError => e
|
47
|
-
raise "YAML syntax error occurred while parsing #{config_file}. " \
|
48
|
-
"Please note that YAML must be consistently indented using spaces. Tabs are not allowed. " \
|
49
|
-
"Error: #{e.message}"
|
50
|
-
rescue => e
|
51
|
-
raise e, "Cannot load `Rails.config.active_storage.service`:\n#{e.message}", e.backtrace
|
52
|
-
end
|
53
|
-
end
|
54
|
-
end
|
55
|
-
end
|
56
|
-
end
|
@@ -1,27 +0,0 @@
|
|
1
|
-
test:
|
2
|
-
service: Disk
|
3
|
-
root: <%= Rails.root.join("tmp/storage") %>
|
4
|
-
|
5
|
-
local:
|
6
|
-
service: Disk
|
7
|
-
root: <%= Rails.root.join("storage") %>
|
8
|
-
|
9
|
-
# Use rails secrets:edit to set the AWS secrets (as shared:aws:access_key_id|secret_access_key)
|
10
|
-
amazon:
|
11
|
-
service: S3
|
12
|
-
access_key_id: <%= Rails.application.secrets.aws[:access_key_id] %>
|
13
|
-
secret_access_key: <%= Rails.application.secrets.aws[:secret_access_key] %>
|
14
|
-
region: us-east-1
|
15
|
-
bucket: your_own_bucket
|
16
|
-
|
17
|
-
# Remember not to checkin your GCS keyfile to a repository
|
18
|
-
google:
|
19
|
-
service: GCS
|
20
|
-
project: your_project
|
21
|
-
keyfile: <%= Rails.root.join("path/to/gcs.keyfile") %>
|
22
|
-
bucket: your_own_bucket
|
23
|
-
|
24
|
-
mirror:
|
25
|
-
service: Mirror
|
26
|
-
primary: local
|
27
|
-
secondaries: [ amazon, google ]
|
@@ -1,24 +0,0 @@
|
|
1
|
-
class ActiveStorage::VerifiedKeyWithExpiration
|
2
|
-
class_attribute :verifier, default: defined?(Rails) ? Rails.application.message_verifier('ActiveStorage') : nil
|
3
|
-
|
4
|
-
class << self
|
5
|
-
def encode(key, expires_in: nil)
|
6
|
-
verifier.generate([ key, expires_at(expires_in) ])
|
7
|
-
end
|
8
|
-
|
9
|
-
def decode(encoded_key)
|
10
|
-
key, expires_at = verifier.verified(encoded_key)
|
11
|
-
|
12
|
-
key if key && fresh?(expires_at)
|
13
|
-
end
|
14
|
-
|
15
|
-
private
|
16
|
-
def expires_at(expires_in)
|
17
|
-
expires_in ? Time.now.utc.advance(seconds: expires_in) : nil
|
18
|
-
end
|
19
|
-
|
20
|
-
def fresh?(expires_at)
|
21
|
-
expires_at.nil? || Time.now.utc < expires_at
|
22
|
-
end
|
23
|
-
end
|
24
|
-
end
|