# :markup: markdown require 'http/cookie' ## # This class is used to manage the Cookies that have been returned from # any particular website. class HTTP::CookieJar class << self def const_missing(name) case name.to_s when /\A([A-Za-z]+)Store\z/ file = 'http/cookie_jar/%s_store' % $1.downcase when /\A([A-Za-z]+)Saver\z/ file = 'http/cookie_jar/%s_saver' % $1.downcase end begin require file rescue LoadError raise NameError, 'can\'t resolve constant %s; failed to load %s' % [name, file] end if const_defined?(name) const_get(name) else raise NameError, 'can\'t resolve constant %s after loading %s' % [name, file] end end end attr_reader :store def get_impl(base, value, *args) case value when base value when Symbol begin base.implementation(value).new(*args) rescue IndexError => e raise ArgumentError, e.message end when Class if base >= value value.new(*args) else raise TypeError, 'not a subclass of %s: %s' % [base, value] end else raise TypeError, 'invalid object: %s' % value.inspect end end private :get_impl # Generates a new cookie jar. # # Available option keywords are as below: # # :store # : The store class that backs this jar. (default: `:hash`) # A symbol addressing a store class, a store class, or an instance # of a store class is accepted. Symbols are mapped to store # classes, like `:hash` to HTTP::CookieJar::HashStore and `:mozilla` # to HTTP::CookieJar::MozillaStore. # # Any options given are passed through to the initializer of the # specified store class. For example, the `:mozilla` # (HTTP::CookieJar::MozillaStore) store class requires a `:filename` # option. See individual store classes for details. def initialize(options = nil) opthash = { :store => :hash, } opthash.update(options) if options @store = get_impl(AbstractStore, opthash[:store], opthash) end # The copy constructor. Not all backend store classes support cloning. def initialize_copy(other) @store = other.instance_eval { @store.dup } end # Adds a cookie to the jar if it is acceptable, and returns self in # any case. A given cookie must have domain and path attributes # set, or ArgumentError is raised. # # Whether a cookie with the `for_domain` flag on overwrites another # with the flag off or vice versa depends on the store used. See # individual store classes for that matter. # # ### Compatibility Note for Mechanize::Cookie users # # In HTTP::Cookie, each cookie object can store its origin URI # (cf. #origin). While the origin URI of a cookie can be set # manually by #origin=, one is typically given in its generation. # To be more specific, HTTP::Cookie.new takes an `:origin` option # and HTTP::Cookie.parse takes one via the second argument. # # # Mechanize::Cookie # jar.add(origin, cookie) # jar.add!(cookie) # no acceptance check is performed # # # HTTP::Cookie # jar.origin = origin # jar.add(cookie) # acceptance check is performed def add(cookie) @store.add(cookie) if begin cookie.acceptable? rescue RuntimeError => e raise ArgumentError, e.message end self end alias << add # Deletes a cookie that has the same name, domain and path as a # given cookie from the jar and returns self. # # How the `for_domain` flag value affects the set of deleted cookies # depends on the store used. See individual store classes for that # matter. def delete(cookie) @store.delete(cookie) self end # Gets an array of cookies sorted by the path and creation time. If # `url` is given, only ones that should be sent to the URL/URI are # selected, with the access time of each of them updated. def cookies(url = nil) each(url).sort end # Tests if the jar is empty. If `url` is given, tests if there is # no cookie for the URL. def empty?(url = nil) if url each(url) { return false } return true else @store.empty? end end # Iterates over all cookies that are not expired in no particular # order. # # An optional argument `uri` specifies a URI/URL indicating the # destination of the cookies being selected. Every cookie yielded # should be good to send to the given URI, # i.e. cookie.valid_for_uri?(uri) evaluates to true. # # If (and only if) the `uri` option is given, last access time of # each cookie is updated to the current time. def each(uri = nil, &block) # :yield: cookie block_given? or return enum_for(__method__, uri) if uri uri = URI(uri) return self unless URI::HTTP === uri && uri.host end @store.each(uri, &block) self end include Enumerable # Parses a Set-Cookie field value `set_cookie` assuming that it is # sent from a source URL/URI `origin`, and adds the cookies parsed # as valid and considered acceptable to the jar. Returns an array # of cookies that have been added. # # If a block is given, it is called for each cookie and the cookie # is added only if the block returns a true value. # # `jar.parse(set_cookie, origin)` is a shorthand for this: # # HTTP::Cookie.parse(set_cookie, origin) { |cookie| # jar.add(cookie) # } # # See HTTP::Cookie.parse for available options. def parse(set_cookie, origin, options = nil) # :yield: cookie if block_given? HTTP::Cookie.parse(set_cookie, origin, options).tap { |cookies| cookies.select! { |cookie| yield(cookie) && add(cookie) } } else HTTP::Cookie.parse(set_cookie, origin, options) { |cookie| add(cookie) } end end # call-seq: # jar.save(filename_or_io, **options) # jar.save(filename_or_io, format = :yaml, **options) # # Saves the cookie jar into a file or an IO in the format specified # and returns self. If a given object responds to #write it is # taken as an IO, or taken as a filename otherwise. # # Available option keywords are below: # # * `:format` # # Specifies the format for saving. A saver class, a symbol # addressing a saver class, or a pre-generated instance of a # saver class is accepted. # #