class Mail::Message

TODO: Test if subclassing breaks integration of mail-gpg.

Attributes

dynamic_pseudoheaders[W]
list[RW]
original_message[RW]
protected_headers_subject[RW]
raise_encryption_errors[RW]
recipient[RW]

Public Class Methods

all_to_message_part(input) click to toggle source
# File lib/schleuder/mail/message.rb, line 192
def self.all_to_message_part(input)
  Array(input).map do |thing|
    case thing
    when Mail::Part
      thing
    when String, StandardError
      Mail::Part.new do
        body thing.to_s
      end
    else
      raise "Don't know how to handle input: #{thing.inspect}"
    end
  end
end

Public Instance Methods

add_list_headers(list) click to toggle source
# File lib/schleuder/mail/message.rb, line 347
def add_list_headers(list)
  if list.include_autocrypt_header
    # Inject whitespaces, to let Mail break the string at these points
    # leading to correct wrapping.
    keydata = list.key_minimal_base64_encoded.gsub(/(.{78})/, '\1 ')
    
    self['Autocrypt'] = "addr=#{list.email}; prefer-encrypt=mutual; keydata=#{keydata}"
  end

  if list.include_list_headers
    self['List-Id'] = "<#{list.email.gsub('@', '.')}>"
    self['List-Owner'] = "<mailto:#{list.owner_address}> (Use list's public key)"
    self['List-Help'] = '<https://schleuder.org/>'

    postmsg = if list.receive_admin_only
                'NO (Admins only)'
              elsif list.receive_authenticated_only
                "<mailto:#{list.email}> (Subscribers only)"
              else
                "<mailto:#{list.email}>"
              end

    self['List-Post'] = postmsg
  end
end
add_msgids(list, orig) click to toggle source
# File lib/schleuder/mail/message.rb, line 336
def add_msgids(list, orig)
  if list.keep_msgid
    # Don't use `orig['in-reply-to']` here, because that sometimes fails to
    # parse the original value and then returns it without the
    # angle-brackets.
    self.message_id = clutch_anglebrackets(orig.message_id)
    self.in_reply_to = clutch_anglebrackets(orig.in_reply_to)
    self.references = clutch_anglebrackets(orig.references)
  end
end
add_openpgp_headers(list) click to toggle source
# File lib/schleuder/mail/message.rb, line 373
def add_openpgp_headers(list)
  if list.include_openpgp_header

    if list.openpgp_header_preference == 'none'
      pref = ''
    else
      pref = "preference=#{list.openpgp_header_preference}"

      # TODO: simplify.
      pref << ' ('
      if list.receive_admin_only
        pref << 'Only encrypted and signed emails by list-admins are accepted'
      elsif ! list.receive_authenticated_only
        if list.receive_encrypted_only && list.receive_signed_only
          pref << 'Only encrypted and signed emails are accepted'
        elsif list.receive_encrypted_only && ! list.receive_signed_only
          pref << 'Only encrypted emails are accepted'
        elsif ! list.receive_encrypted_only && list.receive_signed_only
          pref << 'Only signed emails are accepted'
        else
          pref << 'All kind of emails are accepted'
        end
      elsif list.receive_authenticated_only
        if list.receive_encrypted_only
          pref << 'Only encrypted and signed emails by subscribers are accepted'
        else
          pref << 'Only signed emails by subscribers are accepted'
        end
      else
        pref << 'All kind of emails are accepted'
      end
      pref << ')'
    end

    fingerprint = list.fingerprint
    comment = "(Send an email to #{list.sendkey_address} to receive the public-key)"

    self['OpenPGP'] = "id=0x#{fingerprint} #{comment}; #{pref}"
  end
end
add_pseudoheader(string_or_key, value=nil) click to toggle source
# File lib/schleuder/mail/message.rb, line 270
def add_pseudoheader(string_or_key, value=nil)
  dynamic_pseudoheaders << make_pseudoheader(string_or_key, value)
end
add_subject_prefix!() click to toggle source
# File lib/schleuder/mail/message.rb, line 258
def add_subject_prefix!
  _add_subject_prefix(nil)
end
add_subject_prefix_in!() click to toggle source
# File lib/schleuder/mail/message.rb, line 262
def add_subject_prefix_in!
  _add_subject_prefix(:in)
end
add_subject_prefix_out!() click to toggle source
# File lib/schleuder/mail/message.rb, line 266
def add_subject_prefix_out!
  _add_subject_prefix(:out)
end
attach_list_key!(list) click to toggle source
# File lib/schleuder/mail/message.rb, line 439
def attach_list_key!(list)
  filename = "#{list.email}.asc"
  self.add_file({
    filename: filename,
    content: list.export_key
  })
  self.attachments[filename].content_type = 'application/pgp-keys'
  self.attachments[filename].content_description = 'OpenPGP public key'
  true
end
automated_message?() click to toggle source
# File lib/schleuder/mail/message.rb, line 219
def automated_message?
  @recipient.match(/-bounce@/).present? ||
      # Empty Return-Path
      self.return_path.to_s == '<>' ||
      bounced?
end
bounced?() click to toggle source
# File lib/schleuder/mail/message.rb, line 226
def bounced?
  @bounced ||= bounce_detected? || (error_status != 'unknown')
end
clean_copy(with_pseudoheaders=false) click to toggle source
# File lib/schleuder/mail/message.rb, line 73
def clean_copy(with_pseudoheaders=false)
  clean = Mail.new
  clean.list = self.list
  clean.gpg self.list.gpg_sign_options
  clean.from = list.email
  clean.subject = self.subject
  clean.protected_headers_subject = self.protected_headers_subject

  clean.add_msgids(list, self)
  clean.add_list_headers(list)
  clean.add_openpgp_headers(list)

  if with_pseudoheaders
    new_part = Mail::Part.new
    new_part.body = self.pseudoheaders(list)
    clean.add_part new_part
  end

  if self.protected_headers_subject.present?
    new_part = Mail::Part.new
    new_part.content_type = 'text/rfc822-headers; protected-headers=v1'
    new_part.body = "Subject: #{self.subject}\n"
    clean.add_part new_part
  end

  # Attach body or mime-parts in a new wrapper-part, to preserve the
  # original mime-structure.
  # We can't use self.to_s here — that includes all the headers we *don't*
  # want to copy.
  wrapper_part = Mail::Part.new
  # Copy headers to are relevant for the mime-structure.
  wrapper_part.content_type = self.content_type
  wrapper_part.content_transfer_encoding = self.content_transfer_encoding if self.content_transfer_encoding
  wrapper_part.content_disposition = self.content_disposition if self.content_disposition
  wrapper_part.content_description = self.content_description if self.content_description
  # Copy contents.
  if self.multipart?
    self.parts.each do |part|
      wrapper_part.add_part(part)
    end
  else
    # We copied the content-headers, so we need to copy the body encoded.
    # Otherwise the content might become unlegible.
    wrapper_part.body = self.body.encoded
  end
  clean.add_part(wrapper_part)

  clean
end
decrypt(options = {}) click to toggle source

returns the decrypted mail object.

pass verify: true to verify signatures as well. The gpgme verification result will be available via decrypted_mail.verify_result

# File lib/schleuder/mail/message.rb, line 499
def decrypt(options = {})
  Mail::Gpg.decrypt(self, options)
end
dynamic_pseudoheaders() click to toggle source
# File lib/schleuder/mail/message.rb, line 280
def dynamic_pseudoheaders
  @dynamic_pseudoheaders ||= []
end
empty?() click to toggle source
# File lib/schleuder/mail/message.rb, line 414
def empty?
  if self.multipart?
    if self.parts.empty?
      return true
    else
      # Test parts recursively. E.g. Thunderbird with activated
      # memoryhole-headers send nested parts that might still be empty.
      return parts.inject(true) { |result, part| result && part.empty? }
    end
  else
    return self.body.empty?
  end
end
encrypted?() click to toggle source

true if this mail is encrypted

# File lib/schleuder/mail/message.rb, line 491
def encrypted?
  Mail::Gpg.encrypted?(self)
end
encryption_state() click to toggle source
# File lib/schleuder/mail/message.rb, line 302
def encryption_state
  if was_encrypted?
    encryption_state = I18n.t('encryption_states.encrypted')
  else
    encryption_state = I18n.t('encryption_states.unencrypted')
  end
  encryption_state
end
error_status() click to toggle source
# File lib/schleuder/mail/message.rb, line 230
def error_status
  @error_status ||= detect_error_code
end
first_plaintext_part(part=nil) click to toggle source
# File lib/schleuder/mail/message.rb, line 428
def first_plaintext_part(part=nil)
  part ||= self
  if part.multipart?
    first_plaintext_part(part.parts.first)
  elsif part.mime_type == 'text/plain'
    part
  else
    nil
  end
end
gpg(options = nil) click to toggle source

turn on gpg encryption / set gpg options.

options are:

encrypt: encrypt the message. defaults to true sign: also sign the message. false by default sign_as: UIDs to sign the message with

See Mail::Gpg methods encrypt and sign for more possible options

mail.gpg encrypt: true mail.gpg encrypt: true, sign: true mail.gpg encrypt: true, sign_as: “other_address@host.com”

sign-only mode is also supported: mail.gpg sign: true mail.gpg sign_as: ‘jane@doe.com’

To turn off gpg encryption use: mail.gpg false

# File lib/schleuder/mail/message.rb, line 472
def gpg(options = nil)
  case options
  when nil
    @gpg
  when false
    @gpg = nil
    if Mail::Gpg::DeliveryHandler == delivery_handler
      self.delivery_handler = nil
    end
    nil
  else
    self.raise_encryption_errors = true if raise_encryption_errors.nil?
    @gpg = options
    self.delivery_handler ||= Mail::Gpg::DeliveryHandler
    nil
  end
end
keywords() click to toggle source
# File lib/schleuder/mail/message.rb, line 234
def keywords
  return @keywords if @keywords

  part = first_plaintext_part
  if part.blank?
    return []
  end

  @keywords, lines = extract_keywords(part.decoded.lines)
  new_body = lines.join

  # Work around problems with re-encoding the body. If we delete the
  # content-transfer-encoding prior to re-assigning the body, and let Mail
  # decide itself how to encode, it works. If we don't, some
  # character-sequences are not properly re-encoded.
  part.content_transfer_encoding = nil

  # Set the right charset on the now parsed body
  part.charset = new_body.encoding.to_s
  part.body = new_body

  @keywords
end
make_pseudoheader(key, value) click to toggle source
# File lib/schleuder/mail/message.rb, line 274
def make_pseudoheader(key, value)
  output = "#{key.to_s.camelize}: #{value.to_s}"
  # wrap lines after 76 with 2 indents
  output.gsub(/(.{1,76})( +|$)\n?/, "  \\1\n").chomp.lstrip
end
prepend_part(part) click to toggle source
# File lib/schleuder/mail/message.rb, line 123
def prepend_part(part)
  self.add_part(part)
  self.parts.unshift(parts.delete_at(parts.size-1))
end
pseudoheaders(list) click to toggle source
# File lib/schleuder/mail/message.rb, line 331
def pseudoheaders(list)
  separator = '------------------------------------------------------------------------------'
  (standard_pseudoheaders(list) + dynamic_pseudoheaders).flatten.join("\n") + "\n" + separator + "\n"
end
repeat_validation!() click to toggle source
# File lib/schleuder/mail/message.rb, line 521
def repeat_validation!
  new = self.original_message.dup.setup
  self.verify_result = new.verify_result
  @signatures = new.signatures
  dynamic_pseudoheaders << new.dynamic_pseudoheaders
end
reply_to_signer(output) click to toggle source
# File lib/schleuder/mail/message.rb, line 184
def reply_to_signer(output)
  reply = self.reply
  self.class.all_to_message_part(output).each do |part|
    reply.add_part(part)
  end
  self.signer.send_mail(reply)
end
request?() click to toggle source
# File lib/schleuder/mail/message.rb, line 215
def request?
  @recipient.match(/-request@/)
end
sendkey_request?() click to toggle source
# File lib/schleuder/mail/message.rb, line 207
def sendkey_request?
  @recipient.match(/-sendkey@/)
end
setup() click to toggle source

TODO: This should be in initialize(), but I couldn’t understand the strange errors about wrong number of arguments when overriding Message#initialize.

# File lib/schleuder/mail/message.rb, line 31
def setup
  if self.encrypted?
    # Specify 'loopback'-pinentry-mode to ensure that gnupg never-ever
    # tries to interactively ask for a passphrase.
    new = self.decrypt(verify: true, pinentry_mode: GPGME::PINENTRY_MODE_LOOPBACK)
    # Test if there's a signed multipart inside the ciphertext
    # ("encapsulated" format of pgp/mime).
    if encapsulated_signed?(new)
      new = new.verify
    end
  elsif self.signed?
    new = self.verify
  else
    new = self
  end

  new.list = self.list
  new.recipient = self.recipient

  new.gpg list.gpg_sign_options
  new.original_message = self.dup.freeze
  # Trigger method early to save the information. Later some information
  # might be gone (e.g. request-keywords that delete subscriptions or
  # keys).
  new.signer
  new.dynamic_pseudoheaders = self.dynamic_pseudoheaders.dup

  # Store previously protected subject for later access.
  # mail-gpg pulls headers from the decrypted mime parts "up" into the main
  # headers, which reveals protected subjects.
  if self.subject != new.subject
    new.protected_headers_subject = self.subject.dup
  end

  # Delete the protected headers which might leak information.
  if new.parts.first && new.parts.first.content_type == 'text/rfc822-headers; protected-headers=v1'
    new.parts.shift
  end

  new
end
signature() click to toggle source
# File lib/schleuder/mail/message.rb, line 141
def signature
  case signatures.size
  when 0
    if multipart?
      signature_multipart_inline
    else
      nil
    end
  when 1
    signatures.first
  else
    raise 'Multiple signatures found! Cannot handle!'
  end
end
signature_state() click to toggle source
# File lib/schleuder/mail/message.rb, line 284
def signature_state
  # Careful to add information about the incoming signature. GPGME
  # throws exceptions if it doesn't know the key.
  if self.signature.present?
    # Some versions of gpgme return nil if the key is unknown, so we check
    # for that manually and provide our own fallback. (Calling
    # `signature.key` results in an EOFError in that case.)
    if signing_key.present?
      signature_state = signature.to_s
    else
      signature_state = I18n.t('signature_states.unknown', fingerprint: self.signature.fingerprint)
    end
  else
    signature_state = I18n.t('signature_states.unsigned')
  end
  signature_state
end
signed?() click to toggle source

true if this mail is signed (but not encrypted)

# File lib/schleuder/mail/message.rb, line 504
def signed?
  Mail::Gpg.signed?(self)
end
signer() click to toggle source
# File lib/schleuder/mail/message.rb, line 160
def signer
  @signer ||= begin
    if signing_key.present?
      # Look for a subscription that matches the sending address, in case
      # there're multiple subscriptions for the same key. As a fallback use
      # the first subscription found.
      sender_email = self.from.to_s.downcase
      subscriptions = list.subscriptions.where(fingerprint: signing_key.fingerprint)
      subscriptions.where(email: sender_email).first || subscriptions.first
    end
  end
end
signing_key() click to toggle source

The fingerprint of the signature might be the one of a sub-key, but the subscription-assigned fingerprints are (should be) the ones of the primary keys, so we need to look up the key.

# File lib/schleuder/mail/message.rb, line 176
def signing_key
  @signing_key ||= begin
    if signature.present?
      list.keys(signature.fpr).first
    end
  end
end
standard_pseudoheaders(list) click to toggle source
# File lib/schleuder/mail/message.rb, line 311
def standard_pseudoheaders(list)
  if @standard_pseudoheaders.present?
    return @standard_pseudoheaders
  else
    @standard_pseudoheaders = []
  end

  Array(list.headers_to_meta).each do |field|
    value = case field.to_s
      when 'sig' then signature_state
      when 'enc' then encryption_state
      else self.header[field.to_s]
    end
    @standard_pseudoheaders << make_pseudoheader(field.to_s, value)
  end


  @standard_pseudoheaders
end
to_owner?() click to toggle source
# File lib/schleuder/mail/message.rb, line 211
def to_owner?
  @recipient.match(/-owner@/)
end
verify(options = {}) click to toggle source

verify signatures. returns a new mail object with signatures removed and populated verify_result.

verified = signed_mail.verify() verified.signature_valid? signers = mail.signatures.map{|sig| sig.from}

use import_missing_keys: true in order to try to fetch and import unknown keys for signature validation

# File lib/schleuder/mail/message.rb, line 517
def verify(options = {})
  Mail::Gpg.verify(self, options)
end
was_encrypted?() click to toggle source
# File lib/schleuder/mail/message.rb, line 137
def was_encrypted?
  Mail::Gpg.encrypted?(original_message)
end
was_validly_signed?() click to toggle source
# File lib/schleuder/mail/message.rb, line 156
def was_validly_signed?
  signature.present? && signature.valid? && signer.present?
end

Private Instance Methods

_add_subject_prefix(suffix) click to toggle source
# File lib/schleuder/mail/message.rb, line 587
def _add_subject_prefix(suffix)
  attrib = 'subject_prefix'
  if suffix
    attrib << "_#{suffix}"
  end
  if ! self.list.respond_to?(attrib)
    return false
  end

  string = self.list.send(attrib).to_s.strip
  if ! string.empty?
    prefix = "#{string} "
    # Only insert prefix if it's not present already.
    if self.subject.nil?
      self.subject = string
    elsif ! self.subject.include?(prefix)
      self.subject = "#{prefix}#{self.subject}"
    end
  end
end
bounce_detected?() click to toggle source
# File lib/schleuder/mail/message.rb, line 665
def bounce_detected?
  # Detects bounces from different parts of the email without error status codes
  # from: https://github.com/mailtop/bounce_email
  return true if self.subject.to_s.match(/(returned|undelivered) mail|mail delivery( failed)?|(delivery )(status notification|failure)|failure notice|undeliver(able|ed)( mail)?|return(ing message|ed) to sender/i)
  return true if self.subject.to_s.match(/auto.*reply|vacation|vocation|(out|away).*office|on holiday|abwesenheits|autorespond|Automatische|eingangsbestätigung/i)
  return true if self['precedence'].to_s.match(/auto.*(reply|responder|antwort)/i)
  return true if self.from.to_s.match(/^(MAILER-DAEMON|POSTMASTER)@/i)
  false
end
clutch_anglebrackets(input) click to toggle source
# File lib/schleuder/mail/message.rb, line 625
def clutch_anglebrackets(input)
  Array(input).map do |string|
    if string.first == '<'
      string
    else
      "<#{string}>"
    end
  end.join(' ')
end
detect_bounce_status_code_from_text(text) click to toggle source
# File lib/schleuder/mail/message.rb, line 675
def detect_bounce_status_code_from_text(text)
  # Parses a text and uses pattern matching to determines its error status (RFC 3463)
  # from: https://github.com/mailtop/bounce_email
  return '5.0.0' if text.match(/Status: 5\.0\.0/i)
  return '5.1.1' if text.match(/no such (address|user)|Recipient address rejected|User unknown|does not like recipient|The recipient was unavailable to take delivery of the message|Sorry, no mailbox here by that name|invalid address|unknown user|unknown local part|user not found|invalid recipient|failed after I sent the message|did not reach the following recipient|nicht zugestellt werden|o pode ser entregue para um ou mais/i)
  return '5.1.2' if text.match(/unrouteable mail domain|Esta casilla ha expirado por falta de uso|I couldn't find any host named/i)
  if text.match(/mailbox is full|Mailbox quota (usage|disk) exceeded|quota exceeded|Over quota|User mailbox exceeds allowed size|Message rejected\. Not enough storage space|user has exhausted allowed storage space|too many messages on the server|mailbox is over quota|mailbox exceeds allowed size|excedeu a quota/i)
    return '5.2.2' if text.match(/This is a permanent error||(Status: |)5\.2\.2/i)
    return '4.2.2'
  end
  return '5.1.0' if text.match(/Address rejected/)
  return '4.1.2' if text.match(/I couldn't find any host by that name/)
  return '4.2.0' if text.match(/not yet been delivered/i)
  return '5.1.1' if text.match(/mailbox unavailable|No such mailbox|RecipientNotFound|not found by SMTP address lookup|Status: 5\.1\.1/i)
  return '5.2.3' if text.match(/Status: 5\.2\.3/i) # Too messages in folder
  return '5.4.0' if text.match(/Status: 5\.4\.0/i) # too many hops
  return '5.4.4' if text.match(/Unrouteable address/i)
  return '4.4.7' if text.match(/retry timeout exceeded/i)
  return '5.2.0' if text.match(/The account or domain may not exist, they may be blacklisted, or missing the proper dns entries./i)
  return '5.5.4' if text.match(/554 TRANSACTION FAILED/i)
  return '4.4.1' if text.match(/Status: 4.4.1|delivery temporarily suspended|wasn't able to establish an SMTP connection/i)
  return '5.5.0' if text.match(/550 OU-002|Mail rejected by Windows Live Hotmail for policy reasons/i)
  return '5.1.2' if text.match(/PERM_FAILURE: DNS Error: Domain name not found/i)
  return '4.2.0' if text.match(/Delivery attempts will continue to be made for/i)
  return '5.5.4' if text.match(/554 delivery error:/i)
  return '5.1.1' if text.match(/550-5.1.1|This Gmail user does not exist/i)
  return '5.7.1' if text.match(/5.7.1 Your message.*?was blocked by ROTA DNSBL/i) # AA added
  return '5.7.2' if text.match(/not have permission to post messages to the group/i)
  return '5.3.2' if text.match(/Technical details of permanent failure|Too many bad recipients/i) && (text.match(/The recipient server did not accept our requests to connect/i) || text.match(/Connection was dropped by remote host/i) || text.match(/Could not initiate SMTP conversation/i)) # AA added
  return '4.3.2' if text.match(/Technical details of temporary failure/i) && (text.match(/The recipient server did not accept our requests to connect/i) || text.match(/Connection was dropped by remote host/i) || text.match(/Could not initiate SMTP conversation/i)) # AA added
  return '5.0.0' if text.match(/Delivery to the following recipient failed permanently/i) # AA added
  return '5.2.3' if text.match(/account closed|account has been disabled or discontinued|mailbox not found|prohibited by administrator|access denied|account does not exist/i)
end
detect_error_code() click to toggle source
# File lib/schleuder/mail/message.rb, line 635
def detect_error_code
  # Detects the error code of an email with different heuristics
  # from: https://github.com/mailtop/bounce_email
  
  # Custom status codes
  unicode_subject = self.subject.to_s
  unicode_subject = unicode_subject.encode('utf-8') if unicode_subject.respond_to?(:encode)
 
  return '97' if unicode_subject.match(/delayed/i)
  return '98' if unicode_subject.match(/(unzulässiger|unerlaubter) anhang/i)
  return '99' if unicode_subject.match(/auto.*reply|férias|ferias|Estarei ausente|estou ausente|vacation|vocation|(out|away).*office|on holiday|abwesenheits|autorespond|Automatische|eingangsbestätigung/i)

  # Feedback-Type: abuse
  return '96' if self.to_s.match(/Feedback-Type: abuse/i)

  if self.parts[1]
    match_parts = self.parts[1].body.match(/(Status:.|550 |#)([245]\.[0-9]{1,3}\.[0-9]{1,3})/)
    code = match_parts[2] if match_parts
    return code if code
  end

  # Now try getting it from correct part of tmail
  code = detect_bounce_status_code_from_text(self.body)
  return code if code

  # OK getting desperate so try getting code from entire email
  code = detect_bounce_status_code_from_text(self.to_s)
  code || 'unknown'
end
encapsulated_signed?(mail) click to toggle source

mail.signed? throws an error if it finds pgp boundaries, so we must use the Mail::Gpg methods.

# File lib/schleuder/mail/message.rb, line 562
def encapsulated_signed?(mail)
  (mail.verify_result.nil? || mail.verify_result.signatures.empty?) && \
    (Mail::Gpg.signed_mime?(mail) || Mail::Gpg.signed_inline?(mail))
end
extract_keywords(content_lines) click to toggle source
# File lib/schleuder/mail/message.rb, line 531
def extract_keywords(content_lines)
  keywords = []
  in_keyword_block = false
  content_lines.each_with_index do |line, i|
    if line.blank?
      # Swallow the line: before the actual content or keywords block begins we want to drop blank lines.
      content_lines[i] = nil
      # Stop interpreting the following line as argument to the previous keyword.
      in_keyword_block = false
    elsif match = line.match(/^x-([^:\s]*)[:\s]*(.*)/i)
      keyword = match[1].strip.downcase
      arguments = match[2].to_s.strip.downcase.split(/[,; ]{1,}/)
      keywords << [keyword, arguments]
      in_keyword_block = true
      # Set this line to nil to have it stripped from the message.
      content_lines[i] = nil
    elsif in_keyword_block == true
      # Interpret line as arguments to the previous keyword.
      keywords[-1][-1] += line.downcase.strip.split(/[,; ]{1,}/)
      content_lines[i] = nil
    else
      # Any other line stops the keyword parsing.
      break
    end
  end
  [keywords, content_lines.compact]
end
signature_multipart_inline() click to toggle source

Looking for signatures in each part. They are not aggregated into the main part. We only return the signature if all parts are validly signed by the same key.

# File lib/schleuder/mail/message.rb, line 610
def signature_multipart_inline
  fingerprints = parts.map do |part|
    if part.signature_valid?
      part.signature.fpr
    else
      nil
    end
  end
  if fingerprints.uniq.size == 1
    parts.first.signature
  else
    nil
  end
end
wrapped_single_text_part?() click to toggle source
# File lib/schleuder/mail/message.rb, line 580
def wrapped_single_text_part?
  parts.size == 1 && 
    parts.first.mime_type == 'multipart/mixed' && 
    parts.first.parts.size == 1 && 
    parts.first.parts.first.mime_type == 'text/plain'
end