Boot Linux faster!

Check our new training course

Boot Linux faster!

Check our new training course
and Creative Commons CC-BY-SA
lecture and lab materials

Bootlin logo

Elixir Cross Referencer

/*
 * Copyright (c) 2017 Intel Corporation
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#if 1
#if defined(CONFIG_HTTPS)
#define SYS_LOG_DOMAIN "https-server"
#else
#define SYS_LOG_DOMAIN "http-server"
#endif
#define NET_SYS_LOG_LEVEL SYS_LOG_LEVEL_DEBUG
#define NET_LOG_ENABLED 1
#endif

#include <zephyr.h>
#include <stdio.h>

/* For Basic auth, we need base64 decoder which can be found
 * in mbedtls library.
 */
#include <mbedtls/base64.h>

#include <net/net_context.h>
#include <net/http.h>

#include <net/net_app.h>

#include "config.h"

/* Sets the network parameters */

#if defined(CONFIG_NET_CONTEXT_NET_PKT_POOL)
NET_PKT_TX_SLAB_DEFINE(http_srv_tx, 15);
NET_PKT_DATA_POOL_DEFINE(http_srv_data, 30);

static struct k_mem_slab *tx_slab(void)
{
	return &http_srv_tx;
}

static struct net_buf_pool *data_pool(void)
{
	return &http_srv_data;
}
#else
#if defined(CONFIG_NET_L2_BT)
#error "TCP connections over Bluetooth need CONFIG_NET_CONTEXT_NET_PKT_POOL "\
	"defined."
#endif /* CONFIG_NET_L2_BT */

#define tx_slab NULL
#define data_pool NULL
#endif /* CONFIG_NET_CONTEXT_NET_PKT_POOL */

#define RESULT_BUF_SIZE 1024
static u8_t http_result[RESULT_BUF_SIZE];

#if defined(CONFIG_HTTPS)
#if !defined(CONFIG_HTTPS_STACK_SIZE)
#define CONFIG_HTTPS_STACK_SIZE		8192
#endif /* CONFIG_HTTPS_STACK_SIZE */

#define APP_BANNER "Run HTTPS server"
#define INSTANCE_INFO "Zephyr HTTPS example server #1"

/* Note that each HTTPS context needs its own stack as there will be
 * a separate thread for each HTTPS context.
 */
NET_STACK_DEFINE(HTTPS, https_stack, CONFIG_HTTPS_STACK_SIZE,
		 CONFIG_HTTPS_STACK_SIZE);

#define RX_FIFO_DEPTH 4
K_MEM_POOL_DEFINE(ssl_rx_pool, 4, 64, RX_FIFO_DEPTH, 4);

static struct http_server_ctx https_ctx;

static u8_t https_result[RESULT_BUF_SIZE];

#else /* CONFIG_HTTPS */
#define APP_BANNER "Run HTTP server"
#endif /* CONFIG_HTTPS */

/*
 * Note that the http_server_ctx and http_server_urls are quite large so be
 * careful if those are allocated from stack.
 */
static struct http_server_ctx http_ctx;
static struct http_server_urls http_urls;

void panic(const char *msg)
{
	if (msg) {
		NET_ERR("%s", msg);
	}

	for (;;) {
		k_sleep(K_FOREVER);
	}
}

#define HTTP_STATUS_200_OK	"HTTP/1.1 200 OK\r\n" \
				"Content-Type: text/html\r\n" \
				"Transfer-Encoding: chunked\r\n" \
				"\r\n"

#define HTTP_401_STATUS_US	"HTTP/1.1 401 Unauthorized status\r\n" \
				"WWW-Authenticate: Basic realm=" \
				"\""HTTP_AUTH_REALM"\"\r\n\r\n"

#define HTML_HEADER		"<html><head>" \
				"<title>Zephyr HTTP Server</title>" \
				"</head><body><h1>" \
				"<center>Zephyr HTTP server</center></h1>\r\n"

#define HTML_FOOTER		"</body></html>\r\n"

/* Prints the received HTTP header fields as an HTML list */
static void print_http_headers(struct http_server_ctx *ctx,
			       char *str, int size)
{
	int offset;
	int ret;

	ret = snprintf(str, size,
		       HTML_HEADER
		       "<h2>HTTP Header Fields</h2>\r\n<ul>\r\n");
	if (ret < 0 || ret >= size) {
		return;
	}

	offset = ret;

	for (u8_t i = 0; i < ctx->req.field_values_ctr; i++) {
		struct http_field_value *kv = &ctx->req.field_values[i];

		ret = snprintf(str + offset, size - offset,
			       "<li>%.*s: %.*s</li>\r\n",
			       kv->key_len, kv->key,
			       kv->value_len, kv->value);
		if (ret < 0 || ret >= size) {
			return;
		}

		offset += ret;
	}

	ret = snprintf(str + offset, size - offset, "</ul>\r\n");
	if (ret < 0 || ret >= size) {
		return;
	}

	offset += ret;

	ret = snprintf(str + offset, size - offset,
		       "<h2>HTTP Method: %s</h2>\r\n",
		       http_method_str(ctx->req.parser.method));
	if (ret < 0 || ret >= size) {
		return;
	}

	offset += ret;

	ret = snprintf(str + offset, size - offset,
		       "<h2>URL: %.*s</h2>\r\n",
		       ctx->req.url_len, ctx->req.url);
	if (ret < 0 || ret >= size) {
		return;
	}

	offset += ret;

	snprintf(str + offset, size - offset,
		 "<h2>Server: %s</h2>"HTML_FOOTER, CONFIG_ARCH);
}

int http_response_header_fields(struct http_server_ctx *ctx)
{
#define HTTP_MAX_BODY_STR_SIZE		1024
	static char html_body[HTTP_MAX_BODY_STR_SIZE];

	print_http_headers(ctx, html_body, HTTP_MAX_BODY_STR_SIZE);

	return http_response(ctx, HTTP_STATUS_200_OK, html_body);
}

static int http_response_it_works(struct http_server_ctx *ctx)
{
	/* Let the peer close the connection but if it does not do it
	 * close it ourselves after 1 sec.
	 */
	return http_response_wait(ctx, HTTP_STATUS_200_OK, HTML_HEADER
				  "<body><h2><center>It Works!</center></h2>"
				  HTML_FOOTER, K_SECONDS(1));
}

static int http_response_soft_404(struct http_server_ctx *ctx)
{
	return http_response(ctx, HTTP_STATUS_200_OK, HTML_HEADER
			     "<h2><center>404 Not Found</center></h2>"
			     HTML_FOOTER);
}

static int http_response_auth(struct http_server_ctx *ctx)
{
	return http_response(ctx, HTTP_STATUS_200_OK, HTML_HEADER
			     "<h2><center>user: "HTTP_AUTH_USERNAME
			     ", password: "HTTP_AUTH_PASSWORD
			     "</center></h2>" HTML_FOOTER);
}

int http_response_401(struct http_server_ctx *ctx, s32_t timeout)
{
	return http_response_wait(ctx, HTTP_401_STATUS_US, NULL, timeout);
}

static int http_basic_auth(struct http_server_ctx *ctx)
{
	const char auth_str[] = HTTP_CRLF "Authorization: Basic ";
	char *ptr;

	ptr = strstr(ctx->req.field_values[0].key, auth_str);
	if (ptr) {
		char output[sizeof(HTTP_AUTH_USERNAME) +
			    sizeof(":") +
			    sizeof(HTTP_AUTH_PASSWORD)];
		size_t olen, ilen, alen;
		char *end, *colon;
		int ret;

		memset(output, 0, sizeof(output));

		end = strstr(ptr + 2, HTTP_CRLF);
		if (!end) {
			return http_response_401(ctx, K_NO_WAIT);
		}

		alen = sizeof(auth_str) - 1;
		ilen = end - (ptr + alen);

		ret = mbedtls_base64_decode(output, sizeof(output) - 1,
					    &olen, ptr + alen, ilen);
		if (ret) {
			return http_response_401(ctx, K_NO_WAIT);
		}

		colon = memchr(output, ':', olen);

		if (colon && colon > output && colon < (output + olen) &&
		    memcmp(output, HTTP_AUTH_USERNAME, colon - output) == 0 &&
		    memcmp(colon + 1, HTTP_AUTH_PASSWORD,
			   output + olen - colon) == 0) {
			return http_response_auth(ctx);
		}

		return http_response_401(ctx, K_NO_WAIT);
	}

	/* Wait 2 secs for the reply with proper credentials */
	return http_response_401(ctx, K_SECONDS(2));
}

#if defined(CONFIG_HTTPS)
/* Load the certificates and private RSA key. */

#include "test_certs.h"

static int setup_cert(struct http_server_ctx *ctx,
		      mbedtls_x509_crt *cert,
		      mbedtls_pk_context *pkey)
{
	int ret;

	ret = mbedtls_x509_crt_parse(cert, rsa_example_cert_der,
				     rsa_example_cert_der_len);
	if (ret != 0) {
		NET_ERR("mbedtls_x509_crt_parse returned %d", ret);
		return ret;
	}

	ret = mbedtls_pk_parse_key(pkey, rsa_example_keypair_der,
				   rsa_example_keypair_der_len, NULL, 0);
	if (ret != 0) {
		NET_ERR("mbedtls_pk_parse_key returned %d", ret);
		return ret;
	}

	return 0;
}
#endif /* CONFIG_HTTPS */

void main(void)
{
	struct sockaddr addr, *server_addr;
	int ret;

	/*
	 * There are several options here for binding to local address.
	 * 1) The server address can be left empty in which case the
	 *    library will bind to both IPv4 and IPv6 addresses and to
	 *    port 80 which is the default.
	 * 2) The server address can be partially filled, meaning that
	 *    the address can be left to 0 and port can be set if a value
	 *    other than 80 is desired. If the protocol family in sockaddr
	 *    is set to AF_UNSPEC, then both IPv4 and IPv6 socket is bound.
	 * 3) The address can be set to some real value. There is a helper
	 *    function that can be used to fill the socket address struct.
	 */
#define ADDR_OPTION 1

#if ADDR_OPTION == 1
	server_addr = NULL;

	ARG_UNUSED(addr);

#elif ADDR_OPTION == 2
	/* Accept any local listening address */
	memset(&addr, 0, sizeof(addr));

	net_sin(&addr)->sin_port = htons(ZEPHYR_PORT);

	/* In this example, listen only IPv4 */
	addr.family = AF_INET;

	server_addr = &addr;

#elif ADDR_OPTION == 3
	/* Set the bind address according to your configuration */
	memset(&addr, 0, sizeof(addr));

	/* In this example, listen only IPv6 */
	addr.family = AF_INET6;

	ret = http_server_set_local_addr(&addr, ZEPHYR_ADDR, ZEPHYR_PORT);
	if (ret < 0) {
		NET_ERR("Cannot set local address (%d)", ret);
		panic(NULL);
	}

	server_addr = &addr;

#else
	server_addr = NULL;

	ARG_UNUSED(addr);
#endif

	http_server_add_default(&http_urls, http_response_soft_404);
	http_server_add_url(&http_urls, HTTP_AUTH_URL, HTTP_URL_STANDARD,
			    http_basic_auth);
	http_server_add_url(&http_urls, "/headers", HTTP_URL_STANDARD,
			    http_response_header_fields);
	http_server_add_url(&http_urls, "/index.html", HTTP_URL_STANDARD,
			    http_response_it_works);

#if defined(CONFIG_HTTPS)
	http_server_set_net_pkt_pool(&https_ctx, tx_slab, data_pool);

	ret = https_server_init(&https_ctx, &http_urls, server_addr,
				https_result, sizeof(https_result),
				"Zephyr HTTPS Server",
				INSTANCE_INFO, strlen(INSTANCE_INFO),
				setup_cert, NULL, &ssl_rx_pool,
				https_stack,
				K_THREAD_STACK_SIZEOF(https_stack));
	if (ret < 0) {
		NET_ERR("Cannot initialize HTTPS server (%d)", ret);
		panic(NULL);
	}

	http_server_enable(&https_ctx);
#endif

	http_server_set_net_pkt_pool(&http_ctx, tx_slab, data_pool);

	ret = http_server_init(&http_ctx, &http_urls, server_addr, http_result,
			       sizeof(http_result), "Zephyr HTTP Server");
	if (ret < 0) {
		NET_ERR("Cannot initialize HTTP server (%d)", ret);
		panic(NULL);
	}

	/*
	 * If needed, the HTTP parser callbacks can be set according to
	 * applications own needs before enabling the server. In this example
	 * we use the default callbacks defined in HTTP server API.
	 */

	http_server_enable(&http_ctx);
}