Class: RMail::Header

Inherits:
Object
  • Object
show all
Includes:
Enumerable
Defined in:
lib/rmail/header.rb

Overview

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.

Defined Under Namespace

Classes: Field

Instance Method Summary collapse

Constructor Details

#initializeHeader

Creates a new empty header object.


134
135
136
# File 'lib/rmail/header.rb', line 134

def initialize()
  clear()
end

Instance Method Details

#==(other) ⇒ Object

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


411
412
413
414
415
# File 'lib/rmail/header.rb', line 411

def ==(other)
  return other.kind_of?(self.class) &&
    @fields == other.fields &&
    @mbox_from == other.mbox_from
end

#[](name_or_index) ⇒ Object

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


141
142
143
144
145
146
147
148
149
150
151
152
# File 'lib/rmail/header.rb', line 141

def [](name_or_index)
  if name_or_index.kind_of? Integer
    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

#[]=(name, value) ⇒ Object

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

Returns value.


404
405
406
407
# File 'lib/rmail/header.rb', line 404

def []=(name, value)
  add(name, value)
  value
end

#add(name, value, index = nil, params = nil) ⇒ Object

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.


360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
# File 'lib/rmail/header.rb', line 360

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

#add_message_id(fqdn = nil) ⇒ Object

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).


775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
# File 'lib/rmail/header.rb', line 775

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 [email protected] and
  # [email protected] 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.%[email protected]%s>",
                            base36(Time.now.to_i),
                            base36(rand(MESSAGE_ID_MAXRAND)),
                            base36(digest),
                            fqdn))
end

#add_raw(raw) ⇒ Object

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.


388
389
390
391
# File 'lib/rmail/header.rb', line 388

def add_raw(raw)
  @fields << Field.new(raw)
  self
end

#address_list_assign(field_name, addresses) ⇒ Object

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.


874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
# File 'lib/rmail/header.rb', line 874

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

#address_list_fetch(field_name) ⇒ Object

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.


847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
# File 'lib/rmail/header.rb', line 847

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

#bccObject

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

See #address_list_fetch for details on what is returned.


733
734
735
# File 'lib/rmail/header.rb', line 733

def bcc
  address_list_fetch('bcc')
end

#bcc=(addresses) ⇒ Object

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

See #address_list_assign for information on valid values for addresses.


741
742
743
# File 'lib/rmail/header.rb', line 741

def bcc=(addresses)
  address_list_assign('Bcc', addresses)
end

#ccObject

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

See #address_list_fetch for details on what is returned.


717
718
719
# File 'lib/rmail/header.rb', line 717

def cc
  address_list_fetch('cc')
end

#cc=(addresses) ⇒ Object

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

See #address_list_assign for information on valid values for addresses.


725
726
727
# File 'lib/rmail/header.rb', line 725

def cc=(addresses)
  address_list_assign('Cc', addresses)
end

#clearObject

Delete all fields in this object. Returns self.


177
178
179
180
181
# File 'lib/rmail/header.rb', line 177

def clear()
  @fields = []
  @mbox_from = nil
  self
end

#cloneObject

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.


169
170
171
172
173
174
# File 'lib/rmail/header.rb', line 169

def clone
  h = super
  h.fields = Marshal::load(Marshal::dump(@fields))
  h.mbox_from = Marshal::load(Marshal::dump(@mbox_from))
  h
end

#content_type(default = nil) ⇒ Object

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.


538
539
540
541
542
543
544
545
546
547
548
549
# File 'lib/rmail/header.rb', line 538

def content_type(default = nil)
  content_type = self['content-type']
  if content_type && content_type.length > 0
	content_type.strip.split(/\s*;\s*/)[0].downcase
  else
	if block_given?
      yield
    else
      default
    end
  end
end

#dateObject

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.


649
650
651
652
653
654
655
656
657
658
659
# File 'lib/rmail/header.rb', line 649

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

#date=(time) ⇒ Object

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


663
664
665
666
# File 'lib/rmail/header.rb', line 663

def date=(time)
  delete('Date')
  add('Date', time.rfc2822)
end

#delete(name) ⇒ Object

Deletes all fields with name. Returns self.


259
260
261
262
263
264
265
# File 'lib/rmail/header.rb', line 259

def delete(name)
  name = Field.name_canonicalize(name.to_str)
  delete_if { |n, v|
    n.downcase == name
  }
  self
end

#delete_at(index) ⇒ Object

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


268
269
270
271
# File 'lib/rmail/header.rb', line 268

def delete_at(index)
  @fields.delete_at(index)
  self
end

#delete_ifObject

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


275
276
277
278
279
280
# File 'lib/rmail/header.rb', line 275

def delete_if # yields: name, value
  @fields.delete_if { |i|
    yield i.name, i.value
  }
  self
end

#dupObject

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.


159
160
161
162
163
164
# File 'lib/rmail/header.rb', line 159

def dup
  h = super
  h.fields = @fields.dup
  h.mbox_from = @mbox_from
  h
end

#eachObject Also known as: each_pair

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

Returns self.


286
287
288
289
290
# File 'lib/rmail/header.rb', line 286

def each                    # yields: name, value
  @fields.each { |i|
    yield [i.name, i.value]
  }
end

#each_nameObject Also known as: each_key

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

Returns self


297
298
299
300
301
# File 'lib/rmail/header.rb', line 297

def each_name
  @fields.each { |i|
    yield(i.name)
  }
end

#each_valueObject

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

Returns self


308
309
310
311
312
# File 'lib/rmail/header.rb', line 308

def each_value
  @fields.each { |i|
    yield(i.value)
  }
end

#empty?Boolean

Returns true if the header contains no fields

Returns:

  • (Boolean)

315
316
317
# File 'lib/rmail/header.rb', line 315

def empty?
  @fields.empty?
end

#fetch(name, *rest) ⇒ Object

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.


206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
# File 'lib/rmail/header.rb', line 206

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

#fetch_all(name, *rest) ⇒ Object

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.


229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
# File 'lib/rmail/header.rb', line 229

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

#field?(name) ⇒ Boolean Also known as: member?, include?, has_key?, key?

Returns true if the message has a field named 'name'.

Returns:

  • (Boolean)

250
251
252
# File 'lib/rmail/header.rb', line 250

def field?(name)
  ! self[name].nil?
end

#fromObject

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

682
683
684
# File 'lib/rmail/header.rb', line 682

def from
  address_list_fetch('from')
end

#from=(addresses) ⇒ Object

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.


693
694
695
# File 'lib/rmail/header.rb', line 693

def from=(addresses)
  address_list_assign('From', addresses)
end

#lengthObject Also known as: size

Return the number of fields in this object.


196
197
198
# File 'lib/rmail/header.rb', line 196

def length
  @fields.length
end

#match(name, value) ⇒ Object

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?


509
510
511
512
513
514
515
516
517
518
519
# File 'lib/rmail/header.rb', line 509

def match(name, value)
  massage_match_args(name, value) { |mname, mvalue|
    header = RMail::Header.new
    each { |n, v|
      if n.downcase =~ mname  &&  mvalue =~ v
        header[n] = v
      end
    }
    header
  }
end

#match?(name, value) ⇒ Boolean

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

Returns:

  • (Boolean)

476
477
478
479
480
481
482
483
# File 'lib/rmail/header.rb', line 476

def match?(name, value)
  massage_match_args(name, value) { |mname, mvalue|
    match = detect {|n, v|
      n =~ mname && v =~ mvalue
    }
    ! match.nil?
  }
end

#mbox_fromObject

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


528
529
530
# File 'lib/rmail/header.rb', line 528

def mbox_from
  @mbox_from
end

#mbox_from=(value) ⇒ Object

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


523
524
525
# File 'lib/rmail/header.rb', line 523

def mbox_from=(value)
  @mbox_from = value
end

#media_type(default = nil) ⇒ Object

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.


559
560
561
562
563
564
565
566
567
568
569
# File 'lib/rmail/header.rb', line 559

def media_type(default = nil)
  if value = content_type
    value.split('/')[0]
  else
    if block_given?
      yield
    else
      default
    end
  end
end

#message_idObject

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


760
761
762
# File 'lib/rmail/header.rb', line 760

def message_id
  self['message-id']
end

#namesObject Also known as: keys

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


334
335
336
337
338
# File 'lib/rmail/header.rb', line 334

def names
  collect { |n, v|
    n
  }
end

#param(field_name, param_name, default = nil) ⇒ Object

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.


621
622
623
624
625
626
627
628
629
630
631
632
# File 'lib/rmail/header.rb', line 621

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

#params(field_name, default = nil) ⇒ Object

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.


600
601
602
603
604
605
606
607
608
609
610
611
612
# File 'lib/rmail/header.rb', line 600

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

#recipientsObject

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.


838
839
840
# File 'lib/rmail/header.rb', line 838

def recipients
  RMail::Address::List.new([ to, cc, bcc ].flatten.uniq)
end

#replace(other) ⇒ Object

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


185
186
187
188
189
190
191
192
193
# File 'lib/rmail/header.rb', line 185

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

#reply_toObject

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


747
748
749
# File 'lib/rmail/header.rb', line 747

def reply_to
  address_list_fetch('reply-to')
end

#reply_to=(addresses) ⇒ Object

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

See #address_list_assign for information on valid values for addresses.


755
756
757
# File 'lib/rmail/header.rb', line 755

def reply_to=(addresses)
  address_list_assign('Reply-To', addresses)
end

#select(*names) ⇒ Object

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


321
322
323
324
325
326
327
328
329
330
# File 'lib/rmail/header.rb', line 321

def select(*names)
  result = []
  names.each { |name|
    name = Field.name_canonicalize(name)
    result.concat(find_all { |n, v|
                    n.downcase == name
                  })
  }
  result
end

#set(name, value, params = nil) ⇒ Object

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


395
396
397
398
# File 'lib/rmail/header.rb', line 395

def set(name, value, params = nil)
  delete(name)
  add(name, value, nil, params)
end

#set_boundary(boundary) ⇒ Object

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


636
637
638
639
640
641
642
643
644
# File 'lib/rmail/header.rb', line 636

def set_boundary(boundary)
  params = params('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

#subjectObject

Return the subject of this message.


826
827
828
# File 'lib/rmail/header.rb', line 826

def subject
  self['subject']
end

#subject=(string) ⇒ Object

Set the subject of this message


831
832
833
# File 'lib/rmail/header.rb', line 831

def subject=(string)
  set('Subject', string)
end

#subtype(default = nil) ⇒ Object

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.


579
580
581
582
583
584
585
586
587
588
589
# File 'lib/rmail/header.rb', line 579

def subtype(default = nil)
  if value = content_type
    value.split('/')[1]
  else
    if block_given? then
      yield
    else
      default
    end
  end
end

#toObject

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

See #address_list_fetch for details on what is returned.


701
702
703
# File 'lib/rmail/header.rb', line 701

def to
  address_list_fetch('to')
end

#to=(addresses) ⇒ Object

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

See #address_list_assign for information on valid values for addresses.


709
710
711
# File 'lib/rmail/header.rb', line 709

def to=(addresses)
  address_list_assign('To', addresses)
end

#to_aObject

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


419
420
421
422
423
# File 'lib/rmail/header.rb', line 419

def to_a
  @fields.collect { |field|
    [ field.name, field.value ]
  }
end

#to_sObject

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


427
428
429
# File 'lib/rmail/header.rb', line 427

def to_s
  to_string(true)
end

#to_string(mbox_from = false) ⇒ Object

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


433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
# File 'lib/rmail/header.rb', line 433

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