activestorage 7.2.2.1 → 8.0.5

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 19f4fa25c348a39b38b18a049dd123c5a0c6f80d70e05cdfc4790c7e0dbf96d1
4
- data.tar.gz: 48ba490e74129a1495ed0b3249730bfeba7b12f916e787849e7cfa4f81235bf9
3
+ metadata.gz: bb8d04a9237523518b1a6b00aab432decc944e0432e7296d1f283126c73eb374
4
+ data.tar.gz: 74fabd6db3bfefae7e51f9be208fafb37b76e5df3bdc238bafe60fa8357bd405
5
5
  SHA512:
6
- metadata.gz: 6656a5f10b464e63d86f0e4fc60984735e522d56789f93ce4f0d6ec4684b0b7e6a449f14af01a92e5929b1a4854f283b0bf89b37fe67ecb5d8cc207430b81c71
7
- data.tar.gz: a8fe146a25f52b241de32978f344a2303ad5221f05306a57776337194ba67aa2691c715790131056dec44dfe21cb26b7df1fe33fef5d09efba3ff5f226b83e58
6
+ metadata.gz: e8555d671d585f19dcecb996f38d4aa4f182e3e98082856916e13cefa8f6f9c0288f11dbf83f7f80f641dbf82e5e93a39559bf7e86b98638b3737029c17660c4
7
+ data.tar.gz: bd3b5a92c37258cecf1bca978f4a59a090c6bb20a30cf290479a173add2efa6de64ffe120d4649c1d4d9aa3b0e500c2ae7b24b44ee4b6413e65efafe7eac91e4
data/CHANGELOG.md CHANGED
@@ -1,103 +1,145 @@
1
- ## Rails 7.2.2.1 (December 10, 2024) ##
1
+ ## Rails 8.0.5 (March 24, 2026) ##
2
2
 
3
- * No changes.
3
+ * Fix `ActiveStorage::Blob` content type predicate methods to handle `nil`.
4
4
 
5
+ *Daichi KUDO*
5
6
 
6
- ## Rails 7.2.2 (October 30, 2024) ##
7
7
 
8
- * No changes.
8
+ ## Rails 8.0.4.1 (March 23, 2026) ##
9
9
 
10
+ * Filter user supplied metadata in DirectUploadController
10
11
 
11
- ## Rails 7.2.1.2 (October 23, 2024) ##
12
+ [CVE-2026-33173]
12
13
 
13
- * No changes.
14
+ *Jean Boussier*
14
15
 
16
+ * Configurable maxmimum streaming chunk size
15
17
 
16
- ## Rails 7.2.1.1 (October 15, 2024) ##
18
+ Makes sure that byte ranges for blobs don't exceed 100mb by default.
19
+ Content ranges that are too big can result in denial of service.
17
20
 
18
- * No changes.
21
+ [CVE-2026-33174]
22
+
23
+ *Gannon McGibbon*
24
+
25
+ * Limit range requests to a single range
26
+
27
+ [CVE-2026-33658]
28
+
29
+ *Jean Boussier*
30
+
31
+ * Prevent path traversal in `DiskService`.
32
+
33
+ `DiskService#path_for` now raises an `InvalidKeyError` when passed keys with dot segments (".",
34
+ ".."), or if the resolved path is outside the storage root directory.
35
+
36
+ `#path_for` also now consistently raises `InvalidKeyError` if the key is invalid in any way, for
37
+ example containing null bytes or having an incompatible encoding. Previously, the exception
38
+ raised may have been `ArgumentError` or `Encoding::CompatibilityError`.
39
+
40
+ `DiskController` now explicitly rescues `InvalidKeyError` with appropriate HTTP status codes.
41
+
42
+ [CVE-2026-33195]
43
+
44
+ *Mike Dalessio*
45
+
46
+ * Prevent glob injection in `DiskService#delete_prefixed`.
47
+
48
+ Escape glob metacharacters in the resolved path before passing to `Dir.glob`.
49
+
50
+ Note that this change breaks any existing code that is relying on `delete_prefixed` to expand
51
+ glob metacharacters. This change presumes that is unintended behavior (as other storage services
52
+ do not respect these metacharacters).
19
53
 
54
+ [CVE-2026-33202]
20
55
 
21
- ## Rails 7.2.1 (August 22, 2024) ##
56
+ *Mike Dalessio*
57
+
58
+
59
+ ## Rails 8.0.4 (October 28, 2025) ##
22
60
 
23
61
  * No changes.
24
62
 
25
63
 
26
- ## Rails 7.2.0 (August 09, 2024) ##
64
+ ## Rails 8.0.3 (September 22, 2025) ##
65
+
66
+ * Address deprecation of `Aws::S3::Object#upload_stream` in `ActiveStorage::Service::S3Service`.
67
+
68
+ *Joshua Young*
69
+
70
+ * Fix `config.active_storage.touch_attachment_records` to work with eager loading.
71
+
72
+ *fatkodima*
73
+
74
+
75
+ ## Rails 8.0.2.1 (August 13, 2025) ##
27
76
 
28
- * Remove deprecated `config.active_storage.silence_invalid_content_types_warning`.
77
+ * Remove dangerous transformations
29
78
 
30
- *Rafael Mendonça França*
79
+ [CVE-2025-24293]
31
80
 
32
- * Remove deprecated `config.active_storage.replace_on_assign_to_many`.
81
+ *Zack Deveau*
33
82
 
34
- *Rafael Mendonça França*
83
+ ## Rails 8.0.2 (March 12, 2025) ##
35
84
 
36
- * Add support for custom `key` in `ActiveStorage::Blob#compose`.
85
+ * A Blob will no longer autosave associated Attachment.
37
86
 
38
- *Elvin Efendiev*
87
+ This fixes an issue where a record with an attachment would have
88
+ its dirty attributes reset, preventing your `after commit` callbacks
89
+ on that record to behave as expected.
39
90
 
40
- * Add `image/webp` to `config.active_storage.web_image_content_types` when `load_defaults "7.2"`
41
- is set.
91
+ Note that this change doesn't require any changes on your application
92
+ and is supposed to be internal. Active Storage Attachment will continue
93
+ to be autosaved (through a different relation).
42
94
 
43
- *Lewis Buckley*
95
+ *Edouard-chin*
44
96
 
45
- * Fix JSON-encoding of `ActiveStorage::Filename` instances.
46
97
 
47
- *Jonathan del Strother*
98
+ ## Rails 8.0.1 (December 13, 2024) ##
48
99
 
49
- * Fix N+1 query when fetching preview images for non-image assets.
100
+ * No changes.
101
+
102
+
103
+ ## Rails 8.0.0.1 (December 10, 2024) ##
104
+
105
+ * No changes.
50
106
 
51
- *Aaron Patterson & Justin Searls*
52
107
 
53
- * Fix all Active Storage database related models to respect
54
- `ActiveRecord::Base.table_name_prefix` configuration.
108
+ ## Rails 8.0.0 (November 07, 2024) ##
55
109
 
56
- *Chedli Bourguiba*
110
+ * No changes.
57
111
 
58
- * Fix `ActiveStorage::Representations::ProxyController` not returning the proper
59
- preview image variant for previewable files.
60
112
 
61
- *Chedli Bourguiba*
113
+ ## Rails 8.0.0.rc2 (October 30, 2024) ##
62
114
 
63
- * Fix `ActiveStorage::Representations::ProxyController` to proxy untracked
64
- variants.
115
+ * No changes.
65
116
 
66
- *Chedli Bourguiba*
67
117
 
68
- * When using the `preprocessed: true` option, avoid enqueuing transform jobs
69
- for blobs that are not representable.
118
+ ## Rails 8.0.0.rc1 (October 19, 2024) ##
70
119
 
71
- *Chedli Bourguiba*
120
+ * No changes.
72
121
 
73
- * Prevent `ActiveStorage::Blob#preview` to generate a variant if an empty variation is passed.
74
122
 
75
- Calls to `#url`, `#key` or `#download` will now use the original preview
76
- image instead of generating a variant with the exact same dimensions.
123
+ ## Rails 8.0.0.beta1 (September 26, 2024) ##
77
124
 
78
- *Chedli Bourguiba*
125
+ * Deprecate `ActiveStorage::Service::AzureStorageService`.
79
126
 
80
- * Process preview image variant when calling `ActiveStorage::Preview#processed`.
127
+ *zzak*
81
128
 
82
- For example, `attached_pdf.preview(:thumb).processed` will now immediately
83
- generate the full-sized preview image and the `:thumb` variant of it.
84
- Previously, the `:thumb` variant would not be generated until a further call
85
- to e.g. `processed.url`.
129
+ * Improve `ActiveStorage::Filename#sanitized` method to handle special characters more effectively.
130
+ Replace the characters `"*?<>` with `-` if they exist in the Filename to match the Filename convention of Win OS.
86
131
 
87
- *Chedli Bourguiba* and *Jonathan Hefner*
132
+ *Luong Viet Dung(Martin)*
88
133
 
89
- * Prevent `ActiveRecord::StrictLoadingViolationError` when strict loading is
90
- enabled and the variant of an Active Storage preview has already been
91
- processed (for example, by calling `ActiveStorage::Preview#url`).
134
+ * Improve InvariableError, UnpreviewableError and UnrepresentableError message.
92
135
 
93
- *Jonathan Hefner*
136
+ Include Blob ID and content_type in the messages.
94
137
 
95
- * Fix `preprocessed: true` option for named variants of previewable files.
138
+ *Petrik de Heus*
96
139
 
97
- *Nico Wenterodt*
140
+ * Mark proxied files as `immutable` in their Cache-Control header
98
141
 
99
- * Allow accepting `service` as a proc as well in `has_one_attached` and `has_many_attached`.
142
+ *Nate Matykiewicz*
100
143
 
101
- *Yogesh Khater*
102
144
 
103
- Please check [7-1-stable](https://github.com/rails/rails/blob/7-1-stable/activestorage/CHANGELOG.md) for previous changes.
145
+ Please check [7-2-stable](https://github.com/rails/rails/blob/7-2-stable/activestorage/CHANGELOG.md) for previous changes.
data/README.md CHANGED
@@ -73,7 +73,7 @@ end
73
73
  ```erb
74
74
  <%= form_with model: @message, local: true do |form| %>
75
75
  <%= form.text_field :title, placeholder: "Title" %><br>
76
- <%= form.text_area :content %><br><br>
76
+ <%= form.textarea :content %><br><br>
77
77
 
78
78
  <%= form.file_field :images, multiple: true %><br>
79
79
  <%= form.submit %>
@@ -88,7 +88,7 @@ class MessagesController < ApplicationController
88
88
  end
89
89
 
90
90
  def create
91
- message = Message.create! params.require(:message).permit(:title, :content, images: [])
91
+ message = Message.create! params.expect(message: [ :title, :content, images: [] ])
92
92
  redirect_to message
93
93
  end
94
94
 
@@ -203,6 +203,6 @@ Bug reports for the Ruby on \Rails project can be filed here:
203
203
 
204
204
  * https://github.com/rails/rails/issues
205
205
 
206
- Feature requests should be discussed on the rails-core mailing list here:
206
+ Feature requests should be discussed on the rubyonrails-core forum here:
207
207
 
208
208
  * https://discuss.rubyonrails.org/c/rubyonrails-core
@@ -845,4 +845,4 @@ function autostart() {
845
845
 
846
846
  setTimeout(autostart, 1);
847
847
 
848
- export { DirectUpload, DirectUploadController, DirectUploadsController, start };
848
+ export { DirectUpload, DirectUploadController, DirectUploadsController, dispatchEvent, start };
@@ -822,6 +822,7 @@
822
822
  exports.DirectUpload = DirectUpload;
823
823
  exports.DirectUploadController = DirectUploadController;
824
824
  exports.DirectUploadsController = DirectUploadsController;
825
+ exports.dispatchEvent = dispatchEvent;
825
826
  exports.start = start;
826
827
  Object.defineProperty(exports, "__esModule", {
827
828
  value: true
@@ -11,7 +11,7 @@ class ActiveStorage::DirectUploadsController < ActiveStorage::BaseController
11
11
 
12
12
  private
13
13
  def blob_args
14
- params.require(:blob).permit(:filename, :byte_size, :checksum, :content_type, metadata: {}).to_h.symbolize_keys
14
+ params.expect(blob: [:filename, :byte_size, :checksum, :content_type, metadata: {}]).to_h.symbolize_keys
15
15
  end
16
16
 
17
17
  def direct_upload_json(blob)
@@ -17,6 +17,8 @@ class ActiveStorage::DiskController < ActiveStorage::BaseController
17
17
  end
18
18
  rescue Errno::ENOENT
19
19
  head :not_found
20
+ rescue ActiveStorage::InvalidKeyError
21
+ head :not_found
20
22
  end
21
23
 
22
24
  def update
@@ -25,13 +27,15 @@ class ActiveStorage::DiskController < ActiveStorage::BaseController
25
27
  named_disk_service(token[:service_name]).upload token[:key], request.body, checksum: token[:checksum]
26
28
  head :no_content
27
29
  else
28
- head :unprocessable_entity
30
+ head ActionDispatch::Constants::UNPROCESSABLE_CONTENT
29
31
  end
30
32
  else
31
33
  head :not_found
32
34
  end
33
35
  rescue ActiveStorage::IntegrityError
34
- head :unprocessable_entity
36
+ head ActionDispatch::Constants::UNPROCESSABLE_CONTENT
37
+ rescue ActiveStorage::InvalidKeyError
38
+ head ActionDispatch::Constants::UNPROCESSABLE_CONTENT
35
39
  end
36
40
 
37
41
  private
@@ -14,7 +14,8 @@ module ActiveStorage::Streaming
14
14
  def send_blob_byte_range_data(blob, range_header, disposition: nil)
15
15
  ranges = Rack::Utils.get_byte_ranges(range_header, blob.byte_size)
16
16
 
17
- return head(:range_not_satisfiable) if ranges.blank? || ranges.all?(&:blank?)
17
+ return head(:range_not_satisfiable) unless ranges_valid?(ranges)
18
+ return head(:range_not_satisfiable) if ranges.length > ActiveStorage.streaming_max_ranges
18
19
 
19
20
  if ranges.length == 1
20
21
  range = ranges.first
@@ -51,6 +52,12 @@ module ActiveStorage::Streaming
51
52
  )
52
53
  end
53
54
 
55
+ def ranges_valid?(ranges)
56
+ return false if ranges.blank? || ranges.all?(&:blank?)
57
+
58
+ ranges.sum { |range| range.end - range.begin } < ActiveStorage.streaming_chunk_max_size
59
+ end
60
+
54
61
  # Stream the blob from storage directly to the response. The disposition can be controlled by setting +disposition+.
55
62
  # The content type and filename is set directly from the +blob+.
56
63
  def send_blob_stream(blob, disposition: nil) # :doc:
@@ -61,6 +68,15 @@ module ActiveStorage::Streaming
61
68
  blob.download do |chunk|
62
69
  stream.write chunk
63
70
  end
71
+ rescue ActiveStorage::FileNotFoundError
72
+ expires_now
73
+ head :not_found
74
+ rescue
75
+ # Status and caching headers are already set, but not committed.
76
+ # Change the status to 500 manually.
77
+ expires_now
78
+ head :internal_server_error
79
+ raise
64
80
  end
65
81
  end
66
82
  end
@@ -2,7 +2,8 @@ import { start } from "./ujs"
2
2
  import { DirectUpload } from "./direct_upload"
3
3
  import { DirectUploadController } from "./direct_upload_controller"
4
4
  import { DirectUploadsController } from "./direct_uploads_controller"
5
- export { start, DirectUpload, DirectUploadController, DirectUploadsController }
5
+ import { dispatchEvent } from "./helpers"
6
+ export { start, DirectUpload, DirectUploadController, DirectUploadsController, dispatchEvent }
6
7
 
7
8
  function autostart() {
8
9
  if (window.ActiveStorage) {
@@ -25,17 +25,83 @@ module ActiveStorage::Blob::Representable
25
25
  #
26
26
  # <%= image_tag Current.user.avatar.variant(resize_to_limit: [100, 100]) %>
27
27
  #
28
- # This will create a URL for that specific blob with that specific variant, which the ActiveStorage::RepresentationsController
29
- # can then produce on-demand.
28
+ # This will create a URL for that specific blob with that specific variant, which the ActiveStorage::Representations::ProxyController
29
+ # or ActiveStorage::Representations::RedirectController can then produce on-demand.
30
30
  #
31
31
  # Raises ActiveStorage::InvariableError if the variant processor cannot
32
32
  # transform the blob. To determine whether a blob is variable, call
33
33
  # ActiveStorage::Blob#variable?.
34
+ #
35
+ # ==== Options
36
+ #
37
+ # Options are defined by the {image_processing gem}[https://github.com/janko/image_processing],
38
+ # and depend on which variant processor you are using:
39
+ # {Vips}[https://github.com/janko/image_processing/blob/master/doc/vips.md] or
40
+ # {MiniMagick}[https://github.com/janko/image_processing/blob/master/doc/minimagick.md].
41
+ # However, both variant processors support the following options:
42
+ #
43
+ # [+:resize_to_limit+]
44
+ # Downsizes the image to fit within the specified dimensions while retaining
45
+ # the original aspect ratio. Will only resize the image if it's larger than
46
+ # the specified dimensions.
47
+ #
48
+ # user.avatar.variant(resize_to_limit: [100, 100])
49
+ #
50
+ # [+:resize_to_fit+]
51
+ # Resizes the image to fit within the specified dimensions while retaining
52
+ # the original aspect ratio. Will downsize the image if it's larger than the
53
+ # specified dimensions or upsize if it's smaller.
54
+ #
55
+ # user.avatar.variant(resize_to_fit: [100, 100])
56
+ #
57
+ # [+:resize_to_fill+]
58
+ # Resizes the image to fill the specified dimensions while retaining the
59
+ # original aspect ratio. If necessary, will crop the image in the larger
60
+ # dimension.
61
+ #
62
+ # user.avatar.variant(resize_to_fill: [100, 100])
63
+ #
64
+ # [+:resize_and_pad+]
65
+ # Resizes the image to fit within the specified dimensions while retaining
66
+ # the original aspect ratio. If necessary, will pad the remaining area with
67
+ # transparent color if source image has alpha channel, black otherwise.
68
+ #
69
+ # user.avatar.variant(resize_and_pad: [100, 100])
70
+ #
71
+ # [+:crop+]
72
+ # Extracts an area from an image. The first two arguments are the left and
73
+ # top edges of area to extract, while the last two arguments are the width
74
+ # and height of the area to extract.
75
+ #
76
+ # user.avatar.variant(crop: [20, 50, 300, 300])
77
+ #
78
+ # [+:rotate+]
79
+ # Rotates the image by the specified angle.
80
+ #
81
+ # user.avatar.variant(rotate: 90)
82
+ #
83
+ # Some options, including those listed above, can accept additional
84
+ # processor-specific values which can be passed as a trailing hash:
85
+ #
86
+ # <!-- Vips supports configuring `crop` for many of its transformations -->
87
+ # <%= image_tag user.avatar.variant(resize_to_fill: [100, 100, { crop: :centre }]) %>
88
+ #
89
+ # If migrating an existing application between MiniMagick and Vips, you will
90
+ # need to update processor-specific options:
91
+ #
92
+ # <!-- MiniMagick -->
93
+ # <%= image_tag user.avatar.variant(resize_to_limit: [100, 100], format: :jpeg,
94
+ # sampling_factor: "4:2:0", strip: true, interlace: "JPEG", colorspace: "sRGB", quality: 80) %>
95
+ #
96
+ # <!-- Vips -->
97
+ # <%= image_tag user.avatar.variant(resize_to_limit: [100, 100], format: :jpeg,
98
+ # saver: { subsample_mode: "on", strip: true, interlace: true, quality: 80 }) %>
99
+ #
34
100
  def variant(transformations)
35
101
  if variable?
36
102
  variant_class.new(self, ActiveStorage::Variation.wrap(transformations).default_to(default_variant_transformations))
37
103
  else
38
- raise ActiveStorage::InvariableError
104
+ raise ActiveStorage::InvariableError, "Can't transform blob with ID=#{id} and content_type=#{content_type}"
39
105
  end
40
106
  end
41
107
 
@@ -64,7 +130,7 @@ module ActiveStorage::Blob::Representable
64
130
  if previewable?
65
131
  ActiveStorage::Preview.new(self, transformations)
66
132
  else
67
- raise ActiveStorage::UnpreviewableError
133
+ raise ActiveStorage::UnpreviewableError, "No previewer found for blob with ID=#{id} and content_type=#{content_type}"
68
134
  end
69
135
  end
70
136
 
@@ -89,7 +155,7 @@ module ActiveStorage::Blob::Representable
89
155
  when variable?
90
156
  variant transformations
91
157
  else
92
- raise ActiveStorage::UnrepresentableError
158
+ raise ActiveStorage::UnrepresentableError, "No previewer found and can't transform blob with ID=#{id} and content_type=#{content_type}"
93
159
  end
94
160
  end
95
161
 
@@ -16,10 +16,18 @@
16
16
  # Blobs are intended to be immutable in as-so-far as their reference to a specific file goes. You're allowed to
17
17
  # update a blob's metadata on a subsequent pass, but you should not update the key or change the uploaded file.
18
18
  # If you need to create a derivative or otherwise change the blob, simply create a new blob and purge the old one.
19
+ #
20
+ # When using a custom +key+, the value is treated as trusted. Using untrusted user input
21
+ # as the key may result in unexpected behavior.
19
22
  class ActiveStorage::Blob < ActiveStorage::Record
20
23
  MINIMUM_TOKEN_LENGTH = 28
21
24
 
22
25
  has_secure_token :key, length: MINIMUM_TOKEN_LENGTH
26
+
27
+ # FIXME: these property should never have been stored in the metadata.
28
+ # The blob table should be migrated to have dedicated columns for theses.
29
+ PROTECTED_METADATA = %w(analyzed identified composed)
30
+ private_constant :PROTECTED_METADATA
23
31
  store :metadata, accessors: [ :analyzed, :identified, :composed ], coder: ActiveRecord::Coders::JSON
24
32
 
25
33
  class_attribute :services, default: {}
@@ -29,7 +37,7 @@ class ActiveStorage::Blob < ActiveStorage::Record
29
37
  # :method:
30
38
  #
31
39
  # Returns the associated ActiveStorage::Attachment instances.
32
- has_many :attachments
40
+ has_many :attachments, autosave: false
33
41
 
34
42
  ##
35
43
  # :singleton-method:
@@ -71,9 +79,8 @@ class ActiveStorage::Blob < ActiveStorage::Record
71
79
  end
72
80
 
73
81
  # Works like +find_signed+, but will raise an +ActiveSupport::MessageVerifier::InvalidSignature+
74
- # exception if the +signed_id+ has either expired, has a purpose mismatch, is for another record,
75
- # or has been tampered with. It will also raise an +ActiveRecord::RecordNotFound+ exception if
76
- # the valid signed id can't find a record.
82
+ # exception if the +signed_id+ has either expired, has a purpose mismatch, or has been tampered with.
83
+ # It will also raise an +ActiveRecord::RecordNotFound+ exception if the valid signed id can't find a record.
77
84
  def find_signed!(id, record: nil, purpose: :blob_id)
78
85
  super(id, purpose: purpose)
79
86
  end
@@ -93,6 +100,9 @@ class ActiveStorage::Blob < ActiveStorage::Record
93
100
  # be saved before the upload begins to prevent the upload clobbering another due to key collisions.
94
101
  # When providing a content type, pass <tt>identify: false</tt> to bypass
95
102
  # automatic content type inference.
103
+ #
104
+ # The optional +key+ parameter is treated as trusted. Using untrusted user input
105
+ # as the key may result in unexpected behavior.
96
106
  def create_and_upload!(key: nil, io:, filename:, content_type: nil, metadata: nil, service_name: nil, identify: true, record: nil)
97
107
  create_after_unfurling!(key: key, io: io, filename: filename, content_type: content_type, metadata: metadata, service_name: service_name, identify: identify).tap do |blob|
98
108
  blob.upload_without_unfurling(io)
@@ -105,6 +115,7 @@ class ActiveStorage::Blob < ActiveStorage::Record
105
115
  # Once the form using the direct upload is submitted, the blob can be associated with the right record using
106
116
  # the signed ID.
107
117
  def create_before_direct_upload!(key: nil, filename:, byte_size:, checksum:, content_type: nil, metadata: nil, service_name: nil, record: nil)
118
+ metadata = filter_metadata(metadata)
108
119
  create! key: key, filename: filename, byte_size: byte_size, checksum: checksum, content_type: content_type, metadata: metadata, service_name: service_name
109
120
  end
110
121
 
@@ -153,21 +164,14 @@ class ActiveStorage::Blob < ActiveStorage::Record
153
164
  end
154
165
  end
155
166
 
156
- def validate_service_configuration(service_name, model_class, association_name) # :nodoc:
157
- if service_name
158
- services.fetch(service_name) do
159
- raise ArgumentError, "Cannot configure service #{service_name.inspect} for #{model_class}##{association_name}"
167
+ private
168
+ def filter_metadata(metadata)
169
+ if metadata.is_a?(Hash)
170
+ metadata.without(*PROTECTED_METADATA)
171
+ else
172
+ metadata
160
173
  end
161
- else
162
- validate_global_service_configuration
163
174
  end
164
- end
165
-
166
- def validate_global_service_configuration # :nodoc:
167
- if connected? && table_exists? && Rails.configuration.active_storage.service.nil?
168
- raise RuntimeError, "Missing Active Storage service name. Specify Active Storage service name for config.active_storage.service in config/environments/#{Rails.env}.rb"
169
- end
170
- end
171
175
  end
172
176
 
173
177
  include Analyzable
@@ -206,22 +210,22 @@ class ActiveStorage::Blob < ActiveStorage::Record
206
210
 
207
211
  # Returns true if the content_type of this blob is in the image range, like image/png.
208
212
  def image?
209
- content_type.start_with?("image")
213
+ content_type&.start_with?("image")
210
214
  end
211
215
 
212
216
  # Returns true if the content_type of this blob is in the audio range, like audio/mpeg.
213
217
  def audio?
214
- content_type.start_with?("audio")
218
+ content_type&.start_with?("audio")
215
219
  end
216
220
 
217
221
  # Returns true if the content_type of this blob is in the video range, like video/mp4.
218
222
  def video?
219
- content_type.start_with?("video")
223
+ content_type&.start_with?("video")
220
224
  end
221
225
 
222
226
  # Returns true if the content_type of this blob is in the text range, like text/plain.
223
227
  def text?
224
- content_type.start_with?("text")
228
+ content_type&.start_with?("text")
225
229
  end
226
230
 
227
231
  # Returns the URL of the blob on the service. This returns a permanent URL for public files, and returns a
@@ -57,7 +57,7 @@ class ActiveStorage::Filename
57
57
  #
58
58
  # Characters considered unsafe for storage (e.g. \, $, and the RTL override character) are replaced with a dash.
59
59
  def sanitized
60
- @filename.encode(Encoding::UTF_8, invalid: :replace, undef: :replace, replace: "�").strip.tr("\u{202E}%$|:;/\t\r\n\\", "-")
60
+ @filename.encode(Encoding::UTF_8, invalid: :replace, undef: :replace, replace: "�").strip.tr("\u{202E}%$|:;/<>?*\"\t\r\n\\", "-")
61
61
  end
62
62
 
63
63
  # Returns the sanitized version of the filename.
@@ -22,15 +22,15 @@
22
22
  # Note that to create a variant it's necessary to download the entire blob file from the service. Because of this process,
23
23
  # you also want to be considerate about when the variant is actually processed. You shouldn't be processing variants inline
24
24
  # in a template, for example. Delay the processing to an on-demand controller, like the one provided in
25
- # ActiveStorage::RepresentationsController.
25
+ # ActiveStorage::Representations::ProxyController and ActiveStorage::Representations::RedirectController.
26
26
  #
27
27
  # To refer to such a delayed on-demand variant, simply link to the variant through the resolved route provided
28
28
  # by Active Storage like so:
29
29
  #
30
30
  # <%= image_tag Current.user.avatar.variant(resize_to_limit: [100, 100]) %>
31
31
  #
32
- # This will create a URL for that specific blob with that specific variant, which the ActiveStorage::RepresentationsController
33
- # can then produce on-demand.
32
+ # This will create a URL for that specific blob with that specific variant, which the ActiveStorage::Representations::ProxyController
33
+ # or ActiveStorage::Representations::RedirectController can then produce on-demand.
34
34
  #
35
35
  # When you do want to actually produce the variant needed, call +processed+. This will check that the variant
36
36
  # has already been processed and uploaded to the service, and, if so, just return that. Otherwise it will perform
@@ -74,11 +74,11 @@ class ActiveStorage::Variant
74
74
  "variants/#{blob.key}/#{OpenSSL::Digest::SHA256.hexdigest(variation.key)}"
75
75
  end
76
76
 
77
- # Returns the URL of the blob variant on the service. See {ActiveStorage::Blob#url} for details.
77
+ # Returns the URL of the blob variant on the service. See ActiveStorage::Blob#url for details.
78
78
  #
79
79
  # Use <tt>url_for(variant)</tt> (or the implied form, like <tt>link_to variant</tt> or <tt>redirect_to variant</tt>) to get the stable URL
80
- # for a variant that points to the ActiveStorage::RepresentationsController, which in turn will use this +service_call+ method
81
- # for its redirection.
80
+ # for a variant that points to the ActiveStorage::Representations::ProxyController or ActiveStorage::Representations::RedirectController,
81
+ # which in turn will use this +service_call+ method for its redirection.
82
82
  def url(expires_in: ActiveStorage.service_urls_expire_in, disposition: :inline)
83
83
  service.url key, expires_in: expires_in, disposition: disposition, filename: filename, content_type: content_type
84
84
  end
@@ -32,7 +32,7 @@ module ActiveStorage
32
32
  {}
33
33
  end
34
34
  rescue ::Vips::Error => error
35
- logger.error "Skipping image analysis due to an Vips error: #{error.message}"
35
+ logger.error "Skipping image analysis due to a Vips error: #{error.message}"
36
36
  {}
37
37
  end
38
38
  end
@@ -121,7 +121,7 @@ module ActiveStorage
121
121
  service_name = record.attachment_reflections[name].options[:service_name]
122
122
  if service_name.is_a?(Proc)
123
123
  service_name = service_name.call(record)
124
- ActiveStorage::Blob.validate_service_configuration(service_name, record.class, name)
124
+ Attached::Model.validate_service_configuration(service_name, record.class, name)
125
125
  end
126
126
  service_name
127
127
  end
@@ -61,18 +61,16 @@ module ActiveStorage
61
61
  # There is no column defined on the model side, Active Storage takes
62
62
  # care of the mapping between your records and the attachment.
63
63
  #
64
- # To avoid N+1 queries, you can include the attached blobs in your query like so:
65
- #
66
- # User.with_attached_avatar
67
- #
68
- # Under the covers, this relationship is implemented as a +has_one+ association to a
69
- # ActiveStorage::Attachment record and a +has_one-through+ association to a
64
+ # Under the covers, this relationship is implemented as a +has_one+ association to an
65
+ # ActiveStorage::Attachment record and a +has_one-through+ association to an
70
66
  # ActiveStorage::Blob record. These associations are available as +avatar_attachment+
71
67
  # and +avatar_blob+. But you shouldn't need to work with these associations directly in
72
68
  # most circumstances.
73
69
  #
74
- # The system has been designed to having you go through the ActiveStorage::Attached::One
75
- # proxy that provides the dynamic proxy to the associations and factory methods, like +attach+.
70
+ # Instead, +has_one_attached+ generates an ActiveStorage::Attached::One proxy to
71
+ # provide access to the associations and factory methods, like +attach+:
72
+ #
73
+ # user.avatar.attach(uploaded_file)
76
74
  #
77
75
  # The +:dependent+ option defaults to +:purge_later+. This means the attachment will be
78
76
  # purged (i.e. destroyed) in the background whenever the record is destroyed.
@@ -92,6 +90,10 @@ module ActiveStorage
92
90
  # has_one_attached :avatar, service: ->(user) { user.in_europe_region? ? :s3_europe : :s3_usa }
93
91
  # end
94
92
  #
93
+ # To avoid N+1 queries, you can include the attached blobs in your query like so:
94
+ #
95
+ # User.with_attached_avatar
96
+ #
95
97
  # If you need to enable +strict_loading+ to prevent lazy loading of attachment,
96
98
  # pass the +:strict_loading+ option. You can do:
97
99
  #
@@ -104,7 +106,7 @@ module ActiveStorage
104
106
  # <tt>active_storage_attachments.record_type</tt> polymorphic type column of
105
107
  # the corresponding rows.
106
108
  def has_one_attached(name, dependent: :purge_later, service: nil, strict_loading: false)
107
- ActiveStorage::Blob.validate_service_configuration(service, self, name) unless service.is_a?(Proc)
109
+ Attached::Model.validate_service_configuration(service, self, name) unless service.is_a?(Proc)
108
110
 
109
111
  generated_association_methods.class_eval <<-CODE, __FILE__, __LINE__ + 1
110
112
  # frozen_string_literal: true
@@ -161,18 +163,16 @@ module ActiveStorage
161
163
  # There are no columns defined on the model side, Active Storage takes
162
164
  # care of the mapping between your records and the attachments.
163
165
  #
164
- # To avoid N+1 queries, you can include the attached blobs in your query like so:
165
- #
166
- # Gallery.where(user: Current.user).with_attached_photos
167
- #
168
- # Under the covers, this relationship is implemented as a +has_many+ association to a
169
- # ActiveStorage::Attachment record and a +has_many-through+ association to a
166
+ # Under the covers, this relationship is implemented as a +has_many+ association to an
167
+ # ActiveStorage::Attachment record and a +has_many-through+ association to an
170
168
  # ActiveStorage::Blob record. These associations are available as +photos_attachments+
171
169
  # and +photos_blobs+. But you shouldn't need to work with these associations directly in
172
170
  # most circumstances.
173
171
  #
174
- # The system has been designed to having you go through the ActiveStorage::Attached::Many
175
- # proxy that provides the dynamic proxy to the associations and factory methods, like +#attach+.
172
+ # Instead, +has_many_attached+ generates an ActiveStorage::Attached::Many proxy to
173
+ # provide access to the associations and factory methods, like +attach+:
174
+ #
175
+ # user.photos.attach(uploaded_file)
176
176
  #
177
177
  # The +:dependent+ option defaults to +:purge_later+. This means the attachments will be
178
178
  # purged (i.e. destroyed) in the background whenever the record is destroyed.
@@ -192,6 +192,10 @@ module ActiveStorage
192
192
  # has_many_attached :photos, service: ->(gallery) { gallery.personal? ? :personal_s3 : :s3 }
193
193
  # end
194
194
  #
195
+ # To avoid N+1 queries, you can include the attached blobs in your query like so:
196
+ #
197
+ # Gallery.where(user: Current.user).with_attached_photos
198
+ #
195
199
  # If you need to enable +strict_loading+ to prevent lazy loading of attachments,
196
200
  # pass the +:strict_loading+ option. You can do:
197
201
  #
@@ -204,7 +208,7 @@ module ActiveStorage
204
208
  # <tt>active_storage_attachments.record_type</tt> polymorphic type column of
205
209
  # the corresponding rows.
206
210
  def has_many_attached(name, dependent: :purge_later, service: nil, strict_loading: false)
207
- ActiveStorage::Blob.validate_service_configuration(service, self, name) unless service.is_a?(Proc)
211
+ Attached::Model.validate_service_configuration(service, self, name) unless service.is_a?(Proc)
208
212
 
209
213
  generated_association_methods.class_eval <<-CODE, __FILE__, __LINE__ + 1
210
214
  # frozen_string_literal: true
@@ -255,6 +259,25 @@ module ActiveStorage
255
259
  end
256
260
  end
257
261
 
262
+ class << self
263
+ def validate_service_configuration(service_name, model_class, association_name) # :nodoc:
264
+ if service_name
265
+ ActiveStorage::Blob.services.fetch(service_name) do
266
+ raise ArgumentError, "Cannot configure service #{service_name.inspect} for #{model_class}##{association_name}"
267
+ end
268
+ else
269
+ validate_global_service_configuration(model_class)
270
+ end
271
+ end
272
+
273
+ private
274
+ def validate_global_service_configuration(model_class)
275
+ if model_class.connected? && ActiveStorage::Blob.table_exists? && Rails.configuration.active_storage.service.nil?
276
+ raise RuntimeError, "Missing Active Storage service name. Specify Active Storage service name for config.active_storage.service in config/environments/#{Rails.env}.rb"
277
+ end
278
+ end
279
+ end
280
+
258
281
  def attachment_changes # :nodoc:
259
282
  @attachment_changes ||= {}
260
283
  end
@@ -84,6 +84,10 @@ module ActiveStorage
84
84
  end
85
85
 
86
86
  initializer "active_storage.configs" do
87
+ config.before_initialize do |app|
88
+ ActiveStorage.touch_attachment_records = app.config.active_storage.touch_attachment_records != false
89
+ end
90
+
87
91
  config.after_initialize do |app|
88
92
  ActiveStorage.logger = app.config.active_storage.logger || Rails.logger
89
93
  ActiveStorage.variant_processor = app.config.active_storage.variant_processor || :mini_magick
@@ -112,13 +116,13 @@ module ActiveStorage
112
116
  ActiveStorage.variable_content_types = app.config.active_storage.variable_content_types || []
113
117
  ActiveStorage.web_image_content_types = app.config.active_storage.web_image_content_types || []
114
118
  ActiveStorage.content_types_to_serve_as_binary = app.config.active_storage.content_types_to_serve_as_binary || []
115
- ActiveStorage.touch_attachment_records = app.config.active_storage.touch_attachment_records != false
116
119
  ActiveStorage.service_urls_expire_in = app.config.active_storage.service_urls_expire_in || 5.minutes
117
120
  ActiveStorage.urls_expire_in = app.config.active_storage.urls_expire_in
118
121
  ActiveStorage.content_types_allowed_inline = app.config.active_storage.content_types_allowed_inline || []
119
122
  ActiveStorage.binary_content_type = app.config.active_storage.binary_content_type || "application/octet-stream"
120
123
  ActiveStorage.video_preview_arguments = app.config.active_storage.video_preview_arguments || "-y -vframes 1 -f image2"
121
124
  ActiveStorage.track_variants = app.config.active_storage.track_variants || false
125
+ ActiveStorage.streaming_chunk_max_size = app.config.active_storage.streaming_chunk_max_size || 100.megabytes
122
126
  end
123
127
  end
124
128
 
@@ -26,4 +26,8 @@ module ActiveStorage
26
26
 
27
27
  # Raised when a Previewer is unable to generate a preview image.
28
28
  class PreviewError < Error; end
29
+
30
+ # Raised when a storage key resolves to a path outside the service's root
31
+ # directory, indicating a potential path traversal attack.
32
+ class InvalidKeyError < Error; end
29
33
  end
@@ -50,7 +50,7 @@ module ActiveStorage
50
50
  # by ActiveSupport::Testing::FileFixtures.file_fixture, and upload
51
51
  # the file to the Service
52
52
  #
53
- # === Examples
53
+ # ==== Examples
54
54
  #
55
55
  # # tests/fixtures/active_storage/blobs.yml
56
56
  # second_thumbnail_blob: <%= ActiveStorage::FixtureSet.blob(
@@ -7,10 +7,10 @@ module ActiveStorage
7
7
  end
8
8
 
9
9
  module VERSION
10
- MAJOR = 7
11
- MINOR = 2
12
- TINY = 2
13
- PRE = "1"
10
+ MAJOR = 8
11
+ MINOR = 0
12
+ TINY = 5
13
+ PRE = nil
14
14
 
15
15
  STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
16
16
  end
@@ -15,6 +15,13 @@ module ActiveStorage
15
15
  attr_reader :client, :container, :signer
16
16
 
17
17
  def initialize(storage_account_name:, storage_access_key:, container:, public: false, **options)
18
+ ActiveStorage.deprecator.warn <<~MSG.squish
19
+ `ActiveStorage::Service::AzureStorageService` is deprecated and will be
20
+ removed in Rails 8.1.
21
+ Please try the `azure-blob` gem instead.
22
+ This gem is not maintained by the Rails team, so please test your applications before deploying to production.
23
+ MSG
24
+
18
25
  @client = Azure::Storage::Blob::BlobService.create(storage_account_name: storage_account_name, storage_access_key: storage_access_key, **options)
19
26
  @signer = Azure::Storage::Common::Core::Auth::SharedAccessSignature.new(storage_account_name, storage_access_key)
20
27
  @container = container
@@ -60,7 +60,16 @@ module ActiveStorage
60
60
 
61
61
  def delete_prefixed(prefix)
62
62
  instrument :delete_prefixed, prefix: prefix do
63
- Dir.glob(path_for("#{prefix}*")).each do |path|
63
+ prefix_path = path_for(prefix)
64
+
65
+ # File.expand_path (called within path_for) strips trailing slashes.
66
+ # Restore trailing separator if the original prefix had one, so that
67
+ # the glob "prefix/*" matches files inside the directory, not siblings
68
+ # whose names start with the prefix string.
69
+ prefix_path += "/" if prefix.end_with?("/")
70
+
71
+ escaped = escape_glob_metacharacters(prefix_path)
72
+ Dir.glob("#{escaped}*").each do |path|
64
73
  FileUtils.rm_rf(path)
65
74
  end
66
75
  end
@@ -98,8 +107,39 @@ module ActiveStorage
98
107
  { "Content-Type" => content_type }
99
108
  end
100
109
 
110
+ # Every filesystem operation in DiskService resolves paths through this method (or through
111
+ # make_path_for, which delegates here). This is the primary filesystem security check: all
112
+ # path-traversal protection is enforced here. New methods that touch the filesystem MUST use
113
+ # path_for or make_path_for -- never construct paths from +root+ directly.
101
114
  def path_for(key) # :nodoc:
102
- File.join root, folder_for(key), key
115
+ if key.blank?
116
+ raise ActiveStorage::InvalidKeyError, "key is blank"
117
+ end
118
+
119
+ # Reject keys with dot segments as defense in depth. This prevents path traversal both outside
120
+ # and within the storage root. The root containment check below is a more fundamental check on
121
+ # path traversal outside of the disk service root.
122
+ begin
123
+ if key.split("/").intersect?(%w[. ..])
124
+ raise ActiveStorage::InvalidKeyError, "key has path traversal segments"
125
+ end
126
+ rescue Encoding::CompatibilityError
127
+ raise ActiveStorage::InvalidKeyError, "key has incompatible encoding"
128
+ end
129
+
130
+ begin
131
+ path = File.expand_path(File.join(root, folder_for(key), key))
132
+ rescue ArgumentError
133
+ # ArgumentError catches null bytes
134
+ raise ActiveStorage::InvalidKeyError, "key is an invalid string"
135
+ end
136
+
137
+ # The resolved path must be inside the root directory.
138
+ unless path.start_with?(File.expand_path(root) + "/")
139
+ raise ActiveStorage::InvalidKeyError, "key is outside of disk service root"
140
+ end
141
+
142
+ path
103
143
  end
104
144
 
105
145
  def compose(source_keys, destination_key, **)
@@ -156,6 +196,10 @@ module ActiveStorage
156
196
  [ key[0..1], key[2..3] ].join("/")
157
197
  end
158
198
 
199
+ def escape_glob_metacharacters(path)
200
+ path.gsub(/[\[\]*?{}\\]/) { |c| "\\#{c}" }
201
+ end
202
+
159
203
  def make_path_for(key)
160
204
  path_for(key).tap { |path| FileUtils.mkdir_p File.dirname(path) }
161
205
  end
@@ -30,6 +30,13 @@ module ActiveStorage
30
30
 
31
31
  def initialize(primary:, mirrors:)
32
32
  @primary, @mirrors = primary, mirrors
33
+ @executor = Concurrent::ThreadPoolExecutor.new(
34
+ min_threads: 1,
35
+ max_threads: mirrors.size,
36
+ max_queue: 0,
37
+ fallback_policy: :caller_runs,
38
+ idle_time: 60
39
+ )
33
40
  end
34
41
 
35
42
  # Upload the +io+ to the +key+ specified to all services. The upload to the primary service is done synchronously
@@ -75,10 +82,12 @@ module ActiveStorage
75
82
  end
76
83
 
77
84
  def perform_across_services(method, *args)
78
- # FIXME: Convert to be threaded
79
- each_service.collect do |service|
80
- service.public_send method, *args
85
+ tasks = each_service.collect do |service|
86
+ Concurrent::Promise.execute(executor: @executor) do
87
+ service.public_send method, *args
88
+ end
81
89
  end
90
+ tasks.each(&:value!)
82
91
  end
83
92
  end
84
93
  end
@@ -16,6 +16,7 @@ module ActiveStorage
16
16
 
17
17
  def initialize(bucket:, upload: {}, public: false, **options)
18
18
  @client = Aws::S3::Resource.new(**options)
19
+ @transfer_manager = Aws::S3::TransferManager.new(client: @client.client) if defined?(Aws::S3::TransferManager)
19
20
  @bucket = @client.bucket(bucket)
20
21
 
21
22
  @multipart_upload_threshold = upload.delete(:multipart_threshold) || 100.megabytes
@@ -100,7 +101,8 @@ module ActiveStorage
100
101
  def compose(source_keys, destination_key, filename: nil, content_type: nil, disposition: nil, custom_metadata: {})
101
102
  content_disposition = content_disposition_with(type: disposition, filename: filename) if disposition && filename
102
103
 
103
- object_for(destination_key).upload_stream(
104
+ upload_stream(
105
+ key: destination_key,
104
106
  content_type: content_type,
105
107
  content_disposition: content_disposition,
106
108
  part_size: MINIMUM_UPLOAD_PART_SIZE,
@@ -116,6 +118,14 @@ module ActiveStorage
116
118
  end
117
119
 
118
120
  private
121
+ def upload_stream(key:, **options, &block)
122
+ if @transfer_manager
123
+ @transfer_manager.upload_stream(key: key, bucket: bucket.name, **options, &block)
124
+ else
125
+ object_for(key).upload_stream(**options, &block)
126
+ end
127
+ end
128
+
119
129
  def private_url(key, expires_in:, filename:, disposition:, content_type:, **client_opts)
120
130
  object_for(key).presigned_url :get, expires_in: expires_in.to_i,
121
131
  response_content_disposition: content_disposition_with(type: disposition, filename: filename),
@@ -126,7 +136,6 @@ module ActiveStorage
126
136
  object_for(key).public_url(**client_opts)
127
137
  end
128
138
 
129
-
130
139
  MAXIMUM_UPLOAD_PARTS_COUNT = 10000
131
140
  MINIMUM_UPLOAD_PART_SIZE = 5.megabytes
132
141
 
@@ -139,12 +148,18 @@ module ActiveStorage
139
148
  def upload_with_multipart(key, io, content_type: nil, content_disposition: nil, custom_metadata: {})
140
149
  part_size = [ io.size.fdiv(MAXIMUM_UPLOAD_PARTS_COUNT).ceil, MINIMUM_UPLOAD_PART_SIZE ].max
141
150
 
142
- object_for(key).upload_stream(content_type: content_type, content_disposition: content_disposition, part_size: part_size, metadata: custom_metadata, **upload_options) do |out|
151
+ upload_stream(
152
+ key: key,
153
+ content_type: content_type,
154
+ content_disposition: content_disposition,
155
+ part_size: part_size,
156
+ metadata: custom_metadata,
157
+ **upload_options
158
+ ) do |out|
143
159
  IO.copy_stream(io, out)
144
160
  end
145
161
  end
146
162
 
147
-
148
163
  def object_for(key)
149
164
  bucket.object(key)
150
165
  end
@@ -27,6 +27,7 @@ require "active_record"
27
27
  require "active_support"
28
28
  require "active_support/rails"
29
29
  require "active_support/core_ext/numeric/time"
30
+ require "active_support/core_ext/numeric/bytes"
30
31
 
31
32
  require "active_storage/version"
32
33
  require "active_storage/deprecator"
@@ -72,7 +73,6 @@ module ActiveStorage
72
73
  "annotate",
73
74
  "antialias",
74
75
  "append",
75
- "apply",
76
76
  "attenuate",
77
77
  "authenticate",
78
78
  "auto_gamma",
@@ -213,7 +213,6 @@ module ActiveStorage
213
213
  "linewidth",
214
214
  "liquid_rescale",
215
215
  "list",
216
- "loader",
217
216
  "log",
218
217
  "loop",
219
218
  "lowlight_color",
@@ -276,7 +275,6 @@ module ActiveStorage
276
275
  "rotate",
277
276
  "sample",
278
277
  "sampling_factor",
279
- "saver",
280
278
  "scale",
281
279
  "scene",
282
280
  "screen",
@@ -353,6 +351,7 @@ module ActiveStorage
353
351
  ]
354
352
  mattr_accessor :unsupported_image_processing_arguments
355
353
 
354
+ mattr_accessor :streaming_chunk_max_size, default: 100.megabytes
356
355
  mattr_accessor :service_urls_expire_in, default: 5.minutes
357
356
  mattr_accessor :touch_attachment_records, default: true
358
357
  mattr_accessor :urls_expire_in
@@ -363,6 +362,18 @@ module ActiveStorage
363
362
 
364
363
  mattr_accessor :track_variants, default: false
365
364
 
365
+ singleton_class.attr_accessor :checksum_implementation
366
+ @checksum_implementation = OpenSSL::Digest::MD5
367
+ begin
368
+ @checksum_implementation.hexdigest("test")
369
+ rescue # OpenSSL may have MD5 disabled
370
+ require "digest/md5"
371
+ @checksum_implementation = Digest::MD5
372
+ end
373
+
374
+ singleton_class.attr_accessor :streaming_max_ranges
375
+ @streaming_max_ranges = 1
376
+
366
377
  mattr_accessor :video_preview_arguments, default: "-y -vframes 1 -f image2"
367
378
 
368
379
  module Transformers
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: activestorage
3
3
  version: !ruby/object:Gem::Version
4
- version: 7.2.2.1
4
+ version: 8.0.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - David Heinemeier Hansson
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain: []
11
- date: 2024-12-10 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: activesupport
@@ -16,56 +15,56 @@ dependencies:
16
15
  requirements:
17
16
  - - '='
18
17
  - !ruby/object:Gem::Version
19
- version: 7.2.2.1
18
+ version: 8.0.5
20
19
  type: :runtime
21
20
  prerelease: false
22
21
  version_requirements: !ruby/object:Gem::Requirement
23
22
  requirements:
24
23
  - - '='
25
24
  - !ruby/object:Gem::Version
26
- version: 7.2.2.1
25
+ version: 8.0.5
27
26
  - !ruby/object:Gem::Dependency
28
27
  name: actionpack
29
28
  requirement: !ruby/object:Gem::Requirement
30
29
  requirements:
31
30
  - - '='
32
31
  - !ruby/object:Gem::Version
33
- version: 7.2.2.1
32
+ version: 8.0.5
34
33
  type: :runtime
35
34
  prerelease: false
36
35
  version_requirements: !ruby/object:Gem::Requirement
37
36
  requirements:
38
37
  - - '='
39
38
  - !ruby/object:Gem::Version
40
- version: 7.2.2.1
39
+ version: 8.0.5
41
40
  - !ruby/object:Gem::Dependency
42
41
  name: activejob
43
42
  requirement: !ruby/object:Gem::Requirement
44
43
  requirements:
45
44
  - - '='
46
45
  - !ruby/object:Gem::Version
47
- version: 7.2.2.1
46
+ version: 8.0.5
48
47
  type: :runtime
49
48
  prerelease: false
50
49
  version_requirements: !ruby/object:Gem::Requirement
51
50
  requirements:
52
51
  - - '='
53
52
  - !ruby/object:Gem::Version
54
- version: 7.2.2.1
53
+ version: 8.0.5
55
54
  - !ruby/object:Gem::Dependency
56
55
  name: activerecord
57
56
  requirement: !ruby/object:Gem::Requirement
58
57
  requirements:
59
58
  - - '='
60
59
  - !ruby/object:Gem::Version
61
- version: 7.2.2.1
60
+ version: 8.0.5
62
61
  type: :runtime
63
62
  prerelease: false
64
63
  version_requirements: !ruby/object:Gem::Requirement
65
64
  requirements:
66
65
  - - '='
67
66
  - !ruby/object:Gem::Version
68
- version: 7.2.2.1
67
+ version: 8.0.5
69
68
  - !ruby/object:Gem::Dependency
70
69
  name: marcel
71
70
  requirement: !ruby/object:Gem::Requirement
@@ -190,12 +189,11 @@ licenses:
190
189
  - MIT
191
190
  metadata:
192
191
  bug_tracker_uri: https://github.com/rails/rails/issues
193
- changelog_uri: https://github.com/rails/rails/blob/v7.2.2.1/activestorage/CHANGELOG.md
194
- documentation_uri: https://api.rubyonrails.org/v7.2.2.1/
192
+ changelog_uri: https://github.com/rails/rails/blob/v8.0.5/activestorage/CHANGELOG.md
193
+ documentation_uri: https://api.rubyonrails.org/v8.0.5/
195
194
  mailing_list_uri: https://discuss.rubyonrails.org/c/rubyonrails-talk
196
- source_code_uri: https://github.com/rails/rails/tree/v7.2.2.1/activestorage
195
+ source_code_uri: https://github.com/rails/rails/tree/v8.0.5/activestorage
197
196
  rubygems_mfa_required: 'true'
198
- post_install_message:
199
197
  rdoc_options: []
200
198
  require_paths:
201
199
  - lib
@@ -203,15 +201,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
203
201
  requirements:
204
202
  - - ">="
205
203
  - !ruby/object:Gem::Version
206
- version: 3.1.0
204
+ version: 3.2.0
207
205
  required_rubygems_version: !ruby/object:Gem::Requirement
208
206
  requirements:
209
207
  - - ">="
210
208
  - !ruby/object:Gem::Version
211
209
  version: '0'
212
210
  requirements: []
213
- rubygems_version: 3.5.22
214
- signing_key:
211
+ rubygems_version: 4.0.6
215
212
  specification_version: 4
216
213
  summary: Local and cloud file storage framework.
217
214
  test_files: []