parent
60a3b25dbf
commit
664249d095
@ -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()
|
||||
|
||||
21
configure.ac
21
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)
|
||||
|
||||
@ -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:
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
66
docs/libcurl/curl_ws_recv.3
Normal file
66
docs/libcurl/curl_ws_recv.3
Normal file
@ -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) "
|
||||
73
docs/libcurl/curl_ws_send.3
Normal file
73
docs/libcurl/curl_ws_send.3
Normal file
@ -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) "
|
||||
@ -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.
|
||||
|
||||
|
||||
73
docs/libcurl/opts/CURLOPT_WS_OPTIONS.3
Normal file
73
docs/libcurl/opts/CURLOPT_WS_OPTIONS.3
Normal file
@ -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), "
|
||||
@ -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 \
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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__) && \
|
||||
|
||||
68
include/curl/websockets.h
Normal file
68
include/curl/websockets.h
Normal file
@ -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 */
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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);
|
||||
|
||||
|
||||
28
lib/easy.c
28
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;
|
||||
@ -1271,6 +1268,19 @@ CURLcode curl_easy_send(struct Curl_easy *data, const void *buffer,
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
* 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()
|
||||
*
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
119
lib/http.c
119
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;
|
||||
|
||||
|
||||
23
lib/http.h
23
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
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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);
|
||||
|
||||
16
lib/sendf.c
16
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) {
|
||||
|
||||
18
lib/setopt.c
18
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;
|
||||
|
||||
16
lib/url.c
16
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) && \
|
||||
|
||||
@ -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 {
|
||||
|
||||
610
lib/ws.c
Normal file
610
lib/ws.c
Normal file
@ -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 */
|
||||
50
lib/ws.h
Normal file
50
lib/ws.h
Normal file
@ -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 */
|
||||
Loading…
Reference in New Issue
Block a user