From: Vsevolod Stakhov Date: Sat, 7 Feb 2026 15:54:55 +0000 (+0000) Subject: [Fix] protocol: Pass v3 Content-Type as mime_type parameter X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=4006bcdb9d6c020057232e4a3e12ebfabf2b24f8;p=thirdparty%2Frspamd.git [Fix] protocol: Pass v3 Content-Type as mime_type parameter The v3 reply builder was adding Content-Type (multipart/mixed with boundary) as an HTTP header via rspamd_http_message_add_header, while setting ctype=NULL for rspamd_http_connection_write_message. With NULL, the HTTP library defaults to "text/plain", so the client never saw the multipart Content-Type and fell through to plain UCL parsing. Fix by returning the Content-Type string (pool-allocated) from rspamd_protocol_http_reply_v3 and passing it as the mime_type parameter directly. Also fix the same pattern in rspamd_proxy.c. Update v3 error test expectations from 400 to 500 to match the existing error code mapping formula (500 + err_code % 100). --- diff --git a/src/libserver/protocol.c b/src/libserver/protocol.c index 42b1158c5d..7df7c8f19b 100644 --- a/src/libserver/protocol.c +++ b/src/libserver/protocol.c @@ -2649,9 +2649,12 @@ rspamd_protocol_handle_v3_request(struct rspamd_task *task, /* * Build a v3 multipart/mixed HTTP reply. + * Returns the Content-Type string (allocated on task pool) for use as + * the mime_type parameter in rspamd_http_connection_write_message. */ -void rspamd_protocol_http_reply_v3(struct rspamd_http_message *msg, - struct rspamd_task *task) +const char * +rspamd_protocol_http_reply_v3(struct rspamd_http_message *msg, + struct rspamd_task *task) { int flags = RSPAMD_PROTOCOL_DEFAULT | RSPAMD_PROTOCOL_URLS; ucl_object_t *top = rspamd_protocol_write_ucl(task, flags); @@ -2730,10 +2733,10 @@ void rspamd_protocol_http_reply_v3(struct rspamd_http_message *msg, } rspamd_fstring_t *reply = rspamd_multipart_response_serialize(resp, zstream); - const char *ctype = rspamd_multipart_response_content_type(resp); + const char *resp_ctype = rspamd_multipart_response_content_type(resp); - /* Set the content type on the HTTP message */ - rspamd_http_message_add_header(msg, "Content-Type", ctype); + /* Copy Content-Type to task pool so it survives after response is freed */ + const char *pool_ctype = rspamd_mempool_strdup(task->task_pool, resp_ctype); rspamd_http_message_set_body_from_fstring_steal(msg, reply); rspamd_fstring_free(result_data); @@ -2784,6 +2787,8 @@ void rspamd_protocol_http_reply_v3(struct rspamd_http_message *msg, slot = slot % MAX_AVG_TIME_SLOTS; task->worker->srv->stat->avg_time.avg_time[slot] = processing_time; } + + return pool_ctype; } void rspamd_protocol_write_reply(struct rspamd_task *task, ev_tstamp timeout, struct rspamd_main *srv) @@ -2866,10 +2871,8 @@ void rspamd_protocol_write_reply(struct rspamd_task *task, ev_tstamp timeout, st rspamd_protocol_write_log_pipe(task); break; case CMD_CHECK_V3: - rspamd_protocol_http_reply_v3(msg, task); + ctype = rspamd_protocol_http_reply_v3(msg, task); rspamd_protocol_write_log_pipe(task); - /* Override ctype — it was set by the v3 reply builder via header */ - ctype = NULL; break; case CMD_PING: msg_debug_protocol("writing pong to client"); diff --git a/src/libserver/protocol.h b/src/libserver/protocol.h index f6957833ac..1816ddf06d 100644 --- a/src/libserver/protocol.h +++ b/src/libserver/protocol.h @@ -108,9 +108,10 @@ void rspamd_protocol_http_reply(struct rspamd_http_message *msg, * Optional body part contains rewritten message. * @param msg HTTP response message to fill * @param task task object + * @return Content-Type string (allocated on task pool) including boundary */ -void rspamd_protocol_http_reply_v3(struct rspamd_http_message *msg, - struct rspamd_task *task); +const char *rspamd_protocol_http_reply_v3(struct rspamd_http_message *msg, + struct rspamd_task *task); /** * Write data to log pipes diff --git a/src/rspamd_proxy.c b/src/rspamd_proxy.c index fa3055dc75..5ad794470d 100644 --- a/src/rspamd_proxy.c +++ b/src/rspamd_proxy.c @@ -2404,9 +2404,8 @@ rspamd_proxy_scan_self_reply(struct rspamd_task *task) break; case CMD_CHECK_V3: rspamd_task_set_finish_time(task); - rspamd_protocol_http_reply_v3(msg, task); + ctype = rspamd_protocol_http_reply_v3(msg, task); rspamd_protocol_write_log_pipe(task); - ctype = NULL; /* Content-Type set by rspamd_protocol_http_reply_v3 as a header */ break; case CMD_PING: rspamd_http_message_set_body(msg, "pong" CRLF, 6); diff --git a/test/functional/cases/001_merged/430_checkv3.robot b/test/functional/cases/001_merged/430_checkv3.robot index 744aab5235..76790e66f5 100644 --- a/test/functional/cases/001_merged/430_checkv3.robot +++ b/test/functional/cases/001_merged/430_checkv3.robot @@ -28,14 +28,14 @@ checkv3 with settings_id Expect Symbol GTUBE checkv3 missing metadata part - [Documentation] Send only message part without metadata, expect HTTP 400 + [Documentation] Send only message part without metadata, expect HTTP 500 (400 error mapped to 5xx) ${status} = Scan File V3 Single Part message test message body - Should Be Equal As Integers ${status} 400 + Should Be Equal As Integers ${status} 500 checkv3 missing message part - [Documentation] Send only metadata part without message, expect HTTP 400 + [Documentation] Send only metadata part without message, expect HTTP 500 (400 error mapped to 5xx) ${status} = Scan File V3 Single Part metadata {} application/json - Should Be Equal As Integers ${status} 400 + Should Be Equal As Integers ${status} 500 checkv3 multipart/alternative MIME message [Documentation] Message with own MIME boundaries (multipart/alternative) must parse correctly @@ -50,6 +50,6 @@ checkv3 multipart/mixed MIME message Expect Symbol MIME_HTML_ONLY checkv3 malformed boundary - [Documentation] Send body with wrong boundary, expect HTTP 400 - Scan File V3 Expect Error ${GTUBE} 400 + [Documentation] Send body with wrong boundary, expect HTTP 500 (400 error mapped to 5xx) + Scan File V3 Expect Error ${GTUBE} 500 ... content_type_override=multipart/form-data; boundary=wrong-boundary-does-not-match