]> git.ipfire.org Git - thirdparty/rspamd.git/commitdiff
[Fix] protocol: Pass v3 Content-Type as mime_type parameter
authorVsevolod Stakhov <vsevolod@rspamd.com>
Sat, 7 Feb 2026 15:54:55 +0000 (15:54 +0000)
committerVsevolod Stakhov <vsevolod@rspamd.com>
Sat, 7 Feb 2026 15:54:55 +0000 (15:54 +0000)
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).

src/libserver/protocol.c
src/libserver/protocol.h
src/rspamd_proxy.c
test/functional/cases/001_merged/430_checkv3.robot

index 42b1158c5ddb8d035880677456159712bde8cd07..7df7c8f19babe2a339442eed1707506067725bc7 100644 (file)
@@ -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");
index f6957833aceb318211a3f7631c0efc8672fdf6d8..1816ddf06dbd9b44be78f0ab70636a7bd7e40e90 100644 (file)
@@ -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
index fa3055dc75afe8cccc87cafcf6491a6d13efb378..5ad794470de332e11e0a75e84a57381ab20b2d68 100644 (file)
@@ -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);
index 744aab5235d6d483c6ee24dc7c7a936479494d45..76790e66f5fb21028712993daa0b68f62e8552dc 100644 (file)
@@ -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