Index: trunk/src/libs/curl-8.3.0/lib/bufq.c
===================================================================
--- trunk/src/libs/curl-8.3.0/lib/bufq.c	(revision 101259)
+++ trunk/src/libs/curl-8.3.0/lib/bufq.c	(revision 101259)
@@ -0,0 +1,678 @@
+/***************************************************************************
+ *                                  _   _ ____  _
+ *  Project                     ___| | | |  _ \| |
+ *                             / __| | | | |_) | |
+ *                            | (__| |_| |  _ <| |___
+ *                             \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
+ *
+ * This software is licensed as described in the file COPYING, which
+ * you should have received as part of this distribution. The terms
+ * are also available at https://curl.se/docs/copyright.html.
+ *
+ * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+ * copies of the Software, and permit persons to whom the Software is
+ * furnished to do so, under the terms of the COPYING file.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ * SPDX-License-Identifier: curl
+ *
+ ***************************************************************************/
+
+#include "curl_setup.h"
+#include "bufq.h"
+
+/* The last 3 #include files should be in this order */
+#include "curl_printf.h"
+#include "curl_memory.h"
+#include "memdebug.h"
+
+static bool chunk_is_empty(const struct buf_chunk *chunk)
+{
+  return chunk->r_offset >= chunk->w_offset;
+}
+
+static bool chunk_is_full(const struct buf_chunk *chunk)
+{
+  return chunk->w_offset >= chunk->dlen;
+}
+
+static size_t chunk_len(const struct buf_chunk *chunk)
+{
+  return chunk->w_offset - chunk->r_offset;
+}
+
+static size_t chunk_space(const struct buf_chunk *chunk)
+{
+  return chunk->dlen - chunk->w_offset;
+}
+
+static void chunk_reset(struct buf_chunk *chunk)
+{
+  chunk->next = NULL;
+  chunk->r_offset = chunk->w_offset = 0;
+}
+
+static size_t chunk_append(struct buf_chunk *chunk,
+                           const unsigned char *buf, size_t len)
+{
+  unsigned char *p = &chunk->x.data[chunk->w_offset];
+  size_t n = chunk->dlen - chunk->w_offset;
+  DEBUGASSERT(chunk->dlen >= chunk->w_offset);
+  if(n) {
+    n = CURLMIN(n, len);
+    memcpy(p, buf, n);
+    chunk->w_offset += n;
+  }
+  return n;
+}
+
+static size_t chunk_read(struct buf_chunk *chunk,
+                         unsigned char *buf, size_t len)
+{
+  unsigned char *p = &chunk->x.data[chunk->r_offset];
+  size_t n = chunk->w_offset - chunk->r_offset;
+  DEBUGASSERT(chunk->w_offset >= chunk->r_offset);
+  if(!n) {
+    return 0;
+  }
+  else if(n <= len) {
+    memcpy(buf, p, n);
+    chunk->r_offset = chunk->w_offset = 0;
+    return n;
+  }
+  else {
+    memcpy(buf, p, len);
+    chunk->r_offset += len;
+    return len;
+  }
+}
+
+static ssize_t chunk_slurpn(struct buf_chunk *chunk, size_t max_len,
+                            Curl_bufq_reader *reader,
+                            void *reader_ctx, CURLcode *err)
+{
+  unsigned char *p = &chunk->x.data[chunk->w_offset];
+  size_t n = chunk->dlen - chunk->w_offset; /* free amount */
+  ssize_t nread;
+
+  DEBUGASSERT(chunk->dlen >= chunk->w_offset);
+  if(!n) {
+    *err = CURLE_AGAIN;
+    return -1;
+  }
+  if(max_len && n > max_len)
+    n = max_len;
+  nread = reader(reader_ctx, p, n, err);
+  if(nread > 0) {
+    DEBUGASSERT((size_t)nread <= n);
+    chunk->w_offset += nread;
+  }
+  return nread;
+}
+
+static void chunk_peek(const struct buf_chunk *chunk,
+                       const unsigned char **pbuf, size_t *plen)
+{
+  DEBUGASSERT(chunk->w_offset >= chunk->r_offset);
+  *pbuf = &chunk->x.data[chunk->r_offset];
+  *plen = chunk->w_offset - chunk->r_offset;
+}
+
+static void chunk_peek_at(const struct buf_chunk *chunk, size_t offset,
+                          const unsigned char **pbuf, size_t *plen)
+{
+  offset += chunk->r_offset;
+  DEBUGASSERT(chunk->w_offset >= offset);
+  *pbuf = &chunk->x.data[offset];
+  *plen = chunk->w_offset - offset;
+}
+
+static size_t chunk_skip(struct buf_chunk *chunk, size_t amount)
+{
+  size_t n = chunk->w_offset - chunk->r_offset;
+  DEBUGASSERT(chunk->w_offset >= chunk->r_offset);
+  if(n) {
+    n = CURLMIN(n, amount);
+    chunk->r_offset += n;
+    if(chunk->r_offset == chunk->w_offset)
+      chunk->r_offset = chunk->w_offset = 0;
+  }
+  return n;
+}
+
+static void chunk_shift(struct buf_chunk *chunk)
+{
+  if(chunk->r_offset) {
+    if(!chunk_is_empty(chunk)) {
+      size_t n = chunk->w_offset - chunk->r_offset;
+      memmove(chunk->x.data, chunk->x.data + chunk->r_offset, n);
+      chunk->w_offset -= chunk->r_offset;
+      chunk->r_offset = 0;
+    }
+    else {
+      chunk->r_offset = chunk->w_offset = 0;
+    }
+  }
+}
+
+static void chunk_list_free(struct buf_chunk **anchor)
+{
+  struct buf_chunk *chunk;
+  while(*anchor) {
+    chunk = *anchor;
+    *anchor = chunk->next;
+    free(chunk);
+  }
+}
+
+
+
+void Curl_bufcp_init(struct bufc_pool *pool,
+                     size_t chunk_size, size_t spare_max)
+{
+  DEBUGASSERT(chunk_size > 0);
+  DEBUGASSERT(spare_max > 0);
+  memset(pool, 0, sizeof(*pool));
+  pool->chunk_size = chunk_size;
+  pool->spare_max = spare_max;
+}
+
+static CURLcode bufcp_take(struct bufc_pool *pool,
+                           struct buf_chunk **pchunk)
+{
+  struct buf_chunk *chunk = NULL;
+
+  if(pool->spare) {
+    chunk = pool->spare;
+    pool->spare = chunk->next;
+    --pool->spare_count;
+    chunk_reset(chunk);
+    *pchunk = chunk;
+    return CURLE_OK;
+  }
+
+  chunk = calloc(1, sizeof(*chunk) + pool->chunk_size);
+  if(!chunk) {
+    *pchunk = NULL;
+    return CURLE_OUT_OF_MEMORY;
+  }
+  chunk->dlen = pool->chunk_size;
+  *pchunk = chunk;
+  return CURLE_OK;
+}
+
+static void bufcp_put(struct bufc_pool *pool,
+                      struct buf_chunk *chunk)
+{
+  if(pool->spare_count >= pool->spare_max) {
+    free(chunk);
+  }
+  else {
+    chunk_reset(chunk);
+    chunk->next = pool->spare;
+    pool->spare = chunk;
+    ++pool->spare_count;
+  }
+}
+
+void Curl_bufcp_free(struct bufc_pool *pool)
+{
+  chunk_list_free(&pool->spare);
+  pool->spare_count = 0;
+}
+
+static void bufq_init(struct bufq *q, struct bufc_pool *pool,
+                      size_t chunk_size, size_t max_chunks, int opts)
+{
+  DEBUGASSERT(chunk_size > 0);
+  DEBUGASSERT(max_chunks > 0);
+  memset(q, 0, sizeof(*q));
+  q->chunk_size = chunk_size;
+  q->max_chunks = max_chunks;
+  q->pool = pool;
+  q->opts = opts;
+}
+
+void Curl_bufq_init2(struct bufq *q, size_t chunk_size, size_t max_chunks,
+                     int opts)
+{
+  bufq_init(q, NULL, chunk_size, max_chunks, opts);
+}
+
+void Curl_bufq_init(struct bufq *q, size_t chunk_size, size_t max_chunks)
+{
+  bufq_init(q, NULL, chunk_size, max_chunks, BUFQ_OPT_NONE);
+}
+
+void Curl_bufq_initp(struct bufq *q, struct bufc_pool *pool,
+                     size_t max_chunks, int opts)
+{
+  bufq_init(q, pool, pool->chunk_size, max_chunks, opts);
+}
+
+void Curl_bufq_free(struct bufq *q)
+{
+  chunk_list_free(&q->head);
+  chunk_list_free(&q->spare);
+  q->tail = NULL;
+  q->chunk_count = 0;
+}
+
+void Curl_bufq_reset(struct bufq *q)
+{
+  struct buf_chunk *chunk;
+  while(q->head) {
+    chunk = q->head;
+    q->head = chunk->next;
+    chunk->next = q->spare;
+    q->spare = chunk;
+  }
+  q->tail = NULL;
+}
+
+size_t Curl_bufq_len(const struct bufq *q)
+{
+  const struct buf_chunk *chunk = q->head;
+  size_t len = 0;
+  while(chunk) {
+    len += chunk_len(chunk);
+    chunk = chunk->next;
+  }
+  return len;
+}
+
+size_t Curl_bufq_space(const struct bufq *q)
+{
+  size_t space = 0;
+  if(q->tail)
+    space += chunk_space(q->tail);
+  if(q->spare) {
+    struct buf_chunk *chunk = q->spare;
+    while(chunk) {
+      space += chunk->dlen;
+      chunk = chunk->next;
+    }
+  }
+  if(q->chunk_count < q->max_chunks) {
+    space += (q->max_chunks - q->chunk_count) * q->chunk_size;
+  }
+  return space;
+}
+
+bool Curl_bufq_is_empty(const struct bufq *q)
+{
+  return !q->head || chunk_is_empty(q->head);
+}
+
+bool Curl_bufq_is_full(const struct bufq *q)
+{
+  if(!q->tail || q->spare)
+    return FALSE;
+  if(q->chunk_count < q->max_chunks)
+    return FALSE;
+  if(q->chunk_count > q->max_chunks)
+    return TRUE;
+  /* we have no spares and cannot make more, is the tail full? */
+  return chunk_is_full(q->tail);
+}
+
+static struct buf_chunk *get_spare(struct bufq *q)
+{
+  struct buf_chunk *chunk = NULL;
+
+  if(q->spare) {
+    chunk = q->spare;
+    q->spare = chunk->next;
+    chunk_reset(chunk);
+    return chunk;
+  }
+
+  if(q->chunk_count >= q->max_chunks && (!(q->opts & BUFQ_OPT_SOFT_LIMIT)))
+    return NULL;
+
+  if(q->pool) {
+    if(bufcp_take(q->pool, &chunk))
+      return NULL;
+    ++q->chunk_count;
+    return chunk;
+  }
+  else {
+    chunk = calloc(1, sizeof(*chunk) + q->chunk_size);
+    if(!chunk)
+      return NULL;
+    chunk->dlen = q->chunk_size;
+    ++q->chunk_count;
+    return chunk;
+  }
+}
+
+static void prune_head(struct bufq *q)
+{
+  struct buf_chunk *chunk;
+
+  while(q->head && chunk_is_empty(q->head)) {
+    chunk = q->head;
+    q->head = chunk->next;
+    if(q->tail == chunk)
+      q->tail = q->head;
+    if(q->pool) {
+      bufcp_put(q->pool, chunk);
+      --q->chunk_count;
+    }
+    else if((q->chunk_count > q->max_chunks) ||
+       (q->opts & BUFQ_OPT_NO_SPARES)) {
+      /* SOFT_LIMIT allowed us more than max. free spares until
+       * we are at max again. Or free them if we are configured
+       * to not use spares. */
+      free(chunk);
+      --q->chunk_count;
+    }
+    else {
+      chunk->next = q->spare;
+      q->spare = chunk;
+    }
+  }
+}
+
+static struct buf_chunk *get_non_full_tail(struct bufq *q)
+{
+  struct buf_chunk *chunk;
+
+  if(q->tail && !chunk_is_full(q->tail))
+    return q->tail;
+  chunk = get_spare(q);
+  if(chunk) {
+    /* new tail, and possibly new head */
+    if(q->tail) {
+      q->tail->next = chunk;
+      q->tail = chunk;
+    }
+    else {
+      DEBUGASSERT(!q->head);
+      q->head = q->tail = chunk;
+    }
+  }
+  return chunk;
+}
+
+ssize_t Curl_bufq_write(struct bufq *q,
+                        const unsigned char *buf, size_t len,
+                        CURLcode *err)
+{
+  struct buf_chunk *tail;
+  ssize_t nwritten = 0;
+  size_t n;
+
+  DEBUGASSERT(q->max_chunks > 0);
+  while(len) {
+    tail = get_non_full_tail(q);
+    if(!tail) {
+      if(q->chunk_count < q->max_chunks) {
+        *err = CURLE_OUT_OF_MEMORY;
+        return -1;
+      }
+      break;
+    }
+    n = chunk_append(tail, buf, len);
+    if(!n)
+      break;
+    nwritten += n;
+    buf += n;
+    len -= n;
+  }
+  if(nwritten == 0 && len) {
+    *err = CURLE_AGAIN;
+    return -1;
+  }
+  *err = CURLE_OK;
+  return nwritten;
+}
+
+ssize_t Curl_bufq_read(struct bufq *q, unsigned char *buf, size_t len,
+                       CURLcode *err)
+{
+  ssize_t nread = 0;
+  size_t n;
+
+  *err = CURLE_OK;
+  while(len && q->head) {
+    n = chunk_read(q->head, buf, len);
+    if(n) {
+      nread += n;
+      buf += n;
+      len -= n;
+    }
+    prune_head(q);
+  }
+  if(nread == 0) {
+    *err = CURLE_AGAIN;
+    return -1;
+  }
+  return nread;
+}
+
+bool Curl_bufq_peek(struct bufq *q,
+                    const unsigned char **pbuf, size_t *plen)
+{
+  if(q->head && chunk_is_empty(q->head)) {
+    prune_head(q);
+  }
+  if(q->head && !chunk_is_empty(q->head)) {
+    chunk_peek(q->head, pbuf, plen);
+    return TRUE;
+  }
+  *pbuf = NULL;
+  *plen = 0;
+  return FALSE;
+}
+
+bool Curl_bufq_peek_at(struct bufq *q, size_t offset,
+                       const unsigned char **pbuf, size_t *plen)
+{
+  struct buf_chunk *c = q->head;
+  size_t clen;
+
+  while(c) {
+    clen = chunk_len(c);
+    if(!clen)
+      break;
+    if(offset >= clen) {
+      offset -= clen;
+      c = c->next;
+      continue;
+    }
+    chunk_peek_at(c, offset, pbuf, plen);
+    return TRUE;
+  }
+  *pbuf = NULL;
+  *plen = 0;
+  return FALSE;
+}
+
+void Curl_bufq_skip(struct bufq *q, size_t amount)
+{
+  size_t n;
+
+  while(amount && q->head) {
+    n = chunk_skip(q->head, amount);
+    amount -= n;
+    prune_head(q);
+  }
+}
+
+void Curl_bufq_skip_and_shift(struct bufq *q, size_t amount)
+{
+  Curl_bufq_skip(q, amount);
+  if(q->tail)
+    chunk_shift(q->tail);
+}
+
+ssize_t Curl_bufq_pass(struct bufq *q, Curl_bufq_writer *writer,
+                       void *writer_ctx, CURLcode *err)
+{
+  const unsigned char *buf;
+  size_t blen;
+  ssize_t nwritten = 0;
+
+  while(Curl_bufq_peek(q, &buf, &blen)) {
+    ssize_t chunk_written;
+
+    chunk_written = writer(writer_ctx, buf, blen, err);
+    if(chunk_written < 0) {
+      if(!nwritten || *err != CURLE_AGAIN) {
+        /* blocked on first write or real error, fail */
+        nwritten = -1;
+      }
+      break;
+    }
+    if(!chunk_written) {
+      if(!nwritten) {
+        /* treat as blocked */
+        *err = CURLE_AGAIN;
+        nwritten = -1;
+      }
+      break;
+    }
+    Curl_bufq_skip(q, (size_t)chunk_written);
+    nwritten += chunk_written;
+  }
+  return nwritten;
+}
+
+ssize_t Curl_bufq_write_pass(struct bufq *q,
+                             const unsigned char *buf, size_t len,
+                             Curl_bufq_writer *writer, void *writer_ctx,
+                             CURLcode *err)
+{
+  ssize_t nwritten = 0, n;
+
+  *err = CURLE_OK;
+  while(len) {
+    if(Curl_bufq_is_full(q)) {
+      /* try to make room in case we are full */
+      n = Curl_bufq_pass(q, writer, writer_ctx, err);
+      if(n < 0) {
+        if(*err != CURLE_AGAIN) {
+          /* real error, fail */
+          return -1;
+        }
+        /* would block, bufq is full, give up */
+        break;
+      }
+    }
+
+    /* Add whatever is remaining now to bufq */
+    n = Curl_bufq_write(q, buf, len, err);
+    if(n < 0) {
+      if(*err != CURLE_AGAIN) {
+        /* real error, fail */
+        return -1;
+      }
+      /* no room in bufq */
+      break;
+    }
+    /* edge case of writer returning 0 (and len is >0)
+     * break or we might enter an infinite loop here */
+    if(n == 0)
+      break;
+
+    /* Maybe only part of `data` has been added, continue to loop */
+    buf += (size_t)n;
+    len -= (size_t)n;
+    nwritten += (size_t)n;
+  }
+
+  if(!nwritten && len) {
+    *err = CURLE_AGAIN;
+    return -1;
+  }
+  *err = CURLE_OK;
+  return nwritten;
+}
+
+ssize_t Curl_bufq_sipn(struct bufq *q, size_t max_len,
+                       Curl_bufq_reader *reader, void *reader_ctx,
+                       CURLcode *err)
+{
+  struct buf_chunk *tail = NULL;
+  ssize_t nread;
+
+  *err = CURLE_AGAIN;
+  tail = get_non_full_tail(q);
+  if(!tail) {
+    if(q->chunk_count < q->max_chunks) {
+      *err = CURLE_OUT_OF_MEMORY;
+      return -1;
+    }
+    /* full, blocked */
+    *err = CURLE_AGAIN;
+    return -1;
+  }
+
+  nread = chunk_slurpn(tail, max_len, reader, reader_ctx, err);
+  if(nread < 0) {
+    return -1;
+  }
+  else if(nread == 0) {
+    /* eof */
+    *err = CURLE_OK;
+  }
+  return nread;
+}
+
+/**
+ * Read up to `max_len` bytes and append it to the end of the buffer queue.
+ * if `max_len` is 0, no limit is imposed and the call behaves exactly
+ * the same as `Curl_bufq_slurp()`.
+ * Returns the total amount of buf read (may be 0) or -1 on other
+ * reader errors.
+ * Note that even in case of a -1 chunks may have been read and
+ * the buffer queue will have different length than before.
+ */
+static ssize_t bufq_slurpn(struct bufq *q, size_t max_len,
+                           Curl_bufq_reader *reader, void *reader_ctx,
+                           CURLcode *err)
+{
+  ssize_t nread = 0, n;
+
+  *err = CURLE_AGAIN;
+  while(1) {
+
+    n = Curl_bufq_sipn(q, max_len, reader, reader_ctx, err);
+    if(n < 0) {
+      if(!nread || *err != CURLE_AGAIN) {
+        /* blocked on first read or real error, fail */
+        nread = -1;
+      }
+      else
+        *err = CURLE_OK;
+      break;
+    }
+    else if(n == 0) {
+      /* eof */
+      *err = CURLE_OK;
+      break;
+    }
+    nread += (size_t)n;
+    if(max_len) {
+      DEBUGASSERT((size_t)n <= max_len);
+      max_len -= (size_t)n;
+      if(!max_len)
+        break;
+    }
+    /* give up slurping when we get less bytes than we asked for */
+    if(q->tail && !chunk_is_full(q->tail))
+      break;
+  }
+  return nread;
+}
+
+ssize_t Curl_bufq_slurp(struct bufq *q, Curl_bufq_reader *reader,
+                        void *reader_ctx, CURLcode *err)
+{
+  return bufq_slurpn(q, 0, reader, reader_ctx, err);
+}
Index: trunk/src/libs/curl-8.3.0/lib/bufq.h
===================================================================
--- trunk/src/libs/curl-8.3.0/lib/bufq.h	(revision 101259)
+++ trunk/src/libs/curl-8.3.0/lib/bufq.h	(revision 101259)
@@ -0,0 +1,271 @@
+#ifndef HEADER_CURL_BUFQ_H
+#define HEADER_CURL_BUFQ_H
+/***************************************************************************
+ *                                  _   _ ____  _
+ *  Project                     ___| | | |  _ \| |
+ *                             / __| | | | |_) | |
+ *                            | (__| |_| |  _ <| |___
+ *                             \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
+ *
+ * This software is licensed as described in the file COPYING, which
+ * you should have received as part of this distribution. The terms
+ * are also available at https://curl.se/docs/copyright.html.
+ *
+ * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+ * copies of the Software, and permit persons to whom the Software is
+ * furnished to do so, under the terms of the COPYING file.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ * SPDX-License-Identifier: curl
+ *
+ ***************************************************************************/
+#include "curl_setup.h"
+
+#include <curl/curl.h>
+
+/**
+ * A chunk of bytes for reading and writing.
+ * The size is fixed a creation with read and write offset
+ * for where unread content is.
+ */
+struct buf_chunk {
+  struct buf_chunk *next;  /* to keep it in a list */
+  size_t dlen;             /* the amount of allocated x.data[] */
+  size_t r_offset;         /* first unread bytes */
+  size_t w_offset;         /* one after last written byte */
+  union {
+    unsigned char data[1]; /* the buffer for `dlen` bytes */
+    void *dummy;           /* alignment */
+  } x;
+};
+
+/**
+ * A pool for providing/keeping a number of chunks of the same size
+ *
+ * The same pool can be shared by many `bufq` instances. However, a pool
+ * is not thread safe. All bufqs using it are supposed to operate in the
+ * same thread.
+ */
+struct bufc_pool {
+  struct buf_chunk *spare;  /* list of available spare chunks */
+  size_t chunk_size;        /* the size of chunks in this pool */
+  size_t spare_count;       /* current number of spare chunks in list */
+  size_t spare_max;         /* max number of spares to keep */
+};
+
+void Curl_bufcp_init(struct bufc_pool *pool,
+                     size_t chunk_size, size_t spare_max);
+
+void Curl_bufcp_free(struct bufc_pool *pool);
+
+/**
+ * A queue of byte chunks for reading and writing.
+ * Reading is done from `head`, writing is done to `tail`.
+ *
+ * `bufq`s can be empty or full or neither. Its `len` is the number
+ * of bytes that can be read. For an empty bufq, `len` will be 0.
+ *
+ * By default, a bufq can hold up to `max_chunks * chunk_size` number
+ * of bytes. When `max_chunks` are used (in the `head` list) and the
+ * `tail` chunk is full, the bufq will report that it is full.
+ *
+ * On a full bufq, `len` may be less than the maximum number of bytes,
+ * e.g. when the head chunk is partially read. `len` may also become
+ * larger than the max when option `BUFQ_OPT_SOFT_LIMIT` is used.
+ *
+ * By default, writing to a full bufq will return (-1, CURLE_AGAIN). Same
+ * as reading from an empty bufq.
+ * With `BUFQ_OPT_SOFT_LIMIT` set, a bufq will allow writing becond this
+ * limit and use more than `max_chunks`. However it will report that it
+ * is full nevertheless. This is provided for situation where writes
+ * preferably never fail (except for memory exhaustion).
+ *
+ * By default and without a pool, a bufq will keep chunks that read
+ * read empty in its `spare` list. Option `BUFQ_OPT_NO_SPARES` will
+ * disable that and free chunks once they become empty.
+ *
+ * When providing a pool to a bufq, all chunk creation and spare handling
+ * will be delegated to that pool.
+ */
+struct bufq {
+  struct buf_chunk *head;       /* chunk with bytes to read from */
+  struct buf_chunk *tail;       /* chunk to write to */
+  struct buf_chunk *spare;      /* list of free chunks, unless `pool` */
+  struct bufc_pool *pool;       /* optional pool for free chunks */
+  size_t chunk_count;           /* current number of chunks in `head+spare` */
+  size_t max_chunks;            /* max `head` chunks to use */
+  size_t chunk_size;            /* size of chunks to manage */
+  int opts;                     /* options for handling queue, see below */
+};
+
+/**
+ * Default behaviour: chunk limit is "hard", meaning attempts to write
+ * more bytes than can be hold in `max_chunks` is refused and will return
+ * -1, CURLE_AGAIN. */
+#define BUFQ_OPT_NONE        (0)
+/**
+ * Make `max_chunks` a "soft" limit. A bufq will report that it is "full"
+ * when `max_chunks` are used, but allows writing beyond this limit.
+ */
+#define BUFQ_OPT_SOFT_LIMIT  (1 << 0)
+/**
+ * Do not keep spare chunks.
+ */
+#define BUFQ_OPT_NO_SPARES   (1 << 1)
+
+/**
+ * Initialize a buffer queue that can hold up to `max_chunks` buffers
+ * each of size `chunk_size`. The bufq will not allow writing of
+ * more bytes than can be held in `max_chunks`.
+ */
+void Curl_bufq_init(struct bufq *q, size_t chunk_size, size_t max_chunks);
+
+/**
+ * Initialize a buffer queue that can hold up to `max_chunks` buffers
+ * each of size `chunk_size` with the given options. See `BUFQ_OPT_*`.
+ */
+void Curl_bufq_init2(struct bufq *q, size_t chunk_size,
+                     size_t max_chunks, int opts);
+
+void Curl_bufq_initp(struct bufq *q, struct bufc_pool *pool,
+                     size_t max_chunks, int opts);
+
+/**
+ * Reset the buffer queue to be empty. Will keep any allocated buffer
+ * chunks around.
+ */
+void Curl_bufq_reset(struct bufq *q);
+
+/**
+ * Free all resources held by the buffer queue.
+ */
+void Curl_bufq_free(struct bufq *q);
+
+/**
+ * Return the total amount of data in the queue.
+ */
+size_t Curl_bufq_len(const struct bufq *q);
+
+/**
+ * Return the total amount of free space in the queue.
+ * The returned length is the number of bytes that can
+ * be expected to be written successfully to the bufq,
+ * providing no memory allocations fail.
+ */
+size_t Curl_bufq_space(const struct bufq *q);
+
+/**
+ * Returns TRUE iff there is no data in the buffer queue.
+ */
+bool Curl_bufq_is_empty(const struct bufq *q);
+
+/**
+ * Returns TRUE iff there is no space left in the buffer queue.
+ */
+bool Curl_bufq_is_full(const struct bufq *q);
+
+/**
+ * Write buf to the end of the buffer queue. The buf is copied
+ * and the amount of copied bytes is returned.
+ * A return code of -1 indicates an error, setting `err` to the
+ * cause. An err of CURLE_AGAIN is returned if the buffer queue is full.
+ */
+ssize_t Curl_bufq_write(struct bufq *q,
+                        const unsigned char *buf, size_t len,
+                        CURLcode *err);
+
+/**
+ * Read buf from the start of the buffer queue. The buf is copied
+ * and the amount of copied bytes is returned.
+ * A return code of -1 indicates an error, setting `err` to the
+ * cause. An err of CURLE_AGAIN is returned if the buffer queue is empty.
+ */
+ssize_t Curl_bufq_read(struct bufq *q, unsigned char *buf, size_t len,
+                        CURLcode *err);
+
+/**
+ * Peek at the head chunk in the buffer queue. Returns a pointer to
+ * the chunk buf (at the current offset) and its length. Does not
+ * modify the buffer queue.
+ * Returns TRUE iff bytes are available. Sets `pbuf` to NULL and `plen`
+ * to 0 when no bytes are available.
+ * Repeated calls return the same information until the buffer queue
+ * is modified, see `Curl_bufq_skip()``
+ */
+bool Curl_bufq_peek(struct bufq *q,
+                    const unsigned char **pbuf, size_t *plen);
+
+bool Curl_bufq_peek_at(struct bufq *q, size_t offset,
+                       const unsigned char **pbuf, size_t *plen);
+
+/**
+ * Tell the buffer queue to discard `amount` buf bytes at the head
+ * of the queue. Skipping more buf than is currently buffered will
+ * just empty the queue.
+ */
+void Curl_bufq_skip(struct bufq *q, size_t amount);
+
+/**
+ * Same as `skip` but shift tail data to the start afterwards,
+ * so that further writes will find room in tail.
+ */
+void Curl_bufq_skip_and_shift(struct bufq *q, size_t amount);
+
+typedef ssize_t Curl_bufq_writer(void *writer_ctx,
+                                 const unsigned char *buf, size_t len,
+                                 CURLcode *err);
+/**
+ * Passes the chunks in the buffer queue to the writer and returns
+ * the amount of buf written. A writer may return -1 and CURLE_AGAIN
+ * to indicate blocking at which point the queue will stop and return
+ * the amount of buf passed so far.
+ * -1 is returned on any other errors reported by the writer.
+ * Note that in case of a -1 chunks may have been written and
+ * the buffer queue will have different length than before.
+ */
+ssize_t Curl_bufq_pass(struct bufq *q, Curl_bufq_writer *writer,
+                       void *writer_ctx, CURLcode *err);
+
+typedef ssize_t Curl_bufq_reader(void *reader_ctx,
+                                 unsigned char *buf, size_t len,
+                                 CURLcode *err);
+
+/**
+ * Read date and append it to the end of the buffer queue until the
+ * reader returns blocking or the queue is full. A reader returns
+ * -1 and CURLE_AGAIN to indicate blocking.
+ * Returns the total amount of buf read (may be 0) or -1 on other
+ * reader errors.
+ * Note that in case of a -1 chunks may have been read and
+ * the buffer queue will have different length than before.
+ */
+ssize_t Curl_bufq_slurp(struct bufq *q, Curl_bufq_reader *reader,
+                        void *reader_ctx, CURLcode *err);
+
+/**
+ * Read *once* up to `max_len` bytes and append it to the buffer.
+ * if `max_len` is 0, no limit is imposed besides the chunk space.
+ * Returns the total amount of buf read (may be 0) or -1 on other
+ * reader errors.
+ */
+ssize_t Curl_bufq_sipn(struct bufq *q, size_t max_len,
+                       Curl_bufq_reader *reader, void *reader_ctx,
+                       CURLcode *err);
+
+/**
+ * Write buf to the end of the buffer queue.
+ * Will write bufq content or passed `buf` directly using the `writer`
+ * callback when it sees fit. 'buf' might get passed directly
+ * on or is placed into the buffer, depending on `len` and current
+ * amount buffered, chunk size, etc.
+ */
+ssize_t Curl_bufq_write_pass(struct bufq *q,
+                             const unsigned char *buf, size_t len,
+                             Curl_bufq_writer *writer, void *writer_ctx,
+                             CURLcode *err);
+
+#endif /* HEADER_CURL_BUFQ_H */
Index: trunk/src/libs/curl-8.3.0/lib/cf-h1-proxy.c
===================================================================
--- trunk/src/libs/curl-8.3.0/lib/cf-h1-proxy.c	(revision 101259)
+++ trunk/src/libs/curl-8.3.0/lib/cf-h1-proxy.c	(revision 101259)
@@ -0,0 +1,1194 @@
+/***************************************************************************
+ *                                  _   _ ____  _
+ *  Project                     ___| | | |  _ \| |
+ *                             / __| | | | |_) | |
+ *                            | (__| |_| |  _ <| |___
+ *                             \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
+ *
+ * This software is licensed as described in the file COPYING, which
+ * you should have received as part of this distribution. The terms
+ * are also available at https://curl.se/docs/copyright.html.
+ *
+ * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+ * copies of the Software, and permit persons to whom the Software is
+ * furnished to do so, under the terms of the COPYING file.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ * SPDX-License-Identifier: curl
+ *
+ ***************************************************************************/
+
+#include "curl_setup.h"
+
+#if !defined(CURL_DISABLE_PROXY) && !defined(CURL_DISABLE_HTTP)
+
+#include <curl/curl.h>
+#ifdef USE_HYPER
+#include <hyper.h>
+#endif
+#include "urldata.h"
+#include "dynbuf.h"
+#include "sendf.h"
+#include "http.h"
+#include "http_proxy.h"
+#include "url.h"
+#include "select.h"
+#include "progress.h"
+#include "cfilters.h"
+#include "cf-h1-proxy.h"
+#include "connect.h"
+#include "curl_trc.h"
+#include "curlx.h"
+#include "vtls/vtls.h"
+#include "transfer.h"
+#include "multiif.h"
+
+/* The last 3 #include files should be in this order */
+#include "curl_printf.h"
+#include "curl_memory.h"
+#include "memdebug.h"
+
+
+typedef enum {
+    H1_TUNNEL_INIT,     /* init/default/no tunnel state */
+    H1_TUNNEL_CONNECT,  /* CONNECT request is being send */
+    H1_TUNNEL_RECEIVE,  /* CONNECT answer is being received */
+    H1_TUNNEL_RESPONSE, /* CONNECT response received completely */
+    H1_TUNNEL_ESTABLISHED,
+    H1_TUNNEL_FAILED
+} h1_tunnel_state;
+
+/* struct for HTTP CONNECT tunneling */
+struct h1_tunnel_state {
+  int sockindex;
+  const char *hostname;
+  int remote_port;
+  struct HTTP CONNECT;
+  struct dynbuf rcvbuf;
+  struct dynbuf req;
+  size_t nsend;
+  size_t headerlines;
+  enum keeponval {
+    KEEPON_DONE,
+    KEEPON_CONNECT,
+    KEEPON_IGNORE
+  } keepon;
+  curl_off_t cl; /* size of content to read and ignore */
+  h1_tunnel_state tunnel_state;
+  BIT(chunked_encoding);
+  BIT(close_connection);
+};
+
+
+static bool tunnel_is_established(struct h1_tunnel_state *ts)
+{
+  return ts && (ts->tunnel_state == H1_TUNNEL_ESTABLISHED);
+}
+
+static bool tunnel_is_failed(struct h1_tunnel_state *ts)
+{
+  return ts && (ts->tunnel_state == H1_TUNNEL_FAILED);
+}
+
+static CURLcode tunnel_reinit(struct h1_tunnel_state *ts,
+                              struct connectdata *conn,
+                              struct Curl_easy *data)
+{
+  (void)data;
+  DEBUGASSERT(ts);
+  Curl_dyn_reset(&ts->rcvbuf);
+  Curl_dyn_reset(&ts->req);
+  ts->tunnel_state = H1_TUNNEL_INIT;
+  ts->keepon = KEEPON_CONNECT;
+  ts->cl = 0;
+  ts->close_connection = FALSE;
+
+  if(conn->bits.conn_to_host)
+    ts->hostname = conn->conn_to_host.name;
+  else if(ts->sockindex == SECONDARYSOCKET)
+    ts->hostname = conn->secondaryhostname;
+  else
+    ts->hostname = conn->host.name;
+
+  if(ts->sockindex == SECONDARYSOCKET)
+    ts->remote_port = conn->secondary_port;
+  else if(conn->bits.conn_to_port)
+    ts->remote_port = conn->conn_to_port;
+  else
+    ts->remote_port = conn->remote_port;
+
+  return CURLE_OK;
+}
+
+static CURLcode tunnel_init(struct h1_tunnel_state **pts,
+                            struct Curl_easy *data,
+                            struct connectdata *conn,
+                            int sockindex)
+{
+  struct h1_tunnel_state *ts;
+  CURLcode result;
+
+  if(conn->handler->flags & PROTOPT_NOTCPPROXY) {
+    failf(data, "%s cannot be done over CONNECT", conn->handler->scheme);
+    return CURLE_UNSUPPORTED_PROTOCOL;
+  }
+
+  /* we might need the upload buffer for streaming a partial request */
+  result = Curl_get_upload_buffer(data);
+  if(result)
+    return result;
+
+  ts = calloc(1, sizeof(*ts));
+  if(!ts)
+    return CURLE_OUT_OF_MEMORY;
+
+  ts->sockindex = sockindex;
+  infof(data, "allocate connect buffer");
+
+  Curl_dyn_init(&ts->rcvbuf, DYN_PROXY_CONNECT_HEADERS);
+  Curl_dyn_init(&ts->req, DYN_HTTP_REQUEST);
+
+  *pts =  ts;
+  connkeep(conn, "HTTP proxy CONNECT");
+  return tunnel_reinit(ts, conn, data);
+}
+
+static void h1_tunnel_go_state(struct Curl_cfilter *cf,
+                               struct h1_tunnel_state *ts,
+                               h1_tunnel_state new_state,
+                               struct Curl_easy *data)
+{
+  if(ts->tunnel_state == new_state)
+    return;
+  /* leaving this one */
+  switch(ts->tunnel_state) {
+  case H1_TUNNEL_CONNECT:
+    data->req.ignorebody = FALSE;
+    break;
+  default:
+    break;
+  }
+  /* entering this one */
+  switch(new_state) {
+  case H1_TUNNEL_INIT:
+    CURL_TRC_CF(data, cf, "new tunnel state 'init'");
+    tunnel_reinit(ts, cf->conn, data);
+    break;
+
+  case H1_TUNNEL_CONNECT:
+    CURL_TRC_CF(data, cf, "new tunnel state 'connect'");
+    ts->tunnel_state = H1_TUNNEL_CONNECT;
+    ts->keepon = KEEPON_CONNECT;
+    Curl_dyn_reset(&ts->rcvbuf);
+    break;
+
+  case H1_TUNNEL_RECEIVE:
+    CURL_TRC_CF(data, cf, "new tunnel state 'receive'");
+    ts->tunnel_state = H1_TUNNEL_RECEIVE;
+    break;
+
+  case H1_TUNNEL_RESPONSE:
+    CURL_TRC_CF(data, cf, "new tunnel state 'response'");
+    ts->tunnel_state = H1_TUNNEL_RESPONSE;
+    break;
+
+  case H1_TUNNEL_ESTABLISHED:
+    CURL_TRC_CF(data, cf, "new tunnel state 'established'");
+    infof(data, "CONNECT phase completed");
+    data->state.authproxy.done = TRUE;
+    data->state.authproxy.multipass = FALSE;
+    /* FALLTHROUGH */
+  case H1_TUNNEL_FAILED:
+    if(new_state == H1_TUNNEL_FAILED)
+      CURL_TRC_CF(data, cf, "new tunnel state 'failed'");
+    ts->tunnel_state = new_state;
+    Curl_dyn_reset(&ts->rcvbuf);
+    Curl_dyn_reset(&ts->req);
+    /* restore the protocol pointer */
+    data->info.httpcode = 0; /* clear it as it might've been used for the
+                                proxy */
+    /* If a proxy-authorization header was used for the proxy, then we should
+       make sure that it isn't accidentally used for the document request
+       after we've connected. So let's free and clear it here. */
+    Curl_safefree(data->state.aptr.proxyuserpwd);
+#ifdef USE_HYPER
+    data->state.hconnect = FALSE;
+#endif
+    break;
+  }
+}
+
+static void tunnel_free(struct Curl_cfilter *cf,
+                        struct Curl_easy *data)
+{
+  struct h1_tunnel_state *ts = cf->ctx;
+  if(ts) {
+    h1_tunnel_go_state(cf, ts, H1_TUNNEL_FAILED, data);
+    Curl_dyn_free(&ts->rcvbuf);
+    Curl_dyn_free(&ts->req);
+    free(ts);
+    cf->ctx = NULL;
+  }
+}
+
+static CURLcode CONNECT_host(struct Curl_easy *data,
+                             struct connectdata *conn,
+                             const char *hostname,
+                             int remote_port,
+                             char **connecthostp,
+                             char **hostp)
+{
+  char *hostheader; /* for CONNECT */
+  char *host = NULL; /* Host: */
+  bool ipv6_ip = conn->bits.ipv6_ip;
+
+  /* the hostname may be different */
+  if(hostname != conn->host.name)
+    ipv6_ip = (strchr(hostname, ':') != NULL);
+  hostheader = /* host:port with IPv6 support */
+    aprintf("%s%s%s:%d", ipv6_ip?"[":"", hostname, ipv6_ip?"]":"",
+            remote_port);
+  if(!hostheader)
+    return CURLE_OUT_OF_MEMORY;
+
+  if(!Curl_checkProxyheaders(data, conn, STRCONST("Host"))) {
+    host = aprintf("Host: %s\r\n", hostheader);
+    if(!host) {
+      free(hostheader);
+      return CURLE_OUT_OF_MEMORY;
+    }
+  }
+  *connecthostp = hostheader;
+  *hostp = host;
+  return CURLE_OK;
+}
+
+#ifndef USE_HYPER
+static CURLcode start_CONNECT(struct Curl_cfilter *cf,
+                              struct Curl_easy *data,
+                              struct h1_tunnel_state *ts)
+{
+  struct connectdata *conn = cf->conn;
+  char *hostheader = NULL;
+  char *host = NULL;
+  const char *httpv;
+  CURLcode result;
+
+  infof(data, "Establish HTTP proxy tunnel to %s:%d",
+        ts->hostname, ts->remote_port);
+
+    /* This only happens if we've looped here due to authentication
+       reasons, and we don't really use the newly cloned URL here
+       then. Just free() it. */
+  Curl_safefree(data->req.newurl);
+
+  result = CONNECT_host(data, conn,
+                        ts->hostname, ts->remote_port,
+                        &hostheader, &host);
+  if(result)
+    goto out;
+
+  /* Setup the proxy-authorization header, if any */
+  result = Curl_http_output_auth(data, conn, "CONNECT", HTTPREQ_GET,
+                                 hostheader, TRUE);
+  if(result)
+    goto out;
+
+  httpv = (conn->http_proxy.proxytype == CURLPROXY_HTTP_1_0) ? "1.0" : "1.1";
+
+  result =
+      Curl_dyn_addf(&ts->req,
+                    "CONNECT %s HTTP/%s\r\n"
+                    "%s"  /* Host: */
+                    "%s", /* Proxy-Authorization */
+                    hostheader,
+                    httpv,
+                    host?host:"",
+                    data->state.aptr.proxyuserpwd?
+                    data->state.aptr.proxyuserpwd:"");
+  if(result)
+    goto out;
+
+  if(!Curl_checkProxyheaders(data, conn, STRCONST("User-Agent"))
+     && data->set.str[STRING_USERAGENT])
+    result = Curl_dyn_addf(&ts->req, "User-Agent: %s\r\n",
+                           data->set.str[STRING_USERAGENT]);
+  if(result)
+    goto out;
+
+  if(!Curl_checkProxyheaders(data, conn, STRCONST("Proxy-Connection")))
+    result = Curl_dyn_addn(&ts->req,
+                           STRCONST("Proxy-Connection: Keep-Alive\r\n"));
+  if(result)
+    goto out;
+
+  result = Curl_add_custom_headers(data, TRUE, &ts->req);
+  if(result)
+    goto out;
+
+  /* CRLF terminate the request */
+  result = Curl_dyn_addn(&ts->req, STRCONST("\r\n"));
+  if(result)
+    goto out;
+
+  /* Send the connect request to the proxy */
+  result = Curl_buffer_send(&ts->req, data, &ts->CONNECT,
+                            &data->info.request_size, 0,
+                            ts->sockindex);
+  ts->headerlines = 0;
+
+out:
+  if(result)
+    failf(data, "Failed sending CONNECT to proxy");
+  free(host);
+  free(hostheader);
+  return result;
+}
+
+static CURLcode send_CONNECT(struct Curl_easy *data,
+                             struct connectdata *conn,
+                             struct h1_tunnel_state *ts,
+                             bool *done)
+{
+  struct SingleRequest *k = &data->req;
+  struct HTTP *http = &ts->CONNECT;
+  CURLcode result = CURLE_OK;
+
+  if(http->sending != HTTPSEND_REQUEST)
+    goto out;
+
+  if(!ts->nsend) {
+    size_t fillcount;
+    k->upload_fromhere = data->state.ulbuf;
+    result = Curl_fillreadbuffer(data, data->set.upload_buffer_size,
+                                 &fillcount);
+    if(result)
+      goto out;
+    ts->nsend = fillcount;
+  }
+  if(ts->nsend) {
+    ssize_t bytes_written;
+    /* write to socket (send away data) */
+    result = Curl_write(data,
+                        conn->writesockfd,  /* socket to send to */
+                        k->upload_fromhere, /* buffer pointer */
+                        ts->nsend,          /* buffer size */
+                        &bytes_written);    /* actually sent */
+    if(result)
+      goto out;
+    /* send to debug callback! */
+    Curl_debug(data, CURLINFO_HEADER_OUT,
+               k->upload_fromhere, bytes_written);
+
+    ts->nsend -= bytes_written;
+    k->upload_fromhere += bytes_written;
+  }
+  if(!ts->nsend)
+    http->sending = HTTPSEND_NADA;
+
+out:
+  if(result)
+    failf(data, "Failed sending CONNECT to proxy");
+  *done = (http->sending != HTTPSEND_REQUEST);
+  return result;
+}
+
+static CURLcode on_resp_header(struct Curl_cfilter *cf,
+                               struct Curl_easy *data,
+                               struct h1_tunnel_state *ts,
+                               const char *header)
+{
+  CURLcode result = CURLE_OK;
+  struct SingleRequest *k = &data->req;
+  (void)cf;
+
+  if((checkprefix("WWW-Authenticate:", header) &&
+      (401 == k->httpcode)) ||
+     (checkprefix("Proxy-authenticate:", header) &&
+      (407 == k->httpcode))) {
+
+    bool proxy = (k->httpcode == 407) ? TRUE : FALSE;
+    char *auth = Curl_copy_header_value(header);
+    if(!auth)
+      return CURLE_OUT_OF_MEMORY;
+
+    CURL_TRC_CF(data, cf, "CONNECT: fwd auth header '%s'", header);
+    result = Curl_http_input_auth(data, proxy, auth);
+
+    free(auth);
+
+    if(result)
+      return result;
+  }
+  else if(checkprefix("Content-Length:", header)) {
+    if(k->httpcode/100 == 2) {
+      /* A client MUST ignore any Content-Length or Transfer-Encoding
+         header fields received in a successful response to CONNECT.
+         "Successful" described as: 2xx (Successful). RFC 7231 4.3.6 */
+      infof(data, "Ignoring Content-Length in CONNECT %03d response",
+            k->httpcode);
+    }
+    else {
+      (void)curlx_strtoofft(header + strlen("Content-Length:"),
+                            NULL, 10, &ts->cl);
+    }
+  }
+  else if(Curl_compareheader(header,
+                             STRCONST("Connection:"), STRCONST("close")))
+    ts->close_connection = TRUE;
+  else if(checkprefix("Transfer-Encoding:", header)) {
+    if(k->httpcode/100 == 2) {
+      /* A client MUST ignore any Content-Length or Transfer-Encoding
+         header fields received in a successful response to CONNECT.
+         "Successful" described as: 2xx (Successful). RFC 7231 4.3.6 */
+      infof(data, "Ignoring Transfer-Encoding in "
+            "CONNECT %03d response", k->httpcode);
+    }
+    else if(Curl_compareheader(header,
+                               STRCONST("Transfer-Encoding:"),
+                               STRCONST("chunked"))) {
+      infof(data, "CONNECT responded chunked");
+      ts->chunked_encoding = TRUE;
+      /* init our chunky engine */
+      Curl_httpchunk_init(data);
+    }
+  }
+  else if(Curl_compareheader(header,
+                             STRCONST("Proxy-Connection:"),
+                             STRCONST("close")))
+    ts->close_connection = TRUE;
+  else if(!strncmp(header, "HTTP/1.", 7) &&
+          ((header[7] == '0') || (header[7] == '1')) &&
+          (header[8] == ' ') &&
+          ISDIGIT(header[9]) && ISDIGIT(header[10]) && ISDIGIT(header[11]) &&
+          !ISDIGIT(header[12])) {
+    /* store the HTTP code from the proxy */
+    data->info.httpproxycode =  k->httpcode = (header[9] - '0') * 100 +
+      (header[10] - '0') * 10 + (header[11] - '0');
+  }
+  return result;
+}
+
+static CURLcode recv_CONNECT_resp(struct Curl_cfilter *cf,
+                                  struct Curl_easy *data,
+                                  struct h1_tunnel_state *ts,
+                                  bool *done)
+{
+  CURLcode result = CURLE_OK;
+  struct SingleRequest *k = &data->req;
+  curl_socket_t tunnelsocket = Curl_conn_cf_get_socket(cf, data);
+  char *linep;
+  size_t perline;
+  int error;
+
+#define SELECT_OK      0
+#define SELECT_ERROR   1
+
+  error = SELECT_OK;
+  *done = FALSE;
+
+  if(!Curl_conn_data_pending(data, ts->sockindex))
+    return CURLE_OK;
+
+  while(ts->keepon) {
+    ssize_t gotbytes;
+    char byte;
+
+    /* Read one byte at a time to avoid a race condition. Wait at most one
+       second before looping to ensure continuous pgrsUpdates. */
+    result = Curl_read(data, tunnelsocket, &byte, 1, &gotbytes);
+    if(result == CURLE_AGAIN)
+      /* socket buffer drained, return */
+      return CURLE_OK;
+
+    if(Curl_pgrsUpdate(data))
+      return CURLE_ABORTED_BY_CALLBACK;
+
+    if(result) {
+      ts->keepon = KEEPON_DONE;
+      break;
+    }
+
+    if(gotbytes <= 0) {
+      if(data->set.proxyauth && data->state.authproxy.avail &&
+         data->state.aptr.proxyuserpwd) {
+        /* proxy auth was requested and there was proxy auth available,
+           then deem this as "mere" proxy disconnect */
+        ts->close_connection = TRUE;
+        infof(data, "Proxy CONNECT connection closed");
+      }
+      else {
+        error = SELECT_ERROR;
+        failf(data, "Proxy CONNECT aborted");
+      }
+      ts->keepon = KEEPON_DONE;
+      break;
+    }
+
+    if(ts->keepon == KEEPON_IGNORE) {
+      /* This means we are currently ignoring a response-body */
+
+      if(ts->cl) {
+        /* A Content-Length based body: simply count down the counter
+           and make sure to break out of the loop when we're done! */
+        ts->cl--;
+        if(ts->cl <= 0) {
+          ts->keepon = KEEPON_DONE;
+          break;
+        }
+      }
+      else {
+        /* chunked-encoded body, so we need to do the chunked dance
+           properly to know when the end of the body is reached */
+        CHUNKcode r;
+        CURLcode extra;
+        ssize_t tookcareof = 0;
+
+        /* now parse the chunked piece of data so that we can
+           properly tell when the stream ends */
+        r = Curl_httpchunk_read(data, &byte, 1, &tookcareof, &extra);
+        if(r == CHUNKE_STOP) {
+          /* we're done reading chunks! */
+          infof(data, "chunk reading DONE");
+          ts->keepon = KEEPON_DONE;
+        }
+      }
+      continue;
+    }
+
+    if(Curl_dyn_addn(&ts->rcvbuf, &byte, 1)) {
+      failf(data, "CONNECT response too large");
+      return CURLE_RECV_ERROR;
+    }
+
+    /* if this is not the end of a header line then continue */
+    if(byte != 0x0a)
+      continue;
+
+    ts->headerlines++;
+    linep = Curl_dyn_ptr(&ts->rcvbuf);
+    perline = Curl_dyn_len(&ts->rcvbuf); /* amount of bytes in this line */
+
+    /* output debug if that is requested */
+    Curl_debug(data, CURLINFO_HEADER_IN, linep, perline);
+
+    if(!data->set.suppress_connect_headers) {
+      /* send the header to the callback */
+      int writetype = CLIENTWRITE_HEADER | CLIENTWRITE_CONNECT |
+        (data->set.include_header ? CLIENTWRITE_BODY : 0) |
+        (ts->headerlines == 1 ? CLIENTWRITE_STATUS : 0);
+
+      result = Curl_client_write(data, writetype, linep, perline);
+      if(result)
+        return result;
+    }
+
+    result = Curl_bump_headersize(data, perline, TRUE);
+    if(result)
+      return result;
+
+    /* Newlines are CRLF, so the CR is ignored as the line isn't
+       really terminated until the LF comes. Treat a following CR
+       as end-of-headers as well.*/
+
+    if(('\r' == linep[0]) ||
+       ('\n' == linep[0])) {
+      /* end of response-headers from the proxy */
+
+      if((407 == k->httpcode) && !data->state.authproblem) {
+        /* If we get a 407 response code with content length
+           when we have no auth problem, we must ignore the
+           whole response-body */
+        ts->keepon = KEEPON_IGNORE;
+
+        if(ts->cl) {
+          infof(data, "Ignore %" CURL_FORMAT_CURL_OFF_T
+                " bytes of response-body", ts->cl);
+        }
+        else if(ts->chunked_encoding) {
+          CHUNKcode r;
+          CURLcode extra;
+
+          infof(data, "Ignore chunked response-body");
+
+          /* We set ignorebody true here since the chunked decoder
+             function will acknowledge that. Pay attention so that this is
+             cleared again when this function returns! */
+          k->ignorebody = TRUE;
+
+          if(linep[1] == '\n')
+            /* this can only be a LF if the letter at index 0 was a CR */
+            linep++;
+
+          /* now parse the chunked piece of data so that we can properly
+             tell when the stream ends */
+          r = Curl_httpchunk_read(data, linep + 1, 1, &gotbytes,
+                                  &extra);
+          if(r == CHUNKE_STOP) {
+            /* we're done reading chunks! */
+            infof(data, "chunk reading DONE");
+            ts->keepon = KEEPON_DONE;
+          }
+        }
+        else {
+          /* without content-length or chunked encoding, we
+             can't keep the connection alive since the close is
+             the end signal so we bail out at once instead */
+          CURL_TRC_CF(data, cf, "CONNECT: no content-length or chunked");
+          ts->keepon = KEEPON_DONE;
+        }
+      }
+      else {
+        ts->keepon = KEEPON_DONE;
+      }
+
+      DEBUGASSERT(ts->keepon == KEEPON_IGNORE
+                  || ts->keepon == KEEPON_DONE);
+      continue;
+    }
+
+    result = on_resp_header(cf, data, ts, linep);
+    if(result)
+      return result;
+
+    Curl_dyn_reset(&ts->rcvbuf);
+  } /* while there's buffer left and loop is requested */
+
+  if(error)
+    result = CURLE_RECV_ERROR;
+  *done = (ts->keepon == KEEPON_DONE);
+  if(!result && *done && data->info.httpproxycode/100 != 2) {
+    /* Deal with the possibly already received authenticate
+       headers. 'newurl' is set to a new URL if we must loop. */
+    result = Curl_http_auth_act(data);
+  }
+  return result;
+}
+
+#else /* USE_HYPER */
+/* The Hyper version of CONNECT */
+static CURLcode start_CONNECT(struct Curl_cfilter *cf,
+                              struct Curl_easy *data,
+                              struct h1_tunnel_state *ts)
+{
+  struct connectdata *conn = cf->conn;
+  struct hyptransfer *h = &data->hyp;
+  curl_socket_t tunnelsocket = Curl_conn_cf_get_socket(cf, data);
+  hyper_io *io = NULL;
+  hyper_request *req = NULL;
+  hyper_headers *headers = NULL;
+  hyper_clientconn_options *options = NULL;
+  hyper_task *handshake = NULL;
+  hyper_task *task = NULL; /* for the handshake */
+  hyper_clientconn *client = NULL;
+  hyper_task *sendtask = NULL; /* for the send */
+  char *hostheader = NULL; /* for CONNECT */
+  char *host = NULL; /* Host: */
+  CURLcode result = CURLE_OUT_OF_MEMORY;
+
+  io = hyper_io_new();
+  if(!io) {
+    failf(data, "Couldn't create hyper IO");
+    result = CURLE_OUT_OF_MEMORY;
+    goto error;
+  }
+  /* tell Hyper how to read/write network data */
+  hyper_io_set_userdata(io, data);
+  hyper_io_set_read(io, Curl_hyper_recv);
+  hyper_io_set_write(io, Curl_hyper_send);
+  conn->sockfd = tunnelsocket;
+
+  data->state.hconnect = TRUE;
+
+  /* create an executor to poll futures */
+  if(!h->exec) {
+    h->exec = hyper_executor_new();
+    if(!h->exec) {
+      failf(data, "Couldn't create hyper executor");
+      result = CURLE_OUT_OF_MEMORY;
+      goto error;
+    }
+  }
+
+  options = hyper_clientconn_options_new();
+  if(!options) {
+    failf(data, "Couldn't create hyper client options");
+    result = CURLE_OUT_OF_MEMORY;
+    goto error;
+  }
+  hyper_clientconn_options_set_preserve_header_case(options, 1);
+  hyper_clientconn_options_set_preserve_header_order(options, 1);
+
+  hyper_clientconn_options_exec(options, h->exec);
+
+  /* "Both the `io` and the `options` are consumed in this function
+     call" */
+  handshake = hyper_clientconn_handshake(io, options);
+  if(!handshake) {
+    failf(data, "Couldn't create hyper client handshake");
+    result = CURLE_OUT_OF_MEMORY;
+    goto error;
+  }
+  io = NULL;
+  options = NULL;
+
+  if(HYPERE_OK != hyper_executor_push(h->exec, handshake)) {
+    failf(data, "Couldn't hyper_executor_push the handshake");
+    result = CURLE_OUT_OF_MEMORY;
+    goto error;
+  }
+  handshake = NULL; /* ownership passed on */
+
+  task = hyper_executor_poll(h->exec);
+  if(!task) {
+    failf(data, "Couldn't hyper_executor_poll the handshake");
+    result = CURLE_OUT_OF_MEMORY;
+    goto error;
+  }
+
+  client = hyper_task_value(task);
+  hyper_task_free(task);
+
+  req = hyper_request_new();
+  if(!req) {
+    failf(data, "Couldn't hyper_request_new");
+    result = CURLE_OUT_OF_MEMORY;
+    goto error;
+  }
+  if(hyper_request_set_method(req, (uint8_t *)"CONNECT",
+                              strlen("CONNECT"))) {
+    failf(data, "error setting method");
+    result = CURLE_OUT_OF_MEMORY;
+    goto error;
+  }
+
+  infof(data, "Establish HTTP proxy tunnel to %s:%d",
+        ts->hostname, ts->remote_port);
+
+    /* This only happens if we've looped here due to authentication
+       reasons, and we don't really use the newly cloned URL here
+       then. Just free() it. */
+  Curl_safefree(data->req.newurl);
+
+  result = CONNECT_host(data, conn, ts->hostname, ts->remote_port,
+                        &hostheader, &host);
+  if(result)
+    goto error;
+
+  if(hyper_request_set_uri(req, (uint8_t *)hostheader,
+                           strlen(hostheader))) {
+    failf(data, "error setting path");
+    result = CURLE_OUT_OF_MEMORY;
+    goto error;
+  }
+  if(data->set.verbose) {
+    char *se = aprintf("CONNECT %s HTTP/1.1\r\n", hostheader);
+    if(!se) {
+      result = CURLE_OUT_OF_MEMORY;
+      goto error;
+    }
+    Curl_debug(data, CURLINFO_HEADER_OUT, se, strlen(se));
+    free(se);
+  }
+  /* Setup the proxy-authorization header, if any */
+  result = Curl_http_output_auth(data, conn, "CONNECT", HTTPREQ_GET,
+                                 hostheader, TRUE);
+  if(result)
+    goto error;
+  Curl_safefree(hostheader);
+
+  /* default is 1.1 */
+  if((conn->http_proxy.proxytype == CURLPROXY_HTTP_1_0) &&
+     (HYPERE_OK != hyper_request_set_version(req,
+                                             HYPER_HTTP_VERSION_1_0))) {
+    failf(data, "error setting HTTP version");
+    result = CURLE_OUT_OF_MEMORY;
+    goto error;
+  }
+
+  headers = hyper_request_headers(req);
+  if(!headers) {
+    failf(data, "hyper_request_headers");
+    result = CURLE_OUT_OF_MEMORY;
+    goto error;
+  }
+  if(host) {
+    result = Curl_hyper_header(data, headers, host);
+    if(result)
+      goto error;
+    Curl_safefree(host);
+  }
+
+  if(data->state.aptr.proxyuserpwd) {
+    result = Curl_hyper_header(data, headers,
+                               data->state.aptr.proxyuserpwd);
+    if(result)
+      goto error;
+  }
+
+  if(!Curl_checkProxyheaders(data, conn, STRCONST("User-Agent")) &&
+     data->set.str[STRING_USERAGENT]) {
+    struct dynbuf ua;
+    Curl_dyn_init(&ua, DYN_HTTP_REQUEST);
+    result = Curl_dyn_addf(&ua, "User-Agent: %s\r\n",
+                           data->set.str[STRING_USERAGENT]);
+    if(result)
+      goto error;
+    result = Curl_hyper_header(data, headers, Curl_dyn_ptr(&ua));
+    if(result)
+      goto error;
+    Curl_dyn_free(&ua);
+  }
+
+  if(!Curl_checkProxyheaders(data, conn, STRCONST("Proxy-Connection"))) {
+    result = Curl_hyper_header(data, headers,
+                               "Proxy-Connection: Keep-Alive");
+    if(result)
+      goto error;
+  }
+
+  result = Curl_add_custom_headers(data, TRUE, headers);
+  if(result)
+    goto error;
+
+  sendtask = hyper_clientconn_send(client, req);
+  if(!sendtask) {
+    failf(data, "hyper_clientconn_send");
+    result = CURLE_OUT_OF_MEMORY;
+    goto error;
+  }
+  req = NULL;
+
+  if(HYPERE_OK != hyper_executor_push(h->exec, sendtask)) {
+    failf(data, "Couldn't hyper_executor_push the send");
+    result = CURLE_OUT_OF_MEMORY;
+    goto error;
+  }
+  sendtask = NULL; /* ownership passed on */
+
+  hyper_clientconn_free(client);
+  client = NULL;
+
+error:
+  free(host);
+  free(hostheader);
+  if(io)
+    hyper_io_free(io);
+  if(options)
+    hyper_clientconn_options_free(options);
+  if(handshake)
+    hyper_task_free(handshake);
+  if(client)
+    hyper_clientconn_free(client);
+  if(req)
+    hyper_request_free(req);
+
+  return result;
+}
+
+static CURLcode send_CONNECT(struct Curl_easy *data,
+                             struct connectdata *conn,
+                             struct h1_tunnel_state *ts,
+                             bool *done)
+{
+  struct hyptransfer *h = &data->hyp;
+  hyper_task *task = NULL;
+  hyper_error *hypererr = NULL;
+  CURLcode result = CURLE_OK;
+
+  (void)ts;
+  (void)conn;
+  do {
+    task = hyper_executor_poll(h->exec);
+    if(task) {
+      bool error = hyper_task_type(task) == HYPER_TASK_ERROR;
+      if(error)
+        hypererr = hyper_task_value(task);
+      hyper_task_free(task);
+      if(error) {
+        /* this could probably use a better error code? */
+        result = CURLE_OUT_OF_MEMORY;
+        goto error;
+      }
+    }
+  } while(task);
+error:
+  *done = (result == CURLE_OK);
+  if(hypererr) {
+    uint8_t errbuf[256];
+    size_t errlen = hyper_error_print(hypererr, errbuf, sizeof(errbuf));
+    failf(data, "Hyper: %.*s", (int)errlen, errbuf);
+    hyper_error_free(hypererr);
+  }
+  return result;
+}
+
+static CURLcode recv_CONNECT_resp(struct Curl_cfilter *cf,
+                                  struct Curl_easy *data,
+                                  struct h1_tunnel_state *ts,
+                                  bool *done)
+{
+  struct hyptransfer *h = &data->hyp;
+  CURLcode result;
+  int didwhat;
+
+  (void)ts;
+  *done = FALSE;
+  result = Curl_hyper_stream(data, cf->conn, &didwhat, done,
+                             CURL_CSELECT_IN | CURL_CSELECT_OUT);
+  if(result || !*done)
+    return result;
+  if(h->exec) {
+    hyper_executor_free(h->exec);
+    h->exec = NULL;
+  }
+  if(h->read_waker) {
+    hyper_waker_free(h->read_waker);
+    h->read_waker = NULL;
+  }
+  if(h->write_waker) {
+    hyper_waker_free(h->write_waker);
+    h->write_waker = NULL;
+  }
+  return result;
+}
+
+#endif /* USE_HYPER */
+
+static CURLcode H1_CONNECT(struct Curl_cfilter *cf,
+                           struct Curl_easy *data,
+                           struct h1_tunnel_state *ts)
+{
+  struct connectdata *conn = cf->conn;
+  CURLcode result;
+  bool done;
+
+  if(tunnel_is_established(ts))
+    return CURLE_OK;
+  if(tunnel_is_failed(ts))
+    return CURLE_RECV_ERROR; /* Need a cfilter close and new bootstrap */
+
+  do {
+    timediff_t check;
+
+    check = Curl_timeleft(data, NULL, TRUE);
+    if(check <= 0) {
+      failf(data, "Proxy CONNECT aborted due to timeout");
+      result = CURLE_OPERATION_TIMEDOUT;
+      goto out;
+    }
+
+    switch(ts->tunnel_state) {
+    case H1_TUNNEL_INIT:
+      /* Prepare the CONNECT request and make a first attempt to send. */
+      CURL_TRC_CF(data, cf, "CONNECT start");
+      result = start_CONNECT(cf, data, ts);
+      if(result)
+        goto out;
+      h1_tunnel_go_state(cf, ts, H1_TUNNEL_CONNECT, data);
+      /* FALLTHROUGH */
+
+    case H1_TUNNEL_CONNECT:
+      /* see that the request is completely sent */
+      CURL_TRC_CF(data, cf, "CONNECT send");
+      result = send_CONNECT(data, cf->conn, ts, &done);
+      if(result || !done)
+        goto out;
+      h1_tunnel_go_state(cf, ts, H1_TUNNEL_RECEIVE, data);
+      /* FALLTHROUGH */
+
+    case H1_TUNNEL_RECEIVE:
+      /* read what is there */
+      CURL_TRC_CF(data, cf, "CONNECT receive");
+      result = recv_CONNECT_resp(cf, data, ts, &done);
+      if(Curl_pgrsUpdate(data)) {
+        result = CURLE_ABORTED_BY_CALLBACK;
+        goto out;
+      }
+      /* error or not complete yet. return for more multi-multi */
+      if(result || !done)
+        goto out;
+      /* got it */
+      h1_tunnel_go_state(cf, ts, H1_TUNNEL_RESPONSE, data);
+      /* FALLTHROUGH */
+
+    case H1_TUNNEL_RESPONSE:
+      CURL_TRC_CF(data, cf, "CONNECT response");
+      if(data->req.newurl) {
+        /* not the "final" response, we need to do a follow up request.
+         * If the other side indicated a connection close, or if someone
+         * else told us to close this connection, do so now.
+         */
+        if(ts->close_connection || conn->bits.close) {
+          /* Close this filter and the sub-chain, re-connect the
+           * sub-chain and continue. Closing this filter will
+           * reset our tunnel state. To avoid recursion, we return
+           * and expect to be called again.
+           */
+          CURL_TRC_CF(data, cf, "CONNECT need to close+open");
+          infof(data, "Connect me again please");
+          Curl_conn_cf_close(cf, data);
+          connkeep(conn, "HTTP proxy CONNECT");
+          result = Curl_conn_cf_connect(cf->next, data, FALSE, &done);
+          goto out;
+        }
+        else {
+          /* staying on this connection, reset state */
+          h1_tunnel_go_state(cf, ts, H1_TUNNEL_INIT, data);
+        }
+      }
+      break;
+
+    default:
+      break;
+    }
+
+  } while(data->req.newurl);
+
+  DEBUGASSERT(ts->tunnel_state == H1_TUNNEL_RESPONSE);
+  if(data->info.httpproxycode/100 != 2) {
+    /* a non-2xx response and we have no next url to try. */
+    Curl_safefree(data->req.newurl);
+    /* failure, close this connection to avoid reuse */
+    streamclose(conn, "proxy CONNECT failure");
+    h1_tunnel_go_state(cf, ts, H1_TUNNEL_FAILED, data);
+    failf(data, "CONNECT tunnel failed, response %d", data->req.httpcode);
+    return CURLE_RECV_ERROR;
+  }
+  /* 2xx response, SUCCESS! */
+  h1_tunnel_go_state(cf, ts, H1_TUNNEL_ESTABLISHED, data);
+  infof(data, "CONNECT tunnel established, response %d",
+        data->info.httpproxycode);
+  result = CURLE_OK;
+
+out:
+  if(result)
+    h1_tunnel_go_state(cf, ts, H1_TUNNEL_FAILED, data);
+  return result;
+}
+
+static CURLcode cf_h1_proxy_connect(struct Curl_cfilter *cf,
+                                    struct Curl_easy *data,
+                                    bool blocking, bool *done)
+{
+  CURLcode result;
+  struct h1_tunnel_state *ts = cf->ctx;
+
+  if(cf->connected) {
+    *done = TRUE;
+    return CURLE_OK;
+  }
+
+  CURL_TRC_CF(data, cf, "connect");
+  result = cf->next->cft->do_connect(cf->next, data, blocking, done);
+  if(result || !*done)
+    return result;
+
+  *done = FALSE;
+  if(!ts) {
+    result = tunnel_init(&ts, data, cf->conn, cf->sockindex);
+    if(result)
+      return result;
+    cf->ctx = ts;
+  }
+
+  /* TODO: can we do blocking? */
+  /* We want "seamless" operations through HTTP proxy tunnel */
+
+  result = H1_CONNECT(cf, data, ts);
+  if(result)
+    goto out;
+  Curl_safefree(data->state.aptr.proxyuserpwd);
+
+out:
+  *done = (result == CURLE_OK) && tunnel_is_established(cf->ctx);
+  if(*done) {
+    cf->connected = TRUE;
+    tunnel_free(cf, data);
+  }
+  return result;
+}
+
+static int cf_h1_proxy_get_select_socks(struct Curl_cfilter *cf,
+                                        struct Curl_easy *data,
+                                        curl_socket_t *socks)
+{
+  struct h1_tunnel_state *ts = cf->ctx;
+  int fds;
+
+  fds = cf->next->cft->get_select_socks(cf->next, data, socks);
+  if(!fds && cf->next->connected && !cf->connected) {
+    /* If we are not connected, but the filter "below" is
+     * and not waiting on something, we are tunneling. */
+    socks[0] = Curl_conn_cf_get_socket(cf, data);
+    if(ts) {
+      /* when we've sent a CONNECT to a proxy, we should rather either
+         wait for the socket to become readable to be able to get the
+         response headers or if we're still sending the request, wait
+         for write. */
+      if(ts->CONNECT.sending == HTTPSEND_REQUEST) {
+        return GETSOCK_WRITESOCK(0);
+      }
+      return GETSOCK_READSOCK(0);
+    }
+    return GETSOCK_WRITESOCK(0);
+  }
+  return fds;
+}
+
+static void cf_h1_proxy_destroy(struct Curl_cfilter *cf,
+                                struct Curl_easy *data)
+{
+  CURL_TRC_CF(data, cf, "destroy");
+  tunnel_free(cf, data);
+}
+
+static void cf_h1_proxy_close(struct Curl_cfilter *cf,
+                              struct Curl_easy *data)
+{
+  CURL_TRC_CF(data, cf, "close");
+  cf->connected = FALSE;
+  if(cf->ctx) {
+    h1_tunnel_go_state(cf, cf->ctx, H1_TUNNEL_INIT, data);
+  }
+  if(cf->next)
+    cf->next->cft->do_close(cf->next, data);
+}
+
+
+struct Curl_cftype Curl_cft_h1_proxy = {
+  "H1-PROXY",
+  CF_TYPE_IP_CONNECT,
+  0,
+  cf_h1_proxy_destroy,
+  cf_h1_proxy_connect,
+  cf_h1_proxy_close,
+  Curl_cf_http_proxy_get_host,
+  cf_h1_proxy_get_select_socks,
+  Curl_cf_def_data_pending,
+  Curl_cf_def_send,
+  Curl_cf_def_recv,
+  Curl_cf_def_cntrl,
+  Curl_cf_def_conn_is_alive,
+  Curl_cf_def_conn_keep_alive,
+  Curl_cf_def_query,
+};
+
+CURLcode Curl_cf_h1_proxy_insert_after(struct Curl_cfilter *cf_at,
+                                       struct Curl_easy *data)
+{
+  struct Curl_cfilter *cf;
+  CURLcode result;
+
+  (void)data;
+  result = Curl_cf_create(&cf, &Curl_cft_h1_proxy, NULL);
+  if(!result)
+    Curl_conn_cf_insert_after(cf_at, cf);
+  return result;
+}
+
+#endif /* !CURL_DISABLE_PROXY && ! CURL_DISABLE_HTTP */
Index: trunk/src/libs/curl-8.3.0/lib/cf-h1-proxy.h
===================================================================
--- trunk/src/libs/curl-8.3.0/lib/cf-h1-proxy.h	(revision 101259)
+++ trunk/src/libs/curl-8.3.0/lib/cf-h1-proxy.h	(revision 101259)
@@ -0,0 +1,39 @@
+#ifndef HEADER_CURL_H1_PROXY_H
+#define HEADER_CURL_H1_PROXY_H
+/***************************************************************************
+ *                                  _   _ ____  _
+ *  Project                     ___| | | |  _ \| |
+ *                             / __| | | | |_) | |
+ *                            | (__| |_| |  _ <| |___
+ *                             \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
+ *
+ * This software is licensed as described in the file COPYING, which
+ * you should have received as part of this distribution. The terms
+ * are also available at https://curl.se/docs/copyright.html.
+ *
+ * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+ * copies of the Software, and permit persons to whom the Software is
+ * furnished to do so, under the terms of the COPYING file.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ * SPDX-License-Identifier: curl
+ *
+ ***************************************************************************/
+
+#include "curl_setup.h"
+
+#if !defined(CURL_DISABLE_PROXY) && !defined(CURL_DISABLE_HTTP)
+
+CURLcode Curl_cf_h1_proxy_insert_after(struct Curl_cfilter *cf,
+                                       struct Curl_easy *data);
+
+extern struct Curl_cftype Curl_cft_h1_proxy;
+
+
+#endif /* !CURL_DISABLE_PROXY && !CURL_DISABLE_HTTP */
+
+#endif /* HEADER_CURL_H1_PROXY_H */
Index: trunk/src/libs/curl-8.3.0/lib/cf-h2-proxy.c
===================================================================
--- trunk/src/libs/curl-8.3.0/lib/cf-h2-proxy.c	(revision 101259)
+++ trunk/src/libs/curl-8.3.0/lib/cf-h2-proxy.c	(revision 101259)
@@ -0,0 +1,1613 @@
+/***************************************************************************
+ *                                  _   _ ____  _
+ *  Project                     ___| | | |  _ \| |
+ *                             / __| | | | |_) | |
+ *                            | (__| |_| |  _ <| |___
+ *                             \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
+ *
+ * This software is licensed as described in the file COPYING, which
+ * you should have received as part of this distribution. The terms
+ * are also available at https://curl.se/docs/copyright.html.
+ *
+ * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+ * copies of the Software, and permit persons to whom the Software is
+ * furnished to do so, under the terms of the COPYING file.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ * SPDX-License-Identifier: curl
+ *
+ ***************************************************************************/
+
+#include "curl_setup.h"
+
+#if defined(USE_NGHTTP2) && !defined(CURL_DISABLE_PROXY)
+
+#include <nghttp2/nghttp2.h>
+#include "urldata.h"
+#include "cfilters.h"
+#include "connect.h"
+#include "curl_trc.h"
+#include "bufq.h"
+#include "dynbuf.h"
+#include "dynhds.h"
+#include "http1.h"
+#include "http2.h"
+#include "http_proxy.h"
+#include "multiif.h"
+#include "cf-h2-proxy.h"
+
+/* The last 3 #include files should be in this order */
+#include "curl_printf.h"
+#include "curl_memory.h"
+#include "memdebug.h"
+
+#define PROXY_H2_CHUNK_SIZE  (16*1024)
+
+#define PROXY_HTTP2_HUGE_WINDOW_SIZE (100 * 1024 * 1024)
+#define H2_TUNNEL_WINDOW_SIZE        (10 * 1024 * 1024)
+
+#define PROXY_H2_NW_RECV_CHUNKS  (H2_TUNNEL_WINDOW_SIZE / PROXY_H2_CHUNK_SIZE)
+#define PROXY_H2_NW_SEND_CHUNKS   1
+
+#define H2_TUNNEL_RECV_CHUNKS   (H2_TUNNEL_WINDOW_SIZE / PROXY_H2_CHUNK_SIZE)
+#define H2_TUNNEL_SEND_CHUNKS   ((128 * 1024) / PROXY_H2_CHUNK_SIZE)
+
+
+typedef enum {
+    H2_TUNNEL_INIT,     /* init/default/no tunnel state */
+    H2_TUNNEL_CONNECT,  /* CONNECT request is being send */
+    H2_TUNNEL_RESPONSE, /* CONNECT response received completely */
+    H2_TUNNEL_ESTABLISHED,
+    H2_TUNNEL_FAILED
+} h2_tunnel_state;
+
+struct tunnel_stream {
+  struct http_resp *resp;
+  struct bufq recvbuf;
+  struct bufq sendbuf;
+  char *authority;
+  int32_t stream_id;
+  uint32_t error;
+  size_t upload_blocked_len;
+  h2_tunnel_state state;
+  BIT(has_final_response);
+  BIT(closed);
+  BIT(reset);
+};
+
+static CURLcode tunnel_stream_init(struct Curl_cfilter *cf,
+                                    struct tunnel_stream *ts)
+{
+  const char *hostname;
+  int port;
+  bool ipv6_ip = cf->conn->bits.ipv6_ip;
+
+  ts->state = H2_TUNNEL_INIT;
+  ts->stream_id = -1;
+  Curl_bufq_init2(&ts->recvbuf, PROXY_H2_CHUNK_SIZE, H2_TUNNEL_RECV_CHUNKS,
+                  BUFQ_OPT_SOFT_LIMIT);
+  Curl_bufq_init(&ts->sendbuf, PROXY_H2_CHUNK_SIZE, H2_TUNNEL_SEND_CHUNKS);
+
+  if(cf->conn->bits.conn_to_host)
+    hostname = cf->conn->conn_to_host.name;
+  else if(cf->sockindex == SECONDARYSOCKET)
+    hostname = cf->conn->secondaryhostname;
+  else
+    hostname = cf->conn->host.name;
+
+  if(cf->sockindex == SECONDARYSOCKET)
+    port = cf->conn->secondary_port;
+  else if(cf->conn->bits.conn_to_port)
+    port = cf->conn->conn_to_port;
+  else
+    port = cf->conn->remote_port;
+
+  if(hostname != cf->conn->host.name)
+    ipv6_ip = (strchr(hostname, ':') != NULL);
+
+  ts->authority = /* host:port with IPv6 support */
+    aprintf("%s%s%s:%d", ipv6_ip?"[":"", hostname, ipv6_ip?"]":"", port);
+  if(!ts->authority)
+    return CURLE_OUT_OF_MEMORY;
+
+  return CURLE_OK;
+}
+
+static void tunnel_stream_clear(struct tunnel_stream *ts)
+{
+  Curl_http_resp_free(ts->resp);
+  Curl_bufq_free(&ts->recvbuf);
+  Curl_bufq_free(&ts->sendbuf);
+  Curl_safefree(ts->authority);
+  memset(ts, 0, sizeof(*ts));
+  ts->state = H2_TUNNEL_INIT;
+}
+
+static void h2_tunnel_go_state(struct Curl_cfilter *cf,
+                               struct tunnel_stream *ts,
+                               h2_tunnel_state new_state,
+                               struct Curl_easy *data)
+{
+  (void)cf;
+
+  if(ts->state == new_state)
+    return;
+  /* leaving this one */
+  switch(ts->state) {
+  case H2_TUNNEL_CONNECT:
+    data->req.ignorebody = FALSE;
+    break;
+  default:
+    break;
+  }
+  /* entering this one */
+  switch(new_state) {
+  case H2_TUNNEL_INIT:
+    CURL_TRC_CF(data, cf, "[%d] new tunnel state 'init'", ts->stream_id);
+    tunnel_stream_clear(ts);
+    break;
+
+  case H2_TUNNEL_CONNECT:
+    CURL_TRC_CF(data, cf, "[%d] new tunnel state 'connect'", ts->stream_id);
+    ts->state = H2_TUNNEL_CONNECT;
+    break;
+
+  case H2_TUNNEL_RESPONSE:
+    CURL_TRC_CF(data, cf, "[%d] new tunnel state 'response'", ts->stream_id);
+    ts->state = H2_TUNNEL_RESPONSE;
+    break;
+
+  case H2_TUNNEL_ESTABLISHED:
+    CURL_TRC_CF(data, cf, "[%d] new tunnel state 'established'",
+                ts->stream_id);
+    infof(data, "CONNECT phase completed");
+    data->state.authproxy.done = TRUE;
+    data->state.authproxy.multipass = FALSE;
+    /* FALLTHROUGH */
+  case H2_TUNNEL_FAILED:
+    if(new_state == H2_TUNNEL_FAILED)
+      CURL_TRC_CF(data, cf, "[%d] new tunnel state 'failed'", ts->stream_id);
+    ts->state = new_state;
+    /* If a proxy-authorization header was used for the proxy, then we should
+       make sure that it isn't accidentally used for the document request
+       after we've connected. So let's free and clear it here. */
+    Curl_safefree(data->state.aptr.proxyuserpwd);
+    break;
+  }
+}
+
+struct cf_h2_proxy_ctx {
+  nghttp2_session *h2;
+  /* The easy handle used in the current filter call, cleared at return */
+  struct cf_call_data call_data;
+
+  struct bufq inbufq;  /* network receive buffer */
+  struct bufq outbufq; /* network send buffer */
+
+  struct tunnel_stream tunnel; /* our tunnel CONNECT stream */
+  int32_t goaway_error;
+  int32_t last_stream_id;
+  BIT(conn_closed);
+  BIT(goaway);
+  BIT(nw_out_blocked);
+};
+
+/* How to access `call_data` from a cf_h2 filter */
+#undef CF_CTX_CALL_DATA
+#define CF_CTX_CALL_DATA(cf)  \
+  ((struct cf_h2_proxy_ctx *)(cf)->ctx)->call_data
+
+static void cf_h2_proxy_ctx_clear(struct cf_h2_proxy_ctx *ctx)
+{
+  struct cf_call_data save = ctx->call_data;
+
+  if(ctx->h2) {
+    nghttp2_session_del(ctx->h2);
+  }
+  Curl_bufq_free(&ctx->inbufq);
+  Curl_bufq_free(&ctx->outbufq);
+  tunnel_stream_clear(&ctx->tunnel);
+  memset(ctx, 0, sizeof(*ctx));
+  ctx->call_data = save;
+}
+
+static void cf_h2_proxy_ctx_free(struct cf_h2_proxy_ctx *ctx)
+{
+  if(ctx) {
+    cf_h2_proxy_ctx_clear(ctx);
+    free(ctx);
+  }
+}
+
+static void drain_tunnel(struct Curl_cfilter *cf,
+                         struct Curl_easy *data,
+                         struct tunnel_stream *tunnel)
+{
+  unsigned char bits;
+
+  (void)cf;
+  bits = CURL_CSELECT_IN;
+  if(!tunnel->closed && !tunnel->reset && tunnel->upload_blocked_len)
+    bits |= CURL_CSELECT_OUT;
+  if(data->state.dselect_bits != bits || 1) {
+    CURL_TRC_CF(data, cf, "[%d] DRAIN dselect_bits=%x",
+                tunnel->stream_id, bits);
+    data->state.dselect_bits = bits;
+    Curl_expire(data, 0, EXPIRE_RUN_NOW);
+  }
+}
+
+static ssize_t proxy_nw_in_reader(void *reader_ctx,
+                                  unsigned char *buf, size_t buflen,
+                                  CURLcode *err)
+{
+  struct Curl_cfilter *cf = reader_ctx;
+  ssize_t nread;
+
+  if(cf) {
+    struct Curl_easy *data = CF_DATA_CURRENT(cf);
+    nread = Curl_conn_cf_recv(cf->next, data, (char *)buf, buflen, err);
+    CURL_TRC_CF(data, cf, "[0] nw_in_reader(len=%zu) -> %zd, %d",
+                buflen, nread, *err);
+  }
+  else {
+    nread = 0;
+  }
+  return nread;
+}
+
+static ssize_t proxy_h2_nw_out_writer(void *writer_ctx,
+                                      const unsigned char *buf, size_t buflen,
+                                      CURLcode *err)
+{
+  struct Curl_cfilter *cf = writer_ctx;
+  ssize_t nwritten;
+
+  if(cf) {
+    struct Curl_easy *data = CF_DATA_CURRENT(cf);
+    nwritten = Curl_conn_cf_send(cf->next, data, (const char *)buf, buflen,
+                                 err);
+    CURL_TRC_CF(data, cf, "[0] nw_out_writer(len=%zu) -> %zd, %d",
+                buflen, nwritten, *err);
+  }
+  else {
+    nwritten = 0;
+  }
+  return nwritten;
+}
+
+static int proxy_h2_client_new(struct Curl_cfilter *cf,
+                               nghttp2_session_callbacks *cbs)
+{
+  struct cf_h2_proxy_ctx *ctx = cf->ctx;
+  nghttp2_option *o;
+
+  int rc = nghttp2_option_new(&o);
+  if(rc)
+    return rc;
+  /* We handle window updates ourself to enforce buffer limits */
+  nghttp2_option_set_no_auto_window_update(o, 1);
+#if NGHTTP2_VERSION_NUM >= 0x013200
+  /* with 1.50.0 */
+  /* turn off RFC 9113 leading and trailing white spaces validation against
+     HTTP field value. */
+  nghttp2_option_set_no_rfc9113_leading_and_trailing_ws_validation(o, 1);
+#endif
+  rc = nghttp2_session_client_new2(&ctx->h2, cbs, cf, o);
+  nghttp2_option_del(o);
+  return rc;
+}
+
+static ssize_t on_session_send(nghttp2_session *h2,
+                              const uint8_t *buf, size_t blen,
+                              int flags, void *userp);
+static int proxy_h2_on_frame_recv(nghttp2_session *session,
+                                  const nghttp2_frame *frame,
+                                  void *userp);
+#ifndef CURL_DISABLE_VERBOSE_STRINGS
+static int on_frame_send(nghttp2_session *session, const nghttp2_frame *frame,
+                         void *userp);
+#endif
+static int proxy_h2_on_stream_close(nghttp2_session *session,
+                                    int32_t stream_id,
+                                    uint32_t error_code, void *userp);
+static int proxy_h2_on_header(nghttp2_session *session,
+                              const nghttp2_frame *frame,
+                              const uint8_t *name, size_t namelen,
+                              const uint8_t *value, size_t valuelen,
+                              uint8_t flags,
+                              void *userp);
+static int tunnel_recv_callback(nghttp2_session *session, uint8_t flags,
+                                int32_t stream_id,
+                                const uint8_t *mem, size_t len, void *userp);
+
+/*
+ * Initialize the cfilter context
+ */
+static CURLcode cf_h2_proxy_ctx_init(struct Curl_cfilter *cf,
+                                     struct Curl_easy *data)
+{
+  struct cf_h2_proxy_ctx *ctx = cf->ctx;
+  CURLcode result = CURLE_OUT_OF_MEMORY;
+  nghttp2_session_callbacks *cbs = NULL;
+  int rc;
+
+  DEBUGASSERT(!ctx->h2);
+  memset(&ctx->tunnel, 0, sizeof(ctx->tunnel));
+
+  Curl_bufq_init(&ctx->inbufq, PROXY_H2_CHUNK_SIZE, PROXY_H2_NW_RECV_CHUNKS);
+  Curl_bufq_init(&ctx->outbufq, PROXY_H2_CHUNK_SIZE, PROXY_H2_NW_SEND_CHUNKS);
+
+  if(tunnel_stream_init(cf, &ctx->tunnel))
+    goto out;
+
+  rc = nghttp2_session_callbacks_new(&cbs);
+  if(rc) {
+    failf(data, "Couldn't initialize nghttp2 callbacks");
+    goto out;
+  }
+
+  nghttp2_session_callbacks_set_send_callback(cbs, on_session_send);
+  nghttp2_session_callbacks_set_on_frame_recv_callback(
+    cbs, proxy_h2_on_frame_recv);
+#ifndef CURL_DISABLE_VERBOSE_STRINGS
+  nghttp2_session_callbacks_set_on_frame_send_callback(cbs, on_frame_send);
+#endif
+  nghttp2_session_callbacks_set_on_data_chunk_recv_callback(
+    cbs, tunnel_recv_callback);
+  nghttp2_session_callbacks_set_on_stream_close_callback(
+    cbs, proxy_h2_on_stream_close);
+  nghttp2_session_callbacks_set_on_header_callback(cbs, proxy_h2_on_header);
+
+  /* The nghttp2 session is not yet setup, do it */
+  rc = proxy_h2_client_new(cf, cbs);
+  if(rc) {
+    failf(data, "Couldn't initialize nghttp2");
+    goto out;
+  }
+
+  {
+    nghttp2_settings_entry iv[3];
+
+    iv[0].settings_id = NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS;
+    iv[0].value = Curl_multi_max_concurrent_streams(data->multi);
+    iv[1].settings_id = NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE;
+    iv[1].value = H2_TUNNEL_WINDOW_SIZE;
+    iv[2].settings_id = NGHTTP2_SETTINGS_ENABLE_PUSH;
+    iv[2].value = 0;
+    rc = nghttp2_submit_settings(ctx->h2, NGHTTP2_FLAG_NONE, iv, 3);
+    if(rc) {
+      failf(data, "nghttp2_submit_settings() failed: %s(%d)",
+            nghttp2_strerror(rc), rc);
+      result = CURLE_HTTP2;
+      goto out;
+    }
+  }
+
+  rc = nghttp2_session_set_local_window_size(ctx->h2, NGHTTP2_FLAG_NONE, 0,
+                                             PROXY_HTTP2_HUGE_WINDOW_SIZE);
+  if(rc) {
+    failf(data, "nghttp2_session_set_local_window_size() failed: %s(%d)",
+          nghttp2_strerror(rc), rc);
+    result = CURLE_HTTP2;
+    goto out;
+  }
+
+
+  /* all set, traffic will be send on connect */
+  result = CURLE_OK;
+
+out:
+  if(cbs)
+    nghttp2_session_callbacks_del(cbs);
+  CURL_TRC_CF(data, cf, "[0] init proxy ctx -> %d", result);
+  return result;
+}
+
+static int proxy_h2_should_close_session(struct cf_h2_proxy_ctx *ctx)
+{
+  return !nghttp2_session_want_read(ctx->h2) &&
+    !nghttp2_session_want_write(ctx->h2);
+}
+
+static CURLcode proxy_h2_nw_out_flush(struct Curl_cfilter *cf,
+                                      struct Curl_easy *data)
+{
+  struct cf_h2_proxy_ctx *ctx = cf->ctx;
+  ssize_t nwritten;
+  CURLcode result;
+
+  (void)data;
+  if(Curl_bufq_is_empty(&ctx->outbufq))
+    return CURLE_OK;
+
+  nwritten = Curl_bufq_pass(&ctx->outbufq, proxy_h2_nw_out_writer, cf,
+                            &result);
+  if(nwritten < 0) {
+    if(result == CURLE_AGAIN) {
+      CURL_TRC_CF(data, cf, "[0] flush nw send buffer(%zu) -> EAGAIN",
+                  Curl_bufq_len(&ctx->outbufq));
+      ctx->nw_out_blocked = 1;
+    }
+    return result;
+  }
+  CURL_TRC_CF(data, cf, "[0] nw send buffer flushed");
+  return Curl_bufq_is_empty(&ctx->outbufq)? CURLE_OK: CURLE_AGAIN;
+}
+
+/*
+ * Processes pending input left in network input buffer.
+ * This function returns 0 if it succeeds, or -1 and error code will
+ * be assigned to *err.
+ */
+static int proxy_h2_process_pending_input(struct Curl_cfilter *cf,
+                                          struct Curl_easy *data,
+                                          CURLcode *err)
+{
+  struct cf_h2_proxy_ctx *ctx = cf->ctx;
+  const unsigned char *buf;
+  size_t blen;
+  ssize_t rv;
+
+  while(Curl_bufq_peek(&ctx->inbufq, &buf, &blen)) {
+
+    rv = nghttp2_session_mem_recv(ctx->h2, (const uint8_t *)buf, blen);
+    CURL_TRC_CF(data, cf, "[0] %zu bytes to nghttp2 -> %zd", blen, rv);
+    if(rv < 0) {
+      failf(data,
+            "process_pending_input: nghttp2_session_mem_recv() returned "
+            "%zd:%s", rv, nghttp2_strerror((int)rv));
+      *err = CURLE_RECV_ERROR;
+      return -1;
+    }
+    Curl_bufq_skip(&ctx->inbufq, (size_t)rv);
+    if(Curl_bufq_is_empty(&ctx->inbufq)) {
+      CURL_TRC_CF(data, cf, "[0] all data in connection buffer processed");
+      break;
+    }
+    else {
+      CURL_TRC_CF(data, cf, "[0] process_pending_input: %zu bytes left "
+                  "in connection buffer", Curl_bufq_len(&ctx->inbufq));
+    }
+  }
+
+  return 0;
+}
+
+static CURLcode proxy_h2_progress_ingress(struct Curl_cfilter *cf,
+                                          struct Curl_easy *data)
+{
+  struct cf_h2_proxy_ctx *ctx = cf->ctx;
+  CURLcode result = CURLE_OK;
+  ssize_t nread;
+
+  /* Process network input buffer fist */
+  if(!Curl_bufq_is_empty(&ctx->inbufq)) {
+    CURL_TRC_CF(data, cf, "[0] process %zu bytes in connection buffer",
+                Curl_bufq_len(&ctx->inbufq));
+    if(proxy_h2_process_pending_input(cf, data, &result) < 0)
+      return result;
+  }
+
+  /* Receive data from the "lower" filters, e.g. network until
+   * it is time to stop or we have enough data for this stream */
+  while(!ctx->conn_closed &&               /* not closed the connection */
+        !ctx->tunnel.closed &&             /* nor the tunnel */
+        Curl_bufq_is_empty(&ctx->inbufq) && /* and we consumed our input */
+        !Curl_bufq_is_full(&ctx->tunnel.recvbuf)) {
+
+    nread = Curl_bufq_slurp(&ctx->inbufq, proxy_nw_in_reader, cf, &result);
+    CURL_TRC_CF(data, cf, "[0] read %zu bytes nw data -> %zd, %d",
+                Curl_bufq_len(&ctx->inbufq), nread, result);
+    if(nread < 0) {
+      if(result != CURLE_AGAIN) {
+        failf(data, "Failed receiving HTTP2 data");
+        return result;
+      }
+      break;
+    }
+    else if(nread == 0) {
+      ctx->conn_closed = TRUE;
+      break;
+    }
+
+    if(proxy_h2_process_pending_input(cf, data, &result))
+      return result;
+  }
+
+  if(ctx->conn_closed && Curl_bufq_is_empty(&ctx->inbufq)) {
+    connclose(cf->conn, "GOAWAY received");
+  }
+
+  return CURLE_OK;
+}
+
+static CURLcode proxy_h2_progress_egress(struct Curl_cfilter *cf,
+                                         struct Curl_easy *data)
+{
+  struct cf_h2_proxy_ctx *ctx = cf->ctx;
+  int rv = 0;
+
+  ctx->nw_out_blocked = 0;
+  while(!rv && !ctx->nw_out_blocked && nghttp2_session_want_write(ctx->h2))
+    rv = nghttp2_session_send(ctx->h2);
+
+  if(nghttp2_is_fatal(rv)) {
+    CURL_TRC_CF(data, cf, "[0] nghttp2_session_send error (%s)%d",
+                nghttp2_strerror(rv), rv);
+    return CURLE_SEND_ERROR;
+  }
+  return proxy_h2_nw_out_flush(cf, data);
+}
+
+static ssize_t on_session_send(nghttp2_session *h2,
+                               const uint8_t *buf, size_t blen, int flags,
+                               void *userp)
+{
+  struct Curl_cfilter *cf = userp;
+  struct cf_h2_proxy_ctx *ctx = cf->ctx;
+  struct Curl_easy *data = CF_DATA_CURRENT(cf);
+  ssize_t nwritten;
+  CURLcode result = CURLE_OK;
+
+  (void)h2;
+  (void)flags;
+  DEBUGASSERT(data);
+
+  nwritten = Curl_bufq_write_pass(&ctx->outbufq, buf, blen,
+                                  proxy_h2_nw_out_writer, cf, &result);
+  if(nwritten < 0) {
+    if(result == CURLE_AGAIN) {
+      return NGHTTP2_ERR_WOULDBLOCK;
+    }
+    failf(data, "Failed sending HTTP2 data");
+    return NGHTTP2_ERR_CALLBACK_FAILURE;
+  }
+
+  if(!nwritten)
+    return NGHTTP2_ERR_WOULDBLOCK;
+
+  return nwritten;
+}
+
+#ifndef CURL_DISABLE_VERBOSE_STRINGS
+static int fr_print(const nghttp2_frame *frame, char *buffer, size_t blen)
+{
+  switch(frame->hd.type) {
+    case NGHTTP2_DATA: {
+      return msnprintf(buffer, blen,
+                       "FRAME[DATA, len=%d, eos=%d, padlen=%d]",
+                       (int)frame->hd.length,
+                       !!(frame->hd.flags & NGHTTP2_FLAG_END_STREAM),
+                       (int)frame->data.padlen);
+    }
+    case NGHTTP2_HEADERS: {
+      return msnprintf(buffer, blen,
+                       "FRAME[HEADERS, len=%d, hend=%d, eos=%d]",
+                       (int)frame->hd.length,
+                       !!(frame->hd.flags & NGHTTP2_FLAG_END_HEADERS),
+                       !!(frame->hd.flags & NGHTTP2_FLAG_END_STREAM));
+    }
+    case NGHTTP2_PRIORITY: {
+      return msnprintf(buffer, blen,
+                       "FRAME[PRIORITY, len=%d, flags=%d]",
+                       (int)frame->hd.length, frame->hd.flags);
+    }
+    case NGHTTP2_RST_STREAM: {
+      return msnprintf(buffer, blen,
+                       "FRAME[RST_STREAM, len=%d, flags=%d, error=%u]",
+                       (int)frame->hd.length, frame->hd.flags,
+                       frame->rst_stream.error_code);
+    }
+    case NGHTTP2_SETTINGS: {
+      if(frame->hd.flags & NGHTTP2_FLAG_ACK) {
+        return msnprintf(buffer, blen, "FRAME[SETTINGS, ack=1]");
+      }
+      return msnprintf(buffer, blen,
+                       "FRAME[SETTINGS, len=%d]", (int)frame->hd.length);
+    }
+    case NGHTTP2_PUSH_PROMISE: {
+      return msnprintf(buffer, blen,
+                       "FRAME[PUSH_PROMISE, len=%d, hend=%d]",
+                       (int)frame->hd.length,
+                       !!(frame->hd.flags & NGHTTP2_FLAG_END_HEADERS));
+    }
+    case NGHTTP2_PING: {
+      return msnprintf(buffer, blen,
+                       "FRAME[PING, len=%d, ack=%d]",
+                       (int)frame->hd.length,
+                       frame->hd.flags&NGHTTP2_FLAG_ACK);
+    }
+    case NGHTTP2_GOAWAY: {
+      char scratch[128];
+      size_t s_len = sizeof(scratch)/sizeof(scratch[0]);
+        size_t len = (frame->goaway.opaque_data_len < s_len)?
+                      frame->goaway.opaque_data_len : s_len-1;
+        if(len)
+          memcpy(scratch, frame->goaway.opaque_data, len);
+        scratch[len] = '\0';
+        return msnprintf(buffer, blen, "FRAME[GOAWAY, error=%d, reason='%s', "
+                         "last_stream=%d]", frame->goaway.error_code,
+                         scratch, frame->goaway.last_stream_id);
+    }
+    case NGHTTP2_WINDOW_UPDATE: {
+      return msnprintf(buffer, blen,
+                       "FRAME[WINDOW_UPDATE, incr=%d]",
+                       frame->window_update.window_size_increment);
+    }
+    default:
+      return msnprintf(buffer, blen, "FRAME[%d, len=%d, flags=%d]",
+                       frame->hd.type, (int)frame->hd.length,
+                       frame->hd.flags);
+  }
+}
+
+static int on_frame_send(nghttp2_session *session, const nghttp2_frame *frame,
+                         void *userp)
+{
+  struct Curl_cfilter *cf = userp;
+  struct Curl_easy *data = CF_DATA_CURRENT(cf);
+
+  (void)session;
+  DEBUGASSERT(data);
+  if(data && Curl_trc_cf_is_verbose(cf, data)) {
+    char buffer[256];
+    int len;
+    len = fr_print(frame, buffer, sizeof(buffer)-1);
+    buffer[len] = 0;
+    CURL_TRC_CF(data, cf, "[%d] -> %s", frame->hd.stream_id, buffer);
+  }
+  return 0;
+}
+#endif /* !CURL_DISABLE_VERBOSE_STRINGS */
+
+static int proxy_h2_on_frame_recv(nghttp2_session *session,
+                                  const nghttp2_frame *frame,
+                                  void *userp)
+{
+  struct Curl_cfilter *cf = userp;
+  struct cf_h2_proxy_ctx *ctx = cf->ctx;
+  struct Curl_easy *data = CF_DATA_CURRENT(cf);
+  int32_t stream_id = frame->hd.stream_id;
+
+  (void)session;
+  DEBUGASSERT(data);
+#ifndef CURL_DISABLE_VERBOSE_STRINGS
+  if(Curl_trc_cf_is_verbose(cf, data)) {
+    char buffer[256];
+    int len;
+    len = fr_print(frame, buffer, sizeof(buffer)-1);
+    buffer[len] = 0;
+    CURL_TRC_CF(data, cf, "[%d] <- %s",frame->hd.stream_id, buffer);
+  }
+#endif /* !CURL_DISABLE_VERBOSE_STRINGS */
+
+  if(!stream_id) {
+    /* stream ID zero is for connection-oriented stuff */
+    DEBUGASSERT(data);
+    switch(frame->hd.type) {
+    case NGHTTP2_SETTINGS:
+      /* Since the initial stream window is 64K, a request might be on HOLD,
+       * due to exhaustion. The (initial) SETTINGS may announce a much larger
+       * window and *assume* that we treat this like a WINDOW_UPDATE. Some
+       * servers send an explicit WINDOW_UPDATE, but not all seem to do that.
+       * To be safe, we UNHOLD a stream in order not to stall. */
+      if((data->req.keepon & KEEP_SEND_HOLD) &&
+         (data->req.keepon & KEEP_SEND)) {
+        data->req.keepon &= ~KEEP_SEND_HOLD;
+        drain_tunnel(cf, data, &ctx->tunnel);
+        CURL_TRC_CF(data, cf, "[%d] un-holding after SETTINGS",
+                    stream_id);
+      }
+      break;
+    case NGHTTP2_GOAWAY:
+      ctx->goaway = TRUE;
+      break;
+    default:
+      break;
+    }
+    return 0;
+  }
+
+  if(stream_id != ctx->tunnel.stream_id) {
+    CURL_TRC_CF(data, cf, "[%d] rcvd FRAME not for tunnel", stream_id);
+    return NGHTTP2_ERR_CALLBACK_FAILURE;
+  }
+
+  switch(frame->hd.type) {
+  case NGHTTP2_HEADERS:
+    /* nghttp2 guarantees that :status is received, and we store it to
+       stream->status_code. Fuzzing has proven this can still be reached
+       without status code having been set. */
+    if(!ctx->tunnel.resp)
+      return NGHTTP2_ERR_CALLBACK_FAILURE;
+    /* Only final status code signals the end of header */
+    CURL_TRC_CF(data, cf, "[%d] got http status: %d",
+                stream_id, ctx->tunnel.resp->status);
+    if(!ctx->tunnel.has_final_response) {
+      if(ctx->tunnel.resp->status / 100 != 1) {
+        ctx->tunnel.has_final_response = TRUE;
+      }
+    }
+    break;
+  case NGHTTP2_WINDOW_UPDATE:
+    if((data->req.keepon & KEEP_SEND_HOLD) &&
+       (data->req.keepon & KEEP_SEND)) {
+      data->req.keepon &= ~KEEP_SEND_HOLD;
+      Curl_expire(data, 0, EXPIRE_RUN_NOW);
+      CURL_TRC_CF(data, cf, "[%d] unpausing after win update",
+                  stream_id);
+    }
+    break;
+  default:
+    break;
+  }
+  return 0;
+}
+
+static int proxy_h2_on_header(nghttp2_session *session,
+                              const nghttp2_frame *frame,
+                              const uint8_t *name, size_t namelen,
+                              const uint8_t *value, size_t valuelen,
+                              uint8_t flags,
+                              void *userp)
+{
+  struct Curl_cfilter *cf = userp;
+  struct cf_h2_proxy_ctx *ctx = cf->ctx;
+  struct Curl_easy *data = CF_DATA_CURRENT(cf);
+  int32_t stream_id = frame->hd.stream_id;
+  CURLcode result;
+
+  (void)flags;
+  (void)data;
+  (void)session;
+  DEBUGASSERT(stream_id); /* should never be a zero stream ID here */
+  if(stream_id != ctx->tunnel.stream_id) {
+    CURL_TRC_CF(data, cf, "[%d] header for non-tunnel stream: "
+                "%.*s: %.*s", stream_id,
+                (int)namelen, name, (int)valuelen, value);
+    return NGHTTP2_ERR_CALLBACK_FAILURE;
+  }
+
+  if(frame->hd.type == NGHTTP2_PUSH_PROMISE)
+    return NGHTTP2_ERR_CALLBACK_FAILURE;
+
+  if(ctx->tunnel.has_final_response) {
+    /* we do not do anything with trailers for tunnel streams */
+    return 0;
+  }
+
+  if(namelen == sizeof(HTTP_PSEUDO_STATUS) - 1 &&
+     memcmp(HTTP_PSEUDO_STATUS, name, namelen) == 0) {
+    int http_status;
+    struct http_resp *resp;
+
+    /* status: always comes first, we might get more than one response,
+     * link the previous ones for keepers */
+    result = Curl_http_decode_status(&http_status,
+                                    (const char *)value, valuelen);
+    if(result)
+      return NGHTTP2_ERR_CALLBACK_FAILURE;
+    result = Curl_http_resp_make(&resp, http_status, NULL);
+    if(result)
+      return NGHTTP2_ERR_CALLBACK_FAILURE;
+    resp->prev = ctx->tunnel.resp;
+    ctx->tunnel.resp = resp;
+    CURL_TRC_CF(data, cf, "[%d] status: HTTP/2 %03d",
+                stream_id, ctx->tunnel.resp->status);
+    return 0;
+  }
+
+  if(!ctx->tunnel.resp)
+    return NGHTTP2_ERR_CALLBACK_FAILURE;
+
+  result = Curl_dynhds_add(&ctx->tunnel.resp->headers,
+                           (const char *)name, namelen,
+                           (const char *)value, valuelen);
+  if(result)
+    return NGHTTP2_ERR_CALLBACK_FAILURE;
+
+  CURL_TRC_CF(data, cf, "[%d] header: %.*s: %.*s",
+              stream_id, (int)namelen, name, (int)valuelen, value);
+
+  return 0; /* 0 is successful */
+}
+
+static ssize_t tunnel_send_callback(nghttp2_session *session,
+                                    int32_t stream_id,
+                                    uint8_t *buf, size_t length,
+                                    uint32_t *data_flags,
+                                    nghttp2_data_source *source,
+                                    void *userp)
+{
+  struct Curl_cfilter *cf = userp;
+  struct cf_h2_proxy_ctx *ctx = cf->ctx;
+  struct Curl_easy *data = CF_DATA_CURRENT(cf);
+  struct tunnel_stream *ts;
+  CURLcode result;
+  ssize_t nread;
+
+  (void)source;
+  (void)data;
+  (void)ctx;
+
+  if(!stream_id)
+    return NGHTTP2_ERR_INVALID_ARGUMENT;
+
+  ts = nghttp2_session_get_stream_user_data(session, stream_id);
+  if(!ts)
+    return NGHTTP2_ERR_CALLBACK_FAILURE;
+  DEBUGASSERT(ts == &ctx->tunnel);
+
+  nread = Curl_bufq_read(&ts->sendbuf, buf, length, &result);
+  if(nread < 0) {
+    if(result != CURLE_AGAIN)
+      return NGHTTP2_ERR_CALLBACK_FAILURE;
+    return NGHTTP2_ERR_DEFERRED;
+  }
+  if(ts->closed && Curl_bufq_is_empty(&ts->sendbuf))
+    *data_flags = NGHTTP2_DATA_FLAG_EOF;
+
+  CURL_TRC_CF(data, cf, "[%d] tunnel_send_callback -> %zd",
+              ts->stream_id, nread);
+  return nread;
+}
+
+static int tunnel_recv_callback(nghttp2_session *session, uint8_t flags,
+                                int32_t stream_id,
+                                const uint8_t *mem, size_t len, void *userp)
+{
+  struct Curl_cfilter *cf = userp;
+  struct cf_h2_proxy_ctx *ctx = cf->ctx;
+  ssize_t nwritten;
+  CURLcode result;
+
+  (void)flags;
+  (void)session;
+  DEBUGASSERT(stream_id); /* should never be a zero stream ID here */
+
+  if(stream_id != ctx->tunnel.stream_id)
+    return NGHTTP2_ERR_CALLBACK_FAILURE;
+
+  nwritten = Curl_bufq_write(&ctx->tunnel.recvbuf, mem, len, &result);
+  if(nwritten < 0) {
+    if(result != CURLE_AGAIN)
+      return NGHTTP2_ERR_CALLBACK_FAILURE;
+    nwritten = 0;
+  }
+  DEBUGASSERT((size_t)nwritten == len);
+  return 0;
+}
+
+static int proxy_h2_on_stream_close(nghttp2_session *session,
+                                    int32_t stream_id,
+                                    uint32_t error_code, void *userp)
+{
+  struct Curl_cfilter *cf = userp;
+  struct cf_h2_proxy_ctx *ctx = cf->ctx;
+  struct Curl_easy *data = CF_DATA_CURRENT(cf);
+
+  (void)session;
+  (void)data;
+
+  if(stream_id != ctx->tunnel.stream_id)
+    return 0;
+
+  CURL_TRC_CF(data, cf, "[%d] proxy_h2_on_stream_close, %s (err %d)",
+              stream_id, nghttp2_http2_strerror(error_code), error_code);
+  ctx->tunnel.closed = TRUE;
+  ctx->tunnel.error = error_code;
+
+  return 0;
+}
+
+static CURLcode proxy_h2_submit(int32_t *pstream_id,
+                                struct Curl_cfilter *cf,
+                                struct Curl_easy *data,
+                                nghttp2_session *h2,
+                                struct httpreq *req,
+                                const nghttp2_priority_spec *pri_spec,
+                                void *stream_user_data,
+                               nghttp2_data_source_read_callback read_callback,
+                                void *read_ctx)
+{
+  struct dynhds h2_headers;
+  nghttp2_nv *nva = NULL;
+  unsigned int i;
+  int32_t stream_id = -1;
+  size_t nheader;
+  CURLcode result;
+
+  (void)cf;
+  Curl_dynhds_init(&h2_headers, 0, DYN_HTTP_REQUEST);
+  result = Curl_http_req_to_h2(&h2_headers, req, data);
+  if(result)
+    goto out;
+
+  nheader = Curl_dynhds_count(&h2_headers);
+  nva = malloc(sizeof(nghttp2_nv) * nheader);
+  if(!nva) {
+    result = CURLE_OUT_OF_MEMORY;
+    goto out;
+  }
+
+  for(i = 0; i < nheader; ++i) {
+    struct dynhds_entry *e = Curl_dynhds_getn(&h2_headers, i);
+    nva[i].name = (unsigned char *)e->name;
+    nva[i].namelen = e->namelen;
+    nva[i].value = (unsigned char *)e->value;
+    nva[i].valuelen = e->valuelen;
+    nva[i].flags = NGHTTP2_NV_FLAG_NONE;
+  }
+
+  if(read_callback) {
+    nghttp2_data_provider data_prd;
+
+    data_prd.read_callback = read_callback;
+    data_prd.source.ptr = read_ctx;
+    stream_id = nghttp2_submit_request(h2, pri_spec, nva, nheader,
+                                       &data_prd, stream_user_data);
+  }
+  else {
+    stream_id = nghttp2_submit_request(h2, pri_spec, nva, nheader,
+                                       NULL, stream_user_data);
+  }
+
+  if(stream_id < 0) {
+    failf(data, "nghttp2_session_upgrade2() failed: %s(%d)",
+          nghttp2_strerror(stream_id), stream_id);
+    result = CURLE_SEND_ERROR;
+    goto out;
+  }
+  result = CURLE_OK;
+
+out:
+  free(nva);
+  Curl_dynhds_free(&h2_headers);
+  *pstream_id = stream_id;
+  return result;
+}
+
+static CURLcode submit_CONNECT(struct Curl_cfilter *cf,
+                               struct Curl_easy *data,
+                               struct tunnel_stream *ts)
+{
+  struct cf_h2_proxy_ctx *ctx = cf->ctx;
+  CURLcode result;
+  struct httpreq *req = NULL;
+
+  infof(data, "Establish HTTP/2 proxy tunnel to %s", ts->authority);
+
+  result = Curl_http_req_make(&req, "CONNECT", sizeof("CONNECT")-1,
+                              NULL, 0, ts->authority, strlen(ts->authority),
+                              NULL, 0);
+  if(result)
+    goto out;
+
+  /* Setup the proxy-authorization header, if any */
+  result = Curl_http_output_auth(data, cf->conn, req->method, HTTPREQ_GET,
+                                 req->authority, TRUE);
+  if(result)
+    goto out;
+
+  if(data->state.aptr.proxyuserpwd) {
+    result = Curl_dynhds_h1_cadd_line(&req->headers,
+                                      data->state.aptr.proxyuserpwd);
+    if(result)
+      goto out;
+  }
+
+  if(!Curl_checkProxyheaders(data, cf->conn, STRCONST("User-Agent"))
+     && data->set.str[STRING_USERAGENT]) {
+    result = Curl_dynhds_cadd(&req->headers, "User-Agent",
+                              data->set.str[STRING_USERAGENT]);
+    if(result)
+      goto out;
+  }
+
+  result = Curl_dynhds_add_custom(data, TRUE, &req->headers);
+  if(result)
+    goto out;
+
+  result = proxy_h2_submit(&ts->stream_id, cf, data, ctx->h2, req,
+                           NULL, ts, tunnel_send_callback, cf);
+  if(result) {
+    CURL_TRC_CF(data, cf, "[%d] send, nghttp2_submit_request error: %s",
+                ts->stream_id, nghttp2_strerror(ts->stream_id));
+  }
+
+out:
+  if(req)
+    Curl_http_req_free(req);
+  if(result)
+    failf(data, "Failed sending CONNECT to proxy");
+  return result;
+}
+
+static CURLcode inspect_response(struct Curl_cfilter *cf,
+                                 struct Curl_easy *data,
+                                 struct tunnel_stream *ts)
+{
+  CURLcode result = CURLE_OK;
+  struct dynhds_entry *auth_reply = NULL;
+  (void)cf;
+
+  DEBUGASSERT(ts->resp);
+  if(ts->resp->status/100 == 2) {
+    infof(data, "CONNECT tunnel established, response %d", ts->resp->status);
+    h2_tunnel_go_state(cf, ts, H2_TUNNEL_ESTABLISHED, data);
+    return CURLE_OK;
+  }
+
+  if(ts->resp->status == 401) {
+    auth_reply = Curl_dynhds_cget(&ts->resp->headers, "WWW-Authenticate");
+  }
+  else if(ts->resp->status == 407) {
+    auth_reply = Curl_dynhds_cget(&ts->resp->headers, "Proxy-Authenticate");
+  }
+
+  if(auth_reply) {
+    CURL_TRC_CF(data, cf, "[0] CONNECT: fwd auth header '%s'",
+                auth_reply->value);
+    result = Curl_http_input_auth(data, ts->resp->status == 407,
+                                  auth_reply->value);
+    if(result)
+      return result;
+    if(data->req.newurl) {
+      /* Inidicator that we should try again */
+      Curl_safefree(data->req.newurl);
+      h2_tunnel_go_state(cf, ts, H2_TUNNEL_INIT, data);
+      return CURLE_OK;
+    }
+  }
+
+  /* Seems to have failed */
+  return CURLE_RECV_ERROR;
+}
+
+static CURLcode H2_CONNECT(struct Curl_cfilter *cf,
+                           struct Curl_easy *data,
+                           struct tunnel_stream *ts)
+{
+  struct cf_h2_proxy_ctx *ctx = cf->ctx;
+  CURLcode result = CURLE_OK;
+
+  DEBUGASSERT(ts);
+  DEBUGASSERT(ts->authority);
+  do {
+    switch(ts->state) {
+    case H2_TUNNEL_INIT:
+      /* Prepare the CONNECT request and make a first attempt to send. */
+      CURL_TRC_CF(data, cf, "[0] CONNECT start for %s", ts->authority);
+      result = submit_CONNECT(cf, data, ts);
+      if(result)
+        goto out;
+      h2_tunnel_go_state(cf, ts, H2_TUNNEL_CONNECT, data);
+      /* FALLTHROUGH */
+
+    case H2_TUNNEL_CONNECT:
+      /* see that the request is completely sent */
+      result = proxy_h2_progress_ingress(cf, data);
+      if(!result)
+        result = proxy_h2_progress_egress(cf, data);
+      if(result && result != CURLE_AGAIN) {
+        h2_tunnel_go_state(cf, ts, H2_TUNNEL_FAILED, data);
+        break;
+      }
+
+      if(ts->has_final_response) {
+        h2_tunnel_go_state(cf, ts, H2_TUNNEL_RESPONSE, data);
+      }
+      else {
+        result = CURLE_OK;
+        goto out;
+      }
+      /* FALLTHROUGH */
+
+    case H2_TUNNEL_RESPONSE:
+      DEBUGASSERT(ts->has_final_response);
+      result = inspect_response(cf, data, ts);
+      if(result)
+        goto out;
+      break;
+
+    case H2_TUNNEL_ESTABLISHED:
+      return CURLE_OK;
+
+    case H2_TUNNEL_FAILED:
+      return CURLE_RECV_ERROR;
+
+    default:
+      break;
+    }
+
+  } while(ts->state == H2_TUNNEL_INIT);
+
+out:
+  if(result || ctx->tunnel.closed)
+    h2_tunnel_go_state(cf, ts, H2_TUNNEL_FAILED, data);
+  return result;
+}
+
+static CURLcode cf_h2_proxy_connect(struct Curl_cfilter *cf,
+                                    struct Curl_easy *data,
+                                    bool blocking, bool *done)
+{
+  struct cf_h2_proxy_ctx *ctx = cf->ctx;
+  CURLcode result = CURLE_OK;
+  struct cf_call_data save;
+  timediff_t check;
+  struct tunnel_stream *ts = &ctx->tunnel;
+
+  if(cf->connected) {
+    *done = TRUE;
+    return CURLE_OK;
+  }
+
+  /* Connect the lower filters first */
+  if(!cf->next->connected) {
+    result = Curl_conn_cf_connect(cf->next, data, blocking, done);
+    if(result || !*done)
+      return result;
+  }
+
+  *done = FALSE;
+
+  CF_DATA_SAVE(save, cf, data);
+  if(!ctx->h2) {
+    result = cf_h2_proxy_ctx_init(cf, data);
+    if(result)
+      goto out;
+  }
+  DEBUGASSERT(ts->authority);
+
+  check = Curl_timeleft(data, NULL, TRUE);
+  if(check <= 0) {
+    failf(data, "Proxy CONNECT aborted due to timeout");
+    result = CURLE_OPERATION_TIMEDOUT;
+    goto out;
+  }
+
+  /* for the secondary socket (FTP), use the "connect to host"
+   * but ignore the "connect to port" (use the secondary port)
+   */
+  result = H2_CONNECT(cf, data, ts);
+
+out:
+  *done = (result == CURLE_OK) && (ts->state == H2_TUNNEL_ESTABLISHED);
+  cf->connected = *done;
+  CF_DATA_RESTORE(cf, save);
+  return result;
+}
+
+static void cf_h2_proxy_close(struct Curl_cfilter *cf, struct Curl_easy *data)
+{
+  struct cf_h2_proxy_ctx *ctx = cf->ctx;
+
+  if(ctx) {
+    struct cf_call_data save;
+
+    CF_DATA_SAVE(save, cf, data);
+    cf_h2_proxy_ctx_clear(ctx);
+    CF_DATA_RESTORE(cf, save);
+  }
+}
+
+static void cf_h2_proxy_destroy(struct Curl_cfilter *cf,
+                                struct Curl_easy *data)
+{
+  struct cf_h2_proxy_ctx *ctx = cf->ctx;
+
+  (void)data;
+  if(ctx) {
+    cf_h2_proxy_ctx_free(ctx);
+    cf->ctx = NULL;
+  }
+}
+
+static bool cf_h2_proxy_data_pending(struct Curl_cfilter *cf,
+                                     const struct Curl_easy *data)
+{
+  struct cf_h2_proxy_ctx *ctx = cf->ctx;
+  if((ctx && !Curl_bufq_is_empty(&ctx->inbufq)) ||
+     (ctx && ctx->tunnel.state == H2_TUNNEL_ESTABLISHED &&
+      !Curl_bufq_is_empty(&ctx->tunnel.recvbuf)))
+    return TRUE;
+  return cf->next? cf->next->cft->has_data_pending(cf->next, data) : FALSE;
+}
+
+static int cf_h2_proxy_get_select_socks(struct Curl_cfilter *cf,
+                                        struct Curl_easy *data,
+                                        curl_socket_t *sock)
+{
+  struct cf_h2_proxy_ctx *ctx = cf->ctx;
+  int bitmap = GETSOCK_BLANK;
+  struct cf_call_data save;
+
+  CF_DATA_SAVE(save, cf, data);
+  sock[0] = Curl_conn_cf_get_socket(cf, data);
+  bitmap |= GETSOCK_READSOCK(0);
+
+  /* HTTP/2 layer wants to send data) AND there's a window to send data in */
+  if(nghttp2_session_want_write(ctx->h2) &&
+     nghttp2_session_get_remote_window_size(ctx->h2))
+    bitmap |= GETSOCK_WRITESOCK(0);
+
+  CF_DATA_RESTORE(cf, save);
+  return bitmap;
+}
+
+static ssize_t h2_handle_tunnel_close(struct Curl_cfilter *cf,
+                                      struct Curl_easy *data,
+                                      CURLcode *err)
+{
+  struct cf_h2_proxy_ctx *ctx = cf->ctx;
+  ssize_t rv = 0;
+
+  if(ctx->tunnel.error == NGHTTP2_REFUSED_STREAM) {
+    CURL_TRC_CF(data, cf, "[%d] REFUSED_STREAM, try again on a new "
+                "connection", ctx->tunnel.stream_id);
+    connclose(cf->conn, "REFUSED_STREAM"); /* don't use this anymore */
+    *err = CURLE_RECV_ERROR; /* trigger Curl_retry_request() later */
+    return -1;
+  }
+  else if(ctx->tunnel.error != NGHTTP2_NO_ERROR) {
+    failf(data, "HTTP/2 stream %u was not closed cleanly: %s (err %u)",
+          ctx->tunnel.stream_id, nghttp2_http2_strerror(ctx->tunnel.error),
+          ctx->tunnel.error);
+    *err = CURLE_HTTP2_STREAM;
+    return -1;
+  }
+  else if(ctx->tunnel.reset) {
+    failf(data, "HTTP/2 stream %u was reset", ctx->tunnel.stream_id);
+    *err = CURLE_RECV_ERROR;
+    return -1;
+  }
+
+  *err = CURLE_OK;
+  rv = 0;
+  CURL_TRC_CF(data, cf, "[%d] handle_tunnel_close -> %zd, %d",
+              ctx->tunnel.stream_id, rv, *err);
+  return rv;
+}
+
+static ssize_t tunnel_recv(struct Curl_cfilter *cf, struct Curl_easy *data,
+                           char *buf, size_t len, CURLcode *err)
+{
+  struct cf_h2_proxy_ctx *ctx = cf->ctx;
+  ssize_t nread = -1;
+
+  *err = CURLE_AGAIN;
+  if(!Curl_bufq_is_empty(&ctx->tunnel.recvbuf)) {
+    nread = Curl_bufq_read(&ctx->tunnel.recvbuf,
+                           (unsigned char *)buf, len, err);
+    if(nread < 0)
+      goto out;
+    DEBUGASSERT(nread > 0);
+  }
+
+  if(nread < 0) {
+    if(ctx->tunnel.closed) {
+      nread = h2_handle_tunnel_close(cf, data, err);
+    }
+    else if(ctx->tunnel.reset ||
+            (ctx->conn_closed && Curl_bufq_is_empty(&ctx->inbufq)) ||
+            (ctx->goaway && ctx->last_stream_id < ctx->tunnel.stream_id)) {
+      *err = CURLE_RECV_ERROR;
+      nread = -1;
+    }
+  }
+  else if(nread == 0) {
+    *err = CURLE_AGAIN;
+    nread = -1;
+  }
+
+out:
+  CURL_TRC_CF(data, cf, "[%d] tunnel_recv(len=%zu) -> %zd, %d",
+              ctx->tunnel.stream_id, len, nread, *err);
+  return nread;
+}
+
+static ssize_t cf_h2_proxy_recv(struct Curl_cfilter *cf,
+                                struct Curl_easy *data,
+                                char *buf, size_t len, CURLcode *err)
+{
+  struct cf_h2_proxy_ctx *ctx = cf->ctx;
+  ssize_t nread = -1;
+  struct cf_call_data save;
+  CURLcode result;
+
+  if(ctx->tunnel.state != H2_TUNNEL_ESTABLISHED) {
+    *err = CURLE_RECV_ERROR;
+    return -1;
+  }
+  CF_DATA_SAVE(save, cf, data);
+
+  if(Curl_bufq_is_empty(&ctx->tunnel.recvbuf)) {
+    *err = proxy_h2_progress_ingress(cf, data);
+    if(*err)
+      goto out;
+  }
+
+  nread = tunnel_recv(cf, data, buf, len, err);
+
+  if(nread > 0) {
+    CURL_TRC_CF(data, cf, "[%d] increase window by %zd",
+                ctx->tunnel.stream_id, nread);
+    nghttp2_session_consume(ctx->h2, ctx->tunnel.stream_id, (size_t)nread);
+  }
+
+  result = proxy_h2_progress_egress(cf, data);
+  if(result == CURLE_AGAIN) {
+    /* pending data to send, need to be called again. Ideally, we'd
+     * monitor the socket for POLLOUT, but we might not be in SENDING
+     * transfer state any longer and are unable to make this happen.
+     */
+    CURL_TRC_CF(data, cf, "[%d] egress blocked, DRAIN",
+                ctx->tunnel.stream_id);
+    drain_tunnel(cf, data, &ctx->tunnel);
+  }
+  else if(result) {
+    *err = result;
+    nread = -1;
+  }
+
+out:
+  if(!Curl_bufq_is_empty(&ctx->tunnel.recvbuf) &&
+     (nread >= 0 || *err == CURLE_AGAIN)) {
+    /* data pending and no fatal error to report. Need to trigger
+     * draining to avoid stalling when no socket events happen. */
+    drain_tunnel(cf, data, &ctx->tunnel);
+  }
+  CURL_TRC_CF(data, cf, "[%d] cf_recv(len=%zu) -> %zd %d",
+              ctx->tunnel.stream_id, len, nread, *err);
+  CF_DATA_RESTORE(cf, save);
+  return nread;
+}
+
+static ssize_t cf_h2_proxy_send(struct Curl_cfilter *cf,
+                                struct Curl_easy *data,
+                                const void *buf, size_t len, CURLcode *err)
+{
+  struct cf_h2_proxy_ctx *ctx = cf->ctx;
+  struct cf_call_data save;
+  int rv;
+  ssize_t nwritten;
+  CURLcode result;
+  int blocked = 0;
+
+  if(ctx->tunnel.state != H2_TUNNEL_ESTABLISHED) {
+    *err = CURLE_SEND_ERROR;
+    return -1;
+  }
+  CF_DATA_SAVE(save, cf, data);
+
+  if(ctx->tunnel.closed) {
+    nwritten = -1;
+    *err = CURLE_SEND_ERROR;
+    goto out;
+  }
+  else if(ctx->tunnel.upload_blocked_len) {
+    /* the data in `buf` has already been submitted or added to the
+     * buffers, but have been EAGAINed on the last invocation. */
+    DEBUGASSERT(len >= ctx->tunnel.upload_blocked_len);
+    if(len < ctx->tunnel.upload_blocked_len) {
+      /* Did we get called again with a smaller `len`? This should not
+       * happen. We are not prepared to handle that. */
+      failf(data, "HTTP/2 proxy, send again with decreased length");
+      *err = CURLE_HTTP2;
+      nwritten = -1;
+      goto out;
+    }
+    nwritten = (ssize_t)ctx->tunnel.upload_blocked_len;
+    ctx->tunnel.upload_blocked_len = 0;
+    *err = CURLE_OK;
+  }
+  else {
+    nwritten = Curl_bufq_write(&ctx->tunnel.sendbuf, buf, len, err);
+    if(nwritten < 0) {
+      if(*err != CURLE_AGAIN)
+        goto out;
+      nwritten = 0;
+    }
+  }
+
+  if(!Curl_bufq_is_empty(&ctx->tunnel.sendbuf)) {
+    /* req body data is buffered, resume the potentially suspended stream */
+    rv = nghttp2_session_resume_data(ctx->h2, ctx->tunnel.stream_id);
+    if(nghttp2_is_fatal(rv)) {
+      *err = CURLE_SEND_ERROR;
+      nwritten = -1;
+      goto out;
+    }
+  }
+
+  result = proxy_h2_progress_ingress(cf, data);
+  if(result) {
+    *err = result;
+    nwritten = -1;
+    goto out;
+  }
+
+  /* Call the nghttp2 send loop and flush to write ALL buffered data,
+   * headers and/or request body completely out to the network */
+  result = proxy_h2_progress_egress(cf, data);
+  if(result == CURLE_AGAIN) {
+    blocked = 1;
+  }
+  else if(result) {
+    *err = result;
+    nwritten = -1;
+    goto out;
+  }
+  else if(!Curl_bufq_is_empty(&ctx->tunnel.sendbuf)) {
+    /* although we wrote everything that nghttp2 wants to send now,
+     * there is data left in our stream send buffer unwritten. This may
+     * be due to the stream's HTTP/2 flow window being exhausted. */
+    blocked = 1;
+  }
+
+  if(blocked) {
+    /* Unable to send all data, due to connection blocked or H2 window
+     * exhaustion. Data is left in our stream buffer, or nghttp2's internal
+     * frame buffer or our network out buffer. */
+    size_t rwin = nghttp2_session_get_stream_remote_window_size(
+                    ctx->h2, ctx->tunnel.stream_id);
+    if(rwin == 0) {
+      /* H2 flow window exhaustion.
+       * FIXME: there is no way to HOLD all transfers that use this
+       * proxy connection AND to UNHOLD all of them again when the
+       * window increases.
+       * We *could* iterate over all data on this conn maybe? */
+      CURL_TRC_CF(data, cf, "[%d] remote flow "
+                  "window is exhausted", ctx->tunnel.stream_id);
+    }
+
+    /* Whatever the cause, we need to return CURL_EAGAIN for this call.
+     * We have unwritten state that needs us being invoked again and EAGAIN
+     * is the only way to ensure that. */
+    ctx->tunnel.upload_blocked_len = nwritten;
+    CURL_TRC_CF(data, cf, "[%d] cf_send(len=%zu) BLOCK: win %u/%zu "
+                "blocked_len=%zu",
+                ctx->tunnel.stream_id, len,
+                nghttp2_session_get_remote_window_size(ctx->h2), rwin,
+                nwritten);
+    drain_tunnel(cf, data, &ctx->tunnel);
+    *err = CURLE_AGAIN;
+    nwritten = -1;
+    goto out;
+  }
+  else if(proxy_h2_should_close_session(ctx)) {
+    /* nghttp2 thinks this session is done. If the stream has not been
+     * closed, this is an error state for out transfer */
+    if(ctx->tunnel.closed) {
+      *err = CURLE_SEND_ERROR;
+      nwritten = -1;
+    }
+    else {
+      CURL_TRC_CF(data, cf, "[0] send: nothing to do in this session");
+      *err = CURLE_HTTP2;
+      nwritten = -1;
+    }
+  }
+
+out:
+  if(!Curl_bufq_is_empty(&ctx->tunnel.recvbuf) &&
+     (nwritten >= 0 || *err == CURLE_AGAIN)) {
+    /* data pending and no fatal error to report. Need to trigger
+     * draining to avoid stalling when no socket events happen. */
+    drain_tunnel(cf, data, &ctx->tunnel);
+  }
+  CURL_TRC_CF(data, cf, "[%d] cf_send(len=%zu) -> %zd, %d, "
+              "h2 windows %d-%d (stream-conn), buffers %zu-%zu (stream-conn)",
+              ctx->tunnel.stream_id, len, nwritten, *err,
+              nghttp2_session_get_stream_remote_window_size(
+                  ctx->h2, ctx->tunnel.stream_id),
+              nghttp2_session_get_remote_window_size(ctx->h2),
+              Curl_bufq_len(&ctx->tunnel.sendbuf),
+              Curl_bufq_len(&ctx->outbufq));
+  CF_DATA_RESTORE(cf, save);
+  return nwritten;
+}
+
+static bool proxy_h2_connisalive(struct Curl_cfilter *cf,
+                                 struct Curl_easy *data,
+                                 bool *input_pending)
+{
+  struct cf_h2_proxy_ctx *ctx = cf->ctx;
+  bool alive = TRUE;
+
+  *input_pending = FALSE;
+  if(!cf->next || !cf->next->cft->is_alive(cf->next, data, input_pending))
+    return FALSE;
+
+  if(*input_pending) {
+    /* This happens before we've sent off a request and the connection is
+       not in use by any other transfer, there shouldn't be any data here,
+       only "protocol frames" */
+    CURLcode result;
+    ssize_t nread = -1;
+
+    *input_pending = FALSE;
+    nread = Curl_bufq_slurp(&ctx->inbufq, proxy_nw_in_reader, cf, &result);
+    if(nread != -1) {
+      if(proxy_h2_process_pending_input(cf, data, &result) < 0)
+        /* immediate error, considered dead */
+        alive = FALSE;
+      else {
+        alive = !proxy_h2_should_close_session(ctx);
+      }
+    }
+    else if(result != CURLE_AGAIN) {
+      /* the read failed so let's say this is dead anyway */
+      alive = FALSE;
+    }
+  }
+
+  return alive;
+}
+
+static bool cf_h2_proxy_is_alive(struct Curl_cfilter *cf,
+                                 struct Curl_easy *data,
+                                 bool *input_pending)
+{
+  struct cf_h2_proxy_ctx *ctx = cf->ctx;
+  CURLcode result;
+  struct cf_call_data save;
+
+  CF_DATA_SAVE(save, cf, data);
+  result = (ctx && ctx->h2 && proxy_h2_connisalive(cf, data, input_pending));
+  CURL_TRC_CF(data, cf, "[0] conn alive -> %d, input_pending=%d",
+              result, *input_pending);
+  CF_DATA_RESTORE(cf, save);
+  return result;
+}
+
+struct Curl_cftype Curl_cft_h2_proxy = {
+  "H2-PROXY",
+  CF_TYPE_IP_CONNECT,
+  CURL_LOG_LVL_NONE,
+  cf_h2_proxy_destroy,
+  cf_h2_proxy_connect,
+  cf_h2_proxy_close,
+  Curl_cf_http_proxy_get_host,
+  cf_h2_proxy_get_select_socks,
+  cf_h2_proxy_data_pending,
+  cf_h2_proxy_send,
+  cf_h2_proxy_recv,
+  Curl_cf_def_cntrl,
+  cf_h2_proxy_is_alive,
+  Curl_cf_def_conn_keep_alive,
+  Curl_cf_def_query,
+};
+
+CURLcode Curl_cf_h2_proxy_insert_after(struct Curl_cfilter *cf,
+                                       struct Curl_easy *data)
+{
+  struct Curl_cfilter *cf_h2_proxy = NULL;
+  struct cf_h2_proxy_ctx *ctx;
+  CURLcode result = CURLE_OUT_OF_MEMORY;
+
+  (void)data;
+  ctx = calloc(sizeof(*ctx), 1);
+  if(!ctx)
+    goto out;
+
+  result = Curl_cf_create(&cf_h2_proxy, &Curl_cft_h2_proxy, ctx);
+  if(result)
+    goto out;
+
+  Curl_conn_cf_insert_after(cf, cf_h2_proxy);
+  result = CURLE_OK;
+
+out:
+  if(result)
+    cf_h2_proxy_ctx_free(ctx);
+  return result;
+}
+
+#endif /* defined(USE_NGHTTP2) && !defined(CURL_DISABLE_PROXY) */
Index: trunk/src/libs/curl-8.3.0/lib/cf-h2-proxy.h
===================================================================
--- trunk/src/libs/curl-8.3.0/lib/cf-h2-proxy.h	(revision 101259)
+++ trunk/src/libs/curl-8.3.0/lib/cf-h2-proxy.h	(revision 101259)
@@ -0,0 +1,39 @@
+#ifndef HEADER_CURL_H2_PROXY_H
+#define HEADER_CURL_H2_PROXY_H
+/***************************************************************************
+ *                                  _   _ ____  _
+ *  Project                     ___| | | |  _ \| |
+ *                             / __| | | | |_) | |
+ *                            | (__| |_| |  _ <| |___
+ *                             \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
+ *
+ * This software is licensed as described in the file COPYING, which
+ * you should have received as part of this distribution. The terms
+ * are also available at https://curl.se/docs/copyright.html.
+ *
+ * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+ * copies of the Software, and permit persons to whom the Software is
+ * furnished to do so, under the terms of the COPYING file.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ * SPDX-License-Identifier: curl
+ *
+ ***************************************************************************/
+
+#include "curl_setup.h"
+
+#if defined(USE_NGHTTP2) && !defined(CURL_DISABLE_PROXY)
+
+CURLcode Curl_cf_h2_proxy_insert_after(struct Curl_cfilter *cf,
+                                       struct Curl_easy *data);
+
+extern struct Curl_cftype Curl_cft_h2_proxy;
+
+
+#endif /* defined(USE_NGHTTP2) && !defined(CURL_DISABLE_PROXY) */
+
+#endif /* HEADER_CURL_H2_PROXY_H */
Index: trunk/src/libs/curl-8.3.0/lib/cf-haproxy.c
===================================================================
--- trunk/src/libs/curl-8.3.0/lib/cf-haproxy.c	(revision 101259)
+++ trunk/src/libs/curl-8.3.0/lib/cf-haproxy.c	(revision 101259)
@@ -0,0 +1,251 @@
+/***************************************************************************
+ *                                  _   _ ____  _
+ *  Project                     ___| | | |  _ \| |
+ *                             / __| | | | |_) | |
+ *                            | (__| |_| |  _ <| |___
+ *                             \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
+ *
+ * This software is licensed as described in the file COPYING, which
+ * you should have received as part of this distribution. The terms
+ * are also available at https://curl.se/docs/copyright.html.
+ *
+ * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+ * copies of the Software, and permit persons to whom the Software is
+ * furnished to do so, under the terms of the COPYING file.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ * SPDX-License-Identifier: curl
+ *
+ ***************************************************************************/
+
+#include "curl_setup.h"
+
+#if !defined(CURL_DISABLE_PROXY)
+
+#include <curl/curl.h>
+#include "urldata.h"
+#include "cfilters.h"
+#include "cf-haproxy.h"
+#include "curl_trc.h"
+#include "multiif.h"
+
+/* The last 3 #include files should be in this order */
+#include "curl_printf.h"
+#include "curl_memory.h"
+#include "memdebug.h"
+
+
+typedef enum {
+    HAPROXY_INIT,     /* init/default/no tunnel state */
+    HAPROXY_SEND,     /* data_out being sent */
+    HAPROXY_DONE      /* all work done */
+} haproxy_state;
+
+struct cf_haproxy_ctx {
+  int state;
+  struct dynbuf data_out;
+};
+
+static void cf_haproxy_ctx_reset(struct cf_haproxy_ctx *ctx)
+{
+  DEBUGASSERT(ctx);
+  ctx->state = HAPROXY_INIT;
+  Curl_dyn_reset(&ctx->data_out);
+}
+
+static void cf_haproxy_ctx_free(struct cf_haproxy_ctx *ctx)
+{
+  if(ctx) {
+    Curl_dyn_free(&ctx->data_out);
+    free(ctx);
+  }
+}
+
+static CURLcode cf_haproxy_date_out_set(struct Curl_cfilter*cf,
+                                        struct Curl_easy *data)
+{
+  struct cf_haproxy_ctx *ctx = cf->ctx;
+  CURLcode result;
+  const char *tcp_version;
+  const char *client_ip;
+
+  DEBUGASSERT(ctx);
+  DEBUGASSERT(ctx->state == HAPROXY_INIT);
+#ifdef USE_UNIX_SOCKETS
+  if(cf->conn->unix_domain_socket)
+    /* the buffer is large enough to hold this! */
+    result = Curl_dyn_addn(&ctx->data_out, STRCONST("PROXY UNKNOWN\r\n"));
+  else {
+#endif /* USE_UNIX_SOCKETS */
+  /* Emit the correct prefix for IPv6 */
+  tcp_version = cf->conn->bits.ipv6 ? "TCP6" : "TCP4";
+  if(data->set.str[STRING_HAPROXY_CLIENT_IP])
+    client_ip = data->set.str[STRING_HAPROXY_CLIENT_IP];
+  else
+    client_ip = data->info.conn_local_ip;
+
+  result = Curl_dyn_addf(&ctx->data_out, "PROXY %s %s %s %i %i\r\n",
+                         tcp_version,
+                         client_ip,
+                         data->info.conn_primary_ip,
+                         data->info.conn_local_port,
+                         data->info.conn_primary_port);
+
+#ifdef USE_UNIX_SOCKETS
+  }
+#endif /* USE_UNIX_SOCKETS */
+  return result;
+}
+
+static CURLcode cf_haproxy_connect(struct Curl_cfilter *cf,
+                                   struct Curl_easy *data,
+                                   bool blocking, bool *done)
+{
+  struct cf_haproxy_ctx *ctx = cf->ctx;
+  CURLcode result;
+  size_t len;
+
+  DEBUGASSERT(ctx);
+  if(cf->connected) {
+    *done = TRUE;
+    return CURLE_OK;
+  }
+
+  result = cf->next->cft->do_connect(cf->next, data, blocking, done);
+  if(result || !*done)
+    return result;
+
+  switch(ctx->state) {
+  case HAPROXY_INIT:
+    result = cf_haproxy_date_out_set(cf, data);
+    if(result)
+      goto out;
+    ctx->state = HAPROXY_SEND;
+    /* FALLTHROUGH */
+  case HAPROXY_SEND:
+    len = Curl_dyn_len(&ctx->data_out);
+    if(len > 0) {
+      ssize_t written = Curl_conn_send(data, cf->sockindex,
+                                       Curl_dyn_ptr(&ctx->data_out),
+                                       len, &result);
+      if(written < 0)
+        goto out;
+      Curl_dyn_tail(&ctx->data_out, len - (size_t)written);
+      if(Curl_dyn_len(&ctx->data_out) > 0) {
+        result = CURLE_OK;
+        goto out;
+      }
+    }
+    ctx->state = HAPROXY_DONE;
+    /* FALLTHROUGH */
+  default:
+    Curl_dyn_free(&ctx->data_out);
+    break;
+  }
+
+out:
+  *done = (!result) && (ctx->state == HAPROXY_DONE);
+  cf->connected = *done;
+  return result;
+}
+
+static void cf_haproxy_destroy(struct Curl_cfilter *cf,
+                               struct Curl_easy *data)
+{
+  (void)data;
+  CURL_TRC_CF(data, cf, "destroy");
+  cf_haproxy_ctx_free(cf->ctx);
+}
+
+static void cf_haproxy_close(struct Curl_cfilter *cf,
+                             struct Curl_easy *data)
+{
+  CURL_TRC_CF(data, cf, "close");
+  cf->connected = FALSE;
+  cf_haproxy_ctx_reset(cf->ctx);
+  if(cf->next)
+    cf->next->cft->do_close(cf->next, data);
+}
+
+static int cf_haproxy_get_select_socks(struct Curl_cfilter *cf,
+                                       struct Curl_easy *data,
+                                       curl_socket_t *socks)
+{
+  int fds;
+
+  fds = cf->next->cft->get_select_socks(cf->next, data, socks);
+  if(!fds && cf->next->connected && !cf->connected) {
+    /* If we are not connected, but the filter "below" is
+     * and not waiting on something, we are sending. */
+    socks[0] = Curl_conn_cf_get_socket(cf, data);
+    return GETSOCK_WRITESOCK(0);
+  }
+  return fds;
+}
+
+
+struct Curl_cftype Curl_cft_haproxy = {
+  "HAPROXY",
+  0,
+  0,
+  cf_haproxy_destroy,
+  cf_haproxy_connect,
+  cf_haproxy_close,
+  Curl_cf_def_get_host,
+  cf_haproxy_get_select_socks,
+  Curl_cf_def_data_pending,
+  Curl_cf_def_send,
+  Curl_cf_def_recv,
+  Curl_cf_def_cntrl,
+  Curl_cf_def_conn_is_alive,
+  Curl_cf_def_conn_keep_alive,
+  Curl_cf_def_query,
+};
+
+static CURLcode cf_haproxy_create(struct Curl_cfilter **pcf,
+                                  struct Curl_easy *data)
+{
+  struct Curl_cfilter *cf = NULL;
+  struct cf_haproxy_ctx *ctx;
+  CURLcode result;
+
+  (void)data;
+  ctx = calloc(sizeof(*ctx), 1);
+  if(!ctx) {
+    result = CURLE_OUT_OF_MEMORY;
+    goto out;
+  }
+  ctx->state = HAPROXY_INIT;
+  Curl_dyn_init(&ctx->data_out, DYN_HAXPROXY);
+
+  result = Curl_cf_create(&cf, &Curl_cft_haproxy, ctx);
+  if(result)
+    goto out;
+  ctx = NULL;
+
+out:
+  cf_haproxy_ctx_free(ctx);
+  *pcf = result? NULL : cf;
+  return result;
+}
+
+CURLcode Curl_cf_haproxy_insert_after(struct Curl_cfilter *cf_at,
+                                      struct Curl_easy *data)
+{
+  struct Curl_cfilter *cf;
+  CURLcode result;
+
+  result = cf_haproxy_create(&cf, data);
+  if(result)
+    goto out;
+  Curl_conn_cf_insert_after(cf_at, cf);
+
+out:
+  return result;
+}
+
+#endif /* !CURL_DISABLE_PROXY */
Index: trunk/src/libs/curl-8.3.0/lib/cf-haproxy.h
===================================================================
--- trunk/src/libs/curl-8.3.0/lib/cf-haproxy.h	(revision 101259)
+++ trunk/src/libs/curl-8.3.0/lib/cf-haproxy.h	(revision 101259)
@@ -0,0 +1,39 @@
+#ifndef HEADER_CURL_CF_HAPROXY_H
+#define HEADER_CURL_CF_HAPROXY_H
+/***************************************************************************
+ *                                  _   _ ____  _
+ *  Project                     ___| | | |  _ \| |
+ *                             / __| | | | |_) | |
+ *                            | (__| |_| |  _ <| |___
+ *                             \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
+ *
+ * This software is licensed as described in the file COPYING, which
+ * you should have received as part of this distribution. The terms
+ * are also available at https://curl.se/docs/copyright.html.
+ *
+ * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+ * copies of the Software, and permit persons to whom the Software is
+ * furnished to do so, under the terms of the COPYING file.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ * SPDX-License-Identifier: curl
+ *
+ ***************************************************************************/
+
+#include "curl_setup.h"
+#include "urldata.h"
+
+#if !defined(CURL_DISABLE_PROXY)
+
+CURLcode Curl_cf_haproxy_insert_after(struct Curl_cfilter *cf_at,
+                                      struct Curl_easy *data);
+
+extern struct Curl_cftype Curl_cft_haproxy;
+
+#endif /* !CURL_DISABLE_PROXY */
+
+#endif /* HEADER_CURL_CF_HAPROXY_H */
Index: trunk/src/libs/curl-8.3.0/lib/curl_trc.c
===================================================================
--- trunk/src/libs/curl-8.3.0/lib/curl_trc.c	(revision 101259)
+++ trunk/src/libs/curl-8.3.0/lib/curl_trc.c	(revision 101259)
@@ -0,0 +1,251 @@
+/***************************************************************************
+ *                                  _   _ ____  _
+ *  Project                     ___| | | |  _ \| |
+ *                             / __| | | | |_) | |
+ *                            | (__| |_| |  _ <| |___
+ *                             \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
+ *
+ * This software is licensed as described in the file COPYING, which
+ * you should have received as part of this distribution. The terms
+ * are also available at https://curl.se/docs/copyright.html.
+ *
+ * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+ * copies of the Software, and permit persons to whom the Software is
+ * furnished to do so, under the terms of the COPYING file.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ * SPDX-License-Identifier: curl
+ *
+ ***************************************************************************/
+
+#include "curl_setup.h"
+
+#include <curl/curl.h>
+
+#include "curl_trc.h"
+#include "urldata.h"
+#include "easyif.h"
+#include "cfilters.h"
+#include "timeval.h"
+#include "multiif.h"
+#include "strcase.h"
+
+#include "cf-socket.h"
+#include "connect.h"
+#include "http2.h"
+#include "http_proxy.h"
+#include "cf-h1-proxy.h"
+#include "cf-h2-proxy.h"
+#include "cf-haproxy.h"
+#include "cf-https-connect.h"
+#include "socks.h"
+#include "strtok.h"
+#include "vtls/vtls.h"
+#include "vquic/vquic.h"
+
+/* The last 3 #include files should be in this order */
+#include "curl_printf.h"
+#include "curl_memory.h"
+#include "memdebug.h"
+
+
+void Curl_debug(struct Curl_easy *data, curl_infotype type,
+                char *ptr, size_t size)
+{
+  if(data->set.verbose) {
+    static const char s_infotype[CURLINFO_END][3] = {
+      "* ", "< ", "> ", "{ ", "} ", "{ ", "} " };
+    if(data->set.fdebug) {
+      bool inCallback = Curl_is_in_callback(data);
+      Curl_set_in_callback(data, true);
+      (void)(*data->set.fdebug)(data, type, ptr, size, data->set.debugdata);
+      Curl_set_in_callback(data, inCallback);
+    }
+    else {
+      switch(type) {
+      case CURLINFO_TEXT:
+      case CURLINFO_HEADER_OUT:
+      case CURLINFO_HEADER_IN:
+        fwrite(s_infotype[type], 2, 1, data->set.err);
+        fwrite(ptr, size, 1, data->set.err);
+        break;
+      default: /* nada */
+        break;
+      }
+    }
+  }
+}
+
+
+/* Curl_failf() is for messages stating why we failed.
+ * The message SHALL NOT include any LF or CR.
+ */
+void Curl_failf(struct Curl_easy *data, const char *fmt, ...)
+{
+  DEBUGASSERT(!strchr(fmt, '\n'));
+  if(data->set.verbose || data->set.errorbuffer) {
+    va_list ap;
+    int len;
+    char error[CURL_ERROR_SIZE + 2];
+    va_start(ap, fmt);
+    len = mvsnprintf(error, CURL_ERROR_SIZE, fmt, ap);
+
+    if(data->set.errorbuffer && !data->state.errorbuf) {
+      strcpy(data->set.errorbuffer, error);
+      data->state.errorbuf = TRUE; /* wrote error string */
+    }
+    error[len++] = '\n';
+    error[len] = '\0';
+    Curl_debug(data, CURLINFO_TEXT, error, len);
+    va_end(ap);
+  }
+}
+
+/* Curl_infof() is for info message along the way */
+#define MAXINFO 2048
+
+void Curl_infof(struct Curl_easy *data, const char *fmt, ...)
+{
+  DEBUGASSERT(!strchr(fmt, '\n'));
+  if(data && data->set.verbose) {
+    va_list ap;
+    int len;
+    char buffer[MAXINFO + 2];
+    va_start(ap, fmt);
+    len = mvsnprintf(buffer, MAXINFO, fmt, ap);
+    va_end(ap);
+    buffer[len++] = '\n';
+    buffer[len] = '\0';
+    Curl_debug(data, CURLINFO_TEXT, buffer, len);
+  }
+}
+
+#if !defined(CURL_DISABLE_VERBOSE_STRINGS)
+
+void Curl_trc_cf_infof(struct Curl_easy *data, struct Curl_cfilter *cf,
+                       const char *fmt, ...)
+{
+  DEBUGASSERT(cf);
+  if(data && Curl_trc_cf_is_verbose(cf, data)) {
+    va_list ap;
+    int len;
+    char buffer[MAXINFO + 2];
+    len = msnprintf(buffer, MAXINFO, "[%s] ", cf->cft->name);
+    va_start(ap, fmt);
+    len += mvsnprintf(buffer + len, MAXINFO - len, fmt, ap);
+    va_end(ap);
+    buffer[len++] = '\n';
+    buffer[len] = '\0';
+    Curl_debug(data, CURLINFO_TEXT, buffer, len);
+  }
+}
+
+
+static struct Curl_cftype *cf_types[] = {
+  &Curl_cft_tcp,
+  &Curl_cft_udp,
+  &Curl_cft_unix,
+  &Curl_cft_tcp_accept,
+  &Curl_cft_happy_eyeballs,
+  &Curl_cft_setup,
+#ifdef USE_NGHTTP2
+  &Curl_cft_nghttp2,
+#endif
+#ifdef USE_SSL
+  &Curl_cft_ssl,
+  &Curl_cft_ssl_proxy,
+#endif
+#if !defined(CURL_DISABLE_PROXY)
+#if !defined(CURL_DISABLE_HTTP)
+  &Curl_cft_h1_proxy,
+#ifdef USE_NGHTTP2
+  &Curl_cft_h2_proxy,
+#endif
+  &Curl_cft_http_proxy,
+#endif /* !CURL_DISABLE_HTTP */
+  &Curl_cft_haproxy,
+  &Curl_cft_socks_proxy,
+#endif /* !CURL_DISABLE_PROXY */
+#ifdef ENABLE_QUIC
+  &Curl_cft_http3,
+#endif
+#if !defined(CURL_DISABLE_HTTP) && !defined(USE_HYPER)
+  &Curl_cft_http_connect,
+#endif
+  NULL,
+};
+
+CURLcode Curl_trc_opt(const char *config)
+{
+  char *token, *tok_buf, *tmp;
+  size_t i;
+  int lvl;
+
+  tmp = strdup(config);
+  if(!tmp)
+    return CURLE_OUT_OF_MEMORY;
+
+  token = strtok_r(tmp, ", ", &tok_buf);
+  while(token) {
+    switch(*token) {
+      case '-':
+        lvl = CURL_LOG_LVL_NONE;
+        ++token;
+        break;
+      case '+':
+        lvl = CURL_LOG_LVL_INFO;
+        ++token;
+        break;
+      default:
+        lvl = CURL_LOG_LVL_INFO;
+        break;
+    }
+    for(i = 0; cf_types[i]; ++i) {
+      if(strcasecompare(token, "all")) {
+        cf_types[i]->log_level = lvl;
+      }
+      else if(strcasecompare(token, cf_types[i]->name)) {
+        cf_types[i]->log_level = lvl;
+        break;
+      }
+    }
+    token = strtok_r(NULL, ", ", &tok_buf);
+  }
+  free(tmp);
+  return CURLE_OK;
+}
+
+CURLcode Curl_trc_init(void)
+{
+#ifdef DEBUGBUILD
+  /* WIP: we use the auto-init from an env var only in DEBUG builds for
+   * convenience. */
+  const char *config = getenv("CURL_DEBUG");
+  if(config) {
+    return Curl_trc_opt(config);
+  }
+#endif
+  return CURLE_OK;
+}
+#else /* !CURL_DISABLE_VERBOSE_STRINGS) */
+
+CURLcode Curl_trc_init(void)
+{
+  return CURLE_OK;
+}
+
+#if !defined(__STDC_VERSION__) || (__STDC_VERSION__ < 199901L)
+void Curl_trc_cf_infof(struct Curl_easy *data, struct Curl_cfilter *cf,
+                       const char *fmt, ...)
+{
+  (void)data;
+  (void)cf;
+  (void)fmt;
+}
+#endif
+
+#endif /* !DEBUGBUILD */
Index: trunk/src/libs/curl-8.3.0/lib/curl_trc.h
===================================================================
--- trunk/src/libs/curl-8.3.0/lib/curl_trc.h	(revision 101259)
+++ trunk/src/libs/curl-8.3.0/lib/curl_trc.h	(revision 101259)
@@ -0,0 +1,150 @@
+#ifndef HEADER_CURL_TRC_H
+#define HEADER_CURL_TRC_H
+/***************************************************************************
+ *                                  _   _ ____  _
+ *  Project                     ___| | | |  _ \| |
+ *                             / __| | | | |_) | |
+ *                            | (__| |_| |  _ <| |___
+ *                             \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
+ *
+ * This software is licensed as described in the file COPYING, which
+ * you should have received as part of this distribution. The terms
+ * are also available at https://curl.se/docs/copyright.html.
+ *
+ * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+ * copies of the Software, and permit persons to whom the Software is
+ * furnished to do so, under the terms of the COPYING file.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ * SPDX-License-Identifier: curl
+ *
+ ***************************************************************************/
+
+struct Curl_easy;
+struct Curl_cfilter;
+
+/**
+ * Init logging, return != 0 on failure.
+ */
+CURLcode Curl_trc_init(void);
+
+/**
+ * Configure tracing. May be called several times during global
+ * initialization. Later calls may not take effect.
+ *
+ * Configuration format supported:
+ * - comma-separated list of component names to enable logging on.
+ *   E.g. 'http/2,ssl'. Unknown names are ignored. Names are compared
+ *   case-insensitive.
+ * - component 'all' applies to all known log components
+ * - prefixing a component with '+' or '-' will en-/disable logging for
+ *   that component
+ * Example: 'all,-ssl' would enable logging for all components but the
+ * SSL filters.
+ *
+ * @param config configuration string
+ */
+CURLcode Curl_trc_opt(const char *config);
+
+/* the function used to output verbose information */
+void Curl_debug(struct Curl_easy *data, curl_infotype type,
+                char *ptr, size_t size);
+
+/**
+ * Output an informational message when transfer's verbose logging is enabled.
+ */
+void Curl_infof(struct Curl_easy *data,
+#if defined(__GNUC__) && !defined(printf) &&                    \
+  defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) && \
+  !defined(__MINGW32__)
+                       const char *fmt, ...)
+                       __attribute__((format(printf, 2, 3)));
+#else
+                       const char *fmt, ...);
+#endif
+
+/**
+ * Output a failure message on registered callbacks for transfer.
+ */
+void Curl_failf(struct Curl_easy *data,
+#if defined(__GNUC__) && !defined(printf) &&                    \
+  defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) && \
+  !defined(__MINGW32__)
+                       const char *fmt, ...)
+                       __attribute__((format(printf, 2, 3)));
+#else
+                       const char *fmt, ...);
+#endif
+
+#define failf Curl_failf
+
+/**
+ * Output an informational message when both transfer's verbose logging
+ * and connection filters verbose logging are enabled.
+ */
+void Curl_trc_cf_infof(struct Curl_easy *data, struct Curl_cfilter *cf,
+#if defined(__GNUC__) && !defined(printf) &&                    \
+  defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L) && \
+  !defined(__MINGW32__)
+                       const char *fmt, ...)
+                       __attribute__((format(printf, 3, 4)));
+#else
+                       const char *fmt, ...);
+#endif
+
+#define CURL_LOG_LVL_NONE  0
+#define CURL_LOG_LVL_INFO  1
+
+
+#if !defined(CURL_DISABLE_VERBOSE_STRINGS)
+/* informational messages enabled */
+
+#define Curl_trc_is_verbose(data)    ((data) && (data)->set.verbose)
+#define Curl_trc_cf_is_verbose(cf, data) \
+                            ((data) && (data)->set.verbose && \
+                            (cf) && (cf)->cft->log_level >= CURL_LOG_LVL_INFO)
+
+/* explainer: we have some mix configuration and werror settings
+ * that define HAVE_VARIADIC_MACROS_C99 even though C89 is enforced
+ * on gnuc and some other compiler. Need to treat carefully.
+ */
+#if defined(HAVE_VARIADIC_MACROS_C99) && \
+    defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L)
+
+#define infof(data, ...) \
+  do { if(Curl_trc_is_verbose(data)) \
+         Curl_infof(data, __VA_ARGS__); } while(0)
+#define CURL_TRC_CF(data, cf, ...) \
+  do { if(Curl_trc_cf_is_verbose(cf, data)) \
+         Curl_trc_cf_infof(data, cf, __VA_ARGS__); } while(0)
+
+#else /* no variadic macro args */
+#define infof Curl_infof
+#define CURL_TRC_CF Curl_trc_cf_infof
+#endif /* variadic macro args */
+
+#else /* !CURL_DISABLE_VERBOSE_STRINGS */
+/* All informational messages are not compiled in for size savings */
+
+#define Curl_trc_is_verbose(d)        ((void)(d), FALSE)
+#define Curl_trc_cf_is_verbose(x,y)   ((void)(x), (void)(y), FALSE)
+
+#if defined(HAVE_VARIADIC_MACROS_C99)
+#define infof(...)  Curl_nop_stmt
+#define CURL_TRC_CF(...)  Curl_nop_stmt
+#define Curl_trc_cf_infof(...)  Curl_nop_stmt
+#elif defined(HAVE_VARIADIC_MACROS_GCC)
+#define infof(x...)  Curl_nop_stmt
+#define CURL_TRC_CF(x...)  Curl_nop_stmt
+#define Curl_trc_cf_infof(x...)  Curl_nop_stmt
+#else
+#error "missing VARIADIC macro define, fix and rebuild!"
+#endif
+
+#endif /* CURL_DISABLE_VERBOSE_STRINGS */
+
+#endif /* HEADER_CURL_TRC_H */
Index: trunk/src/libs/curl-8.3.0/lib/dynhds.c
===================================================================
--- trunk/src/libs/curl-8.3.0/lib/dynhds.c	(revision 101259)
+++ trunk/src/libs/curl-8.3.0/lib/dynhds.c	(revision 101259)
@@ -0,0 +1,366 @@
+/***************************************************************************
+ *                                  _   _ ____  _
+ *  Project                     ___| | | |  _ \| |
+ *                             / __| | | | |_) | |
+ *                            | (__| |_| |  _ <| |___
+ *                             \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
+ *
+ * This software is licensed as described in the file COPYING, which
+ * you should have received as part of this distribution. The terms
+ * are also available at https://curl.se/docs/copyright.html.
+ *
+ * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+ * copies of the Software, and permit persons to whom the Software is
+ * furnished to do so, under the terms of the COPYING file.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ * SPDX-License-Identifier: curl
+ *
+ ***************************************************************************/
+
+#include "curl_setup.h"
+#include "dynhds.h"
+#include "strcase.h"
+
+/* The last 3 #include files should be in this order */
+#include "curl_printf.h"
+#include "curl_memory.h"
+#include "memdebug.h"
+
+
+static struct dynhds_entry *
+entry_new(const char *name, size_t namelen,
+          const char *value, size_t valuelen, int opts)
+{
+  struct dynhds_entry *e;
+  char *p;
+
+  DEBUGASSERT(name);
+  DEBUGASSERT(value);
+  e = calloc(1, sizeof(*e) + namelen + valuelen + 2);
+  if(!e)
+    return NULL;
+  e->name = p = ((char *)e) + sizeof(*e);
+  memcpy(p, name, namelen);
+  e->namelen = namelen;
+  e->value = p += namelen + 1; /* leave a \0 at the end of name */
+  memcpy(p, value, valuelen);
+  e->valuelen = valuelen;
+  if(opts & DYNHDS_OPT_LOWERCASE)
+    Curl_strntolower(e->name, e->name, e->namelen);
+  return e;
+}
+
+static struct dynhds_entry *
+entry_append(struct dynhds_entry *e,
+             const char *value, size_t valuelen)
+{
+  struct dynhds_entry *e2;
+  size_t valuelen2 = e->valuelen + 1 + valuelen;
+  char *p;
+
+  DEBUGASSERT(value);
+  e2 = calloc(1, sizeof(*e) + e->namelen + valuelen2 + 2);
+  if(!e2)
+    return NULL;
+  e2->name = p = ((char *)e2) + sizeof(*e2);
+  memcpy(p, e->name, e->namelen);
+  e2->namelen = e->namelen;
+  e2->value = p += e->namelen + 1; /* leave a \0 at the end of name */
+  memcpy(p, e->value, e->valuelen);
+  p += e->valuelen;
+  p[0] = ' ';
+  memcpy(p + 1, value, valuelen);
+  e2->valuelen = valuelen2;
+  return e2;
+}
+
+static void entry_free(struct dynhds_entry *e)
+{
+  free(e);
+}
+
+void Curl_dynhds_init(struct dynhds *dynhds, size_t max_entries,
+                      size_t max_strs_size)
+{
+  DEBUGASSERT(dynhds);
+  DEBUGASSERT(max_strs_size);
+  dynhds->hds = NULL;
+  dynhds->hds_len = dynhds->hds_allc = dynhds->strs_len = 0;
+  dynhds->max_entries = max_entries;
+  dynhds->max_strs_size = max_strs_size;
+  dynhds->opts = 0;
+}
+
+void Curl_dynhds_free(struct dynhds *dynhds)
+{
+  DEBUGASSERT(dynhds);
+  if(dynhds->hds && dynhds->hds_len) {
+    size_t i;
+    DEBUGASSERT(dynhds->hds);
+    for(i = 0; i < dynhds->hds_len; ++i) {
+      entry_free(dynhds->hds[i]);
+    }
+  }
+  Curl_safefree(dynhds->hds);
+  dynhds->hds_len = dynhds->hds_allc = dynhds->strs_len = 0;
+}
+
+void Curl_dynhds_reset(struct dynhds *dynhds)
+{
+  DEBUGASSERT(dynhds);
+  if(dynhds->hds_len) {
+    size_t i;
+    DEBUGASSERT(dynhds->hds);
+    for(i = 0; i < dynhds->hds_len; ++i) {
+      entry_free(dynhds->hds[i]);
+      dynhds->hds[i] = NULL;
+    }
+  }
+  dynhds->hds_len = dynhds->strs_len = 0;
+}
+
+size_t Curl_dynhds_count(struct dynhds *dynhds)
+{
+  return dynhds->hds_len;
+}
+
+void Curl_dynhds_set_opts(struct dynhds *dynhds, int opts)
+{
+  dynhds->opts = opts;
+}
+
+struct dynhds_entry *Curl_dynhds_getn(struct dynhds *dynhds, size_t n)
+{
+  DEBUGASSERT(dynhds);
+  return (n < dynhds->hds_len)? dynhds->hds[n] : NULL;
+}
+
+struct dynhds_entry *Curl_dynhds_get(struct dynhds *dynhds, const char *name,
+                                     size_t namelen)
+{
+  size_t i;
+  for(i = 0; i < dynhds->hds_len; ++i) {
+    if(dynhds->hds[i]->namelen == namelen &&
+       strncasecompare(dynhds->hds[i]->name, name, namelen)) {
+      return dynhds->hds[i];
+    }
+  }
+  return NULL;
+}
+
+struct dynhds_entry *Curl_dynhds_cget(struct dynhds *dynhds, const char *name)
+{
+  return Curl_dynhds_get(dynhds, name, strlen(name));
+}
+
+CURLcode Curl_dynhds_add(struct dynhds *dynhds,
+                         const char *name, size_t namelen,
+                         const char *value, size_t valuelen)
+{
+  struct dynhds_entry *entry = NULL;
+  CURLcode result = CURLE_OUT_OF_MEMORY;
+
+  DEBUGASSERT(dynhds);
+  if(dynhds->max_entries && dynhds->hds_len >= dynhds->max_entries)
+    return CURLE_OUT_OF_MEMORY;
+  if(dynhds->strs_len + namelen + valuelen > dynhds->max_strs_size)
+    return CURLE_OUT_OF_MEMORY;
+
+entry = entry_new(name, namelen, value, valuelen, dynhds->opts);
+  if(!entry)
+    goto out;
+
+  if(dynhds->hds_len + 1 >= dynhds->hds_allc) {
+    size_t nallc = dynhds->hds_len + 16;
+    struct dynhds_entry **nhds;
+
+    if(dynhds->max_entries && nallc > dynhds->max_entries)
+      nallc = dynhds->max_entries;
+
+    nhds = calloc(nallc, sizeof(struct dynhds_entry *));
+    if(!nhds)
+      goto out;
+    if(dynhds->hds) {
+      memcpy(nhds, dynhds->hds,
+             dynhds->hds_len * sizeof(struct dynhds_entry *));
+      Curl_safefree(dynhds->hds);
+    }
+    dynhds->hds = nhds;
+    dynhds->hds_allc = nallc;
+  }
+  dynhds->hds[dynhds->hds_len++] = entry;
+  entry = NULL;
+  dynhds->strs_len += namelen + valuelen;
+  result = CURLE_OK;
+
+out:
+  if(entry)
+    entry_free(entry);
+  return result;
+}
+
+CURLcode Curl_dynhds_cadd(struct dynhds *dynhds,
+                          const char *name, const char *value)
+{
+  return Curl_dynhds_add(dynhds, name, strlen(name), value, strlen(value));
+}
+
+CURLcode Curl_dynhds_h1_add_line(struct dynhds *dynhds,
+                                 const char *line, size_t line_len)
+{
+  const char *p;
+  const char *name;
+  size_t namelen;
+  const char *value;
+  size_t valuelen, i;
+
+  if(!line || !line_len)
+    return CURLE_OK;
+
+  if((line[0] == ' ') || (line[0] == '\t')) {
+    struct dynhds_entry *e, *e2;
+    /* header continuation, yikes! */
+    if(!dynhds->hds_len)
+      return CURLE_BAD_FUNCTION_ARGUMENT;
+
+    while(line_len && ISBLANK(line[0])) {
+      ++line;
+      --line_len;
+    }
+    if(!line_len)
+      return CURLE_BAD_FUNCTION_ARGUMENT;
+    e = dynhds->hds[dynhds->hds_len-1];
+    e2 = entry_append(e, line, line_len);
+    if(!e2)
+      return CURLE_OUT_OF_MEMORY;
+    dynhds->hds[dynhds->hds_len-1] = e2;
+    entry_free(e);
+    return CURLE_OK;
+  }
+  else {
+    p = memchr(line, ':', line_len);
+    if(!p)
+      return CURLE_BAD_FUNCTION_ARGUMENT;
+    name = line;
+    namelen = p - line;
+    p++; /* move past the colon */
+    for(i = namelen + 1; i < line_len; ++i, ++p) {
+      if(!ISBLANK(*p))
+        break;
+    }
+    value = p;
+    valuelen = line_len - i;
+
+    p = memchr(value, '\r', valuelen);
+    if(!p)
+      p = memchr(value, '\n', valuelen);
+    if(p)
+      valuelen = (size_t)(p - value);
+
+    return Curl_dynhds_add(dynhds, name, namelen, value, valuelen);
+  }
+}
+
+CURLcode Curl_dynhds_h1_cadd_line(struct dynhds *dynhds, const char *line)
+{
+  return Curl_dynhds_h1_add_line(dynhds, line, line? strlen(line) : 0);
+}
+
+#ifdef DEBUGBUILD
+/* used by unit2602.c */
+
+bool Curl_dynhds_contains(struct dynhds *dynhds,
+                          const char *name, size_t namelen)
+{
+  return !!Curl_dynhds_get(dynhds, name, namelen);
+}
+
+bool Curl_dynhds_ccontains(struct dynhds *dynhds, const char *name)
+{
+  return Curl_dynhds_contains(dynhds, name, strlen(name));
+}
+
+size_t Curl_dynhds_count_name(struct dynhds *dynhds,
+                              const char *name, size_t namelen)
+{
+  size_t n = 0;
+  if(dynhds->hds_len) {
+    size_t i;
+    for(i = 0; i < dynhds->hds_len; ++i) {
+      if((namelen == dynhds->hds[i]->namelen) &&
+         strncasecompare(name, dynhds->hds[i]->name, namelen))
+        ++n;
+    }
+  }
+  return n;
+}
+
+size_t Curl_dynhds_ccount_name(struct dynhds *dynhds, const char *name)
+{
+  return Curl_dynhds_count_name(dynhds, name, strlen(name));
+}
+
+CURLcode Curl_dynhds_set(struct dynhds *dynhds,
+                         const char *name, size_t namelen,
+                         const char *value, size_t valuelen)
+{
+  Curl_dynhds_remove(dynhds, name, namelen);
+  return Curl_dynhds_add(dynhds, name, namelen, value, valuelen);
+}
+
+size_t Curl_dynhds_remove(struct dynhds *dynhds,
+                          const char *name, size_t namelen)
+{
+  size_t n = 0;
+  if(dynhds->hds_len) {
+    size_t i, len;
+    for(i = 0; i < dynhds->hds_len; ++i) {
+      if((namelen == dynhds->hds[i]->namelen) &&
+         strncasecompare(name, dynhds->hds[i]->name, namelen)) {
+        ++n;
+        --dynhds->hds_len;
+        dynhds->strs_len -= (dynhds->hds[i]->namelen +
+                             dynhds->hds[i]->valuelen);
+        entry_free(dynhds->hds[i]);
+        len = dynhds->hds_len - i; /* remaining entries */
+        if(len) {
+          memmove(&dynhds->hds[i], &dynhds->hds[i + 1],
+                  len * sizeof(dynhds->hds[i]));
+        }
+        --i; /* do this index again */
+      }
+    }
+  }
+  return n;
+}
+
+size_t Curl_dynhds_cremove(struct dynhds *dynhds, const char *name)
+{
+  return Curl_dynhds_remove(dynhds, name, strlen(name));
+}
+
+CURLcode Curl_dynhds_h1_dprint(struct dynhds *dynhds, struct dynbuf *dbuf)
+{
+  CURLcode result = CURLE_OK;
+  size_t i;
+
+  if(!dynhds->hds_len)
+    return result;
+
+  for(i = 0; i < dynhds->hds_len; ++i) {
+    result = Curl_dyn_addf(dbuf, "%.*s: %.*s\r\n",
+               (int)dynhds->hds[i]->namelen, dynhds->hds[i]->name,
+               (int)dynhds->hds[i]->valuelen, dynhds->hds[i]->value);
+    if(result)
+      break;
+  }
+
+  return result;
+}
+
+#endif
Index: trunk/src/libs/curl-8.3.0/lib/dynhds.h
===================================================================
--- trunk/src/libs/curl-8.3.0/lib/dynhds.h	(revision 101259)
+++ trunk/src/libs/curl-8.3.0/lib/dynhds.h	(revision 101259)
@@ -0,0 +1,174 @@
+#ifndef HEADER_CURL_DYNHDS_H
+#define HEADER_CURL_DYNHDS_H
+/***************************************************************************
+ *                                  _   _ ____  _
+ *  Project                     ___| | | |  _ \| |
+ *                             / __| | | | |_) | |
+ *                            | (__| |_| |  _ <| |___
+ *                             \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
+ *
+ * This software is licensed as described in the file COPYING, which
+ * you should have received as part of this distribution. The terms
+ * are also available at https://curl.se/docs/copyright.html.
+ *
+ * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+ * copies of the Software, and permit persons to whom the Software is
+ * furnished to do so, under the terms of the COPYING file.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ * SPDX-License-Identifier: curl
+ *
+ ***************************************************************************/
+#include "curl_setup.h"
+
+#include <curl/curl.h>
+#include "dynbuf.h"
+
+struct dynbuf;
+
+/**
+ * A single header entry.
+ * `name` and `value` are non-NULL and always NUL terminated.
+ */
+struct dynhds_entry {
+  char *name;
+  char *value;
+  size_t namelen;
+  size_t valuelen;
+};
+
+struct dynhds {
+  struct dynhds_entry **hds;
+  size_t hds_len;   /* number of entries in hds */
+  size_t hds_allc;  /* size of hds allocation */
+  size_t max_entries;   /* size limit number of entries */
+  size_t strs_len; /* length of all strings */
+  size_t max_strs_size; /* max length of all strings */
+  int opts;
+};
+
+#define DYNHDS_OPT_NONE          (0)
+#define DYNHDS_OPT_LOWERCASE     (1 << 0)
+
+/**
+ * Init for use on first time or after a reset.
+ * Allow `max_entries` headers to be added, 0 for unlimited.
+ * Allow size of all name and values added to not exceed `max_strs_size``
+ */
+void Curl_dynhds_init(struct dynhds *dynhds, size_t max_entries,
+                      size_t max_strs_size);
+/**
+ * Frees all data held in `dynhds`, but not the struct itself.
+ */
+void Curl_dynhds_free(struct dynhds *dynhds);
+
+/**
+ * Reset `dyndns` to the initial init state. May keep allocations
+ * around.
+ */
+void Curl_dynhds_reset(struct dynhds *dynhds);
+
+/**
+ * Return the number of header entries.
+ */
+size_t Curl_dynhds_count(struct dynhds *dynhds);
+
+/**
+ * Set the options to use, replacing any existing ones.
+ * This will not have an effect on already existing headers.
+ */
+void Curl_dynhds_set_opts(struct dynhds *dynhds, int opts);
+
+/**
+ * Return the n-th header entry or NULL if it does not exist.
+ */
+struct dynhds_entry *Curl_dynhds_getn(struct dynhds *dynhds, size_t n);
+
+/**
+ * Return the 1st header entry of the name or NULL if none exists.
+ */
+struct dynhds_entry *Curl_dynhds_get(struct dynhds *dynhds,
+                                     const char *name, size_t namelen);
+struct dynhds_entry *Curl_dynhds_cget(struct dynhds *dynhds, const char *name);
+
+/**
+ * Return TRUE iff one or more headers with the given name exist.
+ */
+bool Curl_dynhds_contains(struct dynhds *dynhds,
+                          const char *name, size_t namelen);
+bool Curl_dynhds_ccontains(struct dynhds *dynhds, const char *name);
+
+/**
+ * Return how often the given name appears in `dynhds`.
+ * Names are case-insensitive.
+ */
+size_t Curl_dynhds_count_name(struct dynhds *dynhds,
+                              const char *name, size_t namelen);
+
+/**
+ * Return how often the given 0-terminated name appears in `dynhds`.
+ * Names are case-insensitive.
+ */
+size_t Curl_dynhds_ccount_name(struct dynhds *dynhds, const char *name);
+
+/**
+ * Add a header, name + value, to `dynhds` at the end. Does *not*
+ * check for duplicate names.
+ */
+CURLcode Curl_dynhds_add(struct dynhds *dynhds,
+                         const char *name, size_t namelen,
+                         const char *value, size_t valuelen);
+
+/**
+ * Add a header, c-string name + value, to `dynhds` at the end.
+ */
+CURLcode Curl_dynhds_cadd(struct dynhds *dynhds,
+                          const char *name, const char *value);
+
+/**
+ * Remove all entries with the given name.
+ * Returns number of entries removed.
+ */
+size_t Curl_dynhds_remove(struct dynhds *dynhds,
+                          const char *name, size_t namelen);
+size_t Curl_dynhds_cremove(struct dynhds *dynhds, const char *name);
+
+
+/**
+ * Set the give header name and value, replacing any entries with
+ * the same name. The header is added at the end of all (remaining)
+ * entries.
+ */
+CURLcode Curl_dynhds_set(struct dynhds *dynhds,
+                         const char *name, size_t namelen,
+                         const char *value, size_t valuelen);
+
+CURLcode Curl_dynhds_cset(struct dynhds *dynhds,
+                          const char *name, const char *value);
+
+/**
+ * Add a single header from a HTTP/1.1 formatted line at the end. Line
+ * may contain a delimiting \r\n or just \n. Any characters after
+ * that will be ignored.
+ */
+CURLcode Curl_dynhds_h1_cadd_line(struct dynhds *dynhds, const char *line);
+
+/**
+ * Add a single header from a HTTP/1.1 formatted line at the end. Line
+ * may contain a delimiting \r\n or just \n. Any characters after
+ * that will be ignored.
+ */
+CURLcode Curl_dynhds_h1_add_line(struct dynhds *dynhds,
+                                 const char *line, size_t line_len);
+
+/**
+ * Add the headers to the given `dynbuf` in HTTP/1.1 format with
+ * cr+lf line endings. Will NOT output a last empty line.
+ */
+CURLcode Curl_dynhds_h1_dprint(struct dynhds *dynhds, struct dynbuf *dbuf);
+
+#endif /* HEADER_CURL_DYNHDS_H */
Index: trunk/src/libs/curl-8.3.0/lib/http1.c
===================================================================
--- trunk/src/libs/curl-8.3.0/lib/http1.c	(revision 101259)
+++ trunk/src/libs/curl-8.3.0/lib/http1.c	(revision 101259)
@@ -0,0 +1,322 @@
+/***************************************************************************
+ *                                  _   _ ____  _
+ *  Project                     ___| | | |  _ \| |
+ *                             / __| | | | |_) | |
+ *                            | (__| |_| |  _ <| |___
+ *                             \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
+ *
+ * This software is licensed as described in the file COPYING, which
+ * you should have received as part of this distribution. The terms
+ * are also available at https://curl.se/docs/copyright.html.
+ *
+ * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+ * copies of the Software, and permit persons to whom the Software is
+ * furnished to do so, under the terms of the COPYING file.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ * SPDX-License-Identifier: curl
+ *
+ ***************************************************************************/
+
+#include "curl_setup.h"
+
+#ifndef CURL_DISABLE_HTTP
+
+#include "urldata.h"
+#include <curl/curl.h>
+#include "http.h"
+#include "http1.h"
+#include "urlapi-int.h"
+
+/* The last 3 #include files should be in this order */
+#include "curl_printf.h"
+#include "curl_memory.h"
+#include "memdebug.h"
+
+
+#define H1_MAX_URL_LEN   (8*1024)
+
+void Curl_h1_req_parse_init(struct h1_req_parser *parser, size_t max_line_len)
+{
+  memset(parser, 0, sizeof(*parser));
+  parser->max_line_len = max_line_len;
+  Curl_dyn_init(&parser->scratch, max_line_len);
+}
+
+void Curl_h1_req_parse_free(struct h1_req_parser *parser)
+{
+  if(parser) {
+    Curl_http_req_free(parser->req);
+    Curl_dyn_free(&parser->scratch);
+    parser->req = NULL;
+    parser->done = FALSE;
+  }
+}
+
+static CURLcode trim_line(struct h1_req_parser *parser, int options)
+{
+  DEBUGASSERT(parser->line);
+  if(parser->line_len) {
+    if(parser->line[parser->line_len - 1] == '\n')
+      --parser->line_len;
+    if(parser->line_len) {
+      if(parser->line[parser->line_len - 1] == '\r')
+        --parser->line_len;
+      else if(options & H1_PARSE_OPT_STRICT)
+        return CURLE_URL_MALFORMAT;
+    }
+    else if(options & H1_PARSE_OPT_STRICT)
+      return CURLE_URL_MALFORMAT;
+  }
+  else if(options & H1_PARSE_OPT_STRICT)
+    return CURLE_URL_MALFORMAT;
+
+  if(parser->line_len > parser->max_line_len) {
+    return CURLE_URL_MALFORMAT;
+  }
+  return CURLE_OK;
+}
+
+static ssize_t detect_line(struct h1_req_parser *parser,
+                           const char *buf, const size_t buflen,
+                           CURLcode *err)
+{
+  const char  *line_end;
+
+  DEBUGASSERT(!parser->line);
+  line_end = memchr(buf, '\n', buflen);
+  if(!line_end) {
+    *err = CURLE_AGAIN;
+    return -1;
+  }
+  parser->line = buf;
+  parser->line_len = line_end - buf + 1;
+  *err = CURLE_OK;
+  return (ssize_t)parser->line_len;
+}
+
+static ssize_t next_line(struct h1_req_parser *parser,
+                         const char *buf, const size_t buflen, int options,
+                         CURLcode *err)
+{
+  ssize_t nread = 0;
+
+  if(parser->line) {
+    parser->line = NULL;
+    parser->line_len = 0;
+    Curl_dyn_reset(&parser->scratch);
+  }
+
+  nread = detect_line(parser, buf, buflen, err);
+  if(nread >= 0) {
+    if(Curl_dyn_len(&parser->scratch)) {
+      /* append detected line to scratch to have the complete line */
+      *err = Curl_dyn_addn(&parser->scratch, parser->line, parser->line_len);
+      if(*err)
+        return -1;
+      parser->line = Curl_dyn_ptr(&parser->scratch);
+      parser->line_len = Curl_dyn_len(&parser->scratch);
+    }
+    *err = trim_line(parser, options);
+    if(*err)
+      return -1;
+  }
+  else if(*err == CURLE_AGAIN) {
+    /* no line end in `buf`, add it to our scratch */
+    *err = Curl_dyn_addn(&parser->scratch, (const unsigned char *)buf, buflen);
+    nread = (*err)? -1 : (ssize_t)buflen;
+  }
+  return nread;
+}
+
+static CURLcode start_req(struct h1_req_parser *parser,
+                          const char *scheme_default, int options)
+{
+  const char  *p, *m, *target, *hv, *scheme, *authority, *path;
+  size_t m_len, target_len, hv_len, scheme_len, authority_len, path_len;
+  size_t i;
+  CURLU *url = NULL;
+  CURLcode result = CURLE_URL_MALFORMAT; /* Use this as default fail */
+
+  DEBUGASSERT(!parser->req);
+  /* line must match: "METHOD TARGET HTTP_VERSION" */
+  p = memchr(parser->line, ' ', parser->line_len);
+  if(!p || p == parser->line)
+    goto out;
+
+  m = parser->line;
+  m_len = p - parser->line;
+  target = p + 1;
+  target_len = hv_len = 0;
+  hv = NULL;
+
+  /* URL may contain spaces so scan backwards */
+  for(i = parser->line_len; i > m_len; --i) {
+    if(parser->line[i] == ' ') {
+      hv = &parser->line[i + 1];
+      hv_len = parser->line_len - i;
+      target_len = (hv - target) - 1;
+      break;
+    }
+  }
+  /* no SPACE found or empty TARGET or empty HTTP_VERSION */
+  if(!target_len || !hv_len)
+    goto out;
+
+  /* TODO: we do not check HTTP_VERSION for conformity, should
+   + do that when STRICT option is supplied. */
+  (void)hv;
+
+  /* The TARGET can be (rfc 9112, ch. 3.2):
+   * origin-form:     path + optional query
+   * absolute-form:   absolute URI
+   * authority-form:  host+port for CONNECT
+   * asterisk-form:   '*' for OPTIONS
+   *
+   * from TARGET, we derive `scheme` `authority` `path`
+   * origin-form            --        --          TARGET
+   * absolute-form          URL*      URL*        URL*
+   * authority-form         --        TARGET      --
+   * asterisk-form          --        --          TARGET
+   */
+  scheme = authority = path = NULL;
+  scheme_len = authority_len = path_len = 0;
+
+  if(target_len == 1 && target[0] == '*') {
+    /* asterisk-form */
+    path = target;
+    path_len = target_len;
+  }
+  else if(!strncmp("CONNECT", m, m_len)) {
+    /* authority-form */
+    authority = target;
+    authority_len = target_len;
+  }
+  else if(target[0] == '/') {
+    /* origin-form */
+    path = target;
+    path_len = target_len;
+  }
+  else {
+    /* origin-form OR absolute-form */
+    CURLUcode uc;
+    char tmp[H1_MAX_URL_LEN];
+
+    /* default, unless we see an absolute URL */
+    path = target;
+    path_len = target_len;
+
+    /* URL parser wants 0-termination */
+    if(target_len >= sizeof(tmp))
+      goto out;
+    memcpy(tmp, target, target_len);
+    tmp[target_len] = '\0';
+    /* See if treating TARGET as an absolute URL makes sense */
+    if(Curl_is_absolute_url(tmp, NULL, 0, FALSE)) {
+      int url_options;
+
+      url = curl_url();
+      if(!url) {
+        result = CURLE_OUT_OF_MEMORY;
+        goto out;
+      }
+      url_options = (CURLU_NON_SUPPORT_SCHEME|
+                     CURLU_PATH_AS_IS|
+                     CURLU_NO_DEFAULT_PORT);
+      if(!(options & H1_PARSE_OPT_STRICT))
+        url_options |= CURLU_ALLOW_SPACE;
+      uc = curl_url_set(url, CURLUPART_URL, tmp, url_options);
+      if(uc) {
+        goto out;
+      }
+    }
+
+    if(!url && (options & H1_PARSE_OPT_STRICT)) {
+      /* we should have an absolute URL or have seen `/` earlier */
+      goto out;
+    }
+  }
+
+  if(url) {
+    result = Curl_http_req_make2(&parser->req, m, m_len, url, scheme_default);
+  }
+  else {
+    if(!scheme && scheme_default) {
+      scheme = scheme_default;
+      scheme_len = strlen(scheme_default);
+    }
+    result = Curl_http_req_make(&parser->req, m, m_len, scheme, scheme_len,
+                                authority, authority_len, path, path_len);
+  }
+
+out:
+  curl_url_cleanup(url);
+  return result;
+}
+
+ssize_t Curl_h1_req_parse_read(struct h1_req_parser *parser,
+                               const char *buf, size_t buflen,
+                               const char *scheme_default, int options,
+                               CURLcode *err)
+{
+  ssize_t nread = 0, n;
+
+  *err = CURLE_OK;
+  while(!parser->done) {
+    n = next_line(parser, buf, buflen, options, err);
+    if(n < 0) {
+      if(*err != CURLE_AGAIN) {
+        nread = -1;
+      }
+      *err = CURLE_OK;
+      goto out;
+    }
+
+    /* Consume this line */
+    nread += (size_t)n;
+    buf += (size_t)n;
+    buflen -= (size_t)n;
+
+    if(!parser->line) {
+      /* consumed bytes, but line not complete */
+      if(!buflen)
+        goto out;
+    }
+    else if(!parser->req) {
+      *err = start_req(parser, scheme_default, options);
+      if(*err) {
+        nread = -1;
+        goto out;
+      }
+    }
+    else if(parser->line_len == 0) {
+      /* last, empty line, we are finished */
+      if(!parser->req) {
+        *err = CURLE_URL_MALFORMAT;
+        nread = -1;
+        goto out;
+      }
+      parser->done = TRUE;
+      Curl_dyn_reset(&parser->scratch);
+      /* last chance adjustments */
+    }
+    else {
+      *err = Curl_dynhds_h1_add_line(&parser->req->headers,
+                                     parser->line, parser->line_len);
+      if(*err) {
+        nread = -1;
+        goto out;
+      }
+    }
+  }
+
+out:
+  return nread;
+}
+
+
+#endif /* !CURL_DISABLE_HTTP */
Index: trunk/src/libs/curl-8.3.0/lib/http1.h
===================================================================
--- trunk/src/libs/curl-8.3.0/lib/http1.h	(revision 101259)
+++ trunk/src/libs/curl-8.3.0/lib/http1.h	(revision 101259)
@@ -0,0 +1,61 @@
+#ifndef HEADER_CURL_HTTP1_H
+#define HEADER_CURL_HTTP1_H
+/***************************************************************************
+ *                                  _   _ ____  _
+ *  Project                     ___| | | |  _ \| |
+ *                             / __| | | | |_) | |
+ *                            | (__| |_| |  _ <| |___
+ *                             \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
+ *
+ * This software is licensed as described in the file COPYING, which
+ * you should have received as part of this distribution. The terms
+ * are also available at https://curl.se/docs/copyright.html.
+ *
+ * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+ * copies of the Software, and permit persons to whom the Software is
+ * furnished to do so, under the terms of the COPYING file.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ * SPDX-License-Identifier: curl
+ *
+ ***************************************************************************/
+
+#include "curl_setup.h"
+
+#ifndef CURL_DISABLE_HTTP
+#include "bufq.h"
+#include "http.h"
+
+#define H1_PARSE_OPT_NONE       (0)
+#define H1_PARSE_OPT_STRICT     (1 << 0)
+
+#define H1_PARSE_DEFAULT_MAX_LINE_LEN   DYN_HTTP_REQUEST
+
+struct h1_req_parser {
+  struct httpreq *req;
+  struct dynbuf scratch;
+  size_t scratch_skip;
+  const char *line;
+  size_t max_line_len;
+  size_t line_len;
+  bool done;
+};
+
+void Curl_h1_req_parse_init(struct h1_req_parser *parser, size_t max_line_len);
+void Curl_h1_req_parse_free(struct h1_req_parser *parser);
+
+ssize_t Curl_h1_req_parse_read(struct h1_req_parser *parser,
+                               const char *buf, size_t buflen,
+                               const char *scheme_default, int options,
+                               CURLcode *err);
+
+CURLcode Curl_h1_req_dprint(const struct httpreq *req,
+                            struct dynbuf *dbuf);
+
+
+#endif /* !CURL_DISABLE_HTTP */
+#endif /* HEADER_CURL_HTTP1_H */
Index: trunk/src/libs/curl-8.3.0/lib/macos.c
===================================================================
--- trunk/src/libs/curl-8.3.0/lib/macos.c	(revision 101259)
+++ trunk/src/libs/curl-8.3.0/lib/macos.c	(revision 101259)
@@ -0,0 +1,55 @@
+/***************************************************************************
+ *                                  _   _ ____  _
+ *  Project                     ___| | | |  _ \| |
+ *                             / __| | | | |_) | |
+ *                            | (__| |_| |  _ <| |___
+ *                             \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
+ *
+ * This software is licensed as described in the file COPYING, which
+ * you should have received as part of this distribution. The terms
+ * are also available at https://curl.se/docs/copyright.html.
+ *
+ * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+ * copies of the Software, and permit persons to whom the Software is
+ * furnished to do so, under the terms of the COPYING file.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ * SPDX-License-Identifier: curl
+ *
+ ***************************************************************************/
+
+#include "curl_setup.h"
+
+#ifdef CURL_MACOS_CALL_COPYPROXIES
+
+#include <curl/curl.h>
+
+#include "macos.h"
+
+#include <SystemConfiguration/SCDynamicStoreCopySpecific.h>
+
+CURLcode Curl_macos_init(void)
+{
+  {
+    /*
+     * The automagic conversion from IPv4 literals to IPv6 literals only
+     * works if the SCDynamicStoreCopyProxies system function gets called
+     * first. As Curl currently doesn't support system-wide HTTP proxies, we
+     * therefore don't use any value this function might return.
+     *
+     * This function is only available on macOS and is not needed for
+     * IPv4-only builds, hence the conditions for defining
+     * CURL_MACOS_CALL_COPYPROXIES in curl_setup.h.
+     */
+    CFDictionaryRef dict = SCDynamicStoreCopyProxies(NULL);
+    if(dict)
+      CFRelease(dict);
+  }
+  return CURLE_OK;
+}
+
+#endif
Index: trunk/src/libs/curl-8.3.0/lib/macos.h
===================================================================
--- trunk/src/libs/curl-8.3.0/lib/macos.h	(revision 101259)
+++ trunk/src/libs/curl-8.3.0/lib/macos.h	(revision 101259)
@@ -0,0 +1,39 @@
+#ifndef HEADER_CURL_MACOS_H
+#define HEADER_CURL_MACOS_H
+/***************************************************************************
+ *                                  _   _ ____  _
+ *  Project                     ___| | | |  _ \| |
+ *                             / __| | | | |_) | |
+ *                            | (__| |_| |  _ <| |___
+ *                             \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
+ *
+ * This software is licensed as described in the file COPYING, which
+ * you should have received as part of this distribution. The terms
+ * are also available at https://curl.se/docs/copyright.html.
+ *
+ * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+ * copies of the Software, and permit persons to whom the Software is
+ * furnished to do so, under the terms of the COPYING file.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ * SPDX-License-Identifier: curl
+ *
+ ***************************************************************************/
+
+#include "curl_setup.h"
+
+#ifdef CURL_MACOS_CALL_COPYPROXIES
+
+CURLcode Curl_macos_init(void);
+
+#else
+
+#define Curl_macos_init() CURLE_OK
+
+#endif
+
+#endif /* HEADER_CURL_MACOS_H */
Index: trunk/src/libs/curl-8.3.0/lib/vtls/schannel_int.h
===================================================================
--- trunk/src/libs/curl-8.3.0/lib/vtls/schannel_int.h	(revision 101259)
+++ trunk/src/libs/curl-8.3.0/lib/vtls/schannel_int.h	(revision 101259)
@@ -0,0 +1,194 @@
+#ifndef HEADER_CURL_SCHANNEL_INT_H
+#define HEADER_CURL_SCHANNEL_INT_H
+/***************************************************************************
+ *                                  _   _ ____  _
+ *  Project                     ___| | | |  _ \| |
+ *                             / __| | | | |_) | |
+ *                            | (__| |_| |  _ <| |___
+ *                             \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) Marc Hoersken, <info@marc-hoersken.de>, et al.
+ * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
+ *
+ * This software is licensed as described in the file COPYING, which
+ * you should have received as part of this distribution. The terms
+ * are also available at https://curl.se/docs/copyright.html.
+ *
+ * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+ * copies of the Software, and permit persons to whom the Software is
+ * furnished to do so, under the terms of the COPYING file.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ * SPDX-License-Identifier: curl
+ *
+ ***************************************************************************/
+#include "curl_setup.h"
+
+#ifdef USE_SCHANNEL
+
+#ifdef __MINGW32__
+#ifdef __MINGW64_VERSION_MAJOR
+#define HAS_MANUAL_VERIFY_API
+#endif
+#else
+#ifdef CERT_CHAIN_REVOCATION_CHECK_CHAIN
+#define HAS_MANUAL_VERIFY_API
+#endif
+#endif
+
+#if defined(CryptStringToBinary) && defined(CRYPT_STRING_HEX)   \
+  && !defined(DISABLE_SCHANNEL_CLIENT_CERT)
+#define HAS_CLIENT_CERT_PATH
+#endif
+
+#ifndef CRYPT_DECODE_NOCOPY_FLAG
+#define CRYPT_DECODE_NOCOPY_FLAG 0x1
+#endif
+
+#ifndef CRYPT_DECODE_ALLOC_FLAG
+#define CRYPT_DECODE_ALLOC_FLAG 0x8000
+#endif
+
+#ifndef CERT_ALT_NAME_DNS_NAME
+#define CERT_ALT_NAME_DNS_NAME 3
+#endif
+
+#ifndef CERT_ALT_NAME_IP_ADDRESS
+#define CERT_ALT_NAME_IP_ADDRESS 8
+#endif
+
+
+#if defined(__MINGW32__) && !defined(__MINGW64_VERSION_MAJOR)
+/* Original mingw is missing CERT structs or they're disabled.
+   Refer to w32api-5.0.2-mingw32-dev\include\wincrypt.h. */
+
+/* !checksrc! disable TYPEDEFSTRUCT 4 */
+typedef struct _CERT_OTHER_NAME {
+  LPSTR pszObjId;
+  CRYPT_OBJID_BLOB Value;
+} CERT_OTHER_NAME, *PCERT_OTHER_NAME;
+
+typedef struct _CERT_ALT_NAME_ENTRY {
+  DWORD dwAltNameChoice;
+  union {
+    PCERT_OTHER_NAME pOtherName;
+    LPWSTR pwszRfc822Name;
+    LPWSTR pwszDNSName;
+    CERT_NAME_BLOB DirectoryName;
+    LPWSTR pwszURL;
+    CRYPT_DATA_BLOB IPAddress;
+    LPSTR pszRegisteredID;
+  };
+} CERT_ALT_NAME_ENTRY, *PCERT_ALT_NAME_ENTRY;
+
+typedef struct _CERT_ALT_NAME_INFO {
+  DWORD cAltEntry;
+  PCERT_ALT_NAME_ENTRY rgAltEntry;
+} CERT_ALT_NAME_INFO, *PCERT_ALT_NAME_INFO;
+
+typedef struct _CRYPT_DECODE_PARA {
+  DWORD cbSize;
+  PFN_CRYPT_ALLOC pfnAlloc;
+  PFN_CRYPT_FREE pfnFree;
+} CRYPT_DECODE_PARA, *PCRYPT_DECODE_PARA;
+#endif
+
+#ifndef SCH_CREDENTIALS_VERSION
+
+#define SCH_CREDENTIALS_VERSION  0x00000005
+
+typedef enum _eTlsAlgorithmUsage
+{
+    TlsParametersCngAlgUsageKeyExchange,
+    TlsParametersCngAlgUsageSignature,
+    TlsParametersCngAlgUsageCipher,
+    TlsParametersCngAlgUsageDigest,
+    TlsParametersCngAlgUsageCertSig
+} eTlsAlgorithmUsage;
+
+typedef struct _CRYPTO_SETTINGS
+{
+    eTlsAlgorithmUsage  eAlgorithmUsage;
+    UNICODE_STRING      strCngAlgId;
+    DWORD               cChainingModes;
+    PUNICODE_STRING     rgstrChainingModes;
+    DWORD               dwMinBitLength;
+    DWORD               dwMaxBitLength;
+} CRYPTO_SETTINGS, * PCRYPTO_SETTINGS;
+
+typedef struct _TLS_PARAMETERS
+{
+    DWORD               cAlpnIds;
+    PUNICODE_STRING     rgstrAlpnIds;
+    DWORD               grbitDisabledProtocols;
+    DWORD               cDisabledCrypto;
+    PCRYPTO_SETTINGS    pDisabledCrypto;
+    DWORD               dwFlags;
+} TLS_PARAMETERS, * PTLS_PARAMETERS;
+
+typedef struct _SCH_CREDENTIALS
+{
+    DWORD               dwVersion;
+    DWORD               dwCredFormat;
+    DWORD               cCreds;
+    PCCERT_CONTEXT* paCred;
+    HCERTSTORE          hRootStore;
+
+    DWORD               cMappers;
+    struct _HMAPPER **aphMappers;
+
+    DWORD               dwSessionLifespan;
+    DWORD               dwFlags;
+    DWORD               cTlsParameters;
+    PTLS_PARAMETERS     pTlsParameters;
+} SCH_CREDENTIALS, * PSCH_CREDENTIALS;
+
+#define SCH_CRED_MAX_SUPPORTED_PARAMETERS 16
+#define SCH_CRED_MAX_SUPPORTED_ALPN_IDS 16
+#define SCH_CRED_MAX_SUPPORTED_CRYPTO_SETTINGS 16
+#define SCH_CRED_MAX_SUPPORTED_CHAINING_MODES 16
+
+#endif /* SCH_CREDENTIALS_VERSION */
+
+struct Curl_schannel_cred {
+  CredHandle cred_handle;
+  TimeStamp time_stamp;
+  TCHAR *sni_hostname;
+#ifdef HAS_CLIENT_CERT_PATH
+  HCERTSTORE client_cert_store;
+#endif
+  int refcount;
+};
+
+struct Curl_schannel_ctxt {
+  CtxtHandle ctxt_handle;
+  TimeStamp time_stamp;
+};
+
+struct schannel_ssl_backend_data {
+  struct Curl_schannel_cred *cred;
+  struct Curl_schannel_ctxt *ctxt;
+  SecPkgContext_StreamSizes stream_sizes;
+  size_t encdata_length, decdata_length;
+  size_t encdata_offset, decdata_offset;
+  unsigned char *encdata_buffer, *decdata_buffer;
+  /* encdata_is_incomplete: if encdata contains only a partial record that
+     can't be decrypted without another recv() (that is, status is
+     SEC_E_INCOMPLETE_MESSAGE) then set this true. after an recv() adds
+     more bytes into encdata then set this back to false. */
+  bool encdata_is_incomplete;
+  unsigned long req_flags, ret_flags;
+  CURLcode recv_unrecoverable_err; /* schannel_recv had an unrecoverable err */
+  bool recv_sspi_close_notify; /* true if connection closed by close_notify */
+  bool recv_connection_closed; /* true if connection closed, regardless how */
+  bool recv_renegotiating;     /* true if recv is doing renegotiation */
+  bool use_alpn; /* true if ALPN is used for this connection */
+#ifdef HAS_MANUAL_VERIFY_API
+  bool use_manual_cred_validation; /* true if manual cred validation is used */
+#endif
+};
+
+#endif /* USE_SCHANNEL */
+#endif /* HEADER_CURL_SCHANNEL_INT_H */
Index: trunk/src/libs/curl-8.3.0/libcurl.def
===================================================================
--- trunk/src/libs/curl-8.3.0/libcurl.def	(revision 101259)
+++ trunk/src/libs/curl-8.3.0/libcurl.def	(revision 101259)
@@ -0,0 +1,93 @@
+EXPORTS
+curl_easy_cleanup
+curl_easy_duphandle
+curl_easy_escape
+curl_easy_getinfo
+curl_easy_header
+curl_easy_init
+curl_easy_nextheader
+curl_easy_option_by_id
+curl_easy_option_by_name
+curl_easy_option_next
+curl_easy_pause
+curl_easy_perform
+curl_easy_recv
+curl_easy_reset
+curl_easy_send
+curl_easy_setopt
+curl_easy_strerror
+curl_easy_unescape
+curl_easy_upkeep
+curl_escape
+curl_formadd
+curl_formfree
+curl_formget
+curl_free
+curl_getdate
+curl_getenv
+curl_global_cleanup
+curl_global_init
+curl_global_init_mem
+curl_global_sslset
+curl_global_trace
+curl_maprintf
+curl_mfprintf
+curl_mime_addpart
+curl_mime_data
+curl_mime_data_cb
+curl_mime_encoder
+curl_mime_filedata
+curl_mime_filename
+curl_mime_free
+curl_mime_headers
+curl_mime_init
+curl_mime_name
+curl_mime_subparts
+curl_mime_type
+curl_mprintf
+curl_msnprintf
+curl_msprintf
+curl_multi_add_handle
+curl_multi_assign
+curl_multi_cleanup
+curl_multi_fdset
+curl_multi_info_read
+curl_multi_init
+curl_multi_perform
+curl_multi_poll
+curl_multi_remove_handle
+curl_multi_setopt
+curl_multi_socket
+curl_multi_socket_action
+curl_multi_socket_all
+curl_multi_strerror
+curl_multi_timeout
+curl_multi_wait
+curl_multi_wakeup
+curl_mvaprintf
+curl_mvfprintf
+curl_mvprintf
+curl_mvsnprintf
+curl_mvsprintf
+curl_pushheader_byname
+curl_pushheader_bynum
+curl_share_cleanup
+curl_share_init
+curl_share_setopt
+curl_share_strerror
+curl_slist_append
+curl_slist_free_all
+curl_strequal
+curl_strnequal
+curl_unescape
+curl_url
+curl_url_cleanup
+curl_url_dup
+curl_url_get
+curl_url_set
+curl_url_strerror
+curl_version
+curl_version_info
+curl_ws_meta
+curl_ws_recv
+curl_ws_send
