/*
 * 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"

/* max individual proxied header payload size */
#define MAXHDRVAL 1024

#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, (enum lws_token_indexes)index);

	if (n < 1) {
		lwsl_wsi_debug(wsi, "no index %d:", index);

		return 0;
	}

	if (lws_hdr_copy(par, (char *)temp, temp_len, (enum lws_token_indexes)index) < 0) {
		lwsl_wsi_notice(wsi, "unable to copy par hdr idx %d (len %d)",
				      index, n);
		return -1;
	}

	lwsl_wsi_debug(wsi, "index %d: %s", index, (char *)temp);

	if (lws_add_http_header_by_token(wsi, (enum lws_token_indexes)index, temp, n, p, end)) {
		lwsl_wsi_notice(wsi, "unable to append par hdr idx %d (len %d)",
				     index, n);
		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->mux_substream) {
		if (lws_write(wsi, (unsigned char *)buf + LWS_PRE, 0,
			      LWS_WRITE_HTTP_FINAL) < 0)
			goto bail;

		return 0;
	}

	*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)
		goto bail;

	return 0;

bail:
	lwsl_wsi_info(wsi, "h2 fin wr failed");

	return -1;
}

#endif

struct lws_proxy_pkt {
	struct lws_dll2 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_dll2 *dll;

	switch (reason) {

	/* h1 ws proxying... child / client / onward */

	case LWS_CALLBACK_CLIENT_ESTABLISHED:
		if (!wsi->h1_ws_proxied || !wsi->parent)
			break;

		/*
		 * If the parent has started to close, don't try to
		 * upgrade it, just let it go.
		 */
		if ((lwsi_state(wsi->parent) & 0xff) >= (LRS_RETURNED_CLOSE & 0xff))
			return -1;

		if (lws_process_ws_upgrade2(wsi->parent))
			return -1;

#if defined(LWS_WITH_HTTP2)
		if (wsi->parent->mux_substream)
			lwsl_wsi_info(wsi, "proxied h2 -> h1 ws established");
#endif
		break;

	case LWS_CALLBACK_CLIENT_CONFIRM_EXTENSION_SUPPORTED:
		return 1;

	case LWS_CALLBACK_CLIENT_CONNECTION_ERROR:
	case LWS_CALLBACK_CLIENT_CLOSED:
		lwsl_wsi_info(wsi, "client closed: parent %s",
				   lws_wsi_tag(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[MAXHDRVAL];
		char peer[64];

		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);

		lws_get_peer_simple(wsi->parent, peer, sizeof(peer));
		
		if (lws_add_http_header_by_token(wsi, WSI_TOKEN_X_FORWARDED_FOR,
						 (uint8_t *)peer, (int)strlen(peer), p, end))
                	lwsl_wsi_notice(wsi, "unable to append forwarded_for");

		break;
	}

	case LWS_CALLBACK_CLIENT_RECEIVE:
		wsi->parent->ws->proxy_buffered += len;
		if (wsi->parent->ws->proxy_buffered > 10 * 1024 * 1024) {
			lwsl_wsi_err(wsi, "proxied ws connection "
					  "excessive buffering: dropping");
			return -1;
		}
		pkt = lws_zalloc(sizeof(*pkt) + LWS_PRE + len, __func__);
		if (!pkt)
			return -1;

		pkt->len = len;
		pkt->first = (char)lws_is_first_fragment(wsi);
		pkt->final = (char)lws_is_final_fragment(wsi);
		pkt->binary = (char)lws_frame_is_binary(wsi);

		memcpy(((uint8_t *)&pkt[1]) + LWS_PRE, in, len);

		lws_dll2_add_tail(&pkt->pkt_list, &wsi->parent->ws->proxy_owner);
		lws_callback_on_writable(wsi->parent);
		break;

	case LWS_CALLBACK_CLIENT_WRITEABLE:
		dll = lws_dll2_get_head(&wsi->ws->proxy_owner);
		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_dll2_remove(dll);
		lws_free(pkt);

		if (lws_dll2_get_head(&wsi->ws->proxy_owner))
			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_wsi_info(wsi, "closed");
		return -1;

	case LWS_CALLBACK_RECEIVE:

               if (!wsi->child_list) {
                       lwsl_wsi_warn(wsi, "Proxy Srv side RX: no child");
                       break;
               }
               if (!wsi->child_list->ws) {
                       lwsl_wsi_warn(wsi, "Proxy Srv side RX: child does not have ws");
                       break;
               }

		pkt = lws_zalloc(sizeof(*pkt) + LWS_PRE + len, __func__);
		if (!pkt)
			return -1;

		pkt->len = len;
		pkt->first = (char)lws_is_first_fragment(wsi);
		pkt->final = (char)lws_is_final_fragment(wsi);
		pkt->binary = (char)lws_frame_is_binary(wsi);

		memcpy(((uint8_t *)&pkt[1]) + LWS_PRE, in, len);

		lws_dll2_add_tail(&pkt->pkt_list, &wsi->child_list->ws->proxy_owner);
		lws_callback_on_writable(wsi->child_list);
		break;

	case LWS_CALLBACK_SERVER_WRITEABLE:
		dll = lws_dll2_get_head(&wsi->ws->proxy_owner);
		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->ws->proxy_buffered -= pkt->len;

		lws_dll2_remove(dll);
		lws_free(pkt);

		if (lws_dll2_get_head(&wsi->ws->proxy_owner))
			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


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[LWS_PRE + 32 + 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:
#if defined(LWS_WITH_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_WITH_SERVER)
	case LWS_CALLBACK_HTTP_BODY_COMPLETION:
#if defined(LWS_WITH_HTTP_PROXY)
		if (wsi->child_list) {
			lwsl_wsi_info(wsi, "HTTP_BODY_COMPLETION: %d",
					   (int)len);
			lws_callback_on_writable(wsi->child_list);
			break;
		}
#endif
		if (lws_return_http_status(wsi, 200, NULL))
			return -1;
		break;

		/* 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_wsi_info(wsi, "HTTP_BODY: stashing %d", (int)len);
			if (lws_buflist_append_segment(
				     &wsi->http.buflist_post_body, in, len) < 0)
				return -1;
			lws_client_http_body_pending(wsi->child_list, 1);
			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_wsi_debug(wsi, "AUX_BF__CGI forcing close");
				return -1;
			}
			if (!n && wsi->http.cgi && wsi->http.cgi->lsp &&
			    wsi->http.cgi->lsp->stdwsi[LWS_STDOUT])
				lws_rx_flow_control(
					wsi->http.cgi->lsp->stdwsi[LWS_STDOUT], 1);

			if (wsi->reason_bf & LWS_CB_REASON_AUX_BF__CGI_HEADERS)
				wsi->reason_bf &=
					(char)~LWS_CB_REASON_AUX_BF__CGI_HEADERS;
			else
				wsi->reason_bf &= (char)~LWS_CB_REASON_AUX_BF__CGI;

			if (wsi->http.cgi && wsi->http.cgi->cgi_transaction_over) {
				lwsl_wsi_info(wsi, "txn over");
				return -1;
			}

			break;
		}

		if ((wsi->http.cgi && wsi->http.cgi->cgi_transaction_over) ||
		    (wsi->reason_bf & LWS_CB_REASON_AUX_BF__CGI_CHUNK_END)) {
			if (!wsi->mux_substream) {
				memcpy(buf + LWS_PRE, "0\x0d\x0a\x0d\x0a", 5);
				lwsl_wsi_debug(wsi, "wr chunk term and exiting");
				lws_write(wsi, (unsigned char *)buf +
						   LWS_PRE, 5, LWS_WRITE_HTTP);
			} else
				lws_write(wsi, (unsigned char *)buf +
						   LWS_PRE, 0,
						   LWS_WRITE_HTTP_FINAL);

			/* always close after sending it */
#if defined(LWS_WITH_SERVER)
			if (lws_http_transaction_completed(wsi))
#endif
				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 &=
				     (char)~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_wsi_debug(wsi, "issuing proxy headers: clen %d",
				    (int)wsi->http.prh_content_length);
			n = lws_write(wsi, wsi->http.pending_return_headers +
					   LWS_PRE,
				      wsi->http.pending_return_headers_len,
				      (enum lws_write_protocol)n);

			lws_free_set_NULL(wsi->http.pending_return_headers);

			if (n < 0) {
				lwsl_wsi_err(wsi, "EST_CLIENT_HTTP: wr failed");

				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 &= (char)~LWS_CB_REASON_AUX_BF__PROXY;
			if (!lws_get_child(wsi))
				break;

#if defined(LWS_WITH_LATENCY)
			lws_usec_t _proxy_rd_start = lws_now_usecs();
#endif

			/* this causes LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ */
			if (lws_http_client_read(lws_get_child(wsi), &px,
						 &lenx) < 0) {
				lwsl_wsi_info(wsi, "LWS_CB_REASON_AUX_BF__PROXY: "
					   "client closed");

				stream_close(wsi);

				return -1;
			}

#if defined(LWS_WITH_LATENCY)
			{
				unsigned int ms = (unsigned int)((lws_now_usecs() - _proxy_rd_start) / 1000);
				if (ms > 2) {
					struct lws_context_per_thread *pt = &wsi->a.context->pt[(int)wsi->tsi];
					lws_latency_note(pt, _proxy_rd_start, 2000, "proxyrd:%dms", ms);
				}
			}
#endif
			break;
		}

		if (wsi->reason_bf & LWS_CB_REASON_AUX_BF__PROXY_TRANS_END) {
			lwsl_wsi_info(wsi, "PROXY_TRANS_END");

			wsi->reason_bf &= (char)~LWS_CB_REASON_AUX_BF__PROXY_TRANS_END;

			if (stream_close(wsi))
				return -1;

#if defined(LWS_WITH_SERVER)
			if (lws_http_transaction_completed(wsi))
#endif
				return -1;
		}
#endif
		if (wsi->http.deferred_transaction_completed) {
			uint8_t zero = 0;
			lws_write(wsi, &zero, 0, LWS_WRITE_HTTP_FINAL);
#if defined(LWS_WITH_SERVER)
			if (lws_http_transaction_completed(wsi))
#endif
				return -1;
			return 0;
		}

		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 defined(LWS_WITH_LATENCY)
		lws_usec_t _proxy_wr_start = lws_now_usecs();
#endif

		if (wsi->http.proxy_parent_chunked) {

			if (len > sizeof(buf) - LWS_PRE - 16) {
				lwsl_wsi_err(wsi, "oversize buf %d %d", (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,
				      (size_t)(unsigned int)(len + (unsigned int)n + 2), LWS_WRITE_HTTP);
		} else
			n = lws_write(lws_get_parent(wsi), (unsigned char *)in,
				      len, LWS_WRITE_HTTP);

#if defined(LWS_WITH_LATENCY)
		{
			unsigned int ms = (unsigned int)((lws_now_usecs() - _proxy_wr_start) / 1000);
			if (ms > 2) {
				struct lws_context_per_thread *pt = &wsi->a.context->pt[(int)wsi->tsi];
				lws_latency_note(pt, _proxy_wr_start, 2000, "proxywr:%dms", ms);
			}
		}
#endif

		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 - MAXHDRVAL;

		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, MAXHDRVAL,
			     WSI_TOKEN_HTTP_CONTENT_LENGTH, &p, end);
		proxy_header(parent, wsi, end, MAXHDRVAL,
			     WSI_TOKEN_HTTP_CONTENT_TYPE, &p, end);
		proxy_header(parent, wsi, end, MAXHDRVAL,
			     WSI_TOKEN_HTTP_ETAG, &p, end);
		proxy_header(parent, wsi, end, MAXHDRVAL,
			     WSI_TOKEN_HTTP_ACCEPT_LANGUAGE, &p, end);
		proxy_header(parent, wsi, end, MAXHDRVAL,
			     WSI_TOKEN_HTTP_CONTENT_ENCODING, &p, end);
		proxy_header(parent, wsi, end, MAXHDRVAL,
			     WSI_TOKEN_HTTP_CACHE_CONTROL, &p, end);
		proxy_header(parent, wsi, end, MAXHDRVAL,
			     WSI_TOKEN_HTTP_SET_COOKIE, &p, end);
		proxy_header(parent, wsi, end, MAXHDRVAL,
			     WSI_TOKEN_HTTP_LOCATION, &p, end);

		if (!parent->mux_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->mux_substream &&
		    !lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_CONTENT_LENGTH)) {
			lwsl_wsi_debug(wsi, "downstream parent chunked");
			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 = (size_t)-1;
		if (lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP_CONTENT_LENGTH))
			parent->http.prh_content_length = (size_t)atoll(
				lws_hdr_simple_ptr(wsi,
						WSI_TOKEN_HTTP_CONTENT_LENGTH));

		parent->http.pending_return_headers_len = lws_ptr_diff_size_t(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_wsi_debug(wsi, "ESTABLISHED_CLIENT_HTTP: "
			   "prepared %d headers (len %d)",
			   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_wsi_info(wsi, "COMPLETED_CLIENT_HTTP: (parent %s)",
				   lws_wsi_tag(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),
        		       (enum pending_timeout)LWS_TO_KILL_ASYNC,
                               (int)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:
			if (args->stdwsi[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 = (int)read(n, buf, sizeof(buf) - 2);
			if (n > 0) {
				if (buf[n - 1] != '\n')
					buf[n++] = '\n';
				buf[n] = '\0';
				lwsl_wsi_notice(wsi, "CGI-stderr: %s", buf);
			}
			break;
		}
		break;

	case LWS_CALLBACK_CGI_TERMINATED:
		if (wsi->http.cgi) {
			lwsl_wsi_debug(wsi, "CGI_TERMINATED: %d %" PRIu64,
				wsi->http.cgi->explicitly_chunked,
				(uint64_t)wsi->http.cgi->content_length);
			if (!(wsi->http.cgi->explicitly_chunked && wsi->mux_substream) &&
			    !wsi->http.cgi->content_length) {
				/* send terminating chunk */
				lwsl_wsi_debug(wsi, "LWS_CALLBACK_CGI_TERMINATED: ending");
				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 (wsi->mux_substream && !wsi->cgi_stdout_zero_length)
				lws_write(wsi, (unsigned char *)buf + LWS_PRE, 0,
						      LWS_WRITE_HTTP_FINAL);
		}
#if defined(LWS_WITH_SERVER)
		if (lws_http_transaction_completed(wsi))
			return -1;
#endif
		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_wsi_info(wsi, "inflating gzip");

				memset(&wsi->http.cgi->inflate, 0,
				       sizeof(wsi->http.cgi->inflate));

				if (inflateInit2(&wsi->http.cgi->inflate,
						 16 + 15) != Z_OK) {
					lwsl_wsi_err(wsi, "iniflateInit fail");
					return -1;
				}

				wsi->http.cgi->gzip_init = 1;
			}

			wsi->http.cgi->inflate.next_in = args->data;
			wsi->http.cgi->inflate.avail_in = (unsigned int)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_wsi_err(wsi, "zlib err inflate %d", n);
					return -1;
				}

				if (wsi->http.cgi->inflate.avail_out !=
					   sizeof(wsi->http.cgi->inflate_buf)) {
					int written;

					written = (int)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_wsi_notice(wsi,
							"CGI_STDIN_DATA: "
							"sent %d only %d went",
							n, args->len);
					}

					if (n == Z_STREAM_END) {
						lwsl_wsi_err(wsi,
							    "gzip inflate end");
						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 = (int)write(n, args->data, (unsigned int)args->len);
//		lwsl_hexdump_notice(args->data, args->len);
		if (n < args->len)
			lwsl_wsi_notice(wsi, "CGI_STDIN_DATA: "
				    "sent %d only %d went", n, args->len);

		lwsl_wsi_info(wsi, "proxied %d bytes", n);

		if (wsi->http.cgi->post_in_expected && args->stdwsi[LWS_STDIN] &&
		    args->stdwsi[LWS_STDIN]->desc.filefd > 0) {
			wsi->http.cgi->post_in_expected -= (unsigned int)n;

			if (!wsi->http.cgi->post_in_expected) {
				struct lws *siwsi = args->stdwsi[LWS_STDIN];

				/*
				 * The situation here is that we finished
				 * proxying the incoming body from the net to
				 * the STDIN stdwsi... and we want to close it
				 * so it can understand we are done (necessary
				 * if no content-length)...
				 */

				lwsl_wsi_info(siwsi, "expected POST in end: "
						     "closing stdin fd %d",
						     siwsi->desc.sockfd);

				/*
				 * We don't want the child / parent relationship
				 * to be handled in close, since we want the
				 * rest of the cgi and children to stay up
				 */

				lws_remove_child_from_any_parent(siwsi);
				lws_wsi_close(siwsi, LWS_TO_KILL_ASYNC);
				wsi->http.cgi->lsp->stdwsi[LWS_STDIN] = NULL;
				lws_spawn_stdwsi_closed(wsi->http.cgi->lsp, siwsi);
			}
		}

		return n;
#endif /* WITH_CGI */
#endif /* ROLE_ H1 / H2 */
	case LWS_CALLBACK_SSL_INFO:
		si = in;

		(void)si;
		lwsl_wsi_notice(wsi, "SSL_INFO: where: 0x%x, ret: 0x%x",
				si->where, si->ret);
		break;

#if LWS_MAX_SMP > 1
	case LWS_CALLBACK_GET_THREAD_ID:
#ifdef __PTW32_H
		/* If we use implementation of PThreads for Win that is
		 * distributed by VCPKG */
		return (int)(lws_intptr_t)(pthread_self()).p;
#else
		return (int)(lws_intptr_t)pthread_self();
#endif // __PTW32_H
#endif

	default:
		break;
	}

	return 0;
}
