Skip to content

Commit 6bd62fb

Browse files
committed
Preserve Wget explicit cookies across redirect origins
1 parent 1841411 commit 6bd62fb

23 files changed

Lines changed: 154 additions & 13 deletions

‎commands/wget/README.md‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,8 @@ restoration, exact GNU progress and diagnostic bytes, and options outside the
3838
documented package help are not claimed.
3939

4040
The current workspace lets an explicit `--header='Cookie: ...'` replace
41-
the outgoing jar cookie field, as in GNU Wget 1.25.0. The jar still receives
41+
the outgoing jar cookie field, including across origins, as in GNU Wget 1.25.0.
42+
The jar still receives
4243
response cookies. This correction is not included in published 0.2.0.
4344
Cookie matching includes host/domain boundaries, path order, Secure, Max-Age,
4445
Expires, replacement and deletion. Public-suffix database and IDNA behavior

‎commands/wget/main.mbt‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -340,6 +340,7 @@ async fn download_one(
340340
keep_output_on_not_modified=options.timestamping,
341341
cookie_jar?=jar,
342342
custom_cookie_mode=@netops.ReplaceStored,
343+
custom_cookie_cross_origin=true,
343344
basic_credentials?=options.user.map(user => {
344345
user + ":" + options.password.unwrap_or("")
345346
}),

‎commands/wget/options.mbt‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ fn usage() -> String {
6060
#| --password=PASSWORD HTTP Basic password
6161
#| --auth-no-challenge send Basic credentials on every request, including redirects
6262
#| --load-cookies=FILE load a Netscape cookie jar
63-
#| --header='Cookie: ...' replace the outgoing jar cookie field
63+
#| --header='Cookie: ...' override jar cookies, including on redirects
6464
#| --save-cookies=FILE save persistent cookies
6565
#| --keep-session-cookies also save session cookies
6666
#| --no-cookies disable cookie storage

‎core/cli/catalog.mbt‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -205,7 +205,7 @@ pub fn command_catalog() -> Array[CommandContract] {
205205
["duration"],
206206
exit_codes=[0, 124, 125, 126, 127],
207207
),
208-
// header + load-cookies: explicit Cookie replaces the outgoing jar field.
208+
// header + load-cookies: explicit Cookie overrides jar fields across origins.
209209
contract("wget", Restricted, NoInput, ByteStream, [Network, FileWrite], [
210210
"append-output", "body-data", "body-file", "connect-timeout", "continue", "content-disposition",
211211
"header", "input-file", "max-redirect", "method", "no-check-certificate", "no-proxy",

‎core/netops/README.md‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,9 @@ to replay an already consumed stdin body.
4646
The current workspace uses the public client's persistent headers and per-request
4747
headers as separate layers. `CustomCookieMode::SeparateFields` sends stored
4848
cookies before the explicit Cookie field (curl); `ReplaceStored` sends only the
49-
explicit field while continuing to receive cookies (wget). This corrects the
49+
explicit field while continuing to receive cookies (wget). Wget also enables
50+
`custom_cookie_cross_origin`; curl leaves it disabled, independently of Basic
51+
credential scope. This corrects the
5052
0.2.0 rejection without new FFI or a replacement HTTP transport. It does not
5153
provide arbitrary ordered repeated headers: each layer is still a map.
5254
The response cookies array preserves each Set-Cookie field.

‎core/netops/header_layers_wbtest.mbt‎

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ async test "Cookie jar and explicit header remain separate across redirects" {
4545
headers=[http_header("cOoKiE", "sid=explicit")],
4646
cookie_jar=jar,
4747
custom_cookie_mode=mode,
48+
custom_cookie_cross_origin=mode is ReplaceStored,
4849
cookie_data="sid=ignored-when-custom-header",
4950
redirects=FollowRedirects(3),
5051
),
@@ -66,7 +67,13 @@ async test "Cookie jar and explicit header remain separate across redirects" {
6667
},
6768
)
6869
assert_eq(cookies[1], cookies[0])
69-
assert_eq(cookies[2], ["Cookie: sid=destination"])
70+
assert_eq(
71+
cookies[2],
72+
match mode {
73+
SeparateFields => ["Cookie: sid=destination"]
74+
ReplaceStored => ["cOoKiE: sid=explicit"]
75+
},
76+
)
7077
}
7178
})
7279
})

‎core/netops/pkg.generated.mbti‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ pub fn retryable_failure(TransferErrorKind, Int?) -> Bool
2626

2727
pub async fn transfer(String, TransferOutput, TransferOptions, observer? : async (TransferEvent) -> Unit) -> TransferResult
2828

29-
pub fn transfer_options(request_method? : HttpMethod, headers? : Array[HttpHeader], body? : RequestBody, redirects? : RedirectPolicy, preserve_method_on_redirect? : Bool, verify_tls? : Bool, proxy_url? : String, connect_timeout_ms? : Int, idle_timeout_ms? : Int, max_time_ms? : Int, fail_on_http_error? : Bool, http_error_statuses? : Array[Int], resume_from? : Int64, keep_output_on_not_modified? : Bool, cookie_jar? : CookieJar, cookie_data? : String, custom_cookie_mode? : CustomCookieMode, basic_credentials? : String, basic_preemptive? : Bool, basic_credential_scope? : BasicCredentialScope) -> TransferOptions
29+
pub fn transfer_options(request_method? : HttpMethod, headers? : Array[HttpHeader], body? : RequestBody, redirects? : RedirectPolicy, preserve_method_on_redirect? : Bool, verify_tls? : Bool, proxy_url? : String, connect_timeout_ms? : Int, idle_timeout_ms? : Int, max_time_ms? : Int, fail_on_http_error? : Bool, http_error_statuses? : Array[Int], resume_from? : Int64, keep_output_on_not_modified? : Bool, cookie_jar? : CookieJar, cookie_data? : String, custom_cookie_mode? : CustomCookieMode, custom_cookie_cross_origin? : Bool, basic_credentials? : String, basic_preemptive? : Bool, basic_credential_scope? : BasicCredentialScope) -> TransferOptions
3030

3131
pub fn url_host(String) -> String raise TransferError
3232

@@ -177,6 +177,7 @@ pub(all) struct TransferOptions {
177177
cookie_jar : CookieJar?
178178
cookie_data : String?
179179
custom_cookie_mode : CustomCookieMode
180+
custom_cookie_cross_origin : Bool
180181
basic_credentials : String?
181182
basic_preemptive : Bool
182183
basic_credential_scope : BasicCredentialScope

‎core/netops/transfer.mbt‎

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ fn filtered_headers(
102102
headers : ArrayView[HttpHeader],
103103
strip_sensitive : Bool,
104104
strip_entity : Bool,
105+
keep_cookie? : Bool = false,
105106
) -> Array[HttpHeader] {
106107
let result : Array[HttpHeader] = []
107108
for header in headers {
@@ -110,7 +111,7 @@ fn filtered_headers(
110111
(
111112
name == "authorization" ||
112113
name == "proxy-authorization" ||
113-
name == "cookie"
114+
(name == "cookie" && !keep_cookie)
114115
) {
115116
continue
116117
}
@@ -792,7 +793,12 @@ pub async fn transfer(
792793
body,
793794
options.preserve_method_on_redirect,
794795
)
795-
headers = filtered_headers(headers, cross_origin, strip_entity)
796+
headers = filtered_headers(
797+
headers,
798+
cross_origin,
799+
strip_entity,
800+
keep_cookie=options.custom_cookie_cross_origin,
801+
)
796802
request_method = next_method
797803
body = next_body
798804
parsed = next

‎core/netops/types.mbt‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,7 @@ pub(all) struct TransferOptions {
145145
cookie_jar : CookieJar?
146146
cookie_data : String?
147147
custom_cookie_mode : CustomCookieMode
148+
custom_cookie_cross_origin : Bool
148149
basic_credentials : String?
149150
basic_preemptive : Bool
150151
basic_credential_scope : BasicCredentialScope
@@ -169,6 +170,7 @@ pub fn transfer_options(
169170
cookie_jar? : CookieJar,
170171
cookie_data? : String,
171172
custom_cookie_mode? : CustomCookieMode = SeparateFields,
173+
custom_cookie_cross_origin? : Bool = false,
172174
basic_credentials? : String,
173175
basic_preemptive? : Bool = true,
174176
basic_credential_scope? : BasicCredentialScope = InitialOrigin,
@@ -191,6 +193,7 @@ pub fn transfer_options(
191193
cookie_jar,
192194
cookie_data,
193195
custom_cookie_mode,
196+
custom_cookie_cross_origin,
194197
basic_credentials,
195198
basic_preemptive,
196199
basic_credential_scope,

‎docs/adr/0005-transfer.md‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,9 @@ The current workspace sends matching jar cookies through persistent client heade
4040
and an explicit curl `Cookie:` through per-request headers. async 0.22.1 emits
4141
these separately and in that order. Wget selects `ReplaceStored` so its explicit
4242
field replaces outgoing jar cookies without disabling response-cookie storage.
43+
Wget also forwards that explicit field across origins, as its 1.25.0 wire probe
44+
shows; curl removes it. An independent `custom_cookie_cross_origin` flag preserves
45+
this difference without changing Basic or custom Authorization handling.
4346
The earlier single-map rejection was too restrictive. A pure MoonBit raw TCP
4447
probe confirmed the public API path and compared curl 8.7.1 and Wget 1.25.0;
4548
core tests assert the wire fields and redirect behavior, and the shared CLI

0 commit comments

Comments
 (0)