Added HTTP OTA update via ESPAsyncWebServer
This commit is contained in:
parent
5694ff7c97
commit
2af6af2bf0
@ -23,7 +23,7 @@
|
|||||||
--tCol: #328CC1;
|
--tCol: #328CC1;
|
||||||
--cFn: Verdana;
|
--cFn: Verdana;
|
||||||
}
|
}
|
||||||
button {
|
.bt {
|
||||||
background: var(--bCol);
|
background: var(--bCol);
|
||||||
color: var(--tCol);
|
color: var(--tCol);
|
||||||
border: 0.3ch solid var(--bCol);
|
border: 0.3ch solid var(--bCol);
|
||||||
@ -34,6 +34,9 @@
|
|||||||
margin: 8px;
|
margin: 8px;
|
||||||
margin-top: 12px;
|
margin-top: 12px;
|
||||||
}
|
}
|
||||||
|
input[type=file] {
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
body {
|
body {
|
||||||
font-family: var(--cFn), sans-serif;
|
font-family: var(--cFn), sans-serif;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
@ -47,6 +50,7 @@
|
|||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<h2>Sample message.</h2>
|
<h2>Sample message.</h2>
|
||||||
|
Sample detail.
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
@ -12,7 +12,7 @@ const char PAGE_msg[] PROGMEM = R"=====(<!DOCTYPE html>
|
|||||||
<html><head><meta content='width=device-width' name='viewport'>
|
<html><head><meta content='width=device-width' name='viewport'>
|
||||||
<title>WLED Message</title>
|
<title>WLED Message</title>
|
||||||
<script>function B(){window.history.back()};function RS(){window.location = "/settings";}function RP(){top.location.href="/";}</script>
|
<script>function B(){window.history.back()};function RS(){window.location = "/settings";}function RP(){top.location.href="/";}</script>
|
||||||
%CSS%button{background:var(--bCol);color:var(--tCol);font-family:var(--cFn),sans-serif;border:.3ch solid var(--bCol);display:inline-block;filter:drop-shadow(-5px -5px 5px var(--sCol));font-size:20px;margin:8px;margin-top:12px}body{font-family:var(--cFn),sans-serif;text-align:center;background:var(--cCol);color:var(--tCol);line-height:200%%;margin:0;background-attachment:fixed}</style></head>
|
%CSS%.bt{background:var(--bCol);color:var(--tCol);font-family:var(--cFn),sans-serif;border:.3ch solid var(--bCol);display:inline-block;filter:drop-shadow(-5px -5px 5px var(--sCol));font-size:20px;margin:8px;margin-top:12px}input[type=file]{font-size:16px}body{font-family:var(--cFn),sans-serif;text-align:center;background:var(--cCol);color:var(--tCol);line-height:200%%;margin:0;background-attachment:fixed}</style></head>
|
||||||
<body><h2>%MSG%</body></html>)=====";
|
<body><h2>%MSG%</body></html>)=====";
|
||||||
|
|
||||||
|
|
||||||
|
@ -10,7 +10,7 @@
|
|||||||
*/
|
*/
|
||||||
/*
|
/*
|
||||||
* @title Espalexa library
|
* @title Espalexa library
|
||||||
* @version 2.3.3
|
* @version 2.3.4
|
||||||
* @author Christian Schwinne
|
* @author Christian Schwinne
|
||||||
* @license MIT
|
* @license MIT
|
||||||
* @contributors d-999
|
* @contributors d-999
|
||||||
@ -37,7 +37,7 @@
|
|||||||
#else
|
#else
|
||||||
#ifdef ARDUINO_ARCH_ESP32
|
#ifdef ARDUINO_ARCH_ESP32
|
||||||
#include <WiFi.h>
|
#include <WiFi.h>
|
||||||
#include "../webserver/WebServer.h" //if you get an error here please update to ESP32 arduino core 1.0.0
|
#include <WebServer.h> //if you get an error here please update to ESP32 arduino core 1.0.0
|
||||||
#else
|
#else
|
||||||
#include <ESP8266WebServer.h>
|
#include <ESP8266WebServer.h>
|
||||||
#include <ESP8266WiFi.h>
|
#include <ESP8266WiFi.h>
|
||||||
@ -46,7 +46,7 @@
|
|||||||
#include <WiFiUdp.h>
|
#include <WiFiUdp.h>
|
||||||
|
|
||||||
#ifdef ESPALEXA_DEBUG
|
#ifdef ESPALEXA_DEBUG
|
||||||
#pragma message "Espalexa 2.3.3 debug mode"
|
#pragma message "Espalexa 2.3.4 debug mode"
|
||||||
#define EA_DEBUG(x) Serial.print (x)
|
#define EA_DEBUG(x) Serial.print (x)
|
||||||
#define EA_DEBUGLN(x) Serial.println (x)
|
#define EA_DEBUGLN(x) Serial.println (x)
|
||||||
#else
|
#else
|
||||||
@ -80,6 +80,7 @@ private:
|
|||||||
String escapedMac=""; //lowercase mac address
|
String escapedMac=""; //lowercase mac address
|
||||||
|
|
||||||
//private member functions
|
//private member functions
|
||||||
|
//device JSON string: color+temperature device emulates LCT015, dimmable device LWB010, (TODO: on/off Plug 01, color temperature device LWT010, color device LST001)
|
||||||
String deviceJsonString(uint8_t deviceId)
|
String deviceJsonString(uint8_t deviceId)
|
||||||
{
|
{
|
||||||
if (deviceId < 1 || deviceId > currentDeviceCount) return "{}"; //error
|
if (deviceId < 1 || deviceId > currentDeviceCount) return "{}"; //error
|
||||||
@ -89,7 +90,9 @@ private:
|
|||||||
json += "\",\"manufacturername\":\"OpenSource\",\"swversion\":\"0.1\",\"name\":\"";
|
json += "\",\"manufacturername\":\"OpenSource\",\"swversion\":\"0.1\",\"name\":\"";
|
||||||
json += dev->getName();
|
json += dev->getName();
|
||||||
json += "\",\"uniqueid\":\""+ WiFi.macAddress() +"-"+ (deviceId+1) ;
|
json += "\",\"uniqueid\":\""+ WiFi.macAddress() +"-"+ (deviceId+1) ;
|
||||||
json += "\",\"modelid\":\"LST001\",\"state\":{\"on\":";
|
json += "\",\"modelid\":\"";
|
||||||
|
json += dev->isColorDevice() ? "LCT015" : "LWB010";
|
||||||
|
json += "\",\"state\":{\"on\":";
|
||||||
json += boolString(dev->getValue()) +",\"bri\":"+ (String)(dev->getLastValue()-1) ;
|
json += boolString(dev->getValue()) +",\"bri\":"+ (String)(dev->getLastValue()-1) ;
|
||||||
if (dev->isColorDevice())
|
if (dev->isColorDevice())
|
||||||
{
|
{
|
||||||
@ -112,7 +115,7 @@ private:
|
|||||||
}
|
}
|
||||||
res += "\r\nFree Heap: " + (String)ESP.getFreeHeap();
|
res += "\r\nFree Heap: " + (String)ESP.getFreeHeap();
|
||||||
res += "\r\nUptime: " + (String)millis();
|
res += "\r\nUptime: " + (String)millis();
|
||||||
res += "\r\n\r\nEspalexa library v2.3.3 by Christian Schwinne 2019";
|
res += "\r\n\r\nEspalexa library v2.3.4 by Christian Schwinne 2019";
|
||||||
server->send(200, "text/plain", res);
|
server->send(200, "text/plain", res);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -146,7 +149,7 @@ private:
|
|||||||
"<URLBase>http://"+ String(s) +":80/</URLBase>"
|
"<URLBase>http://"+ String(s) +":80/</URLBase>"
|
||||||
"<device>"
|
"<device>"
|
||||||
"<deviceType>urn:schemas-upnp-org:device:Basic:1</deviceType>"
|
"<deviceType>urn:schemas-upnp-org:device:Basic:1</deviceType>"
|
||||||
"<friendlyName>Philips hue ("+ String(s) +")</friendlyName>"
|
"<friendlyName>Espalexa ("+ String(s) +")</friendlyName>"
|
||||||
"<manufacturer>Royal Philips Electronics</manufacturer>"
|
"<manufacturer>Royal Philips Electronics</manufacturer>"
|
||||||
"<manufacturerURL>http://www.philips.com</manufacturerURL>"
|
"<manufacturerURL>http://www.philips.com</manufacturerURL>"
|
||||||
"<modelDescription>Philips hue Personal Wireless Lighting</modelDescription>"
|
"<modelDescription>Philips hue Personal Wireless Lighting</modelDescription>"
|
||||||
@ -164,13 +167,6 @@ private:
|
|||||||
" <depth>24</depth>"
|
" <depth>24</depth>"
|
||||||
" <url>hue_logo_0.png</url>"
|
" <url>hue_logo_0.png</url>"
|
||||||
" </icon>"
|
" </icon>"
|
||||||
" <icon>"
|
|
||||||
" <mimetype>image/png</mimetype>"
|
|
||||||
" <height>120</height>"
|
|
||||||
" <width>120</width>"
|
|
||||||
" <depth>24</depth>"
|
|
||||||
" <url>hue_logo_3.png</url>"
|
|
||||||
" </icon>"
|
|
||||||
"</iconList>"
|
"</iconList>"
|
||||||
"</device>"
|
"</device>"
|
||||||
"</root>";
|
"</root>";
|
||||||
|
@ -1,105 +0,0 @@
|
|||||||
#include <Arduino.h>
|
|
||||||
#include <WiFiClient.h>
|
|
||||||
#include <WiFiServer.h>
|
|
||||||
#ifdef ARDUINO_ARCH_ESP32
|
|
||||||
#include "WebServer.h"
|
|
||||||
#include <Update.h>
|
|
||||||
#else
|
|
||||||
#include <ESP8266WebServer.h>
|
|
||||||
#endif
|
|
||||||
#include <WiFiUdp.h>
|
|
||||||
#include "ESP8266HTTPUpdateServer.h"
|
|
||||||
|
|
||||||
|
|
||||||
const char* ESP8266HTTPUpdateServer::_serverIndex =
|
|
||||||
R"(<html><head><script>function B(){window.history.back()}</script></head><body><h2>WLED Software Update</h2><br>Get the latest binaries on the <a href="https://github.com/Aircoookie/WLED/tree/master/bin">project GitHub page</a>!<br>
|
|
||||||
<i>Unsure which binary is correct? Go to the <a href="./build">/build subpage</a> for the details of this version.</i><br>
|
|
||||||
<b>Be sure to upload a valid .bin file for your ESP! Otherwise you'll need USB recovery!</b><br>
|
|
||||||
<br><br><button onclick='B()'>Back</button><br><br>
|
|
||||||
<form method='POST' action='' enctype='multipart/form-data'>
|
|
||||||
<input type='file' name='update'>
|
|
||||||
<input type='submit' value='Update!'>
|
|
||||||
</form>
|
|
||||||
</body></html>)";
|
|
||||||
const char* ESP8266HTTPUpdateServer::_failedResponse = R"(Update Failed!)";
|
|
||||||
const char* ESP8266HTTPUpdateServer::_successResponse = R"(<script>setTimeout(function(){top.location.href="/";},20000);</script>Update Successful! Rebooting, please wait for redirect...)";
|
|
||||||
|
|
||||||
ESP8266HTTPUpdateServer::ESP8266HTTPUpdateServer(bool serial_debug)
|
|
||||||
{
|
|
||||||
_serial_output = serial_debug;
|
|
||||||
_server = NULL;
|
|
||||||
_username = NULL;
|
|
||||||
_password = NULL;
|
|
||||||
_authenticated = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef ARDUINO_ARCH_ESP32
|
|
||||||
void ESP8266HTTPUpdateServer::setup(WebServer *server, const char * path, const char * username, const char * password)
|
|
||||||
#else
|
|
||||||
void ESP8266HTTPUpdateServer::setup(ESP8266WebServer *server, const char * path, const char * username, const char * password)
|
|
||||||
#endif
|
|
||||||
{
|
|
||||||
_server = server;
|
|
||||||
_username = (char *)username;
|
|
||||||
_password = (char *)password;
|
|
||||||
|
|
||||||
// handler for the /update form page
|
|
||||||
_server->on(path, HTTP_GET, [&](){
|
|
||||||
if(_username != NULL && _password != NULL && !_server->authenticate(_username, _password))
|
|
||||||
return _server->requestAuthentication();
|
|
||||||
_server->send(200, "text/html", _serverIndex);
|
|
||||||
});
|
|
||||||
|
|
||||||
// handler for the /update form POST (once file upload finishes)
|
|
||||||
_server->on(path, HTTP_POST, [&](){
|
|
||||||
if(!_authenticated)
|
|
||||||
return _server->requestAuthentication();
|
|
||||||
_server->send(200, "text/html", Update.hasError() ? _failedResponse : _successResponse);
|
|
||||||
ESP.restart();
|
|
||||||
},[&](){
|
|
||||||
// handler for the file upload, get's the sketch bytes, and writes
|
|
||||||
// them through the Update object
|
|
||||||
HTTPUpload& upload = _server->upload();
|
|
||||||
if(upload.status == UPLOAD_FILE_START){
|
|
||||||
if (_serial_output)
|
|
||||||
Serial.setDebugOutput(true);
|
|
||||||
|
|
||||||
_authenticated = (_username == NULL || _password == NULL || _server->authenticate(_username, _password));
|
|
||||||
if(!_authenticated){
|
|
||||||
if (_serial_output)
|
|
||||||
Serial.printf("Unauthenticated Update\n");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
#ifndef ARDUINO_ARCH_ESP32
|
|
||||||
WiFiUDP::stopAll();
|
|
||||||
#endif
|
|
||||||
if (_serial_output)
|
|
||||||
Serial.printf("Update: %s\n", upload.filename.c_str());
|
|
||||||
#ifdef ARDUINO_ARCH_ESP32
|
|
||||||
uint32_t maxSketchSpace = 0x100000; //dirty workaround, limit to 1MB
|
|
||||||
#else
|
|
||||||
uint32_t maxSketchSpace = (ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000;
|
|
||||||
#endif
|
|
||||||
if(!Update.begin(maxSketchSpace)){//start with max available size
|
|
||||||
if (_serial_output) Update.printError(Serial);
|
|
||||||
}
|
|
||||||
} else if(_authenticated && upload.status == UPLOAD_FILE_WRITE){
|
|
||||||
if (_serial_output) Serial.printf(".");
|
|
||||||
if(Update.write(upload.buf, upload.currentSize) != upload.currentSize){
|
|
||||||
if (_serial_output) Update.printError(Serial);
|
|
||||||
|
|
||||||
}
|
|
||||||
} else if(_authenticated && upload.status == UPLOAD_FILE_END){
|
|
||||||
if(Update.end(true)){ //true to set the size to the current progress
|
|
||||||
if (_serial_output) Serial.printf("Update Success: %u\nRebooting...\n", upload.totalSize);
|
|
||||||
} else {
|
|
||||||
if (_serial_output) Update.printError(Serial);
|
|
||||||
}
|
|
||||||
if (_serial_output) Serial.setDebugOutput(false);
|
|
||||||
} else if(_authenticated && upload.status == UPLOAD_FILE_ABORTED){
|
|
||||||
Update.end();
|
|
||||||
if (_serial_output) Serial.println("Update was aborted");
|
|
||||||
}
|
|
||||||
delay(0);
|
|
||||||
});
|
|
||||||
}
|
|
@ -1,74 +0,0 @@
|
|||||||
#ifndef __HTTP_UPDATE_SERVER_H
|
|
||||||
#define __HTTP_UPDATE_SERVER_H
|
|
||||||
|
|
||||||
#ifdef ARDUINO_ARCH_ESP32
|
|
||||||
class WebServer;
|
|
||||||
|
|
||||||
class ESP8266HTTPUpdateServer
|
|
||||||
{
|
|
||||||
private:
|
|
||||||
bool _serial_output;
|
|
||||||
WebServer *_server;
|
|
||||||
static const char *_serverIndex;
|
|
||||||
static const char *_failedResponse;
|
|
||||||
static const char *_successResponse;
|
|
||||||
char * _username;
|
|
||||||
char * _password;
|
|
||||||
bool _authenticated;
|
|
||||||
public:
|
|
||||||
ESP8266HTTPUpdateServer(bool serial_debug=false);
|
|
||||||
|
|
||||||
void setup(WebServer *server)
|
|
||||||
{
|
|
||||||
setup(server, NULL, NULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
void setup(WebServer *server, const char * path)
|
|
||||||
{
|
|
||||||
setup(server, path, NULL, NULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
void setup(WebServer *server, const char * username, const char * password)
|
|
||||||
{
|
|
||||||
setup(server, "/update", username, password);
|
|
||||||
}
|
|
||||||
|
|
||||||
void setup(WebServer *server, const char * path, const char * username, const char * password);
|
|
||||||
};
|
|
||||||
#else
|
|
||||||
class ESP8266WebServer;
|
|
||||||
|
|
||||||
class ESP8266HTTPUpdateServer
|
|
||||||
{
|
|
||||||
private:
|
|
||||||
bool _serial_output;
|
|
||||||
ESP8266WebServer *_server;
|
|
||||||
static const char *_serverIndex;
|
|
||||||
static const char *_failedResponse;
|
|
||||||
static const char *_successResponse;
|
|
||||||
char * _username;
|
|
||||||
char * _password;
|
|
||||||
bool _authenticated;
|
|
||||||
public:
|
|
||||||
ESP8266HTTPUpdateServer(bool serial_debug=false);
|
|
||||||
|
|
||||||
void setup(ESP8266WebServer *server)
|
|
||||||
{
|
|
||||||
setup(server, NULL, NULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
void setup(ESP8266WebServer *server, const char * path)
|
|
||||||
{
|
|
||||||
setup(server, path, NULL, NULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
void setup(ESP8266WebServer *server, const char * username, const char * password)
|
|
||||||
{
|
|
||||||
setup(server, "/update", username, password);
|
|
||||||
}
|
|
||||||
|
|
||||||
void setup(ESP8266WebServer *server, const char * path, const char * username, const char * password);
|
|
||||||
};
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#endif
|
|
@ -1,29 +0,0 @@
|
|||||||
/*
|
|
||||||
ESP8266WebServer.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)
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
#ifndef ESP8266WEBSERVER_H
|
|
||||||
#define ESP8266WEBSERVER_H
|
|
||||||
|
|
||||||
#include "WebServer.h"
|
|
||||||
|
|
||||||
#endif //ESP8266WEBSERVER_H
|
|
@ -1,504 +0,0 @@
|
|||||||
GNU LESSER GENERAL PUBLIC LICENSE
|
|
||||||
Version 2.1, February 1999
|
|
||||||
|
|
||||||
Copyright (C) 1991, 1999 Free Software Foundation, Inc.
|
|
||||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
||||||
Everyone is permitted to copy and distribute verbatim copies
|
|
||||||
of this license document, but changing it is not allowed.
|
|
||||||
|
|
||||||
(This is the first released version of the Lesser GPL. It also counts
|
|
||||||
as the successor of the GNU Library Public License, version 2, hence
|
|
||||||
the version number 2.1.)
|
|
||||||
|
|
||||||
Preamble
|
|
||||||
|
|
||||||
The licenses for most software are designed to take away your
|
|
||||||
freedom to share and change it. By contrast, the GNU General Public
|
|
||||||
Licenses are intended to guarantee your freedom to share and change
|
|
||||||
free software--to make sure the software is free for all its users.
|
|
||||||
|
|
||||||
This license, the Lesser General Public License, applies to some
|
|
||||||
specially designated software packages--typically libraries--of the
|
|
||||||
Free Software Foundation and other authors who decide to use it. You
|
|
||||||
can use it too, but we suggest you first think carefully about whether
|
|
||||||
this license or the ordinary General Public License is the better
|
|
||||||
strategy to use in any particular case, based on the explanations below.
|
|
||||||
|
|
||||||
When we speak of free software, we are referring to freedom of use,
|
|
||||||
not price. Our General Public Licenses are designed to make sure that
|
|
||||||
you have the freedom to distribute copies of free software (and charge
|
|
||||||
for this service if you wish); that you receive source code or can get
|
|
||||||
it if you want it; that you can change the software and use pieces of
|
|
||||||
it in new free programs; and that you are informed that you can do
|
|
||||||
these things.
|
|
||||||
|
|
||||||
To protect your rights, we need to make restrictions that forbid
|
|
||||||
distributors to deny you these rights or to ask you to surrender these
|
|
||||||
rights. These restrictions translate to certain responsibilities for
|
|
||||||
you if you distribute copies of the library or if you modify it.
|
|
||||||
|
|
||||||
For example, if you distribute copies of the library, whether gratis
|
|
||||||
or for a fee, you must give the recipients all the rights that we gave
|
|
||||||
you. You must make sure that they, too, receive or can get the source
|
|
||||||
code. If you link other code with the library, you must provide
|
|
||||||
complete object files to the recipients, so that they can relink them
|
|
||||||
with the library after making changes to the library and recompiling
|
|
||||||
it. And you must show them these terms so they know their rights.
|
|
||||||
|
|
||||||
We protect your rights with a two-step method: (1) we copyright the
|
|
||||||
library, and (2) we offer you this license, which gives you legal
|
|
||||||
permission to copy, distribute and/or modify the library.
|
|
||||||
|
|
||||||
To protect each distributor, we want to make it very clear that
|
|
||||||
there is no warranty for the free library. Also, if the library is
|
|
||||||
modified by someone else and passed on, the recipients should know
|
|
||||||
that what they have is not the original version, so that the original
|
|
||||||
author's reputation will not be affected by problems that might be
|
|
||||||
introduced by others.
|
|
||||||
|
|
||||||
Finally, software patents pose a constant threat to the existence of
|
|
||||||
any free program. We wish to make sure that a company cannot
|
|
||||||
effectively restrict the users of a free program by obtaining a
|
|
||||||
restrictive license from a patent holder. Therefore, we insist that
|
|
||||||
any patent license obtained for a version of the library must be
|
|
||||||
consistent with the full freedom of use specified in this license.
|
|
||||||
|
|
||||||
Most GNU software, including some libraries, is covered by the
|
|
||||||
ordinary GNU General Public License. This license, the GNU Lesser
|
|
||||||
General Public License, applies to certain designated libraries, and
|
|
||||||
is quite different from the ordinary General Public License. We use
|
|
||||||
this license for certain libraries in order to permit linking those
|
|
||||||
libraries into non-free programs.
|
|
||||||
|
|
||||||
When a program is linked with a library, whether statically or using
|
|
||||||
a shared library, the combination of the two is legally speaking a
|
|
||||||
combined work, a derivative of the original library. The ordinary
|
|
||||||
General Public License therefore permits such linking only if the
|
|
||||||
entire combination fits its criteria of freedom. The Lesser General
|
|
||||||
Public License permits more lax criteria for linking other code with
|
|
||||||
the library.
|
|
||||||
|
|
||||||
We call this license the "Lesser" General Public License because it
|
|
||||||
does Less to protect the user's freedom than the ordinary General
|
|
||||||
Public License. It also provides other free software developers Less
|
|
||||||
of an advantage over competing non-free programs. These disadvantages
|
|
||||||
are the reason we use the ordinary General Public License for many
|
|
||||||
libraries. However, the Lesser license provides advantages in certain
|
|
||||||
special circumstances.
|
|
||||||
|
|
||||||
For example, on rare occasions, there may be a special need to
|
|
||||||
encourage the widest possible use of a certain library, so that it becomes
|
|
||||||
a de-facto standard. To achieve this, non-free programs must be
|
|
||||||
allowed to use the library. A more frequent case is that a free
|
|
||||||
library does the same job as widely used non-free libraries. In this
|
|
||||||
case, there is little to gain by limiting the free library to free
|
|
||||||
software only, so we use the Lesser General Public License.
|
|
||||||
|
|
||||||
In other cases, permission to use a particular library in non-free
|
|
||||||
programs enables a greater number of people to use a large body of
|
|
||||||
free software. For example, permission to use the GNU C Library in
|
|
||||||
non-free programs enables many more people to use the whole GNU
|
|
||||||
operating system, as well as its variant, the GNU/Linux operating
|
|
||||||
system.
|
|
||||||
|
|
||||||
Although the Lesser General Public License is Less protective of the
|
|
||||||
users' freedom, it does ensure that the user of a program that is
|
|
||||||
linked with the Library has the freedom and the wherewithal to run
|
|
||||||
that program using a modified version of the Library.
|
|
||||||
|
|
||||||
The precise terms and conditions for copying, distribution and
|
|
||||||
modification follow. Pay close attention to the difference between a
|
|
||||||
"work based on the library" and a "work that uses the library". The
|
|
||||||
former contains code derived from the library, whereas the latter must
|
|
||||||
be combined with the library in order to run.
|
|
||||||
|
|
||||||
GNU LESSER GENERAL PUBLIC LICENSE
|
|
||||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
|
||||||
|
|
||||||
0. This License Agreement applies to any software library or other
|
|
||||||
program which contains a notice placed by the copyright holder or
|
|
||||||
other authorized party saying it may be distributed under the terms of
|
|
||||||
this Lesser General Public License (also called "this License").
|
|
||||||
Each licensee is addressed as "you".
|
|
||||||
|
|
||||||
A "library" means a collection of software functions and/or data
|
|
||||||
prepared so as to be conveniently linked with application programs
|
|
||||||
(which use some of those functions and data) to form executables.
|
|
||||||
|
|
||||||
The "Library", below, refers to any such software library or work
|
|
||||||
which has been distributed under these terms. A "work based on the
|
|
||||||
Library" means either the Library or any derivative work under
|
|
||||||
copyright law: that is to say, a work containing the Library or a
|
|
||||||
portion of it, either verbatim or with modifications and/or translated
|
|
||||||
straightforwardly into another language. (Hereinafter, translation is
|
|
||||||
included without limitation in the term "modification".)
|
|
||||||
|
|
||||||
"Source code" for a work means the preferred form of the work for
|
|
||||||
making modifications to it. For a library, complete source code means
|
|
||||||
all the source code for all modules it contains, plus any associated
|
|
||||||
interface definition files, plus the scripts used to control compilation
|
|
||||||
and installation of the library.
|
|
||||||
|
|
||||||
Activities other than copying, distribution and modification are not
|
|
||||||
covered by this License; they are outside its scope. The act of
|
|
||||||
running a program using the Library is not restricted, and output from
|
|
||||||
such a program is covered only if its contents constitute a work based
|
|
||||||
on the Library (independent of the use of the Library in a tool for
|
|
||||||
writing it). Whether that is true depends on what the Library does
|
|
||||||
and what the program that uses the Library does.
|
|
||||||
|
|
||||||
1. You may copy and distribute verbatim copies of the Library's
|
|
||||||
complete source code as you receive it, in any medium, provided that
|
|
||||||
you conspicuously and appropriately publish on each copy an
|
|
||||||
appropriate copyright notice and disclaimer of warranty; keep intact
|
|
||||||
all the notices that refer to this License and to the absence of any
|
|
||||||
warranty; and distribute a copy of this License along with the
|
|
||||||
Library.
|
|
||||||
|
|
||||||
You may charge a fee for the physical act of transferring a copy,
|
|
||||||
and you may at your option offer warranty protection in exchange for a
|
|
||||||
fee.
|
|
||||||
|
|
||||||
2. You may modify your copy or copies of the Library or any portion
|
|
||||||
of it, thus forming a work based on the Library, and copy and
|
|
||||||
distribute such modifications or work under the terms of Section 1
|
|
||||||
above, provided that you also meet all of these conditions:
|
|
||||||
|
|
||||||
a) The modified work must itself be a software library.
|
|
||||||
|
|
||||||
b) You must cause the files modified to carry prominent notices
|
|
||||||
stating that you changed the files and the date of any change.
|
|
||||||
|
|
||||||
c) You must cause the whole of the work to be licensed at no
|
|
||||||
charge to all third parties under the terms of this License.
|
|
||||||
|
|
||||||
d) If a facility in the modified Library refers to a function or a
|
|
||||||
table of data to be supplied by an application program that uses
|
|
||||||
the facility, other than as an argument passed when the facility
|
|
||||||
is invoked, then you must make a good faith effort to ensure that,
|
|
||||||
in the event an application does not supply such function or
|
|
||||||
table, the facility still operates, and performs whatever part of
|
|
||||||
its purpose remains meaningful.
|
|
||||||
|
|
||||||
(For example, a function in a library to compute square roots has
|
|
||||||
a purpose that is entirely well-defined independent of the
|
|
||||||
application. Therefore, Subsection 2d requires that any
|
|
||||||
application-supplied function or table used by this function must
|
|
||||||
be optional: if the application does not supply it, the square
|
|
||||||
root function must still compute square roots.)
|
|
||||||
|
|
||||||
These requirements apply to the modified work as a whole. If
|
|
||||||
identifiable sections of that work are not derived from the Library,
|
|
||||||
and can be reasonably considered independent and separate works in
|
|
||||||
themselves, then this License, and its terms, do not apply to those
|
|
||||||
sections when you distribute them as separate works. But when you
|
|
||||||
distribute the same sections as part of a whole which is a work based
|
|
||||||
on the Library, the distribution of the whole must be on the terms of
|
|
||||||
this License, whose permissions for other licensees extend to the
|
|
||||||
entire whole, and thus to each and every part regardless of who wrote
|
|
||||||
it.
|
|
||||||
|
|
||||||
Thus, it is not the intent of this section to claim rights or contest
|
|
||||||
your rights to work written entirely by you; rather, the intent is to
|
|
||||||
exercise the right to control the distribution of derivative or
|
|
||||||
collective works based on the Library.
|
|
||||||
|
|
||||||
In addition, mere aggregation of another work not based on the Library
|
|
||||||
with the Library (or with a work based on the Library) on a volume of
|
|
||||||
a storage or distribution medium does not bring the other work under
|
|
||||||
the scope of this License.
|
|
||||||
|
|
||||||
3. You may opt to apply the terms of the ordinary GNU General Public
|
|
||||||
License instead of this License to a given copy of the Library. To do
|
|
||||||
this, you must alter all the notices that refer to this License, so
|
|
||||||
that they refer to the ordinary GNU General Public License, version 2,
|
|
||||||
instead of to this License. (If a newer version than version 2 of the
|
|
||||||
ordinary GNU General Public License has appeared, then you can specify
|
|
||||||
that version instead if you wish.) Do not make any other change in
|
|
||||||
these notices.
|
|
||||||
|
|
||||||
Once this change is made in a given copy, it is irreversible for
|
|
||||||
that copy, so the ordinary GNU General Public License applies to all
|
|
||||||
subsequent copies and derivative works made from that copy.
|
|
||||||
|
|
||||||
This option is useful when you wish to copy part of the code of
|
|
||||||
the Library into a program that is not a library.
|
|
||||||
|
|
||||||
4. You may copy and distribute the Library (or a portion or
|
|
||||||
derivative of it, under Section 2) in object code or executable form
|
|
||||||
under the terms of Sections 1 and 2 above provided that you accompany
|
|
||||||
it with the complete corresponding machine-readable source code, which
|
|
||||||
must be distributed under the terms of Sections 1 and 2 above on a
|
|
||||||
medium customarily used for software interchange.
|
|
||||||
|
|
||||||
If distribution of object code is made by offering access to copy
|
|
||||||
from a designated place, then offering equivalent access to copy the
|
|
||||||
source code from the same place satisfies the requirement to
|
|
||||||
distribute the source code, even though third parties are not
|
|
||||||
compelled to copy the source along with the object code.
|
|
||||||
|
|
||||||
5. A program that contains no derivative of any portion of the
|
|
||||||
Library, but is designed to work with the Library by being compiled or
|
|
||||||
linked with it, is called a "work that uses the Library". Such a
|
|
||||||
work, in isolation, is not a derivative work of the Library, and
|
|
||||||
therefore falls outside the scope of this License.
|
|
||||||
|
|
||||||
However, linking a "work that uses the Library" with the Library
|
|
||||||
creates an executable that is a derivative of the Library (because it
|
|
||||||
contains portions of the Library), rather than a "work that uses the
|
|
||||||
library". The executable is therefore covered by this License.
|
|
||||||
Section 6 states terms for distribution of such executables.
|
|
||||||
|
|
||||||
When a "work that uses the Library" uses material from a header file
|
|
||||||
that is part of the Library, the object code for the work may be a
|
|
||||||
derivative work of the Library even though the source code is not.
|
|
||||||
Whether this is true is especially significant if the work can be
|
|
||||||
linked without the Library, or if the work is itself a library. The
|
|
||||||
threshold for this to be true is not precisely defined by law.
|
|
||||||
|
|
||||||
If such an object file uses only numerical parameters, data
|
|
||||||
structure layouts and accessors, and small macros and small inline
|
|
||||||
functions (ten lines or less in length), then the use of the object
|
|
||||||
file is unrestricted, regardless of whether it is legally a derivative
|
|
||||||
work. (Executables containing this object code plus portions of the
|
|
||||||
Library will still fall under Section 6.)
|
|
||||||
|
|
||||||
Otherwise, if the work is a derivative of the Library, you may
|
|
||||||
distribute the object code for the work under the terms of Section 6.
|
|
||||||
Any executables containing that work also fall under Section 6,
|
|
||||||
whether or not they are linked directly with the Library itself.
|
|
||||||
|
|
||||||
6. As an exception to the Sections above, you may also combine or
|
|
||||||
link a "work that uses the Library" with the Library to produce a
|
|
||||||
work containing portions of the Library, and distribute that work
|
|
||||||
under terms of your choice, provided that the terms permit
|
|
||||||
modification of the work for the customer's own use and reverse
|
|
||||||
engineering for debugging such modifications.
|
|
||||||
|
|
||||||
You must give prominent notice with each copy of the work that the
|
|
||||||
Library is used in it and that the Library and its use are covered by
|
|
||||||
this License. You must supply a copy of this License. If the work
|
|
||||||
during execution displays copyright notices, you must include the
|
|
||||||
copyright notice for the Library among them, as well as a reference
|
|
||||||
directing the user to the copy of this License. Also, you must do one
|
|
||||||
of these things:
|
|
||||||
|
|
||||||
a) Accompany the work with the complete corresponding
|
|
||||||
machine-readable source code for the Library including whatever
|
|
||||||
changes were used in the work (which must be distributed under
|
|
||||||
Sections 1 and 2 above); and, if the work is an executable linked
|
|
||||||
with the Library, with the complete machine-readable "work that
|
|
||||||
uses the Library", as object code and/or source code, so that the
|
|
||||||
user can modify the Library and then relink to produce a modified
|
|
||||||
executable containing the modified Library. (It is understood
|
|
||||||
that the user who changes the contents of definitions files in the
|
|
||||||
Library will not necessarily be able to recompile the application
|
|
||||||
to use the modified definitions.)
|
|
||||||
|
|
||||||
b) Use a suitable shared library mechanism for linking with the
|
|
||||||
Library. A suitable mechanism is one that (1) uses at run time a
|
|
||||||
copy of the library already present on the user's computer system,
|
|
||||||
rather than copying library functions into the executable, and (2)
|
|
||||||
will operate properly with a modified version of the library, if
|
|
||||||
the user installs one, as long as the modified version is
|
|
||||||
interface-compatible with the version that the work was made with.
|
|
||||||
|
|
||||||
c) Accompany the work with a written offer, valid for at
|
|
||||||
least three years, to give the same user the materials
|
|
||||||
specified in Subsection 6a, above, for a charge no more
|
|
||||||
than the cost of performing this distribution.
|
|
||||||
|
|
||||||
d) If distribution of the work is made by offering access to copy
|
|
||||||
from a designated place, offer equivalent access to copy the above
|
|
||||||
specified materials from the same place.
|
|
||||||
|
|
||||||
e) Verify that the user has already received a copy of these
|
|
||||||
materials or that you have already sent this user a copy.
|
|
||||||
|
|
||||||
For an executable, the required form of the "work that uses the
|
|
||||||
Library" must include any data and utility programs needed for
|
|
||||||
reproducing the executable from it. However, as a special exception,
|
|
||||||
the materials to be distributed need not include anything that is
|
|
||||||
normally distributed (in either source or binary form) with the major
|
|
||||||
components (compiler, kernel, and so on) of the operating system on
|
|
||||||
which the executable runs, unless that component itself accompanies
|
|
||||||
the executable.
|
|
||||||
|
|
||||||
It may happen that this requirement contradicts the license
|
|
||||||
restrictions of other proprietary libraries that do not normally
|
|
||||||
accompany the operating system. Such a contradiction means you cannot
|
|
||||||
use both them and the Library together in an executable that you
|
|
||||||
distribute.
|
|
||||||
|
|
||||||
7. You may place library facilities that are a work based on the
|
|
||||||
Library side-by-side in a single library together with other library
|
|
||||||
facilities not covered by this License, and distribute such a combined
|
|
||||||
library, provided that the separate distribution of the work based on
|
|
||||||
the Library and of the other library facilities is otherwise
|
|
||||||
permitted, and provided that you do these two things:
|
|
||||||
|
|
||||||
a) Accompany the combined library with a copy of the same work
|
|
||||||
based on the Library, uncombined with any other library
|
|
||||||
facilities. This must be distributed under the terms of the
|
|
||||||
Sections above.
|
|
||||||
|
|
||||||
b) Give prominent notice with the combined library of the fact
|
|
||||||
that part of it is a work based on the Library, and explaining
|
|
||||||
where to find the accompanying uncombined form of the same work.
|
|
||||||
|
|
||||||
8. You may not copy, modify, sublicense, link with, or distribute
|
|
||||||
the Library except as expressly provided under this License. Any
|
|
||||||
attempt otherwise to copy, modify, sublicense, link with, or
|
|
||||||
distribute the Library is void, and will automatically terminate your
|
|
||||||
rights under this License. However, parties who have received copies,
|
|
||||||
or rights, from you under this License will not have their licenses
|
|
||||||
terminated so long as such parties remain in full compliance.
|
|
||||||
|
|
||||||
9. You are not required to accept this License, since you have not
|
|
||||||
signed it. However, nothing else grants you permission to modify or
|
|
||||||
distribute the Library or its derivative works. These actions are
|
|
||||||
prohibited by law if you do not accept this License. Therefore, by
|
|
||||||
modifying or distributing the Library (or any work based on the
|
|
||||||
Library), you indicate your acceptance of this License to do so, and
|
|
||||||
all its terms and conditions for copying, distributing or modifying
|
|
||||||
the Library or works based on it.
|
|
||||||
|
|
||||||
10. Each time you redistribute the Library (or any work based on the
|
|
||||||
Library), the recipient automatically receives a license from the
|
|
||||||
original licensor to copy, distribute, link with or modify the Library
|
|
||||||
subject to these terms and conditions. You may not impose any further
|
|
||||||
restrictions on the recipients' exercise of the rights granted herein.
|
|
||||||
You are not responsible for enforcing compliance by third parties with
|
|
||||||
this License.
|
|
||||||
|
|
||||||
11. If, as a consequence of a court judgment or allegation of patent
|
|
||||||
infringement or for any other reason (not limited to patent issues),
|
|
||||||
conditions are imposed on you (whether by court order, agreement or
|
|
||||||
otherwise) that contradict the conditions of this License, they do not
|
|
||||||
excuse you from the conditions of this License. If you cannot
|
|
||||||
distribute so as to satisfy simultaneously your obligations under this
|
|
||||||
License and any other pertinent obligations, then as a consequence you
|
|
||||||
may not distribute the Library at all. For example, if a patent
|
|
||||||
license would not permit royalty-free redistribution of the Library by
|
|
||||||
all those who receive copies directly or indirectly through you, then
|
|
||||||
the only way you could satisfy both it and this License would be to
|
|
||||||
refrain entirely from distribution of the Library.
|
|
||||||
|
|
||||||
If any portion of this section is held invalid or unenforceable under any
|
|
||||||
particular circumstance, the balance of the section is intended to apply,
|
|
||||||
and the section as a whole is intended to apply in other circumstances.
|
|
||||||
|
|
||||||
It is not the purpose of this section to induce you to infringe any
|
|
||||||
patents or other property right claims or to contest validity of any
|
|
||||||
such claims; this section has the sole purpose of protecting the
|
|
||||||
integrity of the free software distribution system which is
|
|
||||||
implemented by public license practices. Many people have made
|
|
||||||
generous contributions to the wide range of software distributed
|
|
||||||
through that system in reliance on consistent application of that
|
|
||||||
system; it is up to the author/donor to decide if he or she is willing
|
|
||||||
to distribute software through any other system and a licensee cannot
|
|
||||||
impose that choice.
|
|
||||||
|
|
||||||
This section is intended to make thoroughly clear what is believed to
|
|
||||||
be a consequence of the rest of this License.
|
|
||||||
|
|
||||||
12. If the distribution and/or use of the Library is restricted in
|
|
||||||
certain countries either by patents or by copyrighted interfaces, the
|
|
||||||
original copyright holder who places the Library under this License may add
|
|
||||||
an explicit geographical distribution limitation excluding those countries,
|
|
||||||
so that distribution is permitted only in or among countries not thus
|
|
||||||
excluded. In such case, this License incorporates the limitation as if
|
|
||||||
written in the body of this License.
|
|
||||||
|
|
||||||
13. The Free Software Foundation may publish revised and/or new
|
|
||||||
versions of the Lesser General Public License from time to time.
|
|
||||||
Such new versions will be similar in spirit to the present version,
|
|
||||||
but may differ in detail to address new problems or concerns.
|
|
||||||
|
|
||||||
Each version is given a distinguishing version number. If the Library
|
|
||||||
specifies a version number of this License which applies to it and
|
|
||||||
"any later version", you have the option of following the terms and
|
|
||||||
conditions either of that version or of any later version published by
|
|
||||||
the Free Software Foundation. If the Library does not specify a
|
|
||||||
license version number, you may choose any version ever published by
|
|
||||||
the Free Software Foundation.
|
|
||||||
|
|
||||||
14. If you wish to incorporate parts of the Library into other free
|
|
||||||
programs whose distribution conditions are incompatible with these,
|
|
||||||
write to the author to ask for permission. For software which is
|
|
||||||
copyrighted by the Free Software Foundation, write to the Free
|
|
||||||
Software Foundation; we sometimes make exceptions for this. Our
|
|
||||||
decision will be guided by the two goals of preserving the free status
|
|
||||||
of all derivatives of our free software and of promoting the sharing
|
|
||||||
and reuse of software generally.
|
|
||||||
|
|
||||||
NO WARRANTY
|
|
||||||
|
|
||||||
15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
|
|
||||||
WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
|
|
||||||
EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
|
|
||||||
OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
|
|
||||||
KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
|
|
||||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
|
||||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
|
|
||||||
LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
|
|
||||||
THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
|
||||||
|
|
||||||
16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
|
|
||||||
WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
|
|
||||||
AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
|
|
||||||
FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
|
|
||||||
CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
|
|
||||||
LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
|
|
||||||
RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
|
|
||||||
FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
|
|
||||||
SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
|
|
||||||
DAMAGES.
|
|
||||||
|
|
||||||
END OF TERMS AND CONDITIONS
|
|
||||||
|
|
||||||
How to Apply These Terms to Your New Libraries
|
|
||||||
|
|
||||||
If you develop a new library, and you want it to be of the greatest
|
|
||||||
possible use to the public, we recommend making it free software that
|
|
||||||
everyone can redistribute and change. You can do so by permitting
|
|
||||||
redistribution under these terms (or, alternatively, under the terms of the
|
|
||||||
ordinary General Public License).
|
|
||||||
|
|
||||||
To apply these terms, attach the following notices to the library. It is
|
|
||||||
safest to attach them to the start of each source file to most effectively
|
|
||||||
convey the exclusion of warranty; and each file should have at least the
|
|
||||||
"copyright" line and a pointer to where the full notice is found.
|
|
||||||
|
|
||||||
{description}
|
|
||||||
Copyright (C) {year} {fullname}
|
|
||||||
|
|
||||||
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 Street, Fifth Floor, Boston, MA 02110-1301
|
|
||||||
USA
|
|
||||||
|
|
||||||
Also add information on how to contact you by electronic and paper mail.
|
|
||||||
|
|
||||||
You should also get your employer (if you work as a programmer) or your
|
|
||||||
school, if any, to sign a "copyright disclaimer" for the library, if
|
|
||||||
necessary. Here is a sample; alter the names:
|
|
||||||
|
|
||||||
Yoyodyne, Inc., hereby disclaims all copyright interest in the
|
|
||||||
library `Frob' (a library for tweaking knobs) written by James Random
|
|
||||||
Hacker.
|
|
||||||
|
|
||||||
{signature of Ty Coon}, 1 April 1990
|
|
||||||
Ty Coon, President of Vice
|
|
||||||
|
|
||||||
That's all there is to it!
|
|
@ -1,613 +0,0 @@
|
|||||||
/*
|
|
||||||
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>
|
|
||||||
#ifdef ARDUINO_ARCH_ESP32 //only use this library if building for ESP32
|
|
||||||
|
|
||||||
#include "WiFiServer.h"
|
|
||||||
#include "WiFiClient.h"
|
|
||||||
#include "WebServer.h"
|
|
||||||
|
|
||||||
//#define DEBUG_ESP_HTTP_SERVER
|
|
||||||
#ifdef DEBUG_ESP_PORT
|
|
||||||
#define DEBUG_OUTPUT DEBUG_ESP_PORT
|
|
||||||
#else
|
|
||||||
#define DEBUG_OUTPUT Serial
|
|
||||||
#endif
|
|
||||||
|
|
||||||
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 WebServer::_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) {
|
|
||||||
#ifdef DEBUG_ESP_HTTP_SERVER
|
|
||||||
DEBUG_OUTPUT.print("Invalid request: ");
|
|
||||||
DEBUG_OUTPUT.println(req);
|
|
||||||
#endif
|
|
||||||
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;
|
|
||||||
|
|
||||||
HTTPMethod method = HTTP_GET;
|
|
||||||
if (methodStr == "POST") {
|
|
||||||
method = HTTP_POST;
|
|
||||||
} else if (methodStr == "DELETE") {
|
|
||||||
method = HTTP_DELETE;
|
|
||||||
} else if (methodStr == "OPTIONS") {
|
|
||||||
method = HTTP_OPTIONS;
|
|
||||||
} else if (methodStr == "PUT") {
|
|
||||||
method = HTTP_PUT;
|
|
||||||
} else if (methodStr == "PATCH") {
|
|
||||||
method = HTTP_PATCH;
|
|
||||||
}
|
|
||||||
_currentMethod = method;
|
|
||||||
|
|
||||||
#ifdef DEBUG_ESP_HTTP_SERVER
|
|
||||||
DEBUG_OUTPUT.print("method: ");
|
|
||||||
DEBUG_OUTPUT.print(methodStr);
|
|
||||||
DEBUG_OUTPUT.print(" url: ");
|
|
||||||
DEBUG_OUTPUT.print(url);
|
|
||||||
DEBUG_OUTPUT.print(" search: ");
|
|
||||||
DEBUG_OUTPUT.println(searchStr);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
//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;
|
|
||||||
uint32_t contentLength = 0;
|
|
||||||
//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());
|
|
||||||
|
|
||||||
#ifdef DEBUG_ESP_HTTP_SERVER
|
|
||||||
DEBUG_OUTPUT.print("headerName: ");
|
|
||||||
DEBUG_OUTPUT.println(headerName);
|
|
||||||
DEBUG_OUTPUT.print("headerValue: ");
|
|
||||||
DEBUG_OUTPUT.println(headerValue);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
if (headerName.equalsIgnoreCase("Content-Type")){
|
|
||||||
if (headerValue.startsWith("text/plain")){
|
|
||||||
isForm = false;
|
|
||||||
} else if (headerValue.startsWith("application/x-www-form-urlencoded")){
|
|
||||||
isForm = false;
|
|
||||||
isEncoded = true;
|
|
||||||
} else if (headerValue.startsWith("multipart/")){
|
|
||||||
boundaryStr = headerValue.substring(headerValue.indexOf('=')+1);
|
|
||||||
isForm = true;
|
|
||||||
}
|
|
||||||
} else if (headerName.equalsIgnoreCase("Content-Length")){
|
|
||||||
contentLength = headerValue.toInt();
|
|
||||||
} else if (headerName.equalsIgnoreCase("Host")){
|
|
||||||
_hostHeader = headerValue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isForm){
|
|
||||||
size_t plainLength;
|
|
||||||
char* plainBuf = readBytesWithTimeout(client, contentLength, plainLength, HTTP_MAX_POST_WAIT);
|
|
||||||
if (plainLength < contentLength) {
|
|
||||||
free(plainBuf);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (contentLength > 0) {
|
|
||||||
if (searchStr != "") searchStr += '&';
|
|
||||||
if(isEncoded){
|
|
||||||
//url encoded form
|
|
||||||
String decoded = urlDecode(plainBuf);
|
|
||||||
size_t decodedLen = decoded.length();
|
|
||||||
memcpy(plainBuf, decoded.c_str(), decodedLen);
|
|
||||||
plainBuf[decodedLen] = 0;
|
|
||||||
searchStr += plainBuf;
|
|
||||||
}
|
|
||||||
_parseArguments(searchStr);
|
|
||||||
if(!isEncoded){
|
|
||||||
//plain post json or other data
|
|
||||||
RequestArgument& arg = _currentArgs[_currentArgCount++];
|
|
||||||
arg.key = "plain";
|
|
||||||
arg.value = String(plainBuf);
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef DEBUG_ESP_HTTP_SERVER
|
|
||||||
DEBUG_OUTPUT.print("Plain: ");
|
|
||||||
DEBUG_OUTPUT.println(plainBuf);
|
|
||||||
#endif
|
|
||||||
free(plainBuf);
|
|
||||||
} else {
|
|
||||||
// No content - but we can still have arguments in the URL.
|
|
||||||
_parseArguments(searchStr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isForm){
|
|
||||||
_parseArguments(searchStr);
|
|
||||||
if (!_parseForm(client, boundaryStr, contentLength)) {
|
|
||||||
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());
|
|
||||||
|
|
||||||
#ifdef DEBUG_ESP_HTTP_SERVER
|
|
||||||
DEBUG_OUTPUT.print("headerName: ");
|
|
||||||
DEBUG_OUTPUT.println(headerName);
|
|
||||||
DEBUG_OUTPUT.print("headerValue: ");
|
|
||||||
DEBUG_OUTPUT.println(headerValue);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
if (headerName.equalsIgnoreCase("Host")){
|
|
||||||
_hostHeader = headerValue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_parseArguments(searchStr);
|
|
||||||
}
|
|
||||||
client.flush();
|
|
||||||
|
|
||||||
#ifdef DEBUG_ESP_HTTP_SERVER
|
|
||||||
DEBUG_OUTPUT.print("Request: ");
|
|
||||||
DEBUG_OUTPUT.println(url);
|
|
||||||
DEBUG_OUTPUT.print(" Arguments: ");
|
|
||||||
DEBUG_OUTPUT.println(searchStr);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool WebServer::_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 WebServer::_parseArguments(String data) {
|
|
||||||
#ifdef DEBUG_ESP_HTTP_SERVER
|
|
||||||
DEBUG_OUTPUT.print("args: ");
|
|
||||||
DEBUG_OUTPUT.println(data);
|
|
||||||
#endif
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
#ifdef DEBUG_ESP_HTTP_SERVER
|
|
||||||
DEBUG_OUTPUT.print("args count: ");
|
|
||||||
DEBUG_OUTPUT.println(_currentArgCount);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
_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);
|
|
||||||
#ifdef DEBUG_ESP_HTTP_SERVER
|
|
||||||
DEBUG_OUTPUT.print("pos ");
|
|
||||||
DEBUG_OUTPUT.print(pos);
|
|
||||||
DEBUG_OUTPUT.print("=@ ");
|
|
||||||
DEBUG_OUTPUT.print(equal_sign_index);
|
|
||||||
DEBUG_OUTPUT.print(" &@ ");
|
|
||||||
DEBUG_OUTPUT.println(next_arg_index);
|
|
||||||
#endif
|
|
||||||
if ((equal_sign_index == -1) || ((equal_sign_index > next_arg_index) && (next_arg_index != -1))) {
|
|
||||||
#ifdef DEBUG_ESP_HTTP_SERVER
|
|
||||||
DEBUG_OUTPUT.print("arg missing value: ");
|
|
||||||
DEBUG_OUTPUT.println(iarg);
|
|
||||||
#endif
|
|
||||||
if (next_arg_index == -1)
|
|
||||||
break;
|
|
||||||
pos = next_arg_index + 1;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
RequestArgument& arg = _currentArgs[iarg];
|
|
||||||
arg.key = data.substring(pos, equal_sign_index);
|
|
||||||
arg.value = urlDecode(data.substring(equal_sign_index + 1, next_arg_index));
|
|
||||||
#ifdef DEBUG_ESP_HTTP_SERVER
|
|
||||||
DEBUG_OUTPUT.print("arg ");
|
|
||||||
DEBUG_OUTPUT.print(iarg);
|
|
||||||
DEBUG_OUTPUT.print(" key: ");
|
|
||||||
DEBUG_OUTPUT.print(arg.key);
|
|
||||||
DEBUG_OUTPUT.print(" value: ");
|
|
||||||
DEBUG_OUTPUT.println(arg.value);
|
|
||||||
#endif
|
|
||||||
++iarg;
|
|
||||||
if (next_arg_index == -1)
|
|
||||||
break;
|
|
||||||
pos = next_arg_index + 1;
|
|
||||||
}
|
|
||||||
_currentArgCount = iarg;
|
|
||||||
#ifdef DEBUG_ESP_HTTP_SERVER
|
|
||||||
DEBUG_OUTPUT.print("args count: ");
|
|
||||||
DEBUG_OUTPUT.println(_currentArgCount);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
void WebServer::_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;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint8_t WebServer::_uploadReadByte(WiFiClient& client){
|
|
||||||
int res = client.read();
|
|
||||||
if(res == -1){
|
|
||||||
while(!client.available() && client.connected())
|
|
||||||
yield();
|
|
||||||
res = client.read();
|
|
||||||
}
|
|
||||||
return (uint8_t)res;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool WebServer::_parseForm(WiFiClient& client, String boundary, uint32_t len){
|
|
||||||
(void) len;
|
|
||||||
#ifdef DEBUG_ESP_HTTP_SERVER
|
|
||||||
DEBUG_OUTPUT.print("Parse Form: Boundary: ");
|
|
||||||
DEBUG_OUTPUT.print(boundary);
|
|
||||||
DEBUG_OUTPUT.print(" Length: ");
|
|
||||||
DEBUG_OUTPUT.println(len);
|
|
||||||
#endif
|
|
||||||
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)){
|
|
||||||
RequestArgument* postArgs = new RequestArgument[32];
|
|
||||||
int 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("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;
|
|
||||||
#ifdef DEBUG_ESP_HTTP_SERVER
|
|
||||||
DEBUG_OUTPUT.print("PostArg FileName: ");
|
|
||||||
DEBUG_OUTPUT.println(argFilename);
|
|
||||||
#endif
|
|
||||||
//use GET to set the filename if uploading using blob
|
|
||||||
if (argFilename == "blob" && hasArg("filename")) argFilename = arg("filename");
|
|
||||||
}
|
|
||||||
#ifdef DEBUG_ESP_HTTP_SERVER
|
|
||||||
DEBUG_OUTPUT.print("PostArg Name: ");
|
|
||||||
DEBUG_OUTPUT.println(argName);
|
|
||||||
#endif
|
|
||||||
argType = "text/plain";
|
|
||||||
line = client.readStringUntil('\r');
|
|
||||||
client.readStringUntil('\n');
|
|
||||||
if (line.length() > 12 && line.substring(0, 12).equalsIgnoreCase("Content-Type")){
|
|
||||||
argType = line.substring(line.indexOf(':')+2);
|
|
||||||
//skip next line
|
|
||||||
client.readStringUntil('\r');
|
|
||||||
client.readStringUntil('\n');
|
|
||||||
}
|
|
||||||
#ifdef DEBUG_ESP_HTTP_SERVER
|
|
||||||
DEBUG_OUTPUT.print("PostArg Type: ");
|
|
||||||
DEBUG_OUTPUT.println(argType);
|
|
||||||
#endif
|
|
||||||
if (!argIsFile){
|
|
||||||
while(1){
|
|
||||||
line = client.readStringUntil('\r');
|
|
||||||
client.readStringUntil('\n');
|
|
||||||
if (line.startsWith("--"+boundary)) break;
|
|
||||||
if (argValue.length() > 0) argValue += "\n";
|
|
||||||
argValue += line;
|
|
||||||
}
|
|
||||||
#ifdef DEBUG_ESP_HTTP_SERVER
|
|
||||||
DEBUG_OUTPUT.print("PostArg Value: ");
|
|
||||||
DEBUG_OUTPUT.println(argValue);
|
|
||||||
DEBUG_OUTPUT.println();
|
|
||||||
#endif
|
|
||||||
|
|
||||||
RequestArgument& arg = postArgs[postArgsLen++];
|
|
||||||
arg.key = argName;
|
|
||||||
arg.value = argValue;
|
|
||||||
|
|
||||||
if (line == ("--"+boundary+"--")){
|
|
||||||
#ifdef DEBUG_ESP_HTTP_SERVER
|
|
||||||
DEBUG_OUTPUT.println("Done Parsing POST");
|
|
||||||
#endif
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
_currentUpload.status = UPLOAD_FILE_START;
|
|
||||||
_currentUpload.name = argName;
|
|
||||||
_currentUpload.filename = argFilename;
|
|
||||||
_currentUpload.type = argType;
|
|
||||||
_currentUpload.totalSize = 0;
|
|
||||||
_currentUpload.currentSize = 0;
|
|
||||||
#ifdef DEBUG_ESP_HTTP_SERVER
|
|
||||||
DEBUG_OUTPUT.print("Start File: ");
|
|
||||||
DEBUG_OUTPUT.print(_currentUpload.filename);
|
|
||||||
DEBUG_OUTPUT.print(" Type: ");
|
|
||||||
DEBUG_OUTPUT.println(_currentUpload.type);
|
|
||||||
#endif
|
|
||||||
if(_currentHandler && _currentHandler->canUpload(_currentUri))
|
|
||||||
_currentHandler->upload(*this, _currentUri, _currentUpload);
|
|
||||||
_currentUpload.status = UPLOAD_FILE_WRITE;
|
|
||||||
uint8_t argByte = _uploadReadByte(client);
|
|
||||||
readfile:
|
|
||||||
while(argByte != 0x0D){
|
|
||||||
if (!client.connected()) return _parseFormUploadAborted();
|
|
||||||
_uploadWriteByte(argByte);
|
|
||||||
argByte = _uploadReadByte(client);
|
|
||||||
}
|
|
||||||
|
|
||||||
argByte = _uploadReadByte(client);
|
|
||||||
if (!client.connected()) return _parseFormUploadAborted();
|
|
||||||
if (argByte == 0x0A){
|
|
||||||
argByte = _uploadReadByte(client);
|
|
||||||
if (!client.connected()) return _parseFormUploadAborted();
|
|
||||||
if ((char)argByte != '-'){
|
|
||||||
//continue reading the file
|
|
||||||
_uploadWriteByte(0x0D);
|
|
||||||
_uploadWriteByte(0x0A);
|
|
||||||
goto readfile;
|
|
||||||
} else {
|
|
||||||
argByte = _uploadReadByte(client);
|
|
||||||
if (!client.connected()) return _parseFormUploadAborted();
|
|
||||||
if ((char)argByte != '-'){
|
|
||||||
//continue reading the file
|
|
||||||
_uploadWriteByte(0x0D);
|
|
||||||
_uploadWriteByte(0x0A);
|
|
||||||
_uploadWriteByte((uint8_t)('-'));
|
|
||||||
goto readfile;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
uint8_t endBuf[boundary.length()];
|
|
||||||
client.readBytes(endBuf, boundary.length());
|
|
||||||
|
|
||||||
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);
|
|
||||||
#ifdef DEBUG_ESP_HTTP_SERVER
|
|
||||||
DEBUG_OUTPUT.print("End File: ");
|
|
||||||
DEBUG_OUTPUT.print(_currentUpload.filename);
|
|
||||||
DEBUG_OUTPUT.print(" Type: ");
|
|
||||||
DEBUG_OUTPUT.print(_currentUpload.type);
|
|
||||||
DEBUG_OUTPUT.print(" Size: ");
|
|
||||||
DEBUG_OUTPUT.println(_currentUpload.totalSize);
|
|
||||||
#endif
|
|
||||||
line = client.readStringUntil(0x0D);
|
|
||||||
client.readStringUntil(0x0A);
|
|
||||||
if (line == "--"){
|
|
||||||
#ifdef DEBUG_ESP_HTTP_SERVER
|
|
||||||
DEBUG_OUTPUT.println("Done Parsing POST");
|
|
||||||
#endif
|
|
||||||
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 = ((32 - postArgsLen) < _currentArgCount)?(32 - 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;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
#ifdef DEBUG_ESP_HTTP_SERVER
|
|
||||||
DEBUG_OUTPUT.print("Error: line: ");
|
|
||||||
DEBUG_OUTPUT.println(line);
|
|
||||||
#endif
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
String WebServer::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 WebServer::_parseFormUploadAborted(){
|
|
||||||
_currentUpload.status = UPLOAD_FILE_ABORTED;
|
|
||||||
if(_currentHandler && _currentHandler->canUpload(_currentUri))
|
|
||||||
_currentHandler->upload(*this, _currentUri, _currentUpload);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
#endif
|
|
@ -1,11 +0,0 @@
|
|||||||
Notice by Aircoookie: Port of the ESP8266HTTPUpdateServer for ESP32 is also included in this directory.
|
|
||||||
|
|
||||||
# WebServer
|
|
||||||
ESP8266/ESP32 WebServer library
|
|
||||||
|
|
||||||
This is an experimental port of the ESP8266WebServer library that should work
|
|
||||||
on ESP8266 and ESP32. This is NOT an official repo supported by Espressif. Do
|
|
||||||
not depend on this code for anything important or expect it to be updated. Once
|
|
||||||
the official repo is created, this repo will be deleted.
|
|
||||||
|
|
||||||
Added Travis CI
|
|
@ -1,529 +0,0 @@
|
|||||||
/*
|
|
||||||
WebServer.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>
|
|
||||||
#ifdef ARDUINO_ARCH_ESP32 //only use this library if building for ESP32
|
|
||||||
|
|
||||||
#include <libb64/cencode.h>
|
|
||||||
#include "WiFiServer.h"
|
|
||||||
#include "WiFiClient.h"
|
|
||||||
#include "WebServer.h"
|
|
||||||
#include "FS.h"
|
|
||||||
#include "detail/RequestHandlersImpl.h"
|
|
||||||
|
|
||||||
//#define DEBUG_ESP_HTTP_SERVER
|
|
||||||
#ifdef DEBUG_ESP_PORT
|
|
||||||
#define DEBUG_OUTPUT DEBUG_ESP_PORT
|
|
||||||
#else
|
|
||||||
#define DEBUG_OUTPUT Serial
|
|
||||||
#endif
|
|
||||||
|
|
||||||
const char * AUTHORIZATION_HEADER = "Authorization";
|
|
||||||
|
|
||||||
WebServer::WebServer(IPAddress addr, int port)
|
|
||||||
: _server(addr, port)
|
|
||||||
, _currentMethod(HTTP_ANY)
|
|
||||||
, _currentVersion(0)
|
|
||||||
, _currentStatus(HC_NONE)
|
|
||||||
, _statusChange(0)
|
|
||||||
, _currentHandler(0)
|
|
||||||
, _firstHandler(0)
|
|
||||||
, _lastHandler(0)
|
|
||||||
, _currentArgCount(0)
|
|
||||||
, _currentArgs(0)
|
|
||||||
, _headerKeysCount(0)
|
|
||||||
, _currentHeaders(0)
|
|
||||||
, _contentLength(0)
|
|
||||||
, _chunked(false)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
WebServer::WebServer(int port)
|
|
||||||
: _server(port)
|
|
||||||
, _currentMethod(HTTP_ANY)
|
|
||||||
, _currentVersion(0)
|
|
||||||
, _currentStatus(HC_NONE)
|
|
||||||
, _statusChange(0)
|
|
||||||
, _currentHandler(0)
|
|
||||||
, _firstHandler(0)
|
|
||||||
, _lastHandler(0)
|
|
||||||
, _currentArgCount(0)
|
|
||||||
, _currentArgs(0)
|
|
||||||
, _headerKeysCount(0)
|
|
||||||
, _currentHeaders(0)
|
|
||||||
, _contentLength(0)
|
|
||||||
, _chunked(false)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
WebServer::~WebServer() {
|
|
||||||
if (_currentHeaders)
|
|
||||||
delete[]_currentHeaders;
|
|
||||||
_headerKeysCount = 0;
|
|
||||||
RequestHandler* handler = _firstHandler;
|
|
||||||
while (handler) {
|
|
||||||
RequestHandler* next = handler->next();
|
|
||||||
delete handler;
|
|
||||||
handler = next;
|
|
||||||
}
|
|
||||||
close();
|
|
||||||
}
|
|
||||||
|
|
||||||
void WebServer::begin() {
|
|
||||||
_currentStatus = HC_NONE;
|
|
||||||
_server.begin();
|
|
||||||
//if(!_headerKeysCount)
|
|
||||||
//collectHeaders(0, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool WebServer::authenticate(const char * username, const char * password){
|
|
||||||
if(hasHeader(AUTHORIZATION_HEADER)){
|
|
||||||
String authReq = header(AUTHORIZATION_HEADER);
|
|
||||||
if(authReq.startsWith("Basic")){
|
|
||||||
authReq = authReq.substring(6);
|
|
||||||
authReq.trim();
|
|
||||||
char toencodeLen = strlen(username)+strlen(password)+1;
|
|
||||||
char *toencode = new char[toencodeLen + 1];
|
|
||||||
if(toencode == NULL){
|
|
||||||
authReq = String();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
char *encoded = new char[base64_encode_expected_len(toencodeLen)+1];
|
|
||||||
if(encoded == NULL){
|
|
||||||
authReq = String();
|
|
||||||
delete[] toencode;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
sprintf(toencode, "%s:%s", username, password);
|
|
||||||
if(base64_encode_chars(toencode, toencodeLen, encoded) > 0 && authReq.equals(encoded)){
|
|
||||||
authReq = String();
|
|
||||||
delete[] toencode;
|
|
||||||
delete[] encoded;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
delete[] toencode;
|
|
||||||
delete[] encoded;
|
|
||||||
}
|
|
||||||
authReq = String();
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void WebServer::requestAuthentication(){
|
|
||||||
sendHeader("WWW-Authenticate", "Basic realm=\"Login Required\"");
|
|
||||||
send(401);
|
|
||||||
}
|
|
||||||
|
|
||||||
void WebServer::on(const String &uri, WebServer::THandlerFunction handler) {
|
|
||||||
on(uri, HTTP_ANY, handler);
|
|
||||||
}
|
|
||||||
|
|
||||||
void WebServer::on(const String &uri, HTTPMethod method, WebServer::THandlerFunction fn) {
|
|
||||||
on(uri, method, fn, _fileUploadHandler);
|
|
||||||
}
|
|
||||||
|
|
||||||
void WebServer::on(const String &uri, HTTPMethod method, WebServer::THandlerFunction fn, WebServer::THandlerFunction ufn) {
|
|
||||||
_addRequestHandler(new FunctionRequestHandler(fn, ufn, uri, method));
|
|
||||||
}
|
|
||||||
|
|
||||||
void WebServer::addHandler(RequestHandler* handler) {
|
|
||||||
_addRequestHandler(handler);
|
|
||||||
}
|
|
||||||
|
|
||||||
void WebServer::_addRequestHandler(RequestHandler* handler) {
|
|
||||||
if (!_lastHandler) {
|
|
||||||
_firstHandler = handler;
|
|
||||||
_lastHandler = handler;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
_lastHandler->next(handler);
|
|
||||||
_lastHandler = handler;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void WebServer::serveStatic(const char* uri, FS& fs, const char* path, const char* cache_header) {
|
|
||||||
_addRequestHandler(new StaticRequestHandler(fs, path, uri, cache_header));
|
|
||||||
}
|
|
||||||
|
|
||||||
void WebServer::handleClient() {
|
|
||||||
if (_currentStatus == HC_NONE) {
|
|
||||||
WiFiClient client = _server.available();
|
|
||||||
if (!client) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef DEBUG_ESP_HTTP_SERVER
|
|
||||||
DEBUG_OUTPUT.println("New client");
|
|
||||||
#endif
|
|
||||||
|
|
||||||
_currentClient = client;
|
|
||||||
_currentStatus = HC_WAIT_READ;
|
|
||||||
_statusChange = millis();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!_currentClient.connected()) {
|
|
||||||
_currentClient = WiFiClient();
|
|
||||||
_currentStatus = HC_NONE;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait for data from client to become available
|
|
||||||
if (_currentStatus == HC_WAIT_READ) {
|
|
||||||
if (!_currentClient.available()) {
|
|
||||||
if (millis() - _statusChange > HTTP_MAX_DATA_WAIT) {
|
|
||||||
_currentClient = WiFiClient();
|
|
||||||
_currentStatus = HC_NONE;
|
|
||||||
}
|
|
||||||
yield();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!_parseRequest(_currentClient)) {
|
|
||||||
_currentClient = WiFiClient();
|
|
||||||
_currentStatus = HC_NONE;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
_currentClient.setTimeout(HTTP_MAX_SEND_WAIT);
|
|
||||||
_contentLength = CONTENT_LENGTH_NOT_SET;
|
|
||||||
_handleRequest();
|
|
||||||
|
|
||||||
if (!_currentClient.connected()) {
|
|
||||||
_currentClient = WiFiClient();
|
|
||||||
_currentStatus = HC_NONE;
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
_currentStatus = HC_WAIT_CLOSE;
|
|
||||||
_statusChange = millis();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_currentStatus == HC_WAIT_CLOSE) {
|
|
||||||
if (millis() - _statusChange > HTTP_MAX_CLOSE_WAIT) {
|
|
||||||
_currentClient = WiFiClient();
|
|
||||||
_currentStatus = HC_NONE;
|
|
||||||
} else {
|
|
||||||
yield();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void WebServer::close() {
|
|
||||||
#ifdef ESP8266
|
|
||||||
_server.stop();
|
|
||||||
#else
|
|
||||||
// TODO add ESP32 WiFiServer::stop()
|
|
||||||
_server.end();
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
void WebServer::stop() {
|
|
||||||
close();
|
|
||||||
}
|
|
||||||
|
|
||||||
void WebServer::sendHeader(const String& name, const String& value, bool first) {
|
|
||||||
String headerLine = name;
|
|
||||||
headerLine += ": ";
|
|
||||||
headerLine += value;
|
|
||||||
headerLine += "\r\n";
|
|
||||||
|
|
||||||
if (first) {
|
|
||||||
_responseHeaders = headerLine + _responseHeaders;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
_responseHeaders += headerLine;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void WebServer::setContentLength(size_t contentLength) {
|
|
||||||
_contentLength = contentLength;
|
|
||||||
}
|
|
||||||
|
|
||||||
void WebServer::_prepareHeader(String& response, int code, const char* content_type, size_t contentLength) {
|
|
||||||
response = "HTTP/1."+String(_currentVersion)+" ";
|
|
||||||
response += String(code);
|
|
||||||
response += " ";
|
|
||||||
response += _responseCodeToString(code);
|
|
||||||
response += "\r\n";
|
|
||||||
|
|
||||||
if (!content_type)
|
|
||||||
content_type = "text/html";
|
|
||||||
|
|
||||||
sendHeader("Content-Type", content_type, true);
|
|
||||||
if (_contentLength == CONTENT_LENGTH_NOT_SET) {
|
|
||||||
sendHeader("Content-Length", String(contentLength));
|
|
||||||
} else if (_contentLength != CONTENT_LENGTH_UNKNOWN) {
|
|
||||||
sendHeader("Content-Length", String(_contentLength));
|
|
||||||
} else if(_contentLength == CONTENT_LENGTH_UNKNOWN && _currentVersion){ //HTTP/1.1 or above client
|
|
||||||
//let's do chunked
|
|
||||||
_chunked = true;
|
|
||||||
sendHeader("Accept-Ranges","none");
|
|
||||||
sendHeader("Transfer-Encoding","chunked");
|
|
||||||
}
|
|
||||||
sendHeader("Connection", "close");
|
|
||||||
|
|
||||||
response += _responseHeaders;
|
|
||||||
response += "\r\n";
|
|
||||||
_responseHeaders = String();
|
|
||||||
}
|
|
||||||
|
|
||||||
void WebServer::send(int code, const char* content_type, const String& content) {
|
|
||||||
String header;
|
|
||||||
// Can we asume the following?
|
|
||||||
//if(code == 200 && content.length() == 0 && _contentLength == CONTENT_LENGTH_NOT_SET)
|
|
||||||
// _contentLength = CONTENT_LENGTH_UNKNOWN;
|
|
||||||
_prepareHeader(header, code, content_type, content.length());
|
|
||||||
_currentClient.write(header.c_str(), header.length());
|
|
||||||
if(content.length())
|
|
||||||
sendContent(content);
|
|
||||||
}
|
|
||||||
|
|
||||||
void WebServer::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);
|
|
||||||
_currentClient.write(header.c_str(), header.length());
|
|
||||||
sendContent_P(content);
|
|
||||||
}
|
|
||||||
|
|
||||||
void WebServer::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 WebServer::send(int code, char* content_type, const String& content) {
|
|
||||||
send(code, (const char*)content_type, content);
|
|
||||||
}
|
|
||||||
|
|
||||||
void WebServer::send(int code, const String& content_type, const String& content) {
|
|
||||||
send(code, (const char*)content_type.c_str(), content);
|
|
||||||
}
|
|
||||||
|
|
||||||
void WebServer::sendContent(const String& content) {
|
|
||||||
const char * footer = "\r\n";
|
|
||||||
size_t len = content.length();
|
|
||||||
if(_chunked) {
|
|
||||||
char * chunkSize = (char *)malloc(11);
|
|
||||||
if(chunkSize){
|
|
||||||
sprintf(chunkSize, "%x%s", len, footer);
|
|
||||||
_currentClient.write(chunkSize, strlen(chunkSize));
|
|
||||||
free(chunkSize);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_currentClient.write(content.c_str(), len);
|
|
||||||
if(_chunked){
|
|
||||||
_currentClient.write(footer, 2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void WebServer::sendContent_P(PGM_P content) {
|
|
||||||
sendContent_P(content, strlen_P(content));
|
|
||||||
}
|
|
||||||
|
|
||||||
void WebServer::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);
|
|
||||||
_currentClient.write(chunkSize, strlen(chunkSize));
|
|
||||||
free(chunkSize);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_currentClient.write_P(content, size);
|
|
||||||
if(_chunked){
|
|
||||||
_currentClient.write(footer, 2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
String WebServer::arg(String name) {
|
|
||||||
for (int i = 0; i < _currentArgCount; ++i) {
|
|
||||||
if ( _currentArgs[i].key == name )
|
|
||||||
return _currentArgs[i].value;
|
|
||||||
}
|
|
||||||
return String();
|
|
||||||
}
|
|
||||||
|
|
||||||
String WebServer::arg(int i) {
|
|
||||||
if (i < _currentArgCount)
|
|
||||||
return _currentArgs[i].value;
|
|
||||||
return String();
|
|
||||||
}
|
|
||||||
|
|
||||||
String WebServer::argName(int i) {
|
|
||||||
if (i < _currentArgCount)
|
|
||||||
return _currentArgs[i].key;
|
|
||||||
return String();
|
|
||||||
}
|
|
||||||
|
|
||||||
int WebServer::args() {
|
|
||||||
return _currentArgCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool WebServer::hasArg(String name) {
|
|
||||||
for (int i = 0; i < _currentArgCount; ++i) {
|
|
||||||
if (_currentArgs[i].key == name)
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
String WebServer::header(String name) {
|
|
||||||
for (int i = 0; i < _headerKeysCount; ++i) {
|
|
||||||
if (_currentHeaders[i].key.equalsIgnoreCase(name))
|
|
||||||
return _currentHeaders[i].value;
|
|
||||||
}
|
|
||||||
return String();
|
|
||||||
}
|
|
||||||
|
|
||||||
//Modified by Aircoookie to work for WLED
|
|
||||||
void WebServer::collectHeaders(String headerKey) {
|
|
||||||
_headerKeysCount = 2;
|
|
||||||
if (_currentHeaders) delete[]_currentHeaders;
|
|
||||||
_currentHeaders = new RequestArgument[2];
|
|
||||||
_currentHeaders[0].key = AUTHORIZATION_HEADER;
|
|
||||||
_currentHeaders[1].key = headerKey;
|
|
||||||
}
|
|
||||||
|
|
||||||
String WebServer::header(int i) {
|
|
||||||
if (i < _headerKeysCount)
|
|
||||||
return _currentHeaders[i].value;
|
|
||||||
return String();
|
|
||||||
}
|
|
||||||
|
|
||||||
String WebServer::headerName(int i) {
|
|
||||||
if (i < _headerKeysCount)
|
|
||||||
return _currentHeaders[i].key;
|
|
||||||
return String();
|
|
||||||
}
|
|
||||||
|
|
||||||
int WebServer::headers() {
|
|
||||||
return _headerKeysCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool WebServer::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 WebServer::hostHeader() {
|
|
||||||
return _hostHeader;
|
|
||||||
}
|
|
||||||
|
|
||||||
void WebServer::onFileUpload(THandlerFunction fn) {
|
|
||||||
_fileUploadHandler = fn;
|
|
||||||
}
|
|
||||||
|
|
||||||
void WebServer::onNotFound(THandlerFunction fn) {
|
|
||||||
_notFoundHandler = fn;
|
|
||||||
}
|
|
||||||
|
|
||||||
void WebServer::_handleRequest() {
|
|
||||||
bool handled = false;
|
|
||||||
if (!_currentHandler){
|
|
||||||
#ifdef DEBUG_ESP_HTTP_SERVER
|
|
||||||
DEBUG_OUTPUT.println("request handler not found");
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
handled = _currentHandler->handle(*this, _currentMethod, _currentUri);
|
|
||||||
#ifdef DEBUG_ESP_HTTP_SERVER
|
|
||||||
if (!handled) {
|
|
||||||
DEBUG_OUTPUT.println("request handler failed to handle request");
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!handled) {
|
|
||||||
if(_notFoundHandler) {
|
|
||||||
_notFoundHandler();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
send(404, "text/plain", String("Not found: ") + _currentUri);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_currentUri = String();
|
|
||||||
}
|
|
||||||
|
|
||||||
String WebServer::_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 "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
@ -1,236 +0,0 @@
|
|||||||
/*
|
|
||||||
WebServer.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)
|
|
||||||
*/
|
|
||||||
|
|
||||||
|
|
||||||
#ifndef WEBSERVER_H
|
|
||||||
#define WEBSERVER_H
|
|
||||||
|
|
||||||
#include <functional>
|
|
||||||
#ifdef ESP8266
|
|
||||||
#define WebServer ESP8266WebServer
|
|
||||||
#include <ESP8266WiFi.h>
|
|
||||||
#else
|
|
||||||
#include <WiFi.h>
|
|
||||||
#define write_P write
|
|
||||||
#endif
|
|
||||||
|
|
||||||
enum HTTPMethod { HTTP_ANY, HTTP_GET, HTTP_POST, HTTP_PUT, HTTP_PATCH, HTTP_DELETE, HTTP_OPTIONS };
|
|
||||||
enum HTTPUploadStatus { UPLOAD_FILE_START, UPLOAD_FILE_WRITE, UPLOAD_FILE_END,
|
|
||||||
UPLOAD_FILE_ABORTED };
|
|
||||||
enum HTTPClientStatus { HC_NONE, HC_WAIT_READ, HC_WAIT_CLOSE };
|
|
||||||
|
|
||||||
#define HTTP_DOWNLOAD_UNIT_SIZE 1460
|
|
||||||
|
|
||||||
#ifndef HTTP_UPLOAD_BUFLEN
|
|
||||||
#define HTTP_UPLOAD_BUFLEN 2048
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#define HTTP_MAX_DATA_WAIT 1000 //ms to wait for the client to send the request
|
|
||||||
#define HTTP_MAX_POST_WAIT 1000 //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 WebServer;
|
|
||||||
|
|
||||||
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 WebServer
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
WebServer(IPAddress addr, int port = 80);
|
|
||||||
WebServer(int port = 80);
|
|
||||||
~WebServer();
|
|
||||||
|
|
||||||
void begin();
|
|
||||||
void handleClient();
|
|
||||||
|
|
||||||
void close();
|
|
||||||
void stop();
|
|
||||||
|
|
||||||
bool authenticate(const char * username, const char * password);
|
|
||||||
void requestAuthentication();
|
|
||||||
|
|
||||||
typedef std::function<void(void)> THandlerFunction;
|
|
||||||
void on(const String &uri, THandlerFunction handler);
|
|
||||||
void on(const String &uri, HTTPMethod method, THandlerFunction fn);
|
|
||||||
void on(const String &uri, HTTPMethod method, THandlerFunction fn, THandlerFunction ufn);
|
|
||||||
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 fn); //handle file uploads
|
|
||||||
|
|
||||||
String uri() { return _currentUri; }
|
|
||||||
HTTPMethod method() { return _currentMethod; }
|
|
||||||
WiFiClient client() { return _currentClient; }
|
|
||||||
HTTPUpload& upload() { return _currentUpload; }
|
|
||||||
|
|
||||||
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(String headerKey); // set the request headers to collect
|
|
||||||
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
|
|
||||||
|
|
||||||
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_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);
|
|
||||||
|
|
||||||
void setContentLength(size_t contentLength);
|
|
||||||
void sendHeader(const String& name, const String& value, bool first = false);
|
|
||||||
void sendContent(const String& content);
|
|
||||||
void sendContent_P(PGM_P content);
|
|
||||||
void sendContent_P(PGM_P content, size_t size);
|
|
||||||
|
|
||||||
static String urlDecode(const String& text);
|
|
||||||
|
|
||||||
#ifdef ESP8266
|
|
||||||
template<typename T> size_t streamFile(T &file, const String& contentType){
|
|
||||||
setContentLength(file.size());
|
|
||||||
if (String(file.name()).endsWith(".gz") &&
|
|
||||||
contentType != "application/x-gzip" &&
|
|
||||||
contentType != "application/octet-stream"){
|
|
||||||
sendHeader("Content-Encoding", "gzip");
|
|
||||||
}
|
|
||||||
send(200, contentType, "");
|
|
||||||
return _currentClient.write(file);
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
template<typename T> size_t streamFile(T &file, const String& contentType){
|
|
||||||
#define STREAMFILE_BUFSIZE 2*1460
|
|
||||||
setContentLength(file.size());
|
|
||||||
if (String(file.name()).endsWith(".gz") &&
|
|
||||||
contentType != "application/x-gzip" &&
|
|
||||||
contentType != "application/octet-stream") {
|
|
||||||
sendHeader("Content-Encoding", "gzip");
|
|
||||||
}
|
|
||||||
send(200, contentType, "");
|
|
||||||
uint8_t *buf = (uint8_t *)malloc(STREAMFILE_BUFSIZE);
|
|
||||||
if (buf == NULL) {
|
|
||||||
//DBG_OUTPUT_PORT.printf("streamFile malloc failed");
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
size_t totalBytesOut = 0;
|
|
||||||
while (client().connected() && (file.available() > 0)) {
|
|
||||||
int bytesOut;
|
|
||||||
int bytesIn = file.read(buf, STREAMFILE_BUFSIZE);
|
|
||||||
if (bytesIn <= 0) break;
|
|
||||||
while (1) {
|
|
||||||
bytesOut = 0;
|
|
||||||
if (!client().connected()) break;
|
|
||||||
bytesOut = client().write(buf, bytesIn);
|
|
||||||
if (bytesIn == bytesOut) break;
|
|
||||||
|
|
||||||
//DBG_OUTPUT_PORT.printf("bytesIn %d != bytesOut %d\r\n",
|
|
||||||
//bytesIn, bytesOut);
|
|
||||||
delay(1);
|
|
||||||
}
|
|
||||||
totalBytesOut += bytesOut;
|
|
||||||
yield();
|
|
||||||
}
|
|
||||||
if (totalBytesOut != file.size()) {
|
|
||||||
//DBG_OUTPUT_PORT.printf("file size %d bytes out %d\r\n",
|
|
||||||
// file.size(), totalBytesOut);
|
|
||||||
}
|
|
||||||
free(buf);
|
|
||||||
return totalBytesOut;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
protected:
|
|
||||||
void _addRequestHandler(RequestHandler* handler);
|
|
||||||
void _handleRequest();
|
|
||||||
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);
|
|
||||||
uint8_t _uploadReadByte(WiFiClient& client);
|
|
||||||
void _prepareHeader(String& response, int code, const char* content_type, size_t contentLength);
|
|
||||||
bool _collectHeader(const char* headerName, const char* headerValue);
|
|
||||||
|
|
||||||
struct RequestArgument {
|
|
||||||
String key;
|
|
||||||
String value;
|
|
||||||
};
|
|
||||||
|
|
||||||
WiFiServer _server;
|
|
||||||
|
|
||||||
WiFiClient _currentClient;
|
|
||||||
HTTPMethod _currentMethod;
|
|
||||||
String _currentUri;
|
|
||||||
uint8_t _currentVersion;
|
|
||||||
HTTPClientStatus _currentStatus;
|
|
||||||
unsigned long _statusChange;
|
|
||||||
|
|
||||||
RequestHandler* _currentHandler;
|
|
||||||
RequestHandler* _firstHandler;
|
|
||||||
RequestHandler* _lastHandler;
|
|
||||||
THandlerFunction _notFoundHandler;
|
|
||||||
THandlerFunction _fileUploadHandler;
|
|
||||||
|
|
||||||
int _currentArgCount;
|
|
||||||
RequestArgument* _currentArgs;
|
|
||||||
HTTPUpload _currentUpload;
|
|
||||||
|
|
||||||
int _headerKeysCount;
|
|
||||||
RequestArgument* _currentHeaders;
|
|
||||||
size_t _contentLength;
|
|
||||||
String _responseHeaders;
|
|
||||||
|
|
||||||
String _hostHeader;
|
|
||||||
bool _chunked;
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
#endif //WEBSERVER_H
|
|
@ -1,19 +0,0 @@
|
|||||||
#ifndef REQUESTHANDLER_H
|
|
||||||
#define REQUESTHANDLER_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(WebServer& server, HTTPMethod requestMethod, String requestUri) { (void) server; (void) requestMethod; (void) requestUri; return false; }
|
|
||||||
virtual void upload(WebServer& 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;
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif //REQUESTHANDLER_H
|
|
@ -1,191 +0,0 @@
|
|||||||
#ifndef REQUESTHANDLERSIMPL_H
|
|
||||||
#define REQUESTHANDLERSIMPL_H
|
|
||||||
|
|
||||||
#include "RequestHandler.h"
|
|
||||||
|
|
||||||
#ifdef ESP8266
|
|
||||||
// Table of extension->MIME strings stored in PROGMEM, needs to be global due to GCC section typing rules
|
|
||||||
static const struct {const char endsWith[16]; const char mimeType[32];} mimeTable[] ICACHE_RODATA_ATTR = {
|
|
||||||
#else
|
|
||||||
static const struct {const char endsWith[16]; const char mimeType[32];} mimeTable[] = {
|
|
||||||
#endif
|
|
||||||
{ ".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" } };
|
|
||||||
|
|
||||||
class FunctionRequestHandler : public RequestHandler {
|
|
||||||
public:
|
|
||||||
FunctionRequestHandler(WebServer::THandlerFunction fn, WebServer::THandlerFunction ufn, const String &uri, HTTPMethod method)
|
|
||||||
: _fn(fn)
|
|
||||||
, _ufn(ufn)
|
|
||||||
, _uri(uri)
|
|
||||||
, _method(method)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
bool canHandle(HTTPMethod requestMethod, String requestUri) override {
|
|
||||||
if (_method != HTTP_ANY && _method != requestMethod)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (requestUri != _uri)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool canUpload(String requestUri) override {
|
|
||||||
if (!_ufn || !canHandle(HTTP_POST, requestUri))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool handle(WebServer& server, HTTPMethod requestMethod, String requestUri) override {
|
|
||||||
(void) server;
|
|
||||||
if (!canHandle(requestMethod, requestUri))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
_fn();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void upload(WebServer& server, String requestUri, HTTPUpload& upload) override {
|
|
||||||
(void) server;
|
|
||||||
(void) upload;
|
|
||||||
if (canUpload(requestUri))
|
|
||||||
_ufn();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected:
|
|
||||||
WebServer::THandlerFunction _fn;
|
|
||||||
WebServer::THandlerFunction _ufn;
|
|
||||||
String _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)
|
|
||||||
{
|
|
||||||
_isFile = fs.exists(path);
|
|
||||||
#ifdef ESP8266
|
|
||||||
DEBUGV("StaticRequestHandler: path=%s uri=%s isFile=%d, cache_header=%s\r\n", path, uri, _isFile, cache_header);
|
|
||||||
#else
|
|
||||||
#ifdef DEBUG_ESP_HTTP_SERVER
|
|
||||||
DEBUG_OUTPUT.printf("StaticRequestHandler: path=%s uri=%s isFile=%d, cache_header=%s\r\n", path, uri, _isFile, cache_header);
|
|
||||||
#endif
|
|
||||||
#endif
|
|
||||||
_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(WebServer& server, HTTPMethod requestMethod, String requestUri) override {
|
|
||||||
if (!canHandle(requestMethod, requestUri))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
#ifdef ESP8266
|
|
||||||
DEBUGV("StaticRequestHandler::handle: request=%s _uri=%s\r\n", requestUri.c_str(), _uri.c_str());
|
|
||||||
#else
|
|
||||||
#ifdef DEBUG_ESP_HTTP_SERVER
|
|
||||||
DEBUG_OUTPUT.printf("StaticRequestHandler::handle: request=%s _uri=%s\r\n", requestUri.c_str(), _uri.c_str());
|
|
||||||
#endif
|
|
||||||
#endif
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
#ifdef ESP8266
|
|
||||||
DEBUGV("StaticRequestHandler::handle: path=%s, isFile=%d\r\n", path.c_str(), _isFile);
|
|
||||||
#else
|
|
||||||
#ifdef DEBUG_ESP_HTTP_SERVER
|
|
||||||
DEBUG_OUTPUT.printf("StaticRequestHandler::handle: path=%s, isFile=%d\r\n", path.c_str(), _isFile);
|
|
||||||
#endif
|
|
||||||
#endif
|
|
||||||
|
|
||||||
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(".gz") && !_fs.exists(path)) {
|
|
||||||
String pathWithGz = path + ".gz";
|
|
||||||
if(_fs.exists(pathWithGz))
|
|
||||||
path += ".gz";
|
|
||||||
}
|
|
||||||
|
|
||||||
File f = _fs.open(path, "r");
|
|
||||||
if (!f)
|
|
||||||
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;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
#endif //REQUESTHANDLERSIMPL_H
|
|
@ -34,7 +34,6 @@
|
|||||||
#include <WiFi.h>
|
#include <WiFi.h>
|
||||||
#include <ESPmDNS.h>
|
#include <ESPmDNS.h>
|
||||||
#include <AsyncTCP.h>
|
#include <AsyncTCP.h>
|
||||||
#include <AsyncWebServer.h>
|
|
||||||
#include <HTTPClient.h>
|
#include <HTTPClient.h>
|
||||||
/*#ifndef WLED_DISABLE_INFRARED
|
/*#ifndef WLED_DISABLE_INFRARED
|
||||||
#include <IRremote.h>
|
#include <IRremote.h>
|
||||||
@ -52,12 +51,12 @@
|
|||||||
#endif
|
#endif
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#include <ESPAsyncWebServer.h>
|
||||||
#include <EEPROM.h>
|
#include <EEPROM.h>
|
||||||
#include <WiFiUdp.h>
|
#include <WiFiUdp.h>
|
||||||
#include <DNSServer.h>
|
#include <DNSServer.h>
|
||||||
#ifndef WLED_DISABLE_OTA
|
#ifndef WLED_DISABLE_OTA
|
||||||
#include <ArduinoOTA.h>
|
#include <ArduinoOTA.h>
|
||||||
//#include "src/dependencies/webserver/ESP8266HTTPUpdateServer.h"
|
|
||||||
#endif
|
#endif
|
||||||
#include "src/dependencies/time/Time.h"
|
#include "src/dependencies/time/Time.h"
|
||||||
#include "src/dependencies/time/TimeLib.h"
|
#include "src/dependencies/time/TimeLib.h"
|
||||||
@ -81,7 +80,7 @@
|
|||||||
|
|
||||||
|
|
||||||
//version code in format yymmddb (b = daily build)
|
//version code in format yymmddb (b = daily build)
|
||||||
#define VERSION 1902162
|
#define VERSION 1902172
|
||||||
char versionString[] = "0.8.4-dev";
|
char versionString[] = "0.8.4-dev";
|
||||||
|
|
||||||
|
|
||||||
@ -399,6 +398,8 @@ uint16_t olen = 0;
|
|||||||
String messageHead, messageSub;
|
String messageHead, messageSub;
|
||||||
uint32_t optionType;
|
uint32_t optionType;
|
||||||
|
|
||||||
|
bool doReboot = false; //flag to initiate reboot from async handlers
|
||||||
|
|
||||||
//server library objects
|
//server library objects
|
||||||
AsyncWebServer server(80);
|
AsyncWebServer server(80);
|
||||||
HTTPClient* hueClient = NULL;
|
HTTPClient* hueClient = NULL;
|
||||||
@ -519,6 +520,7 @@ void loop() {
|
|||||||
handleOverlays();
|
handleOverlays();
|
||||||
|
|
||||||
yield();
|
yield();
|
||||||
|
if (doReboot) reset();
|
||||||
|
|
||||||
if (!realtimeActive) //block stuff if WARLS/Adalight is enabled
|
if (!realtimeActive) //block stuff if WARLS/Adalight is enabled
|
||||||
{
|
{
|
||||||
|
@ -282,7 +282,7 @@ void handleSettingsSet(AsyncWebServerRequest *request, byte subPage)
|
|||||||
{
|
{
|
||||||
clearEEPROM();
|
clearEEPROM();
|
||||||
serveMessage(request, 200, "All Settings erased.", "Connect to WLED-AP to setup again",255);
|
serveMessage(request, 200, "All Settings erased.", "Connect to WLED-AP to setup again",255);
|
||||||
reset();
|
doReboot = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool pwdCorrect = !otaLock; //always allow access if ota not locked
|
bool pwdCorrect = !otaLock; //always allow access if ota not locked
|
||||||
|
@ -28,14 +28,14 @@ void initServer()
|
|||||||
});
|
});
|
||||||
|
|
||||||
server.on("/reset", HTTP_GET, [](AsyncWebServerRequest *request){
|
server.on("/reset", HTTP_GET, [](AsyncWebServerRequest *request){
|
||||||
serveMessage(request, 200,"Rebooting now...","(takes ~20 seconds, wait for auto-redirect)",79);
|
serveMessage(request, 200,"Rebooting now...","Please wait ~15 seconds...",132);
|
||||||
reset();
|
doReboot = true;
|
||||||
});
|
});
|
||||||
|
|
||||||
server.on("/settings/wifi", HTTP_POST, [](AsyncWebServerRequest *request){
|
server.on("/settings/wifi", HTTP_POST, [](AsyncWebServerRequest *request){
|
||||||
if (!(wifiLock && otaLock)) handleSettingsSet(request, 1);
|
if (!(wifiLock && otaLock)) handleSettingsSet(request, 1);
|
||||||
serveMessage(request, 200,"WiFi settings saved.","Rebooting now...",255);
|
serveMessage(request, 200,"WiFi settings saved.","Rebooting now...",255);
|
||||||
reset();
|
doReboot = true;
|
||||||
});
|
});
|
||||||
|
|
||||||
server.on("/settings/leds", HTTP_POST, [](AsyncWebServerRequest *request){
|
server.on("/settings/leds", HTTP_POST, [](AsyncWebServerRequest *request){
|
||||||
@ -66,14 +66,10 @@ void initServer()
|
|||||||
|
|
||||||
server.on("/settings/sec", HTTP_POST, [](AsyncWebServerRequest *request){
|
server.on("/settings/sec", HTTP_POST, [](AsyncWebServerRequest *request){
|
||||||
handleSettingsSet(request, 6);
|
handleSettingsSet(request, 6);
|
||||||
serveMessage(request, 200,"Security settings saved.","Rebooting now... (takes ~20 seconds, wait for auto-redirect)",139);
|
serveMessage(request, 200,"Security settings saved.","Rebooting now, please wait ~15 seconds...",132);
|
||||||
reset();
|
doReboot = true;
|
||||||
});
|
});
|
||||||
|
|
||||||
/*server.on("/json", HTTP_ANY, [](AsyncWebServerRequest *request){
|
|
||||||
request->send(500, "application/json", "{\"error\":\"Not implemented\"}");
|
|
||||||
});*/
|
|
||||||
|
|
||||||
server.on("/json/effects", HTTP_GET, [](AsyncWebServerRequest *request){
|
server.on("/json/effects", HTTP_GET, [](AsyncWebServerRequest *request){
|
||||||
request->send_P(200, "application/json", JSON_mode_names);
|
request->send_P(200, "application/json", JSON_mode_names);
|
||||||
});
|
});
|
||||||
@ -82,6 +78,10 @@ void initServer()
|
|||||||
request->send_P(200, "application/json", JSON_palette_names);
|
request->send_P(200, "application/json", JSON_palette_names);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
server.on("/json", HTTP_ANY, [](AsyncWebServerRequest *request){
|
||||||
|
request->send(500, "application/json", "{\"error\":\"Not implemented\"}");
|
||||||
|
});
|
||||||
|
|
||||||
server.on("/version", HTTP_GET, [](AsyncWebServerRequest *request){
|
server.on("/version", HTTP_GET, [](AsyncWebServerRequest *request){
|
||||||
request->send(200, "text/plain", (String)VERSION);
|
request->send(200, "text/plain", (String)VERSION);
|
||||||
});
|
});
|
||||||
@ -133,7 +133,38 @@ void initServer()
|
|||||||
#endif
|
#endif
|
||||||
//init ota page
|
//init ota page
|
||||||
#ifndef WLED_DISABLE_OTA
|
#ifndef WLED_DISABLE_OTA
|
||||||
//httpUpdater.setup(&server);
|
server.on("/update", HTTP_GET, [](AsyncWebServerRequest *request){
|
||||||
|
serveMessage(request, 200, "WLED Software Update", "Your installed version: " + String(versionString) + "<br>Download the latest binary: "
|
||||||
|
"<a href=\"https://github.com/Aircoookie/WLED/releases\"><img src=\"https://img.shields.io/github/release/Aircoookie/WLED.svg?style=flat-square\"></a>"
|
||||||
|
"<br><form method='POST' action='/update' enctype='multipart/form-data'>"
|
||||||
|
"<input type='file' class=\"bt\" name='update' required><br><input type='submit' class=\"bt\" value='Update!'></form>", 254);
|
||||||
|
});
|
||||||
|
|
||||||
|
server.on("/update", HTTP_POST, [](AsyncWebServerRequest *request){
|
||||||
|
if (Update.hasError())
|
||||||
|
{
|
||||||
|
serveMessage(request, 500, "Failed updating firmware!", "Please check your file and retry!", 254); return;
|
||||||
|
}
|
||||||
|
serveMessage(request, 200, "Successfully updated firmware!", "Please wait while the module reboots...", 132);
|
||||||
|
doReboot = true;
|
||||||
|
},[](AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final){
|
||||||
|
if(!index){
|
||||||
|
DEBUG_PRINTLN("OTA Update Start");
|
||||||
|
#ifndef ARDUINO_ARCH_ESP32
|
||||||
|
Update.runAsync(true);
|
||||||
|
#endif
|
||||||
|
Update.begin((ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000);
|
||||||
|
}
|
||||||
|
if(!Update.hasError()) Update.write(data, len);
|
||||||
|
if(final){
|
||||||
|
if(Update.end(true)){
|
||||||
|
DEBUG_PRINTLN("Update Success");
|
||||||
|
} else {
|
||||||
|
DEBUG_PRINTLN("Update Failed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
#else
|
#else
|
||||||
server.on("/update", HTTP_GET, [](AsyncWebServerRequest *request){
|
server.on("/update", HTTP_GET, [](AsyncWebServerRequest *request){
|
||||||
serveMessage(request, 500, "Not implemented", "OTA updates are unsupported in this build.", 254);
|
serveMessage(request, 500, "Not implemented", "OTA updates are unsupported in this build.", 254);
|
||||||
@ -152,7 +183,6 @@ void initServer()
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
//this ceased working somehow
|
|
||||||
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
|
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
|
||||||
serveIndexOrWelcome(request);
|
serveIndexOrWelcome(request);
|
||||||
});
|
});
|
||||||
@ -169,13 +199,6 @@ void initServer()
|
|||||||
request->send(200); return;
|
request->send(200); return;
|
||||||
}
|
}
|
||||||
|
|
||||||
//workaround for subpage issue
|
|
||||||
/*if (request->url().length() == 1)
|
|
||||||
{
|
|
||||||
serveIndexOrWelcome(request);
|
|
||||||
return;
|
|
||||||
}*/
|
|
||||||
|
|
||||||
if(!handleSet(request, request->url())){
|
if(!handleSet(request, request->url())){
|
||||||
#ifndef WLED_DISABLE_ALEXA
|
#ifndef WLED_DISABLE_ALEXA
|
||||||
if(!espalexa.handleAlexaApiCall(request))
|
if(!espalexa.handleAlexaApiCall(request))
|
||||||
@ -271,18 +294,18 @@ String msgProcessor(const String& var)
|
|||||||
if (optionType < 60) //redirect to settings after optionType seconds
|
if (optionType < 60) //redirect to settings after optionType seconds
|
||||||
{
|
{
|
||||||
messageBody += "<script>setTimeout(RS," + String(optionType*1000) + ")</script>";
|
messageBody += "<script>setTimeout(RS," + String(optionType*1000) + ")</script>";
|
||||||
} else if (optionType < 120) //redirect back after optionType-60 seconds
|
} else if (optionType < 120) //redirect back after optionType-60 seconds, unused
|
||||||
{
|
{
|
||||||
messageBody += "<script>setTimeout(B," + String((optionType-60)*1000) + ")</script>";
|
//messageBody += "<script>setTimeout(B," + String((optionType-60)*1000) + ")</script>";
|
||||||
} else if (optionType < 180) //reload parent after optionType-120 seconds
|
} else if (optionType < 180) //reload parent after optionType-120 seconds
|
||||||
{
|
{
|
||||||
messageBody += "<script>setTimeout(RP," + String((optionType-120)*1000) + ")</script>";
|
messageBody += "<script>setTimeout(RP," + String((optionType-120)*1000) + ")</script>";
|
||||||
} else if (optionType == 253)
|
} else if (optionType == 253)
|
||||||
{
|
{
|
||||||
messageBody += "<br><br><form action=/settings><button type=submit>Back</button></form>"; //button to settings
|
messageBody += "<br><br><form action=/settings><button class=\"bt\" type=submit>Back</button></form>"; //button to settings
|
||||||
} else if (optionType == 254)
|
} else if (optionType == 254)
|
||||||
{
|
{
|
||||||
messageBody += "<br><br><button type=\"button\" onclick=\"B()\">Back</button>";
|
messageBody += "<br><br><button type=\"button\" class=\"bt\" onclick=\"B()\">Back</button>";
|
||||||
}
|
}
|
||||||
return messageBody;
|
return messageBody;
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user