3a53d96526
This commit implements address validation for the broker address in the MQTT configuration page of the CC26XX web demo example. Additionally, the Type ID, Org ID, Auth Token, Command Type and Event Type ID fields have quotes escaped (" -> ") to prevent XSS/broken page issues when displaying user-sourced input in HTML input fields.
1387 lines
50 KiB
C
1387 lines
50 KiB
C
/*
|
|
* Copyright (c) 2010, Swedish Institute of Computer Science.
|
|
* Copyright (c) 2014, Texas Instruments Incorporated - http://www.ti.com/
|
|
* All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
* 3. Neither the name of the Institute nor the names of its contributors
|
|
* may be used to endorse or promote products derived from this software
|
|
* without specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
|
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
|
|
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
|
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
|
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
|
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
|
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
|
* SUCH DAMAGE.
|
|
*
|
|
*/
|
|
/**
|
|
* \addtogroup cc26xx-web-demo
|
|
* @{
|
|
*
|
|
* \file
|
|
* A simple web server which displays network and sensor information
|
|
*/
|
|
/*---------------------------------------------------------------------------*/
|
|
#include "contiki.h"
|
|
#include "httpd-simple.h"
|
|
#include "net/ipv6/uip-ds6-route.h"
|
|
#include "batmon-sensor.h"
|
|
#include "lib/sensors.h"
|
|
#include "lib/list.h"
|
|
#include "cc26xx-web-demo.h"
|
|
#include "mqtt-client.h"
|
|
#include "net-uart.h"
|
|
|
|
#include <stdint.h>
|
|
#include <string.h>
|
|
#include <strings.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <stddef.h>
|
|
#include <strformat.h>
|
|
#include <ctype.h>
|
|
/*---------------------------------------------------------------------------*/
|
|
#define SEND_STRING(s, str) PSOCK_SEND(s, (uint8_t *)str, strlen(str))
|
|
/*---------------------------------------------------------------------------*/
|
|
#define CONNS 2
|
|
#define CONTENT_LENGTH_MAX 256
|
|
#define STATE_WAITING 0
|
|
#define STATE_OUTPUT 1
|
|
#define IPADDR_BUF_LEN 64
|
|
/*---------------------------------------------------------------------------*/
|
|
#define RETURN_CODE_OK 0
|
|
#define RETURN_CODE_NF 1 /* Not Found */
|
|
#define RETURN_CODE_SU 2 /* Service Unavailable */
|
|
#define RETURN_CODE_BR 3 /* Bad Request */
|
|
#define RETURN_CODE_LR 4 /* Length Required */
|
|
#define RETURN_CODE_TL 5 /* Content Length too Large */
|
|
/*---------------------------------------------------------------------------*/
|
|
/* POST request machine states */
|
|
#define PARSE_POST_STATE_INIT 0
|
|
#define PARSE_POST_STATE_MORE 1
|
|
#define PARSE_POST_STATE_READING_KEY 2
|
|
#define PARSE_POST_STATE_READING_VAL 3
|
|
#define PARSE_POST_STATE_ERROR 0xFFFFFFFF
|
|
/*---------------------------------------------------------------------------*/
|
|
#define PARSE_POST_BUF_SIZES 64
|
|
|
|
/* Last byte always used to null terminate */
|
|
#define PARSE_POST_MAX_POS (PARSE_POST_BUF_SIZES - 2)
|
|
|
|
static char key[PARSE_POST_BUF_SIZES];
|
|
static char val_escaped[PARSE_POST_BUF_SIZES];
|
|
static char val[PARSE_POST_BUF_SIZES];
|
|
static int key_len;
|
|
static int val_len;
|
|
static int state;
|
|
/*---------------------------------------------------------------------------*/
|
|
/* Stringified min/max intervals */
|
|
#define STRINGIFY(x) XSTR(x)
|
|
#define XSTR(x) #x
|
|
|
|
#define RSSI_INT_MAX STRINGIFY(CC26XX_WEB_DEMO_RSSI_MEASURE_INTERVAL_MAX)
|
|
#define RSSI_INT_MIN STRINGIFY(CC26XX_WEB_DEMO_RSSI_MEASURE_INTERVAL_MIN)
|
|
#define PUB_INT_MAX STRINGIFY(MQTT_CLIENT_PUBLISH_INTERVAL_MAX)
|
|
#define PUB_INT_MIN STRINGIFY(MQTT_CLIENT_PUBLISH_INTERVAL_MIN)
|
|
/*---------------------------------------------------------------------------*/
|
|
/*
|
|
* We can only handle a single POST request at a time. Since a second POST
|
|
* request cannot interrupt us while obtaining a lock, we don't really need
|
|
* this lock to be atomic.
|
|
*
|
|
* An HTTP connection will first request a lock before it starts processing
|
|
* a POST request. We maintain a global lock which is either NULL or points
|
|
* to the http conn which currently has the lock
|
|
*/
|
|
static struct httpd_state *lock;
|
|
/*---------------------------------------------------------------------------*/
|
|
PROCESS(httpd_simple_process, "CC26XX Web Server");
|
|
/*---------------------------------------------------------------------------*/
|
|
#define ISO_nl 0x0A
|
|
#define ISO_space 0x20
|
|
#define ISO_slash 0x2F
|
|
#define ISO_amp 0x26
|
|
#define ISO_column 0x3A
|
|
#define ISO_equal 0x3D
|
|
/*---------------------------------------------------------------------------*/
|
|
#define HTTP_200_OK "HTTP/1.0 200 OK\r\n"
|
|
#define HTTP_302_FO "HTTP/1.0 302 Found\r\n"
|
|
#define HTTP_400_BR "HTTP/1.0 400 Bad Request\r\n"
|
|
#define HTTP_404_NF "HTTP/1.0 404 Not Found\r\n"
|
|
#define HTTP_411_LR "HTTP/1.0 411 Length Required\r\n"
|
|
#define HTTP_413_TL "HTTP/1.0 413 Request Entity Too Large\r\n"
|
|
#define HTTP_503_SU "HTTP/1.0 503 Service Unavailable\r\n"
|
|
#define CONN_CLOSE "Connection: close\r\n"
|
|
/*---------------------------------------------------------------------------*/
|
|
#define SECTION_TAG "div"
|
|
#define SECTION_OPEN "<" SECTION_TAG ">"
|
|
#define SECTION_CLOSE "</" SECTION_TAG ">"
|
|
|
|
#define CONTENT_OPEN "<pre>"
|
|
#define CONTENT_CLOSE "</pre>"
|
|
/*---------------------------------------------------------------------------*/
|
|
#define REQUEST_TYPE_GET 1
|
|
#define REQUEST_TYPE_POST 2
|
|
/*---------------------------------------------------------------------------*/
|
|
/* Temporary buffer for holding escaped HTML used by html_escape_quotes */
|
|
#define HTML_ESCAPED_BUFFER_SIZE 128
|
|
static char html_escaped_buf[HTML_ESCAPED_BUFFER_SIZE];
|
|
/*---------------------------------------------------------------------------*/
|
|
static const char *NOT_FOUND = "<html><body bgcolor=\"white\">"
|
|
"<center>"
|
|
"<h1>404 - file not found</h1>"
|
|
"</center>"
|
|
"</body>"
|
|
"</html>";
|
|
/*---------------------------------------------------------------------------*/
|
|
/* Page template */
|
|
static const char http_doctype[] = "<!DOCTYPE html>";
|
|
static const char http_header_200[] = HTTP_200_OK;
|
|
static const char http_header_302[] = HTTP_302_FO;
|
|
static const char http_header_400[] = HTTP_400_BR;
|
|
static const char http_header_404[] = HTTP_404_NF;
|
|
static const char http_header_411[] = HTTP_411_LR;
|
|
static const char http_header_413[] = HTTP_413_TL;
|
|
static const char http_header_503[] = HTTP_503_SU;
|
|
static const char http_get[] = "GET ";
|
|
static const char http_post[] = "POST ";
|
|
static const char http_index_html[] = "/index.html";
|
|
static const char http_html_start[] = "<html><head>";
|
|
static const char *http_header_srv_str[] = {
|
|
"Server: Contiki, ",
|
|
BOARD_STRING "\r\n",
|
|
NULL
|
|
};
|
|
|
|
static const char *http_header_con_close[] = {
|
|
CONN_CLOSE,
|
|
NULL
|
|
};
|
|
|
|
static const char *http_config_css[] = {
|
|
"<style>",
|
|
".left{float:left;text-align:right;}",
|
|
".right{margin-left:150px;}",
|
|
"input[type=\"radio\"]",
|
|
"{display: inline-block;}",
|
|
"</style>",
|
|
NULL
|
|
};
|
|
static const char http_head_charset[] = "<meta charset=\"UTF-8\">";
|
|
static const char http_title_start[] = "<title>";
|
|
static const char http_title_end[] = "</title>";
|
|
static const char http_head_end[] = "</head>";
|
|
static const char http_body_start[] = "<body>";
|
|
static const char http_bottom[] = "</body></html>";
|
|
/*---------------------------------------------------------------------------*/
|
|
static const char http_content_type_html[] = "text/html";
|
|
static const char http_content_type_plain[] = "text/plain";
|
|
/*---------------------------------------------------------------------------*/
|
|
/* For the config page */
|
|
static const char config_div_left[] = "<div class=\"left\">";
|
|
static const char config_div_right[] = "<div class=\"right\">";
|
|
static const char config_div_close[] = "</div>";
|
|
/*---------------------------------------------------------------------------*/
|
|
static char generate_index(struct httpd_state *s);
|
|
static char generate_config(struct httpd_state *s);
|
|
/*---------------------------------------------------------------------------*/
|
|
typedef struct page {
|
|
struct page *next;
|
|
char *filename;
|
|
char *title;
|
|
char (*script)(struct httpd_state *s);
|
|
} page_t;
|
|
|
|
static page_t http_index_page = {
|
|
NULL,
|
|
"index.html",
|
|
"Index",
|
|
generate_index,
|
|
};
|
|
|
|
static page_t http_dev_cfg_page = {
|
|
NULL,
|
|
"config.html",
|
|
"Device Config",
|
|
generate_config,
|
|
};
|
|
|
|
#if CC26XX_WEB_DEMO_NET_UART
|
|
static char generate_net_uart_config(struct httpd_state *s);
|
|
|
|
static page_t http_net_cfg_page = {
|
|
NULL,
|
|
"netu.html",
|
|
"Net-UART Config",
|
|
generate_net_uart_config,
|
|
};
|
|
#endif
|
|
|
|
#if CC26XX_WEB_DEMO_MQTT_CLIENT
|
|
static char generate_mqtt_config(struct httpd_state *s);
|
|
|
|
static page_t http_mqtt_cfg_page = {
|
|
NULL,
|
|
"mqtt.html",
|
|
"MQTT/IBM Cloud Config",
|
|
generate_mqtt_config,
|
|
};
|
|
#endif
|
|
/*---------------------------------------------------------------------------*/
|
|
#define IBM_QUICKSTART_LINK_LEN 128
|
|
static char http_mqtt_a[IBM_QUICKSTART_LINK_LEN];
|
|
/*---------------------------------------------------------------------------*/
|
|
static uint16_t numtimes;
|
|
static const httpd_simple_post_handler_t *handler;
|
|
/*---------------------------------------------------------------------------*/
|
|
static uint8_t config_ok;
|
|
process_event_t httpd_simple_event_new_config;
|
|
/*---------------------------------------------------------------------------*/
|
|
struct httpd_state;
|
|
typedef char (*httpd_simple_script_t)(struct httpd_state *s);
|
|
|
|
struct httpd_state {
|
|
char buf[HTTPD_SIMPLE_MAIN_BUF_SIZE];
|
|
char tmp_buf[TMP_BUF_SIZE];
|
|
struct timer timer;
|
|
struct psock sin, sout;
|
|
int blen;
|
|
const char **ptr;
|
|
const cc26xx_web_demo_sensor_reading_t *reading;
|
|
const page_t *page;
|
|
uip_ds6_route_t *r;
|
|
uip_ds6_nbr_t *nbr;
|
|
httpd_simple_script_t script;
|
|
int content_length;
|
|
int tmp_buf_len;
|
|
int tmp_buf_copied;
|
|
char filename[HTTPD_PATHLEN];
|
|
char inputbuf[HTTPD_INBUF_LEN];
|
|
struct pt outputpt;
|
|
struct pt generate_pt;
|
|
struct pt top_matter_pt;
|
|
char state;
|
|
char request_type;
|
|
char return_code;
|
|
};
|
|
/*---------------------------------------------------------------------------*/
|
|
LIST(post_handlers);
|
|
LIST(pages_list);
|
|
MEMB(conns, struct httpd_state, CONNS);
|
|
/*---------------------------------------------------------------------------*/
|
|
#define HEX_TO_INT(x) (isdigit(x) ? x - '0' : x - 'W')
|
|
static size_t
|
|
url_unescape(const char *src, size_t srclen, char *dst, size_t dstlen)
|
|
{
|
|
size_t i, j;
|
|
int a, b;
|
|
|
|
for(i = j = 0; i < srclen && j < dstlen - 1; i++, j++) {
|
|
if(src[i] == '%' && isxdigit(*(unsigned char *)(src + i + 1))
|
|
&& isxdigit(*(unsigned char *)(src + i + 2))) {
|
|
a = tolower(*(unsigned char *)(src + i + 1));
|
|
b = tolower(*(unsigned char *)(src + i + 2));
|
|
dst[j] = ((HEX_TO_INT(a) << 4) | HEX_TO_INT(b)) & 0xff;
|
|
i += 2;
|
|
} else if(src[i] == '+') {
|
|
dst[j] = ' ';
|
|
} else {
|
|
dst[j] = src[i];
|
|
}
|
|
}
|
|
|
|
dst[j] = '\0';
|
|
|
|
return i == srclen;
|
|
}
|
|
/*---------------------------------------------------------------------------*/
|
|
static char*
|
|
html_escape_quotes(const char *src, size_t srclen)
|
|
{
|
|
size_t srcpos, dstpos;
|
|
memset(html_escaped_buf, 0, HTML_ESCAPED_BUFFER_SIZE);
|
|
for(srcpos = dstpos = 0;
|
|
srcpos < srclen && dstpos < HTML_ESCAPED_BUFFER_SIZE - 1; srcpos++) {
|
|
if(src[srcpos] == '\0') {
|
|
break;
|
|
} else if(src[srcpos] == '"') {
|
|
if(dstpos + 7 > HTML_ESCAPED_BUFFER_SIZE) {
|
|
break;
|
|
}
|
|
|
|
strcpy(&html_escaped_buf[dstpos], """);
|
|
dstpos += 6;
|
|
} else {
|
|
html_escaped_buf[dstpos++] = src[srcpos];
|
|
}
|
|
}
|
|
|
|
html_escaped_buf[HTML_ESCAPED_BUFFER_SIZE - 1] = '\0';
|
|
return html_escaped_buf;
|
|
}
|
|
/*---------------------------------------------------------------------------*/
|
|
void
|
|
httpd_simple_register_post_handler(httpd_simple_post_handler_t *h)
|
|
{
|
|
list_add(post_handlers, h);
|
|
}
|
|
/*---------------------------------------------------------------------------*/
|
|
static void
|
|
get_neighbour_state_text(char *buf, uint8_t state)
|
|
{
|
|
switch(state) {
|
|
case NBR_INCOMPLETE:
|
|
memcpy(buf, "INCOMPLETE", strlen("INCOMPLETE"));
|
|
break;
|
|
case NBR_REACHABLE:
|
|
memcpy(buf, "REACHABLE", strlen("REACHABLE"));
|
|
break;
|
|
case NBR_STALE:
|
|
memcpy(buf, "STALE", strlen("STALE"));
|
|
break;
|
|
case NBR_DELAY:
|
|
memcpy(buf, "DELAY", strlen("DELAY"));
|
|
break;
|
|
case NBR_PROBE:
|
|
memcpy(buf, "NBR_PROBE", strlen("NBR_PROBE"));
|
|
break;
|
|
}
|
|
}
|
|
/*---------------------------------------------------------------------------*/
|
|
static
|
|
PT_THREAD(enqueue_chunk(struct httpd_state *s, uint8_t immediate,
|
|
const char *format, ...))
|
|
{
|
|
va_list ap;
|
|
|
|
PSOCK_BEGIN(&s->sout);
|
|
|
|
va_start(ap, format);
|
|
|
|
s->tmp_buf_len = vsnprintf(s->tmp_buf, TMP_BUF_SIZE, format, ap);
|
|
|
|
va_end(ap);
|
|
|
|
if(s->blen + s->tmp_buf_len < HTTPD_SIMPLE_MAIN_BUF_SIZE) {
|
|
/* Enough space for the entire chunk. Copy over */
|
|
memcpy(&s->buf[s->blen], s->tmp_buf, s->tmp_buf_len);
|
|
s->blen += s->tmp_buf_len;
|
|
} else {
|
|
memcpy(&s->buf[s->blen], s->tmp_buf, HTTPD_SIMPLE_MAIN_BUF_SIZE - s->blen);
|
|
s->tmp_buf_copied = HTTPD_SIMPLE_MAIN_BUF_SIZE - s->blen;
|
|
s->blen = HTTPD_SIMPLE_MAIN_BUF_SIZE;
|
|
PSOCK_SEND(&s->sout, (uint8_t *)s->buf, s->blen);
|
|
s->blen = 0;
|
|
if(s->tmp_buf_copied < s->tmp_buf_len) {
|
|
memcpy(s->buf, &s->tmp_buf[s->tmp_buf_copied],
|
|
s->tmp_buf_len - s->tmp_buf_copied);
|
|
s->blen += s->tmp_buf_len - s->tmp_buf_copied;
|
|
}
|
|
}
|
|
|
|
if(immediate != 0 && s->blen > 0) {
|
|
PSOCK_SEND(&s->sout, (uint8_t *)s->buf, s->blen);
|
|
s->blen = 0;
|
|
}
|
|
|
|
PSOCK_END(&s->sout);
|
|
}
|
|
/*---------------------------------------------------------------------------*/
|
|
static
|
|
PT_THREAD(generate_top_matter(struct httpd_state *s, const char *title,
|
|
const char **css))
|
|
{
|
|
|
|
PT_BEGIN(&s->top_matter_pt);
|
|
|
|
PT_WAIT_THREAD(&s->top_matter_pt, enqueue_chunk(s, 0, http_doctype));
|
|
PT_WAIT_THREAD(&s->top_matter_pt, enqueue_chunk(s, 0, http_html_start));
|
|
PT_WAIT_THREAD(&s->top_matter_pt, enqueue_chunk(s, 0, http_title_start));
|
|
|
|
PT_WAIT_THREAD(&s->top_matter_pt, enqueue_chunk(s, 0, title));
|
|
PT_WAIT_THREAD(&s->top_matter_pt, enqueue_chunk(s, 0, http_title_end));
|
|
|
|
if(css != NULL) {
|
|
for(s->ptr = css; *(s->ptr) != NULL; s->ptr++) {
|
|
PT_WAIT_THREAD(&s->top_matter_pt, enqueue_chunk(s, 0, *(s->ptr)));
|
|
}
|
|
}
|
|
|
|
PT_WAIT_THREAD(&s->top_matter_pt, enqueue_chunk(s, 0, http_head_charset));
|
|
PT_WAIT_THREAD(&s->top_matter_pt, enqueue_chunk(s, 0, http_head_end));
|
|
PT_WAIT_THREAD(&s->top_matter_pt, enqueue_chunk(s, 0, http_body_start));
|
|
|
|
/* Links */
|
|
PT_WAIT_THREAD(&s->top_matter_pt,
|
|
enqueue_chunk(s, 0, SECTION_OPEN "<p>"));
|
|
|
|
s->page = list_head(pages_list);
|
|
PT_WAIT_THREAD(&s->top_matter_pt,
|
|
enqueue_chunk(s, 0, "[ <a href=\"%s\">%s</a> ]",
|
|
s->page->filename, s->page->title));
|
|
|
|
for(s->page = s->page->next; s->page != NULL; s->page = s->page->next) {
|
|
PT_WAIT_THREAD(&s->top_matter_pt,
|
|
enqueue_chunk(s, 0, " | [ <a href=\"%s\">%s</a> ]",
|
|
s->page->filename, s->page->title));
|
|
}
|
|
|
|
#if CC26XX_WEB_DEMO_MQTT_CLIENT
|
|
PT_WAIT_THREAD(&s->top_matter_pt,
|
|
enqueue_chunk(s, 0, " | %s", http_mqtt_a));
|
|
#endif
|
|
PT_WAIT_THREAD(&s->top_matter_pt,
|
|
enqueue_chunk(s, 0, "</p>" SECTION_CLOSE));
|
|
|
|
PT_END(&s->top_matter_pt);
|
|
}
|
|
/*---------------------------------------------------------------------------*/
|
|
static
|
|
PT_THREAD(generate_index(struct httpd_state *s))
|
|
{
|
|
char ipaddr_buf[IPADDR_BUF_LEN]; /* Intentionally on stack */
|
|
|
|
PT_BEGIN(&s->generate_pt);
|
|
|
|
/* Generate top matter (doctype, title, nav links etc) */
|
|
PT_WAIT_THREAD(&s->generate_pt,
|
|
generate_top_matter(s, http_index_page.title, NULL));
|
|
|
|
/* ND Cache */
|
|
PT_WAIT_THREAD(&s->generate_pt,
|
|
enqueue_chunk(s, 0, SECTION_OPEN "Neighbors" CONTENT_OPEN));
|
|
|
|
for(s->nbr = nbr_table_head(ds6_neighbors); s->nbr != NULL;
|
|
s->nbr = nbr_table_next(ds6_neighbors, s->nbr)) {
|
|
|
|
PT_WAIT_THREAD(&s->generate_pt, enqueue_chunk(s, 0, "\n"));
|
|
|
|
memset(ipaddr_buf, 0, IPADDR_BUF_LEN);
|
|
cc26xx_web_demo_ipaddr_sprintf(ipaddr_buf, IPADDR_BUF_LEN, &s->nbr->ipaddr);
|
|
PT_WAIT_THREAD(&s->generate_pt, enqueue_chunk(s, 0, "%s", ipaddr_buf));
|
|
|
|
memset(ipaddr_buf, 0, IPADDR_BUF_LEN);
|
|
get_neighbour_state_text(ipaddr_buf, s->nbr->state);
|
|
PT_WAIT_THREAD(&s->generate_pt, enqueue_chunk(s, 0, " %s", ipaddr_buf));
|
|
}
|
|
|
|
PT_WAIT_THREAD(&s->generate_pt,
|
|
enqueue_chunk(s, 0, CONTENT_CLOSE SECTION_CLOSE));
|
|
|
|
/* Default Route */
|
|
PT_WAIT_THREAD(&s->generate_pt,
|
|
enqueue_chunk(s, 0,
|
|
SECTION_OPEN "Default Route" CONTENT_OPEN));
|
|
|
|
memset(ipaddr_buf, 0, IPADDR_BUF_LEN);
|
|
cc26xx_web_demo_ipaddr_sprintf(ipaddr_buf, IPADDR_BUF_LEN,
|
|
uip_ds6_defrt_choose());
|
|
PT_WAIT_THREAD(&s->generate_pt, enqueue_chunk(s, 0, "%s", ipaddr_buf));
|
|
|
|
PT_WAIT_THREAD(&s->generate_pt,
|
|
enqueue_chunk(s, 0, CONTENT_CLOSE SECTION_CLOSE));
|
|
|
|
/* Routes */
|
|
PT_WAIT_THREAD(&s->generate_pt,
|
|
enqueue_chunk(s, 0, SECTION_OPEN "Routes" CONTENT_OPEN));
|
|
|
|
for(s->r = uip_ds6_route_head(); s->r != NULL;
|
|
s->r = uip_ds6_route_next(s->r)) {
|
|
PT_WAIT_THREAD(&s->generate_pt, enqueue_chunk(s, 0, "\n"));
|
|
|
|
memset(ipaddr_buf, 0, IPADDR_BUF_LEN);
|
|
cc26xx_web_demo_ipaddr_sprintf(ipaddr_buf, IPADDR_BUF_LEN, &s->r->ipaddr);
|
|
PT_WAIT_THREAD(&s->generate_pt, enqueue_chunk(s, 0, "%s", ipaddr_buf));
|
|
|
|
PT_WAIT_THREAD(&s->generate_pt,
|
|
enqueue_chunk(s, 0, " / %u via ", s->r->length));
|
|
|
|
memset(ipaddr_buf, 0, IPADDR_BUF_LEN);
|
|
cc26xx_web_demo_ipaddr_sprintf(ipaddr_buf, IPADDR_BUF_LEN,
|
|
uip_ds6_route_nexthop(s->r));
|
|
PT_WAIT_THREAD(&s->generate_pt, enqueue_chunk(s, 0, "%s", ipaddr_buf));
|
|
|
|
PT_WAIT_THREAD(&s->generate_pt,
|
|
enqueue_chunk(s, 0,
|
|
", lifetime=%lus", s->r->state.lifetime));
|
|
}
|
|
|
|
PT_WAIT_THREAD(&s->generate_pt, enqueue_chunk(s, 0,
|
|
CONTENT_CLOSE SECTION_CLOSE));
|
|
|
|
/* Sensors */
|
|
PT_WAIT_THREAD(&s->generate_pt,
|
|
enqueue_chunk(s, 0, SECTION_OPEN "Sensors" CONTENT_OPEN));
|
|
|
|
for(s->reading = cc26xx_web_demo_sensor_first();
|
|
s->reading != NULL; s->reading = s->reading->next) {
|
|
PT_WAIT_THREAD(&s->generate_pt,
|
|
enqueue_chunk(s, 0, "\n%s = %s %s", s->reading->descr,
|
|
s->reading->publish ? s->reading->converted : "N/A",
|
|
s->reading->units));
|
|
}
|
|
|
|
PT_WAIT_THREAD(&s->generate_pt,
|
|
enqueue_chunk(s, 0, CONTENT_CLOSE SECTION_CLOSE));
|
|
|
|
/* Footer */
|
|
PT_WAIT_THREAD(&s->generate_pt, enqueue_chunk(s, 0, SECTION_OPEN));
|
|
PT_WAIT_THREAD(&s->generate_pt, enqueue_chunk(s, 0, "Page hits: %u<br>",
|
|
++numtimes));
|
|
PT_WAIT_THREAD(&s->generate_pt, enqueue_chunk(s, 0, "Uptime: %lu secs<br>",
|
|
clock_seconds()));
|
|
PT_WAIT_THREAD(&s->generate_pt, enqueue_chunk(s, 0, SECTION_CLOSE));
|
|
|
|
PT_WAIT_THREAD(&s->generate_pt, enqueue_chunk(s, 1, http_bottom));
|
|
|
|
PT_END(&s->generate_pt);
|
|
}
|
|
/*---------------------------------------------------------------------------*/
|
|
static
|
|
PT_THREAD(generate_config(struct httpd_state *s))
|
|
{
|
|
PT_BEGIN(&s->generate_pt);
|
|
|
|
/* Generate top matter (doctype, title, nav links etc) */
|
|
PT_WAIT_THREAD(&s->generate_pt,
|
|
generate_top_matter(s, http_dev_cfg_page.title,
|
|
http_config_css));
|
|
|
|
/* Sensor Settings */
|
|
PT_WAIT_THREAD(&s->generate_pt,
|
|
enqueue_chunk(s, 0, "<h1>Sensors</h1>"));
|
|
|
|
PT_WAIT_THREAD(&s->generate_pt,
|
|
enqueue_chunk(s, 0,
|
|
"<form name=\"input\" action=\"%s\" ",
|
|
http_dev_cfg_page.filename));
|
|
PT_WAIT_THREAD(&s->generate_pt,
|
|
enqueue_chunk(s, 0, "method=\"post\" enctype=\""));
|
|
PT_WAIT_THREAD(&s->generate_pt,
|
|
enqueue_chunk(s, 0, "application/x-www-form-urlencoded\" "));
|
|
PT_WAIT_THREAD(&s->generate_pt,
|
|
enqueue_chunk(s, 0, "accept-charset=\"UTF-8\">"));
|
|
|
|
for(s->reading = cc26xx_web_demo_sensor_first();
|
|
s->reading != NULL; s->reading = s->reading->next) {
|
|
PT_WAIT_THREAD(&s->generate_pt,
|
|
enqueue_chunk(s, 0, "%s%s:%s%s", config_div_left,
|
|
s->reading->descr, config_div_close,
|
|
config_div_right));
|
|
|
|
PT_WAIT_THREAD(&s->generate_pt,
|
|
enqueue_chunk(s, 0, "<input type=\"radio\" value=\"1\" "));
|
|
PT_WAIT_THREAD(&s->generate_pt,
|
|
enqueue_chunk(s, 0, "title=\"On\" name=\"%s\"%s>",
|
|
s->reading->form_field,
|
|
s->reading->publish ? " Checked" : ""));
|
|
PT_WAIT_THREAD(&s->generate_pt,
|
|
enqueue_chunk(s, 0, "<input type=\"radio\" value=\"0\" "));
|
|
PT_WAIT_THREAD(&s->generate_pt,
|
|
enqueue_chunk(s, 0, "title=\"Off\" name=\"%s\"%s>%s",
|
|
s->reading->form_field,
|
|
s->reading->publish ? "" : " Checked",
|
|
config_div_close));
|
|
}
|
|
|
|
PT_WAIT_THREAD(&s->generate_pt,
|
|
enqueue_chunk(s, 0,
|
|
"<input type=\"submit\" value=\"Submit\">"));
|
|
PT_WAIT_THREAD(&s->generate_pt,
|
|
enqueue_chunk(s, 0, "</form>"));
|
|
|
|
/* RSSI measurements */
|
|
#if CC26XX_WEB_DEMO_READ_PARENT_RSSI
|
|
PT_WAIT_THREAD(&s->generate_pt,
|
|
enqueue_chunk(s, 0, "<h1>RSSI Probing</h1>"));
|
|
|
|
PT_WAIT_THREAD(&s->generate_pt,
|
|
enqueue_chunk(s, 0,
|
|
"<form name=\"input\" action=\"%s\" ",
|
|
http_dev_cfg_page.filename));
|
|
PT_WAIT_THREAD(&s->generate_pt,
|
|
enqueue_chunk(s, 0, "method=\"post\" enctype=\""));
|
|
PT_WAIT_THREAD(&s->generate_pt,
|
|
enqueue_chunk(s, 0, "application/x-www-form-urlencoded\" "));
|
|
PT_WAIT_THREAD(&s->generate_pt,
|
|
enqueue_chunk(s, 0, "accept-charset=\"UTF-8\">"));
|
|
|
|
PT_WAIT_THREAD(&s->generate_pt,
|
|
enqueue_chunk(s, 0, "%sPeriod (secs):%s",
|
|
config_div_left, config_div_close));
|
|
PT_WAIT_THREAD(&s->generate_pt,
|
|
enqueue_chunk(s, 0, "%s<input type=\"number\" ",
|
|
config_div_right));
|
|
PT_WAIT_THREAD(&s->generate_pt,
|
|
enqueue_chunk(s, 0, "value=\"%lu\" ",
|
|
(clock_time_t)
|
|
(cc26xx_web_demo_config.def_rt_ping_interval
|
|
/ CLOCK_SECOND)));
|
|
PT_WAIT_THREAD(&s->generate_pt,
|
|
enqueue_chunk(s, 0,
|
|
"min=\"" RSSI_INT_MIN "\" "
|
|
"max=\"" RSSI_INT_MAX "\" "
|
|
"name=\"ping_interval\">%s",
|
|
config_div_close));
|
|
|
|
PT_WAIT_THREAD(&s->generate_pt,
|
|
enqueue_chunk(s, 0,
|
|
"<input type=\"submit\" value=\"Submit\">"));
|
|
PT_WAIT_THREAD(&s->generate_pt,
|
|
enqueue_chunk(s, 0, "</form>"));
|
|
#endif
|
|
|
|
/* Actions */
|
|
PT_WAIT_THREAD(&s->generate_pt, enqueue_chunk(s, 0, "<h1>Actions</h1>"));
|
|
PT_WAIT_THREAD(&s->generate_pt,
|
|
enqueue_chunk(s, 0,
|
|
"<form name=\"input\" action=\"%s\" ",
|
|
http_dev_cfg_page.filename));
|
|
PT_WAIT_THREAD(&s->generate_pt,
|
|
enqueue_chunk(s, 0, "method=\"post\" enctype=\""));
|
|
PT_WAIT_THREAD(&s->generate_pt,
|
|
enqueue_chunk(s, 0, "application/x-www-form-urlencoded\" "));
|
|
PT_WAIT_THREAD(&s->generate_pt,
|
|
enqueue_chunk(s, 0, "accept-charset=\"UTF-8\">"));
|
|
PT_WAIT_THREAD(&s->generate_pt,
|
|
enqueue_chunk(s, 0, "<input type=\"hidden\" value=\"1\" "
|
|
"name=\"defaults\">"));
|
|
PT_WAIT_THREAD(&s->generate_pt,
|
|
enqueue_chunk(s, 0, "<button>Restore Defaults</button>"));
|
|
PT_WAIT_THREAD(&s->generate_pt,
|
|
enqueue_chunk(s, 0, "</form>"));
|
|
|
|
PT_WAIT_THREAD(&s->generate_pt, enqueue_chunk(s, 1, http_bottom));
|
|
|
|
PT_END(&s->generate_pt);
|
|
}
|
|
/*---------------------------------------------------------------------------*/
|
|
#if CC26XX_WEB_DEMO_MQTT_CLIENT
|
|
static
|
|
PT_THREAD(generate_mqtt_config(struct httpd_state *s))
|
|
{
|
|
PT_BEGIN(&s->generate_pt);
|
|
|
|
/* Generate top matter (doctype, title, nav links etc) */
|
|
PT_WAIT_THREAD(&s->generate_pt,
|
|
generate_top_matter(s, http_mqtt_cfg_page.title,
|
|
http_config_css));
|
|
|
|
/* MQTT client settings */
|
|
PT_WAIT_THREAD(&s->generate_pt,
|
|
enqueue_chunk(s, 0, "<h1>%s</h1>", http_mqtt_cfg_page.title));
|
|
|
|
PT_WAIT_THREAD(&s->generate_pt,
|
|
enqueue_chunk(s, 0,
|
|
"<form name=\"input\" action=\"%s\" ",
|
|
http_mqtt_cfg_page.filename));
|
|
PT_WAIT_THREAD(&s->generate_pt,
|
|
enqueue_chunk(s, 0, "method=\"post\" enctype=\""));
|
|
PT_WAIT_THREAD(&s->generate_pt,
|
|
enqueue_chunk(s, 0, "application/x-www-form-urlencoded\" "));
|
|
PT_WAIT_THREAD(&s->generate_pt,
|
|
enqueue_chunk(s, 0, "accept-charset=\"UTF-8\">"));
|
|
|
|
PT_WAIT_THREAD(&s->generate_pt,
|
|
enqueue_chunk(s, 0, "%sType ID:%s", config_div_left,
|
|
config_div_close));
|
|
PT_WAIT_THREAD(&s->generate_pt,
|
|
enqueue_chunk(s, 0, "%s<input type=\"text\" ",
|
|
config_div_right));
|
|
PT_WAIT_THREAD(&s->generate_pt,
|
|
enqueue_chunk(s, 0, "value=\"%s\" ",
|
|
html_escape_quotes(
|
|
cc26xx_web_demo_config.mqtt_config.type_id,
|
|
MQTT_CLIENT_CONFIG_TYPE_ID_LEN)));
|
|
PT_WAIT_THREAD(&s->generate_pt,
|
|
enqueue_chunk(s, 0, "name=\"type_id\">%s", config_div_close));
|
|
|
|
PT_WAIT_THREAD(&s->generate_pt,
|
|
enqueue_chunk(s, 0, "%sOrg ID:%s", config_div_left,
|
|
config_div_close));
|
|
PT_WAIT_THREAD(&s->generate_pt,
|
|
enqueue_chunk(s, 0, "%s<input type=\"text\" ",
|
|
config_div_right));
|
|
PT_WAIT_THREAD(&s->generate_pt,
|
|
enqueue_chunk(s, 0, "value=\"%s\" ",
|
|
html_escape_quotes(
|
|
cc26xx_web_demo_config.mqtt_config.org_id,
|
|
MQTT_CLIENT_CONFIG_ORG_ID_LEN)));
|
|
PT_WAIT_THREAD(&s->generate_pt,
|
|
enqueue_chunk(s, 0, "name=\"org_id\">%s", config_div_close));
|
|
|
|
PT_WAIT_THREAD(&s->generate_pt,
|
|
enqueue_chunk(s, 0, "%sAuth Token:%s", config_div_left,
|
|
config_div_close));
|
|
PT_WAIT_THREAD(&s->generate_pt,
|
|
enqueue_chunk(s, 0, "%s<input type=\"text\" ",
|
|
config_div_right));
|
|
PT_WAIT_THREAD(&s->generate_pt,
|
|
enqueue_chunk(s, 0, "value=\"\" "));
|
|
PT_WAIT_THREAD(&s->generate_pt,
|
|
enqueue_chunk(s, 0, "name=\"auth_token\">%s",
|
|
config_div_close));
|
|
|
|
PT_WAIT_THREAD(&s->generate_pt,
|
|
enqueue_chunk(s, 0, "%sCommand Type:%s", config_div_left,
|
|
config_div_close));
|
|
PT_WAIT_THREAD(&s->generate_pt,
|
|
enqueue_chunk(s, 0, "%s<input type=\"text\" ",
|
|
config_div_right));
|
|
PT_WAIT_THREAD(&s->generate_pt,
|
|
enqueue_chunk(s, 0, "value=\"%s\" ",
|
|
html_escape_quotes(
|
|
cc26xx_web_demo_config.mqtt_config.cmd_type,
|
|
MQTT_CLIENT_CONFIG_CMD_TYPE_LEN)));
|
|
PT_WAIT_THREAD(&s->generate_pt,
|
|
enqueue_chunk(s, 0, "name=\"cmd_type\">%s",
|
|
config_div_close));
|
|
|
|
PT_WAIT_THREAD(&s->generate_pt,
|
|
enqueue_chunk(s, 0, "%sEvent Type ID:%s", config_div_left,
|
|
config_div_close));
|
|
PT_WAIT_THREAD(&s->generate_pt,
|
|
enqueue_chunk(s, 0, "%s<input type=\"text\" ",
|
|
config_div_right));
|
|
PT_WAIT_THREAD(&s->generate_pt,
|
|
enqueue_chunk(s, 0, "value=\"%s\" ",
|
|
html_escape_quotes(
|
|
cc26xx_web_demo_config.mqtt_config.event_type_id,
|
|
MQTT_CLIENT_CONFIG_EVENT_TYPE_ID_LEN)));
|
|
PT_WAIT_THREAD(&s->generate_pt,
|
|
enqueue_chunk(s, 0, "name=\"event_type_id\">%s",
|
|
config_div_close));
|
|
|
|
PT_WAIT_THREAD(&s->generate_pt,
|
|
enqueue_chunk(s, 0, "%sInterval (secs):%s",
|
|
config_div_left, config_div_close));
|
|
PT_WAIT_THREAD(&s->generate_pt,
|
|
enqueue_chunk(s, 0, "%s<input type=\"number\" ",
|
|
config_div_right));
|
|
PT_WAIT_THREAD(&s->generate_pt,
|
|
enqueue_chunk(s, 0, "value=\"%lu\" ",
|
|
(clock_time_t)
|
|
(cc26xx_web_demo_config.mqtt_config.pub_interval
|
|
/ CLOCK_SECOND)));
|
|
PT_WAIT_THREAD(&s->generate_pt,
|
|
enqueue_chunk(s, 0,
|
|
"min=\"" PUB_INT_MIN "\" "
|
|
"max=\"" PUB_INT_MAX "\" "
|
|
"name=\"interval\">%s",
|
|
config_div_close));
|
|
|
|
PT_WAIT_THREAD(&s->generate_pt,
|
|
enqueue_chunk(s, 0, "%sBroker IP:%s", config_div_left,
|
|
config_div_close));
|
|
PT_WAIT_THREAD(&s->generate_pt,
|
|
enqueue_chunk(s, 0, "%s<input type=\"text\" ",
|
|
config_div_right));
|
|
PT_WAIT_THREAD(&s->generate_pt,
|
|
enqueue_chunk(s, 0, "value=\"%s\" ",
|
|
cc26xx_web_demo_config.mqtt_config.broker_ip));
|
|
PT_WAIT_THREAD(&s->generate_pt,
|
|
enqueue_chunk(s, 0, "name=\"broker_ip\">%s",
|
|
config_div_close));
|
|
|
|
PT_WAIT_THREAD(&s->generate_pt,
|
|
enqueue_chunk(s, 0, "%sBroker Port:%s", config_div_left,
|
|
config_div_close));
|
|
PT_WAIT_THREAD(&s->generate_pt,
|
|
enqueue_chunk(s, 0, "%s<input type=\"number\" ",
|
|
config_div_right));
|
|
PT_WAIT_THREAD(&s->generate_pt,
|
|
enqueue_chunk(s, 0, "value=\"%d\" ",
|
|
cc26xx_web_demo_config.mqtt_config.broker_port));
|
|
PT_WAIT_THREAD(&s->generate_pt,
|
|
enqueue_chunk(s, 0, "min=\"1\" max=\"65535\" "
|
|
"name=\"broker_port\">%s",
|
|
config_div_close));
|
|
|
|
PT_WAIT_THREAD(&s->generate_pt,
|
|
enqueue_chunk(s, 0,
|
|
"<input type=\"submit\" value=\"Submit\">"));
|
|
PT_WAIT_THREAD(&s->generate_pt,
|
|
enqueue_chunk(s, 0, "</form>"));
|
|
PT_WAIT_THREAD(&s->generate_pt,
|
|
enqueue_chunk(s, 0,
|
|
"<form name=\"input\" action=\"%s\" ",
|
|
http_mqtt_cfg_page.filename));
|
|
PT_WAIT_THREAD(&s->generate_pt,
|
|
enqueue_chunk(s, 0, "method=\"post\" enctype=\""));
|
|
PT_WAIT_THREAD(&s->generate_pt,
|
|
enqueue_chunk(s, 0, "application/x-www-form-urlencoded\" "));
|
|
PT_WAIT_THREAD(&s->generate_pt,
|
|
enqueue_chunk(s, 0, "accept-charset=\"UTF-8\">"));
|
|
PT_WAIT_THREAD(&s->generate_pt,
|
|
enqueue_chunk(s, 0, "<input type=\"hidden\" value=\"1\" "
|
|
"name=\"reconnect\">"));
|
|
PT_WAIT_THREAD(&s->generate_pt,
|
|
enqueue_chunk(s, 0, "<button>MQTT Reconnect</button>"));
|
|
PT_WAIT_THREAD(&s->generate_pt,
|
|
enqueue_chunk(s, 0, "</form>"));
|
|
|
|
PT_WAIT_THREAD(&s->generate_pt, enqueue_chunk(s, 1, http_bottom));
|
|
|
|
PT_END(&s->generate_pt);
|
|
}
|
|
#endif
|
|
/*---------------------------------------------------------------------------*/
|
|
#if CC26XX_WEB_DEMO_NET_UART
|
|
static
|
|
PT_THREAD(generate_net_uart_config(struct httpd_state *s))
|
|
{
|
|
|
|
PT_BEGIN(&s->generate_pt);
|
|
|
|
/* Generate top matter (doctype, title, nav links etc) */
|
|
PT_WAIT_THREAD(&s->generate_pt,
|
|
generate_top_matter(s, http_net_cfg_page.title,
|
|
http_config_css));
|
|
|
|
/* Net-UART settings */
|
|
PT_WAIT_THREAD(&s->generate_pt,
|
|
enqueue_chunk(s, 0, "<h1>%s</h1>", http_net_cfg_page.title));
|
|
|
|
PT_WAIT_THREAD(&s->generate_pt,
|
|
enqueue_chunk(s, 0,
|
|
"<form name=\"input\" action=\"%s\" ",
|
|
http_net_cfg_page.filename));
|
|
PT_WAIT_THREAD(&s->generate_pt,
|
|
enqueue_chunk(s, 0, "method=\"post\" enctype=\""));
|
|
PT_WAIT_THREAD(&s->generate_pt,
|
|
enqueue_chunk(s, 0, "application/x-www-form-urlencoded\" "));
|
|
PT_WAIT_THREAD(&s->generate_pt,
|
|
enqueue_chunk(s, 0, "accept-charset=\"UTF-8\">"));
|
|
|
|
PT_WAIT_THREAD(&s->generate_pt,
|
|
enqueue_chunk(s, 0, "%sRemote IPv6:%s", config_div_left,
|
|
config_div_close));
|
|
PT_WAIT_THREAD(&s->generate_pt,
|
|
enqueue_chunk(s, 0, "%s<input type=\"text\" ",
|
|
config_div_right));
|
|
PT_WAIT_THREAD(&s->generate_pt,
|
|
enqueue_chunk(s, 0, "value=\"%s\" ",
|
|
cc26xx_web_demo_config.net_uart.remote_address));
|
|
PT_WAIT_THREAD(&s->generate_pt,
|
|
enqueue_chunk(s, 0, "name=\"net_uart_ip\">%s",
|
|
config_div_close));
|
|
|
|
PT_WAIT_THREAD(&s->generate_pt,
|
|
enqueue_chunk(s, 0, "%sRemote Port:%s", config_div_left,
|
|
config_div_close));
|
|
PT_WAIT_THREAD(&s->generate_pt,
|
|
enqueue_chunk(s, 0, "%s<input type=\"number\" ",
|
|
config_div_right));
|
|
PT_WAIT_THREAD(&s->generate_pt,
|
|
enqueue_chunk(s, 0, "value=\"%u\" ",
|
|
cc26xx_web_demo_config.net_uart.remote_port));
|
|
PT_WAIT_THREAD(&s->generate_pt,
|
|
enqueue_chunk(s, 0, "min=\"1\" max=\"65535\" "
|
|
"name=\"net_uart_port\">%s",
|
|
config_div_close));
|
|
|
|
PT_WAIT_THREAD(&s->generate_pt,
|
|
enqueue_chunk(s, 0, "%s%s:%s%s", config_div_left,
|
|
"Enable", config_div_close,
|
|
config_div_right));
|
|
|
|
PT_WAIT_THREAD(&s->generate_pt,
|
|
enqueue_chunk(s, 0, "<input type=\"radio\" value=\"1\" "));
|
|
PT_WAIT_THREAD(&s->generate_pt,
|
|
enqueue_chunk(s, 0, "title=\"On\" name=\"net_uart_on\"%s>",
|
|
cc26xx_web_demo_config.net_uart.enable ?
|
|
" Checked" : ""));
|
|
PT_WAIT_THREAD(&s->generate_pt,
|
|
enqueue_chunk(s, 0, "<input type=\"radio\" value=\"0\" "));
|
|
PT_WAIT_THREAD(&s->generate_pt,
|
|
enqueue_chunk(s, 0, "title=\"Off\" name=\"net_uart_on\""
|
|
"%s>%s",
|
|
cc26xx_web_demo_config.net_uart.enable ?
|
|
"" : " Checked", config_div_close));
|
|
|
|
PT_WAIT_THREAD(&s->generate_pt,
|
|
enqueue_chunk(s, 0,
|
|
"<input type=\"submit\" value=\"Submit\">"));
|
|
PT_WAIT_THREAD(&s->generate_pt,
|
|
enqueue_chunk(s, 0, "</form>"));
|
|
|
|
PT_WAIT_THREAD(&s->generate_pt, enqueue_chunk(s, 1, http_bottom));
|
|
|
|
PT_END(&s->generate_pt);
|
|
}
|
|
#endif
|
|
/*---------------------------------------------------------------------------*/
|
|
static void
|
|
lock_obtain(struct httpd_state *s)
|
|
{
|
|
if(lock == NULL) {
|
|
lock = s;
|
|
}
|
|
}
|
|
/*---------------------------------------------------------------------------*/
|
|
static void
|
|
lock_release(struct httpd_state *s)
|
|
{
|
|
if(lock == s) {
|
|
lock = NULL;
|
|
}
|
|
}
|
|
/*---------------------------------------------------------------------------*/
|
|
static void
|
|
parse_post_request_chunk(char *buf, int buf_len, int last_chunk)
|
|
{
|
|
int i;
|
|
int finish;
|
|
|
|
for(i = 0; i < buf_len; i++) {
|
|
switch(state) {
|
|
case PARSE_POST_STATE_INIT:
|
|
state = PARSE_POST_STATE_MORE;
|
|
/* continue */
|
|
case PARSE_POST_STATE_MORE:
|
|
memset(key, 0, PARSE_POST_BUF_SIZES);
|
|
memset(val, 0, PARSE_POST_BUF_SIZES);
|
|
memset(val_escaped, 0, PARSE_POST_BUF_SIZES);
|
|
key_len = 0;
|
|
val_len = 0;
|
|
state = PARSE_POST_STATE_READING_KEY;
|
|
/* continue */
|
|
case PARSE_POST_STATE_READING_KEY:
|
|
if(buf[i] == ISO_equal) {
|
|
state = PARSE_POST_STATE_READING_VAL;
|
|
} else if(buf[i] == ISO_amp) {
|
|
/* Don't accept an amp while reading a key */
|
|
state = PARSE_POST_STATE_ERROR;
|
|
} else {
|
|
/* Make sure we don't overshoot key's boundary */
|
|
if(key_len <= PARSE_POST_MAX_POS) {
|
|
key[key_len] = buf[i];
|
|
key_len++;
|
|
} else {
|
|
/* Not enough space for the key. Abort */
|
|
state = PARSE_POST_STATE_ERROR;
|
|
}
|
|
}
|
|
break;
|
|
case PARSE_POST_STATE_READING_VAL:
|
|
finish = 0;
|
|
if(buf[i] == ISO_amp) {
|
|
finish = 1;
|
|
} else if(buf[i] == ISO_equal) {
|
|
/* Don't accept an '=' while reading a val */
|
|
state = PARSE_POST_STATE_ERROR;
|
|
} else {
|
|
/* Make sure we don't overshoot key's boundary */
|
|
if(val_len <= PARSE_POST_MAX_POS) {
|
|
val[val_len] = buf[i];
|
|
val_len++;
|
|
/* Last character of the last chunk */
|
|
if((i == buf_len - 1) && (last_chunk == 1)) {
|
|
finish = 1;
|
|
}
|
|
} else {
|
|
/* Not enough space for the value. Abort */
|
|
state = PARSE_POST_STATE_ERROR;
|
|
}
|
|
}
|
|
|
|
if(finish == 1) {
|
|
/*
|
|
* Done reading a key=value pair, either because we encountered an amp
|
|
* or because we reached the end of the message body.
|
|
*
|
|
* First, unescape the value.
|
|
*
|
|
* Then invoke handlers. We will bail out with PARSE_POST_STATE_ERROR,
|
|
* unless the key-val gets correctly processed
|
|
*/
|
|
url_unescape(val, val_len, val_escaped, PARSE_POST_BUF_SIZES);
|
|
val_len = strlen(val_escaped);
|
|
|
|
for(handler = list_head(post_handlers); handler != NULL;
|
|
handler = list_item_next((void *)handler)) {
|
|
if(handler->handler != NULL) {
|
|
finish = handler->handler(key, key_len, val_escaped, val_len);
|
|
}
|
|
if(finish == HTTPD_SIMPLE_POST_HANDLER_ERROR) {
|
|
state = PARSE_POST_STATE_ERROR;
|
|
break;
|
|
} else if(finish == HTTPD_SIMPLE_POST_HANDLER_OK) {
|
|
/* Restart the state machine to expect the next pair */
|
|
state = PARSE_POST_STATE_MORE;
|
|
|
|
/*
|
|
* At least one handler returned OK, therefore we must generate a
|
|
* new config event when we're done.
|
|
*/
|
|
config_ok = 1;
|
|
break;
|
|
}
|
|
/* Else, continue */
|
|
}
|
|
}
|
|
break;
|
|
case PARSE_POST_STATE_ERROR:
|
|
/* If we entered the error state earlier, do nothing */
|
|
return;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
/*---------------------------------------------------------------------------*/
|
|
static httpd_simple_script_t
|
|
get_script(const char *name)
|
|
{
|
|
page_t *page;
|
|
|
|
for(page = list_head(pages_list); page != NULL;
|
|
page = list_item_next(page)) {
|
|
if(strncmp(name, page->filename, strlen(page->filename)) == 0) {
|
|
return page->script;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
/*---------------------------------------------------------------------------*/
|
|
static
|
|
PT_THREAD(send_string(struct httpd_state *s, const char *str))
|
|
{
|
|
PSOCK_BEGIN(&s->sout);
|
|
|
|
SEND_STRING(&s->sout, str);
|
|
|
|
PSOCK_END(&s->sout);
|
|
}
|
|
/*---------------------------------------------------------------------------*/
|
|
static
|
|
PT_THREAD(send_headers(struct httpd_state *s, const char *statushdr,
|
|
const char *content_type, const char *redir,
|
|
const char **additional))
|
|
{
|
|
PT_BEGIN(&s->generate_pt);
|
|
|
|
PT_WAIT_THREAD(&s->generate_pt, enqueue_chunk(s, 0, statushdr));
|
|
|
|
for(s->ptr = http_header_srv_str; *(s->ptr) != NULL; s->ptr++) {
|
|
PT_WAIT_THREAD(&s->generate_pt, enqueue_chunk(s, 0, *(s->ptr)));
|
|
}
|
|
|
|
if(redir) {
|
|
PT_WAIT_THREAD(&s->generate_pt,
|
|
enqueue_chunk(s, 0, "Location: %s\r\n", redir));
|
|
}
|
|
|
|
if(additional) {
|
|
for(s->ptr = additional; *(s->ptr) != NULL; s->ptr++) {
|
|
PT_WAIT_THREAD(&s->generate_pt, enqueue_chunk(s, 0, *(s->ptr)));
|
|
}
|
|
}
|
|
|
|
PT_WAIT_THREAD(&s->generate_pt,
|
|
enqueue_chunk(s, 0, "Content-type: %s; ", content_type));
|
|
|
|
PT_WAIT_THREAD(&s->generate_pt, enqueue_chunk(s, 1, "charset=UTF-8\r\n\r\n"));
|
|
|
|
PT_END(&s->generate_pt);
|
|
}
|
|
/*---------------------------------------------------------------------------*/
|
|
static
|
|
PT_THREAD(handle_output(struct httpd_state *s))
|
|
{
|
|
PT_BEGIN(&s->outputpt);
|
|
|
|
s->script = NULL;
|
|
|
|
PT_INIT(&s->generate_pt);
|
|
PT_INIT(&s->top_matter_pt);
|
|
|
|
if(s->request_type == REQUEST_TYPE_POST) {
|
|
if(s->return_code == RETURN_CODE_OK) {
|
|
PT_WAIT_THREAD(&s->outputpt, send_headers(s, http_header_302,
|
|
http_content_type_plain,
|
|
s->filename,
|
|
NULL));
|
|
} else if(s->return_code == RETURN_CODE_LR) {
|
|
PT_WAIT_THREAD(&s->outputpt, send_headers(s, http_header_411,
|
|
http_content_type_plain,
|
|
NULL,
|
|
http_header_con_close));
|
|
PT_WAIT_THREAD(&s->outputpt, send_string(s, "Content-Length Required\n"));
|
|
} else if(s->return_code == RETURN_CODE_TL) {
|
|
PT_WAIT_THREAD(&s->outputpt, send_headers(s, http_header_413,
|
|
http_content_type_plain,
|
|
NULL,
|
|
http_header_con_close));
|
|
PT_WAIT_THREAD(&s->outputpt, send_string(s, "Content-Length too Large\n"));
|
|
} else if(s->return_code == RETURN_CODE_SU) {
|
|
PT_WAIT_THREAD(&s->outputpt, send_headers(s, http_header_503,
|
|
http_content_type_plain,
|
|
NULL,
|
|
http_header_con_close));
|
|
PT_WAIT_THREAD(&s->outputpt, send_string(s, "Service Unavailable\n"));
|
|
} else {
|
|
PT_WAIT_THREAD(&s->outputpt, send_headers(s, http_header_400,
|
|
http_content_type_plain,
|
|
NULL,
|
|
http_header_con_close));
|
|
PT_WAIT_THREAD(&s->outputpt, send_string(s, "Bad Request\n"));
|
|
}
|
|
} else if(s->request_type == REQUEST_TYPE_GET) {
|
|
s->script = get_script(&s->filename[1]);
|
|
if(s->script == NULL) {
|
|
strncpy(s->filename, "/notfound.html", sizeof(s->filename));
|
|
PT_WAIT_THREAD(&s->outputpt, send_headers(s, http_header_404,
|
|
http_content_type_html,
|
|
NULL,
|
|
http_header_con_close));
|
|
PT_WAIT_THREAD(&s->outputpt,
|
|
send_string(s, NOT_FOUND));
|
|
uip_close();
|
|
PT_EXIT(&s->outputpt);
|
|
} else {
|
|
PT_WAIT_THREAD(&s->outputpt, send_headers(s, http_header_200,
|
|
http_content_type_html,
|
|
NULL,
|
|
http_header_con_close));
|
|
PT_WAIT_THREAD(&s->outputpt, s->script(s));
|
|
}
|
|
}
|
|
s->script = NULL;
|
|
PSOCK_CLOSE(&s->sout);
|
|
PT_END(&s->outputpt);
|
|
}
|
|
/*---------------------------------------------------------------------------*/
|
|
static
|
|
PT_THREAD(handle_input(struct httpd_state *s))
|
|
{
|
|
PSOCK_BEGIN(&s->sin);
|
|
|
|
PSOCK_READTO(&s->sin, ISO_space);
|
|
|
|
if(strncasecmp(s->inputbuf, http_get, 4) == 0) {
|
|
s->request_type = REQUEST_TYPE_GET;
|
|
PSOCK_READTO(&s->sin, ISO_space);
|
|
|
|
if(s->inputbuf[0] != ISO_slash) {
|
|
PSOCK_CLOSE_EXIT(&s->sin);
|
|
}
|
|
|
|
if(s->inputbuf[1] == ISO_space) {
|
|
strncpy(s->filename, http_index_html, sizeof(s->filename));
|
|
} else {
|
|
s->inputbuf[PSOCK_DATALEN(&s->sin) - 1] = 0;
|
|
strncpy(s->filename, s->inputbuf, sizeof(s->filename));
|
|
}
|
|
} else if(strncasecmp(s->inputbuf, http_post, 5) == 0) {
|
|
s->request_type = REQUEST_TYPE_POST;
|
|
PSOCK_READTO(&s->sin, ISO_space);
|
|
|
|
if(s->inputbuf[0] != ISO_slash) {
|
|
PSOCK_CLOSE_EXIT(&s->sin);
|
|
}
|
|
|
|
s->inputbuf[PSOCK_DATALEN(&s->sin) - 1] = 0;
|
|
strncpy(s->filename, s->inputbuf, sizeof(s->filename));
|
|
|
|
/* POST: Read out the rest of the line and ignore it */
|
|
PSOCK_READTO(&s->sin, ISO_nl);
|
|
|
|
/*
|
|
* Start parsing headers. We look for Content-Length and ignore everything
|
|
* else until we hit the start of the message body.
|
|
*
|
|
* We will return 411 if the client doesn't send Content-Length and 413
|
|
* if Content-Length is too high
|
|
*/
|
|
s->content_length = 0;
|
|
s->return_code = RETURN_CODE_LR;
|
|
do {
|
|
s->inputbuf[PSOCK_DATALEN(&s->sin)] = 0;
|
|
/* We anticipate a content length */
|
|
if((PSOCK_DATALEN(&s->sin) > 14) &&
|
|
strncasecmp(s->inputbuf, "Content-Length:", 15) == 0) {
|
|
char *val_start = &s->inputbuf[15];
|
|
s->content_length = atoi(val_start);
|
|
|
|
/* So far so good */
|
|
s->return_code = RETURN_CODE_OK;
|
|
}
|
|
PSOCK_READTO(&s->sin, ISO_nl);
|
|
} while(PSOCK_DATALEN(&s->sin) != 2);
|
|
|
|
/*
|
|
* Done reading headers.
|
|
* Reject content length greater than CONTENT_LENGTH_MAX bytes
|
|
*/
|
|
if(s->content_length > CONTENT_LENGTH_MAX) {
|
|
s->content_length = 0;
|
|
s->return_code = RETURN_CODE_TL;
|
|
}
|
|
|
|
if(s->return_code == RETURN_CODE_OK) {
|
|
/* Acceptable Content Length. Try to obtain a lock */
|
|
lock_obtain(s);
|
|
|
|
if(lock == s) {
|
|
state = PARSE_POST_STATE_INIT;
|
|
} else {
|
|
s->return_code = RETURN_CODE_SU;
|
|
}
|
|
}
|
|
|
|
/* Parse the message body, unless we have detected an error. */
|
|
while(s->content_length > 0 && lock == s &&
|
|
s->return_code == RETURN_CODE_OK) {
|
|
PSOCK_READBUF_LEN(&s->sin, s->content_length);
|
|
s->content_length -= PSOCK_DATALEN(&s->sin);
|
|
|
|
/* Parse the message body */
|
|
parse_post_request_chunk(s->inputbuf, PSOCK_DATALEN(&s->sin),
|
|
(s->content_length == 0));
|
|
if(state == PARSE_POST_STATE_ERROR) {
|
|
/* Could not parse: Bad Request and stop parsing */
|
|
s->return_code = RETURN_CODE_BR;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Done. If our return code is OK but the state machine is not in
|
|
* STATE_MORE, it means that the message body ended half-way reading a key
|
|
* or value. Set 'Bad Request'
|
|
*/
|
|
if(s->return_code == RETURN_CODE_OK && state != PARSE_POST_STATE_MORE) {
|
|
s->return_code = RETURN_CODE_BR;
|
|
}
|
|
|
|
/* If the flag is set, we had at least 1 configuration value accepted */
|
|
if(config_ok) {
|
|
process_post(PROCESS_BROADCAST, httpd_simple_event_new_config, NULL);
|
|
}
|
|
config_ok = 0;
|
|
|
|
lock_release(s);
|
|
} else {
|
|
PSOCK_CLOSE_EXIT(&s->sin);
|
|
}
|
|
|
|
s->state = STATE_OUTPUT;
|
|
|
|
while(1) {
|
|
PSOCK_READTO(&s->sin, ISO_nl);
|
|
}
|
|
|
|
PSOCK_END(&s->sin);
|
|
}
|
|
/*---------------------------------------------------------------------------*/
|
|
static void
|
|
handle_connection(struct httpd_state *s)
|
|
{
|
|
handle_input(s);
|
|
if(s->state == STATE_OUTPUT) {
|
|
handle_output(s);
|
|
}
|
|
}
|
|
/*---------------------------------------------------------------------------*/
|
|
static void
|
|
appcall(void *state)
|
|
{
|
|
struct httpd_state *s = (struct httpd_state *)state;
|
|
|
|
if(uip_closed() || uip_aborted() || uip_timedout()) {
|
|
if(s != NULL) {
|
|
memset(s, 0, sizeof(struct httpd_state));
|
|
memb_free(&conns, s);
|
|
}
|
|
} else if(uip_connected()) {
|
|
s = (struct httpd_state *)memb_alloc(&conns);
|
|
if(s == NULL) {
|
|
uip_abort();
|
|
return;
|
|
}
|
|
tcp_markconn(uip_conn, s);
|
|
PSOCK_INIT(&s->sin, (uint8_t *)s->inputbuf, sizeof(s->inputbuf) - 1);
|
|
PSOCK_INIT(&s->sout, (uint8_t *)s->inputbuf, sizeof(s->inputbuf) - 1);
|
|
PT_INIT(&s->outputpt);
|
|
s->script = NULL;
|
|
s->state = STATE_WAITING;
|
|
timer_set(&s->timer, CLOCK_SECOND * 10);
|
|
handle_connection(s);
|
|
} else if(s != NULL) {
|
|
if(uip_poll()) {
|
|
if(timer_expired(&s->timer)) {
|
|
uip_abort();
|
|
memset(s, 0, sizeof(struct httpd_state));
|
|
memb_free(&conns, s);
|
|
}
|
|
} else {
|
|
timer_restart(&s->timer);
|
|
}
|
|
handle_connection(s);
|
|
} else {
|
|
uip_abort();
|
|
}
|
|
}
|
|
/*---------------------------------------------------------------------------*/
|
|
static void
|
|
init(void)
|
|
{
|
|
tcp_listen(UIP_HTONS(80));
|
|
memb_init(&conns);
|
|
|
|
list_add(pages_list, &http_index_page);
|
|
list_add(pages_list, &http_dev_cfg_page);
|
|
|
|
#if CC26XX_WEB_DEMO_NET_UART
|
|
list_add(pages_list, &http_net_cfg_page);
|
|
#endif
|
|
|
|
#if CC26XX_WEB_DEMO_MQTT_CLIENT
|
|
list_add(pages_list, &http_mqtt_cfg_page);
|
|
#endif
|
|
}
|
|
/*---------------------------------------------------------------------------*/
|
|
PROCESS_THREAD(httpd_simple_process, ev, data)
|
|
{
|
|
PROCESS_BEGIN();
|
|
|
|
printf("CC26XX Web Server\n");
|
|
|
|
httpd_simple_event_new_config = process_alloc_event();
|
|
|
|
init();
|
|
|
|
snprintf(http_mqtt_a, IBM_QUICKSTART_LINK_LEN,
|
|
"[ <a href=\"http://quickstart.internetofthings.ibmcloud.com/#/device/"
|
|
"%02x%02x%02x%02x%02x%02x/sensor/\">IBM Quickstart</a> ]",
|
|
linkaddr_node_addr.u8[0], linkaddr_node_addr.u8[1],
|
|
linkaddr_node_addr.u8[2], linkaddr_node_addr.u8[5],
|
|
linkaddr_node_addr.u8[6], linkaddr_node_addr.u8[7]);
|
|
|
|
while(1) {
|
|
PROCESS_WAIT_EVENT_UNTIL(ev == tcpip_event);
|
|
appcall(data);
|
|
}
|
|
|
|
PROCESS_END();
|
|
}
|
|
/*---------------------------------------------------------------------------*/
|
|
/**
|
|
* @}
|
|
*/
|