]> git.ipfire.org Git - thirdparty/curl.git/commitdiff
ws: tests and fixes
authorRuocco, Calvin <calvin.ruocco@vector.com>
Mon, 14 Apr 2025 15:59:45 +0000 (17:59 +0200)
committerDaniel Stenberg <daniel@haxx.se>
Mon, 2 Jun 2025 09:15:38 +0000 (11:15 +0200)
This started out as regression tests for the `curl_ws_recv()` and
`curl_ws_send()` implementation and ended up with a bugfix, additional
protocol validation and minor logging improvements.

- Fix reset of fragmented message decoder state when a PING/PONG is
  received in between message fragments.

- Fix undefined behavior (applying zero offset to null pointer) in
  curl_ws_send() when the given buffer is NULL.

- Detect invalid overlong PING/PONG/CLOSE frames.
- Detect invalid fragmented PING/PONG/CLOSE frames.
- Detect invalid sequences of fragmented frames.

  - a) A continuation frame (0x80...) is received without any ongoing
    fragmented message.
  - b) A new fragmented message is started (0x81/0x01/0x82/0x02...)
    before the ongoing fragmented message has terminated.

- Made logs for invalid opcodes easier to understand.
- Moved noisy logs to the `CURL_TRC_WS` log level.
- Unified the prefixes for WebSocket log messages: `[WS] ...`

- Add env var `CURL_WS_FORCE_ZERO_MASK` in debug builds.

  - If set, it forces the bit mask applied to outgoing payloads to
    0x00000000, which effectively means the payload is not masked at
    all. This drastically simplifies defining the expected `<protocol>`
    data in test cases.

- 2700: Frame types
- 2701: Invalid opcode 0x3
- 2702: Invalid opcode 0xB
- 2703: Invalid reserved bit RSV1 _(replaces 2310)_
- 2704: Invalid reserved bit RSV2
- 2705: Invalid reserved bit RSV3
- 2706: Invalid masked server message
- 2707: Peculiar frame sizes _(part. replaces 2311)_
- 2708: Automatic PONG
- 2709: No automatic PONG _(replaces 2312)_
- 2710: Unsolicited PONG
- 2711: Empty PING/PONG/CLOSE
- 2712: Max sized PING/PONG/CLOSE
- 2713: Invalid oversized PING _(replaces 2307)_
- 2714: Invalid oversized PONG
- 2715: Invalid oversized CLOSE
- 2716: Invalid fragmented PING
- 2717: Invalid fragmented PONG
- 2718: Invalid fragmented CLOSE
- 2719: Fragmented messages _(part. replaces 2311)_
- 2720: Fragmented messages with empty fragments
- 2721: Fragmented messages with interleaved pong
- 2722: Invalid fragmented message without initial frame
- 2723: Invalid fragmented message without final frame

- 2305: curl_ws_recv() loop reading three larger frames
  - This test involuntarily sent an invalid sequence of opcodes (0x01...,0x01...,0x81...) , but neither libcurl nor the test caught this! The correct sequence was tested in 2311 (0x01...,0x00...,0x80...). See below for 2311.
  - Validation of the opcode sequence was added to libcurl and is now tested in 2723.
  - Superseded by 2719 (fragmented message) and 2707 (large frames).
- 2307: overlong PING payload
  - The tested PING payload length check was actually missing, but the test didn't catch this since it involuntarily sent an invalid opcode (0x19... instead of 0x89...) so that the expected error occurred, but for the wrong reason.
  - Superseded by 2713.
- 2310: unknown reserved bit set in frame header
  - Superseded by 2703 and extended by 2704 and 2705.
- 2311: curl_ws_recv() read fragmented message
  - Superseded by 2719 (fragmented message) and 2707 (large frames).
- 2312: WebSockets no auto ping
  - Superseded by 2709.

- No tests for `CURLOPT_WRITEFUNCTION`.
- No tests for sending of invalid frames/fragments.

Closes #17136

41 files changed:
.mailmap
docs/libcurl/libcurl-env-dbg.md
lib/ws.c
tests/data/Makefile.am
tests/data/test2304
tests/data/test2305 [deleted file]
tests/data/test2307 [deleted file]
tests/data/test2310 [deleted file]
tests/data/test2311 [deleted file]
tests/data/test2312 [deleted file]
tests/data/test2700 [new file with mode: 0644]
tests/data/test2701 [new file with mode: 0644]
tests/data/test2702 [new file with mode: 0644]
tests/data/test2703 [new file with mode: 0644]
tests/data/test2704 [new file with mode: 0644]
tests/data/test2705 [new file with mode: 0644]
tests/data/test2706 [new file with mode: 0644]
tests/data/test2707 [new file with mode: 0644]
tests/data/test2708 [new file with mode: 0644]
tests/data/test2709 [new file with mode: 0644]
tests/data/test2710 [new file with mode: 0644]
tests/data/test2711 [new file with mode: 0644]
tests/data/test2712 [new file with mode: 0644]
tests/data/test2713 [new file with mode: 0644]
tests/data/test2714 [new file with mode: 0644]
tests/data/test2715 [new file with mode: 0644]
tests/data/test2716 [new file with mode: 0644]
tests/data/test2717 [new file with mode: 0644]
tests/data/test2718 [new file with mode: 0644]
tests/data/test2719 [new file with mode: 0644]
tests/data/test2720 [new file with mode: 0644]
tests/data/test2721 [new file with mode: 0644]
tests/data/test2722 [new file with mode: 0644]
tests/data/test2723 [new file with mode: 0644]
tests/http/test_20_websockets.py
tests/libtest/Makefile.inc
tests/libtest/lib2305.c [deleted file]
tests/libtest/lib2310.c [deleted file]
tests/libtest/lib2311.c [deleted file]
tests/libtest/lib2312.c [deleted file]
tests/libtest/lib2700.c [new file with mode: 0644]

index 000268adf26ba9a8c65e1e79bfa209da3fae7f96..a67f6e6b5748f86bfa1cd859ded9580289ed7729 100644 (file)
--- a/.mailmap
+++ b/.mailmap
@@ -114,3 +114,4 @@ Aki Sakurai <75532970+AkiSakurai@users.noreply.github.com>
 Sinkevich Artem <artsin666@gmail.com>
 Andrew Kirillov <akirillo@uk.ibm.com>
 Stephen Farrell <stephen.farrell@cs.tcd.ie>
+Calvin Ruocco <calvin.ruocco@vector.com>
index a5e977f3027a6cf48c8b34f5f1d5c4b5e50f98d9..ce4e3c006a76afa5951104510ee8d89a784e54bd 100644 (file)
@@ -141,6 +141,11 @@ decoding.
 Used to simulate blocking sends after this chunk size for WebSocket
 connections.
 
+## `CURL_WS_FORCE_ZERO_MASK`
+
+Used to set the bitmask of all sent WebSocket frames to zero. The value of the
+environment variable does not matter.
+
 ## `CURL_FORBID_REUSE`
 
 Used to set the CURLOPT_FORBID_REUSE flag on each transfer initiated
index 61ab5019fd811f77bfe6c6f08940e7cb0700a8d9..fc02025f1d23acc41d0fe249a140aa81aa81a7dd 100644 (file)
--- a/lib/ws.c
+++ b/lib/ws.c
@@ -145,35 +145,77 @@ static int ws_frame_firstbyte2flags(struct Curl_easy *data,
                                     unsigned char firstbyte, int cont_flags)
 {
   switch(firstbyte) {
+    /* 0x00 - intermediate TEXT/BINARY fragment */
     case WSBIT_OPCODE_CONT:
-      /* continuation of a previous fragment: restore stored flags */
+      if(!(cont_flags & CURLWS_CONT)) {
+        failf(data, "[WS] no ongoing fragmented message to resume");
+        return 0;
+      }
       return cont_flags | CURLWS_CONT;
+    /* 0x80 - final TEXT/BIN fragment */
     case (WSBIT_OPCODE_CONT | WSBIT_FIN):
-      /* continuation of a previous fragment: restore stored flags */
+      if(!(cont_flags & CURLWS_CONT)) {
+        failf(data, "[WS] no ongoing fragmented message to resume");
+        return 0;
+      }
       return cont_flags & ~CURLWS_CONT;
+    /* 0x01 - first TEXT fragment */
     case WSBIT_OPCODE_TEXT:
+      if(cont_flags & CURLWS_CONT) {
+        failf(data, "[WS] fragmented message interrupted by new TEXT msg");
+        return 0;
+      }
       return CURLWS_TEXT | CURLWS_CONT;
+    /* 0x81 - unfragmented TEXT msg */
     case (WSBIT_OPCODE_TEXT | WSBIT_FIN):
+      if(cont_flags & CURLWS_CONT) {
+        failf(data, "[WS] fragmented message interrupted by new TEXT msg");
+        return 0;
+      }
       return CURLWS_TEXT;
+    /* 0x02 - first BINARY fragment */
     case WSBIT_OPCODE_BIN:
+      if(cont_flags & CURLWS_CONT) {
+        failf(data, "[WS] fragmented message interrupted by new BINARY msg");
+        return 0;
+      }
       return CURLWS_BINARY | CURLWS_CONT;
+    /* 0x82 - unfragmented BINARY msg */
     case (WSBIT_OPCODE_BIN | WSBIT_FIN):
+      if(cont_flags & CURLWS_CONT) {
+        failf(data, "[WS] fragmented message interrupted by new BINARY msg");
+        return 0;
+      }
       return CURLWS_BINARY;
+    /* 0x08 - first CLOSE fragment */
+    case WSBIT_OPCODE_CLOSE:
+      failf(data, "[WS] invalid fragmented CLOSE frame");
+      return 0;
+    /* 0x88 - unfragmented CLOSE */
     case (WSBIT_OPCODE_CLOSE | WSBIT_FIN):
       return CURLWS_CLOSE;
+    /* 0x09 - first PING fragment */
+    case WSBIT_OPCODE_PING:
+      failf(data, "[WS] invalid fragmented PING frame");
+      return 0;
+    /* 0x89 - unfragmented PING */
     case (WSBIT_OPCODE_PING | WSBIT_FIN):
       return CURLWS_PING;
+    /* 0x0a - first PONG fragment */
+    case WSBIT_OPCODE_PONG:
+      failf(data, "[WS] invalid fragmented PONG frame");
+      return 0;
+    /* 0x8a - unfragmented PONG */
     case (WSBIT_OPCODE_PONG | WSBIT_FIN):
       return CURLWS_PONG;
+    /* invalid first byte */
     default:
-      if(firstbyte & WSBIT_RSV_MASK) {
-        failf(data, "WS: unknown reserved bit: %x",
-              firstbyte & WSBIT_RSV_MASK);
-      }
-      else {
-        failf(data, "WS: unknown opcode: %x",
-              firstbyte & WSBIT_OPCODE_MASK);
-      }
+      if(firstbyte & WSBIT_RSV_MASK)
+        /* any of the reserved bits 0x40/0x20/0x10 are set */
+        failf(data, "[WS] invalid reserved bits: %02x", firstbyte);
+      else
+        /* any of the reserved opcodes 0x3-0x7 or 0xb-0xf is used */
+        failf(data, "[WS] invalid opcode: %02x", firstbyte);
       return 0;
   }
 }
@@ -186,20 +228,20 @@ static unsigned char ws_frame_flags2firstbyte(struct Curl_easy *data,
   switch(flags & ~CURLWS_OFFSET) {
     case 0:
       if(contfragment) {
-        infof(data, "WS: no flags given; interpreting as continuation "
+        infof(data, "[WS] no flags given; interpreting as continuation "
                     "fragment for compatibility");
         return (WSBIT_OPCODE_CONT | WSBIT_FIN);
       }
-      failf(data, "WS: no flags given");
+      failf(data, "[WS] no flags given");
       *err = CURLE_BAD_FUNCTION_ARGUMENT;
       return 0xff;
     case CURLWS_CONT:
       if(contfragment) {
-        infof(data, "WS: setting CURLWS_CONT flag without message type is "
+        infof(data, "[WS] setting CURLWS_CONT flag without message type is "
                     "supported for compatibility but highly discouraged");
         return WSBIT_OPCODE_CONT;
       }
-      failf(data, "WS: No ongoing fragmented message to continue");
+      failf(data, "[WS] No ongoing fragmented message to continue");
       *err = CURLE_BAD_FUNCTION_ARGUMENT;
       return 0xff;
     case CURLWS_TEXT:
@@ -215,24 +257,24 @@ static unsigned char ws_frame_flags2firstbyte(struct Curl_easy *data,
     case CURLWS_CLOSE:
       return WSBIT_OPCODE_CLOSE | WSBIT_FIN;
     case (CURLWS_CLOSE | CURLWS_CONT):
-      failf(data, "WS: CLOSE frame must not be fragmented");
+      failf(data, "[WS] CLOSE frame must not be fragmented");
       *err = CURLE_BAD_FUNCTION_ARGUMENT;
       return 0xff;
     case CURLWS_PING:
       return WSBIT_OPCODE_PING | WSBIT_FIN;
     case (CURLWS_PING | CURLWS_CONT):
-      failf(data, "WS: PING frame must not be fragmented");
+      failf(data, "[WS] PING frame must not be fragmented");
       *err = CURLE_BAD_FUNCTION_ARGUMENT;
       return 0xff;
     case CURLWS_PONG:
       return WSBIT_OPCODE_PONG | WSBIT_FIN;
     case (CURLWS_PONG | CURLWS_CONT):
-      failf(data, "WS: PONG frame must not be fragmented");
+      failf(data, "[WS] PONG frame must not be fragmented");
       *err = CURLE_BAD_FUNCTION_ARGUMENT;
       return 0xff;
     default:
-      failf(data, "WS: unknown flags: %x", flags);
-      *err = CURLE_SEND_ERROR;
+      failf(data, "[WS] unknown flags: %x", flags);
+      *err = CURLE_BAD_FUNCTION_ARGUMENT;
       return 0xff;
   }
 }
@@ -244,23 +286,23 @@ static void ws_dec_info(struct ws_decoder *dec, struct Curl_easy *data,
   case 0:
     break;
   case 1:
-    CURL_TRC_WRITE(data, "websocket, decoded %s [%s%s]", msg,
-                   ws_frame_name_of_op(dec->head[0]),
-                   (dec->head[0] & WSBIT_FIN) ? "" : " NON-FINAL");
+    CURL_TRC_WS(data, "decoded %s [%s%s]", msg,
+                ws_frame_name_of_op(dec->head[0]),
+                (dec->head[0] & WSBIT_FIN) ? "" : " NON-FINAL");
     break;
   default:
     if(dec->head_len < dec->head_total) {
-      CURL_TRC_WRITE(data, "websocket, decoded %s [%s%s](%d/%d)", msg,
-                     ws_frame_name_of_op(dec->head[0]),
-                     (dec->head[0] & WSBIT_FIN) ? "" : " NON-FINAL",
-                     dec->head_len, dec->head_total);
+      CURL_TRC_WS(data, "decoded %s [%s%s](%d/%d)", msg,
+                  ws_frame_name_of_op(dec->head[0]),
+                  (dec->head[0] & WSBIT_FIN) ? "" : " NON-FINAL",
+                  dec->head_len, dec->head_total);
     }
     else {
-      CURL_TRC_WRITE(data, "websocket, decoded %s [%s%s payload=%"
-                     FMT_OFF_T "/%" FMT_OFF_T "]",
-                     msg, ws_frame_name_of_op(dec->head[0]),
-                     (dec->head[0] & WSBIT_FIN) ? "" : " NON-FINAL",
-                     dec->payload_offset, dec->payload_len);
+      CURL_TRC_WS(data, "decoded %s [%s%s payload=%"
+                  FMT_OFF_T "/%" FMT_OFF_T "]",
+                  msg, ws_frame_name_of_op(dec->head[0]),
+                  (dec->head[0] & WSBIT_FIN) ? "" : " NON-FINAL",
+                  dec->payload_offset, dec->payload_len);
     }
     break;
   }
@@ -318,17 +360,15 @@ static CURLcode ws_dec_read_head(struct ws_decoder *dec,
       dec->frame_flags = ws_frame_firstbyte2flags(data, dec->head[0],
                                                   dec->cont_flags);
       if(!dec->frame_flags) {
-        failf(data, "WS: invalid first byte: %x", dec->head[0]);
         ws_dec_reset(dec);
         return CURLE_RECV_ERROR;
       }
 
-      if(dec->frame_flags & CURLWS_CONT) {
+      /* fragmentation only applies to data frames (text/binary);
+       * control frames (close/ping/pong) do not affect the CONT status */
+      if(dec->frame_flags & (CURLWS_TEXT | CURLWS_BINARY)) {
         dec->cont_flags = dec->frame_flags;
       }
-      else {
-        dec->cont_flags = 0;
-      }
 
       dec->head_len = 1;
       /* ws_dec_info(dec, data, "seeing opcode"); */
@@ -341,10 +381,30 @@ static CURLcode ws_dec_read_head(struct ws_decoder *dec,
 
       if(dec->head[1] & WSBIT_MASK) {
         /* A client MUST close a connection if it detects a masked frame. */
-        failf(data, "WS: masked input frame");
+        failf(data, "[WS] masked input frame");
+        ws_dec_reset(dec);
+        return CURLE_RECV_ERROR;
+      }
+      if(dec->frame_flags & CURLWS_PING && dec->head[1] > 125) {
+        /* The maximum valid size of PING frames is 125 bytes.
+           Accepting overlong pings would mean sending equivalent pongs! */
+        failf(data, "[WS] received PING frame is too big");
         ws_dec_reset(dec);
         return CURLE_RECV_ERROR;
       }
+      if(dec->frame_flags & CURLWS_PONG && dec->head[1] > 125) {
+        /* The maximum valid size of PONG frames is 125 bytes. */
+        failf(data, "[WS] received PONG frame is too big");
+        ws_dec_reset(dec);
+        return CURLE_RECV_ERROR;
+      }
+      if(dec->frame_flags & CURLWS_CLOSE && dec->head[1] > 125) {
+        /* The maximum valid size of CLOSE frames is 125 bytes. */
+        failf(data, "[WS] received CLOSE frame is too big");
+        ws_dec_reset(dec);
+        return CURLE_RECV_ERROR;
+      }
+
       /* How long is the frame head? */
       if(dec->head[1] == 126) {
         dec->head_total = 4;
@@ -379,7 +439,7 @@ static CURLcode ws_dec_read_head(struct ws_decoder *dec,
       break;
     case 10:
       if(dec->head[2] > 127) {
-        failf(data, "WS: frame length longer than 64 signed not supported");
+        failf(data, "[WS] frame length longer than 64 signed not supported");
         return CURLE_RECV_ERROR;
       }
       dec->payload_len = ((curl_off_t)dec->head[2] << 56) |
@@ -394,7 +454,7 @@ static CURLcode ws_dec_read_head(struct ws_decoder *dec,
     default:
       /* this should never happen */
       DEBUGASSERT(0);
-      failf(data, "WS: unexpected frame header length");
+      failf(data, "[WS] unexpected frame header length");
       return CURLE_RECV_ERROR;
     }
 
@@ -430,8 +490,8 @@ static CURLcode ws_dec_pass_payload(struct ws_decoder *dec,
     Curl_bufq_skip(inraw, (size_t)nwritten);
     dec->payload_offset += (curl_off_t)nwritten;
     remain = dec->payload_len - dec->payload_offset;
-    CURL_TRC_WRITE(data, "websocket, passed %zd bytes payload, %"
-                   FMT_OFF_T " remain", nwritten, remain);
+    CURL_TRC_WS(data, "passed %zd bytes payload, %"
+                FMT_OFF_T " remain", nwritten, remain);
   }
 
   return remain ? CURLE_AGAIN : CURLE_OK;
@@ -457,7 +517,7 @@ static CURLcode ws_dec_pass(struct ws_decoder *dec,
     result = ws_dec_read_head(dec, data, inraw);
     if(result) {
       if(result != CURLE_AGAIN) {
-        infof(data, "WS: decode error %d", (int)result);
+        infof(data, "[WS] decode error %d", (int)result);
         break;  /* real error */
       }
       /* incomplete ws frame head */
@@ -555,7 +615,7 @@ static ssize_t ws_cw_dec_next(const unsigned char *buf, size_t buflen,
   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");
+    infof(data, "[WS] auto-respond to PING with a PONG");
     /* send back the exact same content as a PONG */
     *err = curl_ws_send(data, buf, buflen, &bytes, 0, CURLWS_PONG);
     if(*err)
@@ -588,7 +648,7 @@ static CURLcode ws_cw_write(struct Curl_easy *data,
 
   ws = Curl_conn_meta_get(data->conn, CURL_META_PROTO_WS_CONN);
   if(!ws) {
-    failf(data, "WS: not a websocket transfer");
+    failf(data, "[WS] not a websocket transfer");
     return CURLE_FAILED_INIT;
   }
 
@@ -597,7 +657,7 @@ static CURLcode ws_cw_write(struct Curl_easy *data,
     nwritten = Curl_bufq_write(&ctx->buf, (const unsigned char *)buf,
                                nbytes, &result);
     if(nwritten < 0) {
-      infof(data, "WS: error adding data to buffer %d", result);
+      infof(data, "[WS] error adding data to buffer %d", result);
       return result;
     }
   }
@@ -613,17 +673,17 @@ static CURLcode ws_cw_write(struct Curl_easy *data,
     if(result == CURLE_AGAIN) {
       /* insufficient amount of data, keep it for later.
        * we pretend to have written all since we have a copy */
-      CURL_TRC_WRITE(data, "websocket, buffered incomplete frame head");
+      CURL_TRC_WS(data, "buffered incomplete frame head");
       return CURLE_OK;
     }
     else if(result) {
-      infof(data, "WS: decode error %d", (int)result);
+      infof(data, "[WS] decode error %d", (int)result);
       return result;
     }
   }
 
   if((type & CLIENTWRITE_EOS) && !Curl_bufq_is_empty(&ctx->buf)) {
-    infof(data, "WS: decode ending with %zd frame bytes remaining",
+    failf(data, "[WS] decode ending with %zd frame bytes remaining",
           Curl_bufq_len(&ctx->buf));
     return CURLE_RECV_ERROR;
   }
@@ -645,10 +705,11 @@ static const struct Curl_cwtype ws_cw_decode = {
 static void ws_enc_info(struct ws_encoder *enc, struct Curl_easy *data,
                         const char *msg)
 {
-  infof(data, "WS-ENC: %s [%s%s payload=%" FMT_OFF_T "/%" FMT_OFF_T "]",
-        msg, ws_frame_name_of_op(enc->firstbyte),
-        (enc->firstbyte & WSBIT_FIN) ? "" : " NON-FIN",
-        enc->payload_len - enc->payload_remain, enc->payload_len);
+  CURL_TRC_WS(data, "WS-ENC: %s [%s%s payload=%"
+              FMT_OFF_T "/%" FMT_OFF_T "]",
+              msg, ws_frame_name_of_op(enc->firstbyte),
+              (enc->firstbyte & WSBIT_FIN) ? "" : " NON-FIN",
+              enc->payload_len - enc->payload_remain, enc->payload_len);
 }
 
 static void ws_enc_reset(struct ws_encoder *enc)
@@ -699,7 +760,7 @@ static ssize_t ws_enc_write_head(struct Curl_easy *data,
   ssize_t n;
 
   if(payload_len < 0) {
-    failf(data, "WS: starting new frame with negative payload length %"
+    failf(data, "[WS] starting new frame with negative payload length %"
                 FMT_OFF_T, payload_len);
     *err = CURLE_SEND_ERROR;
     return -1;
@@ -707,7 +768,7 @@ static ssize_t ws_enc_write_head(struct Curl_easy *data,
 
   if(enc->payload_remain > 0) {
     /* trying to write a new frame before the previous one is finished */
-    failf(data, "WS: starting new frame with %zd bytes from last one "
+    failf(data, "[WS] starting new frame with %zd bytes from last one "
                 "remaining to be sent", (ssize_t)enc->payload_remain);
     *err = CURLE_SEND_ERROR;
     return -1;
@@ -715,7 +776,6 @@ static ssize_t ws_enc_write_head(struct Curl_easy *data,
 
   firstbyte = ws_frame_flags2firstbyte(data, flags, enc->contfragment, err);
   if(*err) {
-    failf(data, "WS: provided flags not valid: %x", flags);
     return -1;
   }
 
@@ -725,6 +785,25 @@ static ssize_t ws_enc_write_head(struct Curl_easy *data,
     enc->contfragment = (flags & CURLWS_CONT) ? (bit)TRUE : (bit)FALSE;
   }
 
+  if(flags & CURLWS_PING && payload_len > 125) {
+    /* The maximum valid size of PING frames is 125 bytes. */
+    failf(data, "[WS] given PING frame is too big");
+    *err = CURLE_TOO_LARGE;
+    return -1;
+  }
+  if(flags & CURLWS_PONG && payload_len > 125) {
+    /* The maximum valid size of PONG frames is 125 bytes. */
+    failf(data, "[WS] given PONG frame is too big");
+    *err = CURLE_TOO_LARGE;
+    return -1;
+  }
+  if(flags & CURLWS_CLOSE && payload_len > 125) {
+    /* The maximum valid size of CLOSE frames is 125 bytes. */
+    failf(data, "[WS] given CLOSE frame is too big");
+    *err = CURLE_TOO_LARGE;
+    return -1;
+  }
+
   head[0] = enc->firstbyte = firstbyte;
   if(payload_len > 65535) {
     head[1] = 127 | WSBIT_MASK;
@@ -952,7 +1031,14 @@ CURLcode Curl_ws_accept(struct Curl_easy *data,
                      sizeof(ws->enc.mask));
   if(result)
     return result;
-  infof(data, "Received 101, switch to WebSocket; mask %02x%02x%02x%02x",
+
+#ifdef DEBUGBUILD
+  if(getenv("CURL_WS_FORCE_ZERO_MASK"))
+    /* force the bit mask to 0x00000000, effectively disabling masking */
+    memset(ws->enc.mask, 0, sizeof(ws->enc.mask));
+#endif
+
+  infof(data, "[WS] Received 101, switch to WebSocket; mask %02x%02x%02x%02x",
         ws->enc.mask[0], ws->enc.mask[1], ws->enc.mask[2], ws->enc.mask[3]);
 
   /* Install our client writer that decodes WS frames payload */
@@ -976,7 +1062,7 @@ CURLcode Curl_ws_accept(struct Curl_easy *data,
                                nread, &result);
     if(nwritten < 0)
       return result;
-    infof(data, "%zu bytes websocket payload", nread);
+    CURL_TRC_WS(data, "%zu bytes payload", nread);
   }
   else { /* !connect_only */
     /* And pass any additional data to the writers */
@@ -1025,7 +1111,7 @@ static ssize_t ws_client_collect(const unsigned char *buf, size_t buflen,
   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");
+    infof(ctx->data, "[WS] auto-respond to PING with a PONG");
     /* send back the exact same content as a PONG */
     *err = curl_ws_send(ctx->data, buf, buflen, &bytes, 0, CURLWS_PONG);
     if(*err)
@@ -1079,19 +1165,19 @@ CURL_EXTERN CURLcode curl_ws_recv(CURL *d, void *buffer,
   if(!conn) {
     /* Unhappy hack with lifetimes of transfers and connection */
     if(!data->set.connect_only) {
-      failf(data, "CONNECT_ONLY is required");
+      failf(data, "[WS] CONNECT_ONLY is required");
       return CURLE_UNSUPPORTED_PROTOCOL;
     }
 
     Curl_getconnectinfo(data, &conn);
     if(!conn) {
-      failf(data, "connection not found");
+      failf(data, "[WS] connection not found");
       return CURLE_BAD_FUNCTION_ARGUMENT;
     }
   }
   ws = Curl_conn_meta_get(conn, CURL_META_PROTO_WS_CONN);
   if(!ws) {
-    failf(data, "connection is not setup for websocket");
+    failf(data, "[WS] connection is not setup for websocket");
     return CURLE_BAD_FUNCTION_ARGUMENT;
   }
 
@@ -1112,7 +1198,7 @@ CURL_EXTERN CURLcode curl_ws_recv(CURL *d, void *buffer,
       }
       else if(n == 0) {
         /* connection closed */
-        infof(data, "connection expectedly closed?");
+        infof(data, "[WS] connection expectedly closed?");
         return CURLE_GOT_NOTHING;
       }
       CURL_TRC_WS(data, "curl_ws_recv, added %zu bytes from network",
@@ -1196,11 +1282,11 @@ static CURLcode ws_flush(struct Curl_easy *data, struct websocket *ws,
         return result;
       }
       else if(result) {
-        failf(data, "WS: flush, write error %d", result);
+        failf(data, "[WS] flush, write error %d", result);
         return result;
       }
       else {
-        infof(data, "WS: flushed %zu bytes", n);
+        CURL_TRC_WS(data, "flushed %zu bytes", n);
         Curl_bufq_skip(&ws->sendbuf, n);
       }
     }
@@ -1232,7 +1318,7 @@ static CURLcode ws_send_raw_blocking(CURL *d, struct websocket *ws,
                   buflen);
       left_ms = Curl_timeleft(data, NULL, FALSE);
       if(left_ms < 0) {
-        failf(data, "Timeout waiting for socket becoming writable");
+        failf(data, "[WS] Timeout waiting for socket becoming writable");
         return CURLE_SEND_ERROR;
       }
 
@@ -1242,7 +1328,7 @@ static CURLcode ws_send_raw_blocking(CURL *d, struct websocket *ws,
       ev = Curl_socket_check(CURL_SOCKET_BAD, CURL_SOCKET_BAD, sock,
                              left_ms ? left_ms : 500);
       if(ev < 0) {
-        failf(data, "Error while waiting for socket becoming writable");
+        failf(data, "[WS] Error while waiting for socket becoming writable");
         return CURLE_SEND_ERROR;
       }
     }
@@ -1258,7 +1344,7 @@ static CURLcode ws_send_raw(struct Curl_easy *data, const void *buffer,
 
   ws = Curl_conn_meta_get(data->conn, CURL_META_PROTO_WS_CONN);
   if(!ws) {
-    failf(data, "Not a websocket transfer");
+    failf(data, "[WS] Not a websocket transfer");
     return CURLE_SEND_ERROR;
   }
   if(!buflen)
@@ -1308,13 +1394,13 @@ CURL_EXTERN CURLcode curl_ws_send(CURL *d, const void *buffer_arg,
       goto out;
   }
   if(!data->conn) {
-    failf(data, "No associated connection");
+    failf(data, "[WS] No associated connection");
     result = CURLE_SEND_ERROR;
     goto out;
   }
   ws = Curl_conn_meta_get(data->conn, CURL_META_PROTO_WS_CONN);
   if(!ws) {
-    failf(data, "Not a websocket transfer");
+    failf(data, "[WS] Not a websocket transfer");
     result = CURLE_SEND_ERROR;
     goto out;
   }
@@ -1327,7 +1413,7 @@ CURL_EXTERN CURLcode curl_ws_send(CURL *d, const void *buffer_arg,
       goto out;
 
     if(fragsize || flags) {
-      failf(data, "ws_send, raw mode: fragsize and flags cannot be non-zero");
+      failf(data, "[WS] fragsize and flags must be zero in raw mode");
       return CURLE_BAD_FUNCTION_ARGUMENT;
     }
     result = ws_send_raw(data, buffer, buflen, sent);
@@ -1342,7 +1428,7 @@ CURL_EXTERN CURLcode curl_ws_send(CURL *d, const void *buffer_arg,
     if(buflen < ws->sendbuf_payload) {
       /* We have been called with LESS buffer data than before. This
        * is not how it's supposed too work. */
-      failf(data, "curl_ws_send() called with smaller 'buflen' than "
+      failf(data, "[WS] curl_ws_send() called with smaller 'buflen' than "
             "bytes already buffered in previous call, %zu vs %zu",
             buflen, ws->sendbuf_payload);
       result = CURLE_BAD_FUNCTION_ARGUMENT;
@@ -1351,7 +1437,7 @@ CURL_EXTERN CURLcode curl_ws_send(CURL *d, const void *buffer_arg,
     if((curl_off_t)buflen >
        (ws->enc.payload_remain + (curl_off_t)ws->sendbuf_payload)) {
       /* too large buflen beyond payload length of frame */
-      infof(data, "WS: unaligned frame size (sending %zu instead of %"
+      failf(data, "[WS] unaligned frame size (sending %zu instead of %"
                   FMT_OFF_T ")",
             buflen, ws->enc.payload_remain + ws->sendbuf_payload);
       result = CURLE_BAD_FUNCTION_ARGUMENT;
@@ -1392,7 +1478,7 @@ CURL_EXTERN CURLcode curl_ws_send(CURL *d, const void *buffer_arg,
 
     /* flush, blocking when in callback */
     result = ws_flush(data, ws, Curl_is_in_callback(data));
-    if(!result) {
+    if(!result && ws->sendbuf_payload > 0) {
       *sent += ws->sendbuf_payload;
       buffer += ws->sendbuf_payload;
       buflen -= ws->sendbuf_payload;
index 63cde06b02b42778a7d86a8236f0a4bd38034966..1b07400d8586f490176fe8d06bbf8c1c13d3cb14 100644 (file)
@@ -258,8 +258,8 @@ test2100 test2101 test2102 \
 \
 test2200 test2201 test2202 test2203 test2204 test2205 \
 \
-test2300 test2301 test2302 test2303 test2304 test2305 test2306 test2307 \
-test2308 test2309 test2310 test2311 test2312 \
+test2300 test2301 test2302 test2303 test2304 test2306 \
+test2308 test2309 \
 \
 test2400 test2401 test2402 test2403 test2404 test2405 test2406 \
 \
@@ -267,6 +267,10 @@ test2500 test2501 test2502 test2503 \
 \
 test2600 test2601 test2602 test2603 test2604 \
 \
+test2700 test2701 test2702 test2703 test2704 test2705 test2706 test2707 \
+test2708 test2709 test2710 test2711 test2712 test2713 test2714 test2715 \
+test2716 test2717 test2718 test2719 test2720 test2721 test2722 test2723 \
+\
 test3000 test3001 test3002 test3003 test3004 test3005 test3006 test3007 \
 test3008 test3009 test3010 test3011 test3012 test3013 test3014 test3015 \
 test3016 test3017 test3018 test3019 test3020 test3021 test3022 test3023 \
index 8ade3e7718de808846dc6dd92e998a314b338351..7a0324c50ffe5a28fabb4fa2add2cebe37e7bdf0 100644 (file)
@@ -6,17 +6,16 @@ WebSockets
 </info>
 
 #
-# Sends a PING + a 5 byte hello TEXT
+# Close connection during websocket upgrade
 <reply>
 <data nocheck="yes" nonewline="yes">
-HTTP/1.1 101 Switching to WebSockets swsclose\r
-Server: test-server/fake\r
-Upgrade: websocket\r
-Connection: Upgrade\r
-Something: else\r
-Sec-WebSocket-Accept: HkPsVga7+8LuxM4RGQ5p9tZHeYs=\r
-\r
-%hex[%89%00%81%05hello]hex%
+HTTP/1.1 101 Switching to WebSockets swsclose
+Server: test-server/fake
+Upgrade: websocket
+Connection: Upgrade
+Something: else
+Sec-WebSocket-Accept: HkPsVga7+8LuxM4RGQ5p9tZHeYs=
+
 </data>
 # allow upgrade
 <servercmd>
@@ -53,7 +52,7 @@ ws://%HOSTIP:%HTTPPORT/%TESTNUMBER
 <protocol nocheck="yes" crlf="yes">
 GET /%TESTNUMBER HTTP/1.1
 Host: %HOSTIP:%HTTPPORT
-User-Agent: websocket/2304
+User-Agent: websocket/%TESTNUMBER
 Accept: */*
 Upgrade: websocket
 Connection: Upgrade
diff --git a/tests/data/test2305 b/tests/data/test2305
deleted file mode 100644 (file)
index 773b5df..0000000
+++ /dev/null
@@ -1,59 +0,0 @@
-<testcase>
-<info>
-<keywords>
-WebSockets
-</keywords>
-</info>
-
-#
-# Sends three 4097 bytes TEXT frames, as one single message
-<reply>
-<data nocheck="yes">
-HTTP/1.1 101 Switching to WebSockets\r
-Server: test-server/fake\r
-Upgrade: websocket\r
-Connection: Upgrade\r
-Something: else\r
-Sec-WebSocket-Accept: HkPsVga7+8LuxM4RGQ5p9tZHeYs=\r
-\r
-%hex[%01%7e%10%01]hex%%repeat[256 x helothisisdaniel]%
-%hex[%01%7e%10%01]hex%%repeat[256 x helothisisdaniel]%
-%hex[%81%7e%10%01]hex%%repeat[256 x helothisisdaniel]%
-</data>
-# allow upgrade
-<servercmd>
-upgrade
-</servercmd>
-</reply>
-
-#
-# Client-side
-<client>
-# require debug for the forced CURL_ENTROPY
-<features>
-Debug
-ws
-</features>
-<server>
-http
-</server>
-<name>
-WebSocket curl_ws_recv() loop reading three larger frames
-</name>
-<tool>
-lib%TESTNUMBER
-</tool>
-<command>
-ws://%HOSTIP:%HTTPPORT/%TESTNUMBER %LOGDIR/save%TESTNUMBER
-</command>
-</client>
-
-#
-<verify>
-<file name="%LOGDIR/save%TESTNUMBER" mode="text">
-%repeat[256 x helothisisdaniel]%
-%repeat[256 x helothisisdaniel]%
-%repeat[256 x helothisisdaniel]%
-</file>
-</verify>
-</testcase>
diff --git a/tests/data/test2307 b/tests/data/test2307
deleted file mode 100644 (file)
index b51b08d..0000000
+++ /dev/null
@@ -1,70 +0,0 @@
-<testcase>
-<info>
-<keywords>
-WebSockets
-</keywords>
-</info>
-
-#
-# Sends a PING with overlong payload
-<reply>
-<data nocheck="yes" nonewline="yes">
-HTTP/1.1 101 Switching to WebSockets
-Server: test-server/fake
-Upgrade: websocket
-Connection: Upgrade
-Something: else
-Sec-WebSocket-Accept: HkPsVga7+8LuxM4RGQ5p9tZHeYs=
-
-%hex[%19%7f%ff%30%30%30%30%30%30%30%30%30%30%30%30]hex%
-</data>
-# allow upgrade
-<servercmd>
-upgrade
-</servercmd>
-</reply>
-
-#
-# Client-side
-<client>
-# require debug for the forced CURL_ENTROPY
-<features>
-Debug
-ws
-</features>
-<server>
-http
-</server>
-<name>
-WebSockets, overlong PING payload
-</name>
-<tool>
-lib2302
-</tool>
-<command>
-ws://%HOSTIP:%HTTPPORT/%TESTNUMBER
-</command>
-</client>
-
-#
-# PONG with no data and the 32 bit mask
-#
-<verify>
-<protocol nocheck="yes" nonewline="yes">
-GET /%TESTNUMBER HTTP/1.1\r
-Host: %HOSTIP:%HTTPPORT\r
-User-Agent: webbie-sox/3\r
-Accept: */*\r
-Upgrade: websocket\r
-Connection: Upgrade\r
-Sec-WebSocket-Version: 13\r
-Sec-WebSocket-Key: NDMyMTUzMjE2MzIxNzMyMQ==\r
-\r
-
-</protocol>
-# 56 == CURLE_RECV_ERROR
-<errorcode>
-56
-</errorcode>
-</verify>
-</testcase>
diff --git a/tests/data/test2310 b/tests/data/test2310
deleted file mode 100644 (file)
index a5338c5..0000000
+++ /dev/null
@@ -1,68 +0,0 @@
-<testcase>
-<info>
-<keywords>
-WebSockets
-</keywords>
-</info>
-
-#
-# Sends a PING + a TEXT with RSV1 set
-<reply>
-<data nocheck="yes" nonewline="yes">
-HTTP/1.1 101 Switching to WebSockets\r
-Server: test-server/fake\r
-Upgrade: websocket\r
-Connection: Upgrade\r
-Sec-WebSocket-Accept: HkPsVga7+8LuxM4RGQ5p9tZHeYs=\r
-\r
-%hex[%89%00%C1%05hello]hex%
-</data>
-# allow upgrade
-<servercmd>
-upgrade
-</servercmd>
-</reply>
-
-#
-# Client-side
-<client>
-# require debug for the forced CURL_ENTROPY
-<features>
-Debug
-ws
-</features>
-<server>
-http
-</server>
-<name>
-WebSockets unknown reserved bit set in frame header
-</name>
-<tool>
-lib%TESTNUMBER
-</tool>
-<command>
-ws://%HOSTIP:%HTTPPORT/%TESTNUMBER
-</command>
-</client>
-
-#
-# PONG with no data and the 32 bit mask
-#
-<verify>
-<protocol nocheck="yes" nonewline="yes">
-GET /%TESTNUMBER HTTP/1.1\r
-Host: %HOSTIP:%HTTPPORT\r
-User-Agent: webbie-sox/3\r
-Accept: */*\r
-Upgrade: websocket\r
-Connection: Upgrade\r
-Sec-WebSocket-Version: 13\r
-Sec-WebSocket-Key: NDMyMTUzMjE2MzIxNzMyMQ==\r
-\r
-%hex[%8a%808321]hex%
-</protocol>
-<stdout mode="text">
-Returned 56, should be 56.
-</stdout>
-</verify>
-</testcase>
diff --git a/tests/data/test2311 b/tests/data/test2311
deleted file mode 100644 (file)
index 9ca25eb..0000000
+++ /dev/null
@@ -1,59 +0,0 @@
-<testcase>
-<info>
-<keywords>
-WebSockets
-</keywords>
-</info>
-
-#
-# Sends three 4097 bytes TEXT frames, as fragments of one 12291 bytes message
-<reply>
-<data nocheck="yes">
-HTTP/1.1 101 Switching to WebSockets
-Server: test-server/fake
-Upgrade: websocket
-Connection: Upgrade
-Something: else
-Sec-WebSocket-Accept: HkPsVga7+8LuxM4RGQ5p9tZHeYs=
-
-%hex[%01%7e%10%01]hex%%repeat[256 x helothisisdaniel]%
-%hex[%00%7e%10%01]hex%%repeat[256 x helothisisdaniel]%
-%hex[%80%7e%10%01]hex%%repeat[256 x helothisisdaniel]%
-</data>
-# allow upgrade
-<servercmd>
-upgrade
-</servercmd>
-</reply>
-
-#
-# Client-side
-<client>
-# require debug for the forced CURL_ENTROPY
-<features>
-Debug
-ws
-</features>
-<server>
-http
-</server>
-<name>
-WebSocket curl_ws_recv() read fragmented message
-</name>
-<tool>
-lib%TESTNUMBER
-</tool>
-<command>
-ws://%HOSTIP:%HTTPPORT/%TESTNUMBER %LOGDIR/save%TESTNUMBER
-</command>
-</client>
-
-#
-<verify>
-<file name="%LOGDIR/save%TESTNUMBER" mode="text">
-%repeat[256 x helothisisdaniel]%
-%repeat[256 x helothisisdaniel]%
-%repeat[256 x helothisisdaniel]%
-</file>
-</verify>
-</testcase>
diff --git a/tests/data/test2312 b/tests/data/test2312
deleted file mode 100644 (file)
index 964381d..0000000
+++ /dev/null
@@ -1,66 +0,0 @@
-<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>
diff --git a/tests/data/test2700 b/tests/data/test2700
new file mode 100644 (file)
index 0000000..e9c5863
--- /dev/null
@@ -0,0 +1,76 @@
+<testcase>
+<info>
+<keywords>
+WebSockets
+</keywords>
+</info>
+
+<client>
+<name>
+ws: Frame types
+</name>
+<features>
+Debug
+ws
+</features>
+<server>
+http
+</server>
+<tool>
+lib2700
+</tool>
+<command>
+ws://%HOSTIP:%HTTPPORT/%TESTNUMBER
+</command>
+<setenv>
+CURL_WS_FORCE_ZERO_MASK=1
+</setenv>
+</client>
+
+<reply>
+<servercmd>
+upgrade
+</servercmd>
+
+# Full list of frames: see <verify.stdout> below
+#   A TEXT/BINARY/PING/PONG/CLOSE message with payload
+<data nocheck="yes" nonewline="yes">
+HTTP/1.1 101 Switching to WebSockets
+Server: server/%TESTNUMBER
+Upgrade: Websocket
+Connection: Upgrade
+Sec-WebSocket-Accept: HkPsVga7+8LuxM4RGQ5p9tZHeYs=
+
+%hex[%81%03txt]hex%%hex[%82%03bin]hex%%hex[%89%04ping]hex%%hex[%8a%04pong]hex%%hex[%88%07%03%e8close]hex%
+</data>
+</reply>
+
+<verify>
+
+# Exact echo of reply data with additional masking
+<protocol nonewline="yes">
+%hex[%81%83%00%00%00%00txt]hex%%hex[%82%83%00%00%00%00bin]hex%%hex[%89%84%00%00%00%00ping]hex%%hex[%8a%84%00%00%00%00pong]hex%%hex[%88%87%00%00%00%00%03%e8close]hex%
+</protocol>
+
+# PING is handled by lib and never given to application
+<stdout>
+txt fin [3] txt
+bin fin [3] bin
+ping [4] ping
+pong [4] pong
+close [7] %hex[%03%e8]hex%close
+</stdout>
+
+# CURLE_OK
+<errorcode>
+0
+</errorcode>
+
+# Strip HTTP header from <protocol>
+<strip>
+^GET /.*
+^(Host|User-Agent|Accept|Upgrade|Connection|Sec-WebSocket-(Version|Key)): .*
+^\s*$
+</strip>
+</verify>
+</testcase>
diff --git a/tests/data/test2701 b/tests/data/test2701
new file mode 100644 (file)
index 0000000..3f810a6
--- /dev/null
@@ -0,0 +1,72 @@
+<testcase>
+<info>
+<keywords>
+WebSockets
+</keywords>
+</info>
+
+<client>
+<name>
+ws: Invalid opcode 0x3
+</name>
+<features>
+Debug
+ws
+</features>
+<server>
+http
+</server>
+<tool>
+lib2700
+</tool>
+<command>
+ws://%HOSTIP:%HTTPPORT/%TESTNUMBER
+</command>
+<setenv>
+CURL_WS_FORCE_ZERO_MASK=1
+</setenv>
+</client>
+
+<reply>
+<servercmd>
+upgrade
+</servercmd>
+
+# Full list of frames: see <verify.stdout> below
+#   An empty frame with the reserved opcode 0x3
+<data nocheck="yes" nonewline="yes">
+HTTP/1.1 101 Switching to WebSockets
+Server: server/%TESTNUMBER
+Upgrade: Websocket
+Connection: Upgrade
+Sec-WebSocket-Accept: HkPsVga7+8LuxM4RGQ5p9tZHeYs=
+
+%hex[%83%00]hex%
+</data>
+</reply>
+
+<verify>
+
+# No frames
+<protocol nonewline="yes">
+
+</protocol>
+
+# No frames
+<stdout nonewline="yes">
+
+</stdout>
+
+# CURLE_RECV_ERROR
+<errorcode>
+56
+</errorcode>
+
+# Strip HTTP header from <protocol>
+<strip>
+^GET /.*
+^(Host|User-Agent|Accept|Upgrade|Connection|Sec-WebSocket-(Version|Key)): .*
+^\s*$
+</strip>
+</verify>
+</testcase>
diff --git a/tests/data/test2702 b/tests/data/test2702
new file mode 100644 (file)
index 0000000..1c309a0
--- /dev/null
@@ -0,0 +1,72 @@
+<testcase>
+<info>
+<keywords>
+WebSockets
+</keywords>
+</info>
+
+<client>
+<name>
+ws: Invalid opcode 0xB
+</name>
+<features>
+Debug
+ws
+</features>
+<server>
+http
+</server>
+<tool>
+lib2700
+</tool>
+<command>
+ws://%HOSTIP:%HTTPPORT/%TESTNUMBER
+</command>
+<setenv>
+CURL_WS_FORCE_ZERO_MASK=1
+</setenv>
+</client>
+
+<reply>
+<servercmd>
+upgrade
+</servercmd>
+
+# Full list of frames: see <verify.stdout> below
+#   An empty frame with the reserved opcode 0xB
+<data nocheck="yes" nonewline="yes">
+HTTP/1.1 101 Switching to WebSockets
+Server: server/%TESTNUMBER
+Upgrade: Websocket
+Connection: Upgrade
+Sec-WebSocket-Accept: HkPsVga7+8LuxM4RGQ5p9tZHeYs=
+
+%hex[%8b%00]hex%
+</data>
+</reply>
+
+<verify>
+
+# No frames
+<protocol nonewline="yes">
+
+</protocol>
+
+# No frames
+<stdout nonewline="yes">
+
+</stdout>
+
+# CURLE_RECV_ERROR
+<errorcode>
+56
+</errorcode>
+
+# Strip HTTP header from <protocol>
+<strip>
+^GET /.*
+^(Host|User-Agent|Accept|Upgrade|Connection|Sec-WebSocket-(Version|Key)): .*
+^\s*$
+</strip>
+</verify>
+</testcase>
diff --git a/tests/data/test2703 b/tests/data/test2703
new file mode 100644 (file)
index 0000000..627c2bc
--- /dev/null
@@ -0,0 +1,72 @@
+<testcase>
+<info>
+<keywords>
+WebSockets
+</keywords>
+</info>
+
+<client>
+<name>
+ws: Invalid reserved bit RSV1
+</name>
+<features>
+Debug
+ws
+</features>
+<server>
+http
+</server>
+<tool>
+lib2700
+</tool>
+<command>
+ws://%HOSTIP:%HTTPPORT/%TESTNUMBER
+</command>
+<setenv>
+CURL_WS_FORCE_ZERO_MASK=1
+</setenv>
+</client>
+
+<reply>
+<servercmd>
+upgrade
+</servercmd>
+
+# Full list of frames: see <verify.stdout> below
+#   An empty frame with the RSV1 bit set
+<data nocheck="yes" nonewline="yes">
+HTTP/1.1 101 Switching to WebSockets
+Server: server/%TESTNUMBER
+Upgrade: Websocket
+Connection: Upgrade
+Sec-WebSocket-Accept: HkPsVga7+8LuxM4RGQ5p9tZHeYs=
+
+%hex[%C1%00]hex%
+</data>
+</reply>
+
+<verify>
+
+# No frames
+<protocol nonewline="yes">
+
+</protocol>
+
+# No frames
+<stdout nonewline="yes">
+
+</stdout>
+
+# CURLE_RECV_ERROR
+<errorcode>
+56
+</errorcode>
+
+# Strip HTTP header from <protocol>
+<strip>
+^GET /.*
+^(Host|User-Agent|Accept|Upgrade|Connection|Sec-WebSocket-(Version|Key)): .*
+^\s*$
+</strip>
+</verify>
+</testcase>
diff --git a/tests/data/test2704 b/tests/data/test2704
new file mode 100644 (file)
index 0000000..d00983a
--- /dev/null
@@ -0,0 +1,72 @@
+<testcase>
+<info>
+<keywords>
+WebSockets
+</keywords>
+</info>
+
+<client>
+<name>
+ws: Invalid reserved bit RSV2
+</name>
+<features>
+Debug
+ws
+</features>
+<server>
+http
+</server>
+<tool>
+lib2700
+</tool>
+<command>
+ws://%HOSTIP:%HTTPPORT/%TESTNUMBER
+</command>
+<setenv>
+CURL_WS_FORCE_ZERO_MASK=1
+</setenv>
+</client>
+
+<reply>
+<servercmd>
+upgrade
+</servercmd>
+
+# Full list of frames: see <verify.stdout> below
+#   An empty frame with the RSV2 bit set
+<data nocheck="yes" nonewline="yes">
+HTTP/1.1 101 Switching to WebSockets
+Server: server/%TESTNUMBER
+Upgrade: Websocket
+Connection: Upgrade
+Sec-WebSocket-Accept: HkPsVga7+8LuxM4RGQ5p9tZHeYs=
+
+%hex[%A1%00]hex%
+</data>
+</reply>
+
+<verify>
+
+# No frames
+<protocol nonewline="yes">
+
+</protocol>
+
+# No frames
+<stdout nonewline="yes">
+
+</stdout>
+
+# CURLE_RECV_ERROR
+<errorcode>
+56
+</errorcode>
+
+# Strip HTTP header from <protocol>
+<strip>
+^GET /.*
+^(Host|User-Agent|Accept|Upgrade|Connection|Sec-WebSocket-(Version|Key)): .*
+^\s*$
+</strip>
+</verify>
+</testcase>
diff --git a/tests/data/test2705 b/tests/data/test2705
new file mode 100644 (file)
index 0000000..e94b9c4
--- /dev/null
@@ -0,0 +1,72 @@
+<testcase>
+<info>
+<keywords>
+WebSockets
+</keywords>
+</info>
+
+<client>
+<name>
+ws: Invalid reserved bit RSV3
+</name>
+<features>
+Debug
+ws
+</features>
+<server>
+http
+</server>
+<tool>
+lib2700
+</tool>
+<command>
+ws://%HOSTIP:%HTTPPORT/%TESTNUMBER
+</command>
+<setenv>
+CURL_WS_FORCE_ZERO_MASK=1
+</setenv>
+</client>
+
+<reply>
+<servercmd>
+upgrade
+</servercmd>
+
+# Full list of frames: see <verify.stdout> below
+#   An empty frame with the RSV3 bit set
+<data nocheck="yes" nonewline="yes">
+HTTP/1.1 101 Switching to WebSockets
+Server: server/%TESTNUMBER
+Upgrade: Websocket
+Connection: Upgrade
+Sec-WebSocket-Accept: HkPsVga7+8LuxM4RGQ5p9tZHeYs=
+
+%hex[%91%00]hex%
+</data>
+</reply>
+
+<verify>
+
+# No frames
+<protocol nonewline="yes">
+
+</protocol>
+
+# No frames
+<stdout nonewline="yes">
+
+</stdout>
+
+# CURLE_RECV_ERROR
+<errorcode>
+56
+</errorcode>
+
+# Strip HTTP header from <protocol>
+<strip>
+^GET /.*
+^(Host|User-Agent|Accept|Upgrade|Connection|Sec-WebSocket-(Version|Key)): .*
+^\s*$
+</strip>
+</verify>
+</testcase>
diff --git a/tests/data/test2706 b/tests/data/test2706
new file mode 100644 (file)
index 0000000..174dc44
--- /dev/null
@@ -0,0 +1,72 @@
+<testcase>
+<info>
+<keywords>
+WebSockets
+</keywords>
+</info>
+
+<client>
+<name>
+ws: Invalid masked server message
+</name>
+<features>
+Debug
+ws
+</features>
+<server>
+http
+</server>
+<tool>
+lib2700
+</tool>
+<command>
+ws://%HOSTIP:%HTTPPORT/%TESTNUMBER
+</command>
+<setenv>
+CURL_WS_FORCE_ZERO_MASK=1
+</setenv>
+</client>
+
+<reply>
+<servercmd>
+upgrade
+</servercmd>
+
+# Full list of frames: see <verify.stdout> below
+#   An empty frame with masking
+<data nocheck="yes" nonewline="yes">
+HTTP/1.1 101 Switching to WebSockets
+Server: server/%TESTNUMBER
+Upgrade: Websocket
+Connection: Upgrade
+Sec-WebSocket-Accept: HkPsVga7+8LuxM4RGQ5p9tZHeYs=
+
+%hex[%81%80%12%34%56%78]hex%
+</data>
+</reply>
+
+<verify>
+
+# No frames
+<protocol nonewline="yes">
+
+</protocol>
+
+# No frames
+<stdout nonewline="yes">
+
+</stdout>
+
+# CURLE_RECV_ERROR
+<errorcode>
+56
+</errorcode>
+
+# Strip HTTP header from <protocol>
+<strip>
+^GET /.*
+^(Host|User-Agent|Accept|Upgrade|Connection|Sec-WebSocket-(Version|Key)): .*
+^\s*$
+</strip>
+</verify>
+</testcase>
diff --git a/tests/data/test2707 b/tests/data/test2707
new file mode 100644 (file)
index 0000000..c87a73f
--- /dev/null
@@ -0,0 +1,84 @@
+<testcase>
+<info>
+<keywords>
+WebSockets
+</keywords>
+</info>
+
+<client>
+<name>
+ws: Peculiar frame sizes
+</name>
+<features>
+Debug
+ws
+</features>
+<server>
+http
+</server>
+<tool>
+lib2700
+</tool>
+<command>
+ws://%HOSTIP:%HTTPPORT/%TESTNUMBER
+</command>
+<setenv>
+CURL_WS_FORCE_ZERO_MASK=1
+</setenv>
+</client>
+
+<reply>
+<servercmd>
+upgrade
+</servercmd>
+
+# Full list of frames: see <verify.stdout> below
+# Frames with sizes around special cases of the frame encoding
+#   see https://datatracker.ietf.org/doc/html/rfc6455#section-5.2
+#    - 0: empty frame
+#    - 125: largest payload with 7-bit encoding
+#    - 126: smallest payload with 16-bit encoding AND this value in first byte indicates 16-bit encoding
+#    - 127: this value in first byte indicates 64-bit encoding
+#    - 65535: largest payload with 16-bit length encoding
+#    - 65536: smallest payload with 64-bit length encoding
+<data nocheck="yes" nonewline="yes">
+HTTP/1.1 101 Switching to WebSockets
+Server: server/%TESTNUMBER
+Upgrade: Websocket
+Connection: Upgrade
+Sec-WebSocket-Accept: HkPsVga7+8LuxM4RGQ5p9tZHeYs=
+
+%hex[%81%00]hex%%hex[%81%7d]hex%%repeat[125 x _]%%hex[%81%7e%00%7e]hex%%repeat[126 x _]%%hex[%81%7e%00%7f]hex%%repeat[127 x _]%%hex[%81%7e%ff%ff]hex%%repeat[65535 x _]%%hex[%81%7f%00%00%00%00%00%01%00%00]hex%%repeat[65536 x _]%%hex[%88%00]hex%
+</data>
+</reply>
+
+<verify>
+
+# Exact echo of reply data with additional masking
+<protocol nonewline="yes">
+%hex[%81%80%00%00%00%00]hex%%hex[%81%fd%00%00%00%00]hex%%repeat[125 x _]%%hex[%81%fe%00%7e%00%00%00%00]hex%%repeat[126 x _]%%hex[%81%fe%00%7f%00%00%00%00]hex%%repeat[127 x _]%%hex[%81%fe%ff%ff%00%00%00%00]hex%%repeat[65535 x _]%%hex[%81%ff%00%00%00%00%00%01%00%00%00%00%00%00]hex%%repeat[65536 x _]%%hex[%88%80%00%00%00%00]hex%
+</protocol>
+
+<stdout>
+txt fin [0]
+txt fin [125] %repeat[125 x _]%
+txt fin [126] %repeat[126 x _]%
+txt fin [127] %repeat[127 x _]%
+txt fin [65535] %repeat[65535 x _]%
+txt fin [65536] %repeat[65536 x _]%
+close [0]
+</stdout>
+
+# CURLE_OK
+<errorcode>
+0
+</errorcode>
+
+# Strip HTTP header from <protocol>
+<strip>
+^GET /.*
+^(Host|User-Agent|Accept|Upgrade|Connection|Sec-WebSocket-(Version|Key)): .*
+^\s*$
+</strip>
+</verify>
+</testcase>
diff --git a/tests/data/test2708 b/tests/data/test2708
new file mode 100644 (file)
index 0000000..fd0ee07
--- /dev/null
@@ -0,0 +1,75 @@
+<testcase>
+<info>
+<keywords>
+WebSockets
+</keywords>
+</info>
+
+<client>
+<name>
+ws: Automatic PONG
+</name>
+<features>
+Debug
+ws
+</features>
+<server>
+http
+</server>
+<tool>
+lib2700
+</tool>
+<command>
+ws://%HOSTIP:%HTTPPORT/%TESTNUMBER
+</command>
+<setenv>
+CURL_WS_FORCE_ZERO_MASK=1
+LIB2700_AUTO_PONG=1
+</setenv>
+</client>
+
+<reply>
+<servercmd>
+upgrade
+</servercmd>
+
+# PING "test"
+# CLOSE
+<data nocheck="yes" nonewline="yes">
+HTTP/1.1 101 Switching to WebSockets
+Server: server/%TESTNUMBER
+Upgrade: Websocket
+Connection: Upgrade
+Sec-WebSocket-Accept: HkPsVga7+8LuxM4RGQ5p9tZHeYs=
+
+%hex[%89%04test]hex%%hex[%88%00]hex%
+</data>
+</reply>
+
+<verify>
+
+# PONG "test" (payload MUST equal ping payload)
+#   see https://datatracker.ietf.org/doc/html/rfc6455#section-5.2
+# CLOSE
+<protocol nonewline="yes">
+%hex[%8A%84%00%00%00%00test%88%80%00%00%00%00]hex%
+</protocol>
+
+# PING is handled by lib and never given to application
+<stdout>
+close [0]
+</stdout>
+
+# CURLE_OK
+<errorcode>
+0
+</errorcode>
+
+# Strip HTTP header from <protocol>
+<strip>
+^GET /.*
+^(Host|User-Agent|Accept|Upgrade|Connection|Sec-WebSocket-(Version|Key)): .*
+^\s*$
+</strip>
+</verify>
+</testcase>
diff --git a/tests/data/test2709 b/tests/data/test2709
new file mode 100644 (file)
index 0000000..654f4bf
--- /dev/null
@@ -0,0 +1,73 @@
+<testcase>
+<info>
+<keywords>
+WebSockets
+</keywords>
+</info>
+
+<client>
+<name>
+ws: No automatic PONG
+</name>
+<features>
+Debug
+ws
+</features>
+<server>
+http
+</server>
+<tool>
+lib2700
+</tool>
+<command>
+ws://%HOSTIP:%HTTPPORT/%TESTNUMBER
+</command>
+<setenv>
+CURL_WS_FORCE_ZERO_MASK=1
+</setenv>
+</client>
+
+<reply>
+<servercmd>
+upgrade
+</servercmd>
+
+# PING "test"
+# CLOSE
+<data nocheck="yes" nonewline="yes">
+HTTP/1.1 101 Switching to WebSockets
+Server: server/%TESTNUMBER
+Upgrade: Websocket
+Connection: Upgrade
+Sec-WebSocket-Accept: HkPsVga7+8LuxM4RGQ5p9tZHeYs=
+
+%hex[%89%04test]hex%%hex[%88%00]hex%
+</data>
+</reply>
+
+<verify>
+
+# Exact echo of reply data with additional masking
+<protocol nonewline="yes">
+%hex[%89%84%00%00%00%00test]hex%%hex[%88%80%00%00%00%00]hex%
+</protocol>
+
+# PING is given to application
+<stdout>
+ping [4] test
+close [0]
+</stdout>
+
+# CURLE_OK
+<errorcode>
+0
+</errorcode>
+
+# Strip HTTP header from <protocol>
+<strip>
+^GET /.*
+^(Host|User-Agent|Accept|Upgrade|Connection|Sec-WebSocket-(Version|Key)): .*
+^\s*$
+</strip>
+</verify>
+</testcase>
diff --git a/tests/data/test2710 b/tests/data/test2710
new file mode 100644 (file)
index 0000000..5501104
--- /dev/null
@@ -0,0 +1,73 @@
+<testcase>
+<info>
+<keywords>
+WebSockets
+</keywords>
+</info>
+
+<client>
+<name>
+ws: Unsolicited PONG
+</name>
+<features>
+Debug
+ws
+</features>
+<server>
+http
+</server>
+<tool>
+lib2700
+</tool>
+<command>
+ws://%HOSTIP:%HTTPPORT/%TESTNUMBER
+</command>
+<setenv>
+CURL_WS_FORCE_ZERO_MASK=1
+</setenv>
+</client>
+
+<reply>
+<servercmd>
+upgrade
+</servercmd>
+
+# Full list of frames: see <verify.stdout> below
+#   An unsolicited PONG with and without payload
+<data nocheck="yes" nonewline="yes">
+HTTP/1.1 101 Switching to WebSockets
+Server: server/%TESTNUMBER
+Upgrade: Websocket
+Connection: Upgrade
+Sec-WebSocket-Accept: HkPsVga7+8LuxM4RGQ5p9tZHeYs=
+
+%hex[%8a%00]hex%%hex[%8a%04pong]hex%%hex[%88%00]hex%
+</data>
+</reply>
+
+<verify>
+
+# Exact echo of reply data with additional masking
+<protocol nonewline="yes">
+%hex[%8a%80%00%00%00%00]hex%%hex[%8a%84%00%00%00%00pong]hex%%hex[%88%80%00%00%00%00]hex%
+</protocol>
+
+<stdout>
+pong [0]
+pong [4] pong
+close [0]
+</stdout>
+
+# CURLE_OK
+<errorcode>
+0
+</errorcode>
+
+# Strip HTTP header from <protocol>
+<strip>
+^GET /.*
+^(Host|User-Agent|Accept|Upgrade|Connection|Sec-WebSocket-(Version|Key)): .*
+^\s*$
+</strip>
+</verify>
+</testcase>
diff --git a/tests/data/test2711 b/tests/data/test2711
new file mode 100644 (file)
index 0000000..31b40b5
--- /dev/null
@@ -0,0 +1,74 @@
+<testcase>
+<info>
+<keywords>
+WebSockets
+</keywords>
+</info>
+
+<client>
+<name>
+ws: Empty PING/PONG/CLOSE
+</name>
+<features>
+Debug
+ws
+</features>
+<server>
+http
+</server>
+<tool>
+lib2700
+</tool>
+<command>
+ws://%HOSTIP:%HTTPPORT/%TESTNUMBER
+</command>
+<setenv>
+CURL_WS_FORCE_ZERO_MASK=1
+</setenv>
+</client>
+
+<reply>
+<servercmd>
+upgrade
+</servercmd>
+
+# Full list of frames: see <verify.stdout> below
+#   A PING/PONG/CLOSE message without payload
+<data nocheck="yes" nonewline="yes">
+HTTP/1.1 101 Switching to WebSockets
+Server: server/%TESTNUMBER
+Upgrade: Websocket
+Connection: Upgrade
+Sec-WebSocket-Accept: HkPsVga7+8LuxM4RGQ5p9tZHeYs=
+
+%hex[%89%00]hex%%hex[%8a%00]hex%%hex[%88%00]hex%
+</data>
+</reply>
+
+<verify>
+
+# Exact echo of reply data with additional masking
+<protocol nonewline="yes">
+%hex[%89%80%00%00%00%00]hex%%hex[%8a%80%00%00%00%00]hex%%hex[%88%80%00%00%00%00]hex%
+</protocol>
+
+# PING is handled by lib and never given to application
+<stdout>
+ping [0]
+pong [0]
+close [0]
+</stdout>
+
+# CURLE_OK
+<errorcode>
+0
+</errorcode>
+
+# Strip HTTP header from <protocol>
+<strip>
+^GET /.*
+^(Host|User-Agent|Accept|Upgrade|Connection|Sec-WebSocket-(Version|Key)): .*
+^\s*$
+</strip>
+</verify>
+</testcase>
diff --git a/tests/data/test2712 b/tests/data/test2712
new file mode 100644 (file)
index 0000000..d1182c3
--- /dev/null
@@ -0,0 +1,74 @@
+<testcase>
+<info>
+<keywords>
+WebSockets
+</keywords>
+</info>
+
+<client>
+<name>
+ws: Max sized PING/PONG/CLOSE
+</name>
+<features>
+Debug
+ws
+</features>
+<server>
+http
+</server>
+<tool>
+lib2700
+</tool>
+<command>
+ws://%HOSTIP:%HTTPPORT/%TESTNUMBER
+</command>
+<setenv>
+CURL_WS_FORCE_ZERO_MASK=1
+</setenv>
+</client>
+
+<reply>
+<servercmd>
+upgrade
+</servercmd>
+
+# Full list of frames: see <verify.stdout> below
+#   A PING/PONG/CLOSE with 125 bytes payload each
+<data nocheck="yes" nonewline="yes">
+HTTP/1.1 101 Switching to WebSockets
+Server: server/%TESTNUMBER
+Upgrade: Websocket
+Connection: Upgrade
+Sec-WebSocket-Accept: HkPsVga7+8LuxM4RGQ5p9tZHeYs=
+
+%hex[%89%7d]hex%%repeat[125 x _]%%hex[%8a%7d]hex%%repeat[125 x _]%%hex[%88%7d%03%e8]hex%%repeat[123 x _]%
+</data>
+</reply>
+
+<verify>
+
+# Exact echo of reply data with additional masking
+<protocol nonewline="yes">
+%hex[%89%fd%00%00%00%00]hex%%repeat[125 x _]%%hex[%8a%fd%00%00%00%00]hex%%repeat[125 x _]%%hex[%88%fd%00%00%00%00%03%e8]hex%%repeat[123 x _]%
+</protocol>
+
+# PING is handled by lib and never given to application
+<stdout>
+ping [125] %repeat[125 x _]%
+pong [125] %repeat[125 x _]%
+close [125] %hex[%03%e8]hex%%repeat[123 x _]%
+</stdout>
+
+# CURLE_OK
+<errorcode>
+0
+</errorcode>
+
+# Strip HTTP header from <protocol>
+<strip>
+^GET /.*
+^(Host|User-Agent|Accept|Upgrade|Connection|Sec-WebSocket-(Version|Key)): .*
+^\s*$
+</strip>
+</verify>
+</testcase>
diff --git a/tests/data/test2713 b/tests/data/test2713
new file mode 100644 (file)
index 0000000..55188e8
--- /dev/null
@@ -0,0 +1,72 @@
+<testcase>
+<info>
+<keywords>
+WebSockets
+</keywords>
+</info>
+
+<client>
+<name>
+ws: Invalid oversized PING
+</name>
+<features>
+Debug
+ws
+</features>
+<server>
+http
+</server>
+<tool>
+lib2700
+</tool>
+<command>
+ws://%HOSTIP:%HTTPPORT/%TESTNUMBER
+</command>
+<setenv>
+CURL_WS_FORCE_ZERO_MASK=1
+</setenv>
+</client>
+
+<reply>
+<servercmd>
+upgrade
+</servercmd>
+
+# Full list of frames: see <verify.stdout> below
+#   A 126 byte long PING
+<data nocheck="yes" nonewline="yes">
+HTTP/1.1 101 Switching to WebSockets
+Server: server/%TESTNUMBER
+Upgrade: Websocket
+Connection: Upgrade
+Sec-WebSocket-Accept: HkPsVga7+8LuxM4RGQ5p9tZHeYs=
+
+%hex[%89%7e%00%7e]hex%%repeat[126 x _]%
+</data>
+</reply>
+
+<verify>
+
+# No frames
+<protocol nonewline="yes">
+
+</protocol>
+
+# No frames
+<stdout nonewline="yes">
+
+</stdout>
+
+# CURLE_RECV_ERROR
+<errorcode>
+56
+</errorcode>
+
+# Strip HTTP header from <protocol>
+<strip>
+^GET /.*
+^(Host|User-Agent|Accept|Upgrade|Connection|Sec-WebSocket-(Version|Key)): .*
+^\s*$
+</strip>
+</verify>
+</testcase>
diff --git a/tests/data/test2714 b/tests/data/test2714
new file mode 100644 (file)
index 0000000..f47c96b
--- /dev/null
@@ -0,0 +1,72 @@
+<testcase>
+<info>
+<keywords>
+WebSockets
+</keywords>
+</info>
+
+<client>
+<name>
+ws: Invalid oversized PONG
+</name>
+<features>
+Debug
+ws
+</features>
+<server>
+http
+</server>
+<tool>
+lib2700
+</tool>
+<command>
+ws://%HOSTIP:%HTTPPORT/%TESTNUMBER
+</command>
+<setenv>
+CURL_WS_FORCE_ZERO_MASK=1
+</setenv>
+</client>
+
+<reply>
+<servercmd>
+upgrade
+</servercmd>
+
+# Full list of frames: see <verify.stdout> below
+#   A 126 byte long PONG
+<data nocheck="yes" nonewline="yes">
+HTTP/1.1 101 Switching to WebSockets
+Server: server/%TESTNUMBER
+Upgrade: Websocket
+Connection: Upgrade
+Sec-WebSocket-Accept: HkPsVga7+8LuxM4RGQ5p9tZHeYs=
+
+%hex[%8a%7e%00%7e]hex%%repeat[126 x _]%
+</data>
+</reply>
+
+<verify>
+
+# No frames
+<protocol nonewline="yes">
+
+</protocol>
+
+# No frames
+<stdout nonewline="yes">
+
+</stdout>
+
+# CURLE_RECV_ERROR
+<errorcode>
+56
+</errorcode>
+
+# Strip HTTP header from <protocol>
+<strip>
+^GET /.*
+^(Host|User-Agent|Accept|Upgrade|Connection|Sec-WebSocket-(Version|Key)): .*
+^\s*$
+</strip>
+</verify>
+</testcase>
diff --git a/tests/data/test2715 b/tests/data/test2715
new file mode 100644 (file)
index 0000000..a83e3cc
--- /dev/null
@@ -0,0 +1,72 @@
+<testcase>
+<info>
+<keywords>
+WebSockets
+</keywords>
+</info>
+
+<client>
+<name>
+ws: Invalid oversized CLOSE
+</name>
+<features>
+Debug
+ws
+</features>
+<server>
+http
+</server>
+<tool>
+lib2700
+</tool>
+<command>
+ws://%HOSTIP:%HTTPPORT/%TESTNUMBER
+</command>
+<setenv>
+CURL_WS_FORCE_ZERO_MASK=1
+</setenv>
+</client>
+
+<reply>
+<servercmd>
+upgrade
+</servercmd>
+
+# Full list of frames: see <verify.stdout> below
+#   A 126 byte long CLOSE
+<data nocheck="yes" nonewline="yes">
+HTTP/1.1 101 Switching to WebSockets
+Server: server/%TESTNUMBER
+Upgrade: Websocket
+Connection: Upgrade
+Sec-WebSocket-Accept: HkPsVga7+8LuxM4RGQ5p9tZHeYs=
+
+%hex[%88%7e%00%7e%03%e8]hex%%repeat[124 x _]%
+</data>
+</reply>
+
+<verify>
+
+# No frames
+<protocol nonewline="yes">
+
+</protocol>
+
+# No frames
+<stdout nonewline="yes">
+
+</stdout>
+
+# CURLE_RECV_ERROR
+<errorcode>
+56
+</errorcode>
+
+# Strip HTTP header from <protocol>
+<strip>
+^GET /.*
+^(Host|User-Agent|Accept|Upgrade|Connection|Sec-WebSocket-(Version|Key)): .*
+^\s*$
+</strip>
+</verify>
+</testcase>
diff --git a/tests/data/test2716 b/tests/data/test2716
new file mode 100644 (file)
index 0000000..9c0183c
--- /dev/null
@@ -0,0 +1,72 @@
+<testcase>
+<info>
+<keywords>
+WebSockets
+</keywords>
+</info>
+
+<client>
+<name>
+ws: Invalid fragmented PING
+</name>
+<features>
+Debug
+ws
+</features>
+<server>
+http
+</server>
+<tool>
+lib2700
+</tool>
+<command>
+ws://%HOSTIP:%HTTPPORT/%TESTNUMBER
+</command>
+<setenv>
+CURL_WS_FORCE_ZERO_MASK=1
+</setenv>
+</client>
+
+<reply>
+<servercmd>
+upgrade
+</servercmd>
+
+# Full list of frames: see <verify.stdout> below
+#   A fragmented PING
+<data nocheck="yes" nonewline="yes">
+HTTP/1.1 101 Switching to WebSockets
+Server: server/%TESTNUMBER
+Upgrade: Websocket
+Connection: Upgrade
+Sec-WebSocket-Accept: HkPsVga7+8LuxM4RGQ5p9tZHeYs=
+
+%hex[%09%02p1]hex%%hex[%80%02p2]hex%
+</data>
+</reply>
+
+<verify>
+
+# No frames
+<protocol nonewline="yes">
+
+</protocol>
+
+# No frames
+<stdout nonewline="yes">
+
+</stdout>
+
+# CURLE_RECV_ERROR
+<errorcode>
+56
+</errorcode>
+
+# Strip HTTP header from <protocol>
+<strip>
+^GET /.*
+^(Host|User-Agent|Accept|Upgrade|Connection|Sec-WebSocket-(Version|Key)): .*
+^\s*$
+</strip>
+</verify>
+</testcase>
diff --git a/tests/data/test2717 b/tests/data/test2717
new file mode 100644 (file)
index 0000000..faa9a83
--- /dev/null
@@ -0,0 +1,72 @@
+<testcase>
+<info>
+<keywords>
+WebSockets
+</keywords>
+</info>
+
+<client>
+<name>
+ws: Invalid fragmented PONG
+</name>
+<features>
+Debug
+ws
+</features>
+<server>
+http
+</server>
+<tool>
+lib2700
+</tool>
+<command>
+ws://%HOSTIP:%HTTPPORT/%TESTNUMBER
+</command>
+<setenv>
+CURL_WS_FORCE_ZERO_MASK=1
+</setenv>
+</client>
+
+<reply>
+<servercmd>
+upgrade
+</servercmd>
+
+# Full list of frames: see <verify.stdout> below
+#   A fragmented PONG
+<data nocheck="yes" nonewline="yes">
+HTTP/1.1 101 Switching to WebSockets
+Server: server/%TESTNUMBER
+Upgrade: Websocket
+Connection: Upgrade
+Sec-WebSocket-Accept: HkPsVga7+8LuxM4RGQ5p9tZHeYs=
+
+%hex[%0a%02p1]hex%%hex[%80%02p2]hex%
+</data>
+</reply>
+
+<verify>
+
+# No frames
+<protocol nonewline="yes">
+
+</protocol>
+
+# No frames
+<stdout nonewline="yes">
+
+</stdout>
+
+# CURLE_RECV_ERROR
+<errorcode>
+56
+</errorcode>
+
+# Strip HTTP header from <protocol>
+<strip>
+^GET /.*
+^(Host|User-Agent|Accept|Upgrade|Connection|Sec-WebSocket-(Version|Key)): .*
+^\s*$
+</strip>
+</verify>
+</testcase>
diff --git a/tests/data/test2718 b/tests/data/test2718
new file mode 100644 (file)
index 0000000..5a9c773
--- /dev/null
@@ -0,0 +1,72 @@
+<testcase>
+<info>
+<keywords>
+WebSockets
+</keywords>
+</info>
+
+<client>
+<name>
+ws: Invalid fragmented CLOSE
+</name>
+<features>
+Debug
+ws
+</features>
+<server>
+http
+</server>
+<tool>
+lib2700
+</tool>
+<command>
+ws://%HOSTIP:%HTTPPORT/%TESTNUMBER
+</command>
+<setenv>
+CURL_WS_FORCE_ZERO_MASK=1
+</setenv>
+</client>
+
+<reply>
+<servercmd>
+upgrade
+</servercmd>
+
+# Full list of frames: see <verify.stdout> below
+#   A fragmented CLOSE
+<data nocheck="yes" nonewline="yes">
+HTTP/1.1 101 Switching to WebSockets
+Server: server/%TESTNUMBER
+Upgrade: Websocket
+Connection: Upgrade
+Sec-WebSocket-Accept: HkPsVga7+8LuxM4RGQ5p9tZHeYs=
+
+%hex[%08%02p1]hex%%hex[%80%02p2]hex%
+</data>
+</reply>
+
+<verify>
+
+# No frames
+<protocol nonewline="yes">
+
+</protocol>
+
+# No frames
+<stdout nonewline="yes">
+
+</stdout>
+
+# CURLE_RECV_ERROR
+<errorcode>
+56
+</errorcode>
+
+# Strip HTTP header from <protocol>
+<strip>
+^GET /.*
+^(Host|User-Agent|Accept|Upgrade|Connection|Sec-WebSocket-(Version|Key)): .*
+^\s*$
+</strip>
+</verify>
+</testcase>
diff --git a/tests/data/test2719 b/tests/data/test2719
new file mode 100644 (file)
index 0000000..15f9ecc
--- /dev/null
@@ -0,0 +1,81 @@
+<testcase>
+<info>
+<keywords>
+WebSockets
+</keywords>
+</info>
+
+<client>
+<name>
+ws: Fragmented messages
+</name>
+<features>
+Debug
+ws
+</features>
+<server>
+http
+</server>
+<tool>
+lib2700
+</tool>
+<command>
+ws://%HOSTIP:%HTTPPORT/%TESTNUMBER
+</command>
+<setenv>
+CURL_WS_FORCE_ZERO_MASK=1
+</setenv>
+</client>
+
+<reply>
+<servercmd>
+upgrade
+</servercmd>
+
+# Full list of frames: see <verify.stdout> below
+#   Fragmented TEXT/BINARY messages, each with 2/3 fragments
+<data nocheck="yes" nonewline="yes">
+HTTP/1.1 101 Switching to WebSockets
+Server: server/%TESTNUMBER
+Upgrade: Websocket
+Connection: Upgrade
+Sec-WebSocket-Accept: HkPsVga7+8LuxM4RGQ5p9tZHeYs=
+
+%hex[%01%02t1]hex%%hex[%80%02t2]hex%%hex[%01%02t1]hex%%hex[%00%02t2]hex%%hex[%80%02t3]hex%%hex[%02%02b1]hex%%hex[%80%02b2]hex%%hex[%02%02b1]hex%%hex[%00%02b2]hex%%hex[%80%02b3]hex%%hex[%88%00]hex%
+</data>
+</reply>
+
+<verify>
+
+# Exact echo of reply data with additional masking
+<protocol nonewline="yes">
+%hex[%01%82%00%00%00%00t1]hex%%hex[%80%82%00%00%00%00t2]hex%%hex[%01%82%00%00%00%00t1]hex%%hex[%00%82%00%00%00%00t2]hex%%hex[%80%82%00%00%00%00t3]hex%%hex[%02%82%00%00%00%00b1]hex%%hex[%80%82%00%00%00%00b2]hex%%hex[%02%82%00%00%00%00b1]hex%%hex[%00%82%00%00%00%00b2]hex%%hex[%80%82%00%00%00%00b3]hex%%hex[%88%80%00%00%00%00]hex%
+</protocol>
+
+<stdout>
+txt --- [2] t1
+txt fin [2] t2
+txt --- [2] t1
+txt --- [2] t2
+txt fin [2] t3
+bin --- [2] b1
+bin fin [2] b2
+bin --- [2] b1
+bin --- [2] b2
+bin fin [2] b3
+close [0]
+</stdout>
+
+# CURLE_OK
+<errorcode>
+0
+</errorcode>
+
+# Strip HTTP header from <protocol>
+<strip>
+^GET /.*
+^(Host|User-Agent|Accept|Upgrade|Connection|Sec-WebSocket-(Version|Key)): .*
+^\s*$
+</strip>
+</verify>
+</testcase>
diff --git a/tests/data/test2720 b/tests/data/test2720
new file mode 100644 (file)
index 0000000..1f46226
--- /dev/null
@@ -0,0 +1,86 @@
+<testcase>
+<info>
+<keywords>
+WebSockets
+</keywords>
+</info>
+
+<client>
+<name>
+ws: Fragmented messages with empty fragments
+</name>
+<features>
+Debug
+ws
+</features>
+<server>
+http
+</server>
+<tool>
+lib2700
+</tool>
+<command>
+ws://%HOSTIP:%HTTPPORT/%TESTNUMBER
+</command>
+<setenv>
+CURL_WS_FORCE_ZERO_MASK=1
+</setenv>
+</client>
+
+<reply>
+<servercmd>
+upgrade
+</servercmd>
+
+# Full list of frames: see <verify.stdout> below
+#   1st a message with an emtpy fragment at the beginning
+#   2nd a message with an emtpy fragment in the middle
+#   3rd a message with an emtpy fragment at the end
+#   4th a message with only empty fragments
+<data nocheck="yes" nonewline="yes">
+HTTP/1.1 101 Switching to WebSockets
+Server: server/%TESTNUMBER
+Upgrade: Websocket
+Connection: Upgrade
+Sec-WebSocket-Accept: HkPsVga7+8LuxM4RGQ5p9tZHeYs=
+
+%hex[%01%00]hex%%hex[%00%02a1]hex%%hex[%80%02a2]hex%%hex[%01%02b1]hex%%hex[%00%00]hex%%hex[%80%02b2]hex%%hex[%01%02c1]hex%%hex[%00%02c2]hex%%hex[%80%00]hex%%hex[%01%00]hex%%hex[%00%00]hex%%hex[%80%00]hex%%hex[%88%00]hex%
+</data>
+</reply>
+
+<verify>
+
+# Exact echo of reply data with additional masking
+<protocol nonewline="yes">
+%hex[%01%80%00%00%00%00]hex%%hex[%00%82%00%00%00%00a1]hex%%hex[%80%82%00%00%00%00a2]hex%%hex[%01%82%00%00%00%00b1]hex%%hex[%00%80%00%00%00%00]hex%%hex[%80%82%00%00%00%00b2]hex%%hex[%01%82%00%00%00%00c1]hex%%hex[%00%82%00%00%00%00c2]hex%%hex[%80%80%00%00%00%00]hex%%hex[%01%80%00%00%00%00]hex%%hex[%00%80%00%00%00%00]hex%%hex[%80%80%00%00%00%00]hex%%hex[%88%80%00%00%00%00]hex%
+</protocol>
+
+<stdout>
+txt --- [0]
+txt --- [2] a1
+txt fin [2] a2
+txt --- [2] b1
+txt --- [0]
+txt fin [2] b2
+txt --- [2] c1
+txt --- [2] c2
+txt fin [0]
+txt --- [0]
+txt --- [0]
+txt fin [0]
+close [0]
+</stdout>
+
+# CURLE_OK
+<errorcode>
+0
+</errorcode>
+
+# Strip HTTP header from <protocol>
+<strip>
+^GET /.*
+^(Host|User-Agent|Accept|Upgrade|Connection|Sec-WebSocket-(Version|Key)): .*
+^\s*$
+</strip>
+</verify>
+</testcase>
diff --git a/tests/data/test2721 b/tests/data/test2721
new file mode 100644 (file)
index 0000000..2c3fcd8
--- /dev/null
@@ -0,0 +1,77 @@
+<testcase>
+<info>
+<keywords>
+WebSockets
+</keywords>
+</info>
+
+<client>
+<name>
+ws: Fragmented messages with interleaved pong
+</name>
+<features>
+Debug
+ws
+</features>
+<server>
+http
+</server>
+<tool>
+lib2700
+</tool>
+<command>
+ws://%HOSTIP:%HTTPPORT/%TESTNUMBER
+</command>
+<setenv>
+CURL_WS_FORCE_ZERO_MASK=1
+</setenv>
+</client>
+
+<reply>
+<servercmd>
+upgrade
+</servercmd>
+
+# Full list of frames: see <verify.stdout> below
+#   A TEXT/BINARY message fragmented into two frames each, with pongs in the middle
+<data nocheck="yes" nonewline="yes">
+HTTP/1.1 101 Switching to WebSockets
+Server: server/%TESTNUMBER
+Upgrade: Websocket
+Connection: Upgrade
+Sec-WebSocket-Accept: HkPsVga7+8LuxM4RGQ5p9tZHeYs=
+
+%hex[%01%02t1]hex%%hex[%8a%02p1]hex%%hex[%80%02t2]hex%%hex[%02%02b1]hex%%hex[%8a%02p2]hex%%hex[%80%02b2]hex%%hex[%88%00]hex%
+</data>
+</reply>
+
+<verify>
+
+# Exact echo of reply data with additional masking
+<protocol nonewline="yes">
+%hex[%01%82%00%00%00%00t1]hex%%hex[%8a%82%00%00%00%00p1]hex%%hex[%80%82%00%00%00%00t2]hex%%hex[%02%82%00%00%00%00b1]hex%%hex[%8a%82%00%00%00%00p2]hex%%hex[%80%82%00%00%00%00b2]hex%%hex[%88%80%00%00%00%00]hex%
+</protocol>
+
+<stdout>
+txt --- [2] t1
+pong [2] p1
+txt fin [2] t2
+bin --- [2] b1
+pong [2] p2
+bin fin [2] b2
+close [0]
+</stdout>
+
+# CURLE_OK
+<errorcode>
+0
+</errorcode>
+
+# Strip HTTP header from <protocol>
+<strip>
+^GET /.*
+^(Host|User-Agent|Accept|Upgrade|Connection|Sec-WebSocket-(Version|Key)): .*
+^\s*$
+</strip>
+</verify>
+</testcase>
diff --git a/tests/data/test2722 b/tests/data/test2722
new file mode 100644 (file)
index 0000000..0dd1bbb
--- /dev/null
@@ -0,0 +1,73 @@
+<testcase>
+<info>
+<keywords>
+WebSockets
+</keywords>
+</info>
+
+<client>
+<name>
+ws: Invalid fragmented message without initial frame
+</name>
+<features>
+Debug
+ws
+</features>
+<server>
+http
+</server>
+<tool>
+lib2700
+</tool>
+<command>
+ws://%HOSTIP:%HTTPPORT/%TESTNUMBER
+</command>
+<setenv>
+CURL_WS_FORCE_ZERO_MASK=1
+</setenv>
+</client>
+
+<reply>
+<servercmd>
+upgrade
+</servercmd>
+
+# Full list of frames: see <verify.stdout> below
+#   First a valid BINARY frame
+#   Second a fragmented message with the first frame missing
+<data nocheck="yes" nonewline="yes">
+HTTP/1.1 101 Switching to WebSockets
+Server: server/%TESTNUMBER
+Upgrade: Websocket
+Connection: Upgrade
+Sec-WebSocket-Accept: HkPsVga7+8LuxM4RGQ5p9tZHeYs=
+
+%hex[%82%01b]hex%%hex[%00%02m2]hex%%hex[%80%02m3]hex%
+</data>
+</reply>
+
+<verify>
+
+# Echo of reply data with additional masking up to the missing initial frame
+<protocol nonewline="yes">
+%hex[%82%81%00%00%00%00b]hex%
+</protocol>
+
+# Only frames up to the missing initial frame
+<stdout>
+bin fin [1] b
+</stdout>
+
+# CURLE_RECV_ERROR
+<errorcode>
+56
+</errorcode>
+
+# Strip HTTP header from <protocol>
+<strip>
+^GET /.*
+^(Host|User-Agent|Accept|Upgrade|Connection|Sec-WebSocket-(Version|Key)): .*
+^\s*$
+</strip>
+</verify>
+</testcase>
diff --git a/tests/data/test2723 b/tests/data/test2723
new file mode 100644 (file)
index 0000000..7dd2609
--- /dev/null
@@ -0,0 +1,74 @@
+<testcase>
+<info>
+<keywords>
+WebSockets
+</keywords>
+</info>
+
+<client>
+<name>
+ws: Invalid fragmented message without final frame
+</name>
+<features>
+Debug
+ws
+</features>
+<server>
+http
+</server>
+<tool>
+lib2700
+</tool>
+<command>
+ws://%HOSTIP:%HTTPPORT/%TESTNUMBER
+</command>
+<setenv>
+CURL_WS_FORCE_ZERO_MASK=1
+</setenv>
+</client>
+
+<reply>
+<servercmd>
+upgrade
+</servercmd>
+
+# Full list of frames: see <verify.stdout> below
+#   First a fragmented TEXT message with the last frame missing
+#   Second a valid BINARY frame
+<data nocheck="yes" nonewline="yes">
+HTTP/1.1 101 Switching to WebSockets
+Server: server/%TESTNUMBER
+Upgrade: Websocket
+Connection: Upgrade
+Sec-WebSocket-Accept: HkPsVga7+8LuxM4RGQ5p9tZHeYs=
+
+%hex[%01%02t1]hex%%hex[%00%02t2]hex%%hex[%82%01b]hex%
+</data>
+</reply>
+
+<verify>
+
+# Echo of reply data with additional masking up to the missing final frame
+<protocol nonewline="yes">
+%hex[%01%82%00%00%00%00t1]hex%%hex[%00%82%00%00%00%00t2]hex%
+</protocol>
+
+# Only frames up to the missing final frame
+<stdout>
+txt --- [2] t1
+txt --- [2] t2
+</stdout>
+
+# CURLE_RECV_ERROR
+<errorcode>
+56
+</errorcode>
+
+# Strip HTTP header from <protocol>
+<strip>
+^GET /.*
+^(Host|User-Agent|Accept|Upgrade|Connection|Sec-WebSocket-(Version|Key)): .*
+^\s*$
+</strip>
+</verify>
+</testcase>
index 306f418ad57ee3c3066e51ba97f8f10650043c74..d78d64ac4f9b6616631377849e21a656e0dfe232 100644 (file)
@@ -124,7 +124,7 @@ class TestWebsockets:
             pytest.skip(f'example client not built: {client.name}')
         url = f'ws://localhost:{env.ws_port}/'
         r = client.run(args=[url, payload])
-        r.check_exit_code(56)
+        r.check_exit_code(100)  # CURLE_TOO_LARGE
 
     def test_20_04_data_small(self, env: Env, ws_echo):
         client = LocalClient(env=env, name='ws-data')
index 002e7ab5470d1c4ddbbfc8499ba166c0cec545f0..d130d5158d6c719d5e66122740568f9fe933cf33 100644 (file)
@@ -75,10 +75,10 @@ LIBTESTPROGS = libauthretry libntlmconnect libprereq                     \
  lib1945 lib1946 lib1947 lib1948 lib1955 lib1956 lib1957 lib1958 lib1959 \
  lib1960 lib1964 \
  lib1970 lib1971 lib1972 lib1973 lib1974 lib1975 lib1977 lib1978 \
- lib2301 lib2302 lib2304 lib2305 lib2306         lib2308 lib2309 lib2310 \
- lib2311 lib2312 \
+ lib2301 lib2302 lib2304 lib2306 lib2308 lib2309 \
  lib2402 lib2404 lib2405 \
  lib2502 \
+ lib2700 \
  lib3010 lib3025 lib3026 lib3027 \
  lib3100 lib3101 lib3102 lib3103 lib3104 lib3105 lib3207 lib3208
 
@@ -700,9 +700,6 @@ lib2302_LDADD = $(TESTUTIL_LIBS)
 lib2304_SOURCES = lib2304.c $(SUPPORTFILES)
 lib2304_LDADD = $(TESTUTIL_LIBS)
 
-lib2305_SOURCES = lib2305.c $(SUPPORTFILES) $(TESTUTIL) $(TSTTRACE) $(MULTIBYTE)
-lib2305_LDADD = $(TESTUTIL_LIBS)
-
 lib2306_SOURCES = lib2306.c $(SUPPORTFILES)
 lib2306_LDADD = $(TESTUTIL_LIBS)
 
@@ -712,15 +709,6 @@ lib2308_LDADD = $(TESTUTIL_LIBS)
 lib2309_SOURCES = lib2309.c $(SUPPORTFILES)
 lib2309_LDADD = $(TESTUTIL_LIBS)
 
-lib2310_SOURCES = lib2310.c $(SUPPORTFILES) $(TESTUTIL) $(TSTTRACE) $(MULTIBYTE)
-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)
 
@@ -733,6 +721,9 @@ lib2405_LDADD = $(TESTUTIL_LIBS)
 lib2502_SOURCES = lib2502.c $(SUPPORTFILES) $(TESTUTIL) $(TSTTRACE) $(WARNLESS)
 lib2502_LDADD = $(TESTUTIL_LIBS)
 
+lib2700_SOURCES = lib2700.c $(SUPPORTFILES) $(TESTUTIL) $(TSTTRACE) $(MULTIBYTE)
+lib2700_LDADD = $(TESTUTIL_LIBS)
+
 lib3010_SOURCES = lib3010.c $(SUPPORTFILES) $(TESTUTIL) $(WARNLESS)
 lib3010_LDADD = $(TESTUTIL_LIBS)
 
diff --git a/tests/libtest/lib2305.c b/tests/libtest/lib2305.c
deleted file mode 100644 (file)
index fc712ac..0000000
+++ /dev/null
@@ -1,109 +0,0 @@
-/***************************************************************************
- *                                  _   _ ____  _
- *  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"
-#include "testtrace.h"
-#include "memdebug.h"
-
-#ifndef CURL_DISABLE_WEBSOCKETS
-
-/* just close the connection */
-static void websocket_close(CURL *curl)
-{
-  size_t sent;
-  CURLcode result =
-    curl_ws_send(curl, "", 0, &sent, 0, CURLWS_CLOSE);
-  curl_mfprintf(stderr,
-                "ws: curl_ws_send returned %d, sent %d\n", result, (int)sent);
-}
-
-static void websocket(CURL *curl)
-{
-  char buffer[256];
-  const struct curl_ws_frame *meta;
-  size_t nread;
-  size_t i = 0;
-  FILE *save = fopen(libtest_arg2, FOPEN_WRITETEXT);
-  if(!save)
-    return;
-
-  /* Three 4097-bytes frames are expected, 12291 bytes */
-  while(i < 12291) {
-    CURLcode result =
-      curl_ws_recv(curl, buffer, sizeof(buffer), &nread, &meta);
-    if(result) {
-      if(result == CURLE_AGAIN)
-        /* crude busy-loop */
-        continue;
-      fclose(save);
-      curl_mprintf("curl_ws_recv returned %d\n", result);
-      return;
-    }
-    curl_mprintf("%d: nread %zu Age %d Flags %x "
-                 "Offset %" CURL_FORMAT_CURL_OFF_T " "
-                 "Bytesleft %" CURL_FORMAT_CURL_OFF_T "\n",
-                 (int)i,
-                 nread, meta->age, meta->flags, meta->offset, meta->bytesleft);
-    i += meta->len;
-    fwrite(buffer, 1, nread, save);
-  }
-  fclose(save);
-
-  websocket_close(curl);
-}
-
-CURLcode test(char *URL)
-{
-  CURL *curl;
-  CURLcode res = CURLE_OK;
-
-  global_init(CURL_GLOBAL_ALL);
-
-  curl = curl_easy_init();
-  if(curl) {
-    curl_easy_setopt(curl, CURLOPT_URL, URL);
-
-    /* use the callback style */
-    curl_easy_setopt(curl, CURLOPT_USERAGENT, "websocket/2304");
-    libtest_debug_config.nohex = 1;
-    libtest_debug_config.tracetime = 1;
-    curl_easy_setopt(curl, CURLOPT_DEBUGDATA, &libtest_debug_config);
-    curl_easy_setopt(curl, CURLOPT_DEBUGFUNCTION, libtest_debug_cb);
-    curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);
-    curl_easy_setopt(curl, CURLOPT_CONNECT_ONLY, 2L); /* websocket style */
-    res = curl_easy_perform(curl);
-    curl_mfprintf(stderr, "curl_easy_perform() returned %d\n", res);
-    if(res == CURLE_OK)
-      websocket(curl);
-
-    /* always cleanup */
-    curl_easy_cleanup(curl);
-  }
-  curl_global_cleanup();
-  return res;
-}
-
-#else
-NO_SUPPORT_BUILT_IN
-#endif
diff --git a/tests/libtest/lib2310.c b/tests/libtest/lib2310.c
deleted file mode 100644 (file)
index f5fe7dc..0000000
+++ /dev/null
@@ -1,68 +0,0 @@
-/***************************************************************************
-*                                  _   _ ____  _
- *  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"
-#include "testtrace.h"
-#include "memdebug.h"
-
-#ifndef CURL_DISABLE_WEBSOCKETS
-
-static size_t writecb(char *b, size_t size, size_t nitems, void *p)
-{
-  (void)b;
-  (void)size;
-  (void)nitems;
-  (void)p;
-  return 0;
-}
-
-CURLcode test(char *URL)
-{
-  CURL *curl;
-  CURLcode res = CURLE_OK;
-
-  global_init(CURL_GLOBAL_ALL);
-
-  curl = curl_easy_init();
-  if(curl) {
-    curl_easy_setopt(curl, CURLOPT_URL, URL);
-
-    /* use the callback style */
-    curl_easy_setopt(curl, CURLOPT_USERAGENT, "webbie-sox/3");
-    curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);
-    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writecb);
-    curl_easy_setopt(curl, CURLOPT_WRITEDATA, curl);
-    res = curl_easy_perform(curl);
-    curl_mprintf("Returned %d, should be %d.\n", res, CURLE_RECV_ERROR);
-
-    /* always cleanup */
-    curl_easy_cleanup(curl);
-  }
-  curl_global_cleanup();
-  return CURLE_OK;
-}
-
-#else
-NO_SUPPORT_BUILT_IN
-#endif
diff --git a/tests/libtest/lib2311.c b/tests/libtest/lib2311.c
deleted file mode 100644 (file)
index 428b637..0000000
+++ /dev/null
@@ -1,127 +0,0 @@
-/***************************************************************************
- *                                  _   _ ____  _
- *  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"
-#include "testtrace.h"
-#include "memdebug.h"
-
-#ifndef CURL_DISABLE_WEBSOCKETS
-
-/* just close the connection */
-static void websocket_close(CURL *curl)
-{
-  size_t sent;
-  CURLcode result =
-    curl_ws_send(curl, "", 0, &sent, 0, CURLWS_CLOSE);
-  curl_mfprintf(stderr,
-                "ws: curl_ws_send returned %d, sent %zu\n", result, sent);
-}
-
-static void websocket_frame(CURL *curl, FILE *save, int expected_flags)
-{
-  char buffer[256];
-  const struct curl_ws_frame *meta;
-  size_t nread;
-  size_t total_read = 0;
-
-  /* silence "unused parameter" warning */
-  (void)expected_flags;
-
-  /* Frames are expected to have 4097 bytes */
-  while(true) {
-    CURLcode result =
-      curl_ws_recv(curl, buffer, sizeof(buffer), &nread, &meta);
-    if(result) {
-      if(result == CURLE_AGAIN)
-        /* crude busy-loop */
-        continue;
-      curl_mprintf("curl_ws_recv returned %d\n", result);
-      return;
-    }
-    curl_mprintf("%d: nread %zu Age %d Flags %x "
-                 "Offset %" CURL_FORMAT_CURL_OFF_T " "
-                 "Bytesleft %" CURL_FORMAT_CURL_OFF_T "\n",
-                 (int)total_read,
-                 nread, meta->age, meta->flags, meta->offset, meta->bytesleft);
-    assert(meta->flags == expected_flags);
-    total_read += nread;
-    fwrite(buffer, 1, nread, save);
-    /* exit condition */
-    if(meta->bytesleft == 0) {
-      break;
-    }
-  }
-
-  assert(total_read == 4097);
-}
-
-static void websocket(CURL *curl)
-{
-  FILE *save = fopen(libtest_arg2, FOPEN_WRITETEXT);
-  if(!save)
-    return;
-
-  /* Three frames are expected */
-  websocket_frame(curl, save, CURLWS_TEXT | CURLWS_CONT);
-  websocket_frame(curl, save, CURLWS_TEXT | CURLWS_CONT);
-  websocket_frame(curl, save, CURLWS_TEXT);
-
-  fclose(save);
-  websocket_close(curl);
-}
-
-CURLcode test(char *URL)
-{
-  CURL *curl;
-  CURLcode res = CURLE_OK;
-
-  global_init(CURL_GLOBAL_ALL);
-
-  curl = curl_easy_init();
-  if(curl) {
-    curl_easy_setopt(curl, CURLOPT_URL, URL);
-
-    /* use the callback style */
-    curl_easy_setopt(curl, CURLOPT_USERAGENT, "websocket/2311");
-    libtest_debug_config.nohex = 1;
-    libtest_debug_config.tracetime = 1;
-    curl_easy_setopt(curl, CURLOPT_DEBUGDATA, &libtest_debug_config);
-    curl_easy_setopt(curl, CURLOPT_DEBUGFUNCTION, libtest_debug_cb);
-    curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);
-    curl_easy_setopt(curl, CURLOPT_CONNECT_ONLY, 2L); /* websocket style */
-    res = curl_easy_perform(curl);
-    curl_mfprintf(stderr, "curl_easy_perform() returned %d\n", res);
-    if(res == CURLE_OK)
-      websocket(curl);
-
-    /* always cleanup */
-    curl_easy_cleanup(curl);
-  }
-  curl_global_cleanup();
-  return res;
-}
-
-#else
-NO_SUPPORT_BUILT_IN
-#endif
diff --git a/tests/libtest/lib2312.c b/tests/libtest/lib2312.c
deleted file mode 100644 (file)
index 7f1b085..0000000
+++ /dev/null
@@ -1,102 +0,0 @@
-/***************************************************************************
- *                                  _   _ ____  _
- *  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) {
-    curl_mfprintf(stderr, "write_cb received ping with %zd bytes\n",
-                  size * nitems);
-    curl_mfprintf(stderr, "\n");
-    for(i = 0; i < size * nitems; i++) {
-      curl_mfprintf(stderr, "%02X%s", (int)b[i],
-                    (i % 10 == 0 && i != 0) ? "\n" : " ");
-    }
-    curl_mfprintf(stderr, "\n");
-    curl_mfprintf(stderr, "write_cb sending pong response\n");
-    curl_ws_send(curl, b, size * nitems, &sent, 0, CURLWS_PONG);
-    curl_mfprintf(stderr, "write_cb closing websocket\n");
-    curl_ws_send(curl, NULL, 0, &sent, 0, CURLWS_CLOSE);
-    ping_check->pinged = 1;
-  }
-  else {
-    curl_mfprintf(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);
-    curl_mfprintf(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
diff --git a/tests/libtest/lib2700.c b/tests/libtest/lib2700.c
new file mode 100644 (file)
index 0000000..ecec680
--- /dev/null
@@ -0,0 +1,254 @@
+/***************************************************************************
+ *                                  _   _ ____  _
+ *  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"
+#include "testtrace.h"
+#include "memdebug.h"
+
+#ifndef CURL_DISABLE_WEBSOCKETS
+
+static const char *descr_flags(int flags)
+{
+  if(flags & CURLWS_TEXT)
+    return flags & CURLWS_CONT ? "txt ---" : "txt fin";
+  if(flags & CURLWS_BINARY)
+    return flags & CURLWS_CONT ? "bin ---" : "bin fin";
+  if(flags & CURLWS_PING)
+    return "ping";
+  if(flags & CURLWS_PONG)
+    return "pong";
+  if(flags & CURLWS_CLOSE)
+    return "close";
+  assert(false);
+  return "";
+}
+
+static CURLcode send_header(CURL *curl, int flags, size_t size)
+{
+  CURLcode res = CURLE_OK;
+  size_t nsent;
+
+retry:
+  res = curl_ws_send(curl, NULL, 0, &nsent, (curl_off_t)size,
+                     flags | CURLWS_OFFSET);
+  if(res == CURLE_AGAIN) {
+    assert(nsent == 0);
+    goto retry;
+  }
+  if(res) {
+    curl_mfprintf(stderr, "%s:%d curl_ws_send() failed with code %d (%s)\n",
+                  __FILE__, __LINE__, res, curl_easy_strerror(res));
+    assert(nsent == 0);
+    return res;
+  }
+
+  assert(nsent == 0);
+
+  return CURLE_OK;
+}
+
+static CURLcode recv_header(CURL *curl, int *flags, curl_off_t *offset,
+                            curl_off_t *bytesleft)
+{
+  CURLcode res = CURLE_OK;
+  size_t nread;
+  const struct curl_ws_frame *meta;
+
+  *flags = 0;
+  *offset = 0;
+  *bytesleft = 0;
+
+retry:
+  res = curl_ws_recv(curl, NULL, 0, &nread, &meta);
+  if(res == CURLE_AGAIN) {
+    assert(nread == 0);
+    goto retry;
+  }
+  if(res) {
+    curl_mfprintf(stderr, "%s:%d curl_ws_recv() failed with code %d (%s)\n",
+                  __FILE__, __LINE__, res, curl_easy_strerror(res));
+    assert(nread == 0);
+    return res;
+  }
+
+  assert(nread == 0);
+  assert(meta != NULL);
+  assert(meta->flags);
+  assert(meta->offset == 0);
+
+  *flags = meta->flags;
+  *offset = meta->offset;
+  *bytesleft = meta->bytesleft;
+
+  curl_mfprintf(stdout, "%s [%" FMT_OFF_T "]", descr_flags(meta->flags),
+                meta->bytesleft);
+
+  if(meta->bytesleft > 0)
+    curl_mfprintf(stdout, " ");
+
+  res = send_header(curl, meta->flags, (size_t)meta->bytesleft);
+  if(res)
+    return res;
+
+  return CURLE_OK;
+}
+
+static CURLcode send_chunk(CURL *curl, int flags, const char *buffer,
+                           size_t size, size_t *offset)
+{
+  CURLcode res = CURLE_OK;
+  size_t nsent;
+
+retry:
+    res = curl_ws_send(curl, buffer + *offset, size - *offset, &nsent, 0,
+                       flags);
+  if(res == CURLE_AGAIN) {
+    assert(nsent == 0);
+    goto retry;
+  }
+  if(res) {
+    curl_mfprintf(stderr, "%s:%d curl_ws_send() failed with code %d (%s)\n",
+                  __FILE__, __LINE__, res, curl_easy_strerror(res));
+    assert(nsent == 0);
+    return res;
+  }
+
+  assert(nsent <= size - *offset);
+
+  *offset += nsent;
+
+  return CURLE_OK;
+}
+
+static CURLcode recv_chunk(CURL *curl, int flags, curl_off_t *offset,
+                           curl_off_t *bytesleft)
+{
+  CURLcode res = CURLE_OK;
+  char buffer[256];
+  size_t nread;
+  const struct curl_ws_frame *meta;
+  size_t sendoffset = 0;
+
+retry:
+  res = curl_ws_recv(curl, buffer, sizeof(buffer), &nread, &meta);
+  if(res == CURLE_AGAIN) {
+    assert(nread == 0);
+    goto retry;
+  }
+  if(res) {
+    curl_mfprintf(stderr, "%s:%d curl_ws_recv() failed with code %d (%s)\n",
+                  __FILE__, __LINE__, res, curl_easy_strerror(res));
+    assert(nread == 0);
+    return res;
+  }
+
+  assert(nread <= sizeof(buffer));
+  assert(meta != NULL);
+  assert(meta->flags == flags);
+  assert(meta->offset == *offset);
+  assert(meta->bytesleft == (*bytesleft - (curl_off_t)nread));
+
+  *offset += nread;
+  *bytesleft -= nread;
+
+  fwrite(buffer, 1, nread, stdout);
+
+  while(sendoffset < nread) {
+    res = send_chunk(curl, flags, buffer, nread, &sendoffset);
+    if(res)
+      return res;
+  }
+
+  return CURLE_OK;
+}
+
+static CURLcode recv_frame(CURL *curl, bool *stop)
+{
+  CURLcode res = CURLE_OK;
+  int flags = 0;
+  curl_off_t offset = 0;
+  curl_off_t bytesleft = 0;
+
+  res = recv_header(curl, &flags, &offset, &bytesleft);
+  if(res)
+    return res;
+
+  while(bytesleft > 0) {
+    res = recv_chunk(curl, flags, &offset, &bytesleft);
+    if(res)
+      return res;
+  }
+
+  if(flags & CURLWS_CLOSE)
+    *stop = true;
+
+  curl_mfprintf(stdout, "\n");
+
+  return res;
+}
+
+CURLcode test(char *URL)
+{
+  CURLcode res = CURLE_OK;
+  bool stop = false;
+  CURL *curl;
+
+  global_init(CURL_GLOBAL_ALL);
+  curl_global_trace("ws");
+  easy_init(curl);
+
+  easy_setopt(curl, CURLOPT_URL, URL);
+  easy_setopt(curl, CURLOPT_USERAGENT, "client/test2700");
+  libtest_debug_config.nohex = 1;
+  libtest_debug_config.tracetime = 1;
+  easy_setopt(curl, CURLOPT_DEBUGDATA, &libtest_debug_config);
+  easy_setopt(curl, CURLOPT_DEBUGFUNCTION, libtest_debug_cb);
+  easy_setopt(curl, CURLOPT_VERBOSE, 1L);
+  easy_setopt(curl, CURLOPT_CONNECT_ONLY, 2L);
+  if(!getenv("LIB2700_AUTO_PONG"))
+    easy_setopt(curl, CURLOPT_WS_OPTIONS, (long)CURLWS_NOAUTOPONG);
+
+  res = curl_easy_perform(curl);
+  if(res) {
+    curl_mfprintf(stderr,
+                  "%s:%d curl_easy_perform() failed with code %d (%s)\n",
+                  __FILE__, __LINE__, res, curl_easy_strerror(res));
+    goto test_cleanup;
+  }
+
+  while(!stop) {
+    res = recv_frame(curl, &stop);
+    if(res)
+      goto test_cleanup;
+  }
+
+test_cleanup:
+  curl_easy_cleanup(curl);
+  curl_global_cleanup();
+  return res;
+}
+
+#else
+NO_SUPPORT_BUILT_IN
+#endif