File: headers.rb

package info (click to toggle)
ruby-ftw 0.0.49-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 548 kB
  • sloc: ruby: 1,922; makefile: 5
file content (156 lines) | stat: -rw-r--r-- 4,627 bytes parent folder | download | duplicates (4)
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
require "ftw/namespace"
require "ftw/crlf"

# HTTP Headers
#
# See RFC2616 section 4.2: <http://tools.ietf.org/html/rfc2616#section-4.2>
#
# Section 14.44 says Field Names in the header are case-insensitive, so
# this library always forces field names to be lowercase. This includes
# get() calls.
#
#    headers.set("HELLO", "world")
#    headers.get("hello")   # ===> "world"
#
class FTW::HTTP::Headers
  include Enumerable
  include FTW::CRLF

  private

  # Make a new headers container.
  #
  # @param [Hash, optional] a hash of headers to start with.
  def initialize(headers={})
    super()
    @headers = headers
  end # def initialize

  # Set a header field to a specific value.
  # Any existing value(s) for this field are destroyed.
  #
  # @param [String] the name of the field to set
  # @param [String or Array] the value of the field to set
  def set(field, value)
    @headers[field.downcase] = value
  end # def set

  alias_method :[]=, :set

  # Does this header include this field name?
  # @return [true, false]
  def include?(field)
    @headers.include?(field.downcase)
  end # def include?

  # Add a header field with a value.
  #
  # If this field already exists, another value is added.
  # If this field does not already exist, it is set.
  def add(field, value)
    field = field.downcase
    if @headers.include?(field)
      if @headers[field].is_a?(Array)
        @headers[field] << value
      else
        @headers[field] = [@headers[field], value]
      end
    else
      set(field, value)
    end
  end # def add

  # Removes a header entry. If the header has multiple values
  # (like X-Forwarded-For can), you can delete a specific entry
  # by passing the value of the header field to remove.
  #
  #     # Remove all X-Forwarded-For entries
  #     headers.remove("X-Forwarded-For") 
  #     # Remove a specific X-Forwarded-For entry
  #     headers.remove("X-Forwarded-For", "1.2.3.4")
  #
  # * If you remove a field that doesn't exist, no error will occur.
  # * If you remove a field value that doesn't exist, no error will occur.
  # * If you remove a field value that is the only value, it is the same as
  #   removing that field by name.
  def remove(field, value=nil)
    field = field.downcase
    if value.nil?
      # no value, given, remove the entire field.
      @headers.delete(field)
    else
      field_value = @headers[field]
      if field_value.is_a?(Array)
        # remove a specific value
        field_value.delete(value)
        # Down to a String again if there's only one value.
        if field_value.size == 1
          set(field, field_value.first)
        end
      else
        # Remove this field if the value matches
        if field_value == value
          remove(field)
        end
      end
    end
  end # def remove

  # Get a field value. 
  # 
  # @return [String] if there is only one value for this field
  # @return [Array] if there are multiple values for this field
  # @return [nil] if there are no values for this field
  def get(field)
    field = field.downcase
    return @headers[field]
  end # def get

  alias_method :[], :get

  # Iterate over headers. Given to the block are two arguments, the field name
  # and the field value. For fields with multiple values, you will receive
  # that same field name multiple times, like:
  #    yield "Host", "www.example.com"
  #    yield "X-Forwarded-For", "1.2.3.4"
  #    yield "X-Forwarded-For", "1.2.3.5"
  def each(&block)
    @headers.each do |field_name, field_value|
      if field_value.is_a?(Array)
        field_value.map { |value| yield field_name, value }
      else
        yield field_name, field_value
      end
    end
  end # end each

  # @return [Hash] String keys and values of String (field value) or Array (of String field values)
  def to_hash
    return @headers
  end # def to_hash

  # Serialize this object to a string in HTTP format described by RFC2616
  #
  # Example:
  #
  #     headers = FTW::HTTP::Headers.new
  #     headers.add("Host", "example.com")
  #     headers.add("X-Forwarded-For", "1.2.3.4")
  #     headers.add("X-Forwarded-For", "192.168.0.1")
  #     puts headers.to_s
  #
  #     # Result
  #     Host: example.com
  #     X-Forwarded-For: 1.2.3.4
  #     X-Forwarded-For: 192.168.0.1
  def to_s
    return @headers.collect { |name, value| "#{name}: #{value}" }.join(CRLF) + CRLF
  end # def to_s
  
  # Inspect this object
  def inspect
    return "#{self.class.name} <#{to_hash.inspect}>"
  end # def inspect

  public(:set, :[]=, :include?, :add, :remove, :get, :[], :each, :to_hash, :to_s, :inspect)
end # class FTW::HTTP::Headers