-
Notifications
You must be signed in to change notification settings - Fork 49
/
Copy pathhash_store.rb
160 lines (142 loc) · 4 KB
/
hash_store.rb
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
# :markup: markdown
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