cookiejar-0.3.4/0000755000004100000410000000000014574705073013530 5ustar www-datawww-datacookiejar-0.3.4/cookiejar.gemspec0000644000004100000410000000207114574705073017043 0ustar www-datawww-data# -*- encoding: utf-8 -*- lib = File.expand_path('../lib', __FILE__) $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) require 'cookiejar/version' Gem::Specification.new do |s| s.name = 'cookiejar' s.version = CookieJar::VERSION s.authors = ['David Waite'] s.license = 'BSD-2-Clause' s.email = ['david@alkaline-solutions.com'] s.description = 'Allows for parsing and returning cookies in Ruby HTTP client code' s.summary = 'Client-side HTTP Cookie library' s.homepage = 'http://alkaline-solutions.com' s.date = '2014-02-01' s.files = `git ls-files`.split($INPUT_RECORD_SEPARATOR) s.test_files = s.files.grep(%r{^(spec)/}) s.rdoc_options = ['--title', 'CookieJar -- Client-side HTTP Cookies'] s.require_paths = ['lib'] s.add_development_dependency 'rake', '>= 10.0' s.add_development_dependency 'rspec-collection_matchers', '~> 1.0' s.add_development_dependency 'rspec', '~> 3.0' s.add_development_dependency 'yard', '~> 0.9.20' s.add_development_dependency 'bundler', '>= 0.9.3' end cookiejar-0.3.4/README.markdown0000644000004100000410000000303414574705073016231 0ustar www-datawww-dataRuby CookieJar ============== **Git**: [http://github.com/dwaite/cookiejar](http://github.com/dwaite/cookiejar) **Author**: David Waite [![Build Status](https://travis-ci.org/dwaite/cookiejar.svg?branch=master)](https://travis-ci.org/dwaite/cookiejar) Status ------ This project is no longer maintained. There may be other gems more appropriate for use which support newer versions of the cookie specifications, or more maintained forks of this gem such as [cookiejar2](https://github.com/dorianmariefr/cookiejar2). 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. Roadmap ------- For the Next major release, I would like to accomplish: 1. Check against [RFC 6265 - HTTP State Management Mechanism][rfc6265], the latest cookie spec which came out after the initial release of cookiejar 2. Determine better code structure to encourage alternate persistence mechanisms for cookie jars [rfc6265]: http://tools.ietf.org/html/rfc6265 COPYRIGHT --------- The Ruby CookieJar is Copyright © 2009-2014 David Waite, with [additional contributions from various authors][contributions]. Licensing terms are given within the [LICENSE file][LICENSE]. [contributions]: ./contributors.json [LICENSE]: ./LICENSE cookiejar-0.3.4/.gitignore0000644000004100000410000000007314574705073015520 0ustar www-datawww-data.yardoc *.*proj pkg doc .bundle .ruby-version Gemfile.lock cookiejar-0.3.4/contributors.json0000644000004100000410000000055314574705073017163 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.4/lib/0000755000004100000410000000000014574705073014276 5ustar www-datawww-datacookiejar-0.3.4/lib/cookiejar.rb0000644000004100000410000000011714574705073016570 0ustar www-datawww-datarequire 'cookiejar/cookie' require 'cookiejar/jar' require 'cookiejar/version' cookiejar-0.3.4/lib/cookiejar/0000755000004100000410000000000014574705073016244 5ustar www-datawww-datacookiejar-0.3.4/lib/cookiejar/cookie.rb0000644000004100000410000002247214574705073020051 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) return "#{name}=#{value}" if ver == 0 # 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}\"" str << ";$Domain=#{domain}" if domain.start_with? '.' str << ";$Port=\"#{ports.join ','}\"" if ports str 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 = if uri.path == '' '/' else uri.path end path_match = 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 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 @ports = [@ports] if @ports.is_a? Integer end end end cookiejar-0.3.4/lib/cookiejar/version.rb0000644000004100000410000000011614574705073020254 0ustar www-datawww-data# frozen_string_literal: true module CookieJar VERSION = '0.3.4'.freeze end cookiejar-0.3.4/lib/cookiejar/jar.rb0000644000004100000410000002472614574705073017360 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 # hierarchy. # # 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) o = JSON.parse(o) if o.is_a? String o = o['cookies'] if o.is_a? Hash cookies = o.inject([]) do |result, cookie_json| result << (Cookie.json_create cookie_json) end 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. This must be a full URI, i.e. must include the protocol, # if you pass digi.ninja it will fail to find the domain, you must pass # http://digi.ninja # @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 return [] if hosts.nil? path = if uri.path == '' '/' else uri.path end results = [] hosts.each do |host| domain = find_domain host domain.each do |apath, cookies| next unless path.start_with? apath results += cookies.values.select do |cookie| cookie.should_send? uri, opts[:script] 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 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(&:to_s).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 << yield(value) end elsif http_header_value.is_a? String result << yield(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.4/lib/cookiejar/cookie_validation.rb0000644000004100000410000003367714574705073022274 0ustar www-datawww-data# frozen_string_literal: true require '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 # REGEX cookie matching module PATTERN include URI::REGEXP::PATTERN TOKEN = '[^(),\/<>@;:\\\"\[\]?={}\s]+'.freeze VALUE1 = '([^;]*)'.freeze IPADDR = "#{IPV4ADDR}|#{IPV6ADDR}".freeze BASE_HOSTNAME = "(?:#{DOMLABEL}\\.)(?:((?:(?:#{DOMLABEL}\\.)+(?:#{TOPLABEL}\\.?))|local))".freeze QUOTED_PAIR = '\\\\[\\x00-\\x7F]'.freeze LWS = '\\r\\n(?:[ \\t]+)'.freeze # TEXT="[\\t\\x20-\\x7E\\x80-\\xFF]|(?:#{LWS})" QDTEXT = "[\\t\\x20-\\x21\\x23-\\x7E\\x80-\\xFF]|(?:#{LWS})".freeze QUOTED_TEXT = "\\\"(?:#{QDTEXT}|#{QUOTED_PAIR})*\\\"".freeze VALUE2 = "#{TOKEN}|#{QUOTED_TEXT}".freeze end BASE_HOSTNAME = /#{PATTERN::BASE_HOSTNAME}/ BASE_PATH = %r{\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|;)", Regexp::NOENCODING) # 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 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 match[1] if match 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 return nil unless uri.is_a? URI::HTTP 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 result << ".#{base}" if base 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 return effective_host(uri.host) if domain.nil? || domain.empty? domain = domain.downcase if domain =~ IPADDR || domain.start_with?('.') domain else ".#{domain}" 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_path = uri.path 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. errors << 'Version missing' unless cookie.version # The value for the Path attribute is not a prefix of the request-URI # If the initial request path is empty then this will always fail # so check if it is empty and if so then set it to / request_path = '/' if request_path == '' 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.empty? unless cookie.ports.find_index uri.port errors << 'Ports list does not contain request URI port' end end fail InvalidCookieError, 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 unless result fail InvalidCookieError, "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 $ERROR_INFO.message == 'time out of range' args[:expires_at] = Time.at(0x7FFFFFFF) end when :"max-age" args[:max_age] = keyvalue.to_i when :domain, :path args[key] = keyvalue when :secure args[:secure] = true when :httponly args[:http_only] = true when :samesite args[:samesite] = keyvalue.downcase else fail InvalidCookieError, "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/ =~ value value = Regexp.last_match(1) 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/ =~ 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 fail InvalidCookieError, "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(&:to_i) else fail InvalidCookieError, "Unknown cookie parameter '#{key}'" end end end until md.post_match.empty? # if our last match in the scan failed if args[:version] != 1 fail InvalidCookieError, 'Set-Cookie2 declares a non RFC2965 version cookie' end args end end end cookiejar-0.3.4/spec/0000755000004100000410000000000014574705073014462 5ustar www-datawww-datacookiejar-0.3.4/spec/spec_helper.rb0000644000004100000410000000015214574705073017276 0ustar www-datawww-datarequire 'cookiejar' require 'rubygems' require 'rspec' require 'rspec/collection_matchers' require 'yaml' cookiejar-0.3.4/spec/cookie_spec.rb0000644000004100000410000001624614574705073017303 0ustar www-datawww-data# frozen_string_literal: true require 'spec_helper' include CookieJar FOO_URL = 'http://localhost/foo'.freeze AMMO_URL = 'http://localhost/ammo'.freeze 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]].freeze 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.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' expect(cookie.name).to eq 'foo' expect(cookie.value).to eq 'bar' end it 'should normalize domain names' do cookie = Cookie.from_set_cookie 'http://localhost/', 'foo=Bar;domain=LoCaLHoSt.local' expect(cookie.domain).to eq '.localhost.local' end it 'should accept non-normalized .local' do cookie = Cookie.from_set_cookie 'http://localhost/', 'foo=bar;domain=.local' expect(cookie.domain).to eq '.local' end it 'should accept secure cookies' do cookie = Cookie.from_set_cookie 'https://www.google.com/a/blah', 'GALX=RgmSftjnbPM;Path=/a/;Secure' expect(cookie.name).to eq 'GALX' expect(cookie.secure).to be_truthy 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' expect(cookie.name).to eq 'foo' expect(cookie.value).to eq 'bar' end it 'should normalize domain names' do cookie = Cookie.from_set_cookie2 'http://localhost/', 'foo=Bar;domain=LoCaLHoSt.local;Version=1' expect(cookie.domain).to eq '.localhost.local' end it 'should accept non-normalized .local' do cookie = Cookie.from_set_cookie2 'http://localhost/', 'foo=bar;domain=.local;Version=1' expect(cookie.domain).to eq '.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' expect(cookie.name).to eq 'GALX' expect(cookie.path).to eq '/a/' expect(cookie.secure).to be_truthy end it 'should fail on unquoted paths' do expect { Cookie.from_set_cookie2 'https://www.google.com/a/blah', 'GALX=RgmSftjnbPM;Path=/a/;Secure;Version=1' }.to raise_error InvalidCookieError end it 'should accept quoted values' do cookie = Cookie.from_set_cookie2 'http://localhost/', 'foo="bar";Version=1' expect(cookie.name).to eq 'foo' expect(cookie.value).to eq '"bar"' end it 'should accept poorly chosen names' do cookie = Cookie.from_set_cookie2 'http://localhost/', 'Version=mine;Version=1' expect(cookie.name).to eq 'Version' expect(cookie.value).to eq 'mine' end it 'should accept quoted parameter values' do 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' expect(cookie).to be_session expect(cookie).to_not be_expired cookie = Cookie.from_set_cookie2 'http://localhost/', 'f=b;max-age=100;Version=1' expect(cookie).to_not be_session cookie = Cookie.from_set_cookie2 'http://localhost/', 'f=b;Version=1' expect(cookie).to be_session end it 'should handle quotable quotes' do cookie = Cookie.from_set_cookie2 'http://localhost/', 'f="\"";Version=1' expect(cookie.value).to eq '"\""' end it 'should handle quotable apostrophes' do cookie = Cookie.from_set_cookie2 'http://localhost/', 'f="\;";Version=1' expect(cookie.value).to eq '"\;"' end end describe '#decoded_value' do it 'should leave normal values alone' do cookie = Cookie.from_set_cookie2 'http://localhost/', 'f=b;Version=1' expect(cookie.decoded_value).to eq 'b' end it 'should attempt to unencode quoted values' do cookie = Cookie.from_set_cookie2 'http://localhost/', 'f="\"b";Version=1' expect(cookie.value).to eq '"\"b"' expect(cookie.decoded_value).to eq '"b' end end describe '#to_s' do it 'should handle a simple cookie' do cookie = Cookie.from_set_cookie 'http://localhost/', 'f=b' expect(cookie.to_s).to eq 'f=b' expect(cookie.to_s(1)).to eq '$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' expect(cookie.to_s(1)).to eq '$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"' expect(cookie.to_s(1)).to eq '$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/' expect(cookie.to_s).to eq 'f=b' expect(cookie.to_s(1)).to eq '$Version=0;f=b;$Path="/bar/"' end it 'should omit $Version header when asked' do cookie = Cookie.from_set_cookie 'http://localhost/', 'f=b' expect(cookie.to_s(1, false)).to eq '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"' expect(cookie.should_send?('http://localhost/', false)).to be_truthy expect(cookie.should_send?('https://localhost/', false)).to be_falsey 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 expect(json).to 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 expect(c).to 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.4/spec/cookie_validation_spec.rb0000644000004100000410000002514214574705073021510 0ustar www-datawww-datarequire 'spec_helper' include CookieJar describe CookieValidation do describe '#validate_cookie' do localaddr = 'http://localhost/foo/bar/' it 'should fail if version unset' do expect { unversioned = Cookie.from_set_cookie localaddr, 'foo=bar' unversioned.instance_variable_set :@version, nil CookieValidation.validate_cookie localaddr, unversioned }.to raise_error InvalidCookieError end it 'should fail if the path is more specific' do expect { Cookie.from_set_cookie localaddr, 'foo=bar;path=/foo/bar/baz' }.to raise_error InvalidCookieError end it 'should fail if the path is different than the request' do expect { Cookie.from_set_cookie localaddr, 'foo=bar;path=/baz/' }.to raise_error InvalidCookieError end it 'should fail if the domain has no dots' do expect { Cookie.from_set_cookie 'http://zero/', 'foo=bar;domain=zero' }.to raise_error InvalidCookieError end it 'should fail for explicit localhost' do expect { Cookie.from_set_cookie localaddr, 'foo=bar;domain=localhost' }.to raise_error InvalidCookieError end it 'should fail for mismatched domains' do expect { Cookie.from_set_cookie 'http://www.foo.com/', 'foo=bar;domain=bar.com' }.to raise_error InvalidCookieError end it 'should fail for domains more than one level up' do expect { Cookie.from_set_cookie 'http://x.y.z.com/', 'foo=bar;domain=z.com' }.to raise_error InvalidCookieError end it 'should fail for setting subdomain cookies' do expect { Cookie.from_set_cookie 'http://foo.com/', 'foo=bar;domain=auth.foo.com' }.to raise_error InvalidCookieError end it 'should handle a normal implicit internet cookie' do normal = Cookie.from_set_cookie 'http://foo.com/', 'foo=bar' expect(CookieValidation.validate_cookie('http://foo.com/', normal)).to be_truthy end it 'should handle a normal implicit localhost cookie' do localhost = Cookie.from_set_cookie 'http://localhost/', 'foo=bar' expect(CookieValidation.validate_cookie('http://localhost/', localhost)).to be_truthy end it 'should handle an implicit IP address cookie' do ipaddr = Cookie.from_set_cookie 'http://127.0.0.1/', 'foo=bar' expect(CookieValidation.validate_cookie('http://127.0.0.1/', ipaddr)).to be_truthy 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' expect(CookieValidation.validate_cookie('http://foo.com/', explicit)).to be_truthy 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' expect(CookieValidation.validate_cookie('http://foo.com/', superdomain)).to be_truthy 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 expect(CookieValidation.cookie_base_path('/')).to eq '/' end it "should strip off everything after the last '/'" do expect(CookieValidation.cookie_base_path('/foo/bar/baz')).to eq '/foo/bar/' end it 'should handle query parameters and fragments with slashes' do expect(CookieValidation.cookie_base_path('/foo/bar?query=a/b/c#fragment/b/c')).to eq '/foo/' end it 'should handle URI objects' do expect(CookieValidation.cookie_base_path(URI.parse('http://www.foo.com/bar/'))).to eq '/bar/' end it 'should preserve case' do expect(CookieValidation.cookie_base_path('/BaR/')).to eq '/BaR/' end end describe '#determine_cookie_path' do it 'should use the requested path when none is specified for the cookie' do expect(CookieValidation.determine_cookie_path('http://foo.com/', nil)).to eq '/' expect(CookieValidation.determine_cookie_path('http://foo.com/bar/baz', '')).to eq '/bar/' end it 'should handle URI objects' do expect(CookieValidation.determine_cookie_path(URI.parse('http://foo.com/bar/'), '')).to eq '/bar/' end it 'should handle Cookie objects' do cookie = Cookie.from_set_cookie('http://foo.com/', 'name=value;path=/') expect(CookieValidation.determine_cookie_path('http://foo.com/', cookie)).to eq '/' end it 'should ignore the request when a path is specified' do expect(CookieValidation.determine_cookie_path('http://foo.com/ignorable/path', '/path/')).to eq '/path/' end end describe '#compute_search_domains' do it 'should handle subdomains' do expect(CookieValidation.compute_search_domains('http://www.auth.foo.com/')).to eq( ['www.auth.foo.com', '.www.auth.foo.com', '.auth.foo.com']) end it 'should handle root domains' do expect(CookieValidation.compute_search_domains('http://foo.com/')).to eq( ['foo.com', '.foo.com']) end it 'should handle hexadecimal TLDs' do expect(CookieValidation.compute_search_domains('http://tiny.cc/')).to eq( ['tiny.cc', '.tiny.cc']) end it 'should handle IP addresses' do expect(CookieValidation.compute_search_domains('http://127.0.0.1/')).to eq( ['127.0.0.1']) end it 'should handle local addresses' do expect(CookieValidation.compute_search_domains('http://zero/')).to eq( ['zero.local', '.zero.local', '.local']) end end describe '#determine_cookie_domain' do it 'should add a dot to the front of domains' do expect(CookieValidation.determine_cookie_domain('http://foo.com/', 'foo.com')).to eq '.foo.com' end it 'should not add a second dot if one present' do expect(CookieValidation.determine_cookie_domain('http://foo.com/', '.foo.com')).to eq '.foo.com' end it 'should handle Cookie objects' do c = Cookie.from_set_cookie('http://foo.com/', 'foo=bar;domain=foo.com') expect(CookieValidation.determine_cookie_domain('http://foo.com/', c)).to eq '.foo.com' end it 'should handle URI objects' do expect(CookieValidation.determine_cookie_domain(URI.parse('http://foo.com/'), '.foo.com')).to eq '.foo.com' end it 'should use an exact hostname when no domain specified' do expect(CookieValidation.determine_cookie_domain('http://foo.com/', '')).to eq 'foo.com' end it 'should leave IPv4 addresses alone' do expect(CookieValidation.determine_cookie_domain('http://127.0.0.1/', '127.0.0.1')).to eq '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| expect(CookieValidation.determine_cookie_domain("http://[#{value}]/", value)).to eq 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| expect(CookieValidation.effective_host(value)).to eq value end end it 'should handle a URI object' do expect(CookieValidation.effective_host(URI.parse('http://example.com/'))).to eq 'example.com' end it 'should add a local suffix on unqualified hosts' do expect(CookieValidation.effective_host('localhost')).to eq 'localhost.local' end it 'should leave IPv4 addresses alone' do expect(CookieValidation.effective_host('127.0.0.1')).to eq '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| expect(CookieValidation.effective_host(value)).to eq value end end it 'should lowercase addresses' do expect(CookieValidation.effective_host('FOO.COM')).to eq 'foo.com' end end describe '#match_domains' do it 'should handle exact matches' do expect(CookieValidation.domains_match('localhost.local', 'localhost.local')).to eq 'localhost.local' expect(CookieValidation.domains_match('foo.com', 'foo.com')).to eq 'foo.com' expect(CookieValidation.domains_match('127.0.0.1', '127.0.0.1')).to eq '127.0.0.1' expect(CookieValidation.domains_match('::ffff:192.0.2.128', '::ffff:192.0.2.128')).to eq '::ffff:192.0.2.128' end it 'should handle matching a superdomain' do expect(CookieValidation.domains_match('.foo.com', 'auth.foo.com')).to eq '.foo.com' expect(CookieValidation.domains_match('.y.z.foo.com', 'x.y.z.foo.com')).to eq '.y.z.foo.com' end it 'should not match superdomains, or illegal domains' do expect(CookieValidation.domains_match('.z.foo.com', 'x.y.z.foo.com')).to be_nil expect(CookieValidation.domains_match('foo.com', 'com')).to be_nil end it 'should not match domains with and without a dot suffix together' do expect(CookieValidation.domains_match('foo.com.', 'foo.com')).to 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| expect(CookieValidation.hostname_reach(entry[0])).to eq entry[1] end end it 'should handle domains with suffixed dots' do expect(CookieValidation.hostname_reach('www.google.com.')).to eq 'google.com.' end it 'should return nil for a root domain' do expect(CookieValidation.hostname_reach('github.com')).to be_nil end it "should return 'local' for a local domain" do ['foo.local', 'foo.local.'].each do |hostname| expect(CookieValidation.hostname_reach(hostname)).to eq 'local' end end it "should handle mixed-case '.local'" do expect(CookieValidation.hostname_reach('foo.LOCAL')).to eq 'local' end it 'should return nil for an IPv4 address' do expect(CookieValidation.hostname_reach('127.0.0.1')).to 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| expect(CookieValidation.hostname_reach(value)).to be_nil end end end describe '#parse_set_cookie' do it 'should max out at 2038 on 32bit systems' do expect(CookieValidation.parse_set_cookie('TRACK_USER_P=98237480810003948000782774;expires=Sat, 30-Jun-2040 05:39:49 GMT;path=/')[:expires_at].to_i).to be >= 0x7FFFFFFF end end end cookiejar-0.3.4/spec/jar_spec.rb0000644000004100000410000002320014574705073016572 0ustar www-datawww-datarequire 'spec_helper' 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' expect(jar.get_cookies('http://foo.com/')).to 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' expect(jar.get_cookie_header('http://foo.com/')).to eq '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) expect(cookies).to have(4).items expect(cookies[0].name).to eq 'b' expect(cookies[1].name).to eq 'a' expect(cookies[2].name).to eq 'c' expect(cookies[3].name).to eq '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) expect(cookies).to 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 expect(cookie_headers).to eq '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 expect(cookie_headers).to eq '$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' expect(jar.to_a).to 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' expect(jar.to_a).to have(5).items jar.expire_cookies expect(jar.to_a).to 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' expect(jar.to_a).to have(5).items jar.expire_cookies true expect(jar.to_a).to 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' expect(cookies).to have(1).items expect(jar.to_a).to 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' expect(cookies).to have(1).items expect(jar.to_a).to 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'] expect(cookies).to have(2).items expect(jar.to_a).to 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' expect(cookies).to have(1).items expect(jar.to_a).to 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' expect(cookies).to have(1).items expect(jar.to_a).to 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'] expect(cookies).to have(2).items expect(jar.to_a).to 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' expect(cookies).to have(2).items expect(jar.to_a).to 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' expect(cookies).to have(2).items expect(jar.to_a).to have(2).items # and has the version 1 cookie expect(cookies.find do |cookie| cookie.name == 'foo' end.version).to eq 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'] expect(cookies).to have(1).items expect(jar.to_a).to 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 expect(json).to 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 expect(jar.get_cookies('https://localhost/')).to 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 expect(jar.get_cookies('https://localhost/')).to 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 expect(jar.get_cookies('https://localhost/')).to have(1).items 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.4/.rspec0000644000004100000410000000001314574705073014637 0ustar www-datawww-data--color -w cookiejar-0.3.4/Rakefile0000644000004100000410000000124514574705073015177 0ustar www-datawww-datarequire 'bundler/gem_tasks' require 'rake' require 'rake/clean' require 'yard' require 'yard/rake/yardoc_task' 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.ruby_opts = %w(-w) t.pattern = 'spec/**/*_spec.rb' end task test: :spec rescue LoadError puts 'Warning: unable to load rspec tasks' end # Default Rake task is to run all tests task default: :test cookiejar-0.3.4/_config.yml0000644000004100000410000000003314574705073015653 0ustar www-datawww-datatheme: jekyll-theme-tactilecookiejar-0.3.4/Gemfile0000644000004100000410000000004614574705073015023 0ustar www-datawww-datasource 'https://rubygems.org' gemspec cookiejar-0.3.4/LICENSE0000644000004100000410000000245014574705073014536 0ustar www-datawww-dataCopyright (c) 2009 - 2018, 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.4/.travis.yml0000644000004100000410000000031214574705073015635 0ustar www-datawww-datalanguage: ruby sudo: false cache: bundler rvm: - 2.3.0 - 2.2.0 - 2.1.5 - 2.1.4 - 2.1.3 - 2.1.2 - 2.0.0 - jruby-19mode - rbx jdk: - oraclejdk8 before_install: gem install bundler -v ">=1.9.3"