Skip to content

Commit 50f33b4

Browse files
authored
Add domain filter to cookie template function (#452)
1 parent 41fe01a commit 50f33b4

7 files changed

Lines changed: 119 additions & 14 deletions

File tree

crates-cli/yaak-cli/src/plugin_events.rs

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ use yaak::plugin_events::{
1414
use yaak::render::{render_grpc_request, render_http_request};
1515
use yaak::send::{SendHttpRequestWithPluginsParams, send_http_request_with_plugins};
1616
use yaak_crypto::manager::EncryptionManager;
17+
use yaak_http::cookies::get_cookie_value_from_jar;
1718
use yaak_models::blob_manager::BlobManager;
1819
use yaak_models::models::Environment;
1920
use yaak_models::queries::any_request::AnyRequest;
@@ -496,10 +497,8 @@ async fn build_plugin_reply(
496497
}
497498
};
498499

499-
let value = cookie_jar.cookies.into_iter().find_map(|c| {
500-
let (name, value) = parse_cookie_name_value(&c.raw_cookie)?;
501-
if name == req.name { Some(value) } else { None }
502-
});
500+
let value =
501+
get_cookie_value_from_jar(cookie_jar.cookies, &req.name, req.domain.as_deref());
503502
Some(InternalEventPayload::GetCookieValueResponse(GetCookieValueResponse { value }))
504503
}
505504
HostRequest::WindowInfo(req) => {
@@ -532,7 +531,6 @@ async fn render_json_value_for_cli<T: TemplateCallback>(
532531
render_json_value_raw(value, vars, cb, opt).await
533532
}
534533

535-
536534
fn parse_cookie_name_value(raw_cookie: &str) -> Option<(String, String)> {
537535
let first_part = raw_cookie.split(';').next()?.trim();
538536
let (name, value) = first_part.split_once('=')?;

crates-tauri/yaak-app/src/plugin_events.rs

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ use yaak::plugin_events::{
1919
GroupedPluginEvent, HostRequest, SharedPluginEventContext, handle_shared_plugin_event,
2020
};
2121
use yaak_crypto::manager::EncryptionManager;
22+
use yaak_http::cookies::get_cookie_value_from_jar;
2223
use yaak_models::models::{HttpResponse, Plugin};
2324
use yaak_models::queries::any_request::AnyRequest;
2425
use yaak_models::util::UpdateSource;
@@ -420,12 +421,7 @@ async fn handle_host_plugin_request<R: Runtime>(
420421
let window = get_window_from_plugin_context(app_handle, plugin_context)?;
421422
let value = match cookie_jar_from_window(&window) {
422423
None => None,
423-
Some(j) => j.cookies.into_iter().find_map(|c| match Cookie::parse(c.raw_cookie) {
424-
Ok(c) if c.name().to_string().eq(&req.name) => {
425-
Some(c.value_trimmed().to_string())
426-
}
427-
_ => None,
428-
}),
424+
Some(j) => get_cookie_value_from_jar(j.cookies, &req.name, req.domain.as_deref()),
429425
};
430426
Ok(Some(InternalEventPayload::GetCookieValueResponse(GetCookieValueResponse { value })))
431427
}

crates/yaak-http/src/cookies.rs

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,30 @@ impl CookieStore {
124124
}
125125
}
126126

127+
/// Get a stored cookie value by name, optionally scoped to an exact stored domain.
128+
pub fn get_cookie_value_from_jar(
129+
cookies: impl IntoIterator<Item = Cookie>,
130+
name: &str,
131+
domain: Option<&str>,
132+
) -> Option<String> {
133+
let domain = domain.and_then(normalize_cookie_domain_filter);
134+
135+
cookies.into_iter().find_map(|cookie| {
136+
let (cookie_name, value) = parse_cookie_name_value(&cookie.raw_cookie)?;
137+
if cookie_name != name {
138+
return None;
139+
}
140+
141+
if let Some(domain) = domain.as_deref() {
142+
if !cookie_domain_matches_filter(&cookie.domain, domain) {
143+
return None;
144+
}
145+
}
146+
147+
Some(value)
148+
})
149+
}
150+
127151
/// Parse name=value from a cookie string (raw_cookie format)
128152
fn parse_cookie_name_value(raw_cookie: &str) -> Option<(String, String)> {
129153
// The raw_cookie typically looks like "name=value" or "name=value; attr1; attr2=..."
@@ -135,6 +159,20 @@ fn parse_cookie_name_value(raw_cookie: &str) -> Option<(String, String)> {
135159
if name.is_empty() { None } else { Some((name, value)) }
136160
}
137161

162+
fn normalize_cookie_domain_filter(domain: &str) -> Option<String> {
163+
let domain = domain.trim().trim_start_matches('.').to_lowercase();
164+
if domain.is_empty() { None } else { Some(domain) }
165+
}
166+
167+
fn cookie_domain_matches_filter(cookie_domain: &CookieDomain, domain: &str) -> bool {
168+
match cookie_domain {
169+
CookieDomain::HostOnly(cookie_domain) | CookieDomain::Suffix(cookie_domain) => {
170+
normalize_cookie_domain_filter(cookie_domain).is_some_and(|d| d == domain)
171+
}
172+
CookieDomain::NotPresent | CookieDomain::Empty => false,
173+
}
174+
}
175+
138176
/// Parse a Set-Cookie header into a Cookie
139177
fn parse_set_cookie(header_value: &str, request_url: &Url) -> Option<Cookie> {
140178
let parsed = cookie::Cookie::parse(header_value).ok()?;
@@ -278,6 +316,15 @@ fn is_localhost(domain: &str) -> bool {
278316
mod tests {
279317
use super::*;
280318

319+
fn cookie(raw_cookie: &str, domain: CookieDomain) -> Cookie {
320+
Cookie {
321+
raw_cookie: raw_cookie.to_string(),
322+
domain,
323+
expires: CookieExpires::SessionEnd,
324+
path: ("/".to_string(), false),
325+
}
326+
}
327+
281328
#[test]
282329
fn test_parse_cookie_name_value() {
283330
assert_eq!(
@@ -387,6 +434,52 @@ mod tests {
387434
assert_eq!(store.get_all_cookies().len(), 1);
388435
}
389436

437+
#[test]
438+
fn test_get_cookie_value_preserves_name_only_first_match() {
439+
let cookies = vec![
440+
cookie("co-auth=", CookieDomain::HostOnly("foo.example.com".to_string())),
441+
cookie("co-auth=token", CookieDomain::Suffix("example.com".to_string())),
442+
];
443+
444+
assert_eq!(get_cookie_value_from_jar(cookies, "co-auth", None), Some("".to_string()));
445+
}
446+
447+
#[test]
448+
fn test_get_cookie_value_matches_domain() {
449+
let cookies = vec![
450+
cookie("co-auth=", CookieDomain::HostOnly("foo.example.com".to_string())),
451+
cookie("co-auth=token", CookieDomain::Suffix("example.com".to_string())),
452+
];
453+
454+
assert_eq!(
455+
get_cookie_value_from_jar(cookies, "co-auth", Some("example.com")),
456+
Some("token".to_string())
457+
);
458+
}
459+
460+
#[test]
461+
fn test_get_cookie_value_normalizes_domain_filter() {
462+
let cookies = vec![cookie(
463+
"co-auth=token",
464+
CookieDomain::Suffix("Example.COM".to_string()),
465+
)];
466+
467+
assert_eq!(
468+
get_cookie_value_from_jar(cookies, "co-auth", Some(" .example.com ")),
469+
Some("token".to_string())
470+
);
471+
}
472+
473+
#[test]
474+
fn test_get_cookie_value_requires_exact_stored_domain_match() {
475+
let cookies = vec![cookie(
476+
"co-auth=token",
477+
CookieDomain::HostOnly("foo.example.com".to_string()),
478+
)];
479+
480+
assert_eq!(get_cookie_value_from_jar(cookies, "co-auth", Some("example.com")), None);
481+
}
482+
390483
#[test]
391484
fn test_is_single_component_domain() {
392485
// Single-component domains (TLDs)

crates/yaak-plugins/bindings/gen_events.ts

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/yaak-plugins/src/events.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -307,6 +307,9 @@ pub struct ListCookieNamesResponse {
307307
#[ts(export, export_to = "gen_events.ts")]
308308
pub struct GetCookieValueRequest {
309309
pub name: String,
310+
311+
#[ts(optional = nullable)]
312+
pub domain: Option<String>,
310313
}
311314

312315
#[derive(Debug, Clone, Default, Serialize, Deserialize, TS)]

packages/plugin-runtime-types/src/bindings/gen_events.ts

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

plugins/template-function-cookie/src/index.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,27 @@ export const plugin: PluginDefinition = {
1111
type: "text",
1212
name: "name",
1313
label: "Cookie Name",
14+
placeholder: "cookie_name",
15+
},
16+
{
17+
type: "text",
18+
name: "domain",
19+
label: "Domain",
20+
placeholder: "example.com",
21+
description:
22+
"Optionally filter by domain, useful if multiple cookies with the same name.",
23+
optional: true,
1424
},
1525
],
1626
async onRender(ctx: Context, args: CallTemplateFunctionArgs): Promise<string | null> {
1727
// The legacy name was cookie_name, but we changed it
1828
const name = args.values.cookie_name ?? args.values.name;
19-
return ctx.cookies.getValue({ name: String(name) });
29+
const domain = String(args.values.domain ?? "").trim();
30+
31+
return ctx.cookies.getValue({
32+
name: String(name),
33+
...(domain.length > 0 ? { domain } : {}),
34+
});
2035
},
2136
},
2237
],

0 commit comments

Comments
 (0)