Add WebServer, WebServerSecure, HTTPUpdateServer, HTTPUpdateServerSecure (#791)
* Add HTTP-parser lib to support ESP32 WebServer * Add WebServer from ESP32. Only supports HTTP * Separate HTTP server from the network server Instead of managing the WiFiServer/WiFiServerSecure in the same object as the HTTP handling, split them into separate objects. This lets HTTP and HTTPS servers work without templates or duplicating code. The HTTP block just gets a `WiFiClient*` and works with that to only do HTTP processing, while the upper object handles the appropriate server and client types. * Add HTTPS server * Clean up some THandlerFunction refs * Refactor into a template-ized WebServer/WebServerSecure * Add DNSServer examples which need WebServer * Fix CoreMutex infinite recursion crash Core could crash while Serial debugging was going on and prints were happening from LWIP/IRQ land and the main app. * Add HTTPUpdateServer(Secure) * Add MIME include, optimize WebServer::send(size,len) When send()ing a large buffer, the WebServer::send() call would actually convert that buffer into a String (i.e. duplicate it, and potential issues with embedded \0s in binary data). Make a simple override to send(size, len) to allow writing from the source buffer instead. * Fix WiFiClient::send(Stream), add FSBrowser example
This commit is contained in:
parent
c501306c4f
commit
0edba2ee2a
81 changed files with 6837 additions and 58 deletions
2
.github/workflows/pull-request.yml
vendored
2
.github/workflows/pull-request.yml
vendored
|
|
@ -20,7 +20,7 @@ jobs:
|
||||||
- name: Run codespell
|
- name: Run codespell
|
||||||
uses: codespell-project/actions-codespell@master
|
uses: codespell-project/actions-codespell@master
|
||||||
with:
|
with:
|
||||||
skip: ./ArduinoCore-API,./libraries/ESP8266SdFat,./libraries/Adafruit_TinyUSB_Arduino,./libraries/LittleFS/lib,./tools/pyserial,./pico-sdk,./.github,./docs/i2s.rst,./cores/rp2040/api,./libraries/FreeRTOS,./tools/libbearssl/bearssl,./include,./libraries/WiFi/examples/BearSSL_Server,./ota/uzlib
|
skip: ./ArduinoCore-API,./libraries/ESP8266SdFat,./libraries/Adafruit_TinyUSB_Arduino,./libraries/LittleFS/lib,./tools/pyserial,./pico-sdk,./.github,./docs/i2s.rst,./cores/rp2040/api,./libraries/FreeRTOS,./tools/libbearssl/bearssl,./include,./libraries/WiFi/examples/BearSSL_Server,./ota/uzlib,./libraries/http-parser/lib,./libraries/WebServer/examples/HelloServerBearSSL/HelloServerBearSSL.ino,./libraries/HTTPUpdateServer/examples/SecureBearSSLUpdater/SecureBearSSLUpdater.ino
|
||||||
ignore_words_list: ser,dout
|
ignore_words_list: ser,dout
|
||||||
|
|
||||||
# Consistent style
|
# Consistent style
|
||||||
|
|
|
||||||
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -4,3 +4,4 @@ tools/dist
|
||||||
docs/_build
|
docs/_build
|
||||||
ota/build
|
ota/build
|
||||||
tools/libpico/build
|
tools/libpico/build
|
||||||
|
platform.local.txt
|
||||||
|
|
|
||||||
3
.gitmodules
vendored
3
.gitmodules
vendored
|
|
@ -34,3 +34,6 @@
|
||||||
[submodule "ota/uzlib"]
|
[submodule "ota/uzlib"]
|
||||||
path = ota/uzlib
|
path = ota/uzlib
|
||||||
url = https://github.com/pfalcon/uzlib.git
|
url = https://github.com/pfalcon/uzlib.git
|
||||||
|
[submodule "libraries/http_parser/lib/http-parser"]
|
||||||
|
path = libraries/http-parser/lib/http-parser
|
||||||
|
url = https://github.com/nodejs/http-parser.git
|
||||||
|
|
|
||||||
|
|
@ -147,6 +147,8 @@ The installed tools include a version of OpenOCD (in the pqt-openocd directory)
|
||||||
* Adafruit TinyUSB Arduino (USB mouse, keyboard, flash drive, generic HID, CDC Serial, MIDI, WebUSB, others)
|
* Adafruit TinyUSB Arduino (USB mouse, keyboard, flash drive, generic HID, CDC Serial, MIDI, WebUSB, others)
|
||||||
* Generic Arduino USB Serial, Keyboard, and Mouse emulation
|
* Generic Arduino USB Serial, Keyboard, and Mouse emulation
|
||||||
* WiFi (Pico W)
|
* WiFi (Pico W)
|
||||||
|
* HTTP client and server (WebServer)
|
||||||
|
* SSL/TLS/HTTPS
|
||||||
* Over-the-Air (OTA) upgrades
|
* Over-the-Air (OTA) upgrades
|
||||||
* Filesystems (LittleFS and SD/SDFS)
|
* Filesystems (LittleFS and SD/SDFS)
|
||||||
* Multicore support (setup1() and loop1())
|
* Multicore support (setup1() and loop1())
|
||||||
|
|
@ -187,7 +189,10 @@ If you want to contribute or have bugfixes, drop me a note at <earlephilhower@ya
|
||||||
* [lwIP](https://savannah.nongnu.org/projects/lwip/) is (c) the Swedish Institute of Computer Science and licenced under the BSD license.
|
* [lwIP](https://savannah.nongnu.org/projects/lwip/) is (c) the Swedish Institute of Computer Science and licenced under the BSD license.
|
||||||
* [BearSSL](https://bearssl.org) library written by Thomas Pornin, is distributed under the [MIT License](https://bearssl.org/#legal-details).
|
* [BearSSL](https://bearssl.org) library written by Thomas Pornin, is distributed under the [MIT License](https://bearssl.org/#legal-details).
|
||||||
* [UZLib](https://github.com/pfalcon/uzlib) is copyright (c) 2003 Joergen Ibsen and distributed under the zlib license.
|
* [UZLib](https://github.com/pfalcon/uzlib) is copyright (c) 2003 Joergen Ibsen and distributed under the zlib license.
|
||||||
* [LEAmDMS](https://github.com/LaborEtArs/ESP8266mDNS) is copyright multiple authors and distributed under the MIT license.
|
* [LEAmDNS](https://github.com/LaborEtArs/ESP8266mDNS) is copyright multiple authors and distributed under the MIT license.
|
||||||
|
* [http-parser](https://github.com/nodejs/http-parser) is copyright Joyent, Inc. and other Node contributors.
|
||||||
|
* WebServer code modified from the [ESP32 WebServer](https://github.com/espressif/arduino-esp32/tree/master/libraries/WebServer) and is copyright (c) 2015 Ivan Grokhotkov and others
|
||||||
|
|
||||||
|
|
||||||
-Earle F. Philhower, III
|
-Earle F. Philhower, III
|
||||||
earlephilhower@yahoo.com
|
earlephilhower@yahoo.com
|
||||||
|
|
|
||||||
|
|
@ -90,6 +90,15 @@ extern bool __isFreeRTOS;
|
||||||
#define HAVE_HWSERIAL1
|
#define HAVE_HWSERIAL1
|
||||||
#define HAVE_HWSERIAL2
|
#define HAVE_HWSERIAL2
|
||||||
|
|
||||||
|
// PSTR/etc.
|
||||||
|
#ifndef FPSTR
|
||||||
|
#define FPSTR (const char *)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef PGM_VOID_P
|
||||||
|
#define PGM_VOID_P void *
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
|
|
||||||
#ifdef USE_TINYUSB
|
#ifdef USE_TINYUSB
|
||||||
|
|
|
||||||
|
|
@ -27,13 +27,15 @@
|
||||||
|
|
||||||
class CoreMutex {
|
class CoreMutex {
|
||||||
public:
|
public:
|
||||||
CoreMutex(mutex_t *mutex) {
|
CoreMutex(mutex_t *mutex, bool debugEnable = true) {
|
||||||
uint32_t owner;
|
uint32_t owner;
|
||||||
_mutex = mutex;
|
_mutex = mutex;
|
||||||
_acquired = false;
|
_acquired = false;
|
||||||
if (!mutex_try_enter(_mutex, &owner)) {
|
if (!mutex_try_enter(_mutex, &owner)) {
|
||||||
if (owner == get_core_num()) { // Deadlock!
|
if (owner == get_core_num()) { // Deadlock!
|
||||||
|
if (debugEnable) {
|
||||||
DEBUGCORE("CoreMutex - Deadlock detected!\n");
|
DEBUGCORE("CoreMutex - Deadlock detected!\n");
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
mutex_enter_blocking(_mutex);
|
mutex_enter_blocking(_mutex);
|
||||||
|
|
|
||||||
|
|
@ -64,7 +64,7 @@ void SerialUSB::end() {
|
||||||
}
|
}
|
||||||
|
|
||||||
int SerialUSB::peek() {
|
int SerialUSB::peek() {
|
||||||
CoreMutex m(&__usb_mutex);
|
CoreMutex m(&__usb_mutex, false);
|
||||||
if (!_running || !m) {
|
if (!_running || !m) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
@ -74,7 +74,7 @@ int SerialUSB::peek() {
|
||||||
}
|
}
|
||||||
|
|
||||||
int SerialUSB::read() {
|
int SerialUSB::read() {
|
||||||
CoreMutex m(&__usb_mutex);
|
CoreMutex m(&__usb_mutex, false);
|
||||||
if (!_running || !m) {
|
if (!_running || !m) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
@ -86,7 +86,7 @@ int SerialUSB::read() {
|
||||||
}
|
}
|
||||||
|
|
||||||
int SerialUSB::available() {
|
int SerialUSB::available() {
|
||||||
CoreMutex m(&__usb_mutex);
|
CoreMutex m(&__usb_mutex, false);
|
||||||
if (!_running || !m) {
|
if (!_running || !m) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
@ -95,7 +95,7 @@ int SerialUSB::available() {
|
||||||
}
|
}
|
||||||
|
|
||||||
int SerialUSB::availableForWrite() {
|
int SerialUSB::availableForWrite() {
|
||||||
CoreMutex m(&__usb_mutex);
|
CoreMutex m(&__usb_mutex, false);
|
||||||
if (!_running || !m) {
|
if (!_running || !m) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
@ -104,7 +104,7 @@ int SerialUSB::availableForWrite() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void SerialUSB::flush() {
|
void SerialUSB::flush() {
|
||||||
CoreMutex m(&__usb_mutex);
|
CoreMutex m(&__usb_mutex, false);
|
||||||
if (!_running || !m) {
|
if (!_running || !m) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -117,7 +117,7 @@ size_t SerialUSB::write(uint8_t c) {
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t SerialUSB::write(const uint8_t *buf, size_t length) {
|
size_t SerialUSB::write(const uint8_t *buf, size_t length) {
|
||||||
CoreMutex m(&__usb_mutex);
|
CoreMutex m(&__usb_mutex, false);
|
||||||
if (!_running || !m) {
|
if (!_running || !m) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
@ -155,7 +155,7 @@ size_t SerialUSB::write(const uint8_t *buf, size_t length) {
|
||||||
}
|
}
|
||||||
|
|
||||||
SerialUSB::operator bool() {
|
SerialUSB::operator bool() {
|
||||||
CoreMutex m(&__usb_mutex);
|
CoreMutex m(&__usb_mutex, false);
|
||||||
if (!_running || !m) {
|
if (!_running || !m) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,7 @@
|
||||||
For details, see http://sourceforge.net/projects/libb64
|
For details, see http://sourceforge.net/projects/libb64
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef BASE64_CDECODE_H
|
#pragma once
|
||||||
#define BASE64_CDECODE_H
|
|
||||||
|
|
||||||
#define base64_decode_expected_len(n) ((n * 3) / 4)
|
#define base64_decode_expected_len(n) ((n * 3) / 4)
|
||||||
|
|
||||||
|
|
@ -34,5 +33,3 @@ int base64_decode_chars(const char* code_in, const int length_in, char* plaintex
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
} // extern "C"
|
} // extern "C"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#endif /* BASE64_CDECODE_H */
|
|
||||||
|
|
@ -5,8 +5,7 @@
|
||||||
For details, see http://sourceforge.net/projects/libb64
|
For details, see http://sourceforge.net/projects/libb64
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef BASE64_CENCODE_H
|
#pragma once
|
||||||
#define BASE64_CENCODE_H
|
|
||||||
|
|
||||||
#define BASE64_CHARS_PER_LINE 72
|
#define BASE64_CHARS_PER_LINE 72
|
||||||
|
|
||||||
|
|
@ -44,5 +43,3 @@ int base64_encode_chars(const char* plaintext_in, int length_in, char* code_out)
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
} // extern "C"
|
} // extern "C"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#endif /* BASE64_CENCODE_H */
|
|
||||||
138
docs/ota.rst
Executable file → Normal file
138
docs/ota.rst
Executable file → Normal file
|
|
@ -10,8 +10,9 @@ OTA (Over the Air) update is the process of uploading firmware to a Pico using a
|
||||||
OTA may be done using:
|
OTA may be done using:
|
||||||
|
|
||||||
- `Arduino IDE <#arduino-ide>`__
|
- `Arduino IDE <#arduino-ide>`__
|
||||||
- `Web Browser <#web-browser>`__ - Coming soon
|
- `Web Browser <#web-browser>`__
|
||||||
- `HTTP Server <#http-server>`__ - Coming soon
|
- `HTTP Server <#http-server>`__
|
||||||
|
- Any other method (ZModen receive over a UART port, etc.) by using the ``Updater`` object in your sketch
|
||||||
|
|
||||||
The Arduino IDE option is intended primarily for the software development phase. The other two options would be more useful after deployment, to provide the module with application updates either manually with a web browser, or automatically using an HTTP server.
|
The Arduino IDE option is intended primarily for the software development phase. The other two options would be more useful after deployment, to provide the module with application updates either manually with a web browser, or automatically using an HTTP server.
|
||||||
|
|
||||||
|
|
@ -48,16 +49,16 @@ Check functionality provided with the `ArduinoOTA <https://github.com/earlephilh
|
||||||
void setHostname(const char* hostname);
|
void setHostname(const char* hostname);
|
||||||
void setPassword(const char* password);
|
void setPassword(const char* password);
|
||||||
|
|
||||||
Certain basic protection is already built in and does not require any additional coding by the developer. `ArduinoOTA <https://github.com/earlephilhower/arduino-pico/tree/master/libraries/ArduinoOTA>`__ and espota.py use `Digest-MD5 <https://en.wikipedia.org/wiki/Digest_access_authentication>`__ to authenticate uploads. Integrity of transferred data is verified on the ESP side using `MD5 <https://en.wikipedia.org/wiki/MD5>`__ checksum.
|
Certain basic protection is already built in and does not require any additional coding by the developer. `ArduinoOTA <https://github.com/earlephilhower/arduino-pico/tree/master/libraries/ArduinoOTA>`__ and espota.py use `Digest-MD5 <https://en.wikipedia.org/wiki/Digest_access_authentication>`__ to authenticate uploads. Integrity of transferred data is verified on the Pico side using `MD5 <https://en.wikipedia.org/wiki/MD5>`__ checksum.
|
||||||
|
|
||||||
Make your own risk analysis and, depending on the application, decide what library functions to implement. If required, consider implementation of other means of protection from being hacked, like exposing modules for uploads only according to a specific schedule, triggering OTA only when the user presses a dedicated “Update” button wired to the ESP, etc.
|
Make your own risk analysis and, depending on the application, decide what library functions to implement. If required, consider implementation of other means of protection from being hacked, like exposing modules for uploads only according to a specific schedule, triggering OTA only when the user presses a dedicated “Update” button wired to the Pico, etc.
|
||||||
|
|
||||||
Advanced Security - Signed Updates
|
Advanced Security - Signed Updates
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
While the above password-based security will dissuade casual hacking attempts, it is not highly secure. For applications where a higher level of security is needed, cryptographically signed OTA updates can be required. This uses SHA256 hashing in place of MD5 (which is known to be cryptographically broken) and RSA-2048 bit level public-key encryption to guarantee that only the holder of a cryptographic private key can produce signed updates accepted by the OTA update mechanisms.
|
While the above password-based security will dissuade casual hacking attempts, it is not highly secure. For applications where a higher level of security is needed, cryptographically signed OTA updates can be required. This uses SHA256 hashing in place of MD5 (which is known to be cryptographically broken) and RSA-2048 bit level public-key encryption to guarantee that only the holder of a cryptographic private key can produce signed updates accepted by the OTA update mechanisms.
|
||||||
|
|
||||||
Signed updates are updates whose compiled binaries are signed with a private key (held by the developer) and verified with a public key (stored in the application and available for all to see). The signing process computes a hash of the binary code, encrypts the hash with the developer's private key, and appends this encrypted hash (also called a signature) to the binary that is uploaded (via OTA, web, or HTTP server). If the code is modified or replaced in any way by anyone except the holder of the developer's private key, the signature will not match and the ESP8266 will reject the upload.
|
Signed updates are updates whose compiled binaries are signed with a private key (held by the developer) and verified with a public key (stored in the application and available for all to see). The signing process computes a hash of the binary code, encrypts the hash with the developer's private key, and appends this encrypted hash (also called a signature) to the binary that is uploaded (via OTA, web, or HTTP server). If the code is modified or replaced in any way by anyone except the holder of the developer's private key, the signature will not match and the Pico will reject the upload.
|
||||||
|
|
||||||
Cryptographic signing only protects against tampering with binaries delivered via OTA. If someone has physical access, they will always be able to flash the device over the serial port. Signing also does not encrypt anything but the hash (so that it can't be modified), so this does not protect code inside the device: if a user has physical access they can read out your program.
|
Cryptographic signing only protects against tampering with binaries delivered via OTA. If someone has physical access, they will always be able to flash the device over the serial port. Signing also does not encrypt anything but the hash (so that it can't be modified), so this does not protect code inside the device: if a user has physical access they can read out your program.
|
||||||
|
|
||||||
|
|
@ -134,7 +135,7 @@ Compile the sketch normally and, once a `.bin` file is available, sign it using
|
||||||
|
|
||||||
.. code:: bash
|
.. code:: bash
|
||||||
|
|
||||||
<ESP8266ArduinoPath>/tools/signing.py --mode sign --privatekey <path-to-private.key> --bin <path-to-unsigned-bin> --out <path-to-signed-binary>
|
<PicoArduinoPath>/tools/signing.py --mode sign --privatekey <path-to-private.key> --bin <path-to-unsigned-bin> --out <path-to-signed-binary>
|
||||||
|
|
||||||
Compression
|
Compression
|
||||||
-----------
|
-----------
|
||||||
|
|
@ -155,14 +156,14 @@ If signing is desired, sign the gzip compressed file *after* compression.
|
||||||
.. code:: bash
|
.. code:: bash
|
||||||
|
|
||||||
gzip -9 sketch.bin
|
gzip -9 sketch.bin
|
||||||
<ESP8266ArduinoPath>/tools/signing.py --mode sign --privatekey <path-to-private.key> --bin sketch.bin.gz --out sketch.bin.gz.signed
|
<PicoPath>/tools/signing.py --mode sign --privatekey <path-to-private.key> --bin sketch.bin.gz --out sketch.bin.gz.signed
|
||||||
|
|
||||||
Safety
|
Safety
|
||||||
~~~~~~
|
~~~~~~
|
||||||
|
|
||||||
The OTA process consumes some of the ESP’s resources and bandwidth during upload. Then, the module is restarted and a new sketch executed. Analyse and test how this affects the functionality of the existing and new sketches.
|
The OTA process consumes some of the Pico’s resources and bandwidth during upload. Then, the module is restarted and a new sketch executed. Analyse and test how this affects the functionality of the existing and new sketches.
|
||||||
|
|
||||||
If the ESP is in a remote location and controlling some equipment, you should devote additional attention to what happens if operation of this equipment is suddenly interrupted by the update process. Therefore, decide how to put this equipment into a safe state before starting the update. For instance, your module may be controlling a garden watering system in a sequence. If this sequence is not properly shut down and a water valve is left open, the garden may be flooded.
|
If the Pico is in a remote location and controlling some equipment, you should devote additional attention to what happens if operation of this equipment is suddenly interrupted by the update process. Therefore, decide how to put this equipment into a safe state before starting the update. For instance, your module may be controlling a garden watering system in a sequence. If this sequence is not properly shut down and a water valve is left open, the garden may be flooded.
|
||||||
|
|
||||||
The following functions are provided with the `ArduinoOTA <https://github.com/earlephilhower/arduino-pico/tree/master/libraries/ArduinoOTA>`__ library and intended to handle functionality of your application during specific stages of OTA, or on an OTA error:
|
The following functions are provided with the `ArduinoOTA <https://github.com/earlephilhower/arduino-pico/tree/master/libraries/ArduinoOTA>`__ library and intended to handle functionality of your application during specific stages of OTA, or on an OTA error:
|
||||||
|
|
||||||
|
|
@ -215,6 +216,125 @@ You will not be prompted for a reentering the same password next time. Arduino I
|
||||||
|
|
||||||
Please note, it is possible to reveal password entered previously in Arduino IDE, if IDE has not been closed since last upload. This can be done by enabling *Show verbose output during: upload* in *File > Preferences* and attempting to upload the module.
|
Please note, it is possible to reveal password entered previously in Arduino IDE, if IDE has not been closed since last upload. This can be done by enabling *Show verbose output during: upload* in *File > Preferences* and attempting to upload the module.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Web Browser
|
||||||
|
-----------
|
||||||
|
|
||||||
|
Updates described in this chapter are done with a web browser that can be useful in the following typical scenarios:
|
||||||
|
|
||||||
|
- after application deployment if loading directly from Arduino IDE is inconvenient or not possible,
|
||||||
|
- after deployment if user is unable to expose module for OTA from external update server,
|
||||||
|
- to provide updates after deployment to small quantity of modules when setting an update server is not practicable.
|
||||||
|
|
||||||
|
Requirements
|
||||||
|
~~~~~~~~~~~~
|
||||||
|
|
||||||
|
- The Pico and the computer must be connected to the same network, or the IP of the Pico should be known if on a different network.
|
||||||
|
|
||||||
|
Implementation Overview
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Updates with a web browser are implemented using ``HTTPUpdateServer`` class together with ``WebServer`` and ``LEAmDNS`` classes. The following code is required to get it work:
|
||||||
|
|
||||||
|
setup()
|
||||||
|
|
||||||
|
.. code:: cpp
|
||||||
|
|
||||||
|
MDNS.begin(host);
|
||||||
|
|
||||||
|
httpUpdater.setup(&httpServer);
|
||||||
|
httpServer.begin();
|
||||||
|
|
||||||
|
MDNS.addService("http", "tcp", 80);
|
||||||
|
|
||||||
|
loop()
|
||||||
|
|
||||||
|
.. code:: cpp
|
||||||
|
|
||||||
|
httpServer.handleClient();
|
||||||
|
|
||||||
|
In case OTA update fails dead after entering modifications in your sketch, you can always recover module by loading it over a serial port. Then diagnose the issue with sketch using Serial Monitor. Once the issue is fixed try OTA again.
|
||||||
|
|
||||||
|
|
||||||
|
HTTP Server
|
||||||
|
-----------
|
||||||
|
|
||||||
|
``HTTPUpdate`` class can check for updates and download a binary file from HTTP web server. It is possible to download updates from every IP or domain address on the network or Internet.
|
||||||
|
|
||||||
|
Note that by default this class closes all other connections except the one used by the update, this is because the update method blocks. This means that if there's another application receiving data then TCP packets will build up in the buffer leading to out of memory errors causing the OTA update to fail. There's also a limited number of receive buffers available and all may be used up by other applications.
|
||||||
|
|
||||||
|
There are some cases where you know that you won't be receiving any data but would still like to send progress updates.
|
||||||
|
It's possible to disable the default behaviour (and keep connections open) by calling closeConnectionsOnUpdate(false).
|
||||||
|
|
||||||
|
Requirements
|
||||||
|
~~~~~~~~~~~~
|
||||||
|
|
||||||
|
- web server
|
||||||
|
|
||||||
|
Arduino code
|
||||||
|
~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Simple updater
|
||||||
|
^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
Simple updater downloads the file every time the function is called.
|
||||||
|
|
||||||
|
.. code:: cpp
|
||||||
|
|
||||||
|
WiFiClient client;
|
||||||
|
HTTPUpdate.update(client, "192.168.0.2", 80, "/arduino.bin");
|
||||||
|
|
||||||
|
Advanced updater
|
||||||
|
^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
Its possible to point the update function to a script on the server. If a version string argument is given, it will be sent to the server. The server side script can use this string to check whether an update should be performed.
|
||||||
|
|
||||||
|
The server-side script can respond as follows: - response code 200, and send the firmware image, - or response code 304 to notify Pico that no update is required.
|
||||||
|
|
||||||
|
.. code:: cpp
|
||||||
|
|
||||||
|
WiFiClient client;
|
||||||
|
t_httpUpdate_return ret = HTTPUpdate.update(client, "192.168.0.2", 80, "/pico/update/arduino.php", "optional current version string here");
|
||||||
|
switch(ret) {
|
||||||
|
case HTTP_UPDATE_FAILED:
|
||||||
|
Serial.println("[update] Update failed.");
|
||||||
|
break;
|
||||||
|
case HTTP_UPDATE_NO_UPDATES:
|
||||||
|
Serial.println("[update] Update no Update.");
|
||||||
|
break;
|
||||||
|
case HTTP_UPDATE_OK:
|
||||||
|
Serial.println("[update] Update ok."); // may not be called since we reboot the RP2040
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
TLS updater
|
||||||
|
^^^^^^^^^^^
|
||||||
|
|
||||||
|
Please read and try the examples provided with the library.
|
||||||
|
|
||||||
|
Server request handling
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Simple updater
|
||||||
|
^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
For the simple updater the server only needs to deliver the binary file for update.
|
||||||
|
|
||||||
|
Advanced updater
|
||||||
|
^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
For advanced update management a script (such as a PHP script) can run on the server side. It will receive the following headers which it may use to choose a specific firmware file to serve:
|
||||||
|
|
||||||
|
::
|
||||||
|
[User-Agent] => Pico-HTTP-Update
|
||||||
|
[x-Pico-STA-MAC] => 18:FE:AA:AA:AA:AA
|
||||||
|
[x-Pico-AP-MAC] => 1A:FE:AA:AA:AA:AA
|
||||||
|
[x-Pico-Version] => DOOR-7-g14f53a19
|
||||||
|
[x-Pico-Mode] => sketch
|
||||||
|
|
||||||
|
|
||||||
Stream Interface
|
Stream Interface
|
||||||
----------------
|
----------------
|
||||||
|
|
||||||
|
|
|
||||||
13
keywords.txt
13
keywords.txt
|
|
@ -32,9 +32,22 @@ usToPIOCycles KEYWORD2
|
||||||
f_cpu KEYWORD2
|
f_cpu KEYWORD2
|
||||||
getCycleCount KEYWORD2
|
getCycleCount KEYWORD2
|
||||||
getCycleCount64 KEYWORD2
|
getCycleCount64 KEYWORD2
|
||||||
|
|
||||||
|
getFreeHeap KEYWORD2
|
||||||
|
getUsedHeap KEYWORD2
|
||||||
|
getTotalHeap KEYWORD2
|
||||||
|
|
||||||
idleOtherCore KEYWORD2
|
idleOtherCore KEYWORD2
|
||||||
resumeOtherCore KEYWORD2
|
resumeOtherCore KEYWORD2
|
||||||
|
|
||||||
|
restartCore1 KEYWORD2
|
||||||
|
reboot KEYWORD2
|
||||||
|
restart KEYWORD2
|
||||||
|
|
||||||
|
getChipID KEYWORD2
|
||||||
|
|
||||||
|
hwrand32 KEYWORD2
|
||||||
|
|
||||||
PIOProgram KEYWORD2
|
PIOProgram KEYWORD2
|
||||||
prepare KEYWORD2
|
prepare KEYWORD2
|
||||||
SerialPIO KEYWORD2
|
SerialPIO KEYWORD2
|
||||||
|
|
|
||||||
Binary file not shown.
BIN
lib/libpico.a
BIN
lib/libpico.a
Binary file not shown.
36
libraries/DNSServer/examples/CaptivePortal/CaptivePortal.ino
Normal file
36
libraries/DNSServer/examples/CaptivePortal/CaptivePortal.ino
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
#include <WiFi.h>
|
||||||
|
#include <DNSServer.h>
|
||||||
|
#include <WebServer.h>
|
||||||
|
|
||||||
|
const byte DNS_PORT = 53;
|
||||||
|
IPAddress apIP(172, 217, 28, 1);
|
||||||
|
DNSServer dnsServer;
|
||||||
|
WebServer webServer(80);
|
||||||
|
|
||||||
|
String responseHTML = ""
|
||||||
|
"<!DOCTYPE html><html lang='en'><head>"
|
||||||
|
"<meta name='viewport' content='width=device-width'>"
|
||||||
|
"<title>CaptivePortal</title></head><body>"
|
||||||
|
"<h1>Hello World!</h1><p>This is a captive portal example."
|
||||||
|
" All requests will be redirected here.</p></body></html>";
|
||||||
|
|
||||||
|
void setup() {
|
||||||
|
WiFi.mode(WIFI_AP);
|
||||||
|
WiFi.softAPConfig(apIP, apIP, IPAddress(255, 255, 255, 0));
|
||||||
|
WiFi.softAP("DNSServer CaptivePortal example");
|
||||||
|
|
||||||
|
// if DNSServer is started with "*" for domain name, it will reply with
|
||||||
|
// provided IP to all DNS request
|
||||||
|
dnsServer.start(DNS_PORT, "*", apIP);
|
||||||
|
|
||||||
|
// replay to all requests with same HTML
|
||||||
|
webServer.onNotFound([]() {
|
||||||
|
webServer.send(200, "text/html", responseHTML);
|
||||||
|
});
|
||||||
|
webServer.begin();
|
||||||
|
}
|
||||||
|
|
||||||
|
void loop() {
|
||||||
|
dnsServer.processNextRequest();
|
||||||
|
webServer.handleClient();
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,143 @@
|
||||||
|
#include <WiFi.h>
|
||||||
|
#include <WiFiClient.h>
|
||||||
|
#include <WebServer.h>
|
||||||
|
#include <DNSServer.h>
|
||||||
|
#include <LEAmDNS.h>
|
||||||
|
#include <EEPROM.h>
|
||||||
|
|
||||||
|
/*
|
||||||
|
This example serves a "hello world" on a WLAN and a SoftAP at the same time.
|
||||||
|
The SoftAP allow you to configure WLAN parameters at run time. They are not setup in the sketch but saved on EEPROM.
|
||||||
|
|
||||||
|
Connect your computer or cell phone to wifi network pico_ap with password 12345678. A popup may appear and it allow you to go to WLAN config. If it does not then navigate to http://192.168.4.1/wifi and config it there.
|
||||||
|
Then wait for the module to connect to your wifi and take note of the WLAN IP it got. Then you can disconnect from pico_ap and return to your regular WLAN.
|
||||||
|
|
||||||
|
Now the Pico is in your network. You can reach it through http://192.168.x.x/ (the IP you took note of) or maybe at http://picow.local too.
|
||||||
|
|
||||||
|
This is a captive portal because through the softAP it will redirect any http request to http://192.168.4.1/
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* Set these to your desired softAP credentials. They are not configurable at runtime */
|
||||||
|
#ifndef APSSID
|
||||||
|
#define APSSID "pico_ap"
|
||||||
|
#define APPSK "12345678"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
const char *softAP_ssid = APSSID;
|
||||||
|
const char *softAP_password = APPSK;
|
||||||
|
|
||||||
|
/* hostname for mDNS. Should work at least on windows. Try http://picow.local */
|
||||||
|
const char *myHostname = "picow";
|
||||||
|
|
||||||
|
/* Don't set this wifi credentials. They are configurated at runtime and stored on EEPROM */
|
||||||
|
char ssid[33] = "";
|
||||||
|
char password[65] = "";
|
||||||
|
|
||||||
|
// DNS server
|
||||||
|
const byte DNS_PORT = 53;
|
||||||
|
DNSServer dnsServer;
|
||||||
|
|
||||||
|
// Web server
|
||||||
|
WebServer server(80);
|
||||||
|
|
||||||
|
/* Soft AP network parameters */
|
||||||
|
IPAddress apIP(172, 217, 28, 1);
|
||||||
|
IPAddress netMsk(255, 255, 255, 0);
|
||||||
|
|
||||||
|
|
||||||
|
/** Should I connect to WLAN asap? */
|
||||||
|
boolean connect;
|
||||||
|
|
||||||
|
/** Last time I tried to connect to WLAN */
|
||||||
|
unsigned long lastConnectTry = 0;
|
||||||
|
|
||||||
|
/** Current WLAN status */
|
||||||
|
unsigned int status = WL_IDLE_STATUS;
|
||||||
|
|
||||||
|
void setup() {
|
||||||
|
delay(1000);
|
||||||
|
Serial.begin(115200);
|
||||||
|
Serial.println();
|
||||||
|
Serial.println("Configuring access point...");
|
||||||
|
/* You can remove the password parameter if you want the AP to be open. */
|
||||||
|
WiFi.softAPConfig(apIP, apIP, netMsk);
|
||||||
|
WiFi.softAP(softAP_ssid, softAP_password);
|
||||||
|
delay(500); // Without delay I've seen the IP address blank
|
||||||
|
Serial.print("AP IP address: ");
|
||||||
|
Serial.println(WiFi.softAPIP());
|
||||||
|
|
||||||
|
/* Setup the DNS server redirecting all the domains to the apIP */
|
||||||
|
dnsServer.setErrorReplyCode(DNSReplyCode::NoError);
|
||||||
|
dnsServer.start(DNS_PORT, "*", apIP);
|
||||||
|
|
||||||
|
/* Setup web pages: root, wifi config pages, SO captive portal detectors and not found. */
|
||||||
|
server.on("/", handleRoot);
|
||||||
|
server.on("/wifi", handleWifi);
|
||||||
|
server.on("/wifisave", handleWifiSave);
|
||||||
|
server.on("/generate_204", handleRoot); // Android captive portal. Maybe not needed. Might be handled by notFound handler.
|
||||||
|
server.on("/fwlink", handleRoot); // Microsoft captive portal. Maybe not needed. Might be handled by notFound handler.
|
||||||
|
server.onNotFound(handleNotFound);
|
||||||
|
server.begin(); // Web server start
|
||||||
|
Serial.println("HTTP server started");
|
||||||
|
//loadCredentials(); // Load WLAN credentials from network
|
||||||
|
ssid[0] = 0;
|
||||||
|
password[0] = 0;
|
||||||
|
connect = strlen(ssid) > 0; // Request WLAN connect if there is a SSID
|
||||||
|
}
|
||||||
|
|
||||||
|
void connectWifi() {
|
||||||
|
Serial.println("Connecting as wifi client...");
|
||||||
|
WiFi.disconnect();
|
||||||
|
WiFi.end();
|
||||||
|
WiFi.begin(ssid, password);
|
||||||
|
int connRes = WiFi.waitForConnectResult();
|
||||||
|
Serial.print("connRes: ");
|
||||||
|
Serial.println(connRes);
|
||||||
|
}
|
||||||
|
|
||||||
|
void loop() {
|
||||||
|
if (connect) {
|
||||||
|
Serial.println("Connect requested");
|
||||||
|
connect = false;
|
||||||
|
connectWifi();
|
||||||
|
lastConnectTry = millis();
|
||||||
|
}
|
||||||
|
{
|
||||||
|
unsigned int s = WiFi.status();
|
||||||
|
if (s == 0 && millis() > (lastConnectTry + 60000)) {
|
||||||
|
/* If WLAN disconnected and idle try to connect */
|
||||||
|
/* Don't set retry time too low as retry interfere the softAP operation */
|
||||||
|
connect = true;
|
||||||
|
}
|
||||||
|
if (status != s) { // WLAN status change
|
||||||
|
Serial.print("Status: ");
|
||||||
|
Serial.println(s);
|
||||||
|
status = s;
|
||||||
|
if (s == WL_CONNECTED) {
|
||||||
|
/* Just connected to WLAN */
|
||||||
|
Serial.println("");
|
||||||
|
Serial.print("Connected to ");
|
||||||
|
Serial.println(ssid);
|
||||||
|
Serial.print("IP address: ");
|
||||||
|
Serial.println(WiFi.localIP());
|
||||||
|
|
||||||
|
// Setup MDNS responder
|
||||||
|
if (!MDNS.begin(myHostname)) {
|
||||||
|
Serial.println("Error setting up MDNS responder!");
|
||||||
|
} else {
|
||||||
|
Serial.println("mDNS responder started");
|
||||||
|
// Add service to MDNS-SD
|
||||||
|
MDNS.addService("http", "tcp", 80);
|
||||||
|
}
|
||||||
|
} else if (s == WL_NO_SSID_AVAIL) {
|
||||||
|
WiFi.disconnect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (s == WL_CONNECTED) { MDNS.update(); }
|
||||||
|
}
|
||||||
|
// Do work:
|
||||||
|
// DNS
|
||||||
|
dnsServer.processNextRequest();
|
||||||
|
// HTTP
|
||||||
|
server.handleClient();
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
/** Load WLAN credentials from EEPROM */
|
||||||
|
void loadCredentials() {
|
||||||
|
EEPROM.begin(512);
|
||||||
|
EEPROM.get(0, ssid);
|
||||||
|
EEPROM.get(0 + sizeof(ssid), password);
|
||||||
|
char ok[2 + 1];
|
||||||
|
EEPROM.get(0 + sizeof(ssid) + sizeof(password), ok);
|
||||||
|
EEPROM.end();
|
||||||
|
if (String(ok) != String("OK")) {
|
||||||
|
ssid[0] = 0;
|
||||||
|
password[0] = 0;
|
||||||
|
}
|
||||||
|
Serial.println("Recovered credentials:");
|
||||||
|
Serial.println(ssid);
|
||||||
|
Serial.println(strlen(password) > 0 ? "********" : "<no password>");
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Store WLAN credentials to EEPROM */
|
||||||
|
void saveCredentials() {
|
||||||
|
EEPROM.begin(512);
|
||||||
|
EEPROM.put(0, ssid);
|
||||||
|
EEPROM.put(0 + sizeof(ssid), password);
|
||||||
|
char ok[2 + 1] = "OK";
|
||||||
|
EEPROM.put(0 + sizeof(ssid) + sizeof(password), ok);
|
||||||
|
EEPROM.commit();
|
||||||
|
EEPROM.end();
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,122 @@
|
||||||
|
/** Handle root or redirect to captive portal */
|
||||||
|
void handleRoot() {
|
||||||
|
if (captivePortal()) { // If caprive portal redirect instead of displaying the page.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
server.sendHeader("Cache-Control", "no-cache, no-store, must-revalidate");
|
||||||
|
server.sendHeader("Pragma", "no-cache");
|
||||||
|
server.sendHeader("Expires", "-1");
|
||||||
|
|
||||||
|
String Page;
|
||||||
|
Page += F("<!DOCTYPE html><html lang='en'><head>"
|
||||||
|
"<meta name='viewport' content='width=device-width'>"
|
||||||
|
"<title>CaptivePortal</title></head><body>"
|
||||||
|
"<h1>HELLO WORLD!!</h1>");
|
||||||
|
if (server.client().localIP() == apIP) {
|
||||||
|
Page += String(F("<p>You are connected through the soft AP: ")) + softAP_ssid + F("</p>");
|
||||||
|
} else {
|
||||||
|
Page += String(F("<p>You are connected through the wifi network: ")) + ssid + F("</p>");
|
||||||
|
}
|
||||||
|
Page += F("<p>You may want to <a href='/wifi'>config the wifi connection</a>.</p>"
|
||||||
|
"</body></html>");
|
||||||
|
|
||||||
|
server.send(200, "text/html", Page);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Redirect to captive portal if we got a request for another domain. Return true in that case so the page handler do not try to handle the request again. */
|
||||||
|
boolean captivePortal() {
|
||||||
|
if (!isIp(server.hostHeader()) && server.hostHeader() != (String(myHostname) + ".local")) {
|
||||||
|
Serial.println("Request redirected to captive portal");
|
||||||
|
server.sendHeader("Location", String("http://") + toStringIp(server.client().localIP()), true);
|
||||||
|
server.send(302, "text/plain", ""); // Empty content inhibits Content-length header so we have to close the socket ourselves.
|
||||||
|
server.client().stop(); // Stop is needed because we sent no content length
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Wifi config page handler */
|
||||||
|
void handleWifi() {
|
||||||
|
server.sendHeader("Cache-Control", "no-cache, no-store, must-revalidate");
|
||||||
|
server.sendHeader("Pragma", "no-cache");
|
||||||
|
server.sendHeader("Expires", "-1");
|
||||||
|
|
||||||
|
String Page;
|
||||||
|
Page += F("<!DOCTYPE html><html lang='en'><head>"
|
||||||
|
"<meta name='viewport' content='width=device-width'>"
|
||||||
|
"<title>CaptivePortal</title></head><body>"
|
||||||
|
"<h1>Wifi config</h1>");
|
||||||
|
if (server.client().localIP() == apIP) {
|
||||||
|
Page += String(F("<p>You are connected through the soft AP: ")) + softAP_ssid + F("</p>");
|
||||||
|
} else {
|
||||||
|
Page += String(F("<p>You are connected through the wifi network: ")) + ssid + F("</p>");
|
||||||
|
}
|
||||||
|
Page += String(F("\r\n<br />"
|
||||||
|
"<table><tr><th align='left'>SoftAP config</th></tr>"
|
||||||
|
"<tr><td>SSID "))
|
||||||
|
+ String(softAP_ssid) + F("</td></tr>"
|
||||||
|
"<tr><td>IP ")
|
||||||
|
+ toStringIp(WiFi.softAPIP()) + F("</td></tr>"
|
||||||
|
"</table>"
|
||||||
|
"\r\n<br />"
|
||||||
|
"<table><tr><th align='left'>WLAN config</th></tr>"
|
||||||
|
"<tr><td>SSID ")
|
||||||
|
+ String(ssid) + F("</td></tr>"
|
||||||
|
"<tr><td>IP ")
|
||||||
|
+ toStringIp(WiFi.localIP()) + F("</td></tr>"
|
||||||
|
"</table>"
|
||||||
|
"\r\n<br />"
|
||||||
|
"<table><tr><th align='left'>WLAN list (refresh if any missing)</th></tr>");
|
||||||
|
Serial.println("scan start");
|
||||||
|
int n = WiFi.scanNetworks();
|
||||||
|
Serial.println("scan done");
|
||||||
|
if (n > 0) {
|
||||||
|
for (int i = 0; i < n; i++) { Page += String(F("\r\n<tr><td>SSID ")) + WiFi.SSID(i) + ((WiFi.encryptionType(i) == ENC_TYPE_NONE) ? F(" ") : F(" *")) + F(" (") + WiFi.RSSI(i) + F(")</td></tr>"); }
|
||||||
|
} else {
|
||||||
|
Page += F("<tr><td>No WLAN found</td></tr>");
|
||||||
|
}
|
||||||
|
Page += F("</table>"
|
||||||
|
"\r\n<br /><form method='POST' action='wifisave'><h4>Connect to network:</h4>"
|
||||||
|
"<input type='text' placeholder='network' name='n'/>"
|
||||||
|
"<br /><input type='password' placeholder='password' name='p'/>"
|
||||||
|
"<br /><input type='submit' value='Connect/Disconnect'/></form>"
|
||||||
|
"<p>You may want to <a href='/'>return to the home page</a>.</p>"
|
||||||
|
"</body></html>");
|
||||||
|
server.send(200, "text/html", Page);
|
||||||
|
server.client().stop(); // Stop is needed because we sent no content length
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Handle the WLAN save form and redirect to WLAN config page again */
|
||||||
|
void handleWifiSave() {
|
||||||
|
Serial.println("wifi save");
|
||||||
|
server.arg("n").toCharArray(ssid, sizeof(ssid) - 1);
|
||||||
|
server.arg("p").toCharArray(password, sizeof(password) - 1);
|
||||||
|
server.sendHeader("Location", "wifi", true);
|
||||||
|
server.sendHeader("Cache-Control", "no-cache, no-store, must-revalidate");
|
||||||
|
server.sendHeader("Pragma", "no-cache");
|
||||||
|
server.sendHeader("Expires", "-1");
|
||||||
|
server.send(302, "text/plain", ""); // Empty content inhibits Content-length header so we have to close the socket ourselves.
|
||||||
|
server.client().stop(); // Stop is needed because we sent no content length
|
||||||
|
saveCredentials();
|
||||||
|
connect = strlen(ssid) > 0; // Request WLAN connect with new credentials if there is a SSID
|
||||||
|
}
|
||||||
|
|
||||||
|
void handleNotFound() {
|
||||||
|
if (captivePortal()) { // If caprive portal redirect instead of displaying the error page.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String message = F("File Not Found\n\n");
|
||||||
|
message += F("URI: ");
|
||||||
|
message += server.uri();
|
||||||
|
message += F("\nMethod: ");
|
||||||
|
message += (server.method() == HTTP_GET) ? "GET" : "POST";
|
||||||
|
message += F("\nArguments: ");
|
||||||
|
message += server.args();
|
||||||
|
message += F("\n");
|
||||||
|
|
||||||
|
for (uint8_t i = 0; i < server.args(); i++) { message += String(F(" ")) + server.argName(i) + F(": ") + server.arg(i) + F("\n"); }
|
||||||
|
server.sendHeader("Cache-Control", "no-cache, no-store, must-revalidate");
|
||||||
|
server.sendHeader("Pragma", "no-cache");
|
||||||
|
server.sendHeader("Expires", "-1");
|
||||||
|
server.send(404, "text/plain", message);
|
||||||
|
}
|
||||||
16
libraries/DNSServer/examples/CaptivePortalAdvanced/tools.ino
Normal file
16
libraries/DNSServer/examples/CaptivePortalAdvanced/tools.ino
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
/** Is this an IP? */
|
||||||
|
boolean isIp(String str) {
|
||||||
|
for (size_t i = 0; i < str.length(); i++) {
|
||||||
|
int c = str.charAt(i);
|
||||||
|
if (c != '.' && (c < '0' || c > '9')) { return false; }
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** IP to String? */
|
||||||
|
String toStringIp(IPAddress ip) {
|
||||||
|
String res = "";
|
||||||
|
for (int i = 0; i < 3; i++) { res += String((ip >> (8 * i)) & 0xFF) + "."; }
|
||||||
|
res += String(((ip >> 8 * 3)) & 0xFF);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
41
libraries/DNSServer/examples/DNSServer/DNSServer.ino
Normal file
41
libraries/DNSServer/examples/DNSServer/DNSServer.ino
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
#include <WiFi.h>
|
||||||
|
#include <DNSServer.h>
|
||||||
|
#include <WebServer.h>
|
||||||
|
|
||||||
|
const byte DNS_PORT = 53;
|
||||||
|
IPAddress apIP(172, 217, 28, 1);
|
||||||
|
DNSServer dnsServer;
|
||||||
|
WebServer webServer(80);
|
||||||
|
|
||||||
|
void setup() {
|
||||||
|
WiFi.mode(WIFI_AP);
|
||||||
|
WiFi.softAPConfig(apIP, apIP, IPAddress(255, 255, 255, 0));
|
||||||
|
WiFi.softAP("picow", "12345678");
|
||||||
|
|
||||||
|
// modify TTL associated with the domain name (in seconds)
|
||||||
|
// default is 60 seconds
|
||||||
|
dnsServer.setTTL(300);
|
||||||
|
// set which return code will be used for all other domains (e.g. sending
|
||||||
|
// ServerFailure instead of NonExistentDomain will reduce number of queries
|
||||||
|
// sent by clients)
|
||||||
|
// default is DNSReplyCode::NonExistentDomain
|
||||||
|
dnsServer.setErrorReplyCode(DNSReplyCode::ServerFailure);
|
||||||
|
|
||||||
|
// start DNS server for a specific domain name
|
||||||
|
dnsServer.start(DNS_PORT, "www.example.com", apIP);
|
||||||
|
|
||||||
|
// simple HTTP server to see that DNS server is working
|
||||||
|
webServer.onNotFound([]() {
|
||||||
|
String message = "Hello World!\n\n";
|
||||||
|
message += "URI: ";
|
||||||
|
message += webServer.uri();
|
||||||
|
|
||||||
|
webServer.send(200, "text/plain", message);
|
||||||
|
});
|
||||||
|
webServer.begin();
|
||||||
|
}
|
||||||
|
|
||||||
|
void loop() {
|
||||||
|
dnsServer.processNextRequest();
|
||||||
|
webServer.handleClient();
|
||||||
|
}
|
||||||
|
|
@ -165,19 +165,19 @@ HTTPUpdateResult HTTPUpdate::handleUpdate(HTTPClient& http, const String& curren
|
||||||
http.useHTTP10(true);
|
http.useHTTP10(true);
|
||||||
http.setTimeout(_httpClientTimeout);
|
http.setTimeout(_httpClientTimeout);
|
||||||
http.setFollowRedirects(_followRedirects);
|
http.setFollowRedirects(_followRedirects);
|
||||||
http.setUserAgent(F("-http-Update"));
|
http.setUserAgent(F("Pico-HTTP-Update"));
|
||||||
http.addHeader(F("x--Chip-ID"), String(rp2040.getChipID()));
|
http.addHeader(F("x-Pico-Chip-ID"), String(rp2040.getChipID()));
|
||||||
http.addHeader(F("x--STA-MAC"), WiFi.macAddress());
|
http.addHeader(F("x-Pico-STA-MAC"), WiFi.macAddress());
|
||||||
http.addHeader(F("x--AP-MAC"), WiFi.softAPmacAddress());
|
http.addHeader(F("x-Pico-AP-MAC"), WiFi.softAPmacAddress());
|
||||||
|
|
||||||
if (spiffs) {
|
if (spiffs) {
|
||||||
http.addHeader(F("x--mode"), F("spiffs"));
|
http.addHeader(F("x-Pico-Mode"), F("spiffs"));
|
||||||
} else {
|
} else {
|
||||||
http.addHeader(F("x--mode"), F("sketch"));
|
http.addHeader(F("x-Pico-Mode"), F("sketch"));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (currentVersion && currentVersion[0] != 0x00) {
|
if (currentVersion && currentVersion[0] != 0x00) {
|
||||||
http.addHeader(F("x--version"), currentVersion);
|
http.addHeader(F("x-Pico-Version"), currentVersion);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_user != "" && _password != "") {
|
if (_user != "" && _password != "") {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,122 @@
|
||||||
|
/*
|
||||||
|
SecureBearSSLUpdater - SSL encrypted, password-protected firmware update
|
||||||
|
|
||||||
|
This example starts a HTTPS server on the Pico to allow firmware updates
|
||||||
|
to be performed. All communication, including the username and password,
|
||||||
|
is encrypted via SSL. Be sure to update the SSID and PASSWORD before running
|
||||||
|
to allow connection to your WiFi network.
|
||||||
|
|
||||||
|
To upload through terminal you can use:
|
||||||
|
curl -u admin:admin -F "image=@firmware.bin" picow-webupdate.local/firmware
|
||||||
|
|
||||||
|
Adapted by Earle F. Philhower, III, from the SecureWebUpdater.ino example.
|
||||||
|
This example is released into the public domain.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <WiFi.h>
|
||||||
|
#include <WiFiClient.h>
|
||||||
|
#include <WebServerSecure.h>
|
||||||
|
#include <LEAmDNS.h>
|
||||||
|
#include <HTTPUpdateServer.h>
|
||||||
|
|
||||||
|
#ifndef STASSID
|
||||||
|
#define STASSID "your-ssid"
|
||||||
|
#define STAPSK "your-password"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
const char* host = "picow-webupdate";
|
||||||
|
const char* update_path = "/firmware";
|
||||||
|
const char* update_username = "admin";
|
||||||
|
const char* update_password = "admin";
|
||||||
|
const char* ssid = STASSID;
|
||||||
|
const char* password = STAPSK;
|
||||||
|
|
||||||
|
WebServerSecure httpServer(443);
|
||||||
|
HTTPUpdateServerSecure httpUpdater;
|
||||||
|
|
||||||
|
static const char serverCert[] PROGMEM = R"EOF(
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIDSzCCAjMCCQD2ahcfZAwXxDANBgkqhkiG9w0BAQsFADCBiTELMAkGA1UEBhMC
|
||||||
|
VVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDU9yYW5nZSBDb3VudHkx
|
||||||
|
EDAOBgNVBAoMB1ByaXZhZG8xGjAYBgNVBAMMEXNlcnZlci56bGFiZWwuY29tMR8w
|
||||||
|
HQYJKoZIhvcNAQkBFhBlYXJsZUB6bGFiZWwuY29tMB4XDTE4MDMwNjA1NDg0NFoX
|
||||||
|
DTE5MDMwNjA1NDg0NFowRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3Rh
|
||||||
|
dGUxITAfBgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDCCASIwDQYJKoZI
|
||||||
|
hvcNAQEBBQADggEPADCCAQoCggEBAPVKBwbZ+KDSl40YCDkP6y8Sv4iNGvEOZg8Y
|
||||||
|
X7sGvf/xZH7UiCBWPFIRpNmDSaZ3yjsmFqm6sLiYSGSdrBCFqdt9NTp2r7hga6Sj
|
||||||
|
oASSZY4B9pf+GblDy5m10KDx90BFKXdPMCLT+o76Nx9PpCvw13A848wHNG3bpBgI
|
||||||
|
t+w/vJCX3bkRn8yEYAU6GdMbYe7v446hX3kY5UmgeJFr9xz1kq6AzYrMt/UHhNzO
|
||||||
|
S+QckJaY0OGWvmTNspY3xCbbFtIDkCdBS8CZAw+itnofvnWWKQEXlt6otPh5njwy
|
||||||
|
+O1t/Q+Z7OMDYQaH02IQx3188/kW3FzOY32knER1uzjmRO+jhA8CAwEAATANBgkq
|
||||||
|
hkiG9w0BAQsFAAOCAQEAnDrROGRETB0woIcI1+acY1yRq4yAcH2/hdq2MoM+DCyM
|
||||||
|
E8CJaOznGR9ND0ImWpTZqomHOUkOBpvu7u315blQZcLbL1LfHJGRTCHVhvVrcyEb
|
||||||
|
fWTnRtAQdlirUm/obwXIitoz64VSbIVzcqqfg9C6ZREB9JbEX98/9Wp2gVY+31oC
|
||||||
|
JfUvYadSYxh3nblvA4OL+iEZiW8NE3hbW6WPXxvS7Euge0uWMPc4uEcnsE0ZVG3m
|
||||||
|
+TGimzSdeWDvGBRWZHXczC2zD4aoE5vrl+GD2i++c6yjL/otHfYyUpzUfbI2hMAA
|
||||||
|
5tAF1D5vAAwA8nfPysumlLsIjohJZo4lgnhB++AlOg==
|
||||||
|
-----END CERTIFICATE-----
|
||||||
|
)EOF";
|
||||||
|
|
||||||
|
static const char serverKey[] PROGMEM = R"EOF(
|
||||||
|
-----BEGIN RSA PRIVATE KEY-----
|
||||||
|
MIIEpQIBAAKCAQEA9UoHBtn4oNKXjRgIOQ/rLxK/iI0a8Q5mDxhfuwa9//FkftSI
|
||||||
|
IFY8UhGk2YNJpnfKOyYWqbqwuJhIZJ2sEIWp2301OnavuGBrpKOgBJJljgH2l/4Z
|
||||||
|
uUPLmbXQoPH3QEUpd08wItP6jvo3H0+kK/DXcDzjzAc0bdukGAi37D+8kJfduRGf
|
||||||
|
zIRgBToZ0xth7u/jjqFfeRjlSaB4kWv3HPWSroDNisy39QeE3M5L5ByQlpjQ4Za+
|
||||||
|
ZM2yljfEJtsW0gOQJ0FLwJkDD6K2eh++dZYpAReW3qi0+HmePDL47W39D5ns4wNh
|
||||||
|
BofTYhDHfXzz+RbcXM5jfaScRHW7OOZE76OEDwIDAQABAoIBAQDKov5NFbNFQNR8
|
||||||
|
djcM1O7Is6dRaqiwLeH4ZH1pZ3d9QnFwKanPdQ5eCj9yhfhJMrr5xEyCqT0nMn7T
|
||||||
|
yEIGYDXjontfsf8WxWkH2TjvrfWBrHOIOx4LJEvFzyLsYxiMmtZXvy6YByD+Dw2M
|
||||||
|
q2GH/24rRdI2klkozIOyazluTXU8yOsSGxHr/aOa9/sZISgLmaGOOuKI/3Zqjdhr
|
||||||
|
eHeSqoQFt3xXa8jw01YubQUDw/4cv9rk2ytTdAoQUimiKtgtjsggpP1LTq4xcuqN
|
||||||
|
d4jWhTcnorWpbD2cVLxrEbnSR3VuBCJEZv5axg5ZPxLEnlcId8vMtvTRb5nzzszn
|
||||||
|
geYUWDPhAoGBAPyKVNqqwQl44oIeiuRM2FYenMt4voVaz3ExJX2JysrG0jtCPv+Y
|
||||||
|
84R6Cv3nfITz3EZDWp5sW3OwoGr77lF7Tv9tD6BptEmgBeuca3SHIdhG2MR+tLyx
|
||||||
|
/tkIAarxQcTGsZaSqra3gXOJCMz9h2P5dxpdU+0yeMmOEnAqgQ8qtNBfAoGBAPim
|
||||||
|
RAtnrd0WSlCgqVGYFCvDh1kD5QTNbZc+1PcBHbVV45EmJ2fLXnlDeplIZJdYxmzu
|
||||||
|
DMOxZBYgfeLY9exje00eZJNSj/csjJQqiRftrbvYY7m5njX1kM5K8x4HlynQTDkg
|
||||||
|
rtKO0YZJxxmjRTbFGMegh1SLlFLRIMtehNhOgipRAoGBAPnEEpJGCS9GGLfaX0HW
|
||||||
|
YqwiEK8Il12q57mqgsq7ag7NPwWOymHesxHV5mMh/Dw+NyBi4xAGWRh9mtrUmeqK
|
||||||
|
iyICik773Gxo0RIqnPgd4jJWN3N3YWeynzulOIkJnSNx5BforOCTc3uCD2s2YB5X
|
||||||
|
jx1LKoNQxLeLRN8cmpIWicf/AoGBANjRSsZTKwV9WWIDJoHyxav/vPb+8WYFp8lZ
|
||||||
|
zaRxQbGM6nn4NiZI7OF62N3uhWB/1c7IqTK/bVHqFTuJCrCNcsgld3gLZ2QWYaMV
|
||||||
|
kCPgaj1BjHw4AmB0+EcajfKilcqtSroJ6MfMJ6IclVOizkjbByeTsE4lxDmPCDSt
|
||||||
|
/9MKanBxAoGAY9xo741Pn9WUxDyRplww606ccdNf/ksHWNc/Y2B5SPwxxSnIq8nO
|
||||||
|
j01SmsCUYVFAgZVOTiiycakjYLzxlc6p8BxSVqy6LlJqn95N8OXoQ+bkwUux/ekg
|
||||||
|
gz5JWYhbD6c38khSzJb0pNXCo3EuYAVa36kDM96k1BtWuhRS10Q1VXk=
|
||||||
|
-----END RSA PRIVATE KEY-----
|
||||||
|
)EOF";
|
||||||
|
|
||||||
|
|
||||||
|
void setup() {
|
||||||
|
|
||||||
|
Serial.begin(115200);
|
||||||
|
Serial.println();
|
||||||
|
Serial.println("Booting Sketch...");
|
||||||
|
WiFi.mode(WIFI_STA);
|
||||||
|
WiFi.begin(ssid, password);
|
||||||
|
|
||||||
|
while (WiFi.waitForConnectResult() != WL_CONNECTED) {
|
||||||
|
WiFi.begin(ssid, password);
|
||||||
|
Serial.println("WiFi failed, retrying.");
|
||||||
|
}
|
||||||
|
|
||||||
|
configTime(3 * 3600, 0, "pool.ntp.org", "time.nist.gov");
|
||||||
|
|
||||||
|
MDNS.begin(host);
|
||||||
|
|
||||||
|
httpServer.getServer().setRSACert(new BearSSL::X509List(serverCert), new BearSSL::PrivateKey(serverKey));
|
||||||
|
httpUpdater.setup(&httpServer, update_path, update_username, update_password);
|
||||||
|
httpServer.begin();
|
||||||
|
|
||||||
|
MDNS.addService("https", "tcp", 443);
|
||||||
|
Serial.printf("BearSSLUpdateServer ready!\nOpen https://%s.local%s in "
|
||||||
|
"your browser and login with username '%s' and password "
|
||||||
|
"'%s'\n",
|
||||||
|
host, update_path, update_username, update_password);
|
||||||
|
}
|
||||||
|
|
||||||
|
void loop() {
|
||||||
|
httpServer.handleClient();
|
||||||
|
MDNS.update();
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,48 @@
|
||||||
|
/*
|
||||||
|
To upload through terminal you can use: curl -F "image=@firmware.bin" picow-webupdate.local/update
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <WiFi.h>
|
||||||
|
#include <WiFiClient.h>
|
||||||
|
#include <WebServer.h>
|
||||||
|
#include <LEAmDNS.h>
|
||||||
|
#include <HTTPUpdateServer.h>
|
||||||
|
|
||||||
|
#ifndef STASSID
|
||||||
|
#define STASSID "your-ssid"
|
||||||
|
#define STAPSK "your-password"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
const char* host = "picow-webupdate";
|
||||||
|
const char* ssid = STASSID;
|
||||||
|
const char* password = STAPSK;
|
||||||
|
|
||||||
|
WebServer httpServer(80);
|
||||||
|
HTTPUpdateServer httpUpdater;
|
||||||
|
|
||||||
|
void setup(void) {
|
||||||
|
|
||||||
|
Serial.begin(115200);
|
||||||
|
Serial.println();
|
||||||
|
Serial.println("Booting Sketch...");
|
||||||
|
WiFi.mode(WIFI_STA);
|
||||||
|
WiFi.begin(ssid, password);
|
||||||
|
|
||||||
|
while (WiFi.waitForConnectResult() != WL_CONNECTED) {
|
||||||
|
WiFi.begin(ssid, password);
|
||||||
|
Serial.println("WiFi failed, retrying.");
|
||||||
|
}
|
||||||
|
|
||||||
|
MDNS.begin(host);
|
||||||
|
|
||||||
|
httpUpdater.setup(&httpServer);
|
||||||
|
httpServer.begin();
|
||||||
|
|
||||||
|
MDNS.addService("http", "tcp", 80);
|
||||||
|
Serial.printf("HTTPUpdateServer ready! Open http://%s.local/update in your browser\n", host);
|
||||||
|
}
|
||||||
|
|
||||||
|
void loop(void) {
|
||||||
|
httpServer.handleClient();
|
||||||
|
MDNS.update();
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,51 @@
|
||||||
|
/*
|
||||||
|
To upload through terminal you can use: curl -u admin:admin -F "image=@firmware.bin" picow-webupdate.local/firmware
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <WiFi.h>
|
||||||
|
#include <WiFiClient.h>
|
||||||
|
#include <WebServer.h>
|
||||||
|
#include <LEAmDNS.h>
|
||||||
|
#include <HTTPUpdateServer.h>
|
||||||
|
|
||||||
|
#ifndef STASSID
|
||||||
|
#define STASSID "your-ssid"
|
||||||
|
#define STAPSK "your-password"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
const char* host = "picow-webupdate";
|
||||||
|
const char* update_path = "/firmware";
|
||||||
|
const char* update_username = "admin";
|
||||||
|
const char* update_password = "admin";
|
||||||
|
const char* ssid = STASSID;
|
||||||
|
const char* password = STAPSK;
|
||||||
|
|
||||||
|
WebServer httpServer(80);
|
||||||
|
HTTPUpdateServer httpUpdater;
|
||||||
|
|
||||||
|
void setup(void) {
|
||||||
|
|
||||||
|
Serial.begin(115200);
|
||||||
|
Serial.println();
|
||||||
|
Serial.println("Booting Sketch...");
|
||||||
|
WiFi.mode(WIFI_STA);
|
||||||
|
WiFi.begin(ssid, password);
|
||||||
|
|
||||||
|
while (WiFi.waitForConnectResult() != WL_CONNECTED) {
|
||||||
|
WiFi.begin(ssid, password);
|
||||||
|
Serial.println("WiFi failed, retrying.");
|
||||||
|
}
|
||||||
|
|
||||||
|
MDNS.begin(host);
|
||||||
|
|
||||||
|
httpUpdater.setup(&httpServer, update_path, update_username, update_password);
|
||||||
|
httpServer.begin();
|
||||||
|
|
||||||
|
MDNS.addService("http", "tcp", 80);
|
||||||
|
Serial.printf("HTTPUpdateServer ready! Open http://%s.local%s in your browser and login with username '%s' and password '%s'\n", host, update_path, update_username, update_password);
|
||||||
|
}
|
||||||
|
|
||||||
|
void loop(void) {
|
||||||
|
httpServer.handleClient();
|
||||||
|
MDNS.update();
|
||||||
|
}
|
||||||
21
libraries/HTTPUpdateServer/keywords.txt
Normal file
21
libraries/HTTPUpdateServer/keywords.txt
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
#######################################
|
||||||
|
# Syntax Coloring Map For HTTPUpdateServer
|
||||||
|
#######################################
|
||||||
|
|
||||||
|
#######################################
|
||||||
|
# Datatypes (KEYWORD1)
|
||||||
|
#######################################
|
||||||
|
|
||||||
|
HTTPUpdateServer KEYWORD1
|
||||||
|
HTTPUpdateServerSecure KEYWORD1
|
||||||
|
|
||||||
|
#######################################
|
||||||
|
# Methods and Functions (KEYWORD2)
|
||||||
|
#######################################
|
||||||
|
|
||||||
|
begin KEYWORD2
|
||||||
|
setup KEYWORD2
|
||||||
|
|
||||||
|
#######################################
|
||||||
|
# Constants (LITERAL1)
|
||||||
|
#######################################
|
||||||
10
libraries/HTTPUpdateServer/library.properties
Normal file
10
libraries/HTTPUpdateServer/library.properties
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
name=HTTPUpdateServer
|
||||||
|
version=1.0
|
||||||
|
author=Ivan Grokhotkov, Miguel Angel Ajo
|
||||||
|
maintainer=Earle F. Philhower, III <earlephilhower@yahoo.com>
|
||||||
|
sentence=Simple HTTP Update server based on the Pico WebServer
|
||||||
|
paragraph=The library accepts HTTP post requests to the /update url, and updates the Pico firmware.
|
||||||
|
category=Communication
|
||||||
|
url=https://github.com/earlephilhower/arduino-pico
|
||||||
|
architectures=rp2040
|
||||||
|
dot_a_linkage=false
|
||||||
161
libraries/HTTPUpdateServer/src/HTTPUpdateServer-impl.h
Normal file
161
libraries/HTTPUpdateServer/src/HTTPUpdateServer-impl.h
Normal file
|
|
@ -0,0 +1,161 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <Arduino.h>
|
||||||
|
#include <WiFiClient.h>
|
||||||
|
#include <WiFiServer.h>
|
||||||
|
#include <WebServer.h>
|
||||||
|
#include <WiFiUdp.h>
|
||||||
|
#include <LittleFS.h>
|
||||||
|
#include "StreamString.h"
|
||||||
|
#include "HTTPUpdateServer.h"
|
||||||
|
|
||||||
|
extern uint8_t _FS_start;
|
||||||
|
extern uint8_t _FS_end;
|
||||||
|
|
||||||
|
|
||||||
|
static const char serverIndex[] PROGMEM =
|
||||||
|
R"(<!DOCTYPE html>
|
||||||
|
<html lang='en'>
|
||||||
|
<head>
|
||||||
|
<meta charset='utf-8'>
|
||||||
|
<meta name='viewport' content='width=device-width,initial-scale=1'/>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<form method='POST' action='' enctype='multipart/form-data'>
|
||||||
|
Firmware:<br>
|
||||||
|
<input type='file' accept='.bin,.bin.gz' name='firmware'>
|
||||||
|
<input type='submit' value='Update Firmware'>
|
||||||
|
</form>
|
||||||
|
<form method='POST' action='' enctype='multipart/form-data'>
|
||||||
|
FileSystem:<br>
|
||||||
|
<input type='file' accept='.bin,.bin.gz' name='filesystem'>
|
||||||
|
<input type='submit' value='Update FileSystem'>
|
||||||
|
</form>
|
||||||
|
</body>
|
||||||
|
</html>)";
|
||||||
|
static const char successResponse[] PROGMEM =
|
||||||
|
"<META http-equiv=\"refresh\" content=\"15;URL=/\">Update Success! Rebooting...";
|
||||||
|
|
||||||
|
template <typename ServerType, int ServerPort>
|
||||||
|
HTTPUpdateServerTemplate<ServerType, ServerPort>::HTTPUpdateServerTemplate(bool serial_debug) {
|
||||||
|
_serial_output = serial_debug;
|
||||||
|
_server = NULL;
|
||||||
|
_username = "";
|
||||||
|
_password = "";
|
||||||
|
_authenticated = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename ServerType, int ServerPort>
|
||||||
|
void HTTPUpdateServerTemplate<ServerType, ServerPort>::setup(WebServerTemplate<ServerType, ServerPort> *server, const String& path, const String& username, const String& password) {
|
||||||
|
_server = server;
|
||||||
|
_username = username;
|
||||||
|
_password = password;
|
||||||
|
|
||||||
|
// handler for the /update form page
|
||||||
|
_server->on(path.c_str(), HTTP_GET, [&]() {
|
||||||
|
if (_username != "" && _password != "" && !_server->authenticate(_username.c_str(), _password.c_str())) {
|
||||||
|
return _server->requestAuthentication();
|
||||||
|
}
|
||||||
|
_server->send_P(200, PSTR("text/html"), serverIndex);
|
||||||
|
});
|
||||||
|
|
||||||
|
// handler for the /update form page - preflight options
|
||||||
|
_server->on(path.c_str(), HTTP_OPTIONS, [&]() {
|
||||||
|
_server->sendHeader("Access-Control-Allow-Headers", "*");
|
||||||
|
_server->sendHeader("Access-Control-Allow-Origin", "*");
|
||||||
|
_server->send(200, F("text/html"), String(F("y")));
|
||||||
|
}, [&]() {
|
||||||
|
_authenticated = (_username == "" || _password == "" || _server->authenticate(_username.c_str(), _password.c_str()));
|
||||||
|
if (!_authenticated) {
|
||||||
|
if (_serial_output) {
|
||||||
|
Serial.printf("Unauthenticated Update\n");
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// handler for the /update form POST (once file upload finishes)
|
||||||
|
_server->on(path.c_str(), HTTP_POST, [&]() {
|
||||||
|
_server->sendHeader("Access-Control-Allow-Headers", "*");
|
||||||
|
_server->sendHeader("Access-Control-Allow-Origin", "*");
|
||||||
|
if (!_authenticated) {
|
||||||
|
return _server->requestAuthentication();
|
||||||
|
}
|
||||||
|
if (Update.hasError()) {
|
||||||
|
_server->send(200, F("text/html"), String(F("Update error: ")) + _updaterError);
|
||||||
|
} else {
|
||||||
|
_server->client().setNoDelay(true);
|
||||||
|
_server->send_P(200, PSTR("text/html"), successResponse);
|
||||||
|
delay(100);
|
||||||
|
_server->client().stop();
|
||||||
|
rp2040.restart();
|
||||||
|
}
|
||||||
|
}, [&]() {
|
||||||
|
// handler for the file upload, gets the sketch bytes, and writes
|
||||||
|
// them through the Update object
|
||||||
|
HTTPUpload& upload = _server->upload();
|
||||||
|
|
||||||
|
if (upload.status == UPLOAD_FILE_START) {
|
||||||
|
_updaterError = "";
|
||||||
|
|
||||||
|
_authenticated = (_username == "" || _password == "" || _server->authenticate(_username.c_str(), _password.c_str()));
|
||||||
|
if (!_authenticated) {
|
||||||
|
if (_serial_output) {
|
||||||
|
Serial.printf("Unauthenticated Update\n");
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_serial_output) {
|
||||||
|
Serial.printf("Update: %s\n", upload.filename.c_str());
|
||||||
|
}
|
||||||
|
if (upload.name == "filesystem") {
|
||||||
|
size_t fsSize = ((size_t)&_FS_end - (size_t)&_FS_start);
|
||||||
|
LittleFS.end();
|
||||||
|
if (!Update.begin(fsSize, U_FS)) { //start with max available size
|
||||||
|
if (_serial_output) {
|
||||||
|
Update.printError(Serial);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
FSInfo64 i;
|
||||||
|
LittleFS.begin();
|
||||||
|
LittleFS.info64(i);
|
||||||
|
uint32_t maxSketchSpace = i.totalBytes - i.usedBytes;
|
||||||
|
if (!Update.begin(maxSketchSpace, U_FLASH)) { //start with max available size
|
||||||
|
_setUpdaterError();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (_authenticated && upload.status == UPLOAD_FILE_WRITE && !_updaterError.length()) {
|
||||||
|
if (_serial_output) {
|
||||||
|
Serial.printf(".");
|
||||||
|
}
|
||||||
|
if (Update.write(upload.buf, upload.currentSize) != upload.currentSize) {
|
||||||
|
_setUpdaterError();
|
||||||
|
}
|
||||||
|
} else if (_authenticated && upload.status == UPLOAD_FILE_END && !_updaterError.length()) {
|
||||||
|
if (Update.end(true)) { //true to set the size to the current progress
|
||||||
|
if (_serial_output) {
|
||||||
|
Serial.printf("Update Success: %zu\nRebooting...\n", upload.totalSize);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
_setUpdaterError();
|
||||||
|
}
|
||||||
|
} else if (_authenticated && upload.status == UPLOAD_FILE_ABORTED) {
|
||||||
|
Update.end();
|
||||||
|
if (_serial_output) {
|
||||||
|
Serial.println("Update was aborted");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename ServerType, int ServerPort>
|
||||||
|
void HTTPUpdateServerTemplate<ServerType, ServerPort>::_setUpdaterError() {
|
||||||
|
if (_serial_output) {
|
||||||
|
Update.printError(Serial);
|
||||||
|
}
|
||||||
|
StreamString str;
|
||||||
|
Update.printError(str);
|
||||||
|
_updaterError = str.c_str();
|
||||||
|
}
|
||||||
44
libraries/HTTPUpdateServer/src/HTTPUpdateServer.h
Normal file
44
libraries/HTTPUpdateServer/src/HTTPUpdateServer.h
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <WebServer.h>
|
||||||
|
|
||||||
|
template <typename ServerType, int ServerPort>
|
||||||
|
class HTTPUpdateServerTemplate {
|
||||||
|
public:
|
||||||
|
HTTPUpdateServerTemplate(bool serial_debug = false);
|
||||||
|
|
||||||
|
void setup(WebServerTemplate<ServerType, ServerPort> *server) {
|
||||||
|
setup(server, "", "");
|
||||||
|
}
|
||||||
|
|
||||||
|
void setup(WebServerTemplate<ServerType, ServerPort> *server, const String& path) {
|
||||||
|
setup(server, path, "", "");
|
||||||
|
}
|
||||||
|
|
||||||
|
void setup(WebServerTemplate<ServerType, ServerPort> *server, const String& username, const String& password) {
|
||||||
|
setup(server, "/update", username, password);
|
||||||
|
}
|
||||||
|
|
||||||
|
void setup(WebServerTemplate<ServerType, ServerPort> *server, const String& path, const String& username, const String& password);
|
||||||
|
|
||||||
|
void updateCredentials(const String& username, const String& password) {
|
||||||
|
_username = username;
|
||||||
|
_password = password;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void _setUpdaterError();
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool _serial_output;
|
||||||
|
WebServerTemplate<ServerType, ServerPort> *_server;
|
||||||
|
String _username;
|
||||||
|
String _password;
|
||||||
|
bool _authenticated;
|
||||||
|
String _updaterError;
|
||||||
|
};
|
||||||
|
|
||||||
|
#include "HTTPUpdateServer-impl.h"
|
||||||
|
|
||||||
|
using HTTPUpdateServer = HTTPUpdateServerTemplate<WiFiServer, 80>;
|
||||||
|
using HTTPUpdateServerSecure = HTTPUpdateServerTemplate<WiFiServerSecure, 443>;
|
||||||
|
|
@ -0,0 +1,152 @@
|
||||||
|
/*
|
||||||
|
Copyright (c) 2015, Majenko Technologies
|
||||||
|
All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without modification,
|
||||||
|
are permitted provided that the following conditions are met:
|
||||||
|
|
||||||
|
* * Redistributions of source code must retain the above copyright notice, this
|
||||||
|
list of conditions and the following disclaimer.
|
||||||
|
|
||||||
|
* * Redistributions in binary form must reproduce the above copyright notice, this
|
||||||
|
list of conditions and the following disclaimer in the documentation and/or
|
||||||
|
other materials provided with the distribution.
|
||||||
|
|
||||||
|
* * Neither the name of Majenko Technologies nor the names of its
|
||||||
|
contributors may be used to endorse or promote products derived from
|
||||||
|
this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||||
|
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
|
||||||
|
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||||
|
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <WiFi.h>
|
||||||
|
#include <WiFiClient.h>
|
||||||
|
#include <WebServer.h>
|
||||||
|
#include <LEAmDNS.h>
|
||||||
|
|
||||||
|
#ifndef STASSID
|
||||||
|
#define STASSID "your-ssid"
|
||||||
|
#define STAPSK "your-password"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
const char *ssid = STASSID;
|
||||||
|
const char *password = STAPSK;
|
||||||
|
|
||||||
|
WebServer server(80);
|
||||||
|
|
||||||
|
const int led = 13;
|
||||||
|
|
||||||
|
void handleRoot() {
|
||||||
|
digitalWrite(led, 1);
|
||||||
|
char temp[400];
|
||||||
|
int sec = millis() / 1000;
|
||||||
|
int min = sec / 60;
|
||||||
|
int hr = min / 60;
|
||||||
|
|
||||||
|
snprintf(temp, 400,
|
||||||
|
|
||||||
|
"<html>\
|
||||||
|
<head>\
|
||||||
|
<meta http-equiv='refresh' content='5'/>\
|
||||||
|
<title>Pico-W Demo</title>\
|
||||||
|
<style>\
|
||||||
|
body { background-color: #cccccc; font-family: Arial, Helvetica, Sans-Serif; Color: #000088; }\
|
||||||
|
</style>\
|
||||||
|
</head>\
|
||||||
|
<body>\
|
||||||
|
<h1>Hello from the Pico W!</h1>\
|
||||||
|
<p>Uptime: %02d:%02d:%02d</p>\
|
||||||
|
<img src=\"/test.svg\" />\
|
||||||
|
</body>\
|
||||||
|
</html>",
|
||||||
|
|
||||||
|
hr, min % 60, sec % 60);
|
||||||
|
server.send(200, "text/html", temp);
|
||||||
|
digitalWrite(led, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void handleNotFound() {
|
||||||
|
digitalWrite(led, 1);
|
||||||
|
String message = "File Not Found\n\n";
|
||||||
|
message += "URI: ";
|
||||||
|
message += server.uri();
|
||||||
|
message += "\nMethod: ";
|
||||||
|
message += (server.method() == HTTP_GET) ? "GET" : "POST";
|
||||||
|
message += "\nArguments: ";
|
||||||
|
message += server.args();
|
||||||
|
message += "\n";
|
||||||
|
|
||||||
|
for (uint8_t i = 0; i < server.args(); i++) {
|
||||||
|
message += " " + server.argName(i) + ": " + server.arg(i) + "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
server.send(404, "text/plain", message);
|
||||||
|
digitalWrite(led, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void drawGraph() {
|
||||||
|
String out;
|
||||||
|
out.reserve(2600);
|
||||||
|
char temp[70];
|
||||||
|
out += "<svg xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\" width=\"400\" height=\"150\">\n";
|
||||||
|
out += "<rect width=\"400\" height=\"150\" fill=\"rgb(250, 230, 210)\" stroke-width=\"1\" stroke=\"rgb(0, 0, 0)\" />\n";
|
||||||
|
out += "<g stroke=\"black\">\n";
|
||||||
|
int y = rand() % 130;
|
||||||
|
for (int x = 10; x < 390; x += 10) {
|
||||||
|
int y2 = rand() % 130;
|
||||||
|
sprintf(temp, "<line x1=\"%d\" y1=\"%d\" x2=\"%d\" y2=\"%d\" stroke-width=\"1\" />\n", x, 140 - y, x + 10, 140 - y2);
|
||||||
|
out += temp;
|
||||||
|
y = y2;
|
||||||
|
}
|
||||||
|
out += "</g>\n</svg>\n";
|
||||||
|
|
||||||
|
server.send(200, "image/svg+xml", out);
|
||||||
|
}
|
||||||
|
|
||||||
|
void setup(void) {
|
||||||
|
pinMode(led, OUTPUT);
|
||||||
|
digitalWrite(led, 0);
|
||||||
|
Serial.begin(115200);
|
||||||
|
WiFi.mode(WIFI_STA);
|
||||||
|
WiFi.begin(ssid, password);
|
||||||
|
Serial.println("");
|
||||||
|
|
||||||
|
// Wait for connection
|
||||||
|
while (WiFi.status() != WL_CONNECTED) {
|
||||||
|
delay(500);
|
||||||
|
Serial.print(".");
|
||||||
|
}
|
||||||
|
|
||||||
|
Serial.println("");
|
||||||
|
Serial.print("Connected to ");
|
||||||
|
Serial.println(ssid);
|
||||||
|
Serial.print("IP address: ");
|
||||||
|
Serial.println(WiFi.localIP());
|
||||||
|
|
||||||
|
if (MDNS.begin("picow")) {
|
||||||
|
Serial.println("MDNS responder started");
|
||||||
|
}
|
||||||
|
|
||||||
|
server.on("/", handleRoot);
|
||||||
|
server.on("/test.svg", drawGraph);
|
||||||
|
server.on("/inline", []() {
|
||||||
|
server.send(200, "text/plain", "this works as well");
|
||||||
|
});
|
||||||
|
server.onNotFound(handleNotFound);
|
||||||
|
server.begin();
|
||||||
|
Serial.println("HTTP server started");
|
||||||
|
}
|
||||||
|
|
||||||
|
void loop(void) {
|
||||||
|
server.handleClient();
|
||||||
|
MDNS.update();
|
||||||
|
}
|
||||||
634
libraries/WebServer/examples/FSBrowser/FSBrowser.ino
Normal file
634
libraries/WebServer/examples/FSBrowser/FSBrowser.ino
Normal file
|
|
@ -0,0 +1,634 @@
|
||||||
|
/*
|
||||||
|
FSBrowser - A web-based FileSystem Browser for Pico filesystems
|
||||||
|
|
||||||
|
Copyright (c) 2015 Hristo Gochkov. All rights reserved.
|
||||||
|
This file is part of the Pico WebServer library for Arduino environment.
|
||||||
|
|
||||||
|
This library is free software; you can redistribute it and/or
|
||||||
|
modify it under the terms of the GNU Lesser General Public
|
||||||
|
License as published by the Free Software Foundation; either
|
||||||
|
version 2.1 of the License, or (at your option) any later version.
|
||||||
|
|
||||||
|
This library 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
|
||||||
|
Lesser General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Lesser General Public
|
||||||
|
License along with this library; if not, write to the Free Software
|
||||||
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
|
||||||
|
See readme.md for more information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
////////////////////////////////
|
||||||
|
|
||||||
|
// Select the FileSystem by uncommenting one of the lines below
|
||||||
|
|
||||||
|
//#define USE_SPIFFS
|
||||||
|
#define USE_LITTLEFS
|
||||||
|
//#define USE_SDFS
|
||||||
|
|
||||||
|
// Uncomment the following line to embed a version of the web page in the code
|
||||||
|
// (program code will be larger, but no file will have to be written to the filesystem).
|
||||||
|
// Note: the source file "extras/index_htm.h" must have been generated by "extras/reduce_index.sh"
|
||||||
|
|
||||||
|
#define INCLUDE_FALLBACK_INDEX_HTM
|
||||||
|
|
||||||
|
////////////////////////////////
|
||||||
|
|
||||||
|
#include <WiFi.h>
|
||||||
|
#include <WiFiClient.h>
|
||||||
|
#include <WebServer.h>
|
||||||
|
#include <LEAmDNS.h>
|
||||||
|
#include <SPI.h>
|
||||||
|
|
||||||
|
#ifdef INCLUDE_FALLBACK_INDEX_HTM
|
||||||
|
#include "extras/index_htm.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined USE_SPIFFS
|
||||||
|
#include <FS.h>
|
||||||
|
const char* fsName = "SPIFFS";
|
||||||
|
FS* fileSystem = &SPIFFS;
|
||||||
|
SPIFFSConfig fileSystemConfig = SPIFFSConfig();
|
||||||
|
#elif defined USE_LITTLEFS
|
||||||
|
#include <LittleFS.h>
|
||||||
|
const char* fsName = "LittleFS";
|
||||||
|
FS* fileSystem = &LittleFS;
|
||||||
|
LittleFSConfig fileSystemConfig = LittleFSConfig();
|
||||||
|
#elif defined USE_SDFS
|
||||||
|
#include <SDFS.h>
|
||||||
|
const char* fsName = "SDFS";
|
||||||
|
FS* fileSystem = &SDFS;
|
||||||
|
SDFSConfig fileSystemConfig = SDFSConfig();
|
||||||
|
// fileSystemConfig.setCSPin(chipSelectPin);
|
||||||
|
#else
|
||||||
|
#error Please select a filesystem first by uncommenting one of the "#define USE_xxx" lines at the beginning of the sketch.
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
#define DBG_OUTPUT_PORT Serial
|
||||||
|
|
||||||
|
#ifndef STASSID
|
||||||
|
#define STASSID "your-ssid"
|
||||||
|
#define STAPSK "your-password"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
const char* ssid = STASSID;
|
||||||
|
const char* password = STAPSK;
|
||||||
|
const char* host = "fsbrowser";
|
||||||
|
|
||||||
|
WebServer server(80);
|
||||||
|
|
||||||
|
static bool fsOK;
|
||||||
|
String unsupportedFiles = String();
|
||||||
|
|
||||||
|
File uploadFile;
|
||||||
|
|
||||||
|
static const char TEXT_PLAIN[] PROGMEM = "text/plain";
|
||||||
|
static const char FS_INIT_ERROR[] PROGMEM = "FS INIT ERROR";
|
||||||
|
static const char FILE_NOT_FOUND[] PROGMEM = "FileNotFound";
|
||||||
|
|
||||||
|
////////////////////////////////
|
||||||
|
// Utils to return HTTP codes, and determine content-type
|
||||||
|
|
||||||
|
void replyOK() {
|
||||||
|
server.send(200, FPSTR(TEXT_PLAIN), "");
|
||||||
|
}
|
||||||
|
|
||||||
|
void replyOKWithMsg(String msg) {
|
||||||
|
server.send(200, FPSTR(TEXT_PLAIN), msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
void replyNotFound(String msg) {
|
||||||
|
server.send(404, FPSTR(TEXT_PLAIN), msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
void replyBadRequest(String msg) {
|
||||||
|
DBG_OUTPUT_PORT.println(msg);
|
||||||
|
server.send(400, FPSTR(TEXT_PLAIN), msg + "\r\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
void replyServerError(String msg) {
|
||||||
|
DBG_OUTPUT_PORT.println(msg);
|
||||||
|
server.send(500, FPSTR(TEXT_PLAIN), msg + "\r\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef USE_SPIFFS
|
||||||
|
/*
|
||||||
|
Checks filename for character combinations that are not supported by FSBrowser (alhtough valid on SPIFFS).
|
||||||
|
Returns an empty String if supported, or detail of error(s) if unsupported
|
||||||
|
*/
|
||||||
|
String checkForUnsupportedPath(String filename) {
|
||||||
|
String error = String();
|
||||||
|
if (!filename.startsWith("/")) {
|
||||||
|
error += F("!NO_LEADING_SLASH! ");
|
||||||
|
}
|
||||||
|
if (filename.indexOf("//") != -1) {
|
||||||
|
error += F("!DOUBLE_SLASH! ");
|
||||||
|
}
|
||||||
|
if (filename.endsWith("/")) {
|
||||||
|
error += F("!TRAILING_SLASH! ");
|
||||||
|
}
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
////////////////////////////////
|
||||||
|
// Request handlers
|
||||||
|
|
||||||
|
/*
|
||||||
|
Return the FS type, status and size info
|
||||||
|
*/
|
||||||
|
void handleStatus() {
|
||||||
|
DBG_OUTPUT_PORT.println("handleStatus");
|
||||||
|
FSInfo fs_info;
|
||||||
|
String json;
|
||||||
|
json.reserve(128);
|
||||||
|
|
||||||
|
json = "{\"type\":\"";
|
||||||
|
json += fsName;
|
||||||
|
json += "\", \"isOk\":";
|
||||||
|
if (fsOK) {
|
||||||
|
fileSystem->info(fs_info);
|
||||||
|
json += F("\"true\", \"totalBytes\":\"");
|
||||||
|
json += fs_info.totalBytes;
|
||||||
|
json += F("\", \"usedBytes\":\"");
|
||||||
|
json += fs_info.usedBytes;
|
||||||
|
json += "\"";
|
||||||
|
} else {
|
||||||
|
json += "\"false\"";
|
||||||
|
}
|
||||||
|
json += F(",\"unsupportedFiles\":\"");
|
||||||
|
json += unsupportedFiles;
|
||||||
|
json += "\"}";
|
||||||
|
|
||||||
|
server.send(200, "application/json", json);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
Return the list of files in the directory specified by the "dir" query string parameter.
|
||||||
|
Also demonstrates the use of chunked responses.
|
||||||
|
*/
|
||||||
|
void handleFileList() {
|
||||||
|
if (!fsOK) {
|
||||||
|
return replyServerError(FPSTR(FS_INIT_ERROR));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!server.hasArg("dir")) {
|
||||||
|
return replyBadRequest(F("DIR ARG MISSING"));
|
||||||
|
}
|
||||||
|
|
||||||
|
String path = server.arg("dir");
|
||||||
|
if (path != "/" && !fileSystem->exists(path)) {
|
||||||
|
return replyBadRequest("BAD PATH");
|
||||||
|
}
|
||||||
|
|
||||||
|
DBG_OUTPUT_PORT.println(String("handleFileList: ") + path);
|
||||||
|
Dir dir = fileSystem->openDir(path);
|
||||||
|
path = "";
|
||||||
|
|
||||||
|
// use HTTP/1.1 Chunked response to avoid building a huge temporary string
|
||||||
|
if (!server.chunkedResponseModeStart(200, "text/json")) {
|
||||||
|
server.send(505, F("text/html"), F("HTTP1.1 required"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// use the same string for every line
|
||||||
|
String output;
|
||||||
|
output.reserve(64);
|
||||||
|
while (dir.next()) {
|
||||||
|
#ifdef USE_SPIFFS
|
||||||
|
String error = checkForUnsupportedPath(dir.fileName());
|
||||||
|
if (error.length() > 0) {
|
||||||
|
DBG_OUTPUT_PORT.println(String("Ignoring ") + error + dir.fileName());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
if (output.length()) {
|
||||||
|
// send string from previous iteration
|
||||||
|
// as an HTTP chunk
|
||||||
|
Serial.println(output);
|
||||||
|
server.sendContent(output);
|
||||||
|
output = ',';
|
||||||
|
} else {
|
||||||
|
output = '[';
|
||||||
|
}
|
||||||
|
|
||||||
|
output += "{\"type\":\"";
|
||||||
|
if (dir.isDirectory()) {
|
||||||
|
output += "dir";
|
||||||
|
} else {
|
||||||
|
output += F("file\",\"size\":\"");
|
||||||
|
output += dir.fileSize();
|
||||||
|
}
|
||||||
|
|
||||||
|
output += F("\",\"name\":\"");
|
||||||
|
// Always return names without leading "/"
|
||||||
|
if (dir.fileName()[0] == '/') {
|
||||||
|
output += &(dir.fileName()[1]);
|
||||||
|
} else {
|
||||||
|
output += dir.fileName();
|
||||||
|
}
|
||||||
|
|
||||||
|
output += "\"}";
|
||||||
|
}
|
||||||
|
|
||||||
|
// send last string
|
||||||
|
output += "]";
|
||||||
|
server.sendContent(output);
|
||||||
|
server.chunkedResponseFinalize();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
Read the given file from the filesystem and stream it back to the client
|
||||||
|
*/
|
||||||
|
bool handleFileRead(String path) {
|
||||||
|
DBG_OUTPUT_PORT.println(String("handleFileRead: ") + path);
|
||||||
|
if (!fsOK) {
|
||||||
|
replyServerError(FPSTR(FS_INIT_ERROR));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (path.endsWith("/")) {
|
||||||
|
path += "index.htm";
|
||||||
|
}
|
||||||
|
|
||||||
|
String contentType;
|
||||||
|
if (server.hasArg("download")) {
|
||||||
|
contentType = F("application/octet-stream");
|
||||||
|
} else {
|
||||||
|
contentType = mime::getContentType(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!fileSystem->exists(path)) {
|
||||||
|
// File not found, try gzip version
|
||||||
|
path = path + ".gz";
|
||||||
|
}
|
||||||
|
if (fileSystem->exists(path)) {
|
||||||
|
File file = fileSystem->open(path, "r");
|
||||||
|
if (server.streamFile(file, contentType) != file.size()) {
|
||||||
|
DBG_OUTPUT_PORT.println("Sent less data than expected!");
|
||||||
|
}
|
||||||
|
file.close();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
As some FS (e.g. LittleFS) delete the parent folder when the last child has been removed,
|
||||||
|
return the path of the closest parent still existing
|
||||||
|
*/
|
||||||
|
String lastExistingParent(String path) {
|
||||||
|
while (path != "" && !fileSystem->exists(path)) {
|
||||||
|
if (path.lastIndexOf('/') > 0) {
|
||||||
|
path = path.substring(0, path.lastIndexOf('/'));
|
||||||
|
} else {
|
||||||
|
path = String(); // No slash => the top folder does not exist
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DBG_OUTPUT_PORT.println(String("Last existing parent: ") + path);
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Handle the creation/rename of a new file
|
||||||
|
Operation | req.responseText
|
||||||
|
---------------+--------------------------------------------------------------
|
||||||
|
Create file | parent of created file
|
||||||
|
Create folder | parent of created folder
|
||||||
|
Rename file | parent of source file
|
||||||
|
Move file | parent of source file, or remaining ancestor
|
||||||
|
Rename folder | parent of source folder
|
||||||
|
Move folder | parent of source folder, or remaining ancestor
|
||||||
|
*/
|
||||||
|
void handleFileCreate() {
|
||||||
|
if (!fsOK) {
|
||||||
|
return replyServerError(FPSTR(FS_INIT_ERROR));
|
||||||
|
}
|
||||||
|
|
||||||
|
String path = server.arg("path");
|
||||||
|
if (path == "") {
|
||||||
|
return replyBadRequest(F("PATH ARG MISSING"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef USE_SPIFFS
|
||||||
|
if (checkForUnsupportedPath(path).length() > 0) {
|
||||||
|
return replyServerError(F("INVALID FILENAME"));
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (path == "/") {
|
||||||
|
return replyBadRequest("BAD PATH");
|
||||||
|
}
|
||||||
|
if (fileSystem->exists(path)) {
|
||||||
|
return replyBadRequest(F("PATH FILE EXISTS"));
|
||||||
|
}
|
||||||
|
|
||||||
|
String src = server.arg("src");
|
||||||
|
if (src == "") {
|
||||||
|
// No source specified: creation
|
||||||
|
DBG_OUTPUT_PORT.println(String("handleFileCreate: ") + path);
|
||||||
|
if (path.endsWith("/")) {
|
||||||
|
// Create a folder
|
||||||
|
path.remove(path.length() - 1);
|
||||||
|
if (!fileSystem->mkdir(path)) {
|
||||||
|
return replyServerError(F("MKDIR FAILED"));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Create a file
|
||||||
|
File file = fileSystem->open(path, "w");
|
||||||
|
if (file) {
|
||||||
|
file.write((const char*)0);
|
||||||
|
file.close();
|
||||||
|
} else {
|
||||||
|
return replyServerError(F("CREATE FAILED"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (path.lastIndexOf('/') > -1) {
|
||||||
|
path = path.substring(0, path.lastIndexOf('/'));
|
||||||
|
}
|
||||||
|
replyOKWithMsg(path);
|
||||||
|
} else {
|
||||||
|
// Source specified: rename
|
||||||
|
if (src == "/") {
|
||||||
|
return replyBadRequest("BAD SRC");
|
||||||
|
}
|
||||||
|
if (!fileSystem->exists(src)) {
|
||||||
|
return replyBadRequest(F("SRC FILE NOT FOUND"));
|
||||||
|
}
|
||||||
|
|
||||||
|
DBG_OUTPUT_PORT.println(String("handleFileCreate: ") + path + " from " + src);
|
||||||
|
|
||||||
|
if (path.endsWith("/")) {
|
||||||
|
path.remove(path.length() - 1);
|
||||||
|
}
|
||||||
|
if (src.endsWith("/")) {
|
||||||
|
src.remove(src.length() - 1);
|
||||||
|
}
|
||||||
|
if (!fileSystem->rename(src, path)) {
|
||||||
|
return replyServerError(F("RENAME FAILED"));
|
||||||
|
}
|
||||||
|
replyOKWithMsg(lastExistingParent(src));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
Delete the file or folder designed by the given path.
|
||||||
|
If it's a file, delete it.
|
||||||
|
If it's a folder, delete all nested contents first then the folder itself
|
||||||
|
|
||||||
|
IMPORTANT NOTE: using recursion is generally not recommended on embedded devices and can lead to crashes (stack overflow errors).
|
||||||
|
This use is just for demonstration purpose, and FSBrowser might crash in case of deeply nested filesystems.
|
||||||
|
Please don't do this on a production system.
|
||||||
|
*/
|
||||||
|
void deleteRecursive(String path) {
|
||||||
|
File file = fileSystem->open(path, "r");
|
||||||
|
bool isDir = file.isDirectory();
|
||||||
|
file.close();
|
||||||
|
|
||||||
|
// If it's a plain file, delete it
|
||||||
|
if (!isDir) {
|
||||||
|
fileSystem->remove(path);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise delete its contents first
|
||||||
|
Dir dir = fileSystem->openDir(path);
|
||||||
|
|
||||||
|
while (dir.next()) {
|
||||||
|
deleteRecursive(path + '/' + dir.fileName());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Then delete the folder itself
|
||||||
|
fileSystem->rmdir(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
Handle a file deletion request
|
||||||
|
Operation | req.responseText
|
||||||
|
---------------+--------------------------------------------------------------
|
||||||
|
Delete file | parent of deleted file, or remaining ancestor
|
||||||
|
Delete folder | parent of deleted folder, or remaining ancestor
|
||||||
|
*/
|
||||||
|
void handleFileDelete() {
|
||||||
|
if (!fsOK) {
|
||||||
|
return replyServerError(FPSTR(FS_INIT_ERROR));
|
||||||
|
}
|
||||||
|
|
||||||
|
String path = server.arg(0);
|
||||||
|
if (path == "" || path == "/") {
|
||||||
|
return replyBadRequest("BAD PATH");
|
||||||
|
}
|
||||||
|
|
||||||
|
DBG_OUTPUT_PORT.println(String("handleFileDelete: ") + path);
|
||||||
|
if (!fileSystem->exists(path)) {
|
||||||
|
return replyNotFound(FPSTR(FILE_NOT_FOUND));
|
||||||
|
}
|
||||||
|
deleteRecursive(path);
|
||||||
|
|
||||||
|
replyOKWithMsg(lastExistingParent(path));
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Handle a file upload request
|
||||||
|
*/
|
||||||
|
void handleFileUpload() {
|
||||||
|
if (!fsOK) {
|
||||||
|
return replyServerError(FPSTR(FS_INIT_ERROR));
|
||||||
|
}
|
||||||
|
if (server.uri() != "/edit") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
HTTPUpload& upload = server.upload();
|
||||||
|
if (upload.status == UPLOAD_FILE_START) {
|
||||||
|
String filename = upload.filename;
|
||||||
|
// Make sure paths always start with "/"
|
||||||
|
if (!filename.startsWith("/")) {
|
||||||
|
filename = "/" + filename;
|
||||||
|
}
|
||||||
|
DBG_OUTPUT_PORT.println(String("handleFileUpload Name: ") + filename);
|
||||||
|
uploadFile = fileSystem->open(filename, "w");
|
||||||
|
if (!uploadFile) {
|
||||||
|
return replyServerError(F("CREATE FAILED"));
|
||||||
|
}
|
||||||
|
DBG_OUTPUT_PORT.println(String("Upload: START, filename: ") + filename);
|
||||||
|
} else if (upload.status == UPLOAD_FILE_WRITE) {
|
||||||
|
if (uploadFile) {
|
||||||
|
size_t bytesWritten = uploadFile.write(upload.buf, upload.currentSize);
|
||||||
|
if (bytesWritten != upload.currentSize) {
|
||||||
|
return replyServerError(F("WRITE FAILED"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DBG_OUTPUT_PORT.println(String("Upload: WRITE, Bytes: ") + upload.currentSize);
|
||||||
|
} else if (upload.status == UPLOAD_FILE_END) {
|
||||||
|
if (uploadFile) {
|
||||||
|
uploadFile.close();
|
||||||
|
}
|
||||||
|
DBG_OUTPUT_PORT.println(String("Upload: END, Size: ") + upload.totalSize);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
The "Not Found" handler catches all URI not explicitly declared in code
|
||||||
|
First try to find and return the requested file from the filesystem,
|
||||||
|
and if it fails, return a 404 page with debug information
|
||||||
|
*/
|
||||||
|
void handleNotFound() {
|
||||||
|
if (!fsOK) {
|
||||||
|
return replyServerError(FPSTR(FS_INIT_ERROR));
|
||||||
|
}
|
||||||
|
|
||||||
|
String uri = WebServer::urlDecode(server.uri()); // required to read paths with blanks
|
||||||
|
|
||||||
|
if (handleFileRead(uri)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dump debug data
|
||||||
|
String message;
|
||||||
|
message.reserve(100);
|
||||||
|
message = F("Error: File not found\n\nURI: ");
|
||||||
|
message += uri;
|
||||||
|
message += F("\nMethod: ");
|
||||||
|
message += (server.method() == HTTP_GET) ? "GET" : "POST";
|
||||||
|
message += F("\nArguments: ");
|
||||||
|
message += server.args();
|
||||||
|
message += '\n';
|
||||||
|
for (uint8_t i = 0; i < server.args(); i++) {
|
||||||
|
message += F(" NAME:");
|
||||||
|
message += server.argName(i);
|
||||||
|
message += F("\n VALUE:");
|
||||||
|
message += server.arg(i);
|
||||||
|
message += '\n';
|
||||||
|
}
|
||||||
|
message += "path=";
|
||||||
|
message += server.arg("path");
|
||||||
|
message += '\n';
|
||||||
|
DBG_OUTPUT_PORT.print(message);
|
||||||
|
|
||||||
|
return replyNotFound(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
This specific handler returns the index.htm (or a gzipped version) from the /edit folder.
|
||||||
|
If the file is not present but the flag INCLUDE_FALLBACK_INDEX_HTM has been set, falls back to the version
|
||||||
|
embedded in the program code.
|
||||||
|
Otherwise, fails with a 404 page with debug information
|
||||||
|
*/
|
||||||
|
void handleGetEdit() {
|
||||||
|
if (handleFileRead(F("/edit/index.htm"))) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef INCLUDE_FALLBACK_INDEX_HTM
|
||||||
|
server.sendHeader(F("Content-Encoding"), "gzip");
|
||||||
|
server.send(200, "text/html", index_htm_gz, index_htm_gz_len);
|
||||||
|
#else
|
||||||
|
replyNotFound(FPSTR(FILE_NOT_FOUND));
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void setup(void) {
|
||||||
|
////////////////////////////////
|
||||||
|
// SERIAL INIT
|
||||||
|
DBG_OUTPUT_PORT.begin(115200);
|
||||||
|
DBG_OUTPUT_PORT.print('\n');
|
||||||
|
|
||||||
|
////////////////////////////////
|
||||||
|
// FILESYSTEM INIT
|
||||||
|
|
||||||
|
fileSystemConfig.setAutoFormat(false);
|
||||||
|
fileSystem->setConfig(fileSystemConfig);
|
||||||
|
fsOK = fileSystem->begin();
|
||||||
|
DBG_OUTPUT_PORT.println(fsOK ? F("Filesystem initialized.") : F("Filesystem init failed!"));
|
||||||
|
|
||||||
|
#ifdef USE_SPIFFS
|
||||||
|
// Debug: dump on console contents of filesystem with no filter and check filenames validity
|
||||||
|
Dir dir = fileSystem->openDir("");
|
||||||
|
DBG_OUTPUT_PORT.println(F("List of files at root of filesystem:"));
|
||||||
|
while (dir.next()) {
|
||||||
|
String error = checkForUnsupportedPath(dir.fileName());
|
||||||
|
String fileInfo = dir.fileName() + (dir.isDirectory() ? " [DIR]" : String(" (") + dir.fileSize() + "b)");
|
||||||
|
DBG_OUTPUT_PORT.println(error + fileInfo);
|
||||||
|
if (error.length() > 0) {
|
||||||
|
unsupportedFiles += error + fileInfo + '\n';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DBG_OUTPUT_PORT.println();
|
||||||
|
|
||||||
|
// Keep the "unsupportedFiles" variable to show it, but clean it up
|
||||||
|
unsupportedFiles.replace("\n", "<br/>");
|
||||||
|
unsupportedFiles = unsupportedFiles.substring(0, unsupportedFiles.length() - 5);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
////////////////////////////////
|
||||||
|
// WI-FI INIT
|
||||||
|
DBG_OUTPUT_PORT.printf("Connecting to %s\n", ssid);
|
||||||
|
WiFi.mode(WIFI_STA);
|
||||||
|
WiFi.begin(ssid, password);
|
||||||
|
// Wait for connection
|
||||||
|
while (WiFi.status() != WL_CONNECTED) {
|
||||||
|
delay(500);
|
||||||
|
DBG_OUTPUT_PORT.print(".");
|
||||||
|
}
|
||||||
|
DBG_OUTPUT_PORT.println("");
|
||||||
|
DBG_OUTPUT_PORT.print(F("Connected! IP address: "));
|
||||||
|
DBG_OUTPUT_PORT.println(WiFi.localIP());
|
||||||
|
|
||||||
|
////////////////////////////////
|
||||||
|
// MDNS INIT
|
||||||
|
if (MDNS.begin(host)) {
|
||||||
|
MDNS.addService("http", "tcp", 80);
|
||||||
|
DBG_OUTPUT_PORT.print(F("Open http://"));
|
||||||
|
DBG_OUTPUT_PORT.print(host);
|
||||||
|
DBG_OUTPUT_PORT.println(F(".local/edit to open the FileSystem Browser"));
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////
|
||||||
|
// WEB SERVER INIT
|
||||||
|
|
||||||
|
// Filesystem status
|
||||||
|
server.on("/status", HTTP_GET, handleStatus);
|
||||||
|
|
||||||
|
// List directory
|
||||||
|
server.on("/list", HTTP_GET, handleFileList);
|
||||||
|
|
||||||
|
// Load editor
|
||||||
|
server.on("/edit", HTTP_GET, handleGetEdit);
|
||||||
|
|
||||||
|
// Create file
|
||||||
|
server.on("/edit", HTTP_PUT, handleFileCreate);
|
||||||
|
|
||||||
|
// Delete file
|
||||||
|
server.on("/edit", HTTP_DELETE, handleFileDelete);
|
||||||
|
|
||||||
|
// Upload file
|
||||||
|
// - first callback is called after the request has ended with all parsed arguments
|
||||||
|
// - second callback handles file upload at that location
|
||||||
|
server.on("/edit", HTTP_POST, replyOK, handleFileUpload);
|
||||||
|
|
||||||
|
// Default handler for all URIs not defined above
|
||||||
|
// Use it to read files from filesystem
|
||||||
|
server.onNotFound(handleNotFound);
|
||||||
|
|
||||||
|
// Start server
|
||||||
|
server.begin();
|
||||||
|
DBG_OUTPUT_PORT.println("HTTP server started");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void loop(void) {
|
||||||
|
server.handleClient();
|
||||||
|
MDNS.update();
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
I am in a subdir
|
||||||
1128
libraries/WebServer/examples/FSBrowser/data/edit/index.htm
Normal file
1128
libraries/WebServer/examples/FSBrowser/data/edit/index.htm
Normal file
File diff suppressed because it is too large
Load diff
BIN
libraries/WebServer/examples/FSBrowser/data/favicon.ico
Normal file
BIN
libraries/WebServer/examples/FSBrowser/data/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.1 KiB |
22
libraries/WebServer/examples/FSBrowser/data/index.htm
Normal file
22
libraries/WebServer/examples/FSBrowser/data/index.htm
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta http-equiv="Content-type" content="text/html; charset=utf-8">
|
||||||
|
<title>Pico Index</title>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
background-color:black;
|
||||||
|
color:white;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<script type="text/javascript">
|
||||||
|
function onBodyLoad(){
|
||||||
|
console.log("we are loaded!!");
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
<body id="index" onload="onBodyLoad()">
|
||||||
|
<h1>ESP8266 Pin Functions</h1>
|
||||||
|
<img src="pins.png" />
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
BIN
libraries/WebServer/examples/FSBrowser/data/pins.png
Normal file
BIN
libraries/WebServer/examples/FSBrowser/data/pins.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 54 KiB |
BIN
libraries/WebServer/examples/FSBrowser/extras/feathericons.png
Normal file
BIN
libraries/WebServer/examples/FSBrowser/extras/feathericons.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.5 KiB |
BIN
libraries/WebServer/examples/FSBrowser/extras/index.htm.gz
Normal file
BIN
libraries/WebServer/examples/FSBrowser/extras/index.htm.gz
Normal file
Binary file not shown.
529
libraries/WebServer/examples/FSBrowser/extras/index_htm.h
Normal file
529
libraries/WebServer/examples/FSBrowser/extras/index_htm.h
Normal file
|
|
@ -0,0 +1,529 @@
|
||||||
|
// WARNING: Auto-generated file. Please do not modify by hand.
|
||||||
|
// This file is an embeddable version of the gzipped index.htm file.
|
||||||
|
// To update it, please rerun the `reduce_index.sh` script located in the `extras` subfolder
|
||||||
|
// then recompile the sketch after each change to the `index.html` file.
|
||||||
|
unsigned char index_htm_gz[] = {
|
||||||
|
0x1f, 0x8b, 0x08, 0x08, 0x96, 0xc9, 0xa8, 0x5e, 0x00, 0x03, 0x69, 0x6e,
|
||||||
|
0x64, 0x65, 0x78, 0x2e, 0x68, 0x74, 0x6d, 0x00, 0xdc, 0x3b, 0x89, 0x7b,
|
||||||
|
0xda, 0xb8, 0x97, 0xff, 0x8a, 0xe3, 0xee, 0x24, 0xf6, 0x02, 0x06, 0x92,
|
||||||
|
0xe6, 0x28, 0xc4, 0xc9, 0xe4, 0x4e, 0x9a, 0xb3, 0xb9, 0xd3, 0x6e, 0xf7,
|
||||||
|
0xfb, 0x04, 0x16, 0xa0, 0xc4, 0xd8, 0xae, 0x2d, 0x07, 0x48, 0xca, 0xfe,
|
||||||
|
0xed, 0xfb, 0x9e, 0xe4, 0x13, 0x4c, 0x32, 0xdd, 0x9d, 0xf9, 0xcd, 0xec,
|
||||||
|
0xb6, 0xf3, 0x15, 0x5b, 0x96, 0x9e, 0xde, 0x7d, 0x49, 0xb3, 0x3e, 0xb7,
|
||||||
|
0x7b, 0xbe, 0x73, 0xfd, 0x70, 0xb1, 0xa7, 0xf4, 0x78, 0xdf, 0x56, 0x2e,
|
||||||
|
0x6e, 0xb6, 0x4f, 0x8e, 0x76, 0x14, 0xb5, 0x52, 0xad, 0xde, 0x2d, 0xed,
|
||||||
|
0x54, 0xab, 0xbb, 0xd7, 0xbb, 0xca, 0xfd, 0xe1, 0xf5, 0xe9, 0x89, 0x52,
|
||||||
|
0x37, 0x6a, 0xca, 0xb5, 0x4f, 0x9c, 0x80, 0x71, 0xe6, 0x3a, 0xc4, 0xae,
|
||||||
|
0x56, 0xf7, 0xce, 0x54, 0x45, 0xed, 0x71, 0xee, 0x35, 0xaa, 0xd5, 0xc1,
|
||||||
|
0x60, 0x60, 0x0c, 0x96, 0x0c, 0xd7, 0xef, 0x56, 0xaf, 0x2f, 0xab, 0x43,
|
||||||
|
0x84, 0x55, 0xc7, 0xc5, 0xd1, 0x63, 0x85, 0x67, 0x56, 0x1a, 0x16, 0xb7,
|
||||||
|
0xd4, 0x8d, 0x75, 0xb1, 0x9f, 0x4d, 0x9c, 0xae, 0x49, 0x9d, 0x8d, 0x75,
|
||||||
|
0xce, 0xb8, 0x4d, 0x37, 0xf6, 0x99, 0x4d, 0x95, 0x3e, 0x71, 0x48, 0x97,
|
||||||
|
0xfa, 0xeb, 0x55, 0x39, 0xb6, 0x1e, 0xf0, 0x11, 0x8e, 0x52, 0x8b, 0x11,
|
||||||
|
0x33, 0x68, 0xfb, 0x14, 0xa6, 0xb7, 0x5c, 0x6b, 0xf4, 0xda, 0x71, 0x1d,
|
||||||
|
0x5e, 0x09, 0xd8, 0x0b, 0x6d, 0xd4, 0x17, 0xbd, 0x61, 0x53, 0xbc, 0x76,
|
||||||
|
0x48, 0x9f, 0xd9, 0xa3, 0xc6, 0x2d, 0xf5, 0x2d, 0x80, 0x52, 0xde, 0xf2,
|
||||||
|
0x19, 0xb1, 0xcb, 0x57, 0xb0, 0x77, 0x25, 0xa0, 0x3e, 0xeb, 0x8c, 0x8d,
|
||||||
|
0x36, 0xcc, 0xa2, 0x43, 0x7e, 0x4a, 0x9d, 0xf0, 0xf5, 0xa5, 0xc2, 0x1c,
|
||||||
|
0x8b, 0x0e, 0x1b, 0x4b, 0xb5, 0x5a, 0xd3, 0x73, 0x25, 0x7a, 0x0d, 0xd2,
|
||||||
|
0x0a, 0x5c, 0x3b, 0xe4, 0xb4, 0x69, 0xd3, 0x0e, 0x6f, 0x2c, 0x03, 0xe0,
|
||||||
|
0x96, 0xeb, 0x5b, 0xd4, 0x6f, 0xd4, 0xbd, 0xa1, 0x02, 0x9f, 0x98, 0xa5,
|
||||||
|
0x7c, 0xf8, 0xf8, 0xf1, 0x63, 0xb3, 0x45, 0xda, 0x4f, 0x5d, 0xdf, 0x0d,
|
||||||
|
0x1d, 0xab, 0xd2, 0x76, 0x6d, 0xd7, 0x6f, 0x7c, 0xe8, 0x2c, 0xe3, 0xdf,
|
||||||
|
0xa6, 0xc5, 0x02, 0xcf, 0x26, 0xa3, 0x86, 0xe3, 0x3a, 0x14, 0xd6, 0x0e,
|
||||||
|
0x2b, 0x41, 0x8f, 0x58, 0xee, 0xa0, 0x51, 0x53, 0x6a, 0x4a, 0xbd, 0x06,
|
||||||
|
0x40, 0xfc, 0x6e, 0x8b, 0x68, 0xb5, 0x32, 0xfe, 0x35, 0x3e, 0xea, 0xcd,
|
||||||
|
0x0c, 0x19, 0xf5, 0x98, 0x8c, 0x01, 0x65, 0xdd, 0x1e, 0x6f, 0xac, 0xd6,
|
||||||
|
0x6a, 0x39, 0x8c, 0x95, 0xd0, 0x7e, 0xb5, 0x59, 0x00, 0xd3, 0x91, 0x25,
|
||||||
|
0x72, 0x07, 0xee, 0x7a, 0x8d, 0x9a, 0x44, 0xb6, 0xd6, 0xec, 0x13, 0xbf,
|
||||||
|
0xcb, 0x1c, 0x78, 0xf0, 0x88, 0x65, 0x31, 0xa7, 0xdb, 0x98, 0x58, 0x6f,
|
||||||
|
0xb3, 0xd7, 0x84, 0x50, 0x9f, 0xda, 0x84, 0xb3, 0x67, 0xda, 0xec, 0x33,
|
||||||
|
0xa7, 0x32, 0x60, 0x16, 0xef, 0x35, 0x56, 0x00, 0xbd, 0x66, 0x3b, 0xf4,
|
||||||
|
0x03, 0x20, 0xc7, 0x73, 0x19, 0xac, 0xf3, 0xf3, 0xeb, 0x03, 0x8f, 0x38,
|
||||||
|
0xaf, 0x11, 0xb9, 0xc8, 0x84, 0x98, 0x56, 0xe6, 0xd8, 0xcc, 0xa1, 0x95,
|
||||||
|
0x96, 0xed, 0xb6, 0x9f, 0x92, 0xbd, 0x57, 0xbc, 0xe1, 0xe4, 0xee, 0x8d,
|
||||||
|
0x9e, 0xfb, 0x4c, 0xfd, 0xd7, 0x94, 0x77, 0x02, 0x4c, 0xf1, 0xac, 0xdc,
|
||||||
|
0x66, 0x94, 0x52, 0x98, 0x15, 0x04, 0xa0, 0x48, 0x94, 0x3e, 0x33, 0x3a,
|
||||||
|
0x80, 0x69, 0xe5, 0xfc, 0x00, 0xf0, 0x26, 0xa1, 0x3a, 0x65, 0xc4, 0x04,
|
||||||
|
0xbb, 0x26, 0x80, 0x30, 0xc7, 0x0b, 0xf9, 0xeb, 0xb4, 0xec, 0x5d, 0x8f,
|
||||||
|
0xb4, 0x19, 0x1f, 0x09, 0xf6, 0x65, 0xe6, 0xbf, 0x4e, 0xc8, 0xaa, 0xd2,
|
||||||
|
0x77, 0x5f, 0x2a, 0x21, 0x68, 0x16, 0x68, 0x97, 0x4d, 0xdb, 0x5c, 0x4a,
|
||||||
|
0x04, 0xa4, 0xd7, 0x7a, 0x62, 0x7c, 0xfa, 0xc3, 0xe4, 0xc0, 0x04, 0x32,
|
||||||
|
0x59, 0x7a, 0x6b, 0xb5, 0xce, 0xb4, 0x20, 0x26, 0x27, 0x47, 0xdc, 0x44,
|
||||||
|
0xce, 0x55, 0x2c, 0xda, 0x76, 0x7d, 0x22, 0xa8, 0x00, 0xb6, 0x52, 0x1f,
|
||||||
|
0xe5, 0x51, 0x44, 0x6c, 0xc9, 0x26, 0x2d, 0x6a, 0x97, 0x80, 0x59, 0x31,
|
||||||
|
0x8b, 0x14, 0xfc, 0xbb, 0xb8, 0x28, 0x84, 0x35, 0x35, 0xfd, 0xbf, 0x60,
|
||||||
|
0x62, 0x56, 0xa1, 0x27, 0x85, 0x80, 0xc0, 0xca, 0x05, 0x63, 0x8d, 0x46,
|
||||||
|
0x8b, 0x76, 0x5c, 0x9f, 0xbe, 0xbe, 0x49, 0x84, 0xd8, 0xa2, 0x01, 0xf0,
|
||||||
|
0x49, 0xcb, 0xa6, 0x96, 0x44, 0x2d, 0x5e, 0x61, 0xd1, 0x0e, 0x09, 0x6d,
|
||||||
|
0x9e, 0x88, 0xc2, 0x58, 0x29, 0x5c, 0xdc, 0xee, 0xd1, 0xf6, 0x13, 0xb5,
|
||||||
|
0x00, 0x39, 0xae, 0x25, 0x90, 0xf4, 0x2c, 0xda, 0x42, 0x29, 0x7f, 0x15,
|
||||||
|
0xef, 0x72, 0x81, 0x68, 0x32, 0x5a, 0x1b, 0xfa, 0xb6, 0x66, 0x11, 0x4e,
|
||||||
|
0x1a, 0xac, 0x0f, 0xae, 0xaa, 0xea, 0x39, 0x5d, 0xf0, 0x07, 0x01, 0x5d,
|
||||||
|
0xf9, 0x58, 0x66, 0xb7, 0xdb, 0xe7, 0x97, 0x83, 0xda, 0xf1, 0x41, 0xd7,
|
||||||
|
0xdd, 0x82, 0x3f, 0x67, 0x57, 0x37, 0xbd, 0xbd, 0x9b, 0x2e, 0x3c, 0x6d,
|
||||||
|
0xe3, 0xeb, 0x4e, 0x77, 0x67, 0xeb, 0x14, 0x1f, 0x46, 0xcb, 0xc3, 0x41,
|
||||||
|
0x1f, 0x1f, 0x5a, 0xf5, 0xed, 0xd3, 0xdb, 0xbd, 0xdb, 0xc3, 0xf6, 0xde,
|
||||||
|
0xe8, 0xae, 0xbf, 0xbc, 0x7c, 0x77, 0xb7, 0xb8, 0xb7, 0xf2, 0xe5, 0xc6,
|
||||||
|
0xda, 0xfa, 0xb2, 0xb7, 0xcd, 0xc8, 0x41, 0xfd, 0x91, 0x1c, 0xac, 0x56,
|
||||||
|
0xab, 0xd5, 0xb5, 0xe7, 0xb3, 0xc7, 0xa5, 0xe3, 0x97, 0xd3, 0xd5, 0x9d,
|
||||||
|
0xe1, 0x69, 0xab, 0xbf, 0x1c, 0x76, 0x4e, 0x5f, 0xda, 0xd5, 0x87, 0x45,
|
||||||
|
0xeb, 0xc7, 0x90, 0x9f, 0x90, 0x03, 0xe6, 0x2e, 0xaf, 0x75, 0x1f, 0xee,
|
||||||
|
0x3e, 0x3f, 0x7e, 0xdd, 0xbf, 0xbc, 0xdd, 0xff, 0xfa, 0xf9, 0x7a, 0xaf,
|
||||||
|
0x7a, 0xf2, 0xd2, 0x2e, 0x3d, 0x07, 0xad, 0x33, 0xeb, 0xfe, 0x76, 0xf5,
|
||||||
|
0x63, 0xe9, 0xa2, 0xf7, 0x6c, 0x1d, 0xda, 0x41, 0xeb, 0x6e, 0xf1, 0xc9,
|
||||||
|
0x5b, 0xf9, 0xb1, 0xfa, 0x7c, 0xf2, 0x32, 0x5a, 0x7b, 0x3e, 0x0d, 0xcf,
|
||||||
|
0xae, 0x5f, 0x3a, 0x4b, 0x9f, 0x4a, 0x3d, 0x77, 0xe5, 0x66, 0x74, 0x7e,
|
||||||
|
0xb3, 0xb3, 0xdf, 0x7b, 0xb8, 0xbb, 0xb1, 0x97, 0x9d, 0xe7, 0xd5, 0x52,
|
||||||
|
0xd5, 0x5b, 0xa1, 0x4f, 0x5f, 0x58, 0xf5, 0xe0, 0x12, 0x71, 0xdc, 0xba,
|
||||||
|
0xbf, 0xbc, 0xba, 0xb6, 0x4f, 0xb7, 0xbe, 0x9c, 0xb7, 0x1e, 0xbe, 0x22,
|
||||||
|
0x2d, 0x57, 0x97, 0x9f, 0x2f, 0xf7, 0xf6, 0x6f, 0xae, 0x4e, 0x3b, 0xfc,
|
||||||
|
0xe9, 0x13, 0x1f, 0x0d, 0xd8, 0xd6, 0x97, 0x9e, 0x7b, 0xb3, 0xd5, 0xbb,
|
||||||
|
0xdd, 0x1a, 0x7c, 0xf6, 0x7e, 0xec, 0x5e, 0xfe, 0xe8, 0x90, 0xe7, 0xe7,
|
||||||
|
0xb5, 0x17, 0x3b, 0x3c, 0x3b, 0x7e, 0x0a, 0xfd, 0xbd, 0x47, 0xff, 0x61,
|
||||||
|
0xa9, 0x44, 0x57, 0x3f, 0x7e, 0x66, 0x2f, 0x27, 0xce, 0xe2, 0x5d, 0xbd,
|
||||||
|
0xcf, 0xb6, 0x8e, 0x87, 0x5e, 0xef, 0x7c, 0xfb, 0x94, 0xde, 0x3c, 0xfc,
|
||||||
|
0x58, 0x09, 0x0f, 0xab, 0x5b, 0x4b, 0x5b, 0x2b, 0x2b, 0x0f, 0xde, 0xe5,
|
||||||
|
0xf6, 0xe5, 0x8f, 0xcf, 0x5f, 0xc9, 0xe9, 0xd1, 0x1a, 0x1b, 0x04, 0xb7,
|
||||||
|
0xd5, 0x1d, 0xeb, 0x74, 0x65, 0x6b, 0x71, 0xf8, 0xb8, 0xec, 0x1c, 0xdd,
|
||||||
|
0x04, 0xc7, 0xb5, 0x2a, 0xbb, 0xbe, 0xb9, 0xf0, 0x0f, 0xce, 0xfa, 0xb5,
|
||||||
|
0xd3, 0x9b, 0xdd, 0xa3, 0x27, 0x7a, 0x50, 0xfd, 0xbc, 0xfc, 0x31, 0x3c,
|
||||||
|
0x67, 0x4f, 0x41, 0xeb, 0x53, 0xef, 0xbe, 0xb7, 0xbc, 0x7c, 0xd1, 0x3b,
|
||||||
|
0x3a, 0x7a, 0xec, 0x1c, 0x77, 0xad, 0xcf, 0xd7, 0x87, 0x57, 0x7b, 0xa3,
|
||||||
|
0xc5, 0xea, 0xfe, 0x6e, 0x6d, 0xe5, 0xbe, 0xef, 0x5a, 0x6b, 0x67, 0xe7,
|
||||||
|
0x03, 0xdf, 0x1f, 0xec, 0xdf, 0x04, 0x5f, 0xfa, 0xf7, 0x5f, 0x0f, 0xbf,
|
||||||
|
0xf6, 0x7a, 0xf4, 0xe9, 0x70, 0x9b, 0x6d, 0x8f, 0x1e, 0x8e, 0x5c, 0x72,
|
||||||
|
0xf4, 0x79, 0xeb, 0xf1, 0x62, 0xed, 0xe6, 0xea, 0x8e, 0xed, 0x6c, 0xad,
|
||||||
|
0x1d, 0xf7, 0xf6, 0xee, 0xd6, 0x6e, 0x0e, 0xae, 0x57, 0x8f, 0x2f, 0xc8,
|
||||||
|
0xd7, 0xbd, 0x61, 0x70, 0xde, 0x3a, 0x1c, 0xf9, 0x37, 0xdd, 0xeb, 0xa7,
|
||||||
|
0xc7, 0xeb, 0x97, 0x35, 0x9b, 0x5d, 0xdc, 0x0f, 0x5e, 0x06, 0x7b, 0xdb,
|
||||||
|
0xa5, 0xf3, 0x8b, 0xfd, 0xdb, 0xe1, 0xe1, 0xde, 0xda, 0xfd, 0x62, 0xfb,
|
||||||
|
0xe9, 0x72, 0x7b, 0x74, 0x42, 0x6e, 0x47, 0xbd, 0xdb, 0xe3, 0xe1, 0xc5,
|
||||||
|
0xe2, 0xea, 0xf1, 0x59, 0xc9, 0xd9, 0xe2, 0x87, 0xab, 0x97, 0xcf, 0xa1,
|
||||||
|
0xbf, 0xb8, 0xeb, 0xaf, 0x2c, 0xd6, 0x39, 0x7d, 0x3a, 0xa5, 0x41, 0xe9,
|
||||||
|
0x8e, 0x1d, 0xac, 0xad, 0x1c, 0xfa, 0x2b, 0x8f, 0xc7, 0x0f, 0x8f, 0xa5,
|
||||||
|
0xd5, 0x2f, 0xf5, 0x63, 0xab, 0x76, 0xe1, 0x0d, 0x8f, 0x96, 0xd7, 0xce,
|
||||||
|
0x82, 0x2f, 0xd6, 0x59, 0x75, 0x71, 0xf9, 0xc5, 0xfe, 0xb2, 0xfb, 0xc5,
|
||||||
|
0x3a, 0x6e, 0x7d, 0xda, 0x72, 0x4e, 0x57, 0x3a, 0x87, 0x57, 0x07, 0x4f,
|
||||||
|
0x17, 0xc1, 0x17, 0xf2, 0x99, 0xf4, 0x8f, 0xbc, 0x2f, 0x2f, 0x3b, 0xfe,
|
||||||
|
0x68, 0xd0, 0xdb, 0xad, 0xb3, 0xeb, 0xc5, 0xfb, 0xa7, 0xe0, 0x64, 0x67,
|
||||||
|
0x10, 0x54, 0x8f, 0xbe, 0x3e, 0xaf, 0x7d, 0x75, 0x3b, 0xab, 0x7c, 0x71,
|
||||||
|
0xf9, 0xc1, 0x7e, 0x12, 0x62, 0xba, 0xba, 0xb9, 0x3d, 0xbf, 0x3c, 0x5e,
|
||||||
|
0xde, 0x79, 0x38, 0x3a, 0x32, 0x75, 0xc5, 0x71, 0x2b, 0x3e, 0xf5, 0x28,
|
||||||
|
0xe1, 0x7f, 0x82, 0xee, 0x17, 0x0c, 0x25, 0x16, 0x5e, 0x18, 0x03, 0x7a,
|
||||||
|
0x32, 0x80, 0xd5, 0x21, 0x06, 0x34, 0xc5, 0x70, 0x76, 0x00, 0x9c, 0x16,
|
||||||
|
0x67, 0x6d, 0x62, 0x57, 0x88, 0xcd, 0xba, 0x4e, 0xa3, 0xcf, 0x2c, 0xcb,
|
||||||
|
0x2e, 0xf4, 0x2c, 0x19, 0x93, 0xab, 0x24, 0x4e, 0xba, 0xbe, 0x06, 0x31,
|
||||||
|
0xb4, 0x56, 0xe4, 0x3b, 0x67, 0xce, 0xae, 0x7c, 0x5c, 0x9b, 0x72, 0x6f,
|
||||||
|
0xb8, 0xc2, 0xe0, 0x43, 0xfe, 0xc6, 0xaa, 0x95, 0x8f, 0xc5, 0xab, 0x58,
|
||||||
|
0xbf, 0xfb, 0xc6, 0xaa, 0xb5, 0xda, 0xd4, 0xaa, 0x09, 0x97, 0x88, 0xc1,
|
||||||
|
0xce, 0xe1, 0x0d, 0x55, 0x6d, 0xca, 0xb0, 0x2b, 0x98, 0x92, 0xb8, 0x63,
|
||||||
|
0x74, 0xc5, 0xe8, 0x93, 0x8b, 0xd9, 0xd4, 0x2c, 0xda, 0xb8, 0xa6, 0x54,
|
||||||
|
0x96, 0xa6, 0x1d, 0x78, 0x4e, 0x4a, 0x7f, 0x70, 0xd3, 0xb7, 0x7c, 0x6c,
|
||||||
|
0x29, 0x4f, 0xc6, 0x0c, 0x44, 0x10, 0xee, 0xf8, 0x77, 0x91, 0xad, 0x29,
|
||||||
|
0x32, 0x5b, 0x53, 0x88, 0x63, 0x29, 0x5a, 0x1c, 0x15, 0x31, 0xdb, 0xb0,
|
||||||
|
0x00, 0x7a, 0x9b, 0x56, 0x3c, 0x36, 0xa4, 0x76, 0x45, 0x44, 0xad, 0x46,
|
||||||
|
0x4d, 0x7f, 0xcd, 0x87, 0xda, 0x78, 0x3e, 0x71, 0xc0, 0xc7, 0x0a, 0xe0,
|
||||||
|
0xf1, 0x80, 0xf5, 0x48, 0xda, 0x40, 0x4a, 0x05, 0x02, 0x69, 0x1f, 0x7f,
|
||||||
|
0x65, 0x44, 0x75, 0xfd, 0x4a, 0x2b, 0xec, 0x76, 0xd8, 0x10, 0x90, 0xee,
|
||||||
|
0x30, 0x87, 0x71, 0xaa, 0xd4, 0x83, 0xf1, 0xef, 0x31, 0x98, 0x27, 0x3a,
|
||||||
|
0xea, 0xf8, 0xa4, 0x4f, 0x03, 0xe5, 0x0f, 0x82, 0x79, 0xed, 0xf8, 0x6e,
|
||||||
|
0x3f, 0xcd, 0x28, 0xc6, 0xdc, 0xcd, 0xbc, 0x8c, 0xc7, 0x1f, 0x7a, 0x94,
|
||||||
|
0x40, 0x98, 0x2d, 0xc8, 0x1d, 0x64, 0x42, 0xe6, 0x0b, 0x75, 0x4f, 0x12,
|
||||||
|
0xb3, 0x48, 0xfd, 0x17, 0x41, 0xa3, 0x92, 0xfc, 0x08, 0x13, 0xca, 0xba,
|
||||||
|
0x60, 0x3b, 0xa6, 0x85, 0x05, 0x09, 0x25, 0x66, 0x58, 0x99, 0xfc, 0xe7,
|
||||||
|
0x03, 0xf2, 0x66, 0xc6, 0x86, 0x8b, 0x32, 0x51, 0xe5, 0xdc, 0xed, 0xa7,
|
||||||
|
0x9b, 0x4a, 0x41, 0x2f, 0xd6, 0x7e, 0x4b, 0xb6, 0x44, 0x33, 0xf8, 0x00,
|
||||||
|
0xa2, 0x01, 0x32, 0xcb, 0x1f, 0x3c, 0x5f, 0x72, 0xfa, 0x0d, 0x88, 0x31,
|
||||||
|
0x15, 0x79, 0xc8, 0x00, 0x71, 0x9c, 0xac, 0x9e, 0x46, 0x1b, 0x70, 0x4d,
|
||||||
|
0x36, 0x5c, 0xc6, 0x0d, 0x03, 0x4e, 0x78, 0x18, 0xcc, 0xd8, 0x67, 0x29,
|
||||||
|
0xd9, 0x46, 0x70, 0x21, 0x93, 0x61, 0x89, 0xb5, 0x9d, 0xe0, 0x94, 0x42,
|
||||||
|
0xe2, 0xf0, 0x1a, 0x29, 0x6d, 0xad, 0x96, 0x32, 0xb0, 0x12, 0x61, 0x85,
|
||||||
|
0x9a, 0xff, 0x61, 0x40, 0x7c, 0x07, 0xc6, 0x5e, 0x63, 0x3f, 0x53, 0x03,
|
||||||
|
0xaa, 0xa7, 0x50, 0x83, 0x0c, 0xc9, 0xe9, 0xd2, 0x66, 0x92, 0x63, 0x81,
|
||||||
|
0xba, 0x73, 0xd7, 0xb5, 0x39, 0xf3, 0x0a, 0x90, 0x8b, 0xab, 0x83, 0xc5,
|
||||||
|
0x84, 0x0b, 0x82, 0x2d, 0x88, 0xc0, 0x33, 0x0b, 0x58, 0x8b, 0xd9, 0x98,
|
||||||
|
0x96, 0xf4, 0xc0, 0x2a, 0xa9, 0x53, 0x54, 0x0d, 0x74, 0x3a, 0x99, 0x8d,
|
||||||
|
0x9a, 0x22, 0x49, 0x93, 0x86, 0x8c, 0x5a, 0x47, 0xfd, 0x82, 0xb2, 0x02,
|
||||||
|
0xe7, 0xc5, 0x8c, 0x5b, 0xca, 0xf3, 0xa2, 0x96, 0xa1, 0x31, 0x4a, 0x8f,
|
||||||
|
0x13, 0xd4, 0x33, 0xd8, 0x88, 0x47, 0xf0, 0xa5, 0x1f, 0x6c, 0x97, 0x20,
|
||||||
|
0x98, 0x19, 0x3c, 0xaf, 0x00, 0x7b, 0x9e, 0x7b, 0x79, 0x35, 0xc1, 0xa1,
|
||||||
|
0x41, 0x33, 0x65, 0x1f, 0x7c, 0x8f, 0x39, 0x00, 0x6f, 0xd3, 0x04, 0x66,
|
||||||
|
0xab, 0x98, 0x65, 0x3d, 0xcd, 0x98, 0x9b, 0x69, 0xa9, 0xd7, 0x88, 0x06,
|
||||||
|
0x15, 0x63, 0x39, 0x50, 0x28, 0xe4, 0x47, 0x00, 0xaf, 0xe2, 0x86, 0x3c,
|
||||||
|
0x41, 0xcf, 0x08, 0x7a, 0xee, 0xc0, 0x79, 0x95, 0x16, 0x13, 0x43, 0xa8,
|
||||||
|
0x27, 0x9f, 0x2b, 0xfd, 0xa0, 0x5b, 0x1c, 0x5e, 0x66, 0x59, 0x9d, 0xac,
|
||||||
|
0xd9, 0x40, 0xf4, 0x02, 0x09, 0x70, 0x53, 0xfd, 0x86, 0x78, 0x82, 0x4a,
|
||||||
|
0x87, 0xde, 0x6b, 0x15, 0xf8, 0xa2, 0x37, 0x33, 0xf2, 0x49, 0x19, 0x2c,
|
||||||
|
0xdc, 0xe7, 0xef, 0xa9, 0x93, 0x08, 0x3c, 0xe6, 0x38, 0x90, 0xad, 0xa3,
|
||||||
|
0xf7, 0x79, 0xad, 0xfd, 0xf6, 0x9a, 0xc2, 0xf3, 0x5d, 0xd0, 0x66, 0xaa,
|
||||||
|
0xd5, 0xf4, 0x31, 0xea, 0xd8, 0xf4, 0x87, 0xa5, 0x95, 0x9a, 0x45, 0xbb,
|
||||||
|
0xfa, 0x78, 0x6c, 0x64, 0x61, 0xc8, 0xfc, 0xd4, 0xa7, 0x3f, 0x42, 0xe6,
|
||||||
|
0x43, 0x7e, 0xfa, 0x0e, 0x55, 0x49, 0x69, 0x86, 0x54, 0x21, 0x39, 0x6f,
|
||||||
|
0xd1, 0x25, 0xc8, 0x2a, 0x4b, 0xda, 0x52, 0x67, 0x99, 0xdd, 0x1c, 0x1c,
|
||||||
|
0x61, 0xea, 0x14, 0x71, 0x3f, 0x92, 0x6a, 0xdf, 0x4a, 0xaa, 0x7e, 0x68,
|
||||||
|
0xb7, 0x72, 0xb8, 0x22, 0x34, 0x3e, 0x12, 0xb4, 0xd8, 0xc8, 0x23, 0x3e,
|
||||||
|
0x68, 0x6d, 0xf2, 0x19, 0xc4, 0x13, 0x06, 0x82, 0x69, 0xb2, 0xc2, 0x65,
|
||||||
|
0x2f, 0xa8, 0x97, 0xd1, 0x57, 0x18, 0x69, 0xa2, 0x86, 0x76, 0x6c, 0xa8,
|
||||||
|
0x7a, 0x23, 0xfb, 0x10, 0xea, 0x8f, 0xda, 0x04, 0x21, 0xa8, 0xf2, 0x09,
|
||||||
|
0xfe, 0xc0, 0xca, 0xa8, 0xe6, 0x44, 0x97, 0x18, 0xa9, 0x9d, 0x08, 0xb8,
|
||||||
|
0xeb, 0x55, 0x51, 0xae, 0x41, 0xdd, 0xdf, 0xf6, 0x99, 0xc7, 0x37, 0x9e,
|
||||||
|
0x89, 0xaf, 0xa0, 0xe7, 0x2b, 0x77, 0x82, 0x23, 0xa7, 0xe3, 0x36, 0x3b,
|
||||||
|
0xa1, 0xd3, 0x46, 0x12, 0x95, 0x80, 0xf2, 0x13, 0xa9, 0x28, 0x1a, 0x2d,
|
||||||
|
0x73, 0xfd, 0x95, 0xcf, 0xcf, 0x43, 0x94, 0x03, 0x62, 0xa8, 0x61, 0xbb,
|
||||||
|
0x5d, 0x8d, 0xeb, 0x65, 0xcb, 0x6d, 0x87, 0xe8, 0xdb, 0x8d, 0x2e, 0xe5,
|
||||||
|
0x7b, 0xd2, 0xcd, 0x6f, 0x8f, 0x8e, 0x2c, 0x4d, 0xcd, 0x28, 0x98, 0xaa,
|
||||||
|
0x1b, 0x82, 0x53, 0xd8, 0xe4, 0x30, 0xf9, 0xcf, 0x9f, 0xaa, 0x5a, 0xa6,
|
||||||
|
0x9b, 0xef, 0x2d, 0x84, 0x45, 0x6d, 0x9b, 0x04, 0xc1, 0x09, 0x54, 0x97,
|
||||||
|
0x06, 0x58, 0xac, 0xa6, 0x0a, 0x4d, 0x56, 0xf5, 0xc6, 0x2f, 0xad, 0xf4,
|
||||||
|
0x69, 0x1f, 0x18, 0x95, 0x2c, 0x4e, 0xf1, 0xc5, 0xfe, 0x86, 0x21, 0xd8,
|
||||||
|
0x60, 0xc8, 0x72, 0xc8, 0xa4, 0x9b, 0xea, 0x80, 0x30, 0xae, 0x36, 0xd4,
|
||||||
|
0xa8, 0x30, 0x52, 0xc7, 0x09, 0x23, 0x7c, 0x08, 0x45, 0x58, 0xf7, 0x5c,
|
||||||
|
0x81, 0x26, 0x6b, 0x54, 0x7f, 0x65, 0x1d, 0x8d, 0xae, 0xd7, 0x6b, 0x8b,
|
||||||
|
0x1f, 0x75, 0x9f, 0xf2, 0xd0, 0x77, 0x14, 0x5a, 0x52, 0x95, 0x6d, 0x15,
|
||||||
|
0xd4, 0xdd, 0xd7, 0x04, 0x37, 0xcd, 0x4a, 0xbd, 0xc9, 0x4b, 0xa5, 0x32,
|
||||||
|
0xce, 0x59, 0xd7, 0x68, 0xd5, 0x14, 0x93, 0x9b, 0x7a, 0x33, 0x9e, 0x0f,
|
||||||
|
0xbe, 0x65, 0x1f, 0x82, 0xb3, 0xa5, 0x2d, 0xea, 0xa5, 0x6f, 0xaa, 0x72,
|
||||||
|
0xcc, 0xb6, 0xd5, 0xb2, 0xaa, 0x9c, 0xca, 0x9f, 0x03, 0xf9, 0x73, 0x2d,
|
||||||
|
0x7e, 0x2e, 0xe0, 0xdf, 0xef, 0xdf, 0xf8, 0xf7, 0x2c, 0x36, 0x1d, 0x9f,
|
||||||
|
0x06, 0xbd, 0x2b, 0xe1, 0xf4, 0x35, 0x50, 0xf5, 0x59, 0x1c, 0x91, 0x61,
|
||||||
|
0x21, 0xc7, 0x7f, 0x55, 0x8b, 0x56, 0xa3, 0x73, 0x30, 0x0c, 0x5d, 0x6d,
|
||||||
|
0x22, 0xbe, 0xae, 0xe9, 0x40, 0x2e, 0x72, 0x7f, 0x7a, 0x72, 0xc8, 0xb9,
|
||||||
|
0x77, 0x09, 0x36, 0x44, 0x03, 0xa8, 0x0b, 0x0d, 0xd7, 0x41, 0x9e, 0x9a,
|
||||||
|
0xf1, 0xc6, 0x9a, 0x20, 0x7c, 0xb1, 0x56, 0x9b, 0x33, 0x5d, 0x43, 0xc2,
|
||||||
|
0xd6, 0x91, 0xb1, 0xb8, 0x68, 0xcf, 0xf7, 0x81, 0x76, 0x57, 0x6f, 0x52,
|
||||||
|
0x3b, 0xa0, 0xaf, 0x08, 0x93, 0x9a, 0x9a, 0xd4, 0x27, 0xf3, 0xf3, 0xd5,
|
||||||
|
0xf9, 0x99, 0x01, 0xfa, 0x1d, 0x50, 0xcd, 0x05, 0x89, 0x04, 0x1e, 0xe8,
|
||||||
|
0x10, 0xbd, 0x06, 0x7d, 0xd5, 0x75, 0x83, 0x8f, 0x3c, 0xe4, 0x5d, 0x45,
|
||||||
|
0x51, 0x9b, 0x00, 0x5b, 0x2e, 0x30, 0x58, 0x70, 0xfe, 0xa4, 0xbf, 0x4a,
|
||||||
|
0x46, 0xd6, 0x4b, 0xa7, 0x84, 0xf7, 0x0c, 0xe1, 0x18, 0xb5, 0x4f, 0x9f,
|
||||||
|
0xfe, 0x3d, 0x9a, 0x02, 0x95, 0xbe, 0xb5, 0x3d, 0xe2, 0x34, 0xa8, 0x46,
|
||||||
|
0x03, 0x1c, 0x9c, 0x83, 0x2d, 0x46, 0xf4, 0xb2, 0x63, 0xe6, 0x04, 0x36,
|
||||||
|
0x35, 0xa3, 0x32, 0x09, 0x44, 0x07, 0x14, 0x80, 0x2b, 0x54, 0x71, 0x3b,
|
||||||
|
0x8a, 0x5a, 0x7a, 0x7b, 0x31, 0x90, 0x58, 0x32, 0xd5, 0xf5, 0x3e, 0x06,
|
||||||
|
0x4d, 0x85, 0x59, 0xe6, 0x42, 0x14, 0x40, 0x17, 0x14, 0x48, 0xbc, 0xcc,
|
||||||
|
0x85, 0xda, 0x82, 0xe2, 0x7a, 0x9c, 0xf5, 0xc3, 0x3e, 0x3e, 0x83, 0x81,
|
||||||
|
0x9a, 0x0b, 0x9f, 0x60, 0xac, 0x07, 0xe6, 0x07, 0x4f, 0xcb, 0x30, 0x8b,
|
||||||
|
0x0c, 0xcd, 0x05, 0xf0, 0x6c, 0x0b, 0xca, 0x33, 0xb1, 0x43, 0x6a, 0x2e,
|
||||||
|
0xa8, 0x25, 0x5e, 0x52, 0x17, 0x14, 0xd1, 0x8b, 0xc3, 0x37, 0x07, 0xde,
|
||||||
|
0x36, 0xc4, 0xcf, 0x7a, 0x55, 0xec, 0xb2, 0xa1, 0x96, 0x63, 0x84, 0x9d,
|
||||||
|
0x20, 0xf4, 0x3c, 0xd7, 0xe7, 0xd4, 0xc2, 0x46, 0x5e, 0x30, 0x3f, 0xaf,
|
||||||
|
0x21, 0x32, 0xca, 0x3a, 0x66, 0xa2, 0x02, 0x99, 0x28, 0x82, 0x2d, 0x6c,
|
||||||
|
0xdc, 0x6d, 0x5d, 0x9e, 0x1d, 0x9d, 0x1d, 0xc8, 0x2f, 0xc2, 0x20, 0xcc,
|
||||||
|
0x85, 0x28, 0x9e, 0x2d, 0x88, 0x2e, 0x60, 0x30, 0x0a, 0x38, 0xed, 0xcf,
|
||||||
|
0x3b, 0xad, 0xc0, 0x6b, 0x62, 0xee, 0x4a, 0x98, 0x13, 0xc8, 0xb7, 0xcc,
|
||||||
|
0x2e, 0x72, 0xa0, 0x03, 0xd3, 0x1d, 0xf4, 0xda, 0x8d, 0xf5, 0x96, 0x5f,
|
||||||
|
0x05, 0xdc, 0x66, 0xa0, 0x83, 0x18, 0xe3, 0x7e, 0x1b, 0xd1, 0x8f, 0xaa,
|
||||||
|
0x8f, 0x51, 0x1d, 0x14, 0xc1, 0x30, 0x81, 0x88, 0xb0, 0x38, 0x73, 0x61,
|
||||||
|
0x3a, 0xd6, 0x51, 0x2b, 0x0a, 0x1b, 0x83, 0x1e, 0xf8, 0xd0, 0x5c, 0x8f,
|
||||||
|
0xae, 0xe5, 0xda, 0xd6, 0xc2, 0xc6, 0xd1, 0xd9, 0xd1, 0xb5, 0xb2, 0x77,
|
||||||
|
0x79, 0x79, 0x7e, 0xa9, 0xcc, 0xc5, 0xe0, 0x9b, 0xbf, 0xa2, 0xf9, 0xb4,
|
||||||
|
0xac, 0x5e, 0x5d, 0x1c, 0xed, 0xef, 0x5f, 0xa9, 0x73, 0x66, 0x2c, 0x54,
|
||||||
|
0x50, 0x3e, 0x60, 0xe1, 0x4c, 0x28, 0xfd, 0x27, 0x8b, 0xf9, 0x00, 0x44,
|
||||||
|
0xfa, 0x89, 0x28, 0xa2, 0x98, 0xaa, 0x0c, 0x29, 0x40, 0xdc, 0xb8, 0x0c,
|
||||||
|
0x36, 0xe2, 0x51, 0x47, 0x53, 0x0f, 0xf6, 0xae, 0xc1, 0x54, 0xab, 0xd1,
|
||||||
|
0xb6, 0xe5, 0xb9, 0x9a, 0x0e, 0x9f, 0x02, 0x0a, 0x2a, 0xeb, 0x84, 0xb6,
|
||||||
|
0xad, 0xa7, 0xb6, 0x9b, 0xb7, 0x17, 0x70, 0x25, 0xc4, 0x86, 0x22, 0x44,
|
||||||
|
0x53, 0x05, 0x65, 0x0d, 0xe5, 0x9b, 0x5a, 0xa2, 0x91, 0x69, 0x95, 0xd4,
|
||||||
|
0xef, 0x0a, 0xbe, 0xe5, 0x0c, 0x26, 0x05, 0xd4, 0x26, 0x0e, 0xfa, 0xe6,
|
||||||
|
0x33, 0x3a, 0xd8, 0x91, 0x85, 0x07, 0x7a, 0x02, 0xe9, 0x5d, 0xe6, 0xe6,
|
||||||
|
0x66, 0xf3, 0x85, 0x3c, 0xd3, 0x6d, 0x0e, 0x9e, 0xd0, 0x88, 0x3b, 0x39,
|
||||||
|
0x3f, 0x7f, 0xce, 0xcd, 0x81, 0xf8, 0x3b, 0xcc, 0xef, 0x6b, 0xea, 0x4e,
|
||||||
|
0x0f, 0xd3, 0xb7, 0x40, 0xe1, 0xae, 0x32, 0x72, 0x43, 0x5f, 0x89, 0xe1,
|
||||||
|
0x28, 0x03, 0x66, 0xdb, 0x4a, 0x0b, 0x62, 0x9b, 0x1b, 0x70, 0x85, 0x75,
|
||||||
|
0xf0, 0xab, 0x82, 0x4a, 0xc3, 0x9c, 0x10, 0x18, 0x81, 0x6a, 0xe8, 0x08,
|
||||||
|
0x8b, 0x01, 0xf0, 0xbb, 0x2c, 0x68, 0x13, 0xdf, 0x82, 0x5d, 0x02, 0x6d,
|
||||||
|
0xae, 0xae, 0x23, 0x2f, 0x52, 0xac, 0x8b, 0xa7, 0xd1, 0xb7, 0x7c, 0xd8,
|
||||||
|
0x14, 0xc6, 0xe6, 0x1c, 0x9d, 0x1d, 0x77, 0xac, 0x04, 0x6c, 0x7e, 0x45,
|
||||||
|
0x8a, 0x02, 0xac, 0xb8, 0x10, 0xb1, 0x76, 0x1f, 0xf4, 0x8a, 0x0a, 0x19,
|
||||||
|
0x24, 0x4e, 0x19, 0x38, 0xef, 0xf3, 0xe0, 0x8e, 0xf1, 0x9e, 0xa6, 0x56,
|
||||||
|
0x55, 0xfd, 0xe7, 0x4f, 0x8d, 0x9a, 0xf0, 0x50, 0xa2, 0x7a, 0x99, 0x1a,
|
||||||
|
0x20, 0xcd, 0xf4, 0x13, 0x92, 0x6c, 0xc2, 0x02, 0x1b, 0xca, 0x2c, 0x48,
|
||||||
|
0xd2, 0x2a, 0x75, 0x1d, 0xa7, 0x04, 0x61, 0x2b, 0xe0, 0x3e, 0xc6, 0xcb,
|
||||||
|
0x1a, 0xbc, 0x81, 0xd1, 0xf1, 0x23, 0xcc, 0xef, 0xce, 0x3b, 0x62, 0x4d,
|
||||||
|
0x56, 0x7a, 0xe0, 0x63, 0x38, 0x3d, 0x14, 0x15, 0x0e, 0xc4, 0x56, 0x07,
|
||||||
|
0xa3, 0xab, 0x74, 0xc4, 0xb3, 0x28, 0x03, 0x14, 0x48, 0xfa, 0x51, 0xae,
|
||||||
|
0x8f, 0xbe, 0x6b, 0xaa, 0xa8, 0x22, 0x55, 0xc8, 0x4e, 0x84, 0x4a, 0x9b,
|
||||||
|
0x2a, 0x1a, 0xac, 0x5a, 0x26, 0x46, 0x3f, 0x44, 0x7b, 0x07, 0x8b, 0x9b,
|
||||||
|
0xab, 0xc3, 0x1b, 0x9a, 0xb0, 0xa9, 0x62, 0x57, 0x4d, 0x05, 0xed, 0x24,
|
||||||
|
0x1e, 0x68, 0xae, 0xb5, 0xd3, 0x63, 0xb6, 0xa5, 0x11, 0x5d, 0x84, 0x01,
|
||||||
|
0xeb, 0xdd, 0x0d, 0x2c, 0x03, 0x9c, 0x8c, 0xea, 0x81, 0x47, 0x3e, 0x12,
|
||||||
|
0x23, 0x65, 0x2b, 0xda, 0x11, 0xd3, 0x0f, 0x7c, 0x93, 0x7b, 0xe0, 0x04,
|
||||||
|
0x7c, 0x8b, 0x22, 0xe9, 0xad, 0x70, 0x74, 0xc0, 0x82, 0x89, 0x6d, 0x2d,
|
||||||
|
0xb9, 0xad, 0x3d, 0x73, 0xdb, 0x56, 0x08, 0xf5, 0x07, 0x48, 0xb2, 0x69,
|
||||||
|
0x67, 0x43, 0xd8, 0x8d, 0x87, 0x61, 0x69, 0x12, 0x98, 0x2d, 0x81, 0xb1,
|
||||||
|
0xf7, 0x81, 0x31, 0x41, 0x84, 0x34, 0xee, 0x32, 0x9b, 0xb4, 0x6e, 0x6c,
|
||||||
|
0xc2, 0xe2, 0x70, 0x66, 0xc3, 0xd3, 0xa7, 0x5d, 0x9c, 0x9a, 0xdf, 0x8f,
|
||||||
|
0xc9, 0xfd, 0x82, 0xf7, 0xf7, 0x0b, 0xf2, 0xb0, 0xf6, 0x85, 0x70, 0xf2,
|
||||||
|
0xc0, 0x02, 0x09, 0xac, 0x3d, 0x13, 0x18, 0x3a, 0x3c, 0x00, 0xd5, 0x16,
|
||||||
|
0xa8, 0xcb, 0xfa, 0x72, 0x5b, 0xc0, 0x07, 0x6f, 0xd3, 0x2e, 0x26, 0x21,
|
||||||
|
0xbf, 0x43, 0x5b, 0xee, 0xe0, 0xbf, 0x8f, 0xae, 0x2f, 0xf6, 0x88, 0xed,
|
||||||
|
0xae, 0xec, 0x67, 0xb1, 0x47, 0xcb, 0xc5, 0x21, 0xb9, 0xa1, 0x6c, 0x6e,
|
||||||
|
0x9c, 0x40, 0xea, 0x6c, 0xaa, 0x4b, 0x50, 0x40, 0xe1, 0x97, 0xd4, 0xe6,
|
||||||
|
0x6a, 0x80, 0x58, 0x16, 0x03, 0x5f, 0x62, 0xd0, 0x7f, 0x1f, 0x83, 0xbe,
|
||||||
|
0xc0, 0x20, 0x63, 0xca, 0xe5, 0x7e, 0x16, 0x89, 0xc8, 0x75, 0xe0, 0xe8,
|
||||||
|
0xec, 0xdd, 0xfa, 0x72, 0xb7, 0xf0, 0xfd, 0xdd, 0x42, 0xb1, 0x5b, 0x8f,
|
||||||
|
0xda, 0x9e, 0xd8, 0x2a, 0xcc, 0x6e, 0xb5, 0xa9, 0x4e, 0x80, 0x0d, 0x25,
|
||||||
|
0x58, 0xef, 0x3d, 0x41, 0x79, 0x92, 0x89, 0x51, 0x3c, 0xf0, 0x72, 0x19,
|
||||||
|
0x58, 0x94, 0xa8, 0xea, 0x93, 0x22, 0xf2, 0xc0, 0xc4, 0x21, 0xe7, 0x6a,
|
||||||
|
0x0b, 0x27, 0x9c, 0x66, 0x5d, 0x32, 0xdf, 0x84, 0xa4, 0xcb, 0x24, 0x06,
|
||||||
|
0x1a, 0x76, 0x60, 0x40, 0x34, 0xee, 0xf2, 0x5e, 0x9c, 0x21, 0x45, 0xa3,
|
||||||
|
0xdf, 0x6a, 0xdf, 0x85, 0xf5, 0x35, 0x9f, 0x5d, 0xa8, 0x35, 0x6a, 0x60,
|
||||||
|
0x7d, 0x22, 0xbf, 0x28, 0xf0, 0x69, 0xd1, 0x17, 0xe1, 0xd9, 0xa2, 0x67,
|
||||||
|
0x3d, 0x9e, 0x6e, 0x4e, 0xba, 0xc7, 0x78, 0x42, 0x09, 0x67, 0x73, 0x08,
|
||||||
|
0x79, 0x36, 0xa2, 0x08, 0x6e, 0xef, 0x29, 0x87, 0xe1, 0x34, 0x7a, 0xf3,
|
||||||
|
0xf3, 0x8e, 0x81, 0x67, 0x97, 0xd2, 0x5a, 0xb5, 0x14, 0xcb, 0x78, 0x2b,
|
||||||
|
0x7d, 0x5c, 0x0e, 0x0a, 0x61, 0x49, 0xb2, 0x62, 0xfc, 0xc1, 0x9f, 0xf6,
|
||||||
|
0x35, 0xbd, 0xa9, 0xaa, 0xa6, 0x69, 0xf2, 0xcd, 0x28, 0x5c, 0x6e, 0x29,
|
||||||
|
0x71, 0x4e, 0xa2, 0xf4, 0x43, 0x08, 0x49, 0x10, 0x9a, 0xba, 0x50, 0xce,
|
||||||
|
0x61, 0x7d, 0xc0, 0xf3, 0x8e, 0x3a, 0x5e, 0xb1, 0x1f, 0xe7, 0x30, 0x72,
|
||||||
|
0x01, 0x54, 0x8b, 0x10, 0x8c, 0x2c, 0x08, 0x6c, 0xbc, 0xa7, 0x10, 0x65,
|
||||||
|
0xa1, 0xba, 0xa0, 0x00, 0xdf, 0x7d, 0xd2, 0x86, 0x7c, 0x0b, 0x80, 0x48,
|
||||||
|
0xd4, 0x77, 0x84, 0x68, 0xa1, 0xce, 0x19, 0x83, 0x3f, 0xf8, 0x9f, 0x63,
|
||||||
|
0x2a, 0x18, 0xa9, 0x14, 0x22, 0xab, 0x4d, 0x60, 0x0b, 0xd2, 0xe1, 0x25,
|
||||||
|
0x14, 0x0c, 0x24, 0xb1, 0x79, 0x14, 0x00, 0x07, 0xbf, 0x10, 0x07, 0x6e,
|
||||||
|
0xa0, 0xa9, 0x6a, 0xf0, 0xbd, 0x3f, 0xe3, 0x7b, 0x64, 0x48, 0x38, 0x25,
|
||||||
|
0x9c, 0x05, 0x02, 0xd2, 0x92, 0xab, 0x1e, 0x64, 0x72, 0xed, 0x10, 0x13,
|
||||||
|
0x89, 0xf1, 0x64, 0xa4, 0xba, 0x86, 0xe4, 0x18, 0xe2, 0x14, 0x93, 0x24,
|
||||||
|
0x3b, 0x33, 0xa3, 0x94, 0x1a, 0x35, 0xad, 0x00, 0x7f, 0x3e, 0xd3, 0x44,
|
||||||
|
0x2c, 0xf6, 0x0c, 0x16, 0x92, 0xa6, 0x44, 0x6f, 0xc6, 0xff, 0x34, 0xd6,
|
||||||
|
0xe8, 0x91, 0x86, 0xbe, 0x11, 0xfd, 0xa5, 0x63, 0x9c, 0xce, 0xd8, 0xa4,
|
||||||
|
0x43, 0x7c, 0x67, 0x59, 0xec, 0x4f, 0x67, 0xac, 0x76, 0x26, 0x87, 0x45,
|
||||||
|
0x4b, 0x01, 0xc7, 0x33, 0xb6, 0xbd, 0xce, 0xfa, 0x5d, 0x25, 0xf0, 0xdb,
|
||||||
|
0x98, 0xc7, 0x53, 0xcc, 0xea, 0xa3, 0x84, 0x17, 0x92, 0xfe, 0x4a, 0xd2,
|
||||||
|
0x05, 0xfa, 0xad, 0x89, 0x45, 0x40, 0x25, 0xdb, 0x48, 0x53, 0xa2, 0x3e,
|
||||||
|
0x31, 0x09, 0xb9, 0xdb, 0x54, 0x72, 0x07, 0x6b, 0xcd, 0x05, 0x05, 0x52,
|
||||||
|
0xee, 0x8c, 0x40, 0xfe, 0x3f, 0xb1, 0x2b, 0x5b, 0x14, 0xa4, 0x95, 0xc0,
|
||||||
|
0xc2, 0xc6, 0x56, 0x1b, 0xaa, 0x06, 0xb1, 0x0d, 0xa4, 0x99, 0xa1, 0x6d,
|
||||||
|
0x09, 0x7b, 0x15, 0xf9, 0x27, 0x64, 0x4c, 0x96, 0x82, 0x9d, 0x63, 0x85,
|
||||||
|
0xf7, 0xa8, 0x22, 0xce, 0x3c, 0x1d, 0x8a, 0xf6, 0xec, 0xcb, 0xd1, 0x2a,
|
||||||
|
0x2e, 0xab, 0x92, 0x36, 0x35, 0x1e, 0x03, 0xc5, 0x50, 0x76, 0x65, 0x06,
|
||||||
|
0x02, 0xee, 0x16, 0x93, 0x5a, 0xcc, 0x52, 0x14, 0xd4, 0x4f, 0xea, 0x43,
|
||||||
|
0xf1, 0x1b, 0x17, 0x2b, 0xa0, 0xb3, 0xa2, 0x76, 0xe2, 0x43, 0x1e, 0xe7,
|
||||||
|
0xd2, 0x89, 0xe0, 0x92, 0x36, 0x8b, 0x22, 0x44, 0xb3, 0x00, 0xa5, 0x0d,
|
||||||
|
0x4c, 0xdf, 0x90, 0x65, 0x33, 0x2f, 0x2a, 0x9b, 0x79, 0x41, 0xd9, 0x3c,
|
||||||
|
0x93, 0x95, 0x99, 0x2d, 0x81, 0x91, 0x88, 0x5e, 0xf4, 0x6a, 0xf2, 0x1e,
|
||||||
|
0x0b, 0x72, 0xb9, 0xff, 0xb8, 0xcc, 0xb3, 0xc5, 0x06, 0xb8, 0x6c, 0x2e,
|
||||||
|
0x2b, 0x8c, 0x4c, 0x5a, 0xe9, 0xa7, 0x4e, 0xa9, 0xaa, 0x6d, 0x36, 0xfe,
|
||||||
|
0xc3, 0xd0, 0xbe, 0xfd, 0xa7, 0xf1, 0xbd, 0xa4, 0xeb, 0x9b, 0xff, 0x56,
|
||||||
|
0x35, 0xe8, 0x90, 0xa2, 0xee, 0x7c, 0xab, 0x7f, 0xc7, 0x52, 0x5b, 0x46,
|
||||||
|
0x09, 0xf0, 0xda, 0x5c, 0x0f, 0xc0, 0x09, 0xb6, 0x7b, 0xe0, 0x8a, 0xb8,
|
||||||
|
0x7b, 0xe2, 0x02, 0x6b, 0x76, 0x08, 0x54, 0xea, 0xba, 0xfe, 0xda, 0x86,
|
||||||
|
0x5f, 0xc4, 0x50, 0x6d, 0x88, 0xa7, 0x1e, 0xef, 0xa7, 0x4f, 0x76, 0xf4,
|
||||||
|
0xf8, 0x18, 0x24, 0x0f, 0x10, 0x4c, 0xe5, 0x63, 0x3b, 0x9e, 0x16, 0xbf,
|
||||||
|
0x7b, 0x5e, 0xfc, 0x14, 0xc4, 0xd3, 0x87, 0x08, 0x21, 0xca, 0xbb, 0xeb,
|
||||||
|
0x19, 0x77, 0xd3, 0xff, 0xf3, 0x29, 0xf0, 0x9c, 0x6e, 0x8c, 0xa3, 0x97,
|
||||||
|
0x3e, 0xd1, 0xf8, 0xb1, 0xcb, 0x3a, 0xd1, 0x13, 0x6b, 0xbb, 0x85, 0x38,
|
||||||
|
0x85, 0x9a, 0x53, 0x76, 0xf5, 0xa8, 0xa9, 0x31, 0xcb, 0xa9, 0x85, 0x36,
|
||||||
|
0xf8, 0x34, 0x27, 0x17, 0xcf, 0xa9, 0x1e, 0x69, 0xc9, 0xac, 0x35, 0x36,
|
||||||
|
0x83, 0x35, 0x34, 0xb7, 0x86, 0xeb, 0x65, 0x6c, 0xa3, 0x6c, 0xaa, 0x78,
|
||||||
|
0xc7, 0xa0, 0x03, 0x65, 0xa6, 0x05, 0xd1, 0x04, 0x53, 0x6c, 0xb7, 0xa3,
|
||||||
|
0x80, 0x52, 0x6f, 0x02, 0x8d, 0x93, 0xf6, 0xb3, 0x71, 0x0b, 0x0a, 0x1d,
|
||||||
|
0x97, 0xc4, 0xa8, 0x24, 0x45, 0xe1, 0x79, 0x3d, 0xdf, 0xf9, 0x4a, 0xf5,
|
||||||
|
0x30, 0xd8, 0x1e, 0xed, 0x60, 0x77, 0xe0, 0x0c, 0xc2, 0x93, 0xa6, 0x66,
|
||||||
|
0xae, 0x89, 0x80, 0x3e, 0xc6, 0x81, 0x3c, 0xbf, 0x56, 0xf6, 0xd4, 0x24,
|
||||||
|
0xba, 0x8e, 0x5e, 0x2e, 0x2a, 0x44, 0xe7, 0xe7, 0xdb, 0x40, 0xc4, 0x58,
|
||||||
|
0xc4, 0xb7, 0x29, 0x74, 0xf7, 0xc0, 0x40, 0xff, 0x69, 0xe8, 0x32, 0x03,
|
||||||
|
0x4d, 0xf6, 0xc6, 0xb7, 0x25, 0xda, 0x7d, 0xf8, 0x81, 0x0a, 0xaf, 0x00,
|
||||||
|
0xf9, 0x0b, 0x19, 0xdf, 0xfe, 0x69, 0xf8, 0x07, 0x02, 0x6f, 0xa1, 0x6e,
|
||||||
|
0xb3, 0xab, 0xc4, 0x02, 0x75, 0x23, 0x98, 0x72, 0x4e, 0x11, 0xb9, 0xeb,
|
||||||
|
0x0e, 0x84, 0x0b, 0x4b, 0xa8, 0x24, 0xb3, 0xb3, 0x9f, 0xe6, 0x5f, 0x48,
|
||||||
|
0x2b, 0x37, 0xdd, 0x37, 0x2a, 0xfd, 0x08, 0xc9, 0x8a, 0x38, 0x50, 0xc0,
|
||||||
|
0x30, 0x04, 0x61, 0x97, 0x97, 0xd4, 0xcd, 0xf8, 0x83, 0xc9, 0xfd, 0x90,
|
||||||
|
0xaa, 0xe3, 0x77, 0x2a, 0xdb, 0x02, 0xa6, 0x58, 0x98, 0x0d, 0x4f, 0x31,
|
||||||
|
0xe5, 0x52, 0xa4, 0x8f, 0xd5, 0x53, 0x40, 0x30, 0xe1, 0x8b, 0xf5, 0x2f,
|
||||||
|
0x96, 0x7e, 0xe4, 0x4f, 0x3c, 0x08, 0x74, 0x1e, 0xe0, 0x2e, 0x51, 0x52,
|
||||||
|
0xd4, 0x92, 0x5b, 0x52, 0x21, 0xbc, 0x41, 0x3d, 0x01, 0x2e, 0x28, 0xb4,
|
||||||
|
0xed, 0x39, 0x93, 0xcf, 0xcf, 0xf3, 0x39, 0xd3, 0x9d, 0x9f, 0x1f, 0x69,
|
||||||
|
0x6e, 0x19, 0xf2, 0xd7, 0x77, 0xea, 0xec, 0x02, 0x26, 0xd8, 0x7a, 0xd9,
|
||||||
|
0x2e, 0xd0, 0x0c, 0x6a, 0x53, 0x9e, 0xd2, 0x3f, 0xa3, 0x16, 0xf8, 0xeb,
|
||||||
|
0x34, 0xa2, 0x8b, 0x7a, 0x9e, 0x7a, 0x66, 0x1b, 0xcf, 0x25, 0xca, 0xce,
|
||||||
|
0x54, 0xef, 0xa4, 0x28, 0xe1, 0xcc, 0xb6, 0x4f, 0x64, 0xf7, 0xbf, 0xed,
|
||||||
|
0xbb, 0xb6, 0x7d, 0xed, 0x7a, 0x9b, 0x33, 0xc6, 0xd3, 0xa3, 0x86, 0xf8,
|
||||||
|
0x21, 0x82, 0x98, 0x4e, 0x29, 0x5b, 0x85, 0x40, 0xb1, 0x24, 0x2e, 0x84,
|
||||||
|
0x8a, 0x1f, 0xde, 0x03, 0x8b, 0x73, 0xca, 0xb6, 0x49, 0x0d, 0xe0, 0x2c,
|
||||||
|
0x8c, 0xde, 0x97, 0xac, 0x32, 0x4b, 0xde, 0x1e, 0x4a, 0xa4, 0xe9, 0xca,
|
||||||
|
0x03, 0x8e, 0x33, 0xd1, 0x6a, 0xc9, 0x32, 0x11, 0x7b, 0x8e, 0x85, 0x49,
|
||||||
|
0x57, 0x3c, 0x8e, 0xa7, 0x5c, 0xa6, 0x5d, 0x52, 0xb1, 0x54, 0x8f, 0xc7,
|
||||||
|
0xb8, 0xeb, 0x99, 0x4c, 0x0e, 0x69, 0xce, 0x66, 0xd8, 0x48, 0x24, 0xf9,
|
||||||
|
0xaf, 0x0c, 0x7a, 0x93, 0x9e, 0x6b, 0xc2, 0xd8, 0x5d, 0xdd, 0x68, 0xe3,
|
||||||
|
0xcc, 0x33, 0xd7, 0x92, 0x85, 0x6e, 0x74, 0x61, 0xa2, 0xfc, 0x8e, 0x5d,
|
||||||
|
0xe3, 0x39, 0xcd, 0xa4, 0x69, 0x13, 0x48, 0xc7, 0xa6, 0x15, 0x7b, 0x07,
|
||||||
|
0x18, 0x4f, 0xbc, 0xe0, 0xef, 0x33, 0xed, 0x99, 0x8e, 0x6e, 0x16, 0xed,
|
||||||
|
0xe6, 0x5c, 0xfd, 0x4f, 0x33, 0xeb, 0x4b, 0x79, 0x26, 0xf4, 0xf7, 0xd9,
|
||||||
|
0x75, 0x4f, 0xd8, 0xb5, 0x38, 0x2c, 0x28, 0x10, 0xce, 0xde, 0x10, 0xfe,
|
||||||
|
0xb5, 0xfe, 0x2f, 0x89, 0xa6, 0x26, 0x29, 0x7a, 0xa7, 0x23, 0x59, 0x20,
|
||||||
|
0x1f, 0xa6, 0xe7, 0x5b, 0x8f, 0xb3, 0x63, 0x4f, 0x71, 0x47, 0xe2, 0x9f,
|
||||||
|
0x1a, 0x7b, 0x66, 0xb7, 0x49, 0x0b, 0x98, 0x10, 0xe8, 0xe5, 0xe0, 0xdd,
|
||||||
|
0xd8, 0x53, 0xdc, 0x3b, 0xfa, 0xcb, 0x63, 0x8f, 0x2e, 0x88, 0x9a, 0x38,
|
||||||
|
0x48, 0xce, 0x22, 0xef, 0xc6, 0x7d, 0x61, 0xd7, 0x70, 0x3b, 0x9d, 0x80,
|
||||||
|
0xf2, 0x3b, 0xac, 0xfb, 0xcb, 0xed, 0xe4, 0xfd, 0x50, 0xd4, 0xfd, 0xe2,
|
||||||
|
0x78, 0xb5, 0xef, 0x86, 0x01, 0x75, 0x43, 0x9e, 0x23, 0x41, 0x4b, 0x3c,
|
||||||
|
0xff, 0xba, 0xfd, 0xf3, 0x67, 0xf2, 0xb2, 0x61, 0x97, 0x82, 0xf4, 0xf5,
|
||||||
|
0x61, 0x9d, 0x65, 0x5e, 0x36, 0x58, 0xa9, 0x0d, 0x59, 0xe0, 0x5f, 0x45,
|
||||||
|
0x7c, 0x2e, 0xe6, 0x7a, 0x7f, 0x2c, 0xe6, 0x0a, 0xb1, 0xba, 0xd8, 0x05,
|
||||||
|
0xc5, 0xc6, 0x96, 0x89, 0xc7, 0xeb, 0x6a, 0x23, 0x6e, 0x23, 0xbe, 0x93,
|
||||||
|
0xa8, 0x46, 0x3d, 0xd4, 0xa8, 0x14, 0xf3, 0x21, 0x3e, 0x6c, 0x92, 0xc9,
|
||||||
|
0xeb, 0x00, 0x58, 0x97, 0x62, 0xa2, 0xce, 0x81, 0xee, 0xa9, 0x8f, 0xac,
|
||||||
|
0xdf, 0x55, 0xf3, 0x99, 0x2d, 0x24, 0x86, 0xca, 0x3a, 0xdb, 0xd0, 0x26,
|
||||||
|
0x4e, 0x77, 0x1d, 0x40, 0x48, 0x5f, 0xaf, 0xb2, 0x8d, 0xe9, 0x13, 0x11,
|
||||||
|
0x3c, 0xdb, 0x2b, 0x50, 0x30, 0xc2, 0x39, 0x05, 0x03, 0xc0, 0x1c, 0x5c,
|
||||||
|
0x43, 0xf2, 0xf4, 0xb1, 0x9c, 0x28, 0x59, 0x0a, 0xf8, 0x87, 0xb9, 0xe9,
|
||||||
|
0xd4, 0xc0, 0x8e, 0x18, 0x50, 0x15, 0x35, 0x22, 0x34, 0x71, 0x4c, 0x04,
|
||||||
|
0x31, 0xf7, 0xc2, 0x77, 0x3d, 0xd2, 0x25, 0xb2, 0x41, 0x50, 0xc6, 0x44,
|
||||||
|
0x06, 0xa1, 0x89, 0xd3, 0xb2, 0xb2, 0x9b, 0x72, 0xbb, 0xf3, 0xd7, 0x73,
|
||||||
|
0x7b, 0xf2, 0xf0, 0x48, 0x38, 0xb1, 0x96, 0x0b, 0xf9, 0x40, 0x52, 0x61,
|
||||||
|
0x3b, 0xf3, 0xf3, 0xf0, 0x9f, 0x46, 0xd2, 0x7e, 0xbb, 0x1a, 0x3f, 0xa9,
|
||||||
|
0xfa, 0xaf, 0x9e, 0x25, 0x89, 0x4b, 0x8e, 0xa9, 0x78, 0xad, 0x7c, 0xdb,
|
||||||
|
0x63, 0xea, 0x88, 0x68, 0x56, 0x4f, 0x9c, 0xc4, 0xde, 0x76, 0x7e, 0xbe,
|
||||||
|
0x17, 0x4b, 0xa2, 0x38, 0x32, 0x24, 0x33, 0x37, 0x49, 0x26, 0x78, 0x36,
|
||||||
|
0x34, 0x32, 0xe1, 0xaf, 0x11, 0xc6, 0x9f, 0x2d, 0xce, 0x7a, 0x5e, 0x9c,
|
||||||
|
0x2e, 0x66, 0x55, 0x98, 0x82, 0x44, 0xc4, 0xe7, 0xaf, 0x56, 0x64, 0xee,
|
||||||
|
0xdb, 0xe0, 0xc9, 0xa9, 0xb8, 0x69, 0xe1, 0x14, 0xdf, 0xb4, 0x70, 0x72,
|
||||||
|
0x37, 0x2d, 0x66, 0x07, 0x28, 0x91, 0x02, 0xc5, 0x1d, 0x15, 0x79, 0x24,
|
||||||
|
0x19, 0x4d, 0x10, 0xfc, 0x6d, 0x82, 0xb3, 0x56, 0x6f, 0x4e, 0xb0, 0xc7,
|
||||||
|
0x60, 0x70, 0xd2, 0x45, 0xf7, 0x30, 0x3f, 0x4f, 0x73, 0xc6, 0x0f, 0x1e,
|
||||||
|
0x7c, 0x2e, 0x65, 0x81, 0xb8, 0x06, 0x64, 0x04, 0xae, 0xcf, 0xb5, 0xfc,
|
||||||
|
0x60, 0x7a, 0xb1, 0x05, 0x95, 0x08, 0xc1, 0xc1, 0xef, 0x26, 0x15, 0x47,
|
||||||
|
0x12, 0x50, 0x5c, 0xb7, 0x89, 0x4d, 0x77, 0x20, 0x72, 0x10, 0x9f, 0x42,
|
||||||
|
0x59, 0x8d, 0x83, 0x7a, 0x43, 0xce, 0x9d, 0xfa, 0x88, 0x83, 0x71, 0x31,
|
||||||
|
0xeb, 0xbc, 0x9d, 0x7a, 0xce, 0x3e, 0x2c, 0xcd, 0x29, 0x91, 0xa3, 0x27,
|
||||||
|
0x37, 0x73, 0x5c, 0xc0, 0x4c, 0x3a, 0x3c, 0xa8, 0x07, 0x6a, 0x4d, 0xb2,
|
||||||
|
0xee, 0x36, 0x49, 0xa9, 0x24, 0x19, 0x64, 0x41, 0xd6, 0xcd, 0xbf, 0x91,
|
||||||
|
0xef, 0x4d, 0x2b, 0x3a, 0x41, 0x35, 0x4d, 0xd3, 0x96, 0x74, 0xa0, 0xd3,
|
||||||
|
0xb3, 0x05, 0xda, 0xf0, 0x83, 0xf7, 0xd9, 0xf4, 0x46, 0x27, 0x19, 0xc2,
|
||||||
|
0x5e, 0x7d, 0x5e, 0x67, 0xc7, 0x63, 0x88, 0x12, 0x99, 0x6b, 0x2f, 0xce,
|
||||||
|
0xc4, 0xb5, 0x97, 0xd4, 0xb2, 0x7e, 0x35, 0xb5, 0x78, 0xe3, 0xba, 0xc2,
|
||||||
|
0x1b, 0xd9, 0x08, 0x18, 0x11, 0x7a, 0xc9, 0xc4, 0xd3, 0x83, 0x91, 0x8e,
|
||||||
|
0x33, 0x2e, 0x9d, 0x68, 0xc2, 0xc5, 0xfc, 0x61, 0x9d, 0xe4, 0xc5, 0x3a,
|
||||||
|
0xc9, 0xa5, 0x4e, 0x2a, 0xb0, 0xca, 0x89, 0x2b, 0x87, 0xc9, 0xe3, 0x24,
|
||||||
|
0x10, 0x05, 0xcf, 0x31, 0x03, 0x92, 0x85, 0xdc, 0xfb, 0x9c, 0x09, 0x1a,
|
||||||
|
0x18, 0xdd, 0x51, 0xba, 0x20, 0x1c, 0x9b, 0x7a, 0x39, 0xde, 0x95, 0xb3,
|
||||||
|
0xdf, 0xf0, 0x50, 0x3c, 0xe3, 0x8f, 0x9d, 0xe8, 0xbe, 0xc9, 0x1b, 0xcb,
|
||||||
|
0x9b, 0x13, 0x97, 0xa7, 0x32, 0x5c, 0xe8, 0x49, 0x45, 0xce, 0x12, 0x5c,
|
||||||
|
0x2b, 0xab, 0x18, 0x54, 0xb0, 0x7b, 0x1c, 0x35, 0xf4, 0x0d, 0xc3, 0x00,
|
||||||
|
0x8f, 0x37, 0xec, 0xdb, 0x48, 0x76, 0x41, 0xfb, 0x37, 0xfe, 0x14, 0x37,
|
||||||
|
0x81, 0x5d, 0x2d, 0x1a, 0x28, 0x23, 0xf0, 0xf4, 0x6b, 0xf6, 0xd6, 0x08,
|
||||||
|
0xfe, 0x1f, 0x54, 0x9b, 0x16, 0xf3, 0x4d, 0xd8, 0x42, 0x5c, 0x1d, 0x89,
|
||||||
|
0x67, 0x15, 0x5d, 0x20, 0xb1, 0xb2, 0xe6, 0xec, 0xb9, 0x9e, 0xa6, 0xa3,
|
||||||
|
0x0d, 0x03, 0x9d, 0x65, 0x9a, 0x99, 0x35, 0x2a, 0xa4, 0x45, 0xe4, 0x6c,
|
||||||
|
0x19, 0x62, 0xb0, 0x25, 0x1e, 0x5d, 0x3f, 0x92, 0x74, 0xf1, 0x82, 0xd3,
|
||||||
|
0x42, 0x2e, 0xce, 0x09, 0xf9, 0xaf, 0x10, 0x4d, 0x12, 0xa2, 0x79, 0x6c,
|
||||||
|
0xc6, 0xb8, 0x68, 0xdf, 0xf5, 0xfb, 0xbb, 0x84, 0x93, 0xa4, 0x62, 0xd4,
|
||||||
|
0xa2, 0x8b, 0x02, 0x3c, 0xb5, 0x20, 0x08, 0xfe, 0x7e, 0x5b, 0x74, 0xb7,
|
||||||
|
0xf3, 0xac, 0xba, 0xb8, 0x11, 0xac, 0xc2, 0xe6, 0xbe, 0x3a, 0xc9, 0xa0,
|
||||||
|
0x0c, 0xdd, 0x5d, 0xe4, 0xce, 0x04, 0xd5, 0x22, 0x6f, 0xfc, 0x5f, 0x88,
|
||||||
|
0x30, 0xa1, 0x46, 0xcf, 0x34, 0xfd, 0x13, 0x5a, 0xf8, 0x04, 0x2d, 0x53,
|
||||||
|
0x98, 0xef, 0xee, 0x9d, 0xec, 0x5d, 0xef, 0xcd, 0x42, 0x1e, 0xbc, 0x6b,
|
||||||
|
0x64, 0x76, 0x3c, 0x57, 0xd5, 0x67, 0x2e, 0xea, 0x63, 0x93, 0x11, 0x0f,
|
||||||
|
0x93, 0xab, 0xb3, 0x8f, 0x64, 0x26, 0xfc, 0x1d, 0xf0, 0x53, 0x1c, 0x1f,
|
||||||
|
0xb4, 0x6d, 0x4a, 0xfc, 0x53, 0xc2, 0x9c, 0x0b, 0xe2, 0x50, 0xfb, 0x0f,
|
||||||
|
0x1d, 0x48, 0xfc, 0xfd, 0x47, 0x42, 0x16, 0x7b, 0x8e, 0x0f, 0x5f, 0xa6,
|
||||||
|
0xae, 0x74, 0x2f, 0x6c, 0x68, 0xe8, 0x98, 0xc5, 0x69, 0x50, 0x07, 0xaf,
|
||||||
|
0x8f, 0x29, 0x78, 0xec, 0x03, 0xa2, 0x20, 0xf2, 0x44, 0x37, 0xb9, 0x8e,
|
||||||
|
0x06, 0x39, 0x1d, 0xc0, 0xd9, 0x50, 0xc7, 0x92, 0x11, 0x19, 0x27, 0x91,
|
||||||
|
0x0b, 0xe7, 0xd9, 0xdb, 0xac, 0xea, 0x96, 0x9c, 0x84, 0x9a, 0x82, 0x72,
|
||||||
|
0xc7, 0xbb, 0x80, 0x39, 0x8d, 0xf9, 0xa5, 0x33, 0xb7, 0xe2, 0x36, 0xad,
|
||||||
|
0x86, 0x07, 0x34, 0x33, 0x7b, 0xfb, 0x78, 0xb6, 0xd1, 0x48, 0x3b, 0xd1,
|
||||||
|
0x14, 0xd3, 0x5b, 0x98, 0x8e, 0xe7, 0xa3, 0x8d, 0xbc, 0x28, 0x35, 0xcc,
|
||||||
|
0x53, 0xa2, 0x13, 0xa2, 0xc4, 0xd5, 0x4d, 0xde, 0x18, 0x88, 0xaf, 0xbb,
|
||||||
|
0x99, 0xd9, 0xeb, 0x6e, 0x7a, 0x8e, 0xe4, 0xcb, 0xe4, 0x2e, 0x28, 0x9e,
|
||||||
|
0x80, 0x4b, 0x32, 0xa5, 0xe1, 0x27, 0x9e, 0x7c, 0xe6, 0xfc, 0x2c, 0x6b,
|
||||||
|
0x2a, 0x75, 0xd3, 0x2c, 0xb8, 0xef, 0x94, 0x01, 0xf5, 0x9a, 0x5e, 0x8d,
|
||||||
|
0xfd, 0xf6, 0xbd, 0xec, 0x98, 0xb4, 0x59, 0xa9, 0x63, 0x72, 0x33, 0xb9,
|
||||||
|
0x66, 0x7e, 0x7e, 0xe6, 0xe5, 0x35, 0x08, 0x1d, 0x3a, 0x37, 0xbc, 0x30,
|
||||||
|
0xe8, 0x61, 0x35, 0xe6, 0x14, 0x85, 0x16, 0x3c, 0x7a, 0x77, 0x36, 0xa3,
|
||||||
|
0x49, 0x08, 0xae, 0x91, 0x2e, 0x10, 0xb6, 0x16, 0x71, 0x2d, 0xbd, 0x97,
|
||||||
|
0x60, 0xe6, 0xb3, 0x98, 0x09, 0xd7, 0x21, 0xe7, 0x44, 0xe4, 0xfe, 0x8d,
|
||||||
|
0x6e, 0x52, 0xde, 0xd9, 0x2a, 0x88, 0x20, 0x17, 0xff, 0xdd, 0xc9, 0xb5,
|
||||||
|
0xf5, 0xb4, 0x0d, 0x43, 0xe1, 0xbf, 0x02, 0xd6, 0x54, 0x25, 0x23, 0x34,
|
||||||
|
0x30, 0xed, 0x61, 0x4b, 0x49, 0x10, 0xa0, 0x49, 0x93, 0x36, 0xc4, 0xa4,
|
||||||
|
0xb0, 0x27, 0x84, 0x50, 0x92, 0x06, 0x1a, 0x48, 0x63, 0xd4, 0x84, 0xb1,
|
||||||
|
0xaa, 0xea, 0x7f, 0xdf, 0xb9, 0x38, 0xa9, 0xed, 0xa6, 0x65, 0xf4, 0xa5,
|
||||||
|
0x4d, 0x63, 0xc7, 0xb1, 0x8f, 0x8f, 0xcf, 0xf5, 0x3b, 0xbd, 0x8a, 0xb7,
|
||||||
|
0xc9, 0xc5, 0xd5, 0x6a, 0x19, 0x47, 0x60, 0xb0, 0x88, 0xb5, 0x56, 0xea,
|
||||||
|
0xb1, 0xb6, 0xb3, 0x5b, 0x31, 0x71, 0xbb, 0x2c, 0x35, 0xdf, 0x4d, 0x8a,
|
||||||
|
0x6e, 0x91, 0xff, 0x08, 0xcf, 0xe0, 0xb9, 0xd1, 0x72, 0x6d, 0xe8, 0xc2,
|
||||||
|
0x37, 0x12, 0x4d, 0xec, 0x46, 0xb1, 0xed, 0xdd, 0xa7, 0x4b, 0x05, 0x48,
|
||||||
|
0xa4, 0xa2, 0x02, 0x59, 0xf4, 0xff, 0x69, 0xc7, 0xaa, 0x4d, 0x3b, 0x56,
|
||||||
|
0x7a, 0xaa, 0xb4, 0x1b, 0x6a, 0x94, 0xc2, 0xeb, 0x9f, 0x46, 0xab, 0xd4,
|
||||||
|
0x29, 0xb4, 0x50, 0xe2, 0x54, 0x6f, 0xc0, 0xf4, 0x29, 0xdc, 0x7f, 0x4c,
|
||||||
|
0xfe, 0x24, 0x0c, 0xc6, 0x37, 0x5a, 0x33, 0x3d, 0x81, 0x0a, 0xdd, 0xb2,
|
||||||
|
0x3b, 0xbc, 0x32, 0x7a, 0x74, 0x09, 0xd5, 0x7a, 0x75, 0xf9, 0x3c, 0x79,
|
||||||
|
0xee, 0x4b, 0xd4, 0x76, 0xf9, 0x59, 0xca, 0xbd, 0xc2, 0x06, 0x74, 0x9a,
|
||||||
|
0x67, 0xc9, 0x6b, 0x42, 0xd8, 0x0a, 0x66, 0xbd, 0x60, 0x7b, 0xa9, 0x2c,
|
||||||
|
0x65, 0x88, 0xd3, 0x6e, 0x6d, 0xd5, 0x90, 0xbc, 0x40, 0x07, 0x0c, 0x72,
|
||||||
|
0x67, 0x65, 0xc0, 0xc2, 0x4d, 0x09, 0x37, 0x25, 0xe3, 0xff, 0x40, 0x0a,
|
||||||
|
0xe7, 0x7a, 0xff, 0x04, 0x5d, 0x46, 0x6e, 0x02, 0x7e, 0xa9, 0x3c, 0x35,
|
||||||
|
0x7f, 0x35, 0x52, 0xdb, 0xc2, 0xd4, 0x72, 0x5b, 0x28, 0x20, 0x9a, 0x3b,
|
||||||
|
0x5e, 0x11, 0x62, 0x02, 0x1f, 0x37, 0x1b, 0xe3, 0xc9, 0x1a, 0x52, 0xc4,
|
||||||
|
0xe4, 0xda, 0xd6, 0x1e, 0x2d, 0x95, 0x3d, 0x3a, 0x18, 0x98, 0x06, 0x69,
|
||||||
|
0x09, 0x1c, 0x01, 0x4a, 0x54, 0x17, 0x92, 0x8e, 0x2d, 0x35, 0x70, 0x31,
|
||||||
|
0xb6, 0x4d, 0xa8, 0x61, 0x2d, 0xd6, 0x5e, 0xf8, 0x36, 0xf2, 0x65, 0x27,
|
||||||
|
0xd5, 0xb9, 0xfe, 0x98, 0x52, 0x8e, 0xbb, 0xaa, 0x5c, 0x05, 0x52, 0x46,
|
||||||
|
0x02, 0x85, 0x1d, 0x81, 0x4e, 0x9d, 0x02, 0xce, 0x0c, 0xe3, 0x31, 0x9d,
|
||||||
|
0xd2, 0x32, 0xac, 0x0b, 0xb6, 0x17, 0x62, 0xaa, 0xd2, 0x63, 0x3f, 0x76,
|
||||||
|
0x23, 0xaa, 0x17, 0x04, 0x2b, 0x12, 0xd6, 0xd6, 0x4a, 0x16, 0x12, 0xc1,
|
||||||
|
0x36, 0xa5, 0x57, 0xb2, 0x74, 0x25, 0x60, 0x9c, 0xb2, 0x47, 0x7c, 0xb8,
|
||||||
|
0xad, 0xdc, 0xc8, 0x30, 0x14, 0xad, 0x81, 0x1e, 0xc8, 0x3c, 0x2e, 0x75,
|
||||||
|
0xc3, 0x98, 0x79, 0x58, 0x9d, 0x39, 0x0e, 0x55, 0x14, 0x48, 0xab, 0x38,
|
||||||
|
0xaf, 0x6b, 0x5a, 0x04, 0x2e, 0xf8, 0x12, 0x5c, 0x22, 0x47, 0x00, 0x4f,
|
||||||
|
0xf9, 0x53, 0xb8, 0x42, 0x66, 0xc4, 0xe5, 0x42, 0xc3, 0xf5, 0x24, 0x9f,
|
||||||
|
0xaa, 0x96, 0x06, 0x2f, 0xa1, 0x49, 0x62, 0xd3, 0x07, 0xa2, 0x7e, 0x4c,
|
||||||
|
0xf9, 0x18, 0x98, 0x72, 0x78, 0xec, 0x1f, 0x79, 0xeb, 0xc3, 0xfe, 0xae,
|
||||||
|
0xf3, 0x58, 0xde, 0x37, 0xd7, 0x49, 0x0a, 0x44, 0x39, 0x72, 0x7b, 0x7a,
|
||||||
|
0x40, 0x13, 0xc5, 0x99, 0x3e, 0xa9, 0xf7, 0x7d, 0x2f, 0x1e, 0x26, 0x25,
|
||||||
|
0x86, 0xff, 0xce, 0x32, 0x2c, 0x3f, 0xfa, 0x09, 0x5b, 0xa4, 0x9e, 0x84,
|
||||||
|
0xc6, 0x18, 0x98, 0xf7, 0xd7, 0xac, 0xa8, 0x9a, 0x4b, 0x02, 0x03, 0x11,
|
||||||
|
0xbb, 0xc1, 0xa6, 0xc8, 0xe9, 0x34, 0xa9, 0xc6, 0x35, 0x46, 0xb6, 0x2e,
|
||||||
|
0xf8, 0xda, 0x59, 0xa0, 0xdb, 0x19, 0x10, 0x46, 0x53, 0x78, 0x29, 0x1c,
|
||||||
|
0xd6, 0x1f, 0xf9, 0x3c, 0x58, 0xbc, 0x16, 0x55, 0x20, 0x2e, 0x9a, 0x59,
|
||||||
|
0x79, 0x18, 0x0b, 0x6f, 0x9a, 0x64, 0xf0, 0x83, 0xfb, 0xc3, 0xef, 0xa5,
|
||||||
|
0x87, 0x52, 0x2c, 0x30, 0x63, 0x19, 0x2d, 0x70, 0x0c, 0x43, 0x62, 0x57,
|
||||||
|
0x55, 0x39, 0x0f, 0xf6, 0x8f, 0x97, 0x6f, 0xbd, 0x12, 0xe6, 0x08, 0x2f,
|
||||||
|
0x4b, 0x25, 0x30, 0x43, 0x07, 0x18, 0xeb, 0x9d, 0xc3, 0x59, 0xd9, 0x1c,
|
||||||
|
0x4e, 0xac, 0x79, 0xf0, 0xbd, 0xfe, 0xb9, 0xd8, 0x08, 0x34, 0x26, 0x0a,
|
||||||
|
0xd1, 0x12, 0xb8, 0xc1, 0x11, 0x1c, 0x02, 0x12, 0x9e, 0xf1, 0x58, 0x3f,
|
||||||
|
0x7f, 0x1e, 0xb9, 0xf4, 0xb4, 0x32, 0xa4, 0x0c, 0xa5, 0xf7, 0x2e, 0x23,
|
||||||
|
0x6e, 0x33, 0xa8, 0x9d, 0xc4, 0x60, 0x48, 0x98, 0x6c, 0x4d, 0xbe, 0xbd,
|
||||||
|
0x9f, 0x09, 0x67, 0x0a, 0x5c, 0x88, 0x3b, 0xa1, 0xdb, 0xe7, 0x9b, 0xdf,
|
||||||
|
0xac, 0xdb, 0x2b, 0xe8, 0xb4, 0x5b, 0x67, 0x0c, 0x1e, 0x79, 0xe7, 0x11,
|
||||||
|
0xab, 0xb5, 0x0a, 0xa0, 0x4e, 0x1f, 0x4b, 0xcb, 0xf4, 0xc0, 0xc6, 0xf3,
|
||||||
|
0x52, 0xa6, 0xce, 0x4d, 0x73, 0xeb, 0x2d, 0xd0, 0x90, 0x0c, 0x2a, 0xa0,
|
||||||
|
0x71, 0xee, 0x76, 0xe7, 0xd3, 0xb2, 0x44, 0xd4, 0x19, 0x95, 0xee, 0x12,
|
||||||
|
0x3c, 0x53, 0xa2, 0x05, 0x0b, 0x1d, 0xf7, 0x40, 0x08, 0xd0, 0xc1, 0xb8,
|
||||||
|
0x66, 0x05, 0x4b, 0x34, 0xca, 0x8b, 0x3a, 0xe3, 0xb7, 0xd9, 0x22, 0x7c,
|
||||||
|
0x88, 0x60, 0x3a, 0xbb, 0xe8, 0x43, 0xa0, 0xe2, 0xa0, 0x7a, 0x86, 0x07,
|
||||||
|
0x1a, 0x0b, 0x88, 0xfe, 0x52, 0x2a, 0xb2, 0xe3, 0x3e, 0x3d, 0x01, 0xef,
|
||||||
|
0x02, 0xaf, 0x02, 0x8d, 0xee, 0xa6, 0x94, 0x32, 0x35, 0xb9, 0x10, 0x2b,
|
||||||
|
0xf8, 0x38, 0x07, 0xd3, 0xcb, 0xe8, 0xc0, 0x97, 0x78, 0x5c, 0x70, 0x76,
|
||||||
|
0x85, 0x16, 0xd4, 0xab, 0xce, 0xe5, 0x78, 0x4e, 0x11, 0x88, 0x36, 0x4e,
|
||||||
|
0xbb, 0x58, 0x8e, 0xe0, 0x24, 0x8c, 0xe5, 0x2b, 0x45, 0xb7, 0xb0, 0xd7,
|
||||||
|
0x70, 0x02, 0x5a, 0x06, 0x24, 0x2e, 0xf0, 0x48, 0x96, 0x3b, 0xfe, 0xcd,
|
||||||
|
0xe9, 0xe0, 0xf6, 0x00, 0x8c, 0x8c, 0x10, 0xbe, 0xdc, 0x10, 0x2e, 0x06,
|
||||||
|
0xb7, 0x1f, 0x5d, 0xff, 0xa1, 0x58, 0xdb, 0x5f, 0x09, 0x14, 0x07, 0x55,
|
||||||
|
0x4d, 0x81, 0x3c, 0xcd, 0x71, 0xd8, 0xd7, 0x1c, 0x07, 0x97, 0x43, 0x2d,
|
||||||
|
0x86, 0xb9, 0xd3, 0xaa, 0x15, 0x4f, 0x12, 0xee, 0x16, 0xbe, 0xf0, 0x8f,
|
||||||
|
0x80, 0xe0, 0x8b, 0xc4, 0x9c, 0x3b, 0x32, 0x0a, 0x10, 0x04, 0x97, 0x5a,
|
||||||
|
0x0b, 0xd2, 0x96, 0xa1, 0x86, 0xf8, 0x14, 0x78, 0x83, 0xac, 0xb1, 0x7c,
|
||||||
|
0x5d, 0x4b, 0x9e, 0xf8, 0xaa, 0x7e, 0x50, 0xd5, 0x11, 0x12, 0xee, 0x11,
|
||||||
|
0x6d, 0xcd, 0x3a, 0xf0, 0xfd, 0x6c, 0x5c, 0x3d, 0xa2, 0x03, 0x2a, 0x5f,
|
||||||
|
0xc6, 0xf7, 0x25, 0xe8, 0x5b, 0x94, 0x29, 0x7e, 0xf2, 0x98, 0xfc, 0xf5,
|
||||||
|
0xcb, 0x22, 0xad, 0x11, 0x9f, 0xe7, 0x1f, 0x0f, 0x3f, 0x0f, 0xbf, 0xb6,
|
||||||
|
0x48, 0x3d, 0x04, 0xdf, 0x02, 0x2b, 0x87, 0x2f, 0xcd, 0xfd, 0xe1, 0x97,
|
||||||
|
0xc8, 0x1e, 0x3a, 0x32, 0x17, 0xaf, 0x7b, 0x4d, 0x96, 0x13, 0x77, 0x91,
|
||||||
|
0x54, 0xe8, 0x08, 0x92, 0x03, 0xa7, 0x86, 0xee, 0xc0, 0x82, 0xaf, 0x79,
|
||||||
|
0xea, 0xed, 0x35, 0xb3, 0x39, 0x7b, 0x78, 0x59, 0x52, 0xee, 0x65, 0xf2,
|
||||||
|
0x79, 0x2e, 0x54, 0xbe, 0x86, 0x5e, 0xb4, 0x39, 0x25, 0xc1, 0xd6, 0x99,
|
||||||
|
0x3b, 0xe2, 0x0b, 0x02, 0x9b, 0x08, 0x1d, 0x6b, 0x28, 0x3c, 0xd5, 0x92,
|
||||||
|
0xd4, 0xf3, 0x2a, 0xc3, 0x82, 0x8b, 0x6e, 0x28, 0xa4, 0xae, 0x99, 0xdf,
|
||||||
|
0xa2, 0x9e, 0x3a, 0x05, 0x31, 0xe5, 0xb2, 0xd7, 0x86, 0x8c, 0x34, 0x76,
|
||||||
|
0x8a, 0xc8, 0x1d, 0x06, 0xef, 0x9f, 0x77, 0x28, 0x62, 0xb7, 0xb6, 0xbd,
|
||||||
|
0x89, 0xbb, 0x63, 0xdd, 0xe2, 0x6d, 0xb7, 0x6e, 0x2a, 0xd3, 0x44, 0xb9,
|
||||||
|
0xd5, 0xfa, 0xbf, 0xcd, 0x58, 0x1d, 0x95, 0xb7, 0x13, 0x75, 0xf5, 0x64,
|
||||||
|
0x5a, 0x19, 0x67, 0x5b, 0xd4, 0x75, 0x92, 0xce, 0xb8, 0x3f, 0x17, 0x95,
|
||||||
|
0xe9, 0x65, 0xb0, 0x91, 0x92, 0x44, 0x84, 0xad, 0xa4, 0x71, 0xf9, 0xb3,
|
||||||
|
0x20, 0x88, 0x0e, 0x8e, 0x67, 0x82, 0x76, 0xfa, 0x27, 0xc4, 0xbd, 0xa3,
|
||||||
|
0x7f, 0xb9, 0xa1, 0x68, 0x0e, 0x38, 0x4b, 0x00, 0x00
|
||||||
|
};
|
||||||
|
unsigned int index_htm_gz_len = 6261;
|
||||||
60
libraries/WebServer/examples/FSBrowser/extras/reduce_index.sh
Executable file
60
libraries/WebServer/examples/FSBrowser/extras/reduce_index.sh
Executable file
|
|
@ -0,0 +1,60 @@
|
||||||
|
#/bin/sh
|
||||||
|
|
||||||
|
# Processing script to optionally reduce filesystem use by miniying, gzipping and preparing index.htm for embedding in code.
|
||||||
|
# Please see readme.md for more information.
|
||||||
|
|
||||||
|
# Requires xdd which is part of the VIM package
|
||||||
|
# Requires npm
|
||||||
|
# sudo apt install npm
|
||||||
|
# ln -s /usr/bin/nodejs /usr/bin/node
|
||||||
|
# Requires html-minifier
|
||||||
|
# sudo npm install html-minifier -g
|
||||||
|
|
||||||
|
html-minifier \
|
||||||
|
--case-sensitive \
|
||||||
|
--collapse-boolean-attributes \
|
||||||
|
--collapse-whitespace \
|
||||||
|
--minify-css true \
|
||||||
|
--minify-js true \
|
||||||
|
--process-conditional-comments \
|
||||||
|
--remove-attribute-quotes \
|
||||||
|
--remove-comments \
|
||||||
|
--remove-empty-attributes \
|
||||||
|
--remove-optional-tags \
|
||||||
|
--remove-redundant-attributes \
|
||||||
|
--remove-script-type-attributes \
|
||||||
|
--remove-style-link-type-attributes \
|
||||||
|
-o index.htm \
|
||||||
|
../data/edit/index.htm
|
||||||
|
|
||||||
|
if [ $? -ne 0 ]
|
||||||
|
then
|
||||||
|
echo "Error minifying index.htm"
|
||||||
|
exit -1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -e index.htm.gz ]
|
||||||
|
then
|
||||||
|
rm index.htm.gz
|
||||||
|
fi
|
||||||
|
|
||||||
|
gzip index.htm
|
||||||
|
if [ $? -ne 0 ]
|
||||||
|
then
|
||||||
|
echo "Error gzipping minified index.htm"
|
||||||
|
exit -1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo '// WARNING: Auto-generated file. Please do not modify by hand.' > index_htm.h
|
||||||
|
echo '// This file is an embeddable version of the gzipped index.htm file.' >> index_htm.h
|
||||||
|
echo '// To update it, please rerun the `reduce_index.sh` script located in the `extras` subfolder' >> index_htm.h
|
||||||
|
echo '// then recompile the sketch after each change to the `index.html` file.' >> index_htm.h
|
||||||
|
xxd -i index.htm.gz >> index_htm.h
|
||||||
|
if [ $? -ne 0 ]
|
||||||
|
then
|
||||||
|
echo "Error creating include file from index.htm.gz"
|
||||||
|
exit -1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo Reduce complete.
|
||||||
|
|
||||||
137
libraries/WebServer/examples/FSBrowser/readme.md
Normal file
137
libraries/WebServer/examples/FSBrowser/readme.md
Normal file
|
|
@ -0,0 +1,137 @@
|
||||||
|
# FSBrowser readme
|
||||||
|
|
||||||
|
## What is this sketch about ?
|
||||||
|
|
||||||
|
This example is a FileSystem Browser for the Pico using http requests and a html/javascript frontend,
|
||||||
|
working for all of SPIFFS, LittleFS and SDFS.
|
||||||
|
This unified version is based on the previous examples named FSWebServer, FSBrowser and SDWebServer, Copyright (c) 2015 Hristo Gochkov. All rights reserved.
|
||||||
|
|
||||||
|
## How to use it ?
|
||||||
|
1. Uncomment one of the `#define USE_xxx` directives in the sketch
|
||||||
|
2. Add the credentials of your WiFi network (search for `STASSID`)
|
||||||
|
3. Compile and upload the sketch to your Pico
|
||||||
|
4. For normal use, copy the contents of the `data` folder to the filesystem. To do so:
|
||||||
|
- for SDFS, copy that contents (not the data folder itself, just its contents) to the root of a FAT/FAT32-formated SD card connected to the SPI port of the Pico
|
||||||
|
- for LittleFS, please follow the instructions at https://arduino-pico.readthedocs.io/en/latest/fs.html#uploading-files-to-the-littlefs-file-system
|
||||||
|
5. Once the data and sketch have been uploaded, access the editor by pointing your browser to http://fsbrowser.local/edit
|
||||||
|
|
||||||
|
## Options
|
||||||
|
If you need to free some space on the Pico filesystem, you can delete all the sample files at the root but also replace the `index.htm` file in the `data/edit` subfolder by the `index.htm.gz` file from the `extras` folder. That compressed version is not suited for learning or debugging, but will bring the total FS usage under 7KB.
|
||||||
|
If you want to use the browser on a an existing filesystem or don't want to perform step 4 above, you have two possibilities :
|
||||||
|
- either upload the `index.htm` file to the filesystem by opening a command shell in the `data` folder and running the following cURL command:
|
||||||
|
`curl -F file=@edit/index.htm;filename=/edit/index.htm fsbrowser.local/edit`
|
||||||
|
- or embed a version of the html page in the source code itself by uncommenting the following line in the sketch and rebuilding:
|
||||||
|
`#define INCLUDE_FALLBACK_INDEX_HTM`
|
||||||
|
That embedded version is functionally equivalent and will be returned if no `/edit/index.htm` or `/edit/index.htm.gz` file can be found on the filesystem, at the expense of a higher binary size.
|
||||||
|
|
||||||
|
If you use the gzipped or `INCLUDE_FALLBACK_INDEX_HTM` options, please remember to rerun the `reduce_index.sh` script located in the `extras` subfolder and recompile the sketch after each change to the `index.html` file.
|
||||||
|
|
||||||
|
## Dependency
|
||||||
|
The html page uses the [Ace.js](https://ace.c9.io/) (v1.4.9 at the time of writing) text editor which is loaded from a CDN. Consequently, internet access from your web browser is required for the FSBrowser editing feature to work as-is.
|
||||||
|
|
||||||
|
If your browser has no web access (e.g. if you are connected to the Pico as an access-point), you can copy the `ace.js` file to the `edit` subfolder of the Pico filesystem, along with optional plugins etc. according to your needs. A typical set might be:
|
||||||
|
```
|
||||||
|
ace.js
|
||||||
|
ext-keybinding_menu.js
|
||||||
|
ext-searchbox.js
|
||||||
|
mode-html.js
|
||||||
|
worker-html.js
|
||||||
|
worker-css.js
|
||||||
|
worker-javascript.js
|
||||||
|
mode-xml.js
|
||||||
|
worker-xml.js
|
||||||
|
mode-json.js
|
||||||
|
worker-json.js
|
||||||
|
```
|
||||||
|
(see https://github.com/ajaxorg/ace-builds for a full list).
|
||||||
|
|
||||||
|
If `ace.js` cannot be found on the Pico filesystem either, the page will default to a plain text viewer, with a warning message.
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
- See https://arduino-pico.readthedocs.io/en/latest/fs.html for more information on FileSystems supported by the Pico.
|
||||||
|
- For SDFS, if your card's CS pin is not connected to the default pin (4), uncomment the `fileSystemConfig.setCSPin(chipSelectPin);` line, specifying the GPIO the CS pin is connected to
|
||||||
|
- `index.htm` is the default index returned if your URL does not end with a filename (works on subfolders as well)
|
||||||
|
- Directories are supported on SDFS and LittleFS.
|
||||||
|
- The convention here is that the root of the filesystem is "/". On SPIFFS, paths not started with a slash are not supported
|
||||||
|
- For creation, the convention is that a path ending with a "/" means create a folder, while without a "/" we create a file. Having an extension or not does not matter.
|
||||||
|
|
||||||
|
## Changelog since original FSBrowser
|
||||||
|
|
||||||
|
### Fixes to work on LittleFS based on SDFS
|
||||||
|
- #define logic to select FS
|
||||||
|
- switched from SD to SDFS
|
||||||
|
- begin() does not support parameters > removed SS and added optional config
|
||||||
|
- LittleFS.open() second parametsr is mandatory > specified "r" where needed
|
||||||
|
- 'FILE_WRITE' was not declared in this scope > replaced by "w"
|
||||||
|
|
||||||
|
### UI/usability improvements
|
||||||
|
- Never format filesystem, just return "FS INIT ERROR" when FS cannot be mounted
|
||||||
|
- Tree panel width is now proportional (20%) to see long names on big screens
|
||||||
|
- Added icons for files, and indented them to the same level as folders
|
||||||
|
- Changed file/folder icon set to use a lighter and more neutral one, and added specific "text" and "image" icons for formats recognized as such
|
||||||
|
- Items are now sorted (folders first, then plain files, each in alphabetic order)
|
||||||
|
- Added file size after each file name
|
||||||
|
- Added FS status information at the top right
|
||||||
|
- Made clear that an async operation is in progress by dimming screen and showing operation status
|
||||||
|
- Filled filename box in header with the name of the last clicked file
|
||||||
|
- Selecting a file for upload defaults to putting it in the same folder as the last clicked file
|
||||||
|
- Removed limitation to 8.3 lowercase filenames
|
||||||
|
- Support Filenames without extension, Dirnames with extension
|
||||||
|
- Improved recursive refresh of parts of the tree (e.g. refresh folder upon file delete, show last folder upon creating nested file)
|
||||||
|
- Added Save/Discard/Help buttons to ACE editor, discard confirmation on leave, and refresh tree and status upon save
|
||||||
|
- Removed "Upload" from context menu (which didn't work anyway)
|
||||||
|
- Added "Rename/Move" feature to context menu
|
||||||
|
- Sketch can be used on a preexisting filesystem by embedding the index.htm file in the program
|
||||||
|
|
||||||
|
## TODO (maybe)
|
||||||
|
- ? How can we query the fatType of the SDFS (FAT16 or FAT32) to limit filenames to 8.3 on FAT16 ?
|
||||||
|
- ? Add a visible root node "/" (with no delete option) + add the FS type next to it, like <i>LittleFS</i>
|
||||||
|
- ? move "Mkdir" and "MkFile" to context menu, with prompt like for Rename/Move
|
||||||
|
- ? implement drag/drop for move + make "rename" only a local rename operation (no move)
|
||||||
|
- ? Optionally present SPIFFS as a hierarchical FS too
|
||||||
|
- ? Optionally mount several filesystems at the same time (SPIFFS + SDFS or LittleFS + SDFS)
|
||||||
|
|
||||||
|
## Test suite
|
||||||
|
These tests are a checklist of operations to verify the FSBrowser behaviour.
|
||||||
|
### On SPIFFS
|
||||||
|
#### 8.3 filenames
|
||||||
|
- At root : MkFile '/1.txt' / List / Edit / Download / Delete / Upload '/1.png' / View image / Delete image
|
||||||
|
- In subdir : MkFile '/dir/2.txt' / List / Edit / Download / Delete / Upload '/dir/2.png' / View image
|
||||||
|
- Create nested file '/a/b.txt' and delete it
|
||||||
|
- Attempt creation of unsupported filenames
|
||||||
|
#### Long filenames
|
||||||
|
- At root : MkFile '/My text file 1.txt' / List / Edit / Download / Delete / Upload '/My image file 1.png' / View image / Delete image
|
||||||
|
- In subdir : MkFile '/My Directory/My text 2.txt' / List / Edit / Download / Delete / Upload '/My Directory/My image 2.png' / View image
|
||||||
|
- Create nested file '/My folder/My test file.txt' and delete it
|
||||||
|
|
||||||
|
### On LittleFS
|
||||||
|
#### 8.3 filenames
|
||||||
|
- At root : MkFile '/1.txt' / List / Edit / Download / Delete / Upload '/1.png' / View image / Delete image / Mkdir '/dir'
|
||||||
|
- In subdir : MkFile '/dir/2.txt' / List / Edit / Download / Delete / Upload '/dir/2.png' / View image / Mkdir '/dir/sub'
|
||||||
|
- Delete root folder '/dir'
|
||||||
|
- Create nested file '/a/b.txt' and delete file 'b.txt'
|
||||||
|
#### Long filenames
|
||||||
|
- At root : MkFile '/My text file 1.txt' / List / Edit / Download / Delete / Upload '/My image file 1.png' / View image / Delete image / Mkdir '/My Directory'
|
||||||
|
- In subdir : MkFile '/My Directory/My text file 2.txt' / List / Edit / Download / Delete / Upload '/My Directory/My image file 2.png' / View image / Mkdir '/My Directory/My Subdirectory'
|
||||||
|
- Delete root folder '/My Directory'
|
||||||
|
- Create nested file '/My folder/My test file.txt' and delete file 'My test file.txt'
|
||||||
|
|
||||||
|
### On SDFS
|
||||||
|
#### 8.3 filenames
|
||||||
|
- At root : MkFile '/1.txt' / List / Edit / Download / Delete / Upload '/1.png' / View image / Delete image / Mkdir '/dir'
|
||||||
|
- In subdir : MkFile '/dir/2.txt' / List / Edit / Download / Delete / Upload '/dir/2.png' / View image / Mkdir '/dir/sub'
|
||||||
|
- Delete root folder '/dir'
|
||||||
|
- Create nested file '/a/b.txt' and delete file 'b.txt', then delete '/a'
|
||||||
|
#### Long filenames
|
||||||
|
- At root : MkFile '/My text file 1.txt' / List / Edit / Download / Delete / Upload '/My image file 1.png' / View image / Delete image / Mkdir '/My Directory'
|
||||||
|
- In subdir : MkFile '/My Directory/My text file 2.txt' / List / Edit / Download / Delete / Upload '/My Directory/My image file 2.png' / View image / Mkdir '/My Directory/My Subdirectory'
|
||||||
|
- Delete root folder '/My Directory'
|
||||||
|
- Create nested file '/My folder/My test file.txt' and delete file 'My test file.txt'
|
||||||
|
|
||||||
|
## Credits
|
||||||
|
- Original version of FSBrowser written by me-no-dev, contributions over time by various contributors
|
||||||
|
- Icons are from https://feathericons.com/ . The resulting PNG is passed first through https://compresspng.com/ before being converted to base64 using https://www.base64-image.de/
|
||||||
|
- The spinner is based on https://github.com/jlong/css-spinners
|
||||||
|
- Minifiying of index.htm is done using the command line version of https://kangax.github.io/html-minifier/
|
||||||
|
- Idea of embedding webpage in code borrowed from https://github.com/me-no-dev/ESPAsyncWebServer
|
||||||
|
|
||||||
|
|
@ -0,0 +1,148 @@
|
||||||
|
/*
|
||||||
|
HelloServerBearSSL - Simple HTTPS server example
|
||||||
|
|
||||||
|
This example demonstrates a basic WebServerSecure HTTPS server
|
||||||
|
that can serve "/" and "/inline" and generate detailed 404 (not found)
|
||||||
|
HTTP responses. Be sure to update the SSID and PASSWORD before running
|
||||||
|
to allow connection to your WiFi network.
|
||||||
|
|
||||||
|
Adapted by Earle F. Philhower, III, from the HelloServer.ino example.
|
||||||
|
This example is released into the public domain.
|
||||||
|
*/
|
||||||
|
#include <WiFi.h>
|
||||||
|
#include <WiFiClient.h>
|
||||||
|
#include <WebServerSecure.h>
|
||||||
|
#include <LEAmDNS.h>
|
||||||
|
|
||||||
|
#ifndef STASSID
|
||||||
|
#define STASSID "your-ssid"
|
||||||
|
#define STAPSK "your-password"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
const char* ssid = STASSID;
|
||||||
|
const char* password = STAPSK;
|
||||||
|
|
||||||
|
WebServerSecure server(443);
|
||||||
|
ServerSessions serverCache(5);
|
||||||
|
|
||||||
|
static const char serverCert[] PROGMEM = R"EOF(
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIDSzCCAjMCCQD2ahcfZAwXxDANBgkqhkiG9w0BAQsFADCBiTELMAkGA1UEBhMC
|
||||||
|
VVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDU9yYW5nZSBDb3VudHkx
|
||||||
|
EDAOBgNVBAoMB1ByaXZhZG8xGjAYBgNVBAMMEXNlcnZlci56bGFiZWwuY29tMR8w
|
||||||
|
HQYJKoZIhvcNAQkBFhBlYXJsZUB6bGFiZWwuY29tMB4XDTE4MDMwNjA1NDg0NFoX
|
||||||
|
DTE5MDMwNjA1NDg0NFowRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3Rh
|
||||||
|
dGUxITAfBgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDCCASIwDQYJKoZI
|
||||||
|
hvcNAQEBBQADggEPADCCAQoCggEBAPVKBwbZ+KDSl40YCDkP6y8Sv4iNGvEOZg8Y
|
||||||
|
X7sGvf/xZH7UiCBWPFIRpNmDSaZ3yjsmFqm6sLiYSGSdrBCFqdt9NTp2r7hga6Sj
|
||||||
|
oASSZY4B9pf+GblDy5m10KDx90BFKXdPMCLT+o76Nx9PpCvw13A848wHNG3bpBgI
|
||||||
|
t+w/vJCX3bkRn8yEYAU6GdMbYe7v446hX3kY5UmgeJFr9xz1kq6AzYrMt/UHhNzO
|
||||||
|
S+QckJaY0OGWvmTNspY3xCbbFtIDkCdBS8CZAw+itnofvnWWKQEXlt6otPh5njwy
|
||||||
|
+O1t/Q+Z7OMDYQaH02IQx3188/kW3FzOY32knER1uzjmRO+jhA8CAwEAATANBgkq
|
||||||
|
hkiG9w0BAQsFAAOCAQEAnDrROGRETB0woIcI1+acY1yRq4yAcH2/hdq2MoM+DCyM
|
||||||
|
E8CJaOznGR9ND0ImWpTZqomHOUkOBpvu7u315blQZcLbL1LfHJGRTCHVhvVrcyEb
|
||||||
|
fWTnRtAQdlirUm/obwXIitoz64VSbIVzcqqfg9C6ZREB9JbEX98/9Wp2gVY+31oC
|
||||||
|
JfUvYadSYxh3nblvA4OL+iEZiW8NE3hbW6WPXxvS7Euge0uWMPc4uEcnsE0ZVG3m
|
||||||
|
+TGimzSdeWDvGBRWZHXczC2zD4aoE5vrl+GD2i++c6yjL/otHfYyUpzUfbI2hMAA
|
||||||
|
5tAF1D5vAAwA8nfPysumlLsIjohJZo4lgnhB++AlOg==
|
||||||
|
-----END CERTIFICATE-----
|
||||||
|
)EOF";
|
||||||
|
|
||||||
|
static const char serverKey[] PROGMEM = R"EOF(
|
||||||
|
-----BEGIN RSA PRIVATE KEY-----
|
||||||
|
MIIEpQIBAAKCAQEA9UoHBtn4oNKXjRgIOQ/rLxK/iI0a8Q5mDxhfuwa9//FkftSI
|
||||||
|
IFY8UhGk2YNJpnfKOyYWqbqwuJhIZJ2sEIWp2301OnavuGBrpKOgBJJljgH2l/4Z
|
||||||
|
uUPLmbXQoPH3QEUpd08wItP6jvo3H0+kK/DXcDzjzAc0bdukGAi37D+8kJfduRGf
|
||||||
|
zIRgBToZ0xth7u/jjqFfeRjlSaB4kWv3HPWSroDNisy39QeE3M5L5ByQlpjQ4Za+
|
||||||
|
ZM2yljfEJtsW0gOQJ0FLwJkDD6K2eh++dZYpAReW3qi0+HmePDL47W39D5ns4wNh
|
||||||
|
BofTYhDHfXzz+RbcXM5jfaScRHW7OOZE76OEDwIDAQABAoIBAQDKov5NFbNFQNR8
|
||||||
|
djcM1O7Is6dRaqiwLeH4ZH1pZ3d9QnFwKanPdQ5eCj9yhfhJMrr5xEyCqT0nMn7T
|
||||||
|
yEIGYDXjontfsf8WxWkH2TjvrfWBrHOIOx4LJEvFzyLsYxiMmtZXvy6YByD+Dw2M
|
||||||
|
q2GH/24rRdI2klkozIOyazluTXU8yOsSGxHr/aOa9/sZISgLmaGOOuKI/3Zqjdhr
|
||||||
|
eHeSqoQFt3xXa8jw01YubQUDw/4cv9rk2ytTdAoQUimiKtgtjsggpP1LTq4xcuqN
|
||||||
|
d4jWhTcnorWpbD2cVLxrEbnSR3VuBCJEZv5axg5ZPxLEnlcId8vMtvTRb5nzzszn
|
||||||
|
geYUWDPhAoGBAPyKVNqqwQl44oIeiuRM2FYenMt4voVaz3ExJX2JysrG0jtCPv+Y
|
||||||
|
84R6Cv3nfITz3EZDWp5sW3OwoGr77lF7Tv9tD6BptEmgBeuca3SHIdhG2MR+tLyx
|
||||||
|
/tkIAarxQcTGsZaSqra3gXOJCMz9h2P5dxpdU+0yeMmOEnAqgQ8qtNBfAoGBAPim
|
||||||
|
RAtnrd0WSlCgqVGYFCvDh1kD5QTNbZc+1PcBHbVV45EmJ2fLXnlDeplIZJdYxmzu
|
||||||
|
DMOxZBYgfeLY9exje00eZJNSj/csjJQqiRftrbvYY7m5njX1kM5K8x4HlynQTDkg
|
||||||
|
rtKO0YZJxxmjRTbFGMegh1SLlFLRIMtehNhOgipRAoGBAPnEEpJGCS9GGLfaX0HW
|
||||||
|
YqwiEK8Il12q57mqgsq7ag7NPwWOymHesxHV5mMh/Dw+NyBi4xAGWRh9mtrUmeqK
|
||||||
|
iyICik773Gxo0RIqnPgd4jJWN3N3YWeynzulOIkJnSNx5BforOCTc3uCD2s2YB5X
|
||||||
|
jx1LKoNQxLeLRN8cmpIWicf/AoGBANjRSsZTKwV9WWIDJoHyxav/vPb+8WYFp8lZ
|
||||||
|
zaRxQbGM6nn4NiZI7OF62N3uhWB/1c7IqTK/bVHqFTuJCrCNcsgld3gLZ2QWYaMV
|
||||||
|
kCPgaj1BjHw4AmB0+EcajfKilcqtSroJ6MfMJ6IclVOizkjbByeTsE4lxDmPCDSt
|
||||||
|
/9MKanBxAoGAY9xo741Pn9WUxDyRplww606ccdNf/ksHWNc/Y2B5SPwxxSnIq8nO
|
||||||
|
j01SmsCUYVFAgZVOTiiycakjYLzxlc6p8BxSVqy6LlJqn95N8OXoQ+bkwUux/ekg
|
||||||
|
gz5JWYhbD6c38khSzJb0pNXCo3EuYAVa36kDM96k1BtWuhRS10Q1VXk=
|
||||||
|
-----END RSA PRIVATE KEY-----
|
||||||
|
)EOF";
|
||||||
|
|
||||||
|
|
||||||
|
const int led = LED_BUILTIN;
|
||||||
|
|
||||||
|
void handleRoot() {
|
||||||
|
digitalWrite(led, 1);
|
||||||
|
server.send(200, "text/plain", "Hello from the Pico W over HTTPS!");
|
||||||
|
digitalWrite(led, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void handleNotFound() {
|
||||||
|
digitalWrite(led, 1);
|
||||||
|
String message = "File Not Found\n\n";
|
||||||
|
message += "URI: ";
|
||||||
|
message += server.uri();
|
||||||
|
message += "\nMethod: ";
|
||||||
|
message += (server.method() == HTTP_GET) ? "GET" : "POST";
|
||||||
|
message += "\nArguments: ";
|
||||||
|
message += server.args();
|
||||||
|
message += "\n";
|
||||||
|
for (uint8_t i = 0; i < server.args(); i++) { message += " " + server.argName(i) + ": " + server.arg(i) + "\n"; }
|
||||||
|
server.send(404, "text/plain", message);
|
||||||
|
digitalWrite(led, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void setup(void) {
|
||||||
|
pinMode(led, OUTPUT);
|
||||||
|
digitalWrite(led, 0);
|
||||||
|
Serial.begin(115200);
|
||||||
|
WiFi.begin(ssid, password);
|
||||||
|
Serial.println("");
|
||||||
|
|
||||||
|
// Wait for connection
|
||||||
|
while (WiFi.status() != WL_CONNECTED) {
|
||||||
|
delay(500);
|
||||||
|
Serial.print(".");
|
||||||
|
}
|
||||||
|
|
||||||
|
configTime(3 * 3600, 0, "pool.ntp.org", "time.nist.gov");
|
||||||
|
|
||||||
|
Serial.println("");
|
||||||
|
Serial.print("Connected to ");
|
||||||
|
Serial.println(ssid);
|
||||||
|
Serial.print("IP address: ");
|
||||||
|
Serial.println(WiFi.localIP());
|
||||||
|
|
||||||
|
if (MDNS.begin("picow")) { Serial.println("MDNS responder started"); }
|
||||||
|
|
||||||
|
server.getServer().setRSACert(new BearSSL::X509List(serverCert), new BearSSL::PrivateKey(serverKey));
|
||||||
|
|
||||||
|
// Cache SSL sessions to accelerate the TLS handshake.
|
||||||
|
server.getServer().setCache(&serverCache);
|
||||||
|
|
||||||
|
server.on("/", handleRoot);
|
||||||
|
|
||||||
|
server.on("/inline", []() {
|
||||||
|
server.send(200, "text/plain", "this works as well");
|
||||||
|
});
|
||||||
|
|
||||||
|
server.onNotFound(handleNotFound);
|
||||||
|
|
||||||
|
server.begin();
|
||||||
|
Serial.println("HTTPS server started");
|
||||||
|
}
|
||||||
|
|
||||||
|
void loop(void) {
|
||||||
|
server.handleClient();
|
||||||
|
MDNS.update();
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,64 @@
|
||||||
|
/*
|
||||||
|
HTTP Advanced Authentication example
|
||||||
|
Created Mar 16, 2017 by Ahmed El-Sharnoby.
|
||||||
|
This example code is in the public domain.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <WiFi.h>
|
||||||
|
#include <LEAmDNS.h>
|
||||||
|
#include <ArduinoOTA.h>
|
||||||
|
#include <WebServer.h>
|
||||||
|
|
||||||
|
#ifndef STASSID
|
||||||
|
#define STASSID "your-ssid"
|
||||||
|
#define STAPSK "your-password"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
const char* ssid = STASSID;
|
||||||
|
const char* password = STAPSK;
|
||||||
|
|
||||||
|
WebServer server(80);
|
||||||
|
|
||||||
|
const char* www_username = "admin";
|
||||||
|
const char* www_password = "picow";
|
||||||
|
// allows you to set the realm of authentication Default:"Login Required"
|
||||||
|
const char* www_realm = "Custom Auth Realm";
|
||||||
|
// the Content of the HTML response in case of Unautherized Access Default:empty
|
||||||
|
String authFailResponse = "Authentication Failed";
|
||||||
|
|
||||||
|
void setup() {
|
||||||
|
Serial.begin(115200);
|
||||||
|
WiFi.mode(WIFI_STA);
|
||||||
|
WiFi.begin(ssid, password);
|
||||||
|
if (WiFi.waitForConnectResult() != WL_CONNECTED) {
|
||||||
|
Serial.println("WiFi Connect Failed! Rebooting...");
|
||||||
|
delay(1000);
|
||||||
|
rp2040.restart();
|
||||||
|
}
|
||||||
|
ArduinoOTA.begin();
|
||||||
|
|
||||||
|
server.on("/", []() {
|
||||||
|
if (!server.authenticate(www_username, www_password))
|
||||||
|
// Basic Auth Method with Custom realm and Failure Response
|
||||||
|
// return server.requestAuthentication(BASIC_AUTH, www_realm, authFailResponse);
|
||||||
|
// Digest Auth Method with realm="Login Required" and empty Failure Response
|
||||||
|
// return server.requestAuthentication(DIGEST_AUTH);
|
||||||
|
// Digest Auth Method with Custom realm and empty Failure Response
|
||||||
|
// return server.requestAuthentication(DIGEST_AUTH, www_realm);
|
||||||
|
// Digest Auth Method with Custom realm and Failure Response
|
||||||
|
{
|
||||||
|
return server.requestAuthentication(DIGEST_AUTH, www_realm, authFailResponse);
|
||||||
|
}
|
||||||
|
server.send(200, "text/plain", "Login OK");
|
||||||
|
});
|
||||||
|
server.begin();
|
||||||
|
|
||||||
|
Serial.print("Open http://");
|
||||||
|
Serial.print(WiFi.localIP());
|
||||||
|
Serial.println("/ in your browser to see it working");
|
||||||
|
}
|
||||||
|
|
||||||
|
void loop() {
|
||||||
|
ArduinoOTA.handle();
|
||||||
|
server.handleClient();
|
||||||
|
}
|
||||||
46
libraries/WebServer/examples/HttpBasicAuth/HttpBasicAuth.ino
Normal file
46
libraries/WebServer/examples/HttpBasicAuth/HttpBasicAuth.ino
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
#include <WiFi.h>
|
||||||
|
#include <LEAmDNS.h>
|
||||||
|
#include <ArduinoOTA.h>
|
||||||
|
#include <WebServer.h>
|
||||||
|
|
||||||
|
#ifndef STASSID
|
||||||
|
#define STASSID "your-ssid"
|
||||||
|
#define STAPSK "your-password"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
const char* ssid = STASSID;
|
||||||
|
const char* password = STAPSK;
|
||||||
|
|
||||||
|
WebServer server(80);
|
||||||
|
|
||||||
|
const char* www_username = "admin";
|
||||||
|
const char* www_password = "picow";
|
||||||
|
|
||||||
|
void setup() {
|
||||||
|
Serial.begin(115200);
|
||||||
|
WiFi.mode(WIFI_STA);
|
||||||
|
WiFi.begin(ssid, password);
|
||||||
|
if (WiFi.waitForConnectResult() != WL_CONNECTED) {
|
||||||
|
Serial.println("WiFi Connect Failed! Rebooting...");
|
||||||
|
delay(1000);
|
||||||
|
rp2040.restart();
|
||||||
|
}
|
||||||
|
ArduinoOTA.begin();
|
||||||
|
|
||||||
|
server.on("/", []() {
|
||||||
|
if (!server.authenticate(www_username, www_password)) {
|
||||||
|
return server.requestAuthentication();
|
||||||
|
}
|
||||||
|
server.send(200, "text/plain", "Login OK");
|
||||||
|
});
|
||||||
|
server.begin();
|
||||||
|
|
||||||
|
Serial.print("Open http://");
|
||||||
|
Serial.print(WiFi.localIP());
|
||||||
|
Serial.println("/ in your browser to see it working");
|
||||||
|
}
|
||||||
|
|
||||||
|
void loop() {
|
||||||
|
ArduinoOTA.handle();
|
||||||
|
server.handleClient();
|
||||||
|
}
|
||||||
126
libraries/WebServer/examples/PostServer/PostServer.ino
Normal file
126
libraries/WebServer/examples/PostServer/PostServer.ino
Normal file
|
|
@ -0,0 +1,126 @@
|
||||||
|
#include <WiFi.h>
|
||||||
|
#include <WiFiClient.h>
|
||||||
|
#include <WebServer.h>
|
||||||
|
#include <LEAmDNS.h>
|
||||||
|
|
||||||
|
#ifndef STASSID
|
||||||
|
#define STASSID "your-ssid"
|
||||||
|
#define STAPSK "your-password"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
const char* ssid = STASSID;
|
||||||
|
const char* password = STAPSK;
|
||||||
|
|
||||||
|
WebServer server(80);
|
||||||
|
|
||||||
|
const int led = LED_BUILTIN;
|
||||||
|
|
||||||
|
const String postForms = "<html>\
|
||||||
|
<head>\
|
||||||
|
<title>Pico-W Web Server POST handling</title>\
|
||||||
|
<style>\
|
||||||
|
body { background-color: #cccccc; font-family: Arial, Helvetica, Sans-Serif; Color: #000088; }\
|
||||||
|
</style>\
|
||||||
|
</head>\
|
||||||
|
<body>\
|
||||||
|
<h1>POST plain text to /postplain/</h1><br>\
|
||||||
|
<form method=\"post\" enctype=\"text/plain\" action=\"/postplain/\">\
|
||||||
|
<input type=\"text\" name=\'{\"hello\": \"world\", \"trash\": \"\' value=\'\"}\'><br>\
|
||||||
|
<input type=\"submit\" value=\"Submit\">\
|
||||||
|
</form>\
|
||||||
|
<h1>POST form data to /postform/</h1><br>\
|
||||||
|
<form method=\"post\" enctype=\"application/x-www-form-urlencoded\" action=\"/postform/\">\
|
||||||
|
<input type=\"text\" name=\"hello\" value=\"world\"><br>\
|
||||||
|
<input type=\"submit\" value=\"Submit\">\
|
||||||
|
</form>\
|
||||||
|
</body>\
|
||||||
|
</html>";
|
||||||
|
|
||||||
|
void handleRoot() {
|
||||||
|
digitalWrite(led, 1);
|
||||||
|
server.send(200, "text/html", postForms);
|
||||||
|
digitalWrite(led, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void handlePlain() {
|
||||||
|
if (server.method() != HTTP_POST) {
|
||||||
|
digitalWrite(led, 1);
|
||||||
|
server.send(405, "text/plain", "Method Not Allowed");
|
||||||
|
digitalWrite(led, 0);
|
||||||
|
} else {
|
||||||
|
digitalWrite(led, 1);
|
||||||
|
server.send(200, "text/plain", "POST body was:\n" + server.arg("plain"));
|
||||||
|
digitalWrite(led, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void handleForm() {
|
||||||
|
if (server.method() != HTTP_POST) {
|
||||||
|
digitalWrite(led, 1);
|
||||||
|
server.send(405, "text/plain", "Method Not Allowed");
|
||||||
|
digitalWrite(led, 0);
|
||||||
|
} else {
|
||||||
|
digitalWrite(led, 1);
|
||||||
|
String message = "POST form was:\n";
|
||||||
|
for (uint8_t i = 0; i < server.args(); i++) {
|
||||||
|
message += " " + server.argName(i) + ": " + server.arg(i) + "\n";
|
||||||
|
}
|
||||||
|
server.send(200, "text/plain", message);
|
||||||
|
digitalWrite(led, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void handleNotFound() {
|
||||||
|
digitalWrite(led, 1);
|
||||||
|
String message = "File Not Found\n\n";
|
||||||
|
message += "URI: ";
|
||||||
|
message += server.uri();
|
||||||
|
message += "\nMethod: ";
|
||||||
|
message += (server.method() == HTTP_GET) ? "GET" : "POST";
|
||||||
|
message += "\nArguments: ";
|
||||||
|
message += server.args();
|
||||||
|
message += "\n";
|
||||||
|
for (uint8_t i = 0; i < server.args(); i++) {
|
||||||
|
message += " " + server.argName(i) + ": " + server.arg(i) + "\n";
|
||||||
|
}
|
||||||
|
server.send(404, "text/plain", message);
|
||||||
|
digitalWrite(led, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void setup(void) {
|
||||||
|
pinMode(led, OUTPUT);
|
||||||
|
digitalWrite(led, 0);
|
||||||
|
Serial.begin(115200);
|
||||||
|
WiFi.begin(ssid, password);
|
||||||
|
Serial.println("");
|
||||||
|
|
||||||
|
// Wait for connection
|
||||||
|
while (WiFi.status() != WL_CONNECTED) {
|
||||||
|
delay(500);
|
||||||
|
Serial.print(".");
|
||||||
|
}
|
||||||
|
Serial.println("");
|
||||||
|
Serial.print("Connected to ");
|
||||||
|
Serial.println(ssid);
|
||||||
|
Serial.print("IP address: ");
|
||||||
|
Serial.println(WiFi.localIP());
|
||||||
|
|
||||||
|
if (MDNS.begin("picow")) {
|
||||||
|
Serial.println("MDNS responder started");
|
||||||
|
}
|
||||||
|
|
||||||
|
server.on("/", handleRoot);
|
||||||
|
|
||||||
|
server.on("/postplain/", handlePlain);
|
||||||
|
|
||||||
|
server.on("/postform/", handleForm);
|
||||||
|
|
||||||
|
server.onNotFound(handleNotFound);
|
||||||
|
|
||||||
|
server.begin();
|
||||||
|
Serial.println("HTTP server started");
|
||||||
|
}
|
||||||
|
|
||||||
|
void loop(void) {
|
||||||
|
server.handleClient();
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,134 @@
|
||||||
|
#include <WiFi.h>
|
||||||
|
#include <WiFiClient.h>
|
||||||
|
#include <WebServer.h>
|
||||||
|
|
||||||
|
#ifndef STASSID
|
||||||
|
#define STASSID "your-ssid"
|
||||||
|
#define STAPSK "your-password"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
const char* ssid = STASSID;
|
||||||
|
const char* password = STAPSK;
|
||||||
|
|
||||||
|
WebServer server(80);
|
||||||
|
|
||||||
|
// Check if header is present and correct
|
||||||
|
bool is_authenticated() {
|
||||||
|
Serial.println("Enter is_authenticated");
|
||||||
|
if (server.hasHeader("Cookie")) {
|
||||||
|
Serial.print("Found cookie: ");
|
||||||
|
String cookie = server.header("Cookie");
|
||||||
|
Serial.println(cookie);
|
||||||
|
if (cookie.indexOf("PICOSESSIONID=1") != -1) {
|
||||||
|
Serial.println("Authentication Successful");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Serial.println("Authentication Failed");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// login page, also called for disconnect
|
||||||
|
void handleLogin() {
|
||||||
|
String msg;
|
||||||
|
if (server.hasHeader("Cookie")) {
|
||||||
|
Serial.print("Found cookie: ");
|
||||||
|
String cookie = server.header("Cookie");
|
||||||
|
Serial.println(cookie);
|
||||||
|
}
|
||||||
|
if (server.hasArg("DISCONNECT")) {
|
||||||
|
Serial.println("Disconnection");
|
||||||
|
server.sendHeader("Location", "/login");
|
||||||
|
server.sendHeader("Cache-Control", "no-cache");
|
||||||
|
server.sendHeader("Set-Cookie", "PICOSESSIONID=0");
|
||||||
|
server.send(301);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (server.hasArg("USERNAME") && server.hasArg("PASSWORD")) {
|
||||||
|
if (server.arg("USERNAME") == "admin" && server.arg("PASSWORD") == "admin") {
|
||||||
|
server.sendHeader("Location", "/");
|
||||||
|
server.sendHeader("Cache-Control", "no-cache");
|
||||||
|
server.sendHeader("Set-Cookie", "PICOSESSIONID=1");
|
||||||
|
server.send(301);
|
||||||
|
Serial.println("Log in Successful");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
msg = "Wrong username/password! try again.";
|
||||||
|
Serial.println("Log in Failed");
|
||||||
|
}
|
||||||
|
String content = "<html><body><form action='/login' method='POST'>To log in, please use : admin/admin<br>";
|
||||||
|
content += "User:<input type='text' name='USERNAME' placeholder='user name'><br>";
|
||||||
|
content += "Password:<input type='password' name='PASSWORD' placeholder='password'><br>";
|
||||||
|
content += "<input type='submit' name='SUBMIT' value='Submit'></form>" + msg + "<br>";
|
||||||
|
content += "You also can go <a href='/inline'>here</a></body></html>";
|
||||||
|
server.send(200, "text/html", content);
|
||||||
|
}
|
||||||
|
|
||||||
|
// root page can be accessed only if authentication is ok
|
||||||
|
void handleRoot() {
|
||||||
|
Serial.println("Enter handleRoot");
|
||||||
|
String header;
|
||||||
|
if (!is_authenticated()) {
|
||||||
|
server.sendHeader("Location", "/login");
|
||||||
|
server.sendHeader("Cache-Control", "no-cache");
|
||||||
|
server.send(301);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String content = "<html><body><H2>hello, you successfully connected to Pico W!</H2><br>";
|
||||||
|
if (server.hasHeader("User-Agent")) {
|
||||||
|
content += "the user agent used is : " + server.header("User-Agent") + "<br><br>";
|
||||||
|
}
|
||||||
|
content += "You can access this page until you <a href=\"/login?DISCONNECT=YES\">disconnect</a></body></html>";
|
||||||
|
server.send(200, "text/html", content);
|
||||||
|
}
|
||||||
|
|
||||||
|
// no need authentication
|
||||||
|
void handleNotFound() {
|
||||||
|
String message = "File Not Found\n\n";
|
||||||
|
message += "URI: ";
|
||||||
|
message += server.uri();
|
||||||
|
message += "\nMethod: ";
|
||||||
|
message += (server.method() == HTTP_GET) ? "GET" : "POST";
|
||||||
|
message += "\nArguments: ";
|
||||||
|
message += server.args();
|
||||||
|
message += "\n";
|
||||||
|
for (uint8_t i = 0; i < server.args(); i++) {
|
||||||
|
message += " " + server.argName(i) + ": " + server.arg(i) + "\n";
|
||||||
|
}
|
||||||
|
server.send(404, "text/plain", message);
|
||||||
|
}
|
||||||
|
|
||||||
|
void setup(void) {
|
||||||
|
Serial.begin(115200);
|
||||||
|
WiFi.mode(WIFI_STA);
|
||||||
|
WiFi.begin(ssid, password);
|
||||||
|
Serial.println("");
|
||||||
|
|
||||||
|
// Wait for connection
|
||||||
|
while (WiFi.status() != WL_CONNECTED) {
|
||||||
|
delay(500);
|
||||||
|
Serial.print(".");
|
||||||
|
}
|
||||||
|
Serial.println("");
|
||||||
|
Serial.print("Connected to ");
|
||||||
|
Serial.println(ssid);
|
||||||
|
Serial.print("IP address: ");
|
||||||
|
Serial.println(WiFi.localIP());
|
||||||
|
|
||||||
|
|
||||||
|
server.on("/", handleRoot);
|
||||||
|
server.on("/login", handleLogin);
|
||||||
|
server.on("/inline", []() {
|
||||||
|
server.send(200, "text/plain", "this works without need of authentication");
|
||||||
|
});
|
||||||
|
|
||||||
|
server.onNotFound(handleNotFound);
|
||||||
|
// ask server to track these headers
|
||||||
|
server.collectHeaders("User-Agent", "Cookie");
|
||||||
|
server.begin();
|
||||||
|
Serial.println("HTTP server started");
|
||||||
|
}
|
||||||
|
|
||||||
|
void loop(void) {
|
||||||
|
server.handleClient();
|
||||||
|
}
|
||||||
77
libraries/WebServer/examples/WebUpdate/WebUpdate.ino
Normal file
77
libraries/WebServer/examples/WebUpdate/WebUpdate.ino
Normal file
|
|
@ -0,0 +1,77 @@
|
||||||
|
/*
|
||||||
|
To upload through terminal you can use: curl -F "image=@firmware.bin" picow-webupdate.local/update
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <WiFi.h>
|
||||||
|
#include <WiFiClient.h>
|
||||||
|
#include <WebServer.h>
|
||||||
|
#include <LEAmDNS.h>
|
||||||
|
#include <LittleFS.h>
|
||||||
|
|
||||||
|
#ifndef STASSID
|
||||||
|
#define STASSID "your-ssid"
|
||||||
|
#define STAPSK "your-password"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
const char* host = "picow-webupdate";
|
||||||
|
const char* ssid = STASSID;
|
||||||
|
const char* password = STAPSK;
|
||||||
|
|
||||||
|
WebServer server(80);
|
||||||
|
const char* serverIndex = "<form method='POST' action='/update' enctype='multipart/form-data'><input type='file' name='update'><input type='submit' value='Update'></form>";
|
||||||
|
|
||||||
|
void setup(void) {
|
||||||
|
Serial.begin(115200);
|
||||||
|
Serial.println();
|
||||||
|
Serial.println("Booting Sketch...");
|
||||||
|
WiFi.mode(WIFI_STA);
|
||||||
|
WiFi.begin(ssid, password);
|
||||||
|
if (WiFi.waitForConnectResult() == WL_CONNECTED) {
|
||||||
|
MDNS.begin(host);
|
||||||
|
server.on("/", HTTP_GET, []() {
|
||||||
|
server.sendHeader("Connection", "close");
|
||||||
|
server.send(200, "text/html", serverIndex);
|
||||||
|
});
|
||||||
|
server.on(
|
||||||
|
"/update", HTTP_POST, []() {
|
||||||
|
server.sendHeader("Connection", "close");
|
||||||
|
server.send(200, "text/plain", (Update.hasError()) ? "FAIL" : "OK");
|
||||||
|
rp2040.restart();
|
||||||
|
},
|
||||||
|
[]() {
|
||||||
|
HTTPUpload& upload = server.upload();
|
||||||
|
if (upload.status == UPLOAD_FILE_START) {
|
||||||
|
WiFiUDP::stopAll();
|
||||||
|
Serial.printf("Update: %s\n", upload.filename.c_str());
|
||||||
|
FSInfo64 i;
|
||||||
|
LittleFS.begin();
|
||||||
|
LittleFS.info64(i);
|
||||||
|
uint32_t maxSketchSpace = i.totalBytes - i.usedBytes;
|
||||||
|
if (!Update.begin(maxSketchSpace)) { // start with max available size
|
||||||
|
Update.printError(Serial);
|
||||||
|
}
|
||||||
|
} else if (upload.status == UPLOAD_FILE_WRITE) {
|
||||||
|
if (Update.write(upload.buf, upload.currentSize) != upload.currentSize) {
|
||||||
|
Update.printError(Serial);
|
||||||
|
}
|
||||||
|
} else if (upload.status == UPLOAD_FILE_END) {
|
||||||
|
if (Update.end(true)) { // true to set the size to the current progress
|
||||||
|
Serial.printf("Update Success: %u\nRebooting...\n", upload.totalSize);
|
||||||
|
} else {
|
||||||
|
Update.printError(Serial);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
server.begin();
|
||||||
|
MDNS.addService("http", "tcp", 80);
|
||||||
|
|
||||||
|
Serial.printf("Ready! Open http://%s.local in your browser\n", host);
|
||||||
|
} else {
|
||||||
|
Serial.println("WiFi Failed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void loop(void) {
|
||||||
|
server.handleClient();
|
||||||
|
MDNS.update();
|
||||||
|
}
|
||||||
39
libraries/WebServer/keywords.txt
Normal file
39
libraries/WebServer/keywords.txt
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
#######################################
|
||||||
|
# Syntax Coloring Map For Ultrasound
|
||||||
|
#######################################
|
||||||
|
|
||||||
|
#######################################
|
||||||
|
# Datatypes (KEYWORD1)
|
||||||
|
#######################################
|
||||||
|
|
||||||
|
WebServer KEYWORD1
|
||||||
|
WebServerSecure KEYWORD1
|
||||||
|
HTTPServer KEYWORD1
|
||||||
|
HTTPMethod KEYWORD1
|
||||||
|
|
||||||
|
#######################################
|
||||||
|
# Methods and Functions (KEYWORD2)
|
||||||
|
#######################################
|
||||||
|
|
||||||
|
begin KEYWORD2
|
||||||
|
handleClient KEYWORD2
|
||||||
|
on KEYWORD2
|
||||||
|
addHandler KEYWORD2
|
||||||
|
uri KEYWORD2
|
||||||
|
method KEYWORD2
|
||||||
|
client KEYWORD2
|
||||||
|
send KEYWORD2
|
||||||
|
arg KEYWORD2
|
||||||
|
argName KEYWORD2
|
||||||
|
args KEYWORD2
|
||||||
|
hasArg KEYWORD2
|
||||||
|
onNotFound KEYWORD2
|
||||||
|
|
||||||
|
#######################################
|
||||||
|
# Constants (LITERAL1)
|
||||||
|
#######################################
|
||||||
|
|
||||||
|
HTTP_GET LITERAL1
|
||||||
|
HTTP_POST LITERAL1
|
||||||
|
HTTP_ANY LITERAL1
|
||||||
|
CONTENT_LENGTH_UNKNOWN LITERAL1
|
||||||
10
libraries/WebServer/library.properties
Normal file
10
libraries/WebServer/library.properties
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
name=WebServer
|
||||||
|
version=2.0.0
|
||||||
|
author=Ivan Grokhotkov
|
||||||
|
maintainer=Earle F. Philhower, III <earlephilhower@yahoo.com>
|
||||||
|
sentence=Simple web server library
|
||||||
|
paragraph=The library supports HTTP(S) GET and POST requests, provides argument parsing, handles one client at a time.
|
||||||
|
category=Communication
|
||||||
|
url=https://github.com/earlephilhower/arduino-pico
|
||||||
|
architectures=rp2040
|
||||||
|
dot_a_linkage=true
|
||||||
687
libraries/WebServer/src/HTTPServer.cpp
Normal file
687
libraries/WebServer/src/HTTPServer.cpp
Normal file
|
|
@ -0,0 +1,687 @@
|
||||||
|
/*
|
||||||
|
HTTPServer.cpp - Dead simple web-server.
|
||||||
|
Supports only one simultaneous client, knows how to handle GET and POST.
|
||||||
|
|
||||||
|
Copyright (c) 2014 Ivan Grokhotkov. All rights reserved.
|
||||||
|
|
||||||
|
This library is free software; you can redistribute it and/or
|
||||||
|
modify it under the terms of the GNU Lesser General Public
|
||||||
|
License as published by the Free Software Foundation; either
|
||||||
|
version 2.1 of the License, or (at your option) any later version.
|
||||||
|
|
||||||
|
This library 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
|
||||||
|
Lesser General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Lesser General Public
|
||||||
|
License along with this library; if not, write to the Free Software
|
||||||
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
Modified 8 May 2015 by Hristo Gochkov (proper post and file upload handling)
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <Arduino.h>
|
||||||
|
#include <libb64/cencode.h>
|
||||||
|
#include "WiFiServer.h"
|
||||||
|
#include "WiFiClient.h"
|
||||||
|
#include "HTTPServer.h"
|
||||||
|
#include "FS.h"
|
||||||
|
#include "detail/RequestHandlersImpl.h"
|
||||||
|
#include <MD5Builder.h>
|
||||||
|
|
||||||
|
#ifndef log_e
|
||||||
|
#define log_e(...)
|
||||||
|
#define log_w(...)
|
||||||
|
#define log_v(...)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static const char AUTHORIZATION_HEADER[] = "Authorization";
|
||||||
|
static const char qop_auth[] PROGMEM = "qop=auth";
|
||||||
|
static const char qop_auth_quoted[] PROGMEM = "qop=\"auth\"";
|
||||||
|
static const char WWW_Authenticate[] = "WWW-Authenticate";
|
||||||
|
static const char Content_Length[] = "Content-Length";
|
||||||
|
|
||||||
|
HTTPServer::HTTPServer()
|
||||||
|
: _corsEnabled(false)
|
||||||
|
, _currentMethod(HTTP_ANY)
|
||||||
|
, _currentVersion(0)
|
||||||
|
, _currentStatus(HC_NONE)
|
||||||
|
, _statusChange(0)
|
||||||
|
, _nullDelay(true)
|
||||||
|
, _currentHandler(nullptr)
|
||||||
|
, _firstHandler(nullptr)
|
||||||
|
, _lastHandler(nullptr)
|
||||||
|
, _currentArgCount(0)
|
||||||
|
, _currentArgs(nullptr)
|
||||||
|
, _postArgsLen(0)
|
||||||
|
, _postArgs(nullptr)
|
||||||
|
, _headerKeysCount(0)
|
||||||
|
, _currentHeaders(nullptr)
|
||||||
|
, _contentLength(0)
|
||||||
|
, _clientContentLength(0)
|
||||||
|
, _chunked(false) {
|
||||||
|
log_v("HTTPServer::HTTPServer()");
|
||||||
|
}
|
||||||
|
|
||||||
|
HTTPServer::~HTTPServer() {
|
||||||
|
if (_currentHeaders) {
|
||||||
|
delete[]_currentHeaders;
|
||||||
|
}
|
||||||
|
RequestHandler* handler = _firstHandler;
|
||||||
|
while (handler) {
|
||||||
|
RequestHandler* next = handler->next();
|
||||||
|
delete handler;
|
||||||
|
handler = next;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String HTTPServer::_extractParam(String& authReq, const String& param, const char delimit) {
|
||||||
|
int _begin = authReq.indexOf(param);
|
||||||
|
if (_begin == -1) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return authReq.substring(_begin + param.length(), authReq.indexOf(delimit, _begin + param.length()));
|
||||||
|
}
|
||||||
|
|
||||||
|
static String md5str(String &in) {
|
||||||
|
char out[33] = {0};
|
||||||
|
MD5Builder _ctx;
|
||||||
|
uint8_t i;
|
||||||
|
uint8_t * _buf = (uint8_t*)malloc(16);
|
||||||
|
if (_buf == NULL) {
|
||||||
|
return String(out);
|
||||||
|
}
|
||||||
|
memset(_buf, 0x00, 16);
|
||||||
|
_ctx.begin();
|
||||||
|
_ctx.add((const uint8_t *)in.c_str(), in.length());
|
||||||
|
_ctx.calculate();
|
||||||
|
_ctx.getBytes((uint8_t *)_buf);
|
||||||
|
for (i = 0; i < 16; i++) {
|
||||||
|
sprintf(out + (i * 2), "%02x", _buf[i]);
|
||||||
|
}
|
||||||
|
out[32] = 0;
|
||||||
|
free(_buf);
|
||||||
|
return String(out);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool HTTPServer::authenticate(const char * username, const char * password) {
|
||||||
|
if (hasHeader(FPSTR(AUTHORIZATION_HEADER))) {
|
||||||
|
String authReq = header(FPSTR(AUTHORIZATION_HEADER));
|
||||||
|
if (authReq.startsWith(F("Basic"))) {
|
||||||
|
authReq = authReq.substring(6);
|
||||||
|
authReq.trim();
|
||||||
|
char toencodeLen = strlen(username) + strlen(password) + 1;
|
||||||
|
char *toencode = new char[toencodeLen + 1];
|
||||||
|
if (toencode == NULL) {
|
||||||
|
authReq = "";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
char *encoded = new char[base64_encode_expected_len(toencodeLen) + 1];
|
||||||
|
if (encoded == NULL) {
|
||||||
|
authReq = "";
|
||||||
|
delete[] toencode;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
sprintf(toencode, "%s:%s", username, password);
|
||||||
|
if (base64_encode_chars(toencode, toencodeLen, encoded) > 0 && authReq == encoded) {
|
||||||
|
authReq = "";
|
||||||
|
delete[] toencode;
|
||||||
|
delete[] encoded;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
delete[] toencode;
|
||||||
|
delete[] encoded;
|
||||||
|
} else if (authReq.startsWith(F("Digest"))) {
|
||||||
|
authReq = authReq.substring(7);
|
||||||
|
log_v("%s", authReq.c_str());
|
||||||
|
String _username = _extractParam(authReq, F("username=\""), '\"');
|
||||||
|
if (!_username.length() || _username != String(username)) {
|
||||||
|
authReq = "";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// extracting required parameters for RFC 2069 simpler Digest
|
||||||
|
String _realm = _extractParam(authReq, F("realm=\""), '\"');
|
||||||
|
String _nonce = _extractParam(authReq, F("nonce=\""), '\"');
|
||||||
|
String _uri = _extractParam(authReq, F("uri=\""), '\"');
|
||||||
|
String _response = _extractParam(authReq, F("response=\""), '\"');
|
||||||
|
String _opaque = _extractParam(authReq, F("opaque=\""), '\"');
|
||||||
|
|
||||||
|
if ((!_realm.length()) || (!_nonce.length()) || (!_uri.length()) || (!_response.length()) || (!_opaque.length())) {
|
||||||
|
authReq = "";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if ((_opaque != _sopaque) || (_nonce != _snonce) || (_realm != _srealm)) {
|
||||||
|
authReq = "";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// parameters for the RFC 2617 newer Digest
|
||||||
|
String _nc, _cnonce;
|
||||||
|
if (authReq.indexOf(FPSTR(qop_auth)) != -1 || authReq.indexOf(FPSTR(qop_auth_quoted)) != -1) {
|
||||||
|
_nc = _extractParam(authReq, F("nc="), ',');
|
||||||
|
_cnonce = _extractParam(authReq, F("cnonce=\""), '\"');
|
||||||
|
}
|
||||||
|
String _H1 = md5str(String(username) + ':' + _realm + ':' + String(password));
|
||||||
|
log_v("Hash of user:realm:pass=%s", _H1.c_str());
|
||||||
|
String _H2 = "";
|
||||||
|
if (_currentMethod == HTTP_GET) {
|
||||||
|
_H2 = md5str(String(F("GET:")) + _uri);
|
||||||
|
} else if (_currentMethod == HTTP_POST) {
|
||||||
|
_H2 = md5str(String(F("POST:")) + _uri);
|
||||||
|
} else if (_currentMethod == HTTP_PUT) {
|
||||||
|
_H2 = md5str(String(F("PUT:")) + _uri);
|
||||||
|
} else if (_currentMethod == HTTP_DELETE) {
|
||||||
|
_H2 = md5str(String(F("DELETE:")) + _uri);
|
||||||
|
} else {
|
||||||
|
_H2 = md5str(String(F("GET:")) + _uri);
|
||||||
|
}
|
||||||
|
log_v("Hash of GET:uri=%s", _H2.c_str());
|
||||||
|
String _responsecheck = "";
|
||||||
|
if (authReq.indexOf(FPSTR(qop_auth)) != -1 || authReq.indexOf(FPSTR(qop_auth_quoted)) != -1) {
|
||||||
|
_responsecheck = md5str(_H1 + ':' + _nonce + ':' + _nc + ':' + _cnonce + F(":auth:") + _H2);
|
||||||
|
} else {
|
||||||
|
_responsecheck = md5str(_H1 + ':' + _nonce + ':' + _H2);
|
||||||
|
}
|
||||||
|
log_v("The Proper response=%s", _responsecheck.c_str());
|
||||||
|
if (_response == _responsecheck) {
|
||||||
|
authReq = "";
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
authReq = "";
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
String HTTPServer::_getRandomHexString() {
|
||||||
|
char buffer[33]; // buffer to hold 32 Hex Digit + /0
|
||||||
|
int i;
|
||||||
|
for (i = 0; i < 4; i++) {
|
||||||
|
sprintf(buffer + (i * 8), "%08lx", rp2040.hwrand32());
|
||||||
|
}
|
||||||
|
return String(buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
void HTTPServer::requestAuthentication(HTTPAuthMethod mode, const char* realm, const String& authFailMsg) {
|
||||||
|
if (realm == NULL) {
|
||||||
|
_srealm = String(F("Login Required"));
|
||||||
|
} else {
|
||||||
|
_srealm = String(realm);
|
||||||
|
}
|
||||||
|
if (mode == BASIC_AUTH) {
|
||||||
|
sendHeader(String(FPSTR(WWW_Authenticate)), String(F("Basic realm=\"")) + _srealm + String(F("\"")));
|
||||||
|
} else {
|
||||||
|
_snonce = _getRandomHexString();
|
||||||
|
_sopaque = _getRandomHexString();
|
||||||
|
sendHeader(String(FPSTR(WWW_Authenticate)), String(F("Digest realm=\"")) + _srealm + String(F("\", qop=\"auth\", nonce=\"")) + _snonce + String(F("\", opaque=\"")) + _sopaque + String(F("\"")));
|
||||||
|
}
|
||||||
|
using namespace mime;
|
||||||
|
send(401, String(FPSTR(mimeTable[html].mimeType)), authFailMsg);
|
||||||
|
}
|
||||||
|
|
||||||
|
void HTTPServer::on(const Uri &uri, HTTPServer::THandlerFunction handler) {
|
||||||
|
on(uri, HTTP_ANY, handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
void HTTPServer::on(const Uri &uri, HTTPMethod method, HTTPServer::THandlerFunction fn) {
|
||||||
|
on(uri, method, fn, _fileUploadHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
void HTTPServer::on(const Uri &uri, HTTPMethod method, HTTPServer::THandlerFunction fn, HTTPServer::THandlerFunction ufn) {
|
||||||
|
_addRequestHandler(new FunctionRequestHandler(fn, ufn, uri, method));
|
||||||
|
}
|
||||||
|
|
||||||
|
void HTTPServer::addHandler(RequestHandler* handler) {
|
||||||
|
_addRequestHandler(handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
void HTTPServer::_addRequestHandler(RequestHandler* handler) {
|
||||||
|
if (!_lastHandler) {
|
||||||
|
_firstHandler = handler;
|
||||||
|
_lastHandler = handler;
|
||||||
|
} else {
|
||||||
|
_lastHandler->next(handler);
|
||||||
|
_lastHandler = handler;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void HTTPServer::serveStatic(const char* uri, FS& fs, const char* path, const char* cache_header) {
|
||||||
|
_addRequestHandler(new StaticRequestHandler(fs, path, uri, cache_header));
|
||||||
|
}
|
||||||
|
|
||||||
|
void HTTPServer::httpHandleClient() {
|
||||||
|
bool keepCurrentClient = false;
|
||||||
|
bool callYield = false;
|
||||||
|
|
||||||
|
if (_currentClient->connected()) {
|
||||||
|
switch (_currentStatus) {
|
||||||
|
case HC_NONE:
|
||||||
|
// No-op to avoid C++ compiler warning
|
||||||
|
break;
|
||||||
|
case HC_WAIT_READ:
|
||||||
|
// Wait for data from client to become available
|
||||||
|
if (_currentClient->available()) {
|
||||||
|
if (_parseRequest(_currentClient)) {
|
||||||
|
// because HTTP_MAX_SEND_WAIT is expressed in milliseconds,
|
||||||
|
// it must be divided by 1000
|
||||||
|
_currentClient->setTimeout(HTTP_MAX_SEND_WAIT / 1000);
|
||||||
|
_contentLength = CONTENT_LENGTH_NOT_SET;
|
||||||
|
_handleRequest();
|
||||||
|
|
||||||
|
// Fix for issue with Chrome based browsers: https://github.com/espressif/arduino-esp32/issues/3652
|
||||||
|
// if (_currentClient->connected()) {
|
||||||
|
// _currentStatus = HC_WAIT_CLOSE;
|
||||||
|
// _statusChange = millis();
|
||||||
|
// keepCurrentClient = true;
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
} else { // !_currentClient->available()
|
||||||
|
if (millis() - _statusChange <= HTTP_MAX_DATA_WAIT) {
|
||||||
|
keepCurrentClient = true;
|
||||||
|
}
|
||||||
|
callYield = true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case HC_WAIT_CLOSE:
|
||||||
|
// Wait for client to close the connection
|
||||||
|
if (millis() - _statusChange <= HTTP_MAX_CLOSE_WAIT) {
|
||||||
|
keepCurrentClient = true;
|
||||||
|
callYield = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!keepCurrentClient) {
|
||||||
|
_currentClient = nullptr;
|
||||||
|
_currentStatus = HC_NONE;
|
||||||
|
_currentUpload.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (callYield) {
|
||||||
|
yield();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void HTTPServer::httpClose() {
|
||||||
|
_currentStatus = HC_NONE;
|
||||||
|
if (!_headerKeysCount) {
|
||||||
|
collectHeaders(0, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void HTTPServer::sendHeader(const String& name, const String& value, bool first) {
|
||||||
|
String headerLine = name;
|
||||||
|
headerLine += F(": ");
|
||||||
|
headerLine += value;
|
||||||
|
headerLine += "\r\n";
|
||||||
|
|
||||||
|
if (first) {
|
||||||
|
_responseHeaders = headerLine + _responseHeaders;
|
||||||
|
} else {
|
||||||
|
_responseHeaders += headerLine;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void HTTPServer::setContentLength(const size_t contentLength) {
|
||||||
|
_contentLength = contentLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
void HTTPServer::enableDelay(boolean value) {
|
||||||
|
_nullDelay = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
void HTTPServer::enableCORS(boolean value) {
|
||||||
|
_corsEnabled = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
void HTTPServer::enableCrossOrigin(boolean value) {
|
||||||
|
enableCORS(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void HTTPServer::_prepareHeader(String& response, int code, const char* content_type, size_t contentLength) {
|
||||||
|
response = String(F("HTTP/1.")) + String(_currentVersion) + ' ';
|
||||||
|
response += String(code);
|
||||||
|
response += ' ';
|
||||||
|
response += _responseCodeToString(code);
|
||||||
|
response += "\r\n";
|
||||||
|
|
||||||
|
using namespace mime;
|
||||||
|
if (!content_type) {
|
||||||
|
content_type = mimeTable[html].mimeType;
|
||||||
|
}
|
||||||
|
|
||||||
|
sendHeader(String(F("Content-Type")), String(FPSTR(content_type)), true);
|
||||||
|
if (_contentLength == CONTENT_LENGTH_NOT_SET) {
|
||||||
|
sendHeader(String(FPSTR(Content_Length)), String(contentLength));
|
||||||
|
} else if (_contentLength != CONTENT_LENGTH_UNKNOWN) {
|
||||||
|
sendHeader(String(FPSTR(Content_Length)), String(_contentLength));
|
||||||
|
} else if (_contentLength == CONTENT_LENGTH_UNKNOWN && _currentVersion) { //HTTP/1.1 or above client
|
||||||
|
//let's do chunked
|
||||||
|
_chunked = true;
|
||||||
|
sendHeader(String(F("Accept-Ranges")), String(F("none")));
|
||||||
|
sendHeader(String(F("Transfer-Encoding")), String(F("chunked")));
|
||||||
|
}
|
||||||
|
if (_corsEnabled) {
|
||||||
|
sendHeader(String(FPSTR("Access-Control-Allow-Origin")), String("*"));
|
||||||
|
sendHeader(String(FPSTR("Access-Control-Allow-Methods")), String("*"));
|
||||||
|
sendHeader(String(FPSTR("Access-Control-Allow-Headers")), String("*"));
|
||||||
|
}
|
||||||
|
sendHeader(String(F("Connection")), String(F("close")));
|
||||||
|
|
||||||
|
response += _responseHeaders;
|
||||||
|
response += "\r\n";
|
||||||
|
_responseHeaders = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
void HTTPServer::send(int code, const char* content_type, const String& content) {
|
||||||
|
String header;
|
||||||
|
// Can we assume the following?
|
||||||
|
//if(code == 200 && content.length() == 0 && _contentLength == CONTENT_LENGTH_NOT_SET)
|
||||||
|
// _contentLength = CONTENT_LENGTH_UNKNOWN;
|
||||||
|
if (content.length() == 0) {
|
||||||
|
log_w("content length is zero");
|
||||||
|
}
|
||||||
|
_prepareHeader(header, code, content_type, content.length());
|
||||||
|
_currentClientWrite(header.c_str(), header.length());
|
||||||
|
if (content.length()) {
|
||||||
|
sendContent(content);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void HTTPServer::send(int code, char* content_type, const String& content) {
|
||||||
|
send(code, (const char*)content_type, content);
|
||||||
|
}
|
||||||
|
|
||||||
|
void HTTPServer::send(int code, const String& content_type, const String& content) {
|
||||||
|
send(code, (const char*)content_type.c_str(), content);
|
||||||
|
}
|
||||||
|
|
||||||
|
void HTTPServer::send(int code, const char* content_type, const char* content) {
|
||||||
|
const String passStr = (String)content;
|
||||||
|
if (strlen(content) != passStr.length()) {
|
||||||
|
log_e("String cast failed. Use send_P for long arrays");
|
||||||
|
}
|
||||||
|
send(code, content_type, passStr);
|
||||||
|
}
|
||||||
|
|
||||||
|
void HTTPServer::send(int code, const char* content_type, const char* content, size_t contentLength) {
|
||||||
|
String header;
|
||||||
|
_prepareHeader(header, code, content_type, contentLength);
|
||||||
|
_currentClientWrite(header.c_str(), header.length());
|
||||||
|
if (contentLength) {
|
||||||
|
sendContent(content, contentLength);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void HTTPServer::send_P(int code, PGM_P content_type, PGM_P content) {
|
||||||
|
size_t contentLength = 0;
|
||||||
|
|
||||||
|
if (content != NULL) {
|
||||||
|
contentLength = strlen_P(content);
|
||||||
|
}
|
||||||
|
|
||||||
|
String header;
|
||||||
|
char type[64];
|
||||||
|
memccpy_P((void*)type, (PGM_VOID_P)content_type, 0, sizeof(type));
|
||||||
|
_prepareHeader(header, code, (const char*)type, contentLength);
|
||||||
|
_currentClientWrite(header.c_str(), header.length());
|
||||||
|
sendContent_P(content);
|
||||||
|
}
|
||||||
|
|
||||||
|
void HTTPServer::send_P(int code, PGM_P content_type, PGM_P content, size_t contentLength) {
|
||||||
|
String header;
|
||||||
|
char type[64];
|
||||||
|
memccpy_P((void*)type, (PGM_VOID_P)content_type, 0, sizeof(type));
|
||||||
|
_prepareHeader(header, code, (const char*)type, contentLength);
|
||||||
|
sendContent(header);
|
||||||
|
sendContent_P(content, contentLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
void HTTPServer::sendContent(const String& content) {
|
||||||
|
sendContent(content.c_str(), content.length());
|
||||||
|
}
|
||||||
|
|
||||||
|
void HTTPServer::sendContent(const char* content, size_t contentLength) {
|
||||||
|
const char * footer = "\r\n";
|
||||||
|
if (_chunked) {
|
||||||
|
char * chunkSize = (char *)malloc(11);
|
||||||
|
if (chunkSize) {
|
||||||
|
sprintf(chunkSize, "%x%s", contentLength, footer);
|
||||||
|
_currentClientWrite(chunkSize, strlen(chunkSize));
|
||||||
|
free(chunkSize);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_currentClientWrite(content, contentLength);
|
||||||
|
if (_chunked) {
|
||||||
|
_currentClient->write(footer, 2);
|
||||||
|
if (contentLength == 0) {
|
||||||
|
_chunked = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void HTTPServer::sendContent_P(PGM_P content) {
|
||||||
|
sendContent_P(content, strlen_P(content));
|
||||||
|
}
|
||||||
|
|
||||||
|
void HTTPServer::sendContent_P(PGM_P content, size_t size) {
|
||||||
|
const char * footer = "\r\n";
|
||||||
|
if (_chunked) {
|
||||||
|
char * chunkSize = (char *)malloc(11);
|
||||||
|
if (chunkSize) {
|
||||||
|
sprintf(chunkSize, "%x%s", size, footer);
|
||||||
|
_currentClientWrite(chunkSize, strlen(chunkSize));
|
||||||
|
free(chunkSize);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_currentClientWrite_P(content, size);
|
||||||
|
if (_chunked) {
|
||||||
|
_currentClient->write(footer, 2);
|
||||||
|
if (size == 0) {
|
||||||
|
_chunked = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void HTTPServer::_streamFileCore(const size_t fileSize, const String & fileName, const String & contentType, const int code) {
|
||||||
|
using namespace mime;
|
||||||
|
setContentLength(fileSize);
|
||||||
|
if (fileName.endsWith(String(FPSTR(mimeTable[gz].endsWith))) &&
|
||||||
|
contentType != String(FPSTR(mimeTable[gz].mimeType)) &&
|
||||||
|
contentType != String(FPSTR(mimeTable[none].mimeType))) {
|
||||||
|
sendHeader(F("Content-Encoding"), F("gzip"));
|
||||||
|
}
|
||||||
|
send(code, contentType, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
String HTTPServer::pathArg(unsigned int i) {
|
||||||
|
if (_currentHandler != nullptr) {
|
||||||
|
return _currentHandler->pathArg(i);
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
String HTTPServer::arg(String name) {
|
||||||
|
for (int j = 0; j < _postArgsLen; ++j) {
|
||||||
|
if (_postArgs[j].key == name) {
|
||||||
|
return _postArgs[j].value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (int i = 0; i < _currentArgCount; ++i) {
|
||||||
|
if (_currentArgs[i].key == name) {
|
||||||
|
return _currentArgs[i].value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
String HTTPServer::arg(int i) {
|
||||||
|
if (i < _currentArgCount) {
|
||||||
|
return _currentArgs[i].value;
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
String HTTPServer::argName(int i) {
|
||||||
|
if (i < _currentArgCount) {
|
||||||
|
return _currentArgs[i].key;
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
int HTTPServer::args() {
|
||||||
|
return _currentArgCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool HTTPServer::hasArg(String name) {
|
||||||
|
for (int j = 0; j < _postArgsLen; ++j) {
|
||||||
|
if (_postArgs[j].key == name) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (int i = 0; i < _currentArgCount; ++i) {
|
||||||
|
if (_currentArgs[i].key == name) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
String HTTPServer::header(String name) {
|
||||||
|
for (int i = 0; i < _headerKeysCount; ++i) {
|
||||||
|
if (_currentHeaders[i].key.equalsIgnoreCase(name)) {
|
||||||
|
return _currentHeaders[i].value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
void HTTPServer::collectHeaders(const char* headerKeys[], const size_t headerKeysCount) {
|
||||||
|
_headerKeysCount = headerKeysCount + 1;
|
||||||
|
if (_currentHeaders) {
|
||||||
|
delete[]_currentHeaders;
|
||||||
|
}
|
||||||
|
_currentHeaders = new RequestArgument[_headerKeysCount];
|
||||||
|
_currentHeaders[0].key = FPSTR(AUTHORIZATION_HEADER);
|
||||||
|
for (int i = 1; i < _headerKeysCount; i++) {
|
||||||
|
_currentHeaders[i].key = headerKeys[i - 1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String HTTPServer::header(int i) {
|
||||||
|
if (i < _headerKeysCount) {
|
||||||
|
return _currentHeaders[i].value;
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
String HTTPServer::headerName(int i) {
|
||||||
|
if (i < _headerKeysCount) {
|
||||||
|
return _currentHeaders[i].key;
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
int HTTPServer::headers() {
|
||||||
|
return _headerKeysCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool HTTPServer::hasHeader(String name) {
|
||||||
|
for (int i = 0; i < _headerKeysCount; ++i) {
|
||||||
|
if ((_currentHeaders[i].key.equalsIgnoreCase(name)) && (_currentHeaders[i].value.length() > 0)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
String HTTPServer::hostHeader() {
|
||||||
|
return _hostHeader;
|
||||||
|
}
|
||||||
|
|
||||||
|
void HTTPServer::onFileUpload(THandlerFunction fn) {
|
||||||
|
_fileUploadHandler = fn;
|
||||||
|
}
|
||||||
|
|
||||||
|
void HTTPServer::onNotFound(THandlerFunction fn) {
|
||||||
|
_notFoundHandler = fn;
|
||||||
|
}
|
||||||
|
|
||||||
|
void HTTPServer::_handleRequest() {
|
||||||
|
bool handled = false;
|
||||||
|
if (!_currentHandler) {
|
||||||
|
log_e("request handler not found");
|
||||||
|
} else {
|
||||||
|
handled = _currentHandler->handle(*this, _currentMethod, _currentUri);
|
||||||
|
if (!handled) {
|
||||||
|
log_e("request handler failed to handle request");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!handled && _notFoundHandler) {
|
||||||
|
_notFoundHandler();
|
||||||
|
handled = true;
|
||||||
|
}
|
||||||
|
if (!handled) {
|
||||||
|
using namespace mime;
|
||||||
|
send(404, String(FPSTR(mimeTable[html].mimeType)), String(F("Not found: ")) + _currentUri);
|
||||||
|
handled = true;
|
||||||
|
}
|
||||||
|
if (handled) {
|
||||||
|
_finalizeResponse();
|
||||||
|
}
|
||||||
|
_currentUri = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void HTTPServer::_finalizeResponse() {
|
||||||
|
if (_chunked) {
|
||||||
|
sendContent("");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String HTTPServer::_responseCodeToString(int code) {
|
||||||
|
switch (code) {
|
||||||
|
case 100: return F("Continue");
|
||||||
|
case 101: return F("Switching Protocols");
|
||||||
|
case 200: return F("OK");
|
||||||
|
case 201: return F("Created");
|
||||||
|
case 202: return F("Accepted");
|
||||||
|
case 203: return F("Non-Authoritative Information");
|
||||||
|
case 204: return F("No Content");
|
||||||
|
case 205: return F("Reset Content");
|
||||||
|
case 206: return F("Partial Content");
|
||||||
|
case 300: return F("Multiple Choices");
|
||||||
|
case 301: return F("Moved Permanently");
|
||||||
|
case 302: return F("Found");
|
||||||
|
case 303: return F("See Other");
|
||||||
|
case 304: return F("Not Modified");
|
||||||
|
case 305: return F("Use Proxy");
|
||||||
|
case 307: return F("Temporary Redirect");
|
||||||
|
case 400: return F("Bad Request");
|
||||||
|
case 401: return F("Unauthorized");
|
||||||
|
case 402: return F("Payment Required");
|
||||||
|
case 403: return F("Forbidden");
|
||||||
|
case 404: return F("Not Found");
|
||||||
|
case 405: return F("Method Not Allowed");
|
||||||
|
case 406: return F("Not Acceptable");
|
||||||
|
case 407: return F("Proxy Authentication Required");
|
||||||
|
case 408: return F("Request Time-out");
|
||||||
|
case 409: return F("Conflict");
|
||||||
|
case 410: return F("Gone");
|
||||||
|
case 411: return F("Length Required");
|
||||||
|
case 412: return F("Precondition Failed");
|
||||||
|
case 413: return F("Request Entity Too Large");
|
||||||
|
case 414: return F("Request-URI Too Large");
|
||||||
|
case 415: return F("Unsupported Media Type");
|
||||||
|
case 416: return F("Requested range not satisfiable");
|
||||||
|
case 417: return F("Expectation Failed");
|
||||||
|
case 500: return F("Internal Server Error");
|
||||||
|
case 501: return F("Not Implemented");
|
||||||
|
case 502: return F("Bad Gateway");
|
||||||
|
case 503: return F("Service Unavailable");
|
||||||
|
case 504: return F("Gateway Time-out");
|
||||||
|
case 505: return F("HTTP Version not supported");
|
||||||
|
default: return F("");
|
||||||
|
}
|
||||||
|
}
|
||||||
252
libraries/WebServer/src/HTTPServer.h
Normal file
252
libraries/WebServer/src/HTTPServer.h
Normal file
|
|
@ -0,0 +1,252 @@
|
||||||
|
/*
|
||||||
|
HTTPServer.h - Dead simple web-server.
|
||||||
|
Supports only one simultaneous client, knows how to handle GET and POST.
|
||||||
|
|
||||||
|
Copyright (c) 2014 Ivan Grokhotkov. All rights reserved.
|
||||||
|
|
||||||
|
This library is free software; you can redistribute it and/or
|
||||||
|
modify it under the terms of the GNU Lesser General Public
|
||||||
|
License as published by the Free Software Foundation; either
|
||||||
|
version 2.1 of the License, or (at your option) any later version.
|
||||||
|
|
||||||
|
This library 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
|
||||||
|
Lesser General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Lesser General Public
|
||||||
|
License along with this library; if not, write to the Free Software
|
||||||
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
Modified 8 May 2015 by Hristo Gochkov (proper post and file upload handling)
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
|
#include <memory>
|
||||||
|
#include <WiFi.h>
|
||||||
|
#include "HTTP_Method.h"
|
||||||
|
#include "Uri.h"
|
||||||
|
|
||||||
|
enum HTTPUploadStatus { UPLOAD_FILE_START, UPLOAD_FILE_WRITE, UPLOAD_FILE_END,
|
||||||
|
UPLOAD_FILE_ABORTED
|
||||||
|
};
|
||||||
|
enum HTTPClientStatus { HC_NONE, HC_WAIT_READ, HC_WAIT_CLOSE };
|
||||||
|
enum HTTPAuthMethod { BASIC_AUTH, DIGEST_AUTH };
|
||||||
|
|
||||||
|
#define HTTP_DOWNLOAD_UNIT_SIZE 1436
|
||||||
|
|
||||||
|
#ifndef HTTP_UPLOAD_BUFLEN
|
||||||
|
#define HTTP_UPLOAD_BUFLEN 1436
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define HTTP_MAX_DATA_WAIT 5000 //ms to wait for the client to send the request
|
||||||
|
#define HTTP_MAX_POST_WAIT 5000 //ms to wait for POST data to arrive
|
||||||
|
#define HTTP_MAX_SEND_WAIT 5000 //ms to wait for data chunk to be ACKed
|
||||||
|
#define HTTP_MAX_CLOSE_WAIT 2000 //ms to wait for the client to close the connection
|
||||||
|
|
||||||
|
#define CONTENT_LENGTH_UNKNOWN ((size_t) -1)
|
||||||
|
#define CONTENT_LENGTH_NOT_SET ((size_t) -2)
|
||||||
|
|
||||||
|
class HTTPServer;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
HTTPUploadStatus status;
|
||||||
|
String filename;
|
||||||
|
String name;
|
||||||
|
String type;
|
||||||
|
size_t totalSize; // file size
|
||||||
|
size_t currentSize; // size of data currently in buf
|
||||||
|
uint8_t buf[HTTP_UPLOAD_BUFLEN];
|
||||||
|
} HTTPUpload;
|
||||||
|
|
||||||
|
#include "detail/RequestHandler.h"
|
||||||
|
|
||||||
|
namespace fs {
|
||||||
|
class FS;
|
||||||
|
}
|
||||||
|
|
||||||
|
class HTTPServer {
|
||||||
|
public:
|
||||||
|
HTTPServer();
|
||||||
|
virtual ~HTTPServer();
|
||||||
|
|
||||||
|
virtual void httpClose();
|
||||||
|
virtual void httpHandleClient();
|
||||||
|
|
||||||
|
bool authenticate(const char * username, const char * password);
|
||||||
|
void requestAuthentication(HTTPAuthMethod mode = BASIC_AUTH, const char* realm = NULL, const String& authFailMsg = String(""));
|
||||||
|
|
||||||
|
typedef std::function<void(void)> THandlerFunction;
|
||||||
|
void on(const Uri &uri, THandlerFunction fn);
|
||||||
|
void on(const Uri &uri, HTTPMethod method, THandlerFunction fn);
|
||||||
|
void on(const Uri &uri, HTTPMethod method, THandlerFunction fn, THandlerFunction ufn); //ufn handles file uploads
|
||||||
|
void addHandler(RequestHandler* handler);
|
||||||
|
void serveStatic(const char* uri, fs::FS& fs, const char* path, const char* cache_header = NULL);
|
||||||
|
void onNotFound(THandlerFunction fn); //called when handler is not assigned
|
||||||
|
void onFileUpload(THandlerFunction ufn); //handle file uploads
|
||||||
|
|
||||||
|
String uri() {
|
||||||
|
return _currentUri;
|
||||||
|
}
|
||||||
|
HTTPMethod method() {
|
||||||
|
return _currentMethod;
|
||||||
|
}
|
||||||
|
HTTPUpload& upload() {
|
||||||
|
return *_currentUpload;
|
||||||
|
}
|
||||||
|
|
||||||
|
String pathArg(unsigned int i); // get request path argument by number
|
||||||
|
String arg(String name); // get request argument value by name
|
||||||
|
String arg(int i); // get request argument value by number
|
||||||
|
String argName(int i); // get request argument name by number
|
||||||
|
int args(); // get arguments count
|
||||||
|
bool hasArg(String name); // check if argument exists
|
||||||
|
void collectHeaders(const char* headerKeys[], const size_t headerKeysCount); // set the request headers to collect
|
||||||
|
template<typename... Args>
|
||||||
|
void collectHeaders(const Args&... args) { // set the request headers to collect (variadic template version)
|
||||||
|
if (_currentHeaders) {
|
||||||
|
delete[] _currentHeaders;
|
||||||
|
}
|
||||||
|
_headerKeysCount = sizeof...(args) + 1;
|
||||||
|
_currentHeaders = new RequestArgument[_headerKeysCount] {
|
||||||
|
{ .key = "Authorization", .value = "" },
|
||||||
|
{ .key = String(args), .value = "" } ...
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
String header(String name); // get request header value by name
|
||||||
|
String header(int i); // get request header value by number
|
||||||
|
String headerName(int i); // get request header name by number
|
||||||
|
int headers(); // get header count
|
||||||
|
bool hasHeader(String name); // check if header exists
|
||||||
|
|
||||||
|
int clientContentLength() {
|
||||||
|
return _clientContentLength; // return "content-length" of incoming HTTP header from "_currentClient"
|
||||||
|
}
|
||||||
|
|
||||||
|
String hostHeader(); // get request host header if available or empty String if not
|
||||||
|
|
||||||
|
// send response to the client
|
||||||
|
// code - HTTP response code, can be 200 or 404
|
||||||
|
// content_type - HTTP content type, like "text/plain" or "image/png"
|
||||||
|
// content - actual content body
|
||||||
|
void send(int code, const char* content_type = NULL, const String& content = String(""));
|
||||||
|
void send(int code, char* content_type, const String& content);
|
||||||
|
void send(int code, const String& content_type, const String& content);
|
||||||
|
void send(int code, const char* content_type, const char* content);
|
||||||
|
void send(int code, const char* content_type, const char* content, size_t contentLength);
|
||||||
|
|
||||||
|
void send_P(int code, PGM_P content_type, PGM_P content);
|
||||||
|
void send_P(int code, PGM_P content_type, PGM_P content, size_t contentLength);
|
||||||
|
template<typename TypeName>
|
||||||
|
void send(int code, PGM_P content_type, TypeName content, size_t contentLength) {
|
||||||
|
send(code, content_type, (const char *)content, contentLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
void enableDelay(boolean value);
|
||||||
|
void enableCORS(boolean value = true);
|
||||||
|
void enableCrossOrigin(boolean value = true);
|
||||||
|
|
||||||
|
void setContentLength(const size_t contentLength);
|
||||||
|
void sendHeader(const String& name, const String& value, bool first = false);
|
||||||
|
void sendContent(const String& content);
|
||||||
|
void sendContent(const char* content, size_t contentLength);
|
||||||
|
void sendContent_P(PGM_P content);
|
||||||
|
void sendContent_P(PGM_P content, size_t size);
|
||||||
|
|
||||||
|
bool chunkedResponseModeStart_P(int code, PGM_P content_type) {
|
||||||
|
if (_currentVersion == 0)
|
||||||
|
// no chunk mode in HTTP/1.0
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
setContentLength(CONTENT_LENGTH_UNKNOWN);
|
||||||
|
send(code, content_type, "");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
bool chunkedResponseModeStart(int code, const char* content_type) {
|
||||||
|
return chunkedResponseModeStart_P(code, content_type);
|
||||||
|
}
|
||||||
|
bool chunkedResponseModeStart(int code, const String& content_type) {
|
||||||
|
return chunkedResponseModeStart_P(code, content_type.c_str());
|
||||||
|
}
|
||||||
|
void chunkedResponseFinalize() {
|
||||||
|
sendContent("");
|
||||||
|
}
|
||||||
|
|
||||||
|
static String urlDecode(const String& text);
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
size_t streamFile(T &file, const String& contentType, const int code = 200) {
|
||||||
|
_streamFileCore(file.size(), file.name(), contentType, code);
|
||||||
|
return _currentClient->write(file);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
virtual size_t _currentClientWrite(const char* b, size_t l) {
|
||||||
|
return _currentClient->write(b, l);
|
||||||
|
}
|
||||||
|
virtual size_t _currentClientWrite_P(PGM_P b, size_t l) {
|
||||||
|
return _currentClient->write(b, l);
|
||||||
|
}
|
||||||
|
void _addRequestHandler(RequestHandler* handler);
|
||||||
|
void _handleRequest();
|
||||||
|
void _finalizeResponse();
|
||||||
|
bool _parseRequest(WiFiClient* client);
|
||||||
|
void _parseArguments(String data);
|
||||||
|
static String _responseCodeToString(int code);
|
||||||
|
bool _parseForm(WiFiClient* client, String boundary, uint32_t len);
|
||||||
|
bool _parseFormUploadAborted();
|
||||||
|
void _uploadWriteByte(uint8_t b);
|
||||||
|
int _uploadReadByte(WiFiClient* client);
|
||||||
|
void _prepareHeader(String& response, int code, const char* content_type, size_t contentLength);
|
||||||
|
bool _collectHeader(const char* headerName, const char* headerValue);
|
||||||
|
|
||||||
|
void _streamFileCore(const size_t fileSize, const String & fileName, const String & contentType, const int code = 200);
|
||||||
|
|
||||||
|
String _getRandomHexString();
|
||||||
|
// for extracting Auth parameters
|
||||||
|
String _extractParam(String& authReq, const String& param, const char delimit = '"');
|
||||||
|
|
||||||
|
struct RequestArgument {
|
||||||
|
String key;
|
||||||
|
String value;
|
||||||
|
};
|
||||||
|
|
||||||
|
boolean _corsEnabled;
|
||||||
|
|
||||||
|
WiFiClient *_currentClient;
|
||||||
|
HTTPMethod _currentMethod;
|
||||||
|
String _currentUri;
|
||||||
|
uint8_t _currentVersion;
|
||||||
|
HTTPClientStatus _currentStatus;
|
||||||
|
unsigned long _statusChange;
|
||||||
|
boolean _nullDelay;
|
||||||
|
|
||||||
|
RequestHandler* _currentHandler;
|
||||||
|
RequestHandler* _firstHandler;
|
||||||
|
RequestHandler* _lastHandler;
|
||||||
|
THandlerFunction _notFoundHandler;
|
||||||
|
THandlerFunction _fileUploadHandler;
|
||||||
|
|
||||||
|
int _currentArgCount;
|
||||||
|
RequestArgument* _currentArgs;
|
||||||
|
int _postArgsLen;
|
||||||
|
RequestArgument* _postArgs;
|
||||||
|
|
||||||
|
std::unique_ptr<HTTPUpload> _currentUpload;
|
||||||
|
|
||||||
|
int _headerKeysCount;
|
||||||
|
RequestArgument* _currentHeaders;
|
||||||
|
size_t _contentLength;
|
||||||
|
int _clientContentLength; // "Content-Length" from header of incoming POST or GET request
|
||||||
|
String _responseHeaders;
|
||||||
|
|
||||||
|
String _hostHeader;
|
||||||
|
bool _chunked;
|
||||||
|
|
||||||
|
String _snonce; // Store noance and opaque for future comparison
|
||||||
|
String _sopaque;
|
||||||
|
String _srealm; // Store the Auth realm between Calls
|
||||||
|
};
|
||||||
6
libraries/WebServer/src/HTTP_Method.h
Normal file
6
libraries/WebServer/src/HTTP_Method.h
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "http_parser.h"
|
||||||
|
|
||||||
|
typedef enum http_method HTTPMethod;
|
||||||
|
#define HTTP_ANY (HTTPMethod)(255)
|
||||||
624
libraries/WebServer/src/Parsing.cpp
Normal file
624
libraries/WebServer/src/Parsing.cpp
Normal file
|
|
@ -0,0 +1,624 @@
|
||||||
|
/*
|
||||||
|
Parsing.cpp - HTTP request parsing.
|
||||||
|
|
||||||
|
Copyright (c) 2015 Ivan Grokhotkov. All rights reserved.
|
||||||
|
|
||||||
|
This library is free software; you can redistribute it and/or
|
||||||
|
modify it under the terms of the GNU Lesser General Public
|
||||||
|
License as published by the Free Software Foundation; either
|
||||||
|
version 2.1 of the License, or (at your option) any later version.
|
||||||
|
|
||||||
|
This library 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
|
||||||
|
Lesser General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Lesser General Public
|
||||||
|
License along with this library; if not, write to the Free Software
|
||||||
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
Modified 8 May 2015 by Hristo Gochkov (proper post and file upload handling)
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <Arduino.h>
|
||||||
|
#include "WiFiServer.h"
|
||||||
|
#include "WiFiClient.h"
|
||||||
|
#include "HTTPServer.h"
|
||||||
|
#include "detail/mimetable.h"
|
||||||
|
|
||||||
|
#ifndef log_e
|
||||||
|
#define log_e(...)
|
||||||
|
#define log_w(...)
|
||||||
|
#define log_v(...)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef WEBSERVER_MAX_POST_ARGS
|
||||||
|
#define WEBSERVER_MAX_POST_ARGS 32
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define __STR(a) #a
|
||||||
|
#define _STR(a) __STR(a)
|
||||||
|
const char * _http_method_str[] = {
|
||||||
|
#define XX(num, name, string) _STR(name),
|
||||||
|
HTTP_METHOD_MAP(XX)
|
||||||
|
#undef XX
|
||||||
|
};
|
||||||
|
|
||||||
|
static const char Content_Type[] PROGMEM = "Content-Type";
|
||||||
|
static const char filename[] PROGMEM = "filename";
|
||||||
|
|
||||||
|
static char* readBytesWithTimeout(WiFiClient* client, size_t maxLength, size_t& dataLength, int timeout_ms) {
|
||||||
|
char *buf = nullptr;
|
||||||
|
dataLength = 0;
|
||||||
|
while (dataLength < maxLength) {
|
||||||
|
int tries = timeout_ms;
|
||||||
|
size_t newLength;
|
||||||
|
while (!(newLength = client->available()) && tries--) {
|
||||||
|
delay(1);
|
||||||
|
}
|
||||||
|
if (!newLength) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (!buf) {
|
||||||
|
buf = (char *) malloc(newLength + 1);
|
||||||
|
if (!buf) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
char* newBuf = (char *) realloc(buf, dataLength + newLength + 1);
|
||||||
|
if (!newBuf) {
|
||||||
|
free(buf);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
buf = newBuf;
|
||||||
|
}
|
||||||
|
client->readBytes(buf + dataLength, newLength);
|
||||||
|
dataLength += newLength;
|
||||||
|
buf[dataLength] = '\0';
|
||||||
|
}
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool HTTPServer::_parseRequest(WiFiClient* client) {
|
||||||
|
// Read the first line of HTTP request
|
||||||
|
String req = client->readStringUntil('\r');
|
||||||
|
client->readStringUntil('\n');
|
||||||
|
//reset header value
|
||||||
|
for (int i = 0; i < _headerKeysCount; ++i) {
|
||||||
|
_currentHeaders[i].value = String();
|
||||||
|
}
|
||||||
|
|
||||||
|
// First line of HTTP request looks like "GET /path HTTP/1.1"
|
||||||
|
// Retrieve the "/path" part by finding the spaces
|
||||||
|
int addr_start = req.indexOf(' ');
|
||||||
|
int addr_end = req.indexOf(' ', addr_start + 1);
|
||||||
|
if (addr_start == -1 || addr_end == -1) {
|
||||||
|
log_e("Invalid request: %s", req.c_str());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
String methodStr = req.substring(0, addr_start);
|
||||||
|
String url = req.substring(addr_start + 1, addr_end);
|
||||||
|
String versionEnd = req.substring(addr_end + 8);
|
||||||
|
_currentVersion = atoi(versionEnd.c_str());
|
||||||
|
String searchStr = "";
|
||||||
|
int hasSearch = url.indexOf('?');
|
||||||
|
if (hasSearch != -1) {
|
||||||
|
searchStr = url.substring(hasSearch + 1);
|
||||||
|
url = url.substring(0, hasSearch);
|
||||||
|
}
|
||||||
|
_currentUri = url;
|
||||||
|
_chunked = false;
|
||||||
|
_clientContentLength = 0; // not known yet, or invalid
|
||||||
|
|
||||||
|
HTTPMethod method = HTTP_ANY;
|
||||||
|
size_t num_methods = sizeof(_http_method_str) / sizeof(const char *);
|
||||||
|
for (size_t i = 0; i < num_methods; i++) {
|
||||||
|
if (methodStr == _http_method_str[i]) {
|
||||||
|
method = (HTTPMethod)i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (method == HTTP_ANY) {
|
||||||
|
log_e("Unknown HTTP Method: %s", methodStr.c_str());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
_currentMethod = method;
|
||||||
|
|
||||||
|
log_v("method: %s url: %s search: %s", methodStr.c_str(), url.c_str(), searchStr.c_str());
|
||||||
|
|
||||||
|
//attach handler
|
||||||
|
RequestHandler* handler;
|
||||||
|
for (handler = _firstHandler; handler; handler = handler->next()) {
|
||||||
|
if (handler->canHandle(_currentMethod, _currentUri)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_currentHandler = handler;
|
||||||
|
|
||||||
|
String formData;
|
||||||
|
// below is needed only when POST type request
|
||||||
|
if (method == HTTP_POST || method == HTTP_PUT || method == HTTP_PATCH || method == HTTP_DELETE) {
|
||||||
|
String boundaryStr;
|
||||||
|
String headerName;
|
||||||
|
String headerValue;
|
||||||
|
bool isForm = false;
|
||||||
|
bool isEncoded = false;
|
||||||
|
//parse headers
|
||||||
|
while (1) {
|
||||||
|
req = client->readStringUntil('\r');
|
||||||
|
client->readStringUntil('\n');
|
||||||
|
if (req == "") {
|
||||||
|
break; //no moar headers
|
||||||
|
}
|
||||||
|
int headerDiv = req.indexOf(':');
|
||||||
|
if (headerDiv == -1) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
headerName = req.substring(0, headerDiv);
|
||||||
|
headerValue = req.substring(headerDiv + 1);
|
||||||
|
headerValue.trim();
|
||||||
|
_collectHeader(headerName.c_str(), headerValue.c_str());
|
||||||
|
|
||||||
|
log_v("headerName: %s", headerName.c_str());
|
||||||
|
log_v("headerValue: %s", headerValue.c_str());
|
||||||
|
|
||||||
|
if (headerName.equalsIgnoreCase(FPSTR(Content_Type))) {
|
||||||
|
using namespace mime;
|
||||||
|
if (headerValue.startsWith(FPSTR(mimeTable[txt].mimeType))) {
|
||||||
|
isForm = false;
|
||||||
|
} else if (headerValue.startsWith(F("application/x-www-form-urlencoded"))) {
|
||||||
|
isForm = false;
|
||||||
|
isEncoded = true;
|
||||||
|
} else if (headerValue.startsWith(F("multipart/"))) {
|
||||||
|
boundaryStr = headerValue.substring(headerValue.indexOf('=') + 1);
|
||||||
|
boundaryStr.replace("\"", "");
|
||||||
|
isForm = true;
|
||||||
|
}
|
||||||
|
} else if (headerName.equalsIgnoreCase(F("Content-Length"))) {
|
||||||
|
_clientContentLength = headerValue.toInt();
|
||||||
|
} else if (headerName.equalsIgnoreCase(F("Host"))) {
|
||||||
|
_hostHeader = headerValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isForm) {
|
||||||
|
size_t plainLength;
|
||||||
|
char* plainBuf = readBytesWithTimeout(client, _clientContentLength, plainLength, HTTP_MAX_POST_WAIT);
|
||||||
|
if ((int)plainLength < (int)_clientContentLength) {
|
||||||
|
free(plainBuf);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (_clientContentLength > 0) {
|
||||||
|
if (isEncoded) {
|
||||||
|
//url encoded form
|
||||||
|
if (searchStr != "") {
|
||||||
|
searchStr += '&';
|
||||||
|
}
|
||||||
|
searchStr += plainBuf;
|
||||||
|
}
|
||||||
|
_parseArguments(searchStr);
|
||||||
|
if (!isEncoded) {
|
||||||
|
//plain post json or other data
|
||||||
|
RequestArgument& arg = _currentArgs[_currentArgCount++];
|
||||||
|
arg.key = F("plain");
|
||||||
|
arg.value = String(plainBuf);
|
||||||
|
}
|
||||||
|
|
||||||
|
log_v("Plain: %s", plainBuf);
|
||||||
|
free(plainBuf);
|
||||||
|
} else {
|
||||||
|
// No content - but we can still have arguments in the URL.
|
||||||
|
_parseArguments(searchStr);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// it IS a form
|
||||||
|
_parseArguments(searchStr);
|
||||||
|
if (!_parseForm(client, boundaryStr, _clientContentLength)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
String headerName;
|
||||||
|
String headerValue;
|
||||||
|
//parse headers
|
||||||
|
while (1) {
|
||||||
|
req = client->readStringUntil('\r');
|
||||||
|
client->readStringUntil('\n');
|
||||||
|
if (req == "") {
|
||||||
|
break; //no moar headers
|
||||||
|
}
|
||||||
|
int headerDiv = req.indexOf(':');
|
||||||
|
if (headerDiv == -1) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
headerName = req.substring(0, headerDiv);
|
||||||
|
headerValue = req.substring(headerDiv + 2);
|
||||||
|
_collectHeader(headerName.c_str(), headerValue.c_str());
|
||||||
|
|
||||||
|
log_v("headerName: %s", headerName.c_str());
|
||||||
|
log_v("headerValue: %s", headerValue.c_str());
|
||||||
|
|
||||||
|
if (headerName.equalsIgnoreCase("Host")) {
|
||||||
|
_hostHeader = headerValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_parseArguments(searchStr);
|
||||||
|
}
|
||||||
|
client->flush();
|
||||||
|
|
||||||
|
log_v("Request: %s", url.c_str());
|
||||||
|
log_v(" Arguments: %s", searchStr.c_str());
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool HTTPServer::_collectHeader(const char* headerName, const char* headerValue) {
|
||||||
|
for (int i = 0; i < _headerKeysCount; i++) {
|
||||||
|
if (_currentHeaders[i].key.equalsIgnoreCase(headerName)) {
|
||||||
|
_currentHeaders[i].value = headerValue;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void HTTPServer::_parseArguments(String data) {
|
||||||
|
log_v("args: %s", data.c_str());
|
||||||
|
if (_currentArgs) {
|
||||||
|
delete[] _currentArgs;
|
||||||
|
}
|
||||||
|
_currentArgs = 0;
|
||||||
|
if (data.length() == 0) {
|
||||||
|
_currentArgCount = 0;
|
||||||
|
_currentArgs = new RequestArgument[1];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_currentArgCount = 1;
|
||||||
|
|
||||||
|
for (int i = 0; i < (int)data.length();) {
|
||||||
|
i = data.indexOf('&', i);
|
||||||
|
if (i == -1) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
++i;
|
||||||
|
++_currentArgCount;
|
||||||
|
}
|
||||||
|
log_v("args count: %d", _currentArgCount);
|
||||||
|
|
||||||
|
_currentArgs = new RequestArgument[_currentArgCount + 1];
|
||||||
|
int pos = 0;
|
||||||
|
int iarg;
|
||||||
|
for (iarg = 0; iarg < _currentArgCount;) {
|
||||||
|
int equal_sign_index = data.indexOf('=', pos);
|
||||||
|
int next_arg_index = data.indexOf('&', pos);
|
||||||
|
log_v("pos %d =@%d &@%d", pos, equal_sign_index, next_arg_index);
|
||||||
|
if ((equal_sign_index == -1) || ((equal_sign_index > next_arg_index) && (next_arg_index != -1))) {
|
||||||
|
log_e("arg missing value: %d", iarg);
|
||||||
|
if (next_arg_index == -1) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
pos = next_arg_index + 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
RequestArgument& arg = _currentArgs[iarg];
|
||||||
|
arg.key = urlDecode(data.substring(pos, equal_sign_index));
|
||||||
|
arg.value = urlDecode(data.substring(equal_sign_index + 1, next_arg_index));
|
||||||
|
log_v("arg %d key: %s value: %s", iarg, arg.key.c_str(), arg.value.c_str());
|
||||||
|
++iarg;
|
||||||
|
if (next_arg_index == -1) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
pos = next_arg_index + 1;
|
||||||
|
}
|
||||||
|
_currentArgCount = iarg;
|
||||||
|
log_v("args count: %d", _currentArgCount);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void HTTPServer::_uploadWriteByte(uint8_t b) {
|
||||||
|
if (_currentUpload->currentSize == HTTP_UPLOAD_BUFLEN) {
|
||||||
|
if (_currentHandler && _currentHandler->canUpload(_currentUri)) {
|
||||||
|
_currentHandler->upload(*this, _currentUri, *_currentUpload);
|
||||||
|
}
|
||||||
|
_currentUpload->totalSize += _currentUpload->currentSize;
|
||||||
|
_currentUpload->currentSize = 0;
|
||||||
|
}
|
||||||
|
_currentUpload->buf[_currentUpload->currentSize++] = b;
|
||||||
|
}
|
||||||
|
|
||||||
|
int HTTPServer::_uploadReadByte(WiFiClient* client) {
|
||||||
|
int res = client->read();
|
||||||
|
if (res < 0) {
|
||||||
|
// keep trying until you either read a valid byte or timeout
|
||||||
|
unsigned long startMillis = millis();
|
||||||
|
unsigned long timeoutIntervalMillis = client->getTimeout();
|
||||||
|
boolean timedOut = false;
|
||||||
|
for (;;) {
|
||||||
|
if (!client->connected()) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
// loosely modeled after blinkWithoutDelay pattern
|
||||||
|
while (!timedOut && !client->available() && client->connected()) {
|
||||||
|
delay(2);
|
||||||
|
timedOut = millis() - startMillis >= timeoutIntervalMillis;
|
||||||
|
}
|
||||||
|
|
||||||
|
res = client->read();
|
||||||
|
if (res >= 0) {
|
||||||
|
return res; // exit on a valid read
|
||||||
|
}
|
||||||
|
// NOTE: it is possible to get here and have all of the following
|
||||||
|
// assertions hold true
|
||||||
|
//
|
||||||
|
// -- client->available() > 0
|
||||||
|
// -- client->connected == true
|
||||||
|
// -- res == -1
|
||||||
|
//
|
||||||
|
// a simple retry strategy overcomes this which is to say the
|
||||||
|
// assertion is not permanent, but the reason that this works
|
||||||
|
// is elusive, and possibly indicative of a more subtle underlying
|
||||||
|
// issue
|
||||||
|
|
||||||
|
timedOut = millis() - startMillis >= timeoutIntervalMillis;
|
||||||
|
if (timedOut) {
|
||||||
|
return res; // exit on a timeout
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool HTTPServer::_parseForm(WiFiClient* client, String boundary, uint32_t len) {
|
||||||
|
(void) len;
|
||||||
|
log_v("Parse Form: Boundary: %s Length: %d", boundary.c_str(), len);
|
||||||
|
String line;
|
||||||
|
int retry = 0;
|
||||||
|
do {
|
||||||
|
line = client->readStringUntil('\r');
|
||||||
|
++retry;
|
||||||
|
} while (line.length() == 0 && retry < 3);
|
||||||
|
|
||||||
|
client->readStringUntil('\n');
|
||||||
|
//start reading the form
|
||||||
|
if (line == ("--" + boundary)) {
|
||||||
|
if (_postArgs) {
|
||||||
|
delete[] _postArgs;
|
||||||
|
}
|
||||||
|
_postArgs = new RequestArgument[WEBSERVER_MAX_POST_ARGS];
|
||||||
|
_postArgsLen = 0;
|
||||||
|
while (1) {
|
||||||
|
String argName;
|
||||||
|
String argValue;
|
||||||
|
String argType;
|
||||||
|
String argFilename;
|
||||||
|
bool argIsFile = false;
|
||||||
|
|
||||||
|
line = client->readStringUntil('\r');
|
||||||
|
client->readStringUntil('\n');
|
||||||
|
if (line.length() > 19 && line.substring(0, 19).equalsIgnoreCase(F("Content-Disposition"))) {
|
||||||
|
int nameStart = line.indexOf('=');
|
||||||
|
if (nameStart != -1) {
|
||||||
|
argName = line.substring(nameStart + 2);
|
||||||
|
nameStart = argName.indexOf('=');
|
||||||
|
if (nameStart == -1) {
|
||||||
|
argName = argName.substring(0, argName.length() - 1);
|
||||||
|
} else {
|
||||||
|
argFilename = argName.substring(nameStart + 2, argName.length() - 1);
|
||||||
|
argName = argName.substring(0, argName.indexOf('"'));
|
||||||
|
argIsFile = true;
|
||||||
|
log_v("PostArg FileName: %s", argFilename.c_str());
|
||||||
|
//use GET to set the filename if uploading using blob
|
||||||
|
if (argFilename == F("blob") && hasArg(FPSTR(filename))) {
|
||||||
|
argFilename = arg(FPSTR(filename));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
log_v("PostArg Name: %s", argName.c_str());
|
||||||
|
using namespace mime;
|
||||||
|
argType = FPSTR(mimeTable[txt].mimeType);
|
||||||
|
line = client->readStringUntil('\r');
|
||||||
|
client->readStringUntil('\n');
|
||||||
|
if (line.length() > 12 && line.substring(0, 12).equalsIgnoreCase(FPSTR(Content_Type))) {
|
||||||
|
argType = line.substring(line.indexOf(':') + 2);
|
||||||
|
//skip next line
|
||||||
|
client->readStringUntil('\r');
|
||||||
|
client->readStringUntil('\n');
|
||||||
|
}
|
||||||
|
log_v("PostArg Type: %s", argType.c_str());
|
||||||
|
if (!argIsFile) {
|
||||||
|
while (1) {
|
||||||
|
line = client->readStringUntil('\r');
|
||||||
|
client->readStringUntil('\n');
|
||||||
|
if (line.startsWith("--" + boundary)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (argValue.length() > 0) {
|
||||||
|
argValue += "\n";
|
||||||
|
}
|
||||||
|
argValue += line;
|
||||||
|
}
|
||||||
|
log_v("PostArg Value: %s", argValue.c_str());
|
||||||
|
|
||||||
|
RequestArgument& arg = _postArgs[_postArgsLen++];
|
||||||
|
arg.key = argName;
|
||||||
|
arg.value = argValue;
|
||||||
|
|
||||||
|
if (line == ("--" + boundary + "--")) {
|
||||||
|
log_v("Done Parsing POST");
|
||||||
|
break;
|
||||||
|
} else if (_postArgsLen >= WEBSERVER_MAX_POST_ARGS) {
|
||||||
|
log_e("Too many PostArgs (max: %d) in request.", WEBSERVER_MAX_POST_ARGS);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
_currentUpload.reset(new HTTPUpload());
|
||||||
|
_currentUpload->status = UPLOAD_FILE_START;
|
||||||
|
_currentUpload->name = argName;
|
||||||
|
_currentUpload->filename = argFilename;
|
||||||
|
_currentUpload->type = argType;
|
||||||
|
_currentUpload->totalSize = 0;
|
||||||
|
_currentUpload->currentSize = 0;
|
||||||
|
log_v("Start File: %s Type: %s", _currentUpload->filename.c_str(), _currentUpload->type.c_str());
|
||||||
|
if (_currentHandler && _currentHandler->canUpload(_currentUri)) {
|
||||||
|
_currentHandler->upload(*this, _currentUri, *_currentUpload);
|
||||||
|
}
|
||||||
|
_currentUpload->status = UPLOAD_FILE_WRITE;
|
||||||
|
int argByte = _uploadReadByte(client);
|
||||||
|
readfile:
|
||||||
|
|
||||||
|
while (argByte != 0x0D) {
|
||||||
|
if (argByte < 0) {
|
||||||
|
return _parseFormUploadAborted();
|
||||||
|
}
|
||||||
|
_uploadWriteByte(argByte);
|
||||||
|
argByte = _uploadReadByte(client);
|
||||||
|
}
|
||||||
|
|
||||||
|
argByte = _uploadReadByte(client);
|
||||||
|
if (argByte < 0) {
|
||||||
|
return _parseFormUploadAborted();
|
||||||
|
}
|
||||||
|
if (argByte == 0x0A) {
|
||||||
|
argByte = _uploadReadByte(client);
|
||||||
|
if (argByte < 0) {
|
||||||
|
return _parseFormUploadAborted();
|
||||||
|
}
|
||||||
|
if ((char)argByte != '-') {
|
||||||
|
//continue reading the file
|
||||||
|
_uploadWriteByte(0x0D);
|
||||||
|
_uploadWriteByte(0x0A);
|
||||||
|
goto readfile;
|
||||||
|
} else {
|
||||||
|
argByte = _uploadReadByte(client);
|
||||||
|
if (argByte < 0) {
|
||||||
|
return _parseFormUploadAborted();
|
||||||
|
}
|
||||||
|
if ((char)argByte != '-') {
|
||||||
|
//continue reading the file
|
||||||
|
_uploadWriteByte(0x0D);
|
||||||
|
_uploadWriteByte(0x0A);
|
||||||
|
_uploadWriteByte((uint8_t)('-'));
|
||||||
|
goto readfile;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t endBuf[boundary.length()];
|
||||||
|
uint32_t i = 0;
|
||||||
|
while (i < boundary.length()) {
|
||||||
|
argByte = _uploadReadByte(client);
|
||||||
|
if (argByte < 0) {
|
||||||
|
return _parseFormUploadAborted();
|
||||||
|
}
|
||||||
|
if ((char)argByte == 0x0D) {
|
||||||
|
_uploadWriteByte(0x0D);
|
||||||
|
_uploadWriteByte(0x0A);
|
||||||
|
_uploadWriteByte((uint8_t)('-'));
|
||||||
|
_uploadWriteByte((uint8_t)('-'));
|
||||||
|
uint32_t j = 0;
|
||||||
|
while (j < i) {
|
||||||
|
_uploadWriteByte(endBuf[j++]);
|
||||||
|
}
|
||||||
|
goto readfile;
|
||||||
|
}
|
||||||
|
endBuf[i++] = (uint8_t)argByte;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strstr((const char*)endBuf, boundary.c_str()) != NULL) {
|
||||||
|
if (_currentHandler && _currentHandler->canUpload(_currentUri)) {
|
||||||
|
_currentHandler->upload(*this, _currentUri, *_currentUpload);
|
||||||
|
}
|
||||||
|
_currentUpload->totalSize += _currentUpload->currentSize;
|
||||||
|
_currentUpload->status = UPLOAD_FILE_END;
|
||||||
|
if (_currentHandler && _currentHandler->canUpload(_currentUri)) {
|
||||||
|
_currentHandler->upload(*this, _currentUri, *_currentUpload);
|
||||||
|
}
|
||||||
|
log_v("End File: %s Type: %s Size: %d", _currentUpload->filename.c_str(), _currentUpload->type.c_str(), _currentUpload->totalSize);
|
||||||
|
line = client->readStringUntil(0x0D);
|
||||||
|
client->readStringUntil(0x0A);
|
||||||
|
if (line == "--") {
|
||||||
|
log_v("Done Parsing POST");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
_uploadWriteByte(0x0D);
|
||||||
|
_uploadWriteByte(0x0A);
|
||||||
|
_uploadWriteByte((uint8_t)('-'));
|
||||||
|
_uploadWriteByte((uint8_t)('-'));
|
||||||
|
uint32_t i = 0;
|
||||||
|
while (i < boundary.length()) {
|
||||||
|
_uploadWriteByte(endBuf[i++]);
|
||||||
|
}
|
||||||
|
argByte = _uploadReadByte(client);
|
||||||
|
goto readfile;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
_uploadWriteByte(0x0D);
|
||||||
|
goto readfile;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int iarg;
|
||||||
|
int totalArgs = ((WEBSERVER_MAX_POST_ARGS - _postArgsLen) < _currentArgCount) ? (WEBSERVER_MAX_POST_ARGS - _postArgsLen) : _currentArgCount;
|
||||||
|
for (iarg = 0; iarg < totalArgs; iarg++) {
|
||||||
|
RequestArgument& arg = _postArgs[_postArgsLen++];
|
||||||
|
arg.key = _currentArgs[iarg].key;
|
||||||
|
arg.value = _currentArgs[iarg].value;
|
||||||
|
}
|
||||||
|
if (_currentArgs) {
|
||||||
|
delete[] _currentArgs;
|
||||||
|
}
|
||||||
|
_currentArgs = new RequestArgument[_postArgsLen];
|
||||||
|
for (iarg = 0; iarg < _postArgsLen; iarg++) {
|
||||||
|
RequestArgument& arg = _currentArgs[iarg];
|
||||||
|
arg.key = _postArgs[iarg].key;
|
||||||
|
arg.value = _postArgs[iarg].value;
|
||||||
|
}
|
||||||
|
_currentArgCount = iarg;
|
||||||
|
if (_postArgs) {
|
||||||
|
delete[] _postArgs;
|
||||||
|
_postArgs = nullptr;
|
||||||
|
_postArgsLen = 0;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
log_e("Error: line: %s", line.c_str());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
String HTTPServer::urlDecode(const String& text) {
|
||||||
|
String decoded = "";
|
||||||
|
char temp[] = "0x00";
|
||||||
|
unsigned int len = text.length();
|
||||||
|
unsigned int i = 0;
|
||||||
|
while (i < len) {
|
||||||
|
char decodedChar;
|
||||||
|
char encodedChar = text.charAt(i++);
|
||||||
|
if ((encodedChar == '%') && (i + 1 < len)) {
|
||||||
|
temp[2] = text.charAt(i++);
|
||||||
|
temp[3] = text.charAt(i++);
|
||||||
|
|
||||||
|
decodedChar = strtol(temp, NULL, 16);
|
||||||
|
} else {
|
||||||
|
if (encodedChar == '+') {
|
||||||
|
decodedChar = ' ';
|
||||||
|
} else {
|
||||||
|
decodedChar = encodedChar; // normal ascii char
|
||||||
|
}
|
||||||
|
}
|
||||||
|
decoded += decodedChar;
|
||||||
|
}
|
||||||
|
return decoded;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool HTTPServer::_parseFormUploadAborted() {
|
||||||
|
_currentUpload->status = UPLOAD_FILE_ABORTED;
|
||||||
|
if (_currentHandler && _currentHandler->canUpload(_currentUri)) {
|
||||||
|
_currentHandler->upload(*this, _currentUri, *_currentUpload);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
26
libraries/WebServer/src/Uri.h
Normal file
26
libraries/WebServer/src/Uri.h
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <Arduino.h>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
class Uri {
|
||||||
|
|
||||||
|
protected:
|
||||||
|
const String _uri;
|
||||||
|
|
||||||
|
public:
|
||||||
|
Uri(const char *uri) : _uri(uri) {}
|
||||||
|
Uri(const String &uri) : _uri(uri) {}
|
||||||
|
Uri(const __FlashStringHelper *uri) : _uri(String(uri)) {}
|
||||||
|
virtual ~Uri() {}
|
||||||
|
|
||||||
|
virtual Uri* clone() const {
|
||||||
|
return new Uri(_uri);
|
||||||
|
};
|
||||||
|
|
||||||
|
virtual void initPathArgs(__attribute__((unused)) std::vector<String> &pathArgs) {}
|
||||||
|
|
||||||
|
virtual bool canHandle(const String &requestUri, __attribute__((unused)) std::vector<String> &pathArgs) {
|
||||||
|
return _uri == requestUri;
|
||||||
|
}
|
||||||
|
};
|
||||||
26
libraries/WebServer/src/WebServer.h
Normal file
26
libraries/WebServer/src/WebServer.h
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
/*
|
||||||
|
WebServer.h - Create a WebServer class
|
||||||
|
Copyright (c) 2022 Earle F. Philhower, III All rights reserved.
|
||||||
|
|
||||||
|
This library is free software; you can redistribute it and/or
|
||||||
|
modify it under the terms of the GNU Lesser General Public
|
||||||
|
License as published by the Free Software Foundation; either
|
||||||
|
version 2.1 of the License, or (at your option) any later version.
|
||||||
|
|
||||||
|
This library 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
|
||||||
|
Lesser General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Lesser General Public
|
||||||
|
License along with this library; if not, write to the Free Software
|
||||||
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
Modified 8 May 2015 by Hristo Gochkov (proper post and file upload handling)
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "WebServerTemplate.h"
|
||||||
|
#include "detail/mimetable.h"
|
||||||
|
|
||||||
|
using WebServer = WebServerTemplate<WiFiServer>;
|
||||||
25
libraries/WebServer/src/WebServerSecure.h
Normal file
25
libraries/WebServer/src/WebServerSecure.h
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
/*
|
||||||
|
WebServerSecure - Create a WebServerSecure class
|
||||||
|
Copyright (c) 2022 Earle F. Philhower, III All rights reserved.
|
||||||
|
|
||||||
|
This library is free software; you can redistribute it and/or
|
||||||
|
modify it under the terms of the GNU Lesser General Public
|
||||||
|
License as published by the Free Software Foundation; either
|
||||||
|
version 2.1 of the License, or (at your option) any later version.
|
||||||
|
|
||||||
|
This library 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
|
||||||
|
Lesser General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Lesser General Public
|
||||||
|
License along with this library; if not, write to the Free Software
|
||||||
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
Modified 8 May 2015 by Hristo Gochkov (proper post and file upload handling)
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "WebServerTemplate.h"
|
||||||
|
|
||||||
|
using WebServerSecure = WebServerTemplate<WiFiServerSecure, 443>;
|
||||||
117
libraries/WebServer/src/WebServerTemplate.h
Normal file
117
libraries/WebServer/src/WebServerTemplate.h
Normal file
|
|
@ -0,0 +1,117 @@
|
||||||
|
/*
|
||||||
|
WebServerTemplate - Makes an actual Server class from a HTTPServer
|
||||||
|
Copyright (c) 2022 Earle F. Philhower, III All rights reserved.
|
||||||
|
|
||||||
|
This library is free software; you can redistribute it and/or
|
||||||
|
modify it under the terms of the GNU Lesser General Public
|
||||||
|
License as published by the Free Software Foundation; either
|
||||||
|
version 2.1 of the License, or (at your option) any later version.
|
||||||
|
|
||||||
|
This library 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
|
||||||
|
Lesser General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Lesser General Public
|
||||||
|
License along with this library; if not, write to the Free Software
|
||||||
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
Modified 8 May 2015 by Hristo Gochkov (proper post and file upload handling)
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "HTTPServer.h"
|
||||||
|
|
||||||
|
template<typename ServerType, int DefaultPort = 80>
|
||||||
|
class WebServerTemplate;
|
||||||
|
|
||||||
|
template<typename ServerType, int DefaultPort>
|
||||||
|
class WebServerTemplate : public HTTPServer {
|
||||||
|
public:
|
||||||
|
using WebServerType = WebServerTemplate<ServerType>;
|
||||||
|
using ClientType = typename ServerType::ClientType;
|
||||||
|
|
||||||
|
WebServerTemplate(IPAddress addr, int port = DefaultPort);
|
||||||
|
WebServerTemplate(int port = DefaultPort);
|
||||||
|
virtual ~WebServerTemplate();
|
||||||
|
|
||||||
|
virtual void begin();
|
||||||
|
virtual void begin(uint16_t port);
|
||||||
|
virtual void handleClient();
|
||||||
|
|
||||||
|
virtual void close();
|
||||||
|
virtual void stop();
|
||||||
|
|
||||||
|
ServerType &getServer() {
|
||||||
|
return _server;
|
||||||
|
}
|
||||||
|
|
||||||
|
ClientType& client() {
|
||||||
|
// _currentClient is always a WiFiClient*, so we need to coerce to the proper type for SSL
|
||||||
|
return *(ClientType*)_currentClient;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
ServerType _server;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename ServerType, int DefaultPort>
|
||||||
|
WebServerTemplate<ServerType, DefaultPort>::WebServerTemplate(IPAddress addr, int port) : HTTPServer(), _server(addr, port) {
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename ServerType, int DefaultPort>
|
||||||
|
WebServerTemplate<ServerType, DefaultPort>::WebServerTemplate(int port) : HTTPServer(), _server(port) {
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename ServerType, int DefaultPort>
|
||||||
|
WebServerTemplate<ServerType, DefaultPort>::~WebServerTemplate() {
|
||||||
|
_server.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename ServerType, int DefaultPort>
|
||||||
|
void WebServerTemplate<ServerType, DefaultPort>::begin() {
|
||||||
|
close();
|
||||||
|
_server.begin();
|
||||||
|
_server.setNoDelay(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename ServerType, int DefaultPort>
|
||||||
|
void WebServerTemplate<ServerType, DefaultPort>::begin(uint16_t port) {
|
||||||
|
close();
|
||||||
|
_server.begin(port);
|
||||||
|
_server.setNoDelay(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename ServerType, int DefaultPort>
|
||||||
|
void WebServerTemplate<ServerType, DefaultPort>::handleClient() {
|
||||||
|
if (_currentStatus == HC_NONE) {
|
||||||
|
if (_currentClient) {
|
||||||
|
delete _currentClient;
|
||||||
|
_currentClient = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
ClientType client = _server.available();
|
||||||
|
if (!client) {
|
||||||
|
if (_nullDelay) {
|
||||||
|
delay(1);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_currentClient = new ClientType(client);
|
||||||
|
_currentStatus = HC_WAIT_READ;
|
||||||
|
_statusChange = millis();
|
||||||
|
}
|
||||||
|
httpHandleClient();
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename ServerType, int DefaultPort>
|
||||||
|
void WebServerTemplate<ServerType, DefaultPort>::close() {
|
||||||
|
_server.close();
|
||||||
|
httpClose();
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename ServerType, int DefaultPort>
|
||||||
|
void WebServerTemplate<ServerType, DefaultPort>::stop() {
|
||||||
|
close();
|
||||||
|
}
|
||||||
48
libraries/WebServer/src/detail/RequestHandler.h
Normal file
48
libraries/WebServer/src/detail/RequestHandler.h
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
#include <assert.h>
|
||||||
|
|
||||||
|
class RequestHandler {
|
||||||
|
public:
|
||||||
|
virtual ~RequestHandler() { }
|
||||||
|
virtual bool canHandle(HTTPMethod method, String uri) {
|
||||||
|
(void) method;
|
||||||
|
(void) uri;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
virtual bool canUpload(String uri) {
|
||||||
|
(void) uri;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
virtual bool handle(HTTPServer& server, HTTPMethod requestMethod, String requestUri) {
|
||||||
|
(void) server;
|
||||||
|
(void) requestMethod;
|
||||||
|
(void) requestUri;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
virtual void upload(HTTPServer& server, String requestUri, HTTPUpload& upload) {
|
||||||
|
(void) server;
|
||||||
|
(void) requestUri;
|
||||||
|
(void) upload;
|
||||||
|
}
|
||||||
|
|
||||||
|
RequestHandler* next() {
|
||||||
|
return _next;
|
||||||
|
}
|
||||||
|
void next(RequestHandler* r) {
|
||||||
|
_next = r;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
RequestHandler* _next = nullptr;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
std::vector<String> pathArgs;
|
||||||
|
|
||||||
|
public:
|
||||||
|
const String& pathArg(unsigned int i) {
|
||||||
|
assert(i < pathArgs.size());
|
||||||
|
return pathArgs[i];
|
||||||
|
}
|
||||||
|
};
|
||||||
163
libraries/WebServer/src/detail/RequestHandlersImpl.h
Normal file
163
libraries/WebServer/src/detail/RequestHandlersImpl.h
Normal file
|
|
@ -0,0 +1,163 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "RequestHandler.h"
|
||||||
|
#include "mimetable.h"
|
||||||
|
#include <api/String.h>
|
||||||
|
#include "Uri.h"
|
||||||
|
|
||||||
|
#ifndef log_e
|
||||||
|
#define log_e(...)
|
||||||
|
#define log_w(...)
|
||||||
|
#define log_v(...)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
using namespace mime;
|
||||||
|
|
||||||
|
class FunctionRequestHandler : public RequestHandler {
|
||||||
|
public:
|
||||||
|
FunctionRequestHandler(HTTPServer::THandlerFunction fn, HTTPServer::THandlerFunction ufn, const Uri &uri, HTTPMethod method)
|
||||||
|
: _fn(fn)
|
||||||
|
, _ufn(ufn)
|
||||||
|
, _uri(uri.clone())
|
||||||
|
, _method(method) {
|
||||||
|
_uri->initPathArgs(pathArgs);
|
||||||
|
}
|
||||||
|
|
||||||
|
~FunctionRequestHandler() {
|
||||||
|
delete _uri;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool canHandle(HTTPMethod requestMethod, String requestUri) override {
|
||||||
|
if (_method != HTTP_ANY && _method != requestMethod) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return _uri->canHandle(requestUri, pathArgs);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool canUpload(String requestUri) override {
|
||||||
|
if (!_ufn || !canHandle(HTTP_POST, requestUri)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool handle(HTTPServer& server, HTTPMethod requestMethod, String requestUri) override {
|
||||||
|
(void) server;
|
||||||
|
if (!canHandle(requestMethod, requestUri)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
_fn();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void upload(HTTPServer& server, String requestUri, HTTPUpload& upload) override {
|
||||||
|
(void) server;
|
||||||
|
(void) upload;
|
||||||
|
if (canUpload(requestUri)) {
|
||||||
|
_ufn();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
HTTPServer::THandlerFunction _fn;
|
||||||
|
HTTPServer::THandlerFunction _ufn;
|
||||||
|
Uri *_uri;
|
||||||
|
HTTPMethod _method;
|
||||||
|
};
|
||||||
|
|
||||||
|
class StaticRequestHandler : public RequestHandler {
|
||||||
|
public:
|
||||||
|
StaticRequestHandler(FS& fs, const char* path, const char* uri, const char* cache_header)
|
||||||
|
: _fs(fs)
|
||||||
|
, _uri(uri)
|
||||||
|
, _path(path)
|
||||||
|
, _cache_header(cache_header) {
|
||||||
|
File f = fs.open(path, "r");
|
||||||
|
_isFile = (f && (! f.isDirectory()));
|
||||||
|
log_v("StaticRequestHandler: path=%s uri=%s isFile=%d, cache_header=%s\r\n", path, uri, _isFile, cache_header ? cache_header : ""); // issue 5506 - cache_header can be nullptr
|
||||||
|
_baseUriLength = _uri.length();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool canHandle(HTTPMethod requestMethod, String requestUri) override {
|
||||||
|
if (requestMethod != HTTP_GET) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((_isFile && requestUri != _uri) || !requestUri.startsWith(_uri)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool handle(HTTPServer& server, HTTPMethod requestMethod, String requestUri) override {
|
||||||
|
if (!canHandle(requestMethod, requestUri)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
log_v("StaticRequestHandler::handle: request=%s _uri=%s\r\n", requestUri.c_str(), _uri.c_str());
|
||||||
|
|
||||||
|
String path(_path);
|
||||||
|
|
||||||
|
if (!_isFile) {
|
||||||
|
// Base URI doesn't point to a file.
|
||||||
|
// If a directory is requested, look for index file.
|
||||||
|
if (requestUri.endsWith("/")) {
|
||||||
|
requestUri += "index.htm";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Append whatever follows this URI in request to get the file path.
|
||||||
|
path += requestUri.substring(_baseUriLength);
|
||||||
|
}
|
||||||
|
log_v("StaticRequestHandler::handle: path=%s, isFile=%d\r\n", path.c_str(), _isFile);
|
||||||
|
|
||||||
|
String contentType = getContentType(path);
|
||||||
|
|
||||||
|
// look for gz file, only if the original specified path is not a gz. So part only works to send gzip via content encoding when a non compressed is asked for
|
||||||
|
// if you point the the path to gzip you will serve the gzip as content type "application/x-gzip", not text or javascript etc...
|
||||||
|
if (!path.endsWith(FPSTR(mimeTable[gz].endsWith)) && !_fs.exists(path)) {
|
||||||
|
String pathWithGz = path + FPSTR(mimeTable[gz].endsWith);
|
||||||
|
if (_fs.exists(pathWithGz)) {
|
||||||
|
path += FPSTR(mimeTable[gz].endsWith);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
File f = _fs.open(path, "r");
|
||||||
|
if (!f || !f.available()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_cache_header.length() != 0) {
|
||||||
|
server.sendHeader("Cache-Control", _cache_header);
|
||||||
|
}
|
||||||
|
|
||||||
|
server.streamFile(f, contentType);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static String getContentType(const String& path) {
|
||||||
|
char buff[sizeof(mimeTable[0].mimeType)];
|
||||||
|
// Check all entries but last one for match, return if found
|
||||||
|
for (size_t i = 0; i < sizeof(mimeTable) / sizeof(mimeTable[0]) - 1; i++) {
|
||||||
|
strcpy_P(buff, mimeTable[i].endsWith);
|
||||||
|
if (path.endsWith(buff)) {
|
||||||
|
strcpy_P(buff, mimeTable[i].mimeType);
|
||||||
|
return String(buff);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Fall-through and just return default type
|
||||||
|
strcpy_P(buff, mimeTable[sizeof(mimeTable) / sizeof(mimeTable[0]) - 1].mimeType);
|
||||||
|
return String(buff);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
FS _fs;
|
||||||
|
String _uri;
|
||||||
|
String _path;
|
||||||
|
String _cache_header;
|
||||||
|
bool _isFile;
|
||||||
|
size_t _baseUriLength;
|
||||||
|
};
|
||||||
44
libraries/WebServer/src/detail/mimetable.cpp
Normal file
44
libraries/WebServer/src/detail/mimetable.cpp
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
#include <Arduino.h>
|
||||||
|
#include "mimetable.h"
|
||||||
|
#include "pgmspace.h"
|
||||||
|
|
||||||
|
namespace mime {
|
||||||
|
|
||||||
|
// Table of extension->MIME strings stored in PROGMEM, needs to be global due to GCC section typing rules
|
||||||
|
const Entry mimeTable[maxType] = {
|
||||||
|
{ ".html", "text/html" },
|
||||||
|
{ ".htm", "text/html" },
|
||||||
|
{ ".css", "text/css" },
|
||||||
|
{ ".txt", "text/plain" },
|
||||||
|
{ ".js", "application/javascript" },
|
||||||
|
{ ".json", "application/json" },
|
||||||
|
{ ".png", "image/png" },
|
||||||
|
{ ".gif", "image/gif" },
|
||||||
|
{ ".jpg", "image/jpeg" },
|
||||||
|
{ ".ico", "image/x-icon" },
|
||||||
|
{ ".svg", "image/svg+xml" },
|
||||||
|
{ ".ttf", "application/x-font-ttf" },
|
||||||
|
{ ".otf", "application/x-font-opentype" },
|
||||||
|
{ ".woff", "application/font-woff" },
|
||||||
|
{ ".woff2", "application/font-woff2" },
|
||||||
|
{ ".eot", "application/vnd.ms-fontobject" },
|
||||||
|
{ ".sfnt", "application/font-sfnt" },
|
||||||
|
{ ".xml", "text/xml" },
|
||||||
|
{ ".pdf", "application/pdf" },
|
||||||
|
{ ".zip", "application/zip" },
|
||||||
|
{ ".gz", "application/x-gzip" },
|
||||||
|
{ ".appcache", "text/cache-manifest" },
|
||||||
|
{ "", "application/octet-stream" }
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
arduino::String getContentType(const arduino::String& path) {
|
||||||
|
for (size_t i = 0; i < maxType; i++) {
|
||||||
|
if (path.endsWith(FPSTR(mimeTable[i].endsWith))) {
|
||||||
|
return arduino::String(FPSTR(mimeTable[i].mimeType));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Fall-through and just return default type
|
||||||
|
return arduino::String(FPSTR(mimeTable[none].mimeType));
|
||||||
|
}
|
||||||
|
}
|
||||||
43
libraries/WebServer/src/detail/mimetable.h
Normal file
43
libraries/WebServer/src/detail/mimetable.h
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <api/String.h>
|
||||||
|
|
||||||
|
namespace mime {
|
||||||
|
|
||||||
|
enum type {
|
||||||
|
html,
|
||||||
|
htm,
|
||||||
|
css,
|
||||||
|
txt,
|
||||||
|
js,
|
||||||
|
json,
|
||||||
|
png,
|
||||||
|
gif,
|
||||||
|
jpg,
|
||||||
|
ico,
|
||||||
|
svg,
|
||||||
|
ttf,
|
||||||
|
otf,
|
||||||
|
woff,
|
||||||
|
woff2,
|
||||||
|
eot,
|
||||||
|
sfnt,
|
||||||
|
xml,
|
||||||
|
pdf,
|
||||||
|
zip,
|
||||||
|
gz,
|
||||||
|
appcache,
|
||||||
|
none,
|
||||||
|
maxType
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Entry {
|
||||||
|
const char endsWith[16];
|
||||||
|
const char mimeType[32];
|
||||||
|
};
|
||||||
|
|
||||||
|
extern const Entry mimeTable[maxType];
|
||||||
|
|
||||||
|
arduino::String getContentType(const arduino::String& path);
|
||||||
|
|
||||||
|
}
|
||||||
65
libraries/WebServer/src/uri/UriBraces.h
Normal file
65
libraries/WebServer/src/uri/UriBraces.h
Normal file
|
|
@ -0,0 +1,65 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "Uri.h"
|
||||||
|
|
||||||
|
class UriBraces : public Uri {
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit UriBraces(const char *uri) : Uri(uri) {};
|
||||||
|
explicit UriBraces(const String &uri) : Uri(uri) {};
|
||||||
|
|
||||||
|
Uri* clone() const override final {
|
||||||
|
return new UriBraces(_uri);
|
||||||
|
};
|
||||||
|
|
||||||
|
void initPathArgs(std::vector<String> &pathArgs) override final {
|
||||||
|
int numParams = 0, start = 0;
|
||||||
|
do {
|
||||||
|
start = _uri.indexOf("{}", start);
|
||||||
|
if (start > 0) {
|
||||||
|
numParams++;
|
||||||
|
start += 2;
|
||||||
|
}
|
||||||
|
} while (start > 0);
|
||||||
|
pathArgs.resize(numParams);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool canHandle(const String &requestUri, std::vector<String> &pathArgs) override final {
|
||||||
|
if (Uri::canHandle(requestUri, pathArgs)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t uriLength = _uri.length();
|
||||||
|
unsigned int pathArgIndex = 0;
|
||||||
|
unsigned int requestUriIndex = 0;
|
||||||
|
for (unsigned int i = 0; i < uriLength; i++, requestUriIndex++) {
|
||||||
|
char uriChar = _uri[i];
|
||||||
|
char requestUriChar = requestUri[requestUriIndex];
|
||||||
|
|
||||||
|
if (uriChar == requestUriChar) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (uriChar != '{') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
i += 2; // index of char after '}'
|
||||||
|
if (i >= uriLength) {
|
||||||
|
// there is no char after '}'
|
||||||
|
pathArgs[pathArgIndex] = requestUri.substring(requestUriIndex);
|
||||||
|
return pathArgs[pathArgIndex].indexOf("/") == -1; // path argument may not contain a '/'
|
||||||
|
} else {
|
||||||
|
char charEnd = _uri[i];
|
||||||
|
int uriIndex = requestUri.indexOf(charEnd, requestUriIndex);
|
||||||
|
if (uriIndex < 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
pathArgs[pathArgIndex] = requestUri.substring(requestUriIndex, uriIndex);
|
||||||
|
requestUriIndex = (unsigned int) uriIndex;
|
||||||
|
}
|
||||||
|
pathArgIndex++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return requestUriIndex >= requestUri.length();
|
||||||
|
}
|
||||||
|
};
|
||||||
19
libraries/WebServer/src/uri/UriGlob.h
Normal file
19
libraries/WebServer/src/uri/UriGlob.h
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "Uri.h"
|
||||||
|
#include <fnmatch.h>
|
||||||
|
|
||||||
|
class UriGlob : public Uri {
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit UriGlob(const char *uri) : Uri(uri) {};
|
||||||
|
explicit UriGlob(const String &uri) : Uri(uri) {};
|
||||||
|
|
||||||
|
Uri* clone() const override final {
|
||||||
|
return new UriGlob(_uri);
|
||||||
|
};
|
||||||
|
|
||||||
|
bool canHandle(const String &requestUri, __attribute__((unused)) std::vector<String> &pathArgs) override final {
|
||||||
|
return fnmatch(_uri.c_str(), requestUri.c_str(), 0) == 0;
|
||||||
|
}
|
||||||
|
};
|
||||||
42
libraries/WebServer/src/uri/UriRegex.h
Normal file
42
libraries/WebServer/src/uri/UriRegex.h
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "Uri.h"
|
||||||
|
#include <regex>
|
||||||
|
|
||||||
|
class UriRegex : public Uri {
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit UriRegex(const char *uri) : Uri(uri) {};
|
||||||
|
explicit UriRegex(const String &uri) : Uri(uri) {};
|
||||||
|
|
||||||
|
Uri* clone() const override final {
|
||||||
|
return new UriRegex(_uri);
|
||||||
|
};
|
||||||
|
|
||||||
|
void initPathArgs(std::vector<String> &pathArgs) override final {
|
||||||
|
std::regex rgx((_uri + "|").c_str());
|
||||||
|
std::smatch matches;
|
||||||
|
std::string s{""};
|
||||||
|
std::regex_search(s, matches, rgx);
|
||||||
|
pathArgs.resize(matches.size() - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool canHandle(const String &requestUri, std::vector<String> &pathArgs) override final {
|
||||||
|
if (Uri::canHandle(requestUri, pathArgs)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned int pathArgIndex = 0;
|
||||||
|
std::regex rgx(_uri.c_str());
|
||||||
|
std::smatch matches;
|
||||||
|
std::string s(requestUri.c_str());
|
||||||
|
if (std::regex_search(s, matches, rgx)) {
|
||||||
|
for (size_t i = 1; i < matches.size(); ++i) { // skip first
|
||||||
|
pathArgs[pathArgIndex] = String(matches[i].str().c_str());
|
||||||
|
pathArgIndex++;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
@ -235,16 +235,16 @@ const char *WiFiClass::getHostname() {
|
||||||
return: one value of wl_status_t enum
|
return: one value of wl_status_t enum
|
||||||
*/
|
*/
|
||||||
int WiFiClass::disconnect(void) {
|
int WiFiClass::disconnect(void) {
|
||||||
if (_wifiHWInitted) {
|
|
||||||
cyw43_wifi_leave(&cyw43_state, _apMode ? 1 : 0);
|
|
||||||
}
|
|
||||||
_wifiHWInitted = false;
|
|
||||||
if (_dhcpServer) {
|
if (_dhcpServer) {
|
||||||
dhcp_server_deinit(_dhcpServer);
|
dhcp_server_deinit(_dhcpServer);
|
||||||
free(_dhcpServer);
|
free(_dhcpServer);
|
||||||
_dhcpServer = nullptr;
|
_dhcpServer = nullptr;
|
||||||
}
|
}
|
||||||
|
if (_wifiHWInitted) {
|
||||||
|
_wifiHWInitted = false;
|
||||||
|
cyw43_wifi_leave(&cyw43_state, _apMode ? 1 : 0);
|
||||||
_wifi.end();
|
_wifi.end();
|
||||||
|
}
|
||||||
return WL_DISCONNECTED;
|
return WL_DISCONNECTED;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -115,7 +115,7 @@ public:
|
||||||
|
|
||||||
bool softAPdisconnect(bool wifioff = false) {
|
bool softAPdisconnect(bool wifioff = false) {
|
||||||
(void) wifioff;
|
(void) wifioff;
|
||||||
end();
|
disconnect();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -201,18 +201,13 @@ size_t WiFiClient::write(const uint8_t *buf, size_t size) {
|
||||||
return _client->write((const char*)buf, size);
|
return _client->write((const char*)buf, size);
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO - implement!
|
size_t WiFiClient::write(Stream& stream) {
|
||||||
//size_t WiFiClient::write(Stream& stream)
|
if (!_client || !stream.available()) {
|
||||||
//{
|
return 0;
|
||||||
// // (this method is deprecated)
|
}
|
||||||
//
|
_client->setTimeout(_timeout);
|
||||||
// if (!_client || !stream.available())
|
return _client->write(stream);
|
||||||
// {
|
}
|
||||||
// return 0;
|
|
||||||
// }
|
|
||||||
// // core up to 2.7.4 was equivalent to this
|
|
||||||
// return _client->write(stream);
|
|
||||||
//}
|
|
||||||
|
|
||||||
int WiFiClient::available() {
|
int WiFiClient::available() {
|
||||||
if (!_client) {
|
if (!_client) {
|
||||||
|
|
|
||||||
|
|
@ -70,7 +70,7 @@ public:
|
||||||
virtual int connect(const String& host, uint16_t port);
|
virtual int connect(const String& host, uint16_t port);
|
||||||
virtual size_t write(uint8_t) override;
|
virtual size_t write(uint8_t) override;
|
||||||
virtual size_t write(const uint8_t *buf, size_t size) override;
|
virtual size_t write(const uint8_t *buf, size_t size) override;
|
||||||
//size_t write(Stream& stream);
|
size_t write(Stream& stream);
|
||||||
|
|
||||||
virtual int available() override;
|
virtual int available() override;
|
||||||
virtual int read() override;
|
virtual int read() override;
|
||||||
|
|
|
||||||
|
|
@ -80,6 +80,7 @@ public:
|
||||||
virtual ~WiFiServer() {}
|
virtual ~WiFiServer() {}
|
||||||
WiFiClient accept(); // https://www.arduino.cc/en/Reference/EthernetServerAccept
|
WiFiClient accept(); // https://www.arduino.cc/en/Reference/EthernetServerAccept
|
||||||
WiFiClient available(uint8_t* status = NULL);
|
WiFiClient available(uint8_t* status = NULL);
|
||||||
|
|
||||||
bool hasClient();
|
bool hasClient();
|
||||||
// hasClientData():
|
// hasClientData():
|
||||||
// returns the amount of data available from the first client
|
// returns the amount of data available from the first client
|
||||||
|
|
|
||||||
|
|
@ -65,7 +65,7 @@ public:
|
||||||
|
|
||||||
// If awaiting connection available and authenticated (i.e. client cert), return it.
|
// If awaiting connection available and authenticated (i.e. client cert), return it.
|
||||||
WiFiClientSecure accept(); // https://www.arduino.cc/en/Reference/EthernetServerAccept
|
WiFiClientSecure accept(); // https://www.arduino.cc/en/Reference/EthernetServerAccept
|
||||||
WiFiClientSecure available(uint8_t* status = NULL) __attribute__((deprecated("Renamed to accept().")));
|
WiFiClientSecure available(uint8_t* status = NULL);
|
||||||
|
|
||||||
WiFiServerSecure& operator=(const WiFiServerSecure&) = default;
|
WiFiServerSecure& operator=(const WiFiServerSecure&) = default;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -374,6 +374,23 @@ public:
|
||||||
return _write_from_source(ds, dl);
|
return _write_from_source(ds, dl);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
size_t write(Stream& stream) {
|
||||||
|
if (!_pcb) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
size_t sent = 0;
|
||||||
|
while (stream.available()) {
|
||||||
|
char b;
|
||||||
|
b = stream.read();
|
||||||
|
if (write(&b, 1)) {
|
||||||
|
sent ++;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sent;
|
||||||
|
}
|
||||||
|
|
||||||
void keepAlive(uint16_t idle_sec = TCP_DEFAULT_KEEPALIVE_IDLE_SEC, uint16_t intv_sec = TCP_DEFAULT_KEEPALIVE_INTERVAL_SEC, uint8_t count = TCP_DEFAULT_KEEPALIVE_COUNT) {
|
void keepAlive(uint16_t idle_sec = TCP_DEFAULT_KEEPALIVE_IDLE_SEC, uint16_t intv_sec = TCP_DEFAULT_KEEPALIVE_INTERVAL_SEC, uint8_t count = TCP_DEFAULT_KEEPALIVE_COUNT) {
|
||||||
if (idle_sec && intv_sec && count) {
|
if (idle_sec && intv_sec && count) {
|
||||||
_pcb->so_options |= SOF_KEEPALIVE;
|
_pcb->so_options |= SOF_KEEPALIVE;
|
||||||
|
|
|
||||||
19
libraries/http-parser/LICENSE-MIT
Normal file
19
libraries/http-parser/LICENSE-MIT
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
Copyright Joyent, Inc. and other Node contributors.
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to
|
||||||
|
deal in the Software without restriction, including without limitation the
|
||||||
|
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||||
|
sell copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||||
|
IN THE SOFTWARE.
|
||||||
249
libraries/http-parser/README.md
Normal file
249
libraries/http-parser/README.md
Normal file
|
|
@ -0,0 +1,249 @@
|
||||||
|
HTTP Parser
|
||||||
|
===========
|
||||||
|
|
||||||
|
http-parser is [**not** actively maintained](https://github.com/nodejs/http-parser/issues/522).
|
||||||
|
New projects and projects looking to migrate should consider [llhttp](https://github.com/nodejs/llhttp).
|
||||||
|
|
||||||
|
[](https://travis-ci.org/nodejs/http-parser)
|
||||||
|
|
||||||
|
This is a parser for HTTP messages written in C. It parses both requests and
|
||||||
|
responses. The parser is designed to be used in performance HTTP
|
||||||
|
applications. It does not make any syscalls nor allocations, it does not
|
||||||
|
buffer data, it can be interrupted at anytime. Depending on your
|
||||||
|
architecture, it only requires about 40 bytes of data per message
|
||||||
|
stream (in a web server that is per connection).
|
||||||
|
|
||||||
|
Features:
|
||||||
|
|
||||||
|
* No dependencies
|
||||||
|
* Handles persistent streams (keep-alive).
|
||||||
|
* Decodes chunked encoding.
|
||||||
|
* Upgrade support
|
||||||
|
* Defends against buffer overflow attacks.
|
||||||
|
|
||||||
|
The parser extracts the following information from HTTP messages:
|
||||||
|
|
||||||
|
* Header fields and values
|
||||||
|
* Content-Length
|
||||||
|
* Request method
|
||||||
|
* Response status code
|
||||||
|
* Transfer-Encoding
|
||||||
|
* HTTP version
|
||||||
|
* Request URL
|
||||||
|
* Message body
|
||||||
|
|
||||||
|
|
||||||
|
Usage
|
||||||
|
-----
|
||||||
|
|
||||||
|
One `http_parser` object is used per TCP connection. Initialize the struct
|
||||||
|
using `http_parser_init()` and set the callbacks. That might look something
|
||||||
|
like this for a request parser:
|
||||||
|
```c
|
||||||
|
http_parser_settings settings;
|
||||||
|
settings.on_url = my_url_callback;
|
||||||
|
settings.on_header_field = my_header_field_callback;
|
||||||
|
/* ... */
|
||||||
|
|
||||||
|
http_parser *parser = malloc(sizeof(http_parser));
|
||||||
|
http_parser_init(parser, HTTP_REQUEST);
|
||||||
|
parser->data = my_socket;
|
||||||
|
```
|
||||||
|
|
||||||
|
When data is received on the socket execute the parser and check for errors.
|
||||||
|
|
||||||
|
```c
|
||||||
|
size_t len = 80*1024, nparsed;
|
||||||
|
char buf[len];
|
||||||
|
ssize_t recved;
|
||||||
|
|
||||||
|
recved = recv(fd, buf, len, 0);
|
||||||
|
|
||||||
|
if (recved < 0) {
|
||||||
|
/* Handle error. */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Start up / continue the parser.
|
||||||
|
* Note we pass recved==0 to signal that EOF has been received.
|
||||||
|
*/
|
||||||
|
nparsed = http_parser_execute(parser, &settings, buf, recved);
|
||||||
|
|
||||||
|
if (parser->upgrade) {
|
||||||
|
/* handle new protocol */
|
||||||
|
} else if (nparsed != recved) {
|
||||||
|
/* Handle error. Usually just close the connection. */
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`http_parser` needs to know where the end of the stream is. For example, sometimes
|
||||||
|
servers send responses without Content-Length and expect the client to
|
||||||
|
consume input (for the body) until EOF. To tell `http_parser` about EOF, give
|
||||||
|
`0` as the fourth parameter to `http_parser_execute()`. Callbacks and errors
|
||||||
|
can still be encountered during an EOF, so one must still be prepared
|
||||||
|
to receive them.
|
||||||
|
|
||||||
|
Scalar valued message information such as `status_code`, `method`, and the
|
||||||
|
HTTP version are stored in the parser structure. This data is only
|
||||||
|
temporally stored in `http_parser` and gets reset on each new message. If
|
||||||
|
this information is needed later, copy it out of the structure during the
|
||||||
|
`headers_complete` callback.
|
||||||
|
|
||||||
|
The parser decodes the transfer-encoding for both requests and responses
|
||||||
|
transparently. That is, a chunked encoding is decoded before being sent to
|
||||||
|
the on_body callback.
|
||||||
|
|
||||||
|
|
||||||
|
The Special Problem of Upgrade
|
||||||
|
------------------------------
|
||||||
|
|
||||||
|
`http_parser` supports upgrading the connection to a different protocol. An
|
||||||
|
increasingly common example of this is the WebSocket protocol which sends
|
||||||
|
a request like
|
||||||
|
|
||||||
|
GET /demo HTTP/1.1
|
||||||
|
Upgrade: WebSocket
|
||||||
|
Connection: Upgrade
|
||||||
|
Host: example.com
|
||||||
|
Origin: http://example.com
|
||||||
|
WebSocket-Protocol: sample
|
||||||
|
|
||||||
|
followed by non-HTTP data.
|
||||||
|
|
||||||
|
(See [RFC6455](https://tools.ietf.org/html/rfc6455) for more information the
|
||||||
|
WebSocket protocol.)
|
||||||
|
|
||||||
|
To support this, the parser will treat this as a normal HTTP message without a
|
||||||
|
body, issuing both on_headers_complete and on_message_complete callbacks. However
|
||||||
|
http_parser_execute() will stop parsing at the end of the headers and return.
|
||||||
|
|
||||||
|
The user is expected to check if `parser->upgrade` has been set to 1 after
|
||||||
|
`http_parser_execute()` returns. Non-HTTP data begins at the buffer supplied
|
||||||
|
offset by the return value of `http_parser_execute()`.
|
||||||
|
|
||||||
|
|
||||||
|
Callbacks
|
||||||
|
---------
|
||||||
|
|
||||||
|
During the `http_parser_execute()` call, the callbacks set in
|
||||||
|
`http_parser_settings` will be executed. The parser maintains state and
|
||||||
|
never looks behind, so buffering the data is not necessary. If you need to
|
||||||
|
save certain data for later usage, you can do that from the callbacks.
|
||||||
|
|
||||||
|
There are two types of callbacks:
|
||||||
|
|
||||||
|
* notification `typedef int (*http_cb) (http_parser*);`
|
||||||
|
Callbacks: on_message_begin, on_headers_complete, on_message_complete.
|
||||||
|
* data `typedef int (*http_data_cb) (http_parser*, const char *at, size_t length);`
|
||||||
|
Callbacks: (requests only) on_url,
|
||||||
|
(common) on_header_field, on_header_value, on_body;
|
||||||
|
|
||||||
|
Callbacks must return 0 on success. Returning a non-zero value indicates
|
||||||
|
error to the parser, making it exit immediately.
|
||||||
|
|
||||||
|
For cases where it is necessary to pass local information to/from a callback,
|
||||||
|
the `http_parser` object's `data` field can be used.
|
||||||
|
An example of such a case is when using threads to handle a socket connection,
|
||||||
|
parse a request, and then give a response over that socket. By instantiation
|
||||||
|
of a thread-local struct containing relevant data (e.g. accepted socket,
|
||||||
|
allocated memory for callbacks to write into, etc), a parser's callbacks are
|
||||||
|
able to communicate data between the scope of the thread and the scope of the
|
||||||
|
callback in a threadsafe manner. This allows `http_parser` to be used in
|
||||||
|
multi-threaded contexts.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
```c
|
||||||
|
typedef struct {
|
||||||
|
socket_t sock;
|
||||||
|
void* buffer;
|
||||||
|
int buf_len;
|
||||||
|
} custom_data_t;
|
||||||
|
|
||||||
|
|
||||||
|
int my_url_callback(http_parser* parser, const char *at, size_t length) {
|
||||||
|
/* access to thread local custom_data_t struct.
|
||||||
|
Use this access save parsed data for later use into thread local
|
||||||
|
buffer, or communicate over socket
|
||||||
|
*/
|
||||||
|
parser->data;
|
||||||
|
...
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
...
|
||||||
|
|
||||||
|
void http_parser_thread(socket_t sock) {
|
||||||
|
int nparsed = 0;
|
||||||
|
/* allocate memory for user data */
|
||||||
|
custom_data_t *my_data = malloc(sizeof(custom_data_t));
|
||||||
|
|
||||||
|
/* some information for use by callbacks.
|
||||||
|
* achieves thread -> callback information flow */
|
||||||
|
my_data->sock = sock;
|
||||||
|
|
||||||
|
/* instantiate a thread-local parser */
|
||||||
|
http_parser *parser = malloc(sizeof(http_parser));
|
||||||
|
http_parser_init(parser, HTTP_REQUEST); /* initialise parser */
|
||||||
|
/* this custom data reference is accessible through the reference to the
|
||||||
|
parser supplied to callback functions */
|
||||||
|
parser->data = my_data;
|
||||||
|
|
||||||
|
http_parser_settings settings; /* set up callbacks */
|
||||||
|
settings.on_url = my_url_callback;
|
||||||
|
|
||||||
|
/* execute parser */
|
||||||
|
nparsed = http_parser_execute(parser, &settings, buf, recved);
|
||||||
|
|
||||||
|
...
|
||||||
|
/* parsed information copied from callback.
|
||||||
|
can now perform action on data copied into thread-local memory from callbacks.
|
||||||
|
achieves callback -> thread information flow */
|
||||||
|
my_data->buffer;
|
||||||
|
...
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
In case you parse HTTP message in chunks (i.e. `read()` request line
|
||||||
|
from socket, parse, read half headers, parse, etc) your data callbacks
|
||||||
|
may be called more than once. `http_parser` guarantees that data pointer is only
|
||||||
|
valid for the lifetime of callback. You can also `read()` into a heap allocated
|
||||||
|
buffer to avoid copying memory around if this fits your application.
|
||||||
|
|
||||||
|
Reading headers may be a tricky task if you read/parse headers partially.
|
||||||
|
Basically, you need to remember whether last header callback was field or value
|
||||||
|
and apply the following logic:
|
||||||
|
|
||||||
|
(on_header_field and on_header_value shortened to on_h_*)
|
||||||
|
------------------------ ------------ --------------------------------------------
|
||||||
|
| State (prev. callback) | Callback | Description/action |
|
||||||
|
------------------------ ------------ --------------------------------------------
|
||||||
|
| nothing (first call) | on_h_field | Allocate new buffer and copy callback data |
|
||||||
|
| | | into it |
|
||||||
|
------------------------ ------------ --------------------------------------------
|
||||||
|
| value | on_h_field | New header started. |
|
||||||
|
| | | Copy current name,value buffers to headers |
|
||||||
|
| | | list and allocate new buffer for new name |
|
||||||
|
------------------------ ------------ --------------------------------------------
|
||||||
|
| field | on_h_field | Previous name continues. Reallocate name |
|
||||||
|
| | | buffer and append callback data to it |
|
||||||
|
------------------------ ------------ --------------------------------------------
|
||||||
|
| field | on_h_value | Value for current header started. Allocate |
|
||||||
|
| | | new buffer and copy callback data to it |
|
||||||
|
------------------------ ------------ --------------------------------------------
|
||||||
|
| value | on_h_value | Value continues. Reallocate value buffer |
|
||||||
|
| | | and append callback data to it |
|
||||||
|
------------------------ ------------ --------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
Parsing URLs
|
||||||
|
------------
|
||||||
|
|
||||||
|
A simplistic zero-copy URL parser is provided as `http_parser_parse_url()`.
|
||||||
|
Users of this library may wish to use it to parse URLs constructed from
|
||||||
|
consecutive `on_url` callbacks.
|
||||||
|
|
||||||
|
See examples of reading in headers:
|
||||||
|
|
||||||
|
* [partial example](http://gist.github.com/155877) in C
|
||||||
|
* [from http-parser tests](http://github.com/joyent/http-parser/blob/37a0ff8/test.c#L403) in C
|
||||||
|
* [from Node library](http://github.com/joyent/node/blob/842eaf4/src/http.js#L284) in Javascript
|
||||||
1
libraries/http-parser/lib/http-parser
Submodule
1
libraries/http-parser/lib/http-parser
Submodule
|
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit ec8b5ee63f0e51191ea43bb0c6eac7bfbff3141d
|
||||||
11
libraries/http-parser/library.properties
Normal file
11
libraries/http-parser/library.properties
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
name=http-parser
|
||||||
|
version=2.9.4
|
||||||
|
author=Node.JS team
|
||||||
|
maintainer=Earle F. Philhower, III <earlephilhower@yahoo.com>
|
||||||
|
sentence=HTTP parser from Node.js team, MIT license
|
||||||
|
paragraph=HTTP parser from Node.js team, MIT license
|
||||||
|
category=communications
|
||||||
|
url=https://github.com/earlephilhower/arduino-pico
|
||||||
|
architectures=rp2040
|
||||||
|
dot_a_linkage=true
|
||||||
|
includes=http_parser.h
|
||||||
1
libraries/http-parser/src/http_parser.c
Normal file
1
libraries/http-parser/src/http_parser.c
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
#include "../lib/http-parser/http_parser.c"
|
||||||
1
libraries/http-parser/src/http_parser.h
Normal file
1
libraries/http-parser/src/http_parser.h
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
#include "../lib/http-parser/http_parser.h"
|
||||||
|
|
@ -7,7 +7,8 @@ for dir in ./cores/rp2040 ./libraries/EEPROM ./libraries/I2S \
|
||||||
./libraries/WiFi ./libraries/lwIP_Ethernet ./libraries/lwIP_CYW43 \
|
./libraries/WiFi ./libraries/lwIP_Ethernet ./libraries/lwIP_CYW43 \
|
||||||
./libraries/FreeRTOS/src ./libraries/LEAmDNS ./libraries/MD5Builder \
|
./libraries/FreeRTOS/src ./libraries/LEAmDNS ./libraries/MD5Builder \
|
||||||
./libraries/PicoOTA ./libraries/SDFS ./libraries/ArduinoOTA \
|
./libraries/PicoOTA ./libraries/SDFS ./libraries/ArduinoOTA \
|
||||||
./libraries/Updater ./libraries/HTTPClient ./libraries/HTTPUpdate; do
|
./libraries/Updater ./libraries/HTTPClient ./libraries/HTTPUpdate \
|
||||||
|
./libraries/WebServer ./libraries/HTTPUpdateServer ; do
|
||||||
find $dir -type f \( -name "*.c" -o -name "*.h" -o -name "*.cpp" \) -a \! -path '*api*' -exec astyle --suffix=none --options=./tests/astyle_core.conf \{\} \;
|
find $dir -type f \( -name "*.c" -o -name "*.h" -o -name "*.cpp" \) -a \! -path '*api*' -exec astyle --suffix=none --options=./tests/astyle_core.conf \{\} \;
|
||||||
find $dir -type f -name "*.ino" -exec astyle --suffix=none --options=./tests/astyle_examples.conf \{\} \;
|
find $dir -type f -name "*.ino" -exec astyle --suffix=none --options=./tests/astyle_examples.conf \{\} \;
|
||||||
done
|
done
|
||||||
|
|
|
||||||
|
|
@ -65,7 +65,7 @@ def compile(tmp_dir, sketch, cache, tools_dir, hardware_dir, ide_path, f, args):
|
||||||
'dbgport={dbgport},' \
|
'dbgport={dbgport},' \
|
||||||
'dbglvl={dbglvl},' \
|
'dbglvl={dbglvl},' \
|
||||||
'usbstack={usbstack}'.format(**vars(args))
|
'usbstack={usbstack}'.format(**vars(args))
|
||||||
if ("/WiFi" in sketch) or ("/ArduinoOTA" in sketch) or ("/HTTPClient" in sketch) or ('/HTTPUpdate' in sketch):
|
if ("/WiFi" in sketch) or ("/ArduinoOTA" in sketch) or ("/HTTPClient" in sketch) or ('/HTTPUpdate' in sketch) or ('/WebServer' in sketch) or ('/DNSServer' in sketch):
|
||||||
fqbn = fqbn.replace("rpipico", "rpipicow")
|
fqbn = fqbn.replace("rpipico", "rpipicow")
|
||||||
cmd += [fqbn]
|
cmd += [fqbn]
|
||||||
cmd += ['-built-in-libraries', ide_path + '/libraries']
|
cmd += ['-built-in-libraries', ide_path + '/libraries']
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue