File: headers.rb

package info (click to toggle)
ruby-faraday 2.14.0-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 2,008 kB
  • sloc: ruby: 6,509; sh: 10; makefile: 8
file content (150 lines) | stat: -rw-r--r-- 3,473 bytes parent folder | download | duplicates (2)
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
# frozen_string_literal: true

module Faraday
  module Utils
    # A case-insensitive Hash that preserves the original case of a header
    # when set.
    #
    # Adapted from Rack::Utils::HeaderHash
    class Headers < ::Hash
      def self.from(value)
        new(value)
      end

      def self.allocate
        new_self = super
        new_self.initialize_names
        new_self
      end

      def initialize(hash = nil)
        super()
        @names = {}
        update(hash || {})
      end

      def initialize_names
        @names = {}
      end

      # on dup/clone, we need to duplicate @names hash
      def initialize_copy(other)
        super
        @names = other.names.dup
      end

      # need to synchronize concurrent writes to the shared KeyMap
      keymap_mutex = Mutex.new

      # symbol -> string mapper + cache
      KeyMap = Hash.new do |map, key|
        value = if key.respond_to?(:to_str)
                  key
                else
                  key.to_s.split('_') # user_agent: %w(user agent)
                     .each(&:capitalize!) # => %w(User Agent)
                     .join('-') # => "User-Agent"
                end
        keymap_mutex.synchronize { map[key] = value }
      end
      KeyMap[:etag] = 'ETag'

      def [](key)
        key = KeyMap[key]
        super(key) || super(@names[key.downcase])
      end

      def []=(key, val)
        key = KeyMap[key]
        key = (@names[key.downcase] ||= key)
        # join multiple values with a comma
        val = val.to_ary.join(', ') if val.respond_to?(:to_ary)
        super(key, val)
      end

      def fetch(key, ...)
        key = KeyMap[key]
        key = @names.fetch(key.downcase, key)
        super(key, ...)
      end

      def delete(key)
        key = KeyMap[key]
        key = @names[key.downcase]
        return unless key

        @names.delete key.downcase
        super(key)
      end

      def dig(key, *rest)
        key = KeyMap[key]
        key = @names.fetch(key.downcase, key)
        super(key, *rest)
      end

      def include?(key)
        @names.include? key.downcase
      end

      alias has_key? include?
      alias member? include?
      alias key? include?

      def merge!(other)
        other.each { |k, v| self[k] = v }
        self
      end

      alias update merge!

      def merge(other)
        hash = dup
        hash.merge! other
      end

      def replace(other)
        clear
        @names.clear
        update other
        self
      end

      def to_hash
        {}.update(self)
      end

      def parse(header_string)
        return unless header_string && !header_string.empty?

        headers = header_string.split("\r\n")

        # Find the last set of response headers.
        start_index = headers.rindex { |x| x.start_with?('HTTP/') } || 0
        last_response = headers.slice(start_index, headers.size)

        last_response
          .tap { |a| a.shift if a.first.start_with?('HTTP/') }
          .map { |h| h.split(/:\s*/, 2) } # split key and value
          .reject { |p| p[0].nil? } # ignore blank lines
          .each { |key, value| add_parsed(key, value) }
      end

      protected

      attr_reader :names

      private

      # Join multiple values with a comma.
      def add_parsed(key, value)
        if key?(key)
          self[key] = self[key].to_s
          self[key] << ', ' << value
        else
          self[key] = value
        end
      end
    end
  end
end