inline_images.rb 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114
  1. # Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/
  2. class HtmlSanitizer
  3. module Scrubber
  4. class InlineImages < Base
  5. attr_reader :attachments_inline, :prefix
  6. def initialize(prefix = SecureRandom.uuid) # rubocop:disable Lint/MissingSuper
  7. @direction = :top_down
  8. @attachments_inline = []
  9. @prefix = prefix
  10. end
  11. def scrub(node)
  12. return CONTINUE if node.name != 'img'
  13. return CONTINUE if node['src'].blank?
  14. case node['src']
  15. when %r{^(data:image/(jpeg|png);base64,.+?)$}i
  16. process_inline_image(node, $1)
  17. when %r{^(?:/api/v1/attachments/)(\d+)$}
  18. process_uploaded_image(node, $1)
  19. when %r{users/image/(.+)}
  20. process_user_avatar_image(node, $1)
  21. when %r{^(?:blob:)}
  22. node.remove
  23. end
  24. STOP
  25. end
  26. private
  27. def inline_image_data(src)
  28. return if src.blank?
  29. matchdata = src.match %r{^(data:image/(jpeg|png);base64,.+?)$}i
  30. return if !matchdata
  31. matchdata[0]
  32. end
  33. def process_inline_image(node, data)
  34. cid = generate_cid
  35. attachment = parse_inline_image(data, cid)
  36. @attachments_inline.push attachment
  37. node['src'] = "cid:#{cid}"
  38. end
  39. def process_uploaded_image(node, attachment_id)
  40. cid = generate_cid
  41. attachment = Store.find(attachment_id)
  42. @attachments_inline.push attachment(attachment.content, attachment.filename, cid, mime_type: attachment.preferences['Mime-Type'], content_type: attachment.preferences['Content-Type'])
  43. node['src'] = "cid:#{cid}"
  44. end
  45. def process_user_avatar_image(node, image_hash)
  46. return if !node['data-user-avatar']
  47. return if image_hash.blank?
  48. cid = generate_cid
  49. file = avatar_file(image_hash)
  50. return if file.nil?
  51. @attachments_inline.push attachment(file.content, file.filename, cid, mime_type: file.preferences['Mime-Type'], content_type: file.preferences['Content-Type'])
  52. node['src'] = "cid:#{cid}"
  53. end
  54. def parse_inline_image(data, cid)
  55. file_attributes = ImageHelper.data_url_attributes(data)
  56. filename = "image#{@attachments_inline.length + 1}.#{file_attributes[:file_extention]}"
  57. attachment(
  58. file_attributes[:content],
  59. filename,
  60. cid,
  61. mime_type: file_attributes[:mime_type],
  62. )
  63. end
  64. def attachment(content, filename, cid, mime_type: nil, content_type: nil)
  65. {
  66. data: content,
  67. filename: filename,
  68. preferences: {
  69. 'Content-Type' => content_type || mime_type,
  70. 'Mime-Type' => mime_type || content_type,
  71. 'Content-ID' => cid,
  72. 'Content-Disposition' => 'inline',
  73. }
  74. }
  75. end
  76. def generate_cid
  77. "#{prefix}.#{SecureRandom.uuid}@#{fqdn}"
  78. end
  79. def fqdn
  80. @fqdn ||= Setting.get('fqdn')
  81. end
  82. def avatar_file(image_hash)
  83. Avatar.get_by_hash(image_hash)
  84. rescue
  85. nil
  86. end
  87. end
  88. end
  89. end