You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

84 lines
2.9 KiB

  1. # frozen_string_literal: true
  2. module Remotable
  3. extend ActiveSupport::Concern
  4. class_methods do
  5. def remotable_attachment(attachment_name, limit, suppress_errors: true, download_on_assign: true, attribute_name: nil)
  6. attribute_name ||= "#{attachment_name}_remote_url".to_sym
  7. define_method("download_#{attachment_name}!") do |url = nil|
  8. url ||= self[attribute_name]
  9. return if url.blank?
  10. begin
  11. parsed_url = Addressable::URI.parse(url).normalize
  12. rescue Addressable::URI::InvalidURIError
  13. return
  14. end
  15. return if !%w(http https).include?(parsed_url.scheme) || parsed_url.host.blank?
  16. begin
  17. Request.new(:get, url).perform do |response|
  18. raise Mastodon::UnexpectedResponseError, response unless (200...300).cover?(response.code)
  19. content_type = parse_content_type(response.headers.get('content-type').last)
  20. extname = detect_extname_from_content_type(content_type)
  21. if extname.nil?
  22. disposition = response.headers.get('content-disposition').last
  23. matches = disposition&.match(/filename="([^"]*)"/)
  24. filename = matches.nil? ? parsed_url.path.split('/').last : matches[1]
  25. extname = filename.nil? ? '' : File.extname(filename)
  26. end
  27. basename = SecureRandom.hex(8)
  28. public_send("#{attachment_name}_file_name=", basename + extname)
  29. public_send("#{attachment_name}=", StringIO.new(response.body_with_limit(limit)))
  30. end
  31. rescue Mastodon::UnexpectedResponseError, HTTP::TimeoutError, HTTP::ConnectionError, OpenSSL::SSL::SSLError => e
  32. Rails.logger.debug "Error fetching remote #{attachment_name}: #{e}"
  33. raise e unless suppress_errors
  34. rescue Paperclip::Errors::NotIdentifiedByImageMagickError, Addressable::URI::InvalidURIError, Mastodon::HostValidationError, Mastodon::LengthValidationError, Paperclip::Error, Mastodon::DimensionsValidationError => e
  35. Rails.logger.debug "Error fetching remote #{attachment_name}: #{e}"
  36. nil
  37. end
  38. end
  39. define_method("#{attribute_name}=") do |url|
  40. return if self[attribute_name] == url && public_send("#{attachment_name}_file_name").present?
  41. self[attribute_name] = url if has_attribute?(attribute_name)
  42. public_send("download_#{attachment_name}!", url) if download_on_assign
  43. end
  44. alias_method("reset_#{attachment_name}!", "download_#{attachment_name}!")
  45. end
  46. end
  47. private
  48. def detect_extname_from_content_type(content_type)
  49. return if content_type.nil?
  50. type = MIME::Types[content_type].first
  51. return if type.nil?
  52. extname = type.extensions.first
  53. return if extname.nil?
  54. ".#{extname}"
  55. end
  56. def parse_content_type(content_type)
  57. return if content_type.nil?
  58. content_type.split(/\s*;\s*/).first
  59. end
  60. end