1
0
Fork 0
mirror of https://github.com/warmcat/libwebsockets.git synced 2025-03-16 00:00:07 +01:00
libwebsockets/lib/roles/http/client/client-http.c
Andy Green 6710279e21 client: use block parse and buflist
With http, the protocol doesn't indicate where the headers end and the
next transaction or body begin.  Until now, we handled that for client
header response parsing by reading from the tls buffer bytewise.

This modernizes the code to read in up to 256-byte chunks and parse
the chunks in one hit (the parse API is already set up for doing this
elsewhere).

Now we have a generic input buflist, adapt the parser loop to go through
that and arrange that any leftovers are placed on there.
2019-09-22 03:08:36 -07:00

1400 lines
36 KiB
C

/*
* libwebsockets - small server side websockets and web server implementation
*
* Copyright (C) 2010 - 2019 Andy Green <andy@warmcat.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
#include "private-lib-core.h"
LWS_VISIBLE LWS_EXTERN void
lws_client_http_body_pending(struct lws *wsi, int something_left_to_send)
{
wsi->client_http_body_pending = !!something_left_to_send;
}
/*
* return self, or queued client wsi we are acting on behalf of
*
* That is the TAIL of the queue (new queue elements are added at the HEAD)
*/
struct lws *
lws_client_wsi_effective(struct lws *wsi)
{
struct lws_dll2 *tail = lws_dll2_get_tail(&wsi->dll2_cli_txn_queue_owner);
if (!wsi->transaction_from_pipeline_queue || !tail)
return wsi;
return lws_container_of(tail, struct lws, dll2_cli_txn_queue);
}
/*
* return self or the guy we are queued under
*
* REQUIRES VHOST LOCK HELD
*/
static struct lws *
_lws_client_wsi_master(struct lws *wsi)
{
struct lws_dll2_owner *o = wsi->dll2_cli_txn_queue.owner;
if (!o)
return wsi;
return lws_container_of(o, struct lws, dll2_cli_txn_queue_owner);
}
int
lws_client_socket_service(struct lws *wsi, struct lws_pollfd *pollfd,
struct lws *wsi_conn)
{
struct lws_context *context = wsi->context;
struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi];
char *p = (char *)&pt->serv_buf[0];
struct lws *w;
#if defined(LWS_WITH_TLS)
char ebuf[128];
#endif
const char *cce = NULL;
char *sb = p;
int n = 0;
#if defined(LWS_WITH_SOCKS5)
int conn_mode = 0, pending_timeout = 0;
#endif
if ((pollfd->revents & LWS_POLLOUT) &&
wsi->keepalive_active &&
wsi->dll2_cli_txn_queue_owner.head) {
struct lws *wfound = NULL;
lwsl_debug("%s: pollout HANDSHAKE2\n", __func__);
/*
* We have a transaction queued that wants to pipeline.
*
* We have to allow it to send headers strictly in the order
* that it was queued, ie, tail-first.
*/
lws_vhost_lock(wsi->vhost);
lws_start_foreach_dll_safe(struct lws_dll2 *, d, d1,
wsi->dll2_cli_txn_queue_owner.head) {
struct lws *w = lws_container_of(d, struct lws,
dll2_cli_txn_queue);
lwsl_debug("%s: %p states 0x%lx\n", __func__, w,
(unsigned long)w->wsistate);
if (lwsi_state(w) == LRS_H1C_ISSUE_HANDSHAKE2)
wfound = w;
} lws_end_foreach_dll_safe(d, d1);
if (wfound) {
#if defined(LWS_WITH_DETAILED_LATENCY)
wfound->detlat.earliest_write_req_pre_write = lws_now_usecs();
#endif
/*
* pollfd has the master sockfd in it... we
* need to use that in HANDSHAKE2 to understand
* which wsi to actually write on
*/
if (lws_client_socket_service(wfound, pollfd, wsi) < 0) {
/* closed */
lws_vhost_unlock(wsi->vhost);
return -1;
}
lws_callback_on_writable(wsi);
} else
lwsl_debug("%s: didn't find anything in txn q in HS2\n",
__func__);
lws_vhost_unlock(wsi->vhost);
return 0;
}
switch (lwsi_state(wsi)) {
case LRS_WAITING_DNS:
/*
* we are under PENDING_TIMEOUT_SENT_CLIENT_HANDSHAKE
* timeout protection set in client-handshake.c
*/
lwsl_err("%s: wsi %p: WAITING_DNS\n", __func__, wsi);
if (!lws_client_connect_2_dnsreq(wsi)) {
/* closed */
lwsl_client("closed\n");
return -1;
}
/* either still pending connection, or changed mode */
return 0;
case LRS_WAITING_CONNECT:
/*
* we are under PENDING_TIMEOUT_SENT_CLIENT_HANDSHAKE
* timeout protection set in client-handshake.c
*/
if (pollfd->revents & LWS_POLLOUT)
lws_client_connect_3_connect(wsi, NULL, NULL, 0, NULL);
break;
#if defined(LWS_WITH_SOCKS5)
/* SOCKS Greeting Reply */
case LRS_WAITING_SOCKS_GREETING_REPLY:
case LRS_WAITING_SOCKS_AUTH_REPLY:
case LRS_WAITING_SOCKS_CONNECT_REPLY:
/* handle proxy hung up on us */
if (pollfd->revents & LWS_POLLHUP) {
lwsl_warn("SOCKS connection %p (fd=%d) dead\n",
(void *)wsi, pollfd->fd);
cce = "socks conn dead";
goto bail3;
}
n = recv(wsi->desc.sockfd, sb, context->pt_serv_buf_size, 0);
if (n < 0) {
if (LWS_ERRNO == LWS_EAGAIN) {
lwsl_debug("SOCKS read EAGAIN, retrying\n");
return 0;
}
lwsl_err("ERROR reading from SOCKS socket\n");
cce = "socks recv fail";
goto bail3;
}
switch (lwsi_state(wsi)) {
case LRS_WAITING_SOCKS_GREETING_REPLY:
if (pt->serv_buf[0] != SOCKS_VERSION_5)
goto socks_reply_fail;
if (pt->serv_buf[1] == SOCKS_AUTH_NO_AUTH) {
lwsl_client("SOCKS GR: No Auth Method\n");
if (socks_generate_msg(wsi, SOCKS_MSG_CONNECT, &len))
goto socks_send_msg_fail;
conn_mode = LRS_WAITING_SOCKS_CONNECT_REPLY;
pending_timeout =
PENDING_TIMEOUT_AWAITING_SOCKS_CONNECT_REPLY;
goto socks_send;
}
if (pt->serv_buf[1] == SOCKS_AUTH_USERNAME_PASSWORD) {
lwsl_client("SOCKS GR: User/Pw Method\n");
if (socks_generate_msg(wsi,
SOCKS_MSG_USERNAME_PASSWORD,
&len))
goto socks_send_msg_fail;
conn_mode = LRS_WAITING_SOCKS_AUTH_REPLY;
pending_timeout =
PENDING_TIMEOUT_AWAITING_SOCKS_AUTH_REPLY;
goto socks_send;
}
goto socks_reply_fail;
case LRS_WAITING_SOCKS_AUTH_REPLY:
if (pt->serv_buf[0] != SOCKS_SUBNEGOTIATION_VERSION_1 ||
pt->serv_buf[1] !=
SOCKS_SUBNEGOTIATION_STATUS_SUCCESS)
goto socks_reply_fail;
lwsl_client("SOCKS password OK, sending connect\n");
if (socks_generate_msg(wsi, SOCKS_MSG_CONNECT, &len)) {
socks_send_msg_fail:
cce = "socks gen msg fail";
goto bail3;
}
conn_mode = LRS_WAITING_SOCKS_CONNECT_REPLY;
pending_timeout =
PENDING_TIMEOUT_AWAITING_SOCKS_CONNECT_REPLY;
socks_send:
n = send(wsi->desc.sockfd, (char *)pt->serv_buf, len,
MSG_NOSIGNAL);
if (n < 0) {
lwsl_debug("ERROR writing to socks proxy\n");
cce = "socks write fail";
goto bail3;
}
lws_set_timeout(wsi, pending_timeout, AWAITING_TIMEOUT);
lwsi_set_state(wsi, conn_mode);
break;
socks_reply_fail:
lwsl_notice("socks reply: v%d, err %d\n",
pt->serv_buf[0], pt->serv_buf[1]);
cce = "socks reply fail";
goto bail3;
case LRS_WAITING_SOCKS_CONNECT_REPLY:
if (pt->serv_buf[0] != SOCKS_VERSION_5 ||
pt->serv_buf[1] != SOCKS_REQUEST_REPLY_SUCCESS)
goto socks_reply_fail;
lwsl_client("socks connect OK\n");
/* free stash since we are done with it */
lws_free_set_NULL(wsi->stash);
if (lws_hdr_simple_create(wsi,
_WSI_TOKEN_CLIENT_PEER_ADDRESS,
wsi->vhost->socks_proxy_address)) {
cce = "socks connect fail";
goto bail3;
}
wsi->c_port = wsi->vhost->socks_proxy_port;
/* clear his proxy connection timeout */
lws_set_timeout(wsi, NO_PENDING_TIMEOUT, 0);
goto start_ws_handshake;
default:
break;
}
break;
#endif
case LRS_WAITING_PROXY_REPLY:
/* handle proxy hung up on us */
if (pollfd->revents & LWS_POLLHUP) {
lwsl_warn("Proxy connection %p (fd=%d) dead\n",
(void *)wsi, pollfd->fd);
cce = "proxy conn dead";
goto bail3;
}
n = recv(wsi->desc.sockfd, sb, context->pt_serv_buf_size, 0);
if (n < 0) {
if (LWS_ERRNO == LWS_EAGAIN) {
lwsl_debug("Proxy read EAGAIN... retrying\n");
return 0;
}
lwsl_err("ERROR reading from proxy socket\n");
cce = "proxy read err";
goto bail3;
}
pt->serv_buf[13] = '\0';
if (strncmp(sb, "HTTP/1.0 200 ", 13) &&
strncmp(sb, "HTTP/1.1 200 ", 13)) {
lwsl_err("%s: ERROR proxy did not reply with h1\n",
__func__);
cce = "proxy not h1";
goto bail3;
}
/* clear his proxy connection timeout */
lws_set_timeout(wsi, NO_PENDING_TIMEOUT, 0);
/* fallthru */
case LRS_H1C_ISSUE_HANDSHAKE:
/*
* we are under PENDING_TIMEOUT_SENT_CLIENT_HANDSHAKE
* timeout protection set in client-handshake.c
*
* take care of our lws_callback_on_writable
* happening at a time when there's no real connection yet
*/
#if defined(LWS_WITH_SOCKS5)
start_ws_handshake:
#endif
if (lws_change_pollfd(wsi, LWS_POLLOUT, 0))
return -1;
#if defined(LWS_WITH_TLS)
/* we can retry this... just cook the SSL BIO the first time */
if ((wsi->tls.use_ssl & LCCSCF_USE_SSL) && !wsi->tls.ssl &&
lws_ssl_client_bio_create(wsi) < 0) {
cce = "bio_create failed";
goto bail3;
}
if (wsi->tls.use_ssl & LCCSCF_USE_SSL) {
n = lws_ssl_client_connect1(wsi);
if (!n)
return 0;
if (n < 0) {
cce = "lws_ssl_client_connect1 failed";
goto bail3;
}
} else
wsi->tls.ssl = NULL;
/* fallthru */
case LRS_WAITING_SSL:
if (wsi->tls.use_ssl & LCCSCF_USE_SSL) {
n = lws_ssl_client_connect2(wsi, ebuf, sizeof(ebuf));
if (!n)
return 0;
if (n < 0) {
cce = ebuf;
goto bail3;
}
} else
wsi->tls.ssl = NULL;
#endif
#if defined(LWS_WITH_DETAILED_LATENCY)
if (context->detailed_latency_cb) {
wsi->detlat.type = LDLT_TLS_NEG_CLIENT;
wsi->detlat.latencies[LAT_DUR_PROXY_CLIENT_REQ_TO_WRITE] =
lws_now_usecs() -
wsi->detlat.earliest_write_req_pre_write;
wsi->detlat.latencies[LAT_DUR_USERCB] = 0;
lws_det_lat_cb(wsi->context, &wsi->detlat);
}
#endif
#if defined (LWS_WITH_HTTP2)
if (wsi->client_h2_alpn) {
/*
* We connected to the server and set up tls, and
* negotiated "h2".
*
* So this is it, we are an h2 master client connection
* now, not an h1 client connection.
*/
#if defined(LWS_WITH_TLS) && defined(LWS_WITH_SERVER)
lws_tls_server_conn_alpn(wsi);
#endif
/* send the H2 preface to legitimize the connection */
if (lws_h2_issue_preface(wsi)) {
cce = "error sending h2 preface";
goto bail3;
}
break;
}
#endif
lwsi_set_state(wsi, LRS_H1C_ISSUE_HANDSHAKE2);
lws_set_timeout(wsi, PENDING_TIMEOUT_AWAITING_CLIENT_HS_SEND,
context->timeout_secs);
/* fallthru */
case LRS_H1C_ISSUE_HANDSHAKE2:
p = lws_generate_client_handshake(wsi, p);
if (p == NULL) {
if (wsi->role_ops == &role_ops_raw_skt ||
wsi->role_ops == &role_ops_raw_file)
return 0;
lwsl_err("Failed to generate handshake for client\n");
lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS,
"chs");
return 0;
}
/* send our request to the server */
w = _lws_client_wsi_master(wsi);
lwsl_info("%s: HANDSHAKE2: %p: sending headers on %p "
"(wsistate 0x%lx 0x%lx), w sock %d, wsi sock %d\n",
__func__, wsi, w, (unsigned long)wsi->wsistate,
(unsigned long)w->wsistate, w->desc.sockfd,
wsi->desc.sockfd);
#if defined(LWS_WITH_DETAILED_LATENCY)
wsi->detlat.earliest_write_req_pre_write = lws_now_usecs();
#endif
n = lws_ssl_capable_write(w, (unsigned char *)sb, (int)(p - sb));
switch (n) {
case LWS_SSL_CAPABLE_ERROR:
lwsl_debug("ERROR writing to client socket\n");
lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS,
"cws");
return 0;
case LWS_SSL_CAPABLE_MORE_SERVICE:
lws_callback_on_writable(wsi);
break;
}
if (wsi->client_http_body_pending) {
lwsl_debug("body pending\n");
lwsi_set_state(wsi, LRS_ISSUE_HTTP_BODY);
lws_set_timeout(wsi,
PENDING_TIMEOUT_CLIENT_ISSUE_PAYLOAD,
context->timeout_secs);
#if defined(LWS_WITH_HTTP_PROXY)
if (wsi->http.proxy_clientside)
lws_callback_on_writable(wsi);
#endif
/* user code must ask for writable callback */
break;
}
lwsi_set_state(wsi, LRS_WAITING_SERVER_REPLY);
wsi->hdr_parsing_completed = 0;
if (lwsi_state(w) == LRS_IDLING) {
lwsi_set_state(w, LRS_WAITING_SERVER_REPLY);
w->hdr_parsing_completed = 0;
#if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2)
w->http.ah->parser_state = WSI_TOKEN_NAME_PART;
w->http.ah->lextable_pos = 0;
#if defined(LWS_WITH_CUSTOM_HEADERS)
w->http.ah->unk_pos = 0;
#endif
/* If we're (re)starting on hdr, need other implied init */
wsi->http.ah->ues = URIES_IDLE;
#endif
}
lws_set_timeout(wsi, PENDING_TIMEOUT_AWAITING_SERVER_RESPONSE,
wsi->context->timeout_secs);
lws_callback_on_writable(w);
goto client_http_body_sent;
case LRS_ISSUE_HTTP_BODY:
#if defined(LWS_WITH_HTTP_PROXY)
if (wsi->http.proxy_clientside) {
lws_callback_on_writable(wsi);
break;
}
#endif
if (wsi->client_http_body_pending) {
//lws_set_timeout(wsi,
// PENDING_TIMEOUT_CLIENT_ISSUE_PAYLOAD,
// context->timeout_secs);
/* user code must ask for writable callback */
break;
}
client_http_body_sent:
#if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2)
/* prepare ourselves to do the parsing */
wsi->http.ah->parser_state = WSI_TOKEN_NAME_PART;
wsi->http.ah->lextable_pos = 0;
#if defined(LWS_WITH_CUSTOM_HEADERS)
wsi->http.ah->unk_pos = 0;
#endif
#endif
lwsi_set_state(wsi, LRS_WAITING_SERVER_REPLY);
lws_set_timeout(wsi, PENDING_TIMEOUT_AWAITING_SERVER_RESPONSE,
context->timeout_secs);
break;
case LRS_WAITING_SERVER_REPLY:
/*
* handle server hanging up on us...
* but if there is POLLIN waiting, handle that first
*/
if ((pollfd->revents & (LWS_POLLIN | LWS_POLLHUP)) ==
LWS_POLLHUP) {
lwsl_debug("Server connection %p (fd=%d) dead\n",
(void *)wsi, pollfd->fd);
cce = "Peer hung up";
goto bail3;
}
if (!(pollfd->revents & LWS_POLLIN))
break;
#if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2)
/* interpret the server response
*
* HTTP/1.1 101 Switching Protocols
* Upgrade: websocket
* Connection: Upgrade
* Sec-WebSocket-Accept: me89jWimTRKTWwrS3aRrL53YZSo=
* Sec-WebSocket-Nonce: AQIDBAUGBwgJCgsMDQ4PEC==
* Sec-WebSocket-Protocol: chat
*
* we have to take some care here to only take from the
* socket bytewise. The browser may (and has been seen to
* in the case that onopen() performs websocket traffic)
* coalesce both handshake response and websocket traffic
* in one packet, since at that point the connection is
* definitively ready from browser pov.
*/
while (wsi->http.ah->parser_state != WSI_PARSING_COMPLETE) {
struct lws_tokens eb;
int n, m, buffered;
eb.token = NULL;
eb.len = 0;
buffered = lws_buflist_aware_read(pt, wsi, &eb, __func__);
lwsl_debug("%s: buflist-aware-read %d %d\n", __func__,
buffered, eb.len);
if (eb.len == LWS_SSL_CAPABLE_MORE_SERVICE)
return 0;
if (buffered < 0 || eb.len < 0) {
cce = "read failed";
goto bail3;
}
if (!eb.len)
return 0;
n = eb.len;
if (lws_parse(wsi, eb.token, &n)) {
lwsl_warn("problems parsing header\n");
cce = "problems parsing header";
goto bail3;
}
m = eb.len - n;
if (lws_buflist_aware_finished_consuming(wsi, &eb, m,
buffered,
__func__))
return -1;
eb.token += m;
eb.len -= m;
if (n) {
assert(wsi->http.ah->parser_state ==
WSI_PARSING_COMPLETE);
break;
}
}
/*
* hs may also be coming in multiple packets, there is a 5-sec
* libwebsocket timeout still active here too, so if parsing did
* not complete just wait for next packet coming in this state
*/
if (wsi->http.ah->parser_state != WSI_PARSING_COMPLETE)
break;
#endif
/*
* otherwise deal with the handshake. If there's any
* packet traffic already arrived we'll trigger poll() again
* right away and deal with it that way
*/
return lws_client_interpret_server_handshake(wsi);
bail3:
lwsl_info("closing conn at LWS_CONNMODE...SERVER_REPLY\n");
if (cce)
lwsl_info("reason: %s\n", cce);
lws_inform_client_conn_fail(wsi, (void *)cce, strlen(cce));
lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS, "cbail3");
return -1;
default:
break;
}
return 0;
}
#if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2)
int LWS_WARN_UNUSED_RESULT
lws_http_transaction_completed_client(struct lws *wsi)
{
struct lws *wsi_eff = lws_client_wsi_effective(wsi);
lwsl_info("%s: wsi: %p, wsi_eff: %p (%s)\n", __func__, wsi, wsi_eff,
wsi_eff->protocol->name);
if (user_callback_handle_rxflow(wsi_eff->protocol->callback, wsi_eff,
LWS_CALLBACK_COMPLETED_CLIENT_HTTP,
wsi_eff->user_space, NULL, 0)) {
lwsl_debug("%s: Completed call returned nonzero (role 0x%lx)\n",
__func__, (unsigned long)lwsi_role(wsi_eff));
return -1;
}
/*
* Are we constitutionally capable of having a queue, ie, we are on
* the "active client connections" list?
*
* If not, that's it for us.
*/
if (lws_dll2_is_detached(&wsi->dll_cli_active_conns))
return -1;
/* if this was a queued guy, close him and remove from queue */
if (wsi->transaction_from_pipeline_queue) {
lwsl_debug("closing queued wsi %p\n", wsi_eff);
/* so the close doesn't trigger a CCE */
wsi_eff->already_did_cce = 1;
__lws_close_free_wsi(wsi_eff,
LWS_CLOSE_STATUS_CLIENT_TRANSACTION_DONE,
"queued client done");
}
_lws_header_table_reset(wsi->http.ah);
/* after the first one, they can only be coming from the queue */
wsi->transaction_from_pipeline_queue = 1;
wsi->http.rx_content_length = 0;
wsi->hdr_parsing_completed = 0;
/* is there a new tail after removing that one? */
wsi_eff = lws_client_wsi_effective(wsi);
/*
* Do we have something pipelined waiting?
* it's OK if he hasn't managed to send his headers yet... he's next
* in line to do that...
*/
if (wsi_eff == wsi) {
/*
* Nothing pipelined... we should hang around a bit
* in case something turns up...
*/
lwsl_info("%s: nothing pipelined waiting\n", __func__);
lwsi_set_state(wsi, LRS_IDLING);
lws_set_timeout(wsi, PENDING_TIMEOUT_CLIENT_CONN_IDLE, 5);
return 0;
}
/*
* H1: we can serialize the queued guys into the same ah
* H2: everybody needs their own ah until their own STREAM_END
*/
/* otherwise set ourselves up ready to go again */
lwsi_set_state(wsi, LRS_WAITING_SERVER_REPLY);
wsi->http.ah->parser_state = WSI_TOKEN_NAME_PART;
wsi->http.ah->lextable_pos = 0;
#if defined(LWS_WITH_CUSTOM_HEADERS)
wsi->http.ah->unk_pos = 0;
#endif
lws_set_timeout(wsi, PENDING_TIMEOUT_AWAITING_SERVER_RESPONSE,
wsi->context->timeout_secs);
/* If we're (re)starting on headers, need other implied init */
wsi->http.ah->ues = URIES_IDLE;
lwsl_info("%s: %p: new queued transaction as %p\n", __func__, wsi,
wsi_eff);
lws_callback_on_writable(wsi);
return 0;
}
LWS_VISIBLE LWS_EXTERN unsigned int
lws_http_client_http_response(struct lws *_wsi)
{
struct lws *wsi;
unsigned int resp;
if (_wsi->http.ah && _wsi->http.ah->http_response)
return _wsi->http.ah->http_response;
lws_vhost_lock(_wsi->vhost);
wsi = _lws_client_wsi_master(_wsi);
resp = wsi->http.ah->http_response;
lws_vhost_unlock(_wsi->vhost);
return resp;
}
#endif
#if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2)
int
lws_client_interpret_server_handshake(struct lws *wsi)
{
int n, port = 0, ssl = 0;
int close_reason = LWS_CLOSE_STATUS_PROTOCOL_ERR;
const char *prot, *ads = NULL, *path, *cce = NULL;
struct allocated_headers *ah;
struct lws *w = lws_client_wsi_effective(wsi);
char *p, *q;
char new_path[300];
lws_free_set_NULL(wsi->stash);
ah = wsi->http.ah;
if (!wsi->do_ws) {
/* we are being an http client...
*/
#if defined(LWS_ROLE_H2)
if (wsi->client_h2_alpn || wsi->client_h2_substream) {
lwsl_debug("%s: %p: transitioning to h2 client\n",
__func__, wsi);
lws_role_transition(wsi, LWSIFR_CLIENT,
LRS_ESTABLISHED, &role_ops_h2);
} else
#endif
{
#if defined(LWS_ROLE_H1)
{
lwsl_debug("%s: %p: transitioning to h1 client\n",
__func__, wsi);
lws_role_transition(wsi, LWSIFR_CLIENT,
LRS_ESTABLISHED, &role_ops_h1);
}
#else
return -1;
#endif
}
wsi->http.ah = ah;
ah->http_response = 0;
}
/*
* well, what the server sent looked reasonable for syntax.
* Now let's confirm it sent all the necessary headers
*
* http (non-ws) client will expect something like this
*
* HTTP/1.0.200
* server:.libwebsockets
* content-type:.text/html
* content-length:.17703
* set-cookie:.test=LWS_1456736240_336776_COOKIE;Max-Age=360000
*/
wsi->http.conn_type = HTTP_CONNECTION_KEEP_ALIVE;
if (!wsi->client_h2_substream) {
p = lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP);
if (wsi->do_ws && !p) {
lwsl_info("no URI\n");
cce = "HS: URI missing";
goto bail3;
}
if (!p) {
p = lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP1_0);
wsi->http.conn_type = HTTP_CONNECTION_CLOSE;
}
if (!p) {
cce = "HS: URI missing";
lwsl_info("no URI\n");
goto bail3;
}
} else {
p = lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP_COLON_STATUS);
if (!p) {
cce = "HS: :status missing";
lwsl_info("no status\n");
goto bail3;
}
}
n = atoi(p);
if (ah)
ah->http_response = n;
if (
#if defined(LWS_WITH_HTTP_PROXY)
!wsi->http.proxy_clientside &&
#endif
(n == 301 || n == 302 || n == 303 || n == 307 || n == 308)) {
p = lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP_LOCATION);
if (!p) {
cce = "HS: Redirect code but no Location";
goto bail3;
}
/* Relative reference absolute path */
if (p[0] == '/') {
#if defined(LWS_WITH_TLS)
ssl = wsi->tls.use_ssl & LCCSCF_USE_SSL;
#endif
ads = lws_hdr_simple_ptr(wsi,
_WSI_TOKEN_CLIENT_PEER_ADDRESS);
port = wsi->c_port;
/* +1 as lws_client_reset expects leading / omitted */
path = p + 1;
}
/* Absolute (Full) URI */
else if (strchr(p, ':')) {
if (lws_parse_uri(p, &prot, &ads, &port, &path)) {
cce = "HS: URI did not parse";
goto bail3;
}
if (!strcmp(prot, "wss") || !strcmp(prot, "https"))
ssl = 1;
}
/* Relative reference relative path */
else {
/* This doesn't try to calculate an absolute path,
* that will be left to the server */
#if defined(LWS_WITH_TLS)
ssl = wsi->tls.use_ssl & LCCSCF_USE_SSL;
#endif
ads = lws_hdr_simple_ptr(wsi,
_WSI_TOKEN_CLIENT_PEER_ADDRESS);
port = wsi->c_port;
/* +1 as lws_client_reset expects leading / omitted */
path = new_path + 1;
if (lws_hdr_simple_ptr(wsi,_WSI_TOKEN_CLIENT_URI))
lws_strncpy(new_path, lws_hdr_simple_ptr(wsi,
_WSI_TOKEN_CLIENT_URI), sizeof(new_path));
else {
new_path[0] = '/';
new_path[1] = '\0';
}
q = strrchr(new_path, '/');
if (q)
lws_strncpy(q + 1, p, sizeof(new_path) -
(q - new_path) - 1);
else
path = p;
}
#if defined(LWS_WITH_TLS)
if ((wsi->tls.use_ssl & LCCSCF_USE_SSL) && !ssl) {
cce = "HS: Redirect attempted SSL downgrade";
goto bail3;
}
#endif
if (!ads) /* make coverity happy */ {
cce = "no ads";
goto bail3;
}
if (!lws_client_reset(&wsi, ssl, ads, port, path, ads)) {
/* there are two ways to fail out with NULL return...
* simple, early problem where the wsi is intact, or
* we went through with the reconnect attempt and the
* wsi is already closed. In the latter case, the wsi
* has beet set to NULL additionally.
*/
lwsl_err("Redirect failed\n");
cce = "HS: Redirect failed";
if (wsi)
goto bail3;
/* wsi has closed */
return 1;
}
return 0;
}
if (!wsi->do_ws) {
/* if h1 KA is allowed, enable the queued pipeline guys */
if (!wsi->client_h2_alpn && !wsi->client_h2_substream &&
w == wsi) { /* ie, coming to this for the first time */
if (wsi->http.conn_type == HTTP_CONNECTION_KEEP_ALIVE)
wsi->keepalive_active = 1;
else {
/*
* Ugh... now the main http connection has seen
* both sides, we learn the server doesn't
* support keepalive.
*
* That means any guys queued on us are going
* to have to be restarted from connect2 with
* their own connections.
*/
/*
* stick around telling any new guys they can't
* pipeline to this server
*/
wsi->keepalive_rejected = 1;
lws_vhost_lock(wsi->vhost);
lws_start_foreach_dll_safe(struct lws_dll2 *,
d, d1,
wsi->dll2_cli_txn_queue_owner.head) {
struct lws *ww = lws_container_of(d,
struct lws,
dll2_cli_txn_queue);
/* remove him from our queue */
lws_dll2_remove(&ww->dll2_cli_txn_queue);
/* give up on pipelining */
ww->client_pipeline = 0;
/* go back to "trying to connect" state */
lws_role_transition(ww, LWSIFR_CLIENT,
LRS_UNCONNECTED,
#if defined(LWS_ROLE_H1)
&role_ops_h1);
#else
#if defined (LWS_ROLE_H2)
&role_ops_h2);
#else
&role_ops_raw);
#endif
#endif
ww->user_space = NULL;
} lws_end_foreach_dll_safe(d, d1);
lws_vhost_unlock(wsi->vhost);
}
}
#ifdef LWS_WITH_HTTP_PROXY
wsi->http.perform_rewrite = 0;
if (lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_CONTENT_TYPE)) {
if (!strncmp(lws_hdr_simple_ptr(wsi,
WSI_TOKEN_HTTP_CONTENT_TYPE),
"text/html", 9))
wsi->http.perform_rewrite = 0;
}
#endif
/* allocate the per-connection user memory (if any) */
if (lws_ensure_user_space(wsi)) {
lwsl_err("Problem allocating wsi user mem\n");
cce = "HS: OOM";
goto bail2;
}
/* he may choose to send us stuff in chunked transfer-coding */
wsi->chunked = 0;
wsi->chunk_remaining = 0; /* ie, next thing is chunk size */
if (lws_hdr_total_length(wsi,
WSI_TOKEN_HTTP_TRANSFER_ENCODING)) {
wsi->chunked = !strcmp(lws_hdr_simple_ptr(wsi,
WSI_TOKEN_HTTP_TRANSFER_ENCODING),
"chunked");
/* first thing is hex, after payload there is crlf */
wsi->chunk_parser = ELCP_HEX;
}
if (lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_CONTENT_LENGTH)) {
wsi->http.rx_content_length =
atoll(lws_hdr_simple_ptr(wsi,
WSI_TOKEN_HTTP_CONTENT_LENGTH));
lwsl_info("%s: incoming content length %llu\n",
__func__, (unsigned long long)
wsi->http.rx_content_length);
wsi->http.rx_content_remain =
wsi->http.rx_content_length;
} else /* can't do 1.1 without a content length or chunked */
if (!wsi->chunked)
wsi->http.conn_type = HTTP_CONNECTION_CLOSE;
/*
* we seem to be good to go, give client last chance to check
* headers and OK it
*/
if (w->protocol->callback(w,
LWS_CALLBACK_CLIENT_FILTER_PRE_ESTABLISH,
w->user_space, NULL, 0)) {
cce = "HS: disallowed by client filter";
goto bail2;
}
/* clear his proxy connection timeout */
lws_set_timeout(wsi, NO_PENDING_TIMEOUT, 0);
wsi->rxflow_change_to = LWS_RXFLOW_ALLOW;
/* call him back to inform him he is up */
if (w->protocol->callback(w,
LWS_CALLBACK_ESTABLISHED_CLIENT_HTTP,
w->user_space, NULL, 0)) {
cce = "HS: disallowed at ESTABLISHED";
goto bail3;
}
/*
* for pipelining, master needs to keep his ah... guys who
* queued on him can drop it now though.
*/
if (w != wsi)
/* free up parsing allocations for queued guy */
lws_header_table_detach(w, 0);
lwsl_info("%s: client connection up\n", __func__);
/*
* Did we get a response from the server with an explicit
* content-length of zero? If so, this transaction is already
* completed at the end of the header processing...
*/
if (lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_CONTENT_LENGTH) &&
!wsi->http.rx_content_length)
return !!lws_http_transaction_completed_client(wsi);
return 0;
}
#if defined(LWS_ROLE_WS)
switch (lws_client_ws_upgrade(wsi, &cce)) {
case 2:
goto bail2;
case 3:
goto bail3;
}
return 0;
#endif
bail3:
close_reason = LWS_CLOSE_STATUS_NOSTATUS;
bail2:
if (wsi->protocol) {
n = 0;
if (cce)
n = (int)strlen(cce);
lws_inform_client_conn_fail(wsi, (void *)cce, (unsigned int)n);
}
lwsl_info("closing connection (prot %s) "
"due to bail2 connection error: %s\n", wsi->protocol ?
wsi->protocol->name : "unknown", cce);
/* closing will free up his parsing allocations */
lws_close_free_wsi(wsi, close_reason, "c hs interp");
return 1;
}
#endif
char *
lws_generate_client_handshake(struct lws *wsi, char *pkt)
{
char *p = pkt;
const char *meth;
const char *pp = lws_hdr_simple_ptr(wsi,
_WSI_TOKEN_CLIENT_SENT_PROTOCOLS);
meth = lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_METHOD);
if (!meth) {
meth = "GET";
wsi->do_ws = 1;
} else {
wsi->do_ws = 0;
}
if (!strcmp(meth, "RAW")) {
lws_set_timeout(wsi, NO_PENDING_TIMEOUT, 0);
lwsl_notice("client transition to raw\n");
if (pp) {
const struct lws_protocols *pr;
pr = lws_vhost_name_to_protocol(wsi->vhost, pp);
if (!pr) {
lwsl_err("protocol %s not enabled on vhost\n",
pp);
return NULL;
}
lws_bind_protocol(wsi, pr, __func__);
}
if ((wsi->protocol->callback)(wsi, LWS_CALLBACK_RAW_ADOPT,
wsi->user_space, NULL, 0))
return NULL;
lws_role_transition(wsi, LWSIFR_CLIENT, LRS_ESTABLISHED,
&role_ops_raw_skt);
lws_header_table_detach(wsi, 1);
return NULL;
}
/*
* 04 example client handshake
*
* GET /chat HTTP/1.1
* Host: server.example.com
* Upgrade: websocket
* Connection: Upgrade
* Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
* Sec-WebSocket-Origin: http://example.com
* Sec-WebSocket-Protocol: chat, superchat
* Sec-WebSocket-Version: 4
*/
p += lws_snprintf(p, 2048, "%s %s HTTP/1.1\x0d\x0a", meth,
lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_URI));
p += lws_snprintf(p, 64, "Pragma: no-cache\x0d\x0a"
"Cache-Control: no-cache\x0d\x0a");
p += lws_snprintf(p, 128, "Host: %s\x0d\x0a",
lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_HOST));
if (lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_ORIGIN)) {
if (lws_check_opt(wsi->context->options,
LWS_SERVER_OPTION_JUST_USE_RAW_ORIGIN))
p += lws_snprintf(p, 128, "Origin: %s\x0d\x0a",
lws_hdr_simple_ptr(wsi,
_WSI_TOKEN_CLIENT_ORIGIN));
else
p += lws_snprintf(p, 128, "Origin: http://%s\x0d\x0a",
lws_hdr_simple_ptr(wsi,
_WSI_TOKEN_CLIENT_ORIGIN));
}
#if defined(LWS_WITH_HTTP_PROXY)
if (wsi->parent &&
lws_hdr_total_length(wsi->parent, WSI_TOKEN_HTTP_CONTENT_LENGTH)) {
p += lws_snprintf(p, 128, "Content-Length: %s\x0d\x0a",
lws_hdr_simple_ptr(wsi->parent, WSI_TOKEN_HTTP_CONTENT_LENGTH));
if (atoi(lws_hdr_simple_ptr(wsi->parent, WSI_TOKEN_HTTP_CONTENT_LENGTH)))
wsi->client_http_body_pending = 1;
}
if (wsi->parent &&
lws_hdr_total_length(wsi->parent, WSI_TOKEN_HTTP_AUTHORIZATION)) {
p += lws_snprintf(p, 128, "Authorization: %s\x0d\x0a",
lws_hdr_simple_ptr(wsi->parent, WSI_TOKEN_HTTP_AUTHORIZATION));
}
if (wsi->parent &&
lws_hdr_total_length(wsi->parent, WSI_TOKEN_HTTP_CONTENT_TYPE)) {
p += lws_snprintf(p, 128, "Content-Type: %s\x0d\x0a",
lws_hdr_simple_ptr(wsi->parent, WSI_TOKEN_HTTP_CONTENT_TYPE));
}
#endif
#if defined(LWS_ROLE_WS)
if (wsi->do_ws) {
const char *conn1 = "";
// if (!wsi->client_pipeline)
// conn1 = "close, ";
p = lws_generate_client_ws_handshake(wsi, p, conn1);
} else
#endif
{
if (!wsi->client_pipeline)
p += lws_snprintf(p, 64, "connection: close\x0d\x0a");
}
/* give userland a chance to append, eg, cookies */
if (wsi->protocol->callback(wsi,
LWS_CALLBACK_CLIENT_APPEND_HANDSHAKE_HEADER,
wsi->user_space, &p,
(pkt + wsi->context->pt_serv_buf_size) - p - 12))
return NULL;
p += lws_snprintf(p, 4, "\x0d\x0a");
// puts(pkt);
return p;
}
#if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2)
LWS_VISIBLE int
lws_http_client_read(struct lws *wsi, char **buf, int *len)
{
struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi];
struct lws_tokens eb;
int buffered, n, consumed = 0;
eb.token = NULL;
eb.len = 0;
buffered = lws_buflist_aware_read(pt, wsi, &eb, __func__);
*buf = (char *)eb.token;
*len = 0;
/*
* we're taking on responsibility for handling used / unused eb
* when we leave, via lws_buflist_aware_finished_consuming()
*/
// lwsl_notice("%s: eb.len %d ENTRY chunk remaining %d\n", __func__, eb.len,
// wsi->chunk_remaining);
/* allow the source to signal he has data again next time */
if (lws_change_pollfd(wsi, 0, LWS_POLLIN))
return -1;
if (buffered < 0) {
lwsl_debug("%s: SSL capable error\n", __func__);
return -1;
}
if (eb.len <= 0)
return 0;
*len = eb.len;
wsi->client_rx_avail = 0;
/*
* server may insist on transfer-encoding: chunked,
* so http client must deal with it
*/
spin_chunks:
//lwsl_notice("%s: len %d SPIN chunk remaining %d\n", __func__, *len,
// wsi->chunk_remaining);
while (wsi->chunked && (wsi->chunk_parser != ELCP_CONTENT) && *len) {
switch (wsi->chunk_parser) {
case ELCP_HEX:
if ((*buf)[0] == '\x0d') {
wsi->chunk_parser = ELCP_CR;
break;
}
n = char_to_hex((*buf)[0]);
if (n < 0) {
lwsl_err("%s: chunking failure A\n", __func__);
return -1;
}
wsi->chunk_remaining <<= 4;
wsi->chunk_remaining |= n;
break;
case ELCP_CR:
if ((*buf)[0] != '\x0a') {
lwsl_err("%s: chunking failure B\n", __func__);
return -1;
}
wsi->chunk_parser = ELCP_CONTENT;
//lwsl_info("starting chunk size %d (block rem %d)\n",
// wsi->chunk_remaining, *len);
if (wsi->chunk_remaining)
break;
lwsl_info("final chunk\n");
goto completed;
case ELCP_CONTENT:
break;
case ELCP_POST_CR:
if ((*buf)[0] != '\x0d') {
lwsl_err("%s: chunking failure C\n", __func__);
lwsl_hexdump_err(*buf, *len);
return -1;
}
wsi->chunk_parser = ELCP_POST_LF;
break;
case ELCP_POST_LF:
if ((*buf)[0] != '\x0a') {
lwsl_err("%s: chunking failure D\n", __func__);
return -1;
}
wsi->chunk_parser = ELCP_HEX;
wsi->chunk_remaining = 0;
break;
}
(*buf)++;
(*len)--;
consumed++;
}
if (wsi->chunked && !wsi->chunk_remaining)
goto account_and_ret;
if (wsi->http.rx_content_remain &&
wsi->http.rx_content_remain < (unsigned int)*len)
n = (int)wsi->http.rx_content_remain;
else
n = *len;
if (wsi->chunked && wsi->chunk_remaining &&
wsi->chunk_remaining < n)
n = wsi->chunk_remaining;
#if defined(LWS_WITH_HTTP_PROXY) && defined(LWS_WITH_HUBBUB)
/* hubbub */
if (wsi->http.perform_rewrite)
lws_rewrite_parse(wsi->http.rw, (unsigned char *)*buf, n);
else
#endif
{
struct lws *wsi_eff = lws_client_wsi_effective(wsi);
if (
#if defined(LWS_WITH_HTTP_PROXY)
!wsi_eff->protocol_bind_balance ==
!!wsi_eff->http.proxy_clientside &&
#else
!!wsi_eff->protocol_bind_balance &&
#endif
user_callback_handle_rxflow(wsi_eff->protocol->callback,
wsi_eff, LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ,
wsi_eff->user_space, *buf, n)) {
lwsl_info("%s: RECEIVE_CLIENT_HTTP_READ returned -1\n",
__func__);
return -1;
}
}
(*buf) += n;
*len -= n;
if (wsi->chunked && wsi->chunk_remaining)
wsi->chunk_remaining -= n;
//lwsl_notice("chunk_remaining <- %d, block remaining %d\n",
// wsi->chunk_remaining, *len);
consumed += n;
//eb.token += n;
//eb.len -= n;
if (wsi->chunked && !wsi->chunk_remaining)
wsi->chunk_parser = ELCP_POST_CR;
if (wsi->chunked && *len)
goto spin_chunks;
if (wsi->chunked)
goto account_and_ret;
/* if we know the content length, decrement the content remaining */
if (wsi->http.rx_content_length > 0)
wsi->http.rx_content_remain -= n;
// lwsl_notice("rx_content_remain %lld, rx_content_length %lld\n",
// wsi->http.rx_content_remain, wsi->http.rx_content_length);
if (wsi->http.rx_content_remain || !wsi->http.rx_content_length)
goto account_and_ret;
completed:
if (lws_http_transaction_completed_client(wsi)) {
lwsl_notice("%s: transaction completed says -1\n", __func__);
return -1;
}
account_and_ret:
// lwsl_warn("%s: on way out, consuming %d / %d\n", __func__, consumed, eb.len);
if (lws_buflist_aware_finished_consuming(wsi, &eb, consumed, buffered,
__func__))
return -1;
return 0;
}
#endif