This file is indexed.

/usr/lib/ruby/1.8/rmail/header.rb is in librmail-ruby1.8 0.17-1.1.

This file is owned by root:root, with mode 0o644.

The actual contents of the file can be viewed below.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
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
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
#--
#   Copyright (c) 2001, 2002, 2003, 2004 Matt Armstrong.  All rights
#   reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# 1. Redistributions of source code must retain the above copyright notice,
#    this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
#    notice, this list of conditions and the following disclaimer in the
#    documentation and/or other materials provided with the distribution.
# 3. The name of the author may not be used to endorse or promote products
#    derived from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
# NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
# TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
#++
# Implements the RMail::Header class.
require 'rmail/utils'
require 'rmail/address'
require 'digest/md5'
require 'time'

module RMail

  # A class that supports the reading, writing and manipulation of
  # RFC2822 mail headers.

  # =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.
  class Header
    include Enumerable

    class Field                 # :nodoc:
      # fixme, document methadology for this (RFC2822)
      EXTRACT_FIELD_NAME_RE = /\A([^\x00-\x1f\x7f-\xff :]+):\s*/o

      class << self
        def parse(field)
          field = field.to_str
          if field =~ EXTRACT_FIELD_NAME_RE
            [ $1, $'.chomp ]
          else
            [ "", Field.value_strip(field) ]
          end
        end
      end

      def initialize(name, value = nil)
        if value
          @name = Field.name_strip(name.to_str).freeze
          @value = Field.value_strip(value.to_str).freeze
          @raw = nil
        else
          @raw = name.to_str.freeze
          @name, @value = Field.parse(@raw)
          @name.freeze
          @value.freeze
        end
      end

      attr_reader :name, :value, :raw

      def ==(other)
        other.kind_of?(self.class) &&
          @name.downcase == other.name.downcase &&
          @value == other.value
      end

      def Field.name_canonicalize(name)
        name_strip(name.to_str).downcase
      end

      private

      def Field.name_strip(name)
        name.sub(/\s*:.*/, '')
      end

      def Field.value_strip(value)
        if value.frozen?
          value = value.dup
        end
        value.strip!
        value
      end

    end

    # Creates a new empty header object.
    def initialize()
      clear()
    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.
    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

    # 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.
    def dup
      h = super
      h.fields = @fields.dup
      h.mbox_from = @mbox_from
      h
    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.
    def clone
      h = super
      h.fields = Marshal::load(Marshal::dump(@fields))
      h.mbox_from = Marshal::load(Marshal::dump(@mbox_from))
      h
    end

    # Delete all fields in this object.  Returns self.
    def clear()
      @fields = []
      @mbox_from = nil
      self
    end

    # Replaces the contents of this header with that of another header
    # object.  Returns self.
    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

    # Return the number of fields in this object.
    def length
      @fields.length
    end
    alias size length

    # 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.
    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.
    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'.
    def field?(name)
      ! self[name].nil?
    end
    alias member? field?
    alias include? field?
    alias has_key? field?
    alias key? field?

    # Deletes all fields with +name+.  Returns self.
    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.
    def delete_at(index)
      @fields[index, 1] = nil
      self
    end

    # Deletes the field if the passed block returns true.  Returns
    # self.
    def delete_if # yields: name, value
      @fields.delete_if { |i|
        yield i.name, i.value
      }
      self
    end

    # Executes block once for each field in the header, passing the
    # key and value as parameters.
    #
    # Returns self.
    def each                    # yields: name, value
      @fields.each { |i|
        yield(i.name, i.value)
      }
    end
    alias each_pair each

    # Executes block once for each field in the header, passing the
    # field's name as a parameter.
    #
    # Returns self
    def each_name
      @fields.each { |i|
        yield(i.name)
      }
    end
    alias each_key each_name

    # Executes block once for each field in the header, passing the
    # field's value as a parameter.
    #
    # Returns self
    def each_value
      @fields.each { |i|
        yield(i.value)
      }
    end

    # Returns true if the header contains no fields
    def empty?
      @fields.empty?
    end

    # Returns an array of pairs [ name, value ] for all fields with
    # one of the names passed.
    def select(*names)
      result = []
      names.each { |name|
        name = Field.name_canonicalize(name)
        result.concat(find_all { |n, v|
                        n.downcase == name
                      })
      }
      result
    end

    # Returns an array consisting of the names of every field in this
    # header.
    def names
      collect { |n, v|
        n
      }
    end
    alias keys names

    # 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.
    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 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.
    def add_raw(raw)
      @fields << Field.new(raw)
      self
    end

    # First delete any fields with +name+, then append a new field
    # with +name+, +value+, and +params+ as in #add.
    def set(name, value, params = nil)
      delete(name)
      add(name, value, nil, params)
    end

    # Append a new field with +name+ and +value+.  If you want control
    # of where the field is inserted, see #add.
    #
    # Returns +value+.
    def []=(name, value)
      add(name, value)
      value
    end

    # Returns true if the two objects have the same number of fields,
    # in the same order, with the same values.
    def ==(other)
      return other.kind_of?(self.class) &&
        @fields == other.fields &&
        @mbox_from == other.mbox_from
    end

    # Returns a new array holding one [ name, value ] array per field
    # in the header.
    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).
    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.
    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

    # 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
    def match?(name, value)
      massage_match_args(name, value) { |name, value|
        match = detect {|n, v|
          n =~ name && v =~ value
        }
        ! match.nil?
      }
    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?
    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

    # Sets the "From " line commonly used in the Unix mbox mailbox
    # format.  The +value+ supplied should be the entire "From " line.
    def mbox_from=(value)
      @mbox_from = value
    end

    # Gets the "From " line previously set with mbox_from=, or nil.
    def mbox_from
      @mbox_from
    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.
    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

    # 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 <tt>text/plain</tt> has a media type of
    # <tt>text</tt>.
    #
    # 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.
    def media_type(default = nil)
      if value = content_type
        value.split('/')[0]
      else
        if block_given?
          yield
        else
          default
        end
      end
    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 <tt>text/plain</tt> has a media subtype
    # of <tt>plain</tt>.
    #
    # 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.
    def subtype(default = nil)
      if value = content_type
        value.split('/')[1]
      else
        if block_given? then
          yield
        else
          default
        end
      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.
    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

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

    # Set the boundary parameter of this message's Content-Type:
    # field.
    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

    # 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.
    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.
    def date=(time)
      delete('Date')
      add('Date', time.rfc2822)
    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
    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.
    def from=(addresses)
      address_list_assign('From', addresses)
    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.
    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+.
    def to=(addresses)
      address_list_assign('To', 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.
    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+.
    def cc=(addresses)
      address_list_assign('Cc', addresses)
    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.
    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+.
    def bcc=(addresses)
      address_list_assign('Bcc', addresses)
    end

    # Returns the value of the Reply-To: header as an Array of
    # RMail::Address objects.
    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+.
    def reply_to=(addresses)
      address_list_assign('Reply-To', addresses)
    end

    # Returns the value of this object's Message-Id: field.
    def message_id
      self['message-id']
    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).
    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

    # Return the subject of this message.
    def subject
      self['subject']
    end

    # Set the subject of this message
    def subject=(string)
      set('Subject', string)
    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.
    def recipients
      retval = RMail::Address::List.new
      retval.concat(to)
      retval.concat(cc)
      retval.concat(bcc)
      retval.uniq
    end

#    recipients

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

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

    protected

    attr :fields, true

    private

    MESSAGE_ID_MAXRAND = 0x7fffffff

    def string2num(string)
      temp = 0
      string.reverse.each_byte { |b|
        temp <<= 8
        temp |= b
      }
      return temp
    end

    BASE36 = "0123456789abcdefghijklmnopqrstuvwxyz"
    def base36(number)
      if number.kind_of?(String)
        number = string2num(number)
      end
      raise ArgumentError, "need non-negative number" if number < 0
      return "0" if number == 0
      result = ""
      while number > 0
        number, remainder = number.divmod(36)
        result << BASE36[remainder]
      end
      return result.reverse!
    end

    PARAM_SCAN_RE = %r{
        ;
          |
        [^;"]*"(?:|.*?(?:[^\\]|\\\\))"\s*   # fix fontification "
          |
        [^;]+
    }x

    NAME_VALUE_SCAN_RE = %r{
        =
          |
        [^="]*"(?:.*?(?:[^\\]|\\\\))"   # fix fontification "\s*
          |
        [^=]+
    }x

    def params_quoted(field_name, default = nil)
      if value = self[field_name]
        params = {}
        first = true
	value.scan(PARAM_SCAN_RE) do |param|
          if param != ';'
            unless first
              name, value = param.scan(NAME_VALUE_SCAN_RE).collect do |p|
                if p == '=' then nil else p end
              end.compact
              if name && (name = name.strip.downcase) && name.length > 0
                params[name] = (value || '').strip
              end
            else
              first = false
            end
          end
        end
        params
      else
	if block_given? then yield field_name else default end
      end
    end

    def massage_match_args(name, value)
      case name
      when String
        name = /^#{Regexp.escape(Field.name_strip(name))}$/i
      when Regexp
      else
        raise ArgumentError,
          "name not a Regexp or String: #{name.class}:#{name.inspect}"
      end
      case value
      when String
        value = Regexp.new(Regexp.escape(value), Regexp::IGNORECASE)
      when Regexp
      else
        raise ArgumentError, "value not a Regexp or String"
      end
      yield(name, value)
    end
  end
end