Skip to content

Commit fe81363

Browse files
quinnjclaude
andauthored
Write cookie attributes one value per call in stringify (#1350)
* Write cookie attributes one value per call in stringify Base's varargs write(io, x1, xs...) iterates a heterogeneous tuple and dispatches each element dynamically, which juliac --trim=safe cannot statically resolve. Cookie stringify used it in four places; writing one value per call is byte-identical in output and compiles to direct calls. The server-side Request rebuild half of this work already landed via #1349. Found trimming a Servo app on Julia nightly (JuliaCon 2026 workshop); together with #1349 this clears the last HTTP-owned verify errors there. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * test: cover cookie stringify in trim builds --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
1 parent 444b92b commit fe81363

4 files changed

Lines changed: 72 additions & 5 deletions

File tree

src/http_cookies.jl

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -124,12 +124,30 @@ function stringify(c::Cookie, isrequest::Bool=true)::String
124124
nm = strip(c.name)
125125
!iscookienamevalid(nm) && return ""
126126
io = IOBuffer()
127-
write(io, sanitizeCookieName(nm), '=', sanitizeCookieValue(c.value))
127+
# One value per `write`: Base's varargs `write(io, x1, xs...)` iterates a
128+
# heterogeneous tuple and dispatches each element dynamically, which
129+
# juliac --trim cannot resolve; single-argument writes are direct.
130+
write(io, sanitizeCookieName(nm))
131+
write(io, '=')
132+
write(io, sanitizeCookieValue(c.value))
128133
if !isrequest
129-
length(c.path) > 0 && write(io, "; Path=", sanitizeCookiePath(c.path))
130-
length(c.domain) > 0 && validCookieDomain(c.domain) && write(io, "; Domain=", c.domain[1] == '.' ? SubString(c.domain, 2) : c.domain)
131-
validCookieExpires(c.expires) && write(io, "; Expires=", Dates.format(c.expires, Dates.RFC1123Format), " GMT")
132-
c.maxage > 0 && write(io, "; Max-Age=", string(c.maxage))
134+
if length(c.path) > 0
135+
write(io, "; Path=")
136+
write(io, sanitizeCookiePath(c.path))
137+
end
138+
if length(c.domain) > 0 && validCookieDomain(c.domain)
139+
write(io, "; Domain=")
140+
write(io, c.domain[1] == '.' ? SubString(c.domain, 2) : c.domain)
141+
end
142+
if validCookieExpires(c.expires)
143+
write(io, "; Expires=")
144+
write(io, Dates.format(c.expires, Dates.RFC1123Format))
145+
write(io, " GMT")
146+
end
147+
if c.maxage > 0
148+
write(io, "; Max-Age=")
149+
write(io, string(c.maxage))
150+
end
133151
c.maxage < 0 && write(io, "; Max-Age=0")
134152
c.httponly && write(io, "; HttpOnly")
135153
c.secure && write(io, "; Secure")

test/http_cookie_tests.jl

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using Test
22
using HTTP
33
using Reseau
4+
using Dates
45

56
const HT = HTTP
67

@@ -35,6 +36,24 @@ end
3536
@test occursin("; Secure", rendered)
3637
@test occursin("; SameSite=Lax", rendered)
3738

39+
expires = DateTime(2030, 1, 2, 3, 4, 5)
40+
complete = HT.Cookie(
41+
" complete ",
42+
"a b";
43+
path="/docs;private",
44+
domain="example.com",
45+
expires,
46+
maxage=-1,
47+
httponly=true,
48+
secure=true,
49+
samesite=HT.SameSiteStrictMode,
50+
)
51+
expires_text = Dates.format(expires, Dates.RFC1123Format)
52+
@test HT.stringify(complete) == "complete=\"a b\""
53+
@test HT.stringify(complete, false) ==
54+
"complete=\"a b\"; Path=/docsprivate; Domain=example.com; " *
55+
"Expires=$expires_text GMT; Max-Age=0; HttpOnly; Secure; SameSite=Strict"
56+
3857
req_headers = HT.Headers()
3958
HT.appendheader(req_headers, "Cookie", "a=1; b=two")
4059
request = HT.Request("GET", "/"; headers = req_headers)

test/http_trim_cookies.jl

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
include("trim_workload_common.jl")
2+
3+
using Dates
4+
5+
function run_http_trim_cookies()::Nothing
6+
expires = DateTime(2030, 1, 2, 3, 4, 5)
7+
cookie = HT.Cookie("session", "a b")
8+
cookie.path = "/docs;private"
9+
cookie.domain = ".example.com"
10+
cookie.expires = expires
11+
cookie.maxage = 60
12+
cookie.secure = true
13+
cookie.httponly = true
14+
cookie.samesite = HT.SameSiteStrictMode
15+
expected = "session=\"a b\"; Path=/docsprivate; Domain=example.com; " *
16+
"Expires=Wed, 02 Jan 2030 03:04:05 GMT; Max-Age=60; " *
17+
"HttpOnly; Secure; SameSite=Strict"
18+
HT.stringify(cookie, false) == expected || error("unexpected response cookie")
19+
HT.stringify(cookie) == "session=\"a b\"" || error("unexpected request cookie")
20+
return nothing
21+
end
22+
23+
function @main(args::Vector{String})::Cint
24+
_ = args
25+
run_http_trim_cookies()
26+
return 0
27+
end
28+
29+
Base.Experimental.entrypoint(main, (Vector{String},))

test/trim_compile_tests.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,7 @@ end
157157
("http_trim_client_h2_tcp_roundtrip.jl", "http_trim_client_h2_tcp_roundtrip"),
158158
("http_trim_client_h2_roundtrip.jl", "http_trim_client_h2_roundtrip"),
159159
("http_trim_client_server.jl", "http_trim_client_server"),
160+
("http_trim_cookies.jl", "http_trim_cookies"),
160161
("http_trim_open_fileserver.jl", "http_trim_open_fileserver"),
161162
("http_trim_http2.jl", "http_trim_http2"),
162163
("http_trim_websocket.jl", "http_trim_websocket"),

0 commit comments

Comments
 (0)