]> git.ipfire.org Git - thirdparty/curl.git/commitdiff
websocket: add option to disable auto-pong reply
authorBrian Chrzanowski <chrzanowskitest@gmail.com>
Wed, 24 Jul 2024 02:15:23 +0000 (22:15 -0400)
committerDaniel Stenberg <daniel@haxx.se>
Fri, 18 Apr 2025 22:01:28 +0000 (00:01 +0200)
This adds another bitflag on CURLOPT_WS_OPTIONS (CURLWS_NOAUTOPONG) that
disables the default and automatic PONG reply in the WebSocket layer.

Assisted-by: Calvin Ruocco
Closes #16744

15 files changed:
docs/internals/WEBSOCKET.md
docs/libcurl/curl_ws_meta.md
docs/libcurl/libcurl-ws.md
docs/libcurl/opts/CURLOPT_WS_OPTIONS.md
docs/libcurl/symbols-in-versions
include/curl/websockets.h
lib/setopt.c
lib/url.c
lib/urldata.h
lib/ws.c
packages/OS400/curl.inc.in
tests/data/Makefile.am
tests/data/test2312 [new file with mode: 0644]
tests/libtest/Makefile.inc
tests/libtest/lib2312.c [new file with mode: 0644]

index 2f9f5cb24d89bbf3fc11a108d5ae2d9d5a0b4c4e..230bb9124953182c5709e3ad9042004931c788db 100644 (file)
@@ -39,7 +39,8 @@ WebSocket with libcurl can be done two ways.
 The new options to `curl_easy_setopt()`:
 
  `CURLOPT_WS_OPTIONS` - to control specific behavior. `CURLWS_RAW_MODE` makes
- libcurl provide all WebSocket traffic raw in the callback.
+ libcurl provide all WebSocket traffic raw in the callback. `CURLWS_NOAUTOPONG`
+ disables automatic `PONG` replies.
 
 The new function calls:
 
index ed9e154722863efce711ccc3373aee46e2b39615..1fd8b6641fd07121333ba10637997464d6968540 100644 (file)
@@ -106,7 +106,8 @@ This is a ping message. It may contain up to 125 bytes of payload text.
 libcurl does not verify that the payload is valid UTF-8.
 
 Upon receiving a ping message, libcurl automatically responds with a pong
-message unless the **CURLWS_RAW_MODE** bit of CURLOPT_WS_OPTIONS(3) is set.
+message unless the **CURLWS_NOAUTOPONG** or **CURLWS_RAW_MODE** bit of
+CURLOPT_WS_OPTIONS(3) is set.
 
 ## CURLWS_PONG
 
index d8f013acf2f9766bc5def9e2aef08b431f6b2373..1ef3074d5d848238896ddbbf1fc13ba4c3accafe 100644 (file)
@@ -88,7 +88,8 @@ unidirectional heartbeat.
 
 libcurl automatically responds to server PING messages with a PONG that echoes
 the payload of the PING message. libcurl does neither send any PING messages
-nor any unsolicited PONG messages automatically.
+nor any unsolicited PONG messages automatically. The automatic reply to PING
+messages can be disabled through CURLOPT_WS_OPTIONS(3).
 
 # MODELS
 
index 92760a9283b24358b8bac439da3655e68773d1ef..35ae17cf470503c64d689cf76b39bc2d71015ea5 100644 (file)
@@ -44,6 +44,12 @@ callback.
 In raw mode, libcurl does not handle pings or any other frame for the
 application.
 
+## CURLWS_NOAUTOPONG (2)
+
+Disable the automatic reply to PING messages. This means users must
+send a PONG message with curl_ws_send(3). This feature is added with
+version 8.14.0.
+
 # DEFAULT
 
 0
index 1fcacb984810f5247be04be57ed5536bfc4b7cb0..66b0b7c27f3a5af301838eb4f50fe1ce12534b8c 100644 (file)
@@ -1155,6 +1155,7 @@ CURLWARNING                     7.66.0
 CURLWS_BINARY                   7.86.0
 CURLWS_CLOSE                    7.86.0
 CURLWS_CONT                     7.86.0
+CURLWS_NOAUTOPONG               8.14.0
 CURLWS_OFFSET                   7.86.0
 CURLWS_PING                     7.86.0
 CURLWS_PONG                     7.86.0
index 6ef6a2bc92ec5173caa92924fac8d27eff91af09..afb86b4ebcf9b2a5f184541e08ef0eea6ed18c23 100644 (file)
@@ -73,7 +73,8 @@ CURL_EXTERN CURLcode curl_ws_send(CURL *curl, const void *buffer,
                                   unsigned int flags);
 
 /* bits for the CURLOPT_WS_OPTIONS bitmask: */
-#define CURLWS_RAW_MODE (1<<0)
+#define CURLWS_RAW_MODE   (1<<0)
+#define CURLWS_NOAUTOPONG (1<<1)
 
 CURL_EXTERN const struct curl_ws_frame *curl_ws_meta(CURL *curl);
 
index 07ccb933f85b7e76f9a9f9841a135a8a2d7734f3..193ef66e53adec3354974fd746f07816e1441bb7 100644 (file)
@@ -1384,7 +1384,8 @@ static CURLcode setopt_long(struct Curl_easy *data, CURLoption option,
 #endif /* ! CURL_DISABLE_ALTSVC */
 #ifndef CURL_DISABLE_WEBSOCKETS
   case CURLOPT_WS_OPTIONS:
-    data->set.ws_raw_mode =  (bool)(arg & CURLWS_RAW_MODE);
+    data->set.ws_raw_mode = (bool)(arg & CURLWS_RAW_MODE);
+    data->set.ws_no_auto_pong = (bool)(arg & CURLWS_NOAUTOPONG);
     break;
 #endif
   case CURLOPT_QUICK_EXIT:
index ba62b5332804ae60e6632b237577af34400f186e..473582ca40146cfa3ea08a93fb40c726717db0f7 100644 (file)
--- a/lib/url.c
+++ b/lib/url.c
@@ -480,6 +480,11 @@ CURLcode Curl_init_userdefined(struct Curl_easy *data)
   memset(&set->priority, 0, sizeof(set->priority));
 #endif
   set->quick_exit = 0L;
+#ifndef CURL_DISABLE_WEBSOCKETS
+  set->ws_raw_mode = FALSE;
+  set->ws_no_auto_pong = FALSE;
+#endif
+
   return result;
 }
 
index 591e166cc8ec95efc3d36e89be86c449243c156a..e76d4a1043e5c0b6e960330d175e0cfd326db65a 100644 (file)
@@ -1802,6 +1802,7 @@ struct UserDefined {
   BIT(http09_allowed); /* allow HTTP/0.9 responses */
 #ifndef CURL_DISABLE_WEBSOCKETS
   BIT(ws_raw_mode);
+  BIT(ws_no_auto_pong);
 #endif
 };
 
index d6ba74a440961fefbc0c1508e555a430f2357553..3fb1d6edad8323beeae2daff3b6833f2ada9b6a2 100644 (file)
--- a/lib/ws.c
+++ b/lib/ws.c
@@ -502,10 +502,12 @@ static ssize_t ws_cw_dec_next(const unsigned char *buf, size_t buflen,
   struct ws_cw_dec_ctx *ctx = user_data;
   struct Curl_easy *data = ctx->data;
   struct websocket *ws = ctx->ws;
+  bool auto_pong = !data->set.ws_no_auto_pong;
   curl_off_t remain = (payload_len - (payload_offset + buflen));
 
   (void)frame_age;
-  if((frame_flags & CURLWS_PING) && !remain) {
+
+  if(auto_pong && (frame_flags & CURLWS_PING) && !remain) {
     /* auto-respond to PINGs, only works for single-frame payloads atm */
     size_t bytes;
     infof(data, "WS: auto-respond to PING with a PONG");
@@ -949,6 +951,8 @@ static ssize_t ws_client_collect(const unsigned char *buf, size_t buflen,
                                  CURLcode *err)
 {
   struct ws_collect *ctx = userp;
+  struct Curl_easy *data = ctx->data;
+  bool auto_pong = !data->set.ws_no_auto_pong;
   size_t nwritten;
   curl_off_t remain = (payload_len - (payload_offset + buflen));
 
@@ -960,7 +964,7 @@ static ssize_t ws_client_collect(const unsigned char *buf, size_t buflen,
     ctx->payload_len = payload_len;
   }
 
-  if((frame_flags & CURLWS_PING) && !remain) {
+  if(auto_pong && (frame_flags & CURLWS_PING) && !remain) {
     /* auto-respond to PINGs, only works for single-frame payloads atm */
     size_t bytes;
     infof(ctx->data, "WS: auto-respond to PING with a PONG");
index 81ba82c358803a2391168de38f88e3baecac5519..9601b7b728a82036e0f5aadd6047b4ead52e193d 100644 (file)
       *
      d CURLWS_RAW_MODE...
      d                 c                   X'00000001'
+     d CURLWS_NOAUTOPONG...
+     d                 c                   X'00000002'
       *
       **************************************************************************
       *                                Types
index 5193fc810209af8adf78ec7b9f6ec59daa9860c4..8d83f11ed0f57e890e341962e4dfc593cb599696 100644 (file)
@@ -259,7 +259,7 @@ test2100 test2101 test2102 \
 test2200 test2201 test2202 test2203 test2204 test2205 \
 \
 test2300 test2301 test2302 test2303 test2304 test2305 test2306 test2307 \
-test2308 test2309 test2310 test2311 \
+test2308 test2309 test2310 test2311 test2312 \
 \
 test2400 test2401 test2402 test2403 test2404 test2405 test2406 \
 \
diff --git a/tests/data/test2312 b/tests/data/test2312
new file mode 100644 (file)
index 0000000..964381d
--- /dev/null
@@ -0,0 +1,66 @@
+<testcase>
+<info>
+<keywords>
+WebSockets
+</keywords>
+</info>
+
+#
+# Server-side
+<reply>
+<data nocheck="yes" nonewline="yes">
+HTTP/1.1 101 Switching to WebSockets swsclose
+Server: test-server/fake
+Upgrade: websocket
+Connection: Upgrade
+Sec-WebSocket-Accept: HkPsVga7+8LuxM4RGQ5p9tZHeYs=
+
+%hex[%89%00]hex%
+</data>
+# allow upgrade
+<servercmd>
+upgrade
+</servercmd>
+</reply>
+
+#
+# Client-side
+<client>
+# for the forced CURL_ENTROPY
+<features>
+debug
+ws
+</features>
+<server>
+http
+</server>
+<name>
+WebSockets no auto ping
+</name>
+<tool>
+lib%TESTNUMBER
+</tool>
+<command>
+ws://%HOSTIP:%HTTPPORT/%TESTNUMBER
+</command>
+</client>
+
+#
+# Verify data after the test has been "shot"
+<verify>
+<protocol nocheck="yes">
+GET /%TESTNUMBER HTTP/1.1
+Host: %HOSTIP:%HTTPPORT
+User-Agent: webbie-sox/3
+Accept: */*
+Upgrade: websocket
+Connection: Upgrade
+Sec-WebSocket-Version: 13
+Sec-WebSocket-Key: NDMyMTUzMjE2MzIxNzMyMQ==
+
+</protocol>
+<errorcode>
+0
+</errorcode>
+</verify>
+</testcase>
index b93177f6fb1d717ecd7fbf2ae8228b091abdba86..12ef21d9f4aad167a0498597df890d6429286e85 100644 (file)
@@ -74,7 +74,7 @@ LIBTESTPROGS = libauthretry libntlmconnect libprereq                     \
  lib1960 lib1964 \
  lib1970 lib1971 lib1972 lib1973 lib1974 lib1975 lib1977 lib1978 \
  lib2301 lib2302 lib2304 lib2305 lib2306         lib2308 lib2309 lib2310 \
- lib2311 \
+ lib2311 lib2312 \
  lib2402 lib2404 lib2405 \
  lib2502 \
  lib3010 lib3025 lib3026 lib3027 \
@@ -713,6 +713,9 @@ lib2310_LDADD = $(TESTUTIL_LIBS)
 lib2311_SOURCES = lib2311.c $(SUPPORTFILES) $(TESTUTIL) $(TSTTRACE) $(MULTIBYTE)
 lib2311_LDADD = $(TESTUTIL_LIBS)
 
+lib2312_SOURCES = lib2312.c $(SUPPORTFILES) $(TESTUTIL) $(TSTTRACE) $(MULTIBYTE)
+lib2312_LDADD = $(TESTUTIL_LIBS)
+
 lib2402_SOURCES = lib2402.c $(SUPPORTFILES) $(TESTUTIL) $(WARNLESS)
 lib2402_LDADD = $(TESTUTIL_LIBS)
 
diff --git a/tests/libtest/lib2312.c b/tests/libtest/lib2312.c
new file mode 100644 (file)
index 0000000..53998d8
--- /dev/null
@@ -0,0 +1,102 @@
+/***************************************************************************
+ *                                  _   _ ____  _
+ *  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 "test.h"
+
+#ifdef USE_WEBSOCKETS
+
+struct ping_check {
+  CURL *curl;
+  int pinged;
+};
+
+static size_t write_cb(char *b, size_t size, size_t nitems, void *p)
+{
+  struct ping_check *ping_check = p;
+  CURL *curl = ping_check->curl;
+  const struct curl_ws_frame *frame = curl_ws_meta(curl);
+  size_t sent = 0;
+  size_t i = 0;
+
+  /* upon ping, respond with input data, disconnect, mark a success */
+  if(frame->flags & CURLWS_PING) {
+    fprintf(stderr, "write_cb received ping with %zd bytes\n",
+      size * nitems);
+    fprintf(stderr, "\n");
+    for(i = 0; i < size * nitems; i++) {
+      fprintf(stderr, "%02X%s", (int)b[i],
+          (i % 10 == 0 && i != 0) ? "\n" : " ");
+    }
+    fprintf(stderr, "\n");
+    fprintf(stderr, "write_cb sending pong response\n");
+    curl_ws_send(curl, b, size * nitems, &sent, 0, CURLWS_PONG);
+    fprintf(stderr, "write_cb closing websocket\n");
+    curl_ws_send(curl, NULL, 0, &sent, 0, CURLWS_CLOSE);
+    ping_check->pinged = 1;
+  }
+  else {
+    fprintf(stderr, "ping_check_cb: non-ping message, frame->flags %x\n",
+      frame->flags);
+  }
+
+  return size * nitems;
+}
+
+CURLcode test(char *URL)
+{
+  CURL *curl;
+  CURLcode res = CURLE_OK;
+  struct ping_check state;
+
+  global_init(CURL_GLOBAL_ALL);
+
+  curl = curl_easy_init();
+  if(curl) {
+    state.curl = curl;
+    state.pinged = 0;
+
+    curl_easy_setopt(curl, CURLOPT_URL, URL);
+
+    /* use the callback style, without auto-pong */
+    curl_easy_setopt(curl, CURLOPT_USERAGENT, "webbie-sox/3");
+    curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);
+    curl_easy_setopt(curl, CURLOPT_WS_OPTIONS, (long)CURLWS_NOAUTOPONG);
+    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_cb);
+    curl_easy_setopt(curl, CURLOPT_WRITEDATA, &state);
+
+    res = curl_easy_perform(curl);
+    fprintf(stderr, "curl_easy_perform() returned %u\n", (int)res);
+
+    res = state.pinged ? 0 : 1;
+
+    /* always cleanup */
+    curl_easy_cleanup(curl);
+  }
+  curl_global_cleanup();
+  return res;
+}
+
+#else /* no websockets */
+NO_SUPPORT_BUILT_IN
+#endif