Commit b9c984d7 authored by Tiago's avatar Tiago
Browse files

cookies: make Jar follow the CookieStore API

It should implement the following methods from CookieStore:

* #get(name_or_options) -> returns the first matched cookie
* #get_all(name_or_options) -> returns array of cookies
* #set(name_or_options, value_if_name)
* #delete(name_or_options)
parent 3d506444
Loading
Loading
Loading
Loading
+1 −3
Original line number Diff line number Diff line
@@ -7,8 +7,6 @@ module HTTPX
    #
    # This plugin implements a persistent cookie jar for the duration of a session.
    #
    # It also adds a *#cookies* helper, so that you can pre-fill the cookies of a session.
    #
    # https://gitlab.com/os85/httpx/wikis/Cookies
    #
    module Cookies
@@ -96,7 +94,7 @@ module HTTPX
          cookies.each do |ck|
            ck.split(/ *; */).each do |cookie|
              name, value = cookie.split("=", 2)
              jar.add(Cookie.new(name, value))
              jar.set(name, value)
            end
          end
        end
+34 −11
Original line number Diff line number Diff line
@@ -14,12 +14,14 @@ module HTTPX

      attr_reader :domain, :path, :name, :value, :created_at

      # assigns a new +path+ to this cookie.
      def path=(path)
        path = String(path)
        @for_domain = false
        @path = path.start_with?("/") ? path : "/"
      end

      # See #domain.
      # assigns a new +domain+ to this cookie.
      def domain=(domain)
        domain = String(domain)

@@ -37,6 +39,13 @@ module HTTPX
        @domain = @domain_name.hostname
      end

      # checks whether +other+ is the same cookie, i.e. name, value, domain and path are
      # the same.
      def ==(other)
        @name == other.name && @value == other.value &&
          @path == other.path && @domain == other.domain
      end

      # Compares the cookie with another.  When there are many cookies with
      # the same name for a URL, the value of the smallest must be used.
      def <=>(other)
@@ -47,12 +56,30 @@ module HTTPX
          (@created_at <=> other.created_at).nonzero? || 0
      end

      def match?(name_or_options)
        case name_or_options
        when String
          @name == name_or_options
        when Hash, Array
          name_or_options.all? { |k, v| respond_to?(k) && send(k) == v }
        else
          false
        end
      end

      class << self
        def new(cookie, *args)
          return cookie if cookie.is_a?(self)
          case cookie
          when self
            cookie
          when Array, Hash
            options = Hash[cookie] #: cookie_attributes
            super(options[:name], options[:value], options)
          else

            super
          end
        end

        # Tests if +target_path+ is under +base_path+ as described in RFC
        # 6265 5.1.4.  +base_path+ must be an absolute path.
@@ -84,16 +111,12 @@ module HTTPX
        end
      end

      def initialize(arg, *attrs)
      def initialize(arg, value, attrs = nil)
        @created_at = Time.now

        if attrs.empty?
          attr_hash = Hash.try_convert(arg)
        else
        @name = arg
          @value, attr_hash = attrs
          attr_hash = Hash.try_convert(attr_hash)
        end
        @value = value
        attr_hash = Hash.try_convert(attrs)

        attr_hash.each do |key, val|
          key = key.downcase.tr("-", "_").to_sym unless key.is_a?(Symbol)
+62 −36
Original line number Diff line number Diff line
@@ -4,7 +4,12 @@ module HTTPX
  module Plugins::Cookies
    # The Cookie Jar
    #
    # It holds a bunch of cookies.
    # It stores and manages cookies for a session, such as i.e. evicting when expired, access methods, or
    # initialization from parsing `Set-Cookie` HTTP header values.
    #
    # It closely follows the [CookieStore API](https://developer.mozilla.org/en-US/docs/Web/API/CookieStore),
    # by implementing the same methods, with a few specific conveniences for this non-browser manipulation use-case.
    #
    class Jar
      using URIExtensions

@@ -15,6 +20,8 @@ module HTTPX
        @cookies = orig.instance_variable_get(:@cookies).dup
      end

      # initializes the cookie store, either empty, or with whatever is passed as +cookies+, which
      # can be an array of HTTPX::Plugins::Cookies::Cookie objects or hashes-or-tuples of cookie attributes.
      def initialize(cookies = nil)
        @cookies = []

@@ -32,61 +39,80 @@ module HTTPX
        end if cookies
      end

      # parses the `Set-Cookie` header value as +set_cookie+ and does the corresponding updates.
      def parse(set_cookie)
        SetCookieParser.call(set_cookie) do |name, value, attrs|
          add(Cookie.new(name, value, attrs))
          set(Cookie.new(name, value, attrs))
        end
      end

      def add(cookie, path = nil)
        c = cookie.dup
      # returns the first HTTPX::Plugins::Cookie::Cookie instance in the store which matches either the name
      # (when String) or all attributes (when a Hash or array of tuples) passed to +name_or_options+
      def get(name_or_options)
        each.find { |ck| ck.match?(name_or_options) }
      end

        c.path = path if path && c.path == "/"
      # returns all HTTPX::Plugins::Cookie::Cookie instances in the store which match either the name
      # (when String) or all attributes (when a Hash or array of tuples) passed to +name_or_options+
      def get_all(name_or_options)
        each.select { |ck| ck.match?(name_or_options) } # rubocop:disable Style/SelectByRegexp
      end

        # If the user agent receives a new cookie with the same cookie-name, domain-value, and path-value
        # as a cookie that it has already stored, the existing cookie is evicted and replaced with the new cookie.
        @cookies.delete_if { |ck| ck.name == c.name && ck.domain == c.domain && ck.path == c.path }
      # when +name+ is a HTTPX::Plugins::Cookie::Cookie, it stores it internally; when +name+ is a String,
      # it creates a cookie with it and the value-or-attributes passed to +value_or_options+.

        @cookies << c
      end
      # optionally, +name+ can also be the attributes hash-or-array as long it contains a <tt>:name</tt> field).
      def set(name, value_or_options = nil)
        cookie = case name
                 when Cookie
                   raise ArgumentError, "there should not be a second argument" if value_or_options

      def delete(cookie)
        @cookies.delete(cookie)
      end
                   name
                 when Array, Hash
                   raise ArgumentError, "there should not be a second argument" if value_or_options

      def reject!(&block)
        return to_enum(__method__) unless block_given?
                   Cookie.new(name)
                 else
                   raise ArgumentError, "the second argument is required" unless value_or_options

        @cookies.reject!(&block)
        self
                   Cookie.new(name, value_or_options)
        end
      alias_method :delete_if, :reject!

      def select!(&block)
        return to_enum(__method__) unless block_given?
        # If the user agent receives a new cookie with the same cookie-name, domain-value, and path-value
        # as a cookie that it has already stored, the existing cookie is evicted and replaced with the new cookie.
        @cookies.delete_if { |ck| ck.name == cookie.name && ck.domain == cookie.domain && ck.path == cookie.path }

        @cookies.select!(&block)
        self
        @cookies << cookie
      end
      alias_method :keep_if, :select!
      alias_method :filter!, :select!

      def clear(uri = nil)
        unless uri
          @cookies.clear
          return
      # @deprecated
      def add(cookie, path = nil)
        warn "DEPRECATION WARNING: calling `##{__method__}` is deprecated. Use `#set` instead."
        c = cookie.dup
        c.path = path if path && c.path == "/"
        set(c)
      end

        tpath = uri.path
        @cookies.delete_if do |cookie|
          cookie.valid_for_uri?(uri) && Cookie.path_match?(cookie.path, tpath)
      # deletes all cookies  in the store which match either the name (when String) or all attributes (when a Hash
      # or array of tuples) passed to +name_or_options+.
      #
      # alternatively, of +name_or_options+ is an instance of HTTPX::Plugins::Cookies::Cookiem, it deletes it from the store.
      def delete(name_or_options)
        case name_or_options
        when Cookie
          @cookies.delete(name_or_options)
        else
          @cookies.delete_if { |ck| ck.match?(name_or_options) }
        end
      end

      # returns the list of valid cookies which matdh the domain and path from the URI object passed to +uri+.
      def [](uri)
        each(uri).sort
      end

      # enumerates over all stored cookies. if +uri+ is passed, it'll filter out expired cookies and
      # only yield cookies which match its domain and path.
      def each(uri = nil, &blk)
        return enum_for(__method__, uri) unless blk

@@ -106,7 +132,7 @@ module HTTPX
      end

      def merge(other)
        cookies_dup = dup
        jar_dup = dup

        other.each do |elem|
          cookie = case elem
@@ -118,10 +144,10 @@ module HTTPX
                     Cookie.new(elem)
          end

          cookies_dup.add(cookie)
          jar_dup.set(cookie)
        end

        cookies_dup
        jar_dup
      end
    end
  end
+3 −2
Original line number Diff line number Diff line
@@ -31,6 +31,8 @@ module HTTPX
      def cookie_value: () -> String
      alias to_s cookie_value

      def match?: (String | cookie_attributes name_or_options) -> bool

      def valid_for_uri?: (http_uri uri) -> bool

      def self.new: (Cookie) -> instance
@@ -41,8 +43,7 @@ module HTTPX

      private

      def initialize: (cookie_attributes) -> untyped
                    | (_ToS, _ToS, ?cookie_attributes) -> untyped
      def initialize: (_ToS name, _ToS value, ?cookie_attributes) -> void

      def acceptable_from_uri?: (uri) -> bool

+5 −10
Original line number Diff line number Diff line
@@ -9,20 +9,15 @@ module HTTPX

      def parse: (String set_cookie) -> void

      def add: (Cookie name, ?String path) -> void
      def get: (String | cookie_attributes name_or_options) -> Cookie?

      def delete: (Cookie cookie) -> Cookie?
      def get_all: (String | cookie_attributes name_or_options) -> Array[Cookie]

      def reject!: () { (Cookie cookie) -> boolish } -> self?
                 | () -> ::Enumerator[Cookie, self?]
      alias delete_if reject!
      def set: (_ToS | Cookie name, ?(cookie_attributes | _ToS) value_or_options) -> void

      def select!: () { (Cookie cookie) -> boolish } -> self?
                 | () -> ::Enumerator[Cookie, self?]
      alias keep_if select!
      alias filter! select!
      def add: (Cookie name, ?String path) -> void

      def clear: (?http_uri?) -> void
      def delete: (String | Cookie | cookie_attributes name_or_options) -> void

      def []: (http_uri) -> Array[Cookie]

Loading