Class RMail::Header
In: lib/rmail/header.rb
Parent: Object

Overview

The RMail::Header class supports the creation and manipulation of RFC2822 mail headers.

A mail header is a little bit like a Hash. The fields are keyed by a string field name. It is also a little bit like an Array, since the fields are in a specific order. This class provides many of the methods of both the Hash and Array class. It also includes the Enumerable module.

Terminology

header:The entire header. Each RMail::Header object is one mail header.
field:An element of the header. Fields have a name and a value. For example, the field "Subject: Hi Mom!" has a name of "Subject" and a value of "Hi Mom!"
name:A name of a field. For example: "Subject" or "From".
value:The value of a field.

Conventions

The header’s fields are stored in a particular order. Methods such as each process the headers in this order.

When field names or values are added to the object they are frozen. This helps prevent accidental modification to what is stored in the object.

Methods

==   []   []=   add   add_message_id   add_raw   address_list_assign   address_list_fetch   bcc   bcc=   cc   cc=   clear   clone   content_type   date   date=   delete   delete_at   delete_if   dup   each   each_key   each_name   each_pair   each_value   empty?   fetch   fetch_all   field?   from   from=   has_key?   include?   key?   keys   length   match   match?   mbox_from   mbox_from=   media_type   member?   message_id   names   new   param   params   recipients   replace   reply_to   reply_to=   select   set   set_boundary   size   subject   subject=   subtype   to   to=   to_a   to_s   to_string  

Included Modules

Enumerable

Constants

MESSAGE_ID_MAXRAND = 0x7fffffff
BASE36 = "0123456789abcdefghijklmnopqrstuvwxyz"
PARAM_SCAN_RE = %r{ ; | [^;"]*"(?:|.*?(?:[^\\]|\\\\))"\s* # fix fontification " | [^;]+ }x
NAME_VALUE_SCAN_RE = %r{ = | [^="]*"(?:.*?(?:[^\\]|\\\\))" # fix fontification "\s* | [^=]+ }x

Attributes

fields  [RW] 

Public Class methods

Creates a new empty header object.

[Source]

# File lib/rmail/header.rb, line 131
    def initialize()
      clear()
    end

Public Instance methods

Returns true if the two objects have the same number of fields, in the same order, with the same values.

[Source]

# File lib/rmail/header.rb, line 408
    def ==(other)
      return other.kind_of?(self.class) &&
        @fields == other.fields &&
        @mbox_from == other.mbox_from
    end

Return the value of the first matching field of a field name, or nil if none found. If passed a Fixnum, returns the header indexed by the number.

[Source]

# File lib/rmail/header.rb, line 138
    def [](name_or_index)
      if name_or_index.kind_of? Fixnum
        temp = @fields[name_or_index]
        temp = temp.value unless temp.nil?
      else
        name = Field.name_canonicalize(name_or_index)
        result = detect { |n, v|
          if n.downcase == name then true else false end
        }
        if result.nil? then nil else result[1] end
      end
    end

Append a new field with name and value. If you want control of where the field is inserted, see add.

Returns value.

[Source]

# File lib/rmail/header.rb, line 401
    def []=(name, value)
      add(name, value)
      value
    end

Add a new field with name and value. When index is nil (the default if not specified) the line is appended to the header, otherwise it is inserted at the specified index. E.g. an index of 0 will prepend the header line.

You can pass additional parameters for the header as a hash table params. Every key of the hash will be the name of the parameter, and every key’s value the parameter value.

E.g.

   header.add('Content-Type', 'multipart/mixed', nil,
              'boundary' => 'the boundary')

will add this header

   Content-Type: multipart/mixed; boundary="the boundary"

Always returns self.

[Source]

# File lib/rmail/header.rb, line 357
    def add(name, value, index = nil, params = nil)
      value = value.to_str
      if params
        value = value.dup
        sep = "; "
        params.each do |n, v|
          value << sep
          value << n.to_s
          value << '='
          v = v.to_s
          if v =~ /^\w+$/
            value << v
          else
            value << '"'
            value << v
            value << '"'
          end
        end
      end
      field = Field.new(name, value)
      index ||= @fields.length
      @fields[index, 0] = field
      self
    end

Sets the value of this object’s Message-Id: field to a new random value.

If you don’t supply a fqdn (fully qualified domain name) then one will be randomly generated for you. If a valid address exists in the From: field, its domain will be used as a basis.

Part of the randomness in the header is taken from the header itself, so it is best to call this method after adding other fields to the header — especially those that make it unique (Subject:, To:, Cc:, etc).

[Source]

# File lib/rmail/header.rb, line 771
    def add_message_id(fqdn = nil)

      # If they don't supply a fqdn, we supply one for them.
      #
      # First grab the From: field and see if we can use a domain from
      # there.  If so, use that domain name plus the hash of the From:
      # field's value (this guarantees that bob@example.com and
      # sally@example.com will never have clashes).
      #
      # If there is no From: field, grab the current host name and use
      # some randomness from Ruby's random number generator.  Since
      # Ruby's random number generator is fairly good this will
      # suffice so long as it is seeded corretly.
      #
      # P.S. There is no portable way to get the fully qualified
      # domain name of the current host.  Those truly interested in
      # generating "correct" message-ids should pass it in.  We
      # generate a hopefully random and unique domain name.
      unless fqdn
        unless fqdn = from.domains.first
          require 'socket'
          fqdn = sprintf("%s.invalid", Socket.gethostname)
        end
      else
        raise ArgumentError, "fqdn must have at least one dot" unless
          fqdn.index('.')
      end

      # Hash the header we have so far.
      md5 = Digest::MD5.new
      starting_digest = md5.digest
      @fields.each { |f|
        if f.raw
          md5.update(f.raw)
        else
          md5.update(f.name) if f.name
          md5.update(f.value) if f.value
        end
      }
      if (digest = md5.digest) == starting_digest
        digest = 0
      end

      set('Message-Id', sprintf("<%s.%s.%s.rubymail@%s>",
                                base36(Time.now.to_i),
                                base36(rand(MESSAGE_ID_MAXRAND)),
                                base36(digest),
                                fqdn))
    end

Add a new field as a raw string together with a parsed name/value. This method is used mainly by the parser and regular programs should stick to add.

[Source]

# File lib/rmail/header.rb, line 385
    def add_raw(raw)
      @fields << Field.new(raw)
      self
    end

Set a given field to a list of supplied addresses.

The addresses may be a String, RMail::Address, or Array. If a String, it is parsed for valid email addresses and those found are used. If an RMail::Address, the result of RMail::Address#format is used. If an Array, each element of the array must be either a String or RMail::Address and is treated as above.

This method is used to implement many of the convenience methods such as from=, to=, etc.

[Source]

# File lib/rmail/header.rb, line 876
    def address_list_assign(field_name, addresses)
      if addresses.kind_of?(Array)
        value = addresses.collect { |e|
          if e.kind_of?(RMail::Address)
            e.format
          else
            RMail::Address.parse(e.to_str).collect { |a|
              a.format
            }
          end
        }.flatten.join(", ")
        set(field_name, value)
      elsif addresses.kind_of?(RMail::Address)
        set(field_name, addresses.format)
      else
        address_list_assign(field_name,
                            RMail::Address.parse(addresses.to_str))
      end
    end

Retrieve a given field’s value as an RMail::Address::List of RMail::Address objects.

This method is used to implement many of the convenience methods such as from, to, etc.

[Source]

# File lib/rmail/header.rb, line 849
    def address_list_fetch(field_name)
      if values = fetch_all(field_name, nil)
        list = nil
        values.each { |value|
          if list
            list.concat(Address.parse(value))
          else
            list = Address.parse(value)
          end
        }
        if list and !list.empty?
          list
        end
      end or RMail::Address::List.new
    end

Returns the value of the Bcc: field as an Array of RMail::Address objects.

See address_list_fetch for details on what is returned.

[Source]

# File lib/rmail/header.rb, line 729
    def bcc
      address_list_fetch('bcc')
    end

Sets the Bcc: field to the supplied address or addresses.

See address_list_assign for information on valid values for addresses.

[Source]

# File lib/rmail/header.rb, line 737
    def bcc=(addresses)
      address_list_assign('Bcc', addresses)
    end

Returns the value of the Cc: field as an Array of RMail::Address objects.

See address_list_fetch for details on what is returned.

[Source]

# File lib/rmail/header.rb, line 713
    def cc
      address_list_fetch('cc')
    end

Sets the Cc: field to the supplied address or addresses.

See address_list_assign for information on valid values for addresses.

[Source]

# File lib/rmail/header.rb, line 721
    def cc=(addresses)
      address_list_assign('Cc', addresses)
    end

Delete all fields in this object. Returns self.

[Source]

# File lib/rmail/header.rb, line 174
    def clear()
      @fields = []
      @mbox_from = nil
      self
    end

Creates a complete copy of this header object, including any singleton methods and strings. The returned object will be a complete and unrelated duplicate of the original.

[Source]

# File lib/rmail/header.rb, line 166
    def clone
      h = super
      h.fields = Marshal::load(Marshal::dump(@fields))
      h.mbox_from = Marshal::load(Marshal::dump(@mbox_from))
      h
    end

This returns the full content type of this message converted to lower case.

If there is no content type header, returns the passed block is executed and its return value is returned. If no block is passed, the value of the default argument is returned.

[Source]

# File lib/rmail/header.rb, line 535
    def content_type(default = nil)
      if value = self['content-type']
        value.strip.split(/\s*;\s*/)[0].downcase
      else
        if block_given?
          yield
        else
          default
        end
      end
    end

Return the value of the Date: field, parsed into a Time object. Returns nil if there is no Date: field or the field value could not be parsed.

[Source]

# File lib/rmail/header.rb, line 645
    def date
      if value = self['date']
        begin
          # Rely on Ruby's standard time.rb to parse the time.
          (Time.rfc2822(value) rescue Time.parse(value)).localtime
        rescue
          # Exceptions during time parsing just cause nil to be
          # returned.
        end
      end
    end

Deletes any existing Date: fields and appends a new one corresponding to the given Time object.

[Source]

# File lib/rmail/header.rb, line 659
    def date=(time)
      delete('Date')
      add('Date', time.rfc2822)
    end

Deletes all fields with name. Returns self.

[Source]

# File lib/rmail/header.rb, line 256
    def delete(name)
      name = Field.name_canonicalize(name.to_str)
      delete_if { |n, v|
        n.downcase == name
      }
      self
    end

Deletes the field at the specified index and returns its value.

[Source]

# File lib/rmail/header.rb, line 265
    def delete_at(index)
      @fields[index, 1] = nil
      self
    end

Deletes the field if the passed block returns true. Returns self.

[Source]

# File lib/rmail/header.rb, line 272
    def delete_if # yields: name, value
      @fields.delete_if { |i|
        yield i.name, i.value
      }
      self
    end

Creates a copy of this header object. A new RMail::Header is created and the instance data is copied over. However, the new object will still reference the same strings held in the original object. Since these strings are frozen, this usually won’t matter.

[Source]

# File lib/rmail/header.rb, line 156
    def dup
      h = super
      h.fields = @fields.dup
      h.mbox_from = @mbox_from
      h
    end

Executes block once for each field in the header, passing the key and value as parameters.

Returns self.

[Source]

# File lib/rmail/header.rb, line 283
    def each                    # yields: name, value
      @fields.each { |i|
        yield(i.name, i.value)
      }
    end
each_key()

Alias for each_name

Executes block once for each field in the header, passing the field’s name as a parameter.

Returns self

[Source]

# File lib/rmail/header.rb, line 294
    def each_name
      @fields.each { |i|
        yield(i.name)
      }
    end
each_pair(

Alias for each

Executes block once for each field in the header, passing the field’s value as a parameter.

Returns self

[Source]

# File lib/rmail/header.rb, line 305
    def each_value
      @fields.each { |i|
        yield(i.value)
      }
    end

Returns true if the header contains no fields

[Source]

# File lib/rmail/header.rb, line 312
    def empty?
      @fields.empty?
    end

Return the value of the first matching field of a given name. If there is no such field, the value returned by the supplied block is returned. If no block is passed, the value of default_value is returned. If no default_value is specified, an IndexError exception is raised.

[Source]

# File lib/rmail/header.rb, line 203
    def fetch(name, *rest)
      if rest.length > 1
        raise ArgumentError, "wrong # of arguments(#{rest.length + 1} for 2)"
      end
      result = self[name]
      if result.nil?
        if block_given?
          yield name
        elsif rest.length == 1
          rest[0]
        else
          raise IndexError, 'name not found'
        end
      else
        result
      end
    end

Returns the values of every field named name. If there are no such fields, the value returned by the block is returned. If no block is passed, the value of default_value is returned. If no default_value is specified, an IndexError exception is raised.

[Source]

# File lib/rmail/header.rb, line 226
    def fetch_all name, *rest
      if rest.length > 1
        raise ArgumentError, "wrong # of arguments(#{rest.length + 1} for 2)"
      end
      result = select(name)
      if result.nil?
        if block_given?
          yield name
        elsif rest.length == 1
          rest[0]
        else
          raise IndexError, 'name not found'
        end
      else
        result.collect { |n, v|
          v
        }
      end
    end

Returns true if the message has a field named ‘name’.

[Source]

# File lib/rmail/header.rb, line 247
    def field?(name)
      ! self[name].nil?
    end

Returns the value of the From: header as an Array of RMail::Address objects.

See address_list_fetch for details on what is returned.

This method does not return a single RMail::Address value because it is legal to have multiple addresses in a From: header.

This method always returns at least the empty list. So if you are always only interested in the first from address (most likely the case), you can safely say:

   header.from.first

[Source]

# File lib/rmail/header.rb, line 678
    def from
      address_list_fetch('from')
    end

Sets the From: field to the supplied address or addresses.

See address_list_assign for information on valid values for addresses.

Note that the From: header usually contains only one address, but it is legal to have more than one.

[Source]

# File lib/rmail/header.rb, line 689
    def from=(addresses)
      address_list_assign('From', addresses)
    end
has_key?(name)

Alias for field?

include?(name)

Alias for field?

key?(name)

Alias for field?

keys()

Alias for names

Return the number of fields in this object.

[Source]

# File lib/rmail/header.rb, line 193
    def length
      @fields.length
    end

Find all fields that match the given +name and value.

If name is a String, all fields of that name are tested. If name is a Regexp, the field names are matched against the regexp (the field names are converted to lower case first). Use the regexp // if you want to test all field names.

If value is a String, it is converted to a case insensitive Regexp that matches the string. Otherwise, it must be a Regexp. Note that the field value may be folded across many lines, so you may need to use a multi-line Regexp. Also consider using a case insensitive Regexp. Use the regexp // if you want to match all possible field values.

Returns a new RMail::Header holding all matching headers.

Examples:

 received = header.match('Received', //)
 destinations = header.match(/^(to|cc|bcc)$/, //)
 bigfoot_received = header.match('received',
                                 /from.*by.*bigfoot\.com.*LiteMail/im)

See also: match?

[Source]

# File lib/rmail/header.rb, line 506
    def match(name, value)
      massage_match_args(name, value) { |name, value|
        header = RMail::Header.new
        found = each { |n, v|
          if n.downcase =~ name  &&  value =~ v
            header[n] = v
          end
        }
        header
      }
    end

Determine if there is any fields that match the given name and value.

If name is a String, all fields of that name are tested. If name is a Regexp the field names are matched against the regexp (the field names are converted to lower case first). Use the regexp // if you want to test all field names.

If value is a String, it is converted to a case insensitive Regexp that matches the string. Otherwise, it must be a Regexp. Note that the field value may be folded across many lines, so you should use a multi-line Regexp. Also consider using a case insensitive Regexp. Use the regexp // if you want to match all possible field values.

Returns true if there is a match, false otherwise.

Example:

   if h.match?('x-ml-name', /ruby-dev/im)
     # do something
   end

See also: match

[Source]

# File lib/rmail/header.rb, line 473
    def match?(name, value)
      massage_match_args(name, value) { |name, value|
        match = detect {|n, v|
          n =~ name && v =~ value
        }
        ! match.nil?
      }
    end

Gets the "From " line previously set with mbox_from=, or nil.

[Source]

# File lib/rmail/header.rb, line 525
    def mbox_from
      @mbox_from
    end

Sets the "From " line commonly used in the Unix mbox mailbox format. The value supplied should be the entire "From " line.

[Source]

# File lib/rmail/header.rb, line 520
    def mbox_from=(value)
      @mbox_from = value
    end

This returns the main media type for this message converted to lower case. This is the first portion of the content type. E.g. a content type of text/plain has a media type of text.

If there is no content type field, returns the passed block is executed and its return value is returned. If no block is passed, the value of the default argument is returned.

[Source]

# File lib/rmail/header.rb, line 555
    def media_type(default = nil)
      if value = content_type
        value.split('/')[0]
      else
        if block_given?
          yield
        else
          default
        end
      end
    end
member?(name)

Alias for field?

Returns the value of this object’s Message-Id: field.

[Source]

# File lib/rmail/header.rb, line 756
    def message_id
      self['message-id']
    end

Returns an array consisting of the names of every field in this header.

[Source]

# File lib/rmail/header.rb, line 331
    def names
      collect { |n, v|
        n
      }
    end

This returns the parameter value for the given parameter in the given field. The value returned is unquoted.

If the field or parameter does not exist or it is malformed in a way that makes it impossible to parse, then the passed block is executed and its return value is returned. If no block is passed, the value of the default argument is returned.

[Source]

# File lib/rmail/header.rb, line 617
    def param(field_name, param_name, default = nil)
      if field?(field_name)
        params = params_quoted(field_name)
        value = params[param_name]
        return Utils.unquote(value) if value
      end
      if block_given?
        yield field_name, param_name
      else
        default
      end
    end

This returns a hash of parameters. Each key in the hash is the name of the parameter in lower case and each value in the hash is the unquoted parameter value. If a parameter has no value, its value in the hash will be true.

If the field or parameter does not exist or it is malformed in a way that makes it impossible to parse, then the passed block is executed and its return value is returned. If no block is passed, the value of the default argument is returned.

[Source]

# File lib/rmail/header.rb, line 596
    def params(field_name, default = nil)
      if params = params_quoted(field_name)
        params.each { |name, value|
          params[name] = value ? Utils.unquote(value) : nil
        }
      else
        if block_given?
          yield field_name
        else
          default
        end
      end
    end

Returns an RMail::Address::List array holding all the recipients of this message. This uses the contents of the To, Cc, and Bcc fields. Duplicate addresses are eliminated.

[Source]

# File lib/rmail/header.rb, line 834
    def recipients
      retval = RMail::Address::List.new
      retval.concat(to)
      retval.concat(cc)
      retval.concat(bcc)
      retval.uniq
    end

Replaces the contents of this header with that of another header object. Returns self.

[Source]

# File lib/rmail/header.rb, line 182
    def replace(other)
      unless other.kind_of?(RMail::Header)
        raise TypeError, "#{other.class.to_s} is not of type RMail::Header"
      end
      temp = other.dup
      @fields = temp.fields
      @mbox_from = temp.mbox_from
      self
    end

Returns the value of the Reply-To: header as an Array of RMail::Address objects.

[Source]

# File lib/rmail/header.rb, line 743
    def reply_to
      address_list_fetch('reply-to')
    end

Sets the Reply-To: field to the supplied address or addresses.

See address_list_assign for information on valid values for addresses.

[Source]

# File lib/rmail/header.rb, line 751
    def reply_to=(addresses)
      address_list_assign('Reply-To', addresses)
    end

Returns an array of pairs [ name, value ] for all fields with one of the names passed.

[Source]

# File lib/rmail/header.rb, line 318
    def select(*names)
      result = []
      names.each { |name|
        name = Field.name_canonicalize(name)
        result.concat(find_all { |n, v|
                        n.downcase == name
                      })
      }
      result
    end

First delete any fields with name, then append a new field with name, value, and params as in add.

[Source]

# File lib/rmail/header.rb, line 392
    def set(name, value, params = nil)
      delete(name)
      add(name, value, nil, params)
    end

Set the boundary parameter of this message’s Content-Type: field.

[Source]

# File lib/rmail/header.rb, line 632
    def set_boundary(boundary)
      params = params_quoted('content-type')
      params ||= {}
      params['boundary'] = boundary
      content_type = content_type()
      content_type ||= "multipart/mixed"
      delete('Content-Type')
      add('Content-Type', content_type, nil, params)
    end
size()

Alias for length

Return the subject of this message.

[Source]

# File lib/rmail/header.rb, line 822
    def subject
      self['subject']
    end

Set the subject of this message

[Source]

# File lib/rmail/header.rb, line 827
    def subject=(string)
      set('Subject', string)
    end

This returns the media subtype for this message, converted to lower case. This is the second portion of the content type. E.g. a content type of text/plain has a media subtype of plain.

If there is no content type field, returns the passed block is executed and its return value is returned. If no block is passed, the value of the default argument is returned.

[Source]

# File lib/rmail/header.rb, line 575
    def subtype(default = nil)
      if value = content_type
        value.split('/')[1]
      else
        if block_given? then
          yield
        else
          default
        end
      end
    end

Returns the value of the To: field as an Array of RMail::Address objects.

See address_list_fetch for details on what is returned.

[Source]

# File lib/rmail/header.rb, line 697
    def to
      address_list_fetch('to')
    end

Sets the To: field to the supplied address or addresses.

See address_list_assign for information on valid values for addresses.

[Source]

# File lib/rmail/header.rb, line 705
    def to=(addresses)
      address_list_assign('To', addresses)
    end

Returns a new array holding one [ name, value ] array per field in the header.

[Source]

# File lib/rmail/header.rb, line 416
    def to_a
      @fields.collect { |field|
        [ field.name, field.value ]
      }
    end

Converts the header to a string, including any mbox from line. Equivalent to header.to_string(true).

[Source]

# File lib/rmail/header.rb, line 424
    def to_s
      to_string(true)
    end

Converts the header to a string. If mbox_from is true, then the mbox from line is also included.

[Source]

# File lib/rmail/header.rb, line 430
    def to_string(mbox_from = false)
      s = ""
      if mbox_from && ! @mbox_from.nil?
        s << @mbox_from
        s << "\n" unless @mbox_from[-1] == ?\n
      end
      @fields.each { |field|
        if field.raw
          s << field.raw
        else
          s << field.name
          s << ': '
          s << field.value
        end
        s << "\n" unless s[-1] == ?\n
      }
      s
    end

[Validate]