diff --git a/app/builders/messages/messenger/message_builder.rb b/app/builders/messages/messenger/message_builder.rb index 97ccfade7..2a2dff043 100644 --- a/app/builders/messages/messenger/message_builder.rb +++ b/app/builders/messages/messenger/message_builder.rb @@ -1,6 +1,7 @@ class Messages::Messenger::MessageBuilder include ::FileTypeHelper + # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity def process_attachment(attachment) # This check handles very rare case if there are multiple files to attach with only one unsupported file return if unsupported_file_type?(attachment['type']) @@ -22,6 +23,7 @@ class Messages::Messenger::MessageBuilder fetch_ig_post_link(attachment_obj) if attachment_obj.file_type == 'ig_post' update_attachment_file_type(attachment_obj) end + # rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity def attach_file(attachment, file_url) SafeFetch.fetch( diff --git a/app/jobs/avatar/avatar_from_url_job.rb b/app/jobs/avatar/avatar_from_url_job.rb index f810e9af8..16da29490 100644 --- a/app/jobs/avatar/avatar_from_url_job.rb +++ b/app/jobs/avatar/avatar_from_url_job.rb @@ -13,31 +13,11 @@ class Avatar::AvatarFromUrlJob < ApplicationJob RATE_LIMIT_WINDOW = 1.minute def perform(avatarable, avatar_url) - return unless avatarable.respond_to?(:avatar) - return unless url_valid?(avatar_url) + return unless syncable_avatar?(avatarable, avatar_url) - return unless should_sync_avatar?(avatarable, avatar_url) - - SafeFetch.fetch( - avatar_url, - max_bytes: MAX_DOWNLOAD_SIZE, - allowed_content_type_prefixes: [], - allowed_content_types: %w[image/jpeg image/png image/gif] - ) do |avatar_file| - raise SafeFetch::FetchError, 'Invalid file' unless valid_file?(avatar_file) - - avatarable.avatar.attach( - io: avatar_file.tempfile, - filename: avatar_file.original_filename, - content_type: avatar_file.content_type - ) - end + fetch_avatar(avatarable, avatar_url) rescue SafeFetch::HttpError => e - if e.message.start_with?('404') - Rails.logger.info "AvatarFromUrlJob: avatar not found at #{avatar_url}" - else - Rails.logger.error "AvatarFromUrlJob error for #{avatar_url}: #{e.class} - #{e.message}" - end + log_http_error(avatar_url, e) rescue SafeFetch::Error => e Rails.logger.error "AvatarFromUrlJob error for #{avatar_url}: #{e.class} - #{e.message}" ensure @@ -46,6 +26,41 @@ class Avatar::AvatarFromUrlJob < ApplicationJob private + def syncable_avatar?(avatarable, avatar_url) + avatarable.respond_to?(:avatar) && + url_valid?(avatar_url) && + should_sync_avatar?(avatarable, avatar_url) + end + + def fetch_avatar(avatarable, avatar_url) + SafeFetch.fetch( + avatar_url, + max_bytes: MAX_DOWNLOAD_SIZE, + allowed_content_type_prefixes: [], + allowed_content_types: %w[image/jpeg image/png image/gif] + ) do |avatar_file| + attach_avatar(avatarable, avatar_file) + end + end + + def attach_avatar(avatarable, avatar_file) + raise SafeFetch::FetchError, 'Invalid file' unless valid_file?(avatar_file) + + avatarable.avatar.attach( + io: avatar_file.tempfile, + filename: avatar_file.original_filename, + content_type: avatar_file.content_type + ) + end + + def log_http_error(avatar_url, error) + if error.message.start_with?('404') + Rails.logger.info "AvatarFromUrlJob: avatar not found at #{avatar_url}" + else + Rails.logger.error "AvatarFromUrlJob error for #{avatar_url}: #{error.class} - #{error.message}" + end + end + def should_sync_avatar?(avatarable, avatar_url) # Only Contacts are rate-limited and hash-gated. return true unless avatarable.is_a?(Contact) diff --git a/app/services/telegram/incoming_message_service.rb b/app/services/telegram/incoming_message_service.rb index 99d57e167..89718a1ac 100644 --- a/app/services/telegram/incoming_message_service.rb +++ b/app/services/telegram/incoming_message_service.rb @@ -1,6 +1,7 @@ # Find the various telegram payload samples here: https://core.telegram.org/bots/webhooks#testing-your-bot-with-updates # https://core.telegram.org/bots/api#available-types +# rubocop:disable Metrics/ClassLength class Telegram::IncomingMessageService include ::FileTypeHelper include ::Telegram::ParamHelpers @@ -144,31 +145,40 @@ class Telegram::IncomingMessageService def attach_files return unless file - file_download_path = inbox.channel.get_telegram_file_path(file[:file_id]) - if file_download_path.blank? - Rails.logger.info "Telegram file download path is blank for #{file[:file_id]} : inbox_id: #{inbox.id}" - return - end + file_download_path = telegram_file_download_path + return unless file_download_path SafeFetch.fetch( file_download_path, allowed_content_type_prefixes: %w[image/ video/ audio/], allowed_content_types: Attachment::ACCEPTABLE_FILE_TYPES ) do |attachment_file| - @message.attachments.new( - account_id: @message.account_id, - file_type: file_content_type, - file: { - io: attachment_file.tempfile, - filename: attachment_file.original_filename, - content_type: attachment_file.content_type - } - ) + build_file_attachment(attachment_file) end rescue SafeFetch::Error => e Rails.logger.info "Error downloading Telegram attachment from #{file_download_path}: #{e.message}: Skipping" end + def telegram_file_download_path + file_download_path = inbox.channel.get_telegram_file_path(file[:file_id]) + return file_download_path if file_download_path.present? + + Rails.logger.info "Telegram file download path is blank for #{file[:file_id]} : inbox_id: #{inbox.id}" + nil + end + + def build_file_attachment(attachment_file) + @message.attachments.new( + account_id: @message.account_id, + file_type: file_content_type, + file: { + io: attachment_file.tempfile, + filename: attachment_file.original_filename, + content_type: attachment_file.content_type + } + ) + end + def attach_location return unless location @@ -228,3 +238,4 @@ class Telegram::IncomingMessageService params[:message] = params[:business_message] if params[:business_message] && !params[:message] end end +# rubocop:enable Metrics/ClassLength diff --git a/app/services/twilio/incoming_message_service.rb b/app/services/twilio/incoming_message_service.rb index 72b9ee1e8..7981558b2 100644 --- a/app/services/twilio/incoming_message_service.rb +++ b/app/services/twilio/incoming_message_service.rb @@ -1,3 +1,4 @@ +# rubocop:disable Metrics/ClassLength class Twilio::IncomingMessageService include ::FileTypeHelper @@ -218,3 +219,4 @@ class Twilio::IncomingMessageService @contact.name == phone_number || @contact.name == formatted_phone_number end end +# rubocop:enable Metrics/ClassLength diff --git a/lib/safe_fetch.rb b/lib/safe_fetch.rb index 6cad5ef7c..e82f63d76 100644 --- a/lib/safe_fetch.rb +++ b/lib/safe_fetch.rb @@ -1,5 +1,6 @@ require 'ssrf_filter' +# rubocop:disable Metrics/ModuleLength module SafeFetch DEFAULT_ALLOWED_CONTENT_TYPE_PREFIXES = %w[image/ video/].freeze DEFAULT_ALLOWED_CONTENT_TYPES = [].freeze @@ -27,6 +28,7 @@ module SafeFetch class UnsupportedContentTypeError < Error; end class UnsupportedMethodError < Error; end + # rubocop:disable Metrics/MethodLength, Metrics/ParameterLists def self.fetch(url, method: :get, body: nil, @@ -72,10 +74,12 @@ module SafeFetch ensure tempfile&.close! end + # rubocop:enable Metrics/MethodLength, Metrics/ParameterLists class << self private + # rubocop:disable Metrics/MethodLength, Metrics/ParameterLists def stream_to_tempfile(url, method, body, tempfile, max_bytes, headers, http_basic_authentication, allowed_content_type_prefixes, allowed_content_types, validate_content_type) response = nil @@ -109,6 +113,7 @@ module SafeFetch response end + # rubocop:enable Metrics/MethodLength, Metrics/ParameterLists def filename_for(uri) File.basename(uri.path).presence || "download-#{Time.current.to_i}-#{SecureRandom.hex(4)}" @@ -127,7 +132,7 @@ module SafeFetch end def request_proc(http_basic_authentication) - return unless http_basic_authentication.present? + return if http_basic_authentication.blank? proc { |request| request.basic_auth(*http_basic_authentication) } end @@ -163,3 +168,4 @@ module SafeFetch end end end +# rubocop:enable Metrics/ModuleLength