aboutsummaryrefslogtreecommitdiffstats
path: root/actionpack/lib/action_dispatch/http/headers.rb
blob: 6ab913bfd0fe24029b5646e393c0e3c0f4b35916 (plain) (blame)
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
# frozen_string_literal: true

module ActionDispatch
  module Http
    # Provides access to the request's HTTP headers from the environment.
    #
    #   env     = { "CONTENT_TYPE" => "text/plain", "HTTP_USER_AGENT" => "curl/7.43.0" }
    #   headers = ActionDispatch::Http::Headers.from_hash(env)
    #   headers["Content-Type"] # => "text/plain"
    #   headers["User-Agent"] # => "curl/7.43.0"
    #
    # Also note that when headers are mapped to CGI-like variables by the Rack
    # server, both dashes and underscores are converted to underscores. This
    # ambiguity cannot be resolved at this stage anymore. Both underscores and
    # dashes have to be interpreted as if they were originally sent as dashes.
    #
    #   # GET / HTTP/1.1
    #   # ...
    #   # User-Agent: curl/7.43.0
    #   # X_Custom_Header: token
    #
    #   headers["X_Custom_Header"] # => nil
    #   headers["X-Custom-Header"] # => "token"
    class Headers
      CGI_VARIABLES = Set.new(%W[
        AUTH_TYPE
        CONTENT_LENGTH
        CONTENT_TYPE
        GATEWAY_INTERFACE
        HTTPS
        PATH_INFO
        PATH_TRANSLATED
        QUERY_STRING
        REMOTE_ADDR
        REMOTE_HOST
        REMOTE_IDENT
        REMOTE_USER
        REQUEST_METHOD
        SCRIPT_NAME
        SERVER_NAME
        SERVER_PORT
        SERVER_PROTOCOL
        SERVER_SOFTWARE
      ]).freeze

      HTTP_HEADER = /\A[A-Za-z0-9-]+\z/

      include Enumerable

      def self.from_hash(hash)
        new ActionDispatch::Request.new hash
      end

      def initialize(request) # :nodoc:
        @req = request
      end

      # Returns the value for the given key mapped to @env.
      def [](key)
        @req.get_header env_name(key)
      end

      # Sets the given value for the key mapped to @env.
      def []=(key, value)
        @req.set_header env_name(key), value
      end

      # Add a value to a multivalued header like Vary or Accept-Encoding.
      def add(key, value)
        @req.add_header env_name(key), value
      end

      def key?(key)
        @req.has_header? env_name(key)
      end
      alias :include? :key?

      DEFAULT = Object.new # :nodoc:

      # Returns the value for the given key mapped to @env.
      #
      # If the key is not found and an optional code block is not provided,
      # raises a <tt>KeyError</tt> exception.
      #
      # If the code block is provided, then it will be run and
      # its result returned.
      def fetch(key, default = DEFAULT)
        @req.fetch_header(env_name(key)) do
          return default unless default == DEFAULT
          return yield if block_given?
          raise KeyError, key
        end
      end

      def each(&block)
        @req.each_header(&block)
      end

      # Returns a new Http::Headers instance containing the contents of
      # <tt>headers_or_env</tt> and the original instance.
      def merge(headers_or_env)
        headers = @req.dup.headers
        headers.merge!(headers_or_env)
        headers
      end

      # Adds the contents of <tt>headers_or_env</tt> to original instance
      # entries; duplicate keys are overwritten with the values from
      # <tt>headers_or_env</tt>.
      def merge!(headers_or_env)
        headers_or_env.each do |key, value|
          @req.set_header env_name(key), value
        end
      end

      def env; @req.env.dup; end

      private
        # Converts an HTTP header name to an environment variable name if it is
        # not contained within the headers hash.
        def env_name(key)
          key = key.to_s
          if HTTP_HEADER.match?(key)
            key = key.upcase.tr("-", "_")
            key = "HTTP_" + key unless CGI_VARIABLES.include?(key)
          end
          key
        end
    end
  end
end