Sfoglia il codice sorgente

Merge branch 'v1.2.0' into homeassistant_json

Chris Mullins 8 anni fa
parent
commit
e998e12d6f

+ 145 - 0
lib/ESP8266WebServer/examples/AdvancedWebServer/AdvancedWebServer.ino

@@ -0,0 +1,145 @@
+/*
+ * Copyright (c) 2015, Majenko Technologies
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * * Redistributions of source code must retain the above copyright notice, this
+ *   list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright notice, this
+ *   list of conditions and the following disclaimer in the documentation and/or
+ *   other materials provided with the distribution.
+ *
+ * * Neither the name of Majenko Technologies nor the names of its
+ *   contributors may be used to endorse or promote products derived from
+ *   this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <ESP8266WiFi.h>
+#include <WiFiClient.h>
+#include <ESP8266WebServer.h>
+#include <ESP8266mDNS.h>
+
+const char *ssid = "YourSSIDHere";
+const char *password = "YourPSKHere";
+
+ESP8266WebServer server ( 80 );
+
+const int led = 13;
+
+void handleRoot() {
+	digitalWrite ( led, 1 );
+	char temp[400];
+	int sec = millis() / 1000;
+	int min = sec / 60;
+	int hr = min / 60;
+
+	snprintf ( temp, 400,
+
+"<html>\
+  <head>\
+    <meta http-equiv='refresh' content='5'/>\
+    <title>ESP8266 Demo</title>\
+    <style>\
+      body { background-color: #cccccc; font-family: Arial, Helvetica, Sans-Serif; Color: #000088; }\
+    </style>\
+  </head>\
+  <body>\
+    <h1>Hello from ESP8266!</h1>\
+    <p>Uptime: %02d:%02d:%02d</p>\
+    <img src=\"/test.svg\" />\
+  </body>\
+</html>",
+
+		hr, min % 60, sec % 60
+	);
+	server.send ( 200, "text/html", temp );
+	digitalWrite ( led, 0 );
+}
+
+void handleNotFound() {
+	digitalWrite ( led, 1 );
+	String message = "File Not Found\n\n";
+	message += "URI: ";
+	message += server.uri();
+	message += "\nMethod: ";
+	message += ( server.method() == HTTP_GET ) ? "GET" : "POST";
+	message += "\nArguments: ";
+	message += server.args();
+	message += "\n";
+
+	for ( uint8_t i = 0; i < server.args(); i++ ) {
+		message += " " + server.argName ( i ) + ": " + server.arg ( i ) + "\n";
+	}
+
+	server.send ( 404, "text/plain", message );
+	digitalWrite ( led, 0 );
+}
+
+void setup ( void ) {
+	pinMode ( led, OUTPUT );
+	digitalWrite ( led, 0 );
+	Serial.begin ( 115200 );
+	WiFi.begin ( ssid, password );
+	Serial.println ( "" );
+
+	// Wait for connection
+	while ( WiFi.status() != WL_CONNECTED ) {
+		delay ( 500 );
+		Serial.print ( "." );
+	}
+
+	Serial.println ( "" );
+	Serial.print ( "Connected to " );
+	Serial.println ( ssid );
+	Serial.print ( "IP address: " );
+	Serial.println ( WiFi.localIP() );
+
+	if ( MDNS.begin ( "esp8266" ) ) {
+		Serial.println ( "MDNS responder started" );
+	}
+
+	server.on ( "/", handleRoot );
+	server.on ( "/test.svg", drawGraph );
+	server.on ( "/inline", []() {
+		server.send ( 200, "text/plain", "this works as well" );
+	} );
+	server.onNotFound ( handleNotFound );
+	server.begin();
+	Serial.println ( "HTTP server started" );
+}
+
+void loop ( void ) {
+	server.handleClient();
+}
+
+void drawGraph() {
+	String out = "";
+	char temp[100];
+	out += "<svg xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\" width=\"400\" height=\"150\">\n";
+ 	out += "<rect width=\"400\" height=\"150\" fill=\"rgb(250, 230, 210)\" stroke-width=\"1\" stroke=\"rgb(0, 0, 0)\" />\n";
+ 	out += "<g stroke=\"black\">\n";
+ 	int y = rand() % 130;
+ 	for (int x = 10; x < 390; x+= 10) {
+ 		int y2 = rand() % 130;
+ 		sprintf(temp, "<line x1=\"%d\" y1=\"%d\" x2=\"%d\" y2=\"%d\" stroke-width=\"1\" />\n", x, 140 - y, x + 10, 140 - y2);
+ 		out += temp;
+ 		y = y2;
+ 	}
+	out += "</g>\n</svg>\n";
+
+	server.send ( 200, "image/svg+xml", out);
+}

+ 238 - 0
lib/ESP8266WebServer/examples/FSBrowser/FSBrowser.ino

@@ -0,0 +1,238 @@
+/* 
+  FSWebServer - Example WebServer with SPIFFS backend for esp8266
+  Copyright (c) 2015 Hristo Gochkov. All rights reserved.
+  This file is part of the ESP8266WebServer library for Arduino environment.
+ 
+  This library is free software; you can redistribute it and/or
+  modify it under the terms of the GNU Lesser General Public
+  License as published by the Free Software Foundation; either
+  version 2.1 of the License, or (at your option) any later version.
+  This library is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+  Lesser General Public License for more details.
+  You should have received a copy of the GNU Lesser General Public
+  License along with this library; if not, write to the Free Software
+  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+  
+  upload the contents of the data folder with MkSPIFFS Tool ("ESP8266 Sketch Data Upload" in Tools menu in Arduino IDE)
+  or you can upload the contents of a folder if you CD in that folder and run the following command:
+  for file in `ls -A1`; do curl -F "file=@$PWD/$file" esp8266fs.local/edit; done
+  
+  access the sample web page at http://esp8266fs.local
+  edit the page by going to http://esp8266fs.local/edit
+*/
+#include <ESP8266WiFi.h>
+#include <WiFiClient.h>
+#include <ESP8266WebServer.h>
+#include <ESP8266mDNS.h>
+#include <FS.h>
+
+#define DBG_OUTPUT_PORT Serial
+
+const char* ssid = "wifi-ssid";
+const char* password = "wifi-password";
+const char* host = "esp8266fs";
+
+ESP8266WebServer server(80);
+//holds the current upload
+File fsUploadFile;
+
+//format bytes
+String formatBytes(size_t bytes){
+  if (bytes < 1024){
+    return String(bytes)+"B";
+  } else if(bytes < (1024 * 1024)){
+    return String(bytes/1024.0)+"KB";
+  } else if(bytes < (1024 * 1024 * 1024)){
+    return String(bytes/1024.0/1024.0)+"MB";
+  } else {
+    return String(bytes/1024.0/1024.0/1024.0)+"GB";
+  }
+}
+
+String getContentType(String filename){
+  if(server.hasArg("download")) return "application/octet-stream";
+  else if(filename.endsWith(".htm")) return "text/html";
+  else if(filename.endsWith(".html")) return "text/html";
+  else if(filename.endsWith(".css")) return "text/css";
+  else if(filename.endsWith(".js")) return "application/javascript";
+  else if(filename.endsWith(".png")) return "image/png";
+  else if(filename.endsWith(".gif")) return "image/gif";
+  else if(filename.endsWith(".jpg")) return "image/jpeg";
+  else if(filename.endsWith(".ico")) return "image/x-icon";
+  else if(filename.endsWith(".xml")) return "text/xml";
+  else if(filename.endsWith(".pdf")) return "application/x-pdf";
+  else if(filename.endsWith(".zip")) return "application/x-zip";
+  else if(filename.endsWith(".gz")) return "application/x-gzip";
+  return "text/plain";
+}
+
+bool handleFileRead(String path){
+  DBG_OUTPUT_PORT.println("handleFileRead: " + path);
+  if(path.endsWith("/")) path += "index.htm";
+  String contentType = getContentType(path);
+  String pathWithGz = path + ".gz";
+  if(SPIFFS.exists(pathWithGz) || SPIFFS.exists(path)){
+    if(SPIFFS.exists(pathWithGz))
+      path += ".gz";
+    File file = SPIFFS.open(path, "r");
+    size_t sent = server.streamFile(file, contentType);
+    file.close();
+    return true;
+  }
+  return false;
+}
+
+void handleFileUpload(){
+  if(server.uri() != "/edit") return;
+  HTTPUpload& upload = server.upload();
+  if(upload.status == UPLOAD_FILE_START){
+    String filename = upload.filename;
+    if(!filename.startsWith("/")) filename = "/"+filename;
+    DBG_OUTPUT_PORT.print("handleFileUpload Name: "); DBG_OUTPUT_PORT.println(filename);
+    fsUploadFile = SPIFFS.open(filename, "w");
+    filename = String();
+  } else if(upload.status == UPLOAD_FILE_WRITE){
+    //DBG_OUTPUT_PORT.print("handleFileUpload Data: "); DBG_OUTPUT_PORT.println(upload.currentSize);
+    if(fsUploadFile)
+      fsUploadFile.write(upload.buf, upload.currentSize);
+  } else if(upload.status == UPLOAD_FILE_END){
+    if(fsUploadFile)
+      fsUploadFile.close();
+    DBG_OUTPUT_PORT.print("handleFileUpload Size: "); DBG_OUTPUT_PORT.println(upload.totalSize);
+  }
+}
+
+void handleFileDelete(){
+  if(server.args() == 0) return server.send(500, "text/plain", "BAD ARGS");
+  String path = server.arg(0);
+  DBG_OUTPUT_PORT.println("handleFileDelete: " + path);
+  if(path == "/")
+    return server.send(500, "text/plain", "BAD PATH");
+  if(!SPIFFS.exists(path))
+    return server.send(404, "text/plain", "FileNotFound");
+  SPIFFS.remove(path);
+  server.send(200, "text/plain", "");
+  path = String();
+}
+
+void handleFileCreate(){
+  if(server.args() == 0)
+    return server.send(500, "text/plain", "BAD ARGS");
+  String path = server.arg(0);
+  DBG_OUTPUT_PORT.println("handleFileCreate: " + path);
+  if(path == "/")
+    return server.send(500, "text/plain", "BAD PATH");
+  if(SPIFFS.exists(path))
+    return server.send(500, "text/plain", "FILE EXISTS");
+  File file = SPIFFS.open(path, "w");
+  if(file)
+    file.close();
+  else
+    return server.send(500, "text/plain", "CREATE FAILED");
+  server.send(200, "text/plain", "");
+  path = String();
+}
+
+void handleFileList() {
+  if(!server.hasArg("dir")) {server.send(500, "text/plain", "BAD ARGS"); return;}
+  
+  String path = server.arg("dir");
+  DBG_OUTPUT_PORT.println("handleFileList: " + path);
+  Dir dir = SPIFFS.openDir(path);
+  path = String();
+
+  String output = "[";
+  while(dir.next()){
+    File entry = dir.openFile("r");
+    if (output != "[") output += ',';
+    bool isDir = false;
+    output += "{\"type\":\"";
+    output += (isDir)?"dir":"file";
+    output += "\",\"name\":\"";
+    output += String(entry.name()).substring(1);
+    output += "\"}";
+    entry.close();
+  }
+  
+  output += "]";
+  server.send(200, "text/json", output);
+}
+
+void setup(void){
+  DBG_OUTPUT_PORT.begin(115200);
+  DBG_OUTPUT_PORT.print("\n");
+  DBG_OUTPUT_PORT.setDebugOutput(true);
+  SPIFFS.begin();
+  {
+    Dir dir = SPIFFS.openDir("/");
+    while (dir.next()) {    
+      String fileName = dir.fileName();
+      size_t fileSize = dir.fileSize();
+      DBG_OUTPUT_PORT.printf("FS File: %s, size: %s\n", fileName.c_str(), formatBytes(fileSize).c_str());
+    }
+    DBG_OUTPUT_PORT.printf("\n");
+  }
+  
+
+  //WIFI INIT
+  DBG_OUTPUT_PORT.printf("Connecting to %s\n", ssid);
+  if (String(WiFi.SSID()) != String(ssid)) {
+    WiFi.begin(ssid, password);
+  }
+  
+  while (WiFi.status() != WL_CONNECTED) {
+    delay(500);
+    DBG_OUTPUT_PORT.print(".");
+  }
+  DBG_OUTPUT_PORT.println("");
+  DBG_OUTPUT_PORT.print("Connected! IP address: ");
+  DBG_OUTPUT_PORT.println(WiFi.localIP());
+
+  MDNS.begin(host);
+  DBG_OUTPUT_PORT.print("Open http://");
+  DBG_OUTPUT_PORT.print(host);
+  DBG_OUTPUT_PORT.println(".local/edit to see the file browser");
+  
+  
+  //SERVER INIT
+  //list directory
+  server.on("/list", HTTP_GET, handleFileList);
+  //load editor
+  server.on("/edit", HTTP_GET, [](){
+    if(!handleFileRead("/edit.htm")) server.send(404, "text/plain", "FileNotFound");
+  });
+  //create file
+  server.on("/edit", HTTP_PUT, handleFileCreate);
+  //delete file
+  server.on("/edit", HTTP_DELETE, handleFileDelete);
+  //first callback is called after the request has ended with all parsed arguments
+  //second callback handles file uploads at that location
+  server.on("/edit", HTTP_POST, [](){ server.send(200, "text/plain", ""); }, handleFileUpload);
+
+  //called when the url is not defined here
+  //use it to load content from SPIFFS
+  server.onNotFound([](){
+    if(!handleFileRead(server.uri()))
+      server.send(404, "text/plain", "FileNotFound");
+  });
+
+  //get heap status, analog input value and all GPIO statuses in one json call
+  server.on("/all", HTTP_GET, [](){
+    String json = "{";
+    json += "\"heap\":"+String(ESP.getFreeHeap());
+    json += ", \"analog\":"+String(analogRead(A0));
+    json += ", \"gpio\":"+String((uint32_t)(((GPI | GPO) & 0xFFFF) | ((GP16I & 0x01) << 16)));
+    json += "}";
+    server.send(200, "text/json", json);
+    json = String();
+  });
+  server.begin();
+  DBG_OUTPUT_PORT.println("HTTP server started");
+
+}
+ 
+void loop(void){
+  server.handleClient();
+}

BIN
lib/ESP8266WebServer/examples/FSBrowser/data/edit.htm.gz


BIN
lib/ESP8266WebServer/examples/FSBrowser/data/favicon.ico


BIN
lib/ESP8266WebServer/examples/FSBrowser/data/graphs.js.gz


+ 97 - 0
lib/ESP8266WebServer/examples/FSBrowser/data/index.htm

@@ -0,0 +1,97 @@
+<!-- 
+  FSWebServer - Example Index Page
+  Copyright (c) 2015 Hristo Gochkov. All rights reserved.
+  This file is part of the ESP8266WebServer library for Arduino environment.
+ 
+  This library is free software; you can redistribute it and/or
+  modify it under the terms of the GNU Lesser General Public
+  License as published by the Free Software Foundation; either
+  version 2.1 of the License, or (at your option) any later version.
+  This library is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+  Lesser General Public License for more details.
+  You should have received a copy of the GNU Lesser General Public
+  License along with this library; if not, write to the Free Software
+  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+-->
+<!DOCTYPE html>
+<html>
+<head>
+  <meta http-equiv="Content-type" content="text/html; charset=utf-8">
+  <title>ESP Monitor</title>
+  <script type="text/javascript" src="graphs.js"></script>
+  <script type="text/javascript">
+    var heap,temp,digi;
+    var reloadPeriod = 1000;
+    var running = false;
+    
+    function loadValues(){
+      if(!running) return;
+      var xh = new XMLHttpRequest();
+      xh.onreadystatechange = function(){
+        if (xh.readyState == 4){
+          if(xh.status == 200) {
+            var res = JSON.parse(xh.responseText);
+            heap.add(res.heap);
+            temp.add(res.analog);
+            digi.add(res.gpio);
+            if(running) setTimeout(loadValues, reloadPeriod);
+          } else running = false;
+        }
+      };
+      xh.open("GET", "/all", true);
+      xh.send(null);
+    };
+    
+    function run(){
+      if(!running){
+        running = true;
+        loadValues();
+      }
+    }
+    
+    function onBodyLoad(){
+      var refreshInput = document.getElementById("refresh-rate");
+      refreshInput.value = reloadPeriod;
+      refreshInput.onchange = function(e){
+        var value = parseInt(e.target.value);
+        reloadPeriod = (value > 0)?value:0;
+        e.target.value = reloadPeriod;
+      }
+      var stopButton = document.getElementById("stop-button");
+      stopButton.onclick = function(e){
+        running = false;
+      }
+      var startButton = document.getElementById("start-button");
+      startButton.onclick = function(e){
+        run();
+      }
+      
+      // Example with 10K thermistor
+      //function calcThermistor(v) {
+      //  var t = Math.log(((10230000 / v) - 10000));
+      //  t = (1/(0.001129148+(0.000234125*t)+(0.0000000876741*t*t*t)))-273.15;
+      //  return (t>120)?0:Math.round(t*10)/10;
+      //}
+      //temp = createGraph(document.getElementById("analog"), "Temperature", 100, 128, 10, 40, false, "cyan", calcThermistor);
+      
+      temp = createGraph(document.getElementById("analog"), "Analog Input", 100, 128, 0, 1023, false, "cyan");
+      heap = createGraph(document.getElementById("heap"), "Current Heap", 100, 125, 0, 30000, true, "orange");
+      digi = createDigiGraph(document.getElementById("digital"), "GPIO", 100, 146, [0, 4, 5, 16], "gold");
+      run();
+    }
+  </script>
+</head>
+<body id="index" style="margin:0; padding:0;" onload="onBodyLoad()">
+  <div id="controls" style="display: block; border: 1px solid rgb(68, 68, 68); padding: 5px; margin: 5px; width: 362px; background-color: rgb(238, 238, 238);">
+    <label>Period (ms):</label>
+    <input type="number" id="refresh-rate"/>
+    <input type="button" id="start-button" value="Start"/>
+    <input type="button" id="stop-button" value="Stop"/>
+  </div>
+  <div id="heap"></div>
+  <div id="analog"></div>
+  <div id="digital"></div>
+</body>
+</html>

+ 72 - 0
lib/ESP8266WebServer/examples/HelloServer/HelloServer.ino

@@ -0,0 +1,72 @@
+#include <ESP8266WiFi.h>
+#include <WiFiClient.h>
+#include <ESP8266WebServer.h>
+#include <ESP8266mDNS.h>
+
+const char* ssid = "........";
+const char* password = "........";
+
+ESP8266WebServer server(80);
+
+const int led = 13;
+
+void handleRoot() {
+  digitalWrite(led, 1);
+  server.send(200, "text/plain", "hello from esp8266!");
+  digitalWrite(led, 0);
+}
+
+void handleNotFound(){
+  digitalWrite(led, 1);
+  String message = "File Not Found\n\n";
+  message += "URI: ";
+  message += server.uri();
+  message += "\nMethod: ";
+  message += (server.method() == HTTP_GET)?"GET":"POST";
+  message += "\nArguments: ";
+  message += server.args();
+  message += "\n";
+  for (uint8_t i=0; i<server.args(); i++){
+    message += " " + server.argName(i) + ": " + server.arg(i) + "\n";
+  }
+  server.send(404, "text/plain", message);
+  digitalWrite(led, 0);
+}
+
+void setup(void){
+  pinMode(led, OUTPUT);
+  digitalWrite(led, 0);
+  Serial.begin(115200);
+  WiFi.begin(ssid, password);
+  Serial.println("");
+
+  // Wait for connection
+  while (WiFi.status() != WL_CONNECTED) {
+    delay(500);
+    Serial.print(".");
+  }
+  Serial.println("");
+  Serial.print("Connected to ");
+  Serial.println(ssid);
+  Serial.print("IP address: ");
+  Serial.println(WiFi.localIP());
+
+  if (MDNS.begin("esp8266")) {
+    Serial.println("MDNS responder started");
+  }
+
+  server.on("/", handleRoot);
+
+  server.on("/inline", [](){
+    server.send(200, "text/plain", "this works as well");
+  });
+
+  server.onNotFound(handleNotFound);
+
+  server.begin();
+  Serial.println("HTTP server started");
+}
+
+void loop(void){
+  server.handleClient();
+}

+ 40 - 0
lib/ESP8266WebServer/examples/HttpBasicAuth/HttpBasicAuth.ino

@@ -0,0 +1,40 @@
+#include <ESP8266WiFi.h>
+#include <ESP8266mDNS.h>
+#include <ArduinoOTA.h>
+#include <ESP8266WebServer.h>
+
+const char* ssid = "........";
+const char* password = "........";
+
+ESP8266WebServer server(80);
+
+const char* www_username = "admin";
+const char* www_password = "esp8266";
+
+void setup() {
+  Serial.begin(115200);
+  WiFi.mode(WIFI_STA);
+  WiFi.begin(ssid, password);
+  if(WiFi.waitForConnectResult() != WL_CONNECTED) {
+    Serial.println("WiFi Connect Failed! Rebooting...");
+    delay(1000);
+    ESP.restart();
+  }
+  ArduinoOTA.begin();
+
+  server.on("/", [](){
+    if(!server.authenticate(www_username, www_password))
+      return server.requestAuthentication();
+    server.send(200, "text/plain", "Login OK");
+  });
+  server.begin();
+
+  Serial.print("Open http://");
+  Serial.print(WiFi.localIP());
+  Serial.println("/ in your browser to see it working");
+}
+
+void loop() {
+  ArduinoOTA.handle();
+  server.handleClient();
+}

+ 269 - 0
lib/ESP8266WebServer/examples/SDWebServer/SDWebServer.ino

@@ -0,0 +1,269 @@
+/*
+  SDWebServer - Example WebServer with SD Card backend for esp8266
+
+  Copyright (c) 2015 Hristo Gochkov. All rights reserved.
+  This file is part of the ESP8266WebServer library for Arduino environment.
+
+  This library is free software; you can redistribute it and/or
+  modify it under the terms of the GNU Lesser General Public
+  License as published by the Free Software Foundation; either
+  version 2.1 of the License, or (at your option) any later version.
+
+  This library is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+  Lesser General Public License for more details.
+
+  You should have received a copy of the GNU Lesser General Public
+  License along with this library; if not, write to the Free Software
+  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+
+  Have a FAT Formatted SD Card connected to the SPI port of the ESP8266
+  The web root is the SD Card root folder
+  File extensions with more than 3 charecters are not supported by the SD Library
+  File Names longer than 8 charecters will be truncated by the SD library, so keep filenames shorter
+  index.htm is the default index (works on subfolders as well)
+
+  upload the contents of SdRoot to the root of the SDcard and access the editor by going to http://esp8266sd.local/edit
+
+*/
+#include <ESP8266WiFi.h>
+#include <WiFiClient.h>
+#include <ESP8266WebServer.h>
+#include <ESP8266mDNS.h>
+#include <SPI.h>
+#include <SD.h>
+
+#define DBG_OUTPUT_PORT Serial
+
+const char* ssid = "**********";
+const char* password = "**********";
+const char* host = "esp8266sd";
+
+ESP8266WebServer server(80);
+
+static bool hasSD = false;
+File uploadFile;
+
+
+void returnOK() {
+  server.send(200, "text/plain", "");
+}
+
+void returnFail(String msg) {
+  server.send(500, "text/plain", msg + "\r\n");
+}
+
+bool loadFromSdCard(String path){
+  String dataType = "text/plain";
+  if(path.endsWith("/")) path += "index.htm";
+
+  if(path.endsWith(".src")) path = path.substring(0, path.lastIndexOf("."));
+  else if(path.endsWith(".htm")) dataType = "text/html";
+  else if(path.endsWith(".css")) dataType = "text/css";
+  else if(path.endsWith(".js")) dataType = "application/javascript";
+  else if(path.endsWith(".png")) dataType = "image/png";
+  else if(path.endsWith(".gif")) dataType = "image/gif";
+  else if(path.endsWith(".jpg")) dataType = "image/jpeg";
+  else if(path.endsWith(".ico")) dataType = "image/x-icon";
+  else if(path.endsWith(".xml")) dataType = "text/xml";
+  else if(path.endsWith(".pdf")) dataType = "application/pdf";
+  else if(path.endsWith(".zip")) dataType = "application/zip";
+
+  File dataFile = SD.open(path.c_str());
+  if(dataFile.isDirectory()){
+    path += "/index.htm";
+    dataType = "text/html";
+    dataFile = SD.open(path.c_str());
+  }
+
+  if (!dataFile)
+    return false;
+
+  if (server.hasArg("download")) dataType = "application/octet-stream";
+
+  if (server.streamFile(dataFile, dataType) != dataFile.size()) {
+    DBG_OUTPUT_PORT.println("Sent less data than expected!");
+  }
+
+  dataFile.close();
+  return true;
+}
+
+void handleFileUpload(){
+  if(server.uri() != "/edit") return;
+  HTTPUpload& upload = server.upload();
+  if(upload.status == UPLOAD_FILE_START){
+    if(SD.exists((char *)upload.filename.c_str())) SD.remove((char *)upload.filename.c_str());
+    uploadFile = SD.open(upload.filename.c_str(), FILE_WRITE);
+    DBG_OUTPUT_PORT.print("Upload: START, filename: "); DBG_OUTPUT_PORT.println(upload.filename);
+  } else if(upload.status == UPLOAD_FILE_WRITE){
+    if(uploadFile) uploadFile.write(upload.buf, upload.currentSize);
+    DBG_OUTPUT_PORT.print("Upload: WRITE, Bytes: "); DBG_OUTPUT_PORT.println(upload.currentSize);
+  } else if(upload.status == UPLOAD_FILE_END){
+    if(uploadFile) uploadFile.close();
+    DBG_OUTPUT_PORT.print("Upload: END, Size: "); DBG_OUTPUT_PORT.println(upload.totalSize);
+  }
+}
+
+void deleteRecursive(String path){
+  File file = SD.open((char *)path.c_str());
+  if(!file.isDirectory()){
+    file.close();
+    SD.remove((char *)path.c_str());
+    return;
+  }
+
+  file.rewindDirectory();
+  while(true) {
+    File entry = file.openNextFile();
+    if (!entry) break;
+    String entryPath = path + "/" +entry.name();
+    if(entry.isDirectory()){
+      entry.close();
+      deleteRecursive(entryPath);
+    } else {
+      entry.close();
+      SD.remove((char *)entryPath.c_str());
+    }
+    yield();
+  }
+
+  SD.rmdir((char *)path.c_str());
+  file.close();
+}
+
+void handleDelete(){
+  if(server.args() == 0) return returnFail("BAD ARGS");
+  String path = server.arg(0);
+  if(path == "/" || !SD.exists((char *)path.c_str())) {
+    returnFail("BAD PATH");
+    return;
+  }
+  deleteRecursive(path);
+  returnOK();
+}
+
+void handleCreate(){
+  if(server.args() == 0) return returnFail("BAD ARGS");
+  String path = server.arg(0);
+  if(path == "/" || SD.exists((char *)path.c_str())) {
+    returnFail("BAD PATH");
+    return;
+  }
+
+  if(path.indexOf('.') > 0){
+    File file = SD.open((char *)path.c_str(), FILE_WRITE);
+    if(file){
+      file.write((const char *)0);
+      file.close();
+    }
+  } else {
+    SD.mkdir((char *)path.c_str());
+  }
+  returnOK();
+}
+
+void printDirectory() {
+  if(!server.hasArg("dir")) return returnFail("BAD ARGS");
+  String path = server.arg("dir");
+  if(path != "/" && !SD.exists((char *)path.c_str())) return returnFail("BAD PATH");
+  File dir = SD.open((char *)path.c_str());
+  path = String();
+  if(!dir.isDirectory()){
+    dir.close();
+    return returnFail("NOT DIR");
+  }
+  dir.rewindDirectory();
+  server.setContentLength(CONTENT_LENGTH_UNKNOWN);
+  server.send(200, "text/json", "");
+  WiFiClient client = server.client();
+
+  server.sendContent("[");
+  for (int cnt = 0; true; ++cnt) {
+    File entry = dir.openNextFile();
+    if (!entry)
+    break;
+
+    String output;
+    if (cnt > 0)
+      output = ',';
+
+    output += "{\"type\":\"";
+    output += (entry.isDirectory()) ? "dir" : "file";
+    output += "\",\"name\":\"";
+    output += entry.name();
+    output += "\"";
+    output += "}";
+    server.sendContent(output);
+    entry.close();
+ }
+ server.sendContent("]");
+ dir.close();
+}
+
+void handleNotFound(){
+  if(hasSD && loadFromSdCard(server.uri())) return;
+  String message = "SDCARD Not Detected\n\n";
+  message += "URI: ";
+  message += server.uri();
+  message += "\nMethod: ";
+  message += (server.method() == HTTP_GET)?"GET":"POST";
+  message += "\nArguments: ";
+  message += server.args();
+  message += "\n";
+  for (uint8_t i=0; i<server.args(); i++){
+    message += " NAME:"+server.argName(i) + "\n VALUE:" + server.arg(i) + "\n";
+  }
+  server.send(404, "text/plain", message);
+  DBG_OUTPUT_PORT.print(message);
+}
+
+void setup(void){
+  DBG_OUTPUT_PORT.begin(115200);
+  DBG_OUTPUT_PORT.setDebugOutput(true);
+  DBG_OUTPUT_PORT.print("\n");
+  WiFi.begin(ssid, password);
+  DBG_OUTPUT_PORT.print("Connecting to ");
+  DBG_OUTPUT_PORT.println(ssid);
+
+  // Wait for connection
+  uint8_t i = 0;
+  while (WiFi.status() != WL_CONNECTED && i++ < 20) {//wait 10 seconds
+    delay(500);
+  }
+  if(i == 21){
+    DBG_OUTPUT_PORT.print("Could not connect to");
+    DBG_OUTPUT_PORT.println(ssid);
+    while(1) delay(500);
+  }
+  DBG_OUTPUT_PORT.print("Connected! IP address: ");
+  DBG_OUTPUT_PORT.println(WiFi.localIP());
+
+  if (MDNS.begin(host)) {
+    MDNS.addService("http", "tcp", 80);
+    DBG_OUTPUT_PORT.println("MDNS responder started");
+    DBG_OUTPUT_PORT.print("You can now connect to http://");
+    DBG_OUTPUT_PORT.print(host);
+    DBG_OUTPUT_PORT.println(".local");
+  }
+
+
+  server.on("/list", HTTP_GET, printDirectory);
+  server.on("/edit", HTTP_DELETE, handleDelete);
+  server.on("/edit", HTTP_PUT, handleCreate);
+  server.on("/edit", HTTP_POST, [](){ returnOK(); }, handleFileUpload);
+  server.onNotFound(handleNotFound);
+
+  server.begin();
+  DBG_OUTPUT_PORT.println("HTTP server started");
+
+  if (SD.begin(SS)){
+     DBG_OUTPUT_PORT.println("SD Card initialized.");
+     hasSD = true;
+  }
+}
+
+void loop(void){
+  server.handleClient();
+}

File diff suppressed because it is too large
+ 674 - 0
lib/ESP8266WebServer/examples/SDWebServer/SdRoot/edit/index.htm


+ 22 - 0
lib/ESP8266WebServer/examples/SDWebServer/SdRoot/index.htm

@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <meta http-equiv="Content-type" content="text/html; charset=utf-8">
+  <title>ESP Index</title>
+  <style>
+    body {
+      background-color:black;
+      color:white;
+    }
+  </style>
+  <script type="text/javascript">
+    function onBodyLoad(){
+      console.log("we are loaded!!");
+    }
+  </script>
+</head>
+<body id="index" onload="onBodyLoad()">
+  <h1>ESP8266 Pin Functions</h1>
+<img src="pins.png" />
+</body>
+</html>

BIN
lib/ESP8266WebServer/examples/SDWebServer/SdRoot/pins.png


+ 126 - 0
lib/ESP8266WebServer/examples/SimpleAuthentification/SimpleAuthentification.ino

@@ -0,0 +1,126 @@
+#include <ESP8266WiFi.h>
+#include <WiFiClient.h>
+#include <ESP8266WebServer.h>
+
+const char* ssid = "........";
+const char* password = "........";
+
+ESP8266WebServer server(80);
+
+//Check if header is present and correct
+bool is_authentified(){
+  Serial.println("Enter is_authentified");
+  if (server.hasHeader("Cookie")){   
+    Serial.print("Found cookie: ");
+    String cookie = server.header("Cookie");
+    Serial.println(cookie);
+    if (cookie.indexOf("ESPSESSIONID=1") != -1) {
+      Serial.println("Authentification Successful");
+      return true;
+    }
+  }
+  Serial.println("Authentification Failed");
+  return false;	
+}
+
+//login page, also called for disconnect
+void handleLogin(){
+  String msg;
+  if (server.hasHeader("Cookie")){   
+    Serial.print("Found cookie: ");
+    String cookie = server.header("Cookie");
+    Serial.println(cookie);
+  }
+  if (server.hasArg("DISCONNECT")){
+    Serial.println("Disconnection");
+    String header = "HTTP/1.1 301 OK\r\nSet-Cookie: ESPSESSIONID=0\r\nLocation: /login\r\nCache-Control: no-cache\r\n\r\n";
+    server.sendContent(header);
+    return;
+  }
+  if (server.hasArg("USERNAME") && server.hasArg("PASSWORD")){
+    if (server.arg("USERNAME") == "admin" &&  server.arg("PASSWORD") == "admin" ){
+      String header = "HTTP/1.1 301 OK\r\nSet-Cookie: ESPSESSIONID=1\r\nLocation: /\r\nCache-Control: no-cache\r\n\r\n";
+      server.sendContent(header);
+      Serial.println("Log in Successful");
+      return;
+    }
+  msg = "Wrong username/password! try again.";
+  Serial.println("Log in Failed");
+  }
+  String content = "<html><body><form action='/login' method='POST'>To log in, please use : admin/admin<br>";
+  content += "User:<input type='text' name='USERNAME' placeholder='user name'><br>";
+  content += "Password:<input type='password' name='PASSWORD' placeholder='password'><br>";
+  content += "<input type='submit' name='SUBMIT' value='Submit'></form>" + msg + "<br>";
+  content += "You also can go <a href='/inline'>here</a></body></html>";
+  server.send(200, "text/html", content);
+}
+
+//root page can be accessed only if authentification is ok
+void handleRoot(){
+  Serial.println("Enter handleRoot");
+  String header;
+  if (!is_authentified()){
+    String header = "HTTP/1.1 301 OK\r\nLocation: /login\r\nCache-Control: no-cache\r\n\r\n";
+    server.sendContent(header);
+    return;
+  }
+  String content = "<html><body><H2>hello, you successfully connected to esp8266!</H2><br>";
+  if (server.hasHeader("User-Agent")){
+    content += "the user agent used is : " + server.header("User-Agent") + "<br><br>";
+  }
+  content += "You can access this page until you <a href=\"/login?DISCONNECT=YES\">disconnect</a></body></html>";
+  server.send(200, "text/html", content);
+}
+
+//no need authentification
+void handleNotFound(){
+  String message = "File Not Found\n\n";
+  message += "URI: ";
+  message += server.uri();
+  message += "\nMethod: ";
+  message += (server.method() == HTTP_GET)?"GET":"POST";
+  message += "\nArguments: ";
+  message += server.args();
+  message += "\n";
+  for (uint8_t i=0; i<server.args(); i++){
+    message += " " + server.argName(i) + ": " + server.arg(i) + "\n";
+  }
+  server.send(404, "text/plain", message);
+}
+
+void setup(void){
+  Serial.begin(115200);
+  WiFi.begin(ssid, password);
+  Serial.println("");
+
+  // Wait for connection
+  while (WiFi.status() != WL_CONNECTED) {
+    delay(500);
+    Serial.print(".");
+  }
+  Serial.println("");
+  Serial.print("Connected to ");
+  Serial.println(ssid);
+  Serial.print("IP address: ");
+  Serial.println(WiFi.localIP());
+
+
+  server.on("/", handleRoot);
+  server.on("/login", handleLogin);
+  server.on("/inline", [](){
+    server.send(200, "text/plain", "this works without need of authentification");
+  });
+
+  server.onNotFound(handleNotFound);
+  //here the list of headers to be recorded
+  const char * headerkeys[] = {"User-Agent","Cookie"} ;
+  size_t headerkeyssize = sizeof(headerkeys)/sizeof(char*);
+  //ask server to track these headers
+  server.collectHeaders(headerkeys, headerkeyssize );
+  server.begin();
+  Serial.println("HTTP server started");
+}
+
+void loop(void){
+  server.handleClient();
+}

+ 71 - 0
lib/ESP8266WebServer/examples/WebUpdate/WebUpdate.ino

@@ -0,0 +1,71 @@
+/*
+  To upload through terminal you can use: curl -F "image=@firmware.bin" esp8266-webupdate.local/update
+*/
+
+#include <ESP8266WiFi.h>
+#include <WiFiClient.h>
+#include <ESP8266WebServer.h>
+#include <ESP8266mDNS.h>
+
+const char* host = "esp8266-webupdate";
+const char* ssid = "........";
+const char* password = "........";
+
+ESP8266WebServer server(80);
+const char* serverIndex = "<form method='POST' action='/update' enctype='multipart/form-data'><input type='file' name='update'><input type='submit' value='Update'></form>";
+
+void setup(void){
+  Serial.begin(115200);
+  Serial.println();
+  Serial.println("Booting Sketch...");
+  WiFi.mode(WIFI_AP_STA);
+  WiFi.begin(ssid, password);
+  if(WiFi.waitForConnectResult() == WL_CONNECTED){
+    MDNS.begin(host);
+    server.on("/", HTTP_GET, [](){
+      server.sendHeader("Connection", "close");
+      server.sendHeader("Access-Control-Allow-Origin", "*");
+      server.send(200, "text/html", serverIndex);
+    });
+    server.on("/update", HTTP_POST, [](){
+      server.sendHeader("Connection", "close");
+      server.sendHeader("Access-Control-Allow-Origin", "*");
+      server.send(200, "text/plain", (Update.hasError())?"FAIL":"OK");
+      ESP.restart();
+    },[](){
+      HTTPUpload& upload = server.upload();
+      if(upload.status == UPLOAD_FILE_START){
+        Serial.setDebugOutput(true);
+        WiFiUDP::stopAll();
+        Serial.printf("Update: %s\n", upload.filename.c_str());
+        uint32_t maxSketchSpace = (ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000;
+        if(!Update.begin(maxSketchSpace)){//start with max available size
+          Update.printError(Serial);
+        }
+      } else if(upload.status == UPLOAD_FILE_WRITE){
+        if(Update.write(upload.buf, upload.currentSize) != upload.currentSize){
+          Update.printError(Serial);
+        }
+      } else if(upload.status == UPLOAD_FILE_END){
+        if(Update.end(true)){ //true to set the size to the current progress
+          Serial.printf("Update Success: %u\nRebooting...\n", upload.totalSize);
+        } else {
+          Update.printError(Serial);
+        }
+        Serial.setDebugOutput(false);
+      }
+      yield();
+    });
+    server.begin();
+    MDNS.addService("http", "tcp", 80);
+  
+    Serial.printf("Ready! Open http://%s.local in your browser\n", host);
+  } else {
+    Serial.println("WiFi Failed");
+  }
+}
+ 
+void loop(void){
+  server.handleClient();
+  delay(1);
+} 

+ 36 - 0
lib/ESP8266WebServer/keywords.txt

@@ -0,0 +1,36 @@
+#######################################
+# Syntax Coloring Map For Ultrasound
+#######################################
+
+#######################################
+# Datatypes (KEYWORD1)
+#######################################
+
+ESP8266WebServer	KEYWORD1
+HTTPMethod	KEYWORD1
+
+#######################################
+# Methods and Functions (KEYWORD2)
+#######################################
+
+begin	KEYWORD2
+handleClient	KEYWORD2
+on	KEYWORD2
+addHandler	KEYWORD2
+uri	KEYWORD2
+method	KEYWORD2
+client	KEYWORD2
+send	KEYWORD2
+arg	KEYWORD2
+argName	KEYWORD2
+args	KEYWORD2
+hasArg	KEYWORD2
+onNotFound	KEYWORD2
+
+#######################################
+# Constants (LITERAL1)
+#######################################
+
+HTTP_GET	LITERAL1
+HTTP_POST	LITERAL1
+HTTP_ANY	LITERAL1

+ 9 - 0
lib/ESP8266WebServer/library.properties

@@ -0,0 +1,9 @@
+name=ESP8266WebServer
+version=1.0
+author=Ivan Grokhotkov
+maintainer=Ivan Grokhtkov <ivan@esp8266.com>
+sentence=Simple web server library
+paragraph=The library supports HTTP GET and POST requests, provides argument parsing, handles one client at a time.
+category=Communication
+url=
+architectures=esp8266

+ 534 - 0
lib/ESP8266WebServer/src/ESP8266WebServer.cpp

@@ -0,0 +1,534 @@
+/*
+  ESP8266WebServer.cpp - Dead simple web-server.
+  Supports only one simultaneous client, knows how to handle GET and POST.
+
+  Copyright (c) 2014 Ivan Grokhotkov. All rights reserved.
+
+  This library is free software; you can redistribute it and/or
+  modify it under the terms of the GNU Lesser General Public
+  License as published by the Free Software Foundation; either
+  version 2.1 of the License, or (at your option) any later version.
+
+  This library is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+  Lesser General Public License for more details.
+
+  You should have received a copy of the GNU Lesser General Public
+  License along with this library; if not, write to the Free Software
+  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+  Modified 8 May 2015 by Hristo Gochkov (proper post and file upload handling)
+*/
+
+
+#include <Arduino.h>
+#include <libb64/cencode.h>
+#include "WiFiServer.h"
+#include "WiFiClient.h"
+#include "ESP8266WebServer.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";
+
+ESP8266WebServer::ESP8266WebServer(IPAddress addr, int port)
+: _server(addr, port)
+, _currentMethod(HTTP_ANY)
+, _currentHandler(0)
+, _firstHandler(0)
+, _lastHandler(0)
+, _currentArgCount(0)
+, _currentArgs(0)
+, _headerKeysCount(0)
+, _currentHeaders(0)
+, _contentLength(0)
+{
+}
+
+ESP8266WebServer::ESP8266WebServer(int port)
+: _server(port)
+, _currentMethod(HTTP_ANY)
+, _currentHandler(0)
+, _firstHandler(0)
+, _lastHandler(0)
+, _currentArgCount(0)
+, _currentArgs(0)
+, _headerKeysCount(0)
+, _currentHeaders(0)
+, _contentLength(0)
+{
+}
+
+ESP8266WebServer::~ESP8266WebServer() {
+  if (_currentHeaders)
+    delete[]_currentHeaders;
+  _headerKeysCount = 0;
+  RequestHandler* handler = _firstHandler;
+  while (handler) {
+    RequestHandler* next = handler->next();
+    delete handler;
+    handler = next;
+  }
+  close();
+}
+
+void ESP8266WebServer::begin() {
+  _currentStatus = HC_NONE;
+  _server.begin();
+  if(!_headerKeysCount)
+    collectHeaders(0, 0);
+}
+
+bool ESP8266WebServer::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 ESP8266WebServer::requestAuthentication(){
+  sendHeader("WWW-Authenticate", "Basic realm=\"Login Required\"");
+  send(401);
+}
+
+void ESP8266WebServer::on(const char* uri, ESP8266WebServer::THandlerFunction handler) {
+  on(uri, HTTP_ANY, handler);
+}
+
+void ESP8266WebServer::on(const char* uri, HTTPMethod method, ESP8266WebServer::THandlerFunction fn) {
+  on(uri, method, fn, _fileUploadHandler);
+}
+
+void ESP8266WebServer::on(const char* uri, HTTPMethod method, ESP8266WebServer::THandlerFunction fn, ESP8266WebServer::THandlerFunction ufn) {
+  _addRequestHandler(new FunctionRequestHandler(fn, ufn, uri, method));
+}
+
+void ESP8266WebServer::addHandler(RequestHandler* handler) {
+    _addRequestHandler(handler);
+}
+
+void ESP8266WebServer::_addRequestHandler(RequestHandler* handler) {
+    if (!_lastHandler) {
+      _firstHandler = handler;
+      _lastHandler = handler;
+    }
+    else {
+      _lastHandler->next(handler);
+      _lastHandler = handler;
+    }
+}
+
+void ESP8266WebServer::serveStatic(const char* uri, FS& fs, const char* path, const char* cache_header) {
+    _addRequestHandler(new StaticRequestHandler(fs, path, uri, cache_header));
+}
+
+void ESP8266WebServer::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;
+    }
+
+    _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 ESP8266WebServer::close() {
+  _server.close();
+}
+
+void ESP8266WebServer::stop() {
+  close();
+}
+
+void ESP8266WebServer::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 ESP8266WebServer::_prepareHeader(String& response, int code, const char* content_type, size_t contentLength) {
+    response = "HTTP/1.1 ";
+    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));
+    }
+    sendHeader("Connection", "close");
+    sendHeader("Access-Control-Allow-Origin", "*");
+
+    response += _responseHeaders;
+    response += "\r\n";
+    _responseHeaders = String();
+}
+
+void ESP8266WebServer::send(int code, const char* content_type, const String& content) {
+    String header;
+    _prepareHeader(header, code, content_type, content.length());
+    sendContent(header);
+
+    sendContent(content);
+}
+
+void ESP8266WebServer::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);
+    sendContent(header);
+    sendContent_P(content);
+}
+
+void ESP8266WebServer::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 ESP8266WebServer::send(int code, char* content_type, const String& content) {
+  send(code, (const char*)content_type, content);
+}
+
+void ESP8266WebServer::send(int code, const String& content_type, const String& content) {
+  send(code, (const char*)content_type.c_str(), content);
+}
+
+void ESP8266WebServer::sendContent(const String& content) {
+  const size_t unit_size = HTTP_DOWNLOAD_UNIT_SIZE;
+  size_t size_to_send = content.length();
+  const char* send_start = content.c_str();
+
+  while (size_to_send) {
+    size_t will_send = (size_to_send < unit_size) ? size_to_send : unit_size;
+    size_t sent = _currentClient.write(send_start, will_send);
+    if (sent == 0) {
+      break;
+    }
+    size_to_send -= sent;
+    send_start += sent;
+  }
+}
+
+void ESP8266WebServer::sendContent_P(PGM_P content) {
+    char contentUnit[HTTP_DOWNLOAD_UNIT_SIZE + 1];
+
+    contentUnit[HTTP_DOWNLOAD_UNIT_SIZE] = '\0';
+
+    while (content != NULL) {
+        size_t contentUnitLen;
+        PGM_P contentNext;
+
+        // due to the memccpy signature, lots of casts are needed
+        contentNext = (PGM_P)memccpy_P((void*)contentUnit, (PGM_VOID_P)content, 0, HTTP_DOWNLOAD_UNIT_SIZE);
+
+        if (contentNext == NULL) {
+            // no terminator, more data available
+            content += HTTP_DOWNLOAD_UNIT_SIZE;
+            contentUnitLen = HTTP_DOWNLOAD_UNIT_SIZE;
+        }
+        else {
+            // reached terminator. Do not send the terminator
+            contentUnitLen = contentNext - contentUnit - 1;
+            content = NULL;
+        }
+
+        // write is so overloaded, had to use the cast to get it pick the right one
+        _currentClient.write((const char*)contentUnit, contentUnitLen);
+    }
+}
+
+void ESP8266WebServer::sendContent_P(PGM_P content, size_t size) {
+    char contentUnit[HTTP_DOWNLOAD_UNIT_SIZE + 1];
+    contentUnit[HTTP_DOWNLOAD_UNIT_SIZE] = '\0';
+    size_t remaining_size = size;
+
+    while (content != NULL && remaining_size > 0) {
+        size_t contentUnitLen = HTTP_DOWNLOAD_UNIT_SIZE;
+
+        if (remaining_size < HTTP_DOWNLOAD_UNIT_SIZE) contentUnitLen = remaining_size;
+        // due to the memcpy signature, lots of casts are needed
+        memcpy_P((void*)contentUnit, (PGM_VOID_P)content, contentUnitLen);
+
+        content += contentUnitLen;
+        remaining_size -= contentUnitLen;
+
+        // write is so overloaded, had to use the cast to get it pick the right one
+        _currentClient.write((const char*)contentUnit, contentUnitLen);
+    }
+}
+
+
+String ESP8266WebServer::arg(String name) {
+  for (int i = 0; i < _currentArgCount; ++i) {
+    if ( _currentArgs[i].key == name )
+      return _currentArgs[i].value;
+  }
+  return String();
+}
+
+String ESP8266WebServer::arg(int i) {
+  if (i < _currentArgCount)
+    return _currentArgs[i].value;
+  return String();
+}
+
+String ESP8266WebServer::argName(int i) {
+  if (i < _currentArgCount)
+    return _currentArgs[i].key;
+  return String();
+}
+
+int ESP8266WebServer::args() {
+  return _currentArgCount;
+}
+
+bool ESP8266WebServer::hasArg(String  name) {
+  for (int i = 0; i < _currentArgCount; ++i) {
+    if (_currentArgs[i].key == name)
+      return true;
+  }
+  return false;
+}
+
+
+String ESP8266WebServer::header(String name) {
+  for (int i = 0; i < _headerKeysCount; ++i) {
+    if (_currentHeaders[i].key == name)
+      return _currentHeaders[i].value;
+  }
+  return String();
+}
+
+void ESP8266WebServer::collectHeaders(const char* headerKeys[], const size_t headerKeysCount) {
+  _headerKeysCount = headerKeysCount + 1;
+  if (_currentHeaders)
+     delete[]_currentHeaders;
+  _currentHeaders = new RequestArgument[_headerKeysCount];
+  _currentHeaders[0].key = AUTHORIZATION_HEADER;
+  for (int i = 1; i < _headerKeysCount; i++){
+    _currentHeaders[i].key = headerKeys[i-1];
+  }
+}
+
+String ESP8266WebServer::header(int i) {
+  if (i < _headerKeysCount)
+    return _currentHeaders[i].value;
+  return String();
+}
+
+String ESP8266WebServer::headerName(int i) {
+  if (i < _headerKeysCount)
+    return _currentHeaders[i].key;
+  return String();
+}
+
+int ESP8266WebServer::headers() {
+  return _headerKeysCount;
+}
+
+bool ESP8266WebServer::hasHeader(String name) {
+  for (int i = 0; i < _headerKeysCount; ++i) {
+    if ((_currentHeaders[i].key == name) &&  (_currentHeaders[i].value.length() > 0))
+      return true;
+  }
+  return false;
+}
+
+String ESP8266WebServer::hostHeader() {
+  return _hostHeader;
+}
+
+void ESP8266WebServer::onFileUpload(THandlerFunction fn) {
+  _fileUploadHandler = fn;
+}
+
+void ESP8266WebServer::onNotFound(THandlerFunction fn) {
+  _notFoundHandler = fn;
+}
+
+void ESP8266WebServer::_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 ESP8266WebServer::_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 "";
+  }
+}

+ 181 - 0
lib/ESP8266WebServer/src/ESP8266WebServer.h

@@ -0,0 +1,181 @@
+/*
+  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 <functional>
+#include <ESP8266WiFi.h>
+
+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
+#define HTTP_UPLOAD_BUFLEN 20
+#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_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 ESP8266WebServer;
+
+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 ESP8266WebServer
+{
+public:
+  ESP8266WebServer(IPAddress addr, int port = 80);
+  ESP8266WebServer(int port = 80);
+  ~ESP8266WebServer();
+
+  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 char* uri, THandlerFunction handler);
+  void on(const char* uri, HTTPMethod method, THandlerFunction fn);
+  void on(const char* 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(const char* headerKeys[], const size_t headerKeysCount); // 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) { _contentLength = 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);
+
+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, HTTP_DOWNLOAD_UNIT_SIZE);
+}
+
+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;
+  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;
+
+};
+
+
+#endif //ESP8266WEBSERVER_H

+ 589 - 0
lib/ESP8266WebServer/src/Parsing.cpp

@@ -0,0 +1,589 @@
+/*
+  Parsing.cpp - HTTP request parsing.
+
+  Copyright (c) 2015 Ivan Grokhotkov. All rights reserved.
+
+  This library is free software; you can redistribute it and/or
+  modify it under the terms of the GNU Lesser General Public
+  License as published by the Free Software Foundation; either
+  version 2.1 of the License, or (at your option) any later version.
+
+  This library is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+  Lesser General Public License for more details.
+
+  You should have received a copy of the GNU Lesser General Public
+  License along with this library; if not, write to the Free Software
+  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+  Modified 8 May 2015 by Hristo Gochkov (proper post and file upload handling)
+*/
+
+#include <Arduino.h>
+#include "WiFiServer.h"
+#include "WiFiClient.h"
+#include "ESP8266WebServer.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 ESP8266WebServer::_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 searchStr = "";
+  int hasSearch = url.indexOf('?');
+  if (hasSearch != -1){
+    searchStr = url.substring(hasSearch + 1);
+    url = url.substring(0, hasSearch);
+  }
+  _currentUri = url;
+
+  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;
+    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 == "Content-Type"){
+        if (headerValue.startsWith("text/plain")){
+          isForm = false;
+        } else if (headerValue.startsWith("multipart/form-data")){
+          boundaryStr = headerValue.substring(headerValue.indexOf('=')+1);
+          isForm = true;
+        }
+      } else if (headerName == "Content-Length"){
+        contentLength = headerValue.toInt();
+      } else if (headerName == "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;
+      }
+#ifdef DEBUG_ESP_HTTP_SERVER
+      DEBUG_OUTPUT.print("Plain: ");
+      DEBUG_OUTPUT.println(plainBuf);
+#endif
+      if (contentLength > 0) {
+        if (searchStr != "") searchStr += '&';
+        if(plainBuf[0] == '{' || plainBuf[0] == '[' || strstr(plainBuf, "=") == NULL){
+          //plain post json or other data
+          searchStr += "plain=";
+          searchStr += plainBuf;
+        } else {
+          searchStr += plainBuf;
+        }
+        free(plainBuf);
+      }
+    }
+    _parseArguments(searchStr);
+    if (isForm){
+      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 == "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 ESP8266WebServer::_collectHeader(const char* headerName, const char* headerValue) {
+  for (int i = 0; i < _headerKeysCount; i++) {
+    if (_currentHeaders[i].key==headerName) {
+            _currentHeaders[i].value=headerValue;
+            return true;
+        }
+  }
+  return false;
+}
+
+void ESP8266WebServer::_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;
+    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];
+  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 ESP8266WebServer::_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 ESP8266WebServer::_uploadReadByte(WiFiClient& client){
+  int res = client.read();
+  if(res == -1){
+    while(!client.available() && client.connected())
+      yield();
+    res = client.read();
+  }
+  return (uint8_t)res;
+}
+
+bool ESP8266WebServer::_parseForm(WiFiClient& client, String boundary, uint32_t 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.startsWith("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.startsWith("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 ESP8266WebServer::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 ESP8266WebServer::_parseFormUploadAborted(){
+  _currentUpload.status = UPLOAD_FILE_ABORTED;
+  if(_currentHandler && _currentHandler->canUpload(_currentUri))
+    _currentHandler->upload(*this, _currentUri, _currentUpload);
+  return false;
+}

+ 19 - 0
lib/ESP8266WebServer/src/detail/RequestHandler.h

@@ -0,0 +1,19 @@
+#ifndef REQUESTHANDLER_H
+#define REQUESTHANDLER_H
+
+class RequestHandler {
+public:
+    virtual ~RequestHandler() { }
+    virtual bool canHandle(HTTPMethod method, String uri) { return false; }
+    virtual bool canUpload(String uri) { return false; }
+    virtual bool handle(ESP8266WebServer& server, HTTPMethod requestMethod, String requestUri) { return false; }
+    virtual void upload(ESP8266WebServer& server, String requestUri, HTTPUpload& upload) {}
+
+    RequestHandler* next() { return _next; }
+    void next(RequestHandler* r) { _next = r; }
+
+private:
+    RequestHandler* _next = nullptr;
+};
+
+#endif //REQUESTHANDLER_H

+ 150 - 0
lib/ESP8266WebServer/src/detail/RequestHandlersImpl.h

@@ -0,0 +1,150 @@
+#ifndef REQUESTHANDLERSIMPL_H
+#define REQUESTHANDLERSIMPL_H
+
+#include "RequestHandler.h"
+
+class FunctionRequestHandler : public RequestHandler {
+public:
+    FunctionRequestHandler(ESP8266WebServer::THandlerFunction fn, ESP8266WebServer::THandlerFunction ufn, const char* 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(ESP8266WebServer& server, HTTPMethod requestMethod, String requestUri) override {
+        if (!canHandle(requestMethod, requestUri))
+            return false;
+
+        _fn();
+        return true;
+    }
+
+    void upload(ESP8266WebServer& server, String requestUri, HTTPUpload& upload) override {
+        if (canUpload(requestUri))
+            _ufn();
+    }
+
+protected:
+    ESP8266WebServer::THandlerFunction _fn;
+    ESP8266WebServer::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);
+        DEBUGV("StaticRequestHandler: path=%s uri=%s isFile=%d, cache_header=%s\r\n", path, uri, _isFile, cache_header);
+        _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(ESP8266WebServer& server, HTTPMethod requestMethod, String requestUri) override {
+        if (!canHandle(requestMethod, requestUri))
+            return false;
+
+        DEBUGV("StaticRequestHandler::handle: request=%s _uri=%s\r\n", requestUri.c_str(), _uri.c_str());
+
+        String path(_path);
+
+        if (!_isFile) {
+            // Base URI doesn't point to a file.
+            // If a directory is requested, look for index file.
+            if (requestUri.endsWith("/")) requestUri += "index.htm";
+
+            // Append whatever follows this URI in request to get the file path.
+            path += requestUri.substring(_baseUriLength);
+        }
+        DEBUGV("StaticRequestHandler::handle: path=%s, isFile=%d\r\n", path.c_str(), _isFile);
+
+        String contentType = getContentType(path);
+
+        // look for gz file, only if the original specified path is not a gz.  So part only works to send gzip via content encoding when a non compressed is asked for
+        // if you point the the path to gzip you will serve the gzip as content type "application/x-gzip", not text or javascript etc...
+        if (!path.endsWith(".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) {
+        if (path.endsWith(".html")) return "text/html";
+        else if (path.endsWith(".htm")) return "text/html";
+        else if (path.endsWith(".css")) return "text/css";
+        else if (path.endsWith(".txt")) return "text/plain";
+        else if (path.endsWith(".js")) return "application/javascript";
+        else if (path.endsWith(".png")) return "image/png";
+        else if (path.endsWith(".gif")) return "image/gif";
+        else if (path.endsWith(".jpg")) return "image/jpeg";
+        else if (path.endsWith(".ico")) return "image/x-icon";
+        else if (path.endsWith(".svg")) return "image/svg+xml";
+        else if (path.endsWith(".ttf")) return "application/x-font-ttf";
+        else if (path.endsWith(".otf")) return "application/x-font-opentype";
+        else if (path.endsWith(".woff")) return "application/font-woff";
+        else if (path.endsWith(".woff2")) return "application/font-woff2";
+        else if (path.endsWith(".eot")) return "application/vnd.ms-fontobject";
+        else if (path.endsWith(".sfnt")) return "application/font-sfnt";
+        else if (path.endsWith(".xml")) return "text/xml";
+        else if (path.endsWith(".pdf")) return "application/pdf";
+        else if (path.endsWith(".zip")) return "application/zip";
+        else if(path.endsWith(".gz")) return "application/x-gzip";
+        else if (path.endsWith(".appcache")) return "text/cache-manifest";
+        return "application/octet-stream";
+    }
+
+protected:
+    FS _fs;
+    String _uri;
+    String _path;
+    String _cache_header;
+    bool _isFile;
+    size_t _baseUriLength;
+};
+
+
+#endif //REQUESTHANDLERSIMPL_H