uploads: fix "ArgumentError: string contains null byte" error

Fix an error when trying to upload a file larger than the file size
limit. In this case we tried to dump the whole HTTP response into the
error message, which included the binary file itself, which caused this
exception because it contained null bytes.
This commit is contained in:
evazion
2022-02-15 17:34:49 -06:00
parent 16b8d4b607
commit 87a00a1182
3 changed files with 26 additions and 12 deletions

View File

@@ -148,12 +148,12 @@ module Danbooru
response = get(url) response = get(url)
raise DownloadError, "Downloading #{response.uri} failed with code #{response.status}" if response.status != 200 raise DownloadError, "Downloading #{response.uri} failed with code #{response.status}" if response.status != 200
raise FileTooLargeError, response if @max_size && response.content_length.to_i > @max_size raise FileTooLargeError, "File size too large (size: #{response.content_length.to_i.to_formatted_s(:human_size)}; max size: #{@max_size.to_formatted_s(:human_size)})" if @max_size && response.content_length.to_i > @max_size
size = 0 size = 0
response.body.each do |chunk| response.body.each do |chunk|
size += chunk.size size += chunk.size
raise FileTooLargeError if @max_size && size > @max_size raise FileTooLargeError, "File size too large (max size: #{@max_size.to_formatted_s(:human_size)})" if @max_size && size > @max_size
file.write(chunk) file.write(chunk)
end end

View File

@@ -39,7 +39,7 @@ class MediaAsset < ApplicationRecord
validates :md5, uniqueness: { conditions: -> { where(status: [:processing, :active]) } } validates :md5, uniqueness: { conditions: -> { where(status: [:processing, :active]) } }
validates :file_ext, inclusion: { in: %w[jpg png gif mp4 webm swf zip], message: "Not an image or video" } validates :file_ext, inclusion: { in: %w[jpg png gif mp4 webm swf zip], message: "Not an image or video" }
validates :file_size, numericality: { less_than_or_equal_to: Danbooru.config.max_file_size } validates :file_size, numericality: { less_than_or_equal_to: Danbooru.config.max_file_size, message: ->(asset, _) { "too large (size: #{asset.file_size.to_formatted_s(:human_size)}; max size: #{Danbooru.config.max_file_size.to_formatted_s(:human_size)})" } }
validates :duration, numericality: { less_than_or_equal_to: MAX_VIDEO_DURATION, message: "must be less than #{MAX_VIDEO_DURATION} seconds", allow_nil: true }, on: :create # XXX should allow admins to bypass validates :duration, numericality: { less_than_or_equal_to: MAX_VIDEO_DURATION, message: "must be less than #{MAX_VIDEO_DURATION} seconds", allow_nil: true }, on: :create # XXX should allow admins to bypass
validate :validate_resolution, on: :create validate :validate_resolution, on: :create

View File

@@ -177,18 +177,32 @@ class UploadsControllerTest < ActionDispatch::IntegrationTest
assert_match("Not an image or video", Upload.last.error) assert_match("Not an image or video", Upload.last.error)
end end
should "fail if the file size is too large" do context "for a file larger than the file size limit" do
skip "flaky test" setup do
Danbooru.config.stubs(:max_file_size).returns(1.kilobyte) skip "flaky test"
Danbooru.config.stubs(:max_file_size).returns(1.kilobyte)
end
file = Rack::Test::UploadedFile.new("test/files/test.jpg") should "fail for a direct file upload" do
post_auth uploads_path(format: :json), @user, params: { upload: { file: file }} create_upload!("test/files/test.jpg", user: @user)
perform_enqueued_jobs
assert_response 201 assert_response 201
assert_match("File size must be less than or equal to", Upload.last.error) assert_match("File size too large", Upload.last.error)
end
Danbooru.config.unstub(:max_file_size) should "fail for a source upload with a Content-Length header" do
create_upload!("https://nghttp2.org/httpbin/bytes/2000", user: @user)
assert_response 201
assert_match("File size too large", Upload.last.error)
end
should "fail for a source upload without a Content-Length header" do
create_upload!("https://nghttp2.org/httpbin/stream-bytes/2000", user: @user)
assert_response 201
assert_match("File size too large", Upload.last.error)
end
end end
context "for a corrupted image" do context "for a corrupted image" do