mirror of
https://github.com/HoneyryderChuck/httpx.git
synced 2025-08-10 00:01:27 -04:00
moving domain_name module to core httpx, as the punycode IDN translation will be needed at this layer
This commit is contained in:
parent
e271c45582
commit
cbfb5c968e
@ -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:
|
||||
|
@ -6,6 +6,7 @@ require "httpx/extensions"
|
||||
|
||||
require "httpx/errors"
|
||||
require "httpx/utils"
|
||||
require "httpx/domain_name"
|
||||
require "httpx/altsvc"
|
||||
require "httpx/callbacks"
|
||||
require "httpx/loggable"
|
||||
|
@ -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
|
@ -15,7 +15,6 @@ module HTTPX
|
||||
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
|
||||
|
||||
|
@ -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?
|
||||
# See #domain.
|
||||
def domain=(domain)
|
||||
domain = String(domain)
|
||||
|
||||
@domain_name = DomainName.new(domain)
|
||||
# RFC 6265 5.3 5.
|
||||
@for_domain = false if @domain_name.domain.nil? # a public suffix or IP address
|
||||
|
||||
@domain = @domain_name.hostname
|
||||
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
|
||||
|
||||
class << self
|
||||
def new(cookie, *args)
|
||||
return cookie if cookie.is_a?(self)
|
||||
|
||||
super
|
||||
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] == "/"
|
||||
end
|
||||
end
|
||||
|
||||
def initialize(arg, *attrs)
|
||||
@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
|
||||
|
||||
attr_hash.each do |key, val|
|
||||
key = key.downcase.tr("-", "_").to_sym unless key.is_a?(Symbol)
|
||||
|
||||
case key
|
||||
when :domain, :path
|
||||
__send__(:"#{key}=", val)
|
||||
else
|
||||
instance_variable_set(:"@#{key}", val)
|
||||
if domain.start_with?(".")
|
||||
@for_domain = true
|
||||
domain = domain[1..-1]
|
||||
end
|
||||
end if attr_hash
|
||||
|
||||
@path ||= "/"
|
||||
raise ArgumentError, "name must be specified" if @name.nil?
|
||||
end
|
||||
return if domain.empty?
|
||||
|
||||
def expires
|
||||
@expires || (@created_at && @max_age ? @created_at + @max_age : nil)
|
||||
end
|
||||
@domain_name = DomainName.new(domain)
|
||||
# RFC 6265 5.3 5.
|
||||
@for_domain = false if @domain_name.domain.nil? # a public suffix or IP address
|
||||
|
||||
def expired?(time = Time.now)
|
||||
return false unless expires
|
||||
|
||||
expires <= time
|
||||
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
|
||||
|
||||
# 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
|
||||
|
||||
return false if @secure && uri.scheme != "https"
|
||||
|
||||
acceptable_from_uri?(uri) && Cookie.path_match?(@path, uri.path)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# 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?
|
||||
@domain = @domain_name.hostname
|
||||
end
|
||||
end
|
||||
|
||||
module Scanner
|
||||
RE_BAD_CHAR = /([\x00-\x20\x7F",;\\])/.freeze
|
||||
# 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
|
||||
|
||||
module_function
|
||||
class << self
|
||||
def new(cookie, *args)
|
||||
return cookie if cookie.is_a?(self)
|
||||
|
||||
def quote(s)
|
||||
return s unless s.match(RE_BAD_CHAR)
|
||||
super
|
||||
end
|
||||
|
||||
"\"#{s.gsub(/([\\"])/, "\\\\\\1")}\""
|
||||
# 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
|
||||
|
||||
def initialize(arg, *attrs)
|
||||
@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
|
||||
|
||||
attr_hash.each do |key, val|
|
||||
key = key.downcase.tr("-", "_").to_sym unless key.is_a?(Symbol)
|
||||
|
||||
case key
|
||||
when :domain, :path
|
||||
__send__(:"#{key}=", val)
|
||||
else
|
||||
instance_variable_set(:"@#{key}", val)
|
||||
end
|
||||
end if attr_hash
|
||||
|
||||
@path ||= "/"
|
||||
raise ArgumentError, "name must be specified" if @name.nil?
|
||||
end
|
||||
|
||||
def expires
|
||||
@expires || (@created_at && @max_age ? @created_at + @max_age : nil)
|
||||
end
|
||||
|
||||
def expired?(time = Time.now)
|
||||
return false unless expires
|
||||
|
||||
expires <= time
|
||||
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
|
||||
|
||||
# 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
|
||||
|
||||
return false if @secure && uri.scheme != "https"
|
||||
|
||||
acceptable_from_uri?(uri) && Cookie.path_match?(@path, uri.path)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# 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
|
||||
|
||||
module Scanner
|
||||
RE_BAD_CHAR = /([\x00-\x20\x7F",;\\])/.freeze
|
||||
|
||||
module_function
|
||||
|
||||
def quote(s)
|
||||
return s unless s.match(RE_BAD_CHAR)
|
||||
|
||||
"\"#{s.gsub(/([\\"])/, "\\\\\\1")}\""
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
17
sig/domain_name.rbs
Normal file
17
sig/domain_name.rbs
Normal file
@ -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
|
@ -1,19 +0,0 @@
|
||||
module HTTPX
|
||||
module Plugins::Cookies
|
||||
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
|
||||
end
|
Loading…
x
Reference in New Issue
Block a user