File: //lib/ruby/vendor_ruby/mail/header.rb
# encoding: utf-8
# frozen_string_literal: true
require 'mail/constants'
require 'mail/utilities'
module Mail
# Provides access to a header object.
#
# ===Per RFC2822
#
# 2.2. Header Fields
#
# Header fields are lines composed of a field name, followed by a colon
# (":"), followed by a field body, and terminated by CRLF. A field
# name MUST be composed of printable US-ASCII characters (i.e.,
# characters that have values between 33 and 126, inclusive), except
# colon. A field body may be composed of any US-ASCII characters,
# except for CR and LF. However, a field body may contain CRLF when
# used in header "folding" and "unfolding" as described in section
# 2.2.3. All field bodies MUST conform to the syntax described in
# sections 3 and 4 of this standard.
class Header
include Enumerable
@@maximum_amount = 1000
# Large amount of headers in Email might create extra high CPU load
# Use this parameter to limit number of headers that will be parsed by
# mail library.
# Default: 1000
def self.maximum_amount
@@maximum_amount
end
def self.maximum_amount=(value)
@@maximum_amount = value
end
attr_reader :raw_source, :charset
# Creates a new header object.
#
# Accepts raw text or nothing. If given raw text will attempt to parse
# it and split it into the various fields, instantiating each field as
# it goes.
#
# If it finds a field that should be a structured field (such as content
# type), but it fails to parse it, it will simply make it an unstructured
# field and leave it alone. This will mean that the data is preserved but
# no automatic processing of that field will happen. If you find one of
# these cases, please make a patch and send it in, or at the least, send
# me the example so we can fix it.
def initialize(header_text = nil, charset = nil)
@charset = charset
@raw_source = ::Mail::Utilities.to_crlf(header_text).lstrip
split_header if header_text
end
def initialize_copy(original)
super
@fields = @fields.dup
@fields.map!(&:dup)
end
# Returns an array of all the fields in the header in order that they
# were read in.
def fields
@fields ||= FieldList.new
end
# 3.6. Field definitions
#
# It is important to note that the header fields are not guaranteed to
# be in a particular order. They may appear in any order, and they
# have been known to be reordered occasionally when transported over
# the Internet. However, for the purposes of this standard, header
# fields SHOULD NOT be reordered when a message is transported or
# transformed. More importantly, the trace header fields and resent
# header fields MUST NOT be reordered, and SHOULD be kept in blocks
# prepended to the message. See sections 3.6.6 and 3.6.7 for more
# information.
#
# Populates the fields container with Field objects in the order it
# receives them in.
#
# Acceps an array of field string values, for example:
#
# h = Header.new
# h.fields = ['From: mikel@me.com', 'To: bob@you.com']
def fields=(unfolded_fields)
@fields = Mail::FieldList.new
if unfolded_fields.size > self.class.maximum_amount
Kernel.warn "WARNING: More than #{self.class.maximum_amount} header fields; only using the first #{self.class.maximum_amount} and ignoring the rest"
unfolded_fields = unfolded_fields.slice(0...self.class.maximum_amount)
end
unfolded_fields.each do |field|
if field = Field.parse(field, charset)
@fields.add_field field
end
end
end
def errors
@fields.map(&:errors).flatten(1)
end
# 3.6. Field definitions
#
# The following table indicates limits on the number of times each
# field may occur in a message header as well as any special
# limitations on the use of those fields. An asterisk next to a value
# in the minimum or maximum column indicates that a special restriction
# appears in the Notes column.
#
# <snip table from 3.6>
#
# As per RFC, many fields can appear more than once, we will return a string
# of the value if there is only one header, or if there is more than one
# matching header, will return an array of values in order that they appear
# in the header ordered from top to bottom.
#
# Example:
#
# h = Header.new
# h.fields = ['To: mikel@me.com', 'X-Mail-SPAM: 15', 'X-Mail-SPAM: 20']
# h['To'] #=> 'mikel@me.com'
# h['X-Mail-SPAM'] #=> ['15', '20']
def [](name)
fields.get_field(Utilities.dasherize(name))
end
# Sets the FIRST matching field in the header to passed value, or deletes
# the FIRST field matched from the header if passed nil
#
# Example:
#
# h = Header.new
# h.fields = ['To: mikel@me.com', 'X-Mail-SPAM: 15', 'X-Mail-SPAM: 20']
# h['To'] = 'bob@you.com'
# h['To'] #=> 'bob@you.com'
# h['X-Mail-SPAM'] = '10000'
# h['X-Mail-SPAM'] # => ['15', '20', '10000']
# h['X-Mail-SPAM'] = nil
# h['X-Mail-SPAM'] # => nil
def []=(name, value)
name = name.to_s
if name.include?(Constants::COLON)
raise ArgumentError, "Header names may not contain a colon: #{name.inspect}"
end
name = Utilities.dasherize(name)
# Assign nil to delete the field
if value.nil?
fields.delete_field name
else
fields.add_field Field.new(name.to_s, value, charset)
# Update charset if specified in Content-Type
if name == 'content-type'
params = self[:content_type].parameters rescue nil
@charset = params[:charset] if params && params[:charset]
end
end
end
def charset=(val)
params = self[:content_type].parameters rescue nil
if params
if val
params[:charset] = val
else
params.delete(:charset)
end
end
@charset = val
end
def encoded
buffer = String.new
buffer.force_encoding('us-ascii') if buffer.respond_to?(:force_encoding)
fields.each do |field|
buffer << field.encoded
end
buffer
end
def to_s
encoded
end
def decoded
raise NoMethodError, 'Can not decode an entire header as there could be character set conflicts. Try calling #decoded on the various fields.'
end
def field_summary
fields.summary
end
# Returns true if the header has a Message-ID defined (empty or not)
def has_message_id?
fields.has_field? 'Message-ID'
end
# Returns true if the header has a Content-ID defined (empty or not)
def has_content_id?
fields.has_field? 'Content-ID'
end
# Returns true if the header has a Date defined (empty or not)
def has_date?
fields.has_field? 'Date'
end
# Returns true if the header has a MIME version defined (empty or not)
def has_mime_version?
fields.has_field? 'Mime-Version'
end
private
# Splits an unfolded and line break cleaned header into individual field
# strings.
def split_header
self.fields = @raw_source.split(Constants::HEADER_SPLIT)
end
# Enumerable support. Yield each field in order.
def each(&block)
fields.each(&block)
end
end
end