Skip to content

Commit

Permalink
moving domain_name module to core httpx, as the punycode IDN translat…
Browse files Browse the repository at this point in the history
…ion will be needed at this layer
  • Loading branch information
HoneyryderChuck committed Nov 7, 2020
1 parent e271c45 commit cbfb5c9
Show file tree
Hide file tree
Showing 7 changed files with 155 additions and 155 deletions.
2 changes: 1 addition & 1 deletion LICENSE.txt
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@
limitations under the License.


* lib/httpx/plugins/cookies/domain_name.rb
* lib/httpx/domain_name.rb

This file is derived from the implementation of punycode available at
here:
Expand Down
1 change: 1 addition & 0 deletions lib/httpx.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

require "httpx/errors"
require "httpx/utils"
require "httpx/domain_name"
require "httpx/altsvc"
require "httpx/callbacks"
require "httpx/loggable"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@

require "ipaddr"

module HTTPX::Plugins::Cookies
module HTTPX
# Represents a domain name ready for extracting its registered domain
# and TLD.
class DomainName
Expand Down
1 change: 0 additions & 1 deletion lib/httpx/plugins/cookies.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ module Cookies
def self.load_dependencies(*)
require "httpx/plugins/cookies/jar"
require "httpx/plugins/cookies/cookie"
require "httpx/plugins/cookies/domain_name"
require "httpx/plugins/cookies/set_cookie_parser"
end

Expand Down
268 changes: 135 additions & 133 deletions lib/httpx/plugins/cookies/cookie.rb
Original file line number Diff line number Diff line change
@@ -1,176 +1,178 @@
# frozen_string_literal: true

module HTTPX::Plugins::Cookies
# The HTTP Cookie.
#
# Contains the single cookie info: name, value and attributes.
class Cookie
include Comparable
# Maximum number of bytes per cookie (RFC 6265 6.1 requires 4096 at
# least)
MAX_LENGTH = 4096
module HTTPX
module Plugins::Cookies
# The HTTP Cookie.
#
# Contains the single cookie info: name, value and attributes.
class Cookie
include Comparable
# Maximum number of bytes per cookie (RFC 6265 6.1 requires 4096 at
# least)
MAX_LENGTH = 4096

attr_reader :domain
attr_reader :domain

attr_reader :path
attr_reader :path

attr_reader :name, :value
attr_reader :name, :value

attr_reader :created_at
attr_reader :created_at

def path=(path)
path = String(path)
@path = path.start_with?("/") ? path : "/"
end

# See #domain.
def domain=(domain)
domain = String(domain)

if domain.start_with?(".")
@for_domain = true
domain = domain[1..-1]
def path=(path)
path = String(path)
@path = path.start_with?("/") ? path : "/"
end

return if domain.empty?

@domain_name = DomainName.new(domain)
# RFC 6265 5.3 5.
@for_domain = false if @domain_name.domain.nil? # a public suffix or IP address
# See #domain.
def domain=(domain)
domain = String(domain)

@domain = @domain_name.hostname
end
if domain.start_with?(".")
@for_domain = true
domain = domain[1..-1]
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)
# RFC 6265 5.4
# Precedence: 1. longer path 2. older creation
(@name <=> other.name).nonzero? ||
(other.path.length <=> @path.length).nonzero? ||
(@created_at <=> other.created_at).nonzero? ||
@value <=> other.value
end
return if domain.empty?

class << self
def new(cookie, *args)
return cookie if cookie.is_a?(self)
@domain_name = DomainName.new(domain)
# RFC 6265 5.3 5.
@for_domain = false if @domain_name.domain.nil? # a public suffix or IP address

super
@domain = @domain_name.hostname
end

# Tests if +target_path+ is under +base_path+ as described in RFC
# 6265 5.1.4. +base_path+ must be an absolute path.
# +target_path+ may be empty, in which case it is treated as the
# root path.
#
# e.g.
#
# path_match?('/admin/', '/admin/index') == true
# path_match?('/admin/', '/Admin/index') == false
# path_match?('/admin/', '/admin/') == true
# path_match?('/admin/', '/admin') == false
#
# path_match?('/admin', '/admin') == true
# path_match?('/admin', '/Admin') == false
# path_match?('/admin', '/admins') == false
# path_match?('/admin', '/admin/') == true
# path_match?('/admin', '/admin/index') == true
def path_match?(base_path, target_path)
base_path.start_with?("/") || (return false)
# RFC 6265 5.1.4
bsize = base_path.size
tsize = target_path.size
return bsize == 1 if tsize.zero? # treat empty target_path as "/"
return false unless target_path.start_with?(base_path)
return true if bsize == tsize || base_path.end_with?("/")

target_path[bsize] == "/"
# 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)
# RFC 6265 5.4
# Precedence: 1. longer path 2. older creation
(@name <=> other.name).nonzero? ||
(other.path.length <=> @path.length).nonzero? ||
(@created_at <=> other.created_at).nonzero? ||
@value <=> other.value
end
end

def initialize(arg, *attrs)
@created_at = Time.now
class << self
def new(cookie, *args)
return cookie if cookie.is_a?(self)

super
end

if attrs.empty?
attr_hash = Hash.try_convert(arg)
else
@name = arg
@value, attr_hash = attrs
attr_hash = Hash.try_convert(attr_hash)
# Tests if +target_path+ is under +base_path+ as described in RFC
# 6265 5.1.4. +base_path+ must be an absolute path.
# +target_path+ may be empty, in which case it is treated as the
# root path.
#
# e.g.
#
# path_match?('/admin/', '/admin/index') == true
# path_match?('/admin/', '/Admin/index') == false
# path_match?('/admin/', '/admin/') == true
# path_match?('/admin/', '/admin') == false
#
# path_match?('/admin', '/admin') == true
# path_match?('/admin', '/Admin') == false
# path_match?('/admin', '/admins') == false
# path_match?('/admin', '/admin/') == true
# path_match?('/admin', '/admin/index') == true
def path_match?(base_path, target_path)
base_path.start_with?("/") || (return false)
# RFC 6265 5.1.4
bsize = base_path.size
tsize = target_path.size
return bsize == 1 if tsize.zero? # treat empty target_path as "/"
return false unless target_path.start_with?(base_path)
return true if bsize == tsize || base_path.end_with?("/")

target_path[bsize] == "/"
end
end

attr_hash.each do |key, val|
key = key.downcase.tr("-", "_").to_sym unless key.is_a?(Symbol)
def initialize(arg, *attrs)
@created_at = Time.now

case key
when :domain, :path
__send__(:"#{key}=", val)
if attrs.empty?
attr_hash = Hash.try_convert(arg)
else
instance_variable_set(:"@#{key}", val)
@name = arg
@value, attr_hash = attrs
attr_hash = Hash.try_convert(attr_hash)
end
end if attr_hash

@path ||= "/"
raise ArgumentError, "name must be specified" if @name.nil?
end
attr_hash.each do |key, val|
key = key.downcase.tr("-", "_").to_sym unless key.is_a?(Symbol)

def expires
@expires || (@created_at && @max_age ? @created_at + @max_age : nil)
end
case key
when :domain, :path
__send__(:"#{key}=", val)
else
instance_variable_set(:"@#{key}", val)
end
end if attr_hash

def expired?(time = Time.now)
return false unless expires
@path ||= "/"
raise ArgumentError, "name must be specified" if @name.nil?
end

expires <= time
end
def expires
@expires || (@created_at && @max_age ? @created_at + @max_age : nil)
end

# Returns a string for use in the Cookie header, i.e. `name=value`
# or `name="value"`.
def cookie_value
"#{@name}=#{Scanner.quote(@value)}"
end
alias_method :to_s, :cookie_value
def expired?(time = Time.now)
return false unless expires

# Tests if it is OK to send this cookie to a given `uri`. A
# RuntimeError is raised if the cookie's domain is unknown.
def valid_for_uri?(uri)
uri = URI(uri)
# RFC 6265 5.4
expires <= time
end

return false if @secure && uri.scheme != "https"
# Returns a string for use in the Cookie header, i.e. `name=value`
# or `name="value"`.
def cookie_value
"#{@name}=#{Scanner.quote(@value)}"
end
alias_method :to_s, :cookie_value

acceptable_from_uri?(uri) && Cookie.path_match?(@path, uri.path)
end
# Tests if it is OK to send this cookie to a given `uri`. A
# RuntimeError is raised if the cookie's domain is unknown.
def valid_for_uri?(uri)
uri = URI(uri)
# RFC 6265 5.4

private
return false if @secure && uri.scheme != "https"

# Tests if it is OK to accept this cookie if it is sent from a given
# URI/URL, `uri`.
def acceptable_from_uri?(uri)
uri = URI(uri)
acceptable_from_uri?(uri) && Cookie.path_match?(@path, uri.path)
end

host = DomainName.new(uri.host)
private

# RFC 6265 5.3
if host.hostname == @domain
true
elsif @for_domain # !host-only-flag
host.cookie_domain?(@domain_name)
else
@domain.nil?
# Tests if it is OK to accept this cookie if it is sent from a given
# URI/URL, `uri`.
def acceptable_from_uri?(uri)
uri = URI(uri)

host = DomainName.new(uri.host)

# RFC 6265 5.3
if host.hostname == @domain
true
elsif @for_domain # !host-only-flag
host.cookie_domain?(@domain_name)
else
@domain.nil?
end
end
end

module Scanner
RE_BAD_CHAR = /([\x00-\x20\x7F",;\\])/.freeze
module Scanner
RE_BAD_CHAR = /([\x00-\x20\x7F",;\\])/.freeze

module_function
module_function

def quote(s)
return s unless s.match(RE_BAD_CHAR)
def quote(s)
return s unless s.match(RE_BAD_CHAR)

"\"#{s.gsub(/([\\"])/, "\\\\\\1")}\""
"\"#{s.gsub(/([\\"])/, "\\\\\\1")}\""
end
end
end
end
Expand Down
17 changes: 17 additions & 0 deletions sig/domain_name.rbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
module HTTPX
class DomainName
type domain = string | DomainName

include Comparable

def normalize: (String) -> String

def cookie_domain?: (domain, ?bool?) -> bool

def self.new: (domain) -> untyped

private

def initialize: (string) -> untyped
end
end
19 changes: 0 additions & 19 deletions sig/plugins/cookies/domain_name.rbs

This file was deleted.

0 comments on commit cbfb5c9

Please sign in to comment.