mirror of
https://github.com/warmcat/libwebsockets.git
synced 2025-03-16 00:00:07 +01:00

Generic sessions has been overdue some love to align it with the progress in the rest of lws. 1) Strict Content Security Policy 2) http2 compatibility 3) fixes and additions for use in a separate process via unix domain socket 4) work on ws and http proxying in lws 5) add minimal example
820 lines
21 KiB
C
820 lines
21 KiB
C
/*
|
|
* libwebsockets - small server side websockets and web server implementation
|
|
*
|
|
* Copyright (C) 2010-2018 Andy Green <andy@warmcat.com>
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation:
|
|
* version 2.1 of the License.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
|
* MA 02110-1301 USA
|
|
*/
|
|
|
|
#include "core/private.h"
|
|
|
|
#if defined(LWS_WITH_HTTP_PROXY)
|
|
static int
|
|
proxy_header(struct lws *wsi, struct lws *par, unsigned char *temp,
|
|
int temp_len, int index, unsigned char **p, unsigned char *end)
|
|
{
|
|
int n = lws_hdr_total_length(par, index);
|
|
|
|
if (n < 1) {
|
|
lwsl_debug("%s: no index %d:\n", __func__, index);
|
|
return 0;
|
|
}
|
|
|
|
if (lws_hdr_copy(par, (char *)temp, temp_len, index) < 0)
|
|
return -1;
|
|
|
|
lwsl_debug("%s: index %d: %s\n", __func__, index, (char *)temp);
|
|
|
|
if (lws_add_http_header_by_token(wsi, index, temp, n, p, end))
|
|
return -1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
stream_close(struct lws *wsi)
|
|
{
|
|
char buf[LWS_PRE + 6], *out = buf + LWS_PRE;
|
|
|
|
if (wsi->http.did_stream_close)
|
|
return 0;
|
|
|
|
wsi->http.did_stream_close = 1;
|
|
|
|
if (wsi->http2_substream) {
|
|
if (lws_write(wsi, (unsigned char *)buf + LWS_PRE, 0,
|
|
LWS_WRITE_HTTP_FINAL) < 0) {
|
|
lwsl_info("%s: COMPL_CLIENT_HTTP: h2 fin wr failed\n",
|
|
__func__);
|
|
|
|
return -1;
|
|
}
|
|
} else {
|
|
*out++ = '0';
|
|
*out++ = '\x0d';
|
|
*out++ = '\x0a';
|
|
*out++ = '\x0d';
|
|
*out++ = '\x0a';
|
|
|
|
if (lws_write(wsi, (unsigned char *)buf + LWS_PRE, 5,
|
|
LWS_WRITE_HTTP_FINAL) < 0) {
|
|
lwsl_err("%s: COMPL_CLIENT_HTTP: "
|
|
"h2 final write failed\n", __func__);
|
|
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
#endif
|
|
|
|
struct lws_proxy_pkt {
|
|
struct lws_dll pkt_list;
|
|
size_t len;
|
|
char binary;
|
|
char first;
|
|
char final;
|
|
|
|
/* data follows */
|
|
};
|
|
|
|
#if defined(LWS_WITH_HTTP_PROXY) && defined(LWS_ROLE_WS)
|
|
int
|
|
lws_callback_ws_proxy(struct lws *wsi, enum lws_callback_reasons reason,
|
|
void *user, void *in, size_t len)
|
|
{
|
|
struct lws_proxy_pkt *pkt;
|
|
struct lws_dll *dll;
|
|
|
|
switch (reason) {
|
|
|
|
/* h1 ws proxying... child / client / onward */
|
|
|
|
case LWS_CALLBACK_CLIENT_ESTABLISHED:
|
|
if (!wsi->h1_ws_proxied || !wsi->parent)
|
|
break;
|
|
|
|
lws_process_ws_upgrade2(wsi->parent);
|
|
|
|
#if defined(LWS_WITH_HTTP2)
|
|
if (wsi->parent->http2_substream)
|
|
lwsl_info("%s: proxied h2 -> h1 ws established\n", __func__);
|
|
#endif
|
|
break;
|
|
|
|
case LWS_CALLBACK_CLIENT_CONFIRM_EXTENSION_SUPPORTED:
|
|
return 1;
|
|
|
|
case LWS_CALLBACK_CLIENT_CONNECTION_ERROR:
|
|
case LWS_CALLBACK_CLIENT_CLOSED:
|
|
lwsl_user("%s: client closed: parent %p\n", __func__, wsi->parent);
|
|
if (wsi->parent)
|
|
lws_set_timeout(wsi->parent, 1, LWS_TO_KILL_ASYNC);
|
|
break;
|
|
|
|
case LWS_CALLBACK_CLIENT_APPEND_HANDSHAKE_HEADER:
|
|
{
|
|
unsigned char **p = (unsigned char **)in, *end = (*p) + len,
|
|
tmp[128];
|
|
|
|
proxy_header(wsi, wsi->parent, tmp, sizeof(tmp),
|
|
WSI_TOKEN_HTTP_ACCEPT_LANGUAGE, p, end);
|
|
|
|
proxy_header(wsi, wsi->parent, tmp, sizeof(tmp),
|
|
WSI_TOKEN_HTTP_COOKIE, p, end);
|
|
|
|
proxy_header(wsi, wsi->parent, tmp, sizeof(tmp),
|
|
WSI_TOKEN_HTTP_SET_COOKIE, p, end);
|
|
break;
|
|
}
|
|
|
|
case LWS_CALLBACK_CLIENT_RECEIVE:
|
|
wsi->parent->ws->proxy_buffered += len;
|
|
if (wsi->parent->ws->proxy_buffered > 10 * 1024 * 1024) {
|
|
lwsl_err("%s: proxied ws connection excessive buffering: dropping\n",
|
|
__func__);
|
|
return -1;
|
|
}
|
|
pkt = lws_malloc(sizeof(*pkt) + LWS_PRE + len, __func__);
|
|
if (!pkt)
|
|
return -1;
|
|
|
|
pkt->pkt_list.prev = pkt->pkt_list.next = NULL;
|
|
pkt->len = len;
|
|
pkt->first = lws_is_first_fragment(wsi);
|
|
pkt->final = lws_is_final_fragment(wsi);
|
|
pkt->binary = lws_frame_is_binary(wsi);
|
|
|
|
memcpy(((uint8_t *)&pkt[1]) + LWS_PRE, in, len);
|
|
|
|
lws_dll_add_tail(&pkt->pkt_list, &wsi->parent->ws->proxy_head);
|
|
lws_callback_on_writable(wsi->parent);
|
|
break;
|
|
|
|
case LWS_CALLBACK_CLIENT_WRITEABLE:
|
|
dll = lws_dll_get_tail(&wsi->ws->proxy_head);
|
|
if (!dll)
|
|
break;
|
|
|
|
pkt = (struct lws_proxy_pkt *)dll;
|
|
if (lws_write(wsi, ((unsigned char *)&pkt[1]) +
|
|
LWS_PRE, pkt->len, lws_write_ws_flags(
|
|
pkt->binary ? LWS_WRITE_BINARY : LWS_WRITE_TEXT,
|
|
pkt->first, pkt->final)) < 0)
|
|
return -1;
|
|
|
|
wsi->parent->ws->proxy_buffered -= pkt->len;
|
|
|
|
lws_dll_remove_track_tail(dll, &wsi->ws->proxy_head);
|
|
lws_free(pkt);
|
|
|
|
if (lws_dll_get_tail(&wsi->ws->proxy_head))
|
|
lws_callback_on_writable(wsi);
|
|
break;
|
|
|
|
/* h1 ws proxying... parent / server / incoming */
|
|
|
|
case LWS_CALLBACK_CONFIRM_EXTENSION_OKAY:
|
|
return 1;
|
|
|
|
case LWS_CALLBACK_CLOSED:
|
|
lwsl_user("%s: closed\n", __func__);
|
|
return -1;
|
|
|
|
case LWS_CALLBACK_RECEIVE:
|
|
pkt = lws_malloc(sizeof(*pkt) + LWS_PRE + len, __func__);
|
|
if (!pkt)
|
|
return -1;
|
|
|
|
pkt->pkt_list.prev = pkt->pkt_list.next = NULL;
|
|
pkt->len = len;
|
|
pkt->first = lws_is_first_fragment(wsi);
|
|
pkt->final = lws_is_final_fragment(wsi);
|
|
pkt->binary = lws_frame_is_binary(wsi);
|
|
|
|
memcpy(((uint8_t *)&pkt[1]) + LWS_PRE, in, len);
|
|
|
|
lws_dll_add_tail(&pkt->pkt_list, &wsi->child_list->ws->proxy_head);
|
|
lws_callback_on_writable(wsi->child_list);
|
|
break;
|
|
|
|
case LWS_CALLBACK_SERVER_WRITEABLE:
|
|
dll = lws_dll_get_tail(&wsi->ws->proxy_head);
|
|
if (!dll)
|
|
break;
|
|
|
|
pkt = (struct lws_proxy_pkt *)dll;
|
|
if (lws_write(wsi, ((unsigned char *)&pkt[1]) +
|
|
LWS_PRE, pkt->len, lws_write_ws_flags(
|
|
pkt->binary ? LWS_WRITE_BINARY : LWS_WRITE_TEXT,
|
|
pkt->first, pkt->final)) < 0)
|
|
return -1;
|
|
|
|
lws_dll_remove_track_tail(dll, &wsi->ws->proxy_head);
|
|
lws_free(pkt);
|
|
|
|
if (lws_dll_get_tail(&wsi->ws->proxy_head))
|
|
lws_callback_on_writable(wsi);
|
|
break;
|
|
|
|
default:
|
|
return 0;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
const struct lws_protocols lws_ws_proxy = {
|
|
"lws-ws-proxy",
|
|
lws_callback_ws_proxy,
|
|
0,
|
|
8192,
|
|
8192, NULL, 0
|
|
};
|
|
|
|
#endif
|
|
|
|
LWS_VISIBLE int
|
|
lws_callback_http_dummy(struct lws *wsi, enum lws_callback_reasons reason,
|
|
void *user, void *in, size_t len)
|
|
{
|
|
struct lws_ssl_info *si;
|
|
#ifdef LWS_WITH_CGI
|
|
struct lws_cgi_args *args;
|
|
#endif
|
|
#if defined(LWS_WITH_CGI) || defined(LWS_WITH_HTTP_PROXY)
|
|
char buf[8192];
|
|
int n;
|
|
#endif
|
|
#if defined(LWS_WITH_HTTP_PROXY)
|
|
unsigned char **p, *end;
|
|
struct lws *parent;
|
|
#endif
|
|
|
|
switch (reason) {
|
|
#if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2)
|
|
case LWS_CALLBACK_HTTP:
|
|
#ifndef LWS_NO_SERVER
|
|
if (lws_return_http_status(wsi, HTTP_STATUS_NOT_FOUND, NULL))
|
|
return -1;
|
|
|
|
if (lws_http_transaction_completed(wsi))
|
|
#endif
|
|
return -1;
|
|
break;
|
|
#if !defined(LWS_NO_SERVER)
|
|
case LWS_CALLBACK_HTTP_BODY_COMPLETION:
|
|
#if defined(LWS_WITH_HTTP_PROXY)
|
|
if (wsi->child_list) {
|
|
lwsl_user("%s: LWS_CALLBACK_HTTP_BODY_COMPLETION: %d\n", __func__, (int)len);
|
|
break;
|
|
}
|
|
#endif
|
|
/* fallthru */
|
|
case LWS_CALLBACK_HTTP_FILE_COMPLETION:
|
|
if (lws_http_transaction_completed(wsi))
|
|
return -1;
|
|
break;
|
|
#endif
|
|
|
|
#if defined(LWS_WITH_HTTP_PROXY)
|
|
case LWS_CALLBACK_HTTP_BODY:
|
|
if (wsi->child_list) {
|
|
lwsl_user("%s: LWS_CALLBACK_HTTP_BODY: stashing %d\n", __func__, (int)len);
|
|
lws_buflist_append_segment(&wsi->http.buflist_post_body, in, len);
|
|
lws_callback_on_writable(wsi->child_list);
|
|
}
|
|
break;
|
|
#endif
|
|
|
|
case LWS_CALLBACK_HTTP_WRITEABLE:
|
|
// lwsl_err("%s: LWS_CALLBACK_HTTP_WRITEABLE\n", __func__);
|
|
#ifdef LWS_WITH_CGI
|
|
if (wsi->reason_bf & (LWS_CB_REASON_AUX_BF__CGI_HEADERS |
|
|
LWS_CB_REASON_AUX_BF__CGI)) {
|
|
n = lws_cgi_write_split_stdout_headers(wsi);
|
|
if (n < 0) {
|
|
lwsl_debug("AUX_BF__CGI forcing close\n");
|
|
return -1;
|
|
}
|
|
if (!n)
|
|
lws_rx_flow_control(
|
|
wsi->http.cgi->stdwsi[LWS_STDOUT], 1);
|
|
|
|
if (wsi->reason_bf & LWS_CB_REASON_AUX_BF__CGI_HEADERS)
|
|
wsi->reason_bf &=
|
|
~LWS_CB_REASON_AUX_BF__CGI_HEADERS;
|
|
else
|
|
wsi->reason_bf &= ~LWS_CB_REASON_AUX_BF__CGI;
|
|
|
|
if (wsi->http.cgi && wsi->http.cgi->cgi_transaction_over)
|
|
return -1;
|
|
break;
|
|
}
|
|
|
|
if (wsi->reason_bf & LWS_CB_REASON_AUX_BF__CGI_CHUNK_END) {
|
|
if (!wsi->http2_substream) {
|
|
memcpy(buf + LWS_PRE, "0\x0d\x0a\x0d\x0a", 5);
|
|
lwsl_debug("writing chunk term and exiting\n");
|
|
n = lws_write(wsi, (unsigned char *)buf +
|
|
LWS_PRE, 5, LWS_WRITE_HTTP);
|
|
} else
|
|
n = lws_write(wsi, (unsigned char *)buf +
|
|
LWS_PRE, 0,
|
|
LWS_WRITE_HTTP_FINAL);
|
|
|
|
/* always close after sending it */
|
|
if (lws_http_transaction_completed(wsi))
|
|
return -1;
|
|
return 0;
|
|
}
|
|
#endif
|
|
#if defined(LWS_WITH_HTTP_PROXY)
|
|
|
|
if (wsi->reason_bf & LWS_CB_REASON_AUX_BF__PROXY_HEADERS) {
|
|
|
|
wsi->reason_bf &= ~LWS_CB_REASON_AUX_BF__PROXY_HEADERS;
|
|
|
|
n = LWS_WRITE_HTTP_HEADERS;
|
|
if (!wsi->http.prh_content_length)
|
|
n |= LWS_WRITE_H2_STREAM_END;
|
|
|
|
lwsl_debug("%s: %p: issuing proxy headers: clen %d\n",
|
|
__func__, wsi, (int)wsi->http.prh_content_length);
|
|
n = lws_write(wsi, wsi->http.pending_return_headers +
|
|
LWS_PRE,
|
|
wsi->http.pending_return_headers_len, n);
|
|
|
|
lws_free_set_NULL(wsi->http.pending_return_headers);
|
|
|
|
if (n < 0) {
|
|
lwsl_err("%s: EST_CLIENT_HTTP: write failed\n",
|
|
__func__);
|
|
return -1;
|
|
}
|
|
|
|
lws_callback_on_writable(wsi);
|
|
break;
|
|
}
|
|
|
|
if (wsi->reason_bf & LWS_CB_REASON_AUX_BF__PROXY) {
|
|
char *px = buf + LWS_PRE;
|
|
int lenx = sizeof(buf) - LWS_PRE - 32;
|
|
|
|
/*
|
|
* our sink is writeable and our source has something
|
|
* to read. So read a lump of source material of
|
|
* suitable size to send or what's available, whichever
|
|
* is the smaller.
|
|
*/
|
|
wsi->reason_bf &= ~LWS_CB_REASON_AUX_BF__PROXY;
|
|
if (!lws_get_child(wsi))
|
|
break;
|
|
|
|
/* this causes LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ */
|
|
if (lws_http_client_read(lws_get_child(wsi), &px,
|
|
&lenx) < 0) {
|
|
lwsl_info("%s: LWS_CB_REASON_AUX_BF__PROXY: "
|
|
"client closed\n", __func__);
|
|
|
|
stream_close(wsi);
|
|
|
|
return -1;
|
|
}
|
|
break;
|
|
}
|
|
|
|
if (wsi->reason_bf & LWS_CB_REASON_AUX_BF__PROXY_TRANS_END) {
|
|
lwsl_info("%s: LWS_CB_REASON_AUX_BF__PROXY_TRANS_END\n",
|
|
__func__);
|
|
|
|
wsi->reason_bf &= ~LWS_CB_REASON_AUX_BF__PROXY_TRANS_END;
|
|
|
|
if (stream_close(wsi))
|
|
return -1;
|
|
|
|
if (lws_http_transaction_completed(wsi))
|
|
return -1;
|
|
}
|
|
#endif
|
|
break;
|
|
|
|
#if defined(LWS_WITH_HTTP_PROXY)
|
|
case LWS_CALLBACK_RECEIVE_CLIENT_HTTP:
|
|
assert(lws_get_parent(wsi));
|
|
if (!lws_get_parent(wsi))
|
|
break;
|
|
lws_get_parent(wsi)->reason_bf |= LWS_CB_REASON_AUX_BF__PROXY;
|
|
lws_callback_on_writable(lws_get_parent(wsi));
|
|
break;
|
|
|
|
case LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ: {
|
|
char *out = buf + LWS_PRE;
|
|
|
|
assert(lws_get_parent(wsi));
|
|
|
|
if (wsi->http.proxy_parent_chunked) {
|
|
|
|
if (len > sizeof(buf) - LWS_PRE - 16) {
|
|
lwsl_err("oversize buf %d %d\n", (int)len,
|
|
(int)sizeof(buf) - LWS_PRE - 16);
|
|
return -1;
|
|
}
|
|
|
|
/*
|
|
* this only needs dealing with on http/1.1 to allow
|
|
* pipelining
|
|
*/
|
|
n = lws_snprintf(out, 14, "%X\x0d\x0a", (int)len);
|
|
out += n;
|
|
memcpy(out, in, len);
|
|
out += len;
|
|
*out++ = '\x0d';
|
|
*out++ = '\x0a';
|
|
|
|
n = lws_write(lws_get_parent(wsi),
|
|
(unsigned char *)buf + LWS_PRE,
|
|
len + n + 2, LWS_WRITE_HTTP);
|
|
} else
|
|
n = lws_write(lws_get_parent(wsi), (unsigned char *)in,
|
|
len, LWS_WRITE_HTTP);
|
|
if (n < 0)
|
|
return -1;
|
|
break; }
|
|
|
|
/* h1 http proxying... */
|
|
|
|
case LWS_CALLBACK_ESTABLISHED_CLIENT_HTTP: {
|
|
unsigned char *start, *p, *end;
|
|
|
|
/*
|
|
* We want to proxy these headers, but we are being called
|
|
* at the point the onward client was established, which is
|
|
* unrelated to the state or writability of our proxy
|
|
* connection.
|
|
*
|
|
* Therefore produce the headers using the onward client ah
|
|
* while we have it, and stick them on the output buflist to be
|
|
* written on the proxy connection as soon as convenient.
|
|
*/
|
|
|
|
parent = lws_get_parent(wsi);
|
|
|
|
if (!parent)
|
|
return 0;
|
|
|
|
start = p = (unsigned char *)buf + LWS_PRE;
|
|
end = p + sizeof(buf) - LWS_PRE - 256;
|
|
|
|
if (lws_add_http_header_status(lws_get_parent(wsi),
|
|
lws_http_client_http_response(wsi), &p, end))
|
|
return 1;
|
|
|
|
/*
|
|
* copy these headers from the client connection to the parent
|
|
*/
|
|
|
|
proxy_header(parent, wsi, end, 256,
|
|
WSI_TOKEN_HTTP_CONTENT_LENGTH, &p, end);
|
|
proxy_header(parent, wsi, end, 256,
|
|
WSI_TOKEN_HTTP_CONTENT_TYPE, &p, end);
|
|
proxy_header(parent, wsi, end, 256,
|
|
WSI_TOKEN_HTTP_ETAG, &p, end);
|
|
proxy_header(parent, wsi, end, 256,
|
|
WSI_TOKEN_HTTP_ACCEPT_LANGUAGE, &p, end);
|
|
proxy_header(parent, wsi, end, 256,
|
|
WSI_TOKEN_HTTP_CONTENT_ENCODING, &p, end);
|
|
proxy_header(parent, wsi, end, 256,
|
|
WSI_TOKEN_HTTP_CACHE_CONTROL, &p, end);
|
|
proxy_header(parent, wsi, end, 256,
|
|
WSI_TOKEN_HTTP_SET_COOKIE, &p, end);
|
|
proxy_header(parent, wsi, end, 256,
|
|
WSI_TOKEN_HTTP_LOCATION, &p, end);
|
|
|
|
if (!parent->http2_substream)
|
|
if (lws_add_http_header_by_token(parent,
|
|
WSI_TOKEN_CONNECTION, (unsigned char *)"close",
|
|
5, &p, end))
|
|
return -1;
|
|
|
|
/*
|
|
* We proxy using h1 only atm, and strip any chunking so it
|
|
* can go back out on h2 just fine.
|
|
*
|
|
* However if we are actually going out on h1, we need to add
|
|
* our own chunking since we still don't know the size.
|
|
*/
|
|
|
|
if (!parent->http2_substream &&
|
|
!lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_CONTENT_LENGTH)) {
|
|
lwsl_debug("downstream parent chunked\n");
|
|
if (lws_add_http_header_by_token(parent,
|
|
WSI_TOKEN_HTTP_TRANSFER_ENCODING,
|
|
(unsigned char *)"chunked", 7, &p, end))
|
|
return -1;
|
|
|
|
wsi->http.proxy_parent_chunked = 1;
|
|
}
|
|
|
|
if (lws_finalize_http_header(parent, &p, end))
|
|
return 1;
|
|
|
|
parent->http.prh_content_length = -1;
|
|
if (lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP_CONTENT_LENGTH))
|
|
parent->http.prh_content_length = atoll(
|
|
lws_hdr_simple_ptr(wsi,
|
|
WSI_TOKEN_HTTP_CONTENT_LENGTH));
|
|
|
|
parent->http.pending_return_headers_len = lws_ptr_diff(p, start);
|
|
parent->http.pending_return_headers =
|
|
lws_malloc(parent->http.pending_return_headers_len +
|
|
LWS_PRE, "return proxy headers");
|
|
if (!parent->http.pending_return_headers)
|
|
return -1;
|
|
|
|
memcpy(parent->http.pending_return_headers + LWS_PRE, start,
|
|
parent->http.pending_return_headers_len);
|
|
|
|
parent->reason_bf |= LWS_CB_REASON_AUX_BF__PROXY_HEADERS;
|
|
|
|
lwsl_debug("%s: LWS_CALLBACK_ESTABLISHED_CLIENT_HTTP: "
|
|
"prepared %d headers (len %d)\n", __func__,
|
|
lws_http_client_http_response(wsi),
|
|
(int)parent->http.prh_content_length);
|
|
|
|
/*
|
|
* so at this point, the onward client connection can bear
|
|
* traffic. We might be doing a POST and have pending cached
|
|
* inbound stuff to send, it can go now.
|
|
*/
|
|
|
|
lws_callback_on_writable(parent);
|
|
|
|
break; }
|
|
|
|
case LWS_CALLBACK_COMPLETED_CLIENT_HTTP:
|
|
lwsl_info("%s: COMPLETED_CLIENT_HTTP: %p (parent %p)\n",
|
|
__func__, wsi, lws_get_parent(wsi));
|
|
if (!lws_get_parent(wsi))
|
|
break;
|
|
lws_get_parent(wsi)->reason_bf |=
|
|
LWS_CB_REASON_AUX_BF__PROXY_TRANS_END;
|
|
lws_callback_on_writable(lws_get_parent(wsi));
|
|
break;
|
|
|
|
case LWS_CALLBACK_CLOSED_CLIENT_HTTP:
|
|
if (!lws_get_parent(wsi))
|
|
break;
|
|
lwsl_err("%s: LWS_CALLBACK_CLOSED_CLIENT_HTTP\n", __func__);
|
|
lws_set_timeout(lws_get_parent(wsi), LWS_TO_KILL_ASYNC,
|
|
PENDING_TIMEOUT_KILLED_BY_PROXY_CLIENT_CLOSE);
|
|
break;
|
|
|
|
case LWS_CALLBACK_CLIENT_APPEND_HANDSHAKE_HEADER:
|
|
parent = lws_get_parent(wsi);
|
|
if (!parent)
|
|
break;
|
|
|
|
p = (unsigned char **)in;
|
|
end = (*p) + len;
|
|
|
|
/*
|
|
* copy these headers from the parent request to the client
|
|
* connection's request
|
|
*/
|
|
|
|
proxy_header(wsi, parent, (unsigned char *)buf, sizeof(buf),
|
|
WSI_TOKEN_HTTP_ETAG, p, end);
|
|
proxy_header(wsi, parent, (unsigned char *)buf, sizeof(buf),
|
|
WSI_TOKEN_HTTP_IF_MODIFIED_SINCE, p, end);
|
|
proxy_header(wsi, parent, (unsigned char *)buf, sizeof(buf),
|
|
WSI_TOKEN_HTTP_ACCEPT_LANGUAGE, p, end);
|
|
proxy_header(wsi, parent, (unsigned char *)buf, sizeof(buf),
|
|
WSI_TOKEN_HTTP_ACCEPT_ENCODING, p, end);
|
|
proxy_header(wsi, parent, (unsigned char *)buf, sizeof(buf),
|
|
WSI_TOKEN_HTTP_CACHE_CONTROL, p, end);
|
|
proxy_header(wsi, parent, (unsigned char *)buf, sizeof(buf),
|
|
WSI_TOKEN_HTTP_COOKIE, p, end);
|
|
|
|
buf[0] = '\0';
|
|
lws_get_peer_simple(parent, buf, sizeof(buf));
|
|
if (lws_add_http_header_by_token(wsi, WSI_TOKEN_X_FORWARDED_FOR,
|
|
(unsigned char *)buf, (int)strlen(buf), p, end))
|
|
return -1;
|
|
|
|
break;
|
|
#endif
|
|
|
|
#ifdef LWS_WITH_CGI
|
|
/* CGI IO events (POLLIN/OUT) appear here, our default policy is:
|
|
*
|
|
* - POST data goes on subprocess stdin
|
|
* - subprocess stdout goes on http via writeable callback
|
|
* - subprocess stderr goes to the logs
|
|
*/
|
|
case LWS_CALLBACK_CGI:
|
|
args = (struct lws_cgi_args *)in;
|
|
switch (args->ch) { /* which of stdin/out/err ? */
|
|
case LWS_STDIN:
|
|
/* TBD stdin rx flow control */
|
|
break;
|
|
case LWS_STDOUT:
|
|
/* quench POLLIN on STDOUT until MASTER got writeable */
|
|
lws_rx_flow_control(args->stdwsi[LWS_STDOUT], 0);
|
|
wsi->reason_bf |= LWS_CB_REASON_AUX_BF__CGI;
|
|
/* when writing to MASTER would not block */
|
|
lws_callback_on_writable(wsi);
|
|
break;
|
|
case LWS_STDERR:
|
|
n = lws_get_socket_fd(args->stdwsi[LWS_STDERR]);
|
|
if (n < 0)
|
|
break;
|
|
n = read(n, buf, sizeof(buf) - 2);
|
|
if (n > 0) {
|
|
if (buf[n - 1] != '\n')
|
|
buf[n++] = '\n';
|
|
buf[n] = '\0';
|
|
lwsl_notice("CGI-stderr: %s\n", buf);
|
|
}
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case LWS_CALLBACK_CGI_TERMINATED:
|
|
lwsl_debug("LWS_CALLBACK_CGI_TERMINATED: %d %" PRIu64 "\n",
|
|
wsi->http.cgi->explicitly_chunked,
|
|
(uint64_t)wsi->http.cgi->content_length);
|
|
if (!wsi->http.cgi->explicitly_chunked &&
|
|
!wsi->http.cgi->content_length) {
|
|
/* send terminating chunk */
|
|
lwsl_debug("LWS_CALLBACK_CGI_TERMINATED: ending\n");
|
|
wsi->reason_bf |= LWS_CB_REASON_AUX_BF__CGI_CHUNK_END;
|
|
lws_callback_on_writable(wsi);
|
|
lws_set_timeout(wsi, PENDING_TIMEOUT_CGI, 3);
|
|
break;
|
|
}
|
|
if (lws_http_transaction_completed(wsi))
|
|
return -1;
|
|
return 0;
|
|
|
|
case LWS_CALLBACK_CGI_STDIN_DATA: /* POST body for stdin */
|
|
args = (struct lws_cgi_args *)in;
|
|
args->data[args->len] = '\0';
|
|
if (!args->stdwsi[LWS_STDIN])
|
|
return -1;
|
|
n = lws_get_socket_fd(args->stdwsi[LWS_STDIN]);
|
|
if (n < 0)
|
|
return -1;
|
|
|
|
#if defined(LWS_WITH_ZLIB)
|
|
if (wsi->http.cgi->gzip_inflate) {
|
|
/* gzip handling */
|
|
|
|
if (!wsi->http.cgi->gzip_init) {
|
|
lwsl_info("inflating gzip\n");
|
|
|
|
memset(&wsi->http.cgi->inflate, 0,
|
|
sizeof(wsi->http.cgi->inflate));
|
|
|
|
if (inflateInit2(&wsi->http.cgi->inflate,
|
|
16 + 15) != Z_OK) {
|
|
lwsl_err("%s: iniflateInit failed\n",
|
|
__func__);
|
|
return -1;
|
|
}
|
|
|
|
wsi->http.cgi->gzip_init = 1;
|
|
}
|
|
|
|
wsi->http.cgi->inflate.next_in = args->data;
|
|
wsi->http.cgi->inflate.avail_in = args->len;
|
|
|
|
do {
|
|
|
|
wsi->http.cgi->inflate.next_out =
|
|
wsi->http.cgi->inflate_buf;
|
|
wsi->http.cgi->inflate.avail_out =
|
|
sizeof(wsi->http.cgi->inflate_buf);
|
|
|
|
n = inflate(&wsi->http.cgi->inflate,
|
|
Z_SYNC_FLUSH);
|
|
|
|
switch (n) {
|
|
case Z_NEED_DICT:
|
|
case Z_STREAM_ERROR:
|
|
case Z_DATA_ERROR:
|
|
case Z_MEM_ERROR:
|
|
inflateEnd(&wsi->http.cgi->inflate);
|
|
wsi->http.cgi->gzip_init = 0;
|
|
lwsl_err("zlib error inflate %d\n", n);
|
|
return -1;
|
|
}
|
|
|
|
if (wsi->http.cgi->inflate.avail_out !=
|
|
sizeof(wsi->http.cgi->inflate_buf)) {
|
|
int written;
|
|
|
|
written = write(args->stdwsi[LWS_STDIN]->desc.filefd,
|
|
wsi->http.cgi->inflate_buf,
|
|
sizeof(wsi->http.cgi->inflate_buf) -
|
|
wsi->http.cgi->inflate.avail_out);
|
|
|
|
if (written != (int)(
|
|
sizeof(wsi->http.cgi->inflate_buf) -
|
|
wsi->http.cgi->inflate.avail_out)) {
|
|
lwsl_notice("LWS_CALLBACK_CGI_STDIN_DATA: "
|
|
"sent %d only %d went", n, args->len);
|
|
}
|
|
|
|
if (n == Z_STREAM_END) {
|
|
lwsl_err("gzip inflate end\n");
|
|
inflateEnd(&wsi->http.cgi->inflate);
|
|
wsi->http.cgi->gzip_init = 0;
|
|
break;
|
|
}
|
|
|
|
} else
|
|
break;
|
|
|
|
if (wsi->http.cgi->inflate.avail_out)
|
|
break;
|
|
|
|
} while (1);
|
|
|
|
return args->len;
|
|
}
|
|
#endif /* WITH_ZLIB */
|
|
|
|
n = write(n, args->data, args->len);
|
|
// lwsl_hexdump_notice(args->data, args->len);
|
|
if (n < args->len)
|
|
lwsl_notice("LWS_CALLBACK_CGI_STDIN_DATA: "
|
|
"sent %d only %d went", n, args->len);
|
|
|
|
if (wsi->http.cgi->post_in_expected && args->stdwsi[LWS_STDIN] &&
|
|
args->stdwsi[LWS_STDIN]->desc.filefd > 0) {
|
|
wsi->http.cgi->post_in_expected -= n;
|
|
if (!wsi->http.cgi->post_in_expected) {
|
|
struct lws *siwsi = args->stdwsi[LWS_STDIN];
|
|
|
|
lwsl_debug("%s: expected POST in end: "
|
|
"closing stdin wsi %p, fd %d\n",
|
|
__func__, siwsi, siwsi->desc.sockfd);
|
|
|
|
__remove_wsi_socket_from_fds(siwsi);
|
|
lwsi_set_state(siwsi, LRS_DEAD_SOCKET);
|
|
siwsi->socket_is_permanently_unusable = 1;
|
|
// lws_remove_child_from_any_parent(siwsi);
|
|
if (wsi->context->event_loop_ops->
|
|
close_handle_manually) {
|
|
|
|
wsi->context->event_loop_ops->
|
|
close_handle_manually(siwsi);
|
|
siwsi->told_event_loop_closed = 1;
|
|
} else {
|
|
compatible_close(siwsi->desc.sockfd);
|
|
__lws_free_wsi(siwsi);
|
|
}
|
|
wsi->http.cgi->pipe_fds[LWS_STDIN][1] = -1;
|
|
|
|
// args->stdwsi[LWS_STDIN] = NULL;
|
|
}
|
|
}
|
|
|
|
return n;
|
|
#endif /* WITH_CGI */
|
|
#endif /* ROLE_ H1 / H2 */
|
|
case LWS_CALLBACK_SSL_INFO:
|
|
si = in;
|
|
|
|
(void)si;
|
|
lwsl_notice("LWS_CALLBACK_SSL_INFO: where: 0x%x, ret: 0x%x\n",
|
|
si->where, si->ret);
|
|
break;
|
|
|
|
#if LWS_MAX_SMP > 1
|
|
case LWS_CALLBACK_GET_THREAD_ID:
|
|
return (int)(unsigned long long)pthread_self();
|
|
#endif
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return 0;
|
|
}
|