diff --git a/changes/http-text-copy.md b/changes/http-text-copy.md new file mode 100644 index 00000000..1297b000 --- /dev/null +++ b/changes/http-text-copy.md @@ -0,0 +1,6 @@ +bump: patch +type: fix +**`Http.text` and `Http.header` return copies.** They handed back the handle's own buffer (and on macOS the +response object's string), which `Http.free` then released: a text read before the free and used after it +was garbage or empty - maroon-lake's map list wrote a 0-byte maps.json. Each call now returns a string that +is the caller's to keep. Read a body once per response. diff --git a/docs/language/http/http-header.md b/docs/language/http/http-header.md index 0eb7ed47..308f9fcc 100644 --- a/docs/language/http/http-header.md +++ b/docs/language/http/http-header.md @@ -11,7 +11,7 @@ ns: Http member: header --- -Returns a response header value by name (case-insensitive), or an empty result if absent. +Returns a response header value by name (case-insensitive), or an empty result if absent. The value is a copy, yours to keep after Http.free. ```ludic program Demo { diff --git a/docs/language/http/http-text.md b/docs/language/http/http-text.md index b3d43516..a23df222 100644 --- a/docs/language/http/http-text.md +++ b/docs/language/http/http-text.md @@ -11,7 +11,7 @@ ns: Http member: text --- -Returns the response body as a NUL-terminated string. Pair with Json.parse (#44) for JSON APIs. +Returns the response body as a NUL-terminated string: a copy that stays yours after Http.free lets the handle go, so read it once and keep what you read. Pair with Json.parse (#44) for JSON APIs. ```ludic program Demo { diff --git a/runtime/native/http.ludic b/runtime/native/http.ludic index e8a2e22c..97d4cb0b 100644 --- a/runtime/native/http.ludic +++ b/runtime/native/http.ludic @@ -237,9 +237,13 @@ function http_ok(rt_http_st: mut RtHttpState, h: int) -> bool { return (st >= 200) and (st < 300) } +# the body as text: a COPY, the caller's to keep - the handle's own buffer goes with Http.free, and a +# text read before the free and used after it wrote an empty file (maroon-lake's maps.json) function http_text(rt_http_st: mut RtHttpState, h: int) -> string { if not http_valid(rt_http_st, h) { return null } - return rt_http_st.h_body[h - 1] + let b = rt_http_st.h_body[h - 1] + if b == null { return null } + return http_slice_dup(b, 0, rt_http_st.h_blen[h - 1]) } function http_body_len(rt_http_st: mut RtHttpState, h: int) -> int { @@ -250,7 +254,12 @@ function http_body_len(rt_http_st: mut RtHttpState, h: int) -> int { function http_header_of(rt_http_st: mut RtHttpState, h: int, name: pointer) -> string { if not http_valid(rt_http_st, h) { return null } let s = h - 1 - if rt_http_st.h_native[s] == 1 { return hs_header(s, name) } + # a copy on both paths: the native value is the response's own, released with Http.free + if rt_http_st.h_native[s] == 1 { + let v = hs_header(s, name) + if v == null { return null } + return http_slice_dup(v, 0, http_cstr_len(v)) + } return http_find_header(rt_http_st.h_hdr[s], name) }