text: /*
   * 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"
  
  static const unsigned char lextable_h1[] = {
  	#include "lextable.h"
  };
  
  #define FAIL_CHAR 0x08
  
  #if defined(LWS_WITH_CUSTOM_HEADERS)
  
  #define UHO_NLEN	0
  #define UHO_VLEN	2
  #define UHO_LL		4
  #define UHO_NAME	8
  
  #endif
  
  static struct allocated_headers *
  _lws_create_ah(struct lws_context_per_thread *pt, ah_data_idx_t data_size)
  {
  	struct allocated_headers *ah = lws_zalloc(sizeof(*ah), "ah struct");
  
  	if (!ah)
  		return NULL;
  
  	ah->data = lws_malloc(data_size, "ah data");
  	if (!ah->data) {
  		lws_free(ah);
  
  		return NULL;
  	}
  	ah->next = pt->http.ah_list;
  	pt->http.ah_list = ah;
  	ah->data_length = data_size;
  	pt->http.ah_pool_length++;
  
  	lwsl_info("%s: created ah %p (size %d): pool length %u\n", __func__,
  		    ah, (int)data_size, (unsigned int)pt->http.ah_pool_length);
  
  	return ah;
  }
  
  int
  _lws_destroy_ah(struct lws_context_per_thread *pt, struct allocated_headers *ah)
  {
  	lws_start_foreach_llp(struct allocated_headers **, a, pt->http.ah_list) {
  		if ((*a) == ah) {
  			*a = ah->next;
  			pt->http.ah_pool_length--;
  			lwsl_info("%s: freed ah %p : pool length %u\n",
  				    __func__, ah,
  				    (unsigned int)pt->http.ah_pool_length);
  			if (ah->data)
  				lws_free(ah->data);
  			lws_free(ah);
  
  			return 0;
  		}
  	} lws_end_foreach_llp(a, next);
  
  	return 1;
  }
  
  void
  _lws_header_table_reset(struct allocated_headers *ah)
  {
  	/* init the ah to reflect no headers or data have appeared yet */
  	memset(ah->frag_index, 0, sizeof(ah->frag_index));
  	memset(ah->frags, 0, sizeof(ah->frags));
  	ah->nfrag = 0;
  	ah->pos = 0;
  	ah->http_response = 0;
  	ah->parser_state = WSI_TOKEN_NAME_PART;
  	ah->lextable_pos = 0;
  	ah->unk_pos = 0;
  #if defined(LWS_WITH_CUSTOM_HEADERS)
  	ah->unk_ll_head = 0;
  	ah->unk_ll_tail = 0;
  #endif
  }
  
  // doesn't scrub the ah rxbuffer by default, parent must do if needed
  
  void
  __lws_header_table_reset(struct lws *wsi, int autoservice)
  {
  	struct allocated_headers *ah = wsi->http.ah;
  	struct lws_context_per_thread *pt;
  	struct lws_pollfd *pfd;
  
  	/* if we have the idea we're resetting 'our' ah, must be bound to one */
  	assert(ah);
  	/* ah also concurs with ownership */
  	assert(ah->wsi == wsi);
  
  	_lws_header_table_reset(ah);
  
  	/* since we will restart the ah, our new headers are not completed */
  	wsi->hdr_parsing_completed = 0;
  
  	/* while we hold the ah, keep a timeout on the wsi */
  	__lws_set_timeout(wsi, PENDING_TIMEOUT_HOLDING_AH,
  			  wsi->a.vhost->timeout_secs_ah_idle);
  
  	time(&ah->assigned);
  
  	if (wsi->position_in_fds_table != LWS_NO_FDS_POS &&
  	    lws_buflist_next_segment_len(&wsi->buflist, NULL) &&
  	    autoservice) {
  		lwsl_debug("%s: service on readbuf ah\n", __func__);
  
  		pt = &wsi->a.context->pt[(int)wsi->tsi];
  		/*
  		 * Unlike a normal connect, we have the headers already
  		 * (or the first part of them anyway)
  		 */
  		pfd = &pt->fds[wsi->position_in_fds_table];
  		pfd->revents |= LWS_POLLIN;
  		lwsl_err("%s: calling service\n", __func__);
  		lws_service_fd_tsi(wsi->a.context, pfd, wsi->tsi);
  	}
  }
  
  void
  lws_header_table_reset(struct lws *wsi, int autoservice)
  {
  	struct lws_context_per_thread *pt = &wsi->a.context->pt[(int)wsi->tsi];
  
  	lws_pt_lock(pt, __func__);
  
  	__lws_header_table_reset(wsi, autoservice);
  
  	lws_pt_unlock(pt);
  }
  
  static void
  _lws_header_ensure_we_are_on_waiting_list(struct lws *wsi)
  {
  	struct lws_context_per_thread *pt = &wsi->a.context->pt[(int)wsi->tsi];
  	struct lws_pollargs pa;
  	struct lws **pwsi = &pt->http.ah_wait_list;
  
  	while (*pwsi) {
  		if (*pwsi == wsi)
  			return;
  		pwsi = &(*pwsi)->http.ah_wait_list;
  	}
  
  	lwsl_info("%s: wsi: %s\n", __func__, lws_wsi_tag(wsi));
  	wsi->http.ah_wait_list = pt->http.ah_wait_list;
  	pt->http.ah_wait_list = wsi;
  	pt->http.ah_wait_list_length++;
  
  	/* we cannot accept input then */
  
  	_lws_change_pollfd(wsi, LWS_POLLIN, 0, &pa);
  }
  
  static int
  __lws_remove_from_ah_waiting_list(struct lws *wsi)
  {
          struct lws_context_per_thread *pt = &wsi->a.context->pt[(int)wsi->tsi];
  	struct lws **pwsi =&pt->http.ah_wait_list;
  
  	while (*pwsi) {
  		if (*pwsi == wsi) {
  			lwsl_info("%s: wsi %s\n", __func__, lws_wsi_tag(wsi));
  			/* point prev guy to our next */
  			*pwsi = wsi->http.ah_wait_list;
  			/* we shouldn't point anywhere now */
  			wsi->http.ah_wait_list = NULL;
  			pt->http.ah_wait_list_length--;
  
  			return 1;
  		}
  		pwsi = &(*pwsi)->http.ah_wait_list;
  	}
  
  	return 0;
  }
  
  int LWS_WARN_UNUSED_RESULT
  lws_header_table_attach(struct lws *wsi, int autoservice)
  {
  	struct lws_context *context = wsi->a.context;
  	struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi];
  	struct lws_pollargs pa;
  	int n;
  
  #if defined(LWS_ROLE_MQTT) && defined(LWS_WITH_CLIENT)
  	if (lwsi_role_mqtt(wsi))
  		goto connect_via_info2;
  #endif
  
  	lwsl_info("%s: %s: ah %p (tsi %d, count = %d) in\n", __func__,
  		  lws_wsi_tag(wsi), (void *)wsi->http.ah, wsi->tsi,
  		  pt->http.ah_count_in_use);
  
  	if (!lwsi_role_http(wsi)) {
  		lwsl_err("%s: bad role %s\n", __func__, wsi->role_ops->name);
  		assert(0);
  		return -1;
  	}
  
  	lws_pt_lock(pt, __func__);
  
  	/* if we are already bound to one, just clear it down */
  	if (wsi->http.ah) {
  		lwsl_info("%s: cleardown\n", __func__);
  		goto reset;
  	}
  
  	n = pt->http.ah_count_in_use == (int)context->max_http_header_pool;
  #if defined(LWS_WITH_PEER_LIMITS)
  	if (!n)
  		n = lws_peer_confirm_ah_attach_ok(context, wsi->peer);
  #endif
  	if (n) {
  		/*
  		 * Pool is either all busy, or we don't want to give this
  		 * particular guy an ah right now...
  		 *
  		 * Make sure we are on the waiting list, and return that we
  		 * weren't able to provide the ah
  		 */
  		_lws_header_ensure_we_are_on_waiting_list(wsi);
  
  		goto bail;
  	}
  
  	__lws_remove_from_ah_waiting_list(wsi);
  
  	wsi->http.ah = _lws_create_ah(pt, context->max_http_header_data);
  	if (!wsi->http.ah) { /* we could not create an ah */
  		_lws_header_ensure_we_are_on_waiting_list(wsi);
  
  		goto bail;
  	}
  
  	wsi->http.ah->in_use = 1;
  	wsi->http.ah->wsi = wsi; /* mark our owner */
  	pt->http.ah_count_in_use++;
  
  #if defined(LWS_WITH_PEER_LIMITS) && (defined(LWS_ROLE_H1) || \
      defined(LWS_ROLE_H2))
  	lws_context_lock(context, "ah attach"); /* <========================= */
  	if (wsi->peer)
  		wsi->peer->http.count_ah++;
  	lws_context_unlock(context); /* ====================================> */
  #endif
  
  	_lws_change_pollfd(wsi, 0, LWS_POLLIN, &pa);
  
  	lwsl_info("%s: did attach wsi %s: ah %p: count %d (on exit)\n", __func__,
  		  lws_wsi_tag(wsi), (void *)wsi->http.ah, pt->http.ah_count_in_use);
  
  reset:
  	__lws_header_table_reset(wsi, autoservice);
  
  	lws_pt_unlock(pt);
  
  #if defined(LWS_WITH_CLIENT)
  #if defined(LWS_ROLE_MQTT)
  connect_via_info2:
  #endif
  	if (lwsi_role_client(wsi) && lwsi_state(wsi) == LRS_UNCONNECTED)
  		if (!lws_http_client_connect_via_info2(wsi))
  			/* our client connect has failed, the wsi
  			 * has been closed
  			 */
  			return -1;
  #endif
  
  	return 0;
  
  bail:
  	lws_pt_unlock(pt);
  
  	return 1;
  }
  
  int __lws_header_table_detach(struct lws *wsi, int autoservice)
  {
  	struct lws_context *context = wsi->a.context;
  	struct allocated_headers *ah = wsi->http.ah;
  	struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi];
  	struct lws_pollargs pa;
  	struct lws **pwsi, **pwsi_eligible;
  	time_t now;
  
  	__lws_remove_from_ah_waiting_list(wsi);
  
  	if (!ah)
  		return 0;
  
  	lwsl_info("%s: %s: ah %p (tsi=%d, count = %d)\n", __func__,
  		  lws_wsi_tag(wsi), (void *)ah, wsi->tsi,
  		  pt->http.ah_count_in_use);
  
  	/* we did have an ah attached */
  	time(&now);
  	if (ah->assigned && now - ah->assigned > 3) {
  		/*
  		 * we're detaching the ah, but it was held an
  		 * unreasonably long time
  		 */
  		lwsl_debug("%s: %s: ah held %ds, role/state 0x%lx 0x%x,"
  			    "\n", __func__, lws_wsi_tag(wsi),
  			    (int)(now - ah->assigned),
  			    (unsigned long)lwsi_role(wsi), lwsi_state(wsi));
  	}
  
  	ah->assigned = 0;
  
  	/* if we think we're detaching one, there should be one in use */
  	assert(pt->http.ah_count_in_use > 0);
  	/* and this specific one should have been in use */
  	assert(ah->in_use);
  	memset(&wsi->http.ah, 0, sizeof(wsi->http.ah));
  
  #if defined(LWS_WITH_PEER_LIMITS)
  	if (ah->wsi)
  		lws_peer_track_ah_detach(context, wsi->peer);
  #endif
  	ah->wsi = NULL; /* no owner */
  	wsi->http.ah = NULL;
  
  	pwsi = &pt->http.ah_wait_list;
  
  	/* oh there is nobody on the waiting list... leave the ah unattached */
  	if (!*pwsi)
  		goto nobody_usable_waiting;
  
  	/*
  	 * at least one wsi on the same tsi is waiting, give it to oldest guy
  	 * who is allowed to take it (if any)
  	 */
  	lwsl_info("%s: pt wait list %s\n", __func__, lws_wsi_tag(*pwsi));
  	wsi = NULL;
  	pwsi_eligible = NULL;
  
  	while (*pwsi) {
  #if defined(LWS_WITH_PEER_LIMITS)
  		/* are we willing to give this guy an ah? */
  		if (!lws_peer_confirm_ah_attach_ok(context, (*pwsi)->peer))
  #endif
  		{
  			wsi = *pwsi;
  			pwsi_eligible = pwsi;
  		}
  
  		pwsi = &(*pwsi)->http.ah_wait_list;
  	}
  
  	if (!wsi) /* everybody waiting already has too many ah... */
  		goto nobody_usable_waiting;
  
  	lwsl_info("%s: transferring ah to last eligible wsi in wait list "
  		  "%s (wsistate 0x%lx)\n", __func__, lws_wsi_tag(wsi),
  		  (unsigned long)wsi->wsistate);
  
  	wsi->http.ah = ah;
  	ah->wsi = wsi; /* new owner */
  
  	__lws_header_table_reset(wsi, autoservice);
  #if defined(LWS_WITH_PEER_LIMITS) && (defined(LWS_ROLE_H1) || \
      defined(LWS_ROLE_H2))
  	lws_context_lock(context, "ah detach"); /* <========================= */
  	if (wsi->peer)
  		wsi->peer->http.count_ah++;
  	lws_context_unlock(context); /* ====================================> */
  #endif
  
  	/* clients acquire the ah and then insert themselves in fds table... */
  	if (wsi->position_in_fds_table != LWS_NO_FDS_POS) {
  		lwsl_info("%s: Enabling %s POLLIN\n", __func__, lws_wsi_tag(wsi));
  
  		/* he has been stuck waiting for an ah, but now his wait is
  		 * over, let him progress */
  
  		_lws_change_pollfd(wsi, 0, LWS_POLLIN, &pa);
  	}
  
  	/* point prev guy to next guy in list instead */
  	*pwsi_eligible = wsi->http.ah_wait_list;
  	/* the guy who got one is out of the list */
  	wsi->http.ah_wait_list = NULL;
  	pt->http.ah_wait_list_length--;
  
  #if defined(LWS_WITH_CLIENT)
  	if (lwsi_role_client(wsi) && lwsi_state(wsi) == LRS_UNCONNECTED) {
  		lws_pt_unlock(pt);
  
  		if (!lws_http_client_connect_via_info2(wsi)) {
  			/* our client connect has failed, the wsi
  			 * has been closed
  			 */
  
  			return -1;
  		}
  		return 0;
  	}
  #endif
  
  	assert(!!pt->http.ah_wait_list_length ==
  			!!(lws_intptr_t)pt->http.ah_wait_list);
  bail:
  	lwsl_info("%s: %s: ah %p (tsi=%d, count = %d)\n", __func__,
  		  lws_wsi_tag(wsi), (void *)ah, pt->tid, pt->http.ah_count_in_use);
  
  	return 0;
  
  nobody_usable_waiting:
  	lwsl_info("%s: nobody usable waiting\n", __func__);
  	_lws_destroy_ah(pt, ah);
  	pt->http.ah_count_in_use--;
  
  	goto bail;
  }
  
  int lws_header_table_detach(struct lws *wsi, int autoservice)
  {
  	struct lws_context *context = wsi->a.context;
  	struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi];
  	int n;
  
  	lws_pt_lock(pt, __func__);
  	n = __lws_header_table_detach(wsi, autoservice);
  	lws_pt_unlock(pt);
  
  	return n;
  }
  
  int
  lws_hdr_fragment_length(struct lws *wsi, enum lws_token_indexes h, int frag_idx)
  {
  	int n;
  
  	if (!wsi->http.ah)
  		return 0;
  
  	n = wsi->http.ah->frag_index[h];
  	if (!n)
  		return 0;
  	do {
  		if (!frag_idx)
  			return wsi->http.ah->frags[n].len;
  		n = wsi->http.ah->frags[n].nfrag;
  	} while (frag_idx-- && n);
  
  	return 0;
  }
  
  int lws_hdr_total_length(struct lws *wsi, enum lws_token_indexes h)
  {
  	int n;
  	int len = 0;
  
  	if (!wsi->http.ah)
  		return 0;
  
  	n = wsi->http.ah->frag_index[h];
  	if (!n)
  		return 0;
  	do {
  		len += wsi->http.ah->frags[n].len;
  		n = wsi->http.ah->frags[n].nfrag;
  
  		if (n)
  			len++;
  
  	} while (n);
  
  	return len;
  }
  
  int lws_hdr_copy_fragment(struct lws *wsi, char *dst, int len,
  				      enum lws_token_indexes h, int frag_idx)
  {
  	int n = 0;
  	int f;
  
  	if (!wsi->http.ah)
  		return -1;
  
  	f = wsi->http.ah->frag_index[h];
  
  	if (!f)
  		return -1;
  
  	while (n < frag_idx) {
  		f = wsi->http.ah->frags[f].nfrag;
  		if (!f)
  			return -1;
  		n++;
  	}
  
  	if (wsi->http.ah->frags[f].len >= len)
  		return -1;
  
  	memcpy(dst, wsi->http.ah->data + wsi->http.ah->frags[f].offset,
  	       wsi->http.ah->frags[f].len);
  	dst[wsi->http.ah->frags[f].len] = '\0';
  
  	return wsi->http.ah->frags[f].len;
  }
  
  int lws_hdr_copy(struct lws *wsi, char *dst, int len,
  			     enum lws_token_indexes h)
  {
  	int toklen = lws_hdr_total_length(wsi, h), n, comma;
  
  	*dst = '\0';
  	if (!toklen)
  		return 0;
  
  	if (toklen >= len)
  		return -1;
  
  	if (!wsi->http.ah)
  		return -1;
  
  	n = wsi->http.ah->frag_index[h];
  	if (!n)
  		return 0;
  	do {
  		comma = (wsi->http.ah->frags[n].nfrag) ? 1 : 0;
  
  		if (h == WSI_TOKEN_HTTP_URI_ARGS)
  			lwsl_notice("%s: WSI_TOKEN_HTTP_URI_ARGS '%.*s'\n",
  				    __func__, (int)wsi->http.ah->frags[n].len,
  				    &wsi->http.ah->data[
  				                wsi->http.ah->frags[n].offset]);
  
  		if (wsi->http.ah->frags[n].len + comma >= len) {
  			lwsl_notice("blowout len\n");
  			return -1;
  		}
  		strncpy(dst, &wsi->http.ah->data[wsi->http.ah->frags[n].offset],
  		        wsi->http.ah->frags[n].len);
  		dst += wsi->http.ah->frags[n].len;
  		len -= wsi->http.ah->frags[n].len;
  		n = wsi->http.ah->frags[n].nfrag;
  
  		/*
  		 * Note if you change this logic, take care about updating len
  		 * and make sure lws_hdr_total_length() gives the same resulting
  		 * length
  		 */
  
  		if (comma) {
  			if (h == WSI_TOKEN_HTTP_COOKIE ||
  			    h == WSI_TOKEN_HTTP_SET_COOKIE)
  				*dst++ = ';';
  			else
  				if (h == WSI_TOKEN_HTTP_URI_ARGS)
  					*dst++ = '&';
  				else
  					*dst++ = ',';
  			len--;
  		}
  				
  	} while (n);
  	*dst = '\0';
  
  	if (h == WSI_TOKEN_HTTP_URI_ARGS)
  		lwsl_err("%s: WSI_TOKEN_HTTP_URI_ARGS toklen %d\n", __func__, (int)toklen);
  
  	return toklen;
  }
  
  #if defined(LWS_WITH_CUSTOM_HEADERS)
  int
  lws_hdr_custom_length(struct lws *wsi, const char *name, int nlen)
  {
  	ah_data_idx_t ll;
  
  	if (!wsi->http.ah || wsi->mux_substream)
  		return -1;
  
  	ll = wsi->http.ah->unk_ll_head;
  	while (ll) {
  		if (ll >= wsi->http.ah->data_length)
  			return -1;
  		if (nlen == lws_ser_ru16be(
  			(uint8_t *)&wsi->http.ah->data[ll + UHO_NLEN]) &&
  		    !strncmp(name, &wsi->http.ah->data[ll + UHO_NAME], (unsigned int)nlen))
  			return lws_ser_ru16be(
  				(uint8_t *)&wsi->http.ah->data[ll + UHO_VLEN]);
  
  		ll = lws_ser_ru32be((uint8_t *)&wsi->http.ah->data[ll + UHO_LL]);
  	}
  
  	return -1;
  }
  
  int
  lws_hdr_custom_copy(struct lws *wsi, char *dst, int len, const char *name,
  		    int nlen)
  {
  	ah_data_idx_t ll;
  	int n;
  
  	if (!wsi->http.ah || wsi->mux_substream)
  		return -1;
  
  	*dst = '\0';
  
  	ll = wsi->http.ah->unk_ll_head;
  	while (ll) {
  		if (ll >= wsi->http.ah->data_length)
  			return -1;
  		if (nlen == lws_ser_ru16be(
  			(uint8_t *)&wsi->http.ah->data[ll + UHO_NLEN]) &&
  		    !strncmp(name, &wsi->http.ah->data[ll + UHO_NAME], (unsigned int)nlen)) {
  			n = lws_ser_ru16be(
  				(uint8_t *)&wsi->http.ah->data[ll + UHO_VLEN]);
  			if (n + 1 > len)
  				return -1;
  			strncpy(dst, &wsi->http.ah->data[ll + UHO_NAME + (unsigned int)nlen], (unsigned int)n);
  			dst[n] = '\0';
  
  			return n;
  		}
  		ll = lws_ser_ru32be((uint8_t *)&wsi->http.ah->data[ll + UHO_LL]);
  	}
  
  	return -1;
  }
  
  int
  lws_hdr_custom_name_foreach(struct lws *wsi, lws_hdr_custom_fe_cb_t cb,
  			    void *custom)
  {
  	ah_data_idx_t ll;
  
  	if (!wsi->http.ah || wsi->mux_substream)
  		return -1;
  
  	ll = wsi->http.ah->unk_ll_head;
  
  	while (ll) {
  		if (ll >= wsi->http.ah->data_length)
  			return -1;
  
  		cb(&wsi->http.ah->data[ll + UHO_NAME],
  		   lws_ser_ru16be((uint8_t *)&wsi->http.ah->data[ll + UHO_NLEN]),
  		   custom);
  
  		ll = lws_ser_ru32be((uint8_t *)&wsi->http.ah->data[ll + UHO_LL]);
  	}
  
  	return 0;
  }
  #endif
  
  char *lws_hdr_simple_ptr(struct lws *wsi, enum lws_token_indexes h)
  {
  	int n;
  
  	if (!wsi->http.ah)
  		return NULL;
  
  	n = wsi->http.ah->frag_index[h];
  	if (!n)
  		return NULL;
  
  	return wsi->http.ah->data + wsi->http.ah->frags[n].offset;
  }
  
  static int LWS_WARN_UNUSED_RESULT
  lws_pos_in_bounds(struct lws *wsi)
  {
  	if (!wsi->http.ah)
  		return -1;
  
  	if (wsi->http.ah->pos <
  	    (unsigned int)wsi->a.context->max_http_header_data)
  		return 0;
  
  	if ((int)wsi->http.ah->pos >= (int)wsi->a.context->max_http_header_data - 1) {
  		lwsl_err("Ran out of header data space\n");
  		return 1;
  	}
  
  	/*
  	 * with these tests everywhere, it should never be able to exceed
  	 * the limit, only meet it
  	 */
  	lwsl_err("%s: pos %ld, limit %ld\n", __func__,
  		 (unsigned long)wsi->http.ah->pos,
  		 (unsigned long)wsi->a.context->max_http_header_data);
  	assert(0);
  
  	return 1;
  }
  
  int LWS_WARN_UNUSED_RESULT
  lws_hdr_simple_create(struct lws *wsi, enum lws_token_indexes h, const char *s)
  {
  	if (!*s) {
  		/*
  		 * If we get an empty string, then remove any entry for the
  		 * header
  		 */
  		wsi->http.ah->frag_index[h] = 0;
  
  		return 0;
  	}
  
  	wsi->http.ah->nfrag++;
  	if (wsi->http.ah->nfrag == LWS_ARRAY_SIZE(wsi->http.ah->frags)) {
  		lwsl_warn("More hdr frags than we can deal with, dropping\n");
  		return -1;
  	}
  
  	wsi->http.ah->frag_index[h] = wsi->http.ah->nfrag;
  
  	wsi->http.ah->frags[wsi->http.ah->nfrag].offset = wsi->http.ah->pos;
  	wsi->http.ah->frags[wsi->http.ah->nfrag].len = 0;
  	wsi->http.ah->frags[wsi->http.ah->nfrag].nfrag = 0;
  
  	do {
  		if (lws_pos_in_bounds(wsi))
  			return -1;
  
  		wsi->http.ah->data[wsi->http.ah->pos++] = *s;
  		if (*s)
  			wsi->http.ah->frags[wsi->http.ah->nfrag].len++;
  	} while (*s++);
  
  	return 0;
  }
  
  static int LWS_WARN_UNUSED_RESULT
  issue_char(struct lws *wsi, unsigned char c)
  {
  	unsigned short frag_len;
  
  	if (lws_pos_in_bounds(wsi))
  		return -1;
  
  	frag_len = wsi->http.ah->frags[wsi->http.ah->nfrag].len;
  	/*
  	 * If we haven't hit the token limit, just copy the character into
  	 * the header
  	 */
  	if (!wsi->http.ah->current_token_limit ||
  	    frag_len < wsi->http.ah->current_token_limit) {
  		wsi->http.ah->data[wsi->http.ah->pos++] = (char)c;
  		wsi->http.ah->frags[wsi->http.ah->nfrag].len++;
  		return 0;
  	}
  
  	/* Insert a null character when we *hit* the limit: */
  	if (frag_len == wsi->http.ah->current_token_limit) {
  		if (lws_pos_in_bounds(wsi))
  			return -1;
  
  		wsi->http.ah->data[wsi->http.ah->pos++] = '\0';
  		lwsl_warn("header %li exceeds limit %ld\n",
  			  (long)wsi->http.ah->parser_state,
  			  (long)wsi->http.ah->current_token_limit);
  	}
  
  	return 1;
  }
  
  int
  lws_parse_urldecode(struct lws *wsi, uint8_t *_c)
  {
  	struct allocated_headers *ah = wsi->http.ah;
  	unsigned int enc = 0;
  	uint8_t c = *_c;
  
  	// lwsl_notice("ah->ups %d\n", ah->ups);
  
  	/*
  	 * PRIORITY 1
  	 * special URI processing... convert %xx
  	 */
  	switch (ah->ues) {
  	case URIES_IDLE:
  		if (c == '%') {
  			ah->ues = URIES_SEEN_PERCENT;
  			goto swallow;
  		}
  		break;
  	case URIES_SEEN_PERCENT:
  		if (char_to_hex((char)c) < 0)
  			/* illegal post-% char */
  			goto forbid;
  
  		ah->esc_stash = (char)c;
  		ah->ues = URIES_SEEN_PERCENT_H1;
  		goto swallow;
  
  	case URIES_SEEN_PERCENT_H1:
  		if (char_to_hex((char)c) < 0)
  			/* illegal post-% char */
  			goto forbid;
  
  		*_c = (uint8_t)(unsigned int)((char_to_hex(ah->esc_stash) << 4) |
  				char_to_hex((char)c));
  		c = *_c;
  		enc = 1;
  		ah->ues = URIES_IDLE;
  		break;
  	}
  
  	/*
  	 * PRIORITY 2
  	 * special URI processing...
  	 *  convert /.. or /... or /../ etc to /
  	 *  convert /./ to /
  	 *  convert // or /// etc to /
  	 *  leave /.dir or whatever alone
  	 */
  
  	if (!c && (!ah->frag_index[WSI_TOKEN_HTTP_URI_ARGS] ||
  		   !ah->post_literal_equal)) {
  		/*
  		 * Since user code is typically going to parse the path using
  		 * NUL-terminated apis, it's too dangerous to allow NUL
  		 * injection here.
  		 *
  		 * It's allowed in the urlargs, because the apis to access
  		 * those only allow retreival with explicit length.
  		 */
  		lwsl_warn("%s: saw NUL outside of uri args\n", __func__);
  		return -1;
  	}
  
  	switch (ah->ups) {
  	case URIPS_IDLE:
  
  		/* genuine delimiter */
  		if ((c == '&' || c == ';') && !enc) {
  			if (issue_char(wsi, '\0') < 0)
  				return -1;
  			/* don't account for it */
  			wsi->http.ah->frags[wsi->http.ah->nfrag].len--;
  			/* link to next fragment */
  			ah->frags[ah->nfrag].nfrag = (uint8_t)(ah->nfrag + 1);
  			ah->nfrag++;
  			if (ah->nfrag >= LWS_ARRAY_SIZE(ah->frags))
  				goto excessive;
  			/* start next fragment after the & */
  			ah->post_literal_equal = 0;
  			ah->frags[ah->nfrag].offset = ++ah->pos;
  			ah->frags[ah->nfrag].len = 0;
  			ah->frags[ah->nfrag].nfrag = 0;
  			goto swallow;
  		}
  		/* uriencoded = in the name part, disallow */
  		if (c == '=' && enc &&
  		    ah->frag_index[WSI_TOKEN_HTTP_URI_ARGS] &&
  		    !ah->post_literal_equal) {
  			c = '_';
  			*_c =c;
  		}
  
  		/* after the real =, we don't care how many = */
  		if (c == '=' && !enc)
  			ah->post_literal_equal = 1;
  
  		/* + to space */
  		if (c == '+' && !enc) {
  			c = ' ';
  			*_c = c;
  		}
  		/* issue the first / always */
  		if (c == '/' && !ah->frag_index[WSI_TOKEN_HTTP_URI_ARGS])
  			ah->ups = URIPS_SEEN_SLASH;
  		break;
  	case URIPS_SEEN_SLASH:
  		/* swallow subsequent slashes */
  		if (c == '/')
  			goto swallow;
  		/* track and swallow the first . after / */
  		if (c == '.') {
  			ah->ups = URIPS_SEEN_SLASH_DOT;
  			goto swallow;
  		}
  		ah->ups = URIPS_IDLE;
  		break;
  	case URIPS_SEEN_SLASH_DOT:
  		/* swallow second . */
  		if (c == '.') {
  			ah->ups = URIPS_SEEN_SLASH_DOT_DOT;
  			goto swallow;
  		}
  		/* change /./ to / */
  		if (c == '/') {
  			ah->ups = URIPS_SEEN_SLASH;
  			goto swallow;
  		}
  		/* it was like /.dir ... regurgitate the . */
  		ah->ups = URIPS_IDLE;
  		if (issue_char(wsi, '.') < 0)
  			return -1;
  		break;
  
  	case URIPS_SEEN_SLASH_DOT_DOT:
  
  		/* /../ or /..[End of URI] --> backup to last / */
  		if (c == '/' || c == '?') {
  			/*
  			 * back up one dir level if possible
  			 * safe against header fragmentation because
  			 * the method URI can only be in 1 fragment
  			 */
  			if (ah->frags[ah->nfrag].len > 2) {
  				ah->pos--;
  				ah->frags[ah->nfrag].len--;
  				do {
  					ah->pos--;
  					ah->frags[ah->nfrag].len--;
  				} while (ah->frags[ah->nfrag].len > 1 &&
  					 ah->data[ah->pos] != '/');
  			}
  			ah->ups = URIPS_SEEN_SLASH;
  			if (ah->frags[ah->nfrag].len > 1)
  				break;
  			goto swallow;
  		}
  
  		/*  /..[^/] ... regurgitate and allow */
  
  		if (issue_char(wsi, '.') < 0)
  			return -1;
  		if (issue_char(wsi, '.') < 0)
  			return -1;
  		ah->ups = URIPS_IDLE;
  		break;
  	}
  
  	if (c == '?' && !enc &&
  	    !ah->frag_index[WSI_TOKEN_HTTP_URI_ARGS]) { /* start of URI args */
  		if (ah->ues != URIES_IDLE)
  			goto forbid;
  
  		/* seal off uri header */
  		if (issue_char(wsi, '\0') < 0)
  			return -1;
  
  		/* don't account for it */
  		wsi->http.ah->frags[wsi->http.ah->nfrag].len--;
  
  		/* move to using WSI_TOKEN_HTTP_URI_ARGS */
  		ah->nfrag++;
  		if (ah->nfrag >= LWS_ARRAY_SIZE(ah->frags))
  			goto excessive;
  		ah->frags[ah->nfrag].offset = ++ah->pos;
  		ah->frags[ah->nfrag].len = 0;
  		ah->frags[ah->nfrag].nfrag = 0;
  
  		ah->post_literal_equal = 0;
  		ah->frag_index[WSI_TOKEN_HTTP_URI_ARGS] = ah->nfrag;
  		ah->ups = URIPS_IDLE;
  		goto swallow;
  	}
  
  	return LPUR_CONTINUE;
  
  swallow:
  	return LPUR_SWALLOW;
  
  forbid:
  	return LPUR_FORBID;
  
  excessive:
  	return LPUR_EXCESSIVE;
  }
  
  static const unsigned char methods[] = {
  	WSI_TOKEN_GET_URI,
  	WSI_TOKEN_POST_URI,
  #if defined(LWS_WITH_HTTP_UNCOMMON_HEADERS)
  	WSI_TOKEN_OPTIONS_URI,
  	WSI_TOKEN_PUT_URI,
  	WSI_TOKEN_PATCH_URI,
  	WSI_TOKEN_DELETE_URI,
  #endif
  	WSI_TOKEN_CONNECT,
  	WSI_TOKEN_HEAD_URI,
  };
  
  /*
   * possible returns:, -1 fail, 0 ok or 2, transition to raw
   */
  
  lws_parser_return_t LWS_WARN_UNUSED_RESULT
  lws_parse(struct lws *wsi, unsigned char *buf, int *len)
  {
  	struct allocated_headers *ah = wsi->http.ah;
  	struct lws_context *context = wsi->a.context;
  	unsigned int n, m;
  	unsigned char c;
  	int r, pos;
  
  	assert(wsi->http.ah);
  
  	do {
  		(*len)--;
  		c = *buf++;
  
  		switch (ah->parser_state) {
  #if defined(LWS_WITH_CUSTOM_HEADERS)
  		case WSI_TOKEN_UNKNOWN_VALUE_PART:
  
  			if (c == '\r')
  				break;
  			if (c == '\n') {
  				lws_ser_wu16be((uint8_t *)&ah->data[ah->unk_pos + 2],
  					       (uint16_t)(ah->pos - ah->unk_value_pos));
  				ah->parser_state = WSI_TOKEN_NAME_PART;
  				ah->unk_pos = 0;
  				ah->lextable_pos = 0;
  				break;
  			}
  
  			/* trim leading whitespace */
  			if (ah->pos != ah->unk_value_pos ||
  			    (c != ' ' && c != '\t')) {
  
  				if (lws_pos_in_bounds(wsi))
  					return LPR_FAIL;
  
  				ah->data[ah->pos++] = (char)c;
  			}
  			pos = ah->lextable_pos;
  			break;
  #endif
  		default:
  
  			lwsl_parser("WSI_TOK_(%d) '%c'\n", ah->parser_state, c);
  
  			/* collect into malloc'd buffers */
  			/* optional initial space swallow */
  			if (!ah->frags[ah->frag_index[ah->parser_state]].len &&
  			    c == ' ')
  				break;
  
  			for (m = 0; m < LWS_ARRAY_SIZE(methods); m++)
  				if (ah->parser_state == methods[m])
  					break;
  			if (m == LWS_ARRAY_SIZE(methods))
  				/* it was not any of the methods */
  				goto check_eol;
  
  			/* special URI processing... end at space */
  
  			if (c == ' ') {
  				/* enforce starting with / */
  				if (!ah->frags[ah->nfrag].len)
  					if (issue_char(wsi, '/') < 0)
  						return LPR_FAIL;
  
  				if (ah->ups == URIPS_SEEN_SLASH_DOT_DOT) {
  					/*
  					 * back up one dir level if possible
  					 * safe against header fragmentation
  					 * because the method URI can only be
  					 * in 1 fragment
  					 */
  					if (ah->frags[ah->nfrag].len > 2) {
  						ah->pos--;
  						ah->frags[ah->nfrag].len--;
  						do {
  							ah->pos--;
  							ah->frags[ah->nfrag].len--;
  						} while (ah->frags[ah->nfrag].len > 1 &&
  							 ah->data[ah->pos] != '/');
  					}
  				}
  
  				/* begin parsing HTTP version: */
  				if (issue_char(wsi, '\0') < 0)
  					return LPR_FAIL;
  				/* don't account for it */
  				wsi->http.ah->frags[wsi->http.ah->nfrag].len--;
  				ah->parser_state = WSI_TOKEN_HTTP;
  				goto start_fragment;
  			}
  
  			r = lws_parse_urldecode(wsi, &c);
  			switch (r) {
  			case LPUR_CONTINUE:
  				break;
  			case LPUR_SWALLOW:
  				goto swallow;
  			case LPUR_FORBID:
  				goto forbid;
  			case LPUR_EXCESSIVE:
  				goto excessive;
  			default:
  				return LPR_FAIL;
  			}
  check_eol:
  			/* bail at EOL */
  			if (ah->parser_state != WSI_TOKEN_CHALLENGE &&
  			    (c == '\x0d' || c == '\x0a')) {
  				if (ah->ues != URIES_IDLE)
  					goto forbid;
  
  				if (c == '\x0a') {
  					/* broken peer */
  					ah->parser_state = WSI_TOKEN_NAME_PART;
  					ah->unk_pos = 0;
  					ah->lextable_pos = 0;
  				} else
  					ah->parser_state = WSI_TOKEN_SKIPPING_SAW_CR;
  
  				c = '\0';
  				lwsl_parser("*\n");
  			}
  
  			n = (unsigned int)issue_char(wsi, c);
  			if ((int)n < 0)
  				return LPR_FAIL;
  			if (n > 0)
  				ah->parser_state = WSI_TOKEN_SKIPPING;
  			else {
  				/*
  				 * Explicit zeroes are legal in URI ARGS.
  				 * They can only exist as a safety terminator
  				 * after the valid part of the token contents
  				 * for other types.
  				 */
  				if (!c && ah->parser_state != WSI_TOKEN_HTTP_URI_ARGS)
  					/* don't account for safety terminator */
  					wsi->http.ah->frags[wsi->http.ah->nfrag].len--;
  			}
  
  swallow:
  			/* per-protocol end of headers management */
  
  			if (ah->parser_state == WSI_TOKEN_CHALLENGE)
  				goto set_parsing_complete;
  			break;
  
  			/* collecting and checking a name part */
  		case WSI_TOKEN_NAME_PART:
  			lwsl_parser("WSI_TOKEN_NAME_PART '%c' 0x%02X "
  				    "(role=0x%lx) "
  				    "wsi->lextable_pos=%d\n", c, c,
  				    (unsigned long)lwsi_role(wsi),
  				    ah->lextable_pos);
  
  			if (!ah->unk_pos && c == '\x0a')
  				/* broken peer */
  				goto set_parsing_complete;
  
  			if (c >= 'A' && c <= 'Z')
  				c = (unsigned char)(c + 'a' - 'A');
  			/*
  			 * ...in case it's an unknown header, speculatively
  			 * store it as the name comes in.  If we recognize it as
  			 * a known header, we'll snip this.
  			 */
  
  			if (!wsi->mux_substream && !ah->unk_pos) {
  				ah->unk_pos = ah->pos;
  
  #if defined(LWS_WITH_CUSTOM_HEADERS)
  				/*
  				 * Prepare new unknown header linked-list entry
  				 *
  				 *  - 16-bit BE: name part length
  				 *  - 16-bit BE: value part length
  				 *  - 32-bit BE: data offset of next, or 0
  				 */
  				for (n = 0; n < 8; n++)
  					if (!lws_pos_in_bounds(wsi))
  						ah->data[ah->pos++] = 0;
  #endif
  			}
  
  			if (lws_pos_in_bounds(wsi))
  				return LPR_FAIL;
  
  			ah->data[ah->pos++] = (char)c;
  			pos = ah->lextable_pos;
  
  #if defined(LWS_WITH_CUSTOM_HEADERS)
  			if (!wsi->mux_substream && pos < 0 && c == ':') {
  #if defined(_DEBUG)
  				char dotstar[64];
  				int uhlen;
  #endif
  
  				/*
  				 * process unknown headers
  				 *
  				 * register us in the unknown hdr ll
  				 */
  
  				if (!ah->unk_ll_head)
  					ah->unk_ll_head = ah->unk_pos;
  
  				if (ah->unk_ll_tail)
  					lws_ser_wu32be(
  				(uint8_t *)&ah->data[ah->unk_ll_tail + UHO_LL],
  						       ah->unk_pos);
  
  				ah->unk_ll_tail = ah->unk_pos;
  
  #if defined(_DEBUG)
  				uhlen = (int)(ah->pos - (ah->unk_pos + UHO_NAME));
  				lws_strnncpy(dotstar,
  					&ah->data[ah->unk_pos + UHO_NAME],
  					uhlen, sizeof(dotstar));
  				lwsl_debug("%s: unk header %d '%s'\n",
  					    __func__,
  					    ah->pos - (ah->unk_pos + UHO_NAME),
  					    dotstar);
  #endif
  
  				/* set the unknown header name part length */
  
  				lws_ser_wu16be((uint8_t *)&ah->data[ah->unk_pos],
  					       (uint16_t)((ah->pos - ah->unk_pos) - UHO_NAME));
  
  				ah->unk_value_pos = ah->pos;
  
  				/*
  				 * collect whatever's coming for the unknown header
  				 * argument until the next CRLF
  				 */
  				ah->parser_state = WSI_TOKEN_UNKNOWN_VALUE_PART;
  				break;
  			}
  #endif
  			if (pos < 0)
  				break;
  
  			while (1) {
  				if (lextable_h1[pos] & (1 << 7)) {
  					/* 1-byte, fail on mismatch */
  					if ((lextable_h1[pos] & 0x7f) != c) {
  nope:
  						ah->lextable_pos = -1;
  						break;
  					}
  					/* fall thru */
  					pos++;
  					if (lextable_h1[pos] == FAIL_CHAR)
  						goto nope;
  
  					ah->lextable_pos = (int16_t)pos;
  					break;
  				}
  
  				if (lextable_h1[pos] == FAIL_CHAR)
  					goto nope;
  
  				/* b7 = 0, end or 3-byte */
  				if (lextable_h1[pos] < FAIL_CHAR) {
  					if (!wsi->mux_substream) {
  						/*
  						 * We hit a terminal marker, so
  						 * we recognized this header...
  						 * drop the speculative name
  						 * part storage
  						 */
  						ah->pos = ah->unk_pos;
  						ah->unk_pos = 0;
  					}
  
  					ah->lextable_pos = (int16_t)pos;
  					break;
  				}
  
  				if (lextable_h1[pos] == c) { /* goto */
  					ah->lextable_pos = (int16_t)(pos +
  						(lextable_h1[pos + 1]) +
  						(lextable_h1[pos + 2] << 8));
  					break;
  				}
  
  				/* fall thru goto */
  				pos += 3;
  				/* continue */
  			}
  
  			/*
  			 * If it's h1, server needs to be on the look out for
  			 * unknown methods...
  			 */
  			if (ah->lextable_pos < 0 && lwsi_role_h1(wsi) &&
  			    lwsi_role_server(wsi)) {
  				/*
  				 * this is not a header we know about... did
  				 * we get a valid method (GET, POST etc)
  				 * already, or is this the bogus method?
  				 */
  				for (m = 0; m < LWS_ARRAY_SIZE(methods); m++)
  					if (ah->frag_index[methods[m]]) {
  						/*
  						 * already had the method
  						 */
  #if !defined(LWS_WITH_CUSTOM_HEADERS)
  						ah->parser_state = WSI_TOKEN_SKIPPING;
  #endif
  						if (wsi->mux_substream)
  							ah->parser_state = WSI_TOKEN_SKIPPING;
  						break;
  					}
  
  				if (m != LWS_ARRAY_SIZE(methods)) {
  #if defined(LWS_WITH_CUSTOM_HEADERS)
  					/*
  					 * We have the method, this is just an
  					 * unknown header then
  					 */
  					if (!wsi->mux_substream)
  						goto unknown_hdr;
  					else
  						break;
  #else
  					break;
  #endif
  				}
  				/*
  				 * ...it's an unknown http method from a client
  				 * in fact, it cannot be valid http.
  				 *
  				 * Are we set up to transition to another role
  				 * in these cases?
  				 */
  				if (lws_check_opt(wsi->a.vhost->options,
  		    LWS_SERVER_OPTION_FALLBACK_TO_APPLY_LISTEN_ACCEPT_CONFIG)) {
  					lwsl_notice("%s: http fail fallback\n",
  						    __func__);
  					 /* transition to other role */
  					return LPR_DO_FALLBACK;
  				}
  
  				lwsl_info("Unknown method - dropping\n");
  				goto forbid;
  			}
  			if (ah->lextable_pos < 0) {
  				/*
  				 * It's not a header that lws knows about...
  				 */
  #if defined(LWS_WITH_CUSTOM_HEADERS)
  				if (!wsi->mux_substream)
  					goto unknown_hdr;
  #endif
  				/*
  				 * ...otherwise for a client, let him ignore
  				 * unknown headers coming from the server
  				 */
  				ah->parser_state = WSI_TOKEN_SKIPPING;
  				break;
  			}
  
  			if (lextable_h1[ah->lextable_pos] < FAIL_CHAR) {
  				/* terminal state */
  
  				n = ((unsigned int)lextable_h1[ah->lextable_pos] << 8) |
  						lextable_h1[ah->lextable_pos + 1];
  
  				lwsl_parser("known hdr %d\n", n);
  				for (m = 0; m < LWS_ARRAY_SIZE(methods); m++)
  					if (n == methods[m] &&
  					    ah->frag_index[methods[m]]) {
  						lwsl_warn("Duplicated method\n");
  						return LPR_FAIL;
  					}
  
  				if (!wsi->mux_substream) {
  					/*
  					 * Whether we are collecting unknown names or not,
  					 * if we matched an internal header we can dispense
  					 * with the header name part we were keeping
  					 */
  					ah->pos = ah->unk_pos;
  					ah->unk_pos = 0;
  				}
  
  #if defined(LWS_ROLE_WS)
  				/*
  				 * WSORIGIN is protocol equiv to ORIGIN,
  				 * JWebSocket likes to send it, map to ORIGIN
  				 */
  				if (n == WSI_TOKEN_SWORIGIN)
  					n = WSI_TOKEN_ORIGIN;
  #endif
  
  				ah->parser_state = (uint8_t)
  							(WSI_TOKEN_GET_URI + n);
  				ah->ups = URIPS_IDLE;
  
  				if (context->token_limits)
  					ah->current_token_limit = context->
  						token_limits->token_limit[
  							      ah->parser_state];
  				else
  					ah->current_token_limit =
  						wsi->a.context->max_http_header_data;
  
  				if (ah->parser_state == WSI_TOKEN_CHALLENGE)
  					goto set_parsing_complete;
  
  				goto start_fragment;
  			}
  			break;
  
  #if defined(LWS_WITH_CUSTOM_HEADERS)
  unknown_hdr:
  			//ah->parser_state = WSI_TOKEN_SKIPPING;
  			//break;
  			if (!wsi->mux_substream)
  				break;
  #endif
  
  start_fragment:
  			ah->nfrag++;
  excessive:
  			if (ah->nfrag == LWS_ARRAY_SIZE(ah->frags)) {
  				lwsl_warn("More hdr frags than we can deal with\n");
  				return LPR_FAIL;
  			}
  
  			ah->frags[ah->nfrag].offset = ah->pos;
  			ah->frags[ah->nfrag].len = 0;
  			ah->frags[ah->nfrag].nfrag = 0;
  			ah->frags[ah->nfrag].flags = 2;
  
  			n = ah->frag_index[ah->parser_state];
  			if (!n) { /* first fragment */
  				ah->frag_index[ah->parser_state] = ah->nfrag;
  				ah->hdr_token_idx = ah->parser_state;
  				break;
  			}
  			/* continuation */
  			while (ah->frags[n].nfrag)
  				n = ah->frags[n].nfrag;
  			ah->frags[n].nfrag = ah->nfrag;
  
  			if (issue_char(wsi, ' ') < 0)
  				return LPR_FAIL;
  			break;
  
  			/* skipping arg part of a name we didn't recognize */
  		case WSI_TOKEN_SKIPPING:
  			lwsl_parser("WSI_TOKEN_SKIPPING '%c'\n", c);
  
  			if (c == '\x0a') {
  				/* broken peer */
  				ah->parser_state = WSI_TOKEN_NAME_PART;
  				ah->unk_pos = 0;
  				ah->lextable_pos = 0;
  			}
  
  			if (c == '\x0d')
  				ah->parser_state = WSI_TOKEN_SKIPPING_SAW_CR;
  			break;
  
  		case WSI_TOKEN_SKIPPING_SAW_CR:
  			lwsl_parser("WSI_TOKEN_SKIPPING_SAW_CR '%c'\n", c);
  			if (ah->ues != URIES_IDLE)
  				goto forbid;
  			if (c == '\x0a') {
  				ah->parser_state = WSI_TOKEN_NAME_PART;
  				ah->unk_pos = 0;
  				ah->lextable_pos = 0;
  			} else
  				ah->parser_state = WSI_TOKEN_SKIPPING;
  			break;
  			/* we're done, ignore anything else */
  
  		case WSI_PARSING_COMPLETE:
  			lwsl_parser("WSI_PARSING_COMPLETE '%c'\n", c);
  			break;
  		}
  
  	} while (*len);
  
  	return LPR_OK;
  
  set_parsing_complete:
  	if (ah->ues != URIES_IDLE)
  		goto forbid;
  
  	if (lws_hdr_total_length(wsi, WSI_TOKEN_UPGRADE)) {
  #if defined(LWS_ROLE_WS)
  		const char *pv = lws_hdr_simple_ptr(wsi, WSI_TOKEN_VERSION);
  		if (pv)
  			wsi->rx_frame_type = (char)atoi(pv);
  
  		lwsl_parser("v%02d hdrs done\n", wsi->rx_frame_type);
  #endif
  	}
  	ah->parser_state = WSI_PARSING_COMPLETE;
  	wsi->hdr_parsing_completed = 1;
  
  	return LPR_OK;
  
  forbid:
  	lwsl_info(" forbidding on uri sanitation\n");
  #if defined(LWS_WITH_SERVER)
  	lws_return_http_status(wsi, HTTP_STATUS_FORBIDDEN, NULL);
  #endif
  
  	return LPR_FORBIDDEN;
  }
  
  int
  lws_http_cookie_get(struct lws *wsi, const char *name, char *buf,
  		    size_t *max_len)
  {
  	size_t max = *max_len, bl = strlen(name);
  	char *p, *bo = buf;
  	int n;
  
  	n = lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_COOKIE);
  	if ((unsigned int)n < bl + 1)
  		return 1;
  
  	/*
  	 * This can come to us two ways, in ah fragments (h2) or as a single
  	 * semicolon-delimited string (h1)
  	 */
  
  #if defined(LWS_ROLE_H2)
  	if (lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_COLON_METHOD)) {
  
  		/*
  		 * The h2 way...
  		 */
  
  		int f = wsi->http.ah->frag_index[WSI_TOKEN_HTTP_COOKIE];
  		size_t fl;
  
  		while (f) {
  			p = wsi->http.ah->data + wsi->http.ah->frags[f].offset;
  			fl = (size_t)wsi->http.ah->frags[f].len;
  			if (fl >= bl + 1 &&
  			    p[bl] == '=' &&
  			    !memcmp(p, name, bl)) {
  				fl -= bl + 1;
  				if (max - 1 < fl)
  					fl = max - 1;
  				if (fl)
  					memcpy(buf, p + bl + 1, fl);
  				*max_len = fl;
  				buf[fl] = '\0';
  
  				return 0;
  			}
  			f = wsi->http.ah->frags[f].nfrag;
  		}
  
  		return -1;
  	}
  #endif
  
  	/*
  	 * The h1 way...
  	 */
  
  	p = lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP_COOKIE);
  	if (!p)
  		return 1;
  
  	p += bl;
  	n -= (int)bl;
  	while (n-- > (int)bl) {
  		if (*p == '=' && !memcmp(p - bl, name, (unsigned int)bl)) {
  			p++;
  			while (*p != ';' && n-- && max) {
  				*buf++ = *p++;
  				max--;
  			}
  			if (!max)
  				return 2;
  
  			*buf = '\0';
  			*max_len = lws_ptr_diff_size_t(buf, bo);
  
  			return 0;
  		}
  		p++;
  	}
  
  	return 1;
  }
  
  #if defined(LWS_WITH_JOSE)
  
  #define MAX_JWT_SIZE 1024
  
  int
  lws_jwt_get_http_cookie_validate_jwt(struct lws *wsi,
  				     struct lws_jwt_sign_set_cookie *i,
  				     char *out, size_t *out_len)
  {
  	char temp[MAX_JWT_SIZE * 2];
  	size_t cml = *out_len;
  	const char *cp;
  
  	/* first use out to hold the encoded JWT */
  
  	if (lws_http_cookie_get(wsi, i->cookie_name, out, out_len)) {
  		lwsl_debug("%s: cookie %s not provided\n", __func__,
  				i->cookie_name);
  		return 1;
  	}
  
  	/* decode the JWT into temp */
  
  	if (lws_jwt_signed_validate(wsi->a.context, i->jwk, i->alg, out,
  				    *out_len, temp, sizeof(temp), out, &cml)) {
  		lwsl_info("%s: jwt validation failed\n", __func__);
  		return 1;
  	}
  
  	/*
  	 * Copy out the decoded JWT payload into out, overwriting the
  	 * original encoded JWT taken from the cookie (that has long ago been
  	 * translated into allocated buffers in the JOSE object)
  	 */
  
  	if (lws_jwt_token_sanity(out, cml, i->iss, i->aud, i->csrf_in,
  				 i->sub, sizeof(i->sub),
  				 &i->expiry_unix_time)) {
  		lwsl_notice("%s: jwt sanity failed\n", __func__);
  		return 1;
  	}
  
  	/*
  	 * If he's interested in his private JSON part, point him to that in
  	 * the args struct (it's pointing to the data in out
  	 */
  
  	cp = lws_json_simple_find(out, cml, "\"ext\":", &i->extra_json_len);
  	if (cp)
  		i->extra_json = cp;
  
  	if (!cp)
  		lwsl_notice("%s: no ext JWT payload\n", __func__);
  
  	return 0;
  }
  
  int
  lws_jwt_sign_token_set_http_cookie(struct lws *wsi,
  				   const struct lws_jwt_sign_set_cookie *i,
  				   uint8_t **p, uint8_t *end)
  {
  	char plain[MAX_JWT_SIZE + 1], temp[MAX_JWT_SIZE * 2], csrf[17];
  	size_t pl = sizeof(plain);
  	unsigned long long ull;
  	int n;
  
  	/*
  	 * Create a 16-char random csrf token with the same lifetime as the JWT
  	 */
  
  	lws_hex_random(wsi->a.context, csrf, sizeof(csrf));
  	ull = lws_now_secs();
  	if (lws_jwt_sign_compact(wsi->a.context, i->jwk, i->alg, plain, &pl,
  			         temp, sizeof(temp),
  			         "{\"iss\":\"%s\",\"aud\":\"%s\","
  			          "\"iat\":%llu,\"nbf\":%llu,\"exp\":%llu,"
  			          "\"csrf\":\"%s\",\"sub\":\"%s\"%s%s%s}",
  			         i->iss, i->aud, ull, ull - 60,
  			         ull + i->expiry_unix_time,
  			         csrf, i->sub,
  			         i->extra_json ? ",\"ext\":{" : "",
  			         i->extra_json ? i->extra_json : "",
  			         i->extra_json ? "}" : "")) {
  		lwsl_err("%s: failed to create JWT\n", __func__);
  
  		return 1;
  	}
  
  	/*
  	 * There's no point the browser holding on to a JWT beyond the JWT's
  	 * expiry time, so set it to be the same.
  	 */
  
  	n = lws_snprintf(temp, sizeof(temp), "__Host-%s=%s;"
  			 "HttpOnly;"
  			 "Secure;"
  			 "SameSite=strict;"
  			 "Path=/;"
  			 "Max-Age=%lu",
  			 i->cookie_name, plain, i->expiry_unix_time);
  
  	if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_SET_COOKIE,
  					 (uint8_t *)temp, n, p, end)) {
  		lwsl_err("%s: failed to add JWT cookie header\n", __func__);
  		return 1;
  	}
  
  	return 0;
  }
  #endif
  