require 'http/cookie'

module Msf
  class Exploit
    class Remote
      module HTTP
        # This class is a representation of a Http Cookie with some built in convenience methods.
        # Acts as a wrapper for the +HTTP::Cookie+ (https://www.rubydoc.info/gems/http-cookie/1.0.3/HTTP/Cookie) class .
        class HttpCookie
          include Comparable

          # Returns a new +HttpCookie+.
          #
          # Name can be a string.
          # - If a +String+, the name of the cookie is set to the passed +name+.
          # 	- If only a +String+ is passed to +name+, the cookie is set as a session cookie.
          #
          # Value can be a +String+ or +nil+.
          # - If a +String+, the value of the cookie is set as the passed +cookie+.
          # - If +nil+, the value of the cookie is set as an empty +String+ +''+ and the cookie is set to expire at
          #   +UNIX_EPOCH+
          #
          # +attr_hash+ can be used to set the values of +domain+, +path+, +max_age+, +expires+, +secure+, +httponly+,
          # +accessed_at+, +created_at+.
          def initialize(name, value = nil, **attr_hash)
            if value
              @cookie = ::HTTP::Cookie.new(name, value)
            else
              @cookie = ::HTTP::Cookie.new(name)
            end

            attr_hash.each_pair do |k, v|
              if k == 'max-age'.to_sym
                self.max_age= v
              elsif respond_to?("#{k}=".to_sym)
                self.send("#{k}=".to_sym, v)
              end
            end
          end

          # Returns the name of cookie of type +String+.
          def name
            @cookie.name
          end

          # Sets the cookie name.
          def name=(name)
            @cookie.name = name.to_s
          end

          # Returns the value of cookie of type +String+.
          def value
            @cookie.value
          end

          # Sets the cookie value.
          #
          # Passed +value+ must be +nil+, an instance of +String+, or an object that can be converted successfully to
          # a +String+ with +to_s+.
          def value=(value)
            if value.nil? || value.is_a?(String)
              @cookie.value = value
            else
              @cookie.value = value.to_s
            end
          end

          # Returns the value of max_age.
          #
          # max_age is the number of seconds until a cookie expires.
          def max_age
            @cookie.max_age
          end

          # Sets the cookie max_age of type +Integer+.
          #
          # Passed +max_age+ must be +nil+, an +Integer+, or an object that can be converted successfully to an
          # +Integer+ with +Integer(max_age)+.
          def max_age=(max_age)
            if max_age.nil? || max_age.is_a?(Integer)
              @cookie.max_age = max_age
            else
              @cookie.max_age = Integer(max_age)
            end
          end

          # Returns the value of cookie expires of type +Time+.
          #
          # expires is the date and time at which a cookie expires.
          def expires
            @cookie.expires
          end

          # Sets the cookie expires value.
          #
          # Passed +expires+ must be +nil+, an instance of +Time+, or an object that can be converted successfully to
          # an +Time+ with +Time.parse(expires)+.
          def expires=(expires)
            if expires.nil? || expires.is_a?(Time)
              @cookie.expires = expires
            else
              t = Time.parse(expires)
              @cookie.expires = t
            end
          end

          # Returns the cookie path of type +String+.
          #
          # path is the URL for which the cookie is valid.
          def path
            @cookie.path
          end

          # Sets the cookie path.
          #
          # Passed +path+ must be +nil+, an instance of +String+, or an object that can be converted successfully to a
          # +String+ with +to_s+.
          def path=(path)
            if path.nil? || path.is_a?(String)
              @cookie.path = path
            else
              @cookie.path = path.to_s
            end
          end

          # Returns the cookie secure value of type +Boolean+.
          #
          # secure is a boolean that indicates if the cookie should be limited to the scope of secure channels as
          # defined by the user agent.
          def secure
            @cookie.secure
          end

          # Sets the cookie secure value.
          #
          # Passed +secure+ is converted to a Boolean with +!!secure+ and set.
          def secure=(secure)
            @cookie.secure = !!secure
          end

          # Returns the cookie httponly value of type +Boolean+.
          #
          # httponly is a +Boolean+ that indicates if client-side scripts should be prevented from accessing data.
          def httponly
            @cookie.httponly
          end

          # Sets the cookie httponly value.
          #
          # Passed +httponly+ is converted to a Boolean with +!!httponly+ and set.
          def httponly=(httponly)
            @cookie.httponly = !!httponly
          end

          # Returns the cookie domain of type +String+.
          #
          # If omitted, defaults to the host of the current document URL, not including subdomains. Leading dots in
          # domain names (.example.com) are ignored. Multiple host/domain values are not allowed, but if a domain is
          # specified, then subdomains are always included.
          def domain
            if @cookie.domain.nil?
              nil
            else
              @cookie.domain.to_s
            end
          end

          # Sets the cookie domain.
          #
          # Passed +domain+ must be +nil+, an instance of +String+, or an object that can be converted successfully to
          # an +String+ with +to_s+.
          def domain=(domain)
            if domain.nil?
              @cookie.domain = domain
            else
              @cookie.domain = domain.to_s
            end
          end

          def origin=(origin)
            @cookie.origin = origin
          end

          def origin
            @cookie.origin
          end

          # Returns the cookie accessed_at value of type +Time+. accessed_at indicates when a cookie was last interacted
          # with.
          def accessed_at
            @cookie.accessed_at
          end

          # Sets the cookie accessed_at time.
          #
          # Passed +time+ must be +nil+, an instance of +Time+, or an object that can be converted successfully to an
          # +Time+ with +Time.parse+.
          def accessed_at=(time)
            if time.nil? || time.is_a?(Time)
              @cookie.accessed_at = time
            else
              @cookie.accessed_at = Time.parse(time)
            end
          end

          # Returns the cookie created_at value of type +Time+. created_at indicates when a cookie was created.
          def created_at
            @cookie.created_at
          end

          # Sets the cookie accessed_at time.
          #
          # Passed +time+ must be +nil+, an instance of +Time+, or an object that can be converted successfully to an
          # +Time+ with +Time.parse+.
          def created_at=(time)
            if time.nil? || time.is_a?(Time)
              @cookie.created_at = time
            else
              @cookie.created_at = Time.parse(time)
            end
          end

          # Returns a string representation of the cookie for use in a cookie header.
          # Comes in format "#{name}=#{value}".
          def cookie_value
            @cookie.cookie_value
          end
          alias to_s cookie_value

          # Returns a boolean indicating if the cookie will have expired by the date and time represented by +time+.
          # +time+ defaults to +Time.now+, so the method can return a different value after enough calls.
          def expired?(time = Time.now)
            @cookie.expired?(time)
          end

          # Returns a boolean indicating if the cookie is a Session Cookie.
          def session?
            @cookie.session?
          end

          # Tests if it is OK to accept this cookie. If either domain or path is missing an ArgumentError is raised.
          def acceptable?
            @cookie.acceptable?
          end

          # Returns a boolean indicating if the cookie can be sent to the passed +uri+.
          # Raises an ArgumentError if domain is nil (unset).
          def valid_for_uri?(uri)
            return false if uri.nil?
            raise ArgumentError, 'cannot tell if this cookie is valid as domain is nil' if domain.nil?

            @cookie.valid_for_uri?(uri)
          end

          # Tests if it is OK to accept this cookie if it is sent from the passed +uri+.
          #
          # @param [String] uri The uri that will be checked
          # @return [Boolean] True if the URI is an acceptable URI, false if the URI is nil or resolves to a blank string
          def acceptable_from_uri?(uri)
            return false if uri.nil?
            return false if URI(uri.strip).host == ''

            @cookie.acceptable_from_uri?(uri)
          end

          def <=>(other)
            @cookie <=> other
          end
        end
      end
    end
  end
end
