1
0
مراية لـ https://github.com/postalserver/postal.git تم المزامنة 2026-03-03 14:24:06 +00:00
الملفات
postal/lib/postal/message_parser.rb
Adam Cooke 2834e2c379 fix: update raw headers after changing messages to during parsing
Previously, headers were not updated but the mail gem parsing may decide
to change the transfer encoding. If it does this, the body is updated to
the new format but the header is not, this results in a mismatch when the
message it sent onwards and the client being unable render the message.

The specific situation identified was changing an HTML-only e-mail from
quoted-printable to 7bit.

closes #2816
2024-02-23 22:16:36 +00:00

157 أسطر
4.0 KiB
Ruby

# frozen_string_literal: true
module Postal
class MessageParser
URL_REGEX = /(?<url>(?<protocol>https?):\/\/(?<domain>[A-Za-z0-9\-.:]+)(?<path>\/[A-Za-z0-9.\/+?&\-_%=~:;()\[\]#]*)?+)/
def initialize(message)
@message = message
@actioned = false
@tracked_links = 0
@tracked_images = 0
@domain = @message.server.track_domains.where(domain: @message.domain, dns_status: "OK").first
return unless @domain
@parsed_output = generate.split("\r\n\r\n", 2)
end
attr_reader :tracked_links
attr_reader :tracked_images
def actioned?
@actioned || @tracked_links.positive? || @tracked_images.positive?
end
def new_body
@parsed_output[1]
end
def new_headers
@parsed_output[0]
end
private
def generate
@mail = Mail.new(@message.raw_message)
@original_message = @message.raw_message
if @mail.parts.empty?
if @mail.mime_type
if @mail.mime_type =~ /text\/plain/
@mail.body = parse(@mail.body.decoded.dup, :text)
@mail.content_transfer_encoding = nil
@mail.charset = "UTF-8"
elsif @mail.mime_type =~ /text\/html/
@mail.body = parse(@mail.body.decoded.dup, :html)
@mail.content_transfer_encoding = nil
@mail.charset = "UTF-8"
end
end
else
parse_parts(@mail.parts)
end
@mail.to_s
rescue StandardError => e
raise if Rails.env.development?
if defined?(Sentry)
Sentry.capture_exception(e)
end
@actioned = false
@tracked_links = 0
@tracked_images = 0
@original_message
end
def parse_parts(parts)
parts.each do |part|
case part.content_type
when /text\/html/
part.body = parse(part.body.decoded.dup, :html)
part.content_transfer_encoding = nil
part.charset = "UTF-8"
when /text\/plain/
part.body = parse(part.body.decoded.dup, :text)
part.content_transfer_encoding = nil
part.charset = "UTF-8"
when /multipart\/(alternative|related)/
unless part.parts.empty?
parse_parts(part.parts)
end
end
end
end
def parse(part, type = nil)
if @domain.track_clicks?
part = insert_links(part, type)
end
if @domain.track_loads? && type == :html
part = insert_tracking_image(part)
end
part
end
def insert_links(part, type = nil)
if type == :text
part.gsub!(/(#{URL_REGEX})(?=\s|$)/) do
if track_domain?($~[:domain])
@tracked_links += 1
url = $~[:url]
while url =~ /[^\w]$/
theend = url.size - 2
url = url[0..theend]
end
token = @message.create_link(url)
"#{domain}/#{@message.server.token}/#{token}"
else
::Regexp.last_match(0)
end
end
end
if type == :html
part.gsub!(/href=(['"])(#{URL_REGEX})['"]/) do
if track_domain?($~[:domain])
@tracked_links += 1
token = @message.create_link($~[:url])
"href='#{domain}/#{@message.server.token}/#{token}'"
else
::Regexp.last_match(0)
end
end
end
part.gsub!(/(https?)\+notrack:\/\//) do
@actioned = true
"#{::Regexp.last_match(1)}://"
end
part
end
def insert_tracking_image(part)
@tracked_images += 1
container = "<p class='ampimg' style='display:none;visibility:none;margin:0;padding:0;line-height:0;'><img src='#{domain}/img/#{@message.server.token}/#{@message.token}' alt=''></p>"
if part =~ /<\/body>/
part.gsub("</body>", "#{container}</body>")
else
part + container
end
end
def domain
"#{@domain.use_ssl? ? 'https' : 'http'}://#{@domain.full_name}"
end
def track_domain?(domain)
!@domain.excluded_click_domains_array.include?(domain)
end
end
end