From 9836f2c256a8d545a4417feb1a8e4d699e4d8600 Mon Sep 17 00:00:00 2001 From: Just Call Me Koko Date: Fri, 17 May 2024 11:17:22 -0400 Subject: [PATCH 01/13] Add dev board pro gps enclosure lid --- .../CAD/GPS-Expansion-Enclosure-Top.stl | Bin 0 -> 502784 bytes .../CAD/GPS-Expansion-SMA-Enclosure-Top.stl | Bin 0 -> 557484 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 mechanical/FlipperZero-WiFi-Devboard-Pro/CAD/GPS-Expansion-Enclosure-Top.stl create mode 100644 mechanical/FlipperZero-WiFi-Devboard-Pro/CAD/GPS-Expansion-SMA-Enclosure-Top.stl diff --git a/mechanical/FlipperZero-WiFi-Devboard-Pro/CAD/GPS-Expansion-Enclosure-Top.stl b/mechanical/FlipperZero-WiFi-Devboard-Pro/CAD/GPS-Expansion-Enclosure-Top.stl new file mode 100644 index 0000000000000000000000000000000000000000..4d1a4e4cf463cfd9bb66768415a32efc2dfc5cc2 GIT binary patch literal 502784 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

mp^qnk3yh=L`u(^B0YonVW^wY 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�kNv2fA;n5+8TltCNAt; zH8!pxh>Xmx*%$m-DN8a~VdA>%sV1}RHRKpu#^zS9Mx69a8Ty+5vssxGT8(pH2tXA6_uM0iB zK-PoX3cmLx`F7f9q$L0FMh%YL)EGqay|u}9gYSB|ct|L#WKTo02PK0EyhBF*j`!I{ zBb{ozbZn2US*9nl!tKi6K}RFczw&!g(|vi*%Tn$_H= zF1@>%QIxk26WhKyJ~lpI>1gEl*M~N~r0J=4J(6v)D|p6PpYdknhu^-$ZYzCA<>B`PX_x zF~P1r<4%el{t}1*Un~m!cJvC9!OAgr)`=bWp1QcDXynd%_l0WYl%y;b6aN&S9DDX_ zWYqd|c_@` zrI)b6#KI|=u?rh19gVDd`n4uk_W8HCBEhc9zN#Dh_Dm3uHJg;xW%L@eRIFUxw|=Z% zTh~%OxMutFHy-o6IXm-GG102LLG0`H$Y`?Usd*2tf6tuNm|z#ryP}aQS=WUcRK3R# ztT3^7z&WuO&q0RY5eua^%9=C5E}lO|BTFjp4ox`YL(|(?VdC%1k+D(dBcuJXTN~DW zzqi>|Ot6dR!O_Th2VZWuZKD#bFu}9)Xyn-*JBlB=dW0DhSz+Rl`=`aS2VtpJ-&iI4 z-dYcsZN&t;_$nhBxo-J|#oJ#hG`)lsCXOtg729_SGTylM$c2$h+ocff;;W@-5P1*ZHzPSYe{W;Fn_WT#k%iU%#VxefcA1>oUPEzUqrcS{IzOu-B}648aN$2j<6P z>xLntZH-BxHub(X+lmQx@ztyU>y9&TDQ?tsnITwVg0HC)v+zP$b5@x6;_i=Pm9D^2 zz1(PA==Rx}CW8rf;SPe1qVE;whk73~z!0o3aeL*xu^B^=vFoVsL+4#y$?QQU*o8YG znsLeLyBmz$)Y}lOFj1xYVexT8kWuBpp5o!(Ji0*EoC$W}j*e!S1J<_-!3q<&&!ohd zA+3u)9sP<~b5@wR=&EDl9}mV-4PU&v!K95k`Y^#R+%MCNBl6D*{V<`r$zX+vCy%TX z-`o!wc0K(6DJ*A#UAXV38K=!^UhKW!#$>RypqD-(0_b)Z$ z*Vn5zykMQKIV((T$T};2SQlixadF#)lh<5ljwmMBh5N9Y(Q@A}p{uS~Y6w=C_;6mw z_~;JExM*$5?8h!2okFk+_mef_r%sFJJ+@9qQC671U2Y|AS+!{1_wOjd3KO4>=o$Ye zaUSfm{FQkbHiHRv;cmTVeBSWYd41Mu1}jWV8Z#*VNaE~#`_oN}Uw?FvS#u`Xg-;zc zqt^ba^BX-r%Mh$EaZK5j@q13f*8M%ZQRt$qhf)Z3;nNe%=+v`p-n_STJy>C)_`I?4 zAF3jw@bGz|GnX81GMHc&J_XW@mFpfY-Zl5AVmUroVWLUqU^@dMV2zKF9InChliWMgQTycNA z@)yXsr}8t!&pffqWH7-le7dL^9hR5R`)sxD4_261^ZR4*aa)ja(i5i^Z^@l*_6HN} z!l$H~(c_3?=U?-f_A6GH*xc`#`2ID>xck%-L!W+hwaH+DUHG(DGdfqBQCy`|uRmB} zqW@d-;|ofV!J{Y>?82wkn(=p&>7lO=UTl_%6()M0zc{`iac%JVxzEpQWsh?v*fsmR zSL4Ud#ZompP&99nJwBLV7e24orCQ(ZnR)v*=^kW-iB%VQ@jLE8M(&4=LZ$QWFujBc zcHy}LÐ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

@ww;bXR7j+4&as3?!ZVj0&x7Bj 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

d?AGU;%_ zGZ{h0V62cYMlS;nClsSft7STz@JvR~u{~D$Q_;(S!wJQx(yE^hCp?o8bR3Hv%hS=z zfWryJsM79*4ktX55p=u|JFjN+GT?ARF{-o+qr(Z$WCR@tVh4F9dKqvyp%_)#P0}G^ z`@Sy@c_t(1*cChBspw_=^{ZFZ2+^q0u9yxH7?o!-f{wPgub(&`y^OhpXjEx;Plpqp z$p|{Giq6E%(aV6t3B{;Vmx2x_Jd+XSJpOZH#j)tHz~O{qRH@ri&cg`LWCR`Gj80M8 zbA6S=3B{;V*N6@$Jd+W0Ohw1*57F_$c{rgMRqB4x;e=;0f{xpxbGJA;cW^kN7**=x z(cy$=GJ=jh(SckT9Y{EwP>d>dBk6F$GZ{h0hUldBMkf^xClsSfT~#`q@JvR~F&!P@ z{m~JI!wJQxQg@gRCp?o8bUYHB?R%rM4TlqoQKc?79Zq;ABj{Kc9s058(8J+`VpOSH zPlpqp$p|`r9G^5i8lN=4;e=vT=~D+fobXIW&=IpA-iv-XoKP$W`VVvz0zgG5lM%J^ zn8*mwsKN-bBCDN8MR*2|xda{M)qMvc8dZ3uv9hea9~I%5jG&`jCGQ|aqY76HR>*WX z;hBt}qg?Hic&(gJj4E9DSn1Q@gl95>j&j#l4kr|&3U@GeEOa>GnT(*LJbfyM6N*uV zClPjDbU5LejG&`DMJtCBicy6pDt3@`IN_O$prgzNmBR_esKU&EoiH6vcqSw0D6?AS za6&PvFvDR-Plpqp$p|{yI6GGkClsR!Gc`IBbU5LejG&{eGT`WULNTha;y^Eh4ktX5 z5#>C%zN#EfC`J`lPUvNn^Dx3Q89_%`^;Hfh6r&0&K=d-`5D_cOPS0cn9cA@88LQVC zAsSU!$)cA*hX}m7p2-M0$|}4%4<{6(3M+E-GU#x^GZ{fg*-caqClsR!I}7wO=y1X_ z89_(c6;%!=6r&0|B=j=qaKbYgK}Xr$;o08rgkn@-r-xn!9Zq;ABj_l*)XL$6VpL(r zie3gCPIx9G=qS7G%Hf1!RAJ|hUIrabcqSw0z_$%wh=0jEUaRzaBWYdigRZ6r<{v=T9~k-TJOL4<|PC zjMN-nrI@3_Hvu1r@5$`w8Cm8T#W?PkD^50E?z_}YhlpzrUpA~N9LuW|(}C{}o{sM> z?HaqR%QK2`+?#GZ(O7x(&EgR8T+i}hRpD4(rI?P{^4-DD^(^o5jAA&tx?gLY9Gfi; z5rb3fhgF4Rd6i;1@ZG_4@!h5V>GfTnQ4B}N;y*X8duXOOL|hsh7*-XI2@(`-N8;?#bj#rcVjYR2(lhY^b0Fi#Rb0-a z{I+jjJgJqz*!9%*b1I`M#P?%15P^&nitEHv=5PYBj?@|zW}KymTMlIWxQa<=R{Lbk zYRX{jn&JGMI4TiWt{GV-0vRV1*NN?8Er%0`b)@>JZeQDSAmhhXOhUgNcx~;-L1i#@ ztr`5BI4TjxW0fHS87CCii65Ie*SjZg*mPZu5RIw>r#CkHUwpebL~L2p@vLVuf({%X)MGo( z4LPA0RjZ%g)I8PE7QIDWS0eBZdL|?2!0UvwpG$~F)zwRTn`=f~Uh16A$lqLNThgt=b&##|O%O zP(+-0zh@NVNhF$Ai4&g52tMI-9&k9J7*#qaI-KxKM$n=60}dw?qe|}$9Zq;ABj~W} zs^1C4sM0k?hlnj7d}E1cGJ+1>9}DLFt44@MmF^WfMC^X!&X0K}Bk0h5{;9pIYlLW2 z>7J)U#QxF2PS0cn9eQ5fu<1~Z5REE5yXX)RjwPPS2s-rKjb}-X5REE5gXs{l>sNzI zJd+W0X#QAq@w+ubG^#Y0&>>>%!X2HS$p|_$55}{kMuQg|pQ&N`L?V1dqEp{^h3@ IftbX90q9@GAOHXW literal 0 HcmV?d00001 diff --git a/mechanical/FlipperZero-WiFi-Devboard-Pro/CAD/GPS-Expansion-SMA-Enclosure-Top.stl b/mechanical/FlipperZero-WiFi-Devboard-Pro/CAD/GPS-Expansion-SMA-Enclosure-Top.stl new file mode 100644 index 0000000000000000000000000000000000000000..8d8f436dc6c9114ffccf1178194819d530277402 GIT binary patch literal 557484 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 literal 0 HcmV?d00001 From 9690c73c35d734e2cf6c03694547d8e7377f4bc7 Mon Sep 17 00:00:00 2001 From: Just Call Me Koko Date: Thu, 6 Jun 2024 16:19:41 -0400 Subject: [PATCH 02/13] Add new enclosure top for Marauder Mini --- esp32_marauder/configs.h | 6 +++--- .../MarauderMini-Top Solid v2.stl | Bin 0 -> 204584 bytes 2 files changed, 3 insertions(+), 3 deletions(-) create mode 100644 mechanical/Marauder-Mini/ThreadFormingScrews/MarauderMini-Top Solid v2.stl diff --git a/esp32_marauder/configs.h b/esp32_marauder/configs.h index f097218c8..9da578c9f 100644 --- a/esp32_marauder/configs.h +++ b/esp32_marauder/configs.h @@ -8,10 +8,10 @@ //// BOARD TARGETS //#define MARAUDER_M5STICKC - #define MARAUDER_MINI + //#define MARAUDER_MINI //#define MARAUDER_V4 //#define MARAUDER_V6 - //#define MARAUDER_V6_1 + #define MARAUDER_V6_1 //#define MARAUDER_KIT //#define GENERIC_ESP32 //#define MARAUDER_FLIPPER @@ -21,7 +21,7 @@ //#define MARAUDER_REV_FEATHER //// END BOARD TARGETS - #define MARAUDER_VERSION "v0.13.10" + #define MARAUDER_VERSION "v0.13.11" //// HARDWARE NAMES #ifdef MARAUDER_M5STICKC diff --git a/mechanical/Marauder-Mini/ThreadFormingScrews/MarauderMini-Top Solid v2.stl b/mechanical/Marauder-Mini/ThreadFormingScrews/MarauderMini-Top Solid v2.stl new file mode 100644 index 0000000000000000000000000000000000000000..d35a66b881ffb0aadc89313b9bc22fe7b01af3b4 GIT binary patch literal 204584 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 rqA0J^@o5GLDmfvK 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 literal 0 HcmV?d00001 From ccf0c1cbbd9cf7315449d6f7cbfa6ae434fca094 Mon Sep 17 00:00:00 2001 From: Just Call Me Koko Date: Wed, 26 Jun 2024 14:12:24 -0400 Subject: [PATCH 03/13] Remove title screen and add select stations for mini --- esp32_marauder/Assets.h | 8 +- esp32_marauder/Display.cpp | 2 + esp32_marauder/Display.h | 2 +- esp32_marauder/MenuFunctions.cpp | 128 ++++++++++++++++++++++++++++-- esp32_marauder/MenuFunctions.h | 3 + esp32_marauder/WiFiScan.cpp | 8 ++ esp32_marauder/WiFiScan.h | 1 + esp32_marauder/configs.h | 4 +- esp32_marauder/esp32_marauder.ino | 3 + 9 files changed, 148 insertions(+), 11 deletions(-) diff --git a/esp32_marauder/Assets.h b/esp32_marauder/Assets.h index 583638791..c52dd11d7 100644 --- a/esp32_marauder/Assets.h +++ b/esp32_marauder/Assets.h @@ -206,7 +206,7 @@ PROGMEM static const unsigned char menu_icons[][66] = { 0xE7, 0xE7, 0x0B, 0xD0, 0xFD, 0xBF, 0xFF, 0xFF} }; -#ifndef MARAUDER_MINI +/*#ifndef MARAUDER_MINI static const uint8_t MarauderTitle[] PROGMEM = { 0xFF, 0xD8, 0xFF, 0xE0, 0x00, 0x10, 0x4A, 0x46, 0x49, 0x46, 0x00, 0x01, 0x01, 0x01, 0x00, 0x60, 0x00, 0x60, 0x00, 0x00, 0xFF, 0xE1, 0x00, 0x22, 0x45, 0x78, 0x69, 0x66, 0x00, 0x00, 0x4D, 0x4D, @@ -1057,7 +1057,7 @@ PROGMEM static const unsigned char menu_icons[][66] = { 0x28, 0xA2, 0x80, 0x0A, 0x28, 0xA2, 0x80, 0x0A, 0x28, 0xA2, 0x80, 0x0A, 0x28, 0xA2, 0x80, 0x0A, 0x28, 0xA2, 0x80, 0x0A, 0x28, 0xA2, 0x80, 0x0A, 0x28, 0xA2, 0x80, 0x0A, 0x28, 0xA2, 0x80, 0x0A, 0x28, 0xA2, 0x80, 0x0A, 0x28, 0xA2, 0x80, 0x3F, 0xFF, 0xD9}; - /*static const uint8_t MarauderTitle[] PROGMEM = { + *//*static const uint8_t MarauderTitle[] PROGMEM = { 0xff, 0xd8, 0xff, 0xe0, 0x00, 0x10, 0x4a, 0x46, 0x49, 0x46, 0x00, 0x01, 0x01, 0x01, 0x00, 0x60, 0x00, 0x60, 0x00, 0x00, 0xff, 0xe1, 0x00, 0x66, 0x45, 0x78, 0x69, 0x66, 0x00, 0x00, 0x4d, 0x4d, 0x00, 0x2a, 0x00, 0x00, 0x00, 0x08, 0x00, 0x04, 0x01, 0x1a, 0x00, 0x05, 0x00, 0x00, 0x00, 0x01, @@ -1455,8 +1455,9 @@ PROGMEM static const unsigned char menu_icons[][66] = { 0xc4, 0x8b, 0x1c, 0x7b, 0xe4, 0x66, 0x6d, 0xaa, 0xbc, 0x05, 0xce, 0x00, 0xe0, 0x50, 0x07, 0x96, 0xd1, 0x45, 0x14, 0x00, 0x51, 0x45, 0x14, 0x00, 0x51, 0x45, 0x14, 0x01, 0xff, 0xd9 };*/ -#endif +//#endif +/* #ifdef MARAUDER_MINI const uint8_t MarauderTitle[] PROGMEM = { 0xFF, 0xD8, 0xFF, 0xE0, 0x00, 0x10, 0x4A, 0x46, 0x49, 0x46, 0x00, 0x01, 0x01, 0x01, 0x00, 0x60, @@ -1832,5 +1833,6 @@ PROGMEM static const unsigned char menu_icons[][66] = { 0x28, 0x00, 0xA2, 0x80, 0x0A, 0x28, 0x03, 0xFF, 0xD9}; #endif +*/ #endif diff --git a/esp32_marauder/Display.cpp b/esp32_marauder/Display.cpp index f150e5c29..d23776ac3 100644 --- a/esp32_marauder/Display.cpp +++ b/esp32_marauder/Display.cpp @@ -422,6 +422,7 @@ void Display::scrollAddress(uint16_t vsp) { // JPEG_functions +/* void Display::drawJpeg(const char *filename, int xpos, int ypos) { // Open the named file (the Jpeg decoder library will close it after rendering image) @@ -452,6 +453,7 @@ void Display::drawJpeg(const char *filename, int xpos, int ypos) { // Serial.println(F("Jpeg file format not supported!")); //} } +*/ /*void Display::setupDraw() { this->tft.drawLine(0, 0, 10, 0, TFT_MAGENTA); diff --git a/esp32_marauder/Display.h b/esp32_marauder/Display.h index 93976515c..4526c9cb0 100644 --- a/esp32_marauder/Display.h +++ b/esp32_marauder/Display.h @@ -110,7 +110,7 @@ class Display void buildBanner(String msg, int xpos); void clearScreen(); void displayBuffer(bool do_clear = false); - void drawJpeg(const char *filename, int xpos, int ypos); + //void drawJpeg(const char *filename, int xpos, int ypos); void getTouchWhileFunction(bool pressed); void initScrollValues(bool tte = false); void jpegInfo(); diff --git a/esp32_marauder/MenuFunctions.cpp b/esp32_marauder/MenuFunctions.cpp index ae49925ce..ba4b8b91b 100644 --- a/esp32_marauder/MenuFunctions.cpp +++ b/esp32_marauder/MenuFunctions.cpp @@ -1222,6 +1222,7 @@ void MenuFunctions::displaySetting(String key, Menu* menu, int index) { void MenuFunctions::RunSetup() { extern LinkedList* access_points; + extern LinkedList* stations; this->disable_touch = false; @@ -1257,6 +1258,9 @@ void MenuFunctions::RunSetup() wifiAttackMenu.list = new LinkedList(); wifiGeneralMenu.list = new LinkedList(); wifiAPMenu.list = new LinkedList(); + #ifndef HAS_ILI9341 + wifiStationMenu.list = new LinkedList(); + #endif // WiFi HTML menu stuff htmlMenu.list = new LinkedList(); @@ -1312,6 +1316,9 @@ void MenuFunctions::RunSetup() clearSSIDsMenu.name = text_table1[28]; clearAPsMenu.name = text_table1[29]; wifiAPMenu.name = "Access Points"; + #ifndef HAS_ILI9341 + wifiStationMenu.name = "Select Stations"; + #endif #ifdef HAS_GPS gpsInfoMenu.name = "GPS Data"; #endif @@ -1533,12 +1540,14 @@ void MenuFunctions::RunSetup() wifi_scan_obj.StartScan(LV_ADD_SSID, TFT_RED); addAPGFX(); }); + // Select Stations on OG this->addNodes(&wifiGeneralMenu, text_table1[61], TFT_LIGHTGREY, NULL, KEYBOARD_ICO, [this](){ display_obj.clearScreen(); wifi_scan_obj.currentScanMode = LV_ADD_SSID; wifi_scan_obj.StartScan(LV_ADD_SSID, TFT_RED); addStationGFX(); }); + // Select Evil Portal Files on OG this->addNodes(&wifiGeneralMenu, "Select EP HTML File", TFT_CYAN, NULL, KEYBOARD_ICO, [this](){ display_obj.clearScreen(); wifi_scan_obj.currentScanMode = LV_ADD_SSID; @@ -1621,11 +1630,11 @@ void MenuFunctions::RunSetup() current_menu->list->set(i + 1, new_node); // Change selection status of button key - if (new_ap.selected) { - this->buttonSelected(i + 1); - } else { - this->buttonNotSelected(i + 1); - } + //if (new_ap.selected) { + // this->buttonSelected(i + 1); + //} else { + // this->buttonNotSelected(i + 1); + //} access_points->set(i, new_ap); }, access_points->get(i).selected); } @@ -1636,6 +1645,115 @@ void MenuFunctions::RunSetup() this->addNodes(&wifiAPMenu, text09, TFT_LIGHTGREY, NULL, 0, [this]() { this->changeMenu(wifiAPMenu.parentMenu); }); + + // Select Stations on Mini v1 + /* + this->addNodes(&wifiGeneralMenu, "Select Stations", TFT_CYAN, NULL, KEYBOARD_ICO, [this](){ + wifiStationMenu.list->clear(); + this->addNodes(&wifiStationMenu, text09, TFT_LIGHTGREY, NULL, 0, [this]() { + this->changeMenu(wifiStationMenu.parentMenu); + }); + int menu_limit; + + // Find out how many buttons we will need + if (stations->size() <= BUTTON_ARRAY_LEN) + menu_limit = stations->size(); + else + menu_limit = BUTTON_ARRAY_LEN; + + // Load buttons with stations + for (int i = 0; i < stations->size(); i++) { + + // Check if there is even space left + if (current_menu->list->size() >= menu_limit) + break; + + int cur_ap_sta = i; + + this->addNodes(&wifiStationMenu, wifi_scan_obj.macToString(stations->get(cur_ap_sta)), TFT_CYAN, NULL, KEYBOARD_ICO, [this, i, cur_ap_sta](){ + Station new_sta = stations->get(cur_ap_sta); + new_sta.selected = !stations->get(cur_ap_sta).selected; + + // Change selection status of menu node + MenuNode new_node = current_menu->list->get(i + 1); + new_node.selected = !current_menu->list->get(i + 1).selected; + current_menu->list->set(i + 1, new_node); + + // Change selection status of button key + //if (new_sta.selected) { + // this->buttonSelected(i + 1); + //} else { + // this->buttonNotSelected(i + 1); + //} + + stations->set(cur_ap_sta, new_sta); + }, stations->get(cur_ap_sta).selected); + } + this->changeMenu(&wifiStationMenu); + }); + */ + + // Select Stations on Mini v2 + this->addNodes(&wifiGeneralMenu, "Select Stations", TFT_CYAN, NULL, KEYBOARD_ICO, [this](){ + wifiAPMenu.list->clear(); + this->addNodes(&wifiAPMenu, text09, TFT_LIGHTGREY, NULL, 0, [this]() { + this->changeMenu(wifiAPMenu.parentMenu); + }); + + int menu_limit; + + if (access_points->size() <= BUTTON_ARRAY_LEN) + menu_limit = access_points->size(); + else + menu_limit = BUTTON_ARRAY_LEN; + + for (int i = 0; i < menu_limit - 1; i++) { + wifiStationMenu.list->clear(); + this->addNodes(&wifiAPMenu, access_points->get(i).essid, TFT_CYAN, NULL, KEYBOARD_ICO, [this, i](){ + + wifiStationMenu.list->clear(); + + // Add back button to the APs + this->addNodes(&wifiStationMenu, text09, TFT_LIGHTGREY, NULL, 0, [this]() { + this->changeMenu(wifiStationMenu.parentMenu); + }); + + // Add the AP's stations to the specific AP menu + for (int x = 0; x < access_points->get(i).stations->size(); x++) { + int cur_ap_sta = access_points->get(i).stations->get(x); + + this->addNodes(&wifiStationMenu, wifi_scan_obj.macToString(stations->get(cur_ap_sta)), TFT_CYAN, NULL, KEYBOARD_ICO, [this, i, cur_ap_sta, x](){ + Station new_sta = stations->get(cur_ap_sta); + new_sta.selected = !stations->get(cur_ap_sta).selected; + + // Change selection status of menu node + MenuNode new_node = current_menu->list->get(x + 1); + new_node.selected = !current_menu->list->get(x + 1).selected; + current_menu->list->set(x + 1, new_node); + + // Change selection status of button key + //if (new_sta.selected) { + // this->buttonSelected(i + 1); + //} else { + // this->buttonNotSelected(i + 1); + //} + + stations->set(cur_ap_sta, new_sta); + }, stations->get(cur_ap_sta).selected); + } + + // Final change menu to the menu of Stations + this->changeMenu(&wifiStationMenu); + + }, false); + } + this->changeMenu(&wifiAPMenu); + }); + + wifiStationMenu.parentMenu = &wifiAPMenu; + this->addNodes(&wifiStationMenu, text09, TFT_LIGHTGREY, NULL, 0, [this]() { + this->changeMenu(wifiStationMenu.parentMenu); + }); #endif // Build generate ssids menu diff --git a/esp32_marauder/MenuFunctions.h b/esp32_marauder/MenuFunctions.h index 0f225d929..a41a7395b 100644 --- a/esp32_marauder/MenuFunctions.h +++ b/esp32_marauder/MenuFunctions.h @@ -148,6 +148,9 @@ class MenuFunctions Menu wifiAttackMenu; Menu wifiGeneralMenu; Menu wifiAPMenu; + #ifndef HAS_ILI9341 + Menu wifiStationMenu; + #endif // WiFi General Menu Menu htmlMenu; diff --git a/esp32_marauder/WiFiScan.cpp b/esp32_marauder/WiFiScan.cpp index b19320ebb..148ccf73e 100644 --- a/esp32_marauder/WiFiScan.cpp +++ b/esp32_marauder/WiFiScan.cpp @@ -305,6 +305,14 @@ WiFiScan::WiFiScan() { } +String WiFiScan::macToString(const Station& station) { + char macStr[18]; // 6 pairs of hex digits + 5 colons + null terminator + snprintf(macStr, sizeof(macStr), "%02X:%02X:%02X:%02X:%02X:%02X", + station.mac[0], station.mac[1], station.mac[2], + station.mac[3], station.mac[4], station.mac[5]); + return String(macStr); +} + void WiFiScan::RunSetup() { if (ieee80211_raw_frame_sanity_check(31337, 0, 0) == 1) this->wsl_bypass_enabled = true; diff --git a/esp32_marauder/WiFiScan.h b/esp32_marauder/WiFiScan.h index b6145530e..5b5ec6074 100644 --- a/esp32_marauder/WiFiScan.h +++ b/esp32_marauder/WiFiScan.h @@ -400,6 +400,7 @@ class WiFiScan bool save_serial = false; void startPcap(String file_name); void startLog(String file_name); + String macToString(const Station& station); static void getMAC(char *addr, uint8_t* data, uint16_t offset); static void pwnSnifferCallback(void* buf, wifi_promiscuous_pkt_type_t type); diff --git a/esp32_marauder/configs.h b/esp32_marauder/configs.h index 9da578c9f..5fe5a4a6f 100644 --- a/esp32_marauder/configs.h +++ b/esp32_marauder/configs.h @@ -8,10 +8,10 @@ //// BOARD TARGETS //#define MARAUDER_M5STICKC - //#define MARAUDER_MINI + #define MARAUDER_MINI //#define MARAUDER_V4 //#define MARAUDER_V6 - #define MARAUDER_V6_1 + //#define MARAUDER_V6_1 //#define MARAUDER_KIT //#define GENERIC_ESP32 //#define MARAUDER_FLIPPER diff --git a/esp32_marauder/esp32_marauder.ino b/esp32_marauder/esp32_marauder.ino index a974e19e9..a310ac156 100644 --- a/esp32_marauder/esp32_marauder.ino +++ b/esp32_marauder/esp32_marauder.ino @@ -192,6 +192,7 @@ void setup() backlightOff(); // Draw the title screen + /* #ifdef HAS_SCREEN #ifndef MARAUDER_MINI display_obj.drawJpeg("/marauder3L.jpg", 0 , 0); // 240 x 320 image @@ -199,6 +200,7 @@ void setup() display_obj.drawJpeg("/marauder3L.jpg", 0, 0); #endif #endif + */ #ifdef HAS_SCREEN #ifndef MARAUDER_MINI @@ -210,6 +212,7 @@ void setup() #endif #endif + backlightOn(); // Need this #ifdef HAS_SCREEN From e09d4435985c361f9dc5e172f33cd5370e56f343 Mon Sep 17 00:00:00 2001 From: Just Call Me Koko Date: Wed, 26 Jun 2024 15:29:31 -0400 Subject: [PATCH 04/13] Fix some workflow stuff --- .github/workflows/build_push.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build_push.yml b/.github/workflows/build_push.yml index 867149351..6e8867ac4 100644 --- a/.github/workflows/build_push.yml +++ b/.github/workflows/build_push.yml @@ -163,7 +163,7 @@ jobs: - name: Modify platform.txt run: | echo "Chicken" - for i in $(find /home/runner/.arduino15/packages/esp32/hardware/esp32/ -name "platform.txt"); do + for i in $(find /home/runner/ -name "platform.txt"); do sed -i 's/compiler.c.elf.libs.esp32c3=/compiler.c.elf.libs.esp32c3=-zmuldefs /' "$i" sed -i 's/compiler.c.elf.libs.esp32s3=/compiler.c.elf.libs.esp32s3=-zmuldefs /' "$i" sed -i 's/compiler.c.elf.libs.esp32s2=/compiler.c.elf.libs.esp32s2=-zmuldefs /' "$i" From 862a0baf8f93cdddae4ce40c1aef9a5d24bf9f13 Mon Sep 17 00:00:00 2001 From: Just Call Me Koko Date: Wed, 26 Jun 2024 15:35:46 -0400 Subject: [PATCH 05/13] Do some more workflow stuff --- .github/workflows/build_push.yml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build_push.yml b/.github/workflows/build_push.yml index 6e8867ac4..610275486 100644 --- a/.github/workflows/build_push.yml +++ b/.github/workflows/build_push.yml @@ -21,6 +21,10 @@ jobs: sketch-names: TestFile.ino arduino-board-fqbn: esp32:esp32:esp32s2 platform-url: https://github.com/espressif/arduino-esp32/releases/download/2.0.10/package_esp32_dev_index.json + + - name: Show Arduino dir structure + run: | + find /home/runner/ - name: Pull arduino-esp32 v2.0.4 uses: actions/checkout@v2 @@ -162,8 +166,7 @@ jobs: - name: Modify platform.txt run: | - echo "Chicken" - for i in $(find /home/runner/ -name "platform.txt"); do + for i in $(find /home/runner/.arduino15/packages/esp32/hardware/esp32/ -name "platform.txt"); do sed -i 's/compiler.c.elf.libs.esp32c3=/compiler.c.elf.libs.esp32c3=-zmuldefs /' "$i" sed -i 's/compiler.c.elf.libs.esp32s3=/compiler.c.elf.libs.esp32s3=-zmuldefs /' "$i" sed -i 's/compiler.c.elf.libs.esp32s2=/compiler.c.elf.libs.esp32s2=-zmuldefs /' "$i" From eeb8b1f13842c9a045acf08e785fd2eff171dcda Mon Sep 17 00:00:00 2001 From: Just Call Me Koko Date: Wed, 26 Jun 2024 16:15:19 -0400 Subject: [PATCH 06/13] Do some other shit --- .github/workflows/build_push.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build_push.yml b/.github/workflows/build_push.yml index 610275486..73e1618e6 100644 --- a/.github/workflows/build_push.yml +++ b/.github/workflows/build_push.yml @@ -24,7 +24,7 @@ jobs: - name: Show Arduino dir structure run: | - find /home/runner/ + find /home/runner/.arduino15/packages/esp32/ - name: Pull arduino-esp32 v2.0.4 uses: actions/checkout@v2 From 5c494724b489336073be10607b8e889cc5b7b568 Mon Sep 17 00:00:00 2001 From: Just Call Me Koko Date: Wed, 26 Jun 2024 16:19:55 -0400 Subject: [PATCH 07/13] Some other bullshit that github or espressif switched around idk --- .github/workflows/build_push.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build_push.yml b/.github/workflows/build_push.yml index 73e1618e6..d9d6fdd35 100644 --- a/.github/workflows/build_push.yml +++ b/.github/workflows/build_push.yml @@ -24,7 +24,7 @@ jobs: - name: Show Arduino dir structure run: | - find /home/runner/.arduino15/packages/esp32/ + find /home/runner/.arduino15/packages/esp32/hardware/ - name: Pull arduino-esp32 v2.0.4 uses: actions/checkout@v2 From 5cd0ffb2b98959456b6cb1603b6b966748b451b9 Mon Sep 17 00:00:00 2001 From: Just Call Me Koko Date: Wed, 26 Jun 2024 16:38:47 -0400 Subject: [PATCH 08/13] Do a fancy search --- .github/workflows/build_push.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build_push.yml b/.github/workflows/build_push.yml index d9d6fdd35..0dac63e05 100644 --- a/.github/workflows/build_push.yml +++ b/.github/workflows/build_push.yml @@ -24,7 +24,7 @@ jobs: - name: Show Arduino dir structure run: | - find /home/runner/.arduino15/packages/esp32/hardware/ + find /home/runner/.arduino15/packages/esp32/ -type f -exec grep -l "-lefuse" {} + - name: Pull arduino-esp32 v2.0.4 uses: actions/checkout@v2 From 8f5fce387725329019d20bd494f6f08c9edea1a0 Mon Sep 17 00:00:00 2001 From: Just Call Me Koko Date: Wed, 26 Jun 2024 17:02:31 -0400 Subject: [PATCH 09/13] This...might work? --- .github/workflows/build_push.yml | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/.github/workflows/build_push.yml b/.github/workflows/build_push.yml index 0dac63e05..a966a5f61 100644 --- a/.github/workflows/build_push.yml +++ b/.github/workflows/build_push.yml @@ -24,7 +24,7 @@ jobs: - name: Show Arduino dir structure run: | - find /home/runner/.arduino15/packages/esp32/ -type f -exec grep -l "-lefuse" {} + + find /home/runner/.arduino15/packages/esp32/hardware/ - name: Pull arduino-esp32 v2.0.4 uses: actions/checkout@v2 @@ -166,15 +166,9 @@ jobs: - name: Modify platform.txt run: | - for i in $(find /home/runner/.arduino15/packages/esp32/hardware/esp32/ -name "platform.txt"); do - sed -i 's/compiler.c.elf.libs.esp32c3=/compiler.c.elf.libs.esp32c3=-zmuldefs /' "$i" - sed -i 's/compiler.c.elf.libs.esp32s3=/compiler.c.elf.libs.esp32s3=-zmuldefs /' "$i" - sed -i 's/compiler.c.elf.libs.esp32s2=/compiler.c.elf.libs.esp32s2=-zmuldefs /' "$i" - sed -i 's/compiler.c.elf.libs.esp32=/compiler.c.elf.libs.esp32=-zmuldefs /' "$i" - cat "$i" | grep compiler.c.elf.libs.esp32c3 - cat "$i" | grep compiler.c.elf.libs.esp32s3 - cat "$i" | grep compiler.c.elf.libs.esp32s2 - cat "$i" | grep compiler.c.elf.libs.esp32 + for i in $(find /home/runner/.arduino15/packages/esp32/ -name "ld_libs"); do + sed -i 's/-lxtensa/-lxtensa -zmuldefs /' "$i" + cat "$i" done - name: Configure configs.h for Flipper Zero WiFi Dev Board From 1f10c73c5e02245a50d22e87c8001d4e5c91b5a4 Mon Sep 17 00:00:00 2001 From: Just Call Me Koko Date: Wed, 26 Jun 2024 18:00:03 -0400 Subject: [PATCH 10/13] Add fixed ESPAsyncWebServer update NimBLE --- .github/workflows/build_push.yml | 4 +- esp32_marauder/WiFiScan.cpp | 1 + libraries/ESPAsyncWebServer/LICENSE | 165 ++ libraries/ESPAsyncWebServer/README.md | 87 ++ libraries/ESPAsyncWebServer/docs/_config.yml | 8 + libraries/ESPAsyncWebServer/docs/index.md | 87 ++ .../examples/CaptivePortal/CaptivePortal.ino | 54 + .../examples/Filters/Filters.ino | 108 ++ .../examples/SimpleServer/SimpleServer.ino | 74 + .../examples/StreamFiles/StreamConcat.h | 33 + .../examples/StreamFiles/StreamFiles.ino | 73 + .../examples/StreamFiles/StreamString.h | 36 + libraries/ESPAsyncWebServer/library.json | 54 + .../ESPAsyncWebServer/library.properties | 10 + libraries/ESPAsyncWebServer/platformio.ini | 44 + .../src/AsyncEventSource.cpp | 412 +++++ .../ESPAsyncWebServer/src/AsyncEventSource.h | 147 ++ libraries/ESPAsyncWebServer/src/AsyncJson.h | 282 ++++ .../ESPAsyncWebServer/src/AsyncWebSocket.cpp | 1353 +++++++++++++++++ .../ESPAsyncWebServer/src/AsyncWebSocket.h | 354 +++++ .../src/AsyncWebSynchronization.h | 134 ++ .../ESPAsyncWebServer/src/ESPAsyncWebServer.h | 501 ++++++ .../src/ESP_Async_WebServer.h | 2 + libraries/ESPAsyncWebServer/src/StringArray.h | 174 +++ .../src/WebAuthentication.cpp | 247 +++ .../ESPAsyncWebServer/src/WebAuthentication.h | 34 + .../ESPAsyncWebServer/src/WebHandlerImpl.h | 151 ++ .../ESPAsyncWebServer/src/WebHandlers.cpp | 233 +++ .../ESPAsyncWebServer/src/WebRequest.cpp | 993 ++++++++++++ .../ESPAsyncWebServer/src/WebResponseImpl.h | 138 ++ .../ESPAsyncWebServer/src/WebResponses.cpp | 704 +++++++++ libraries/ESPAsyncWebServer/src/WebServer.cpp | 198 +++ 32 files changed, 6893 insertions(+), 2 deletions(-) create mode 100644 libraries/ESPAsyncWebServer/LICENSE create mode 100644 libraries/ESPAsyncWebServer/README.md create mode 100644 libraries/ESPAsyncWebServer/docs/_config.yml create mode 100644 libraries/ESPAsyncWebServer/docs/index.md create mode 100644 libraries/ESPAsyncWebServer/examples/CaptivePortal/CaptivePortal.ino create mode 100644 libraries/ESPAsyncWebServer/examples/Filters/Filters.ino create mode 100644 libraries/ESPAsyncWebServer/examples/SimpleServer/SimpleServer.ino create mode 100644 libraries/ESPAsyncWebServer/examples/StreamFiles/StreamConcat.h create mode 100644 libraries/ESPAsyncWebServer/examples/StreamFiles/StreamFiles.ino create mode 100644 libraries/ESPAsyncWebServer/examples/StreamFiles/StreamString.h create mode 100644 libraries/ESPAsyncWebServer/library.json create mode 100644 libraries/ESPAsyncWebServer/library.properties create mode 100644 libraries/ESPAsyncWebServer/platformio.ini create mode 100644 libraries/ESPAsyncWebServer/src/AsyncEventSource.cpp create mode 100644 libraries/ESPAsyncWebServer/src/AsyncEventSource.h create mode 100644 libraries/ESPAsyncWebServer/src/AsyncJson.h create mode 100644 libraries/ESPAsyncWebServer/src/AsyncWebSocket.cpp create mode 100644 libraries/ESPAsyncWebServer/src/AsyncWebSocket.h create mode 100644 libraries/ESPAsyncWebServer/src/AsyncWebSynchronization.h create mode 100644 libraries/ESPAsyncWebServer/src/ESPAsyncWebServer.h create mode 100644 libraries/ESPAsyncWebServer/src/ESP_Async_WebServer.h create mode 100644 libraries/ESPAsyncWebServer/src/StringArray.h create mode 100644 libraries/ESPAsyncWebServer/src/WebAuthentication.cpp create mode 100644 libraries/ESPAsyncWebServer/src/WebAuthentication.h create mode 100644 libraries/ESPAsyncWebServer/src/WebHandlerImpl.h create mode 100644 libraries/ESPAsyncWebServer/src/WebHandlers.cpp create mode 100644 libraries/ESPAsyncWebServer/src/WebRequest.cpp create mode 100644 libraries/ESPAsyncWebServer/src/WebResponseImpl.h create mode 100644 libraries/ESPAsyncWebServer/src/WebResponses.cpp create mode 100644 libraries/ESPAsyncWebServer/src/WebServer.cpp diff --git a/.github/workflows/build_push.yml b/.github/workflows/build_push.yml index a966a5f61..5c359edf8 100644 --- a/.github/workflows/build_push.yml +++ b/.github/workflows/build_push.yml @@ -86,7 +86,7 @@ jobs: uses: actions/checkout@v2 with: repository: h2zero/NimBLE-Arduino - ref: 1.2.0 + ref: 1.4.2 path: CustomNimBLE-Arduino - name: Install Adafruit_NeoPixel @@ -167,7 +167,7 @@ jobs: - name: Modify platform.txt run: | for i in $(find /home/runner/.arduino15/packages/esp32/ -name "ld_libs"); do - sed -i 's/-lxtensa/-lxtensa -zmuldefs /' "$i" + sed -i 's/-lxtensa /-lxtensa -zmuldefs /' "$i" cat "$i" done diff --git a/esp32_marauder/WiFiScan.cpp b/esp32_marauder/WiFiScan.cpp index 148ccf73e..0d5253d37 100644 --- a/esp32_marauder/WiFiScan.cpp +++ b/esp32_marauder/WiFiScan.cpp @@ -19,6 +19,7 @@ extern "C" int ieee80211_raw_frame_sanity_check(int32_t arg, int32_t arg2, int32 extern "C" { uint8_t esp_base_mac_addr[6]; + esp_err_t esp_base_mac_addr_set(const uint8_t *addr); esp_err_t esp_ble_gap_set_rand_addr(const uint8_t *rand_addr); } diff --git a/libraries/ESPAsyncWebServer/LICENSE b/libraries/ESPAsyncWebServer/LICENSE new file mode 100644 index 000000000..153d416dc --- /dev/null +++ b/libraries/ESPAsyncWebServer/LICENSE @@ -0,0 +1,165 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + + This version of the GNU Lesser General Public License incorporates +the terms and conditions of version 3 of the GNU General Public +License, supplemented by the additional permissions listed below. + + 0. Additional Definitions. + + As used herein, "this License" refers to version 3 of the GNU Lesser +General Public License, and the "GNU GPL" refers to version 3 of the GNU +General Public License. + + "The Library" refers to a covered work governed by this License, +other than an Application or a Combined Work as defined below. + + An "Application" is any work that makes use of an interface provided +by the Library, but which is not otherwise based on the Library. +Defining a subclass of a class defined by the Library is deemed a mode +of using an interface provided by the Library. + + A "Combined Work" is a work produced by combining or linking an +Application with the Library. The particular version of the Library +with which the Combined Work was made is also called the "Linked +Version". + + The "Minimal Corresponding Source" for a Combined Work means the +Corresponding Source for the Combined Work, excluding any source code +for portions of the Combined Work that, considered in isolation, are +based on the Application, and not on the Linked Version. + + The "Corresponding Application Code" for a Combined Work means the +object code and/or source code for the Application, including any data +and utility programs needed for reproducing the Combined Work from the +Application, but excluding the System Libraries of the Combined Work. + + 1. Exception to Section 3 of the GNU GPL. + + You may convey a covered work under sections 3 and 4 of this License +without being bound by section 3 of the GNU GPL. + + 2. Conveying Modified Versions. + + If you modify a copy of the Library, and, in your modifications, a +facility refers to a function or data to be supplied by an Application +that uses the facility (other than as an argument passed when the +facility is invoked), then you may convey a copy of the modified +version: + + a) under this License, provided that you make a good faith effort to + ensure that, in the event an Application does not supply the + function or data, the facility still operates, and performs + whatever part of its purpose remains meaningful, or + + b) under the GNU GPL, with none of the additional permissions of + this License applicable to that copy. + + 3. Object Code Incorporating Material from Library Header Files. + + The object code form of an Application may incorporate material from +a header file that is part of the Library. You may convey such object +code under terms of your choice, provided that, if the incorporated +material is not limited to numerical parameters, data structure +layouts and accessors, or small macros, inline functions and templates +(ten or fewer lines in length), you do both of the following: + + a) Give prominent notice with each copy of the object code that the + Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the object code with a copy of the GNU GPL and this license + document. + + 4. Combined Works. + + You may convey a Combined Work under terms of your choice that, +taken together, effectively do not restrict modification of the +portions of the Library contained in the Combined Work and reverse +engineering for debugging such modifications, if you also do each of +the following: + + a) Give prominent notice with each copy of the Combined Work that + the Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the Combined Work with a copy of the GNU GPL and this license + document. + + c) For a Combined Work that displays copyright notices during + execution, include the copyright notice for the Library among + these notices, as well as a reference directing the user to the + copies of the GNU GPL and this license document. + + d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of this + License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (a) uses at run time + a copy of the Library already present on the user's computer + system, and (b) will operate properly with a modified version + of the Library that is interface-compatible with the Linked + Version. + + e) Provide Installation Information, but only if you would otherwise + be required to provide such information under section 6 of the + GNU GPL, and only to the extent that such information is + necessary to install and execute a modified version of the + Combined Work produced by recombining or relinking the + Application with a modified version of the Linked Version. (If + you use option 4d0, the Installation Information must accompany + the Minimal Corresponding Source and Corresponding Application + Code. If you use option 4d1, you must provide the Installation + Information in the manner specified by section 6 of the GNU GPL + for conveying Corresponding Source.) + + 5. Combined Libraries. + + You may place library facilities that are a work based on the +Library side by side in a single library together with other library +facilities that are not Applications and are not covered by this +License, and convey such a combined library under terms of your +choice, if you do both of the following: + + a) Accompany the combined library with a copy of the same work based + on the Library, uncombined with any other library facilities, + conveyed under the terms of this License. + + b) Give prominent notice with the combined library that part of it + is a work based on the Library, and explaining where to find the + accompanying uncombined form of the same work. + + 6. Revised Versions of the GNU Lesser General Public License. + + The Free Software Foundation may publish revised and/or new versions +of the GNU Lesser General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. + + Each version is given a distinguishing version number. If the +Library as you received it specifies that a certain numbered version +of the GNU Lesser General Public License "or any later version" +applies to it, you have the option of following the terms and +conditions either of that published version or of any later version +published by the Free Software Foundation. If the Library as you +received it does not specify a version number of the GNU Lesser +General Public License, you may choose any version of the GNU Lesser +General Public License ever published by the Free Software Foundation. + + If the Library as you received it specifies that a proxy can decide +whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is +permanent authorization for you to choose that version for the +Library. \ No newline at end of file diff --git a/libraries/ESPAsyncWebServer/README.md b/libraries/ESPAsyncWebServer/README.md new file mode 100644 index 000000000..b10865c7d --- /dev/null +++ b/libraries/ESPAsyncWebServer/README.md @@ -0,0 +1,87 @@ +# ESP Async WebServer + +[![License: LGPL 3.0](https://img.shields.io/badge/License-LGPL%203.0-yellow.svg)](https://opensource.org/license/lgpl-3-0/) +[![Continuous Integration](https://github.com/mathieucarbou/ESPAsyncWebServer/actions/workflows/ci.yml/badge.svg)](https://github.com/mathieucarbou/ESPAsyncWebServer/actions/workflows/ci.yml) +[![PlatformIO Registry](https://badges.registry.platformio.org/packages/mathieucarbou/library/ESP%20Async%20WebServer.svg)](https://registry.platformio.org/libraries/mathieucarbou/ESP%20Async%20WebServer) + +Asynchronous HTTP and WebSocket Server Library for ESP32. +Supports: WebSocket, SSE, Authentication, Arduino Json 7, File Upload, Static File serving, URL Rewrite, URL Redirect, etc. + +This fork is based on [yubox-node-org/ESPAsyncWebServer](https://github.com/yubox-node-org/ESPAsyncWebServer) and includes all the concurrency fixes. + +## Changes in this fork + +- Removed SPIFFSEditor +- Deployed in PlatformIO registry and Arduino IDE library manager +- CI +- Some code cleanup +- `SSE_MAX_QUEUED_MESSAGES` to control the maximum number of messages that can be queued for a SSE client +- `write()` function public in `AsyncEventSource.h` +- Arduino Json 7 compatibility and backward compatible with 6 and 6 (changes in `AsyncJson.h`). The API to use Json has not changed. These are only internal changes. +- `WS_MAX_QUEUED_MESSAGES`: control the maximum number of messages that can be queued for a Websocket client +- Resurrected `AsyncWebSocketMessageBuffer` and `makeBuffer()` in order to make the fork API-compatible with the original library from me-no-dev regarding WebSocket. +- [#5](https://github.com/mathieucarbou/ESPAsyncWebServer/pull/5) ([@vortigont](https://github.com/vortigont)): set real "Last-Modified" header based on file's LastWrite time +- [#13](https://github.com/mathieucarbou/ESPAsyncWebServer/pull/13) ([@tueddy](https://github.com/tueddy)): Compile with Arduino 3 (ESP-IDF 5.1) +- [#14](https://github.com/mathieucarbou/ESPAsyncWebServer/pull/14) ([@nilo85](https://github.com/nilo85)): Add support for Auth & GET requests in AsyncCallbackJsonWebHandler +- Added `setAuthentication(const String& username, const String& password)` +- Added `StreamConcat` example to shoiw how to stream multiple files in one response +- Remove filename after inline in Content-Disposition header according to RFC2183 +- Depends on `mathieucarbou/Async TCP @ ^3.1.4` +- Arduino 3 / ESP-IDF 5.1 compatibility +- Added all flavors of `binary()`, `text()`, `binaryAll()` and `textAll()` in `AsyncWebSocket` +- Added `setCloseClientOnQueueFull(bool)` which can be set on a client to either close the connection or discard messages but not close the connection when the queue is full + +## Documentation + +Usage and API stays the same as the original library. +Please look at the original libraries for more examples and documentation. + +[https://github.com/yubox-node-org/ESPAsyncWebServer](https://github.com/yubox-node-org/ESPAsyncWebServer) + +## `AsyncWebSocketMessageBuffer` and `makeBuffer()` + +The fork from `yubox-node-org` introduces some breaking API changes compared to the original library, especially regarding the use of `std::shared_ptr>` for WebSocket. + +This fork is compatible with the original library from `me-no-dev` regarding WebSocket, and wraps the optimizations done by `yubox-node-org` in the `AsyncWebSocketMessageBuffer` class. +So you have the choice of which API to use. +I strongly suggest to use the optimized API from `yubox-node-org` as it is much more efficient. + +Here is an example for serializing a Json document in a websocket message buffer. This code is compatible with any forks, but not optimized: + +```cpp +void send(JsonDocument& doc) { + const size_t len = measureJson(doc); + + // original API from me-no-dev + AsyncWebSocketMessageBuffer* buffer = _ws->makeBuffer(len); + assert(buffer); // up to you to keep or remove this + serializeJson(doc, buffer->get(), len); + _ws->textAll(buffer); +} +``` + +Here is an example for serializing a Json document in a more optimized way, and compatible with both forks: + +```cpp +void send(JsonDocument& doc) { + const size_t len = measureJson(doc); + +#if defined(ASYNCWEBSERVER_FORK_mathieucarbou) + + // this fork (originally from yubox-node-org), uses another API with shared pointer that better support concurrent use cases then the original project + auto buffer = std::make_shared>(len); + assert(buffer); // up to you to keep or remove this + serializeJson(doc, buffer->data(), len); + _ws->textAll(std::move(buffer)); + +#else + + // original API from me-no-dev + AsyncWebSocketMessageBuffer* buffer = _ws->makeBuffer(len); + assert(buffer); // up to you to keep or remove this + serializeJson(doc, buffer->get(), len); + _ws->textAll(buffer); + +#endif +} +``` diff --git a/libraries/ESPAsyncWebServer/docs/_config.yml b/libraries/ESPAsyncWebServer/docs/_config.yml new file mode 100644 index 000000000..532070ba6 --- /dev/null +++ b/libraries/ESPAsyncWebServer/docs/_config.yml @@ -0,0 +1,8 @@ +# bundle exec jekyll serve --host=0.0.0.0 + +title: ESP Async WebServer +description: "Asynchronous HTTP and WebSocket Server Library for ESP32" +remote_theme: pages-themes/cayman@v0.2.0 +plugins: + - jekyll-remote-theme + \ No newline at end of file diff --git a/libraries/ESPAsyncWebServer/docs/index.md b/libraries/ESPAsyncWebServer/docs/index.md new file mode 100644 index 000000000..b10865c7d --- /dev/null +++ b/libraries/ESPAsyncWebServer/docs/index.md @@ -0,0 +1,87 @@ +# ESP Async WebServer + +[![License: LGPL 3.0](https://img.shields.io/badge/License-LGPL%203.0-yellow.svg)](https://opensource.org/license/lgpl-3-0/) +[![Continuous Integration](https://github.com/mathieucarbou/ESPAsyncWebServer/actions/workflows/ci.yml/badge.svg)](https://github.com/mathieucarbou/ESPAsyncWebServer/actions/workflows/ci.yml) +[![PlatformIO Registry](https://badges.registry.platformio.org/packages/mathieucarbou/library/ESP%20Async%20WebServer.svg)](https://registry.platformio.org/libraries/mathieucarbou/ESP%20Async%20WebServer) + +Asynchronous HTTP and WebSocket Server Library for ESP32. +Supports: WebSocket, SSE, Authentication, Arduino Json 7, File Upload, Static File serving, URL Rewrite, URL Redirect, etc. + +This fork is based on [yubox-node-org/ESPAsyncWebServer](https://github.com/yubox-node-org/ESPAsyncWebServer) and includes all the concurrency fixes. + +## Changes in this fork + +- Removed SPIFFSEditor +- Deployed in PlatformIO registry and Arduino IDE library manager +- CI +- Some code cleanup +- `SSE_MAX_QUEUED_MESSAGES` to control the maximum number of messages that can be queued for a SSE client +- `write()` function public in `AsyncEventSource.h` +- Arduino Json 7 compatibility and backward compatible with 6 and 6 (changes in `AsyncJson.h`). The API to use Json has not changed. These are only internal changes. +- `WS_MAX_QUEUED_MESSAGES`: control the maximum number of messages that can be queued for a Websocket client +- Resurrected `AsyncWebSocketMessageBuffer` and `makeBuffer()` in order to make the fork API-compatible with the original library from me-no-dev regarding WebSocket. +- [#5](https://github.com/mathieucarbou/ESPAsyncWebServer/pull/5) ([@vortigont](https://github.com/vortigont)): set real "Last-Modified" header based on file's LastWrite time +- [#13](https://github.com/mathieucarbou/ESPAsyncWebServer/pull/13) ([@tueddy](https://github.com/tueddy)): Compile with Arduino 3 (ESP-IDF 5.1) +- [#14](https://github.com/mathieucarbou/ESPAsyncWebServer/pull/14) ([@nilo85](https://github.com/nilo85)): Add support for Auth & GET requests in AsyncCallbackJsonWebHandler +- Added `setAuthentication(const String& username, const String& password)` +- Added `StreamConcat` example to shoiw how to stream multiple files in one response +- Remove filename after inline in Content-Disposition header according to RFC2183 +- Depends on `mathieucarbou/Async TCP @ ^3.1.4` +- Arduino 3 / ESP-IDF 5.1 compatibility +- Added all flavors of `binary()`, `text()`, `binaryAll()` and `textAll()` in `AsyncWebSocket` +- Added `setCloseClientOnQueueFull(bool)` which can be set on a client to either close the connection or discard messages but not close the connection when the queue is full + +## Documentation + +Usage and API stays the same as the original library. +Please look at the original libraries for more examples and documentation. + +[https://github.com/yubox-node-org/ESPAsyncWebServer](https://github.com/yubox-node-org/ESPAsyncWebServer) + +## `AsyncWebSocketMessageBuffer` and `makeBuffer()` + +The fork from `yubox-node-org` introduces some breaking API changes compared to the original library, especially regarding the use of `std::shared_ptr>` for WebSocket. + +This fork is compatible with the original library from `me-no-dev` regarding WebSocket, and wraps the optimizations done by `yubox-node-org` in the `AsyncWebSocketMessageBuffer` class. +So you have the choice of which API to use. +I strongly suggest to use the optimized API from `yubox-node-org` as it is much more efficient. + +Here is an example for serializing a Json document in a websocket message buffer. This code is compatible with any forks, but not optimized: + +```cpp +void send(JsonDocument& doc) { + const size_t len = measureJson(doc); + + // original API from me-no-dev + AsyncWebSocketMessageBuffer* buffer = _ws->makeBuffer(len); + assert(buffer); // up to you to keep or remove this + serializeJson(doc, buffer->get(), len); + _ws->textAll(buffer); +} +``` + +Here is an example for serializing a Json document in a more optimized way, and compatible with both forks: + +```cpp +void send(JsonDocument& doc) { + const size_t len = measureJson(doc); + +#if defined(ASYNCWEBSERVER_FORK_mathieucarbou) + + // this fork (originally from yubox-node-org), uses another API with shared pointer that better support concurrent use cases then the original project + auto buffer = std::make_shared>(len); + assert(buffer); // up to you to keep or remove this + serializeJson(doc, buffer->data(), len); + _ws->textAll(std::move(buffer)); + +#else + + // original API from me-no-dev + AsyncWebSocketMessageBuffer* buffer = _ws->makeBuffer(len); + assert(buffer); // up to you to keep or remove this + serializeJson(doc, buffer->get(), len); + _ws->textAll(buffer); + +#endif +} +``` diff --git a/libraries/ESPAsyncWebServer/examples/CaptivePortal/CaptivePortal.ino b/libraries/ESPAsyncWebServer/examples/CaptivePortal/CaptivePortal.ino new file mode 100644 index 000000000..c06dccd83 --- /dev/null +++ b/libraries/ESPAsyncWebServer/examples/CaptivePortal/CaptivePortal.ino @@ -0,0 +1,54 @@ +#include +#ifdef ESP32 +#include +#include +#elif defined(ESP8266) +#include +#include +#endif +#include "ESPAsyncWebServer.h" + +DNSServer dnsServer; +AsyncWebServer server(80); + +class CaptiveRequestHandler : public AsyncWebHandler { +public: + CaptiveRequestHandler() {} + virtual ~CaptiveRequestHandler() {} + + bool canHandle(__unused AsyncWebServerRequest *request){ + //request->addInterestingHeader("ANY"); + return true; + } + + void handleRequest(AsyncWebServerRequest *request) { + AsyncResponseStream *response = request->beginResponseStream("text/html"); + response->print("Captive Portal"); + response->print("

This is out captive portal front page.

"); + response->printf("

You were trying to reach: http://%s%s

", request->host().c_str(), request->url().c_str()); + response->printf("
", WiFi.softAPIP().toString().c_str()); + response->print(""); + request->send(response); + } +}; + + +void setup(){ + Serial.begin(115200); + Serial.println(); + Serial.println("Configuring access point..."); + + if (!WiFi.softAP("esp-captive")) { + Serial.println("Soft AP creation failed."); + while (1); + } + + dnsServer.start(53, "*", WiFi.softAPIP()); + server.addHandler(new CaptiveRequestHandler()).setFilter(ON_AP_FILTER);//only when requested from AP + //more handlers... + server.begin(); +} + +void loop(){ + dnsServer.processNextRequest(); +} diff --git a/libraries/ESPAsyncWebServer/examples/Filters/Filters.ino b/libraries/ESPAsyncWebServer/examples/Filters/Filters.ino new file mode 100644 index 000000000..90b6f19a0 --- /dev/null +++ b/libraries/ESPAsyncWebServer/examples/Filters/Filters.ino @@ -0,0 +1,108 @@ +// Reproduced issue https://github.com/mathieucarbou/ESPAsyncWebServer/issues/26 + +#include +#ifdef ESP32 +#include +#include +#elif defined(ESP8266) +#include +#include +#endif +#include "ESPAsyncWebServer.h" + +DNSServer dnsServer; +AsyncWebServer server(80); + +class CaptiveRequestHandler : public AsyncWebHandler { + public: + CaptiveRequestHandler() {} + virtual ~CaptiveRequestHandler() {} + + bool canHandle(__unused AsyncWebServerRequest* request) { + // request->addInterestingHeader("ANY"); + return true; + } + + void handleRequest(AsyncWebServerRequest* request) { + AsyncResponseStream* response = request->beginResponseStream("text/html"); + response->print("Captive Portal"); + response->print("

This is out captive portal front page.

"); + response->printf("

You were trying to reach: http://%s%s

", request->host().c_str(), request->url().c_str()); + response->printf("

Try opening this link instead

", WiFi.softAPIP().toString().c_str()); + response->print(""); + request->send(response); + } +}; + +bool hit1 = false; +bool hit2 = false; + +void setup() { + Serial.begin(115200); + + server + .on("/", HTTP_GET, [](AsyncWebServerRequest* request) { + Serial.println("Captive portal request..."); + Serial.println("WiFi.localIP(): " + WiFi.localIP().toString()); + Serial.println("request->client()->localIP(): " + request->client()->localIP().toString()); +#if ESP_IDF_VERSION_MAJOR >= 5 + Serial.println("WiFi.type(): " + String((int)WiFi.localIP().type())); + Serial.println("request->client()->type(): " + String((int)request->client()->localIP().type())); +#endif + Serial.println(WiFi.localIP() == request->client()->localIP() ? "should be: ON_STA_FILTER" : "should be: ON_AP_FILTER"); + Serial.println(WiFi.localIP() == request->client()->localIP()); + Serial.println(WiFi.localIP().toString() == request->client()->localIP().toString()); + request->send(200, "text/plain", "This is the captive portal"); + hit1 = true; + }) + .setFilter(ON_AP_FILTER); + + server + .on("/", HTTP_GET, [](AsyncWebServerRequest* request) { + Serial.println("Website request..."); + Serial.println("WiFi.localIP(): " + WiFi.localIP().toString()); + Serial.println("request->client()->localIP(): " + request->client()->localIP().toString()); +#if ESP_IDF_VERSION_MAJOR >= 5 + Serial.println("WiFi.type(): " + String((int)WiFi.localIP().type())); + Serial.println("request->client()->type(): " + String((int)request->client()->localIP().type())); +#endif + Serial.println(WiFi.localIP() == request->client()->localIP() ? "should be: ON_STA_FILTER" : "should be: ON_AP_FILTER"); + Serial.println(WiFi.localIP() == request->client()->localIP()); + Serial.println(WiFi.localIP().toString() == request->client()->localIP().toString()); + request->send(200, "text/plain", "This is the website"); + hit2 = true; + }) + .setFilter(ON_STA_FILTER); + + // assert(WiFi.softAP("esp-captive-portal")); + // dnsServer.start(53, "*", WiFi.softAPIP()); + // server.begin(); + // Serial.println("Captive portal started!"); + + // while (!hit1) { + // dnsServer.processNextRequest(); + // yield(); + // } + // delay(1000); // Wait for the client to process the response + + // Serial.println("Captive portal opened, stopping it and connecting to WiFi..."); + // dnsServer.stop(); + // WiFi.softAPdisconnect(); + + WiFi.persistent(false); + WiFi.begin("IoT"); + while (WiFi.status() != WL_CONNECTED) { + delay(500); + } + Serial.println("Connected to WiFi with IP address: " + WiFi.localIP().toString()); + server.begin(); + + // while (!hit2) { + // delay(10); + // } + // delay(1000); // Wait for the client to process the response + // ESP.restart(); +} + +void loop() { +} diff --git a/libraries/ESPAsyncWebServer/examples/SimpleServer/SimpleServer.ino b/libraries/ESPAsyncWebServer/examples/SimpleServer/SimpleServer.ino new file mode 100644 index 000000000..bdbcf60dc --- /dev/null +++ b/libraries/ESPAsyncWebServer/examples/SimpleServer/SimpleServer.ino @@ -0,0 +1,74 @@ +// +// A simple server implementation showing how to: +// * serve static messages +// * read GET and POST parameters +// * handle missing pages / 404s +// + +#include +#ifdef ESP32 +#include +#include +#elif defined(ESP8266) +#include +#include +#endif +#include + +AsyncWebServer server(80); + +const char* ssid = "YOUR_SSID"; +const char* password = "YOUR_PASSWORD"; + +const char* PARAM_MESSAGE = "message"; + +void notFound(AsyncWebServerRequest *request) { + request->send(404, "text/plain", "Not found"); +} + +void setup() { + + Serial.begin(115200); + WiFi.mode(WIFI_STA); + WiFi.begin(ssid, password); + if (WiFi.waitForConnectResult() != WL_CONNECTED) { + Serial.printf("WiFi Failed!\n"); + return; + } + + Serial.print("IP Address: "); + Serial.println(WiFi.localIP()); + + server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){ + request->send(200, "text/plain", "Hello, world"); + }); + + // Send a GET request to /get?message= + server.on("/get", HTTP_GET, [] (AsyncWebServerRequest *request) { + String message; + if (request->hasParam(PARAM_MESSAGE)) { + message = request->getParam(PARAM_MESSAGE)->value(); + } else { + message = "No message sent"; + } + request->send(200, "text/plain", "Hello, GET: " + message); + }); + + // Send a POST request to /post with a form field message set to + server.on("/post", HTTP_POST, [](AsyncWebServerRequest *request){ + String message; + if (request->hasParam(PARAM_MESSAGE, true)) { + message = request->getParam(PARAM_MESSAGE, true)->value(); + } else { + message = "No message sent"; + } + request->send(200, "text/plain", "Hello, POST: " + message); + }); + + server.onNotFound(notFound); + + server.begin(); +} + +void loop() { +} \ No newline at end of file diff --git a/libraries/ESPAsyncWebServer/examples/StreamFiles/StreamConcat.h b/libraries/ESPAsyncWebServer/examples/StreamFiles/StreamConcat.h new file mode 100644 index 000000000..1a53f2cd9 --- /dev/null +++ b/libraries/ESPAsyncWebServer/examples/StreamFiles/StreamConcat.h @@ -0,0 +1,33 @@ +#pragma once + +#include + +class StreamConcat : public Stream { + public: + StreamConcat(Stream* s1, Stream* s2) : _s1(s1), _s2(s2) {} + + size_t write(__unused const uint8_t* p, __unused size_t n) override { return 0; } + size_t write(__unused uint8_t c) override { return 0; } + void flush() override {} + + int available() override { return _s1->available() + _s2->available(); } + + int read() override { + int c = _s1->read(); + return c != -1 ? c : _s2->read(); + } + + size_t readBytes(char* buffer, size_t length) override { + size_t count = _s1->readBytes(buffer, length); + return count > 0 ? count : _s2->readBytes(buffer, length); + } + + int peek() override { + int c = _s1->peek(); + return c != -1 ? c : _s2->peek(); + } + + private: + Stream* _s1; + Stream* _s2; +}; diff --git a/libraries/ESPAsyncWebServer/examples/StreamFiles/StreamFiles.ino b/libraries/ESPAsyncWebServer/examples/StreamFiles/StreamFiles.ino new file mode 100644 index 000000000..d5621f574 --- /dev/null +++ b/libraries/ESPAsyncWebServer/examples/StreamFiles/StreamFiles.ino @@ -0,0 +1,73 @@ +#include +#include +#ifdef ESP32 +#include +#include +#elif defined(ESP8266) +#include +#include +#endif +#include "StreamConcat.h" +#include "StreamString.h" +#include +#include + +DNSServer dnsServer; +AsyncWebServer server(80); + +void setup() { + Serial.begin(115200); + + LittleFS.begin(); + + WiFi.mode(WIFI_AP); + WiFi.softAP("esp-captive"); + dnsServer.start(53, "*", WiFi.softAPIP()); + + File file1 = LittleFS.open("/header.html", "w"); + file1.print("ESP Captive Portal"); + file1.close(); + + File file2 = LittleFS.open("/body.html", "w"); + file2.print("

Welcome to ESP Captive Portal

"); + file2.close(); + + File file3 = LittleFS.open("/footer.html", "w"); + file3.print(""); + file3.close(); + + server.on("/", HTTP_GET, [](AsyncWebServerRequest* request) { + File header = LittleFS.open("/header.html", "r"); + File body = LittleFS.open("/body.html", "r"); + StreamConcat stream1(&header, &body); + + StreamString content; + content.printf("FreeHeap: %" PRIu32, ESP.getFreeHeap()); + StreamConcat stream2 = StreamConcat(&stream1, &content); + + File footer = LittleFS.open("/footer.html", "r"); + StreamConcat stream3 = StreamConcat(&stream2, &footer); + + request->send(stream3, "text/html", stream3.available()); + header.close(); + body.close(); + footer.close(); + }); + + server.onNotFound([](AsyncWebServerRequest* request) { + request->send(404, "text/plain", "Not found"); + }); + + server.begin(); +} + +uint32_t last = 0; + +void loop() { + // dnsServer.processNextRequest(); + + if (millis() - last > 2000) { + Serial.printf("FreeHeap: %" PRIu32, ESP.getFreeHeap()); + last = millis(); + } +} \ No newline at end of file diff --git a/libraries/ESPAsyncWebServer/examples/StreamFiles/StreamString.h b/libraries/ESPAsyncWebServer/examples/StreamFiles/StreamString.h new file mode 100644 index 000000000..f392b4525 --- /dev/null +++ b/libraries/ESPAsyncWebServer/examples/StreamFiles/StreamString.h @@ -0,0 +1,36 @@ +#pragma once + +#include + +class StreamString : public Stream { + public: + size_t write(const uint8_t* p, size_t n) override { return _buffer.concat(reinterpret_cast(p), n) ? n : 0; } + size_t write(uint8_t c) override { return _buffer.concat(static_cast(c)) ? 1 : 0; } + void flush() override {} + + int available() override { return static_cast(_buffer.length()); } + + int read() override { + if (_buffer.length() == 0) + return -1; + char c = _buffer[0]; + _buffer.remove(0, 1); + return c; + } + + size_t readBytes(char* buffer, size_t length) override { + if (length > _buffer.length()) + length = _buffer.length(); + // Don't use _str.ToCharArray() because it inserts a terminator + memcpy(buffer, _buffer.c_str(), length); + _buffer.remove(0, static_cast(length)); + return length; + } + + int peek() override { return _buffer.length() > 0 ? _buffer[0] : -1; } + + const String& buffer() const { return _buffer; } + + private: + String _buffer; +}; diff --git a/libraries/ESPAsyncWebServer/library.json b/libraries/ESPAsyncWebServer/library.json new file mode 100644 index 000000000..539ff5d45 --- /dev/null +++ b/libraries/ESPAsyncWebServer/library.json @@ -0,0 +1,54 @@ +{ + "name": "ESP Async WebServer", + "version": "2.10.4", + "description": "Asynchronous HTTP and WebSocket Server Library for ESP32. Supports: WebSocket, SSE, Authentication, Arduino Json 7, File Upload, Static File serving, URL Rewrite, URL Redirect, etc.", + "keywords": "http,async,websocket,webserver", + "homepage": "https://github.com/mathieucarbou/ESPAsyncWebServer", + "repository": { + "type": "git", + "url": "https://github.com/mathieucarbou/ESPAsyncWebServer.git" + }, + "authors": [ + { + "name": "Hristo Gochkov" + }, + { + "name": "Mathieu Carbou", + "maintainer": true + } + ], + "license": "LGPL-3.0", + "frameworks": "arduino", + "platforms": [ + "espressif32", + "espressif8266" + ], + "dependencies": [ + { + "owner": "mathieucarbou", + "name": "Async TCP", + "version": "^3.1.4", + "platforms": "espressif32" + }, + { + "owner": "esphome", + "name": "ESPAsyncTCP-esphome", + "version": "^2.0.0", + "platforms": "espressif8266" + }, + { + "name": "Hash", + "platforms": "espressif8266" + } + ], + "export": { + "include": [ + "examples", + "src", + "library.json", + "library.properties", + "LICENSE", + "README.md" + ] + } +} \ No newline at end of file diff --git a/libraries/ESPAsyncWebServer/library.properties b/libraries/ESPAsyncWebServer/library.properties new file mode 100644 index 000000000..fc68a1c3f --- /dev/null +++ b/libraries/ESPAsyncWebServer/library.properties @@ -0,0 +1,10 @@ +name=ESP Async WebServer +version=2.10.4 +author=Me-No-Dev +maintainer=Mathieu Carbou +sentence=Asynchronous HTTP and WebSocket Server Library for ESP32 +paragraph=Supports: WebSocket, SSE, Authentication, Arduino Json 7, File Upload, Static File serving, URL Rewrite, URL Redirect, etc +category=Other +url=https://github.com/mathieucarbou/ESPAsyncWebServer +architectures=esp8266,esp32 +license=LGPL-3.0 diff --git a/libraries/ESPAsyncWebServer/platformio.ini b/libraries/ESPAsyncWebServer/platformio.ini new file mode 100644 index 000000000..eff66778f --- /dev/null +++ b/libraries/ESPAsyncWebServer/platformio.ini @@ -0,0 +1,44 @@ +[env] +framework = arduino +build_flags = + -Wall -Wextra + -D CONFIG_ARDUHAL_LOG_COLORS + -D CORE_DEBUG_LEVEL=ARDUHAL_LOG_LEVEL_VERBOSE +lib_deps = + bblanchon/ArduinoJson @ 7.0.4 + mathieucarbou/Async TCP @ ^3.1.4 + ; https://github.com/mathieucarbou/AsyncTCP + ; https://github.com/me-no-dev/AsyncTCP + esphome/ESPAsyncTCP-esphome @ 2.0.0 +upload_protocol = esptool +monitor_speed = 115200 +monitor_filters = esp32_exception_decoder, log2file + +[platformio] +lib_dir = . +; src_dir = examples/CaptivePortal +; src_dir = examples/SimpleServer +src_dir = examples/StreamFiles +; src_dir = examples/Filters + +[env:arduino] +platform = espressif32 +board = esp32dev + +[env:arduino-2] +platform = espressif32@6.7.0 +board = esp32dev + +[env:arduino-3] +platform = espressif32 +platform_packages= + platformio/framework-arduinoespressif32 @ https://github.com/espressif/arduino-esp32.git#3.0.0 + platformio/framework-arduinoespressif32-libs @ https://github.com/espressif/arduino-esp32/releases/download/3.0.0/esp32-arduino-libs-3.0.0.zip +board = esp32dev + +[env:esp8266] +platform = espressif8266 +board = huzzah +lib_deps = + bblanchon/ArduinoJson @ 7.0.4 + esphome/ESPAsyncTCP-esphome @ 2.0.0 diff --git a/libraries/ESPAsyncWebServer/src/AsyncEventSource.cpp b/libraries/ESPAsyncWebServer/src/AsyncEventSource.cpp new file mode 100644 index 000000000..3c0e390b7 --- /dev/null +++ b/libraries/ESPAsyncWebServer/src/AsyncEventSource.cpp @@ -0,0 +1,412 @@ +/* + Asynchronous WebServer library for Espressif MCUs + + Copyright (c) 2016 Hristo Gochkov. All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ +#include "Arduino.h" +#include "AsyncEventSource.h" +#ifndef ESP8266 + #include +#endif + +static String generateEventMessage(const char *message, const char *event, uint32_t id, uint32_t reconnect){ + String ev; + + if(reconnect){ + ev += F("retry: "); + ev += reconnect; + ev += F("\r\n"); + } + + if(id){ + ev += F("id: "); + ev += String(id); + ev += F("\r\n"); + } + + if(event != NULL){ + ev += F("event: "); + ev += String(event); + ev += F("\r\n"); + } + + if(message != NULL){ + size_t messageLen = strlen(message); + char * lineStart = (char *)message; + char * lineEnd; + do { + char * nextN = strchr(lineStart, '\n'); + char * nextR = strchr(lineStart, '\r'); + if(nextN == NULL && nextR == NULL){ + size_t llen = ((char *)message + messageLen) - lineStart; + char * ldata = (char *)malloc(llen+1); + if(ldata != NULL){ + memcpy(ldata, lineStart, llen); + ldata[llen] = 0; + ev += F("data: "); + ev += ldata; + ev += F("\r\n\r\n"); + free(ldata); + } + lineStart = (char *)message + messageLen; + } else { + char * nextLine = NULL; + if(nextN != NULL && nextR != NULL){ + if(nextR < nextN){ + lineEnd = nextR; + if(nextN == (nextR + 1)) + nextLine = nextN + 1; + else + nextLine = nextR + 1; + } else { + lineEnd = nextN; + if(nextR == (nextN + 1)) + nextLine = nextR + 1; + else + nextLine = nextN + 1; + } + } else if(nextN != NULL){ + lineEnd = nextN; + nextLine = nextN + 1; + } else { + lineEnd = nextR; + nextLine = nextR + 1; + } + + size_t llen = lineEnd - lineStart; + char * ldata = (char *)malloc(llen+1); + if(ldata != NULL){ + memcpy(ldata, lineStart, llen); + ldata[llen] = 0; + ev += F("data: "); + ev += ldata; + ev += F("\r\n"); + free(ldata); + } + lineStart = nextLine; + if(lineStart == ((char *)message + messageLen)) + ev += F("\r\n"); + } + } while(lineStart < ((char *)message + messageLen)); + } + + return ev; +} + +// Message + +AsyncEventSourceMessage::AsyncEventSourceMessage(const char * data, size_t len) +: _data(nullptr), _len(len), _sent(0), _acked(0) +{ + _data = (uint8_t*)malloc(_len+1); + if(_data == nullptr){ + _len = 0; + } else { + memcpy(_data, data, len); + _data[_len] = 0; + } +} + +AsyncEventSourceMessage::~AsyncEventSourceMessage() { + if(_data != NULL) + free(_data); +} + +size_t AsyncEventSourceMessage::ack(size_t len, uint32_t time) { + (void)time; + // If the whole message is now acked... + if(_acked + len > _len){ + // Return the number of extra bytes acked (they will be carried on to the next message) + const size_t extra = _acked + len - _len; + _acked = _len; + return extra; + } + // Return that no extra bytes left. + _acked += len; + return 0; +} + +// This could also return void as the return value is not used. +// Leaving as-is for compatibility... +size_t AsyncEventSourceMessage::send(AsyncClient *client) { + if (_sent >= _len) { + return 0; + } + const size_t len_to_send = _len - _sent; + auto position = reinterpret_cast(_data + _sent); + const size_t sent_now = client->write(position, len_to_send); + _sent += sent_now; + return sent_now; +} + +// Client + +AsyncEventSourceClient::AsyncEventSourceClient(AsyncWebServerRequest *request, AsyncEventSource *server) +: _messageQueue(AlternativeLinkedList([](AsyncEventSourceMessage *m){ delete m; })) +{ + _client = request->client(); + _server = server; + _lastId = 0; + if(request->hasHeader(F("Last-Event-ID"))) + _lastId = atoi(request->getHeader(F("Last-Event-ID"))->value().c_str()); + + _client->setRxTimeout(0); + _client->onError(NULL, NULL); + _client->onAck([](void *r, AsyncClient* c, size_t len, uint32_t time){ (void)c; ((AsyncEventSourceClient*)(r))->_onAck(len, time); }, this); + _client->onPoll([](void *r, AsyncClient* c){ (void)c; ((AsyncEventSourceClient*)(r))->_onPoll(); }, this); + _client->onData(NULL, NULL); + _client->onTimeout([this](void *r, AsyncClient* c __attribute__((unused)), uint32_t time){ ((AsyncEventSourceClient*)(r))->_onTimeout(time); }, this); + _client->onDisconnect([this](void *r, AsyncClient* c){ ((AsyncEventSourceClient*)(r))->_onDisconnect(); delete c; }, this); + + _server->_addClient(this); + delete request; +} + +AsyncEventSourceClient::~AsyncEventSourceClient(){ + _lockmq.lock(); + _messageQueue.free(); + _lockmq.unlock(); + close(); +} + +void AsyncEventSourceClient::_queueMessage(AsyncEventSourceMessage *dataMessage){ + if(dataMessage == NULL) + return; + if(!connected()){ + delete dataMessage; + return; + } + //length() is not thread-safe, thus acquiring the lock before this call.. + _lockmq.lock(); + if(_messageQueue.length() >= SSE_MAX_QUEUED_MESSAGES){ +#ifdef ESP8266 + ets_printf(String(F("ERROR: Too many messages queued\n")).c_str()); +#else + log_e("Too many messages queued: deleting message"); +#endif + delete dataMessage; + } else { + _messageQueue.add(dataMessage); + // runqueue trigger when new messages added + if(_client->canSend()) { + _runQueue(); + } + } + _lockmq.unlock(); +} + +void AsyncEventSourceClient::_onAck(size_t len, uint32_t time){ + // Same here, acquiring the lock early + _lockmq.lock(); + while(len && !_messageQueue.isEmpty()){ + len = _messageQueue.front()->ack(len, time); + if(_messageQueue.front()->finished()) + _messageQueue.remove(_messageQueue.front()); + } + _runQueue(); + _lockmq.unlock(); +} + +void AsyncEventSourceClient::_onPoll(){ + _lockmq.lock(); + if(!_messageQueue.isEmpty()){ + _runQueue(); + } + _lockmq.unlock(); +} + +void AsyncEventSourceClient::_onTimeout(uint32_t time __attribute__((unused))){ + _client->close(true); +} + +void AsyncEventSourceClient::_onDisconnect(){ + _client = NULL; + _server->_handleDisconnect(this); +} + +void AsyncEventSourceClient::close(){ + if(_client != NULL) + _client->close(); +} + +void AsyncEventSourceClient::write(const char * message, size_t len){ + _queueMessage(new AsyncEventSourceMessage(message, len)); +} + +void AsyncEventSourceClient::send(const char *message, const char *event, uint32_t id, uint32_t reconnect){ + String ev = generateEventMessage(message, event, id, reconnect); + _queueMessage(new AsyncEventSourceMessage(ev.c_str(), ev.length())); +} + +size_t AsyncEventSourceClient::packetsWaiting() const { + size_t len; + _lockmq.lock(); + len = _messageQueue.length(); + _lockmq.unlock(); + return len; +} + +void AsyncEventSourceClient::_runQueue() { + // Calls to this private method now already protected by _lockmq acquisition + // so no extra call of _lockmq.lock() here.. + for (auto i = _messageQueue.begin(); i != _messageQueue.end(); ++i) { + // If it crashes here, iterator (i) has been invalidated as _messageQueue + // has been changed... (UL 2020-11-15: Not supposed to happen any more ;-) ) + if (!(*i)->sent()) { + (*i)->send(_client); + } + } +} + + +// Handler + +AsyncEventSource::AsyncEventSource(const String& url) + : _url(url) + , _clients(AlternativeLinkedList([](AsyncEventSourceClient *c){ delete c; })) + , _connectcb(NULL) +{} + +AsyncEventSource::~AsyncEventSource(){ + close(); +} + +void AsyncEventSource::onConnect(ArEventHandlerFunction cb){ + _connectcb = cb; +} + +void AsyncEventSource::authorizeConnect(ArAuthorizeConnectHandler cb){ + _authorizeConnectHandler = cb; +} + +void AsyncEventSource::_addClient(AsyncEventSourceClient * client){ + /*char * temp = (char *)malloc(2054); + if(temp != NULL){ + memset(temp+1,' ',2048); + temp[0] = ':'; + temp[2049] = '\r'; + temp[2050] = '\n'; + temp[2051] = '\r'; + temp[2052] = '\n'; + temp[2053] = 0; + client->write((const char *)temp, 2053); + free(temp); + }*/ + AsyncWebLockGuard l(_client_queue_lock); + _clients.add(client); + if(_connectcb) + _connectcb(client); +} + +void AsyncEventSource::_handleDisconnect(AsyncEventSourceClient * client){ + AsyncWebLockGuard l(_client_queue_lock); + _clients.remove(client); +} + +void AsyncEventSource::close(){ + // While the whole loop is not done, the linked list is locked and so the + // iterator should remain valid even when AsyncEventSource::_handleDisconnect() + // is called very early + AsyncWebLockGuard l(_client_queue_lock); + for(const auto &c: _clients){ + if(c->connected()) + c->close(); + } +} + +// pmb fix +size_t AsyncEventSource::avgPacketsWaiting() const { + size_t aql = 0; + uint32_t nConnectedClients = 0; + AsyncWebLockGuard l(_client_queue_lock); + if (_clients.isEmpty()) { + return 0; + } + for(const auto &c: _clients){ + if(c->connected()) { + aql += c->packetsWaiting(); + ++nConnectedClients; + } + } + return ((aql) + (nConnectedClients/2)) / (nConnectedClients); // round up +} + +void AsyncEventSource::send( + const char *message, const char *event, uint32_t id, uint32_t reconnect){ + String ev = generateEventMessage(message, event, id, reconnect); + AsyncWebLockGuard l(_client_queue_lock); + for(const auto &c: _clients){ + if(c->connected()) { + c->write(ev.c_str(), ev.length()); + } + } +} + +size_t AsyncEventSource::count() const { + size_t n_clients; + AsyncWebLockGuard l(_client_queue_lock); + n_clients = _clients.count_if([](AsyncEventSourceClient *c){ + return c->connected(); + }); + return n_clients; +} + +bool AsyncEventSource::canHandle(AsyncWebServerRequest *request){ + if(request->method() != HTTP_GET || !request->url().equals(_url)) { + return false; + } + request->addInterestingHeader(F("Last-Event-ID")); + request->addInterestingHeader("Cookie"); + return true; +} + +void AsyncEventSource::handleRequest(AsyncWebServerRequest *request){ + if((_username.length() && _password.length()) && !request->authenticate(_username.c_str(), _password.c_str())) { + return request->requestAuthentication(); + } + if(_authorizeConnectHandler != NULL){ + if(!_authorizeConnectHandler(request)){ + return request->send(401); + } + } + request->send(new AsyncEventSourceResponse(this)); +} + +// Response + +AsyncEventSourceResponse::AsyncEventSourceResponse(AsyncEventSource *server){ + _server = server; + _code = 200; + _contentType = F("text/event-stream"); + _sendContentLength = false; + addHeader(F("Cache-Control"), F("no-cache")); + addHeader(F("Connection"), F("keep-alive")); +} + +void AsyncEventSourceResponse::_respond(AsyncWebServerRequest *request){ + String out = _assembleHead(request->version()); + request->client()->write(out.c_str(), _headLength); + _state = RESPONSE_WAIT_ACK; +} + +size_t AsyncEventSourceResponse::_ack(AsyncWebServerRequest *request, size_t len, uint32_t time __attribute__((unused))){ + if(len){ + new AsyncEventSourceClient(request, _server); + } + return 0; +} + diff --git a/libraries/ESPAsyncWebServer/src/AsyncEventSource.h b/libraries/ESPAsyncWebServer/src/AsyncEventSource.h new file mode 100644 index 000000000..ac8c36498 --- /dev/null +++ b/libraries/ESPAsyncWebServer/src/AsyncEventSource.h @@ -0,0 +1,147 @@ +/* + Asynchronous WebServer library for Espressif MCUs + + Copyright (c) 2016 Hristo Gochkov. All rights reserved. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ +#ifndef ASYNCEVENTSOURCE_H_ +#define ASYNCEVENTSOURCE_H_ + +#include +#ifdef ESP32 +#include +#ifndef SSE_MAX_QUEUED_MESSAGES +#define SSE_MAX_QUEUED_MESSAGES 32 +#endif +#else +#include +#ifndef SSE_MAX_QUEUED_MESSAGES +#define SSE_MAX_QUEUED_MESSAGES 8 +#endif +#endif + +#include + +#include "AsyncWebSynchronization.h" + +#ifdef ESP8266 +#include +#ifdef CRYPTO_HASH_h // include Hash.h from espressif framework if the first include was from the crypto library +#include <../src/Hash.h> +#endif +#endif + +#ifdef ESP32 +#define DEFAULT_MAX_SSE_CLIENTS 8 +#else +#define DEFAULT_MAX_SSE_CLIENTS 4 +#endif + +class AsyncEventSource; +class AsyncEventSourceResponse; +class AsyncEventSourceClient; +typedef std::function ArEventHandlerFunction; +typedef std::function ArAuthorizeConnectHandler; + +class AsyncEventSourceMessage { + private: + uint8_t * _data; + size_t _len; + size_t _sent; + //size_t _ack; + size_t _acked; + public: + AsyncEventSourceMessage(const char * data, size_t len); + ~AsyncEventSourceMessage(); + size_t ack(size_t len, uint32_t time __attribute__((unused))); + size_t send(AsyncClient *client); + bool finished(){ return _acked == _len; } + bool sent() { return _sent == _len; } +}; + +class AsyncEventSourceClient { + private: + AsyncClient *_client; + AsyncEventSource *_server; + uint32_t _lastId; + AlternativeLinkedList _messageQueue; + // ArFi 2020-08-27 for protecting/serializing _messageQueue + AsyncPlainLock _lockmq; + void _queueMessage(AsyncEventSourceMessage *dataMessage); + void _runQueue(); + + public: + + AsyncEventSourceClient(AsyncWebServerRequest *request, AsyncEventSource *server); + ~AsyncEventSourceClient(); + + AsyncClient* client(){ return _client; } + void close(); + void write(const char * message, size_t len); + void send(const char *message, const char *event=NULL, uint32_t id=0, uint32_t reconnect=0); + bool connected() const { return (_client != NULL) && _client->connected(); } + uint32_t lastId() const { return _lastId; } + size_t packetsWaiting() const; + + //system callbacks (do not call) + void _onAck(size_t len, uint32_t time); + void _onPoll(); + void _onTimeout(uint32_t time); + void _onDisconnect(); +}; + +class AsyncEventSource: public AsyncWebHandler { + private: + String _url; + AlternativeLinkedList _clients; + // Same as for individual messages, protect mutations of _clients list + // since simultaneous access from different tasks is possible + AsyncWebLock _client_queue_lock; + ArEventHandlerFunction _connectcb; + ArAuthorizeConnectHandler _authorizeConnectHandler; + public: + AsyncEventSource(const String& url); + ~AsyncEventSource(); + + const char * url() const { return _url.c_str(); } + void close(); + void onConnect(ArEventHandlerFunction cb); + void authorizeConnect(ArAuthorizeConnectHandler cb); + void send(const char *message, const char *event=NULL, uint32_t id=0, uint32_t reconnect=0); + // number of clients connected + size_t count() const; + size_t avgPacketsWaiting() const; + + //system callbacks (do not call) + void _addClient(AsyncEventSourceClient * client); + void _handleDisconnect(AsyncEventSourceClient * client); + virtual bool canHandle(AsyncWebServerRequest *request) override final; + virtual void handleRequest(AsyncWebServerRequest *request) override final; +}; + +class AsyncEventSourceResponse: public AsyncWebServerResponse { + private: + String _content; + AsyncEventSource *_server; + public: + AsyncEventSourceResponse(AsyncEventSource *server); + void _respond(AsyncWebServerRequest *request); + size_t _ack(AsyncWebServerRequest *request, size_t len, uint32_t time); + bool _sourceValid() const { return true; } +}; + + +#endif /* ASYNCEVENTSOURCE_H_ */ diff --git a/libraries/ESPAsyncWebServer/src/AsyncJson.h b/libraries/ESPAsyncWebServer/src/AsyncJson.h new file mode 100644 index 000000000..677445401 --- /dev/null +++ b/libraries/ESPAsyncWebServer/src/AsyncJson.h @@ -0,0 +1,282 @@ +// AsyncJson.h +/* + Async Response to use with ArduinoJson and AsyncWebServer + Written by Andrew Melvin (SticilFace) with help from me-no-dev and BBlanchon. + + Example of callback in use + + server.on("/json", HTTP_ANY, [](AsyncWebServerRequest * request) { + + AsyncJsonResponse * response = new AsyncJsonResponse(); + JsonObject& root = response->getRoot(); + root["key1"] = "key number one"; + JsonObject& nested = root.createNestedObject("nested"); + nested["key1"] = "key number one"; + + response->setLength(); + request->send(response); + }); + + -------------------- + + Async Request to use with ArduinoJson and AsyncWebServer + Written by Arsène von Wyss (avonwyss) + + Example + + AsyncCallbackJsonWebHandler* handler = new AsyncCallbackJsonWebHandler("/rest/endpoint"); + handler->onRequest([](AsyncWebServerRequest *request, JsonVariant &json) { + JsonObject& jsonObj = json.as(); + // ... + }); + server.addHandler(handler); + +*/ +#ifndef ASYNC_JSON_H_ +#define ASYNC_JSON_H_ +#include +#include +#include + +#if ARDUINOJSON_VERSION_MAJOR == 6 +#ifndef DYNAMIC_JSON_DOCUMENT_SIZE +#define DYNAMIC_JSON_DOCUMENT_SIZE 1024 +#endif +#endif + +constexpr const char* JSON_MIMETYPE = "application/json"; + +/* + * Json Response + * */ + +class ChunkPrint : public Print { + private: + uint8_t* _destination; + size_t _to_skip; + size_t _to_write; + size_t _pos; + + public: + ChunkPrint(uint8_t* destination, size_t from, size_t len) + : _destination(destination), _to_skip(from), _to_write(len), _pos{0} {} + virtual ~ChunkPrint() {} + size_t write(uint8_t c) { + if (_to_skip > 0) { + _to_skip--; + return 1; + } else if (_to_write > 0) { + _to_write--; + _destination[_pos++] = c; + return 1; + } + return 0; + } + size_t write(const uint8_t* buffer, size_t size) { + return this->Print::write(buffer, size); + } +}; + +class AsyncJsonResponse : public AsyncAbstractResponse { + protected: +#if ARDUINOJSON_VERSION_MAJOR == 5 + DynamicJsonBuffer _jsonBuffer; +#elif ARDUINOJSON_VERSION_MAJOR == 6 + DynamicJsonDocument _jsonBuffer; +#else + JsonDocument _jsonBuffer; +#endif + + JsonVariant _root; + bool _isValid; + + public: +#if ARDUINOJSON_VERSION_MAJOR == 5 + AsyncJsonResponse(bool isArray = false) : _isValid{false} { + _code = 200; + _contentType = JSON_MIMETYPE; + if (isArray) + _root = _jsonBuffer.createArray(); + else + _root = _jsonBuffer.createObject(); + } +#elif ARDUINOJSON_VERSION_MAJOR == 6 + AsyncJsonResponse(bool isArray = false, size_t maxJsonBufferSize = DYNAMIC_JSON_DOCUMENT_SIZE) : _jsonBuffer(maxJsonBufferSize), _isValid{false} { + _code = 200; + _contentType = JSON_MIMETYPE; + if (isArray) + _root = _jsonBuffer.createNestedArray(); + else + _root = _jsonBuffer.createNestedObject(); + } +#else + AsyncJsonResponse(bool isArray = false) : _isValid{false} { + _code = 200; + _contentType = JSON_MIMETYPE; + if (isArray) + _root = _jsonBuffer.add(); + else + _root = _jsonBuffer.add(); + } +#endif + + ~AsyncJsonResponse() {} + JsonVariant& getRoot() { return _root; } + bool _sourceValid() const { return _isValid; } + size_t setLength() { + +#if ARDUINOJSON_VERSION_MAJOR == 5 + _contentLength = _root.measureLength(); +#else + _contentLength = measureJson(_root); +#endif + + if (_contentLength) { + _isValid = true; + } + return _contentLength; + } + + size_t getSize() const { return _jsonBuffer.size(); } + +#if ARDUINOJSON_VERSION_MAJOR >= 6 + bool overflowed() const { return _jsonBuffer.overflowed(); } +#endif + + size_t _fillBuffer(uint8_t* data, size_t len) { + ChunkPrint dest(data, _sentLength, len); + +#if ARDUINOJSON_VERSION_MAJOR == 5 + _root.printTo(dest); +#else + serializeJson(_root, dest); +#endif + return len; + } +}; + +class PrettyAsyncJsonResponse : public AsyncJsonResponse { + public: +#if ARDUINOJSON_VERSION_MAJOR == 6 + PrettyAsyncJsonResponse(bool isArray = false, size_t maxJsonBufferSize = DYNAMIC_JSON_DOCUMENT_SIZE) : AsyncJsonResponse{isArray, maxJsonBufferSize} {} +#else + PrettyAsyncJsonResponse(bool isArray = false) : AsyncJsonResponse{isArray} {} +#endif + size_t setLength() { +#if ARDUINOJSON_VERSION_MAJOR == 5 + _contentLength = _root.measurePrettyLength(); +#else + _contentLength = measureJsonPretty(_root); +#endif + if (_contentLength) { + _isValid = true; + } + return _contentLength; + } + size_t _fillBuffer(uint8_t* data, size_t len) { + ChunkPrint dest(data, _sentLength, len); +#if ARDUINOJSON_VERSION_MAJOR == 5 + _root.prettyPrintTo(dest); +#else + serializeJsonPretty(_root, dest); +#endif + return len; + } +}; + +typedef std::function ArJsonRequestHandlerFunction; + +class AsyncCallbackJsonWebHandler : public AsyncWebHandler { + private: + protected: + const String _uri; + WebRequestMethodComposite _method; + ArJsonRequestHandlerFunction _onRequest; + size_t _contentLength; +#if ARDUINOJSON_VERSION_MAJOR == 6 + const size_t maxJsonBufferSize; +#endif + size_t _maxContentLength; + + public: +#if ARDUINOJSON_VERSION_MAJOR == 6 + AsyncCallbackJsonWebHandler(const String& uri, ArJsonRequestHandlerFunction onRequest, size_t maxJsonBufferSize = DYNAMIC_JSON_DOCUMENT_SIZE) + : _uri(uri), _method(HTTP_GET | HTTP_POST | HTTP_PUT | HTTP_PATCH), _onRequest(onRequest), maxJsonBufferSize(maxJsonBufferSize), _maxContentLength(16384) {} +#else + AsyncCallbackJsonWebHandler(const String& uri, ArJsonRequestHandlerFunction onRequest) + : _uri(uri), _method(HTTP_GET | HTTP_POST | HTTP_PUT | HTTP_PATCH), _onRequest(onRequest), _maxContentLength(16384) {} +#endif + + void setMethod(WebRequestMethodComposite method) { _method = method; } + void setMaxContentLength(int maxContentLength) { _maxContentLength = maxContentLength; } + void onRequest(ArJsonRequestHandlerFunction fn) { _onRequest = fn; } + + virtual bool canHandle(AsyncWebServerRequest* request) override final { + if (!_onRequest) + return false; + + WebRequestMethodComposite request_method = request->method(); + if (!(_method & request_method)) + return false; + + if (_uri.length() && (_uri != request->url() && !request->url().startsWith(_uri + "/"))) + return false; + + if (request_method != HTTP_GET && !request->contentType().equalsIgnoreCase(JSON_MIMETYPE)) + return false; + + request->addInterestingHeader("ANY"); + return true; + } + + virtual void handleRequest(AsyncWebServerRequest* request) override final { + if((_username != "" && _password != "") && !request->authenticate(_username.c_str(), _password.c_str())) + return request->requestAuthentication(); + if (_onRequest) { + if (request->method() == HTTP_GET) { + JsonVariant json; + _onRequest(request, json); + return; + } else if (request->_tempObject != NULL) { + +#if ARDUINOJSON_VERSION_MAJOR == 5 + DynamicJsonBuffer jsonBuffer; + JsonVariant json = jsonBuffer.parse((uint8_t*)(request->_tempObject)); + if (json.success()) { +#elif ARDUINOJSON_VERSION_MAJOR == 6 + DynamicJsonDocument jsonBuffer(this->maxJsonBufferSize); + DeserializationError error = deserializeJson(jsonBuffer, (uint8_t*)(request->_tempObject)); + if (!error) { + JsonVariant json = jsonBuffer.as(); +#else + JsonDocument jsonBuffer; + DeserializationError error = deserializeJson(jsonBuffer, (uint8_t*)(request->_tempObject)); + if (!error) { + JsonVariant json = jsonBuffer.as(); +#endif + + _onRequest(request, json); + return; + } + } + request->send(_contentLength > _maxContentLength ? 413 : 400); + } else { + request->send(500); + } + } + virtual void handleUpload(__unused AsyncWebServerRequest* request, __unused const String& filename, __unused size_t index, __unused uint8_t* data, __unused size_t len, __unused bool final) override final { + } + virtual void handleBody(AsyncWebServerRequest* request, uint8_t* data, size_t len, size_t index, size_t total) override final { + if (_onRequest) { + _contentLength = total; + if (total > 0 && request->_tempObject == NULL && total < _maxContentLength) { + request->_tempObject = malloc(total); + } + if (request->_tempObject != NULL) { + memcpy((uint8_t*)(request->_tempObject) + index, data, len); + } + } + } + virtual bool isRequestHandlerTrivial() override final { return _onRequest ? false : true; } +}; +#endif diff --git a/libraries/ESPAsyncWebServer/src/AsyncWebSocket.cpp b/libraries/ESPAsyncWebServer/src/AsyncWebSocket.cpp new file mode 100644 index 000000000..f231d2364 --- /dev/null +++ b/libraries/ESPAsyncWebServer/src/AsyncWebSocket.cpp @@ -0,0 +1,1353 @@ +/* + Asynchronous WebServer library for Espressif MCUs + + Copyright (c) 2016 Hristo Gochkov. All rights reserved. + This file is part of the esp8266 core for Arduino environment. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ +#include "Arduino.h" +#include "AsyncWebSocket.h" + +#include + +#include + +#ifndef ESP8266 +#include "mbedtls/sha1.h" +#include +#else +#include +#endif + +#define MAX_PRINTF_LEN 64 + +size_t webSocketSendFrameWindow(AsyncClient *client){ + if(!client->canSend()) + return 0; + size_t space = client->space(); + if(space < 9) + return 0; + return space - 8; +} + +size_t webSocketSendFrame(AsyncClient *client, bool final, uint8_t opcode, bool mask, uint8_t *data, size_t len){ + if(!client->canSend()) { + // Serial.println("SF 1"); + return 0; + } + size_t space = client->space(); + if(space < 2) { + // Serial.println("SF 2"); + return 0; + } + uint8_t mbuf[4] = {0,0,0,0}; + uint8_t headLen = 2; + if(len && mask){ + headLen += 4; + mbuf[0] = rand() % 0xFF; + mbuf[1] = rand() % 0xFF; + mbuf[2] = rand() % 0xFF; + mbuf[3] = rand() % 0xFF; + } + if(len > 125) + headLen += 2; + if(space < headLen) { + // Serial.println("SF 2"); + return 0; + } + space -= headLen; + + if(len > space) len = space; + + uint8_t *buf = (uint8_t*)malloc(headLen); + if(buf == NULL){ + //os_printf("could not malloc %u bytes for frame header\n", headLen); + // Serial.println("SF 3"); + return 0; + } + + buf[0] = opcode & 0x0F; + if(final) + buf[0] |= 0x80; + if(len < 126) + buf[1] = len & 0x7F; + else { + buf[1] = 126; + buf[2] = (uint8_t)((len >> 8) & 0xFF); + buf[3] = (uint8_t)(len & 0xFF); + } + if(len && mask){ + buf[1] |= 0x80; + memcpy(buf + (headLen - 4), mbuf, 4); + } + if(client->add((const char *)buf, headLen) != headLen){ + //os_printf("error adding %lu header bytes\n", headLen); + free(buf); + // Serial.println("SF 4"); + return 0; + } + free(buf); + + if(len){ + if(len && mask){ + size_t i; + for(i=0;iadd((const char *)data, len) != len){ + //os_printf("error adding %lu data bytes\n", len); + // Serial.println("SF 5"); + return 0; + } + } + if(!client->send()){ + //os_printf("error sending frame: %lu\n", headLen+len); + // Serial.println("SF 6"); + return 0; + } + // Serial.println("SF"); + return len; +} + + + +/* + * AsyncWebSocketMessageBuffer + */ + +AsyncWebSocketMessageBuffer::AsyncWebSocketMessageBuffer() + : _buffer(std::make_shared>(0)) +{ +} + +AsyncWebSocketMessageBuffer::AsyncWebSocketMessageBuffer(uint8_t* data, size_t size) + : _buffer(std::make_shared>(size)) +{ + if (_buffer->capacity() < size) { + _buffer.reset(); + _buffer = std::make_shared>(0); + } else { + std::memcpy(_buffer->data(), data, size); + } +} + +AsyncWebSocketMessageBuffer::AsyncWebSocketMessageBuffer(size_t size) + : _buffer(std::make_shared>(size)) +{ + if (_buffer->capacity() < size) { + _buffer.reset(); + _buffer = std::make_shared>(0); + } +} + +AsyncWebSocketMessageBuffer::~AsyncWebSocketMessageBuffer() +{ + _buffer.reset(); +} + +bool AsyncWebSocketMessageBuffer::reserve(size_t size) +{ + if (_buffer->capacity() >= size) + return true; + _buffer->reserve(size); + return _buffer->capacity() >= size; +} + +/* + * Control Frame + */ + +class AsyncWebSocketControl { +private: + uint8_t _opcode; + uint8_t *_data; + size_t _len; + bool _mask; + bool _finished; + +public: + AsyncWebSocketControl(uint8_t opcode, const uint8_t *data=NULL, size_t len=0, bool mask=false) + :_opcode(opcode) + ,_len(len) + ,_mask(len && mask) + ,_finished(false) + { + if (data == NULL) + _len = 0; + if (_len) + { + if (_len > 125) + _len = 125; + + _data = (uint8_t*)malloc(_len); + + if(_data == NULL) + _len = 0; + else + memcpy(_data, data, len); + } + else + _data = NULL; + } + + virtual ~AsyncWebSocketControl() + { + if (_data != NULL) + free(_data); + } + + virtual bool finished() const { return _finished; } + uint8_t opcode(){ return _opcode; } + uint8_t len(){ return _len + 2; } + size_t send(AsyncClient *client){ + _finished = true; + return webSocketSendFrame(client, true, _opcode & 0x0F, _mask, _data, _len); + } +}; + + +/* + * AsyncWebSocketMessage Message + */ + + +AsyncWebSocketMessage::AsyncWebSocketMessage(std::shared_ptr> buffer, uint8_t opcode, bool mask) : + _WSbuffer{buffer}, + _opcode(opcode & 0x07), + _mask{mask}, + _status{_WSbuffer?WS_MSG_SENDING:WS_MSG_ERROR} +{ +} + +void AsyncWebSocketMessage::ack(size_t len, uint32_t time) +{ + (void)time; + _acked += len; + if (_sent >= _WSbuffer->size() && _acked >= _ack) + { + _status = WS_MSG_SENT; + } + //ets_printf("A: %u\n", len); +} + +size_t AsyncWebSocketMessage::send(AsyncClient *client) +{ + if (_status != WS_MSG_SENDING) + return 0; + if (_acked < _ack){ + return 0; + } + if (_sent == _WSbuffer->size()) + { + if(_acked == _ack) + _status = WS_MSG_SENT; + return 0; + } + if (_sent > _WSbuffer->size()) + { + _status = WS_MSG_ERROR; + //ets_printf("E: %u > %u\n", _sent, _WSbuffer->length()); + return 0; + } + + size_t toSend = _WSbuffer->size() - _sent; + size_t window = webSocketSendFrameWindow(client); + + if (window < toSend) { + toSend = window; + } + + _sent += toSend; + _ack += toSend + ((toSend < 126)?2:4) + (_mask * 4); + + //ets_printf("W: %u %u\n", _sent - toSend, toSend); + + bool final = (_sent == _WSbuffer->size()); + uint8_t* dPtr = (uint8_t*)(_WSbuffer->data() + (_sent - toSend)); + uint8_t opCode = (toSend && _sent == toSend)?_opcode:(uint8_t)WS_CONTINUATION; + + size_t sent = webSocketSendFrame(client, final, opCode, _mask, dPtr, toSend); + _status = WS_MSG_SENDING; + if (toSend && sent != toSend){ + //ets_printf("E: %u != %u\n", toSend, sent); + _sent -= (toSend - sent); + _ack -= (toSend - sent); + } + //ets_printf("S: %u %u\n", _sent, sent); + return sent; +} + + +/* + * Async WebSocket Client + */ + const char * AWSC_PING_PAYLOAD = "ESPAsyncWebServer-PING"; + const size_t AWSC_PING_PAYLOAD_LEN = 22; + +AsyncWebSocketClient::AsyncWebSocketClient(AsyncWebServerRequest *request, AsyncWebSocket *server) + : _tempObject(NULL) +{ + _client = request->client(); + _server = server; + _clientId = _server->_getNextId(); + _status = WS_CONNECTED; + _pstate = 0; + _lastMessageTime = millis(); + _keepAlivePeriod = 0; + _client->setRxTimeout(0); + _client->onError([](void *r, AsyncClient* c, int8_t error){ (void)c; ((AsyncWebSocketClient*)(r))->_onError(error); }, this); + _client->onAck([](void *r, AsyncClient* c, size_t len, uint32_t time){ (void)c; ((AsyncWebSocketClient*)(r))->_onAck(len, time); }, this); + _client->onDisconnect([](void *r, AsyncClient* c){ ((AsyncWebSocketClient*)(r))->_onDisconnect(); delete c; }, this); + _client->onTimeout([](void *r, AsyncClient* c, uint32_t time){ (void)c; ((AsyncWebSocketClient*)(r))->_onTimeout(time); }, this); + _client->onData([](void *r, AsyncClient* c, void *buf, size_t len){ (void)c; ((AsyncWebSocketClient*)(r))->_onData(buf, len); }, this); + _client->onPoll([](void *r, AsyncClient* c){ (void)c; ((AsyncWebSocketClient*)(r))->_onPoll(); }, this); + _server->_handleEvent(this, WS_EVT_CONNECT, request, NULL, 0); + delete request; + memset(&_pinfo,0,sizeof(_pinfo)); +} + +AsyncWebSocketClient::~AsyncWebSocketClient() +{ + { + AsyncWebLockGuard l(_lock); + + _messageQueue.clear(); + _controlQueue.clear(); + } + _server->_handleEvent(this, WS_EVT_DISCONNECT, NULL, NULL, 0); +} + +void AsyncWebSocketClient::_clearQueue() +{ + while (!_messageQueue.empty() && _messageQueue.front().finished()) + _messageQueue.pop_front(); +} + +void AsyncWebSocketClient::_onAck(size_t len, uint32_t time){ + _lastMessageTime = millis(); + + AsyncWebLockGuard l(_lock); + + if (!_controlQueue.empty()) { + auto &head = _controlQueue.front(); + if (head.finished()){ + len -= head.len(); + if (_status == WS_DISCONNECTING && head.opcode() == WS_DISCONNECT){ + _controlQueue.pop_front(); + _status = WS_DISCONNECTED; + l.unlock(); + if (_client) _client->close(true); + return; + } + _controlQueue.pop_front(); + } + } + + if(len && !_messageQueue.empty()){ + _messageQueue.front().ack(len, time); + } + + _clearQueue(); + + _runQueue(); +} + +void AsyncWebSocketClient::_onPoll() +{ + if (!_client) + return; + + AsyncWebLockGuard l(_lock); + if (_client->canSend() && (!_controlQueue.empty() || !_messageQueue.empty())) + { + l.unlock(); + _runQueue(); + } + else if (_keepAlivePeriod > 0 && (millis() - _lastMessageTime) >= _keepAlivePeriod && (_controlQueue.empty() && _messageQueue.empty())) + { + l.unlock(); + ping((uint8_t *)AWSC_PING_PAYLOAD, AWSC_PING_PAYLOAD_LEN); + } +} + +void AsyncWebSocketClient::_runQueue() +{ + if (!_client) + return; + + AsyncWebLockGuard l(_lock); + + _clearQueue(); + + if (!_controlQueue.empty() && (_messageQueue.empty() || _messageQueue.front().betweenFrames()) && webSocketSendFrameWindow(_client) > (size_t)(_controlQueue.front().len() - 1)) + { + //l.unlock(); + _controlQueue.front().send(_client); + } + else if (!_messageQueue.empty() && _messageQueue.front().betweenFrames() && webSocketSendFrameWindow(_client)) + { + //l.unlock(); + _messageQueue.front().send(_client); + } +} + +bool AsyncWebSocketClient::queueIsFull() const +{ + size_t size; + { + AsyncWebLockGuard l(_lock); + size = _messageQueue.size(); + } + return (size >= WS_MAX_QUEUED_MESSAGES) || (_status != WS_CONNECTED); +} + +size_t AsyncWebSocketClient::queueLen() const +{ + AsyncWebLockGuard l(_lock); + + return _messageQueue.size() + _controlQueue.size(); +} + +bool AsyncWebSocketClient::canSend() const +{ + size_t size; + { + AsyncWebLockGuard l(_lock); + size = _messageQueue.size(); + } + return size < WS_MAX_QUEUED_MESSAGES; +} + +void AsyncWebSocketClient::_queueControl(uint8_t opcode, const uint8_t *data, size_t len, bool mask) +{ + if (!_client) + return; + + { + AsyncWebLockGuard l(_lock); + _controlQueue.emplace_back(opcode, data, len, mask); + } + + if (_client && _client->canSend()) + _runQueue(); +} + +void AsyncWebSocketClient::_queueMessage(std::shared_ptr> buffer, uint8_t opcode, bool mask) +{ + if(_status != WS_CONNECTED) + return; + + if (!_client) + return; + + if (buffer->size() == 0) + return; + + { + AsyncWebLockGuard l(_lock); + if (_messageQueue.size() >= WS_MAX_QUEUED_MESSAGES) + { + l.unlock(); + if(closeWhenFull) + { +#ifdef ESP8266 + ets_printf("AsyncWebSocketClient::_queueMessage: Too many messages queued: closing connection\n"); +#else + log_e("Too many messages queued: closing connection"); +#endif + _status = WS_DISCONNECTED; + if (_client) _client->close(true); + } else { +#ifdef ESP8266 + ets_printf("AsyncWebSocketClient::_queueMessage: Too many messages queued: discarding new message\n"); +#else + log_e("Too many messages queued: discarding new message"); +#endif + } + return; + } + else + { + _messageQueue.emplace_back(buffer, opcode, mask); + } + } + + if (_client && _client->canSend()) + _runQueue(); +} + +void AsyncWebSocketClient::close(uint16_t code, const char * message) +{ + if(_status != WS_CONNECTED) + return; + + if(code) + { + uint8_t packetLen = 2; + if (message != NULL) + { + size_t mlen = strlen(message); + if(mlen > 123) mlen = 123; + packetLen += mlen; + } + char * buf = (char*)malloc(packetLen); + if (buf != NULL) + { + buf[0] = (uint8_t)(code >> 8); + buf[1] = (uint8_t)(code & 0xFF); + if(message != NULL){ + memcpy(buf+2, message, packetLen -2); + } + _queueControl(WS_DISCONNECT, (uint8_t*)buf, packetLen); + free(buf); + return; + } + } + _queueControl(WS_DISCONNECT); +} + +void AsyncWebSocketClient::ping(const uint8_t *data, size_t len) +{ + if (_status == WS_CONNECTED) + _queueControl(WS_PING, data, len); +} + +void AsyncWebSocketClient::_onError(int8_t) +{ + //Serial.println("onErr"); +} + +void AsyncWebSocketClient::_onTimeout(uint32_t time) +{ + // Serial.println("onTime"); + (void)time; + _client->close(true); +} + +void AsyncWebSocketClient::_onDisconnect() +{ + // Serial.println("onDis"); + _client = NULL; +} + +void AsyncWebSocketClient::_onData(void *pbuf, size_t plen) +{ + // Serial.println("onData"); + _lastMessageTime = millis(); + uint8_t *data = (uint8_t*)pbuf; + while(plen > 0){ + if(!_pstate){ + const uint8_t *fdata = data; + _pinfo.index = 0; + _pinfo.final = (fdata[0] & 0x80) != 0; + _pinfo.opcode = fdata[0] & 0x0F; + _pinfo.masked = (fdata[1] & 0x80) != 0; + _pinfo.len = fdata[1] & 0x7F; + data += 2; + plen -= 2; + if(_pinfo.len == 126){ + _pinfo.len = fdata[3] | (uint16_t)(fdata[2]) << 8; + data += 2; + plen -= 2; + } else if(_pinfo.len == 127){ + _pinfo.len = fdata[9] | (uint16_t)(fdata[8]) << 8 | (uint32_t)(fdata[7]) << 16 | (uint32_t)(fdata[6]) << 24 | (uint64_t)(fdata[5]) << 32 | (uint64_t)(fdata[4]) << 40 | (uint64_t)(fdata[3]) << 48 | (uint64_t)(fdata[2]) << 56; + data += 8; + plen -= 8; + } + + if(_pinfo.masked){ + memcpy(_pinfo.mask, data, 4); + data += 4; + plen -= 4; + } + } + + const size_t datalen = std::min((size_t)(_pinfo.len - _pinfo.index), plen); + const auto datalast = data[datalen]; + + if(_pinfo.masked){ + for(size_t i=0;i 0) _server->_handleEvent(this, WS_EVT_DATA, (void *)&_pinfo, (uint8_t*)data, datalen); + + _pinfo.index += datalen; + } else if((datalen + _pinfo.index) == _pinfo.len){ + _pstate = 0; + if(_pinfo.opcode == WS_DISCONNECT){ + if(datalen){ + uint16_t reasonCode = (uint16_t)(data[0] << 8) + data[1]; + char * reasonString = (char*)(data+2); + if(reasonCode > 1001){ + _server->_handleEvent(this, WS_EVT_ERROR, (void *)&reasonCode, (uint8_t*)reasonString, strlen(reasonString)); + } + } + if(_status == WS_DISCONNECTING){ + _status = WS_DISCONNECTED; + _client->close(true); + } else { + _status = WS_DISCONNECTING; + _client->ackLater(); + _queueControl(WS_DISCONNECT, data, datalen); + } + } else if(_pinfo.opcode == WS_PING){ + _queueControl(WS_PONG, data, datalen); + } else if(_pinfo.opcode == WS_PONG){ + if(datalen != AWSC_PING_PAYLOAD_LEN || memcmp(AWSC_PING_PAYLOAD, data, AWSC_PING_PAYLOAD_LEN) != 0) + _server->_handleEvent(this, WS_EVT_PONG, NULL, data, datalen); + } else if(_pinfo.opcode < 8){//continuation or text/binary frame + _server->_handleEvent(this, WS_EVT_DATA, (void *)&_pinfo, data, datalen); + if (_pinfo.final) _pinfo.num = 0; + else _pinfo.num += 1; + } + } else { + //os_printf("frame error: len: %u, index: %llu, total: %llu\n", datalen, _pinfo.index, _pinfo.len); + //what should we do? + break; + } + + // restore byte as _handleEvent may have added a null terminator i.e., data[len] = 0; + if (datalen > 0) + data[datalen] = datalast; + + data += datalen; + plen -= datalen; + } +} + +size_t AsyncWebSocketClient::printf(const char *format, ...) +{ + va_list arg; + va_start(arg, format); + char* temp = new char[MAX_PRINTF_LEN]; + if(!temp){ + va_end(arg); + return 0; + } + char* buffer = temp; + size_t len = vsnprintf(temp, MAX_PRINTF_LEN, format, arg); + va_end(arg); + + if (len > (MAX_PRINTF_LEN - 1)) { + buffer = new char[len + 1]; + if (!buffer) { + delete[] temp; + return 0; + } + va_start(arg, format); + vsnprintf(buffer, len + 1, format, arg); + va_end(arg); + } + text(buffer, len); + if (buffer != temp) { + delete[] buffer; + } + delete[] temp; + return len; +} + +#ifndef ESP32 +size_t AsyncWebSocketClient::printf_P(PGM_P formatP, ...) +{ + va_list arg; + va_start(arg, formatP); + char* temp = new char[MAX_PRINTF_LEN]; + if(!temp){ + va_end(arg); + return 0; + } + char* buffer = temp; + size_t len = vsnprintf_P(temp, MAX_PRINTF_LEN, formatP, arg); + va_end(arg); + + if (len > (MAX_PRINTF_LEN - 1)) { + buffer = new char[len + 1]; + if (!buffer) { + delete[] temp; + return 0; + } + va_start(arg, formatP); + vsnprintf_P(buffer, len + 1, formatP, arg); + va_end(arg); + } + text(buffer, len); + if (buffer != temp) { + delete[] buffer; + } + delete[] temp; + return len; +} +#endif + +namespace { +std::shared_ptr> makeSharedBuffer(const uint8_t *message, size_t len) +{ + auto buffer = std::make_shared>(len); + std::memcpy(buffer->data(), message, len); + return buffer; +} +} + +void AsyncWebSocketClient::text(AsyncWebSocketMessageBuffer * buffer) +{ + if (buffer) { + text(std::move(buffer->_buffer)); + delete buffer; + } +} + +void AsyncWebSocketClient::text(std::shared_ptr> buffer) +{ + _queueMessage(buffer); +} + +void AsyncWebSocketClient::text(const uint8_t *message, size_t len) +{ + text(makeSharedBuffer(message, len)); +} + +void AsyncWebSocketClient::text(const char *message, size_t len) +{ + text((const uint8_t *)message, len); +} + +void AsyncWebSocketClient::text(const char *message) +{ + text(message, strlen(message)); +} + +void AsyncWebSocketClient::text(const String &message) +{ + text(message.c_str(), message.length()); +} + +void AsyncWebSocketClient::text(const __FlashStringHelper *data) +{ + PGM_P p = reinterpret_cast(data); + + size_t n = 0; + while (1) + { + if (pgm_read_byte(p+n) == 0) break; + n += 1; + } + + char * message = (char*) malloc(n+1); + if(message) + { + memcpy_P(message, p, n); + message[n] = 0; + text(message, n); + free(message); + } +} + +void AsyncWebSocketClient::binary(AsyncWebSocketMessageBuffer * buffer) +{ + if (buffer) { + binary(std::move(buffer->_buffer)); + delete buffer; + } +} + +void AsyncWebSocketClient::binary(std::shared_ptr> buffer) +{ + _queueMessage(buffer, WS_BINARY); +} + +void AsyncWebSocketClient::binary(const uint8_t *message, size_t len) +{ + binary(makeSharedBuffer(message, len)); +} + +void AsyncWebSocketClient::binary(const char *message, size_t len) +{ + binary((const uint8_t *)message, len); +} + +void AsyncWebSocketClient::binary(const char *message) +{ + binary(message, strlen(message)); +} + +void AsyncWebSocketClient::binary(const String &message) +{ + binary(message.c_str(), message.length()); +} + +void AsyncWebSocketClient::binary(const __FlashStringHelper *data, size_t len) +{ + PGM_P p = reinterpret_cast(data); + char *message = (char*) malloc(len); + if (message) { + memcpy_P(message, p, len); + binary(message, len); + free(message); + } +} + +IPAddress AsyncWebSocketClient::remoteIP() const +{ + if (!_client) + return IPAddress((uint32_t)0U); + + return _client->remoteIP(); +} + +uint16_t AsyncWebSocketClient::remotePort() const +{ + if(!_client) + return 0; + + return _client->remotePort(); +} + + + +/* + * Async Web Socket - Each separate socket location + */ + +AsyncWebSocket::AsyncWebSocket(const String& url) + :_url(url) + ,_cNextId(1) + ,_enabled(true) +{ + _eventHandler = NULL; +} + +AsyncWebSocket::~AsyncWebSocket(){} + +void AsyncWebSocket::_handleEvent(AsyncWebSocketClient * client, AwsEventType type, void * arg, uint8_t *data, size_t len){ + if(_eventHandler != NULL){ + _eventHandler(this, client, type, arg, data, len); + } +} + +AsyncWebSocketClient *AsyncWebSocket::_newClient(AsyncWebServerRequest *request) +{ + _clients.emplace_back(request, this); + return &_clients.back(); +} + +bool AsyncWebSocket::availableForWriteAll() +{ + return std::none_of(std::begin(_clients), std::end(_clients), + [](const AsyncWebSocketClient &c){ return c.queueIsFull(); }); +} + +bool AsyncWebSocket::availableForWrite(uint32_t id) +{ + const auto iter = std::find_if(std::begin(_clients), std::end(_clients), + [id](const AsyncWebSocketClient &c){ return c.id() == id; }); + if (iter == std::end(_clients)) + return true; + return !iter->queueIsFull(); +} + +size_t AsyncWebSocket::count() const +{ + return std::count_if(std::begin(_clients), std::end(_clients), + [](const AsyncWebSocketClient &c){ return c.status() == WS_CONNECTED; }); +} + +AsyncWebSocketClient * AsyncWebSocket::client(uint32_t id) +{ + const auto iter = std::find_if(std::begin(_clients), std::end(_clients), + [id](const AsyncWebSocketClient &c){ return c.id() == id && c.status() == WS_CONNECTED; }); + if (iter == std::end(_clients)) + return nullptr; + + return &(*iter); +} + + +void AsyncWebSocket::close(uint32_t id, uint16_t code, const char * message) +{ + if (AsyncWebSocketClient *c = client(id)) + c->close(code, message); +} + +void AsyncWebSocket::closeAll(uint16_t code, const char * message) +{ + for (auto &c : _clients) + if (c.status() == WS_CONNECTED) + c.close(code, message); +} + +void AsyncWebSocket::cleanupClients(uint16_t maxClients) +{ + if (count() > maxClients) + _clients.front().close(); + + for (auto iter = std::begin(_clients); iter != std::end(_clients);) + { + if (iter->shouldBeDeleted()) + iter = _clients.erase(iter); + else + iter++; + } +} + +void AsyncWebSocket::ping(uint32_t id, const uint8_t *data, size_t len) +{ + if (AsyncWebSocketClient * c = client(id)) + c->ping(data, len); +} + +void AsyncWebSocket::pingAll(const uint8_t *data, size_t len) +{ + for (auto &c : _clients) + if (c.status() == WS_CONNECTED) + c.ping(data, len); +} + +void AsyncWebSocket::text(uint32_t id, const uint8_t *message, size_t len) +{ + if (AsyncWebSocketClient * c = client(id)) + c->text(makeSharedBuffer(message, len)); +} +void AsyncWebSocket::text(uint32_t id, const char *message, size_t len) +{ + text(id, (const uint8_t *)message, len); +} +void AsyncWebSocket::text(uint32_t id, const char * message) +{ + text(id, message, strlen(message)); +} +void AsyncWebSocket::text(uint32_t id, const String &message) +{ + text(id, message.c_str(), message.length()); +} +void AsyncWebSocket::text(uint32_t id, const __FlashStringHelper *data) +{ + PGM_P p = reinterpret_cast(data); + + size_t n = 0; + while (true) + { + if (pgm_read_byte(p+n) == 0) + break; + n += 1; + } + + char * message = (char*) malloc(n+1); + if (message) + { + memcpy_P(message, p, n); + message[n] = 0; + text(id, message, n); + free(message); + } +} +void AsyncWebSocket::text(uint32_t id, AsyncWebSocketMessageBuffer *buffer) +{ + if (buffer) { + text(id, std::move(buffer->_buffer)); + delete buffer; + } +} +void AsyncWebSocket::text(uint32_t id, std::shared_ptr> buffer) +{ + if (AsyncWebSocketClient *c = client(id)) + c->text(buffer); +} + +void AsyncWebSocket::textAll(const uint8_t *message, size_t len) +{ + textAll(makeSharedBuffer(message, len)); +} +void AsyncWebSocket::textAll(const char * message, size_t len) +{ + textAll((const uint8_t *)message, len); +} +void AsyncWebSocket::textAll(const char *message) +{ + textAll(message, strlen(message)); +} +void AsyncWebSocket::textAll(const String &message) +{ + textAll(message.c_str(), message.length()); +} +void AsyncWebSocket::textAll(const __FlashStringHelper *data) +{ + PGM_P p = reinterpret_cast(data); + + size_t n = 0; + while (1) + { + if (pgm_read_byte(p+n) == 0) break; + n += 1; + } + + char *message = (char*)malloc(n+1); + if(message) + { + memcpy_P(message, p, n); + message[n] = 0; + textAll(message, n); + free(message); + } +} +void AsyncWebSocket::textAll(AsyncWebSocketMessageBuffer * buffer) +{ + if (buffer) { + textAll(std::move(buffer->_buffer)); + delete buffer; + } +} + +void AsyncWebSocket::textAll(std::shared_ptr> buffer) +{ + for (auto &c : _clients) + if (c.status() == WS_CONNECTED) + c.text(buffer); +} + +void AsyncWebSocket::binary(uint32_t id, const uint8_t *message, size_t len) +{ + if (AsyncWebSocketClient *c = client(id)) + c->binary(makeSharedBuffer(message, len)); +} +void AsyncWebSocket::binary(uint32_t id, const char * message, size_t len) +{ + binary(id, (const uint8_t *)message, len); +} +void AsyncWebSocket::binary(uint32_t id, const char * message) +{ + binary(id, message, strlen(message)); +} +void AsyncWebSocket::binary(uint32_t id, const String &message) +{ + binary(id, message.c_str(), message.length()); +} +void AsyncWebSocket::binary(uint32_t id, const __FlashStringHelper *data, size_t len) +{ + PGM_P p = reinterpret_cast(data); + char *message = (char*) malloc(len); + if (message) + { + memcpy_P(message, p, len); + binary(id, message, len); + free(message); + } +} +void AsyncWebSocket::binary(uint32_t id, AsyncWebSocketMessageBuffer *buffer) +{ + if (buffer) { + binary(id, std::move(buffer->_buffer)); + delete buffer; + } +} +void AsyncWebSocket::binary(uint32_t id, std::shared_ptr> buffer) +{ + if (AsyncWebSocketClient *c = client(id)) + c->binary(buffer); +} + + +void AsyncWebSocket::binaryAll(const uint8_t *message, size_t len) +{ + binaryAll(makeSharedBuffer(message, len)); +} +void AsyncWebSocket::binaryAll(const char *message, size_t len) +{ + binaryAll((const uint8_t *)message, len); +} +void AsyncWebSocket::binaryAll(const char *message) +{ + binaryAll(message, strlen(message)); +} +void AsyncWebSocket::binaryAll(const String &message) +{ + binaryAll(message.c_str(), message.length()); +} +void AsyncWebSocket::binaryAll(const __FlashStringHelper *data, size_t len) +{ + PGM_P p = reinterpret_cast(data); + char * message = (char*) malloc(len); + if(message) + { + memcpy_P(message, p, len); + binaryAll(message, len); + free(message); + } +} +void AsyncWebSocket::binaryAll(AsyncWebSocketMessageBuffer * buffer) +{ + if (buffer) { + binaryAll(std::move(buffer->_buffer)); + delete buffer; + } +} +void AsyncWebSocket::binaryAll(std::shared_ptr> buffer) +{ + for (auto &c : _clients) + if (c.status() == WS_CONNECTED) + c.binary(buffer); +} + +size_t AsyncWebSocket::printf(uint32_t id, const char *format, ...){ + AsyncWebSocketClient * c = client(id); + if (c) + { + va_list arg; + va_start(arg, format); + size_t len = c->printf(format, arg); + va_end(arg); + return len; + } + return 0; +} + +size_t AsyncWebSocket::printfAll(const char *format, ...) +{ + va_list arg; + char *temp = new char[MAX_PRINTF_LEN]; + if (!temp) + return 0; + + va_start(arg, format); + size_t len = vsnprintf(temp, MAX_PRINTF_LEN, format, arg); + va_end(arg); + delete[] temp; + + std::shared_ptr> buffer = std::make_shared>(len); + + va_start(arg, format); + vsnprintf( (char *)buffer->data(), len + 1, format, arg); + va_end(arg); + + textAll(buffer); + return len; +} + +#ifndef ESP32 +size_t AsyncWebSocket::printf_P(uint32_t id, PGM_P formatP, ...){ + AsyncWebSocketClient * c = client(id); + if(c != NULL){ + va_list arg; + va_start(arg, formatP); + size_t len = c->printf_P(formatP, arg); + va_end(arg); + return len; + } + return 0; +} +#endif + +size_t AsyncWebSocket::printfAll_P(PGM_P formatP, ...) +{ + va_list arg; + char *temp = new char[MAX_PRINTF_LEN]; + if (!temp) + return 0; + + va_start(arg, formatP); + size_t len = vsnprintf_P(temp, MAX_PRINTF_LEN, formatP, arg); + va_end(arg); + delete[] temp; + + std::shared_ptr> buffer = std::make_shared>(len + 1); + + va_start(arg, formatP); + vsnprintf_P((char *)buffer->data(), len + 1, formatP, arg); + va_end(arg); + + textAll(buffer); + return len; +} + +const char __WS_STR_CONNECTION[] PROGMEM = { "Connection" }; +const char __WS_STR_UPGRADE[] PROGMEM = { "Upgrade" }; +const char __WS_STR_ORIGIN[] PROGMEM = { "Origin" }; +const char __WS_STR_COOKIE[] PROGMEM = { "Cookie" }; +const char __WS_STR_VERSION[] PROGMEM = { "Sec-WebSocket-Version" }; +const char __WS_STR_KEY[] PROGMEM = { "Sec-WebSocket-Key" }; +const char __WS_STR_PROTOCOL[] PROGMEM = { "Sec-WebSocket-Protocol" }; +const char __WS_STR_ACCEPT[] PROGMEM = { "Sec-WebSocket-Accept" }; +const char __WS_STR_UUID[] PROGMEM = { "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" }; + +#define WS_STR_CONNECTION FPSTR(__WS_STR_CONNECTION) +#define WS_STR_UPGRADE FPSTR(__WS_STR_UPGRADE) +#define WS_STR_ORIGIN FPSTR(__WS_STR_ORIGIN) +#define WS_STR_COOKIE FPSTR(__WS_STR_COOKIE) +#define WS_STR_VERSION FPSTR(__WS_STR_VERSION) +#define WS_STR_KEY FPSTR(__WS_STR_KEY) +#define WS_STR_PROTOCOL FPSTR(__WS_STR_PROTOCOL) +#define WS_STR_ACCEPT FPSTR(__WS_STR_ACCEPT) +#define WS_STR_UUID FPSTR(__WS_STR_UUID) + +bool AsyncWebSocket::canHandle(AsyncWebServerRequest *request){ + if(!_enabled) + return false; + + if(request->method() != HTTP_GET || !request->url().equals(_url) || !request->isExpectedRequestedConnType(RCT_WS)) + return false; + + request->addInterestingHeader(WS_STR_CONNECTION); + request->addInterestingHeader(WS_STR_UPGRADE); + request->addInterestingHeader(WS_STR_ORIGIN); + request->addInterestingHeader(WS_STR_COOKIE); + request->addInterestingHeader(WS_STR_VERSION); + request->addInterestingHeader(WS_STR_KEY); + request->addInterestingHeader(WS_STR_PROTOCOL); + return true; +} + +void AsyncWebSocket::handleRequest(AsyncWebServerRequest *request) +{ + if (!request->hasHeader(WS_STR_VERSION) || !request->hasHeader(WS_STR_KEY)) + { + request->send(400); + return; + } + if ((_username.length() && _password.length()) && !request->authenticate(_username.c_str(), _password.c_str())) + { + return request->requestAuthentication(); + } + if (_handshakeHandler != nullptr){ + if(!_handshakeHandler(request)){ + request->send(401); + return; + } + } + AsyncWebHeader* version = request->getHeader(WS_STR_VERSION); + if (version->value().toInt() != 13) + { + AsyncWebServerResponse *response = request->beginResponse(400); + response->addHeader(WS_STR_VERSION, F("13")); + request->send(response); + return; + } + AsyncWebHeader* key = request->getHeader(WS_STR_KEY); + AsyncWebServerResponse *response = new AsyncWebSocketResponse(key->value(), this); + if (request->hasHeader(WS_STR_PROTOCOL)) + { + AsyncWebHeader* protocol = request->getHeader(WS_STR_PROTOCOL); + //ToDo: check protocol + response->addHeader(WS_STR_PROTOCOL, protocol->value()); + } + request->send(response); +} + +AsyncWebSocketMessageBuffer * AsyncWebSocket::makeBuffer(size_t size) +{ + AsyncWebSocketMessageBuffer * buffer = new AsyncWebSocketMessageBuffer(size); + if (buffer->length() != size) + { + delete buffer; + return nullptr; + } else { + return buffer; + } +} + +AsyncWebSocketMessageBuffer * AsyncWebSocket::makeBuffer(uint8_t * data, size_t size) +{ + AsyncWebSocketMessageBuffer * buffer = new AsyncWebSocketMessageBuffer(data, size); + if (buffer->length() != size) + { + delete buffer; + return nullptr; + } else { + return buffer; + } +} + +/* + * Response to Web Socket request - sends the authorization and detaches the TCP Client from the web server + * Authentication code from https://github.com/Links2004/arduinoWebSockets/blob/master/src/WebSockets.cpp#L480 + */ + +AsyncWebSocketResponse::AsyncWebSocketResponse(const String& key, AsyncWebSocket *server) +{ + _server = server; + _code = 101; + _sendContentLength = false; + + uint8_t * hash = (uint8_t*)malloc(20); + if(hash == NULL) + { + _state = RESPONSE_FAILED; + return; + } + char * buffer = (char *) malloc(33); + if(buffer == NULL) + { + free(hash); + _state = RESPONSE_FAILED; + return; + } +#ifdef ESP8266 + sha1(key + WS_STR_UUID, hash); +#else + (String&)key += WS_STR_UUID; + mbedtls_sha1_context ctx; + mbedtls_sha1_init(&ctx); +#if ESP_IDF_VERSION_MAJOR == 5 + mbedtls_sha1_starts(&ctx); + mbedtls_sha1_update(&ctx, (const unsigned char*)key.c_str(), key.length()); + mbedtls_sha1_finish(&ctx, hash); +#else + mbedtls_sha1_starts_ret(&ctx); + mbedtls_sha1_update_ret(&ctx, (const unsigned char*)key.c_str(), key.length()); + mbedtls_sha1_finish_ret(&ctx, hash); +#endif + mbedtls_sha1_free(&ctx); +#endif + base64_encodestate _state; + base64_init_encodestate(&_state); + int len = base64_encode_block((const char *) hash, 20, buffer, &_state); + len = base64_encode_blockend((buffer + len), &_state); + addHeader(WS_STR_CONNECTION, WS_STR_UPGRADE); + addHeader(WS_STR_UPGRADE, F("websocket")); + addHeader(WS_STR_ACCEPT,buffer); + free(buffer); + free(hash); +} + +void AsyncWebSocketResponse::_respond(AsyncWebServerRequest *request) +{ + if(_state == RESPONSE_FAILED) + { + request->client()->close(true); + return; + } + String out = _assembleHead(request->version()); + request->client()->write(out.c_str(), _headLength); + _state = RESPONSE_WAIT_ACK; +} + +size_t AsyncWebSocketResponse::_ack(AsyncWebServerRequest *request, size_t len, uint32_t time) +{ + (void)time; + + if(len) + _server->_newClient(request); + + return 0; +} diff --git a/libraries/ESPAsyncWebServer/src/AsyncWebSocket.h b/libraries/ESPAsyncWebServer/src/AsyncWebSocket.h new file mode 100644 index 000000000..ab182ea9f --- /dev/null +++ b/libraries/ESPAsyncWebServer/src/AsyncWebSocket.h @@ -0,0 +1,354 @@ +/* + Asynchronous WebServer library for Espressif MCUs + + Copyright (c) 2016 Hristo Gochkov. All rights reserved. + This file is part of the esp8266 core for Arduino environment. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ +#ifndef ASYNCWEBSOCKET_H_ +#define ASYNCWEBSOCKET_H_ + +#include +#ifdef ESP32 +#include +#ifndef WS_MAX_QUEUED_MESSAGES +#define WS_MAX_QUEUED_MESSAGES 32 +#endif +#else +#include +#ifndef WS_MAX_QUEUED_MESSAGES +#define WS_MAX_QUEUED_MESSAGES 8 +#endif +#endif +#include + +#include "AsyncWebSynchronization.h" + +#include +#include +#include + +#ifdef ESP8266 +#include +#ifdef CRYPTO_HASH_h // include Hash.h from espressif framework if the first include was from the crypto library +#include <../src/Hash.h> +#endif +#endif + +#ifdef ESP32 +#define DEFAULT_MAX_WS_CLIENTS 8 +#else +#define DEFAULT_MAX_WS_CLIENTS 4 +#endif + +class AsyncWebSocket; +class AsyncWebSocketResponse; +class AsyncWebSocketClient; +class AsyncWebSocketControl; + +typedef struct { + /** Message type as defined by enum AwsFrameType. + * Note: Applications will only see WS_TEXT and WS_BINARY. + * All other types are handled by the library. */ + uint8_t message_opcode; + /** Frame number of a fragmented message. */ + uint32_t num; + /** Is this the last frame in a fragmented message ?*/ + uint8_t final; + /** Is this frame masked? */ + uint8_t masked; + /** Message type as defined by enum AwsFrameType. + * This value is the same as message_opcode for non-fragmented + * messages, but may also be WS_CONTINUATION in a fragmented message. */ + uint8_t opcode; + /** Length of the current frame. + * This equals the total length of the message if num == 0 && final == true */ + uint64_t len; + /** Mask key */ + uint8_t mask[4]; + /** Offset of the data inside the current frame. */ + uint64_t index; +} AwsFrameInfo; + +typedef enum { WS_DISCONNECTED, WS_CONNECTED, WS_DISCONNECTING } AwsClientStatus; +typedef enum { WS_CONTINUATION, WS_TEXT, WS_BINARY, WS_DISCONNECT = 0x08, WS_PING, WS_PONG } AwsFrameType; +typedef enum { WS_MSG_SENDING, WS_MSG_SENT, WS_MSG_ERROR } AwsMessageStatus; +typedef enum { WS_EVT_CONNECT, WS_EVT_DISCONNECT, WS_EVT_PONG, WS_EVT_ERROR, WS_EVT_DATA } AwsEventType; + +class AsyncWebSocketMessageBuffer { + friend AsyncWebSocket; + friend AsyncWebSocketClient; + + private: + std::shared_ptr> _buffer; + + public: + AsyncWebSocketMessageBuffer(); + AsyncWebSocketMessageBuffer(size_t size); + AsyncWebSocketMessageBuffer(uint8_t* data, size_t size); + ~AsyncWebSocketMessageBuffer(); + bool reserve(size_t size); + uint8_t* get() { return _buffer->data(); } + size_t length() const { return _buffer->size(); } +}; + +class AsyncWebSocketMessage +{ +private: + std::shared_ptr> _WSbuffer; + uint8_t _opcode{WS_TEXT}; + bool _mask{false}; + AwsMessageStatus _status{WS_MSG_ERROR}; + size_t _sent{}; + size_t _ack{}; + size_t _acked{}; + +public: + AsyncWebSocketMessage(std::shared_ptr> buffer, uint8_t opcode=WS_TEXT, bool mask=false); + + bool finished() const { return _status != WS_MSG_SENDING; } + bool betweenFrames() const { return _acked == _ack; } + + void ack(size_t len, uint32_t time); + size_t send(AsyncClient *client); +}; + +class AsyncWebSocketClient { + private: + AsyncClient *_client; + AsyncWebSocket *_server; + uint32_t _clientId; + AwsClientStatus _status; + + AsyncWebLock _lock; + + std::deque _controlQueue; + std::deque _messageQueue; + bool closeWhenFull = true; + + uint8_t _pstate; + AwsFrameInfo _pinfo; + + uint32_t _lastMessageTime; + uint32_t _keepAlivePeriod; + + void _queueControl(uint8_t opcode, const uint8_t *data=NULL, size_t len=0, bool mask=false); + void _queueMessage(std::shared_ptr> buffer, uint8_t opcode=WS_TEXT, bool mask=false); + void _runQueue(); + void _clearQueue(); + + public: + void *_tempObject; + + AsyncWebSocketClient(AsyncWebServerRequest *request, AsyncWebSocket *server); + ~AsyncWebSocketClient(); + + //client id increments for the given server + uint32_t id() const { return _clientId; } + AwsClientStatus status() const { return _status; } + AsyncClient* client() { return _client; } + const AsyncClient* client() const { return _client; } + AsyncWebSocket *server(){ return _server; } + const AsyncWebSocket *server() const { return _server; } + AwsFrameInfo const &pinfo() const { return _pinfo; } + + // - If "true" (default), the connection will be closed if the message queue is full. + // This is the default behavior in yubox-node-org, which is not silently discarding messages but instead closes the connection. + // The big issue with this behavior is that is can cause the UI to automatically re-create a new WS connection, which can be filled again, + // and so on, causing a resource exhaustion. + // + // - If "false", the incoming message will be discarded if the queue is full. + // This is the default behavior in the original ESPAsyncWebServer library from me-no-dev. + // This behavior allows the best performance at the expense of unreliable message delivery in case the queue is full (some messages may be lost). + // + // - In any case, when the queue is full, a message is logged. + // - IT is recommended to use the methods queueIsFull(), availableForWriteAll(), availableForWrite(clientId) to check if the queue is full before sending a message. + // + // Usage: + // - can be set in the onEvent listener when connecting (event type is: WS_EVT_CONNECT) + // + // Use cases:, + // - if using websocket to send logging messages, maybe some loss is acceptable. + // - But if using websocket to send UI update messages, maybe the connection should be closed and the UI redrawn. + void setCloseClientOnQueueFull(bool close) { closeWhenFull = close; } + bool willCloseClientOnQueueFull() const { return closeWhenFull; } + + IPAddress remoteIP() const; + uint16_t remotePort() const; + + bool shouldBeDeleted() const { return !_client; } + + //control frames + void close(uint16_t code=0, const char * message=NULL); + void ping(const uint8_t *data=NULL, size_t len=0); + + //set auto-ping period in seconds. disabled if zero (default) + void keepAlivePeriod(uint16_t seconds){ + _keepAlivePeriod = seconds * 1000; + } + uint16_t keepAlivePeriod(){ + return (uint16_t)(_keepAlivePeriod / 1000); + } + + //data packets + void message(std::shared_ptr> buffer, uint8_t opcode=WS_TEXT, bool mask=false) { _queueMessage(buffer, opcode, mask); } + bool queueIsFull() const; + size_t queueLen() const; + + size_t printf(const char *format, ...) __attribute__ ((format (printf, 2, 3))); +#ifndef ESP32 + size_t printf_P(PGM_P formatP, ...) __attribute__ ((format (printf, 2, 3))); +#endif + + void text(std::shared_ptr> buffer); + void text(const uint8_t *message, size_t len); + void text(const char *message, size_t len); + void text(const char *message); + void text(const String &message); + void text(const __FlashStringHelper *message); + void text(AsyncWebSocketMessageBuffer *buffer); + + void binary(std::shared_ptr> buffer); + void binary(const uint8_t *message, size_t len); + void binary(const char * message, size_t len); + void binary(const char * message); + void binary(const String &message); + void binary(const __FlashStringHelper *message, size_t len); + void binary(AsyncWebSocketMessageBuffer *buffer); + + bool canSend() const; + + //system callbacks (do not call) + void _onAck(size_t len, uint32_t time); + void _onError(int8_t); + void _onPoll(); + void _onTimeout(uint32_t time); + void _onDisconnect(); + void _onData(void *pbuf, size_t plen); +}; + +typedef std::function AwsHandshakeHandler; +typedef std::function AwsEventHandler; + +//WebServer Handler implementation that plays the role of a socket server +class AsyncWebSocket: public AsyncWebHandler { + private: + String _url; + std::list _clients; + uint32_t _cNextId; + AwsEventHandler _eventHandler; + AwsHandshakeHandler _handshakeHandler; + bool _enabled; + AsyncWebLock _lock; + + public: + AsyncWebSocket(const String& url); + ~AsyncWebSocket(); + const char * url() const { return _url.c_str(); } + void enable(bool e){ _enabled = e; } + bool enabled() const { return _enabled; } + bool availableForWriteAll(); + bool availableForWrite(uint32_t id); + + size_t count() const; + AsyncWebSocketClient * client(uint32_t id); + bool hasClient(uint32_t id){ return client(id) != NULL; } + + void close(uint32_t id, uint16_t code=0, const char * message=NULL); + void closeAll(uint16_t code=0, const char * message=NULL); + void cleanupClients(uint16_t maxClients = DEFAULT_MAX_WS_CLIENTS); + + void ping(uint32_t id, const uint8_t *data=NULL, size_t len=0); + void pingAll(const uint8_t *data=NULL, size_t len=0); // done + + void text(uint32_t id, const uint8_t * message, size_t len); + void text(uint32_t id, const char *message, size_t len); + void text(uint32_t id, const char *message); + void text(uint32_t id, const String &message); + void text(uint32_t id, const __FlashStringHelper *message); + void text(uint32_t id, AsyncWebSocketMessageBuffer *buffer); + void text(uint32_t id, std::shared_ptr> buffer); + + void textAll(const uint8_t *message, size_t len); + void textAll(const char * message, size_t len); + void textAll(const char * message); + void textAll(const String &message); + void textAll(const __FlashStringHelper *message); + void textAll(AsyncWebSocketMessageBuffer *buffer); + void textAll(std::shared_ptr> buffer); + + void binary(uint32_t id, const uint8_t *message, size_t len); + void binary(uint32_t id, const char *message, size_t len); + void binary(uint32_t id, const char *message); + void binary(uint32_t id, const String &message); + void binary(uint32_t id, const __FlashStringHelper *message, size_t len); + void binary(uint32_t id, AsyncWebSocketMessageBuffer *buffer); + void binary(uint32_t id, std::shared_ptr> buffer); + + void binaryAll(const uint8_t *message, size_t len); + void binaryAll(const char *message, size_t len); + void binaryAll(const char *message); + void binaryAll(const String &message); + void binaryAll(const __FlashStringHelper *message, size_t len); + void binaryAll(AsyncWebSocketMessageBuffer *buffer); + void binaryAll(std::shared_ptr> buffer); + + size_t printf(uint32_t id, const char *format, ...) __attribute__ ((format (printf, 3, 4))); + size_t printfAll(const char *format, ...) __attribute__ ((format (printf, 2, 3))); +#ifndef ESP32 + size_t printf_P(uint32_t id, PGM_P formatP, ...) __attribute__ ((format (printf, 3, 4))); +#endif + size_t printfAll_P(PGM_P formatP, ...) __attribute__ ((format (printf, 2, 3))); + + //event listener + void onEvent(AwsEventHandler handler){ + _eventHandler = handler; + } + + // Handshake Handler + void handleHandshake(AwsHandshakeHandler handler){ + _handshakeHandler = handler; + } + + //system callbacks (do not call) + uint32_t _getNextId(){ return _cNextId++; } + AsyncWebSocketClient *_newClient(AsyncWebServerRequest *request); + void _handleEvent(AsyncWebSocketClient * client, AwsEventType type, void * arg, uint8_t *data, size_t len); + virtual bool canHandle(AsyncWebServerRequest *request) override final; + virtual void handleRequest(AsyncWebServerRequest *request) override final; + + + // messagebuffer functions/objects. + AsyncWebSocketMessageBuffer * makeBuffer(size_t size = 0); + AsyncWebSocketMessageBuffer * makeBuffer(uint8_t * data, size_t size); + + const std::list &getClients() const { return _clients; } +}; + +//WebServer response to authenticate the socket and detach the tcp client from the web server request +class AsyncWebSocketResponse: public AsyncWebServerResponse { + private: + String _content; + AsyncWebSocket *_server; + public: + AsyncWebSocketResponse(const String& key, AsyncWebSocket *server); + void _respond(AsyncWebServerRequest *request); + size_t _ack(AsyncWebServerRequest *request, size_t len, uint32_t time); + bool _sourceValid() const { return true; } +}; + + +#endif /* ASYNCWEBSOCKET_H_ */ diff --git a/libraries/ESPAsyncWebServer/src/AsyncWebSynchronization.h b/libraries/ESPAsyncWebServer/src/AsyncWebSynchronization.h new file mode 100644 index 000000000..0ff8ab63b --- /dev/null +++ b/libraries/ESPAsyncWebServer/src/AsyncWebSynchronization.h @@ -0,0 +1,134 @@ +#ifndef ASYNCWEBSYNCHRONIZATION_H_ +#define ASYNCWEBSYNCHRONIZATION_H_ + +// Synchronisation is only available on ESP32, as the ESP8266 isn't using FreeRTOS by default + +#include + +#ifdef ESP32 + +// This is the ESP32 version of the Sync Lock, using the FreeRTOS Semaphore +// Modified 'AsyncWebLock' to just only use mutex since pxCurrentTCB is not +// always available. According to example by Arjan Filius, changed name, +// added unimplemented version for ESP8266 +class AsyncPlainLock +{ +private: + SemaphoreHandle_t _lock; + +public: + AsyncPlainLock() { + _lock = xSemaphoreCreateBinary(); + // In this fails, the system is likely that much out of memory that + // we should abort anyways. If assertions are disabled, nothing is lost.. + assert(_lock); + xSemaphoreGive(_lock); + } + + ~AsyncPlainLock() { + vSemaphoreDelete(_lock); + } + + bool lock() const { + xSemaphoreTake(_lock, portMAX_DELAY); + return true; + } + + void unlock() const { + xSemaphoreGive(_lock); + } +}; + +// This is the ESP32 version of the Sync Lock, using the FreeRTOS Semaphore +class AsyncWebLock +{ +private: + SemaphoreHandle_t _lock; + mutable TaskHandle_t _lockedBy{}; + +public: + AsyncWebLock() + { + _lock = xSemaphoreCreateBinary(); + // In this fails, the system is likely that much out of memory that + // we should abort anyways. If assertions are disabled, nothing is lost.. + assert(_lock); + _lockedBy = NULL; + xSemaphoreGive(_lock); + } + + ~AsyncWebLock() { + vSemaphoreDelete(_lock); + } + + bool lock() const { + const auto currentTask = xTaskGetCurrentTaskHandle(); + if (_lockedBy != currentTask) { + xSemaphoreTake(_lock, portMAX_DELAY); + _lockedBy = currentTask; + return true; + } + return false; + } + + void unlock() const { + _lockedBy = NULL; + xSemaphoreGive(_lock); + } +}; + +#else + +// This is the 8266 version of the Sync Lock which is currently unimplemented +class AsyncWebLock +{ + +public: + AsyncWebLock() { + } + + ~AsyncWebLock() { + } + + bool lock() const { + return false; + } + + void unlock() const { + } +}; + +// Same for AsyncPlainLock, for ESP8266 this is just the unimplemented version above. +using AsyncPlainLock = AsyncWebLock; + +#endif + +class AsyncWebLockGuard +{ +private: + const AsyncWebLock *_lock; + +public: + AsyncWebLockGuard(const AsyncWebLock &l) { + if (l.lock()) { + _lock = &l; + } else { + _lock = NULL; + } + } + + ~AsyncWebLockGuard() { + if (_lock) { + _lock->unlock(); + } + } + + void unlock() { + if (_lock) { + _lock->unlock(); + _lock = NULL; + } + } +}; + +#endif // ASYNCWEBSYNCHRONIZATION_H_ diff --git a/libraries/ESPAsyncWebServer/src/ESPAsyncWebServer.h b/libraries/ESPAsyncWebServer/src/ESPAsyncWebServer.h new file mode 100644 index 000000000..e0cc06830 --- /dev/null +++ b/libraries/ESPAsyncWebServer/src/ESPAsyncWebServer.h @@ -0,0 +1,501 @@ +/* + Asynchronous WebServer library for Espressif MCUs + + Copyright (c) 2016 Hristo Gochkov. All rights reserved. + This file is part of the esp8266 core for Arduino environment. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ +#ifndef _ESPAsyncWebServer_H_ +#define _ESPAsyncWebServer_H_ + +#include "Arduino.h" + +#include +#include +#include +#include "FS.h" + +#include "StringArray.h" + +#ifdef ESP32 +#include +#include +#elif defined(ESP8266) +#include +#include +#else +#error Platform not supported +#endif + +#define ASYNCWEBSERVER_VERSION "2.10.4" +#define ASYNCWEBSERVER_VERSION_MAJOR 2 +#define ASYNCWEBSERVER_VERSION_MINOR 10 +#define ASYNCWEBSERVER_VERSION_REVISION 4 +#define ASYNCWEBSERVER_FORK_mathieucarbou + +#ifdef ASYNCWEBSERVER_REGEX +#define ASYNCWEBSERVER_REGEX_ATTRIBUTE +#else +#define ASYNCWEBSERVER_REGEX_ATTRIBUTE __attribute__((warning("ASYNCWEBSERVER_REGEX not defined"))) +#endif + +class AsyncWebServer; +class AsyncWebServerRequest; +class AsyncWebServerResponse; +class AsyncWebHeader; +class AsyncWebParameter; +class AsyncWebRewrite; +class AsyncWebHandler; +class AsyncStaticWebHandler; +class AsyncCallbackWebHandler; +class AsyncResponseStream; + +#ifndef WEBSERVER_H +typedef enum { + HTTP_GET = 0b00000001, + HTTP_POST = 0b00000010, + HTTP_DELETE = 0b00000100, + HTTP_PUT = 0b00001000, + HTTP_PATCH = 0b00010000, + HTTP_HEAD = 0b00100000, + HTTP_OPTIONS = 0b01000000, + HTTP_ANY = 0b01111111, +} WebRequestMethod; +#endif + +#ifndef HAVE_FS_FILE_OPEN_MODE +namespace fs { + class FileOpenMode { + public: + static const char *read; + static const char *write; + static const char *append; + }; +}; +#else +#include "FileOpenMode.h" +#endif + +//if this value is returned when asked for data, packet will not be sent and you will be asked for data again +#define RESPONSE_TRY_AGAIN 0xFFFFFFFF + +typedef uint8_t WebRequestMethodComposite; +typedef std::function ArDisconnectHandler; + +/* + * PARAMETER :: Chainable object to hold GET/POST and FILE parameters + * */ + +class AsyncWebParameter { + private: + String _name; + String _value; + size_t _size; + bool _isForm; + bool _isFile; + + public: + + AsyncWebParameter(const String& name, const String& value, bool form=false, bool file=false, size_t size=0): _name(name), _value(value), _size(size), _isForm(form), _isFile(file){} + const String& name() const { return _name; } + const String& value() const { return _value; } + size_t size() const { return _size; } + bool isPost() const { return _isForm; } + bool isFile() const { return _isFile; } +}; + +/* + * HEADER :: Chainable object to hold the headers + * */ + +class AsyncWebHeader { + private: + String _name; + String _value; + + public: + AsyncWebHeader() = default; + AsyncWebHeader(const AsyncWebHeader &) = default; + + AsyncWebHeader(const String& name, const String& value): _name(name), _value(value){} + AsyncWebHeader(const String& data): _name(), _value(){ + if(!data) return; + int index = data.indexOf(':'); + if (index < 0) return; + _name = data.substring(0, index); + _value = data.substring(index + 2); + } + + AsyncWebHeader &operator=(const AsyncWebHeader &) = default; + + const String& name() const { return _name; } + const String& value() const { return _value; } + String toString() const { return _name + F(": ") + _value + F("\r\n"); } +}; + +/* + * REQUEST :: Each incoming Client is wrapped inside a Request and both live together until disconnect + * */ + +typedef enum { RCT_NOT_USED = -1, RCT_DEFAULT = 0, RCT_HTTP, RCT_WS, RCT_EVENT, RCT_MAX } RequestedConnectionType; + +typedef std::function AwsResponseFiller; +typedef std::function AwsTemplateProcessor; + +class AsyncWebServerRequest { + using File = fs::File; + using FS = fs::FS; + friend class AsyncWebServer; + friend class AsyncCallbackWebHandler; + private: + AsyncClient* _client; + AsyncWebServer* _server; + AsyncWebHandler* _handler; + AsyncWebServerResponse* _response; + std::vector _interestingHeaders; + ArDisconnectHandler _onDisconnectfn; + + String _temp; + uint8_t _parseState; + + uint8_t _version; + WebRequestMethodComposite _method; + String _url; + String _host; + String _contentType; + String _boundary; + String _authorization; + RequestedConnectionType _reqconntype; + void _removeNotInterestingHeaders(); + bool _isDigest; + bool _isMultipart; + bool _isPlainPost; + bool _expectingContinue; + size_t _contentLength; + size_t _parsedLength; + + std::list _headers; + AlternativeLinkedList _params; + std::vector _pathParams; + + uint8_t _multiParseState; + uint8_t _boundaryPosition; + size_t _itemStartIndex; + size_t _itemSize; + String _itemName; + String _itemFilename; + String _itemType; + String _itemValue; + uint8_t *_itemBuffer; + size_t _itemBufferIndex; + bool _itemIsFile; + + void _onPoll(); + void _onAck(size_t len, uint32_t time); + void _onError(int8_t error); + void _onTimeout(uint32_t time); + void _onDisconnect(); + void _onData(void *buf, size_t len); + + void _addParam(AsyncWebParameter*); + void _addPathParam(const char *param); + + bool _parseReqHead(); + bool _parseReqHeader(); + void _parseLine(); + void _parsePlainPostChar(uint8_t data); + void _parseMultipartPostByte(uint8_t data, bool last); + void _addGetParams(const String& params); + + void _handleUploadStart(); + void _handleUploadByte(uint8_t data, bool last); + void _handleUploadEnd(); + + public: + File _tempFile; + void *_tempObject; + + AsyncWebServerRequest(AsyncWebServer*, AsyncClient*); + ~AsyncWebServerRequest(); + + AsyncClient* client(){ return _client; } + uint8_t version() const { return _version; } + WebRequestMethodComposite method() const { return _method; } + const String& url() const { return _url; } + const String& host() const { return _host; } + const String& contentType() const { return _contentType; } + size_t contentLength() const { return _contentLength; } + bool multipart() const { return _isMultipart; } + const __FlashStringHelper *methodToString() const; + const __FlashStringHelper *requestedConnTypeToString() const; + RequestedConnectionType requestedConnType() const { return _reqconntype; } + bool isExpectedRequestedConnType(RequestedConnectionType erct1, RequestedConnectionType erct2 = RCT_NOT_USED, RequestedConnectionType erct3 = RCT_NOT_USED); + void onDisconnect (ArDisconnectHandler fn); + + //hash is the string representation of: + // base64(user:pass) for basic or + // user:realm:md5(user:realm:pass) for digest + bool authenticate(const char * hash); + bool authenticate(const char * username, const char * password, const char * realm = NULL, bool passwordIsHash = false); + void requestAuthentication(const char * realm = NULL, bool isDigest = true); + + void setHandler(AsyncWebHandler *handler){ _handler = handler; } + void addInterestingHeader(const String& name); + + void redirect(const String& url); + + void send(AsyncWebServerResponse *response); + void send(int code, const String& contentType=String(), const String& content=String()); + void send(FS &fs, const String& path, const String& contentType=String(), bool download=false, AwsTemplateProcessor callback=nullptr); + void send(File content, const String& path, const String& contentType=String(), bool download=false, AwsTemplateProcessor callback=nullptr); + void send(Stream &stream, const String& contentType, size_t len, AwsTemplateProcessor callback=nullptr); + void send(const String& contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback=nullptr); + void sendChunked(const String& contentType, AwsResponseFiller callback, AwsTemplateProcessor templateCallback=nullptr); + void send_P(int code, const String& contentType, const uint8_t * content, size_t len, AwsTemplateProcessor callback=nullptr); + void send_P(int code, const String& contentType, PGM_P content, AwsTemplateProcessor callback=nullptr); + + AsyncWebServerResponse *beginResponse(int code, const String& contentType=String(), const String& content=String()); + AsyncWebServerResponse *beginResponse(FS &fs, const String& path, const String& contentType=String(), bool download=false, AwsTemplateProcessor callback=nullptr); + AsyncWebServerResponse *beginResponse(File content, const String& path, const String& contentType=String(), bool download=false, AwsTemplateProcessor callback=nullptr); + AsyncWebServerResponse *beginResponse(Stream &stream, const String& contentType, size_t len, AwsTemplateProcessor callback=nullptr); + AsyncWebServerResponse *beginResponse(const String& contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback=nullptr); + AsyncWebServerResponse *beginChunkedResponse(const String& contentType, AwsResponseFiller callback, AwsTemplateProcessor templateCallback=nullptr); + AsyncResponseStream *beginResponseStream(const String& contentType, size_t bufferSize=1460); + AsyncWebServerResponse *beginResponse_P(int code, const String& contentType, const uint8_t * content, size_t len, AwsTemplateProcessor callback=nullptr); + AsyncWebServerResponse *beginResponse_P(int code, const String& contentType, PGM_P content, AwsTemplateProcessor callback=nullptr); + + size_t headers() const; // get header count + bool hasHeader(const String& name) const; // check if header exists + bool hasHeader(const __FlashStringHelper * data) const; // check if header exists + + AsyncWebHeader* getHeader(const String& name); + const AsyncWebHeader* getHeader(const String& name) const; + AsyncWebHeader* getHeader(const __FlashStringHelper * data); + const AsyncWebHeader* getHeader(const __FlashStringHelper * data) const; + AsyncWebHeader* getHeader(size_t num); + const AsyncWebHeader* getHeader(size_t num) const; + + size_t params() const; // get arguments count + bool hasParam(const String& name, bool post=false, bool file=false) const; + bool hasParam(const __FlashStringHelper * data, bool post=false, bool file=false) const; + + AsyncWebParameter* getParam(const String& name, bool post=false, bool file=false) const; + AsyncWebParameter* getParam(const __FlashStringHelper * data, bool post, bool file) const; + AsyncWebParameter* getParam(size_t num) const; + + size_t args() const { return params(); } // get arguments count + const String& arg(const String& name) const; // get request argument value by name + const String& arg(const __FlashStringHelper * data) const; // get request argument value by F(name) + const String& arg(size_t i) const; // get request argument value by number + const String& argName(size_t i) const; // get request argument name by number + bool hasArg(const char* name) const; // check if argument exists + bool hasArg(const __FlashStringHelper * data) const; // check if F(argument) exists + + const String& ASYNCWEBSERVER_REGEX_ATTRIBUTE pathArg(size_t i) const; + + const String& header(const char* name) const;// get request header value by name + const String& header(const __FlashStringHelper * data) const;// get request header value by F(name) + const String& header(size_t i) const; // get request header value by number + const String& headerName(size_t i) const; // get request header name by number + String urlDecode(const String& text) const; +}; + +/* + * FILTER :: Callback to filter AsyncWebRewrite and AsyncWebHandler (done by the Server) + * */ + +typedef std::function ArRequestFilterFunction; + +bool ON_STA_FILTER(AsyncWebServerRequest *request); + +bool ON_AP_FILTER(AsyncWebServerRequest *request); + +/* + * REWRITE :: One instance can be handle any Request (done by the Server) + * */ + +class AsyncWebRewrite { + protected: + String _from; + String _toUrl; + String _params; + ArRequestFilterFunction _filter; + public: + AsyncWebRewrite(const char* from, const char* to): _from(from), _toUrl(to), _params(String()), _filter(NULL){ + int index = _toUrl.indexOf('?'); + if (index > 0) { + _params = _toUrl.substring(index +1); + _toUrl = _toUrl.substring(0, index); + } + } + virtual ~AsyncWebRewrite(){} + AsyncWebRewrite& setFilter(ArRequestFilterFunction fn) { _filter = fn; return *this; } + bool filter(AsyncWebServerRequest *request) const { return _filter == NULL || _filter(request); } + const String& from(void) const { return _from; } + const String& toUrl(void) const { return _toUrl; } + const String& params(void) const { return _params; } + virtual bool match(AsyncWebServerRequest *request) { return from() == request->url() && filter(request); } +}; + +/* + * HANDLER :: One instance can be attached to any Request (done by the Server) + * */ + +class AsyncWebHandler { + protected: + ArRequestFilterFunction _filter; + String _username; + String _password; + public: + AsyncWebHandler():_username(""), _password(""){} + AsyncWebHandler& setFilter(ArRequestFilterFunction fn) { _filter = fn; return *this; } + AsyncWebHandler& setAuthentication(const char *username, const char *password){ _username = String(username);_password = String(password); return *this; }; + AsyncWebHandler& setAuthentication(const String& username, const String& password){ _username = username;_password = password; return *this; }; + bool filter(AsyncWebServerRequest *request){ return _filter == NULL || _filter(request); } + virtual ~AsyncWebHandler(){} + virtual bool canHandle(AsyncWebServerRequest *request __attribute__((unused))){ + return false; + } + virtual void handleRequest(AsyncWebServerRequest *request __attribute__((unused))){} + virtual void handleUpload(AsyncWebServerRequest *request __attribute__((unused)), const String& filename __attribute__((unused)), size_t index __attribute__((unused)), uint8_t *data __attribute__((unused)), size_t len __attribute__((unused)), bool final __attribute__((unused))){} + virtual void handleBody(AsyncWebServerRequest *request __attribute__((unused)), uint8_t *data __attribute__((unused)), size_t len __attribute__((unused)), size_t index __attribute__((unused)), size_t total __attribute__((unused))){} + virtual bool isRequestHandlerTrivial(){return true;} +}; + +/* + * RESPONSE :: One instance is created for each Request (attached by the Handler) + * */ + +typedef enum { + RESPONSE_SETUP, RESPONSE_HEADERS, RESPONSE_CONTENT, RESPONSE_WAIT_ACK, RESPONSE_END, RESPONSE_FAILED +} WebResponseState; + +class AsyncWebServerResponse { + protected: + int _code; + std::list _headers; + String _contentType; + size_t _contentLength; + bool _sendContentLength; + bool _chunked; + size_t _headLength; + size_t _sentLength; + size_t _ackedLength; + size_t _writtenLength; + WebResponseState _state; + const char* _responseCodeToString(int code); +public: + static const __FlashStringHelper *responseCodeToString(int code); + + public: + AsyncWebServerResponse(); + virtual ~AsyncWebServerResponse(); + virtual void setCode(int code); + virtual void setContentLength(size_t len); + virtual void setContentType(const String& type); + virtual void addHeader(const String& name, const String& value); + virtual String _assembleHead(uint8_t version); + virtual bool _started() const; + virtual bool _finished() const; + virtual bool _failed() const; + virtual bool _sourceValid() const; + virtual void _respond(AsyncWebServerRequest *request); + virtual size_t _ack(AsyncWebServerRequest *request, size_t len, uint32_t time); +}; + +/* + * SERVER :: One instance + * */ + +typedef std::function ArRequestHandlerFunction; +typedef std::function ArUploadHandlerFunction; +typedef std::function ArBodyHandlerFunction; + +class AsyncWebServer { + protected: + AsyncServer _server; + AlternativeLinkedList _rewrites; + AlternativeLinkedList _handlers; + AsyncCallbackWebHandler* _catchAllHandler; + + public: + AsyncWebServer(uint16_t port); + ~AsyncWebServer(); + + void begin(); + void end(); + +#if ASYNC_TCP_SSL_ENABLED + void onSslFileRequest(AcSSlFileHandler cb, void* arg); + void beginSecure(const char *cert, const char *private_key_file, const char *password); +#endif + + AsyncWebRewrite& addRewrite(AsyncWebRewrite* rewrite); + bool removeRewrite(AsyncWebRewrite* rewrite); + AsyncWebRewrite& rewrite(const char* from, const char* to); + + AsyncWebHandler& addHandler(AsyncWebHandler* handler); + bool removeHandler(AsyncWebHandler* handler); + + AsyncCallbackWebHandler& on(const char* uri, ArRequestHandlerFunction onRequest); + AsyncCallbackWebHandler& on(const char* uri, WebRequestMethodComposite method, ArRequestHandlerFunction onRequest); + AsyncCallbackWebHandler& on(const char* uri, WebRequestMethodComposite method, ArRequestHandlerFunction onRequest, ArUploadHandlerFunction onUpload); + AsyncCallbackWebHandler& on(const char* uri, WebRequestMethodComposite method, ArRequestHandlerFunction onRequest, ArUploadHandlerFunction onUpload, ArBodyHandlerFunction onBody); + + AsyncStaticWebHandler& serveStatic(const char* uri, fs::FS& fs, const char* path, const char* cache_control = NULL); + + void onNotFound(ArRequestHandlerFunction fn); //called when handler is not assigned + void onFileUpload(ArUploadHandlerFunction fn); //handle file uploads + void onRequestBody(ArBodyHandlerFunction fn); //handle posts with plain body content (JSON often transmitted this way as a request) + + void reset(); //remove all writers and handlers, with onNotFound/onFileUpload/onRequestBody + + void _handleDisconnect(AsyncWebServerRequest *request); + void _attachHandler(AsyncWebServerRequest *request); + void _rewriteRequest(AsyncWebServerRequest *request); +}; + +class DefaultHeaders { + using headers_t = std::list; + headers_t _headers; + +public: + DefaultHeaders() = default; + + using ConstIterator = headers_t::const_iterator; + + void addHeader(const String& name, const String& value){ + _headers.emplace_back(name, value); + } + + ConstIterator begin() const { return _headers.begin(); } + ConstIterator end() const { return _headers.end(); } + + DefaultHeaders(DefaultHeaders const &) = delete; + DefaultHeaders &operator=(DefaultHeaders const &) = delete; + + static DefaultHeaders &Instance() { + static DefaultHeaders instance; + return instance; + } +}; + +#include "WebResponseImpl.h" +#include "WebHandlerImpl.h" +#include "AsyncWebSocket.h" +#include "AsyncEventSource.h" + +#endif /* _AsyncWebServer_H_ */ diff --git a/libraries/ESPAsyncWebServer/src/ESP_Async_WebServer.h b/libraries/ESPAsyncWebServer/src/ESP_Async_WebServer.h new file mode 100644 index 000000000..ca6a11262 --- /dev/null +++ b/libraries/ESPAsyncWebServer/src/ESP_Async_WebServer.h @@ -0,0 +1,2 @@ +// to please Arduino Lint +#include "ESPAsyncWebServer.h" diff --git a/libraries/ESPAsyncWebServer/src/StringArray.h b/libraries/ESPAsyncWebServer/src/StringArray.h new file mode 100644 index 000000000..39b175a35 --- /dev/null +++ b/libraries/ESPAsyncWebServer/src/StringArray.h @@ -0,0 +1,174 @@ +/* + Asynchronous WebServer library for Espressif MCUs + + Copyright (c) 2016 Hristo Gochkov. All rights reserved. + This file is part of the esp8266 core for Arduino environment. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ +#ifndef STRINGARRAY_H_ +#define STRINGARRAY_H_ + +#include "stddef.h" +#include "WString.h" + +template +class AlternativeLinkedListNode { + T _value; + public: + AlternativeLinkedListNode* next; + AlternativeLinkedListNode(const T val): _value(val), next(nullptr) {} + ~AlternativeLinkedListNode(){} + const T& value() const { return _value; }; + T& value(){ return _value; } +}; + +template class Item = AlternativeLinkedListNode> +class AlternativeLinkedList { + public: + typedef Item ItemType; + typedef std::function OnRemove; + typedef std::function Predicate; + private: + ItemType* _root; + OnRemove _onRemove; + + class Iterator { + ItemType* _node; + public: + Iterator(ItemType* current = nullptr) : _node(current) {} + Iterator(const Iterator& i) : _node(i._node) {} + Iterator& operator ++() { _node = _node->next; return *this; } + bool operator != (const Iterator& i) const { return _node != i._node; } + const T& operator * () const { return _node->value(); } + const T* operator -> () const { return &_node->value(); } + }; + + public: + typedef const Iterator ConstIterator; + ConstIterator begin() const { return ConstIterator(_root); } + ConstIterator end() const { return ConstIterator(nullptr); } + + AlternativeLinkedList(OnRemove onRemove) : _root(nullptr), _onRemove(onRemove) {} + ~AlternativeLinkedList(){} + void add(const T& t){ + auto it = new ItemType(t); + if(!_root){ + _root = it; + } else { + auto i = _root; + while(i->next) i = i->next; + i->next = it; + } + } + T& front() const { + return _root->value(); + } + + bool isEmpty() const { + return _root == nullptr; + } + size_t length() const { + size_t i = 0; + auto it = _root; + while(it){ + i++; + it = it->next; + } + return i; + } + size_t count_if(Predicate predicate) const { + size_t i = 0; + auto it = _root; + while(it){ + if (!predicate){ + i++; + } + else if (predicate(it->value())) { + i++; + } + it = it->next; + } + return i; + } + const T* nth(size_t N) const { + size_t i = 0; + auto it = _root; + while(it){ + if(i++ == N) + return &(it->value()); + it = it->next; + } + return nullptr; + } + bool remove(const T& t){ + auto it = _root; + auto pit = _root; + while(it){ + if(it->value() == t){ + if(it == _root){ + _root = _root->next; + } else { + pit->next = it->next; + } + + if (_onRemove) { + _onRemove(it->value()); + } + + delete it; + return true; + } + pit = it; + it = it->next; + } + return false; + } + bool remove_first(Predicate predicate){ + auto it = _root; + auto pit = _root; + while(it){ + if(predicate(it->value())){ + if(it == _root){ + _root = _root->next; + } else { + pit->next = it->next; + } + if (_onRemove) { + _onRemove(it->value()); + } + delete it; + return true; + } + pit = it; + it = it->next; + } + return false; + } + + void free(){ + while(_root != nullptr){ + auto it = _root; + _root = _root->next; + if (_onRemove) { + _onRemove(it->value()); + } + delete it; + } + _root = nullptr; + } +}; + +#endif /* STRINGARRAY_H_ */ diff --git a/libraries/ESPAsyncWebServer/src/WebAuthentication.cpp b/libraries/ESPAsyncWebServer/src/WebAuthentication.cpp new file mode 100644 index 000000000..2f122d7cd --- /dev/null +++ b/libraries/ESPAsyncWebServer/src/WebAuthentication.cpp @@ -0,0 +1,247 @@ +/* + Asynchronous WebServer library for Espressif MCUs + + Copyright (c) 2016 Hristo Gochkov. All rights reserved. + This file is part of the esp8266 core for Arduino environment. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ +#include "WebAuthentication.h" +#include +#ifdef ESP32 +#include "mbedtls/md5.h" +#else +#include "md5.h" +#endif + + +// Basic Auth hash = base64("username:password") + +bool checkBasicAuthentication(const char * hash, const char * username, const char * password){ + if(username == NULL || password == NULL || hash == NULL) + return false; + + size_t toencodeLen = strlen(username)+strlen(password)+1; + size_t encodedLen = base64_encode_expected_len(toencodeLen); + if(strlen(hash) != encodedLen) +// Fix from https://github.com/me-no-dev/ESPAsyncWebServer/issues/667 +#ifdef ARDUINO_ARCH_ESP32 + if(strlen(hash) != encodedLen) +#else + if (strlen(hash) != encodedLen - 1) +#endif + return false; + + char *toencode = new char[toencodeLen+1]; + if(toencode == NULL){ + return false; + } + char *encoded = new char[base64_encode_expected_len(toencodeLen)+1]; + if(encoded == NULL){ + delete[] toencode; + return false; + } + sprintf_P(toencode, PSTR("%s:%s"), username, password); + if(base64_encode_chars(toencode, toencodeLen, encoded) > 0 && memcmp(hash, encoded, encodedLen) == 0){ + delete[] toencode; + delete[] encoded; + return true; + } + delete[] toencode; + delete[] encoded; + return false; +} + +static bool getMD5(uint8_t * data, uint16_t len, char * output){//33 bytes or more +#ifdef ESP32 + mbedtls_md5_context _ctx; +#else + md5_context_t _ctx; +#endif + uint8_t i; + uint8_t * _buf = (uint8_t*)malloc(16); + if(_buf == NULL) + return false; + memset(_buf, 0x00, 16); +#ifdef ESP32 + mbedtls_md5_init(&_ctx); +#if ESP_IDF_VERSION_MAJOR == 5 + mbedtls_md5_starts(&_ctx); + mbedtls_md5_update(&_ctx, data, len); + mbedtls_md5_finish(&_ctx, _buf); +#else + mbedtls_md5_starts_ret(&_ctx); + mbedtls_md5_update_ret(&_ctx, data, len); + mbedtls_md5_finish_ret(&_ctx, _buf); +#endif +#else + MD5Init(&_ctx); + MD5Update(&_ctx, data, len); + MD5Final(_buf, &_ctx); +#endif + for(i = 0; i < 16; i++) { + sprintf_P(output + (i * 2), PSTR("%02x"), _buf[i]); + } + free(_buf); + return true; +} + +static String genRandomMD5(){ +#ifdef ESP8266 + uint32_t r = RANDOM_REG32; +#else + uint32_t r = rand(); +#endif + char * out = (char*)malloc(33); + if(out == NULL || !getMD5((uint8_t*)(&r), 4, out)) + return emptyString; + String res = String(out); + free(out); + return res; +} + +static String stringMD5(const String& in){ + char * out = (char*)malloc(33); + if(out == NULL || !getMD5((uint8_t*)(in.c_str()), in.length(), out)) + return emptyString; + String res = String(out); + free(out); + return res; +} + +String generateDigestHash(const char * username, const char * password, const char * realm){ + if(username == NULL || password == NULL || realm == NULL){ + return emptyString; + } + char * out = (char*)malloc(33); + String res = String(username); + res += ':'; + res.concat(realm); + res += ':'; + String in = res; + in.concat(password); + if(out == NULL || !getMD5((uint8_t*)(in.c_str()), in.length(), out)) + return emptyString; + res.concat(out); + free(out); + return res; +} + +String requestDigestAuthentication(const char * realm){ + String header = F("realm=\""); + if(realm == NULL) + header.concat(F("asyncesp")); + else + header.concat(realm); + header.concat(F("\", qop=\"auth\", nonce=\"")); + header.concat(genRandomMD5()); + header.concat(F("\", opaque=\"")); + header.concat(genRandomMD5()); + header += '"'; + return header; +} + +bool checkDigestAuthentication(const char * header, const __FlashStringHelper *method, const char * username, const char * password, const char * realm, bool passwordIsHash, const char * nonce, const char * opaque, const char * uri){ + if(username == NULL || password == NULL || header == NULL || method == NULL){ + //os_printf("AUTH FAIL: missing requred fields\n"); + return false; + } + + String myHeader = String(header); + int nextBreak = myHeader.indexOf(','); + if(nextBreak < 0){ + //os_printf("AUTH FAIL: no variables\n"); + return false; + } + + String myUsername = String(); + String myRealm = String(); + String myNonce = String(); + String myUri = String(); + String myResponse = String(); + String myQop = String(); + String myNc = String(); + String myCnonce = String(); + + myHeader += F(", "); + do { + String avLine = myHeader.substring(0, nextBreak); + avLine.trim(); + myHeader = myHeader.substring(nextBreak+1); + nextBreak = myHeader.indexOf(','); + + int eqSign = avLine.indexOf('='); + if(eqSign < 0){ + //os_printf("AUTH FAIL: no = sign\n"); + return false; + } + String varName = avLine.substring(0, eqSign); + avLine = avLine.substring(eqSign + 1); + if(avLine.startsWith(String('"'))){ + avLine = avLine.substring(1, avLine.length() - 1); + } + + if(varName.equals(F("username"))){ + if(!avLine.equals(username)){ + //os_printf("AUTH FAIL: username\n"); + return false; + } + myUsername = avLine; + } else if(varName.equals(F("realm"))){ + if(realm != NULL && !avLine.equals(realm)){ + //os_printf("AUTH FAIL: realm\n"); + return false; + } + myRealm = avLine; + } else if(varName.equals(F("nonce"))){ + if(nonce != NULL && !avLine.equals(nonce)){ + //os_printf("AUTH FAIL: nonce\n"); + return false; + } + myNonce = avLine; + } else if(varName.equals(F("opaque"))){ + if(opaque != NULL && !avLine.equals(opaque)){ + //os_printf("AUTH FAIL: opaque\n"); + return false; + } + } else if(varName.equals(F("uri"))){ + if(uri != NULL && !avLine.equals(uri)){ + //os_printf("AUTH FAIL: uri\n"); + return false; + } + myUri = avLine; + } else if(varName.equals(F("response"))){ + myResponse = avLine; + } else if(varName.equals(F("qop"))){ + myQop = avLine; + } else if(varName.equals(F("nc"))){ + myNc = avLine; + } else if(varName.equals(F("cnonce"))){ + myCnonce = avLine; + } + } while(nextBreak > 0); + + String ha1 = (passwordIsHash) ? String(password) : stringMD5(myUsername + ':' + myRealm + ':' + String(password)); + String ha2 = String(method) + ':' + myUri; + String response = ha1 + ':' + myNonce + ':' + myNc + ':' + myCnonce + ':' + myQop + ':' + stringMD5(ha2); + + if(myResponse.equals(stringMD5(response))){ + //os_printf("AUTH SUCCESS\n"); + return true; + } + + //os_printf("AUTH FAIL: password\n"); + return false; +} diff --git a/libraries/ESPAsyncWebServer/src/WebAuthentication.h b/libraries/ESPAsyncWebServer/src/WebAuthentication.h new file mode 100644 index 000000000..a6f1966e3 --- /dev/null +++ b/libraries/ESPAsyncWebServer/src/WebAuthentication.h @@ -0,0 +1,34 @@ +/* + Asynchronous WebServer library for Espressif MCUs + + Copyright (c) 2016 Hristo Gochkov. All rights reserved. + This file is part of the esp8266 core for Arduino environment. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef WEB_AUTHENTICATION_H_ +#define WEB_AUTHENTICATION_H_ + +#include "Arduino.h" + +bool checkBasicAuthentication(const char * header, const char * username, const char * password); +String requestDigestAuthentication(const char * realm); +bool checkDigestAuthentication(const char * header, const __FlashStringHelper *method, const char * username, const char * password, const char * realm, bool passwordIsHash, const char * nonce, const char * opaque, const char * uri); + +//for storing hashed versions on the device that can be authenticated against +String generateDigestHash(const char * username, const char * password, const char * realm); + +#endif diff --git a/libraries/ESPAsyncWebServer/src/WebHandlerImpl.h b/libraries/ESPAsyncWebServer/src/WebHandlerImpl.h new file mode 100644 index 000000000..9b7ba1b04 --- /dev/null +++ b/libraries/ESPAsyncWebServer/src/WebHandlerImpl.h @@ -0,0 +1,151 @@ +/* + Asynchronous WebServer library for Espressif MCUs + + Copyright (c) 2016 Hristo Gochkov. All rights reserved. + This file is part of the esp8266 core for Arduino environment. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ +#ifndef ASYNCWEBSERVERHANDLERIMPL_H_ +#define ASYNCWEBSERVERHANDLERIMPL_H_ + +#include +#ifdef ASYNCWEBSERVER_REGEX +#include +#endif + +#include "stddef.h" +#include + +class AsyncStaticWebHandler: public AsyncWebHandler { + using File = fs::File; + using FS = fs::FS; + private: + bool _getFile(AsyncWebServerRequest *request); + bool _fileExists(AsyncWebServerRequest *request, const String& path); + uint8_t _countBits(const uint8_t value) const; + protected: + FS _fs; + String _uri; + String _path; + String _default_file; + String _cache_control; + String _last_modified; + AwsTemplateProcessor _callback; + bool _isDir; + bool _gzipFirst; + uint8_t _gzipStats; + public: + AsyncStaticWebHandler(const char* uri, FS& fs, const char* path, const char* cache_control); + virtual bool canHandle(AsyncWebServerRequest *request) override final; + virtual void handleRequest(AsyncWebServerRequest *request) override final; + AsyncStaticWebHandler& setIsDir(bool isDir); + AsyncStaticWebHandler& setDefaultFile(const char* filename); + AsyncStaticWebHandler& setCacheControl(const char* cache_control); + AsyncStaticWebHandler& setLastModified(const char* last_modified); + AsyncStaticWebHandler& setLastModified(struct tm* last_modified); + #ifdef ESP8266 + AsyncStaticWebHandler& setLastModified(time_t last_modified); + AsyncStaticWebHandler& setLastModified(); //sets to current time. Make sure sntp is runing and time is updated + #endif + AsyncStaticWebHandler& setTemplateProcessor(AwsTemplateProcessor newCallback) {_callback = newCallback; return *this;} +}; + +class AsyncCallbackWebHandler: public AsyncWebHandler { + private: + protected: + String _uri; + WebRequestMethodComposite _method; + ArRequestHandlerFunction _onRequest; + ArUploadHandlerFunction _onUpload; + ArBodyHandlerFunction _onBody; + bool _isRegex; + public: + AsyncCallbackWebHandler() : _uri(), _method(HTTP_ANY), _onRequest(NULL), _onUpload(NULL), _onBody(NULL), _isRegex(false) {} + void setUri(const String& uri){ + _uri = uri; + _isRegex = uri.startsWith("^") && uri.endsWith("$"); + } + void setMethod(WebRequestMethodComposite method){ _method = method; } + void onRequest(ArRequestHandlerFunction fn){ _onRequest = fn; } + void onUpload(ArUploadHandlerFunction fn){ _onUpload = fn; } + void onBody(ArBodyHandlerFunction fn){ _onBody = fn; } + + virtual bool canHandle(AsyncWebServerRequest *request) override final{ + + if(!_onRequest) + return false; + + if(!(_method & request->method())) + return false; + +#ifdef ASYNCWEBSERVER_REGEX + if (_isRegex) { + std::regex pattern(_uri.c_str()); + std::smatch matches; + std::string s(request->url().c_str()); + if(std::regex_search(s, matches, pattern)) { + for (size_t i = 1; i < matches.size(); ++i) { // start from 1 + request->_addPathParam(matches[i].str().c_str()); + } + } else { + return false; + } + } else +#endif + if (_uri.length() && _uri.startsWith("/*.")) { + String uriTemplate = String (_uri); + uriTemplate = uriTemplate.substring(uriTemplate.lastIndexOf(".")); + if (!request->url().endsWith(uriTemplate)) + return false; + } + else + if (_uri.length() && _uri.endsWith("*")) { + String uriTemplate = String(_uri); + uriTemplate = uriTemplate.substring(0, uriTemplate.length() - 1); + if (!request->url().startsWith(uriTemplate)) + return false; + } + else if(_uri.length() && (_uri != request->url() && !request->url().startsWith(_uri+"/"))) + return false; + + request->addInterestingHeader("ANY"); + return true; + } + + virtual void handleRequest(AsyncWebServerRequest *request) override final { + if((_username != "" && _password != "") && !request->authenticate(_username.c_str(), _password.c_str())) + return request->requestAuthentication(); + if(_onRequest) + _onRequest(request); + else + request->send(500); + } + virtual void handleUpload(AsyncWebServerRequest *request, const String& filename, size_t index, uint8_t *data, size_t len, bool final) override final { + if((_username != "" && _password != "") && !request->authenticate(_username.c_str(), _password.c_str())) + return request->requestAuthentication(); + if(_onUpload) + _onUpload(request, filename, index, data, len, final); + } + virtual void handleBody(AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total) override final { + if((_username != "" && _password != "") && !request->authenticate(_username.c_str(), _password.c_str())) + return request->requestAuthentication(); + if(_onBody) + _onBody(request, data, len, index, total); + } + virtual bool isRequestHandlerTrivial() override final {return _onRequest ? false : true;} +}; + +#endif /* ASYNCWEBSERVERHANDLERIMPL_H_ */ diff --git a/libraries/ESPAsyncWebServer/src/WebHandlers.cpp b/libraries/ESPAsyncWebServer/src/WebHandlers.cpp new file mode 100644 index 000000000..0b55b0ddf --- /dev/null +++ b/libraries/ESPAsyncWebServer/src/WebHandlers.cpp @@ -0,0 +1,233 @@ +/* + Asynchronous WebServer library for Espressif MCUs + + Copyright (c) 2016 Hristo Gochkov. All rights reserved. + This file is part of the esp8266 core for Arduino environment. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ +#include "ESPAsyncWebServer.h" +#include "WebHandlerImpl.h" + +AsyncStaticWebHandler::AsyncStaticWebHandler(const char* uri, FS& fs, const char* path, const char* cache_control) + : _fs(fs), _uri(uri), _path(path), _default_file(F("index.htm")), _cache_control(cache_control), _last_modified(), _callback(nullptr) +{ + // Ensure leading '/' + if (_uri.length() == 0 || _uri[0] != '/') _uri = String('/') + _uri; + if (_path.length() == 0 || _path[0] != '/') _path = String('/') + _path; + + // If path ends with '/' we assume a hint that this is a directory to improve performance. + // However - if it does not end with '/' we, can't assume a file, path can still be a directory. + _isDir = _path[_path.length()-1] == '/'; + + // Remove the trailing '/' so we can handle default file + // Notice that root will be "" not "/" + if (_uri[_uri.length()-1] == '/') _uri = _uri.substring(0, _uri.length()-1); + if (_path[_path.length()-1] == '/') _path = _path.substring(0, _path.length()-1); + + // Reset stats + _gzipFirst = false; + _gzipStats = 0xF8; +} + +AsyncStaticWebHandler& AsyncStaticWebHandler::setIsDir(bool isDir){ + _isDir = isDir; + return *this; +} + +AsyncStaticWebHandler& AsyncStaticWebHandler::setDefaultFile(const char* filename){ + _default_file = String(filename); + return *this; +} + +AsyncStaticWebHandler& AsyncStaticWebHandler::setCacheControl(const char* cache_control){ + _cache_control = String(cache_control); + return *this; +} + +AsyncStaticWebHandler& AsyncStaticWebHandler::setLastModified(const char* last_modified){ + _last_modified = last_modified; + return *this; +} + +AsyncStaticWebHandler& AsyncStaticWebHandler::setLastModified(struct tm* last_modified){ + auto formatP = PSTR("%a, %d %b %Y %H:%M:%S %Z"); + char format[strlen_P(formatP) + 1]; + strcpy_P(format, formatP); + + char result[30]; + strftime(result, sizeof(result), format, last_modified); + return setLastModified((const char *)result); +} + +#ifdef ESP8266 +AsyncStaticWebHandler& AsyncStaticWebHandler::setLastModified(time_t last_modified){ + return setLastModified((struct tm *)gmtime(&last_modified)); +} + +AsyncStaticWebHandler& AsyncStaticWebHandler::setLastModified(){ + time_t last_modified; + if(time(&last_modified) == 0) //time is not yet set + return *this; + return setLastModified(last_modified); +} +#endif +bool AsyncStaticWebHandler::canHandle(AsyncWebServerRequest *request){ + if(request->method() != HTTP_GET + || !request->url().startsWith(_uri) + || !request->isExpectedRequestedConnType(RCT_DEFAULT, RCT_HTTP) + ){ + return false; + } + if (_getFile(request)) { + // We interested in "If-Modified-Since" header to check if file was modified + if (_last_modified.length()) + request->addInterestingHeader(F("If-Modified-Since")); + + if(_cache_control.length()) + request->addInterestingHeader(F("If-None-Match")); + + return true; + } + + return false; +} + +bool AsyncStaticWebHandler::_getFile(AsyncWebServerRequest *request) +{ + // Remove the found uri + String path = request->url().substring(_uri.length()); + + // We can skip the file check and look for default if request is to the root of a directory or that request path ends with '/' + bool canSkipFileCheck = (_isDir && path.length() == 0) || (path.length() && path[path.length()-1] == '/'); + + path = _path + path; + + // Do we have a file or .gz file + if (!canSkipFileCheck && _fileExists(request, path)) + return true; + + // Can't handle if not default file + if (_default_file.length() == 0) + return false; + + // Try to add default file, ensure there is a trailing '/' ot the path. + if (path.length() == 0 || path[path.length()-1] != '/') + path += String('/'); + path += _default_file; + + return _fileExists(request, path); +} + +#ifdef ESP32 +#define FILE_IS_REAL(f) (f == true && !f.isDirectory()) +#else +#define FILE_IS_REAL(f) (f == true) +#endif + +bool AsyncStaticWebHandler::_fileExists(AsyncWebServerRequest *request, const String& path) +{ + bool fileFound = false; + bool gzipFound = false; + + String gzip = path + F(".gz"); + + if (_gzipFirst) { + if (_fs.exists(gzip)) { + request->_tempFile = _fs.open(gzip, fs::FileOpenMode::read); + gzipFound = FILE_IS_REAL(request->_tempFile); + } + if (!gzipFound){ + if (_fs.exists(path)) { + request->_tempFile = _fs.open(path, fs::FileOpenMode::read); + fileFound = FILE_IS_REAL(request->_tempFile); + } + } + } else { + if (_fs.exists(path)) { + request->_tempFile = _fs.open(path, fs::FileOpenMode::read); + fileFound = FILE_IS_REAL(request->_tempFile); + } + if (!fileFound){ + if (_fs.exists(gzip)) { + request->_tempFile = _fs.open(gzip, fs::FileOpenMode::read); + gzipFound = FILE_IS_REAL(request->_tempFile); + } + } + } + + bool found = fileFound || gzipFound; + + if (found) { + // Extract the file name from the path and keep it in _tempObject + size_t pathLen = path.length(); + char * _tempPath = (char*)malloc(pathLen+1); + snprintf_P(_tempPath, pathLen+1, PSTR("%s"), path.c_str()); + request->_tempObject = (void*)_tempPath; + + // Calculate gzip statistic + _gzipStats = (_gzipStats << 1) + (gzipFound ? 1 : 0); + if (_gzipStats == 0x00) _gzipFirst = false; // All files are not gzip + else if (_gzipStats == 0xFF) _gzipFirst = true; // All files are gzip + else _gzipFirst = _countBits(_gzipStats) > 4; // IF we have more gzip files - try gzip first + } + + return found; +} + +uint8_t AsyncStaticWebHandler::_countBits(const uint8_t value) const +{ + uint8_t w = value; + uint8_t n; + for (n=0; w!=0; n++) w&=w-1; + return n; +} + +void AsyncStaticWebHandler::handleRequest(AsyncWebServerRequest *request) +{ + // Get the filename from request->_tempObject and free it + String filename = String((char*)request->_tempObject); + free(request->_tempObject); + request->_tempObject = NULL; + if((_username.length() && _password.length()) && !request->authenticate(_username.c_str(), _password.c_str())) + return request->requestAuthentication(); + + if (request->_tempFile == true) { + time_t lw = request->_tempFile.getLastWrite(); // get last file mod time (if supported by FS) + if (lw) setLastModified(gmtime(&lw)); + String etag(lw ? lw : request->_tempFile.size()); // set etag to lastmod timestamp if available, otherwise to size + if (_last_modified.length() && _last_modified == request->header(F("If-Modified-Since"))) { + request->_tempFile.close(); + request->send(304); // Not modified + } else if (_cache_control.length() && request->hasHeader(F("If-None-Match")) && request->header(F("If-None-Match")).equals(etag)) { + request->_tempFile.close(); + AsyncWebServerResponse * response = new AsyncBasicResponse(304); // Not modified + response->addHeader(F("Cache-Control"), _cache_control); + response->addHeader(F("ETag"), etag); + request->send(response); + } else { + AsyncWebServerResponse * response = new AsyncFileResponse(request->_tempFile, filename, String(), false, _callback); + if (_last_modified.length()) + response->addHeader(F("Last-Modified"), _last_modified); + if (_cache_control.length()){ + response->addHeader(F("Cache-Control"), _cache_control); + response->addHeader(F("ETag"), etag); + } + request->send(response); + } + } else { + request->send(404); + } +} diff --git a/libraries/ESPAsyncWebServer/src/WebRequest.cpp b/libraries/ESPAsyncWebServer/src/WebRequest.cpp new file mode 100644 index 000000000..9b71f1691 --- /dev/null +++ b/libraries/ESPAsyncWebServer/src/WebRequest.cpp @@ -0,0 +1,993 @@ +/* + Asynchronous WebServer library for Espressif MCUs + + Copyright (c) 2016 Hristo Gochkov. All rights reserved. + This file is part of the esp8266 core for Arduino environment. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ +#include "ESPAsyncWebServer.h" +#include "WebResponseImpl.h" +#include "WebAuthentication.h" + +#ifndef ESP8266 +#define os_strlen strlen +#endif + +#define __is_param_char(c) ((c) && ((c) != '{') && ((c) != '[') && ((c) != '&') && ((c) != '=')) + +enum { PARSE_REQ_START, PARSE_REQ_HEADERS, PARSE_REQ_BODY, PARSE_REQ_END, PARSE_REQ_FAIL }; + +AsyncWebServerRequest::AsyncWebServerRequest(AsyncWebServer* s, AsyncClient* c) + : _client(c) + , _server(s) + , _handler(NULL) + , _response(NULL) + , _temp() + , _parseState(0) + , _version(0) + , _method(HTTP_ANY) + , _url() + , _host() + , _contentType() + , _boundary() + , _authorization() + , _reqconntype(RCT_HTTP) + , _isDigest(false) + , _isMultipart(false) + , _isPlainPost(false) + , _expectingContinue(false) + , _contentLength(0) + , _parsedLength(0) + , _params(AlternativeLinkedList([](AsyncWebParameter *p){ delete p; })) + , _multiParseState(0) + , _boundaryPosition(0) + , _itemStartIndex(0) + , _itemSize(0) + , _itemName() + , _itemFilename() + , _itemType() + , _itemValue() + , _itemBuffer(0) + , _itemBufferIndex(0) + , _itemIsFile(false) + , _tempObject(NULL) +{ + c->onError([](void *r, AsyncClient* c, int8_t error){ (void)c; AsyncWebServerRequest *req = (AsyncWebServerRequest*)r; req->_onError(error); }, this); + c->onAck([](void *r, AsyncClient* c, size_t len, uint32_t time){ (void)c; AsyncWebServerRequest *req = (AsyncWebServerRequest*)r; req->_onAck(len, time); }, this); + c->onDisconnect([](void *r, AsyncClient* c){ AsyncWebServerRequest *req = (AsyncWebServerRequest*)r; req->_onDisconnect(); delete c; }, this); + c->onTimeout([](void *r, AsyncClient* c, uint32_t time){ (void)c; AsyncWebServerRequest *req = (AsyncWebServerRequest*)r; req->_onTimeout(time); }, this); + c->onData([](void *r, AsyncClient* c, void *buf, size_t len){ (void)c; AsyncWebServerRequest *req = (AsyncWebServerRequest*)r; req->_onData(buf, len); }, this); + c->onPoll([](void *r, AsyncClient* c){ (void)c; AsyncWebServerRequest *req = ( AsyncWebServerRequest*)r; req->_onPoll(); }, this); +} + +AsyncWebServerRequest::~AsyncWebServerRequest(){ + _headers.clear(); + + _params.free(); + _pathParams.clear(); + + _interestingHeaders.clear(); + + if(_response != NULL){ + delete _response; + } + + if(_tempObject != NULL){ + free(_tempObject); + } + + if(_tempFile){ + _tempFile.close(); + } + + if(_itemBuffer){ + free(_itemBuffer); + } + +} + +void AsyncWebServerRequest::_onData(void *buf, size_t len){ + size_t i = 0; + while (true) { + + if(_parseState < PARSE_REQ_BODY){ + // Find new line in buf + char *str = (char*)buf; + for (i = 0; i < len; i++) { + if (str[i] == '\n') { + break; + } + } + if (i == len) { // No new line, just add the buffer in _temp + char ch = str[len-1]; + str[len-1] = 0; + _temp.reserve(_temp.length()+len); + _temp.concat(str); + _temp.concat(ch); + } else { // Found new line - extract it and parse + str[i] = 0; // Terminate the string at the end of the line. + _temp.concat(str); + _temp.trim(); + _parseLine(); + if (++i < len) { + // Still have more buffer to process + buf = str+i; + len-= i; + continue; + } + } + } else if(_parseState == PARSE_REQ_BODY){ + // A handler should be already attached at this point in _parseLine function. + // If handler does nothing (_onRequest is NULL), we don't need to really parse the body. + const bool needParse = _handler && !_handler->isRequestHandlerTrivial(); + if(_isMultipart){ + if(needParse){ + size_t i; + for(i=0; i end) equal = end; + String name = params.substring(start, equal); + String value = equal + 1 < end ? params.substring(equal + 1, end) : String(); + _addParam(new AsyncWebParameter(urlDecode(name), urlDecode(value))); + start = end + 1; + } +} + +bool AsyncWebServerRequest::_parseReqHead(){ + // Split the head into method, url and version + int index = _temp.indexOf(' '); + String m = _temp.substring(0, index); + index = _temp.indexOf(' ', index+1); + String u = _temp.substring(m.length()+1, index); + _temp = _temp.substring(index+1); + + if(m == F("GET")){ + _method = HTTP_GET; + } else if(m == F("POST")){ + _method = HTTP_POST; + } else if(m == F("DELETE")){ + _method = HTTP_DELETE; + } else if(m == F("PUT")){ + _method = HTTP_PUT; + } else if(m == F("PATCH")){ + _method = HTTP_PATCH; + } else if(m == F("HEAD")){ + _method = HTTP_HEAD; + } else if(m == F("OPTIONS")){ + _method = HTTP_OPTIONS; + } + + String g; + index = u.indexOf('?'); + if(index > 0){ + g = u.substring(index +1); + u = u.substring(0, index); + } + _url = urlDecode(u); + _addGetParams(g); + + if(!_temp.startsWith(F("HTTP/1.0"))) + _version = 1; + + _temp = String(); + return true; +} + +bool strContains(const String &src, const String &find, bool mindcase = true) { + int pos=0, i=0; + const int slen = src.length(); + const int flen = find.length(); + + if (slen < flen) return false; + while (pos <= (slen - flen)) { + for (i=0; i < flen; i++) { + if (mindcase) { + if (src[pos+i] != find[i]) i = flen + 1; // no match + } + else if (tolower(src[pos+i]) != tolower(find[i])) { + i = flen + 1; // no match + } + } + if (i == flen) return true; + pos++; + } + return false; +} + +bool AsyncWebServerRequest::_parseReqHeader(){ + int index = _temp.indexOf(':'); + if(index){ + String name = _temp.substring(0, index); + String value = _temp.substring(index + 2); + if(name.equalsIgnoreCase("Host")){ + _host = value; + } else if(name.equalsIgnoreCase(F("Content-Type"))){ + _contentType = value.substring(0, value.indexOf(';')); + if (value.startsWith(F("multipart/"))){ + _boundary = value.substring(value.indexOf('=')+1); + _boundary.replace(String('"'), String()); + _isMultipart = true; + } + } else if(name.equalsIgnoreCase(F("Content-Length"))){ + _contentLength = atoi(value.c_str()); + } else if(name.equalsIgnoreCase(F("Expect")) && value == F("100-continue")){ + _expectingContinue = true; + } else if(name.equalsIgnoreCase(F("Authorization"))){ + if(value.length() > 5 && value.substring(0,5).equalsIgnoreCase(F("Basic"))){ + _authorization = value.substring(6); + } else if(value.length() > 6 && value.substring(0,6).equalsIgnoreCase(F("Digest"))){ + _isDigest = true; + _authorization = value.substring(7); + } + } else { + if(name.equalsIgnoreCase(F("Upgrade")) && value.equalsIgnoreCase(F("websocket"))){ + // WebSocket request can be uniquely identified by header: [Upgrade: websocket] + _reqconntype = RCT_WS; + } else { + if(name.equalsIgnoreCase(F("Accept")) && strContains(value, F("text/event-stream"), false)){ + // WebEvent request can be uniquely identified by header: [Accept: text/event-stream] + _reqconntype = RCT_EVENT; + } + } + } + _headers.emplace_back(name, value); + } + _temp = String(); + return true; +} + +void AsyncWebServerRequest::_parsePlainPostChar(uint8_t data){ + if(data && (char)data != '&') + _temp += (char)data; + if(!data || (char)data == '&' || _parsedLength == _contentLength){ + String name = F("body"); + String value = _temp; + if(!_temp.startsWith(String('{')) && !_temp.startsWith(String('[')) && _temp.indexOf('=') > 0){ + name = _temp.substring(0, _temp.indexOf('=')); + value = _temp.substring(_temp.indexOf('=') + 1); + } + _addParam(new AsyncWebParameter(urlDecode(name), urlDecode(value), true)); + _temp = String(); + } +} + +void AsyncWebServerRequest::_handleUploadByte(uint8_t data, bool last){ + _itemBuffer[_itemBufferIndex++] = data; + + if(last || _itemBufferIndex == 1460){ + //check if authenticated before calling the upload + if(_handler) + _handler->handleUpload(this, _itemFilename, _itemSize - _itemBufferIndex, _itemBuffer, _itemBufferIndex, false); + _itemBufferIndex = 0; + } +} + +enum { + EXPECT_BOUNDARY, + PARSE_HEADERS, + WAIT_FOR_RETURN1, + EXPECT_FEED1, + EXPECT_DASH1, + EXPECT_DASH2, + BOUNDARY_OR_DATA, + DASH3_OR_RETURN2, + EXPECT_FEED2, + PARSING_FINISHED, + PARSE_ERROR +}; + +void AsyncWebServerRequest::_parseMultipartPostByte(uint8_t data, bool last){ +#define itemWriteByte(b) do { _itemSize++; if(_itemIsFile) _handleUploadByte(b, last); else _itemValue+=(char)(b); } while(0) + + if(!_parsedLength){ + _multiParseState = EXPECT_BOUNDARY; + _temp = String(); + _itemName = String(); + _itemFilename = String(); + _itemType = String(); + } + + if(_multiParseState == WAIT_FOR_RETURN1){ + if(data != '\r'){ + itemWriteByte(data); + } else { + _multiParseState = EXPECT_FEED1; + } + } else if(_multiParseState == EXPECT_BOUNDARY){ + if(_parsedLength < 2 && data != '-'){ + _multiParseState = PARSE_ERROR; + return; + } else if(_parsedLength - 2 < _boundary.length() && _boundary.c_str()[_parsedLength - 2] != data){ + _multiParseState = PARSE_ERROR; + return; + } else if(_parsedLength - 2 == _boundary.length() && data != '\r'){ + _multiParseState = PARSE_ERROR; + return; + } else if(_parsedLength - 3 == _boundary.length()){ + if(data != '\n'){ + _multiParseState = PARSE_ERROR; + return; + } + _multiParseState = PARSE_HEADERS; + _itemIsFile = false; + } + } else if(_multiParseState == PARSE_HEADERS){ + if((char)data != '\r' && (char)data != '\n') + _temp += (char)data; + if((char)data == '\n'){ + if(_temp.length()){ + if(_temp.length() > 12 && _temp.substring(0, 12).equalsIgnoreCase(F("Content-Type"))){ + _itemType = _temp.substring(14); + _itemIsFile = true; + } else if(_temp.length() > 19 && _temp.substring(0, 19).equalsIgnoreCase(F("Content-Disposition"))){ + _temp = _temp.substring(_temp.indexOf(';') + 2); + while(_temp.indexOf(';') > 0){ + String name = _temp.substring(0, _temp.indexOf('=')); + String nameVal = _temp.substring(_temp.indexOf('=') + 2, _temp.indexOf(';') - 1); + if(name == F("name")){ + _itemName = nameVal; + } else if(name == F("filename")){ + _itemFilename = nameVal; + _itemIsFile = true; + } + _temp = _temp.substring(_temp.indexOf(';') + 2); + } + String name = _temp.substring(0, _temp.indexOf('=')); + String nameVal = _temp.substring(_temp.indexOf('=') + 2, _temp.length() - 1); + if(name == F("name")){ + _itemName = nameVal; + } else if(name == F("filename")){ + _itemFilename = nameVal; + _itemIsFile = true; + } + } + _temp = String(); + } else { + _multiParseState = WAIT_FOR_RETURN1; + //value starts from here + _itemSize = 0; + _itemStartIndex = _parsedLength; + _itemValue = String(); + if(_itemIsFile){ + if(_itemBuffer) + free(_itemBuffer); + _itemBuffer = (uint8_t*)malloc(1460); + if(_itemBuffer == NULL){ + _multiParseState = PARSE_ERROR; + return; + } + _itemBufferIndex = 0; + } + } + } + } else if(_multiParseState == EXPECT_FEED1){ + if(data != '\n'){ + _multiParseState = WAIT_FOR_RETURN1; + itemWriteByte('\r'); _parseMultipartPostByte(data, last); + } else { + _multiParseState = EXPECT_DASH1; + } + } else if(_multiParseState == EXPECT_DASH1){ + if(data != '-'){ + _multiParseState = WAIT_FOR_RETURN1; + itemWriteByte('\r'); itemWriteByte('\n'); _parseMultipartPostByte(data, last); + } else { + _multiParseState = EXPECT_DASH2; + } + } else if(_multiParseState == EXPECT_DASH2){ + if(data != '-'){ + _multiParseState = WAIT_FOR_RETURN1; + itemWriteByte('\r'); itemWriteByte('\n'); itemWriteByte('-'); _parseMultipartPostByte(data, last); + } else { + _multiParseState = BOUNDARY_OR_DATA; + _boundaryPosition = 0; + } + } else if(_multiParseState == BOUNDARY_OR_DATA){ + if(_boundaryPosition < _boundary.length() && _boundary.c_str()[_boundaryPosition] != data){ + _multiParseState = WAIT_FOR_RETURN1; + itemWriteByte('\r'); itemWriteByte('\n'); itemWriteByte('-'); itemWriteByte('-'); + uint8_t i; + for(i=0; i<_boundaryPosition; i++) + itemWriteByte(_boundary.c_str()[i]); + _parseMultipartPostByte(data, last); + } else if(_boundaryPosition == _boundary.length() - 1){ + _multiParseState = DASH3_OR_RETURN2; + if(!_itemIsFile){ + _addParam(new AsyncWebParameter(_itemName, _itemValue, true)); + } else { + if(_itemSize){ + //check if authenticated before calling the upload + if(_handler) _handler->handleUpload(this, _itemFilename, _itemSize - _itemBufferIndex, _itemBuffer, _itemBufferIndex, true); + _itemBufferIndex = 0; + _addParam(new AsyncWebParameter(_itemName, _itemFilename, true, true, _itemSize)); + } + free(_itemBuffer); + _itemBuffer = NULL; + } + + } else { + _boundaryPosition++; + } + } else if(_multiParseState == DASH3_OR_RETURN2){ + if(data == '-' && (_contentLength - _parsedLength - 4) != 0){ + //os_printf("ERROR: The parser got to the end of the POST but is expecting %u bytes more!\nDrop an issue so we can have more info on the matter!\n", _contentLength - _parsedLength - 4); + _contentLength = _parsedLength + 4;//lets close the request gracefully + } + if(data == '\r'){ + _multiParseState = EXPECT_FEED2; + } else if(data == '-' && _contentLength == (_parsedLength + 4)){ + _multiParseState = PARSING_FINISHED; + } else { + _multiParseState = WAIT_FOR_RETURN1; + itemWriteByte('\r'); itemWriteByte('\n'); itemWriteByte('-'); itemWriteByte('-'); + uint8_t i; for(i=0; i<_boundary.length(); i++) itemWriteByte(_boundary.c_str()[i]); + _parseMultipartPostByte(data, last); + } + } else if(_multiParseState == EXPECT_FEED2){ + if(data == '\n'){ + _multiParseState = PARSE_HEADERS; + _itemIsFile = false; + } else { + _multiParseState = WAIT_FOR_RETURN1; + itemWriteByte('\r'); itemWriteByte('\n'); itemWriteByte('-'); itemWriteByte('-'); + uint8_t i; for(i=0; i<_boundary.length(); i++) itemWriteByte(_boundary.c_str()[i]); + itemWriteByte('\r'); _parseMultipartPostByte(data, last); + } + } +} + +void AsyncWebServerRequest::_parseLine(){ + if(_parseState == PARSE_REQ_START){ + if(!_temp.length()){ + _parseState = PARSE_REQ_FAIL; + _client->close(); + } else { + _parseReqHead(); + _parseState = PARSE_REQ_HEADERS; + } + return; + } + + if(_parseState == PARSE_REQ_HEADERS){ + if(!_temp.length()){ + //end of headers + _server->_rewriteRequest(this); + _server->_attachHandler(this); + _removeNotInterestingHeaders(); + if(_expectingContinue){ + String response = F("HTTP/1.1 100 Continue\r\n\r\n"); + _client->write(response.c_str(), response.length()); + } + //check handler for authentication + if(_contentLength){ + _parseState = PARSE_REQ_BODY; + } else { + _parseState = PARSE_REQ_END; + if(_handler) _handler->handleRequest(this); + else send(501); + } + } else _parseReqHeader(); + } +} + +size_t AsyncWebServerRequest::headers() const{ + return _headers.size(); +} + +bool AsyncWebServerRequest::hasHeader(const String& name) const { + for(const auto& h: _headers){ + if(h.name().equalsIgnoreCase(name)){ + return true; + } + } + return false; +} + +bool AsyncWebServerRequest::hasHeader(const __FlashStringHelper * data) const { + return hasHeader(String(data)); +} + +AsyncWebHeader* AsyncWebServerRequest::getHeader(const String& name) { + auto iter = std::find_if(std::begin(_headers), std::end(_headers), + [&name](const AsyncWebHeader &header){ return header.name().equalsIgnoreCase(name); }); + + if (iter == std::end(_headers)) + return nullptr; + + return &(*iter); +} + +const AsyncWebHeader* AsyncWebServerRequest::getHeader(const String& name) const { + auto iter = std::find_if(std::begin(_headers), std::end(_headers), + [&name](const AsyncWebHeader &header){ return header.name().equalsIgnoreCase(name); }); + + if (iter == std::end(_headers)) + return nullptr; + + return &(*iter); +} + +AsyncWebHeader* AsyncWebServerRequest::getHeader(const __FlashStringHelper * data) { + PGM_P p = reinterpret_cast(data); + size_t n = strlen_P(p); + char * name = (char*) malloc(n+1); + if (name) { + strcpy_P(name, p); + AsyncWebHeader* result = getHeader( String(name)); + free(name); + return result; + } else { + return nullptr; + } +} + +const AsyncWebHeader* AsyncWebServerRequest::getHeader(const __FlashStringHelper * data) const { + PGM_P p = reinterpret_cast(data); + size_t n = strlen_P(p); + char * name = (char*) malloc(n+1); + if (name) { + strcpy_P(name, p); + const AsyncWebHeader* result = getHeader( String(name)); + free(name); + return result; + } else { + return nullptr; + } +} + +AsyncWebHeader* AsyncWebServerRequest::getHeader(size_t num) { + if (num >= _headers.size()) + return nullptr; + return &(*std::next(std::begin(_headers), num)); +} + +const AsyncWebHeader* AsyncWebServerRequest::getHeader(size_t num) const { + if (num >= _headers.size()) + return nullptr; + return &(*std::next(std::begin(_headers), num)); +} + +size_t AsyncWebServerRequest::params() const { + return _params.length(); +} + +bool AsyncWebServerRequest::hasParam(const String& name, bool post, bool file) const { + for(const auto& p: _params){ + if(p->name() == name && p->isPost() == post && p->isFile() == file){ + return true; + } + } + return false; +} + +bool AsyncWebServerRequest::hasParam(const __FlashStringHelper * data, bool post, bool file) const { + return hasParam(String(data).c_str(), post, file); +} + +AsyncWebParameter* AsyncWebServerRequest::getParam(const String& name, bool post, bool file) const { + for(const auto& p: _params){ + if(p->name() == name && p->isPost() == post && p->isFile() == file){ + return p; + } + } + return nullptr; +} + +AsyncWebParameter* AsyncWebServerRequest::getParam(const __FlashStringHelper * data, bool post, bool file) const { + return getParam(String(data).c_str(), post, file); +} + +AsyncWebParameter* AsyncWebServerRequest::getParam(size_t num) const { + auto param = _params.nth(num); + return param ? *param : nullptr; +} + +void AsyncWebServerRequest::addInterestingHeader(const String& name){ + if(std::none_of(std::begin(_interestingHeaders), std::end(_interestingHeaders), + [&name](const String &str){ return str.equalsIgnoreCase(name); })) + _interestingHeaders.push_back(name); +} + +void AsyncWebServerRequest::send(AsyncWebServerResponse *response){ + _response = response; + if(_response == NULL){ + _client->close(true); + _onDisconnect(); + return; + } + if(!_response->_sourceValid()){ + delete response; + _response = NULL; + send(500); + } + else { + _client->setRxTimeout(0); + _response->_respond(this); + } +} + +AsyncWebServerResponse * AsyncWebServerRequest::beginResponse(int code, const String& contentType, const String& content){ + return new AsyncBasicResponse(code, contentType, content); +} + +AsyncWebServerResponse * AsyncWebServerRequest::beginResponse(FS &fs, const String& path, const String& contentType, bool download, AwsTemplateProcessor callback){ + if(fs.exists(path) || (!download && fs.exists(path+F(".gz")))) + return new AsyncFileResponse(fs, path, contentType, download, callback); + return NULL; +} + +AsyncWebServerResponse * AsyncWebServerRequest::beginResponse(File content, const String& path, const String& contentType, bool download, AwsTemplateProcessor callback){ + if(content == true) + return new AsyncFileResponse(content, path, contentType, download, callback); + return NULL; +} + +AsyncWebServerResponse * AsyncWebServerRequest::beginResponse(Stream &stream, const String& contentType, size_t len, AwsTemplateProcessor callback){ + return new AsyncStreamResponse(stream, contentType, len, callback); +} + +AsyncWebServerResponse * AsyncWebServerRequest::beginResponse(const String& contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback){ + return new AsyncCallbackResponse(contentType, len, callback, templateCallback); +} + +AsyncWebServerResponse * AsyncWebServerRequest::beginChunkedResponse(const String& contentType, AwsResponseFiller callback, AwsTemplateProcessor templateCallback){ + if(_version) + return new AsyncChunkedResponse(contentType, callback, templateCallback); + return new AsyncCallbackResponse(contentType, 0, callback, templateCallback); +} + +AsyncResponseStream * AsyncWebServerRequest::beginResponseStream(const String& contentType, size_t bufferSize){ + return new AsyncResponseStream(contentType, bufferSize); +} + +AsyncWebServerResponse * AsyncWebServerRequest::beginResponse_P(int code, const String& contentType, const uint8_t * content, size_t len, AwsTemplateProcessor callback){ + return new AsyncProgmemResponse(code, contentType, content, len, callback); +} + +AsyncWebServerResponse * AsyncWebServerRequest::beginResponse_P(int code, const String& contentType, PGM_P content, AwsTemplateProcessor callback){ + return beginResponse_P(code, contentType, (const uint8_t *)content, strlen_P(content), callback); +} + +void AsyncWebServerRequest::send(int code, const String& contentType, const String& content){ + send(beginResponse(code, contentType, content)); +} + +void AsyncWebServerRequest::send(FS &fs, const String& path, const String& contentType, bool download, AwsTemplateProcessor callback){ + if(fs.exists(path) || (!download && fs.exists(path+F(".gz")))){ + send(beginResponse(fs, path, contentType, download, callback)); + } else send(404); +} + +void AsyncWebServerRequest::send(File content, const String& path, const String& contentType, bool download, AwsTemplateProcessor callback){ + if(content == true){ + send(beginResponse(content, path, contentType, download, callback)); + } else send(404); +} + +void AsyncWebServerRequest::send(Stream &stream, const String& contentType, size_t len, AwsTemplateProcessor callback){ + send(beginResponse(stream, contentType, len, callback)); +} + +void AsyncWebServerRequest::send(const String& contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback){ + send(beginResponse(contentType, len, callback, templateCallback)); +} + +void AsyncWebServerRequest::sendChunked(const String& contentType, AwsResponseFiller callback, AwsTemplateProcessor templateCallback){ + send(beginChunkedResponse(contentType, callback, templateCallback)); +} + +void AsyncWebServerRequest::send_P(int code, const String& contentType, const uint8_t * content, size_t len, AwsTemplateProcessor callback){ + send(beginResponse_P(code, contentType, content, len, callback)); +} + +void AsyncWebServerRequest::send_P(int code, const String& contentType, PGM_P content, AwsTemplateProcessor callback){ + send(beginResponse_P(code, contentType, content, callback)); +} + +void AsyncWebServerRequest::redirect(const String& url){ + AsyncWebServerResponse * response = beginResponse(302); + response->addHeader(F("Location"), url); + send(response); +} + +bool AsyncWebServerRequest::authenticate(const char * username, const char * password, const char * realm, bool passwordIsHash){ + if(_authorization.length()){ + if(_isDigest) + return checkDigestAuthentication(_authorization.c_str(), methodToString(), username, password, realm, passwordIsHash, NULL, NULL, NULL); + else if(!passwordIsHash) + return checkBasicAuthentication(_authorization.c_str(), username, password); + else + return _authorization.equals(password); + } + return false; +} + +bool AsyncWebServerRequest::authenticate(const char * hash){ + if(!_authorization.length() || hash == NULL) + return false; + + if(_isDigest){ + String hStr = String(hash); + int separator = hStr.indexOf(':'); + if(separator <= 0) + return false; + String username = hStr.substring(0, separator); + hStr = hStr.substring(separator + 1); + separator = hStr.indexOf(':'); + if(separator <= 0) + return false; + String realm = hStr.substring(0, separator); + hStr = hStr.substring(separator + 1); + return checkDigestAuthentication(_authorization.c_str(), methodToString(), username.c_str(), hStr.c_str(), realm.c_str(), true, NULL, NULL, NULL); + } + + return (_authorization.equals(hash)); +} + +void AsyncWebServerRequest::requestAuthentication(const char * realm, bool isDigest){ + AsyncWebServerResponse * r = beginResponse(401); + if(!isDigest && realm == NULL){ + r->addHeader(F("WWW-Authenticate"), F("Basic realm=\"Login Required\"")); + } else if(!isDigest){ + String header = F("Basic realm=\""); + header.concat(realm); + header += '"'; + r->addHeader(F("WWW-Authenticate"), header); + } else { + String header = F("Digest "); + header.concat(requestDigestAuthentication(realm)); + r->addHeader(F("WWW-Authenticate"), header); + } + send(r); +} + +bool AsyncWebServerRequest::hasArg(const char* name) const { + for(const auto& arg: _params){ + if(arg->name() == name){ + return true; + } + } + return false; +} + +bool AsyncWebServerRequest::hasArg(const __FlashStringHelper * data) const { + return hasArg(String(data).c_str()); +} + + +const String& AsyncWebServerRequest::arg(const String& name) const { + for(const auto& arg: _params){ + if(arg->name() == name){ + return arg->value(); + } + } + return emptyString; +} + +const String& AsyncWebServerRequest::arg(const __FlashStringHelper * data) const { + return arg(String(data).c_str()); +} + +const String& AsyncWebServerRequest::arg(size_t i) const { + return getParam(i)->value(); +} + +const String& AsyncWebServerRequest::argName(size_t i) const { + return getParam(i)->name(); +} + +const String& AsyncWebServerRequest::pathArg(size_t i) const { + return i < _pathParams.size() ? _pathParams[i] : emptyString; +} + +const String& AsyncWebServerRequest::header(const char* name) const { + const AsyncWebHeader* h = getHeader(String(name)); + return h ? h->value() : emptyString; +} + +const String& AsyncWebServerRequest::header(const __FlashStringHelper * data) const { + return header(String(data).c_str()); +}; + + +const String& AsyncWebServerRequest::header(size_t i) const { + const AsyncWebHeader* h = getHeader(i); + return h ? h->value() : emptyString; +} + +const String& AsyncWebServerRequest::headerName(size_t i) const { + const AsyncWebHeader* h = getHeader(i); + return h ? h->name() : emptyString; +} + +String AsyncWebServerRequest::urlDecode(const String& text) const { + char temp[] = "0x00"; + unsigned int len = text.length(); + unsigned int i = 0; + String decoded = String(); + decoded.reserve(len); // Allocate the string internal buffer - never longer from source text + while (i < len){ + char decodedChar; + char encodedChar = text.charAt(i++); + if ((encodedChar == '%') && (i + 1 < len)){ + temp[2] = text.charAt(i++); + temp[3] = text.charAt(i++); + decodedChar = strtol(temp, NULL, 16); + } else if (encodedChar == '+') { + decodedChar = ' '; + } else { + decodedChar = encodedChar; // normal ascii char + } + decoded.concat(decodedChar); + } + return decoded; +} + + +const __FlashStringHelper *AsyncWebServerRequest::methodToString() const { + if(_method == HTTP_ANY) return F("ANY"); + else if(_method & HTTP_GET) return F("GET"); + else if(_method & HTTP_POST) return F("POST"); + else if(_method & HTTP_DELETE) return F("DELETE"); + else if(_method & HTTP_PUT) return F("PUT"); + else if(_method & HTTP_PATCH) return F("PATCH"); + else if(_method & HTTP_HEAD) return F("HEAD"); + else if(_method & HTTP_OPTIONS) return F("OPTIONS"); + return F("UNKNOWN"); +} + +const __FlashStringHelper *AsyncWebServerRequest::requestedConnTypeToString() const { + switch (_reqconntype) { + case RCT_NOT_USED: return F("RCT_NOT_USED"); + case RCT_DEFAULT: return F("RCT_DEFAULT"); + case RCT_HTTP: return F("RCT_HTTP"); + case RCT_WS: return F("RCT_WS"); + case RCT_EVENT: return F("RCT_EVENT"); + default: return F("ERROR"); + } +} + +bool AsyncWebServerRequest::isExpectedRequestedConnType(RequestedConnectionType erct1, RequestedConnectionType erct2, RequestedConnectionType erct3) { + bool res = false; + if ((erct1 != RCT_NOT_USED) && (erct1 == _reqconntype)) res = true; + if ((erct2 != RCT_NOT_USED) && (erct2 == _reqconntype)) res = true; + if ((erct3 != RCT_NOT_USED) && (erct3 == _reqconntype)) res = true; + return res; +} diff --git a/libraries/ESPAsyncWebServer/src/WebResponseImpl.h b/libraries/ESPAsyncWebServer/src/WebResponseImpl.h new file mode 100644 index 000000000..4a472254b --- /dev/null +++ b/libraries/ESPAsyncWebServer/src/WebResponseImpl.h @@ -0,0 +1,138 @@ +/* + Asynchronous WebServer library for Espressif MCUs + + Copyright (c) 2016 Hristo Gochkov. All rights reserved. + This file is part of the esp8266 core for Arduino environment. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ +#ifndef ASYNCWEBSERVERRESPONSEIMPL_H_ +#define ASYNCWEBSERVERRESPONSEIMPL_H_ + +#ifdef Arduino_h +// arduino is not compatible with std::vector +#undef min +#undef max +#endif +#include +#include + +// It is possible to restore these defines, but one can use _min and _max instead. Or std::min, std::max. + +class AsyncBasicResponse: public AsyncWebServerResponse { + private: + String _content; + public: + AsyncBasicResponse(int code, const String& contentType=String(), const String& content=String()); + void _respond(AsyncWebServerRequest *request); + size_t _ack(AsyncWebServerRequest *request, size_t len, uint32_t time); + bool _sourceValid() const { return true; } +}; + +class AsyncAbstractResponse: public AsyncWebServerResponse { + private: + String _head; + // Data is inserted into cache at begin(). + // This is inefficient with vector, but if we use some other container, + // we won't be able to access it as contiguous array of bytes when reading from it, + // so by gaining performance in one place, we'll lose it in another. + std::vector _cache; + size_t _readDataFromCacheOrContent(uint8_t* data, const size_t len); + size_t _fillBufferAndProcessTemplates(uint8_t* buf, size_t maxLen); + protected: + AwsTemplateProcessor _callback; + public: + AsyncAbstractResponse(AwsTemplateProcessor callback=nullptr); + void _respond(AsyncWebServerRequest *request); + size_t _ack(AsyncWebServerRequest *request, size_t len, uint32_t time); + bool _sourceValid() const { return false; } + virtual size_t _fillBuffer(uint8_t *buf __attribute__((unused)), size_t maxLen __attribute__((unused))) { return 0; } +}; + +#ifndef TEMPLATE_PLACEHOLDER +#define TEMPLATE_PLACEHOLDER '%' +#endif + +#define TEMPLATE_PARAM_NAME_LENGTH 32 +class AsyncFileResponse: public AsyncAbstractResponse { + using File = fs::File; + using FS = fs::FS; + private: + File _content; + String _path; + void _setContentType(const String& path); + public: + AsyncFileResponse(FS &fs, const String& path, const String& contentType=String(), bool download=false, AwsTemplateProcessor callback=nullptr); + AsyncFileResponse(File content, const String& path, const String& contentType=String(), bool download=false, AwsTemplateProcessor callback=nullptr); + ~AsyncFileResponse(); + bool _sourceValid() const { return !!(_content); } + virtual size_t _fillBuffer(uint8_t *buf, size_t maxLen) override; +}; + +class AsyncStreamResponse: public AsyncAbstractResponse { + private: + Stream *_content; + public: + AsyncStreamResponse(Stream &stream, const String& contentType, size_t len, AwsTemplateProcessor callback=nullptr); + bool _sourceValid() const { return !!(_content); } + virtual size_t _fillBuffer(uint8_t *buf, size_t maxLen) override; +}; + +class AsyncCallbackResponse: public AsyncAbstractResponse { + private: + AwsResponseFiller _content; + size_t _filledLength; + public: + AsyncCallbackResponse(const String& contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback=nullptr); + bool _sourceValid() const { return !!(_content); } + virtual size_t _fillBuffer(uint8_t *buf, size_t maxLen) override; +}; + +class AsyncChunkedResponse: public AsyncAbstractResponse { + private: + AwsResponseFiller _content; + size_t _filledLength; + public: + AsyncChunkedResponse(const String& contentType, AwsResponseFiller callback, AwsTemplateProcessor templateCallback=nullptr); + bool _sourceValid() const { return !!(_content); } + virtual size_t _fillBuffer(uint8_t *buf, size_t maxLen) override; +}; + +class AsyncProgmemResponse: public AsyncAbstractResponse { + private: + const uint8_t * _content; + size_t _readLength; + public: + AsyncProgmemResponse(int code, const String& contentType, const uint8_t * content, size_t len, AwsTemplateProcessor callback=nullptr); + bool _sourceValid() const { return true; } + virtual size_t _fillBuffer(uint8_t *buf, size_t maxLen) override; +}; + +class cbuf; + +class AsyncResponseStream: public AsyncAbstractResponse, public Print { + private: + std::unique_ptr _content; + public: + AsyncResponseStream(const String& contentType, size_t bufferSize); + ~AsyncResponseStream(); + bool _sourceValid() const { return (_state < RESPONSE_END); } + virtual size_t _fillBuffer(uint8_t *buf, size_t maxLen) override; + size_t write(const uint8_t *data, size_t len); + size_t write(uint8_t data); + using Print::write; +}; + +#endif /* ASYNCWEBSERVERRESPONSEIMPL_H_ */ diff --git a/libraries/ESPAsyncWebServer/src/WebResponses.cpp b/libraries/ESPAsyncWebServer/src/WebResponses.cpp new file mode 100644 index 000000000..a783de0f4 --- /dev/null +++ b/libraries/ESPAsyncWebServer/src/WebResponses.cpp @@ -0,0 +1,704 @@ +/* + Asynchronous WebServer library for Espressif MCUs + + Copyright (c) 2016 Hristo Gochkov. All rights reserved. + This file is part of the esp8266 core for Arduino environment. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ +#include "ESPAsyncWebServer.h" +#include "WebResponseImpl.h" +#include "cbuf.h" + +// Since ESP8266 does not link memchr by default, here's its implementation. +void* memchr(void* ptr, int ch, size_t count) +{ + unsigned char* p = static_cast(ptr); + while(count--) + if(*p++ == static_cast(ch)) + return --p; + return nullptr; +} + + +/* + * Abstract Response + * */ +const char* AsyncWebServerResponse::_responseCodeToString(int code) { + return reinterpret_cast(responseCodeToString(code)); +} + +const __FlashStringHelper *AsyncWebServerResponse::responseCodeToString(int code) { + switch (code) { + case 100: return F("Continue"); + case 101: return F("Switching Protocols"); + case 200: return F("OK"); + case 201: return F("Created"); + case 202: return F("Accepted"); + case 203: return F("Non-Authoritative Information"); + case 204: return F("No Content"); + case 205: return F("Reset Content"); + case 206: return F("Partial Content"); + case 300: return F("Multiple Choices"); + case 301: return F("Moved Permanently"); + case 302: return F("Found"); + case 303: return F("See Other"); + case 304: return F("Not Modified"); + case 305: return F("Use Proxy"); + case 307: return F("Temporary Redirect"); + case 400: return F("Bad Request"); + case 401: return F("Unauthorized"); + case 402: return F("Payment Required"); + case 403: return F("Forbidden"); + case 404: return F("Not Found"); + case 405: return F("Method Not Allowed"); + case 406: return F("Not Acceptable"); + case 407: return F("Proxy Authentication Required"); + case 408: return F("Request Time-out"); + case 409: return F("Conflict"); + case 410: return F("Gone"); + case 411: return F("Length Required"); + case 412: return F("Precondition Failed"); + case 413: return F("Request Entity Too Large"); + case 414: return F("Request-URI Too Large"); + case 415: return F("Unsupported Media Type"); + case 416: return F("Requested range not satisfiable"); + case 417: return F("Expectation Failed"); + case 500: return F("Internal Server Error"); + case 501: return F("Not Implemented"); + case 502: return F("Bad Gateway"); + case 503: return F("Service Unavailable"); + case 504: return F("Gateway Time-out"); + case 505: return F("HTTP Version not supported"); + default: return F(""); + } +} + +AsyncWebServerResponse::AsyncWebServerResponse() + : _code(0) + , _contentType() + , _contentLength(0) + , _sendContentLength(true) + , _chunked(false) + , _headLength(0) + , _sentLength(0) + , _ackedLength(0) + , _writtenLength(0) + , _state(RESPONSE_SETUP) +{ + for(const auto &header: DefaultHeaders::Instance()) { + _headers.emplace_back(header); + } +} + +AsyncWebServerResponse::~AsyncWebServerResponse() = default; + +void AsyncWebServerResponse::setCode(int code){ + if(_state == RESPONSE_SETUP) + _code = code; +} + +void AsyncWebServerResponse::setContentLength(size_t len){ + if(_state == RESPONSE_SETUP) + _contentLength = len; +} + +void AsyncWebServerResponse::setContentType(const String& type){ + if(_state == RESPONSE_SETUP) + _contentType = type; +} + +void AsyncWebServerResponse::addHeader(const String& name, const String& value){ + _headers.emplace_back(name, value); +} + +String AsyncWebServerResponse::_assembleHead(uint8_t version){ + if(version){ + addHeader(F("Accept-Ranges"), F("none")); + if(_chunked) + addHeader(F("Transfer-Encoding"), F("chunked")); + } + String out = String(); + int bufSize = 300; + char buf[bufSize]; + + snprintf_P(buf, bufSize, PSTR("HTTP/1.%d %d %s\r\n"), version, _code, _responseCodeToString(_code)); + out.concat(buf); + + if(_sendContentLength) { + snprintf_P(buf, bufSize, PSTR("Content-Length: %d\r\n"), _contentLength); + out.concat(buf); + } + if(_contentType.length()) { + snprintf_P(buf, bufSize, PSTR("Content-Type: %s\r\n"), _contentType.c_str()); + out.concat(buf); + } + + for(const auto& header: _headers){ + snprintf_P(buf, bufSize, PSTR("%s: %s\r\n"), header.name().c_str(), header.value().c_str()); + out.concat(buf); + } + _headers.clear(); + + out.concat(F("\r\n")); + _headLength = out.length(); + return out; +} + +bool AsyncWebServerResponse::_started() const { return _state > RESPONSE_SETUP; } +bool AsyncWebServerResponse::_finished() const { return _state > RESPONSE_WAIT_ACK; } +bool AsyncWebServerResponse::_failed() const { return _state == RESPONSE_FAILED; } +bool AsyncWebServerResponse::_sourceValid() const { return false; } +void AsyncWebServerResponse::_respond(AsyncWebServerRequest *request){ _state = RESPONSE_END; request->client()->close(); } +size_t AsyncWebServerResponse::_ack(AsyncWebServerRequest *request, size_t len, uint32_t time){ (void)request; (void)len; (void)time; return 0; } + +/* + * String/Code Response + * */ +AsyncBasicResponse::AsyncBasicResponse(int code, const String& contentType, const String& content){ + _code = code; + _content = content; + _contentType = contentType; + if(_content.length()){ + _contentLength = _content.length(); + if(!_contentType.length()) + _contentType = F("text/plain"); + } + addHeader(F("Connection"), F("close")); +} + +void AsyncBasicResponse::_respond(AsyncWebServerRequest *request){ + _state = RESPONSE_HEADERS; + String out = _assembleHead(request->version()); + size_t outLen = out.length(); + size_t space = request->client()->space(); + if(!_contentLength && space >= outLen){ + _writtenLength += request->client()->write(out.c_str(), outLen); + _state = RESPONSE_WAIT_ACK; + } else if(_contentLength && space >= outLen + _contentLength){ + out += _content; + outLen += _contentLength; + _writtenLength += request->client()->write(out.c_str(), outLen); + _state = RESPONSE_WAIT_ACK; + } else if(space && space < outLen){ + String partial = out.substring(0, space); + _content = out.substring(space) + _content; + _contentLength += outLen - space; + _writtenLength += request->client()->write(partial.c_str(), partial.length()); + _state = RESPONSE_CONTENT; + } else if(space > outLen && space < (outLen + _contentLength)){ + size_t shift = space - outLen; + outLen += shift; + _sentLength += shift; + out += _content.substring(0, shift); + _content = _content.substring(shift); + _writtenLength += request->client()->write(out.c_str(), outLen); + _state = RESPONSE_CONTENT; + } else { + _content = out + _content; + _contentLength += outLen; + _state = RESPONSE_CONTENT; + } +} + +size_t AsyncBasicResponse::_ack(AsyncWebServerRequest *request, size_t len, uint32_t time){ + (void)time; + _ackedLength += len; + if(_state == RESPONSE_CONTENT){ + size_t available = _contentLength - _sentLength; + size_t space = request->client()->space(); + //we can fit in this packet + if(space > available){ + _writtenLength += request->client()->write(_content.c_str(), available); + _content = String(); + _state = RESPONSE_WAIT_ACK; + return available; + } + //send some data, the rest on ack + String out = _content.substring(0, space); + _content = _content.substring(space); + _sentLength += space; + _writtenLength += request->client()->write(out.c_str(), space); + return space; + } else if(_state == RESPONSE_WAIT_ACK){ + if(_ackedLength >= _writtenLength){ + _state = RESPONSE_END; + } + } + return 0; +} + + +/* + * Abstract Response + * */ + +AsyncAbstractResponse::AsyncAbstractResponse(AwsTemplateProcessor callback): _callback(callback) +{ + // In case of template processing, we're unable to determine real response size + if(callback) { + _contentLength = 0; + _sendContentLength = false; + _chunked = true; + } +} + +void AsyncAbstractResponse::_respond(AsyncWebServerRequest *request){ + addHeader(F("Connection"), F("close")); + _head = _assembleHead(request->version()); + _state = RESPONSE_HEADERS; + _ack(request, 0, 0); +} + +size_t AsyncAbstractResponse::_ack(AsyncWebServerRequest *request, size_t len, uint32_t time){ + (void)time; + if(!_sourceValid()){ + _state = RESPONSE_FAILED; + request->client()->close(); + return 0; + } + _ackedLength += len; + size_t space = request->client()->space(); + + size_t headLen = _head.length(); + if(_state == RESPONSE_HEADERS){ + if(space >= headLen){ + _state = RESPONSE_CONTENT; + space -= headLen; + } else { + String out = _head.substring(0, space); + _head = _head.substring(space); + _writtenLength += request->client()->write(out.c_str(), out.length()); + return out.length(); + } + } + + if(_state == RESPONSE_CONTENT){ + size_t outLen; + if(_chunked){ + if(space <= 8){ + return 0; + } + outLen = space; + } else if(!_sendContentLength){ + outLen = space; + } else { + outLen = ((_contentLength - _sentLength) > space)?space:(_contentLength - _sentLength); + } + + uint8_t *buf = (uint8_t *)malloc(outLen+headLen); + if (!buf) { + // os_printf("_ack malloc %d failed\n", outLen+headLen); + return 0; + } + + if(headLen){ + memcpy(buf, _head.c_str(), _head.length()); + } + + size_t readLen = 0; + + if(_chunked){ + // HTTP 1.1 allows leading zeros in chunk length. Or spaces may be added. + // See RFC2616 sections 2, 3.6.1. + readLen = _fillBufferAndProcessTemplates(buf+headLen+6, outLen - 8); + if(readLen == RESPONSE_TRY_AGAIN){ + free(buf); + return 0; + } + outLen = sprintf_P((char*)buf+headLen, PSTR("%x"), readLen) + headLen; + while(outLen < headLen + 4) buf[outLen++] = ' '; + buf[outLen++] = '\r'; + buf[outLen++] = '\n'; + outLen += readLen; + buf[outLen++] = '\r'; + buf[outLen++] = '\n'; + } else { + readLen = _fillBufferAndProcessTemplates(buf+headLen, outLen); + if(readLen == RESPONSE_TRY_AGAIN){ + free(buf); + return 0; + } + outLen = readLen + headLen; + } + + if(headLen){ + _head = String(); + } + + if(outLen){ + _writtenLength += request->client()->write((const char*)buf, outLen); + } + + if(_chunked){ + _sentLength += readLen; + } else { + _sentLength += outLen - headLen; + } + + free(buf); + + if((_chunked && readLen == 0) || (!_sendContentLength && outLen == 0) || (!_chunked && _sentLength == _contentLength)){ + _state = RESPONSE_WAIT_ACK; + } + return outLen; + + } else if(_state == RESPONSE_WAIT_ACK){ + if(!_sendContentLength || _ackedLength >= _writtenLength){ + _state = RESPONSE_END; + if(!_chunked && !_sendContentLength) + request->client()->close(true); + } + } + return 0; +} + +size_t AsyncAbstractResponse::_readDataFromCacheOrContent(uint8_t* data, const size_t len) +{ + // If we have something in cache, copy it to buffer + const size_t readFromCache = std::min(len, _cache.size()); + if(readFromCache) { + memcpy(data, _cache.data(), readFromCache); + _cache.erase(_cache.begin(), _cache.begin() + readFromCache); + } + // If we need to read more... + const size_t needFromFile = len - readFromCache; + const size_t readFromContent = _fillBuffer(data + readFromCache, needFromFile); + return readFromCache + readFromContent; +} + +size_t AsyncAbstractResponse::_fillBufferAndProcessTemplates(uint8_t* data, size_t len) +{ + if(!_callback) + return _fillBuffer(data, len); + + const size_t originalLen = len; + len = _readDataFromCacheOrContent(data, len); + // Now we've read 'len' bytes, either from cache or from file + // Search for template placeholders + uint8_t* pTemplateStart = data; + while((pTemplateStart < &data[len]) && (pTemplateStart = (uint8_t*)memchr(pTemplateStart, TEMPLATE_PLACEHOLDER, &data[len - 1] - pTemplateStart + 1))) { // data[0] ... data[len - 1] + uint8_t* pTemplateEnd = (pTemplateStart < &data[len - 1]) ? (uint8_t*)memchr(pTemplateStart + 1, TEMPLATE_PLACEHOLDER, &data[len - 1] - pTemplateStart) : nullptr; + // temporary buffer to hold parameter name + uint8_t buf[TEMPLATE_PARAM_NAME_LENGTH + 1]; + String paramName; + // If closing placeholder is found: + if(pTemplateEnd) { + // prepare argument to callback + const size_t paramNameLength = std::min((size_t)sizeof(buf) - 1, (size_t)(pTemplateEnd - pTemplateStart - 1)); + if(paramNameLength) { + memcpy(buf, pTemplateStart + 1, paramNameLength); + buf[paramNameLength] = 0; + paramName = String(reinterpret_cast(buf)); + } else { // double percent sign encountered, this is single percent sign escaped. + // remove the 2nd percent sign + memmove(pTemplateEnd, pTemplateEnd + 1, &data[len] - pTemplateEnd - 1); + len += _readDataFromCacheOrContent(&data[len - 1], 1) - 1; + ++pTemplateStart; + } + } else if(&data[len - 1] - pTemplateStart + 1 < TEMPLATE_PARAM_NAME_LENGTH + 2) { // closing placeholder not found, check if it's in the remaining file data + memcpy(buf, pTemplateStart + 1, &data[len - 1] - pTemplateStart); + const size_t readFromCacheOrContent = _readDataFromCacheOrContent(buf + (&data[len - 1] - pTemplateStart), TEMPLATE_PARAM_NAME_LENGTH + 2 - (&data[len - 1] - pTemplateStart + 1)); + if(readFromCacheOrContent) { + pTemplateEnd = (uint8_t*)memchr(buf + (&data[len - 1] - pTemplateStart), TEMPLATE_PLACEHOLDER, readFromCacheOrContent); + if(pTemplateEnd) { + // prepare argument to callback + *pTemplateEnd = 0; + paramName = String(reinterpret_cast(buf)); + // Copy remaining read-ahead data into cache + _cache.insert(_cache.begin(), pTemplateEnd + 1, buf + (&data[len - 1] - pTemplateStart) + readFromCacheOrContent); + pTemplateEnd = &data[len - 1]; + } + else // closing placeholder not found in file data, store found percent symbol as is and advance to the next position + { + // but first, store read file data in cache + _cache.insert(_cache.begin(), buf + (&data[len - 1] - pTemplateStart), buf + (&data[len - 1] - pTemplateStart) + readFromCacheOrContent); + ++pTemplateStart; + } + } + else // closing placeholder not found in content data, store found percent symbol as is and advance to the next position + ++pTemplateStart; + } + else // closing placeholder not found in content data, store found percent symbol as is and advance to the next position + ++pTemplateStart; + if(paramName.length()) { + // call callback and replace with result. + // Everything in range [pTemplateStart, pTemplateEnd] can be safely replaced with parameter value. + // Data after pTemplateEnd may need to be moved. + // The first byte of data after placeholder is located at pTemplateEnd + 1. + // It should be located at pTemplateStart + numBytesCopied (to begin right after inserted parameter value). + const String paramValue(_callback(paramName)); + const char* pvstr = paramValue.c_str(); + const unsigned int pvlen = paramValue.length(); + const size_t numBytesCopied = std::min(pvlen, static_cast(&data[originalLen - 1] - pTemplateStart + 1)); + // make room for param value + // 1. move extra data to cache if parameter value is longer than placeholder AND if there is no room to store + if((pTemplateEnd + 1 < pTemplateStart + numBytesCopied) && (originalLen - (pTemplateStart + numBytesCopied - pTemplateEnd - 1) < len)) { + _cache.insert(_cache.begin(), &data[originalLen - (pTemplateStart + numBytesCopied - pTemplateEnd - 1)], &data[len]); + //2. parameter value is longer than placeholder text, push the data after placeholder which not saved into cache further to the end + memmove(pTemplateStart + numBytesCopied, pTemplateEnd + 1, &data[originalLen] - pTemplateStart - numBytesCopied); + len = originalLen; // fix issue with truncated data, not sure if it has any side effects + } else if(pTemplateEnd + 1 != pTemplateStart + numBytesCopied) + //2. Either parameter value is shorter than placeholder text OR there is enough free space in buffer to fit. + // Move the entire data after the placeholder + memmove(pTemplateStart + numBytesCopied, pTemplateEnd + 1, &data[len] - pTemplateEnd - 1); + // 3. replace placeholder with actual value + memcpy(pTemplateStart, pvstr, numBytesCopied); + // If result is longer than buffer, copy the remainder into cache (this could happen only if placeholder text itself did not fit entirely in buffer) + if(numBytesCopied < pvlen) { + _cache.insert(_cache.begin(), pvstr + numBytesCopied, pvstr + pvlen); + } else if(pTemplateStart + numBytesCopied < pTemplateEnd + 1) { // result is copied fully; if result is shorter than placeholder text... + // there is some free room, fill it from cache + const size_t roomFreed = pTemplateEnd + 1 - pTemplateStart - numBytesCopied; + const size_t totalFreeRoom = originalLen - len + roomFreed; + len += _readDataFromCacheOrContent(&data[len - roomFreed], totalFreeRoom) - roomFreed; + } else { // result is copied fully; it is longer than placeholder text + const size_t roomTaken = pTemplateStart + numBytesCopied - pTemplateEnd - 1; + len = std::min(len + roomTaken, originalLen); + } + } + } // while(pTemplateStart) + return len; +} + + +/* + * File Response + * */ + +AsyncFileResponse::~AsyncFileResponse(){ + if(_content) + _content.close(); +} + +void AsyncFileResponse::_setContentType(const String& path){ +#if HAVE_EXTERN_GET_CONTENT_TYPE_FUNCTION + extern const __FlashStringHelper *getContentType(const String &path); + _contentType = getContentType(path); +#else + if (path.endsWith(F(".html"))) _contentType = F("text/html"); + else if (path.endsWith(F(".htm"))) _contentType = F("text/html"); + else if (path.endsWith(F(".css"))) _contentType = F("text/css"); + else if (path.endsWith(F(".json"))) _contentType = F("application/json"); + else if (path.endsWith(F(".js"))) _contentType = F("application/javascript"); + else if (path.endsWith(F(".png"))) _contentType = F("image/png"); + else if (path.endsWith(F(".gif"))) _contentType = F("image/gif"); + else if (path.endsWith(F(".jpg"))) _contentType = F("image/jpeg"); + else if (path.endsWith(F(".ico"))) _contentType = F("image/x-icon"); + else if (path.endsWith(F(".svg"))) _contentType = F("image/svg+xml"); + else if (path.endsWith(F(".eot"))) _contentType = F("font/eot"); + else if (path.endsWith(F(".woff"))) _contentType = F("font/woff"); + else if (path.endsWith(F(".woff2"))) _contentType = F("font/woff2"); + else if (path.endsWith(F(".ttf"))) _contentType = F("font/ttf"); + else if (path.endsWith(F(".xml"))) _contentType = F("text/xml"); + else if (path.endsWith(F(".pdf"))) _contentType = F("application/pdf"); + else if (path.endsWith(F(".zip"))) _contentType = F("application/zip"); + else if(path.endsWith(F(".gz"))) _contentType = F("application/x-gzip"); + else _contentType = F("text/plain"); +#endif +} + +AsyncFileResponse::AsyncFileResponse(FS &fs, const String& path, const String& contentType, bool download, AwsTemplateProcessor callback): AsyncAbstractResponse(callback){ + _code = 200; + _path = path; + + if(!download && !fs.exists(_path) && fs.exists(_path + F(".gz"))){ + _path = _path + F(".gz"); + addHeader(F("Content-Encoding"), F("gzip")); + _callback = nullptr; // Unable to process zipped templates + _sendContentLength = true; + _chunked = false; + } + + _content = fs.open(_path, fs::FileOpenMode::read); + _contentLength = _content.size(); + + if(contentType.length() == 0) + _setContentType(path); + else + _contentType = contentType; + + int filenameStart = path.lastIndexOf('/') + 1; + char buf[26+path.length()-filenameStart]; + char* filename = (char*)path.c_str() + filenameStart; + + if(download) { + // set filename and force download + snprintf_P(buf, sizeof (buf), PSTR("attachment; filename=\"%s\""), filename); + } else { + // set filename and force rendering + snprintf_P(buf, sizeof (buf), PSTR("inline")); + } + addHeader(F("Content-Disposition"), buf); +} + +AsyncFileResponse::AsyncFileResponse(File content, const String& path, const String& contentType, bool download, AwsTemplateProcessor callback): AsyncAbstractResponse(callback){ + _code = 200; + _path = path; + + if(!download && String(content.name()).endsWith(F(".gz")) && !path.endsWith(F(".gz"))){ + addHeader(F("Content-Encoding"), F("gzip")); + _callback = nullptr; // Unable to process gzipped templates + _sendContentLength = true; + _chunked = false; + } + + _content = content; + _contentLength = _content.size(); + + if(contentType.length() == 0) + _setContentType(path); + else + _contentType = contentType; + + int filenameStart = path.lastIndexOf('/') + 1; + char buf[26+path.length()-filenameStart]; + char* filename = (char*)path.c_str() + filenameStart; + + if(download) { + snprintf_P(buf, sizeof (buf), PSTR("attachment; filename=\"%s\""), filename); + } else { + snprintf_P(buf, sizeof (buf), PSTR("inline")); + } + addHeader(F("Content-Disposition"), buf); +} + +size_t AsyncFileResponse::_fillBuffer(uint8_t *data, size_t len){ + return _content.read(data, len); +} + +/* + * Stream Response + * */ + +AsyncStreamResponse::AsyncStreamResponse(Stream &stream, const String& contentType, size_t len, AwsTemplateProcessor callback): AsyncAbstractResponse(callback) { + _code = 200; + _content = &stream; + _contentLength = len; + _contentType = contentType; +} + +size_t AsyncStreamResponse::_fillBuffer(uint8_t *data, size_t len){ + size_t available = _content->available(); + size_t outLen = (available > len)?len:available; + size_t i; + for(i=0;iread(); + return outLen; +} + +/* + * Callback Response + * */ + +AsyncCallbackResponse::AsyncCallbackResponse(const String& contentType, size_t len, AwsResponseFiller callback, AwsTemplateProcessor templateCallback): AsyncAbstractResponse(templateCallback) { + _code = 200; + _content = callback; + _contentLength = len; + if(!len) + _sendContentLength = false; + _contentType = contentType; + _filledLength = 0; +} + +size_t AsyncCallbackResponse::_fillBuffer(uint8_t *data, size_t len){ + size_t ret = _content(data, len, _filledLength); + if(ret != RESPONSE_TRY_AGAIN){ + _filledLength += ret; + } + return ret; +} + +/* + * Chunked Response + * */ + +AsyncChunkedResponse::AsyncChunkedResponse(const String& contentType, AwsResponseFiller callback, AwsTemplateProcessor processorCallback): AsyncAbstractResponse(processorCallback) { + _code = 200; + _content = callback; + _contentLength = 0; + _contentType = contentType; + _sendContentLength = false; + _chunked = true; + _filledLength = 0; +} + +size_t AsyncChunkedResponse::_fillBuffer(uint8_t *data, size_t len){ + size_t ret = _content(data, len, _filledLength); + if(ret != RESPONSE_TRY_AGAIN){ + _filledLength += ret; + } + return ret; +} + +/* + * Progmem Response + * */ + +AsyncProgmemResponse::AsyncProgmemResponse(int code, const String& contentType, const uint8_t * content, size_t len, AwsTemplateProcessor callback): AsyncAbstractResponse(callback) { + _code = code; + _content = content; + _contentType = contentType; + _contentLength = len; + _readLength = 0; +} + +size_t AsyncProgmemResponse::_fillBuffer(uint8_t *data, size_t len){ + size_t left = _contentLength - _readLength; + if (left > len) { + memcpy_P(data, _content + _readLength, len); + _readLength += len; + return len; + } + memcpy_P(data, _content + _readLength, left); + _readLength += left; + return left; +} + + +/* + * Response Stream (You can print/write/printf to it, up to the contentLen bytes) + * */ + +AsyncResponseStream::AsyncResponseStream(const String& contentType, size_t bufferSize) +{ + _code = 200; + _contentLength = 0; + _contentType = contentType; + _content = std::unique_ptr(new cbuf(bufferSize)); //std::make_unique(bufferSize); +} + +AsyncResponseStream::~AsyncResponseStream() = default; + +size_t AsyncResponseStream::_fillBuffer(uint8_t *buf, size_t maxLen){ + return _content->read((char*)buf, maxLen); +} + +size_t AsyncResponseStream::write(const uint8_t *data, size_t len){ + if(_started()) + return 0; + + if(len > _content->room()){ + size_t needed = len - _content->room(); + _content->resizeAdd(needed); + } + size_t written = _content->write((const char*)data, len); + _contentLength += written; + return written; +} + +size_t AsyncResponseStream::write(uint8_t data){ + return write(&data, 1); +} diff --git a/libraries/ESPAsyncWebServer/src/WebServer.cpp b/libraries/ESPAsyncWebServer/src/WebServer.cpp new file mode 100644 index 000000000..2a531261e --- /dev/null +++ b/libraries/ESPAsyncWebServer/src/WebServer.cpp @@ -0,0 +1,198 @@ +/* + Asynchronous WebServer library for Espressif MCUs + + Copyright (c) 2016 Hristo Gochkov. All rights reserved. + This file is part of the esp8266 core for Arduino environment. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ +#include "ESPAsyncWebServer.h" +#include "WebHandlerImpl.h" + +bool ON_STA_FILTER(AsyncWebServerRequest *request) { + return WiFi.localIP() == request->client()->localIP(); +} + +bool ON_AP_FILTER(AsyncWebServerRequest *request) { + return WiFi.localIP() != request->client()->localIP(); +} + +#ifndef HAVE_FS_FILE_OPEN_MODE +const char *fs::FileOpenMode::read = "r"; +const char *fs::FileOpenMode::write = "w"; +const char *fs::FileOpenMode::append = "a"; +#endif + +AsyncWebServer::AsyncWebServer(uint16_t port) + : _server(port) + , _rewrites(AlternativeLinkedList([](AsyncWebRewrite* r){ delete r; })) + , _handlers(AlternativeLinkedList([](AsyncWebHandler* h){ delete h; })) +{ + _catchAllHandler = new AsyncCallbackWebHandler(); + if(_catchAllHandler == NULL) + return; + _server.onClient([](void *s, AsyncClient* c){ + if(c == NULL) + return; + c->setRxTimeout(3); + AsyncWebServerRequest *r = new AsyncWebServerRequest((AsyncWebServer*)s, c); + if(r == NULL){ + c->close(true); + c->free(); + delete c; + } + }, this); +} + +AsyncWebServer::~AsyncWebServer(){ + reset(); + end(); + if(_catchAllHandler) delete _catchAllHandler; +} + +AsyncWebRewrite& AsyncWebServer::addRewrite(AsyncWebRewrite* rewrite){ + _rewrites.add(rewrite); + return *rewrite; +} + +bool AsyncWebServer::removeRewrite(AsyncWebRewrite *rewrite){ + return _rewrites.remove(rewrite); +} + +AsyncWebRewrite& AsyncWebServer::rewrite(const char* from, const char* to){ + return addRewrite(new AsyncWebRewrite(from, to)); +} + +AsyncWebHandler& AsyncWebServer::addHandler(AsyncWebHandler* handler){ + _handlers.add(handler); + return *handler; +} + +bool AsyncWebServer::removeHandler(AsyncWebHandler *handler){ + return _handlers.remove(handler); +} + +void AsyncWebServer::begin(){ + _server.setNoDelay(true); + _server.begin(); +} + +void AsyncWebServer::end(){ + _server.end(); +} + +#if ASYNC_TCP_SSL_ENABLED +void AsyncWebServer::onSslFileRequest(AcSSlFileHandler cb, void* arg){ + _server.onSslFileRequest(cb, arg); +} + +void AsyncWebServer::beginSecure(const char *cert, const char *key, const char *password){ + _server.beginSecure(cert, key, password); +} +#endif + +void AsyncWebServer::_handleDisconnect(AsyncWebServerRequest *request){ + delete request; +} + +void AsyncWebServer::_rewriteRequest(AsyncWebServerRequest *request){ + for(const auto& r: _rewrites){ + if (r->match(request)){ + request->_url = r->toUrl(); + request->_addGetParams(r->params()); + } + } +} + +void AsyncWebServer::_attachHandler(AsyncWebServerRequest *request){ + for(const auto& h: _handlers){ + if (h->filter(request) && h->canHandle(request)){ + request->setHandler(h); + return; + } + } + + request->addInterestingHeader(F("ANY")); + request->setHandler(_catchAllHandler); +} + + +AsyncCallbackWebHandler& AsyncWebServer::on(const char* uri, WebRequestMethodComposite method, ArRequestHandlerFunction onRequest, ArUploadHandlerFunction onUpload, ArBodyHandlerFunction onBody){ + AsyncCallbackWebHandler* handler = new AsyncCallbackWebHandler(); + handler->setUri(uri); + handler->setMethod(method); + handler->onRequest(onRequest); + handler->onUpload(onUpload); + handler->onBody(onBody); + addHandler(handler); + return *handler; +} + +AsyncCallbackWebHandler& AsyncWebServer::on(const char* uri, WebRequestMethodComposite method, ArRequestHandlerFunction onRequest, ArUploadHandlerFunction onUpload){ + AsyncCallbackWebHandler* handler = new AsyncCallbackWebHandler(); + handler->setUri(uri); + handler->setMethod(method); + handler->onRequest(onRequest); + handler->onUpload(onUpload); + addHandler(handler); + return *handler; +} + +AsyncCallbackWebHandler& AsyncWebServer::on(const char* uri, WebRequestMethodComposite method, ArRequestHandlerFunction onRequest){ + AsyncCallbackWebHandler* handler = new AsyncCallbackWebHandler(); + handler->setUri(uri); + handler->setMethod(method); + handler->onRequest(onRequest); + addHandler(handler); + return *handler; +} + +AsyncCallbackWebHandler& AsyncWebServer::on(const char* uri, ArRequestHandlerFunction onRequest){ + AsyncCallbackWebHandler* handler = new AsyncCallbackWebHandler(); + handler->setUri(uri); + handler->onRequest(onRequest); + addHandler(handler); + return *handler; +} + +AsyncStaticWebHandler& AsyncWebServer::serveStatic(const char* uri, fs::FS& fs, const char* path, const char* cache_control){ + AsyncStaticWebHandler* handler = new AsyncStaticWebHandler(uri, fs, path, cache_control); + addHandler(handler); + return *handler; +} + +void AsyncWebServer::onNotFound(ArRequestHandlerFunction fn){ + _catchAllHandler->onRequest(fn); +} + +void AsyncWebServer::onFileUpload(ArUploadHandlerFunction fn){ + _catchAllHandler->onUpload(fn); +} + +void AsyncWebServer::onRequestBody(ArBodyHandlerFunction fn){ + _catchAllHandler->onBody(fn); +} + +void AsyncWebServer::reset(){ + _rewrites.free(); + _handlers.free(); + + if (_catchAllHandler != NULL){ + _catchAllHandler->onRequest(NULL); + _catchAllHandler->onUpload(NULL); + _catchAllHandler->onBody(NULL); + } +} + From 3ebab15c69bf796e38c1258240b5c88e9085139c Mon Sep 17 00:00:00 2001 From: Just Call Me Koko Date: Wed, 26 Jun 2024 18:42:51 -0400 Subject: [PATCH 11/13] Update NimBLE usage --- .github/workflows/build_push.yml | 21 +++++++-------------- esp32_marauder/WiFiScan.cpp | 12 +++++++----- 2 files changed, 14 insertions(+), 19 deletions(-) diff --git a/.github/workflows/build_push.yml b/.github/workflows/build_push.yml index 5c359edf8..6f553b364 100644 --- a/.github/workflows/build_push.yml +++ b/.github/workflows/build_push.yml @@ -54,12 +54,12 @@ jobs: ref: v2.0.6 path: CustomMicroNMEA - - name: Install ESPAsyncWebServer - uses: actions/checkout@v2 - with: - repository: bigbrodude6119/ESPAsyncWebServer - ref: master - path: CustomESPAsyncWebServer + #- name: Install ESPAsyncWebServer + # uses: actions/checkout@v2 + # with: + # repository: bigbrodude6119/ESPAsyncWebServer + # ref: master + # path: CustomESPAsyncWebServer - name: Install TFT_eSPI uses: actions/checkout@v2 @@ -86,7 +86,7 @@ jobs: uses: actions/checkout@v2 with: repository: h2zero/NimBLE-Arduino - ref: 1.4.2 + ref: master path: CustomNimBLE-Arduino - name: Install Adafruit_NeoPixel @@ -102,13 +102,6 @@ jobs: repository: bblanchon/ArduinoJson ref: v6.18.2 path: CustomArduinoJson - - #- name: Install SwitchLib - # uses: actions/checkout@v2 - # with: - # repository: justcallmekoko/SwitchLib - # ref: main - # path: CustomSwitchLib - name: Install LinkedList uses: actions/checkout@v2 diff --git a/esp32_marauder/WiFiScan.cpp b/esp32_marauder/WiFiScan.cpp index 0d5253d37..be8a270d6 100644 --- a/esp32_marauder/WiFiScan.cpp +++ b/esp32_marauder/WiFiScan.cpp @@ -1928,9 +1928,11 @@ void WiFiScan::executeSwiftpairSpam(EBLEPayloadType type) { //NimBLEAdvertisementData advertisementData = getSwiftAdvertisementData(); NimBLEAdvertisementData advertisementData = this->GetUniversalAdvertisementData(type); pAdvertising->setAdvertisementData(advertisementData); + Serial.println("Advertising..."); pAdvertising->start(); delay(10); pAdvertising->stop(); + Serial.println("Advertising stop"); NimBLEDevice::deinit(); #endif @@ -2362,7 +2364,7 @@ void WiFiScan::RunBluetoothScan(uint8_t scan_mode, uint16_t color) display_obj.tft.setTextColor(TFT_CYAN, TFT_BLACK); display_obj.setupScrollArea(display_obj.TOP_FIXED_AREA_2, BOT_FIXED_AREA); #endif - pBLEScan->setAdvertisedDeviceCallbacks(new bluetoothScanAllCallback(), false); + pBLEScan->setScanCallbacks(new bluetoothScanAllCallback(), false); } else if ((scan_mode == BT_SCAN_WAR_DRIVE) || (scan_mode == BT_SCAN_WAR_DRIVE_CONT)) { #ifdef HAS_GPS @@ -2399,9 +2401,9 @@ void WiFiScan::RunBluetoothScan(uint8_t scan_mode, uint16_t color) display_obj.setupScrollArea(display_obj.TOP_FIXED_AREA_2, BOT_FIXED_AREA); #endif if (scan_mode != BT_SCAN_WAR_DRIVE_CONT) - pBLEScan->setAdvertisedDeviceCallbacks(new bluetoothScanAllCallback(), false); + pBLEScan->setScanCallbacks(new bluetoothScanAllCallback(), false); else - pBLEScan->setAdvertisedDeviceCallbacks(new bluetoothScanAllCallback(), true); + pBLEScan->setScanCallbacks(new bluetoothScanAllCallback(), true); } else if (scan_mode == BT_SCAN_SKIMMERS) { @@ -2418,13 +2420,13 @@ void WiFiScan::RunBluetoothScan(uint8_t scan_mode, uint16_t color) display_obj.tft.setTextColor(TFT_BLACK, TFT_DARKGREY); display_obj.setupScrollArea(display_obj.TOP_FIXED_AREA_2, BOT_FIXED_AREA); #endif - pBLEScan->setAdvertisedDeviceCallbacks(new bluetoothScanSkimmersCallback(), false); + pBLEScan->setScanCallbacks(new bluetoothScanSkimmersCallback(), false); } pBLEScan->setActiveScan(true); //active scan uses more power, but get results faster pBLEScan->setInterval(97); pBLEScan->setWindow(37); // less or equal setInterval value pBLEScan->setMaxResults(0); - pBLEScan->start(0, scanCompleteCB, false); + pBLEScan->start(0, false); Serial.println("Started BLE Scan"); this->ble_initialized = true; initTime = millis(); From 99810dd9454425947f7f4ca913da70d3ec561f6f Mon Sep 17 00:00:00 2001 From: Just Call Me Koko Date: Wed, 26 Jun 2024 19:03:32 -0400 Subject: [PATCH 12/13] Idk...remove a bunch of shit espressif broke --- esp32_marauder/WiFiScan.cpp | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/esp32_marauder/WiFiScan.cpp b/esp32_marauder/WiFiScan.cpp index be8a270d6..148ccf73e 100644 --- a/esp32_marauder/WiFiScan.cpp +++ b/esp32_marauder/WiFiScan.cpp @@ -19,7 +19,6 @@ extern "C" int ieee80211_raw_frame_sanity_check(int32_t arg, int32_t arg2, int32 extern "C" { uint8_t esp_base_mac_addr[6]; - esp_err_t esp_base_mac_addr_set(const uint8_t *addr); esp_err_t esp_ble_gap_set_rand_addr(const uint8_t *rand_addr); } @@ -1928,11 +1927,9 @@ void WiFiScan::executeSwiftpairSpam(EBLEPayloadType type) { //NimBLEAdvertisementData advertisementData = getSwiftAdvertisementData(); NimBLEAdvertisementData advertisementData = this->GetUniversalAdvertisementData(type); pAdvertising->setAdvertisementData(advertisementData); - Serial.println("Advertising..."); pAdvertising->start(); delay(10); pAdvertising->stop(); - Serial.println("Advertising stop"); NimBLEDevice::deinit(); #endif @@ -2364,7 +2361,7 @@ void WiFiScan::RunBluetoothScan(uint8_t scan_mode, uint16_t color) display_obj.tft.setTextColor(TFT_CYAN, TFT_BLACK); display_obj.setupScrollArea(display_obj.TOP_FIXED_AREA_2, BOT_FIXED_AREA); #endif - pBLEScan->setScanCallbacks(new bluetoothScanAllCallback(), false); + pBLEScan->setAdvertisedDeviceCallbacks(new bluetoothScanAllCallback(), false); } else if ((scan_mode == BT_SCAN_WAR_DRIVE) || (scan_mode == BT_SCAN_WAR_DRIVE_CONT)) { #ifdef HAS_GPS @@ -2401,9 +2398,9 @@ void WiFiScan::RunBluetoothScan(uint8_t scan_mode, uint16_t color) display_obj.setupScrollArea(display_obj.TOP_FIXED_AREA_2, BOT_FIXED_AREA); #endif if (scan_mode != BT_SCAN_WAR_DRIVE_CONT) - pBLEScan->setScanCallbacks(new bluetoothScanAllCallback(), false); + pBLEScan->setAdvertisedDeviceCallbacks(new bluetoothScanAllCallback(), false); else - pBLEScan->setScanCallbacks(new bluetoothScanAllCallback(), true); + pBLEScan->setAdvertisedDeviceCallbacks(new bluetoothScanAllCallback(), true); } else if (scan_mode == BT_SCAN_SKIMMERS) { @@ -2420,13 +2417,13 @@ void WiFiScan::RunBluetoothScan(uint8_t scan_mode, uint16_t color) display_obj.tft.setTextColor(TFT_BLACK, TFT_DARKGREY); display_obj.setupScrollArea(display_obj.TOP_FIXED_AREA_2, BOT_FIXED_AREA); #endif - pBLEScan->setScanCallbacks(new bluetoothScanSkimmersCallback(), false); + pBLEScan->setAdvertisedDeviceCallbacks(new bluetoothScanSkimmersCallback(), false); } pBLEScan->setActiveScan(true); //active scan uses more power, but get results faster pBLEScan->setInterval(97); pBLEScan->setWindow(37); // less or equal setInterval value pBLEScan->setMaxResults(0); - pBLEScan->start(0, false); + pBLEScan->start(0, scanCompleteCB, false); Serial.println("Started BLE Scan"); this->ble_initialized = true; initTime = millis(); From 585b7afdb3cf94a8852a4fbd8c53289c038eb03b Mon Sep 17 00:00:00 2001 From: Just Call Me Koko Date: Wed, 26 Jun 2024 19:07:08 -0400 Subject: [PATCH 13/13] Revert everything Yea don't expect another release. I'm getting tired of this bullshit --- .github/workflows/build_push.yml | 33 ++++++++++++++++++++++---------- 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/.github/workflows/build_push.yml b/.github/workflows/build_push.yml index 6f553b364..d9d6fdd35 100644 --- a/.github/workflows/build_push.yml +++ b/.github/workflows/build_push.yml @@ -54,12 +54,12 @@ jobs: ref: v2.0.6 path: CustomMicroNMEA - #- name: Install ESPAsyncWebServer - # uses: actions/checkout@v2 - # with: - # repository: bigbrodude6119/ESPAsyncWebServer - # ref: master - # path: CustomESPAsyncWebServer + - name: Install ESPAsyncWebServer + uses: actions/checkout@v2 + with: + repository: bigbrodude6119/ESPAsyncWebServer + ref: master + path: CustomESPAsyncWebServer - name: Install TFT_eSPI uses: actions/checkout@v2 @@ -86,7 +86,7 @@ jobs: uses: actions/checkout@v2 with: repository: h2zero/NimBLE-Arduino - ref: master + ref: 1.2.0 path: CustomNimBLE-Arduino - name: Install Adafruit_NeoPixel @@ -102,6 +102,13 @@ jobs: repository: bblanchon/ArduinoJson ref: v6.18.2 path: CustomArduinoJson + + #- name: Install SwitchLib + # uses: actions/checkout@v2 + # with: + # repository: justcallmekoko/SwitchLib + # ref: main + # path: CustomSwitchLib - name: Install LinkedList uses: actions/checkout@v2 @@ -159,9 +166,15 @@ jobs: - name: Modify platform.txt run: | - for i in $(find /home/runner/.arduino15/packages/esp32/ -name "ld_libs"); do - sed -i 's/-lxtensa /-lxtensa -zmuldefs /' "$i" - cat "$i" + for i in $(find /home/runner/.arduino15/packages/esp32/hardware/esp32/ -name "platform.txt"); do + sed -i 's/compiler.c.elf.libs.esp32c3=/compiler.c.elf.libs.esp32c3=-zmuldefs /' "$i" + sed -i 's/compiler.c.elf.libs.esp32s3=/compiler.c.elf.libs.esp32s3=-zmuldefs /' "$i" + sed -i 's/compiler.c.elf.libs.esp32s2=/compiler.c.elf.libs.esp32s2=-zmuldefs /' "$i" + sed -i 's/compiler.c.elf.libs.esp32=/compiler.c.elf.libs.esp32=-zmuldefs /' "$i" + cat "$i" | grep compiler.c.elf.libs.esp32c3 + cat "$i" | grep compiler.c.elf.libs.esp32s3 + cat "$i" | grep compiler.c.elf.libs.esp32s2 + cat "$i" | grep compiler.c.elf.libs.esp32 done - name: Configure configs.h for Flipper Zero WiFi Dev Board

Try opening this link instead