web server tweaks

This commit is contained in:
cottongin 2026-01-25 21:22:49 -05:00
parent af58eb1987
commit cda8a5ec6d
No known key found for this signature in database
GPG Key ID: 0ECC91FE4655C262
3 changed files with 268 additions and 1 deletions

View File

@ -0,0 +1,262 @@
# CrossPointWebServer API Reference
Source: `src/network/CrossPointWebServer.cpp` and `CrossPointWebServer.h`
## Server Configuration
- HTTP port: 80 (default)
- WebSocket port: 81 (default)
- WiFi sleep disabled for responsiveness
- Supports both STA (station) and AP (access point) modes
## HTTP Endpoints
### GET /
**Handler:** `handleRoot()`
**Response:** HTML homepage from `HomePageHtml` (generated from `html/HomePage.html`)
**Content-Type:** text/html
### GET /files
**Handler:** `handleFileList()`
**Response:** HTML file browser page from `FilesPageHtml` (generated from `html/FilesPage.html`)
**Content-Type:** text/html
### GET /api/status
**Handler:** `handleStatus()`
**Response:** JSON device status
**Content-Type:** application/json
```json
{
"version": "CROSSPOINT_VERSION",
"ip": "192.168.x.x",
"mode": "AP" | "STA",
"rssi": -50, // 0 in AP mode
"freeHeap": 123456,
"uptime": 3600 // seconds
}
```
### GET /api/files
**Handler:** `handleFileListData()`
**Query params:**
- `path` (optional): Directory path, defaults to "/"
- `showHidden` (optional): "true" to show dot-files (except .crosspoint)
**Response:** JSON array of files
**Content-Type:** application/json
```json
[
{"name": "book.epub", "size": 123456, "isDirectory": false, "isEpub": true},
{"name": "folder", "size": 0, "isDirectory": true, "isEpub": false}
]
```
**Notes:**
- Hidden by default: files starting with ".", "System Volume Information", "XTCache"
- Always hidden: ".crosspoint" (internal cache folder)
- Streamed response (chunked encoding) to reduce memory usage
### GET /api/archived
**Handler:** `handleArchivedList()`
**Response:** JSON array of archived books
**Content-Type:** application/json
```json
[
{"filename": "archived_file.epub", "originalPath": "/Books/archived_file.epub"}
]
```
**Notes:** Uses `BookManager::listArchivedBooks()` and `BookManager::getArchivedBookOriginalPath()`
### GET /download
**Handler:** `handleDownload()`
**Query params:**
- `path` (required): File path to download
**Response:** File binary with Content-Disposition attachment header
**Content-Type:** application/octet-stream
**Errors:**
- 400: Missing path, path is directory
- 403: Hidden/system file, protected item
- 404: File not found
**Notes:**
- Streams in 4KB chunks
- Updates `totalBytesDownloaded` and `totalFilesDownloaded` stats
- Security: rejects paths with "..", files starting with ".", protected items
### POST /upload
**Handler:** `handleUpload()` (multipart handler), `handleUploadPost()` (response handler)
**Query params:**
- `path` (optional): Upload directory, defaults to "/"
**Form data:** multipart/form-data with file
**Response:** "File uploaded successfully: filename" or error message
**Notes:**
- Uses 4KB write buffer for SD card efficiency
- Overwrites existing files
- Clears epub cache after upload via `clearEpubCacheIfNeeded()`
- Updates `totalBytesUploaded` and `totalFilesUploaded` stats
- Logs progress every 100KB
### POST /mkdir
**Handler:** `handleCreateFolder()`
**Form params:**
- `name` (required): Folder name
- `path` (optional): Parent directory, defaults to "/"
**Response:** "Folder created: foldername" or error
**Errors:**
- 400: Missing name, empty name, folder exists
### POST /delete
**Handler:** `handleDelete()`
**Form params:**
- `path` (required): Item path to delete
- `type` (optional): "file" (default) or "folder"
- `archived` (optional): "true" for archived books
**Response:** "Deleted successfully" or error
**Errors:**
- 400: Missing path, root directory, folder not empty
- 403: Hidden/system file, protected item
- 404: Item not found
- 500: Delete failed
**Notes:**
- For files: uses `BookManager::deleteBook()` which handles cache and recent books cleanup
- For folders: must be empty first
- For archived: passes filename to `BookManager::deleteBook(filename, true)`
### POST /archive
**Handler:** `handleArchive()`
**Form params:**
- `path` (required): Book path to archive
**Response:** "Book archived successfully" or error
**Notes:** Uses `BookManager::archiveBook()`
### POST /unarchive
**Handler:** `handleUnarchive()`
**Form params:**
- `filename` (required): Archived book filename
**Response:** JSON with original path
**Content-Type:** application/json
```json
{"success": true, "originalPath": "/Books/book.epub"}
```
**Notes:** Uses `BookManager::unarchiveBook()` and `BookManager::getArchivedBookOriginalPath()`
### POST /rename
**Handler:** `handleRename()`
**Form params:**
- `path` (required): Current item path
- `newName` (required): New name (filename only, no path separators)
**Response:** "Renamed successfully" or error
**Errors:**
- 400: Missing params, empty name, name contains "/" or "\\", root directory, destination exists
- 403: System file, protected item
- 404: Source not found
- 500: Rename failed
**Notes:**
- Renames in place (same directory, new name)
- Uses `SdMan.rename()`
- Clears epub cache after rename via `clearEpubCacheIfNeeded()`
### POST /copy
**Handler:** `handleCopy()`
**Form params:**
- `srcPath` (required): Source path
- `destPath` (required): Full destination path (including new name)
**Response:** "Copied successfully" or error
**Errors:**
- 400: Missing params, root directory, destination exists, copy into self
- 403: System file, protected item
- 404: Source not found
- 500: Copy failed
**Notes:**
- Uses `copyFile()` for files (4KB buffer chunks)
- Uses `copyFolder()` for recursive directory copy
- Skips hidden files in folder copy
### POST /move
**Handler:** `handleMove()`
**Form params:**
- `srcPath` (required): Source path
- `destPath` (required): Full destination path (including new name)
**Response:** "Moved successfully" or error
**Errors:** Same as copy
**Notes:**
- First attempts atomic `SdMan.rename()` (fast)
- Falls back to copy+delete if rename fails
- Uses `deleteFolderRecursive()` for folder cleanup
## WebSocket Protocol (port 81)
**Handler:** `onWebSocketEvent()` via `wsEventCallback()` trampoline
### Upload Protocol
1. Client connects
2. Server: (implicit connection acknowledgment)
3. Client TEXT: `START:<filename>:<size>:<path>`
4. Server TEXT: `READY` or `ERROR:<message>`
5. Client BIN: file data chunks (any size, recommend 64KB)
6. Server TEXT: `PROGRESS:<received>:<total>` (every 64KB or at end)
7. Server TEXT: `DONE` or `ERROR:<message>`
### Events
- `WStype_CONNECTED`: Client connected, logs connection
- `WStype_DISCONNECTED`: Cleanup incomplete upload, delete partial file
- `WStype_TEXT`: Parse control messages (START)
- `WStype_BIN`: Write file data, send progress, complete upload
### Notes
- Faster than HTTP multipart for large files
- Direct binary writes to SD card
- Clears epub cache after upload
- Updates traffic statistics
## Security
### Protected Items (HIDDEN_ITEMS[])
- "System Volume Information"
- "XTCache"
### Always Hidden
- ".crosspoint" (internal cache)
### Security Checks Applied To
- `/delete`: Rejects dot-files (unless archived), protected items
- `/download`: Rejects dot-files, protected items, path traversal (..)
- `/rename`: Rejects dot-files, protected items
- `/copy`: Rejects dot-files, protected items
- `/move`: Rejects dot-files, protected items
## Helper Functions
### clearEpubCacheIfNeeded(filePath)
- Location: anonymous namespace at top of file
- Clears epub cache if file ends with ".epub"
- Uses `Epub(filePath, "/.crosspoint").clearCache()`
- Called by: upload, WebSocket upload, rename
### scanFiles(path, callback, showHidden)
- Iterates directory, calls callback for each FileInfo
- Yields and resets watchdog during iteration
- Filters hidden items based on showHidden flag
### copyFile(srcPath, destPath) / copyFolder(srcPath, destPath)
- 4KB buffer for file copy
- Recursive for folders
- Returns bool success
### deleteFolderRecursive(path)
- Static helper for move fallback
- Recursively deletes contents then directory
## Traffic Statistics (mutable, updated from const handlers)
- `totalBytesUploaded`
- `totalBytesDownloaded`
- `totalFilesUploaded`
- `totalFilesDownloaded`
- `serverStartTime` (for uptime calculation)
## Dependencies
- `<WebServer.h>` - ESP32 HTTP server
- `<WebSocketsServer.h>` - WebSocket support
- `<ArduinoJson.h>` - JSON serialization
- `<SDCardManager.h>` - SD card operations (SdMan singleton)
- `<Epub.h>` - Epub cache management
- `BookManager.h` - Book deletion, archiving, recent books
- `StringUtils.h` - File extension checking

View File

@ -77,7 +77,7 @@ void CrossPointWebServerActivity::onEnter() {
updateRequired = true;
xTaskCreate(&CrossPointWebServerActivity::taskTrampoline, "WebServerActivityTask",
2048, // Stack size
6144, // Stack size (increased: QR code + string ops need ~4KB)
this, // Parameters
1, // Priority
&displayTaskHandle // Task handle

View File

@ -1143,6 +1143,11 @@ void CrossPointWebServer::handleRename() const {
esp_task_wdt_reset();
if (SdMan.rename(itemPath.c_str(), newPath.c_str())) {
Serial.printf("[%lu] [WEB] Rename successful\n", millis());
// Clear epub cache for both old and new paths to prevent stale metadata
clearEpubCacheIfNeeded(itemPath); // Old path cache is now invalid
clearEpubCacheIfNeeded(newPath); // Ensure clean cache for new path
server->send(200, "text/plain", "Renamed successfully");
} else {
Serial.printf("[%lu] [WEB] Rename failed\n", millis());