File: hash_store.rb

package info (click to toggle)
ruby-http-cookie 1.0.5-1
  • links: PTS, VCS
  • area: main
  • in suites: bookworm, forky, sid, trixie
  • size: 280 kB
  • sloc: ruby: 3,356; makefile: 2
file content (161 lines) | stat: -rw-r--r-- 4,125 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
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
# :markup: markdown
require 'http/cookie_jar'

class HTTP::CookieJar
  # A store class that uses a hash-based cookie store.
  #
  # In this store, cookies that share the same name, domain and path
  # will overwrite each other regardless of the `for_domain` flag
  # value.  This store is built after the storage model described in
  # RFC 6265 5.3 where there is no mention of how the host-only-flag
  # affects in storing cookies.  On the other hand, in MozillaStore
  # two cookies with the same name, domain and path coexist as long as
  # they differ in the `for_domain` flag value, which means they need
  # to be expired individually.
  class HashStore < AbstractStore
    def default_options
      {
        :gc_threshold => HTTP::Cookie::MAX_COOKIES_TOTAL / 20
      }
    end

    # :call-seq:
    #   new(**options)
    #
    # Generates a hash based cookie store.
    #
    # Available option keywords are as below:
    #
    # :gc_threshold
    # : GC threshold; A GC happens when this many times cookies have
    # been stored (default: `HTTP::Cookie::MAX_COOKIES_TOTAL / 20`)
    def initialize(options = nil)
      super

      @jar = {
      # hostname => {
      #   path => {
      #     name => cookie,
      #     ...
      #   },
      #   ...
      # },
      # ...
      }

      @gc_index = 0
    end

    # The copy constructor.  This store class supports cloning.
    def initialize_copy(other)
      @jar = Marshal.load(Marshal.dump(other.instance_variable_get(:@jar)))
    end

    def add(cookie)
      path_cookies = ((@jar[cookie.domain] ||= {})[cookie.path] ||= {})
      path_cookies[cookie.name] = cookie
      cleanup if (@gc_index += 1) >= @gc_threshold
      self
    end

    def delete(cookie)
      path_cookies = ((@jar[cookie.domain] ||= {})[cookie.path] ||= {})
      path_cookies.delete(cookie.name)
      self
    end

    def each(uri = nil) # :yield: cookie
      now = Time.now
      if uri
        tpath = uri.path
        @jar.each { |domain, paths|
          paths.each { |path, hash|
            next unless HTTP::Cookie.path_match?(path, tpath)
            hash.delete_if { |name, cookie|
              if cookie.expired?(now)
                true
              else
                if cookie.valid_for_uri?(uri)
                  cookie.accessed_at = now
                  yield cookie
                end
                false
              end
            }
          }
        }
      else
        synchronize {
          @jar.each { |domain, paths|
            paths.each { |path, hash|
              hash.delete_if { |name, cookie|
                if cookie.expired?(now)
                  true
                else
                  yield cookie
                  false
                end
              }
            }
          }
        }
      end
      self
    end

    def clear
      @jar.clear
      self
    end

    def cleanup(session = false)
      now = Time.now
      all_cookies = []

      synchronize {
        break if @gc_index == 0

        @jar.each { |domain, paths|
          domain_cookies = []

          paths.each { |path, hash|
            hash.delete_if { |name, cookie|
              if cookie.expired?(now) || (session && cookie.session?)
                true
              else
                domain_cookies << cookie
                false
              end
            }
          }

          if (debt = domain_cookies.size - HTTP::Cookie::MAX_COOKIES_PER_DOMAIN) > 0
            domain_cookies.sort_by!(&:created_at)
            domain_cookies.slice!(0, debt).each { |cookie|
              delete(cookie)
            }
          end

          all_cookies.concat(domain_cookies)
        }

        if (debt = all_cookies.size - HTTP::Cookie::MAX_COOKIES_TOTAL) > 0
          all_cookies.sort_by!(&:created_at)
          all_cookies.slice!(0, debt).each { |cookie|
            delete(cookie)
          }
        end

        @jar.delete_if { |domain, paths|
          paths.delete_if { |path, hash|
            hash.empty?
          }
          paths.empty?
        }

        @gc_index = 0
      }
      self
    end
  end
end