Merge pull request #6638 from Neradoc/nera-web-workflow

Refine web workflow access control and http responses
This commit is contained in:
Scott Shawcroft 2022-07-25 15:48:15 -07:00 committed by GitHub
commit 39a06395fa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

View File

@ -350,38 +350,40 @@ static bool _endswith(const char *str, const char *suffix) {
return strcmp(str + (strlen(str) - strlen(suffix)), suffix) == 0; return strcmp(str + (strlen(str) - strlen(suffix)), suffix) == 0;
} }
const char *ok_hosts[] = {"code.circuitpython.org"}; const char *ok_hosts[] = {
"code.circuitpython.org",
"127.0.0.1",
"localhost",
};
static bool _origin_ok(const char *origin) { static bool _origin_ok(const char *origin) {
const char *http = "http://"; const char *http = "http://";
const char *local = ".local"; const char *local = ".local";
if (memcmp(origin, http, strlen(http)) != 0) { // note: redirected requests send an Origin of "null" and will be caught by this
if (strncmp(origin, http, strlen(http)) != 0) {
return false; return false;
} }
// These are prefix checks up to : so that any port works. // These are prefix checks up to : so that any port works.
const char *hostname = common_hal_mdns_server_get_hostname(&mdns); const char *hostname = common_hal_mdns_server_get_hostname(&mdns);
const char *end = origin + strlen(http) + strlen(hostname) + strlen(local); const char *end = origin + strlen(http) + strlen(hostname) + strlen(local);
if (memcmp(origin + strlen(http), hostname, strlen(hostname)) == 0 && if (strncmp(origin + strlen(http), hostname, strlen(hostname)) == 0 &&
memcmp(origin + strlen(http) + strlen(hostname), local, strlen(local)) == 0 && strncmp(origin + strlen(http) + strlen(hostname), local, strlen(local)) == 0 &&
(end[0] == '\0' || end[0] == ':')) { (end[0] == '\0' || end[0] == ':')) {
return true; return true;
} }
end = origin + strlen(http) + strlen(_our_ip_encoded); end = origin + strlen(http) + strlen(_our_ip_encoded);
if (memcmp(origin + strlen(http), _our_ip_encoded, strlen(_our_ip_encoded)) == 0 && if (strncmp(origin + strlen(http), _our_ip_encoded, strlen(_our_ip_encoded)) == 0 &&
(end[0] == '\0' || end[0] == ':')) { (end[0] == '\0' || end[0] == ':')) {
return true; return true;
} }
const char *localhost = "127.0.0.1:";
if (memcmp(origin + strlen(http), localhost, strlen(localhost)) == 0) {
return true;
}
for (size_t i = 0; i < MP_ARRAY_SIZE(ok_hosts); i++) { for (size_t i = 0; i < MP_ARRAY_SIZE(ok_hosts); i++) {
// This checks exactly. // Allows any port
if (strcmp(origin + strlen(http), ok_hosts[i]) == 0) { end = origin + strlen(http) + strlen(ok_hosts[i]);
if (strncmp(origin + strlen(http), ok_hosts[i], strlen(ok_hosts[i])) == 0
&& (end[0] == '\0' || end[0] == ':')) {
return true; return true;
} }
} }
@ -908,8 +910,11 @@ static bool _reply(socketpool_socket_obj_t *socket, _request *request) {
} else if (strlen(request->origin) > 0 && !_origin_ok(request->origin)) { } else if (strlen(request->origin) > 0 && !_origin_ok(request->origin)) {
ESP_LOGE(TAG, "bad origin %s", request->origin); ESP_LOGE(TAG, "bad origin %s", request->origin);
_reply_forbidden(socket, request); _reply_forbidden(socket, request);
} else if (memcmp(request->path, "/fs/", 4) == 0) { } else if (strncmp(request->path, "/fs/", 4) == 0) {
if (!request->authenticated) { if (strcasecmp(request->method, "OPTIONS") == 0) {
// OPTIONS is sent for CORS preflight, unauthenticated
_reply_access_control(socket, request);
} else if (!request->authenticated) {
if (_api_password[0] != '\0') { if (_api_password[0] != '\0') {
_reply_unauthorized(socket, request); _reply_unauthorized(socket, request);
} else { } else {
@ -930,9 +935,7 @@ static bool _reply(socketpool_socket_obj_t *socket, _request *request) {
} }
// Delete is almost identical for files and directories so share the // Delete is almost identical for files and directories so share the
// implementation. // implementation.
if (strcmp(request->method, "OPTIONS") == 0) { if (strcasecmp(request->method, "DELETE") == 0) {
_reply_access_control(socket, request);
} else if (strcmp(request->method, "DELETE") == 0) {
if (_usb_active()) { if (_usb_active()) {
_reply_conflict(socket, request); _reply_conflict(socket, request);
return false; return false;
@ -962,7 +965,7 @@ static bool _reply(socketpool_socket_obj_t *socket, _request *request) {
return true; return true;
} }
} else if (directory) { } else if (directory) {
if (strcmp(request->method, "GET") == 0) { if (strcasecmp(request->method, "GET") == 0) {
FF_DIR dir; FF_DIR dir;
FRESULT res = f_opendir(fs, &dir, path); FRESULT res = f_opendir(fs, &dir, path);
// Put the / back for replies. // Put the / back for replies.
@ -982,7 +985,7 @@ static bool _reply(socketpool_socket_obj_t *socket, _request *request) {
} }
f_closedir(&dir); f_closedir(&dir);
} else if (strcmp(request->method, "PUT") == 0) { } else if (strcasecmp(request->method, "PUT") == 0) {
if (_usb_active()) { if (_usb_active()) {
_reply_conflict(socket, request); _reply_conflict(socket, request);
return false; return false;
@ -1011,7 +1014,7 @@ static bool _reply(socketpool_socket_obj_t *socket, _request *request) {
} }
} }
} else { // Dealing with a file. } else { // Dealing with a file.
if (strcmp(request->method, "GET") == 0) { if (strcasecmp(request->method, "GET") == 0) {
FIL active_file; FIL active_file;
FRESULT result = f_open(fs, &active_file, path, FA_READ); FRESULT result = f_open(fs, &active_file, path, FA_READ);
@ -1022,15 +1025,18 @@ static bool _reply(socketpool_socket_obj_t *socket, _request *request) {
} }
f_close(&active_file); f_close(&active_file);
} else if (strcmp(request->method, "PUT") == 0) { } else if (strcasecmp(request->method, "PUT") == 0) {
_write_file_and_reply(socket, request, fs, path); _write_file_and_reply(socket, request, fs, path);
return true; return true;
} }
} }
} }
} else if (memcmp(request->path, "/cp/", 4) == 0) { } else if (strncmp(request->path, "/cp/", 4) == 0) {
const char *path = request->path + 3; const char *path = request->path + 3;
if (strcmp(request->method, "GET") != 0) { if (strcasecmp(request->method, "OPTIONS") == 0) {
// handle preflight requests to /cp/
_reply_access_control(socket, request);
} else if (strcasecmp(request->method, "GET") != 0) {
_reply_method_not_allowed(socket, request); _reply_method_not_allowed(socket, request);
} else if (strcmp(path, "/devices.json") == 0) { } else if (strcmp(path, "/devices.json") == 0) {
_reply_with_devices_json(socket, request); _reply_with_devices_json(socket, request);
@ -1051,7 +1057,7 @@ static bool _reply(socketpool_socket_obj_t *socket, _request *request) {
} else { } else {
_reply_missing(socket, request); _reply_missing(socket, request);
} }
} else if (strcmp(request->method, "GET") != 0) { } else if (strcasecmp(request->method, "GET") != 0) {
_reply_method_not_allowed(socket, request); _reply_method_not_allowed(socket, request);
} else { } else {
if (strcmp(request->path, "/") == 0) { if (strcmp(request->path, "/") == 0) {
@ -1168,27 +1174,27 @@ static void _process_request(socketpool_socket_obj_t *socket, _request *request)
request->header_value[request->offset - 1] = '\0'; request->header_value[request->offset - 1] = '\0';
request->offset = 0; request->offset = 0;
request->state = STATE_HEADER_KEY; request->state = STATE_HEADER_KEY;
if (strcmp(request->header_key, "Authorization") == 0) { if (strcasecmp(request->header_key, "Authorization") == 0) {
const char *prefix = "Basic "; const char *prefix = "Basic ";
request->authenticated = memcmp(request->header_value, prefix, strlen(prefix)) == 0 && request->authenticated = strncmp(request->header_value, prefix, strlen(prefix)) == 0 &&
strcmp(_api_password, request->header_value + strlen(prefix)) == 0; strcmp(_api_password, request->header_value + strlen(prefix)) == 0;
} else if (strcmp(request->header_key, "Host") == 0) { } else if (strcasecmp(request->header_key, "Host") == 0) {
request->redirect = strcmp(request->header_value, "circuitpython.local") == 0; request->redirect = strcmp(request->header_value, "circuitpython.local") == 0;
} else if (strcmp(request->header_key, "Content-Length") == 0) { } else if (strcasecmp(request->header_key, "Content-Length") == 0) {
request->content_length = strtoul(request->header_value, NULL, 10); request->content_length = strtoul(request->header_value, NULL, 10);
} else if (strcmp(request->header_key, "Expect") == 0) { } else if (strcasecmp(request->header_key, "Expect") == 0) {
request->expect = strcmp(request->header_value, "100-continue") == 0; request->expect = strcmp(request->header_value, "100-continue") == 0;
} else if (strcmp(request->header_key, "Accept") == 0) { } else if (strcasecmp(request->header_key, "Accept") == 0) {
request->json = strcmp(request->header_value, "application/json") == 0; request->json = strcasecmp(request->header_value, "application/json") == 0;
} else if (strcmp(request->header_key, "Origin") == 0) { } else if (strcasecmp(request->header_key, "Origin") == 0) {
strcpy(request->origin, request->header_value); strcpy(request->origin, request->header_value);
} else if (strcmp(request->header_key, "X-Timestamp") == 0) { } else if (strcasecmp(request->header_key, "X-Timestamp") == 0) {
request->timestamp_ms = strtoull(request->header_value, NULL, 10); request->timestamp_ms = strtoull(request->header_value, NULL, 10);
} else if (strcmp(request->header_key, "Upgrade") == 0) { } else if (strcasecmp(request->header_key, "Upgrade") == 0) {
request->websocket = strcmp(request->header_value, "websocket") == 0; request->websocket = strcmp(request->header_value, "websocket") == 0;
} else if (strcmp(request->header_key, "Sec-WebSocket-Version") == 0) { } else if (strcasecmp(request->header_key, "Sec-WebSocket-Version") == 0) {
request->websocket_version = strtoul(request->header_value, NULL, 10); request->websocket_version = strtoul(request->header_value, NULL, 10);
} else if (strcmp(request->header_key, "Sec-WebSocket-Key") == 0 && } else if (strcasecmp(request->header_key, "Sec-WebSocket-Key") == 0 &&
strlen(request->header_value) == 24) { strlen(request->header_value) == 24) {
strcpy(request->websocket_key, request->header_value); strcpy(request->websocket_key, request->header_value);
} }