Skip to content
Snippets Groups Projects
context.c 45 KiB
Newer Older
/*
 * libwebsockets - small server side websockets and web server implementation
 *
Andy Green's avatar
Andy Green committed
 * Copyright (C) 2010-2017 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 "private-libwebsockets.h"

#ifndef LWS_BUILD_HASH
#define LWS_BUILD_HASH "unknown-build-hash"
#endif

static const char *library_version = LWS_LIBRARY_VERSION " " LWS_BUILD_HASH;

/**
 * lws_get_library_version: get version and git hash library built from
 *
 *	returns a const char * to a string like "1.1 178d78c"
 *	representing the library version followed by the git head hash it
 *	was built from
 */
LWS_VISIBLE const char *
lws_get_library_version(void)
{
	return library_version;
}

Andy Green's avatar
Andy Green committed
static const char * const mount_protocols[] = {
	"http://",
	"https://",
	"file://",
	"cgi://",
	">http://",
	">https://",
	"callback://"
Andy Green's avatar
Andy Green committed
};

Andy Green's avatar
Andy Green committed
LWS_VISIBLE void *
Andy Green's avatar
Andy Green committed
lws_protocol_vh_priv_zalloc(struct lws_vhost *vhost,
			    const struct lws_protocols *prot, int size)
Andy Green's avatar
Andy Green committed
{
	int n = 0;

	/* allocate the vh priv array only on demand */
	if (!vhost->protocol_vh_privs) {
		vhost->protocol_vh_privs = (void **)lws_zalloc(
Andy Green's avatar
Andy Green committed
				vhost->count_protocols * sizeof(void *),
				"protocol_vh_privs");
Andy Green's avatar
Andy Green committed
		if (!vhost->protocol_vh_privs)
			return NULL;
	}

	while (n < vhost->count_protocols && &vhost->protocols[n] != prot)
		n++;

Andy Green's avatar
Andy Green committed
	if (n == vhost->count_protocols) {
		n = 0;
		while (n < vhost->count_protocols &&
		       strcmp(vhost->protocols[n].name, prot->name))
			n++;

		if (n == vhost->count_protocols)
			return NULL;
	}
Andy Green's avatar
Andy Green committed

Andy Green's avatar
Andy Green committed
	vhost->protocol_vh_privs[n] = lws_zalloc(size, "vh priv");
Andy Green's avatar
Andy Green committed
	return vhost->protocol_vh_privs[n];
}

LWS_VISIBLE void *
Andy Green's avatar
Andy Green committed
lws_protocol_vh_priv_get(struct lws_vhost *vhost,
			 const struct lws_protocols *prot)
Andy Green's avatar
Andy Green committed
{
	int n = 0;

	if (!vhost || !vhost->protocol_vh_privs)
Andy Green's avatar
Andy Green committed
		return NULL;

	while (n < vhost->count_protocols && &vhost->protocols[n] != prot)
		n++;

	if (n == vhost->count_protocols) {
Andy Green's avatar
Andy Green committed
		n = 0;
		while (n < vhost->count_protocols &&
		       strcmp(vhost->protocols[n].name, prot->name))
			n++;

		if (n == vhost->count_protocols) {
			lwsl_err("%s: unknown protocol %p\n", __func__, prot);
			return NULL;
		}
Andy Green's avatar
Andy Green committed
	}

	return vhost->protocol_vh_privs[n];
}

static const struct lws_protocol_vhost_options *
lws_vhost_protocol_options(struct lws_vhost *vh, const char *name)
{
	const struct lws_protocol_vhost_options *pvo = vh->pvo;
	while (pvo) {
		if (!strcmp(pvo->name, name))
			return pvo;
		pvo = pvo->next;
	}

	return NULL;
}

/*
 * inform every vhost that hasn't already done it, that
 * his protocols are initializing
 */
Andy Green's avatar
Andy Green committed
LWS_VISIBLE int
Andy Green's avatar
Andy Green committed
lws_protocol_init(struct lws_context *context)
{
	struct lws_vhost *vh = context->vhost_list;
	const struct lws_protocol_vhost_options *pvo, *pvo1;
Andy Green's avatar
Andy Green committed
	struct lws wsi;
	int n, any = 0;
Andy Green's avatar
Andy Green committed

	if (context->doing_protocol_init)
		return 0;

	context->doing_protocol_init = 1;

Andy Green's avatar
Andy Green committed
	memset(&wsi, 0, sizeof(wsi));
	wsi.context = context;

	lwsl_info("%s\n", __func__);
Andy Green's avatar
Andy Green committed
	while (vh) {
		wsi.vhost = vh;

		/* only do the protocol init once for a given vhost */
		if (vh->created_vhost_protocols ||
		    (vh->options & LWS_SERVER_OPTION_SKIP_PROTOCOL_INIT))
Andy Green's avatar
Andy Green committed
		/* initialize supported protocols on this vhost */

		for (n = 0; n < vh->count_protocols; n++) {
			wsi.protocol = &vh->protocols[n];
Andy Green's avatar
Andy Green committed
			if (!vh->protocols[n].name)
				continue;
			pvo = lws_vhost_protocol_options(vh,
							 vh->protocols[n].name);
			if (pvo) {
				/*
				 * linked list of options specific to
				 * vh + protocol
				 */
				pvo1 = pvo;
				pvo = pvo1->options;

				while (pvo) {
					lwsl_debug(
Andy Green's avatar
Andy Green committed
						"    vhost \"%s\", "
						"protocol \"%s\", "
						"option \"%s\"\n",
							vh->name,
							vh->protocols[n].name,
							pvo->name);

					if (!strcmp(pvo->name, "default")) {
						lwsl_info("Setting default "
						   "protocol for vh %s to %s\n",
						   vh->name,
						   vh->protocols[n].name);
						vh->default_protocol_index = n;
					}
					if (!strcmp(pvo->name, "raw")) {
						lwsl_info("Setting raw "
						   "protocol for vh %s to %s\n",
						   vh->name,
						   vh->protocols[n].name);
						vh->raw_protocol_index = n;
					}
					pvo = pvo->next;
				}

Andy Green's avatar
Andy Green committed
				pvo = pvo1->options;
			}
Andy Green's avatar
Andy Green committed
#if defined(LWS_WITH_TLS)
			any |= !!vh->ssl_ctx;
#endif

Andy Green's avatar
Andy Green committed
			/*
Andy Green's avatar
Andy Green committed
			 * inform all the protocols that they are doing their
			 * one-time initialization if they want to.
Andy Green's avatar
Andy Green committed
			 *
Andy Green's avatar
Andy Green committed
			 * NOTE the wsi is all zeros except for the context, vh
			 * + protocol ptrs so lws_get_context(wsi) etc can work
Andy Green's avatar
Andy Green committed
			 */
Andy Green's avatar
Andy Green committed
			if (vh->protocols[n].callback(&wsi,
Andy Green's avatar
Andy Green committed
					LWS_CALLBACK_PROTOCOL_INIT, NULL,
Andy Green's avatar
Andy Green committed
				lws_free(vh->protocol_vh_privs[n]);
				vh->protocol_vh_privs[n] = NULL;
				lwsl_err("%s: protocol %s failed init\n", __func__,
Andy Green's avatar
Andy Green committed
		}

		vh->created_vhost_protocols = 1;
next:
Andy Green's avatar
Andy Green committed
		vh = vh->vhost_next;
	}

	if (!context->protocol_init_done)
		lws_finalize_startup(context);

Andy Green's avatar
Andy Green committed
	context->protocol_init_done = 1;

	if (any)
		lws_tls_check_all_cert_lifetimes(context);

Andy Green's avatar
Andy Green committed
	return 0;
}

Andy Green's avatar
Andy Green committed
LWS_VISIBLE int
lws_callback_http_dummy(struct lws *wsi, enum lws_callback_reasons reason,
		    void *user, void *in, size_t len)
Andy Green's avatar
Andy Green committed
	struct lws_ssl_info *si;
#ifdef LWS_WITH_CGI
	struct lws_cgi_args *args;
Andy Green's avatar
Andy Green committed
#endif
#if defined(LWS_WITH_CGI) || defined(LWS_WITH_HTTP_PROXY)
	char buf[512];
	int n;
#endif

	switch (reason) {
	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_FILE_COMPLETION:
		if (lws_http_transaction_completed(wsi))
			return -1;
		break;
#endif

	case LWS_CALLBACK_HTTP_WRITEABLE:
#ifdef LWS_WITH_CGI
Andy Green's avatar
Andy Green committed
		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) {
Andy Green's avatar
Andy Green committed
				lwsl_debug("AUX_BF__CGI forcing close\n");
			}
			if (!n)
Andy Green's avatar
Andy Green committed
				lws_rx_flow_control(
					wsi->cgi->stdwsi[LWS_STDOUT], 1);
			if (wsi->reason_bf & LWS_CB_REASON_AUX_BF__CGI_HEADERS)
Andy Green's avatar
Andy Green committed
				wsi->reason_bf &=
					~LWS_CB_REASON_AUX_BF__CGI_HEADERS;
				wsi->reason_bf &= ~LWS_CB_REASON_AUX_BF__CGI;
		if (wsi->reason_bf & LWS_CB_REASON_AUX_BF__CGI_CHUNK_END) {
Andy Green's avatar
Andy Green committed
			if (!wsi->http2_substream) {
				memcpy(buf + LWS_PRE, "0\x0d\x0a\x0d\x0a", 5);
Andy Green's avatar
Andy Green committed
				lwsl_debug("writing chunk term and exiting\n");
				n = lws_write(wsi, (unsigned char *)buf +
						   LWS_PRE, 5, LWS_WRITE_HTTP);
Andy Green's avatar
Andy Green committed
			} else
Andy Green's avatar
Andy Green committed
				n = lws_write(wsi, (unsigned char *)buf +
						   LWS_PRE, 0,
						   LWS_WRITE_HTTP_FINAL);
Andy Green's avatar
Andy Green committed

			/* always close after sending it */
			return -1;
Andy Green's avatar
Andy Green committed
#if defined(LWS_WITH_HTTP_PROXY)
		if (wsi->reason_bf & LWS_CB_REASON_AUX_BF__PROXY) {
Andy Green's avatar
Andy Green committed
			char *px = buf + LWS_PRE;
			int lenx = sizeof(buf) - LWS_PRE;
Andy Green's avatar
Andy Green committed

Andy Green's avatar
Andy Green committed
			/*
			 * 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;
Andy Green's avatar
Andy Green committed
			if (!lws_get_child(wsi))
				break;
Andy Green's avatar
Andy Green committed
			if (lws_http_client_read(lws_get_child(wsi), &px,
						 &lenx) < 0)
Andy Green's avatar
Andy Green committed
				return -1;
			break;
		}
#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;
Andy Green's avatar
Andy Green committed
		lws_callback_on_writable(lws_get_parent(wsi));
Andy Green's avatar
Andy Green committed
	case LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ:
		assert(lws_get_parent(wsi));
		n = lws_write(lws_get_parent(wsi), (unsigned char *)in,
				len, LWS_WRITE_HTTP);
		if (n < 0)
			return -1;
		break;

	case LWS_CALLBACK_ESTABLISHED_CLIENT_HTTP: {
		unsigned char *p, *end;
		char ctype[64], ctlen = 0;
	
		p = (unsigned char *)buf + LWS_PRE;
		end = p + sizeof(buf) - LWS_PRE;

Andy Green's avatar
Andy Green committed
		if (lws_add_http_header_status(lws_get_parent(wsi),
					       HTTP_STATUS_OK, &p, end))
Andy Green's avatar
Andy Green committed
			return 1;
		if (lws_add_http_header_by_token(lws_get_parent(wsi),
				WSI_TOKEN_HTTP_SERVER,
			    	(unsigned char *)"libwebsockets",
				13, &p, end))
			return 1;

Andy Green's avatar
Andy Green committed
		ctlen = lws_hdr_copy(wsi, ctype, sizeof(ctype),
				     WSI_TOKEN_HTTP_CONTENT_TYPE);
Andy Green's avatar
Andy Green committed
		if (ctlen > 0) {
			if (lws_add_http_header_by_token(lws_get_parent(wsi),
				WSI_TOKEN_HTTP_CONTENT_TYPE,
				(unsigned char *)ctype, ctlen, &p, end))
				return 1;
		}
Andy Green's avatar
Andy Green committed

Andy Green's avatar
Andy Green committed
		if (lws_finalize_http_header(lws_get_parent(wsi), &p, end))
			return 1;

		*p = '\0';
Andy Green's avatar
Andy Green committed
		n = lws_write(lws_get_parent(wsi),
			      (unsigned char *)buf + LWS_PRE,
Andy Green's avatar
Andy Green committed
			      p - ((unsigned char *)buf + LWS_PRE),
			      LWS_WRITE_HTTP_HEADERS);
		if (n < 0)
			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:
Andy Green's avatar
Andy Green committed
			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",
Andy Green's avatar
Andy Green committed
				wsi->cgi->explicitly_chunked,
				(uint64_t)wsi->cgi->content_length);
Andy Green's avatar
Andy Green committed
		if (!wsi->cgi->explicitly_chunked &&
		    !wsi->cgi->content_length) {
			/* send terminating chunk */
Andy Green's avatar
Andy Green committed
			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;
		return -1;

	case LWS_CALLBACK_CGI_STDIN_DATA:  /* POST body for stdin */
		args = (struct lws_cgi_args *)in;
		args->data[args->len] = '\0';
Andy Green's avatar
Andy Green committed
		n = lws_get_socket_fd(args->stdwsi[LWS_STDIN]);
		if (n < 0)
			return -1;
		n = write(n, args->data, args->len);
		if (n < args->len)
			lwsl_notice("LWS_CALLBACK_CGI_STDIN_DATA: "
				    "sent %d only %d went", n, args->len);
		return n;
#endif

	case LWS_CALLBACK_SSL_INFO:
Andy Green's avatar
Andy Green committed
		si = in;
Andy Green's avatar
Andy Green committed
		(void)si;
		lwsl_notice("LWS_CALLBACK_SSL_INFO: where: 0x%x, ret: 0x%x\n",
			    si->where, si->ret);
	return 0;
}

/* list of supported protocols and callbacks */

static const struct lws_protocols protocols_dummy[] = {
	/* first protocol must always be HTTP handler */

	{
		"http-only",		/* name */
Andy Green's avatar
Andy Green committed
		lws_callback_http_dummy,		/* callback */
		0,			/* max frame size / rx buffer */
		0, NULL, 0
	},
	/*
	 * the other protocols are provided by lws plugins
Loading
Loading full blame...