/*
 * libwebsockets - small server side websockets and web server implementation
 *
 * Copyright (C) 2010 - 2021 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>

/*
 * In-place str to lower case
 */

static void
strtolower(char *s)
{
	while (*s) {
#ifdef LWS_PLAT_OPTEE
		int tolower_optee(int c);
		*s = tolower_optee((int)*s);
#else
		*s = (char)tolower((int)*s);
#endif
		s++;
	}
}

int
lws_create_client_ws_object(const struct lws_client_connect_info *i,
			    struct lws *wsi)
{
	int v = SPEC_LATEST_SUPPORTED;

	/* allocate the ws struct for the wsi */
	wsi->ws = lws_zalloc(sizeof(*wsi->ws), "client ws struct");
	if (!wsi->ws) {
		lwsl_wsi_notice(wsi, "OOM");
		return 1;
	}

	/* -1 means just use latest supported */
	if (i->ietf_version_or_minus_one != -1 &&
	    i->ietf_version_or_minus_one)
		v = i->ietf_version_or_minus_one;

	wsi->ws->ietf_spec_revision = (uint8_t)v;

	if (i->allow_reserved_bits)
		wsi->ws->allow_reserved_bits = 1;

	if (i->allow_unknown_opcode)
		wsi->ws->allow_unknown_opcode = 1;

	return 0;
}

#if defined(LWS_WITH_CLIENT)

/*
 * Returns either LWS_HPI_RET_PLEASE_CLOSE_ME or LWS_HPI_RET_HANDLED
 */

lws_handling_result_t
lws_ws_handshake_client(struct lws *wsi, unsigned char **buf, size_t len)
{
	const uint8_t **cbuf = (const uint8_t **)buf;
	unsigned char *start_buf = *buf;

	if ((lwsi_state(wsi) != LRS_WAITING_PROXY_REPLY) &&
	    (lwsi_state(wsi) != LRS_H1C_ISSUE_HANDSHAKE) &&
	    (lwsi_state(wsi) != LRS_WAITING_SERVER_REPLY) &&
	    !lwsi_role_client(wsi))
		return LWS_HPI_RET_HANDLED;

	lwsl_wsi_debug(wsi, "hs client has %d in", (int)len);

	if (lws_is_flowcontrolled(wsi)) {
		lwsl_wsi_debug(wsi, "caching %ld", (long)len);
		if (lws_rxflow_cache(wsi, *buf, 0, len) == LWSRXFC_TRIMMED) {
			lwsl_wsi_info(wsi, "trimming inside rxflow cache");
			/* we indicate we used nothing, caller will not advance */
		} else {
			*buf += len; /* we indicate we used it all */
		}
		return LWS_HPI_RET_HANDLED;
	}

#if !defined(LWS_WITHOUT_EXTENSIONS)
	if (wsi->ws->rx_draining_ext) {
		lwsl_wsi_info(wsi, "draining ext");
		if (lws_ws_client_rx_sm(wsi, 0) == LWS_HPI_RET_PLEASE_CLOSE_ME)
			return LWS_HPI_RET_PLEASE_CLOSE_ME;
	}
#endif

	if (len) {
		if (lws_ws_client_rx_parser_block(wsi, cbuf, &len) !=
							LWS_HPI_RET_HANDLED) {
			lwsl_wsi_info(wsi, "client_rx_parser exited, closing");
			*buf = start_buf + len; /* Update how much we used */
			return LWS_HPI_RET_PLEASE_CLOSE_ME;
		}
	}

	return LWS_HPI_RET_HANDLED;
}
#endif

char *
lws_generate_client_ws_handshake(struct lws *wsi, char *p, const char *conn1, size_t p_len)
{
	char buf[128], hash[20], key_b64[40], *end = p + p_len;
	size_t s;
#if !defined(LWS_WITHOUT_EXTENSIONS)
	const struct lws_extension *ext;
	int ext_count = 0;
#endif

	/*
	 * create the random key
	 */
	if (lws_get_random(wsi->a.context, hash, 16) != 16) {
		lwsl_wsi_err(wsi, "Unable to read from random dev %s",
			 SYSTEM_RANDOM_FILEPATH);
		return NULL;
	}

#if !defined(__COVERITY__)
	lws_b64_encode_string(hash, 16, key_b64, sizeof(key_b64));
#else
	lws_strncpy(key_b64, "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", sizeof(key_b64));
#endif

	p += lws_snprintf(p, lws_ptr_diff_size_t(end, p),
			  "Upgrade: websocket\x0d\x0a"
			  "Connection: %sUpgrade\x0d\x0a"
			  "Sec-WebSocket-Key: ", conn1);

	if (lws_ptr_diff_size_t(end, p) < strlen(key_b64) + 2 + 128)
		return NULL;

	lws_strncpy(p, key_b64, lws_ptr_diff_size_t(end, p));
	p += strlen(key_b64);
	p += lws_snprintf(p, lws_ptr_diff_size_t(end, p), "\x0d\x0a");
	if (lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_SENT_PROTOCOLS))
		p += lws_snprintf(p, lws_ptr_diff_size_t(end, p),
				  "Sec-WebSocket-Protocol: %s\x0d\x0a",
		     lws_hdr_simple_ptr(wsi,
				     _WSI_TOKEN_CLIENT_SENT_PROTOCOLS));

	/* tell the server what extensions we could support */

#if !defined(LWS_WITHOUT_EXTENSIONS)
	ext = wsi->a.vhost->ws.extensions;
	while (ext && ext->callback) {

		int n = wsi->a.vhost->protocols[0].callback(wsi,
			LWS_CALLBACK_CLIENT_CONFIRM_EXTENSION_SUPPORTED,
				wsi->user_space, (char *)ext->name, 0);

		/*
		 * zero return from callback means go ahead and allow
		 * the extension, it's what we get if the callback is
		 * unhandled
		 */

		if (n) {
			ext++;
			continue;
		}

		/* apply it */

		if (ext_count)
			*p++ = ',';
		else
			p += lws_snprintf(p, lws_ptr_diff_size_t(end, p),
					  "Sec-WebSocket-Extensions: ");
		p += lws_snprintf(p, lws_ptr_diff_size_t(end, p),
					"%s", ext->client_offer);
		ext_count++;

		ext++;
	}
	if (ext_count)
		p += lws_snprintf(p, lws_ptr_diff_size_t(end, p), "\x0d\x0a");
#endif

	if (wsi->ws->ietf_spec_revision)
		p += lws_snprintf(p, lws_ptr_diff_size_t(end, p),
				  "Sec-WebSocket-Version: %d\x0d\x0a",
				  wsi->ws->ietf_spec_revision);

	/* prepare the expected server accept response */

	key_b64[39] = '\0'; /* enforce composed length below buf sizeof */

	s = strlen(key_b64);
	memcpy(buf, key_b64, s);
	memcpy(buf + s, "258EAFA5-E914-47DA-95CA-C5AB0DC85B11", 36);
	s += 36;

	lws_SHA1((unsigned char *)buf, (unsigned int)s, (unsigned char *)hash);

	lws_b64_encode_string(hash, 20,
		  wsi->http.ah->initial_handshake_hash_base64,
		  sizeof(wsi->http.ah->initial_handshake_hash_base64));

	return p;
}

int
lws_client_ws_upgrade(struct lws *wsi, const char **cce)
{
	struct lws_context *context = wsi->a.context;
	struct lws_tokenize ts;
	int n, len, okay = 0;
	lws_tokenize_elem e;
	char *p, buf[64];
	const char *pc;
#if !defined(LWS_WITHOUT_EXTENSIONS)
	struct lws_context_per_thread *pt = &wsi->a.context->pt[(int)wsi->tsi];
	char *sb = (char *)&pt->serv_buf[0];
	const struct lws_ext_options *opts;
	const struct lws_extension *ext;
	char ext_name[128];
	const char *c, *a;
	int more = 1;
	char ignore;
#endif

	if (wsi->client_mux_substream) {
		if (wsi->http.ah->http_response != 200) {
			lwsl_wsi_warn(wsi, "got bad HTTP response '%ld'",
				      (long)wsi->http.ah->http_response);
			*cce = "HS: ws upgrade response not 200";
			goto bail3;
		}
	} else {
		if (wsi->http.ah->http_response != 101) {
			lwsl_wsi_warn(wsi, "got bad HTTP response '%ld'",
				      (long)wsi->http.ah->http_response);
			*cce = "HS: ws upgrade response not 101";
			goto bail3;
		}

		if (lws_hdr_total_length(wsi, WSI_TOKEN_ACCEPT) == 0) {
			lwsl_wsi_info(wsi, "no ACCEPT");
			*cce = "HS: ACCEPT missing";
			goto bail3;
		}

		p = lws_hdr_simple_ptr(wsi, WSI_TOKEN_UPGRADE);
		if (!p) {
			lwsl_wsi_info(wsi, "no UPGRADE");
			*cce = "HS: UPGRADE missing";
			goto bail3;
		}
		strtolower(p);
		if (strcmp(p, "websocket")) {
			lwsl_wsi_warn(wsi, "got bad Upgrade header '%s'", p);
			*cce = "HS: Upgrade to something other than websocket";
			goto bail3;
		}

		/* connection: must have "upgrade" */

		lws_tokenize_init(&ts, buf, LWS_TOKENIZE_F_COMMA_SEP_LIST |
					    LWS_TOKENIZE_F_MINUS_NONTERM);
		n = lws_hdr_copy(wsi, buf, sizeof(buf) - 1, WSI_TOKEN_CONNECTION);
		if (n <= 0) { /* won't fit, or absent */
			lwsl_wsi_info(wsi, "malformed connection '%s'", buf);
			*cce = "HS: UPGRADE malformed";
			goto bail3;
		}
		ts.len = (unsigned int)n;

		do {
			e = lws_tokenize(&ts);
			switch (e) {
			case LWS_TOKZE_TOKEN:
				if (!strncasecmp(ts.token, "upgrade", ts.token_len))
					e = LWS_TOKZE_ENDED;
				break;

			case LWS_TOKZE_DELIMITER:
				break;

			default: /* includes ENDED found by the tokenizer itself */
				lwsl_wsi_info(wsi, "malformed connection '%s'", buf);
				*cce = "HS: UPGRADE malformed";
				goto bail3;
			}
		} while (e > 0);
	}

	pc = lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_SENT_PROTOCOLS);
#if defined(_DEBUG)
	if (!pc)
		lwsl_wsi_parser(wsi, "lws_client_int_s_hs: no protocol list");
	else
		lwsl_wsi_parser(wsi, "lws_client_int_s_hs: protocol list '%s'", pc);
#endif

	/*
	 * confirm the protocol the server wants to talk was in the list
	 * of protocols we offered
	 */

	len = lws_hdr_total_length(wsi, WSI_TOKEN_PROTOCOL);
	if (!len) {
		lwsl_wsi_info(wsi, "WSI_TOKEN_PROTOCOL is null");
		/*
		 * no protocol name to work from, if we don't already have one
		 * default to first protocol
		 */

		if (wsi->a.protocol) {
			p = (char *)wsi->a.protocol->name;
			goto identify_protocol;
		}

		/* no choice but to use the default protocol */

		n = 0;
		wsi->a.protocol = &wsi->a.vhost->protocols[0];
		goto check_extensions;
	}

	p = lws_hdr_simple_ptr(wsi, WSI_TOKEN_PROTOCOL);
	len = (int)strlen(p);

	while (pc && *pc && !okay) {
		if (!strncmp(pc, p, (unsigned int)len) &&
		    (pc[len] == ',' || pc[len] == '\0')) {
			okay = 1;
			continue;
		}
		while (*pc && *pc != ',')
			pc++;
		if (*pc == ',')
			pc++;
		while (*pc == ' ')
			pc++;
	}

	if (!okay) {
		lwsl_wsi_info(wsi, "got bad protocol %s", p);
		*cce = "HS: PROTOCOL malformed";
		goto bail2;
	}

identify_protocol:

#if defined(LWS_WITH_HTTP_PROXY)
	lws_strncpy(wsi->ws->actual_protocol, p,
		    sizeof(wsi->ws->actual_protocol));
#endif

	/*
	 * identify the selected protocol struct and set it
	 */
	n = 0;
	/* keep client connection pre-bound protocol */
	if (!lwsi_role_client(wsi))
		wsi->a.protocol = NULL;

	while (n < wsi->a.vhost->count_protocols) {
		if (!wsi->a.protocol &&
		    strcmp(p, wsi->a.vhost->protocols[n].name) == 0) {
			wsi->a.protocol = &wsi->a.vhost->protocols[n];
			break;
		}
		n++;
	}

	if (n == wsi->a.vhost->count_protocols) { /* no match */
		/* if server, that's already fatal */
		if (!lwsi_role_client(wsi)) {
			lwsl_wsi_info(wsi, "fail protocol %s", p);
			*cce = "HS: Cannot match protocol";
			goto bail2;
		}

		/* for client, find the index of our pre-bound protocol */

		n = 0;
		while (wsi->a.vhost->protocols[n].callback) {
			if (wsi->a.protocol && strcmp(wsi->a.protocol->name,
				   wsi->a.vhost->protocols[n].name) == 0) {
				wsi->a.protocol = &wsi->a.vhost->protocols[n];
				break;
			}
			n++;
		}

		if (!wsi->a.vhost->protocols[n].callback) {
			if (wsi->a.protocol)
				lwsl_wsi_err(wsi, "Failed to match protocol %s",
						wsi->a.protocol->name);
			else
				lwsl_wsi_err(wsi, "No protocol on client");
			*cce = "ws protocol no match";
			goto bail2;
		}
	}

	lwsl_wsi_debug(wsi, "Selected protocol %s", wsi->a.protocol ?
					     wsi->a.protocol->name : "no pcol");

check_extensions:
	/*
	 * stitch protocol choice into the vh protocol linked list
	 * We always insert ourselves at the start of the list
	 *
	 * X <-> B
	 * X <-> pAn <-> pB
	 */

	lws_same_vh_protocol_insert(wsi, n);

#if !defined(LWS_WITHOUT_EXTENSIONS)
	/* instantiate the accepted extensions */

	if (!lws_hdr_total_length(wsi, WSI_TOKEN_EXTENSIONS)) {
		lwsl_wsi_ext(wsi, "no client extensions allowed by server");
		goto check_accept;
	}

	/*
	 * break down the list of server accepted extensions
	 * and go through matching them or identifying bogons
	 */

	if (lws_hdr_copy(wsi, sb, (int)context->pt_serv_buf_size,
			 WSI_TOKEN_EXTENSIONS) < 0) {
		lwsl_wsi_warn(wsi, "ext list from server failed to copy");
		*cce = "HS: EXT: list too big";
		goto bail2;
	}

	c = sb;
	n = 0;
	ignore = 0;
	a = NULL;
	while (more) {

		if (*c && (*c != ',' && *c != '\t')) {
			if (*c == ';') {
				ignore = 1;
				if (!a)
					a = c + 1;
			}
			if (ignore || *c == ' ') {
				c++;
				continue;
			}

			ext_name[n] = *c++;
			if (n < (int)sizeof(ext_name) - 1)
				n++;
			continue;
		}
		ext_name[n] = '\0';
		ignore = 0;
		if (!*c)
			more = 0;
		else {
			c++;
			if (!n)
				continue;
		}

		/* check we actually support it */

		lwsl_wsi_notice(wsi, "checking client ext %s", ext_name);

		n = 0;
		ext = wsi->a.vhost->ws.extensions;
		while (ext && ext->callback) {
			if (strcmp(ext_name, ext->name)) {
				ext++;
				continue;
			}

			n = 1;
			lwsl_wsi_notice(wsi, "instantiating client ext %s", ext_name);

			if (wsi->ws->count_act_ext >= LWS_MAX_EXTENSIONS_ACTIVE) {
				lwsl_wsi_notice(wsi, "too many extensions");
				*cce = "HS: EXT: too many extensions";
				goto bail2;
			}

			/* instantiate the extension on this conn */

			wsi->ws->active_extensions[wsi->ws->count_act_ext] = ext;

			/* allow him to construct his ext instance */

			if (ext->callback(lws_get_context(wsi), ext, wsi,
				   LWS_EXT_CB_CLIENT_CONSTRUCT,
				   (void *)&wsi->ws->act_ext_user[
				                        wsi->ws->count_act_ext],
				   (void *)&opts, 0)) {
				lwsl_wsi_info(wsi, " ext %s failed construction",
					  ext_name);
				ext++;
				continue;
			}

			/*
			 * allow the user code to override ext defaults if it
			 * wants to
			 */
			ext_name[0] = '\0';
			if (user_callback_handle_rxflow(wsi->a.protocol->callback,
					wsi, LWS_CALLBACK_WS_EXT_DEFAULTS,
					(char *)ext->name, ext_name,
					sizeof(ext_name))) {
				*cce = "HS: EXT: failed setting defaults";
				goto bail2;
			}

			if (ext_name[0] &&
			    lws_ext_parse_options(ext, wsi,
					          wsi->ws->act_ext_user[
						        wsi->ws->count_act_ext],
					          opts, ext_name,
						  (int)strlen(ext_name))) {
				lwsl_wsi_err(wsi, "unable to parse user defaults '%s'",
					     ext_name);
				*cce = "HS: EXT: failed parsing defaults";
				goto bail2;
			}

			/*
			 * give the extension the server options
			 */
			if (a && lws_ext_parse_options(ext, wsi,
					wsi->ws->act_ext_user[
					                wsi->ws->count_act_ext],
					opts, a, lws_ptr_diff(c, a))) {
				lwsl_wsi_err(wsi, "unable to parse remote def '%s'", a);
				*cce = "HS: EXT: failed parsing options";
				goto bail2;
			}

			if (ext->callback(lws_get_context(wsi), ext, wsi,
					LWS_EXT_CB_OPTION_CONFIRM,
				      wsi->ws->act_ext_user[wsi->ws->count_act_ext],
				      NULL, 0)) {
				lwsl_wsi_err(wsi, "ext %s rejects server options %s",
					     ext->name, a);
				*cce = "HS: EXT: Rejects server options";
				goto bail2;
			}

			wsi->ws->count_act_ext++;

			ext++;
		}

		if (n == 0) {
			lwsl_wsi_warn(wsi, "Unknown ext '%s'!", ext_name);
			*cce = "HS: EXT: unknown ext";
			goto bail2;
		}

		a = NULL;
		n = 0;
	}

check_accept:
#endif

	/*
	 * Confirm his accept token is the one we precomputed.  An extended
	 * CONNECT reply (ws-over-h2, RFC 8441) has no key exchange and so no
	 * accept token; p is NULL there and there is nothing to check.
	 */

	p = lws_hdr_simple_ptr(wsi, WSI_TOKEN_ACCEPT);
	if (!wsi->client_mux_substream &&
	    (!p || strcmp(p, wsi->http.ah->initial_handshake_hash_base64))) {
		lwsl_wsi_warn(wsi, "lws_client_int_s_hs: accept '%s' wrong vs '%s'",
				  p ? p : "(null)",
				  wsi->http.ah->initial_handshake_hash_base64);
		*cce = "HS: Accept hash wrong";
		goto bail2;
	}

	/* allocate the per-connection user memory (if any) */
	if (lws_ensure_user_space(wsi)) {
		lwsl_wsi_err(wsi, "Problem allocating wsi user mem");
		*cce = "HS: OOM";
		goto bail2;
	}

	/*
	 * we seem to be good to go, give client last chance to check
	 * headers and OK it
	 */
	if (wsi->a.protocol->callback(wsi,
				    LWS_CALLBACK_CLIENT_FILTER_PRE_ESTABLISH,
				    wsi->user_space, NULL, 0)) {
		*cce = "HS: Rejected by filter cb";
		goto bail2;
	}

	/* clear his proxy connection timeout */
	lws_set_timeout(wsi, NO_PENDING_TIMEOUT, 0);

	/* free up his parsing allocations */
	lws_header_table_detach(wsi, 0);

	/*
	 * A ws stream carried inside an h2 connection (RFC 8441) must keep
	 * the encapsulation visible in its role, like the server side does:
	 * writes, writable requests and tx credit all route via the h2
	 * parent based on lwsi_role_h2_ENCAPSULATION().
	 */
	lws_role_transition(wsi, LWSIFR_CLIENT |
			    (wsi->client_mux_substream ? LWSIFR_P_ENCAP_H2 : 0),
			    LRS_ESTABLISHED, &role_ops_ws);
	lws_validity_confirmed(wsi);

	wsi->rxflow_change_to = LWS_RXFLOW_ALLOW;

	/*
	 * create the frame buffer for this connection according to the
	 * size mentioned in the protocol definition.  If 0 there, then
	 * use a big default for compatibility
	 */
	n = (int)wsi->a.protocol->rx_buffer_size;
	if (!n)
		n = (int)context->pt_serv_buf_size;
	n += LWS_PRE;
	wsi->ws->rx_ubuf = lws_malloc((unsigned int)n + 4 /* 0x0000ffff zlib */,
				"client frame buffer");
	if (!wsi->ws->rx_ubuf) {
		lwsl_wsi_err(wsi, "OOM allocating rx buffer %d", n);
		*cce = "HS: OOM";
		goto bail2;
	}
	wsi->ws->rx_ubuf_alloc = (unsigned int)n;

	lwsl_wsi_debug(wsi, "handshake OK for protocol %s", wsi->a.protocol->name);

	/* call him back to inform him he is up */

	if (wsi->a.protocol->callback(wsi, LWS_CALLBACK_CLIENT_ESTABLISHED,
				    wsi->user_space, NULL, 0)) {
		*cce = "HS: Rejected at CLIENT_ESTABLISHED";
		goto bail3;
	}

	return 0;

bail3:
	return 3;

bail2:
	return 2;
}
