Merge pull request 'feat(box3): POST /game/file over HTTP' (#12) from feat/box3-game-file into main
CI / platformio (push) Failing after 17m50s
CI / platformio (push) Failing after 17m50s
This commit was merged in pull request #12.
This commit is contained in:
@@ -22,6 +22,9 @@
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
|
||||
/* BSP SD card support (bsp_sdcard_mount, BSP_SD_MOUNT_POINT) */
|
||||
#include "bsp/esp-bsp.h"
|
||||
|
||||
#define TAG "scenario_srv"
|
||||
|
||||
#define MAX_SCENARIO_BYTES (64 * 1024)
|
||||
@@ -216,6 +219,100 @@ static esp_err_t handle_scenario_post(httpd_req_t *req) {
|
||||
return send_json(req, "200 OK", buf);
|
||||
}
|
||||
|
||||
// ---------- POST /game/file ----------
|
||||
//
|
||||
// Write binary assets directly to the SD card over HTTP.
|
||||
// Query param: ?path=sd/<relative/path> (only "sd/" prefix accepted)
|
||||
// Route: sd/<…> → /sdcard/<…> (mkdir -p, up to 8 MiB, anti-traversal).
|
||||
// Returns 503 if SD is not mounted, 403 on bad path, 200 JSON on success.
|
||||
|
||||
#define MAX_FILE_BYTES (8L * 1024L * 1024L)
|
||||
|
||||
static bool s_sd_mounted = false;
|
||||
|
||||
static esp_err_t ensure_sd_mounted(void) {
|
||||
if (s_sd_mounted) return ESP_OK;
|
||||
esp_err_t ret = bsp_sdcard_mount();
|
||||
if (ret == ESP_OK || ret == ESP_ERR_INVALID_STATE /* already mounted */) {
|
||||
s_sd_mounted = true;
|
||||
ESP_LOGI(TAG, "file: SD mounted at %s", BSP_SD_MOUNT_POINT);
|
||||
return ESP_OK;
|
||||
}
|
||||
ESP_LOGW(TAG, "file: SD mount failed: %s", esp_err_to_name(ret));
|
||||
return ret;
|
||||
}
|
||||
|
||||
static esp_err_t handle_file_post(httpd_req_t *req) {
|
||||
char query[160], path_param[96];
|
||||
if (httpd_req_get_url_query_str(req, query, sizeof(query)) != ESP_OK ||
|
||||
httpd_query_key_value(query, "path", path_param,
|
||||
sizeof(path_param)) != ESP_OK) {
|
||||
return send_error(req, "400 Bad Request", "missing path param");
|
||||
}
|
||||
|
||||
// Only accept sd/<…> prefix; reject traversal and bare directory paths.
|
||||
if (strncmp(path_param, "sd/", 3) != 0 ||
|
||||
strstr(path_param, "..") ||
|
||||
path_param[strlen(path_param) - 1] == '/') {
|
||||
return send_error(req, "403 Forbidden", "path must start with sd/ and name a file");
|
||||
}
|
||||
|
||||
if (req->content_len <= 0 || (long)req->content_len > MAX_FILE_BYTES) {
|
||||
return send_error(req, "400 Bad Request", "file too large or empty");
|
||||
}
|
||||
|
||||
if (ensure_sd_mounted() != ESP_OK) {
|
||||
return send_error(req, "503 Service Unavailable", "sd_not_mounted");
|
||||
}
|
||||
|
||||
// Build absolute destination: /sdcard/<…> (strip the "sd/" prefix).
|
||||
char full[192];
|
||||
snprintf(full, sizeof(full), "%s/%s", BSP_SD_MOUNT_POINT, path_param + 3);
|
||||
|
||||
// mkdir -p for all intermediate directories.
|
||||
const size_t root_len = strlen(BSP_SD_MOUNT_POINT) + 1; // e.g. strlen("/sdcard/")
|
||||
for (char *p = full + root_len; *p; p++) {
|
||||
if (*p == '/') {
|
||||
*p = '\0';
|
||||
mkdir(full, 0775); // EEXIST is fine
|
||||
*p = '/';
|
||||
}
|
||||
}
|
||||
|
||||
FILE *f = fopen(full, "wb");
|
||||
if (!f) {
|
||||
ESP_LOGE(TAG, "file: fopen %s failed (errno=%d)", full, errno);
|
||||
return send_error(req, "500 Internal Server Error", "open failed");
|
||||
}
|
||||
|
||||
char buf[1024];
|
||||
int remaining = (int)req->content_len;
|
||||
while (remaining > 0) {
|
||||
const int want = remaining < (int)sizeof(buf) ? remaining : (int)sizeof(buf);
|
||||
int got = httpd_req_recv(req, buf, want);
|
||||
if (got <= 0) {
|
||||
if (got == HTTPD_SOCK_ERR_TIMEOUT) continue;
|
||||
fclose(f);
|
||||
unlink(full);
|
||||
return send_error(req, "400 Bad Request", "recv failed");
|
||||
}
|
||||
if (fwrite(buf, 1, (size_t)got, f) != (size_t)got) {
|
||||
fclose(f);
|
||||
unlink(full);
|
||||
return send_error(req, "500 Internal Server Error", "write failed");
|
||||
}
|
||||
remaining -= got;
|
||||
}
|
||||
fclose(f);
|
||||
|
||||
char resp[192];
|
||||
snprintf(resp, sizeof(resp),
|
||||
"{\"status\":\"ok\",\"path\":\"%s\",\"bytes\":%d}",
|
||||
path_param, (int)req->content_len);
|
||||
ESP_LOGI(TAG, "file: stored %s (%d B)", full, (int)req->content_len);
|
||||
return send_json(req, "200 OK", resp);
|
||||
}
|
||||
|
||||
// ---------- public init ----------
|
||||
|
||||
httpd_handle_t scenario_server_handle(void) {
|
||||
@@ -229,7 +326,7 @@ esp_err_t scenario_server_start(void) {
|
||||
}
|
||||
httpd_config_t cfg = HTTPD_DEFAULT_CONFIG();
|
||||
cfg.server_port = 80;
|
||||
cfg.max_uri_handlers = 8;
|
||||
cfg.max_uri_handlers = 10;
|
||||
cfg.stack_size = 8192;
|
||||
|
||||
esp_err_t err = httpd_start(&s_server, &cfg);
|
||||
@@ -246,9 +343,14 @@ esp_err_t scenario_server_start(void) {
|
||||
.uri = "/game/scenario", .method = HTTP_POST,
|
||||
.handler = handle_scenario_post, .user_ctx = NULL,
|
||||
};
|
||||
static const httpd_uri_t uri_file = {
|
||||
.uri = "/game/file", .method = HTTP_POST,
|
||||
.handler = handle_file_post, .user_ctx = NULL,
|
||||
};
|
||||
httpd_register_uri_handler(s_server, &uri_healthz);
|
||||
httpd_register_uri_handler(s_server, &uri_scenario);
|
||||
httpd_register_uri_handler(s_server, &uri_file);
|
||||
|
||||
ESP_LOGI(TAG, "scenario server up on :80 (GET /healthz, POST /game/scenario)");
|
||||
ESP_LOGI(TAG, "scenario server up on :80 (GET /healthz, POST /game/scenario, POST /game/file)");
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user