diff --git a/mocket.js.mbt b/mocket.js.mbt index 33de5b5..d190c5e 100644 --- a/mocket.js.mbt +++ b/mocket.js.mbt @@ -369,16 +369,20 @@ pub fn listen_ffi(mocket : Mocket, address : String) -> Unit { let responder = mocket.execute_middlewares(event, handler) // let boundary = "----------------moonbit-" + port.to_string() responder.options(event.res) + let safe_headers : Map[StringView, StringView] = Map([]) + event.res.headers.each(fn(k, v) { + safe_headers[k] = sanitize_header_value(v.to_owned()) + }) res.write_head( event.res.status_code.to_int(), { - let headers_obj = @js.Value::from_json(event.res.headers.to_json()) catch { + let headers_obj = @js.Value::from_json(safe_headers.to_json()) catch { _ => @js.Object::new().to_value() } if !event.res.cookies.is_empty() { let cookies = event.res.cookies .values() - .map(fn(c) { c.to_string() }) + .map(fn(c) { sanitize_header_value(c.to_string()) }) .to_array() set_js_property(headers_obj, "Set-Cookie", array_to_js(cookies)) } diff --git a/mocket.native.mbt b/mocket.native.mbt index b0c6ff0..fd2ca79 100644 --- a/mocket.native.mbt +++ b/mocket.native.mbt @@ -359,11 +359,13 @@ async fn send_native_response( conn : @http.ServerConnection, response : HttpResponse, ) -> Unit { - let headers = view_headers_to_strings(response.headers) + let raw_headers = view_headers_to_strings(response.headers) + let headers : Map[String, String] = Map([]) + raw_headers.each(fn(k, v) { headers[k] = sanitize_header_value(v) }) if !response.cookies.is_empty() { let cookies = response.cookies .values() - .map(cookie => cookie.to_string()) + .map(fn(c) { sanitize_header_value(c.to_string()) }) .to_array() headers.set("Set-Cookie", cookies.join("\r\nSet-Cookie: ")) } diff --git a/native/mongoose/mongoose.mbt b/native/mongoose/mongoose.mbt index bb655ba..5be42e4 100644 --- a/native/mongoose/mongoose.mbt +++ b/native/mongoose/mongoose.mbt @@ -187,12 +187,18 @@ fn handle_request( ) res.status(response.status_code.to_int()) response.headers.each((key, value) => { - res.set_header(to_cbytes(key), to_cbytes(value)) + res.set_header( + to_cbytes(key), + to_cbytes(@mocket.sanitize_header_value(value.to_owned())), + ) }) response.cookies .values() .each(cookie => { - res.set_header(to_cbytes("Set-Cookie"), to_cbytes(cookie.to_string())) + res.set_header( + to_cbytes("Set-Cookie"), + to_cbytes(@mocket.sanitize_header_value(cookie.to_string())), + ) }) res.end_bytes(response.raw_body, response.raw_body.length()) }) diff --git a/pkg.generated.mbti b/pkg.generated.mbti index bc31229..dfb5456 100644 --- a/pkg.generated.mbti +++ b/pkg.generated.mbti @@ -41,6 +41,8 @@ pub fn register_ws_connection(String, (String) -> Unit, (Bytes) -> Unit, () -> U pub fn register_ws_handler(Mocket, Int) -> Unit +pub fn sanitize_header_value(String) -> String + pub async fn serve_ffi(Mocket, port~ : Int) -> Unit noraise pub async fn[T, E : Error] suspend(((T) -> Unit, (E) -> Unit) -> Unit) -> T raise E diff --git a/utils.mbt b/utils.mbt index 3a1572f..d59ae47 100644 --- a/utils.mbt +++ b/utils.mbt @@ -100,6 +100,21 @@ fn parse_kv(part : BytesView, map : Map[String, String]) -> Unit { } } +///| +pub fn sanitize_header_value(s : String) -> String { + if !s.contains("\r") && !s.contains("\n") { + return s + } + let buf = StringBuilder::new() + for c in s { + match c { + '\r' | '\n' => () + _ => buf.write_char(c) + } + } + buf.to_string() +} + ///| pub fn escape_html(s : String) -> String { let buf = StringBuilder::new()