Skip to content

Commit

Permalink
Merge pull request #5958 from avalonmediasystem/tempfile_location
Browse files Browse the repository at this point in the history
Add setting for alternative rack tempfile directory
  • Loading branch information
masaball authored Jul 31, 2024
2 parents e7a4323 + dd32dd9 commit 71b03a4
Show file tree
Hide file tree
Showing 4 changed files with 117 additions and 0 deletions.
3 changes: 3 additions & 0 deletions config/application.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
require_relative 'boot'
require_relative '../lib/tempfile_factory'

require 'rails/all'
require 'resolv-replace'
Expand Down Expand Up @@ -56,6 +57,8 @@ class Application < Rails::Application
end
end

config.middleware.insert_before 0, TempfileFactory

config.active_storage.service = (Settings&.active_storage&.service.presence || "local").to_sym
end
end
5 changes: 5 additions & 0 deletions config/settings.yml
Original file line number Diff line number Diff line change
Expand Up @@ -123,3 +123,8 @@ derivative:
allow_download: true
# Maximum size for uploaded files in bytes (default is disabled)
#max_upload_size: 2147483648 # Use :none or comment out to disable limit
# Rack Multipart creates temporary files when processing multipart form data with a large payload.
# If the default system /tmp directory is storage constrained, you can define an alternative here.
# Leave commented out to use the system default.
# tempfile:
# location: '/tmp'
30 changes: 30 additions & 0 deletions lib/tempfile_factory.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Middleware to override the default Rack::Multipart tempfile factory:
# https://www.rubydoc.info/gems/rack/Rack/Multipart/Parser#TEMPFILE_FACTORY-constant
class TempfileFactory
def initialize(app)
@app = app
return unless Settings.tempfile.present?
if Settings.tempfile&.location.present? && File.directory?(Settings.tempfile&.location) && File.writable?(Settings.tempfile&.location)
@tempfile_location = Settings.tempfile.location
else
logger.warn("[Rack::Multipart] [Tempfile] #{Settings.tempfile.location} is not a diretory or not writable. Falling back to #{Dir.tmpdir}.")
end
end

def call(env)
if @tempfile_location
env["rack.multipart.tempfile_factory"] = lambda { |filename, content_type|
extension = ::File.extname(filename.gsub("\0", '%00'))[0, 129]
Tempfile.new(["RackMultipart", extension], @tempfile_location)
}
end

@app.call(env)
end

private

def logger
@logger ||= Logger.new(File.join(Rails.root, 'log', "#{Rails.env}.log"))
end
end
79 changes: 79 additions & 0 deletions spec/lib/tempfile_factory_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
# Copyright 2011-2024, The Trustees of Indiana University and Northwestern
# University. Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
#
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software distributed
# under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
# CONDITIONS OF ANY KIND, either express or implied. See the License for the
# specific language governing permissions and limitations under the License.
# --- END LICENSE_HEADER BLOCK ---

require 'rails_helper'

describe TempfileFactory do
class MockApp
def call(env)
[200, {}, env]
end
end

let(:file) { fixture_file_upload("videoshort.mp4", "video/mp4") }
let(:env) { Rack::MockRequest.env_for('/', method: :post, params: file) }
let(:app) { MockApp.new }
subject { TempfileFactory.new(app) }

around do |example|
@old_config = Settings.dig(:tempfile, :location)
if tempfile_config.present?
Settings.tempfile ||= Config::Options.new
Settings.tempfile.location = tempfile_config
else
Settings.tempfile = nil
end
example.run
if @old_config.present?
Settings.tempfile ||= Config::Options.new
Settings.tempfile.location = @old_config
else
Settings.tempfile = nil
end
end

context "when an alternate directory is defined" do
let(:tempfile_config) { Rails.root.join('spec', 'fixtures').to_s }

it "sets `env['rack.multipart.tempfile_factory']`" do
status, headers, response = subject.call(env)
expect(response).to include 'rack.multipart.tempfile_factory'
expect(response['rack.multipart.tempfile_factory'].call("videoshort.mp4", "video/mp4").path).to start_with(tempfile_config)
end
end

context "when an alternate directory is NOT defined" do
let(:tempfile_config) { nil }

it "does not set `env['rack.multipart.tempfile_factory']`" do
status, headers, response = subject.call(env)
expect(response).to_not include 'rack.multipart.tempfile_factory'
end
end

context "when there is a problem with the defined alternate directory" do
let(:tempfile_config) { 'does not exist' }
let(:logger) { double() }

before do
allow_any_instance_of(TempfileFactory).to receive(:logger).and_return(logger)
end

it "does not set `env['rack.multipart.tempfile_factory']`" do
expect(logger).to receive(:warn).with(match("Falling back"))
status, headers, response = subject.call(env)
expect(response).to_not include 'rack.multipart.tempfile_factory'
end
end
end

0 comments on commit 71b03a4

Please sign in to comment.