mqtt: convert sendleftovers to dynbuf

Avoid frequent strdups/free calls, including the double-free risk.

Reported-by: Ronald Crane
Closes #16823
diff --git a/lib/dynbuf.h b/lib/dynbuf.h
index 71b0cc2..cc7e5a1 100644
--- a/lib/dynbuf.h
+++ b/lib/dynbuf.h
@@ -97,5 +97,6 @@
 #define DYN_PINGPPONG_CMD   (64*1024)
 #define DYN_IMAP_CMD        (64*1024)
 #define DYN_MQTT_RECV       (64*1024)
+#define DYN_MQTT_SEND       0xFFFFFFF
 #define DYN_CRLFILE_SIZE    (400*1024*1024) /* 400mb */
 #endif
diff --git a/lib/mqtt.c b/lib/mqtt.c
index 69bbf65..287037d 100644
--- a/lib/mqtt.c
+++ b/lib/mqtt.c
@@ -112,6 +112,7 @@
   if(!mq)
     return CURLE_OUT_OF_MEMORY;
   Curl_dyn_init(&mq->recvbuf, DYN_MQTT_RECV);
+  Curl_dyn_init(&mq->sendbuf, DYN_MQTT_SEND);
   data->req.p.mqtt = mq;
   return CURLE_OK;
 }
@@ -119,25 +120,24 @@
 static CURLcode mqtt_send(struct Curl_easy *data,
                           const char *buf, size_t len)
 {
-  CURLcode result = CURLE_OK;
   struct MQTT *mq = data->req.p.mqtt;
   size_t n;
-  result = Curl_xfer_send(data, buf, len, FALSE, &n);
+  CURLcode result = Curl_xfer_send(data, buf, len, FALSE, &n);
   if(result)
     return result;
   Curl_debug(data, CURLINFO_HEADER_OUT, buf, (size_t)n);
   if(len != n) {
     size_t nsend = len - n;
-    char *sendleftovers = Curl_memdup(&buf[n], nsend);
-    if(!sendleftovers)
-      return CURLE_OUT_OF_MEMORY;
-    mq->sendleftovers = sendleftovers;
-    mq->nsend = nsend;
+    if(Curl_dyn_len(&mq->sendbuf)) {
+      DEBUGASSERT(Curl_dyn_len(&mq->sendbuf) >= nsend);
+      result = Curl_dyn_tail(&mq->sendbuf, nsend); /* keep this much */
+    }
+    else {
+      result = Curl_dyn_addn(&mq->sendbuf, &buf[n], nsend);
+    }
   }
-  else {
-    mq->sendleftovers = NULL;
-    mq->nsend = 0;
-  }
+  else
+    Curl_dyn_reset(&mq->sendbuf);
   return result;
 }
 
@@ -352,7 +352,7 @@
   CURLcode result = CURLE_OK;
   struct MQTT *mq = data->req.p.mqtt;
   result = mqtt_send(data, "\xe0\x00", 2);
-  Curl_safefree(mq->sendleftovers);
+  Curl_dyn_free(&mq->sendbuf);
   Curl_dyn_free(&mq->recvbuf);
   return result;
 }
@@ -732,7 +732,7 @@
   struct MQTT *mq = data->req.p.mqtt;
   (void)status;
   (void)premature;
-  Curl_safefree(mq->sendleftovers);
+  Curl_dyn_free(&mq->sendbuf);
   Curl_dyn_free(&mq->recvbuf);
   return CURLE_OK;
 }
@@ -740,19 +740,17 @@
 static CURLcode mqtt_doing(struct Curl_easy *data, bool *done)
 {
   CURLcode result = CURLE_OK;
-  struct connectdata *conn = data->conn;
-  struct mqtt_conn *mqtt = &conn->proto.mqtt;
+  struct mqtt_conn *mqtt = &data->conn->proto.mqtt;
   struct MQTT *mq = data->req.p.mqtt;
   ssize_t nread;
   unsigned char recvbyte;
 
   *done = FALSE;
 
-  if(mq->nsend) {
+  if(Curl_dyn_len(&mq->sendbuf)) {
     /* send the remainder of an outgoing packet */
-    char *ptr = mq->sendleftovers;
-    result = mqtt_send(data, mq->sendleftovers, mq->nsend);
-    free(ptr);
+    result = mqtt_send(data, Curl_dyn_ptr(&mq->sendbuf),
+                       Curl_dyn_len(&mq->sendbuf));
     if(result)
       return result;
   }
diff --git a/lib/mqtt.h b/lib/mqtt.h
index 99ab12a..b181e3c 100644
--- a/lib/mqtt.h
+++ b/lib/mqtt.h
@@ -49,15 +49,13 @@
 
 /* protocol-specific transfer-related data */
 struct MQTT {
-  char *sendleftovers;
-  size_t nsend; /* size of sendleftovers */
-
+  struct dynbuf sendbuf;
   /* when receiving */
-  size_t npacket; /* byte counter */
-  unsigned char firstbyte;
-  size_t remaining_length;
   struct dynbuf recvbuf;
+  size_t npacket; /* byte counter */
+  size_t remaining_length;
   unsigned char pkt_hd[4]; /* for decoding the arriving packet length */
+  unsigned char firstbyte;
 };
 
 #endif /* HEADER_CURL_MQTT_H */