cookiejar-0.3.2/0000755000004100000410000000000012315032213013502 5ustar www-datawww-datacookiejar-0.3.2/Rakefile0000644000004100000410000000131512315032213015147 0ustar www-datawww-datarequire 'rake' require 'rake/clean' require 'rake/packagetask' require 'yard' require 'yard/rake/yardoc_task' require 'fileutils' include FileUtils # Default Rake task is to run all tests task :default => :test CLEAN << Rake::FileList['doc/**', '.yardoc'] #Yard YARD::Rake::YardocTask.new do |t| t.files = ['lib/**/*.rb'] # optional t.options = ['--title', 'CookieJar, a HTTP Client Cookie Parsing Library', '--main', 'README.markdown', '--files', 'LICENSE'] end begin require 'rspec/core/rake_task' RSpec::Core::RakeTask.new do |t| # t.libs << 'lib' # t.pattern = 'test/**/*_test.rb' end task :test => :spec rescue LoadError puts "Warning: unable to load rspec tasks" end cookiejar-0.3.2/contributors.json0000644000004100000410000000055312315032213017135 0ustar www-datawww-data[ { "author": { "github": "http://github.com/secobarbital", "name": "Seggy Umboh" }, "contributions": [ "widen supported IP addresses", "fix case-sensitivity issue with HTTP headers", "correct issue when running under Ruby 1.8.x", "made Jar act more like a browser (dropping cookies w/o exception)" ] } ] cookiejar-0.3.2/spec/0000755000004100000410000000000012315032213014434 5ustar www-datawww-datacookiejar-0.3.2/spec/jar_spec.rb0000644000004100000410000002243112315032213016551 0ustar www-datawww-datarequire 'cookiejar' require 'yaml' require 'rubygems' include CookieJar describe Jar do describe '.setCookie' do it "should allow me to set a cookie" do jar = Jar.new jar.set_cookie 'http://foo.com/', 'foo=bar' end it "should allow me to set multiple cookies" do jar = Jar.new jar.set_cookie 'http://foo.com/', 'foo=bar' jar.set_cookie 'http://foo.com/', 'bar=baz' jar.set_cookie 'http://auth.foo.com/', 'foo=bar' jar.set_cookie 'http://auth.foo.com/', 'auth=135121...;domain=foo.com' end it "should allow me to set multiple cookies in 1 header" do jar = Jar.new jar.set_cookie 'http://foo.com/', 'my_cookie=123456; Domain=foo.com; expires=Thu, 31 Dec 2037 23:59:59 GMT; Path=/, other_cookie=helloworld; Domain=foo.com; expires=Thu, 31 Dec 2037 23:59:59 GMT, last_cookie=098765' end end describe '.get_cookies' do it "should let me read back cookies which are set" do jar = Jar.new jar.set_cookie 'http://foo.com/', 'foo=bar' jar.set_cookie 'http://foo.com/', 'bar=baz' jar.set_cookie 'http://auth.foo.com/', 'foo=bar' jar.set_cookie 'http://auth.foo.com/', 'auth=135121...;domain=foo.com' jar.get_cookies('http://foo.com/').should have(3).items end it "should let me read back a multiple cookies from 1 header" do jar = Jar.new jar.set_cookie 'http://foo.com/', 'my_cookie=123456; Domain=foo.com; expires=Thu, 31 Dec 2037 23:59:59 GMT; Path=/, other_cookie=helloworld; Domain=foo.com; expires=Thu, 31 Dec 2037 23:59:59 GMT, last_cookie=098765' jar.get_cookie_header('http://foo.com/').should == 'last_cookie=098765;my_cookie=123456;other_cookie=helloworld' end it "should return cookies longest path first" do jar = Jar.new uri = 'http://foo.com/a/b/c/d' jar.set_cookie uri, 'a=bar' jar.set_cookie uri, 'b=baz;path=/a/b/c/d' jar.set_cookie uri, 'c=bar;path=/a/b' jar.set_cookie uri, 'd=bar;path=/a/' cookies = jar.get_cookies(uri) cookies.should have(4).items cookies[0].name.should == 'b' cookies[1].name.should == 'a' cookies[2].name.should == 'c' cookies[3].name.should == 'd' end it "should not return expired cookies" do jar = Jar.new uri = 'http://localhost/' jar.set_cookie uri, 'foo=bar;expires=Wednesday, 09-Nov-99 23:12:40 GMT' cookies = jar.get_cookies(uri) cookies.should have(0).items end end describe '.get_cookie_headers' do it "should return cookie headers" do jar = Jar.new uri = 'http://foo.com/a/b/c/d' jar.set_cookie uri, 'a=bar' jar.set_cookie uri, 'b=baz;path=/a/b/c/d' cookie_headers = jar.get_cookie_header uri cookie_headers.should == "b=baz;a=bar" end it "should handle a version 1 cookie" do jar = Jar.new uri = 'http://foo.com/a/b/c/d' jar.set_cookie uri, 'a=bar' jar.set_cookie uri, 'b=baz;path=/a/b/c/d' jar.set_cookie2 uri, 'c=baz;Version=1;path="/"' cookie_headers = jar.get_cookie_header uri cookie_headers.should == '$Version=0;b=baz;$Path="/a/b/c/d";a=bar;$Path="/a/b/c/",$Version=1;c=baz;$Path="/"' end end describe '.add_cookie' do it "should let me add a pre-existing cookie" do jar = Jar.new cookie = Cookie.from_set_cookie 'http://localhost/', 'foo=bar' jar.add_cookie cookie end end describe '.to_a' do it "should return me an array of all cookie objects" do uri = 'http://foo.com/a/b/c/d' jar = Jar.new jar.set_cookie uri, 'a=bar;expires=Wednesday, 09-Nov-99 23:12:40 GMT' jar.set_cookie uri, 'b=baz;path=/a/b/c/d' jar.set_cookie uri, 'c=bar;path=/a/b' jar.set_cookie uri, 'd=bar;path=/a/' jar.set_cookie 'http://localhost/', 'foo=bar' jar.to_a.should have(5).items end end describe '.expire_cookies' do it "should expire cookies which are no longer valid" do uri = 'http://foo.com/a/b/c/d' jar = Jar.new jar.set_cookie uri, 'a=bar;expires=Wednesday, 09-Nov-99 23:12:40 GMT' jar.set_cookie uri, 'b=baz;path=/a/b/c/d;expires=Wednesday, 01-Nov-2028 12:00:00 GMT' jar.set_cookie uri, 'c=bar;path=/a/b' jar.set_cookie uri, 'd=bar;path=/a/' jar.set_cookie 'http://localhost/', 'foo=bar' jar.to_a.should have(5).items jar.expire_cookies jar.to_a.should have(4).items end it "should let me expire all session cookies" do uri = 'http://foo.com/a/b/c/d' jar = Jar.new jar.set_cookie uri, 'a=bar;expires=Wednesday, 09-Nov-99 23:12:40 GMT' jar.set_cookie uri, 'b=baz;path=/a/b/c/d;expires=Wednesday, 01-Nov-2028 12:00:00 GMT' jar.set_cookie uri, 'c=bar;path=/a/b' jar.set_cookie uri, 'd=bar;path=/a/' jar.set_cookie 'http://localhost/', 'foo=bar' jar.to_a.should have(5).items jar.expire_cookies true jar.to_a.should have(1).items end end describe '#set_cookies_from_headers' do it "should handle a Set-Cookie header" do jar = Jar.new cookies = jar.set_cookies_from_headers 'http://localhost/', { 'Set-Cookie' => 'foo=bar' } cookies.should have(1).items jar.to_a.should have(1).items end it "should handle a set-cookie header" do jar = Jar.new cookies = jar.set_cookies_from_headers 'http://localhost/', { 'set-cookie' => 'foo=bar' } cookies.should have(1).items jar.to_a.should have(1).items end it "should handle multiple Set-Cookie headers" do jar = Jar.new cookies = jar.set_cookies_from_headers 'http://localhost/', { 'Set-Cookie' => ['foo=bar','bar=baz'] } cookies.should have(2).items jar.to_a.should have(2).items end it "should handle a Set-Cookie2 header" do jar = Jar.new cookies = jar.set_cookies_from_headers 'http://localhost/', { 'Set-Cookie2' => 'foo=bar;Version=1' } cookies.should have(1).items jar.to_a.should have(1).items end it "should handle a set-cookie2 header" do jar = Jar.new cookies = jar.set_cookies_from_headers 'http://localhost/', { 'set-cookie2' => 'foo=bar;Version=1' } cookies.should have(1).items jar.to_a.should have(1).items end it "should handle multiple Set-Cookie2 headers" do jar = Jar.new cookies = jar.set_cookies_from_headers 'http://localhost/', { 'Set-Cookie2' => ['foo=bar;Version=1','bar=baz;Version=1'] } cookies.should have(2).items jar.to_a.should have(2).items end it "should handle mixed distinct Set-Cookie and Set-Cookie2 headers" do jar = Jar.new cookies = jar.set_cookies_from_headers 'http://localhost/', { 'Set-Cookie' => 'foo=bar', 'Set-Cookie2' => 'bar=baz;Version=1' } cookies.should have(2).items jar.to_a.should have(2).items end it "should handle overlapping Set-Cookie and Set-Cookie2 headers" do jar = Jar.new cookies = jar.set_cookies_from_headers 'http://localhost/', { 'Set-Cookie' => ['foo=bar','bar=baz'], 'Set-Cookie2' => 'foo=bar;Version=1' } cookies.should have(2).items jar.to_a.should have(2).items # and has the version 1 cookie cookies.find do |cookie| cookie.name == 'foo' end.version.should == 1 end it "should silently drop invalid cookies" do jar = Jar.new cookies = jar.set_cookies_from_headers 'http://localhost/', { 'Set-Cookie' => ['foo=bar','bar=baz;domain=.foo.com'] } cookies.should have(1).items jar.to_a.should have(1).items end end begin require 'json' describe ".to_json" do it "should serialize cookies to JSON" do c = Cookie.from_set_cookie 'https://localhost/', 'foo=bar;secure;expires=Wed, 01-Nov-2028 12:00:00 GMT' jar = Jar.new jar.add_cookie c json = jar.to_json json.should be_a String end end describe ".json_create" do it "should deserialize a JSON array to a jar" do json = "[{\"name\":\"foo\",\"value\":\"bar\",\"domain\":\"localhost.local\",\"path\":\"\\/\",\"created_at\":\"2009-09-11 12:51:03 -0600\",\"expiry\":\"2028-11-01 12:00:00 GMT\",\"secure\":true}]" array = JSON.parse json jar = Jar.json_create array jar.get_cookies('https://localhost/').should have(1).items end it "should deserialize a JSON hash to a jar" do json = "{\"cookies\":[{\"name\":\"foo\",\"value\":\"bar\",\"domain\":\"localhost.local\",\"path\":\"\\/\",\"created_at\":\"2009-09-11 12:51:03 -0600\",\"expiry\":\"2028-11-01 12:00:00 GMT\",\"secure\":true}]}" hash = JSON.parse json jar = Jar.json_create hash jar.get_cookies('https://localhost/').should have(1).items end it "should automatically deserialize to a jar" do json = "{\"json_class\":\"CookieJar::Jar\",\"cookies\":[{\"name\":\"foo\",\"value\":\"bar\",\"domain\":\"localhost.local\",\"path\":\"\\/\",\"created_at\":\"2009-09-11 12:51:03 -0600\",\"expiry\":\"2028-11-01 12:00:00 GMT\",\"secure\":true}]}" jar = JSON.parse json, :create_additions => true jar.get_cookies('https://localhost/').should have(1).items end end rescue LoadError it "does not appear the JSON library is installed" do raise 'please install the JSON lirbary' end end end cookiejar-0.3.2/spec/cookie_spec.rb0000644000004100000410000001605712315032213017255 0ustar www-datawww-datarequire 'cookiejar' require 'rubygems' include CookieJar FOO_URL = 'http://localhost/foo' AMMO_URL = 'http://localhost/ammo' NETSCAPE_SPEC_SET_COOKIE_HEADERS = [['CUSTOMER=WILE_E_COYOTE; path=/; expires=Wednesday, 09-Nov-99 23:12:40 GMT', FOO_URL], ['PART_NUMBER=ROCKET_LAUNCHER_0001; path=/', FOO_URL], ['SHIPPING=FEDEX; path=/foo', FOO_URL], ['PART_NUMBER=ROCKET_LAUNCHER_0001; path=/', FOO_URL], ['PART_NUMBER=RIDING_ROCKET_0023; path=/ammo', AMMO_URL]] describe Cookie do describe "#from_set_cookie" do it "should handle cookies from the netscape spec" do NETSCAPE_SPEC_SET_COOKIE_HEADERS.each do |value| header, url = *value cookie = Cookie.from_set_cookie url, header end end it "should give back the input names and values" do cookie = Cookie.from_set_cookie 'http://localhost/', 'foo=bar' cookie.name.should == 'foo' cookie.value.should == 'bar' end it "should normalize domain names" do cookie = Cookie.from_set_cookie 'http://localhost/', 'foo=Bar;domain=LoCaLHoSt.local' cookie.domain.should == '.localhost.local' end it "should accept non-normalized .local" do cookie = Cookie.from_set_cookie 'http://localhost/', 'foo=bar;domain=.local' cookie.domain.should == '.local' end it "should accept secure cookies" do cookie = Cookie.from_set_cookie 'https://www.google.com/a/blah', 'GALX=RgmSftjnbPM;Path=/a/;Secure' cookie.name.should == 'GALX' cookie.secure.should be_true end end describe "#from_set_cookie2" do it "should give back the input names and values" do cookie = Cookie.from_set_cookie2 'http://localhost/', 'foo=bar;Version=1' cookie.name.should == 'foo' cookie.value.should == 'bar' end it "should normalize domain names" do cookie = Cookie.from_set_cookie2 'http://localhost/', 'foo=Bar;domain=LoCaLHoSt.local;Version=1' cookie.domain.should == '.localhost.local' end it "should accept non-normalized .local" do cookie = Cookie.from_set_cookie2 'http://localhost/', 'foo=bar;domain=.local;Version=1' cookie.domain.should == '.local' end it "should accept secure cookies" do cookie = Cookie.from_set_cookie2 'https://www.google.com/a/blah', 'GALX=RgmSftjnbPM;Path="/a/";Secure;Version=1' cookie.name.should == 'GALX' cookie.path.should == '/a/' cookie.secure.should be_true end it "should fail on unquoted paths" do lambda do Cookie.from_set_cookie2 'https://www.google.com/a/blah', 'GALX=RgmSftjnbPM;Path=/a/;Secure;Version=1' end.should raise_error InvalidCookieError end it "should accept quoted values" do cookie = Cookie.from_set_cookie2 'http://localhost/', 'foo="bar";Version=1' cookie.name.should == 'foo' cookie.value.should == '"bar"' end it "should accept poorly chosen names" do cookie = Cookie.from_set_cookie2 'http://localhost/', 'Version=mine;Version=1' cookie.name.should == 'Version' cookie.value.should == 'mine' end it "should accept quoted parameter values" do cookie = Cookie.from_set_cookie2 'http://localhost/', 'foo=bar;Version="1"' end it "should honor the discard and max-age parameters" do cookie = Cookie.from_set_cookie2 'http://localhost/', 'f=b;max-age=100;discard;Version=1' cookie.should be_session cookie.should_not be_expired cookie = Cookie.from_set_cookie2 'http://localhost/', 'f=b;max-age=100;Version=1' cookie.should_not be_session cookie = Cookie.from_set_cookie2 'http://localhost/', 'f=b;Version=1' cookie.should be_session end it "should handle quotable quotes" do cookie = Cookie.from_set_cookie2 'http://localhost/', 'f="\"";Version=1' cookie.value.should eql '"\""' end it "should handle quotable apostrophes" do cookie = Cookie.from_set_cookie2 'http://localhost/', 'f="\;";Version=1' cookie.value.should eql '"\;"' end end describe '#decoded_value' do it "should leave normal values alone" do cookie = Cookie.from_set_cookie2 'http://localhost/', 'f=b;Version=1' cookie.decoded_value.should eql 'b' end it "should attempt to unencode quoted values" do cookie = Cookie.from_set_cookie2 'http://localhost/', 'f="\"b";Version=1' cookie.value.should eql '"\"b"' cookie.decoded_value.should eql '"b' end end describe '#to_s' do it "should handle a simple cookie" do cookie = Cookie.from_set_cookie 'http://localhost/', 'f=b' cookie.to_s.should == 'f=b' cookie.to_s(1).should == '$Version=0;f=b;$Path="/"' end it "should report an explicit domain" do cookie = Cookie.from_set_cookie2 'http://localhost/', 'f=b;Version=1;Domain=.local' cookie.to_s(1).should == '$Version=1;f=b;$Path="/";$Domain=.local' end it "should return specified ports" do cookie = Cookie.from_set_cookie2 'http://localhost/', 'f=b;Version=1;Port="80,443"' cookie.to_s(1).should == '$Version=1;f=b;$Path="/";$Port="80,443"' end it "should handle specified paths" do cookie = Cookie.from_set_cookie 'http://localhost/bar/', 'f=b;path=/bar/' cookie.to_s.should == 'f=b' cookie.to_s(1).should == '$Version=0;f=b;$Path="/bar/"' end it "should omit $Version header when asked" do cookie = Cookie.from_set_cookie 'http://localhost/', 'f=b' cookie.to_s(1,false).should == 'f=b;$Path="/"' end end describe '#should_send?' do it "should not send if ports do not match" do cookie = Cookie.from_set_cookie2 'http://localhost/', 'f=b;Version=1;Port="80"' cookie.should_send?("http://localhost/", false).should be_true cookie.should_send?("https://localhost/", false).should be_false end end begin require 'json' describe ".to_json" do it "should serialize a cookie to JSON" do c = Cookie.from_set_cookie 'https://localhost/', 'foo=bar;secure;expires=Fri, September 11 2009 18:10:00 -0700' json = c.to_json json.should be_a String end end describe ".json_create" do it "should deserialize JSON to a cookie" do json = "{\"name\":\"foo\",\"value\":\"bar\",\"domain\":\"localhost.local\",\"path\":\"\\/\",\"created_at\":\"2009-09-11 12:51:03 -0600\",\"expiry\":\"2009-09-11 19:10:00 -0600\",\"secure\":true}" hash = JSON.parse json c = Cookie.json_create hash CookieValidation.validate_cookie 'https://localhost/', c end it "should automatically deserialize to a cookie" do json = "{\"json_class\":\"CookieJar::Cookie\",\"name\":\"foo\",\"value\":\"bar\",\"domain\":\"localhost.local\",\"path\":\"\\/\",\"created_at\":\"2009-09-11 12:51:03 -0600\",\"expiry\":\"2009-09-11 19:10:00 -0600\",\"secure\":true}" c = JSON.parse json, :create_additions => true c.should be_a Cookie CookieValidation.validate_cookie 'https://localhost/', c end end rescue LoadError it "does not appear the JSON library is installed" do raise "please install the JSON library" end end end cookiejar-0.3.2/spec/cookie_validation_spec.rb0000644000004100000410000002555312315032213021470 0ustar www-datawww-datarequire 'cookiejar' require 'rubygems' include CookieJar describe CookieValidation do describe "#validate_cookie" do localaddr = 'http://localhost/foo/bar/' it "should fail if version unset" do lambda do unversioned = Cookie.from_set_cookie localaddr, 'foo=bar' unversioned.instance_variable_set :@version, nil CookieValidation.validate_cookie localaddr, unversioned end.should raise_error InvalidCookieError end it "should fail if the path is more specific" do lambda do subdirred = Cookie.from_set_cookie localaddr, 'foo=bar;path=/foo/bar/baz' # validate_cookie localaddr, subdirred end.should raise_error InvalidCookieError end it "should fail if the path is different than the request" do lambda do difdirred = Cookie.from_set_cookie localaddr, 'foo=bar;path=/baz/' # validate_cookie localaddr, difdirred end.should raise_error InvalidCookieError end it "should fail if the domain has no dots" do lambda do nodot = Cookie.from_set_cookie 'http://zero/', 'foo=bar;domain=zero' # validate_cookie 'http://zero/', nodot end.should raise_error InvalidCookieError end it "should fail for explicit localhost" do lambda do localhost = Cookie.from_set_cookie localaddr, 'foo=bar;domain=localhost' # validate_cookie localaddr, localhost end.should raise_error InvalidCookieError end it "should fail for mismatched domains" do lambda do foobar = Cookie.from_set_cookie 'http://www.foo.com/', 'foo=bar;domain=bar.com' # validate_cookie 'http://www.foo.com/', foobar end.should raise_error InvalidCookieError end it "should fail for domains more than one level up" do lambda do xyz = Cookie.from_set_cookie 'http://x.y.z.com/', 'foo=bar;domain=z.com' # validate_cookie 'http://x.y.z.com/', xyz end.should raise_error InvalidCookieError end it "should fail for setting subdomain cookies" do lambda do subdomain = Cookie.from_set_cookie 'http://foo.com/', 'foo=bar;domain=auth.foo.com' # validate_cookie 'http://foo.com/', subdomain end.should raise_error InvalidCookieError end it "should handle a normal implicit internet cookie" do normal = Cookie.from_set_cookie 'http://foo.com/', 'foo=bar' CookieValidation.validate_cookie('http://foo.com/', normal).should be_true end it "should handle a normal implicit localhost cookie" do localhost = Cookie.from_set_cookie 'http://localhost/', 'foo=bar' CookieValidation.validate_cookie('http://localhost/', localhost).should be_true end it "should handle an implicit IP address cookie" do ipaddr = Cookie.from_set_cookie 'http://127.0.0.1/', 'foo=bar' CookieValidation.validate_cookie('http://127.0.0.1/', ipaddr).should be_true end it "should handle an explicit domain on an internet site" do explicit = Cookie.from_set_cookie 'http://foo.com/', 'foo=bar;domain=.foo.com' CookieValidation.validate_cookie('http://foo.com/', explicit).should be_true end it "should handle setting a cookie explicitly on a superdomain" do superdomain = Cookie.from_set_cookie 'http://auth.foo.com/', 'foo=bar;domain=.foo.com' CookieValidation.validate_cookie('http://foo.com/', superdomain).should be_true end it "should handle explicitly setting a cookie" do explicit = Cookie.from_set_cookie 'http://foo.com/bar/', 'foo=bar;path=/bar/' CookieValidation.validate_cookie('http://foo.com/bar/', explicit) end it "should handle setting a cookie on a higher path" do higher = Cookie.from_set_cookie 'http://foo.com/bar/baz/', 'foo=bar;path=/bar/' CookieValidation.validate_cookie('http://foo.com/bar/baz/', higher) end end describe '#cookie_base_path' do it "should leave '/' alone" do CookieValidation.cookie_base_path('/').should == '/' end it "should strip off everything after the last '/'" do CookieValidation.cookie_base_path('/foo/bar/baz').should == '/foo/bar/' end it "should handle query parameters and fragments with slashes" do CookieValidation.cookie_base_path('/foo/bar?query=a/b/c#fragment/b/c').should == '/foo/' end it "should handle URI objects" do CookieValidation.cookie_base_path(URI.parse('http://www.foo.com/bar/')).should == '/bar/' end it "should preserve case" do CookieValidation.cookie_base_path("/BaR/").should == '/BaR/' end end describe '#determine_cookie_path' do it "should use the requested path when none is specified for the cookie" do CookieValidation.determine_cookie_path('http://foo.com/', nil).should == '/' CookieValidation.determine_cookie_path('http://foo.com/bar/baz', '').should == '/bar/' end it "should handle URI objects" do CookieValidation.determine_cookie_path(URI.parse('http://foo.com/bar/'), '').should == '/bar/' end it "should handle Cookie objects" do cookie = Cookie.from_set_cookie('http://foo.com/', "name=value;path=/") CookieValidation.determine_cookie_path('http://foo.com/', cookie).should == '/' end it "should ignore the request when a path is specified" do CookieValidation.determine_cookie_path('http://foo.com/ignorable/path', '/path/').should == '/path/' end end describe '#compute_search_domains' do it "should handle subdomains" do CookieValidation.compute_search_domains('http://www.auth.foo.com/').should == ['www.auth.foo.com', '.www.auth.foo.com', '.auth.foo.com'] end it "should handle root domains" do CookieValidation.compute_search_domains('http://foo.com/').should == ['foo.com', '.foo.com'] end it "should handle hexadecimal TLDs" do CookieValidation.compute_search_domains('http://tiny.cc/').should == ['tiny.cc', '.tiny.cc'] end it "should handle IP addresses" do CookieValidation.compute_search_domains('http://127.0.0.1/').should == ['127.0.0.1'] end it "should handle local addresses" do CookieValidation.compute_search_domains('http://zero/').should == ['zero.local', '.zero.local', '.local'] end end describe '#determine_cookie_domain' do it "should add a dot to the front of domains" do CookieValidation.determine_cookie_domain('http://foo.com/', 'foo.com').should == '.foo.com' end it "should not add a second dot if one present" do CookieValidation.determine_cookie_domain('http://foo.com/', '.foo.com').should == '.foo.com' end it "should handle Cookie objects" do c = Cookie.from_set_cookie('http://foo.com/', "foo=bar;domain=foo.com") CookieValidation.determine_cookie_domain('http://foo.com/', c).should == '.foo.com' end it "should handle URI objects" do CookieValidation.determine_cookie_domain(URI.parse('http://foo.com/'), '.foo.com').should == '.foo.com' end it "should use an exact hostname when no domain specified" do CookieValidation.determine_cookie_domain('http://foo.com/', '').should == 'foo.com' end it "should leave IPv4 addresses alone" do CookieValidation.determine_cookie_domain('http://127.0.0.1/', '127.0.0.1').should == '127.0.0.1' end it "should leave IPv6 addresses alone" do ['2001:db8:85a3::8a2e:370:7334', '::ffff:192.0.2.128'].each do |value| CookieValidation.determine_cookie_domain("http://[#{value}]/", value).should == value end end end describe "#effective_host" do it "should leave proper domains the same" do ['google.com', 'www.google.com', 'google.com.'].each do |value| CookieValidation.effective_host(value).should == value end end it "should handle a URI object" do CookieValidation.effective_host(URI.parse('http://example.com/')).should == 'example.com' end it "should add a local suffix on unqualified hosts" do CookieValidation.effective_host('localhost').should == 'localhost.local' end it "should leave IPv4 addresses alone" do CookieValidation.effective_host('127.0.0.1').should == '127.0.0.1' end it "should leave IPv6 addresses alone" do ['2001:db8:85a3::8a2e:370:7334', ':ffff:192.0.2.128'].each do |value| CookieValidation.effective_host(value).should == value end end it "should lowercase addresses" do CookieValidation.effective_host('FOO.COM').should == 'foo.com' end end describe '#match_domains' do it "should handle exact matches" do CookieValidation.domains_match('localhost.local', 'localhost.local').should == 'localhost.local' CookieValidation.domains_match('foo.com', 'foo.com').should == 'foo.com' CookieValidation.domains_match('127.0.0.1', '127.0.0.1').should == '127.0.0.1' CookieValidation.domains_match('::ffff:192.0.2.128', '::ffff:192.0.2.128').should == '::ffff:192.0.2.128' end it "should handle matching a superdomain" do CookieValidation.domains_match('.foo.com', 'auth.foo.com').should == '.foo.com' CookieValidation.domains_match('.y.z.foo.com', 'x.y.z.foo.com').should == '.y.z.foo.com' end it "should not match superdomains, or illegal domains" do CookieValidation.domains_match('.z.foo.com', 'x.y.z.foo.com').should be_nil CookieValidation.domains_match('foo.com', 'com').should be_nil end it "should not match domains with and without a dot suffix together" do CookieValidation.domains_match('foo.com.', 'foo.com').should be_nil end end describe '#hostname_reach' do it "should find the next highest subdomain" do {'www.google.com' => 'google.com', 'auth.corp.companyx.com' => 'corp.companyx.com'}.each do |entry| CookieValidation.hostname_reach(entry[0]).should == entry[1] end end it "should handle domains with suffixed dots" do CookieValidation.hostname_reach('www.google.com.').should == 'google.com.' end it "should return nil for a root domain" do CookieValidation.hostname_reach('github.com').should be_nil end it "should return 'local' for a local domain" do ['foo.local', 'foo.local.'].each do |hostname| CookieValidation.hostname_reach(hostname).should == 'local' end end it "should handle mixed-case '.local'" do CookieValidation.hostname_reach('foo.LOCAL').should == 'local' end it "should return nil for an IPv4 address" do CookieValidation.hostname_reach('127.0.0.1').should be_nil end it "should return nil for IPv6 addresses" do ['2001:db8:85a3::8a2e:370:7334', '::ffff:192.0.2.128'].each do |value| CookieValidation.hostname_reach(value).should be_nil end end end describe '#parse_set_cookie' do it "should max out at 2038 on 32bit systems" do CookieValidation.parse_set_cookie("TRACK_USER_P=98237480810003948000782774;expires=Sat, 30-Jun-2040 05:39:49 GMT;path=/")[:expires_at].to_i.should >= 0x7FFFFFFF end end endcookiejar-0.3.2/lib/0000755000004100000410000000000012315032213014250 5ustar www-datawww-datacookiejar-0.3.2/lib/cookiejar.rb0000644000004100000410000000006212315032213016541 0ustar www-datawww-datarequire 'cookiejar/cookie' require 'cookiejar/jar'cookiejar-0.3.2/lib/cookiejar/0000755000004100000410000000000012315032213016216 5ustar www-datawww-datacookiejar-0.3.2/lib/cookiejar/cookie_validation.rb0000644000004100000410000003314312315032213022232 0ustar www-datawww-datarequire 'cgi' require 'uri' module CookieJar # Represents a set of cookie validation errors class InvalidCookieError < StandardError # [Array] the specific validation issues encountered attr_reader :messages # Create a new instance # @param [String, Array] the validation issue(s) encountered def initialize message if message.is_a? Array @messages = message message = message.join ', ' else @messages = [message] end super message end end # Contains logic to parse and validate cookie headers module CookieValidation module PATTERN include URI::REGEXP::PATTERN TOKEN = '[^(),\/<>@;:\\\"\[\]?={}\s]+' VALUE1 = "([^;]*)" IPADDR = "#{IPV4ADDR}|#{IPV6ADDR}" BASE_HOSTNAME = "(?:#{DOMLABEL}\\.)(?:((?:(?:#{DOMLABEL}\\.)+(?:#{TOPLABEL}\\.?))|local))" QUOTED_PAIR = "\\\\[\\x00-\\x7F]" LWS = "\\r\\n(?:[ \\t]+)" # TEXT="[\\t\\x20-\\x7E\\x80-\\xFF]|(?:#{LWS})" QDTEXT="[\\t\\x20-\\x21\\x23-\\x7E\\x80-\\xFF]|(?:#{LWS})" QUOTED_TEXT = "\\\"(?:#{QDTEXT}|#{QUOTED_PAIR})*\\\"" VALUE2 = "#{TOKEN}|#{QUOTED_TEXT}" end BASE_HOSTNAME = /#{PATTERN::BASE_HOSTNAME}/ BASE_PATH = /\A((?:[^\/?#]*\/)*)/ IPADDR = /\A#{PATTERN::IPV4ADDR}\Z|\A#{PATTERN::IPV6ADDR}\Z/ HDN = /\A#{PATTERN::HOSTNAME}\Z/ TOKEN = /\A#{PATTERN::TOKEN}\Z/ PARAM1 = /\A(#{PATTERN::TOKEN})(?:=#{PATTERN::VALUE1})?\Z/ PARAM2 = Regexp.new "(#{PATTERN::TOKEN})(?:=(#{PATTERN::VALUE2}))?(?:\\Z|;)", '', 'n' # TWO_DOT_DOMAINS = /\A\.(com|edu|net|mil|gov|int|org)\Z/ # Converts the input object to a URI (if not already a URI) # # @param [String, URI] request_uri URI we are normalizing # @param [URI] URI representation of input string, or original URI def self.to_uri request_uri (request_uri.is_a? URI)? request_uri : (URI.parse request_uri) end # Converts an input cookie or uri to a string representing the path. # Assume strings are already paths # # @param [String, URI, Cookie] object containing the path # @return [String] path information def self.to_path uri_or_path if (uri_or_path.is_a? URI) || (uri_or_path.is_a? Cookie) uri_or_path.path else uri_or_path end end # Converts an input cookie or uri to a string representing the domain. # Assume strings are already domains. Value may not be an effective host. # # @param [String, URI, Cookie] object containing the domain # @return [String] domain information. def self.to_domain uri_or_domain if uri_or_domain.is_a? URI uri_or_domain.host elsif uri_or_domain.is_a? Cookie uri_or_domain.domain else uri_or_domain end end # Compare a tested domain against the base domain to see if they match, or # if the base domain is reachable. # # @param [String] tested_domain domain to be tested against # @param [String] base_domain new domain being tested # @return [String,nil] matching domain on success, nil on failure def self.domains_match tested_domain, base_domain base = effective_host base_domain search_domains = compute_search_domains_for_host base result = search_domains.find do |domain| domain == tested_domain end end # Compute the reach of a hostname (RFC 2965, section 1) # Determines the next highest superdomain # # @param [String,URI,Cookie] hostname hostname, or object holding hostname # @return [String,nil] next highest hostname, or nil if none def self.hostname_reach hostname host = to_domain hostname host = host.downcase match = BASE_HOSTNAME.match host if match match[1] end end # Compute the base of a path, for default cookie path assignment # # @param [String, URI, Cookie] path, or object holding path # @return base path (all characters up to final '/') def self.cookie_base_path path BASE_PATH.match(to_path path)[1] end # Processes cookie path data using the following rules: # Paths are separated by '/' characters, and accepted values are truncated # to the last '/' character. If no path is specified in the cookie, a path # value will be taken from the request URI which was used for the site. # # Note that this will not attempt to detect a mismatch of the request uri domain # and explicitly specified cookie path # # @param [String,URI] request URI yielding this cookie # @param [String] path on cookie def self.determine_cookie_path request_uri, cookie_path uri = to_uri request_uri cookie_path = to_path cookie_path if cookie_path == nil || cookie_path.empty? cookie_path = cookie_base_path uri.path end cookie_path end # Given a URI, compute the relevant search domains for pre-existing # cookies. This includes all the valid dotted forms for a named or IP # domains. # # @param [String, URI] request_uri requested uri # @return [Array] all cookie domain values which would match the # requested uri def self.compute_search_domains request_uri uri = to_uri request_uri host = uri.host compute_search_domains_for_host host end # Given a host, compute the relevant search domains for pre-existing # cookies # # @param [String] host host being requested # @return [Array] all cookie domain values which would match the # requested uri def self.compute_search_domains_for_host host host = effective_host host result = [host] unless host =~ IPADDR result << ".#{host}" base = hostname_reach host if base result << ".#{base}" end end result end # Processes cookie domain data using the following rules: # Domains strings of the form .foo.com match 'foo.com' and all immediate # subdomains of 'foo.com'. Domain strings specified of the form 'foo.com' are # modified to '.foo.com', and as such will still apply to subdomains. # # Cookies without an explicit domain will have their domain value taken directly # from the URL, and will _NOT_ have any leading dot applied. For example, a request # to http://foo.com/ will cause an entry for 'foo.com' to be created - which applies # to foo.com but no subdomain. # # Note that this will not attempt to detect a mismatch of the request uri domain # and explicitly specified cookie domain # # @param [String, URI] request_uri originally requested URI # @param [String] cookie domain value # @return [String] effective host def self.determine_cookie_domain request_uri, cookie_domain uri = to_uri request_uri domain = to_domain cookie_domain if domain == nil || domain.empty? domain = effective_host uri.host else domain = domain.downcase if domain =~ IPADDR || domain.start_with?('.') domain else ".#{domain}" end end end # Compute the effective host (RFC 2965, section 1) # # Has the added additional logic of searching for interior dots specifically, and # matches colons to prevent .local being suffixed on IPv6 addresses # # @param [String, URI] host_or_uridomain name, or absolute URI # @return [String] effective host per RFC rules def self.effective_host host_or_uri hostname = to_domain host_or_uri hostname = hostname.downcase if /.[\.:]./.match(hostname) || hostname == '.local' hostname else hostname + '.local' end end # Check whether a cookie meets all of the rules to be created, based on # its internal settings and the URI it came from. # # @param [String,URI] request_uri originally requested URI # @param [Cookie] cookie object # @param [true] will always return true on success # @raise [InvalidCookieError] on failures, containing all validation errors def self.validate_cookie request_uri, cookie uri = to_uri request_uri request_host = effective_host uri.host request_path = uri.path request_secure = (uri.scheme == 'https') cookie_host = cookie.domain cookie_path = cookie.path errors = [] # From RFC 2965, Section 3.3.2 Rejecting Cookies # A user agent rejects (SHALL NOT store its information) if the # Version attribute is missing. Note that the legacy Set-Cookie # directive will result in an implicit version 0. unless cookie.version errors << "Version missing" end # The value for the Path attribute is not a prefix of the request-URI unless request_path.start_with? cookie_path errors << "Path is not a prefix of the request uri path" end unless cookie_host =~ IPADDR || #is an IPv4 or IPv6 address cookie_host =~ /.\../ || #contains an embedded dot cookie_host == '.local' #is the domain cookie for local addresses errors << "Domain format is illegal" end # The effective host name that derives from the request-host does # not domain-match the Domain attribute. # # The request-host is a HDN (not IP address) and has the form HD, # where D is the value of the Domain attribute, and H is a string # that contains one or more dots. unless domains_match cookie_host, uri errors << "Domain is inappropriate based on request URI hostname" end # The Port attribute has a "port-list", and the request-port was # not in the list. unless cookie.ports.nil? || cookie.ports.length != 0 unless cookie.ports.find_index uri.port errors << "Ports list does not contain request URI port" end end raise InvalidCookieError.new(errors) unless errors.empty? # Note: 'secure' is not explicitly defined as an SSL channel, and no # test is defined around validity and the 'secure' attribute true end # Break apart a traditional (non RFC 2965) cookie value into its core # components. This does not do any validation, or defaulting of values # based on requested URI # # @param [String] set_cookie_value a Set-Cookie header formatted cookie # definition # @return [Hash] Contains the parsed values of the cookie def self.parse_set_cookie set_cookie_value args = { } params=set_cookie_value.split(/;\s*/) first=true params.each do |param| result = PARAM1.match param if !result raise InvalidCookieError.new "Invalid cookie parameter in cookie '#{set_cookie_value}'" end key = result[1].downcase.to_sym keyvalue = result[2] if first args[:name] = result[1] args[:value] = keyvalue first = false else case key when :expires begin args[:expires_at] = Time.parse keyvalue rescue ArgumentError raise unless $!.message == "time out of range" args[:expires_at] = Time.at(0x7FFFFFFF) end when *[:domain, :path] args[key] = keyvalue when :secure args[:secure] = true when :httponly args[:http_only] = true else raise InvalidCookieError.new "Unknown cookie parameter '#{key}'" end end end args[:version] = 0 args end # Parse a RFC 2965 value and convert to a literal string def self.value_to_string value if /\A"(.*)"\Z/.match value value = $1 value = value.gsub(/\\(.)/, '\1') else value end end # Attempt to decipher a partially decoded version of text cookie values def self.decode_value value if /\A"(.*)"\Z/.match value value_to_string value else CGI.unescape value end end # Break apart a RFC 2965 cookie value into its core components. # This does not do any validation, or defaulting of values # based on requested URI # # @param [String] set_cookie_value a Set-Cookie2 header formatted cookie # definition # @return [Hash] Contains the parsed values of the cookie def self.parse_set_cookie2 set_cookie_value args = { } first = true index = 0 begin md = PARAM2.match set_cookie_value[index..-1] if md.nil? || md.offset(0).first != 0 raise InvalidCookieError.new "Invalid Set-Cookie2 header '#{set_cookie_value}'" end index+=md.offset(0)[1] key = md[1].downcase.to_sym keyvalue = md[2] || md[3] if first args[:name] = md[1] args[:value] = keyvalue first = false else keyvalue = value_to_string keyvalue case key when *[:comment,:commenturl,:domain,:path] args[key] = keyvalue when *[:discard,:secure] args[key] = true when :httponly args[:http_only] = true when :"max-age" args[:max_age] = keyvalue.to_i when :version args[:version] = keyvalue.to_i when :port # must be in format '"port,port"' ports = keyvalue.split(/,\s*/) args[:ports] = ports.map do |portstr| portstr.to_i end else raise InvalidCookieError.new "Unknown cookie parameter '#{key}'" end end end until md.post_match.empty? # if our last match in the scan failed if args[:version] != 1 raise InvalidCookieError.new "Set-Cookie2 declares a non RFC2965 version cookie" end args end end end cookiejar-0.3.2/lib/cookiejar/cookie.rb0000644000004100000410000002256412315032213020025 0ustar www-datawww-datarequire 'time' require 'uri' require 'cookiejar/cookie_validation' module CookieJar # Cookie is an immutable object which defines the data model of a HTTP Cookie. # The data values within the cookie may be different from the # values described in the literal cookie declaration. # Specifically, the 'domain' and 'path' values may be set to defaults # based on the requested resource that resulted in the cookie being set. class Cookie # [String] The name of the cookie. attr_reader :name # [String] The value of the cookie, without any attempts at decoding. attr_reader :value # [String] The domain scope of the cookie. Follows the RFC 2965 # 'effective host' rules. A 'dot' prefix indicates that it applies both # to the non-dotted domain and child domains, while no prefix indicates # that only exact matches of the domain are in scope. attr_reader :domain # [String] The path scope of the cookie. The cookie applies to URI paths # that prefix match this value. attr_reader :path # [Boolean] The secure flag is set to indicate that the cookie should # only be sent securely. Nearly all HTTP User Agent implementations assume # this to mean that the cookie should only be sent over a # SSL/TLS-protected connection attr_reader :secure # [Boolean] Popular browser extension to mark a cookie as invisible # to code running within the browser, such as JavaScript attr_reader :http_only # [Fixnum] Version indicator, currently either # * 0 for netscape cookies # * 1 for RFC 2965 cookies attr_reader :version # [String] RFC 2965 field for indicating comment (or a location) # describing the cookie to a usesr agent. attr_reader :comment, :comment_url # [Boolean] RFC 2965 field for indicating session lifetime for a cookie attr_reader :discard # [Array, nil] RFC 2965 port scope for the cookie. If not nil, # indicates specific ports on the HTTP server which should receive this # cookie if contacted. attr_reader :ports # [Time] Time when this cookie was first evaluated and created. attr_reader :created_at # Evaluate when this cookie will expire. Uses the original cookie fields # for a max age or expires # # @return [Time, nil] Time of expiry, if this cookie has an expiry set def expires_at if @expiry.nil? || @expiry.is_a?(Time) @expiry else @created_at + @expiry end end # Indicates whether the cookie is currently considered valid # # @param [Time] time to compare against, or 'now' if omitted # @return [Boolean] def expired? (time = Time.now) expires_at != nil && time > expires_at end # Indicates whether the cookie will be considered invalid after the end # of the current user session # @return [Boolean] def session? @expiry == nil || @discard end # Create a cookie based on an absolute URI and the string value of a # 'Set-Cookie' header. # # @param request_uri [String, URI] HTTP/HTTPS absolute URI of request. # This is used to fill in domain and port if missing from the cookie, # and to perform appropriate validation. # @param set_cookie_value [String] HTTP value for the Set-Cookie header. # @return [Cookie] created from the header string and request URI # @raise [InvalidCookieError] on validation failure(s) def self.from_set_cookie request_uri, set_cookie_value args = CookieJar::CookieValidation.parse_set_cookie set_cookie_value args[:domain] = CookieJar::CookieValidation. determine_cookie_domain request_uri, args[:domain] args[:path] = CookieJar::CookieValidation. determine_cookie_path request_uri, args[:path] cookie = Cookie.new args CookieJar::CookieValidation.validate_cookie request_uri, cookie cookie end # Create a cookie based on an absolute URI and the string value of a # 'Set-Cookie2' header. # # @param request_uri [String, URI] HTTP/HTTPS absolute URI of request. # This is used to fill in domain and port if missing from the cookie, # and to perform appropriate validation. # @param set_cookie_value [String] HTTP value for the Set-Cookie2 header. # @return [Cookie] created from the header string and request URI # @raise [InvalidCookieError] on validation failure(s) def self.from_set_cookie2 request_uri, set_cookie_value args = CookieJar::CookieValidation.parse_set_cookie2 set_cookie_value args[:domain] = CookieJar::CookieValidation. determine_cookie_domain request_uri, args[:domain] args[:path] = CookieJar::CookieValidation. determine_cookie_path request_uri, args[:path] cookie = Cookie.new args CookieJar::CookieValidation.validate_cookie request_uri, cookie cookie end # Returns cookie in a format appropriate to send to a server. # # @param [FixNum] 0 version, 0 for Netscape-style cookies, 1 for # RFC2965-style. # @param [Boolean] true prefix, for RFC2965, whether to prefix with # "$Version=;". Ignored for Netscape-style cookies def to_s ver=0, prefix=true case ver when 0 "#{name}=#{value}" when 1 # we do not need to encode path; the only characters required to be # quoted must be escaped in URI str = prefix ? "$Version=#{version};" : "" str << "#{name}=#{value};$Path=\"#{path}\"" if domain.start_with? '.' str << ";$Domain=#{domain}" end if ports str << ";$Port=\"#{ports.join ','}\"" end str end end # Return a hash representation of the cookie. def to_hash result = { :name => @name, :value => @value, :domain => @domain, :path => @path, :created_at => @created_at } { :expiry => @expiry, :secure => (true if @secure), :http_only => (true if @http_only), :version => (@version if version != 0), :comment => @comment, :comment_url => @comment_url, :discard => (true if @discard), :ports => @ports }.each do |name, value| result[name] = value if value end result end # Determine if a cookie should be sent given a request URI along with # other options. # # This currently ignores domain. # # @param uri [String, URI] the requested page which may need to receive # this cookie # @param script [Boolean] indicates that cookies with the 'httponly' # extension should be ignored # @return [Boolean] whether this cookie should be sent to the server def should_send? request_uri, script uri = CookieJar::CookieValidation.to_uri request_uri # cookie path must start with the uri, it must not be a secure cookie # being sent over http, and it must not be a http_only cookie sent to # a script path_match = uri.path.start_with? @path secure_match = !(@secure && uri.scheme == 'http') script_match = !(script && @http_only) expiry_match = !expired? ports_match = ports.nil? || (ports.include? uri.port) path_match && secure_match && script_match && expiry_match && ports_match end def decoded_value CookieJar::CookieValidation::decode_value value end # Return a JSON 'object' for the various data values. Allows for # persistence of the cookie information # # @param [Array] a options controlling output JSON text # (usually a State and a depth) # @return [String] JSON representation of object data def to_json *a to_hash.merge(:json_class => self.class.name).to_json(*a) end # Given a Hash representation of a JSON document, create a local cookie # from the included data. # # @param [Hash] o JSON object of array data # @return [Cookie] cookie formed from JSON data def self.json_create o params = o.inject({}) do |hash, (key, value)| hash[key.to_sym] = value hash end params[:version] ||= 0 params[:created_at] = Time.parse params[:created_at] if params[:expiry].is_a? String params[:expires_at] = Time.parse params[:expiry] else params[:max_age] = params[:expiry] end params.delete :expiry self.new params end # Compute the cookie search domains for a given request URI # This will be the effective host of the request uri, along with any # possibly matching dot-prefixed domains # # @param request_uri [String, URI] address being requested # @return [Array] String domain matches def self.compute_search_domains request_uri CookieValidation.compute_search_domains request_uri end protected # Call {from_set_cookie} to create a new Cookie instance def initialize args @created_at, @name, @value, @domain, @path, @secure, @http_only, @version, @comment, @comment_url, @discard, @ports \ = args.values_at \ :created_at, :name, :value, :domain, :path, :secure, :http_only, :version, :comment, :comment_url, :discard, :ports @created_at ||= Time.now @expiry = args[:max_age] || args[:expires_at] @secure ||= false @http_only ||= false @discard ||= false if @ports.is_a? Integer @ports = [@ports] end end end end cookiejar-0.3.2/lib/cookiejar/jar.rb0000644000004100000410000002441112315032213017321 0ustar www-datawww-datarequire 'cookiejar/cookie' module CookieJar # 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 are not represented in this # heirarchy. # # Cookies returned are ordered solely by specificity (length) of the # path. class Jar # Create a new empty Jar def initialize @domains = {} end # Given a request URI and a literal Set-Cookie header value, attempt to # add the cookie(s) to the cookie store. # # @param [String, URI] request_uri the resource returning the header # @param [String] cookie_header_value the contents of the Set-Cookie # @return [Cookie] which was created and stored # @raise [InvalidCookieError] if the cookie header did not validate 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 # Given a request URI and a literal Set-Cookie2 header value, attempt to # add the cookie to the cookie store. # # @param [String, URI] request_uri the resource returning the header # @param [String] cookie_header_value the contents of the Set-Cookie2 # @return [Cookie] which was created and stored # @raise [InvalidCookieError] if the cookie header did not validate def set_cookie2 request_uri, cookie_header_value cookie = Cookie.from_set_cookie2 request_uri, cookie_header_value add_cookie cookie end # 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. # # @param [String, URI] request_uri the resource returning the header # @param [Hash]>] http_headers a Hash # which may have a key of "Set-Cookie" or "Set-Cookie2", and values of # either strings or arrays of strings # @return [Array,nil] the cookies created, or nil if none found. # @raise [InvalidCookieError] if one of the cookie headers contained # invalid formatting or data 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 # Add a pre-existing cookie object to the jar. # # @param [Cookie] cookie a pre-existing cookie object # @return [Cookie] the cookie added to the store def add_cookie cookie domain_paths = find_or_add_domain_for_cookie cookie add_cookie_to_path domain_paths, cookie cookie end # Return an array of all cookie objects in the jar # # @return [Array] all cookies. Includes any expired cookies # which have not yet been removed with expire_cookies def to_a result = [] @domains.values.each do |paths| paths.values.each do |cookies| cookies.values.inject result, :<< end end result end # Return a JSON 'object' for the various data values. Allows for # persistence of the cookie information # # @param [Array] a options controlling output JSON text # (usually a State and a depth) # @return [String] JSON representation of object data def to_json *a { 'json_class' => self.class.name, 'cookies' => to_a.to_json(*a) }.to_json(*a) end # Create a new Jar from a JSON-backed hash # # @param o [Hash] the expanded JSON object # @return [CookieJar] a new CookieJar instance def self.json_create o if o.is_a? String o = JSON.parse(o) end 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 # 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. # # @param [Array] cookies array of cookie objects # @return [CookieJar] a new CookieJar instance def self.from_a cookies jar = new cookies.each do |cookie| jar.add_cookie cookie end jar end # Look through the jar for any cookies which have passed their expiration # date, or session cookies from a previous session # # @param session [Boolean] whether session cookies should be expired, # or just cookies past their expiration date. 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 sorted list of Cookie objects. Cookies # will be in order per RFC 2965 - sorted by longest path length, but # otherwise unordered. # # @param [String, URI] request_uri the address the HTTP request will be # sent to # @param [Hash] opts options controlling returned cookies # @option opts [Boolean] :script (false) Cookies marked HTTP-only will be ignored # if true # @return [Array] cookies which should be sent in the HTTP request 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, return a string Cookie header.Cookies will be in # order per RFC 2965 - sorted by longest path length, but otherwise # unordered. # # @param [String, URI] request_uri the address the HTTP request will be # sent to # @param [Hash] opts options controlling returned cookies # @option opts [Boolean] :script (false) Cookies marked HTTP-only will be ignored # if true # @return String value of the Cookie header which should be sent on the # HTTP request 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 protected def gather_header_values http_header_value, &block result = [] if http_header_value.is_a? Array http_header_value.each do |value| result << block.call(value) end elsif http_header_value.is_a? String result << block.call(http_header_value) end result.compact end def to_uri request_uri (request_uri.is_a? URI)? request_uri : (URI.parse request_uri) end def find_domain host @domains[host] || {} end def find_or_add_domain_for_cookie cookie @domains[cookie.domain] ||= {} end def add_cookie_to_path paths, cookie path_entry = (paths[cookie.path] ||= {}) path_entry[cookie.name] = cookie end end end cookiejar-0.3.2/README.markdown0000644000004100000410000000123612315032213016205 0ustar www-datawww-dataRuby CookieJar ============== **Git**: [http://github.com/dwaite/cookiejar](http://github.com/dwaite/cookiejar) **Author**: David Waite Synopsis -------- The Ruby CookieJar is a library to help manage client-side cookies in pure Ruby. It enables parsing and setting of cookie headers, alternating between multiple 'jars' of cookies at one time (such as having a set of cookies for each browser or thread), and supports persistence of the cookies in a JSON string. Both Netscape/RFC 2109 cookies and RFC 2965 cookies are supported. COPYRIGHT --------- The Ruby CookieJar is Copyright © 2009 David Waite. Licensing terms are given within the LICENSE file. cookiejar-0.3.2/metadata.yml0000644000004100000410000000467612315032213016022 0ustar www-datawww-data--- !ruby/object:Gem::Specification name: cookiejar version: !ruby/object:Gem::Version version: 0.3.2 platform: ruby authors: - David Waite autorequire: bindir: bin cert_chain: [] date: 2014-02-16 00:00:00.000000000 Z dependencies: - !ruby/object:Gem::Dependency name: rake requirement: !ruby/object:Gem::Requirement requirements: - - "~>" - !ruby/object:Gem::Version version: '10.0' type: :development prerelease: false version_requirements: !ruby/object:Gem::Requirement requirements: - - "~>" - !ruby/object:Gem::Version version: '10.0' - !ruby/object:Gem::Dependency name: rspec requirement: !ruby/object:Gem::Requirement requirements: - - "~>" - !ruby/object:Gem::Version version: '2.14' type: :development prerelease: false version_requirements: !ruby/object:Gem::Requirement requirements: - - "~>" - !ruby/object:Gem::Version version: '2.14' - !ruby/object:Gem::Dependency name: yard requirement: !ruby/object:Gem::Requirement requirements: - - "~>" - !ruby/object:Gem::Version version: '0.8' - - ">=" - !ruby/object:Gem::Version version: 0.8.7 type: :development prerelease: false version_requirements: !ruby/object:Gem::Requirement requirements: - - "~>" - !ruby/object:Gem::Version version: '0.8' - - ">=" - !ruby/object:Gem::Version version: 0.8.7 description: Allows for parsing and returning cookies in Ruby HTTP client code email: david@alkaline-solutions.com executables: [] extensions: [] extra_rdoc_files: [] files: - LICENSE - README.markdown - Rakefile - contributors.json - lib/cookiejar.rb - lib/cookiejar/cookie.rb - lib/cookiejar/cookie_validation.rb - lib/cookiejar/jar.rb - spec/cookie_spec.rb - spec/cookie_validation_spec.rb - spec/jar_spec.rb homepage: https://alkaline-solutions.com licenses: - BSD-2-Clause metadata: {} post_install_message: rdoc_options: - "--title" - CookieJar -- Client-side HTTP Cookies require_paths: - lib required_ruby_version: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: '0' required_rubygems_version: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: '0' requirements: [] rubyforge_project: rubygems_version: 2.2.2 signing_key: specification_version: 3 summary: Client-side HTTP Cookie library test_files: [] has_rdoc: cookiejar-0.3.2/LICENSE0000644000004100000410000000245012315032213014510 0ustar www-datawww-dataCopyright (c) 2009 - 2014, David Waite and Other Contributors All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. cookiejar-0.3.2/checksums.yaml.gz0000444000004100000410000000041412315032213016767 0ustar www-datawww-data!Se1V@D"tvXn,nW_z||߯Vʹ VVvKVmc>/_=QdpG>kDl8bρ@NL-^L91kBڌE\.a'!"`i3)1lR MwO2 ,^q.8kgkwW05u9Fȶ!1&Ď^CT=bs("TpB0