[
  {
    "path": "examples/AdvancedWebServer/AdvancedWebServer.ino",
    "content": "/*\n * Copyright (c) 2015, Majenko Technologies\n * All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without modification,\n * are permitted provided that the following conditions are met:\n *\n * * Redistributions of source code must retain the above copyright notice, this\n *   list of conditions and the following disclaimer.\n *\n * * Redistributions in binary form must reproduce the above copyright notice, this\n *   list of conditions and the following disclaimer in the documentation and/or\n *   other materials provided with the distribution.\n *\n * * Neither the name of Majenko Technologies nor the names of its\n *   contributors may be used to endorse or promote products derived from\n *   this software without specific prior written permission.\n *\n * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND\n * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\n * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\n * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR\n * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES\n * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;\n * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON\n * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n */\n\n#include <ESP8266WiFi.h>\n#include <WiFiClient.h>\n#include <ESP8266WebServer.h>\n#include <ESP8266mDNS.h>\n\nconst char *ssid = \"YourSSIDHere\";\nconst char *password = \"YourPSKHere\";\n\nESP8266WebServer server ( 80 );\n\nconst int led = 13;\n\nvoid handleRoot() {\n\tdigitalWrite ( led, 1 );\n\tchar temp[400];\n\tint sec = millis() / 1000;\n\tint min = sec / 60;\n\tint hr = min / 60;\n\n\tsnprintf ( temp, 400,\n\n\"<html>\\\n  <head>\\\n    <meta http-equiv='refresh' content='5'/>\\\n    <title>ESP8266 Demo</title>\\\n    <style>\\\n      body { background-color: #cccccc; font-family: Arial, Helvetica, Sans-Serif; Color: #000088; }\\\n    </style>\\\n  </head>\\\n  <body>\\\n    <h1>Hello from ESP8266!</h1>\\\n    <p>Uptime: %02d:%02d:%02d</p>\\\n    <img src=\\\"/test.svg\\\" />\\\n  </body>\\\n</html>\",\n\n\t\thr, min % 60, sec % 60\n\t);\n\tserver.send ( 200, \"text/html\", temp );\n\tdigitalWrite ( led, 0 );\n}\n\nvoid handleNotFound() {\n\tdigitalWrite ( led, 1 );\n\tString message = \"File Not Found\\n\\n\";\n\tmessage += \"URI: \";\n\tmessage += server.uri();\n\tmessage += \"\\nMethod: \";\n\tmessage += ( server.method() == HTTP_GET ) ? \"GET\" : \"POST\";\n\tmessage += \"\\nArguments: \";\n\tmessage += server.args();\n\tmessage += \"\\n\";\n\n\tfor ( uint8_t i = 0; i < server.args(); i++ ) {\n\t\tmessage += \" \" + server.argName ( i ) + \": \" + server.arg ( i ) + \"\\n\";\n\t}\n\n\tserver.send ( 404, \"text/plain\", message );\n\tdigitalWrite ( led, 0 );\n}\n\nvoid setup ( void ) {\n\tpinMode ( led, OUTPUT );\n\tdigitalWrite ( led, 0 );\n\tSerial.begin ( 115200 );\n\tWiFi.begin ( ssid, password );\n\tSerial.println ( \"\" );\n\n\t// Wait for connection\n\twhile ( WiFi.status() != WL_CONNECTED ) {\n\t\tdelay ( 500 );\n\t\tSerial.print ( \".\" );\n\t}\n\n\tSerial.println ( \"\" );\n\tSerial.print ( \"Connected to \" );\n\tSerial.println ( ssid );\n\tSerial.print ( \"IP address: \" );\n\tSerial.println ( WiFi.localIP() );\n\n\tif ( MDNS.begin ( \"esp8266\" ) ) {\n\t\tSerial.println ( \"MDNS responder started\" );\n\t}\n\n\tserver.on ( \"/\", handleRoot );\n\tserver.on ( \"/test.svg\", drawGraph );\n\tserver.on ( \"/inline\", []() {\n\t\tserver.send ( 200, \"text/plain\", \"this works as well\" );\n\t} );\n\tserver.onNotFound ( handleNotFound );\n\tserver.begin();\n\tSerial.println ( \"HTTP server started\" );\n}\n\nvoid loop ( void ) {\n\tserver.handleClient();\n}\n\nvoid drawGraph() {\n\tString out = \"\";\n\tchar temp[100];\n\tout += \"<svg xmlns=\\\"http://www.w3.org/2000/svg\\\" version=\\\"1.1\\\" width=\\\"400\\\" height=\\\"150\\\">\\n\";\n \tout += \"<rect width=\\\"400\\\" height=\\\"150\\\" fill=\\\"rgb(250, 230, 210)\\\" stroke-width=\\\"1\\\" stroke=\\\"rgb(0, 0, 0)\\\" />\\n\";\n \tout += \"<g stroke=\\\"black\\\">\\n\";\n \tint y = rand() % 130;\n \tfor (int x = 10; x < 390; x+= 10) {\n \t\tint y2 = rand() % 130;\n \t\tsprintf(temp, \"<line x1=\\\"%d\\\" y1=\\\"%d\\\" x2=\\\"%d\\\" y2=\\\"%d\\\" stroke-width=\\\"1\\\" />\\n\", x, 140 - y, x + 10, 140 - y2);\n \t\tout += temp;\n \t\ty = y2;\n \t}\n\tout += \"</g>\\n</svg>\\n\";\n\n\tserver.send ( 200, \"image/svg+xml\", out);\n}\n"
  },
  {
    "path": "examples/FSBrowser/FSBrowser.ino",
    "content": "/* \n  FSWebServer - Example WebServer with SPIFFS backend for esp8266\n  Copyright (c) 2015 Hristo Gochkov. All rights reserved.\n  This file is part of the ESP8266WebServer library for Arduino environment.\n \n  This library is free software; you can redistribute it and/or\n  modify it under the terms of the GNU Lesser General Public\n  License as published by the Free Software Foundation; either\n  version 2.1 of the License, or (at your option) any later version.\n  This library is distributed in the hope that it will be useful,\n  but WITHOUT ANY WARRANTY; without even the implied warranty of\n  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n  Lesser General Public License for more details.\n  You should have received a copy of the GNU Lesser General Public\n  License along with this library; if not, write to the Free Software\n  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA\n  \n  upload the contents of the data folder with MkSPIFFS Tool (\"ESP8266 Sketch Data Upload\" in Tools menu in Arduino IDE)\n  or you can upload the contents of a folder if you CD in that folder and run the following command:\n  for file in `ls -A1`; do curl -F \"file=@$PWD/$file\" esp8266fs.local/edit; done\n  \n  access the sample web page at http://esp8266fs.local\n  edit the page by going to http://esp8266fs.local/edit\n*/\n#include <ESP8266WiFi.h>\n#include <WiFiClient.h>\n#include <ESP8266WebServer.h>\n#include <ESP8266mDNS.h>\n#include <FS.h>\n\n#define DBG_OUTPUT_PORT Serial\n\nconst char* ssid = \"wifi-ssid\";\nconst char* password = \"wifi-password\";\nconst char* host = \"esp8266fs\";\n\nESP8266WebServer server(80);\n//holds the current upload\nFile fsUploadFile;\n\n//format bytes\nString formatBytes(size_t bytes){\n  if (bytes < 1024){\n    return String(bytes)+\"B\";\n  } else if(bytes < (1024 * 1024)){\n    return String(bytes/1024.0)+\"KB\";\n  } else if(bytes < (1024 * 1024 * 1024)){\n    return String(bytes/1024.0/1024.0)+\"MB\";\n  } else {\n    return String(bytes/1024.0/1024.0/1024.0)+\"GB\";\n  }\n}\n\nString getContentType(String filename){\n  if(server.hasArg(\"download\")) return \"application/octet-stream\";\n  else if(filename.endsWith(\".htm\")) return \"text/html\";\n  else if(filename.endsWith(\".html\")) return \"text/html\";\n  else if(filename.endsWith(\".css\")) return \"text/css\";\n  else if(filename.endsWith(\".js\")) return \"application/javascript\";\n  else if(filename.endsWith(\".png\")) return \"image/png\";\n  else if(filename.endsWith(\".gif\")) return \"image/gif\";\n  else if(filename.endsWith(\".jpg\")) return \"image/jpeg\";\n  else if(filename.endsWith(\".ico\")) return \"image/x-icon\";\n  else if(filename.endsWith(\".xml\")) return \"text/xml\";\n  else if(filename.endsWith(\".pdf\")) return \"application/x-pdf\";\n  else if(filename.endsWith(\".zip\")) return \"application/x-zip\";\n  else if(filename.endsWith(\".gz\")) return \"application/x-gzip\";\n  return \"text/plain\";\n}\n\nbool handleFileRead(String path){\n  DBG_OUTPUT_PORT.println(\"handleFileRead: \" + path);\n  if(path.endsWith(\"/\")) path += \"index.htm\";\n  String contentType = getContentType(path);\n  String pathWithGz = path + \".gz\";\n  if(SPIFFS.exists(pathWithGz) || SPIFFS.exists(path)){\n    if(SPIFFS.exists(pathWithGz))\n      path += \".gz\";\n    File file = SPIFFS.open(path, \"r\");\n    size_t sent = server.streamFile(file, contentType);\n    file.close();\n    return true;\n  }\n  return false;\n}\n\nvoid handleFileUpload(){\n  if(server.uri() != \"/edit\") return;\n  HTTPUpload& upload = server.upload();\n  if(upload.status == UPLOAD_FILE_START){\n    String filename = upload.filename;\n    if(!filename.startsWith(\"/\")) filename = \"/\"+filename;\n    DBG_OUTPUT_PORT.print(\"handleFileUpload Name: \"); DBG_OUTPUT_PORT.println(filename);\n    fsUploadFile = SPIFFS.open(filename, \"w\");\n    filename = String();\n  } else if(upload.status == UPLOAD_FILE_WRITE){\n    //DBG_OUTPUT_PORT.print(\"handleFileUpload Data: \"); DBG_OUTPUT_PORT.println(upload.currentSize);\n    if(fsUploadFile)\n      fsUploadFile.write(upload.buf, upload.currentSize);\n  } else if(upload.status == UPLOAD_FILE_END){\n    if(fsUploadFile)\n      fsUploadFile.close();\n    DBG_OUTPUT_PORT.print(\"handleFileUpload Size: \"); DBG_OUTPUT_PORT.println(upload.totalSize);\n  }\n}\n\nvoid handleFileDelete(){\n  if(server.args() == 0) return server.send(500, \"text/plain\", \"BAD ARGS\");\n  String path = server.arg(0);\n  DBG_OUTPUT_PORT.println(\"handleFileDelete: \" + path);\n  if(path == \"/\")\n    return server.send(500, \"text/plain\", \"BAD PATH\");\n  if(!SPIFFS.exists(path))\n    return server.send(404, \"text/plain\", \"FileNotFound\");\n  SPIFFS.remove(path);\n  server.send(200, \"text/plain\", \"\");\n  path = String();\n}\n\nvoid handleFileCreate(){\n  if(server.args() == 0)\n    return server.send(500, \"text/plain\", \"BAD ARGS\");\n  String path = server.arg(0);\n  DBG_OUTPUT_PORT.println(\"handleFileCreate: \" + path);\n  if(path == \"/\")\n    return server.send(500, \"text/plain\", \"BAD PATH\");\n  if(SPIFFS.exists(path))\n    return server.send(500, \"text/plain\", \"FILE EXISTS\");\n  File file = SPIFFS.open(path, \"w\");\n  if(file)\n    file.close();\n  else\n    return server.send(500, \"text/plain\", \"CREATE FAILED\");\n  server.send(200, \"text/plain\", \"\");\n  path = String();\n}\n\nvoid handleFileList() {\n  if(!server.hasArg(\"dir\")) {server.send(500, \"text/plain\", \"BAD ARGS\"); return;}\n  \n  String path = server.arg(\"dir\");\n  DBG_OUTPUT_PORT.println(\"handleFileList: \" + path);\n  Dir dir = SPIFFS.openDir(path);\n  path = String();\n\n  String output = \"[\";\n  while(dir.next()){\n    File entry = dir.openFile(\"r\");\n    if (output != \"[\") output += ',';\n    bool isDir = false;\n    output += \"{\\\"type\\\":\\\"\";\n    output += (isDir)?\"dir\":\"file\";\n    output += \"\\\",\\\"name\\\":\\\"\";\n    output += String(entry.name()).substring(1);\n    output += \"\\\"}\";\n    entry.close();\n  }\n  \n  output += \"]\";\n  server.send(200, \"text/json\", output);\n}\n\nvoid setup(void){\n  DBG_OUTPUT_PORT.begin(115200);\n  DBG_OUTPUT_PORT.print(\"\\n\");\n  DBG_OUTPUT_PORT.setDebugOutput(true);\n  SPIFFS.begin();\n  {\n    Dir dir = SPIFFS.openDir(\"/\");\n    while (dir.next()) {    \n      String fileName = dir.fileName();\n      size_t fileSize = dir.fileSize();\n      DBG_OUTPUT_PORT.printf(\"FS File: %s, size: %s\\n\", fileName.c_str(), formatBytes(fileSize).c_str());\n    }\n    DBG_OUTPUT_PORT.printf(\"\\n\");\n  }\n  \n\n  //WIFI INIT\n  DBG_OUTPUT_PORT.printf(\"Connecting to %s\\n\", ssid);\n  if (String(WiFi.SSID()) != String(ssid)) {\n    WiFi.begin(ssid, password);\n  }\n  \n  while (WiFi.status() != WL_CONNECTED) {\n    delay(500);\n    DBG_OUTPUT_PORT.print(\".\");\n  }\n  DBG_OUTPUT_PORT.println(\"\");\n  DBG_OUTPUT_PORT.print(\"Connected! IP address: \");\n  DBG_OUTPUT_PORT.println(WiFi.localIP());\n\n  MDNS.begin(host);\n  DBG_OUTPUT_PORT.print(\"Open http://\");\n  DBG_OUTPUT_PORT.print(host);\n  DBG_OUTPUT_PORT.println(\".local/edit to see the file browser\");\n  \n  \n  //SERVER INIT\n  //list directory\n  server.on(\"/list\", HTTP_GET, handleFileList);\n  //load editor\n  server.on(\"/edit\", HTTP_GET, [](){\n    if(!handleFileRead(\"/edit.htm\")) server.send(404, \"text/plain\", \"FileNotFound\");\n  });\n  //create file\n  server.on(\"/edit\", HTTP_PUT, handleFileCreate);\n  //delete file\n  server.on(\"/edit\", HTTP_DELETE, handleFileDelete);\n  //first callback is called after the request has ended with all parsed arguments\n  //second callback handles file uploads at that location\n  server.on(\"/edit\", HTTP_POST, [](){ server.send(200, \"text/plain\", \"\"); }, handleFileUpload);\n\n  //called when the url is not defined here\n  //use it to load content from SPIFFS\n  server.onNotFound([](){\n    if(!handleFileRead(server.uri()))\n      server.send(404, \"text/plain\", \"FileNotFound\");\n  });\n\n  //get heap status, analog input value and all GPIO statuses in one json call\n  server.on(\"/all\", HTTP_GET, [](){\n    String json = \"{\";\n    json += \"\\\"heap\\\":\"+String(ESP.getFreeHeap());\n    json += \", \\\"analog\\\":\"+String(analogRead(A0));\n    json += \", \\\"gpio\\\":\"+String((uint32_t)(((GPI | GPO) & 0xFFFF) | ((GP16I & 0x01) << 16)));\n    json += \"}\";\n    server.send(200, \"text/json\", json);\n    json = String();\n  });\n  server.begin();\n  DBG_OUTPUT_PORT.println(\"HTTP server started\");\n\n}\n \nvoid loop(void){\n  server.handleClient();\n}\n"
  },
  {
    "path": "examples/FSBrowser/data/index.htm",
    "content": "<!-- \n  FSWebServer - Example Index Page\n  Copyright (c) 2015 Hristo Gochkov. All rights reserved.\n  This file is part of the ESP8266WebServer library for Arduino environment.\n \n  This library is free software; you can redistribute it and/or\n  modify it under the terms of the GNU Lesser General Public\n  License as published by the Free Software Foundation; either\n  version 2.1 of the License, or (at your option) any later version.\n  This library is distributed in the hope that it will be useful,\n  but WITHOUT ANY WARRANTY; without even the implied warranty of\n  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n  Lesser General Public License for more details.\n  You should have received a copy of the GNU Lesser General Public\n  License along with this library; if not, write to the Free Software\n  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA\n-->\n<!DOCTYPE html>\n<html>\n<head>\n  <meta http-equiv=\"Content-type\" content=\"text/html; charset=utf-8\">\n  <title>ESP Monitor</title>\n  <script type=\"text/javascript\" src=\"graphs.js\"></script>\n  <script type=\"text/javascript\">\n    var heap,temp,digi;\n    var reloadPeriod = 1000;\n    var running = false;\n    \n    function loadValues(){\n      if(!running) return;\n      var xh = new XMLHttpRequest();\n      xh.onreadystatechange = function(){\n        if (xh.readyState == 4){\n          if(xh.status == 200) {\n            var res = JSON.parse(xh.responseText);\n            heap.add(res.heap);\n            temp.add(res.analog);\n            digi.add(res.gpio);\n            if(running) setTimeout(loadValues, reloadPeriod);\n          } else running = false;\n        }\n      };\n      xh.open(\"GET\", \"/all\", true);\n      xh.send(null);\n    };\n    \n    function run(){\n      if(!running){\n        running = true;\n        loadValues();\n      }\n    }\n    \n    function onBodyLoad(){\n      var refreshInput = document.getElementById(\"refresh-rate\");\n      refreshInput.value = reloadPeriod;\n      refreshInput.onchange = function(e){\n        var value = parseInt(e.target.value);\n        reloadPeriod = (value > 0)?value:0;\n        e.target.value = reloadPeriod;\n      }\n      var stopButton = document.getElementById(\"stop-button\");\n      stopButton.onclick = function(e){\n        running = false;\n      }\n      var startButton = document.getElementById(\"start-button\");\n      startButton.onclick = function(e){\n        run();\n      }\n      \n      // Example with 10K thermistor\n      //function calcThermistor(v) {\n      //  var t = Math.log(((10230000 / v) - 10000));\n      //  t = (1/(0.001129148+(0.000234125*t)+(0.0000000876741*t*t*t)))-273.15;\n      //  return (t>120)?0:Math.round(t*10)/10;\n      //}\n      //temp = createGraph(document.getElementById(\"analog\"), \"Temperature\", 100, 128, 10, 40, false, \"cyan\", calcThermistor);\n      \n      temp = createGraph(document.getElementById(\"analog\"), \"Analog Input\", 100, 128, 0, 1023, false, \"cyan\");\n      heap = createGraph(document.getElementById(\"heap\"), \"Current Heap\", 100, 125, 0, 30000, true, \"orange\");\n      digi = createDigiGraph(document.getElementById(\"digital\"), \"GPIO\", 100, 146, [0, 4, 5, 16], \"gold\");\n      run();\n    }\n  </script>\n</head>\n<body id=\"index\" style=\"margin:0; padding:0;\" onload=\"onBodyLoad()\">\n  <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);\">\n    <label>Period (ms):</label>\n    <input type=\"number\" id=\"refresh-rate\"/>\n    <input type=\"button\" id=\"start-button\" value=\"Start\"/>\n    <input type=\"button\" id=\"stop-button\" value=\"Stop\"/>\n  </div>\n  <div id=\"heap\"></div>\n  <div id=\"analog\"></div>\n  <div id=\"digital\"></div>\n</body>\n</html>"
  },
  {
    "path": "examples/HelloServer/HelloServer.ino",
    "content": "#include <ESP8266WiFi.h>\n#include <WiFiClient.h>\n#include <ESP8266WebServer.h>\n#include <ESP8266mDNS.h>\n\nconst char* ssid = \"........\";\nconst char* password = \"........\";\n\nESP8266WebServer server(80);\n\nconst int led = 13;\n\nvoid handleRoot() {\n  digitalWrite(led, 1);\n  server.send(200, \"text/plain\", \"hello from esp8266!\");\n  digitalWrite(led, 0);\n}\n\nvoid handleNotFound(){\n  digitalWrite(led, 1);\n  String message = \"File Not Found\\n\\n\";\n  message += \"URI: \";\n  message += server.uri();\n  message += \"\\nMethod: \";\n  message += (server.method() == HTTP_GET)?\"GET\":\"POST\";\n  message += \"\\nArguments: \";\n  message += server.args();\n  message += \"\\n\";\n  for (uint8_t i=0; i<server.args(); i++){\n    message += \" \" + server.argName(i) + \": \" + server.arg(i) + \"\\n\";\n  }\n  server.send(404, \"text/plain\", message);\n  digitalWrite(led, 0);\n}\n\nvoid setup(void){\n  pinMode(led, OUTPUT);\n  digitalWrite(led, 0);\n  Serial.begin(115200);\n  WiFi.begin(ssid, password);\n  Serial.println(\"\");\n\n  // Wait for connection\n  while (WiFi.status() != WL_CONNECTED) {\n    delay(500);\n    Serial.print(\".\");\n  }\n  Serial.println(\"\");\n  Serial.print(\"Connected to \");\n  Serial.println(ssid);\n  Serial.print(\"IP address: \");\n  Serial.println(WiFi.localIP());\n\n  if (MDNS.begin(\"esp8266\")) {\n    Serial.println(\"MDNS responder started\");\n  }\n\n  server.on(\"/\", handleRoot);\n\n  server.on(\"/inline\", [](){\n    server.send(200, \"text/plain\", \"this works as well\");\n  });\n\n  server.onNotFound(handleNotFound);\n\n  server.begin();\n  Serial.println(\"HTTP server started\");\n}\n\nvoid loop(void){\n  server.handleClient();\n}\n"
  },
  {
    "path": "examples/HttpBasicAuth/HttpBasicAuth.ino",
    "content": "#include <ESP8266WiFi.h>\n#include <ESP8266mDNS.h>\n#include <ArduinoOTA.h>\n#include <ESP8266WebServer.h>\n\nconst char* ssid = \"........\";\nconst char* password = \"........\";\n\nESP8266WebServer server(80);\n\nconst char* www_username = \"admin\";\nconst char* www_password = \"esp8266\";\n\nvoid setup() {\n  Serial.begin(115200);\n  WiFi.mode(WIFI_STA);\n  WiFi.begin(ssid, password);\n  if(WiFi.waitForConnectResult() != WL_CONNECTED) {\n    Serial.println(\"WiFi Connect Failed! Rebooting...\");\n    delay(1000);\n    ESP.restart();\n  }\n  ArduinoOTA.begin();\n\n  server.on(\"/\", [](){\n    if(!server.authenticate(www_username, www_password))\n      return server.requestAuthentication();\n    server.send(200, \"text/plain\", \"Login OK\");\n  });\n  server.begin();\n\n  Serial.print(\"Open http://\");\n  Serial.print(WiFi.localIP());\n  Serial.println(\"/ in your browser to see it working\");\n}\n\nvoid loop() {\n  ArduinoOTA.handle();\n  server.handleClient();\n}\n"
  },
  {
    "path": "examples/SDWebServer/SDWebServer.ino",
    "content": "/*\n  SDWebServer - Example WebServer with SD Card backend for esp8266\n\n  Copyright (c) 2015 Hristo Gochkov. All rights reserved.\n  This file is part of the ESP8266WebServer library for Arduino environment.\n\n  This library is free software; you can redistribute it and/or\n  modify it under the terms of the GNU Lesser General Public\n  License as published by the Free Software Foundation; either\n  version 2.1 of the License, or (at your option) any later version.\n\n  This library is distributed in the hope that it will be useful,\n  but WITHOUT ANY WARRANTY; without even the implied warranty of\n  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n  Lesser General Public License for more details.\n\n  You should have received a copy of the GNU Lesser General Public\n  License along with this library; if not, write to the Free Software\n  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA\n\n  Have a FAT Formatted SD Card connected to the SPI port of the ESP8266\n  The web root is the SD Card root folder\n  File extensions with more than 3 charecters are not supported by the SD Library\n  File Names longer than 8 charecters will be truncated by the SD library, so keep filenames shorter\n  index.htm is the default index (works on subfolders as well)\n\n  upload the contents of SdRoot to the root of the SDcard and access the editor by going to http://esp8266sd.local/edit\n\n*/\n#include <ESP8266WiFi.h>\n#include <WiFiClient.h>\n#include <ESP8266WebServer.h>\n#include <ESP8266mDNS.h>\n#include <SPI.h>\n#include <SD.h>\n\n#define DBG_OUTPUT_PORT Serial\n\nconst char* ssid = \"**********\";\nconst char* password = \"**********\";\nconst char* host = \"esp8266sd\";\n\nESP8266WebServer server(80);\n\nstatic bool hasSD = false;\nFile uploadFile;\n\n\nvoid returnOK() {\n  server.send(200, \"text/plain\", \"\");\n}\n\nvoid returnFail(String msg) {\n  server.send(500, \"text/plain\", msg + \"\\r\\n\");\n}\n\nbool loadFromSdCard(String path){\n  String dataType = \"text/plain\";\n  if(path.endsWith(\"/\")) path += \"index.htm\";\n\n  if(path.endsWith(\".src\")) path = path.substring(0, path.lastIndexOf(\".\"));\n  else if(path.endsWith(\".htm\")) dataType = \"text/html\";\n  else if(path.endsWith(\".css\")) dataType = \"text/css\";\n  else if(path.endsWith(\".js\")) dataType = \"application/javascript\";\n  else if(path.endsWith(\".png\")) dataType = \"image/png\";\n  else if(path.endsWith(\".gif\")) dataType = \"image/gif\";\n  else if(path.endsWith(\".jpg\")) dataType = \"image/jpeg\";\n  else if(path.endsWith(\".ico\")) dataType = \"image/x-icon\";\n  else if(path.endsWith(\".xml\")) dataType = \"text/xml\";\n  else if(path.endsWith(\".pdf\")) dataType = \"application/pdf\";\n  else if(path.endsWith(\".zip\")) dataType = \"application/zip\";\n\n  File dataFile = SD.open(path.c_str());\n  if(dataFile.isDirectory()){\n    path += \"/index.htm\";\n    dataType = \"text/html\";\n    dataFile = SD.open(path.c_str());\n  }\n\n  if (!dataFile)\n    return false;\n\n  if (server.hasArg(\"download\")) dataType = \"application/octet-stream\";\n\n  if (server.streamFile(dataFile, dataType) != dataFile.size()) {\n    DBG_OUTPUT_PORT.println(\"Sent less data than expected!\");\n  }\n\n  dataFile.close();\n  return true;\n}\n\nvoid handleFileUpload(){\n  if(server.uri() != \"/edit\") return;\n  HTTPUpload& upload = server.upload();\n  if(upload.status == UPLOAD_FILE_START){\n    if(SD.exists((char *)upload.filename.c_str())) SD.remove((char *)upload.filename.c_str());\n    uploadFile = SD.open(upload.filename.c_str(), FILE_WRITE);\n    DBG_OUTPUT_PORT.print(\"Upload: START, filename: \"); DBG_OUTPUT_PORT.println(upload.filename);\n  } else if(upload.status == UPLOAD_FILE_WRITE){\n    if(uploadFile) uploadFile.write(upload.buf, upload.currentSize);\n    DBG_OUTPUT_PORT.print(\"Upload: WRITE, Bytes: \"); DBG_OUTPUT_PORT.println(upload.currentSize);\n  } else if(upload.status == UPLOAD_FILE_END){\n    if(uploadFile) uploadFile.close();\n    DBG_OUTPUT_PORT.print(\"Upload: END, Size: \"); DBG_OUTPUT_PORT.println(upload.totalSize);\n  }\n}\n\nvoid deleteRecursive(String path){\n  File file = SD.open((char *)path.c_str());\n  if(!file.isDirectory()){\n    file.close();\n    SD.remove((char *)path.c_str());\n    return;\n  }\n\n  file.rewindDirectory();\n  while(true) {\n    File entry = file.openNextFile();\n    if (!entry) break;\n    String entryPath = path + \"/\" +entry.name();\n    if(entry.isDirectory()){\n      entry.close();\n      deleteRecursive(entryPath);\n    } else {\n      entry.close();\n      SD.remove((char *)entryPath.c_str());\n    }\n    yield();\n  }\n\n  SD.rmdir((char *)path.c_str());\n  file.close();\n}\n\nvoid handleDelete(){\n  if(server.args() == 0) return returnFail(\"BAD ARGS\");\n  String path = server.arg(0);\n  if(path == \"/\" || !SD.exists((char *)path.c_str())) {\n    returnFail(\"BAD PATH\");\n    return;\n  }\n  deleteRecursive(path);\n  returnOK();\n}\n\nvoid handleCreate(){\n  if(server.args() == 0) return returnFail(\"BAD ARGS\");\n  String path = server.arg(0);\n  if(path == \"/\" || SD.exists((char *)path.c_str())) {\n    returnFail(\"BAD PATH\");\n    return;\n  }\n\n  if(path.indexOf('.') > 0){\n    File file = SD.open((char *)path.c_str(), FILE_WRITE);\n    if(file){\n      file.write((const char *)0);\n      file.close();\n    }\n  } else {\n    SD.mkdir((char *)path.c_str());\n  }\n  returnOK();\n}\n\nvoid printDirectory() {\n  if(!server.hasArg(\"dir\")) return returnFail(\"BAD ARGS\");\n  String path = server.arg(\"dir\");\n  if(path != \"/\" && !SD.exists((char *)path.c_str())) return returnFail(\"BAD PATH\");\n  File dir = SD.open((char *)path.c_str());\n  path = String();\n  if(!dir.isDirectory()){\n    dir.close();\n    return returnFail(\"NOT DIR\");\n  }\n  dir.rewindDirectory();\n  server.setContentLength(CONTENT_LENGTH_UNKNOWN);\n  server.send(200, \"text/json\", \"\");\n  WiFiClient client = server.client();\n\n  server.sendContent(\"[\");\n  for (int cnt = 0; true; ++cnt) {\n    File entry = dir.openNextFile();\n    if (!entry)\n    break;\n\n    String output;\n    if (cnt > 0)\n      output = ',';\n\n    output += \"{\\\"type\\\":\\\"\";\n    output += (entry.isDirectory()) ? \"dir\" : \"file\";\n    output += \"\\\",\\\"name\\\":\\\"\";\n    output += entry.name();\n    output += \"\\\"\";\n    output += \"}\";\n    server.sendContent(output);\n    entry.close();\n }\n server.sendContent(\"]\");\n dir.close();\n}\n\nvoid handleNotFound(){\n  if(hasSD && loadFromSdCard(server.uri())) return;\n  String message = \"SDCARD Not Detected\\n\\n\";\n  message += \"URI: \";\n  message += server.uri();\n  message += \"\\nMethod: \";\n  message += (server.method() == HTTP_GET)?\"GET\":\"POST\";\n  message += \"\\nArguments: \";\n  message += server.args();\n  message += \"\\n\";\n  for (uint8_t i=0; i<server.args(); i++){\n    message += \" NAME:\"+server.argName(i) + \"\\n VALUE:\" + server.arg(i) + \"\\n\";\n  }\n  server.send(404, \"text/plain\", message);\n  DBG_OUTPUT_PORT.print(message);\n}\n\nvoid setup(void){\n  DBG_OUTPUT_PORT.begin(115200);\n  DBG_OUTPUT_PORT.setDebugOutput(true);\n  DBG_OUTPUT_PORT.print(\"\\n\");\n  WiFi.begin(ssid, password);\n  DBG_OUTPUT_PORT.print(\"Connecting to \");\n  DBG_OUTPUT_PORT.println(ssid);\n\n  // Wait for connection\n  uint8_t i = 0;\n  while (WiFi.status() != WL_CONNECTED && i++ < 20) {//wait 10 seconds\n    delay(500);\n  }\n  if(i == 21){\n    DBG_OUTPUT_PORT.print(\"Could not connect to\");\n    DBG_OUTPUT_PORT.println(ssid);\n    while(1) delay(500);\n  }\n  DBG_OUTPUT_PORT.print(\"Connected! IP address: \");\n  DBG_OUTPUT_PORT.println(WiFi.localIP());\n\n  if (MDNS.begin(host)) {\n    MDNS.addService(\"http\", \"tcp\", 80);\n    DBG_OUTPUT_PORT.println(\"MDNS responder started\");\n    DBG_OUTPUT_PORT.print(\"You can now connect to http://\");\n    DBG_OUTPUT_PORT.print(host);\n    DBG_OUTPUT_PORT.println(\".local\");\n  }\n\n\n  server.on(\"/list\", HTTP_GET, printDirectory);\n  server.on(\"/edit\", HTTP_DELETE, handleDelete);\n  server.on(\"/edit\", HTTP_PUT, handleCreate);\n  server.on(\"/edit\", HTTP_POST, [](){ returnOK(); }, handleFileUpload);\n  server.onNotFound(handleNotFound);\n\n  server.begin();\n  DBG_OUTPUT_PORT.println(\"HTTP server started\");\n\n  if (SD.begin(SS)){\n     DBG_OUTPUT_PORT.println(\"SD Card initialized.\");\n     hasSD = true;\n  }\n}\n\nvoid loop(void){\n  server.handleClient();\n}\n"
  },
  {
    "path": "examples/SDWebServer/SdRoot/edit/index.htm",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <title>SD Editor</title>\n    <style type=\"text/css\" media=\"screen\">\n      .contextMenu {\n        z-index: 300;\n        position: absolute;\n        left: 5px;\n        border: 1px solid #444;\n        background-color: #F5F5F5;\n        display: none;\n        box-shadow: 0 0 10px rgba( 0, 0, 0, .4 );\n        font-size: 12px;\n        font-family: sans-serif;\n        font-weight:bold;\n      }\n      .contextMenu ul {\n        list-style: none;\n        top: 0;\n        left: 0;\n        margin: 0;\n        padding: 0;\n      }\n      .contextMenu li {\n        position: relative;\n        min-width: 60px;\n        cursor: pointer;\n      }\n      .contextMenu span {\n        color: #444;\n        display: inline-block;\n        padding: 6px;\n      }\n      .contextMenu li:hover { background: #444; }\n      .contextMenu li:hover span { color: #EEE; }\n    \n      .css-treeview ul, .css-treeview li {\n        padding: 0;\n        margin: 0;\n        list-style: none;\n      }\n\n      .css-treeview input {\n        position: absolute;\n        opacity: 0;\n      }\n\n      .css-treeview {\n        font: normal 11px Verdana, Arial, Sans-serif;\n        -moz-user-select: none;\n        -webkit-user-select: none;\n        user-select: none;\n      }\n\n      .css-treeview span {\n        color: #00f;\n        cursor: pointer;\n      }\n\n      .css-treeview span:hover {\n        text-decoration: underline;\n      }\n\n      .css-treeview input + label + ul {\n        margin: 0 0 0 22px;\n      }\n\n      .css-treeview input ~ ul {\n        display: none;\n      }\n\n      .css-treeview label, .css-treeview label::before {\n        cursor: pointer;\n      }\n\n      .css-treeview input:disabled + label {\n        cursor: default;\n        opacity: .6;\n      }\n\n      .css-treeview input:checked:not(:disabled) ~ ul {\n        display: block;\n      }\n\n      .css-treeview label, .css-treeview label::before {\n        background: url(\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAACgCAYAAAAFOewUAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAApxJREFUeNrslM1u00AQgGdthyalFFOK+ClIIKQKyqUVQvTEE3DmAhLwAhU8QZoH4A2Q2gMSFace4MCtJ8SPBFwAkRuiHKpA6sRN/Lu7zG5i14kctaUqRGhGXnu9O/Pt7MzsMiklvF+9t2kWTDvyIrAsA0aKRRi1T0C/hJ4LUbt5/8rNpWVlp8RSr9J40b48fxFaTQ9+ft8EZ6MJYb0Ok+dnYGpmPgXwKIAvLx8vYXc5GdMAQJgQEkpjRTh36TS2U+DWW/D17WuYgm8pwJyY1npZsZKOxImOV1I/h4+O6vEg5GCZBpgmA6hX8wHKUHDRBXQYicQ4rlc3Tf0VMs8DHBS864F2YFspjgUYjKX/Az3gsdQd2eeBHwmdGWXHcgBGSkZXOXohcEXebRoQcAgjqediNY+AVyu3Z3sAKqfKoGMsewBeEIOPgQxxPJIjcGH6qtL/0AdADzKGnuuD+2tLK7Q8DhHHbOBW+KEzcHLuYc82MkEUekLiwuvVH+guQBQzOG4XdAb8EOcRcqQvDkY2iCLuxECJ43JobMXoutqGgDa2T7UqLKwt9KRyuxKVByqVXXqIoCCUCAqhUOioTWC7G4TQEOD0APy2/7G2Xpu1J4+lxeQ4TXBbITDpoVelRN/BVFbwu5oMMJUBhoXy5tmdRcMwymP2OLQaLjx9/vnBo6V3K6izATmSnMa0Dq7ferIohJhr1p01zrlz49rZF4OMs8JkX23vVQzYp+wbYGV/KpXKjvspl8tsIKCrMNAYFxj2GKS5ZWxg4ewKsJfaGMIY5KXqPz8LBBj6+yDvVP79+yDp/9F9oIx3OisHWwe7Oal0HxCAAAQgAAEIQAACEIAABCAAAQhAAAIQgAAEIAABCEAAAhCAAAQgwD8E/BZgAP0qhKj3rXO7AAAAAElFTkSuQmCC\") no-repeat;\n      }\n\n      .css-treeview label, .css-treeview span, .css-treeview label::before {\n        display: inline-block;\n        height: 16px;\n        line-height: 16px;\n        vertical-align: middle;\n      }\n\n      .css-treeview label {\n        background-position: 18px 0;\n      }\n\n      .css-treeview label::before {\n        content: \"\";\n        width: 16px;\n        margin: 0 22px 0 0;\n        vertical-align: middle;\n        background-position: 0 -32px;\n      }\n\n      .css-treeview input:checked + label::before {\n        background-position: 0 -16px;\n      }\n\n      /* webkit adjacent element selector bugfix */\n      @media screen and (-webkit-min-device-pixel-ratio:0)\n      {\n        .css-treeview{\n          -webkit-animation: webkit-adjacent-element-selector-bugfix infinite 1s;\n        }\n\n        @-webkit-keyframes webkit-adjacent-element-selector-bugfix \n        {\n          from  { \n            padding: 0;\n          } \n          to  { \n            padding: 0;\n          }\n        }\n      }\n\n      #uploader { \n        position: absolute;\n        top: 0;\n        right: 0;\n        left: 0;\n        height:28px;\n        line-height: 24px;\n        padding-left: 10px;\n        background-color: #444;\n        color:#EEE;\n      }\n      #tree { \n        position: absolute;\n        top: 28px;\n        bottom: 0;\n        left: 0;\n        width:200px;\n        padding: 8px;\n      }\n      #editor, #preview { \n        position: absolute;\n        top: 28px;\n        right: 0;\n        bottom: 0;\n        left: 200px;\n      }\n      #preview {\n        background-color: #EEE;\n        padding:5px;\n      }\n    </style>\n    <script>\n      function createFileUploader(element, tree, editor){\n        var xmlHttp;\n        var input = document.createElement(\"input\");\n        input.type = \"file\";\n        input.multiple = false;\n        input.name = \"data\";\n        document.getElementById(element).appendChild(input);\n        var path = document.createElement(\"input\");\n        path.id = \"upload-path\";\n        path.type = \"text\";\n        path.name = \"path\";\n        path.defaultValue = \"/\";\n        document.getElementById(element).appendChild(path);\n        var button = document.createElement(\"button\");\n        button.innerHTML = 'Upload';\n        document.getElementById(element).appendChild(button);\n        var mkdir = document.createElement(\"button\");\n        mkdir.innerHTML = 'MkDir';\n        document.getElementById(element).appendChild(mkdir);\n        var mkfile = document.createElement(\"button\");\n        mkfile.innerHTML = 'MkFile';\n        document.getElementById(element).appendChild(mkfile);\n  \n        function httpPostProcessRequest(){\n          if (xmlHttp.readyState == 4){\n            if(xmlHttp.status != 200) alert(\"ERROR[\"+xmlHttp.status+\"]: \"+xmlHttp.responseText);\n            else {\n              tree.refreshPath(path.value);\n            }\n          }\n        }\n        function createPath(p){\n          xmlHttp = new XMLHttpRequest();\n          xmlHttp.onreadystatechange = httpPostProcessRequest;\n          var formData = new FormData();\n          formData.append(\"path\", p);\n          xmlHttp.open(\"PUT\", \"/edit\");\n          xmlHttp.send(formData);\n        }\n        \n        mkfile.onclick = function(e){\n          if(path.value.indexOf(\".\") === -1) return;\n          createPath(path.value);\n          editor.loadUrl(path.value);\n        };\n        mkdir.onclick = function(e){\n          if(path.value.length < 2) return;\n          var dir = path.value\n          if(dir.indexOf(\".\") !== -1){\n            if(dir.lastIndexOf(\"/\") === 0) return;\n            dir = dir.substring(0, dir.lastIndexOf(\"/\"));\n          }\n          createPath(dir);\n        };\n        button.onclick = function(e){\n          if(input.files.length === 0){\n            return;\n          }\n          xmlHttp = new XMLHttpRequest();\n          xmlHttp.onreadystatechange = httpPostProcessRequest;\n          var formData = new FormData();\n          formData.append(\"data\", input.files[0], path.value);\n          xmlHttp.open(\"POST\", \"/edit\");\n          xmlHttp.send(formData);\n        }\n        input.onchange = function(e){\n          if(input.files.length === 0) return;\n          var filename = input.files[0].name;\n          var ext = /(?:\\.([^.]+))?$/.exec(filename)[1];\n          var name = /(.*)\\.[^.]+$/.exec(filename)[1];\n          if(typeof name !== undefined){\n            if(name.length > 8) name = name.substring(0, 8);\n            filename = name;\n          }\n          if(typeof ext !== undefined){\n            if(ext === \"html\") ext = \"htm\";\n            else if(ext === \"jpeg\") ext = \"jpg\";\n            filename = filename + \".\" + ext;\n          }\n          if(path.value === \"/\" || path.value.lastIndexOf(\"/\") === 0){\n            path.value = \"/\"+filename;\n          } else {\n            path.value = path.value.substring(0, path.value.lastIndexOf(\"/\")+1)+filename;\n          }\n        }\n      }\n\n      function createTree(element, editor){\n        var preview = document.getElementById(\"preview\");\n        var treeRoot = document.createElement(\"div\");\n        treeRoot.className = \"css-treeview\";\n        document.getElementById(element).appendChild(treeRoot);\n  \n        function loadDownload(path){\n          document.getElementById('download-frame').src = path+\"?download=true\";\n        }\n  \n        function loadPreview(path){\n          document.getElementById(\"editor\").style.display = \"none\";\n          preview.style.display = \"block\";\n          preview.innerHTML = '<img src=\"'+path+'\" style=\"max-width:100%; max-height:100%; margin:auto; display:block;\" />';\n        }\n  \n        function fillFolderMenu(el, path){\n          var list = document.createElement(\"ul\");\n          el.appendChild(list);\n          var action = document.createElement(\"li\");\n          list.appendChild(action);\n          var isChecked = document.getElementById(path).checked;\n          var expnd = document.createElement(\"li\");\n          list.appendChild(expnd);\n          if(isChecked){\n            expnd.innerHTML = \"<span>Collapse</span>\";\n            expnd.onclick = function(e){\n              document.getElementById(path).checked = false;\n              if(document.body.getElementsByClassName('contextMenu').length > 0) document.body.removeChild(el);\n            };\n            var refrsh = document.createElement(\"li\");\n            list.appendChild(refrsh);\n            refrsh.innerHTML = \"<span>Refresh</span>\";\n            refrsh.onclick = function(e){\n              var leaf = document.getElementById(path).parentNode;\n              if(leaf.childNodes.length == 3) leaf.removeChild(leaf.childNodes[2]);\n              httpGet(leaf, path);\n              if(document.body.getElementsByClassName('contextMenu').length > 0) document.body.removeChild(el);\n            };\n          } else {\n            expnd.innerHTML = \"<span>Expand</span>\";\n            expnd.onclick = function(e){\n              document.getElementById(path).checked = true;\n              var leaf = document.getElementById(path).parentNode;\n              if(leaf.childNodes.length == 3) leaf.removeChild(leaf.childNodes[2]);\n              httpGet(leaf, path);\n              if(document.body.getElementsByClassName('contextMenu').length > 0) document.body.removeChild(el);\n            };\n          }\n          var upload = document.createElement(\"li\");\n          list.appendChild(upload);\n          upload.innerHTML = \"<span>Upload</span>\";\n          upload.onclick = function(e){\n            var pathEl = document.getElementById(\"upload-path\");\n            if(pathEl){\n              var subPath = pathEl.value;\n              if(subPath.lastIndexOf(\"/\") < 1) pathEl.value = path+subPath;\n              else pathEl.value = path.substring(subPath.lastIndexOf(\"/\"))+subPath;\n            }\n            if(document.body.getElementsByClassName('contextMenu').length > 0) document.body.removeChild(el);\n          };\n          var delFile = document.createElement(\"li\");\n          list.appendChild(delFile);\n          delFile.innerHTML = \"<span>Delete</span>\";\n          delFile.onclick = function(e){\n            httpDelete(path);\n            if(document.body.getElementsByClassName('contextMenu').length > 0) document.body.removeChild(el);\n          };\n        }\n  \n        function fillFileMenu(el, path){\n          var list = document.createElement(\"ul\");\n          el.appendChild(list);\n          var action = document.createElement(\"li\");\n          list.appendChild(action);\n          if(isTextFile(path)){\n            action.innerHTML = \"<span>Edit</span>\";\n            action.onclick = function(e){\n              editor.loadUrl(path);\n              if(document.body.getElementsByClassName('contextMenu').length > 0) document.body.removeChild(el);\n            };\n          } else if(isImageFile(path)){\n            action.innerHTML = \"<span>Preview</span>\";\n            action.onclick = function(e){\n              loadPreview(path);\n              if(document.body.getElementsByClassName('contextMenu').length > 0) document.body.removeChild(el);\n            };\n          }\n          var download = document.createElement(\"li\");\n          list.appendChild(download);\n          download.innerHTML = \"<span>Download</span>\";\n          download.onclick = function(e){\n            loadDownload(path);\n            if(document.body.getElementsByClassName('contextMenu').length > 0) document.body.removeChild(el);\n          };\n          var delFile = document.createElement(\"li\");\n          list.appendChild(delFile);\n          delFile.innerHTML = \"<span>Delete</span>\";\n          delFile.onclick = function(e){\n            httpDelete(path);\n            if(document.body.getElementsByClassName('contextMenu').length > 0) document.body.removeChild(el);\n          };\n        }\n  \n        function showContextMenu(e, path, isfile){\n          var divContext = document.createElement(\"div\");\n          var scrollTop = document.body.scrollTop ? document.body.scrollTop : document.documentElement.scrollTop;\n          var scrollLeft = document.body.scrollLeft ? document.body.scrollLeft : document.documentElement.scrollLeft;\n          var left = e.clientX + scrollLeft;\n          var top = e.clientY + scrollTop;\n          divContext.className = 'contextMenu';\n          divContext.style.display = 'block';\n          divContext.style.left = left + 'px';\n          divContext.style.top = top + 'px';\n          if(isfile) fillFileMenu(divContext, path);\n          else fillFolderMenu(divContext, path);\n          document.body.appendChild(divContext);\n          var width = divContext.offsetWidth;\n          var height = divContext.offsetHeight;\n          divContext.onmouseout = function(e){\n            if(e.clientX < left || e.clientX > (left + width) || e.clientY < top || e.clientY > (top + height)){\n              if(document.body.getElementsByClassName('contextMenu').length > 0) document.body.removeChild(divContext);\n            }\n          };\n        }\n  \n        function createTreeLeaf(path, name, size){\n          var leaf = document.createElement(\"li\");\n          leaf.id = (((path == \"/\")?\"\":path)+\"/\"+name).toLowerCase();\n          var label = document.createElement(\"span\");\n          label.textContent = name.toLowerCase();\n          leaf.appendChild(label);\n          leaf.onclick = function(e){\n            if(isTextFile(leaf.id)){\n              editor.loadUrl(leaf.id);\n            } else if(isImageFile(leaf.id)){\n              loadPreview(leaf.id);\n            }\n          };\n          leaf.oncontextmenu = function(e){\n            e.preventDefault();\n            e.stopPropagation();\n            showContextMenu(e, leaf.id, true);\n          };\n          return leaf;\n        }\n  \n        function createTreeBranch(path, name, disabled){\n          var leaf = document.createElement(\"li\");\n          var check = document.createElement(\"input\");\n          check.type = \"checkbox\";\n          check.id = (((path == \"/\")?\"\":path)+\"/\"+name).toLowerCase();\n          if(typeof disabled !== \"undefined\" && disabled) check.disabled = \"disabled\";\n          leaf.appendChild(check);\n          var label = document.createElement(\"label\");\n          label.for = check.id;\n          label.textContent = name.toLowerCase();\n          leaf.appendChild(label);\n          check.onchange = function(e){\n            if(check.checked){\n              if(leaf.childNodes.length == 3) leaf.removeChild(leaf.childNodes[2]);\n              httpGet(leaf, check.id);\n            }\n          };\n          label.onclick = function(e){\n            if(!check.checked){\n              check.checked = true;\n              if(leaf.childNodes.length == 3) leaf.removeChild(leaf.childNodes[2]);\n              httpGet(leaf, check.id);\n            } else {\n              check.checked = false;\n            }\n          };\n          leaf.oncontextmenu = function(e){\n            e.preventDefault();\n            e.stopPropagation();\n            showContextMenu(e, check.id, false);\n          }\n          return leaf;\n        }\n  \n        function addList(parent, path, items){\n          var list = document.createElement(\"ul\");\n          parent.appendChild(list);\n          var ll = items.length;\n          for(var i = 0; i < ll; i++){\n            var item = items[i];\n            var itemEl;\n            if(item.type === \"file\"){\n              itemEl = createTreeLeaf(path, item.name, item.size);\n            } else {\n              itemEl = createTreeBranch(path, item.name);\n            }\n            list.appendChild(itemEl);\n          }\n    \n        }\n  \n        function isTextFile(path){\n          var ext = /(?:\\.([^.]+))?$/.exec(path)[1];\n          if(typeof ext !== undefined){\n            switch(ext){\n              case \"txt\":\n              case \"htm\":\n              case \"html\":\n              case \"js\":\n              case \"json\":\n              case \"c\":\n              case \"h\":\n              case \"cpp\":\n              case \"css\":\n              case \"xml\":\n                return true;\n            }\n          }\n          return false;\n        }\n  \n        function isImageFile(path){\n          var ext = /(?:\\.([^.]+))?$/.exec(path)[1];\n          if(typeof ext !== undefined){\n            switch(ext){\n              case \"png\":\n              case \"jpg\":\n              case \"gif\":\n              case \"ico\":\n                return true;\n            }\n          }\n          return false;\n        }\n  \n        this.refreshPath = function(path){\n          if(path.lastIndexOf('/') < 1){\n            path = '/';\n            treeRoot.removeChild(treeRoot.childNodes[0]);\n            httpGet(treeRoot, \"/\");\n          } else {\n            path = path.substring(0, path.lastIndexOf('/'));\n            var leaf = document.getElementById(path).parentNode;\n            if(leaf.childNodes.length == 3) leaf.removeChild(leaf.childNodes[2]);\n            httpGet(leaf, path);\n          }\n        };\n  \n        function delCb(path){\n          return function(){\n            if (xmlHttp.readyState == 4){\n              if(xmlHttp.status != 200){\n                alert(\"ERROR[\"+xmlHttp.status+\"]: \"+xmlHttp.responseText);\n              } else {\n                if(path.lastIndexOf('/') < 1){\n                  path = '/';\n                  treeRoot.removeChild(treeRoot.childNodes[0]);\n                  httpGet(treeRoot, \"/\");\n                } else {\n                  path = path.substring(0, path.lastIndexOf('/'));\n                  var leaf = document.getElementById(path).parentNode;\n                  if(leaf.childNodes.length == 3) leaf.removeChild(leaf.childNodes[2]);\n                  httpGet(leaf, path);\n                }\n              }\n            }\n          }\n        }\n  \n        function httpDelete(filename){\n          xmlHttp = new XMLHttpRequest();\n          xmlHttp.onreadystatechange = delCb(filename);\n          var formData = new FormData();\n          formData.append(\"path\", filename);\n          xmlHttp.open(\"DELETE\", \"/edit\");\n          xmlHttp.send(formData);\n        }\n  \n        function getCb(parent, path){\n          return function(){\n            if (xmlHttp.readyState == 4){\n              //clear loading\n              if(xmlHttp.status == 200) addList(parent, path, JSON.parse(xmlHttp.responseText));\n            }\n          }\n        }\n  \n        function httpGet(parent, path){\n          xmlHttp = new XMLHttpRequest(parent, path);\n          xmlHttp.onreadystatechange = getCb(parent, path);\n          xmlHttp.open(\"GET\", \"/list?dir=\"+path, true);\n          xmlHttp.send(null);\n          //start loading\n        }\n  \n        httpGet(treeRoot, \"/\");\n        return this;\n      }\n\n      function createEditor(element, file, lang, theme, type){\n        function getLangFromFilename(filename){\n          var lang = \"plain\";\n          var ext = /(?:\\.([^.]+))?$/.exec(filename)[1];\n          if(typeof ext !== undefined){\n            switch(ext){\n              case \"txt\": lang = \"plain\"; break;\n              case \"htm\": lang = \"html\"; break;\n              case \"js\": lang = \"javascript\"; break;\n              case \"c\": lang = \"c_cpp\"; break;\n              case \"cpp\": lang = \"c_cpp\"; break;\n              case \"css\":\n              case \"scss\":\n              case \"php\":\n              case \"html\":\n              case \"json\":\n              case \"xml\":\n                lang = ext;\n            }\n          }\n          return lang;\n        }\n  \n        if(typeof file === \"undefined\") file = \"/index.htm\";\n  \n        if(typeof lang === \"undefined\"){\n          lang = getLangFromFilename(file);\n        }\n  \n        if(typeof theme === \"undefined\") theme = \"textmate\";\n  \n        if(typeof type === \"undefined\"){\n          type = \"text/\"+lang;\n          if(lang === \"c_cpp\") type = \"text/plain\";\n        }\n  \n        var xmlHttp = null;\n        var editor = ace.edit(element);\n  \n        //post\n        function httpPostProcessRequest(){\n          if (xmlHttp.readyState == 4){\n            if(xmlHttp.status != 200) alert(\"ERROR[\"+xmlHttp.status+\"]: \"+xmlHttp.responseText);\n          }\n        }\n        function httpPost(filename, data, type){\n          xmlHttp = new XMLHttpRequest();\n          xmlHttp.onreadystatechange = httpPostProcessRequest;\n          var formData = new FormData();\n          formData.append(\"data\", new Blob([data], { type: type }), filename);\n          xmlHttp.open(\"POST\", \"/edit\");\n          xmlHttp.send(formData);\n        }\n        //get\n        function httpGetProcessRequest(){\n          if (xmlHttp.readyState == 4){\n            document.getElementById(\"preview\").style.display = \"none\";\n            document.getElementById(\"editor\").style.display = \"block\";\n            if(xmlHttp.status == 200) editor.setValue(xmlHttp.responseText);\n            else editor.setValue(\"\");\n            editor.clearSelection();\n          }\n        }\n        function httpGet(theUrl){\n            xmlHttp = new XMLHttpRequest();\n            xmlHttp.onreadystatechange = httpGetProcessRequest;\n            xmlHttp.open(\"GET\", theUrl, true);\n            xmlHttp.send(null);\n        }\n  \n        if(lang !== \"plain\") editor.getSession().setMode(\"ace/mode/\"+lang);\n        editor.setTheme(\"ace/theme/\"+theme);\n        editor.$blockScrolling = Infinity;\n        editor.getSession().setUseSoftTabs(true);\n        editor.getSession().setTabSize(2);\n        editor.setHighlightActiveLine(true);\n        editor.setShowPrintMargin(false);\n        editor.commands.addCommand({\n            name: 'saveCommand',\n            bindKey: {win: 'Ctrl-S',  mac: 'Command-S'},\n            exec: function(editor) {\n              httpPost(file, editor.getValue()+\"\", type);\n            },\n            readOnly: false\n        });\n        editor.commands.addCommand({\n            name: 'undoCommand',\n            bindKey: {win: 'Ctrl-Z',  mac: 'Command-Z'},\n            exec: function(editor) {\n              editor.getSession().getUndoManager().undo(false);\n            },\n            readOnly: false\n        });\n        editor.commands.addCommand({\n            name: 'redoCommand',\n            bindKey: {win: 'Ctrl-Shift-Z',  mac: 'Command-Shift-Z'},\n            exec: function(editor) {\n              editor.getSession().getUndoManager().redo(false);\n            },\n            readOnly: false\n        });\n        httpGet(file);\n        editor.loadUrl = function(filename){\n          file = filename;\n          lang = getLangFromFilename(file);\n          type = \"text/\"+lang;\n          if(lang !== \"plain\") editor.getSession().setMode(\"ace/mode/\"+lang);\n          httpGet(file);\n        }\n        return editor;\n      }\n      function onBodyLoad(){\n        var vars = {};\n        var parts = window.location.href.replace(/[?&]+([^=&]+)=([^&]*)/gi, function(m,key,value) { vars[key] = value; });\n        var editor = createEditor(\"editor\", vars.file, vars.lang, vars.theme);\n        var tree = createTree(\"tree\", editor);\n        createFileUploader(\"uploader\", tree, editor);\n      };\n    </script>\n    <script src=\"https://cdnjs.cloudflare.com/ajax/libs/ace/1.1.9/ace.js\" type=\"text/javascript\" charset=\"utf-8\"></script>\n  </head>\n  <body onload=\"onBodyLoad();\">\n    <div id=\"uploader\"></div>\n    <div id=\"tree\"></div>\n    <div id=\"editor\"></div>\n    <div id=\"preview\" style=\"display:none;\"></div>\n    <iframe id=download-frame style='display:none;'></iframe>\n  </body>\n</html>\n"
  },
  {
    "path": "examples/SDWebServer/SdRoot/index.htm",
    "content": "<!DOCTYPE html>\n<html>\n<head>\n  <meta http-equiv=\"Content-type\" content=\"text/html; charset=utf-8\">\n  <title>ESP Index</title>\n  <style>\n    body {\n      background-color:black;\n      color:white;\n    }\n  </style>\n  <script type=\"text/javascript\">\n    function onBodyLoad(){\n      console.log(\"we are loaded!!\");\n    }\n  </script>\n</head>\n<body id=\"index\" onload=\"onBodyLoad()\">\n  <h1>ESP8266 Pin Functions</h1>\n<img src=\"pins.png\" />\n</body>\n</html>\n"
  },
  {
    "path": "examples/SimpleAuthentification/SimpleAuthentification.ino",
    "content": "#include <ESP8266WiFi.h>\n#include <WiFiClient.h>\n#include <ESP8266WebServer.h>\n\nconst char* ssid = \"........\";\nconst char* password = \"........\";\n\nESP8266WebServer server(80);\n\n//Check if header is present and correct\nbool is_authentified(){\n  Serial.println(\"Enter is_authentified\");\n  if (server.hasHeader(\"Cookie\")){\n    Serial.print(\"Found cookie: \");\n    String cookie = server.header(\"Cookie\");\n    Serial.println(cookie);\n    if (cookie.indexOf(\"ESPSESSIONID=1\") != -1) {\n      Serial.println(\"Authentification Successful\");\n      return true;\n    }\n  }\n  Serial.println(\"Authentification Failed\");\n  return false;\n}\n\n//login page, also called for disconnect\nvoid handleLogin(){\n  String msg;\n  if (server.hasHeader(\"Cookie\")){\n    Serial.print(\"Found cookie: \");\n    String cookie = server.header(\"Cookie\");\n    Serial.println(cookie);\n  }\n  if (server.hasArg(\"DISCONNECT\")){\n    Serial.println(\"Disconnection\");\n    server.sendHeader(\"Location\",\"/login\");\n    server.sendHeader(\"Cache-Control\",\"no-cache\");\n    server.sendHeader(\"Set-Cookie\",\"ESPSESSIONID=0\");\n    server.send(301);\n    return;\n  }\n  if (server.hasArg(\"USERNAME\") && server.hasArg(\"PASSWORD\")){\n    if (server.arg(\"USERNAME\") == \"admin\" &&  server.arg(\"PASSWORD\") == \"admin\" ){\n      server.sendHeader(\"Location\",\"/\");\n      server.sendHeader(\"Cache-Control\",\"no-cache\");\n      server.sendHeader(\"Set-Cookie\",\"ESPSESSIONID=1\");\n      server.send(301);\n      Serial.println(\"Log in Successful\");\n      return;\n    }\n  msg = \"Wrong username/password! try again.\";\n  Serial.println(\"Log in Failed\");\n  }\n  String content = \"<html><body><form action='/login' method='POST'>To log in, please use : admin/admin<br>\";\n  content += \"User:<input type='text' name='USERNAME' placeholder='user name'><br>\";\n  content += \"Password:<input type='password' name='PASSWORD' placeholder='password'><br>\";\n  content += \"<input type='submit' name='SUBMIT' value='Submit'></form>\" + msg + \"<br>\";\n  content += \"You also can go <a href='/inline'>here</a></body></html>\";\n  server.send(200, \"text/html\", content);\n}\n\n//root page can be accessed only if authentification is ok\nvoid handleRoot(){\n  Serial.println(\"Enter handleRoot\");\n  String header;\n  if (!is_authentified()){\n    server.sendHeader(\"Location\",\"/login\");\n    server.sendHeader(\"Cache-Control\",\"no-cache\");\n    server.send(301);\n    return;\n  }\n  String content = \"<html><body><H2>hello, you successfully connected to esp8266!</H2><br>\";\n  if (server.hasHeader(\"User-Agent\")){\n    content += \"the user agent used is : \" + server.header(\"User-Agent\") + \"<br><br>\";\n  }\n  content += \"You can access this page until you <a href=\\\"/login?DISCONNECT=YES\\\">disconnect</a></body></html>\";\n  server.send(200, \"text/html\", content);\n}\n\n//no need authentification\nvoid handleNotFound(){\n  String message = \"File Not Found\\n\\n\";\n  message += \"URI: \";\n  message += server.uri();\n  message += \"\\nMethod: \";\n  message += (server.method() == HTTP_GET)?\"GET\":\"POST\";\n  message += \"\\nArguments: \";\n  message += server.args();\n  message += \"\\n\";\n  for (uint8_t i=0; i<server.args(); i++){\n    message += \" \" + server.argName(i) + \": \" + server.arg(i) + \"\\n\";\n  }\n  server.send(404, \"text/plain\", message);\n}\n\nvoid setup(void){\n  Serial.begin(115200);\n  WiFi.begin(ssid, password);\n  Serial.println(\"\");\n\n  // Wait for connection\n  while (WiFi.status() != WL_CONNECTED) {\n    delay(500);\n    Serial.print(\".\");\n  }\n  Serial.println(\"\");\n  Serial.print(\"Connected to \");\n  Serial.println(ssid);\n  Serial.print(\"IP address: \");\n  Serial.println(WiFi.localIP());\n\n\n  server.on(\"/\", handleRoot);\n  server.on(\"/login\", handleLogin);\n  server.on(\"/inline\", [](){\n    server.send(200, \"text/plain\", \"this works without need of authentification\");\n  });\n\n  server.onNotFound(handleNotFound);\n  //here the list of headers to be recorded\n  const char * headerkeys[] = {\"User-Agent\",\"Cookie\"} ;\n  size_t headerkeyssize = sizeof(headerkeys)/sizeof(char*);\n  //ask server to track these headers\n  server.collectHeaders(headerkeys, headerkeyssize );\n  server.begin();\n  Serial.println(\"HTTP server started\");\n}\n\nvoid loop(void){\n  server.handleClient();\n}\n"
  },
  {
    "path": "examples/WebUpdate/WebUpdate.ino",
    "content": "/*\n  To upload through terminal you can use: curl -F \"image=@firmware.bin\" esp8266-webupdate.local/update\n*/\n\n#include <ESP8266WiFi.h>\n#include <WiFiClient.h>\n#include <ESP8266WebServer.h>\n#include <ESP8266mDNS.h>\n\nconst char* host = \"esp8266-webupdate\";\nconst char* ssid = \"........\";\nconst char* password = \"........\";\n\nESP8266WebServer server(80);\nconst char* serverIndex = \"<form method='POST' action='/update' enctype='multipart/form-data'><input type='file' name='update'><input type='submit' value='Update'></form>\";\n\nvoid setup(void){\n  Serial.begin(115200);\n  Serial.println();\n  Serial.println(\"Booting Sketch...\");\n  WiFi.mode(WIFI_AP_STA);\n  WiFi.begin(ssid, password);\n  if(WiFi.waitForConnectResult() == WL_CONNECTED){\n    MDNS.begin(host);\n    server.on(\"/\", HTTP_GET, [](){\n      server.sendHeader(\"Connection\", \"close\");\n      server.send(200, \"text/html\", serverIndex);\n    });\n    server.on(\"/update\", HTTP_POST, [](){\n      server.sendHeader(\"Connection\", \"close\");\n      server.send(200, \"text/plain\", (Update.hasError())?\"FAIL\":\"OK\");\n      ESP.restart();\n    },[](){\n      HTTPUpload& upload = server.upload();\n      if(upload.status == UPLOAD_FILE_START){\n        Serial.setDebugOutput(true);\n        WiFiUDP::stopAll();\n        Serial.printf(\"Update: %s\\n\", upload.filename.c_str());\n        uint32_t maxSketchSpace = (ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000;\n        if(!Update.begin(maxSketchSpace)){//start with max available size\n          Update.printError(Serial);\n        }\n      } else if(upload.status == UPLOAD_FILE_WRITE){\n        if(Update.write(upload.buf, upload.currentSize) != upload.currentSize){\n          Update.printError(Serial);\n        }\n      } else if(upload.status == UPLOAD_FILE_END){\n        if(Update.end(true)){ //true to set the size to the current progress\n          Serial.printf(\"Update Success: %u\\nRebooting...\\n\", upload.totalSize);\n        } else {\n          Update.printError(Serial);\n        }\n        Serial.setDebugOutput(false);\n      }\n      yield();\n    });\n    server.begin();\n    MDNS.addService(\"http\", \"tcp\", 80);\n\n    Serial.printf(\"Ready! Open http://%s.local in your browser\\n\", host);\n  } else {\n    Serial.println(\"WiFi Failed\");\n  }\n}\n\nvoid loop(void){\n  server.handleClient();\n  delay(1);\n}\n"
  },
  {
    "path": "keywords.txt",
    "content": "#######################################\n# Syntax Coloring Map For Ultrasound\n#######################################\n\n#######################################\n# Datatypes (KEYWORD1)\n#######################################\n\nESP8266WebServer\tKEYWORD1\nHTTPMethod\tKEYWORD1\n\n#######################################\n# Methods and Functions (KEYWORD2)\n#######################################\n\nbegin\tKEYWORD2\nhandleClient\tKEYWORD2\non\tKEYWORD2\naddHandler\tKEYWORD2\nuri\tKEYWORD2\nmethod\tKEYWORD2\nclient\tKEYWORD2\nsend\tKEYWORD2\narg\tKEYWORD2\nargName\tKEYWORD2\nargs\tKEYWORD2\nhasArg\tKEYWORD2\nonNotFound\tKEYWORD2\n\n#######################################\n# Constants (LITERAL1)\n#######################################\n\nHTTP_GET\tLITERAL1\nHTTP_POST\tLITERAL1\nHTTP_ANY\tLITERAL1\n"
  },
  {
    "path": "library.properties",
    "content": "name=ESP8266WebServer\nversion=1.0\nauthor=Ivan Grokhotkov\nmaintainer=Ivan Grokhtkov <ivan@esp8266.com>\nsentence=Simple web server library\nparagraph=The library supports HTTP GET and POST requests, provides argument parsing, handles one client at a time.\ncategory=Communication\nurl=\narchitectures=esp8266\n"
  },
  {
    "path": "src/ESP8266WebServer.cpp",
    "content": "/*\n  ESP8266WebServer.cpp - Dead simple web-server.\n  Supports only one simultaneous client, knows how to handle GET and POST.\n\n  Copyright (c) 2014 Ivan Grokhotkov. All rights reserved.\n\n  This library is free software; you can redistribute it and/or\n  modify it under the terms of the GNU Lesser General Public\n  License as published by the Free Software Foundation; either\n  version 2.1 of the License, or (at your option) any later version.\n\n  This library is distributed in the hope that it will be useful,\n  but WITHOUT ANY WARRANTY; without even the implied warranty of\n  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n  Lesser General Public License for more details.\n\n  You should have received a copy of the GNU Lesser General Public\n  License along with this library; if not, write to the Free Software\n  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA\n  Modified 8 May 2015 by Hristo Gochkov (proper post and file upload handling)\n*/\n\n\n#include <Arduino.h>\n#include <libb64/cencode.h>\n#include \"WiFiServer.h\"\n#include \"WiFiClient.h\"\n#include \"ESP8266WebServer.h\"\n#include \"FS.h\"\n#include \"detail/RequestHandlersImpl.h\"\n\n//#define DEBUG_ESP_HTTP_SERVER\n#ifdef DEBUG_ESP_PORT\n#define DEBUG_OUTPUT DEBUG_ESP_PORT\n#else\n#define DEBUG_OUTPUT Serial\n#endif\n\nconst char * AUTHORIZATION_HEADER = \"Authorization\";\n\nESP8266WebServer::ESP8266WebServer(IPAddress addr, int port)\n: _server(addr, port)\n, _currentMethod(HTTP_ANY)\n, _currentVersion(0)\n, _currentStatus(HC_NONE)\n, _statusChange(0)\n, _currentHandler(0)\n, _firstHandler(0)\n, _lastHandler(0)\n, _currentArgCount(0)\n, _currentArgs(0)\n, _headerKeysCount(0)\n, _currentHeaders(0)\n, _contentLength(0)\n, _chunked(false)\n{\n}\n\nESP8266WebServer::ESP8266WebServer(int port)\n: _server(port)\n, _currentMethod(HTTP_ANY)\n, _currentVersion(0)\n, _currentStatus(HC_NONE)\n, _statusChange(0)\n, _currentHandler(0)\n, _firstHandler(0)\n, _lastHandler(0)\n, _currentArgCount(0)\n, _currentArgs(0)\n, _headerKeysCount(0)\n, _currentHeaders(0)\n, _contentLength(0)\n, _chunked(false)\n{\n}\n\nESP8266WebServer::~ESP8266WebServer() {\n  if (_currentHeaders)\n    delete[]_currentHeaders;\n  _headerKeysCount = 0;\n  RequestHandler* handler = _firstHandler;\n  while (handler) {\n    RequestHandler* next = handler->next();\n    delete handler;\n    handler = next;\n  }\n  close();\n}\n\nvoid ESP8266WebServer::begin() {\n  _currentStatus = HC_NONE;\n  _server.begin();\n  if(!_headerKeysCount)\n    collectHeaders(0, 0);\n}\n\nbool ESP8266WebServer::authenticate(const char * username, const char * password){\n  if(hasHeader(AUTHORIZATION_HEADER)){\n    String authReq = header(AUTHORIZATION_HEADER);\n    if(authReq.startsWith(\"Basic\")){\n      authReq = authReq.substring(6);\n      authReq.trim();\n      char toencodeLen = strlen(username)+strlen(password)+1;\n      char *toencode = new char[toencodeLen + 1];\n      if(toencode == NULL){\n        authReq = String();\n        return false;\n      }\n      char *encoded = new char[base64_encode_expected_len(toencodeLen)+1];\n      if(encoded == NULL){\n        authReq = String();\n        delete[] toencode;\n        return false;\n      }\n      sprintf(toencode, \"%s:%s\", username, password);\n      if(base64_encode_chars(toencode, toencodeLen, encoded) > 0 && authReq.equals(encoded)){\n        authReq = String();\n        delete[] toencode;\n        delete[] encoded;\n        return true;\n      }\n      delete[] toencode;\n      delete[] encoded;\n    }\n    authReq = String();\n  }\n  return false;\n}\n\nvoid ESP8266WebServer::requestAuthentication(){\n  sendHeader(\"WWW-Authenticate\", \"Basic realm=\\\"Login Required\\\"\");\n  send(401);\n}\n\nvoid ESP8266WebServer::on(const String &uri, ESP8266WebServer::THandlerFunction handler) {\n  on(uri, HTTP_ANY, handler);\n}\n\nvoid ESP8266WebServer::on(const String &uri, HTTPMethod method, ESP8266WebServer::THandlerFunction fn) {\n  on(uri, method, fn, _fileUploadHandler);\n}\n\nvoid ESP8266WebServer::on(const String &uri, HTTPMethod method, ESP8266WebServer::THandlerFunction fn, ESP8266WebServer::THandlerFunction ufn) {\n  _addRequestHandler(new FunctionRequestHandler(fn, ufn, uri, method));\n}\n\nvoid ESP8266WebServer::addHandler(RequestHandler* handler) {\n    _addRequestHandler(handler);\n}\n\nvoid ESP8266WebServer::_addRequestHandler(RequestHandler* handler) {\n    if (!_lastHandler) {\n      _firstHandler = handler;\n      _lastHandler = handler;\n    }\n    else {\n      _lastHandler->next(handler);\n      _lastHandler = handler;\n    }\n}\n\nvoid ESP8266WebServer::serveStatic(const char* uri, FS& fs, const char* path, const char* cache_header) {\n    _addRequestHandler(new StaticRequestHandler(fs, path, uri, cache_header));\n}\n\nvoid ESP8266WebServer::handleClient() {\n  if (_currentStatus == HC_NONE) {\n    WiFiClient client = _server.available();\n    if (!client) {\n      return;\n    }\n\n#ifdef DEBUG_ESP_HTTP_SERVER\n    DEBUG_OUTPUT.println(\"New client\");\n#endif\n\n    _currentClient = client;\n    _currentStatus = HC_WAIT_READ;\n    _statusChange = millis();\n  }\n\n  if (!_currentClient.connected()) {\n    _currentClient = WiFiClient();\n    _currentStatus = HC_NONE;\n    return;\n  }\n\n  // Wait for data from client to become available\n  if (_currentStatus == HC_WAIT_READ) {\n    if (!_currentClient.available()) {\n      if (millis() - _statusChange > HTTP_MAX_DATA_WAIT) {\n        _currentClient = WiFiClient();\n        _currentStatus = HC_NONE;\n      }\n      yield();\n      return;\n    }\n\n    if (!_parseRequest(_currentClient)) {\n      _currentClient = WiFiClient();\n      _currentStatus = HC_NONE;\n      return;\n    }\n    _currentClient.setTimeout(HTTP_MAX_SEND_WAIT);\n    _contentLength = CONTENT_LENGTH_NOT_SET;\n    _handleRequest();\n\n    if (!_currentClient.connected()) {\n      _currentClient = WiFiClient();\n      _currentStatus = HC_NONE;\n      return;\n    } else {\n      _currentStatus = HC_WAIT_CLOSE;\n      _statusChange = millis();\n      return;\n    }\n  }\n\n  if (_currentStatus == HC_WAIT_CLOSE) {\n    if (millis() - _statusChange > HTTP_MAX_CLOSE_WAIT) {\n      _currentClient = WiFiClient();\n      _currentStatus = HC_NONE;\n    } else {\n      yield();\n      return;\n    }\n  }\n}\n\nvoid ESP8266WebServer::close() {\n  _server.close();\n}\n\nvoid ESP8266WebServer::stop() {\n  close();\n}\n\nvoid ESP8266WebServer::sendHeader(const String& name, const String& value, bool first) {\n  String headerLine = name;\n  headerLine += \": \";\n  headerLine += value;\n  headerLine += \"\\r\\n\";\n\n  if (first) {\n    _responseHeaders = headerLine + _responseHeaders;\n  }\n  else {\n    _responseHeaders += headerLine;\n  }\n}\n\nvoid ESP8266WebServer::setContentLength(size_t contentLength) {\n    _contentLength = contentLength;\n}\n\nvoid ESP8266WebServer::_prepareHeader(String& response, int code, const char* content_type, size_t contentLength) {\n    response = \"HTTP/1.\"+String(_currentVersion)+\" \";\n    response += String(code);\n    response += \" \";\n    response += _responseCodeToString(code);\n    response += \"\\r\\n\";\n\n    if (!content_type)\n        content_type = \"text/html\";\n\n    sendHeader(\"Content-Type\", content_type, true);\n    if (_contentLength == CONTENT_LENGTH_NOT_SET) {\n        sendHeader(\"Content-Length\", String(contentLength));\n    } else if (_contentLength != CONTENT_LENGTH_UNKNOWN) {\n        sendHeader(\"Content-Length\", String(_contentLength));\n    } else if(_contentLength == CONTENT_LENGTH_UNKNOWN && _currentVersion){ //HTTP/1.1 or above client\n      //let's do chunked\n      _chunked = true;\n      sendHeader(\"Accept-Ranges\",\"none\");\n      sendHeader(\"Transfer-Encoding\",\"chunked\");\n    }\n    sendHeader(\"Connection\", \"close\");\n\n    response += _responseHeaders;\n    response += \"\\r\\n\";\n    _responseHeaders = String();\n}\n\nvoid ESP8266WebServer::send(int code, const char* content_type, const String& content) {\n    String header;\n    // Can we asume the following?\n    //if(code == 200 && content.length() == 0 && _contentLength == CONTENT_LENGTH_NOT_SET)\n    //  _contentLength = CONTENT_LENGTH_UNKNOWN;\n    _prepareHeader(header, code, content_type, content.length());\n    _currentClient.write(header.c_str(), header.length());\n    if(content.length())\n      sendContent(content);\n}\n\nvoid ESP8266WebServer::send_P(int code, PGM_P content_type, PGM_P content) {\n    size_t contentLength = 0;\n\n    if (content != NULL) {\n        contentLength = strlen_P(content);\n    }\n\n    String header;\n    char type[64];\n    memccpy_P((void*)type, (PGM_VOID_P)content_type, 0, sizeof(type));\n    _prepareHeader(header, code, (const char* )type, contentLength);\n    _currentClient.write(header.c_str(), header.length());\n    sendContent_P(content);\n}\n\nvoid ESP8266WebServer::send_P(int code, PGM_P content_type, PGM_P content, size_t contentLength) {\n    String header;\n    char type[64];\n    memccpy_P((void*)type, (PGM_VOID_P)content_type, 0, sizeof(type));\n    _prepareHeader(header, code, (const char* )type, contentLength);\n    sendContent(header);\n    sendContent_P(content, contentLength);\n}\n\nvoid ESP8266WebServer::send(int code, char* content_type, const String& content) {\n  send(code, (const char*)content_type, content);\n}\n\nvoid ESP8266WebServer::send(int code, const String& content_type, const String& content) {\n  send(code, (const char*)content_type.c_str(), content);\n}\n\nvoid ESP8266WebServer::sendContent(const String& content) {\n  const char * footer = \"\\r\\n\";\n  size_t len = content.length();\n  if(_chunked) {\n    char * chunkSize = (char *)malloc(11);\n    if(chunkSize){\n      sprintf(chunkSize, \"%x%s\", len, footer);\n      _currentClient.write(chunkSize, strlen(chunkSize));\n      free(chunkSize);\n    }\n  }\n  _currentClient.write(content.c_str(), len);\n  if(_chunked){\n    _currentClient.write(footer, 2);\n  }\n}\n\nvoid ESP8266WebServer::sendContent_P(PGM_P content) {\n  sendContent_P(content, strlen_P(content));\n}\n\nvoid ESP8266WebServer::sendContent_P(PGM_P content, size_t size) {\n  const char * footer = \"\\r\\n\";\n  if(_chunked) {\n    char * chunkSize = (char *)malloc(11);\n    if(chunkSize){\n      sprintf(chunkSize, \"%x%s\", size, footer);\n      _currentClient.write(chunkSize, strlen(chunkSize));\n      free(chunkSize);\n    }\n  }\n  _currentClient.write_P(content, size);\n  if(_chunked){\n    _currentClient.write(footer, 2);\n  }\n}\n\n\nString ESP8266WebServer::arg(String name) {\n  for (int i = 0; i < _currentArgCount; ++i) {\n    if ( _currentArgs[i].key == name )\n      return _currentArgs[i].value;\n  }\n  return String();\n}\n\nString ESP8266WebServer::arg(int i) {\n  if (i < _currentArgCount)\n    return _currentArgs[i].value;\n  return String();\n}\n\nString ESP8266WebServer::argName(int i) {\n  if (i < _currentArgCount)\n    return _currentArgs[i].key;\n  return String();\n}\n\nint ESP8266WebServer::args() {\n  return _currentArgCount;\n}\n\nbool ESP8266WebServer::hasArg(String  name) {\n  for (int i = 0; i < _currentArgCount; ++i) {\n    if (_currentArgs[i].key == name)\n      return true;\n  }\n  return false;\n}\n\n\nString ESP8266WebServer::header(String name) {\n  for (int i = 0; i < _headerKeysCount; ++i) {\n    if (_currentHeaders[i].key.equalsIgnoreCase(name))\n      return _currentHeaders[i].value;\n  }\n  return String();\n}\n\nvoid ESP8266WebServer::collectHeaders(const char* headerKeys[], const size_t headerKeysCount) {\n  _headerKeysCount = headerKeysCount + 1;\n  if (_currentHeaders)\n     delete[]_currentHeaders;\n  _currentHeaders = new RequestArgument[_headerKeysCount];\n  _currentHeaders[0].key = AUTHORIZATION_HEADER;\n  for (int i = 1; i < _headerKeysCount; i++){\n    _currentHeaders[i].key = headerKeys[i-1];\n  }\n}\n\nString ESP8266WebServer::header(int i) {\n  if (i < _headerKeysCount)\n    return _currentHeaders[i].value;\n  return String();\n}\n\nString ESP8266WebServer::headerName(int i) {\n  if (i < _headerKeysCount)\n    return _currentHeaders[i].key;\n  return String();\n}\n\nint ESP8266WebServer::headers() {\n  return _headerKeysCount;\n}\n\nbool ESP8266WebServer::hasHeader(String name) {\n  for (int i = 0; i < _headerKeysCount; ++i) {\n    if ((_currentHeaders[i].key.equalsIgnoreCase(name)) &&  (_currentHeaders[i].value.length() > 0))\n      return true;\n  }\n  return false;\n}\n\nString ESP8266WebServer::hostHeader() {\n  return _hostHeader;\n}\n\nvoid ESP8266WebServer::onFileUpload(THandlerFunction fn) {\n  _fileUploadHandler = fn;\n}\n\nvoid ESP8266WebServer::onNotFound(THandlerFunction fn) {\n  _notFoundHandler = fn;\n}\n\nvoid ESP8266WebServer::_handleRequest() {\n  bool handled = false;\n  if (!_currentHandler){\n#ifdef DEBUG_ESP_HTTP_SERVER\n    DEBUG_OUTPUT.println(\"request handler not found\");\n#endif\n  }\n  else {\n    handled = _currentHandler->handle(*this, _currentMethod, _currentUri);\n#ifdef DEBUG_ESP_HTTP_SERVER\n    if (!handled) {\n      DEBUG_OUTPUT.println(\"request handler failed to handle request\");\n    }\n#endif\n  }\n\n  if (!handled) {\n    if(_notFoundHandler) {\n      _notFoundHandler();\n    }\n    else {\n      send(404, \"text/plain\", String(\"Not found: \") + _currentUri);\n    }\n  }\n\n  _currentUri = String();\n}\n\nString ESP8266WebServer::_responseCodeToString(int code) {\n  switch (code) {\n    case 100: return F(\"Continue\");\n    case 101: return F(\"Switching Protocols\");\n    case 200: return F(\"OK\");\n    case 201: return F(\"Created\");\n    case 202: return F(\"Accepted\");\n    case 203: return F(\"Non-Authoritative Information\");\n    case 204: return F(\"No Content\");\n    case 205: return F(\"Reset Content\");\n    case 206: return F(\"Partial Content\");\n    case 300: return F(\"Multiple Choices\");\n    case 301: return F(\"Moved Permanently\");\n    case 302: return F(\"Found\");\n    case 303: return F(\"See Other\");\n    case 304: return F(\"Not Modified\");\n    case 305: return F(\"Use Proxy\");\n    case 307: return F(\"Temporary Redirect\");\n    case 400: return F(\"Bad Request\");\n    case 401: return F(\"Unauthorized\");\n    case 402: return F(\"Payment Required\");\n    case 403: return F(\"Forbidden\");\n    case 404: return F(\"Not Found\");\n    case 405: return F(\"Method Not Allowed\");\n    case 406: return F(\"Not Acceptable\");\n    case 407: return F(\"Proxy Authentication Required\");\n    case 408: return F(\"Request Time-out\");\n    case 409: return F(\"Conflict\");\n    case 410: return F(\"Gone\");\n    case 411: return F(\"Length Required\");\n    case 412: return F(\"Precondition Failed\");\n    case 413: return F(\"Request Entity Too Large\");\n    case 414: return F(\"Request-URI Too Large\");\n    case 415: return F(\"Unsupported Media Type\");\n    case 416: return F(\"Requested range not satisfiable\");\n    case 417: return F(\"Expectation Failed\");\n    case 500: return F(\"Internal Server Error\");\n    case 501: return F(\"Not Implemented\");\n    case 502: return F(\"Bad Gateway\");\n    case 503: return F(\"Service Unavailable\");\n    case 504: return F(\"Gateway Time-out\");\n    case 505: return F(\"HTTP Version not supported\");\n    default:  return \"\";\n  }\n}\n"
  },
  {
    "path": "src/ESP8266WebServer.h",
    "content": "/*\n  ESP8266WebServer.h - Dead simple web-server.\n  Supports only one simultaneous client, knows how to handle GET and POST.\n\n  Copyright (c) 2014 Ivan Grokhotkov. All rights reserved.\n\n  This library is free software; you can redistribute it and/or\n  modify it under the terms of the GNU Lesser General Public\n  License as published by the Free Software Foundation; either\n  version 2.1 of the License, or (at your option) any later version.\n\n  This library is distributed in the hope that it will be useful,\n  but WITHOUT ANY WARRANTY; without even the implied warranty of\n  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n  Lesser General Public License for more details.\n\n  You should have received a copy of the GNU Lesser General Public\n  License along with this library; if not, write to the Free Software\n  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA\n  Modified 8 May 2015 by Hristo Gochkov (proper post and file upload handling)\n*/\n\n\n#ifndef ESP8266WEBSERVER_H\n#define ESP8266WEBSERVER_H\n\n#include <functional>\n#include <ESP8266WiFi.h>\n\nenum HTTPMethod { HTTP_ANY, HTTP_GET, HTTP_POST, HTTP_PUT, HTTP_PATCH, HTTP_DELETE, HTTP_OPTIONS };\nenum HTTPUploadStatus { UPLOAD_FILE_START, UPLOAD_FILE_WRITE, UPLOAD_FILE_END,\n                        UPLOAD_FILE_ABORTED };\nenum HTTPClientStatus { HC_NONE, HC_WAIT_READ, HC_WAIT_CLOSE };\n\n#define HTTP_DOWNLOAD_UNIT_SIZE 1460\n\n#ifndef HTTP_UPLOAD_BUFLEN\n#define HTTP_UPLOAD_BUFLEN 2048\n#endif\n\n#define HTTP_MAX_DATA_WAIT 1000 //ms to wait for the client to send the request\n#define HTTP_MAX_POST_WAIT 1000 //ms to wait for POST data to arrive\n#define HTTP_MAX_SEND_WAIT 5000 //ms to wait for data chunk to be ACKed\n#define HTTP_MAX_CLOSE_WAIT 2000 //ms to wait for the client to close the connection\n\n#define CONTENT_LENGTH_UNKNOWN ((size_t) -1)\n#define CONTENT_LENGTH_NOT_SET ((size_t) -2)\n\nclass ESP8266WebServer;\n\ntypedef struct {\n  HTTPUploadStatus status;\n  String  filename;\n  String  name;\n  String  type;\n  size_t  totalSize;    // file size\n  size_t  currentSize;  // size of data currently in buf\n  uint8_t buf[HTTP_UPLOAD_BUFLEN];\n} HTTPUpload;\n\n#include \"detail/RequestHandler.h\"\n\nnamespace fs {\nclass FS;\n}\n\nclass ESP8266WebServer\n{\npublic:\n  ESP8266WebServer(IPAddress addr, int port = 80);\n  ESP8266WebServer(int port = 80);\n  ~ESP8266WebServer();\n\n  void begin();\n  void handleClient();\n\n  void close();\n  void stop();\n\n  bool authenticate(const char * username, const char * password);\n  void requestAuthentication();\n\n  typedef std::function<void(void)> THandlerFunction;\n  void on(const String &uri, THandlerFunction handler);\n  void on(const String &uri, HTTPMethod method, THandlerFunction fn);\n  void on(const String &uri, HTTPMethod method, THandlerFunction fn, THandlerFunction ufn);\n  void addHandler(RequestHandler* handler);\n  void serveStatic(const char* uri, fs::FS& fs, const char* path, const char* cache_header = NULL );\n  void onNotFound(THandlerFunction fn);  //called when handler is not assigned\n  void onFileUpload(THandlerFunction fn); //handle file uploads\n\n  String uri() { return _currentUri; }\n  HTTPMethod method() { return _currentMethod; }\n  WiFiClient client() { return _currentClient; }\n  HTTPUpload& upload() { return _currentUpload; }\n\n  String arg(String name);        // get request argument value by name\n  String arg(int i);              // get request argument value by number\n  String argName(int i);          // get request argument name by number\n  int args();                     // get arguments count\n  bool hasArg(String name);       // check if argument exists\n  void collectHeaders(const char* headerKeys[], const size_t headerKeysCount); // set the request headers to collect\n  String header(String name);      // get request header value by name\n  String header(int i);              // get request header value by number\n  String headerName(int i);          // get request header name by number\n  int headers();                     // get header count\n  bool hasHeader(String name);       // check if header exists\n\n  String hostHeader();            // get request host header if available or empty String if not\n\n  // send response to the client\n  // code - HTTP response code, can be 200 or 404\n  // content_type - HTTP content type, like \"text/plain\" or \"image/png\"\n  // content - actual content body\n  void send(int code, const char* content_type = NULL, const String& content = String(\"\"));\n  void send(int code, char* content_type, const String& content);\n  void send(int code, const String& content_type, const String& content);\n  void send_P(int code, PGM_P content_type, PGM_P content);\n  void send_P(int code, PGM_P content_type, PGM_P content, size_t contentLength);\n\n  void setContentLength(size_t contentLength);\n  void sendHeader(const String& name, const String& value, bool first = false);\n  void sendContent(const String& content);\n  void sendContent_P(PGM_P content);\n  void sendContent_P(PGM_P content, size_t size);\n\n  static String urlDecode(const String& text);\n\ntemplate<typename T> size_t streamFile(T &file, const String& contentType){\n  setContentLength(file.size());\n  if (String(file.name()).endsWith(\".gz\") &&\n      contentType != \"application/x-gzip\" &&\n      contentType != \"application/octet-stream\"){\n    sendHeader(\"Content-Encoding\", \"gzip\");\n  }\n  send(200, contentType, \"\");\n  return _currentClient.write(file);\n}\n\nprotected:\n  void _addRequestHandler(RequestHandler* handler);\n  void _handleRequest();\n  bool _parseRequest(WiFiClient& client);\n  void _parseArguments(String data);\n  static String _responseCodeToString(int code);\n  bool _parseForm(WiFiClient& client, String boundary, uint32_t len);\n  bool _parseFormUploadAborted();\n  void _uploadWriteByte(uint8_t b);\n  uint8_t _uploadReadByte(WiFiClient& client);\n  void _prepareHeader(String& response, int code, const char* content_type, size_t contentLength);\n  bool _collectHeader(const char* headerName, const char* headerValue);\n\n  struct RequestArgument {\n    String key;\n    String value;\n  };\n\n  WiFiServer  _server;\n\n  WiFiClient  _currentClient;\n  HTTPMethod  _currentMethod;\n  String      _currentUri;\n  uint8_t     _currentVersion;\n  HTTPClientStatus _currentStatus;\n  unsigned long _statusChange;\n\n  RequestHandler*  _currentHandler;\n  RequestHandler*  _firstHandler;\n  RequestHandler*  _lastHandler;\n  THandlerFunction _notFoundHandler;\n  THandlerFunction _fileUploadHandler;\n\n  int              _currentArgCount;\n  RequestArgument* _currentArgs;\n  HTTPUpload       _currentUpload;\n\n  int              _headerKeysCount;\n  RequestArgument* _currentHeaders;\n  size_t           _contentLength;\n  String           _responseHeaders;\n\n  String           _hostHeader;\n  bool             _chunked;\n\n};\n\n\n#endif //ESP8266WEBSERVER_H\n"
  },
  {
    "path": "src/Parsing.cpp",
    "content": "/*\n  Parsing.cpp - HTTP request parsing.\n\n  Copyright (c) 2015 Ivan Grokhotkov. All rights reserved.\n\n  This library is free software; you can redistribute it and/or\n  modify it under the terms of the GNU Lesser General Public\n  License as published by the Free Software Foundation; either\n  version 2.1 of the License, or (at your option) any later version.\n\n  This library is distributed in the hope that it will be useful,\n  but WITHOUT ANY WARRANTY; without even the implied warranty of\n  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n  Lesser General Public License for more details.\n\n  You should have received a copy of the GNU Lesser General Public\n  License along with this library; if not, write to the Free Software\n  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA\n  Modified 8 May 2015 by Hristo Gochkov (proper post and file upload handling)\n*/\n\n#include <Arduino.h>\n#include \"WiFiServer.h\"\n#include \"WiFiClient.h\"\n#include \"ESP8266WebServer.h\"\n\n//#define DEBUG_ESP_HTTP_SERVER\n#ifdef DEBUG_ESP_PORT\n#define DEBUG_OUTPUT DEBUG_ESP_PORT\n#else\n#define DEBUG_OUTPUT Serial\n#endif\n\nstatic char* readBytesWithTimeout(WiFiClient& client, size_t maxLength, size_t& dataLength, int timeout_ms)\n{\n  char *buf = nullptr;\n  dataLength = 0;\n  while (dataLength < maxLength) {\n    int tries = timeout_ms;\n    size_t newLength;\n    while (!(newLength = client.available()) && tries--) delay(1);\n    if (!newLength) {\n      break;\n    }\n    if (!buf) {\n      buf = (char *) malloc(newLength + 1);\n      if (!buf) {\n        return nullptr;\n      }\n    }\n    else {\n      char* newBuf = (char *) realloc(buf, dataLength + newLength + 1);\n      if (!newBuf) {\n        free(buf);\n        return nullptr;\n      }\n      buf = newBuf;\n    }\n    client.readBytes(buf + dataLength, newLength);\n    dataLength += newLength;\n    buf[dataLength] = '\\0';\n  }\n  return buf;\n}\n\nbool ESP8266WebServer::_parseRequest(WiFiClient& client) {\n  // Read the first line of HTTP request\n  String req = client.readStringUntil('\\r');\n  client.readStringUntil('\\n');\n  //reset header value\n  for (int i = 0; i < _headerKeysCount; ++i) {\n    _currentHeaders[i].value =String();\n   }\n\n  // First line of HTTP request looks like \"GET /path HTTP/1.1\"\n  // Retrieve the \"/path\" part by finding the spaces\n  int addr_start = req.indexOf(' ');\n  int addr_end = req.indexOf(' ', addr_start + 1);\n  if (addr_start == -1 || addr_end == -1) {\n#ifdef DEBUG_ESP_HTTP_SERVER\n    DEBUG_OUTPUT.print(\"Invalid request: \");\n    DEBUG_OUTPUT.println(req);\n#endif\n    return false;\n  }\n\n  String methodStr = req.substring(0, addr_start);\n  String url = req.substring(addr_start + 1, addr_end);\n  String versionEnd = req.substring(addr_end + 8);\n  _currentVersion = atoi(versionEnd.c_str());\n  String searchStr = \"\";\n  int hasSearch = url.indexOf('?');\n  if (hasSearch != -1){\n    searchStr = urlDecode(url.substring(hasSearch + 1));\n    url = url.substring(0, hasSearch);\n  }\n  _currentUri = url;\n  _chunked = false;\n\n  HTTPMethod method = HTTP_GET;\n  if (methodStr == \"POST\") {\n    method = HTTP_POST;\n  } else if (methodStr == \"DELETE\") {\n    method = HTTP_DELETE;\n  } else if (methodStr == \"OPTIONS\") {\n    method = HTTP_OPTIONS;\n  } else if (methodStr == \"PUT\") {\n    method = HTTP_PUT;\n  } else if (methodStr == \"PATCH\") {\n    method = HTTP_PATCH;\n  }\n  _currentMethod = method;\n\n#ifdef DEBUG_ESP_HTTP_SERVER\n  DEBUG_OUTPUT.print(\"method: \");\n  DEBUG_OUTPUT.print(methodStr);\n  DEBUG_OUTPUT.print(\" url: \");\n  DEBUG_OUTPUT.print(url);\n  DEBUG_OUTPUT.print(\" search: \");\n  DEBUG_OUTPUT.println(searchStr);\n#endif\n\n  //attach handler\n  RequestHandler* handler;\n  for (handler = _firstHandler; handler; handler = handler->next()) {\n    if (handler->canHandle(_currentMethod, _currentUri))\n      break;\n  }\n  _currentHandler = handler;\n\n  String formData;\n  // below is needed only when POST type request\n  if (method == HTTP_POST || method == HTTP_PUT || method == HTTP_PATCH || method == HTTP_DELETE){\n    String boundaryStr;\n    String headerName;\n    String headerValue;\n    bool isForm = false;\n    bool isEncoded = false;\n    uint32_t contentLength = 0;\n    //parse headers\n    while(1){\n      req = client.readStringUntil('\\r');\n      client.readStringUntil('\\n');\n      if (req == \"\") break;//no moar headers\n      int headerDiv = req.indexOf(':');\n      if (headerDiv == -1){\n        break;\n      }\n      headerName = req.substring(0, headerDiv);\n      headerValue = req.substring(headerDiv + 1);\n      headerValue.trim();\n       _collectHeader(headerName.c_str(),headerValue.c_str());\n\n      #ifdef DEBUG_ESP_HTTP_SERVER\n      DEBUG_OUTPUT.print(\"headerName: \");\n      DEBUG_OUTPUT.println(headerName);\n      DEBUG_OUTPUT.print(\"headerValue: \");\n      DEBUG_OUTPUT.println(headerValue);\n      #endif\n\n      if (headerName.equalsIgnoreCase(\"Content-Type\")){\n        if (headerValue.startsWith(\"text/plain\")){\n          isForm = false;\n        } else if (headerValue.startsWith(\"application/x-www-form-urlencoded\")){\n          isForm = false;\n          isEncoded = true;\n        } else if (headerValue.startsWith(\"multipart/\")){\n          boundaryStr = headerValue.substring(headerValue.indexOf('=')+1);\n          isForm = true;\n        }\n      } else if (headerName.equalsIgnoreCase(\"Content-Length\")){\n        contentLength = headerValue.toInt();\n      } else if (headerName.equalsIgnoreCase(\"Host\")){\n        _hostHeader = headerValue;\n      }\n    }\n\n    if (!isForm){\n      size_t plainLength;\n      char* plainBuf = readBytesWithTimeout(client, contentLength, plainLength, HTTP_MAX_POST_WAIT);\n      if (plainLength < contentLength) {\n      \tfree(plainBuf);\n      \treturn false;\n      }\n      if (contentLength > 0) {\n        if (searchStr != \"\") searchStr += '&';\n        if(isEncoded){\n          //url encoded form\n          String decoded = urlDecode(plainBuf);\n          size_t decodedLen = decoded.length();\n          memcpy(plainBuf, decoded.c_str(), decodedLen);\n          plainBuf[decodedLen] = 0;\n          searchStr += plainBuf;\n        }\n        _parseArguments(searchStr);\n        if(!isEncoded){\n          //plain post json or other data\n          RequestArgument& arg = _currentArgs[_currentArgCount++];\n          arg.key = \"plain\";\n          arg.value = String(plainBuf);\n        }\n\n  #ifdef DEBUG_ESP_HTTP_SERVER\n        DEBUG_OUTPUT.print(\"Plain: \");\n        DEBUG_OUTPUT.println(plainBuf);\n  #endif\n        free(plainBuf);\n      }\n    }\n\n    if (isForm){\n      _parseArguments(searchStr);\n      if (!_parseForm(client, boundaryStr, contentLength)) {\n        return false;\n      }\n    }\n  } else {\n    String headerName;\n    String headerValue;\n    //parse headers\n    while(1){\n      req = client.readStringUntil('\\r');\n      client.readStringUntil('\\n');\n      if (req == \"\") break;//no moar headers\n      int headerDiv = req.indexOf(':');\n      if (headerDiv == -1){\n        break;\n      }\n      headerName = req.substring(0, headerDiv);\n      headerValue = req.substring(headerDiv + 2);\n      _collectHeader(headerName.c_str(),headerValue.c_str());\n\n\t  #ifdef DEBUG_ESP_HTTP_SERVER\n\t  DEBUG_OUTPUT.print(\"headerName: \");\n\t  DEBUG_OUTPUT.println(headerName);\n\t  DEBUG_OUTPUT.print(\"headerValue: \");\n\t  DEBUG_OUTPUT.println(headerValue);\n\t  #endif\n\n\t  if (headerName.equalsIgnoreCase(\"Host\")){\n        _hostHeader = headerValue;\n      }\n    }\n    _parseArguments(searchStr);\n  }\n  client.flush();\n\n#ifdef DEBUG_ESP_HTTP_SERVER\n  DEBUG_OUTPUT.print(\"Request: \");\n  DEBUG_OUTPUT.println(url);\n  DEBUG_OUTPUT.print(\" Arguments: \");\n  DEBUG_OUTPUT.println(searchStr);\n#endif\n\n  return true;\n}\n\nbool ESP8266WebServer::_collectHeader(const char* headerName, const char* headerValue) {\n  for (int i = 0; i < _headerKeysCount; i++) {\n    if (_currentHeaders[i].key.equalsIgnoreCase(headerName)) {\n            _currentHeaders[i].value=headerValue;\n            return true;\n        }\n  }\n  return false;\n}\n\nvoid ESP8266WebServer::_parseArguments(String data) {\n#ifdef DEBUG_ESP_HTTP_SERVER\n  DEBUG_OUTPUT.print(\"args: \");\n  DEBUG_OUTPUT.println(data);\n#endif\n  if (_currentArgs)\n    delete[] _currentArgs;\n  _currentArgs = 0;\n  if (data.length() == 0) {\n    _currentArgCount = 0;\n    _currentArgs = new RequestArgument[1];\n    return;\n  }\n  _currentArgCount = 1;\n\n  for (int i = 0; i < (int)data.length(); ) {\n    i = data.indexOf('&', i);\n    if (i == -1)\n      break;\n    ++i;\n    ++_currentArgCount;\n  }\n#ifdef DEBUG_ESP_HTTP_SERVER\n  DEBUG_OUTPUT.print(\"args count: \");\n  DEBUG_OUTPUT.println(_currentArgCount);\n#endif\n\n  _currentArgs = new RequestArgument[_currentArgCount+1];\n  int pos = 0;\n  int iarg;\n  for (iarg = 0; iarg < _currentArgCount;) {\n    int equal_sign_index = data.indexOf('=', pos);\n    int next_arg_index = data.indexOf('&', pos);\n#ifdef DEBUG_ESP_HTTP_SERVER\n    DEBUG_OUTPUT.print(\"pos \");\n    DEBUG_OUTPUT.print(pos);\n    DEBUG_OUTPUT.print(\"=@ \");\n    DEBUG_OUTPUT.print(equal_sign_index);\n    DEBUG_OUTPUT.print(\" &@ \");\n    DEBUG_OUTPUT.println(next_arg_index);\n#endif\n    if ((equal_sign_index == -1) || ((equal_sign_index > next_arg_index) && (next_arg_index != -1))) {\n#ifdef DEBUG_ESP_HTTP_SERVER\n      DEBUG_OUTPUT.print(\"arg missing value: \");\n      DEBUG_OUTPUT.println(iarg);\n#endif\n      if (next_arg_index == -1)\n        break;\n      pos = next_arg_index + 1;\n      continue;\n    }\n    RequestArgument& arg = _currentArgs[iarg];\n    arg.key = data.substring(pos, equal_sign_index);\n\targ.value = data.substring(equal_sign_index + 1, next_arg_index);\n#ifdef DEBUG_ESP_HTTP_SERVER\n    DEBUG_OUTPUT.print(\"arg \");\n    DEBUG_OUTPUT.print(iarg);\n    DEBUG_OUTPUT.print(\" key: \");\n    DEBUG_OUTPUT.print(arg.key);\n    DEBUG_OUTPUT.print(\" value: \");\n    DEBUG_OUTPUT.println(arg.value);\n#endif\n    ++iarg;\n    if (next_arg_index == -1)\n      break;\n    pos = next_arg_index + 1;\n  }\n  _currentArgCount = iarg;\n#ifdef DEBUG_ESP_HTTP_SERVER\n  DEBUG_OUTPUT.print(\"args count: \");\n  DEBUG_OUTPUT.println(_currentArgCount);\n#endif\n\n}\n\nvoid ESP8266WebServer::_uploadWriteByte(uint8_t b){\n  if (_currentUpload.currentSize == HTTP_UPLOAD_BUFLEN){\n    if(_currentHandler && _currentHandler->canUpload(_currentUri))\n      _currentHandler->upload(*this, _currentUri, _currentUpload);\n    _currentUpload.totalSize += _currentUpload.currentSize;\n    _currentUpload.currentSize = 0;\n  }\n  _currentUpload.buf[_currentUpload.currentSize++] = b;\n}\n\nuint8_t ESP8266WebServer::_uploadReadByte(WiFiClient& client){\n  int res = client.read();\n  if(res == -1){\n    while(!client.available() && client.connected())\n      yield();\n    res = client.read();\n  }\n  return (uint8_t)res;\n}\n\nbool ESP8266WebServer::_parseForm(WiFiClient& client, String boundary, uint32_t len){\n  (void) len;\n#ifdef DEBUG_ESP_HTTP_SERVER\n  DEBUG_OUTPUT.print(\"Parse Form: Boundary: \");\n  DEBUG_OUTPUT.print(boundary);\n  DEBUG_OUTPUT.print(\" Length: \");\n  DEBUG_OUTPUT.println(len);\n#endif\n  String line;\n  int retry = 0;\n  do {\n    line = client.readStringUntil('\\r');\n    ++retry;\n  } while (line.length() == 0 && retry < 3);\n\n  client.readStringUntil('\\n');\n  //start reading the form\n  if (line == (\"--\"+boundary)){\n    RequestArgument* postArgs = new RequestArgument[32];\n    int postArgsLen = 0;\n    while(1){\n      String argName;\n      String argValue;\n      String argType;\n      String argFilename;\n      bool argIsFile = false;\n\n      line = client.readStringUntil('\\r');\n      client.readStringUntil('\\n');\n      if (line.length() > 19 && line.substring(0, 19).equalsIgnoreCase(\"Content-Disposition\")){\n        int nameStart = line.indexOf('=');\n        if (nameStart != -1){\n          argName = line.substring(nameStart+2);\n          nameStart = argName.indexOf('=');\n          if (nameStart == -1){\n            argName = argName.substring(0, argName.length() - 1);\n          } else {\n            argFilename = argName.substring(nameStart+2, argName.length() - 1);\n            argName = argName.substring(0, argName.indexOf('\"'));\n            argIsFile = true;\n#ifdef DEBUG_ESP_HTTP_SERVER\n            DEBUG_OUTPUT.print(\"PostArg FileName: \");\n            DEBUG_OUTPUT.println(argFilename);\n#endif\n            //use GET to set the filename if uploading using blob\n            if (argFilename == \"blob\" && hasArg(\"filename\")) argFilename = arg(\"filename\");\n          }\n#ifdef DEBUG_ESP_HTTP_SERVER\n          DEBUG_OUTPUT.print(\"PostArg Name: \");\n          DEBUG_OUTPUT.println(argName);\n#endif\n          argType = \"text/plain\";\n          line = client.readStringUntil('\\r');\n          client.readStringUntil('\\n');\n          if (line.length() > 12 && line.substring(0, 12).equalsIgnoreCase(\"Content-Type\")){\n            argType = line.substring(line.indexOf(':')+2);\n            //skip next line\n            client.readStringUntil('\\r');\n            client.readStringUntil('\\n');\n          }\n#ifdef DEBUG_ESP_HTTP_SERVER\n          DEBUG_OUTPUT.print(\"PostArg Type: \");\n          DEBUG_OUTPUT.println(argType);\n#endif\n          if (!argIsFile){\n            while(1){\n              line = client.readStringUntil('\\r');\n              client.readStringUntil('\\n');\n              if (line.startsWith(\"--\"+boundary)) break;\n              if (argValue.length() > 0) argValue += \"\\n\";\n              argValue += line;\n            }\n#ifdef DEBUG_ESP_HTTP_SERVER\n            DEBUG_OUTPUT.print(\"PostArg Value: \");\n            DEBUG_OUTPUT.println(argValue);\n            DEBUG_OUTPUT.println();\n#endif\n\n            RequestArgument& arg = postArgs[postArgsLen++];\n            arg.key = argName;\n            arg.value = argValue;\n\n            if (line == (\"--\"+boundary+\"--\")){\n#ifdef DEBUG_ESP_HTTP_SERVER\n              DEBUG_OUTPUT.println(\"Done Parsing POST\");\n#endif\n              break;\n            }\n          } else {\n            _currentUpload.status = UPLOAD_FILE_START;\n            _currentUpload.name = argName;\n            _currentUpload.filename = argFilename;\n            _currentUpload.type = argType;\n            _currentUpload.totalSize = 0;\n            _currentUpload.currentSize = 0;\n#ifdef DEBUG_ESP_HTTP_SERVER\n            DEBUG_OUTPUT.print(\"Start File: \");\n            DEBUG_OUTPUT.print(_currentUpload.filename);\n            DEBUG_OUTPUT.print(\" Type: \");\n            DEBUG_OUTPUT.println(_currentUpload.type);\n#endif\n            if(_currentHandler && _currentHandler->canUpload(_currentUri))\n              _currentHandler->upload(*this, _currentUri, _currentUpload);\n            _currentUpload.status = UPLOAD_FILE_WRITE;\n            uint8_t argByte = _uploadReadByte(client);\nreadfile:\n            while(argByte != 0x0D){\n              if (!client.connected()) return _parseFormUploadAborted();\n              _uploadWriteByte(argByte);\n              argByte = _uploadReadByte(client);\n            }\n\n            argByte = _uploadReadByte(client);\n            if (!client.connected()) return _parseFormUploadAborted();\n            if (argByte == 0x0A){\n              argByte = _uploadReadByte(client);\n              if (!client.connected()) return _parseFormUploadAborted();\n              if ((char)argByte != '-'){\n                //continue reading the file\n                _uploadWriteByte(0x0D);\n                _uploadWriteByte(0x0A);\n                goto readfile;\n              } else {\n                argByte = _uploadReadByte(client);\n                if (!client.connected()) return _parseFormUploadAborted();\n                if ((char)argByte != '-'){\n                  //continue reading the file\n                  _uploadWriteByte(0x0D);\n                  _uploadWriteByte(0x0A);\n                  _uploadWriteByte((uint8_t)('-'));\n                  goto readfile;\n                }\n              }\n\n              uint8_t endBuf[boundary.length()];\n              client.readBytes(endBuf, boundary.length());\n\n              if (strstr((const char*)endBuf, boundary.c_str()) != NULL){\n                if(_currentHandler && _currentHandler->canUpload(_currentUri))\n                  _currentHandler->upload(*this, _currentUri, _currentUpload);\n                _currentUpload.totalSize += _currentUpload.currentSize;\n                _currentUpload.status = UPLOAD_FILE_END;\n                if(_currentHandler && _currentHandler->canUpload(_currentUri))\n                  _currentHandler->upload(*this, _currentUri, _currentUpload);\n#ifdef DEBUG_ESP_HTTP_SERVER\n                DEBUG_OUTPUT.print(\"End File: \");\n                DEBUG_OUTPUT.print(_currentUpload.filename);\n                DEBUG_OUTPUT.print(\" Type: \");\n                DEBUG_OUTPUT.print(_currentUpload.type);\n                DEBUG_OUTPUT.print(\" Size: \");\n                DEBUG_OUTPUT.println(_currentUpload.totalSize);\n#endif\n                line = client.readStringUntil(0x0D);\n                client.readStringUntil(0x0A);\n                if (line == \"--\"){\n#ifdef DEBUG_ESP_HTTP_SERVER\n                  DEBUG_OUTPUT.println(\"Done Parsing POST\");\n#endif\n                  break;\n                }\n                continue;\n              } else {\n                _uploadWriteByte(0x0D);\n                _uploadWriteByte(0x0A);\n                _uploadWriteByte((uint8_t)('-'));\n                _uploadWriteByte((uint8_t)('-'));\n                uint32_t i = 0;\n                while(i < boundary.length()){\n                  _uploadWriteByte(endBuf[i++]);\n                }\n                argByte = _uploadReadByte(client);\n                goto readfile;\n              }\n            } else {\n              _uploadWriteByte(0x0D);\n              goto readfile;\n            }\n            break;\n          }\n        }\n      }\n    }\n\n    int iarg;\n    int totalArgs = ((32 - postArgsLen) < _currentArgCount)?(32 - postArgsLen):_currentArgCount;\n    for (iarg = 0; iarg < totalArgs; iarg++){\n      RequestArgument& arg = postArgs[postArgsLen++];\n      arg.key = _currentArgs[iarg].key;\n      arg.value = _currentArgs[iarg].value;\n    }\n    if (_currentArgs) delete[] _currentArgs;\n    _currentArgs = new RequestArgument[postArgsLen];\n    for (iarg = 0; iarg < postArgsLen; iarg++){\n      RequestArgument& arg = _currentArgs[iarg];\n      arg.key = postArgs[iarg].key;\n      arg.value = postArgs[iarg].value;\n    }\n    _currentArgCount = iarg;\n    if (postArgs) delete[] postArgs;\n    return true;\n  }\n#ifdef DEBUG_ESP_HTTP_SERVER\n  DEBUG_OUTPUT.print(\"Error: line: \");\n  DEBUG_OUTPUT.println(line);\n#endif\n  return false;\n}\n\nString ESP8266WebServer::urlDecode(const String& text)\n{\n\tString decoded = \"\";\n\tchar temp[] = \"0x00\";\n\tunsigned int len = text.length();\n\tunsigned int i = 0;\n\twhile (i < len)\n\t{\n\t\tchar decodedChar;\n\t\tchar encodedChar = text.charAt(i++);\n\t\tif ((encodedChar == '%') && (i + 1 < len))\n\t\t{\n\t\t\ttemp[2] = text.charAt(i++);\n\t\t\ttemp[3] = text.charAt(i++);\n\n\t\t\tdecodedChar = strtol(temp, NULL, 16);\n\t\t}\n\t\telse {\n\t\t\tif (encodedChar == '+')\n\t\t\t{\n\t\t\t\tdecodedChar = ' ';\n\t\t\t}\n\t\t\telse {\n\t\t\t\tdecodedChar = encodedChar;  // normal ascii char\n\t\t\t}\n\t\t}\n\t\tdecoded += decodedChar;\n\t}\n\treturn decoded;\n}\n\nbool ESP8266WebServer::_parseFormUploadAborted(){\n  _currentUpload.status = UPLOAD_FILE_ABORTED;\n  if(_currentHandler && _currentHandler->canUpload(_currentUri))\n    _currentHandler->upload(*this, _currentUri, _currentUpload);\n  return false;\n}\n"
  },
  {
    "path": "src/detail/RequestHandler.h",
    "content": "#ifndef REQUESTHANDLER_H\n#define REQUESTHANDLER_H\n\nclass RequestHandler {\npublic:\n    virtual ~RequestHandler() { }\n    virtual bool canHandle(HTTPMethod method, String uri) { (void) method; (void) uri; return false; }\n    virtual bool canUpload(String uri) { (void) uri; return false; }\n    virtual bool handle(ESP8266WebServer& server, HTTPMethod requestMethod, String requestUri) { (void) server; (void) requestMethod; (void) requestUri; return false; }\n    virtual void upload(ESP8266WebServer& server, String requestUri, HTTPUpload& upload) { (void) server; (void) requestUri; (void) upload; }\n\n    RequestHandler* next() { return _next; }\n    void next(RequestHandler* r) { _next = r; }\n\nprivate:\n    RequestHandler* _next = nullptr;\n};\n\n#endif //REQUESTHANDLER_H\n"
  },
  {
    "path": "src/detail/RequestHandlersImpl.h",
    "content": "#ifndef REQUESTHANDLERSIMPL_H\n#define REQUESTHANDLERSIMPL_H\n\n#include \"RequestHandler.h\"\n\nclass FunctionRequestHandler : public RequestHandler {\npublic:\n    FunctionRequestHandler(ESP8266WebServer::THandlerFunction fn, ESP8266WebServer::THandlerFunction ufn, const String &uri, HTTPMethod method)\n    : _fn(fn)\n    , _ufn(ufn)\n    , _uri(uri)\n    , _method(method)\n    {\n    }\n\n    bool canHandle(HTTPMethod requestMethod, String requestUri) override  {\n        if (_method != HTTP_ANY && _method != requestMethod)\n            return false;\n\n        if (requestUri != _uri)\n            return false;\n\n        return true;\n    }\n\n    bool canUpload(String requestUri) override  {\n        if (!_ufn || !canHandle(HTTP_POST, requestUri))\n            return false;\n\n        return true;\n    }\n\n    bool handle(ESP8266WebServer& server, HTTPMethod requestMethod, String requestUri) override {\n        (void) server;\n        if (!canHandle(requestMethod, requestUri))\n            return false;\n\n        _fn();\n        return true;\n    }\n\n    void upload(ESP8266WebServer& server, String requestUri, HTTPUpload& upload) override {\n        (void) server;\n        (void) upload;\n        if (canUpload(requestUri))\n            _ufn();\n    }\n\nprotected:\n    ESP8266WebServer::THandlerFunction _fn;\n    ESP8266WebServer::THandlerFunction _ufn;\n    String _uri;\n    HTTPMethod _method;\n};\n\nclass StaticRequestHandler : public RequestHandler {\npublic:\n    StaticRequestHandler(FS& fs, const char* path, const char* uri, const char* cache_header)\n    : _fs(fs)\n    , _uri(uri)\n    , _path(path)\n    , _cache_header(cache_header)\n    {\n        _isFile = fs.exists(path);\n        DEBUGV(\"StaticRequestHandler: path=%s uri=%s isFile=%d, cache_header=%s\\r\\n\", path, uri, _isFile, cache_header);\n        _baseUriLength = _uri.length();\n    }\n\n    bool canHandle(HTTPMethod requestMethod, String requestUri) override  {\n        if (requestMethod != HTTP_GET)\n            return false;\n\n        if ((_isFile && requestUri != _uri) || !requestUri.startsWith(_uri))\n            return false;\n\n        return true;\n    }\n\n    bool handle(ESP8266WebServer& server, HTTPMethod requestMethod, String requestUri) override {\n        if (!canHandle(requestMethod, requestUri))\n            return false;\n\n        DEBUGV(\"StaticRequestHandler::handle: request=%s _uri=%s\\r\\n\", requestUri.c_str(), _uri.c_str());\n\n        String path(_path);\n\n        if (!_isFile) {\n            // Base URI doesn't point to a file.\n            // If a directory is requested, look for index file.\n            if (requestUri.endsWith(\"/\")) requestUri += \"index.htm\";\n\n            // Append whatever follows this URI in request to get the file path.\n            path += requestUri.substring(_baseUriLength);\n        }\n        DEBUGV(\"StaticRequestHandler::handle: path=%s, isFile=%d\\r\\n\", path.c_str(), _isFile);\n\n        String contentType = getContentType(path);\n\n        // 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\n        // if you point the the path to gzip you will serve the gzip as content type \"application/x-gzip\", not text or javascript etc...\n        if (!path.endsWith(\".gz\") && !_fs.exists(path))  {\n            String pathWithGz = path + \".gz\";\n            if(_fs.exists(pathWithGz))\n                path += \".gz\";\n        }\n\n        File f = _fs.open(path, \"r\");\n        if (!f)\n            return false;\n\n        if (_cache_header.length() != 0)\n            server.sendHeader(\"Cache-Control\", _cache_header);\n\n        server.streamFile(f, contentType);\n        return true;\n    }\n\n    static String getContentType(const String& path) {\n        if (path.endsWith(\".html\")) return \"text/html\";\n        else if (path.endsWith(\".htm\")) return \"text/html\";\n        else if (path.endsWith(\".css\")) return \"text/css\";\n        else if (path.endsWith(\".txt\")) return \"text/plain\";\n        else if (path.endsWith(\".js\")) return \"application/javascript\";\n        else if (path.endsWith(\".json\")) return \"application/json\";\n        else if (path.endsWith(\".png\")) return \"image/png\";\n        else if (path.endsWith(\".gif\")) return \"image/gif\";\n        else if (path.endsWith(\".jpg\")) return \"image/jpeg\";\n        else if (path.endsWith(\".ico\")) return \"image/x-icon\";\n        else if (path.endsWith(\".svg\")) return \"image/svg+xml\";\n        else if (path.endsWith(\".ttf\")) return \"application/x-font-ttf\";\n        else if (path.endsWith(\".otf\")) return \"application/x-font-opentype\";\n        else if (path.endsWith(\".woff\")) return \"application/font-woff\";\n        else if (path.endsWith(\".woff2\")) return \"application/font-woff2\";\n        else if (path.endsWith(\".eot\")) return \"application/vnd.ms-fontobject\";\n        else if (path.endsWith(\".sfnt\")) return \"application/font-sfnt\";\n        else if (path.endsWith(\".xml\")) return \"text/xml\";\n        else if (path.endsWith(\".pdf\")) return \"application/pdf\";\n        else if (path.endsWith(\".zip\")) return \"application/zip\";\n        else if(path.endsWith(\".gz\")) return \"application/x-gzip\";\n        else if (path.endsWith(\".appcache\")) return \"text/cache-manifest\";\n        return \"application/octet-stream\";\n    }\n\nprotected:\n    FS _fs;\n    String _uri;\n    String _path;\n    String _cache_header;\n    bool _isFile;\n    size_t _baseUriLength;\n};\n\n\n#endif //REQUESTHANDLERSIMPL_H\n"
  }
]