Class: CookieJar::Jar

Inherits:
Object
  • Object
show all
Defined in:
lib/cookiejar/jar.rb

Overview

A cookie store for client side usage.

  • Enforces cookie validity rules

  • Returns just the cookies valid for a given URI

  • Handles expiration of cookies

  • Allows for persistence of cookie data (with or without session)

Internal format:

Internally, the data structure is a set of nested hashes. Domain Level: At the domain level, the hashes are of individual domains, down-cased and without any leading period. For instance, imagine cookies for .foo.com, .bar.com, and .auth.bar.com:

{
  "foo.com"      : (host data),
  "bar.com"      : (host data),
  "auth.bar.com" : (host data)
}

Lookups are done both for the matching entry, and for an entry without the first segment up to the dot, ie. for /^.?[^.]+.(.*)$/. A lookup of auth.bar.com would match both bar.com and auth.bar.com, but not entries for com or www.auth.bar.com.

Host Level: Entries are in an hash, with keys of the path and values of a hash of cookie names to cookie object

{
  "/" : {"session" : (Cookie object), "cart_id" : (Cookie object)}
  "/protected" : {"authentication" : (Cookie Object)}
}

Paths are given a straight prefix string comparison to match. Further filters <secure, http only, ports> are not represented in this heirarchy.

Cookies returned are ordered solely by specificity (length) of the path.

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeJar

Create a new empty Jar



48
49
50
# File 'lib/cookiejar/jar.rb', line 48

def initialize
 @domains = {}
end

Class Method Details

.from_a(cookies) ⇒ CookieJar

Create a new Jar from an array of Cookie objects. Expired cookies will still be added to the archive, and conflicting cookies will be overwritten by the last cookie in the array.

Parameters:

  • cookies (Array<Cookie>)

    array of cookie objects

Returns:



179
180
181
182
183
184
185
# File 'lib/cookiejar/jar.rb', line 179

def self.from_a cookies
  jar = new
  cookies.each do |cookie|
    jar.add_cookie cookie
  end
  jar
end

.json_create(o) ⇒ CookieJar

Create a new Jar from a JSON-backed hash

Parameters:

  • o (Hash)

    the expanded JSON object

Returns:



163
164
165
166
167
168
169
170
171
# File 'lib/cookiejar/jar.rb', line 163

def self.json_create o
  if o.is_a? Hash
    o = o['cookies']
  end
  cookies = o.inject [] do |result, cookie_json|
    result << (Cookie.json_create cookie_json)
  end
  self.from_a cookies
end

Instance Method Details

Add a pre-existing cookie object to the jar.

Parameters:

  • cookie (Cookie)

    a pre-existing cookie object

Returns:

  • (Cookie)

    the cookie added to the store



126
127
128
129
130
# File 'lib/cookiejar/jar.rb', line 126

def add_cookie cookie
   domain_paths = find_or_add_domain_for_cookie cookie
  add_cookie_to_path domain_paths, cookie
  cookie
end

#expire_cookies(session = false) ⇒ Object

Look through the jar for any cookies which have passed their expiration date, or session cookies from a previous session

Parameters:

  • session (Boolean) (defaults to: false)

    whether session cookies should be expired, or just cookies past their expiration date.



192
193
194
195
196
197
198
199
200
201
202
# File 'lib/cookiejar/jar.rb', line 192

def expire_cookies session = false
  @domains.delete_if do |domain, paths|
    paths.delete_if do |path, cookies|
      cookies.delete_if do |cookie_name, cookie|
        cookie.expired? || (session && cookie.session?)
      end
      cookies.empty?
    end
    paths.empty?
  end
end

Given a request URI, return a string Cookie header.Cookies will be in order per RFC 2965 - sorted by longest path length, but otherwise unordered.

Parameters:

  • request_uri (String, URI)

    the address the HTTP request will be sent to

  • opts (Hash) (defaults to: { })

    options controlling returned cookies

Options Hash (opts):

  • :script (Boolean) — default: false

    Cookies marked HTTP-only will be ignored if true

Returns:

  • String value of the Cookie header which should be sent on the HTTP request



246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
# File 'lib/cookiejar/jar.rb', line 246

def get_cookie_header request_uri, opts = { }
	cookies = get_cookies request_uri, opts
	version = 0
	ver = [[],[]]
	cookies.each do |cookie|
	  ver[cookie.version] << cookie
 end
 if (ver[1].empty?)
   # can do a netscape-style cookie header, relish the opportunity
	  cookies.map do |cookie|
	    cookie.to_s
   end.join ";"
 else
   # build a RFC 2965-style cookie header. Split the cookies into
   # version 0 and 1 groups so that we can reuse the '$Version' header
   result = ''
   unless ver[0].empty?
     result << '$Version=0;'
     result << ver[0].map do |cookie|
       (cookie.to_s 1,false)
     end.join(';')
     # separate version 0 and 1 with a comma
     result << ','
    end
    result << '$Version=1;'
   ver[1].map do |cookie|
     result << (cookie.to_s 1,false)
    end
   result
  end
end

#get_cookies(request_uri, opts = { }) ⇒ Array<Cookie>

Given a request URI, return a sorted list of Cookie objects. Cookies will be in order per RFC 2965 - sorted by longest path length, but otherwise unordered.

Parameters:

  • request_uri (String, URI)

    the address the HTTP request will be sent to

  • opts (Hash) (defaults to: { })

    options controlling returned cookies

Options Hash (opts):

  • :script (Boolean) — default: false

    Cookies marked HTTP-only will be ignored if true

Returns:

  • (Array<Cookie>)

    cookies which should be sent in the HTTP request



214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
# File 'lib/cookiejar/jar.rb', line 214

def get_cookies request_uri, opts = { }
  uri = to_uri request_uri
  hosts = Cookie.compute_search_domains uri

  results = []
  hosts.each do |host|
    domain = find_domain host
    domain.each do |path, cookies|
      if uri.path.start_with? path
        results += cookies.values.select do |cookie|
          cookie.should_send? uri, opts[:script]
        end
      end
    end
  end
  #Sort by path length, longest first
  results.sort do |lhs, rhs|
    rhs.path.length <=> lhs.path.length
  end
end

Given a request URI and a literal Set-Cookie header value, attempt to add the cookie(s) to the cookie store.

Parameters:

  • request_uri (String, URI)

    the resource returning the header

  • cookie_header_value (String)

    the contents of the Set-Cookie

Returns:

  • (Cookie)

    which was created and stored

Raises:



59
60
61
62
63
64
# File 'lib/cookiejar/jar.rb', line 59

def set_cookie request_uri, cookie_header_values
  cookie_header_values.split(/, (?=[\w]+=)/).each do |cookie_header_value|
	cookie = Cookie.from_set_cookie request_uri, cookie_header_value
	add_cookie cookie
  end
end

#set_cookie2(request_uri, cookie_header_value) ⇒ Cookie

Given a request URI and a literal Set-Cookie2 header value, attempt to add the cookie to the cookie store.

Parameters:

  • request_uri (String, URI)

    the resource returning the header

  • cookie_header_value (String)

    the contents of the Set-Cookie2

Returns:

  • (Cookie)

    which was created and stored

Raises:



73
74
75
76
# File 'lib/cookiejar/jar.rb', line 73

def set_cookie2 request_uri, cookie_header_value
	cookie = Cookie.from_set_cookie2 request_uri, cookie_header_value
	add_cookie cookie
end

#set_cookies_from_headers(request_uri, http_headers) ⇒ Array<Cookie>?

Given a request URI and some HTTP headers, attempt to add the cookie(s) (from Set-Cookie or Set-Cookie2 headers) to the cookie store. If a cookie is defined (by equivalent name, domain, and path) via Set-Cookie and Set-Cookie2, the Set-Cookie version is ignored.

Parameters:

  • request_uri (String, URI)

    the resource returning the header

  • http_headers (Hash<String,[String,Array<String>]>)

    a Hash which may have a key of “Set-Cookie” or “Set-Cookie2”, and values of either strings or arrays of strings

Returns:

  • (Array<Cookie>, nil)

    the cookies created, or nil if none found.

Raises:

  • (InvalidCookieError)

    if one of the cookie headers contained invalid formatting or data



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
# File 'lib/cookiejar/jar.rb', line 90

def set_cookies_from_headers request_uri, http_headers
  set_cookie_key = http_headers.keys.detect { |k| /\ASet-Cookie\Z/i.match k }
  cookies = gather_header_values http_headers[set_cookie_key] do |value|
    begin
      Cookie.from_set_cookie request_uri, value
    rescue InvalidCookieError
    end
  end

  set_cookie2_key = http_headers.keys.detect { |k| /\ASet-Cookie2\Z/i.match k }
  cookies += gather_header_values(http_headers[set_cookie2_key]) do |value|
    begin
      Cookie.from_set_cookie2 request_uri, value
    rescue InvalidCookieError
    end
  end

  # build the list of cookies, using a Jar. Since Set-Cookie2 values
  # come second, they will replace the Set-Cookie versions.
  jar = Jar.new
  cookies.each do |cookie|
    jar.add_cookie cookie
  end
  cookies = jar.to_a

  # now add them all to our own store.
  cookies.each do |cookie|
    add_cookie cookie
  end
  cookies
end

#to_aArray<Cookie>

Return an array of all cookie objects in the jar

which have not yet been removed with expire_cookies

Returns:

  • (Array<Cookie>)

    all cookies. Includes any expired cookies



136
137
138
139
140
141
142
143
144
# File 'lib/cookiejar/jar.rb', line 136

def to_a
  result = []
  @domains.values.each do |paths|
    paths.values.each do |cookies|
      cookies.values.inject result, :<<
    end
   end
   result
end

#to_json(*a) ⇒ String

persistence of the cookie information

Parameters:

  • a (Array)

    options controlling output JSON text (usually a State and a depth)

Returns:

  • (String)

    JSON representation of object data



152
153
154
155
156
157
# File 'lib/cookiejar/jar.rb', line 152

def to_json *a
 {
   'json_class' => self.class.name,
   'cookies' => (to_a.to_json *a)
  }.to_json *a
end