summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDaniel Stenberg <daniel@haxx.se>2022-09-09 15:11:14 +0200
committerDaniel Stenberg <daniel@haxx.se>2022-09-09 15:11:14 +0200
commit664249d095275ec532f55dd1752d80c8c1093a77 (patch)
treef8e3add4b66fb64271d22178743f3dc2f1758dcd
parent60a3b25dbf1f211f6ba5216f2d774cfb26cb3e29 (diff)
downloadcurl-664249d095275ec532f55dd1752d80c8c1093a77.tar.gz
ws: initial websockets support
Closes #8995
-rw-r--r--CMakeLists.txt12
-rw-r--r--configure.ac21
-rw-r--r--docs/WebSockets.md16
-rw-r--r--docs/libcurl/curl_easy_setopt.33
-rw-r--r--docs/libcurl/curl_ws_recv.366
-rw-r--r--docs/libcurl/curl_ws_send.373
-rw-r--r--docs/libcurl/opts/CURLOPT_CONNECT_ONLY.34
-rw-r--r--docs/libcurl/opts/CURLOPT_WS_OPTIONS.373
-rw-r--r--docs/libcurl/opts/Makefile.inc1
-rw-r--r--docs/libcurl/symbols-in-versions1
-rw-r--r--include/curl/Makefile.am2
-rw-r--r--include/curl/curl.h4
-rw-r--r--include/curl/websockets.h68
-rw-r--r--lib/Makefile.inc6
-rw-r--r--lib/c-hyper.c22
-rw-r--r--lib/conncache.c2
-rw-r--r--lib/easy.c28
-rw-r--r--lib/easyif.h3
-rw-r--r--lib/easyoptions.c3
-rw-r--r--lib/http.c119
-rw-r--r--lib/http.h23
-rw-r--r--lib/http2.c2
-rw-r--r--lib/multi.c4
-rw-r--r--lib/sendf.c16
-rw-r--r--lib/setopt.c18
-rw-r--r--lib/url.c16
-rw-r--r--lib/urldata.h24
-rw-r--r--lib/ws.c610
-rw-r--r--lib/ws.h50
29 files changed, 1238 insertions, 52 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index ba79d9ec7..564c4dbc5 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1176,6 +1176,16 @@ endif()
set(CMAKE_REQUIRED_FLAGS)
+option(ENABLE_WEBSOCKETS "Set to ON to enable EXPERIMENTAL websockets" OFF)
+
+if(ENABLE_WEBSOCKETS)
+ if(${SIZEOF_CURL_OFF_T} GREATER "4")
+ set(USE_WEBSOCKETS ON)
+ else()
+ message(WARNING "curl_off_t is too small to enable WebSockets")
+ endif()
+endif()
+
foreach(CURL_TEST
HAVE_GLIBC_STRERROR_R
HAVE_POSIX_STRERROR_R
@@ -1486,6 +1496,8 @@ _add_if("SFTP" USE_LIBSSH2 OR USE_LIBSSH)
_add_if("RTSP" NOT CURL_DISABLE_RTSP)
_add_if("RTMP" USE_LIBRTMP)
_add_if("MQTT" NOT CURL_DISABLE_MQTT)
+_add_if("WS" USE_WEBSOCKETS)
+_add_if("WSS" USE_WEBSOCKETS)
if(_items)
list(SORT _items)
endif()
diff --git a/configure.ac b/configure.ac
index 919370c43..17c0a7cdc 100644
--- a/configure.ac
+++ b/configure.ac
@@ -4192,14 +4192,21 @@ AS_HELP_STRING([--disable-websockets],[Disable WebSockets support]),
no)
AC_MSG_RESULT(no)
;;
- *) AC_MSG_RESULT(yes)
- curl_ws_msg="enabled"
- AC_DEFINE_UNQUOTED(USE_WEBSOCKETS, [1], [enable websockets support])
- SUPPORT_PROTOCOLS="$SUPPORT_PROTOCOLS WS"
- if test "x$SSL_ENABLED" = "x1"; then
- SUPPORT_PROTOCOLS="$SUPPORT_PROTOCOLS WSS"
+ *)
+ if test ${ac_cv_sizeof_curl_off_t} -gt 4; then
+ AC_MSG_RESULT(yes)
+ curl_ws_msg="enabled"
+ AC_DEFINE_UNQUOTED(USE_WEBSOCKETS, [1], [enable websockets support])
+ SUPPORT_PROTOCOLS="$SUPPORT_PROTOCOLS WS"
+ if test "x$SSL_ENABLED" = "x1"; then
+ SUPPORT_PROTOCOLS="$SUPPORT_PROTOCOLS WSS"
+ fi
+ experimental="$experimental Websockets"
+ else
+ dnl websockets requires >32 bit curl_off_t
+ AC_MSG_RESULT(no)
+ AC_MSG_WARN([Websockets disabled due to lack of >32 bit curl_off_t])
fi
- experimental="$experimental Websockets"
;;
esac ],
AC_MSG_RESULT(no)
diff --git a/docs/WebSockets.md b/docs/WebSockets.md
index 736f96538..5e36e1aa7 100644
--- a/docs/WebSockets.md
+++ b/docs/WebSockets.md
@@ -12,18 +12,20 @@ The Websockets API is described in the individual man pages for the new API.
Websockets with libcurl can be done two ways.
-1. Get the websockets frames from the server sent to a WS write callback. You
- can then respond with `curl_ws_send()` from within the callback or outside
- of it.
+1. Get the websockets frames from the server sent to the write callback. You
+ can then respond with `curl_ws_send()` from within the callback (or outside
+ of it).
2. Set `CURLOPT_CONNECT_ONLY` to 2L (new for websockets), which makes libcurl
- do the `Upgrade:` dance in the `curl_easy_perform()` call and then you can
- use `curl_ws_recv()` and `curl_ws_send()` to receive and send websocket
- frames from and to the server.
+ do a HTTP GET + `Upgrade:` request plus response in the
+ `curl_easy_perform()` call before it returns and then you can use
+ `curl_ws_recv()` and `curl_ws_send()` to receive and send websocket frames
+ from and to the server.
The new options to `curl_easy_setopt()`:
- `CURLOPT_WS_OPTIONS` - to control specific behavior (no bits implemented yet)
+ `CURLOPT_WS_OPTIONS` - to control specific behavior. `CURLWS_RAW_MODE` makes
+ libcurl provide all websocket traffic raw in the callback.
The new function calls:
diff --git a/docs/libcurl/curl_easy_setopt.3 b/docs/libcurl/curl_easy_setopt.3
index ee95ff9f4..33be54d36 100644
--- a/docs/libcurl/curl_easy_setopt.3
+++ b/docs/libcurl/curl_easy_setopt.3
@@ -675,6 +675,9 @@ Custom pointer to pass to ssh key callback. See \fICURLOPT_SSH_KEYDATA(3)\fP
Callback for checking host key handling. See \fICURLOPT_SSH_HOSTKEYFUNCTION(3)\fP
.IP CURLOPT_SSH_HOSTKEYDATA
Custom pointer to pass to ssh host key callback. See \fICURLOPT_SSH_HOSTKEYDATA(3)\fP
+.SH WEBSOCKETS
+.IP CURLOPT_WS_OPTIONS
+Set Websockets options. See \fICURLOPT_WS_OPTIONS(3)\fP
.SH OTHER OPTIONS
.IP CURLOPT_PRIVATE
Private pointer to store. See \fICURLOPT_PRIVATE(3)\fP
diff --git a/docs/libcurl/curl_ws_recv.3 b/docs/libcurl/curl_ws_recv.3
new file mode 100644
index 000000000..edbce84c4
--- /dev/null
+++ b/docs/libcurl/curl_ws_recv.3
@@ -0,0 +1,66 @@
+.\" **************************************************************************
+.\" * _ _ ____ _
+.\" * Project ___| | | | _ \| |
+.\" * / __| | | | |_) | |
+.\" * | (__| |_| | _ <| |___
+.\" * \___|\___/|_| \_\_____|
+.\" *
+.\" * Copyright (C) 1998 - 2022, 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
+.\" *
+.\" **************************************************************************
+.\"
+.TH curl_ws_recv 3 "12 Jun 2022" "libcurl 7.85.0" "libcurl Manual"
+.SH NAME
+curl_ws_recv - receive websocket data
+.SH SYNOPSIS
+.nf
+#include <curl/easy.h>
+
+CURLcode curl_ws_recv(CURL *curl, void *buffer, size_t buflen,
+ size_t *nread, unsigned int *recvflags);
+.fi
+.SH DESCRIPTION
+This function call is EXPERIMENTAL.
+
+Retrives as much as possible of a received WebSockets data fragment into the
+\fBbuffer\fP, but not more than \fBbuflen\fP bytes. The provide
+\fIrecvflags\fP argument gets bits set to help characterize the fragment.
+.IP RECVFLAGS
+.IP CURLWS_TEXT
+The buffer contains text data. Note that this makes a difference to WebSockets
+but libcurl itself will not make any verification of the content or
+precautions that you actually receive valid UTF-8 content.
+.IP CURLWS_BINARY
+This is binary data.
+.IP CURLWS_FINAL
+This is the final fragment of the message, if this is not set, it implies that
+there will be another fragment coming as part of the same message.
+.IP CURLWS_CLOSE
+This transfer is now closed.
+.IP CURLWS_PING
+This as an incoming ping message, that expects a pong response.
+.SH EXAMPLE
+.nf
+
+.fi
+.SH AVAILABILITY
+Added in 7.85.0.
+.SH RETURN VALUE
+
+.SH "SEE ALSO"
+.BR curl_easy_setopt "(3), " curl_easy_perform "(3), "
+.BR curl_easy_getinfo "(3), "
+.BR curl_ws_send "(3) "
diff --git a/docs/libcurl/curl_ws_send.3 b/docs/libcurl/curl_ws_send.3
new file mode 100644
index 000000000..7cf103dbe
--- /dev/null
+++ b/docs/libcurl/curl_ws_send.3
@@ -0,0 +1,73 @@
+.\" **************************************************************************
+.\" * _ _ ____ _
+.\" * Project ___| | | | _ \| |
+.\" * / __| | | | |_) | |
+.\" * | (__| |_| | _ <| |___
+.\" * \___|\___/|_| \_\_____|
+.\" *
+.\" * Copyright (C) 1998 - 2022, 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
+.\" *
+.\" **************************************************************************
+.\"
+.TH curl_ws_send 3 "12 Jun 2022" "libcurl 7.85.0" "libcurl Manual"
+.SH NAME
+curl_ws_send - receive websocket data
+.SH SYNOPSIS
+.nf
+#include <curl/easy.h>
+
+CURLcode curl_ws_send(CURL *curl, char *buffer, size_t buflen, size_t *sent,
+ unsigned int sendflags);
+.fi
+.SH DESCRIPTION
+This function call is EXPERIMENTAL.
+
+Send the specific message fragment over the established websockets connection.
+
+If \fBCURLWS_RAW_MODE\fP is enabled in \fICURLOPT_WS_OPTIONS(3)\fP, the
+\fBsendflags\fP argument should be set to 0.
+
+.SH SENDFLAGS
+.IP CURLWS_TEXT
+The buffer contains text data. Note that this makes a difference to WebSockets
+but libcurl itself will not make any verification of the content or
+precautions that you actually send valid UTF-8 content.
+.IP CURLWS_BINARY
+This is binary data.
+.IP CURLWS_NOCOMPRESS
+No-op if there’s no compression anyway.
+.IP CURLWS_CONT
+This is not the final fragment of the message, which implies that there will
+be another fragment coming as part of the same message where this bit is not
+set.
+.IP CURLWS_CLOSE
+Close this transfer.
+.IP CURLWS_PING
+This as a ping.
+.IP CURLWS_PONG
+This as a pong.
+.SH EXAMPLE
+.nf
+
+.fi
+.SH AVAILABILITY
+Added in 7.85.0.
+.SH RETURN VALUE
+
+.SH "SEE ALSO"
+.BR curl_easy_setopt "(3), " curl_easy_perform "(3), "
+.BR curl_easy_getinfo "(3), "
+.BR curl_ws_recv "(3) "
diff --git a/docs/libcurl/opts/CURLOPT_CONNECT_ONLY.3 b/docs/libcurl/opts/CURLOPT_CONNECT_ONLY.3
index 8429e37d9..b198da6c3 100644
--- a/docs/libcurl/opts/CURLOPT_CONNECT_ONLY.3
+++ b/docs/libcurl/opts/CURLOPT_CONNECT_ONLY.3
@@ -42,6 +42,10 @@ useful when used with the \fICURLINFO_ACTIVESOCKET(3)\fP option to
the application can obtain the most recently used socket for special data
transfers.
+Since 7.85.0, this option can be set to '2' and if HTTP or WebSockets are
+used, libcurl will do the request and read all response headers before handing
+over control to the application.
+
Transfers marked connect only will not reuse any existing connections and
connections marked connect only will not be allowed to get reused.
diff --git a/docs/libcurl/opts/CURLOPT_WS_OPTIONS.3 b/docs/libcurl/opts/CURLOPT_WS_OPTIONS.3
new file mode 100644
index 000000000..b5c21b64b
--- /dev/null
+++ b/docs/libcurl/opts/CURLOPT_WS_OPTIONS.3
@@ -0,0 +1,73 @@
+.\" **************************************************************************
+.\" * _ _ ____ _
+.\" * Project ___| | | | _ \| |
+.\" * / __| | | | |_) | |
+.\" * | (__| |_| | _ <| |___
+.\" * \___|\___/|_| \_\_____|
+.\" *
+.\" * Copyright (C) 1998 - 2022, 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
+.\" *
+.\" **************************************************************************
+.\"
+.TH CURLOPT_WS_OPTIONS 3 "10 Jun 2022" "libcurl 7.85.0" "curl_easy_setopt options"
+.SH NAME
+CURLOPT_WS_OPTIONS \- WebSockets behavior options
+.SH SYNOPSIS
+.nf
+#include <curl/curl.h>
+
+CURLcode curl_easy_setopt(CURL *handle, CURLOPT_WS_OPTIONS, long bitmask);
+.fi
+.SH DESCRIPTION
+Pass a long with a bitmask to tell libcurl about specific WebSockets
+behaviors.
+
+To "detatch" a websockets connection and use the \fIcurl_ws_send(3)\fP and
+\fIcurl_ws_recv(3)\fP functions after the HTTP upgrade procedure, set the
+\fICURLOPT_CONNECT_ONLY(3)\fP option to 2L.
+
+Available bits in the bitmask
+.IP "CURLWS_RAW_MODE (1)"
+Deliver "raw" websockets traffic to the \fICURLOPT_WRITEFUNCTION(3)\fP
+callback.
+
+In raw mode, libcurl does not handle pings or any other frame for the
+application.
+.IP "CURLWS_COMPRESS_MODE (2)"
+Negotiate compression for this transfer. (NOT IMPLEMENTED YET)
+.IP "CURLWS_PINGOFF_MODE (4)"
+Disable automated ping/pong handling. (NOT IMPLEMENTED YET)
+.SH DEFAULT
+0
+.SH PROTOCOLS
+WebSockets
+.SH EXAMPLE
+.nf
+CURL *curl = curl_easy_init();
+if(curl) {
+ curl_easy_setopt(curl, CURLOPT_URL, "ws://example.com/");
+ /* use the stand alone API */
+ curl_easy_setopt(curl, CURLOPT_WS_OPTIONS, CURLWS_ALONE);
+ ret = curl_easy_perform(curl);
+ curl_easy_cleanup(curl);
+}
+.fi
+.SH AVAILABILITY
+Added in 7.85.0
+.SH RETURN VALUE
+Returns CURLE_OK if the option is supported, and CURLE_UNKNOWN_OPTION if not.
+.SH "SEE ALSO"
+.BR curl_ws_recv "(3), " curl_ws_send "(3), "
diff --git a/docs/libcurl/opts/Makefile.inc b/docs/libcurl/opts/Makefile.inc
index 03554e85f..a139d0676 100644
--- a/docs/libcurl/opts/Makefile.inc
+++ b/docs/libcurl/opts/Makefile.inc
@@ -404,6 +404,7 @@ man_MANS = \
CURLOPT_WILDCARDMATCH.3 \
CURLOPT_WRITEDATA.3 \
CURLOPT_WRITEFUNCTION.3 \
+ CURLOPT_WS_OPTIONS.3 \
CURLOPT_XFERINFODATA.3 \
CURLOPT_XFERINFOFUNCTION.3 \
CURLOPT_XOAUTH2_BEARER.3 \
diff --git a/docs/libcurl/symbols-in-versions b/docs/libcurl/symbols-in-versions
index d6bcbc6b1..afe800216 100644
--- a/docs/libcurl/symbols-in-versions
+++ b/docs/libcurl/symbols-in-versions
@@ -874,6 +874,7 @@ CURLOPT_WRITEDATA 7.9.7
CURLOPT_WRITEFUNCTION 7.1
CURLOPT_WRITEHEADER 7.1
CURLOPT_WRITEINFO 7.1
+CURLOPT_WS_OPTIONS 7.85.0
CURLOPT_XFERINFODATA 7.32.0
CURLOPT_XFERINFOFUNCTION 7.32.0
CURLOPT_XOAUTH2_BEARER 7.33.0
diff --git a/include/curl/Makefile.am b/include/curl/Makefile.am
index 60e26526e..29f470c09 100644
--- a/include/curl/Makefile.am
+++ b/include/curl/Makefile.am
@@ -23,7 +23,7 @@
###########################################################################
pkginclude_HEADERS = \
curl.h curlver.h easy.h mprintf.h stdcheaders.h multi.h \
- typecheck-gcc.h system.h urlapi.h options.h header.h
+ typecheck-gcc.h system.h urlapi.h options.h header.h websockets.h
pkgincludedir= $(includedir)/curl
diff --git a/include/curl/curl.h b/include/curl/curl.h
index 7a1b56196..45d7978ab 100644
--- a/include/curl/curl.h
+++ b/include/curl/curl.h
@@ -2154,6 +2154,9 @@ typedef enum {
/* specify which protocols that libcurl is allowed to follow directs to */
CURLOPT(CURLOPT_REDIR_PROTOCOLS_STR, CURLOPTTYPE_STRINGPOINT, 319),
+ /* websockets options */
+ CURLOPT(CURLOPT_WS_OPTIONS, CURLOPTTYPE_LONG, 320),
+
CURLOPT_LASTENTRY /* the last unused */
} CURLoption;
@@ -3109,6 +3112,7 @@ CURL_EXTERN CURLcode curl_easy_pause(CURL *handle, int bitmask);
#include "urlapi.h"
#include "options.h"
#include "header.h"
+#include "websockets.h"
/* the typechecker doesn't work in C++ (yet) */
#if defined(__GNUC__) && defined(__GNUC_MINOR__) && \
diff --git a/include/curl/websockets.h b/include/curl/websockets.h
new file mode 100644
index 000000000..ffb9c19b8
--- /dev/null
+++ b/include/curl/websockets.h
@@ -0,0 +1,68 @@
+#ifndef CURLINC_WEBSOCKETS_H
+#define CURLINC_WEBSOCKETS_H
+/***************************************************************************
+ * _ _ ____ _
+ * Project ___| | | | _ \| |
+ * / __| | | | |_) | |
+ * | (__| |_| | _ <| |___
+ * \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) 1998 - 2022, 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
+ *
+ ***************************************************************************/
+
+/* generic in/out flag bits */
+#define CURLWS_TEXT (1<<0)
+#define CURLWS_BINARY (1<<1)
+#define CURLWS_CONT (1<<2)
+#define CURLWS_CLOSE (1<<3)
+#define CURLWS_PING (1<<4)
+
+/*
+ * NAME curl_ws_recv()
+ *
+ * DESCRIPTION
+ *
+ * Receives data from the websocket connection. Use after successful
+ * curl_easy_perform() with CURLOPT_CONNECT_ONLY option.
+ */
+CURL_EXTERN CURLcode curl_ws_recv(CURL *curl, void *buffer, size_t buflen,
+ size_t *recv, unsigned int *recvflags);
+
+/* sendflags for curl_ws_send() */
+#define CURLWS_NOCOMPRESS (1<<5)
+#define CURLWS_PONG (1<<6)
+
+/*
+ * NAME curl_easy_send()
+ *
+ * DESCRIPTION
+ *
+ * Sends data over the websocket connection. Use after successful
+ * curl_easy_perform() with CURLOPT_CONNECT_ONLY option.
+ */
+CURL_EXTERN CURLcode curl_ws_send(CURL *curl, const void *buffer,
+ size_t buflen, size_t *sent,
+ unsigned int sendflags);
+
+typedef ssize_t (*curl_ws_write_callback)(void *userdata, char *data,
+ size_t len,
+ unsigned int flags);
+
+/* bits for the CURLOPT_WS_OPTIONS bitmask: */
+#define CURLWS_RAW_MODE (1<<0)
+
+#endif /* CURLINC_WEBSOCKETS_H */
diff --git a/lib/Makefile.inc b/lib/Makefile.inc
index 0c8972ce5..668712508 100644
--- a/lib/Makefile.inc
+++ b/lib/Makefile.inc
@@ -216,7 +216,8 @@ LIB_CFILES = \
version.c \
version_win32.c \
warnless.c \
- wildcard.c
+ wildcard.c \
+ ws.c
LIB_HFILES = \
altsvc.h \
@@ -338,7 +339,8 @@ LIB_HFILES = \
urldata.h \
version_win32.h \
warnless.h \
- wildcard.h
+ wildcard.h \
+ ws.h
LIB_RCFILES = libcurl.rc
diff --git a/lib/c-hyper.c b/lib/c-hyper.c
index d53b2366c..86abcdb0f 100644
--- a/lib/c-hyper.c
+++ b/lib/c-hyper.c
@@ -54,6 +54,7 @@
#include "multiif.h"
#include "progress.h"
#include "content_encoding.h"
+#include "ws.h"
/* The last 3 #include files should be in this order */
#include "curl_printf.h"
@@ -471,6 +472,24 @@ CURLcode Curl_hyper_stream(struct Curl_easy *data,
if(result)
break;
+ k->deductheadercount =
+ (100 <= http_status && 199 >= http_status)?k->headerbytecount:0;
+#ifdef USE_WEBSOCKETS
+ if(k->upgr101 == UPGR101_WS) {
+ if(http_status == 101) {
+ /* verify the response */
+ result = Curl_ws_accept(data);
+ if(result)
+ return result;
+ }
+ else {
+ failf(data, "Expected 101, got %u", k->httpcode);
+ result = CURLE_HTTP_RETURNED_ERROR;
+ break;
+ }
+ }
+#endif
+
/* Curl_http_auth_act() checks what authentication methods that are
* available and decides which one (if any) to use. It will set 'newurl'
* if an auth method was picked. */
@@ -1123,6 +1142,9 @@ CURLcode Curl_http(struct Curl_easy *data, bool *done)
if(result)
goto error;
+ if(!result && conn->handler->protocol&(CURLPROTO_WS|CURLPROTO_WSS))
+ result = Curl_ws_request(data, headers);
+
result = Curl_add_timecondition(data, headers);
if(result)
goto error;
diff --git a/lib/conncache.c b/lib/conncache.c
index 2a399c881..a557ac6dc 100644
--- a/lib/conncache.c
+++ b/lib/conncache.c
@@ -498,7 +498,7 @@ Curl_conncache_extract_oldest(struct Curl_easy *data)
conn = curr->ptr;
if(!CONN_INUSE(conn) && !conn->bits.close &&
- !conn->bits.connect_only) {
+ !conn->connect_only) {
/* Set higher score for the age passed since the connection was used */
score = Curl_timediff(now, conn->lastused);
diff --git a/lib/easy.c b/lib/easy.c
index 978ea5ac3..3a190d74d 100644
--- a/lib/easy.c
+++ b/lib/easy.c
@@ -1170,8 +1170,7 @@ CURLcode curl_easy_pause(struct Curl_easy *data, int action)
}
-static CURLcode easy_connection(struct Curl_easy *data,
- curl_socket_t *sfd,
+static CURLcode easy_connection(struct Curl_easy *data, curl_socket_t *sfd,
struct connectdata **connp)
{
if(!data)
@@ -1230,11 +1229,12 @@ CURLcode curl_easy_recv(struct Curl_easy *data, void *buffer, size_t buflen,
}
/*
- * Sends data over the connected socket. Use after successful
- * curl_easy_perform() with CURLOPT_CONNECT_ONLY option.
+ * Sends data over the connected socket.
+ *
+ * This is the private internal version of curl_easy_send()
*/
-CURLcode curl_easy_send(struct Curl_easy *data, const void *buffer,
- size_t buflen, size_t *n)
+CURLcode Curl_senddata(struct Curl_easy *data, const void *buffer,
+ size_t buflen, size_t *n)
{
curl_socket_t sfd;
CURLcode result;
@@ -1242,9 +1242,6 @@ CURLcode curl_easy_send(struct Curl_easy *data, const void *buffer,
struct connectdata *c = NULL;
SIGPIPE_VARIABLE(pipe_st);
- if(Curl_is_in_callback(data))
- return CURLE_RECURSIVE_API_CALL;
-
result = easy_connection(data, &sfd, &c);
if(result)
return result;
@@ -1272,6 +1269,19 @@ CURLcode curl_easy_send(struct Curl_easy *data, const void *buffer,
}
/*
+ * Sends data over the connected socket. Use after successful
+ * curl_easy_perform() with CURLOPT_CONNECT_ONLY option.
+ */
+CURLcode curl_easy_send(struct Curl_easy *data, const void *buffer,
+ size_t buflen, size_t *n)
+{
+ if(Curl_is_in_callback(data))
+ return CURLE_RECURSIVE_API_CALL;
+
+ return Curl_senddata(data, buffer, buflen, n);
+}
+
+/*
* Wrapper to call functions in Curl_conncache_foreach()
*
* Returns always 0.
diff --git a/lib/easyif.h b/lib/easyif.h
index 615df3f06..a8289e75a 100644
--- a/lib/easyif.h
+++ b/lib/easyif.h
@@ -27,6 +27,9 @@
/*
* Prototypes for library-wide functions provided by easy.c
*/
+CURLcode Curl_senddata(struct Curl_easy *data, const void *buffer,
+ size_t buflen, size_t *n);
+
#ifdef CURLDEBUG
CURL_EXTERN CURLcode curl_easy_perform_ev(struct Curl_easy *easy);
#endif
diff --git a/lib/easyoptions.c b/lib/easyoptions.c
index 412aefd99..e59b63af7 100644
--- a/lib/easyoptions.c
+++ b/lib/easyoptions.c
@@ -354,6 +354,7 @@ struct curl_easyoption Curl_easyopts[] = {
{"WRITEDATA", CURLOPT_WRITEDATA, CURLOT_CBPTR, 0},
{"WRITEFUNCTION", CURLOPT_WRITEFUNCTION, CURLOT_FUNCTION, 0},
{"WRITEHEADER", CURLOPT_HEADERDATA, CURLOT_CBPTR, CURLOT_FLAG_ALIAS},
+ {"WS_OPTIONS", CURLOPT_WS_OPTIONS, CURLOT_LONG, 0},
{"XFERINFODATA", CURLOPT_XFERINFODATA, CURLOT_CBPTR, 0},
{"XFERINFOFUNCTION", CURLOPT_XFERINFOFUNCTION, CURLOT_FUNCTION, 0},
{"XOAUTH2_BEARER", CURLOPT_XOAUTH2_BEARER, CURLOT_STRING, 0},
@@ -367,6 +368,6 @@ struct curl_easyoption Curl_easyopts[] = {
*/
int Curl_easyopts_check(void)
{
- return ((CURLOPT_LASTENTRY%10000) != (319 + 1));
+ return ((CURLOPT_LASTENTRY%10000) != (320 + 1));
}
#endif
diff --git a/lib/http.c b/lib/http.c
index a527b04d5..2fbd80fb2 100644
--- a/lib/http.c
+++ b/lib/http.c
@@ -84,6 +84,7 @@
#include "strdup.h"
#include "altsvc.h"
#include "hsts.h"
+#include "ws.h"
#include "c-hyper.h"
/* The last 3 #include files should be in this order */
@@ -114,6 +115,10 @@ static int https_getsock(struct Curl_easy *data,
#endif
static CURLcode http_setup_conn(struct Curl_easy *data,
struct connectdata *conn);
+#ifdef USE_WEBSOCKETS
+static CURLcode ws_setup_conn(struct Curl_easy *data,
+ struct connectdata *conn);
+#endif
/*
* HTTP handler interface.
@@ -142,6 +147,32 @@ const struct Curl_handler Curl_handler_http = {
PROTOPT_USERPWDCTRL
};
+#ifdef USE_WEBSOCKETS
+const struct Curl_handler Curl_handler_ws = {
+ "WS", /* scheme */
+ ws_setup_conn, /* setup_connection */
+ Curl_http, /* do_it */
+ Curl_http_done, /* done */
+ ZERO_NULL, /* do_more */
+ Curl_http_connect, /* connect_it */
+ ZERO_NULL, /* connecting */
+ ZERO_NULL, /* doing */
+ ZERO_NULL, /* proto_getsock */
+ http_getsock_do, /* doing_getsock */
+ ZERO_NULL, /* domore_getsock */
+ ZERO_NULL, /* perform_getsock */
+ ZERO_NULL, /* disconnect */
+ ZERO_NULL, /* readwrite */
+ ZERO_NULL, /* connection_check */
+ ZERO_NULL, /* attach connection */
+ PORT_HTTP, /* defport */
+ CURLPROTO_WS, /* protocol */
+ CURLPROTO_HTTP, /* family */
+ PROTOPT_CREDSPERREQUEST | /* flags */
+ PROTOPT_USERPWDCTRL
+};
+#endif
+
#ifdef USE_SSL
/*
* HTTPS handler interface.
@@ -169,6 +200,33 @@ const struct Curl_handler Curl_handler_https = {
PROTOPT_SSL | PROTOPT_CREDSPERREQUEST | PROTOPT_ALPN | /* flags */
PROTOPT_USERPWDCTRL
};
+
+#ifdef USE_WEBSOCKETS
+const struct Curl_handler Curl_handler_wss = {
+ "WSS", /* scheme */
+ ws_setup_conn, /* setup_connection */
+ Curl_http, /* do_it */
+ Curl_http_done, /* done */
+ ZERO_NULL, /* do_more */
+ Curl_http_connect, /* connect_it */
+ https_connecting, /* connecting */
+ ZERO_NULL, /* doing */
+ https_getsock, /* proto_getsock */
+ http_getsock_do, /* doing_getsock */
+ ZERO_NULL, /* domore_getsock */
+ ZERO_NULL, /* perform_getsock */
+ ZERO_NULL, /* disconnect */
+ ZERO_NULL, /* readwrite */
+ ZERO_NULL, /* connection_check */
+ ZERO_NULL, /* attach connection */
+ PORT_HTTPS, /* defport */
+ CURLPROTO_WSS, /* protocol */
+ CURLPROTO_HTTP, /* family */
+ PROTOPT_SSL | PROTOPT_CREDSPERREQUEST | /* flags */
+ PROTOPT_USERPWDCTRL
+};
+#endif
+
#endif
static CURLcode http_setup_conn(struct Curl_easy *data,
@@ -205,6 +263,16 @@ static CURLcode http_setup_conn(struct Curl_easy *data,
return CURLE_OK;
}
+#ifdef USE_WEBSOCKETS
+static CURLcode ws_setup_conn(struct Curl_easy *data,
+ struct connectdata *conn)
+{
+ /* websockets is 1.1 only (for now) */
+ data->state.httpwant = CURL_HTTP_VERSION_1_1;
+ return http_setup_conn(data, conn);
+}
+#endif
+
#ifndef CURL_DISABLE_PROXY
/*
* checkProxyHeaders() checks the linked list of custom proxy headers
@@ -1518,7 +1586,7 @@ CURLcode Curl_http_connect(struct Curl_easy *data, bool *done)
}
#endif
- if(conn->given->protocol & CURLPROTO_HTTPS) {
+ if(conn->given->flags & PROTOPT_SSL) {
/* perform SSL initialization */
result = https_connecting(data, done);
if(result)
@@ -1643,6 +1711,7 @@ CURLcode Curl_http_done(struct Curl_easy *data,
Curl_mime_cleanpart(&http->form);
Curl_dyn_reset(&data->state.headerb);
Curl_hyper_done(data);
+ Curl_ws_done(data);
if(status)
return status;
@@ -2151,9 +2220,9 @@ CURLcode Curl_http_host(struct Curl_easy *data, struct connectdata *conn)
[brackets] if the host name is a plain IPv6-address. RFC2732-style. */
const char *host = conn->host.name;
- if(((conn->given->protocol&CURLPROTO_HTTPS) &&
+ if(((conn->given->protocol&(CURLPROTO_HTTPS|CURLPROTO_WSS)) &&
(conn->remote_port == PORT_HTTPS)) ||
- ((conn->given->protocol&CURLPROTO_HTTP) &&
+ ((conn->given->protocol&(CURLPROTO_HTTP|CURLPROTO_WS)) &&
(conn->remote_port == PORT_HTTP)) )
/* if(HTTPS on port 443) OR (HTTP on port 80) then don't include
the port number in the host string */
@@ -2702,6 +2771,13 @@ CURLcode Curl_http_bodysend(struct Curl_easy *data, struct connectdata *conn,
FIRSTSOCKET);
if(result)
failf(data, "Failed sending HTTP request");
+#ifdef USE_WEBSOCKETS
+ else if((conn->handler->protocol & (CURLPROTO_WS|CURLPROTO_WSS)) &&
+ !(data->set.connect_only))
+ /* Set up the transfer for two-way since without CONNECT_ONLY set, this
+ request probably wants to send data too post upgrade */
+ Curl_setup_transfer(data, FIRSTSOCKET, -1, TRUE, FIRSTSOCKET);
+#endif
else
/* HTTP GET/HEAD download: */
Curl_setup_transfer(data, FIRSTSOCKET, -1, TRUE, -1);
@@ -2731,7 +2807,7 @@ CURLcode Curl_http_cookies(struct Curl_easy *data,
const char *host = data->state.aptr.cookiehost ?
data->state.aptr.cookiehost : conn->host.name;
const bool secure_context =
- conn->handler->protocol&CURLPROTO_HTTPS ||
+ conn->handler->protocol&(CURLPROTO_HTTPS|CURLPROTO_WSS) ||
strcasecompare("localhost", host) ||
!strcmp(host, "127.0.0.1") ||
!strcmp(host, "[::1]") ? TRUE : FALSE;
@@ -3256,6 +3332,8 @@ CURLcode Curl_http(struct Curl_easy *data, bool *done)
}
result = Curl_http_cookies(data, conn, &req);
+ if(!result && conn->handler->protocol&(CURLPROTO_WS|CURLPROTO_WSS))
+ result = Curl_ws_request(data, &req);
if(!result)
result = Curl_add_timecondition(data, &req);
if(!result)
@@ -3568,7 +3646,7 @@ CURLcode Curl_http_header(struct Curl_easy *data, struct connectdata *conn,
const char *host = data->state.aptr.cookiehost?
data->state.aptr.cookiehost:conn->host.name;
const bool secure_context =
- conn->handler->protocol&CURLPROTO_HTTPS ||
+ conn->handler->protocol&(CURLPROTO_HTTPS|CURLPROTO_WSS) ||
strcasecompare("localhost", host) ||
!strcmp(host, "127.0.0.1") ||
!strcmp(host, "[::1]") ? TRUE : FALSE;
@@ -3734,7 +3812,7 @@ CURLcode Curl_http_statusline(struct Curl_easy *data,
connclose(conn, "HTTP/1.0 close after body");
}
else if(conn->httpversion == 20 ||
- (k->upgr101 == UPGR101_REQUESTED && k->httpcode == 101)) {
+ (k->upgr101 == UPGR101_H2 && k->httpcode == 101)) {
DEBUGF(infof(data, "HTTP/2 found, allow multiplexing"));
/* HTTP/2 cannot avoid multiplexing since it is a core functionality
of the protocol */
@@ -3960,9 +4038,9 @@ CURLcode Curl_http_readwrite_headers(struct Curl_easy *data,
break;
case 101:
/* Switching Protocols */
- if(k->upgr101 == UPGR101_REQUESTED) {
+ if(k->upgr101 == UPGR101_H2) {
/* Switching to HTTP/2 */
- infof(data, "Received 101");
+ infof(data, "Received 101, Switching to HTTP/2");
k->upgr101 = UPGR101_RECEIVED;
/* we'll get more headers (HTTP/2 response) */
@@ -3976,8 +4054,21 @@ CURLcode Curl_http_readwrite_headers(struct Curl_easy *data,
return result;
*nread = 0;
}
+#ifdef USE_WEBSOCKETS
+ else if(k->upgr101 == UPGR101_WS) {
+ /* verify the response */
+ result = Curl_ws_accept(data);
+ if(result)
+ return result;
+ k->header = FALSE; /* no more header to parse! */
+ if(data->set.connect_only) {
+ k->keepon &= ~KEEP_RECV; /* read no more content */
+ *nread = 0;
+ }
+ }
+#endif
else {
- /* Switching to another protocol (e.g. WebSocket) */
+ /* Not switching to another protocol */
k->header = FALSE; /* no more header to parse! */
}
break;
@@ -4070,6 +4161,16 @@ CURLcode Curl_http_readwrite_headers(struct Curl_easy *data,
return CURLE_HTTP_RETURNED_ERROR;
}
+#ifdef USE_WEBSOCKETS
+ /* All non-101 HTTP status codes are bad when wanting to upgrade to
+ websockets */
+ if(data->req.upgr101 == UPGR101_WS) {
+ failf(data, "Refused WebSockets upgrade: %d", k->httpcode);
+ return CURLE_HTTP_RETURNED_ERROR;
+ }
+#endif
+
+
data->req.deductheadercount =
(100 <= k->httpcode && 199 >= k->httpcode)?data->req.headerbytecount:0;
diff --git a/lib/http.h b/lib/http.h
index 2ac287eca..a335eee23 100644
--- a/lib/http.h
+++ b/lib/http.h
@@ -24,6 +24,7 @@
*
***************************************************************************/
#include "curl_setup.h"
+#include "ws.h"
typedef enum {
HTTPREQ_GET,
@@ -50,6 +51,15 @@ extern const struct Curl_handler Curl_handler_http;
extern const struct Curl_handler Curl_handler_https;
#endif
+#ifdef USE_WEBSOCKETS
+extern const struct Curl_handler Curl_handler_ws;
+
+#ifdef USE_SSL
+extern const struct Curl_handler Curl_handler_wss;
+#endif
+#endif /* websockets */
+
+
/* Header specific functions */
bool Curl_compareheader(const char *headerline, /* line to check */
const char *header, /* header keyword _with_ colon */
@@ -192,6 +202,15 @@ struct h3out; /* see ngtcp2 */
#endif /* _WIN32 */
#endif /* USE_MSH3 */
+struct websockets {
+ bool contfragment; /* set TRUE if the previous fragment sent was not final */
+ unsigned char mask[4]; /* 32 bit mask for this connection */
+ struct Curl_easy *data; /* used for write callback handling */
+ struct dynbuf buf;
+ size_t usedbuf; /* number of leading bytes in 'buf' the most recent complete
+ websocket frame uses */
+};
+
/****************************************************************************
* HTTP unique setup
***************************************************************************/
@@ -218,6 +237,10 @@ struct HTTP {
HTTPSEND_BODY /* sending body */
} sending;
+#ifdef USE_WEBSOCKETS
+ struct websockets ws;
+#endif
+
#ifndef CURL_DISABLE_HTTP
struct dynbuf send_buffer; /* used if the request couldn't be sent in one
chunk, points to an allocated send_buffer
diff --git a/lib/http2.c b/lib/http2.c
index 3a70528e4..b7409b027 100644
--- a/lib/http2.c
+++ b/lib/http2.c
@@ -1392,7 +1392,7 @@ CURLcode Curl_http2_request_upgrade(struct dynbuf *req,
NGHTTP2_CLEARTEXT_PROTO_VERSION_ID, base64);
free(base64);
- k->upgr101 = UPGR101_REQUESTED;
+ k->upgr101 = UPGR101_H2;
return result;
}
diff --git a/lib/multi.c b/lib/multi.c
index 6e41fb274..01b27f770 100644
--- a/lib/multi.c
+++ b/lib/multi.c
@@ -753,7 +753,7 @@ static int close_connect_only(struct Curl_easy *data,
if(data->state.lastconnect_id != conn->connection_id)
return 0;
- if(!conn->bits.connect_only)
+ if(!conn->connect_only)
return 1;
connclose(conn, "Removing connect-only easy handle");
@@ -2144,7 +2144,7 @@ static CURLMcode multi_runsingle(struct Curl_multi *multi,
}
}
- if(data->set.connect_only) {
+ if(data->set.connect_only == 1) {
/* keep connection open for application to use the socket */
connkeep(data->conn, "CONNECT_ONLY");
multistate(data, MSTATE_DONE);
diff --git a/lib/sendf.c b/lib/sendf.c
index 2fe7169dd..66cec0597 100644
--- a/lib/sendf.c
+++ b/lib/sendf.c
@@ -48,6 +48,7 @@
#include "strdup.h"
#include "http2.h"
#include "headers.h"
+#include "ws.h"
/* The last 3 #include files should be in this order */
#include "curl_printf.h"
@@ -534,6 +535,7 @@ static CURLcode chop_write(struct Curl_easy *data,
curl_write_callback writebody = NULL;
char *ptr = optr;
size_t len = olen;
+ void *writebody_ptr = data->set.out;
if(!len)
return CURLE_OK;
@@ -544,8 +546,18 @@ static CURLcode chop_write(struct Curl_easy *data,
return pausewrite(data, type, ptr, len);
/* Determine the callback(s) to use. */
- if(type & CLIENTWRITE_BODY)
+ if(type & CLIENTWRITE_BODY) {
+#ifdef USE_WEBSOCKETS
+ if(conn->handler->protocol & (CURLPROTO_WS|CURLPROTO_WSS)) {
+ struct HTTP *ws = data->req.p.http;
+ writebody = Curl_ws_writecb;
+ ws->ws.data = data;
+ writebody_ptr = ws;
+ }
+ else
+#endif
writebody = data->set.fwrite_func;
+ }
if((type & CLIENTWRITE_HEADER) &&
(data->set.fwrite_header || data->set.writeheader)) {
/*
@@ -563,7 +575,7 @@ static CURLcode chop_write(struct Curl_easy *data,
if(writebody) {
size_t wrote;
Curl_set_in_callback(data, true);
- wrote = writebody(ptr, 1, chunklen, data->set.out);
+ wrote = writebody(ptr, 1, chunklen, writebody_ptr);
Curl_set_in_callback(data, false);
if(CURL_WRITEFUNC_PAUSE == wrote) {
diff --git a/lib/setopt.c b/lib/setopt.c
index 795c8f450..afc44013c 100644
--- a/lib/setopt.c
+++ b/lib/setopt.c
@@ -2430,9 +2430,14 @@ CURLcode Curl_vsetopt(struct Curl_easy *data, CURLoption option, va_list param)
case CURLOPT_CONNECT_ONLY:
/*
- * No data transfer, set up connection and let application use the socket
+ * No data transfer.
+ * (1) - only do connection
+ * (2) - do first get request but get no content
*/
- data->set.connect_only = (0 != va_arg(param, long)) ? TRUE : FALSE;
+ arg = va_arg(param, long);
+ if(arg > 2)
+ return CURLE_BAD_FUNCTION_ARGUMENT;
+ data->set.connect_only = (unsigned char)arg;
break;
case CURLOPT_SOCKOPTFUNCTION:
@@ -3127,6 +3132,15 @@ CURLcode Curl_vsetopt(struct Curl_easy *data, CURLoption option, va_list param)
case CURLOPT_PREREQDATA:
data->set.prereq_userp = va_arg(param, void *);
break;
+#ifdef USE_WEBSOCKETS
+ case CURLOPT_WS_OPTIONS: {
+ bool raw;
+ arg = va_arg(param, long);
+ raw = (arg & CURLWS_RAW_MODE);
+ data->set.ws_raw_mode = raw;
+ break;
+ }
+#endif
default:
/* unknown tag and its companion, just ignore: */
result = CURLE_UNKNOWN_OPTION;
diff --git a/lib/url.c b/lib/url.c
index 231a00fec..23d8caa10 100644
--- a/lib/url.c
+++ b/lib/url.c
@@ -191,6 +191,16 @@ static const struct Curl_handler * const protocols[] = {
&Curl_handler_http,
#endif
+#ifdef USE_WEBSOCKETS
+#if defined(USE_SSL) && !defined(CURL_DISABLE_HTTP)
+ &Curl_handler_wss,
+#endif
+
+#ifndef CURL_DISABLE_HTTP
+ &Curl_handler_ws,
+#endif
+#endif
+
#ifndef CURL_DISABLE_FTP
&Curl_handler_ftp,
#endif
@@ -867,7 +877,7 @@ void Curl_disconnect(struct Curl_easy *data,
/* Cleanup NEGOTIATE connection-related data */
Curl_http_auth_cleanup_negotiate(conn);
- if(conn->bits.connect_only)
+ if(conn->connect_only)
/* treat the connection as dead in CONNECT_ONLY situations */
dead_connection = TRUE;
@@ -1215,7 +1225,7 @@ ConnectionExists(struct Curl_easy *data,
check = curr->ptr;
curr = curr->next;
- if(check->bits.connect_only || check->bits.close)
+ if(check->connect_only || check->bits.close)
/* connect-only or to-be-closed connections will not be reused */
continue;
@@ -1799,7 +1809,7 @@ static struct connectdata *allocate_conn(struct Curl_easy *data)
conn->proxy_ssl_config.ssl_options = data->set.proxy_ssl.primary.ssl_options;
#endif
conn->ip_version = data->set.ipver;
- conn->bits.connect_only = data->set.connect_only;
+ conn->connect_only = data->set.connect_only;
conn->transport = TRNSPRT_TCP; /* most of them are TCP streams */
#if !defined(CURL_DISABLE_HTTP) && defined(USE_NTLM) && \
diff --git a/lib/urldata.h b/lib/urldata.h
index 0e69ce3d0..f6a644e67 100644
--- a/lib/urldata.h
+++ b/lib/urldata.h
@@ -53,6 +53,14 @@
#define PORT_GOPHER 70
#define PORT_MQTT 1883
+#ifdef USE_WEBSOCKETS
+#define CURLPROTO_WS (1<<30)
+#define CURLPROTO_WSS (1LL<<31)
+#else
+#define CURLPROTO_WS 0
+#define CURLPROTO_WSS 0
+#endif
+
#define DICT_MATCH "/MATCH:"
#define DICT_MATCH2 "/M:"
#define DICT_MATCH3 "/FIND:"
@@ -66,7 +74,8 @@
/* Convenience defines for checking protocols or their SSL based version. Each
protocol handler should only ever have a single CURLPROTO_ in its protocol
field. */
-#define PROTO_FAMILY_HTTP (CURLPROTO_HTTP|CURLPROTO_HTTPS)
+#define PROTO_FAMILY_HTTP (CURLPROTO_HTTP|CURLPROTO_HTTPS|CURLPROTO_WS| \
+ CURLPROTO_WSS)
#define PROTO_FAMILY_FTP (CURLPROTO_FTP|CURLPROTO_FTPS)
#define PROTO_FAMILY_POP3 (CURLPROTO_POP3|CURLPROTO_POP3S)
#define PROTO_FAMILY_SMB (CURLPROTO_SMB|CURLPROTO_SMBS)
@@ -508,7 +517,6 @@ struct ConnectBits {
BIT(multiplex); /* connection is multiplexed */
BIT(tcp_fastopen); /* use TCP Fast Open */
BIT(tls_enable_alpn); /* TLS ALPN extension? */
- BIT(connect_only);
#ifndef CURL_DISABLE_DOH
BIT(doh);
#endif
@@ -574,8 +582,9 @@ enum expect100 {
enum upgrade101 {
UPGR101_INIT, /* default state */
- UPGR101_REQUESTED, /* upgrade requested */
- UPGR101_RECEIVED, /* response received */
+ UPGR101_WS, /* upgrade to WebSockets requested */
+ UPGR101_H2, /* upgrade to HTTP/2 requested */
+ UPGR101_RECEIVED, /* 101 response received */
UPGR101_WORKING /* talking upgraded protocol */
};
@@ -1122,6 +1131,7 @@ struct connectdata {
unsigned char transport; /* one of the TRNSPRT_* defines */
unsigned char ip_version; /* copied from the Curl_easy at creation time */
unsigned char httpversion; /* the HTTP version*10 reported by the server */
+ unsigned char connect_only;
};
/* The end of connectdata. */
@@ -1816,6 +1826,8 @@ struct UserDefined {
BIT(mail_rcpt_allowfails); /* allow RCPT TO command to fail for some
recipients */
#endif
+ unsigned char connect_only; /* make connection/request, then let
+ application use the socket */
BIT(is_fread_set); /* has read callback been set to non-NULL? */
#ifndef CURL_DISABLE_TFTP
BIT(tftp_no_options); /* do not send TFTP options requests */
@@ -1861,7 +1873,6 @@ struct UserDefined {
BIT(no_signal); /* do not use any signal/alarm handler */
BIT(tcp_nodelay); /* whether to enable TCP_NODELAY or not */
BIT(ignorecl); /* ignore content length */
- BIT(connect_only); /* make connection, let application use the socket */
BIT(http_te_skip); /* pass the raw body data to the user, even when
transfer-encoded (chunked, compressed) */
BIT(http_ce_skip); /* pass the raw body data to the user, even when
@@ -1893,6 +1904,9 @@ struct UserDefined {
BIT(doh_verifystatus); /* DoH certificate status verification */
#endif
BIT(http09_allowed); /* allow HTTP/0.9 responses */
+#ifdef USE_WEBSOCKETS
+ BIT(ws_raw_mode);
+#endif
};
struct Names {
diff --git a/lib/ws.c b/lib/ws.c
new file mode 100644
index 000000000..c8884126c
--- /dev/null
+++ b/lib/ws.c
@@ -0,0 +1,610 @@
+/***************************************************************************
+ * _ _ ____ _
+ * Project ___| | | | _ \| |
+ * / __| | | | |_) | |
+ * | (__| |_| | _ <| |___
+ * \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ *
+ * This software is licensed as described in the file COPYING, which
+ * you should have received as part of this distribution. The terms
+ * are also available at https://curl.se/docs/copyright.html.
+ *
+ * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+ * copies of the Software, and permit persons to whom the Software is
+ * furnished to do so, under the terms of the COPYING file.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ * SPDX-License-Identifier: curl
+ *
+ ***************************************************************************/
+#include "curl_setup.h"
+
+#ifdef USE_WEBSOCKETS
+
+#include "urldata.h"
+#include "dynbuf.h"
+#include "rand.h"
+#include "curl_base64.h"
+#include "sendf.h"
+#include "multiif.h"
+#include "ws.h"
+#include "easyif.h"
+#include "transfer.h"
+#include "nonblock.h"
+
+/* The last 3 #include files should be in this order */
+#include "curl_printf.h"
+#include "curl_memory.h"
+#include "memdebug.h"
+
+struct wsfield {
+ const char *name;
+ const char *val;
+};
+
+CURLcode Curl_ws_request(struct Curl_easy *data, REQTYPE *req)
+{
+ unsigned int i;
+ CURLcode result = CURLE_OK;
+ unsigned char rand[16];
+ char *randstr;
+ size_t randlen;
+ char keyval[40];
+ struct SingleRequest *k = &data->req;
+ const struct wsfield heads[]= {
+ {
+ /* The request MUST contain an |Upgrade| header field whose value
+ MUST include the "websocket" keyword. */
+ "Upgrade:", "websocket"
+ },
+ {
+ /* The request MUST contain a |Connection| header field whose value
+ MUST include the "Upgrade" token. */
+ "Connection:", "Upgrade",
+ },
+ {
+ /* The request MUST include a header field with the name
+ |Sec-WebSocket-Version|. The value of this header field MUST be
+ 13. */
+ "Sec-WebSocket-Version:", "13",
+ },
+ {
+ /* The request MUST include a header field with the name
+ |Sec-WebSocket-Key|. The value of this header field MUST be a nonce
+ consisting of a randomly selected 16-byte value that has been
+ base64-encoded (see Section 4 of [RFC4648]). The nonce MUST be
+ selected randomly for each connection. */
+ "Sec-WebSocket-Key:", &keyval[0]
+ }
+ };
+
+ /* 16 bytes random */
+ result = Curl_rand(data, (unsigned char *)rand, sizeof(rand));
+ if(result)
+ return result;
+ result = Curl_base64_encode((char *)rand, sizeof(rand), &randstr, &randlen);
+ if(result)
+ return result;
+ DEBUGASSERT(randlen < sizeof(keyval));
+ if(randlen >= sizeof(keyval))
+ return CURLE_FAILED_INIT;
+ strcpy(keyval, randstr);
+ free(randstr);
+ for(i = 0; !result && (i < sizeof(heads)/sizeof(heads[0])); i++) {
+ if(!Curl_checkheaders(data, STRCONST(heads[i].name))) {
+#ifdef USE_HYPER
+ char field[128];
+ msnprintf(field, sizeof(field), "%s %s", heads[i].name,
+ heads[i].val);
+ result = Curl_hyper_header(data, req, field);
+#else
+ (void)data;
+ result = Curl_dyn_addf(req, "%s %s\r\n", heads[i].name,
+ heads[i].val);
+#endif
+ }
+ }
+ k->upgr101 = UPGR101_WS;
+ Curl_dyn_init(&data->req.p.http->ws.buf, MAX_WS_SIZE * 2);
+ return result;
+}
+
+CURLcode Curl_ws_accept(struct Curl_easy *data)
+{
+ struct SingleRequest *k = &data->req;
+ struct HTTP *ws = data->req.p.http;
+ struct connectdata *conn = data->conn;
+ CURLcode result;
+
+ /* Verify the Sec-WebSocket-Accept response.
+
+ The sent value is the base64 encoded version of a SHA-1 hash done on the
+ |Sec-WebSocket-Key| header field concatenated with
+ the string "258EAFA5-E914-47DA-95CA-C5AB0DC85B11".
+ */
+
+ /* If the response includes a |Sec-WebSocket-Extensions| header field and
+ this header field indicates the use of an extension that was not present
+ in the client's handshake (the server has indicated an extension not
+ requested by the client), the client MUST Fail the WebSocket Connection.
+ */
+
+ /* If the response includes a |Sec-WebSocket-Protocol| header field
+ and this header field indicates the use of a subprotocol that was
+ not present in the client's handshake (the server has indicated a
+ subprotocol not requested by the client), the client MUST Fail
+ the WebSocket Connection. */
+
+ /* 4 bytes random */
+ result = Curl_rand(data, (unsigned char *)&ws->ws.mask, sizeof(ws->ws.mask));
+ if(result)
+ return result;
+
+ infof(data, "Recevied 101, switch to WebSockets; mask %02x%02x%02x%02x",
+ ws->ws.mask[0], ws->ws.mask[1], ws->ws.mask[2], ws->ws.mask[3]);
+ k->upgr101 = UPGR101_RECEIVED;
+
+ if(data->set.connect_only)
+ /* switch off non-blocking sockets */
+ (void)curlx_nonblock(conn->sock[FIRSTSOCKET], FALSE);
+
+ return result;
+}
+
+#define WSBIT_FIN 0x80
+#define WSBIT_OPCODE_CONT 0
+#define WSBIT_OPCODE_TEXT (1)
+#define WSBIT_OPCODE_BIN (2)
+#define WSBIT_OPCODE_CLOSE (8)
+#define WSBIT_OPCODE_PING (9)
+#define WSBIT_OPCODE_PONG (0xa)
+#define WSBIT_OPCODE_MASK (0xf)
+
+#define WSBIT_MASK 0x80
+
+/* remove the spent bytes from the beginning of the buffer as that part has
+ now been delivered to the application */
+static void ws_decode_clear(struct Curl_easy *data)
+{
+ struct websockets *wsp = &data->req.p.http->ws;
+ size_t spent = wsp->usedbuf;
+ size_t len = Curl_dyn_len(&wsp->buf);
+ size_t keep = len - spent;
+ DEBUGASSERT(len >= spent);
+ Curl_dyn_tail(&wsp->buf, keep);
+}
+
+/* ws_decode() decodes a binary frame into structured WebSocket data,
+
+ wpkt - the incoming raw data. If NULL, work on the already buffered data.
+ ilen - the size of the provided data, perhaps too little, perhaps too much
+ out - stored pointed to extracted data
+ olen - stored length of the extracted data
+ endp - stored pointer to data immediately following the parsed data, if
+ there is more data in there. NULL if there's no more data.
+ flags - stored bitmask about the frame
+
+ Returns CURLE_AGAIN if there is only a partial frame in the buffer. Then it
+ stores the first part in the ->extra buffer to be used in the next call
+ when more data is provided.
+*/
+
+static CURLcode ws_decode(struct Curl_easy *data,
+ unsigned char *wpkt, size_t ilen,
+ unsigned char **out, size_t *olen,
+ unsigned char **endp,
+ unsigned int *flags)
+{
+ bool fin;
+ unsigned char opcode;
+ size_t total;
+ size_t dataindex = 2;
+ size_t plen; /* size of data in the buffer */
+ size_t payloadssize;
+ struct websockets *wsp = &data->req.p.http->ws;
+ unsigned char *p;
+ CURLcode result;
+
+ *olen = 0;
+
+ /* add the incoming bytes, if any */
+ if(wpkt) {
+ result = Curl_dyn_addn(&wsp->buf, wpkt, ilen);
+ if(result)
+ return result;
+ }
+
+ plen = Curl_dyn_len(&wsp->buf);
+ if(plen < 2) {
+ /* the smallest possible frame is two bytes */
+ infof(data, "WS: plen == %u, EAGAIN", (int)plen);
+ return CURLE_AGAIN;
+ }
+
+ p = Curl_dyn_uptr(&wsp->buf);
+
+ fin = p[0] & WSBIT_FIN;
+ opcode = p[0] & WSBIT_OPCODE_MASK;
+ infof(data, "WS:%d received FIN bit %u", __LINE__, (int)fin);
+ *flags = 0;
+ switch(opcode) {
+ case WSBIT_OPCODE_CONT:
+ if(!fin)
+ *flags |= CURLWS_CONT;
+ infof(data, "WS: received OPCODE CONT");
+ break;
+ case WSBIT_OPCODE_TEXT:
+ infof(data, "WS: received OPCODE TEXT");
+ *flags |= CURLWS_TEXT;
+ break;
+ case WSBIT_OPCODE_BIN:
+ infof(data, "WS: received OPCODE BINARY");
+ *flags |= CURLWS_BINARY;
+ break;
+ case WSBIT_OPCODE_CLOSE:
+ infof(data, "WS: received OPCODE CLOSE");
+ *flags |= CURLWS_CLOSE;
+ break;
+ case WSBIT_OPCODE_PING:
+ infof(data, "WS: received OPCODE PING");
+ *flags |= CURLWS_PING;
+ break;
+ case WSBIT_OPCODE_PONG:
+ infof(data, "WS: received OPCODE PONG");
+ *flags |= CURLWS_PONG;
+ break;
+ }
+
+ if(p[1] & WSBIT_MASK) {
+ /* A client MUST close a connection if it detects a masked frame. */
+ failf(data, "WS: masked input frame");
+ return CURLE_RECV_ERROR;
+ }
+ payloadssize = p[1];
+ if(payloadssize == 126) {
+ if(plen < 4) {
+ infof(data, "WS:%d plen == %u, EAGAIN", __LINE__, (int)plen);
+ return CURLE_AGAIN; /* not enough data available */
+ }
+ payloadssize = (p[2] << 8) | p[3];
+ dataindex += 2;
+ }
+ else if(payloadssize == 127) {
+ failf(data, "WS: too large frame received");
+ return CURLE_RECV_ERROR;
+ }
+
+ total = dataindex + payloadssize;
+ if(total > plen) {
+ /* not enough data in buffer yet */
+ infof(data, "WS:%d plen == %u (%u), EAGAIN", __LINE__, (int)plen,
+ (int)total);
+ return CURLE_AGAIN;
+ }
+
+ /* point to the payload */
+ *out = &p[dataindex];
+
+ /* return the payload length */
+ *olen = payloadssize;
+ wsp->usedbuf = total; /* number of bytes "used" from the buffer */
+ *endp = &p[total];
+ infof(data, "WS: received %u bytes payload", payloadssize);
+ return CURLE_OK;
+}
+
+/* Curl_ws_writecb() is the write callback for websocket traffic. The
+ websocket data is provided to this raw, in chunks. This function should
+ handle/decode the data and call the "real" underlying callback accordingly.
+*/
+size_t Curl_ws_writecb(char *buffer, size_t size /* 1 */,
+ size_t nitems, void *userp)
+{
+ struct HTTP *ws = (struct HTTP *)userp;
+ struct Curl_easy *data = ws->ws.data;
+ void *writebody_ptr = data->set.out;
+ if(data->set.ws_raw_mode)
+ return data->set.fwrite_func(buffer, size, nitems, writebody_ptr);
+ else if(nitems) {
+ unsigned char *wsp;
+ size_t wslen;
+ unsigned int recvflags;
+ CURLcode result;
+ unsigned char *endp;
+ decode:
+ result = ws_decode(data, (unsigned char *)buffer, nitems,
+ &wsp, &wslen, &endp, &recvflags);
+ if(result == CURLE_AGAIN)
+ /* insufficient amount of data, keep it for later */
+ return nitems;
+ else if(result) {
+ infof(data, "WS: decode error %d", (int)result);
+ return nitems - 1;
+ }
+ /* auto-respond to PINGs */
+ if(recvflags & CURLWS_PING) {
+ size_t bytes;
+ infof(data, "WS: auto-respond to PING with a PONG");
+ /* send back the exact same content as a PONG */
+ result = curl_ws_send(data, wsp, wslen, &bytes, CURLWS_PONG);
+ if(result)
+ return result;
+ }
+ else {
+ /* TODO: store details about the frame in a struct to be reachable with
+ curl_ws_meta() from within the write callback */
+
+ /* deliver the decoded frame to the user callback */
+ if(data->set.fwrite_func((char *)wsp, 1, wslen, writebody_ptr) != wslen)
+ return 0;
+ }
+ /* the websocket frame has been delivered */
+ ws_decode_clear(data);
+ if(endp) {
+ /* there's more websocket data to deal with in the buffer */
+ buffer = NULL; /* don't pass in the data again */
+ goto decode;
+ }
+ }
+ return nitems;
+}
+
+
+CURLcode curl_ws_recv(struct Curl_easy *data, void *buffer, size_t buflen,
+ size_t *nread, unsigned int *recvflags)
+{
+ size_t bytes;
+ CURLcode result;
+
+ *nread = 0;
+ *recvflags = 0;
+ /* get a download buffer */
+ result = Curl_preconnect(data);
+ if(result)
+ return result;
+
+ do {
+ result = curl_easy_recv(data, data->state.buffer,
+ data->set.buffer_size, &bytes);
+ if(result)
+ return result;
+
+ if(bytes) {
+ unsigned char *out;
+ size_t olen;
+ unsigned char *endp;
+ infof(data, "WS: got %u websocket bytes to decode", (int)bytes);
+ result = ws_decode(data, (unsigned char *)data->state.buffer,
+ bytes, &out, &olen, &endp, recvflags);
+ if(result == CURLE_AGAIN)
+ /* a packet fragment only */
+ break;
+ else if(result)
+ return result;
+
+ /* auto-respond to PINGs */
+ if(*recvflags & CURLWS_PING) {
+ infof(data, "WS: auto-respond to PING with a PONG");
+ /* send back the exact same content as a PONG */
+ result = curl_ws_send(data, out, olen, &bytes, CURLWS_PONG);
+ if(result)
+ return result;
+ }
+ else {
+ if(olen < buflen) {
+ /* copy the payload to the user buffer */
+ memcpy(buffer, out, olen);
+ *nread = olen;
+ }
+ else {
+ /* Received a larger websocket frame than what could fit in the user
+ provided buffer! */
+ infof(data, "WS: too large websocket frame received");
+ return CURLE_RECV_ERROR;
+ }
+ }
+ /* the websocket frame has been delivered */
+ ws_decode_clear(data);
+ }
+ else
+ *nread = bytes;
+ break;
+ } while(1);
+ return CURLE_OK;
+}
+
+/***
+ RFC 6455 Section 5.2
+
+ 0 1 2 3
+ 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ +-+-+-+-+-------+-+-------------+-------------------------------+
+ |F|R|R|R| opcode|M| Payload len | Extended payload length |
+ |I|S|S|S| (4) |A| (7) | (16/64) |
+ |N|V|V|V| |S| | (if payload len==126/127) |
+ | |1|2|3| |K| | |
+ +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
+ | Extended payload length continued, if payload len == 127 |
+ + - - - - - - - - - - - - - - - +-------------------------------+
+ | |Masking-key, if MASK set to 1 |
+ +-------------------------------+-------------------------------+
+ | Masking-key (continued) | Payload Data |
+ +-------------------------------- - - - - - - - - - - - - - - - +
+ : Payload Data continued ... :
+ + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
+ | Payload Data continued ... |
+ +---------------------------------------------------------------+
+*/
+
+static size_t ws_packet(struct Curl_easy *data,
+ const unsigned char *payload, size_t len,
+ unsigned int flags)
+{
+ struct HTTP *ws = data->req.p.http;
+ unsigned char *out = (unsigned char *)data->state.ulbuf;
+ unsigned char firstbyte = 0;
+ int outi;
+ unsigned char opcode;
+ unsigned int xori;
+ unsigned int i;
+ if(flags & CURLWS_TEXT) {
+ opcode = WSBIT_OPCODE_TEXT;
+ infof(data, "WS: send OPCODE TEXT");
+ }
+ else if(flags & CURLWS_CLOSE) {
+ opcode = WSBIT_OPCODE_CLOSE;
+ infof(data, "WS: send OPCODE CLOSE");
+ }
+ else if(flags & CURLWS_PING) {
+ opcode = WSBIT_OPCODE_PING;
+ infof(data, "WS: send OPCODE PING");
+ }
+ else if(flags & CURLWS_PONG) {
+ opcode = WSBIT_OPCODE_PONG;
+ infof(data, "WS: send OPCODE PONG");
+ }
+ else {
+ opcode = WSBIT_OPCODE_BIN;
+ infof(data, "WS: send OPCODE BINARY");
+ }
+
+ if(!(flags & CURLWS_CONT)) {
+ /* if not marked as continuing, assume this is the final fragment */
+ firstbyte |= WSBIT_FIN | opcode;
+ ws->ws.contfragment = FALSE;
+ }
+ else if(ws->ws.contfragment) {
+ /* the previous fragment was not a final one and this isn't either, keep a
+ CONT opcode and no FIN bit */
+ firstbyte |= WSBIT_OPCODE_CONT;
+ }
+ else {
+ ws->ws.contfragment = TRUE;
+ }
+ out[0] = firstbyte;
+ if(len > 126) {
+ /* no support for > 16 bit fragment sizes */
+ out[1] = 126 | WSBIT_MASK;
+ out[2] = (len >> 8) & 0xff;
+ out[3] = len & 0xff;
+ outi = 4;
+ }
+ else {
+ out[1] = (unsigned char)len | WSBIT_MASK;
+ outi = 2;
+ }
+
+ infof(data, "WS: send FIN bit %u (byte %02x)",
+ firstbyte & WSBIT_FIN ? 1 : 0,
+ firstbyte);
+ infof(data, "WS: send payload len %u", (int)len);
+
+ /* 4 bytes mask */
+ memcpy(&out[outi], &ws->ws.mask, 4);
+
+ if(data->set.upload_buffer_size < (len + 10))
+ return 0;
+
+ /* pass over the mask */
+ outi += 4;
+
+ /* append payload after the mask, XOR appropriately */
+ for(i = 0, xori = 0; i < len; i++, outi++) {
+ out[outi] = payload[i] ^ ws->ws.mask[xori];
+ xori++;
+ xori &= 3;
+ }
+
+ /* return packet size */
+ return outi;
+}
+
+CURLcode curl_ws_send(struct Curl_easy *data, const void *buffer,
+ size_t buflen, size_t *sent,
+ unsigned int sendflags)
+{
+ size_t bytes;
+ CURLcode result;
+ size_t plen;
+ char *out;
+
+ if(buflen > MAX_WS_SIZE) {
+ failf(data, "too large packet");
+ return CURLE_BAD_FUNCTION_ARGUMENT;
+ }
+
+ if(!data->set.ws_raw_mode) {
+ result = Curl_get_upload_buffer(data);
+ if(result)
+ return result;
+ }
+
+ if(Curl_is_in_callback(data)) {
+ ssize_t written;
+ if(data->set.ws_raw_mode) {
+ /* raw mode sends exactly what was requested, and this is from within
+ the write callback */
+ result = Curl_write(data, data->conn->writesockfd, buffer, buflen,
+ &written);
+ infof(data, "WS: wanted to send %u bytes, sent %u bytes",
+ (int)buflen, (int)written);
+ }
+ else {
+ plen = ws_packet(data, buffer, buflen, sendflags);
+ out = data->state.ulbuf;
+ result = Curl_write(data, data->conn->writesockfd, out, plen,
+ &written);
+ infof(data, "WS: wanted to send %u bytes, sent %u bytes",
+ (int)plen, (int)written);
+ }
+ bytes = written;
+ }
+ else {
+ plen = ws_packet(data, buffer, buflen, sendflags);
+
+ out = data->state.ulbuf;
+ result = Curl_senddata(data, out, plen, &bytes);
+ (void)sendflags;
+ }
+ *sent = bytes;
+
+ return result;
+}
+
+void Curl_ws_done(struct Curl_easy *data)
+{
+ struct websockets *wsp = &data->req.p.http->ws;
+ DEBUGASSERT(wsp);
+ Curl_dyn_free(&wsp->buf);
+}
+
+#else
+
+CURL_EXTERN CURLcode curl_ws_recv(CURL *curl, void *buffer, size_t buflen,
+ size_t *nread, unsigned int *recvflags)
+{
+ (void)curl;
+ (void)buffer;
+ (void)buflen;
+ (void)nread;
+ (void)recvflags;
+ return CURLE_OK;
+}
+
+CURL_EXTERN CURLcode curl_ws_send(CURL *curl, const void *buffer,
+ size_t buflen, size_t *sent,
+ unsigned int sendflags)
+{
+ (void)curl;
+ (void)buffer;
+ (void)buflen;
+ (void)sent;
+ (void)sendflags;
+ return CURLE_OK;
+}
+
+#endif /* USE_WEBSOCKETS */
diff --git a/lib/ws.h b/lib/ws.h
new file mode 100644
index 000000000..2af5362cb
--- /dev/null
+++ b/lib/ws.h
@@ -0,0 +1,50 @@
+#ifndef HEADER_CURL_WS_H
+#define HEADER_CURL_WS_H
+/***************************************************************************
+ * _ _ ____ _
+ * Project ___| | | | _ \| |
+ * / __| | | | |_) | |
+ * | (__| |_| | _ <| |___
+ * \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ *
+ * This software is licensed as described in the file COPYING, which
+ * you should have received as part of this distribution. The terms
+ * are also available at https://curl.se/docs/copyright.html.
+ *
+ * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+ * copies of the Software, and permit persons to whom the Software is
+ * furnished to do so, under the terms of the COPYING file.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ * SPDX-License-Identifier: curl
+ *
+ ***************************************************************************/
+#include "curl_setup.h"
+
+#ifdef USE_WEBSOCKETS
+
+#ifdef USE_HYPER
+#define REQTYPE void
+#else
+#define REQTYPE struct dynbuf
+#endif
+
+/* this is the largest single fragment size we support */
+#define MAX_WS_SIZE 65535
+
+CURLcode Curl_ws_request(struct Curl_easy *data, REQTYPE *req);
+CURLcode Curl_ws_accept(struct Curl_easy *data);
+
+size_t Curl_ws_writecb(char *buffer, size_t size, size_t nitems, void *userp);
+void Curl_ws_done(struct Curl_easy *data);
+
+#else
+#define Curl_ws_request(x,y) CURLE_OK
+#define Curl_ws_done(x) Curl_nop_stmt
+#endif
+
+#endif /* HEADER_CURL_WS_H */