このコミットが含まれているのは:
2026-07-18 11:55:09 +09:00
コミット b3e67d8cca
4個のファイルの変更70行の追加7行の削除
+30 -2
ファイルの表示
@@ -17,6 +17,7 @@ class Post < ApplicationRecord
MAX_SVG_DIMENSION = 4_096 MAX_SVG_DIMENSION = 4_096
MAX_SVG_PIXELS = 16_777_216 MAX_SVG_PIXELS = 16_777_216
THUMBNAIL_PROCESS_TIMEOUT = 5.seconds THUMBNAIL_PROCESS_TIMEOUT = 5.seconds
RASTER_THUMBNAIL_FORMATS = ['jpeg', 'png', 'gif', 'webp'].freeze
def self.resized_thumbnail_attachment(upload, content_type: nil) def self.resized_thumbnail_attachment(upload, content_type: nil)
upload.rewind upload.rewind
@@ -225,9 +226,23 @@ class Post < ApplicationRecord
end end
def self.image_for_thumbnail_upload(bytes, content_type: nil) def self.image_for_thumbnail_upload(bytes, content_type: nil)
return decode_raster_thumbnail(bytes) unless svg_content_type?(content_type) || svg_document_bytes?(bytes) if svg_content_type?(content_type) || svg_document_bytes?(bytes)
return decode_svg_thumbnail(bytes)
end
decode_svg_thumbnail(bytes) raise MiniMagick::Error, 'サムネイル画像の形式が不正です.' unless raster_thumbnail_bytes?(bytes)
decode_raster_thumbnail(bytes)
end
def self.raster_thumbnail_bytes?(bytes)
raster_thumbnail_format(bytes).present?
end
def self.remote_thumbnail_image_bytes?(bytes, content_type: nil)
return true if svg_content_type?(content_type) || svg_document_bytes?(bytes)
raster_thumbnail_bytes?(bytes)
end end
def self.svg_content_type?(content_type) def self.svg_content_type?(content_type)
@@ -254,6 +269,18 @@ class Post < ApplicationRecord
MiniMagick::Image.read(sanitised_svg_bytes(bytes)) MiniMagick::Image.read(sanitised_svg_bytes(bytes))
end end
def self.raster_thumbnail_format(bytes)
binary = bytes.to_s.b
return 'jpeg' if binary.start_with?("\xFF\xD8\xFF".b)
return 'png' if binary.start_with?("\x89PNG\r\n\x1A\n".b)
return 'gif' if binary.start_with?('GIF87a'.b) || binary.start_with?('GIF89a'.b)
return 'webp' if binary.bytesize >= 12 &&
binary.start_with?('RIFF'.b) &&
binary.byteslice(8, 4) == 'WEBP'
nil
end
def self.sanitised_svg_bytes(bytes) def self.sanitised_svg_bytes(bytes)
parse_options = parse_options =
Nokogiri::XML::ParseOptions::STRICT | Nokogiri::XML::ParseOptions::STRICT |
@@ -354,6 +381,7 @@ class Post < ApplicationRecord
:svg_content_type?, :svg_content_type?,
:decode_raster_thumbnail, :decode_raster_thumbnail,
:decode_svg_thumbnail, :decode_svg_thumbnail,
:raster_thumbnail_format,
:sanitised_svg_bytes, :sanitised_svg_bytes,
:svg_uses_disallowed_features?, :svg_uses_disallowed_features?,
:external_svg_reference?, :external_svg_reference?,
+26
ファイルの表示
@@ -12,6 +12,7 @@ class PostCreatePlan
direct_tag_specs, tag_sections = parse_direct_tag_specs direct_tag_specs, tag_sections = parse_direct_tag_specs
default_tag_specs = build_default_tag_specs(direct_tag_specs) default_tag_specs = build_default_tag_specs(direct_tag_specs)
snapshot_tag_specs = merge_tag_specs(direct_tag_specs + default_tag_specs) snapshot_tag_specs = merge_tag_specs(direct_tag_specs + default_tag_specs)
validate_new_tag_specs!(snapshot_tag_specs)
post_tag_specs = expand_parent_tag_specs(snapshot_tag_specs) post_tag_specs = expand_parent_tag_specs(snapshot_tag_specs)
video_ms = normalise_video_ms(snapshot_tag_specs) video_ms = normalise_video_ms(snapshot_tag_specs)
validate_video_sections!(video_ms, tag_sections) validate_video_sections!(video_ms, tag_sections)
@@ -103,6 +104,31 @@ class PostCreatePlan
default_tag_specs default_tag_specs
end end
def validate_new_tag_specs! specs
Array(specs).each do |spec|
next if existing_tags_by_name.key?(spec[:name])
validate_new_tag_spec!(spec)
end
end
def validate_new_tag_spec! spec
tag_name = TagName.new(name: spec[:name])
tag = Tag.new(category: spec[:category], tag_name:)
return if tag_name.valid? && tag.valid?
post = Post.new
tag_name.errors[:name].each do |message|
post.errors.add :tags, "#{ spec[:name] }: #{ message }"
end
tag.errors.each do |error|
next if error.attribute == :tag_name
post.errors.add :tags, "#{ spec[:name] }: #{ error.message }"
end
raise ActiveRecord::RecordInvalid, post
end
def expand_parent_tag_specs snapshot_tag_specs def expand_parent_tag_specs snapshot_tag_specs
existing_snapshot_tags = snapshot_tag_specs.filter_map { existing_tags_by_name[_1[:name]] } existing_snapshot_tags = snapshot_tag_specs.filter_map { existing_tags_by_name[_1[:name]] }
expanded_parent_specs = expanded_parent_specs =
+8 -3
ファイルの表示
@@ -11,10 +11,15 @@ class PostThumbnailUploadValidator
end end
raise InvalidUpload, 'thumbnail file size が大きすぎます.' if thumbnail.size > MAX_THUMBNAIL_BYTES raise InvalidUpload, 'thumbnail file size が大きすぎます.' if thumbnail.size > MAX_THUMBNAIL_BYTES
raise InvalidUpload, 'サムネイル画像の形式が不正です.' unless allowed_content_type?(thumbnail.content_type) raise InvalidUpload, 'サムネイル画像の形式が不正です.' unless allowed_content_type?(thumbnail.content_type)
raise InvalidUpload, 'サムネイル画像の形式が不正です.' if Post.svg_document_bytes?(thumbnail.read) bytes = thumbnail.read
thumbnail.rewind raise InvalidUpload, 'サムネイル画像の形式が不正です.' if Post.svg_document_bytes?(bytes)
unless Post.raster_thumbnail_bytes?(bytes)
raise InvalidUpload, 'サムネイル画像の形式が不正です.'
end
attachment = Post.resized_thumbnail_attachment(thumbnail, content_type: thumbnail.content_type) attachment = Post.resized_thumbnail_attachment(
StringIO.new(bytes),
content_type: thumbnail.content_type)
attachment[:io].close if attachment[:io].respond_to?(:close) attachment[:io].close if attachment[:io].respond_to?(:close)
rescue MiniMagick::Error, Timeout::Error rescue MiniMagick::Error, Timeout::Error
raise InvalidUpload, 'サムネイル画像の変換に失敗しました.' raise InvalidUpload, 'サムネイル画像の変換に失敗しました.'
+6 -2
ファイルの表示
@@ -32,7 +32,9 @@ module Preview
def self.fetch_image_response(raw_url) def self.fetch_image_response(raw_url)
uri, = UrlSafety.validate(raw_url) uri, = UrlSafety.validate(raw_url)
response = HttpFetcher.fetch(uri.to_s) response = HttpFetcher.fetch(uri.to_s)
unless allowed_remote_image_content_type?(response.content_type) || Post.svg_document_bytes?(response.body) unless Post.remote_thumbnail_image_bytes?(
response.body,
content_type: response.content_type)
raise GenerationFailed, 'サムネール画像が見つかりませんでした.' raise GenerationFailed, 'サムネール画像が見つかりませんでした.'
end end
@@ -55,7 +57,9 @@ module Preview
return nil if url.blank? return nil if url.blank?
response = HttpFetcher.fetch(url) response = HttpFetcher.fetch(url)
return nil unless allowed_remote_image_content_type?(response.content_type) return nil unless Post.remote_thumbnail_image_bytes?(
response.body,
content_type: response.content_type)
response.body response.body
rescue HttpFetcher::FetchTimeout rescue HttpFetcher::FetchTimeout