Skip to content

Commit 139af0c

Browse files
authored
Handle tcp fragmentation and also fix XSS problem. (#3275)
1 parent c000a08 commit 139af0c

File tree

5 files changed

+406
-574
lines changed

5 files changed

+406
-574
lines changed

app/modules/enduser_setup.c

+178-27
Original file line numberDiff line numberDiff line change
@@ -90,27 +90,45 @@ static const char dns_body[] = { 0x00, 0x01, 0x00, 0x01,
9090

9191
static const char http_html_gz_filename[] = "enduser_setup.html.gz";
9292
static const char http_html_filename[] = "enduser_setup.html";
93-
static const char http_header_200[] = "HTTP/1.1 200 OK\r\nCache-control:no-cache\r\nConnection:close\r\nContent-Type:text/html\r\n"; /* Note single \r\n here! */
93+
static const char http_header_200[] = "HTTP/1.1 200 OK\r\nCache-control:no-cache\r\nConnection:close\r\nContent-Type:text/html; charset=utf-8\r\n"; /* Note single \r\n here! */
9494
static const char http_header_204[] = "HTTP/1.1 204 No Content\r\nContent-Length:0\r\nConnection:close\r\n\r\n";
9595
static const char http_header_302[] = "HTTP/1.1 302 Moved\r\nLocation: /\r\nContent-Length:0\r\nConnection:close\r\n\r\n";
9696
static const char http_header_302_trying[] = "HTTP/1.1 302 Moved\r\nLocation: /?trying=true\r\nContent-Length:0\r\nConnection:close\r\n\r\n";
9797
static const char http_header_400[] = "HTTP/1.1 400 Bad request\r\nContent-Length:0\r\nConnection:close\r\n\r\n";
98-
static const char http_header_404[] = "HTTP/1.1 404 Not found\r\nContent-Length:0\r\nConnection:close\r\n\r\n";
98+
static const char http_header_404[] = "HTTP/1.1 404 Not found\r\nContent-Length:10\r\nConnection:close\r\n\r\nNot found\n";
9999
static const char http_header_405[] = "HTTP/1.1 405 Method Not Allowed\r\nContent-Length:0\r\nConnection:close\r\n\r\n";
100-
static const char http_header_500[] = "HTTP/1.1 500 Internal Error\r\nContent-Length:0\r\nConnection:close\r\n\r\n";
100+
static const char http_header_500[] = "HTTP/1.1 500 Internal Error\r\nContent-Length:6\r\nConnection:close\r\n\r\nError\n";
101101

102102
static const char http_header_content_len_fmt[] = "Content-length:%5d\r\n\r\n";
103103
static const char http_html_gzip_contentencoding[] = "Content-Encoding: gzip\r\n";
104104

105105
/* Externally defined: static const char enduser_setup_html_default[] = ... */
106106
#include "enduser_setup/enduser_setup.html.gz.def.h"
107107

108+
// The tcp_arg can be either a pointer to the scan_listener_t or http_request_buffer_t.
109+
// The enum defines which one it is.
110+
typedef enum {
111+
SCAN_LISTENER_STRUCT_TYPE = 1,
112+
HTTP_REQUEST_BUFFER_STRUCT_TYPE = 2
113+
} struct_type_t;
114+
115+
typedef struct {
116+
struct_type_t struct_type;
117+
} tcp_arg_t;
118+
108119
typedef struct scan_listener
109120
{
121+
struct_type_t struct_type;
110122
struct tcp_pcb *conn;
111123
struct scan_listener *next;
112124
} scan_listener_t;
113125

126+
typedef struct {
127+
struct_type_t struct_type;
128+
size_t length;
129+
char data[0];
130+
} http_request_buffer_t;
131+
114132
typedef struct
115133
{
116134
struct espconn *espconn_dns_udp;
@@ -398,8 +416,8 @@ static err_t close_once_sent (void *arg, struct tcp_pcb *pcb, u16_t len)
398416

399417
/**
400418
* Get length of param value
401-
*
402-
* This is being called with a fragment of the parameters passed in the
419+
*
420+
* This is being called with a fragment of the parameters passed in the
403421
* URL for GET requests or part of the body of a POST request.
404422
* The string will look like one of these
405423
* "SecretPassword HTTP/1.1"
@@ -1124,14 +1142,13 @@ static void enduser_setup_handle_POST(struct tcp_pcb *http_client, char* data, s
11241142
if (strncmp(data + 5, "/setwifi ", 9) == 0) // User clicked the submit button
11251143
{
11261144
char* body=strstr(data, "\r\n\r\n");
1127-
char *content_length_str = strstr(data, "Content-Length: ");
1128-
if( body == NULL || content_length_str == NULL)
1145+
if( body == NULL)
11291146
{
11301147
enduser_setup_http_serve_header(http_client, http_header_400, LITLEN(http_header_400));
11311148
return;
11321149
}
1133-
int bodylength = atoi(content_length_str + 16);
11341150
body += 4; // length of the double CRLF found above
1151+
int bodylength = (data + data_len) - body;
11351152
switch (enduser_setup_http_handle_credentials(body, bodylength))
11361153
{
11371154
case 0: {
@@ -1148,6 +1165,8 @@ static void enduser_setup_handle_POST(struct tcp_pcb *http_client, char* data, s
11481165
ENDUSER_SETUP_ERROR_VOID("http_recvcb failed. Failed to handle wifi credentials.", ENDUSER_SETUP_ERR_UNKOWN_ERROR, ENDUSER_SETUP_ERR_NONFATAL);
11491166
break;
11501167
}
1168+
} else {
1169+
enduser_setup_http_serve_header(http_client, http_header_404, LITLEN(http_header_404));
11511170
}
11521171
}
11531172

@@ -1333,27 +1352,154 @@ static err_t enduser_setup_http_recvcb(void *arg, struct tcp_pcb *http_client, s
13331352
return ERR_ABRT;
13341353
}
13351354

1355+
tcp_arg_t *tcp_arg_ptr = arg;
1356+
13361357
if (!p) /* remote side closed, close our end too */
13371358
{
13381359
ENDUSER_SETUP_DEBUG("connection closed");
1339-
scan_listener_t *l = arg; /* if it's waiting for scan, we have a ptr here */
1340-
if (l)
1341-
remove_scan_listener (l);
1360+
if (tcp_arg_ptr) {
1361+
if (tcp_arg_ptr->struct_type == SCAN_LISTENER_STRUCT_TYPE) {
1362+
remove_scan_listener ((scan_listener_t *)tcp_arg_ptr);
1363+
} else if (tcp_arg_ptr->struct_type == HTTP_REQUEST_BUFFER_STRUCT_TYPE) {
1364+
free(tcp_arg_ptr);
1365+
}
1366+
}
13421367

13431368
deferred_close (http_client);
13441369
return ERR_OK;
13451370
}
13461371

1347-
char *data = calloc (1, p->tot_len + 1);
1348-
if (!data)
1349-
return ERR_MEM;
1372+
http_request_buffer_t *hrb;
1373+
if (!tcp_arg_ptr) {
1374+
hrb = calloc(1, sizeof(*hrb));
1375+
if (!hrb) {
1376+
goto general_fail;
1377+
}
1378+
hrb->struct_type = HTTP_REQUEST_BUFFER_STRUCT_TYPE;
1379+
tcp_arg(http_client, hrb);
1380+
} else if (tcp_arg_ptr->struct_type == HTTP_REQUEST_BUFFER_STRUCT_TYPE) {
1381+
hrb = (http_request_buffer_t *) tcp_arg_ptr;
1382+
} else {
1383+
goto general_fail;
1384+
}
1385+
1386+
// Append the new data
1387+
size_t newlen = hrb->length + p->tot_len;
1388+
void *old_hrb = hrb;
1389+
hrb = realloc(hrb, sizeof(*hrb) + newlen + 1);
1390+
tcp_arg(http_client, hrb);
1391+
if (!hrb) {
1392+
free(old_hrb);
1393+
goto general_fail;
1394+
}
1395+
1396+
pbuf_copy_partial(p, hrb->data + hrb->length, p->tot_len, 0);
1397+
hrb->data[newlen] = 0;
1398+
hrb->length = newlen;
13501399

1351-
unsigned data_len = pbuf_copy_partial (p, data, p->tot_len, 0);
13521400
tcp_recved (http_client, p->tot_len);
13531401
pbuf_free (p);
13541402

1403+
// see if we have the whole request.
1404+
// Rely on the fact that the header should not contain a null character
1405+
char *end_of_header = strstr(hrb->data, "\r\n\r\n");
1406+
if (end_of_header == 0) {
1407+
return ERR_OK;
1408+
}
1409+
1410+
end_of_header += 4;
1411+
1412+
// We have the entire header, now see if there is any content. If we don't find the
1413+
// content-length header, then there is no content and we can process immediately.
1414+
// The content-length header can also be missing if the browser is using chunked
1415+
// encoding.
1416+
1417+
bool is_chunked = FALSE;
1418+
for (const char *hdr = hrb->data; hdr && hdr < end_of_header; hdr = strchr(hdr, '\n')) {
1419+
hdr += 1; // Skip the \n
1420+
if (strncasecmp(hdr, "transfer-encoding:", 18) == 0) {
1421+
const char *field = hdr + 18;
1422+
1423+
while (*field != '\n') {
1424+
if (memcmp(field, "chunked", 7) == 0) {
1425+
is_chunked = TRUE;
1426+
break;
1427+
}
1428+
field++;
1429+
}
1430+
}
1431+
if (strncasecmp(hdr, "Content-length:", 15) == 0) {
1432+
// There is a content-length header
1433+
const char *field = hdr + 15;
1434+
size_t extra = strtol(field + 1, 0, 10);
1435+
if (extra + (end_of_header - hrb->data) > hrb->length) {
1436+
return ERR_OK;
1437+
}
1438+
}
1439+
}
1440+
1441+
if (is_chunked) {
1442+
// More complex to determine if the whole body has arrived
1443+
// Format is one or more chunks each preceded by their length (in hex)
1444+
// A zero length chunk ends the body
1445+
const char *ptr = end_of_header;
1446+
bool seen_end = FALSE;
1447+
1448+
while (ptr < hrb->data + hrb->length && ptr > hrb->data) {
1449+
size_t chunk_len = strtol(ptr, 0, 16);
1450+
// Skip to end of chunk length (note that there can be parameters after the length)
1451+
ptr = strchr(ptr, '\n');
1452+
if (!ptr) {
1453+
// Don't have the entire chunk header
1454+
return ERR_OK;
1455+
}
1456+
ptr++;
1457+
ptr += chunk_len;
1458+
if (chunk_len == 0) {
1459+
seen_end = TRUE;
1460+
break;
1461+
}
1462+
if (ptr + 2 > hrb->data + hrb->length) {
1463+
// We don't have the CRLF yet
1464+
return ERR_OK;
1465+
}
1466+
if (memcmp(ptr, "\r\n", 2)) {
1467+
// Bail out here -- something bad happened
1468+
goto general_fail;
1469+
}
1470+
ptr += 2;
1471+
}
1472+
if (!seen_end) {
1473+
// Still waiting for the end chunk
1474+
return ERR_OK;
1475+
}
1476+
1477+
// Now rewrite the buffer to eliminate all the chunk headers
1478+
const char *src = end_of_header;
1479+
char *dst = end_of_header;
1480+
1481+
while (src < hrb->data + hrb->length && src > hrb->data) {
1482+
size_t chunk_len = strtol(src, 0, 16);
1483+
src = strchr(src, '\n');
1484+
src++;
1485+
if (chunk_len == 0) {
1486+
break;
1487+
}
1488+
memmove(dst, src, chunk_len);
1489+
dst += chunk_len;
1490+
src += chunk_len + 2;
1491+
}
1492+
*dst = '\0'; // Move the null termination down
1493+
hrb->length = dst - hrb->data; // Adjust the length down
1494+
}
1495+
13551496
err_t ret = ERR_OK;
13561497

1498+
char *data = hrb->data;
1499+
size_t data_len = hrb->length;
1500+
1501+
tcp_arg(http_client, 0); // Forget the data pointer.
1502+
13571503
#if ENDUSER_SETUP_DEBUG_SHOW_HTTP_REQUEST
13581504
ENDUSER_SETUP_DEBUG(data);
13591505
#endif
@@ -1364,7 +1510,7 @@ static err_t enduser_setup_http_recvcb(void *arg, struct tcp_pcb *http_client, s
13641510
{
13651511
if (enduser_setup_http_serve_html(http_client) != 0)
13661512
{
1367-
ENDUSER_SETUP_ERROR("http_recvcb failed. Unable to send HTML.", ENDUSER_SETUP_ERR_UNKOWN_ERROR, ENDUSER_SETUP_ERR_NONFATAL);
1513+
goto general_fail;
13681514
}
13691515
else
13701516
{
@@ -1376,27 +1522,29 @@ static err_t enduser_setup_http_recvcb(void *arg, struct tcp_pcb *http_client, s
13761522
/* Don't do an AP Scan while station is trying to connect to Wi-Fi */
13771523
if (state->connecting == 0)
13781524
{
1379-
scan_listener_t *l = malloc (sizeof (scan_listener_t));
1380-
if (!l)
1525+
scan_listener_t *sl = malloc (sizeof (scan_listener_t));
1526+
if (!sl)
13811527
{
13821528
ENDUSER_SETUP_ERROR("out of memory", ENDUSER_SETUP_ERR_OUT_OF_MEMORY, ENDUSER_SETUP_ERR_NONFATAL);
13831529
}
13841530

1531+
sl->struct_type = SCAN_LISTENER_STRUCT_TYPE;
1532+
13851533
bool already = (state->scan_listeners != NULL);
13861534

1387-
tcp_arg (http_client, l);
1535+
tcp_arg (http_client, sl);
13881536
/* TODO: check if also need a tcp_err() cb, or if recv() is enough */
1389-
l->conn = http_client;
1390-
l->next = state->scan_listeners;
1391-
state->scan_listeners = l;
1537+
sl->conn = http_client;
1538+
sl->next = state->scan_listeners;
1539+
state->scan_listeners = sl;
13921540

13931541
if (!already)
13941542
{
13951543
if (!wifi_station_scan(NULL, on_scan_done))
13961544
{
13971545
enduser_setup_http_serve_header(http_client, http_header_500, LITLEN(http_header_500));
1398-
deferred_close (l->conn);
1399-
l->conn = 0;
1546+
deferred_close (sl->conn);
1547+
sl->conn = 0;
14001548
free_scan_listeners();
14011549
}
14021550
}
@@ -1410,13 +1558,12 @@ static err_t enduser_setup_http_recvcb(void *arg, struct tcp_pcb *http_client, s
14101558
}
14111559
else if (strncmp(data + 4, "/status.json", 12) == 0)
14121560
{
1413-
enduser_setup_serve_status_as_json(http_client);
1561+
enduser_setup_serve_status_as_json(http_client);
14141562
}
14151563
else if (strncmp(data + 4, "/status", 7) == 0)
14161564
{
14171565
enduser_setup_serve_status(http_client);
14181566
}
1419-
14201567
else if (strncmp(data + 4, "/update?", 8) == 0)
14211568
{
14221569
switch (enduser_setup_http_handle_credentials(data, data_len))
@@ -1459,8 +1606,11 @@ static err_t enduser_setup_http_recvcb(void *arg, struct tcp_pcb *http_client, s
14591606
deferred_close (http_client);
14601607

14611608
free_out:
1462-
free (data);
1609+
free (hrb);
14631610
return ret;
1611+
1612+
general_fail:
1613+
ENDUSER_SETUP_ERROR("http_recvcb failed. Unable to send HTML.", ENDUSER_SETUP_ERR_UNKOWN_ERROR, ENDUSER_SETUP_ERR_NONFATAL);
14641614
}
14651615

14661616

@@ -1476,6 +1626,7 @@ static err_t enduser_setup_http_connectcb(void *arg, struct tcp_pcb *pcb, err_t
14761626
}
14771627

14781628
tcp_accepted (state->http_pcb);
1629+
tcp_arg(pcb, 0); // Initialize to known value
14791630
tcp_recv (pcb, enduser_setup_http_recvcb);
14801631
tcp_nagle_disable (pcb);
14811632
return ERR_OK;

app/modules/enduser_setup/enduser_setup.html

+6-1
Original file line numberDiff line numberDiff line change
@@ -274,8 +274,13 @@ <h4 id='st'>Updating Status...</h4>
274274
return b.rssi - a.rssi;
275275
});
276276
var ops = '<option>Select a Network...</option>';
277+
var seen = {};
277278
for (var i = 0; i < list.length; ++i) {
278-
ops += '<option>' + list[i].ssid + '</option>';
279+
var ssid = list[i].ssid;
280+
if (!seen[ssid]) {
281+
seen[ssid] = 1;
282+
ops += '<option>' + ssid.replace(/&/g, "&amp;").replace(/>/g, "&gt;").replace(/</g, "&lt;") + '</option>';
283+
}
279284
}
280285
ap.innerHTML = ops;
281286
ab.disabled = false;

0 commit comments

Comments
 (0)