ronin-rb/ronin-support

View on GitHub
lib/ronin/support/network/http/set_cookie.rb

Summary

Maintainability
A
2 hrs
Test Coverage
# frozen_string_literal: true
#
# Copyright (c) 2006-2023 Hal Brodigan (postmodern.mod3 at gmail.com)
#
# ronin-support is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# ronin-support is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with ronin-support.  If not, see <https://www.gnu.org/licenses/>.
#

require 'ronin/support/network/http/cookie'

require 'time'

module Ronin
  module Support
    module Network
      class HTTP
        #
        # Parses and generates `Set-Cookie` header values.
        #
        # @api public
        #
        # @since 1.0.0
        #
        class SetCookie < Cookie

          # The `Max-Age` cookie attribute.
          #
          # @return [Integer, nil]
          attr_reader :max_age

          # The `Expires` cookie attribute.
          #
          # @return [Time, nil]
          attr_reader :expires

          # The `Domain` cookie attribute.
          #
          # @return [String, nil]
          attr_reader :domain

          # The `Path` cookie attribute.
          #
          # @return [String, nil]
          attr_reader :path

          # The `SameSite` cookie attribute.
          #
          # @return [:strict, :lax, :none]
          attr_reader :same_site

          # The `HttpOnly` flag.
          #
          # @return [true, nil]
          attr_reader :http_only

          # The `Secure` flag.
          #
          # @return [true, nil]
          attr_reader :secure

          #
          # Initializes the `Set-Cookie` object.
          #
          # @param [Hash{String => String}] params
          #
          # @param [Time, nil] expires
          #   The parsed `Expires` value.
          #
          # @param [Integer, nil] max_age
          #   The parsed `Max-Age` value.
          #
          # @param [String, nil] path
          #   The parsed `Path` value.
          #
          # @param [String, nil] domain
          #   The parsed `Domain` value.
          #
          # @param [true, nil] http_only
          #   Indicates the `HttpOnly` flag is enabled.
          #
          # @param [true, nil] secure
          #   Indicates the `Secure` flag is enabled.
          #
          # @param [:strict, :lax, :none, nil] same_site
          #   The parsed `SameSite` value.
          #
          def initialize(params, expires:   nil,
                                 max_age:   nil,
                                 path:      nil,
                                 domain:    nil,
                                 http_only: nil,
                                 secure:    nil,
                                 same_site: nil)
            super(params)

            @expires   = expires
            @max_age   = max_age
            @path      = path
            @domain    = domain
            @http_only = http_only
            @secure    = secure
            @same_site = same_site
          end

          # Mapping of `SameSite` values to Symbols.
          SAME_SITE = {
            'None'   => :none,
            'Strict' => :strict,
            'Lax'    => :lax
          }

          #
          # Parses a `Set-Cookie` string.
          #
          # @param [String] string
          #   The raw `Set-Cookie` string.
          #
          # @return [Cookie]
          #   The parsed cookie.
          #
          # @raise [ArgumentError]
          #   The string contained an unknown `SameSite` value or flag.
          #
          def self.parse(string)
            kwargs = {}
            params = {}

            string.split(/;\s+/) do |field|
              if field.include?('=')
                key, value = field.split('=',2)

                case key
                when 'Max-Age' then kwargs[:max_age] = value.to_i
                when 'Expires' then kwargs[:expires] = Time.parse(value)
                when 'Path'    then kwargs[:path]    = value
                when 'Domain'  then kwargs[:domain]  = value
                when 'SameSite'
                  kwargs[:same_site] = SAME_SITE.fetch(value) do
                    raise(ArgumentError,"unrecognized SameSite value: #{value.inspect}")
                  end
                else
                  params[unescape(key)] = unescape(value)
                end
              else
                case field
                when 'HttpOnly' then kwargs[:http_only] = true
                when 'Secure'   then kwargs[:secure]    = true
                else
                  raise(ArgumentError,"unrecognized Cookie flag: #{field.inspect}")
                end
              end
            end

            return new(params,**kwargs)
          end

          #
          # Determines if the `HttpOnly` flag is set.
          #
          # @return [Boolean]
          #
          def http_only?
            @http_only == true
          end

          #
          # Determines if the `Secure` flag is set.
          #
          # @return [Boolean]
          #
          def secure?
            @secure == true
          end

          #
          # Converts the cookie back into a `Set-Cookie` value.
          #
          # @return [String]
          #   The formatted cookie.
          #
          def to_s
            string = super()
            string << "; Max-Age=#{@max_age}"          if @max_age
            string << "; Expires=#{@expires.httpdate}" if @expires
            string << "; Path=#{@path}"                if @path
            string << "; Domain=#{@domain}"            if @domain
            string << "; SameSite=#{@same_site.to_s.capitalize}" if @same_site

            if    @secure    then string << '; Secure'
            elsif @http_only then string << '; HttpOnly'
            end

            string
          end

        end
      end
    end
  end
end