Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 20 additions & 2 deletions lib/wreq_ruby/cookie.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ module Wreq
# Cookie SameSite attribute.
#
# Values follow the Rust enum exposed by the native extension.
# Constant names mirror the native SameSite variants.
# standard:disable Naming/ConstantName
class SameSite
# Strict same-site policy.
Strict = nil
Expand Down Expand Up @@ -38,6 +40,7 @@ def eql?(other)
def hash
end
end
# standard:enable Naming/ConstantName

# A single HTTP cookie.
#
Expand Down Expand Up @@ -116,6 +119,11 @@ def same_site_lax?
def same_site_strict?
end

# Returns the SameSite directive, or nil when it is not set.
# @return [Wreq::SameSite, nil]
def same_site
end

# @return [String, nil] Path attribute
def path
end
Expand All @@ -142,6 +150,11 @@ def expires_at
# @return [Float, nil]
def expires
end

# Serializes the cookie as a Set-Cookie string.
# @return [String]
def to_s
end
end

# A cookie store (jar) used by the client to manage cookies across requests.
Expand All @@ -156,10 +169,11 @@ def self.new
def get_all
end

# Add a cookie from a Set-Cookie string for the given URL.
# @param cookie [String, Wreq::Cookie] A Set-Cookie string
# Add a Cookie object or Set-Cookie string for the given URL.
# @param cookie [String, Wreq::Cookie] A Set-Cookie string or Cookie object
# @param url [String]
# @return [void]
# @raise [TypeError] if cookie is neither a String nor Wreq::Cookie
def add(cookie, url)
end

Expand All @@ -180,6 +194,8 @@ def clear

module Wreq
class Cookie
# Returns a compact representation for debugging.
# @return [String]
def inspect
parts = ["#<Wreq::Cookie", name]
parts << "domain=#{domain}" if domain
Expand All @@ -191,6 +207,8 @@ def inspect
end

class Jar
# Returns a compact representation including the cookie count.
# @return [String]
def inspect
"#<Wreq::Jar [#{get_all.length} cookies]>"
end
Expand Down
31 changes: 26 additions & 5 deletions src/cookie.rs
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,12 @@ impl Cookie {
self.0.same_site() == Some(cookie::SameSite::Strict)
}

/// Return the SameSite directive, if set.
#[inline]
pub fn same_site(&self) -> Option<SameSite> {
self.0.same_site().map(SameSite::from_ffi)
}

/// Returns the path directive of the cookie, if set.
#[inline]
pub fn path(&self) -> Option<&str> {
Expand Down Expand Up @@ -216,6 +222,12 @@ impl Cookie {
pub fn expires(&self) -> Option<f64> {
self.0.expires_datetime().map(to_unix_timestamp)
}

/// Serialize the cookie as a Set-Cookie string.
#[inline]
pub fn to_s(&self) -> String {
self.to_string()
}
}

// ===== Native Cookie helpers =====
Expand Down Expand Up @@ -318,14 +330,21 @@ impl Jar {
}

/// Add a cookie to this jar.
pub fn add(&self, cookie: Value, url: String) {
///
/// # Errors
///
/// Returns `TypeError` when `cookie` is neither a [`Cookie`] nor a String.
pub fn add(&self, cookie: Value, url: String) -> Result<(), Error> {
if let Ok(cookie) = Obj::<Cookie>::try_convert(cookie) {
return gvl::nogvl(|| self.0.add(cookie.clone_for_jar(), &url));
gvl::nogvl(|| self.0.add(cookie.clone_for_jar(), &url));
return Ok(());
}

if let Ok(cookie_str) = String::try_convert(cookie) {
gvl::nogvl(|| self.0.add(cookie_str.as_ref(), &url))
}
let ruby = Ruby::get_with(cookie);
let cookie = String::try_convert(cookie)
.map_err(|_| type_error(&ruby, "cookie must be a Wreq::Cookie or String"))?;
gvl::nogvl(|| self.0.add(cookie.as_ref(), &url));
Ok(())
}

/// Remove a cookie from this jar by name and URL.
Expand Down Expand Up @@ -466,11 +485,13 @@ pub fn include(ruby: &Ruby, gem_module: &RModule) -> Result<(), Error> {
cookie_class.define_method("secure?", method!(Cookie::secure, 0))?;
cookie_class.define_method("same_site_lax?", method!(Cookie::same_site_lax, 0))?;
cookie_class.define_method("same_site_strict?", method!(Cookie::same_site_strict, 0))?;
cookie_class.define_method("same_site", method!(Cookie::same_site, 0))?;
cookie_class.define_method("path", method!(Cookie::path, 0))?;
cookie_class.define_method("domain", method!(Cookie::domain, 0))?;
cookie_class.define_method("max_age", method!(Cookie::max_age, 0))?;
cookie_class.define_method("expires_at", method!(Cookie::expires_at, 0))?;
cookie_class.define_method("expires", method!(Cookie::expires, 0))?;
cookie_class.define_method("to_s", method!(Cookie::to_s, 0))?;

// Jar class
let jar_class = gem_module.define_class("Jar", ruby.class_object())?;
Expand Down
27 changes: 24 additions & 3 deletions test/cookie_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,14 @@ def test_add_and_get_all
assert_equal true, (c.secure || c.secure?)
end

def test_add_rejects_invalid_cookie_type
error = assert_raises(TypeError) do
@jar.add(Object.new, @base_url)
end

assert_equal "cookie must be a Wreq::Cookie or String", error.message
end

def test_add_multiple_and_remove
@jar.add("a=1; Path=/", @base_url)
@jar.add("b=2; Path=/", @base_url)
Expand Down Expand Up @@ -115,6 +123,8 @@ def test_cookie_new_minimal
assert_equal false, (c.secure || c.secure?)
assert_equal false, c.same_site_lax?
assert_equal false, c.same_site_strict?
assert_nil c.same_site
assert_equal "sid=abc", c.to_s
end

def test_cookie_new_full_attributes
Expand Down Expand Up @@ -144,6 +154,15 @@ def test_cookie_new_full_attributes
assert_equal true, (c.secure || c.secure?)
assert_equal true, c.same_site_lax?
assert_equal false, c.same_site_strict?
assert_equal Wreq::SameSite::Lax, c.same_site
end

def test_same_site_reader_distinguishes_none_from_unset
cookie = Wreq::Cookie.new("same-site", "value", same_site: Wreq::SameSite::None)

assert_equal Wreq::SameSite::None, cookie.same_site
refute cookie.same_site_lax?
refute cookie.same_site_strict?
end

def test_cookie_new_uses_shared_keyword_validation
Expand Down Expand Up @@ -328,10 +347,12 @@ def test_same_site_flags_from_parsed_header
@jar.add("s2=1; Path=/; SameSite=Lax", @base_url)

cookies = @jar.get_all
h = cookies.to_h { |ck| [ck.name, [ck.same_site_strict?, ck.same_site_lax?]] }
h = cookies.to_h do |cookie|
[cookie.name, [cookie.same_site, cookie.same_site_strict?, cookie.same_site_lax?]]
end

assert_equal [true, false], h["s1"]
assert_equal [false, true], h["s2"]
assert_equal [Wreq::SameSite::Strict, true, false], h["s1"]
assert_equal [Wreq::SameSite::Lax, false, true], h["s2"]
end

def test_request_uncompressed_cookies
Expand Down