Skip to content

Commit 10ed0b8

Browse files
committed
Add HTTP request body size limits and update API docs
Introduced a maximum HTTP body size for the viewer to prevent memory exhaustion, returning 413 if exceeded. Updated the server and viewer code to handle large payloads safely. Expanded the README with detailed REST API endpoint documentation and clarified security considerations.
1 parent 2c21c2a commit 10ed0b8

File tree

3 files changed

+49
-7
lines changed

3 files changed

+49
-7
lines changed

TankAlarm-112025-Server-BluesOpta/README.md

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -90,8 +90,22 @@ Configure server behavior:
9090
The server exposes a simple REST API:
9191

9292
- `GET /` - Web dashboard (HTML)
93-
- `GET /api/status` - JSON status of all clients and tanks
94-
- `POST /api/config` - Push configuration to a specific client
93+
- `GET /client-console` - Client configuration console (HTML)
94+
- `GET /serial-monitor` - Server + client serial log viewer (HTML)
95+
- `GET /calibration` - Calibration dashboard (HTML)
96+
- `GET /api/tanks` - Tank records (JSON)
97+
- `GET /api/clients` - Client metadata + cached configs (JSON)
98+
- `GET /api/calibration` - Calibration status (JSON)
99+
- `GET /api/serial-logs?...` - Fetch serial logs (JSON)
100+
- `GET /api/serial-export?...` - Download serial logs (CSV)
101+
- `POST /api/pin` - Set/verify/change admin PIN
102+
- `POST /api/config` - Update server settings and/or push client configuration (PIN required)
103+
- `POST /api/refresh` - Trigger manual refresh of a client (PIN required)
104+
- `POST /api/relay` - Send relay command to a client (PIN required)
105+
- `POST /api/relay/clear` - Clear a relay alarm on a client tank (PIN required if PIN configured)
106+
- `POST /api/pause` - Pause/resume Notecard processing (PIN required if PIN configured)
107+
- `POST /api/ftp-backup` - Backup configs to FTP (PIN required)
108+
- `POST /api/ftp-restore` - Restore configs from FTP (PIN required)
95109

96110
Example: Push config to client
97111
```bash
@@ -306,13 +320,14 @@ Ethernet.begin(mac, ip, dns, gateway, subnet);
306320
## Security Considerations
307321
308322
**Intranet Use Only:**
309-
- Web dashboard has no authentication
323+
- Read-only pages and `GET` endpoints have no authentication
324+
- Control endpoints are protected by a 4-digit admin PIN once configured
310325
- Designed for trusted local networks only
311326
- Do not expose port 80 to internet
312327
- Use firewall rules to restrict access
313328
314329
**For Internet Access:**
315-
- Implement authentication (not included)
330+
- Implement authentication (not included) and avoid exposing the Opta directly
316331
- Use HTTPS with valid certificates
317332
- Consider VPN for remote access
318333
- Add rate limiting and input validation

TankAlarm-112025-Server-BluesOpta/TankAlarm-112025-Server-BluesOpta.ino

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7186,6 +7186,12 @@ static bool readHttpRequest(EthernetClient &client, String &method, String &path
71867186
}
71877187
}
71887188

7189+
// If the body is already too large, don't read it into RAM.
7190+
// We'll respond with 413 and close the connection.
7191+
if (bodyTooLarge) {
7192+
return true;
7193+
}
7194+
71897195
if (contentLength > 0) {
71907196
size_t readBytes = 0;
71917197
while (readBytes < contentLength && client.connected()) {

TankAlarm-112025-Viewer-BluesOpta/TankAlarm-112025-Viewer-BluesOpta.ino

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,11 @@
107107
#define SUMMARY_FETCH_BASE_HOUR 6
108108
#endif
109109

110+
// Viewer is intended for GET-only use; cap request bodies to avoid memory exhaustion.
111+
#ifndef MAX_HTTP_BODY_BYTES
112+
#define MAX_HTTP_BODY_BYTES 1024
113+
#endif
114+
110115
#define STR_HELPER(x) #x
111116
#define STR(x) STR_HELPER(x)
112117

@@ -393,7 +398,7 @@ static const char VIEWER_DASHBOARD_HTML[] PROGMEM = R"HTML(
393398
static void initializeNotecard();
394399
static void initializeEthernet();
395400
static void handleWebRequests();
396-
static bool readHttpRequest(EthernetClient &client, String &method, String &path, String &body, size_t &contentLength);
401+
static bool readHttpRequest(EthernetClient &client, String &method, String &path, String &body, size_t &contentLength, bool &bodyTooLarge);
397402
static void respondHtml(EthernetClient &client, const char *body, size_t len);
398403
static void respondJson(EthernetClient &client, const String &body);
399404
static void respondStatus(EthernetClient &client, int status, const char *message);
@@ -584,13 +589,20 @@ static void handleWebRequests() {
584589
String path;
585590
String body;
586591
size_t contentLength = 0;
592+
bool bodyTooLarge = false;
587593

588-
if (!readHttpRequest(client, method, path, body, contentLength)) {
594+
if (!readHttpRequest(client, method, path, body, contentLength, bodyTooLarge)) {
589595
respondStatus(client, 400, "Bad Request");
590596
client.stop();
591597
return;
592598
}
593599

600+
if (bodyTooLarge) {
601+
respondStatus(client, 413, "Payload Too Large");
602+
client.stop();
603+
return;
604+
}
605+
594606
if (method == "GET" && path == "/") {
595607
sendDashboard(client);
596608
} else if (method == "GET" && path == "/api/tanks") {
@@ -603,11 +615,12 @@ static void handleWebRequests() {
603615
client.stop();
604616
}
605617

606-
static bool readHttpRequest(EthernetClient &client, String &method, String &path, String &body, size_t &contentLength) {
618+
static bool readHttpRequest(EthernetClient &client, String &method, String &path, String &body, size_t &contentLength, bool &bodyTooLarge) {
607619
method = "";
608620
path = "";
609621
contentLength = 0;
610622
body = "";
623+
bodyTooLarge = false;
611624

612625
String line;
613626
bool firstLine = true;
@@ -648,6 +661,10 @@ static bool readHttpRequest(EthernetClient &client, String &method, String &path
648661
headerValue.trim();
649662
if (headerKey.equalsIgnoreCase("Content-Length")) {
650663
contentLength = headerValue.toInt();
664+
if (contentLength > MAX_HTTP_BODY_BYTES) {
665+
bodyTooLarge = true;
666+
contentLength = MAX_HTTP_BODY_BYTES;
667+
}
651668
}
652669
}
653670
}
@@ -668,6 +685,10 @@ static bool readHttpRequest(EthernetClient &client, String &method, String &path
668685
body += c;
669686
readBytes++;
670687
}
688+
if (readBytes >= MAX_HTTP_BODY_BYTES) {
689+
bodyTooLarge = true;
690+
break;
691+
}
671692
}
672693
}
673694

0 commit comments

Comments
 (0)