File: set_cookie_parser.rb

package info (click to toggle)
ruby-httpx 1.7.2-3
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 1,816 kB
  • sloc: ruby: 12,209; makefile: 4
file content (143 lines) | stat: -rw-r--r-- 3,758 bytes parent folder | download
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
# frozen_string_literal: true

require "strscan"
require "time"

module HTTPX
  module Plugins::Cookies
    module SetCookieParser
      # Whitespace.
      RE_WSP = /[ \t]+/.freeze

      # A pattern that matches a cookie name or attribute name which may
      # be empty, capturing trailing whitespace.
      RE_NAME = /(?!#{RE_WSP})[^,;\\"=]*/.freeze

      RE_BAD_CHAR = /([\x00-\x20\x7F",;\\])/.freeze

      # A pattern that matches the comma in a (typically date) value.
      RE_COOKIE_COMMA = /,(?=#{RE_WSP}?#{RE_NAME}=)/.freeze

      module_function

      def scan_dquoted(scanner)
        s = +""

        until scanner.eos?
          break if scanner.skip(/"/)

          if scanner.skip(/\\/)
            s << scanner.getch
          elsif scanner.scan(/[^"\\]+/)
            s << scanner.matched
          end
        end

        s
      end

      def scan_value(scanner, comma_as_separator = false)
        value = +""

        until scanner.eos?
          if scanner.scan(/[^,;"]+/)
            value << scanner.matched
          elsif scanner.skip(/"/)
            # RFC 6265 2.2
            # A cookie-value may be DQUOTE'd.
            value << scan_dquoted(scanner)
          elsif scanner.check(/;/)
            break
          elsif comma_as_separator && scanner.check(RE_COOKIE_COMMA)
            break
          else
            value << scanner.getch
          end
        end

        value.rstrip!
        value
      end

      def scan_name_value(scanner, comma_as_separator = false)
        name = scanner.scan(RE_NAME)
        name.rstrip! if name

        if scanner.skip(/=/)
          value = scan_value(scanner, comma_as_separator)
        else
          scan_value(scanner, comma_as_separator)
          value = nil
        end
        [name, value]
      end

      def call(set_cookie)
        scanner = StringScanner.new(set_cookie)

        # RFC 6265 4.1.1 & 5.2
        until scanner.eos?
          start = scanner.pos
          len = nil

          scanner.skip(RE_WSP)

          name, value = scan_name_value(scanner, true)
          value = nil if name && name.empty?

          attrs = {}

          until scanner.eos?
            if scanner.skip(/,/)
              # The comma is used as separator for concatenating multiple
              # values of a header.
              len = (scanner.pos - 1) - start
              break
            elsif scanner.skip(/;/)
              scanner.skip(RE_WSP)

              aname, avalue = scan_name_value(scanner, true)

              next if (aname.nil? || aname.empty?) || value.nil?

              aname.downcase!

              case aname
              when "expires"
                next unless avalue

                # RFC 6265 5.2.1
                (avalue = Time.parse(avalue)) || next
              when "max-age"
                next unless avalue
                # RFC 6265 5.2.2
                next unless /\A-?\d+\z/.match?(avalue)

                avalue = Integer(avalue)
              when "domain"
                # RFC 6265 5.2.3
                # An empty value SHOULD be ignored.
                next if avalue.nil? || avalue.empty?
              when "path"
                # RFC 6265 5.2.4
                # A relative path must be ignored rather than normalizing it
                # to "/".
                next unless avalue && avalue.start_with?("/")
              when "secure", "httponly"
                # RFC 6265 5.2.5, 5.2.6
                avalue = true
              end
              attrs[aname] = avalue
            end
          end

          len ||= scanner.pos - start

          next if len > Cookie::MAX_LENGTH

          yield(name, value, attrs) if name && !name.empty? && value
        end
      end
    end
  end
end