You are viewing a plain text version of this content. The canonical link for it is here.
Posted to c-commits@axis.apache.org by ro...@apache.org on 2026/08/28 20:55:11 UTC

(axis-axis2-c-core) 01/02: Write the whole chunk the chunk header announced

This is an automated email from the ASF dual-hosted git repository.

robertlazarski pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/axis-axis2-c-core.git

commit e379cb7bf80dfdb655525375bfdcb176e51a7dd3
Author: Robert Lazarski <ro...@gmail.com>
AuthorDate: Fri Aug 28 03:40:11 2026 -1000

    Write the whole chunk the chunk header announced
    
    axutil_http_chunked_stream_write emitted a header naming count bytes
    and then wrote whatever one stream write accepted, which for a socket
    is whatever fits in its send buffer. Callers cannot recover from a
    short return there: two of the three respond by calling again with the
    entire buffer, so a second chunk header lands inside a chunk that was
    never finished, and it repeats while the writes keep coming up short.
    Complete the announced body or fail.
    
    Co-Authored-By: Claude Opus 5 (1M context) <no...@anthropic.com>
---
 src/core/transport/http/sender/http_client.c | 20 +++++++++++++-------
 util/src/http_chunked_stream.c               | 28 +++++++++++++++++++++++++++-
 2 files changed, 40 insertions(+), 8 deletions(-)

diff --git a/src/core/transport/http/sender/http_client.c b/src/core/transport/http/sender/http_client.c
index a9202a2a2..fa3b14cc7 100644
--- a/src/core/transport/http/sender/http_client.c
+++ b/src/core/transport/http/sender/http_client.c
@@ -770,20 +770,26 @@ axis2_http_client_send(
                 return AXIS2_FAILURE;
             }
 
-            while(written < client->req_body_size)
+            /* One call, not a loop. axutil_http_chunked_stream_write writes the
+             * whole body it was given or fails; looping on a short return used
+             * to re-send the body from the start under a fresh chunk header,
+             * which corrupts the framing rather than completing the write.
+             * Take the return in an int and test it before widening -- the -1
+             * is only distinguishable from a huge length while it is signed. */
             {
-                /* Take the return in an int and test it before widening: the
-                 * -1 is only distinguishable from a huge length while it is
-                 * still signed. */
                 int chunk_written = axutil_http_chunked_stream_write(chunked_stream, env,
                     client->req_body, client->req_body_size);
 
-                if(chunk_written < 0)
+                if(chunk_written < 0 || (size_t)chunk_written != client->req_body_size)
                 {
+                    AXIS2_LOG_ERROR(env->log, AXIS2_LOG_SI,
+                        "Failed to write the request body to the chunked stream");
                     status = AXIS2_FAILURE;
-                    break;
                 }
-                written = (size_t)chunk_written;
+                else
+                {
+                    written = (size_t)chunk_written;
+                }
             }
 
             if(AXIS2_SUCCESS == status)
diff --git a/util/src/http_chunked_stream.c b/util/src/http_chunked_stream.c
index ee37b6c22..06e101883 100644
--- a/util/src/http_chunked_stream.c
+++ b/util/src/http_chunked_stream.c
@@ -158,7 +158,33 @@ axutil_http_chunked_stream_write(
     }
     sprintf(tmp_buf, "%x%s", (unsigned int)count, AXIS2_HTTP_CRLF);
     axutil_stream_write(stream, env, tmp_buf, axutil_strlen(tmp_buf));
-    len = axutil_stream_write(stream, env, buffer, count);
+
+    /* The header above has already announced count bytes, so exactly count
+     * have to follow it or the framing is wrong for everything after this
+     * chunk. axutil_stream_write is under no obligation to take them all in
+     * one call -- a socket accepts what fits in its send buffer and reports
+     * the rest back -- so keep going until the announced body is complete.
+     *
+     * Returning a short count instead was worse than it looks: callers cannot
+     * recover from it. Two of them respond by calling again with the whole
+     * buffer, which emits a second chunk header in the middle of a chunk that
+     * was never finished, and repeats if the writes keep coming up short. */
+    {
+        size_t body_written = 0;
+
+        while(body_written < count)
+        {
+            len = axutil_stream_write(stream, env, (const axis2_char_t *)buffer + body_written,
+                count - body_written);
+            if(len <= 0)
+            {
+                return -1;
+            }
+            body_written += (size_t)len;
+        }
+        len = (int)body_written;
+    }
+
     axutil_stream_write(stream, env, AXIS2_HTTP_CRLF, 2);
     return len;
 }