├── .editorconfig ├── .gitignore ├── .gitmodules ├── CMakeLists.txt ├── Kconfig ├── LICENSE.txt ├── Makefile ├── README-flash_api.md ├── README-wifi_api.md ├── README.md ├── component.mk ├── core ├── auth.c ├── httpd-espfs.c ├── httpd-freertos.c ├── httpd-nonos.c ├── httpd-platform.h ├── httpd.c ├── libesphttpd_base64.c ├── libesphttpd_base64.h ├── linux │ └── esp_log.c └── sha1.c ├── include ├── libesphttpd │ ├── auth.h │ ├── captdns.h │ ├── cgi_common.h │ ├── cgiflash.h │ ├── cgiredirect.h │ ├── cgiwebsocket.h │ ├── cgiwifi.h │ ├── esp.h │ ├── esp32_httpd_vfs.h │ ├── espmissingincludes.h │ ├── httpd-espfs.h │ ├── httpd-freertos.h │ ├── httpd.h │ ├── kref.h │ ├── linux.h │ ├── platform.h │ ├── route.h │ ├── sha1.h │ └── user_config.h └── linux │ └── esp_log.h ├── mkupgimg ├── .gitignore ├── Makefile └── mkupgimg.c ├── standalone └── CMakeLists.txt └── util ├── captdns.c ├── cgi_common.c ├── cgiflash.c ├── cgiredirect.c ├── cgiwebsocket.c ├── cgiwifi.c ├── esp32_httpd_vfs.c └── esp32_wifi.c /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps maintain consistent coding styles for multiple developers working on the same project across various editors and IDEs. 2 | # For more details, visit https://editorconfig.org 3 | # This is top-most EditorConfig file 4 | root = true 5 | 6 | # Unix-style newlines with a newline ending every file 7 | [*] 8 | charset = utf-8 9 | indent_style = tab 10 | indent_size = 4 11 | trim_trailing_whitespace = true 12 | # end_of_line = lf # this was causing vscode to modify every file it opens (on windows) 13 | insert_final_newline = true 14 | 15 | # Tab indentation (no size specified) 16 | [Makefile] 17 | indent_style = tab 18 | 19 | [*.{c,h,cpp,hpp}] 20 | indent_style = tab 21 | indent_size = 4 22 | cpp_indent_braces=false 23 | cpp_indent_multi_line_relative_to=innermost_parenthesis 24 | cpp_indent_within_parentheses=indent 25 | cpp_indent_preserve_within_parentheses=false 26 | cpp_indent_case_labels=false 27 | cpp_indent_case_contents=true 28 | cpp_indent_case_contents_when_block=false 29 | cpp_indent_lambda_braces_when_parameter=false 30 | cpp_indent_goto_labels=one_left 31 | cpp_indent_preprocessor=leftmost_column 32 | cpp_indent_access_specifiers=false 33 | cpp_indent_namespace_contents=true 34 | cpp_indent_preserve_comments=false 35 | cpp_new_line_before_open_brace_namespace=ignore 36 | cpp_new_line_before_open_brace_type=ignore 37 | cpp_new_line_before_open_brace_function=ignore 38 | cpp_new_line_before_open_brace_block=ignore 39 | cpp_new_line_before_open_brace_lambda=ignore 40 | cpp_new_line_scope_braces_on_separate_lines=false 41 | cpp_new_line_close_brace_same_line_empty_type=false 42 | cpp_new_line_close_brace_same_line_empty_function=false 43 | cpp_new_line_before_catch=true 44 | cpp_new_line_before_else=true 45 | cpp_new_line_before_while_in_do_while=false 46 | cpp_space_before_function_open_parenthesis=remove 47 | cpp_space_within_parameter_list_parentheses=false 48 | cpp_space_between_empty_parameter_list_parentheses=false 49 | cpp_space_after_keywords_in_control_flow_statements=true 50 | cpp_space_within_control_flow_statement_parentheses=false 51 | cpp_space_before_lambda_open_parenthesis=false 52 | cpp_space_within_cast_parentheses=false 53 | cpp_space_after_cast_close_parenthesis=false 54 | cpp_space_within_expression_parentheses=false 55 | cpp_space_before_block_open_brace=true 56 | cpp_space_between_empty_braces=false 57 | cpp_space_before_initializer_list_open_brace=false 58 | cpp_space_within_initializer_list_braces=false 59 | cpp_space_preserve_in_initializer_list=true 60 | cpp_space_before_open_square_bracket=false 61 | cpp_space_within_square_brackets=false 62 | cpp_space_before_empty_square_brackets=false 63 | cpp_space_between_empty_square_brackets=false 64 | cpp_space_group_square_brackets=true 65 | cpp_space_within_lambda_brackets=false 66 | cpp_space_between_empty_lambda_brackets=false 67 | cpp_space_before_comma=false 68 | cpp_space_after_comma=true 69 | cpp_space_remove_around_member_operators=true 70 | cpp_space_before_inheritance_colon=true 71 | cpp_space_before_constructor_colon=true 72 | cpp_space_remove_before_semicolon=true 73 | cpp_space_after_semicolon=false 74 | cpp_space_remove_around_unary_operator=true 75 | cpp_space_around_binary_operator=insert 76 | cpp_space_around_assignment_operator=insert 77 | cpp_space_pointer_reference_alignment=right 78 | cpp_space_around_ternary_operator=insert 79 | cpp_wrap_preserve_blocks=one_liners 80 | 81 | [*.{htm,html,css,sass,scss,less,svg}] 82 | indent_size = 2 83 | 84 | [*.json] 85 | indent_size = 2 86 | trim_trailing_whitespace = true 87 | insert_final_newline = true 88 | 89 | [*.js] 90 | block_comment_start = /** 91 | block_comment = * 92 | block_comment_end = */ 93 | 94 | [*.{cmd,bat}] 95 | end_of_line = crlf 96 | 97 | [*.sh] 98 | end_of_line = lf -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | espfs/mkespfsimage/*.o 3 | espfs/mkespfsimage/mkespfsimage 4 | webpages.espfs 5 | libesphttpd.a 6 | espfs/espfstest/*.o 7 | espfs/espfstest/espfstest 8 | *.DS_Store 9 | html_compressed/ 10 | libwebpages-espfs.a 11 | *.swp 12 | standalone/CMakeCache.txt 13 | standalone/CMakeFiles/ 14 | standalone/Makefile 15 | standalone/cmake_install.cmake 16 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chmorgan/libesphttpd/c964dd42e73f5d1a8249b26a8ee0bb727548a728/.gitmodules -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set (libesphttpd_SOURCES "core/auth.c" 2 | "core/httpd-freertos.c" 3 | "core/httpd.c" 4 | "core/sha1.c" 5 | "core/libesphttpd_base64.c" 6 | "util/captdns.c" 7 | "util/cgi_common.c" 8 | "util/cgiflash.c" 9 | "util/cgiredirect.c" 10 | "util/cgiwebsocket.c" 11 | "util/cgiwifi.c" 12 | "util/cgiredirect.c" 13 | "util/esp32_httpd_vfs.c" 14 | "util/esp32_wifi.c") 15 | 16 | set (libesphttpd_PRIV_INCLUDE_DIRS "core" 17 | "util") 18 | 19 | if (CONFIG_ESPHTTPD_USE_ESPFS) 20 | list (APPEND libesphttpd_SOURCES "core/httpd-espfs.c") 21 | list (APPEND libesphttpd_PRIV_INCLUDE_DIRS "../espfs/include") 22 | endif (CONFIG_ESPHTTPD_USE_ESPFS) 23 | 24 | idf_component_register( 25 | SRCS "${libesphttpd_SOURCES}" 26 | INCLUDE_DIRS "include" 27 | PRIV_INCLUDE_DIRS "${libesphttpd_PRIV_INCLUDE_DIRS}" 28 | REQUIRES "app_update" 29 | "json" 30 | "spi_flash" 31 | "wpa_supplicant" 32 | "openssl" 33 | ) 34 | 35 | target_compile_definitions (${COMPONENT_TARGET} PUBLIC -DFREERTOS) 36 | -------------------------------------------------------------------------------- /Kconfig: -------------------------------------------------------------------------------- 1 | #ESP-IDF Kconfig configuration file. Not used for ESP8266 RTOS/non-RTOS SDKs. 2 | menu "esphttpd" 3 | 4 | menuconfig ESPHTTPD_ENABLED 5 | bool "esphttpd" 6 | help 7 | Select this option to enable libesphttpd and show the submodule with libesphttpd configuration 8 | 9 | config ESPHTTPD_STACK_SIZE 10 | int "Stack size of ESP-HTTPD task" 11 | depends on ESPHTTPD_ENABLED 12 | range 1024 16384 13 | default 4096 14 | help 15 | Stack size reserved for the esp-httpd main task plus CGIs. 16 | 17 | config ESPHTTPD_PROC_AFFINITY 18 | bool "Processor Affinity Support" 19 | depends on ESPHTTPD_ENABLED 20 | default n 21 | help 22 | Enable Processor Affinity Support 23 | 24 | config ESPHTTPD_PROC_CORE 25 | int "Bind to Processor Core" 26 | depends on ESPHTTPD_PROC_AFFINITY 27 | range 0 1 28 | default 0 29 | help 30 | Select Core 0 or Core 1 31 | 32 | config ESPHTTPD_PROC_PRI 33 | int "Process Task Priority" 34 | depends on ESPHTTPD_ENABLED 35 | range 1 17 36 | default 4 37 | help 38 | Set esphttpd Process Task Priority 39 | 40 | config ESPHTTPD_SHUTDOWN_SUPPORT 41 | bool "Enable shutdown support" 42 | depends on ESPHTTPD_ENABLED 43 | default n 44 | help 45 | Add support for server shutdown. Adds ~500 bytes of code. 46 | 47 | config ESPHTTPD_CORS_SUPPORT 48 | bool "CORS support" 49 | depends on ESPHTTPD_ENABLED 50 | help 51 | Enable support for CORS, cross origin resource sharing. 52 | NOTE: Requires 256 bytes of RAM for each connection 53 | 54 | config ESPHTTPD_USE_ESPFS 55 | bool "Enable espfs filesystem" 56 | depends on ESPHTTPD_ENABLED 57 | default y 58 | help 59 | Enables integration with espfs (readonly) filesystem. You need to include espfs submodule in your project if enabled. 60 | 61 | config ESPHTTPD_SO_REUSEADDR 62 | bool "Set SO_REUSEADDR to avoid waiting for a port in TIME_WAIT." 63 | depends on ESPHTTPD_ENABLED 64 | default n 65 | help 66 | Requires LWIP_SO_REUSE to be enabled. 67 | 68 | Sets the SO_REUSEADDR flag on a socket prior to attempting to bind(). Avoids the condition where 69 | the bind() calls will fail while the port is in TIME_WAIT for a number of minutes. 70 | 71 | config ESPHTTPD_TCP_NODELAY 72 | bool "Set TCP_NODELAY." 73 | depends on ESPHTTPD_ENABLED 74 | default y 75 | help 76 | Set TCP_NODELAY to avoid waiting for a ACK to send multiple small frames (It will disable Nagle's TCP Algorithm). It can speed-up transfers for small files. 77 | 78 | config ESPHTTPD_SSL_SUPPORT 79 | bool "Enable SSL support" 80 | depends on ESPHTTPD_ENABLED 81 | default n 82 | help 83 | SSL connections require ~32k of ram each. 84 | 85 | Enabling this allows the server to be placed into ssl mode. 86 | 87 | config ESPHTTPD_BACKLOG_SUPPORT 88 | bool "Write data to backlog when it can't be sent" 89 | depends on ESPHTTPD_ENABLED 90 | default n 91 | help 92 | A non-os specific option. FreeRTOS uses blocking sockets so data will always be sent and there is 93 | no need for the backlog. 94 | 95 | If you are using FreeRTOS you'll save codespace by leaving this option disabled. 96 | 97 | config ESPHTTPD_SANITIZE_URLS 98 | bool "Sanitize client requests" 99 | depends on ESPHTTPD_ENABLED 100 | default y 101 | help 102 | Sanitize client's URL requests by treating multiple repeated slashes in the 103 | URL's path as a single slash. 104 | 105 | config ESPHTTPD_SINGLE_REQUEST 106 | bool "Single request per connection" 107 | depends on ESPHTTPD_ENABLED 108 | default n 109 | help 110 | Include the "Connection: close" header. This is useful for captive portals. 111 | 112 | config ESPHTTPD_ALLOW_OTA_FACTORY_APP 113 | bool "Allow OTA of Factory Partition (not recommended)" 114 | depends on ESPHTTPD_ENABLED 115 | default n 116 | help 117 | Allows cgiUploadFirmware() in cgiflash.c to write to the Factory partition. It it not recommended for production use. 118 | 119 | endmenu 120 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Mozilla Public License, version 2.0 2 | 3 | 1. Definitions 4 | 5 | 1.1. "Contributor" 6 | 7 | means each individual or legal entity that creates, contributes to the 8 | creation of, or owns Covered Software. 9 | 10 | 1.2. "Contributor Version" 11 | 12 | means the combination of the Contributions of others (if any) used by a 13 | Contributor and that particular Contributor's Contribution. 14 | 15 | 1.3. "Contribution" 16 | 17 | means Covered Software of a particular Contributor. 18 | 19 | 1.4. "Covered Software" 20 | 21 | means Source Code Form to which the initial Contributor has attached the 22 | notice in Exhibit A, the Executable Form of such Source Code Form, and 23 | Modifications of such Source Code Form, in each case including portions 24 | thereof. 25 | 26 | 1.5. "Incompatible With Secondary Licenses" 27 | means 28 | 29 | a. that the initial Contributor has attached the notice described in 30 | Exhibit B to the Covered Software; or 31 | 32 | b. that the Covered Software was made available under the terms of 33 | version 1.1 or earlier of the License, but not also under the terms of 34 | a Secondary License. 35 | 36 | 1.6. "Executable Form" 37 | 38 | means any form of the work other than Source Code Form. 39 | 40 | 1.7. "Larger Work" 41 | 42 | means a work that combines Covered Software with other material, in a 43 | separate file or files, that is not Covered Software. 44 | 45 | 1.8. "License" 46 | 47 | means this document. 48 | 49 | 1.9. "Licensable" 50 | 51 | means having the right to grant, to the maximum extent possible, whether 52 | at the time of the initial grant or subsequently, any and all of the 53 | rights conveyed by this License. 54 | 55 | 1.10. "Modifications" 56 | 57 | means any of the following: 58 | 59 | a. any file in Source Code Form that results from an addition to, 60 | deletion from, or modification of the contents of Covered Software; or 61 | 62 | b. any new file in Source Code Form that contains any Covered Software. 63 | 64 | 1.11. "Patent Claims" of a Contributor 65 | 66 | means any patent claim(s), including without limitation, method, 67 | process, and apparatus claims, in any patent Licensable by such 68 | Contributor that would be infringed, but for the grant of the License, 69 | by the making, using, selling, offering for sale, having made, import, 70 | or transfer of either its Contributions or its Contributor Version. 71 | 72 | 1.12. "Secondary License" 73 | 74 | means either the GNU General Public License, Version 2.0, the GNU Lesser 75 | General Public License, Version 2.1, the GNU Affero General Public 76 | License, Version 3.0, or any later versions of those licenses. 77 | 78 | 1.13. "Source Code Form" 79 | 80 | means the form of the work preferred for making modifications. 81 | 82 | 1.14. "You" (or "Your") 83 | 84 | means an individual or a legal entity exercising rights under this 85 | License. For legal entities, "You" includes any entity that controls, is 86 | controlled by, or is under common control with You. For purposes of this 87 | definition, "control" means (a) the power, direct or indirect, to cause 88 | the direction or management of such entity, whether by contract or 89 | otherwise, or (b) ownership of more than fifty percent (50%) of the 90 | outstanding shares or beneficial ownership of such entity. 91 | 92 | 93 | 2. License Grants and Conditions 94 | 95 | 2.1. Grants 96 | 97 | Each Contributor hereby grants You a world-wide, royalty-free, 98 | non-exclusive license: 99 | 100 | a. under intellectual property rights (other than patent or trademark) 101 | Licensable by such Contributor to use, reproduce, make available, 102 | modify, display, perform, distribute, and otherwise exploit its 103 | Contributions, either on an unmodified basis, with Modifications, or 104 | as part of a Larger Work; and 105 | 106 | b. under Patent Claims of such Contributor to make, use, sell, offer for 107 | sale, have made, import, and otherwise transfer either its 108 | Contributions or its Contributor Version. 109 | 110 | 2.2. Effective Date 111 | 112 | The licenses granted in Section 2.1 with respect to any Contribution 113 | become effective for each Contribution on the date the Contributor first 114 | distributes such Contribution. 115 | 116 | 2.3. Limitations on Grant Scope 117 | 118 | The licenses granted in this Section 2 are the only rights granted under 119 | this License. No additional rights or licenses will be implied from the 120 | distribution or licensing of Covered Software under this License. 121 | Notwithstanding Section 2.1(b) above, no patent license is granted by a 122 | Contributor: 123 | 124 | a. for any code that a Contributor has removed from Covered Software; or 125 | 126 | b. for infringements caused by: (i) Your and any other third party's 127 | modifications of Covered Software, or (ii) the combination of its 128 | Contributions with other software (except as part of its Contributor 129 | Version); or 130 | 131 | c. under Patent Claims infringed by Covered Software in the absence of 132 | its Contributions. 133 | 134 | This License does not grant any rights in the trademarks, service marks, 135 | or logos of any Contributor (except as may be necessary to comply with 136 | the notice requirements in Section 3.4). 137 | 138 | 2.4. Subsequent Licenses 139 | 140 | No Contributor makes additional grants as a result of Your choice to 141 | distribute the Covered Software under a subsequent version of this 142 | License (see Section 10.2) or under the terms of a Secondary License (if 143 | permitted under the terms of Section 3.3). 144 | 145 | 2.5. Representation 146 | 147 | Each Contributor represents that the Contributor believes its 148 | Contributions are its original creation(s) or it has sufficient rights to 149 | grant the rights to its Contributions conveyed by this License. 150 | 151 | 2.6. Fair Use 152 | 153 | This License is not intended to limit any rights You have under 154 | applicable copyright doctrines of fair use, fair dealing, or other 155 | equivalents. 156 | 157 | 2.7. Conditions 158 | 159 | Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in 160 | Section 2.1. 161 | 162 | 163 | 3. Responsibilities 164 | 165 | 3.1. Distribution of Source Form 166 | 167 | All distribution of Covered Software in Source Code Form, including any 168 | Modifications that You create or to which You contribute, must be under 169 | the terms of this License. You must inform recipients that the Source 170 | Code Form of the Covered Software is governed by the terms of this 171 | License, and how they can obtain a copy of this License. You may not 172 | attempt to alter or restrict the recipients' rights in the Source Code 173 | Form. 174 | 175 | 3.2. Distribution of Executable Form 176 | 177 | If You distribute Covered Software in Executable Form then: 178 | 179 | a. such Covered Software must also be made available in Source Code Form, 180 | as described in Section 3.1, and You must inform recipients of the 181 | Executable Form how they can obtain a copy of such Source Code Form by 182 | reasonable means in a timely manner, at a charge no more than the cost 183 | of distribution to the recipient; and 184 | 185 | b. You may distribute such Executable Form under the terms of this 186 | License, or sublicense it under different terms, provided that the 187 | license for the Executable Form does not attempt to limit or alter the 188 | recipients' rights in the Source Code Form under this License. 189 | 190 | 3.3. Distribution of a Larger Work 191 | 192 | You may create and distribute a Larger Work under terms of Your choice, 193 | provided that You also comply with the requirements of this License for 194 | the Covered Software. If the Larger Work is a combination of Covered 195 | Software with a work governed by one or more Secondary Licenses, and the 196 | Covered Software is not Incompatible With Secondary Licenses, this 197 | License permits You to additionally distribute such Covered Software 198 | under the terms of such Secondary License(s), so that the recipient of 199 | the Larger Work may, at their option, further distribute the Covered 200 | Software under the terms of either this License or such Secondary 201 | License(s). 202 | 203 | 3.4. Notices 204 | 205 | You may not remove or alter the substance of any license notices 206 | (including copyright notices, patent notices, disclaimers of warranty, or 207 | limitations of liability) contained within the Source Code Form of the 208 | Covered Software, except that You may alter any license notices to the 209 | extent required to remedy known factual inaccuracies. 210 | 211 | 3.5. Application of Additional Terms 212 | 213 | You may choose to offer, and to charge a fee for, warranty, support, 214 | indemnity or liability obligations to one or more recipients of Covered 215 | Software. However, You may do so only on Your own behalf, and not on 216 | behalf of any Contributor. You must make it absolutely clear that any 217 | such warranty, support, indemnity, or liability obligation is offered by 218 | You alone, and You hereby agree to indemnify every Contributor for any 219 | liability incurred by such Contributor as a result of warranty, support, 220 | indemnity or liability terms You offer. You may include additional 221 | disclaimers of warranty and limitations of liability specific to any 222 | jurisdiction. 223 | 224 | 4. Inability to Comply Due to Statute or Regulation 225 | 226 | If it is impossible for You to comply with any of the terms of this License 227 | with respect to some or all of the Covered Software due to statute, 228 | judicial order, or regulation then You must: (a) comply with the terms of 229 | this License to the maximum extent possible; and (b) describe the 230 | limitations and the code they affect. Such description must be placed in a 231 | text file included with all distributions of the Covered Software under 232 | this License. Except to the extent prohibited by statute or regulation, 233 | such description must be sufficiently detailed for a recipient of ordinary 234 | skill to be able to understand it. 235 | 236 | 5. Termination 237 | 238 | 5.1. The rights granted under this License will terminate automatically if You 239 | fail to comply with any of its terms. However, if You become compliant, 240 | then the rights granted under this License from a particular Contributor 241 | are reinstated (a) provisionally, unless and until such Contributor 242 | explicitly and finally terminates Your grants, and (b) on an ongoing 243 | basis, if such Contributor fails to notify You of the non-compliance by 244 | some reasonable means prior to 60 days after You have come back into 245 | compliance. Moreover, Your grants from a particular Contributor are 246 | reinstated on an ongoing basis if such Contributor notifies You of the 247 | non-compliance by some reasonable means, this is the first time You have 248 | received notice of non-compliance with this License from such 249 | Contributor, and You become compliant prior to 30 days after Your receipt 250 | of the notice. 251 | 252 | 5.2. If You initiate litigation against any entity by asserting a patent 253 | infringement claim (excluding declaratory judgment actions, 254 | counter-claims, and cross-claims) alleging that a Contributor Version 255 | directly or indirectly infringes any patent, then the rights granted to 256 | You by any and all Contributors for the Covered Software under Section 257 | 2.1 of this License shall terminate. 258 | 259 | 5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user 260 | license agreements (excluding distributors and resellers) which have been 261 | validly granted by You or Your distributors under this License prior to 262 | termination shall survive termination. 263 | 264 | 6. Disclaimer of Warranty 265 | 266 | Covered Software is provided under this License on an "as is" basis, 267 | without warranty of any kind, either expressed, implied, or statutory, 268 | including, without limitation, warranties that the Covered Software is free 269 | of defects, merchantable, fit for a particular purpose or non-infringing. 270 | The entire risk as to the quality and performance of the Covered Software 271 | is with You. Should any Covered Software prove defective in any respect, 272 | You (not any Contributor) assume the cost of any necessary servicing, 273 | repair, or correction. This disclaimer of warranty constitutes an essential 274 | part of this License. No use of any Covered Software is authorized under 275 | this License except under this disclaimer. 276 | 277 | 7. Limitation of Liability 278 | 279 | Under no circumstances and under no legal theory, whether tort (including 280 | negligence), contract, or otherwise, shall any Contributor, or anyone who 281 | distributes Covered Software as permitted above, be liable to You for any 282 | direct, indirect, special, incidental, or consequential damages of any 283 | character including, without limitation, damages for lost profits, loss of 284 | goodwill, work stoppage, computer failure or malfunction, or any and all 285 | other commercial damages or losses, even if such party shall have been 286 | informed of the possibility of such damages. This limitation of liability 287 | shall not apply to liability for death or personal injury resulting from 288 | such party's negligence to the extent applicable law prohibits such 289 | limitation. Some jurisdictions do not allow the exclusion or limitation of 290 | incidental or consequential damages, so this exclusion and limitation may 291 | not apply to You. 292 | 293 | 8. Litigation 294 | 295 | Any litigation relating to this License may be brought only in the courts 296 | of a jurisdiction where the defendant maintains its principal place of 297 | business and such litigation shall be governed by laws of that 298 | jurisdiction, without reference to its conflict-of-law provisions. Nothing 299 | in this Section shall prevent a party's ability to bring cross-claims or 300 | counter-claims. 301 | 302 | 9. Miscellaneous 303 | 304 | This License represents the complete agreement concerning the subject 305 | matter hereof. If any provision of this License is held to be 306 | unenforceable, such provision shall be reformed only to the extent 307 | necessary to make it enforceable. Any law or regulation which provides that 308 | the language of a contract shall be construed against the drafter shall not 309 | be used to construe this License against a Contributor. 310 | 311 | 312 | 10. Versions of the License 313 | 314 | 10.1. New Versions 315 | 316 | Mozilla Foundation is the license steward. Except as provided in Section 317 | 10.3, no one other than the license steward has the right to modify or 318 | publish new versions of this License. Each version will be given a 319 | distinguishing version number. 320 | 321 | 10.2. Effect of New Versions 322 | 323 | You may distribute the Covered Software under the terms of the version 324 | of the License under which You originally received the Covered Software, 325 | or under the terms of any subsequent version published by the license 326 | steward. 327 | 328 | 10.3. Modified Versions 329 | 330 | If you create software not governed by this License, and you want to 331 | create a new license for such software, you may create and use a 332 | modified version of this License if you rename the license and remove 333 | any references to the name of the license steward (except to note that 334 | such modified license differs from this License). 335 | 336 | 10.4. Distributing Source Code Form that is Incompatible With Secondary 337 | Licenses If You choose to distribute Source Code Form that is 338 | Incompatible With Secondary Licenses under the terms of this version of 339 | the License, the notice described in Exhibit B of this License must be 340 | attached. 341 | 342 | Exhibit A - Source Code Form License Notice 343 | 344 | This Source Code Form is subject to the 345 | terms of the Mozilla Public License, v. 346 | 2.0. If a copy of the MPL was not 347 | distributed with this file, You can 348 | obtain one at 349 | http://mozilla.org/MPL/2.0/. 350 | 351 | If it is not possible or desirable to put the notice in a particular file, 352 | then You may include the notice in a location (such as a LICENSE file in a 353 | relevant directory) where a recipient would be likely to look for such a 354 | notice. 355 | 356 | You may add additional accurate notices of copyright ownership. 357 | 358 | Exhibit B - "Incompatible With Secondary Licenses" Notice 359 | 360 | This Source Code Form is "Incompatible 361 | With Secondary Licenses", as defined by 362 | the Mozilla Public License, v. 2.0. 363 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | #Makefile, only used on esp8266 RTOS and non-RTOS SDK. Esp32 uses component.mk instead. 2 | 3 | # Directory the Makefile is in. Please don't include other Makefiles before this. 4 | THISDIR:=$(dir $(abspath $(lastword $(MAKEFILE_LIST)))) 5 | 6 | #Include httpd config from lower level, if it exists 7 | -include ../esphttpdconfig.mk 8 | 9 | #Default options. If you want to change them, please create ../esphttpdconfig.mk with the options you want in it. 10 | GZIP_COMPRESSION ?= no 11 | COMPRESS_W_YUI ?= no 12 | YUI-COMPRESSOR ?= /usr/bin/yui-compressor 13 | USE_HEATSHRINK ?= yes 14 | HTTPD_WEBSOCKETS ?= yes 15 | USE_OPENSDK ?= no 16 | HTTPD_MAX_CONNECTIONS ?= 4 17 | #For FreeRTOS 18 | HTTPD_STACKSIZE ?= 2048 19 | ENABLE_SSL_SUPPORT ?= no 20 | ENABLE_CORS_SUPPORT ?= no 21 | #Auto-detect ESP32 build if not given. 22 | ifneq (,$(wildcard $(SDK_PATH)/include/esp32)) 23 | ESP32 ?= yes 24 | FREERTOS ?= yes 25 | else 26 | ESP32 ?= no 27 | FREERTOS ?= no 28 | endif 29 | 30 | # Output directors to store intermediate compiled files 31 | # relative to the project directory 32 | BUILD_BASE = build 33 | 34 | # Base directory for the compiler. Needs a / at the end; if not set it'll use the tools that are in 35 | # the PATH. 36 | XTENSA_TOOLS_ROOT ?= 37 | 38 | # base directory of the ESP8266 SDK package, absolute 39 | # Only used for the non-FreeRTOS build 40 | SDK_BASE ?= /opt/Espressif/ESP8266_SDK 41 | 42 | # Base directory of the ESP8266 FreeRTOS SDK package, absolute 43 | # Only used for the FreeRTOS build 44 | SDK_PATH ?= /opt/Espressif/ESP8266_RTOS_SDK 45 | 46 | 47 | # name for the target project 48 | LIB = libesphttpd.a 49 | 50 | # which modules (subdirectories) of the project to include in compiling 51 | MODULES = espfs core util 52 | EXTRA_INCDIR = ./include \ 53 | . \ 54 | lib/heatshrink/ 55 | 56 | 57 | # for non-os builds osapi.h includes "user_config.h" so we have to ensure that 58 | # the include/libesphttpd folder is in the include path so this file can be found 59 | ifeq ("$(FREERTOS)","no") 60 | EXTRA_INCDIR += ./include/libesphttpd 61 | endif 62 | 63 | # compiler flags using during compilation of source files 64 | CFLAGS = -Os -ggdb -std=c99 -Werror -Wpointer-arith -Wundef -Wall -Wl,-EL -fno-inline-functions \ 65 | -nostdlib -mlongcalls -mtext-section-literals -D__ets__ -DICACHE_FLASH \ 66 | -Wno-address -DHTTPD_MAX_CONNECTIONS=$(HTTPD_MAX_CONNECTIONS) -DHTTPD_STACKSIZE=$(HTTPD_STACKSIZE) \ 67 | 68 | 69 | # various paths from the SDK used in this project 70 | SDK_LIBDIR = lib 71 | SDK_LDDIR = ld 72 | 73 | 74 | ifeq ("$(FREERTOS)","yes") 75 | CFLAGS += -DFREERTOS -DLWIP_OPEN_SRC -ffunction-sections -fdata-sections 76 | ifeq ("$(ESP32)","yes") 77 | SDK_INCDIR = include \ 78 | include/esp32 \ 79 | driver_lib/include \ 80 | extra_include \ 81 | third_party/include \ 82 | third_party/include/cjson \ 83 | third_party/include/freertos \ 84 | third_party/include/lwip \ 85 | third_party/include/lwip/ipv4 \ 86 | third_party/include/lwip/ipv6 \ 87 | third_party/include/ssl 88 | CFLAGS += -DESP32 -DFREERTOS -DLWIP_OPEN_SRC -ffunction-sections -fdata-sections 89 | else 90 | SDK_INCDIR = include \ 91 | include/freertos \ 92 | include/espressif/esp8266 \ 93 | include/espressif \ 94 | extra_include \ 95 | include/lwip \ 96 | include/lwip/lwip \ 97 | include/lwip/ipv4 \ 98 | include/lwip/ipv6 99 | CFLAGS += -DFREERTOS -DLWIP_OPEN_SRC -ffunction-sections -fdata-sections 100 | endif 101 | SDK_INCDIR := $(addprefix -I$(SDK_PATH)/,$(SDK_INCDIR)) 102 | else 103 | SDK_INCDIR = include 104 | SDK_INCDIR := $(addprefix -I$(SDK_BASE)/,$(SDK_INCDIR)) 105 | endif 106 | 107 | 108 | ifeq ("$(ESP32)","yes") 109 | TOOLPREFIX=xtensa-esp108-elf- 110 | CFLAGS+=-DESP32 111 | else 112 | TOOLPREFIX=xtensa-lx106-elf- 113 | endif 114 | 115 | # select which tools to use as compiler, librarian and linker 116 | CC := $(XTENSA_TOOLS_ROOT)$(TOOLPREFIX)gcc 117 | AR := $(XTENSA_TOOLS_ROOT)$(TOOLPREFIX)ar 118 | LD := $(XTENSA_TOOLS_ROOT)$(TOOLPREFIX)gcc 119 | OBJCOPY := $(XTENSA_TOOLS_ROOT)$(TOOLPREFIX)objcopy 120 | 121 | #### 122 | #### no user configurable options below here 123 | #### 124 | SRC_DIR := $(MODULES) 125 | BUILD_DIR := $(addprefix $(BUILD_BASE)/,$(MODULES)) 126 | 127 | SRC := $(foreach sdir,$(SRC_DIR),$(wildcard $(sdir)/*.c)) 128 | OBJ := $(patsubst %.c,$(BUILD_BASE)/%.o,$(SRC)) 129 | 130 | INCDIR := $(addprefix -I,$(SRC_DIR)) 131 | EXTRA_INCDIR := $(addprefix -I,$(EXTRA_INCDIR)) 132 | MODULE_INCDIR := $(addsuffix /include,$(INCDIR)) 133 | 134 | V ?= $(VERBOSE) 135 | ifeq ("$(V)","1") 136 | Q := 137 | vecho := @true 138 | else 139 | Q := @ 140 | vecho := @echo 141 | endif 142 | 143 | 144 | ifneq ("$(FREERTOS)","yes") 145 | ifeq ("$(USE_OPENSDK)","yes") 146 | CFLAGS += -DUSE_OPENSDK 147 | else 148 | CFLAGS += -D_STDINT_H 149 | endif 150 | endif 151 | 152 | ifeq ("$(GZIP_COMPRESSION)","yes") 153 | CFLAGS += -DGZIP_COMPRESSION 154 | endif 155 | 156 | ifeq ("$(USE_HEATSHRINK)","yes") 157 | CFLAGS += -DESPFS_HEATSHRINK 158 | endif 159 | 160 | ifeq ("$(HTTPD_WEBSOCKETS)","yes") 161 | CFLAGS += -DHTTPD_WEBSOCKETS 162 | endif 163 | 164 | ifeq ("$(ENABLE_SSL_SUPPORT)", "yes") 165 | CFLAGS += -DCONFIG_ESPHTTPD_SSL_SUPPORT=1 166 | endif 167 | 168 | ifeq ("$(ENABLE_CORS_SUPPORT)", "yes") 169 | CFLAGS += -DCONFIG_ESPHTTPD_CORS_SUPPORT=1 170 | endif 171 | 172 | ifeq ("$(ESP32)", "yes") 173 | CFLAGS += -DESP32=1 174 | endif 175 | 176 | vpath %.c $(SRC_DIR) 177 | 178 | define compile-objects 179 | $1/%.o: %.c 180 | $(vecho) "CC $$<" 181 | $(Q) $(CC) $(INCDIR) $(MODULE_INCDIR) $(EXTRA_INCDIR) $(SDK_INCDIR) $(CFLAGS) -c $$< -o $$@ 182 | endef 183 | 184 | .PHONY: all checkdirs clean webpages.espfs submodules 185 | 186 | all: checkdirs $(LIB) webpages.espfs libwebpages-espfs.a 187 | 188 | submodules: lib/heatshrink/Makefile 189 | lib/heatshrink/Makefile: 190 | $(Q) echo "Heatshrink isn't found. Checking out submodules to fetch it." 191 | $(Q) git submodule init 192 | $(Q) git submodule update 193 | 194 | 195 | $(LIB): $(BUILD_DIR) submodules $(OBJ) 196 | $(vecho) "AR $@" 197 | $(Q) $(AR) cru $@ $(OBJ) 198 | 199 | checkdirs: $(BUILD_DIR) 200 | 201 | $(BUILD_DIR): 202 | $(Q) mkdir -p $@ 203 | 204 | #ignore vim swap files 205 | FIND_OPTIONS = -not -iname '*.swp' 206 | 207 | webpages.espfs: $(HTMLDIR) espfs/mkespfsimage/mkespfsimage 208 | ifeq ("$(COMPRESS_W_YUI)","yes") 209 | $(Q) rm -rf html_compressed; 210 | $(Q) cp -r ../html html_compressed; 211 | $(Q) echo "Compression assets with yui-compressor. This may take a while..." 212 | $(Q) for file in `find html_compressed -type f -name "*.js"`; do $(YUI-COMPRESSOR) --type js $$file -o $$file; done 213 | $(Q) for file in `find html_compressed -type f -name "*.css"`; do $(YUI-COMPRESSOR) --type css $$file -o $$file; done 214 | $(Q) awk "BEGIN {printf \"YUI compression ratio was: %.2f%%\\n\", (`du -b -s html_compressed/ | sed 's/\([0-9]*\).*/\1/'`/`du -b -s ../html/ | sed 's/\([0-9]*\).*/\1/'`)*100}" 215 | # mkespfsimage will compress html, css, svg and js files with gzip by default if enabled 216 | # override with -g cmdline parameter 217 | $(Q) cd html_compressed; find . $(FIND_OPTIONS) | $(THISDIR)/espfs/mkespfsimage/mkespfsimage > $(THISDIR)/webpages.espfs; cd ..; 218 | else 219 | $(Q) cd ../html; find . $(FIND_OPTIONS) | $(THISDIR)/espfs/mkespfsimage/mkespfsimage > $(THISDIR)/webpages.espfs; cd .. 220 | endif 221 | 222 | libwebpages-espfs.a: webpages.espfs 223 | $(Q) $(OBJCOPY) -I binary -O elf32-xtensa-le -B xtensa --rename-section .data=.irom0.literal \ 224 | webpages.espfs build/webpages.espfs.o.tmp 225 | $(Q) $(LD) -nostdlib -Wl,-r build/webpages.espfs.o.tmp -o build/webpages.espfs.o -Wl,-T webpages.espfs.ld 226 | $(Q) $(AR) cru $@ build/webpages.espfs.o 227 | 228 | espfs/mkespfsimage/mkespfsimage: espfs/mkespfsimage/ 229 | $(Q) $(MAKE) -C espfs/mkespfsimage USE_HEATSHRINK="$(USE_HEATSHRINK)" GZIP_COMPRESSION="$(GZIP_COMPRESSION)" 230 | 231 | clean: 232 | $(Q) rm -f $(LIB) 233 | $(Q) find $(BUILD_BASE) -type f | xargs rm -f 234 | $(Q) make -C espfs/mkespfsimage/ clean 235 | $(Q) rm -rf $(FW_BASE) 236 | $(Q) rm -f webpages.espfs libwebpages-espfs.a 237 | ifeq ("$(COMPRESS_W_YUI)","yes") 238 | $(Q) rm -rf html_compressed 239 | endif 240 | 241 | $(foreach bdir,$(BUILD_DIR),$(eval $(call compile-objects,$(bdir)))) 242 | -------------------------------------------------------------------------------- /README-flash_api.md: -------------------------------------------------------------------------------- 1 | # Libesphttpd Flash-API 2 | 3 | Functions to flash firmware Over-The-Air. These are only useful if you have enabled OTA support. 4 | 5 | The simplest way to use the partition table is to make menuconfig and choose the simple predefined partition table: 6 | 7 | “Factory app, two OTA definitions” 8 | 9 | ## GUI 10 | See the example js/html code for the GUI here: https://github.com/chmorgan/esphttpd-freertos/blob/master/html/flash/index.html 11 | 12 | ## Functions defined in cgiflash.h 13 | 14 | * __cgiGetFirmwareNext()__ 15 | Legacy function for ESP8266 (not needed for ESP32) 16 | 17 | * __cgiUploadFirmware()__ 18 | CGI function writes HTTP POST data to flash. 19 | 20 | * __cgiRebootFirmware()__ 21 | CGI function reboots the ESP firmware after a short time-delay. 22 | 23 | * __cgiSetBoot()__ 24 | CGI function to change the selected boot partition. 25 | 26 | * __cgiEraseFlash()__ 27 | CGI function to erase flash memory. (only supports erasing data partitions) 28 | 29 | * __cgiGetFlashInfo()__ 30 | CGI function returns a JSON object describing the partition table. It can also verify the firmware images, but not by default because that process takes several seconds. 31 | 32 | ## Configuration Options 33 | 34 | * __Allow OTA of Factory Partition (not recommended)__ 35 | Defines CONFIG_ESPHTTPD_ALLOW_OTA_FACTORY_APP. This option allows cgiUploadFirmware() (in cgiflash.c) to write to the Factory partition. It it not recommended for production use. It is useful if you need to update the factory app via OTA for some reason. To use it, build this project with CONFIG_ESPHTTPD_ALLOW_OTA_FACTORY_APP and upload it to one of the OTA partitions on your device. Then reboot your device into that OTA and then you will be able to upload an image to the factory partition. 36 | 37 | ## HTTP REST API 38 | 39 | The flash API is specified in RAML. (see https://raml.org) 40 | 41 | ```yaml 42 | #%RAML 1.0 43 | title: flash 44 | version: v1 45 | baseUri: http://me.local/flash 46 | 47 | /flashinfo.json: 48 | description: Flash info 49 | get: 50 | description: Returns a JSON of info about the partition table 51 | queryParameters: 52 | ptype: 53 | displayName: ptype 54 | type: string 55 | description: String name of partition type (app, data). If not specified, return both app and data partitions. 56 | example: app 57 | required: false 58 | verify: 59 | displayName: verify 60 | type: number 61 | description: 1: verify the app partitions. 0 (default): don't verify. Note: verification takes >2s per partition! 62 | example: 1 63 | required: false 64 | partition: 65 | displayName: partition 66 | type: string 67 | description: String name of partition (i.e. factory, ota_0, ota_1). If not specified, return all. 68 | example: ota_0 69 | required: false 70 | responses: 71 | 200: 72 | body: 73 | application/json: 74 | type: object 75 | properties: 76 | app: array 77 | data: array 78 | example: | 79 | { 80 | "app": [{ 81 | "name": "factory", 82 | "size": 4259840, 83 | "version": "", 84 | "ota": false, 85 | "valid": true, 86 | "running": false, 87 | "bootset": false 88 | }, { 89 | "name": "ota_0", 90 | "size": 4259840, 91 | "version": "", 92 | "ota": true, 93 | "valid": true, 94 | "running": true, 95 | "bootset": true 96 | }, { 97 | "name": "ota_1", 98 | "size": 4259840, 99 | "version": "", 100 | "ota": true, 101 | "valid": true, 102 | "running": false, 103 | "bootset": false 104 | }], 105 | "data": [{ 106 | "name": "nvs", 107 | "size": 16384, 108 | "format": 2 109 | }, { 110 | "name": "otadata", 111 | "size": 8192, 112 | "format": 0 113 | }, { 114 | "name": "phy_init", 115 | "size": 4096, 116 | "format": 1 117 | }, { 118 | "name": "internalfs", 119 | "size": 3932160, 120 | "format": 129 121 | }] 122 | } 123 | 124 | /setboot: 125 | description: boot flag 126 | get: 127 | description: Set the boot flag. example GET /flash/setboot?partition=ota_1 128 | queryParameters: 129 | partition: 130 | displayName: partition 131 | type: string 132 | description: String name of partition (i.e. factory, ota_0, ota_1). If not specified, just return the current setting. 133 | example: ota_0 134 | required: false 135 | responses: 136 | 200: 137 | body: 138 | application/json: 139 | type: object 140 | properties: 141 | success: boolean 142 | boot: string 143 | example: | 144 | { 145 | "success": true 146 | "boot": "ota_0" 147 | } 148 | 149 | /reboot: 150 | description: Reboot the processor 151 | get: 152 | description: Reboot the processor. Waits 0.5s before rebooting to allow the JSON response to be sent. 153 | responses: 154 | 200: 155 | body: 156 | application/json: 157 | type: object 158 | properties: 159 | success: boolean 160 | message: string 161 | example: | 162 | { 163 | "success": true 164 | "message": "Rebooting..." 165 | } 166 | 167 | /upload: 168 | description: Upload APP 169 | post: 170 | description: Upload APP firmware to flash memory. 171 | queryParameters: 172 | partition: 173 | displayName: partition 174 | type: string 175 | description: String name of partition (i.e. factory, ota_0, ota_1). If not specified, automatically choose the next available OTA slot. 176 | example: ota_0 177 | required: false 178 | responses: 179 | 200: 180 | body: 181 | application/json: 182 | type: object 183 | properties: 184 | success: boolean 185 | message: string 186 | example: | 187 | { 188 | "success": true 189 | "message": "Flash Success." 190 | } 191 | 192 | /erase: 193 | description: Erase data 194 | get: 195 | description: Erase data in a data (not app) partition. Recommend reboot immediately after to force a reformat of the data. 196 | queryParameters: 197 | partition: 198 | displayName: partition 199 | type: string 200 | description: String name of data partition (i.e. internalfs, nvs, ota_data). If not specified, nothing is erased. 201 | example: internalfs 202 | required: true 203 | responses: 204 | 200: 205 | body: 206 | application/json: 207 | type: object 208 | properties: 209 | success: boolean 210 | message: string 211 | example: | 212 | { 213 | "success": true 214 | "erased": "internalfs" 215 | } 216 | ``` 217 | -------------------------------------------------------------------------------- /README-wifi_api.md: -------------------------------------------------------------------------------- 1 | # Libesphttpd WiFi-API 2 | 3 | Functions to configure ESP32 WiFi settings via HTTP API. 4 | 5 | ## GUI 6 | See the example js/html code for the GUI here: https://github.com/chmorgan/esphttpd-freertos/blob/master/html/wifi/index.html 7 | 8 | ## Functions defined in libesphttpd/cgiwifi.h 9 | 10 | * __cgiWiFiScan()__ 11 | 12 | Gets the results of an earler scan in JSON format. Optionally start a new scan. 13 | 14 | Examples: 15 | * `http://my-esp32-ip/wifi/scan?clear=1&start=1` - Clear the previous results and start a new scan. Returned APs list will be empty and inProgress will be true. 16 | 17 | Note: If client is connected via WiFi, then start=1 may interrupt communication breifly, so use sparingly. 18 | * `http://my-esp32-ip/wifi/scan` - After sending start command, poll this until `inProgress:false` and APs list contains results. 19 | 20 | Note: "enc" value is from `enum wifi_auth_mode_t`, where 0=Open, 1=WEP, 2+ is WPA. 21 | 22 | GET/POST args: 23 | ```js 24 | "clear": number // 1: Clear the previous results first. 25 | "start": number // 1: Start a new scan now. 26 | ``` 27 | Response: 28 | ```js 29 | { 30 | "args": { // Args are repeated here in the response 31 | "clear": number, 32 | "start": number, 33 | }, 34 | "APs": [{ 35 | "essid": string, // Name of AP discovered 36 | "bssid": string, // MAC of AP discoverd 37 | "rssi": number, // Signal strength i.e. -55 38 | "enc": number, // WiFi security (encryption) type. 39 | "channel": number // Channel used by AP 40 | },{...}], 41 | "working": boolean, // A scan is in progress. Poll this. 42 | "success": boolean, // CGI success/fail 43 | "error": string, // Optional error message if failure 44 | } 45 | ``` 46 | 47 | * __cgiWiFiConnect()__ 48 | 49 | Set WiFi STAtion (ESP WiFI Client) settings and trigger a connection. 50 | 51 | Note: The "success" response of this CGI call does not indicate if the WiFi connection succeeds. Poll /wifi/sta (cgiWiFiConnStatus) for connection pending/success/fail. 52 | 53 | Examples: 54 | * http://my-esp32-ip/wifi/connect?ssid=my-ssid&pass=mysecretpasswd - Trigger a connection attempt to the AP with the given SSID and password. 55 | 56 | GET/POST args: 57 | ```js 58 | "ssid": string 59 | "pass": string 60 | ``` 61 | Response: 62 | ```js 63 | { 64 | "args": { // Args are repeated here in the response 65 | "ssid": string, 66 | "pass": string, 67 | }, 68 | "success": boolean, // CGI success/fail 69 | "error": string, // Optional error message if failure 70 | } 71 | ``` 72 | 73 | * __cgiWiFiSetMode()__ 74 | 75 | CGI used to get/set the WiFi mode. 76 | 77 | The mode values are defined by `enum wifi_mode_t` 78 | ```c 79 | 0 /**< null mode */ 80 | 1 /**< WiFi station mode */ 81 | 2 /**< WiFi soft-AP mode */ 82 | 3 /**< WiFi station + soft-AP mode */ 83 | ``` 84 | 85 | Examples 86 | * i.e. http://ip/wifi/mode?mode=1 - Change mode to WIFI_MODE_STA 87 | 88 | GET/POST args: 89 | ```js 90 | "mode": number // The desired Mode (as number specified in enum wifi_mode_t) 91 | "force": number // 1: Force the change, regardless of whether ESP's STA is connected to an AP. 92 | ``` 93 | Response: 94 | ```js 95 | { 96 | "args": { // Args are repeated here in the response 97 | "mode": number, 98 | "force": number, 99 | }, 100 | "mode": number, // The current Mode (as number specified in enum wifi_mode_t) 101 | "mode_str": string, // The current Mode (as a string specified in wifi_mode_names[]= "Disabled","STA","AP""AP+STA") 102 | "success": boolean, // CGI success/fail 103 | "error": string, // Optional error message if failure 104 | } 105 | ``` 106 | 107 | * __cgiWiFiStartWps()__ 108 | 109 | CGI for triggering a WPS push button connection attempt. 110 | 111 | * __cgiWiFiAPSettings()__ 112 | 113 | CGI for get/set settings in AP mode. 114 | 115 | Examples: 116 | * http://ip/wifi/ap?ssid=myssid&pass=mypass&chan=1 - Change AP settings 117 | 118 | GET/POST args: 119 | ```js 120 | "chan": number, 121 | "ssid": string, 122 | "pass": string 123 | ``` 124 | Response: 125 | ```js 126 | { 127 | "args": { // Args are repeated here in the response 128 | "chan": number, 129 | "ssid": string, 130 | "pass": string, 131 | }, 132 | "enabled" : boolean, // AP is enabled 133 | "success": boolean, // CGI success/fail 134 | "error": string, // Optional error message if failure 135 | } 136 | ``` 137 | 138 | * __cgiWiFiConnStatus()__ 139 | 140 | CGI returning the current state of the WiFi STA connection to an AP. 141 | 142 | Examples: 143 | * `http://my-esp32-ip/wifi/sta` - Get the state of the STAtion 144 | 145 | Response: 146 | ```js 147 | { 148 | "ssid": string, // SSID that the STAtion should connect to. 149 | "pass": string, // WiFi network password. 150 | "enabled" : boolean, // STA is enabled 151 | "ip" : string, // Optional IP address of STAtion (only if connected) 152 | "working": boolean, // A connect is in progress. Poll this. 153 | "connected": boolean, // STAtion is connected to a WiFi network. Poll this. 154 | "success": boolean, // CGI success/fail 155 | "error": string, // Optional error message if failure 156 | } 157 | ``` 158 | -------------------------------------------------------------------------------- /component.mk: -------------------------------------------------------------------------------- 1 | # 2 | # Component Makefile (for esp-idf) 3 | # 4 | # This Makefile should, at the very least, just include $(SDK_PATH)/make/component.mk. By default, 5 | # this will take the sources in this directory, compile them and link them into 6 | # lib(subdirectory_name).a in the build directory. This behaviour is entirely configurable, 7 | # please read the SDK documents if you need to do this. 8 | # 9 | 10 | COMPONENT_SRCDIRS := core util 11 | COMPONENT_ADD_INCLUDEDIRS := core util include 12 | COMPONENT_ADD_LDFLAGS := -llibesphttpd 13 | 14 | CFLAGS += -DFREERTOS -------------------------------------------------------------------------------- /core/auth.c: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | /* 6 | HTTP auth implementation. Only does basic authentication for now. 7 | */ 8 | 9 | #ifdef linux 10 | #include 11 | #else 12 | #include 13 | #endif 14 | 15 | #include "libesphttpd/auth.h" 16 | #include "libesphttpd_base64.h" 17 | 18 | CgiStatus ICACHE_FLASH_ATTR authBasic(HttpdConnData *connData) { 19 | const char *unauthorized = "401 Unauthorized."; 20 | int no=0; 21 | int r; 22 | char hdr[(AUTH_MAX_USER_LEN+AUTH_MAX_PASS_LEN+2)*10]; 23 | char userpass[AUTH_MAX_USER_LEN+AUTH_MAX_PASS_LEN+2]; 24 | char user[AUTH_MAX_USER_LEN]; 25 | char pass[AUTH_MAX_PASS_LEN]; 26 | 27 | if(connData->isConnectionClosed) 28 | { 29 | //Connection closed. Clean up. 30 | return HTTPD_CGI_DONE; 31 | } 32 | 33 | r=httpdGetHeader(connData, "Authorization", hdr, sizeof(hdr)); 34 | if (r && strncmp(hdr, "Basic", 5)==0) { 35 | r=libesphttpd_base64_decode(strlen(hdr)-6, hdr+6, sizeof(userpass), (unsigned char *)userpass); 36 | if (r<0) r=0; //just clean out string on decode error 37 | userpass[r]=0; //zero-terminate user:pass string 38 | // printf("Auth: %s\n", userpass); 39 | while (((AuthGetUserPw)(connData->cgiArg))(connData, no, 40 | user, AUTH_MAX_USER_LEN, pass, AUTH_MAX_PASS_LEN)) { 41 | //Check user/pass against auth header 42 | if (strlen(userpass)==strlen(user)+strlen(pass)+1 && 43 | strncmp(userpass, user, strlen(user))==0 && 44 | userpass[strlen(user)]==':' && 45 | strcmp(userpass+strlen(user)+1, pass)==0) { 46 | //Authenticated. Yay! 47 | return HTTPD_CGI_AUTHENTICATED; 48 | } 49 | no++; //Not authenticated with this user/pass. Check next user/pass combo. 50 | } 51 | } 52 | 53 | //Not authenticated. Go bug user with login screen. 54 | httpdStartResponse(connData, 401); 55 | httpdHeader(connData, "Content-Type", "text/plain"); 56 | httpdHeader(connData, "WWW-Authenticate", "Basic realm=\""HTTP_AUTH_REALM"\""); 57 | httpdEndHeaders(connData); 58 | httpdSend(connData, unauthorized, -1); 59 | //Okay, all done. 60 | return HTTPD_CGI_DONE; 61 | } 62 | -------------------------------------------------------------------------------- /core/httpd-espfs.c: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | /* 6 | Connector to let httpd use the espfs filesystem to serve the files in it. 7 | */ 8 | 9 | #ifdef linux 10 | #include 11 | #else 12 | #include // for sdkconfig.h 13 | #endif 14 | 15 | #include 16 | 17 | #ifdef CONFIG_ESPHTTPD_USE_ESPFS 18 | #include "libespfs/espfs.h" 19 | #include "esp_log.h" 20 | const static char* TAG = "httpdespfs"; 21 | 22 | #define FILE_CHUNK_LEN 1024 23 | 24 | // The static files marked with ESPFS_FLAG_GZIP are compressed and will be served with GZIP compression. 25 | // If the client does not advertise that he accepts GZIP send following warning message (telnet users for e.g.) 26 | static const char *gzipNonSupportedMessage = "HTTP/1.0 501 Not implemented\r\nServer: esp8266-httpd/"HTTPDVER"\r\nConnection: close\r\nContent-Type: text/plain\r\nContent-Length: 52\r\n\r\nYour browser does not accept gzip-compressed data.\r\n"; 27 | 28 | static espfs_fs_t *espfs = NULL; 29 | 30 | void httpdRegisterEspfs(espfs_fs_t *fs) { 31 | espfs = fs; 32 | } 33 | 34 | /** 35 | * Try to open a file 36 | * @param path - path to the file, may end with slash 37 | * @param indexname - filename at the path 38 | * @return file pointer or NULL 39 | */ 40 | static espfs_file_t *tryOpenIndex_do(const char *path, const char *indexname) { 41 | char fname[100]; 42 | espfs_file_t *retval; 43 | size_t url_len = strlen(path); 44 | size_t index_len = strlen(indexname); 45 | bool needSlash = false; 46 | 47 | // will we need to append a slash? 48 | if((url_len > 0) && (path[url_len - 1] != '/')) { 49 | url_len++; 50 | needSlash = true; 51 | } 52 | 53 | // do we have enough space to handle the input strings 54 | // -1 to leave space for a trailing null 55 | if((url_len + index_len) >= (sizeof(fname) - 1)) 56 | { 57 | retval = NULL; 58 | ESP_LOGE(TAG, "fname too small"); 59 | } else 60 | { 61 | strcpy(fname, path); 62 | 63 | // Append slash if missing 64 | if(needSlash) 65 | { 66 | strcat(fname, "/"); 67 | } 68 | 69 | strcat(fname, indexname); 70 | 71 | // Try to open, returns NULL if failed 72 | retval = espfs_fopen(espfs, fname); 73 | } 74 | 75 | return retval; 76 | } 77 | 78 | /** 79 | * Try to find index file on a path 80 | * @param path - directory 81 | * @return file pointer or NULL 82 | */ 83 | espfs_file_t *tryOpenIndex(const char *path) { 84 | espfs_file_t * file; 85 | // A dot in the filename probably means extension 86 | // no point in trying to look for index. 87 | if (strchr(path, '.') != NULL) return NULL; 88 | 89 | file = tryOpenIndex_do(path, "index.html"); 90 | if (file != NULL) return file; 91 | 92 | file = tryOpenIndex_do(path, "index.htm"); 93 | if (file != NULL) return file; 94 | 95 | file = tryOpenIndex_do(path, "index.tpl.html"); 96 | if (file != NULL) return file; 97 | 98 | file = tryOpenIndex_do(path, "index.tpl"); 99 | if (file != NULL) return file; 100 | 101 | return NULL; // failed to guess the right name 102 | } 103 | 104 | CgiStatus ICACHE_FLASH_ATTR 105 | serveStaticFile(HttpdConnData *connData, const char* filepath) { 106 | espfs_file_t *file=connData->cgiData; 107 | int len; 108 | char buff[FILE_CHUNK_LEN+1]; 109 | char acceptEncodingBuffer[64]; 110 | int isGzip; 111 | 112 | if (connData->isConnectionClosed) { 113 | //Connection closed. Clean up. 114 | espfs_fclose(file); 115 | return HTTPD_CGI_DONE; 116 | } 117 | 118 | //First call to this cgi. 119 | if (file==NULL) { 120 | // invalid call. 121 | if (filepath == NULL) { 122 | ESP_LOGE(TAG, "serveStaticFile called with NULL path"); 123 | return HTTPD_CGI_NOTFOUND; 124 | } 125 | 126 | //First call to this cgi. Open the file so we can read it. 127 | file = espfs_fopen(espfs, filepath); 128 | if (file == NULL) { 129 | // file not found 130 | 131 | // If this is a folder, look for index file 132 | file = tryOpenIndex(filepath); 133 | if (file == NULL) return HTTPD_CGI_NOTFOUND; 134 | 135 | // A file was found by tryOpenIndex, but we should require a 136 | // trailing slash so clients can properly resolve relative paths. 137 | // I.e. "GET /hello" should redirect to "/hello/" 138 | // because /hello/index.html might require "./app.js", 139 | // which should resolve to "/hello/app.js", NOT "/app.js" 140 | // The exception to this is the root directory does not require a trailing slash. 141 | // I.e. "GET myapp.com" can serve "/index.html" 142 | size_t url_len = strlen(filepath); 143 | if((url_len > 0) && (filepath[url_len - 1] != '/')) { 144 | espfs_fclose(file); // not serving the file this time 145 | const size_t fnameBufSize = 100; 146 | char fname[fnameBufSize]; 147 | 148 | // do we have enough space to add a trailing '/'? 149 | // -1 to leave space for a trailing null 150 | if((url_len + 1) >= (sizeof(fname) - 1)) 151 | { 152 | ESP_LOGE(TAG, "fname too small"); 153 | } else 154 | { 155 | strcpy(fname, filepath); 156 | strcat(fname, "/"); 157 | httpdRedirect(connData, fname); 158 | return HTTPD_CGI_DONE; 159 | } 160 | } 161 | } 162 | 163 | // The gzip checking code is intentionally without #ifdefs because checking 164 | // for ESPFS_FLAG_GZIP (which indicates gzip compressed file) is very easy, doesn't 165 | // mean additional overhead and is actually safer to be on at all times. 166 | // If there are no gzipped files in the image, the code bellow will not cause any harm. 167 | 168 | // Check if requested file was GZIP compressed 169 | espfs_stat_t s = {0}; 170 | espfs_fstat(file, &s); 171 | isGzip = s.flags & ESPFS_FLAG_GZIP; 172 | if (isGzip) { 173 | // Check the browser's "Accept-Encoding" header. If the client does not 174 | // advertise that he accepts GZIP send a warning message (telnet users for e.g.) 175 | bool found = httpdGetHeader(connData, "Accept-Encoding", acceptEncodingBuffer, sizeof(acceptEncodingBuffer)); 176 | if (!found || (strstr(acceptEncodingBuffer, "gzip") == NULL)) { 177 | //No Accept-Encoding: gzip header present 178 | httpdSend(connData, gzipNonSupportedMessage, -1); 179 | espfs_fclose(file); 180 | return HTTPD_CGI_DONE; 181 | } 182 | } 183 | 184 | connData->cgiData=file; 185 | httpdStartResponse(connData, 200); 186 | 187 | const char *mimetype = NULL; 188 | bool sendContentType = false; 189 | bool sentHeaders = false; 190 | 191 | if (connData->cgiArg == &httpdCgiEx) { 192 | HttpdCgiExArg *ex = (HttpdCgiExArg *)connData->cgiArg2; 193 | if (ex->mimetype) { 194 | mimetype = ex->mimetype; 195 | sendContentType = true; 196 | } else if (!ex->headerCb) { 197 | sendContentType = true; 198 | } 199 | } else { 200 | sendContentType = true; 201 | } 202 | 203 | if (sendContentType) { 204 | if (!mimetype) { 205 | mimetype = httpdGetMimetype(connData->url); 206 | } 207 | httpdHeader(connData, "Content-Type", mimetype); 208 | } 209 | 210 | if (isGzip) { 211 | httpdHeader(connData, "Content-Encoding", "gzip"); 212 | } 213 | 214 | if (connData->cgiArg == &httpdCgiEx) { 215 | HttpdCgiExArg *ex = (HttpdCgiExArg *)connData->cgiArg2; 216 | if (ex->headerCb) { 217 | ex->headerCb(connData); 218 | sentHeaders = true; 219 | } 220 | } 221 | 222 | if (!sentHeaders) { 223 | httpdHeader(connData, "Cache-Control", "max-age=3600, must-revalidate"); 224 | } 225 | httpdEndHeaders(connData); 226 | return HTTPD_CGI_MORE; 227 | } 228 | 229 | len=espfs_fread(file, buff, FILE_CHUNK_LEN); 230 | if (len>0) httpdSend(connData, buff, len); 231 | if (len!=FILE_CHUNK_LEN) { 232 | //We're done. 233 | espfs_fclose(file); 234 | return HTTPD_CGI_DONE; 235 | } else { 236 | //Ok, till next time. 237 | return HTTPD_CGI_MORE; 238 | } 239 | } 240 | 241 | 242 | static size_t getFilepath(HttpdConnData *connData, char *filepath, size_t len) 243 | { 244 | espfs_stat_t s; 245 | int outlen; 246 | if (!espfs) 247 | { 248 | ESP_LOGE(TAG, "espfs not registered"); 249 | return -1; 250 | } 251 | if (connData->cgiArg != &httpdCgiEx) { 252 | filepath[0] = '\0'; 253 | if (connData->cgiArg != NULL) { 254 | outlen = strlcpy(filepath, connData->cgiArg, len); 255 | if (espfs_stat(espfs, filepath, &s) == 0 && s.type == ESPFS_TYPE_FILE) { 256 | return outlen; 257 | } 258 | } 259 | return strlcat(filepath, connData->url, len); 260 | } 261 | 262 | HttpdCgiExArg *ex = (HttpdCgiExArg *)connData->cgiArg2; 263 | const char *route = connData->route; 264 | char *url = connData->url; 265 | while (*url && *route == *url) { 266 | route++; 267 | url++; 268 | } 269 | 270 | size_t basepathLen = 0; 271 | if (ex->basepath) { 272 | basepathLen = strlen(ex->basepath); 273 | } 274 | if (basepathLen == 0) { 275 | return strlcpy(filepath, url, len); 276 | } 277 | 278 | if (url[0] == '/') { 279 | url++; 280 | } 281 | 282 | outlen = strlcpy(filepath, ex->basepath, len); 283 | if (!espfs_stat(espfs, ex->basepath, &s) || s.type == ESPFS_TYPE_DIR) { 284 | if (ex->basepath[basepathLen - 1] != '/') { 285 | strlcat(filepath, "/", len); 286 | } 287 | outlen = strlcat(filepath, url, len); 288 | } 289 | return outlen; 290 | } 291 | 292 | 293 | //This is a catch-all cgi function. It takes the url passed to it, looks up the corresponding 294 | //path in the filesystem and if it exists, passes the file through. This simulates what a normal 295 | //webserver would do with static files. 296 | CgiStatus ICACHE_FLASH_ATTR cgiEspFsHook(HttpdConnData *connData) { 297 | if (connData->cgiData) { 298 | return serveStaticFile(connData, NULL); 299 | } 300 | 301 | char filepath[256]; 302 | getFilepath(connData, filepath, sizeof(filepath)); 303 | return serveStaticFile(connData, filepath); 304 | } 305 | 306 | 307 | //cgiEspFsTemplate can be used as a template. 308 | 309 | typedef enum { 310 | ENCODE_PLAIN = 0, 311 | ENCODE_HTML, 312 | ENCODE_JS, 313 | } TplEncode; 314 | 315 | typedef struct { 316 | espfs_file_t *file; 317 | void *tplArg; 318 | char token[64]; 319 | int tokenPos; 320 | 321 | char buff[FILE_CHUNK_LEN + 1]; 322 | 323 | bool chunk_resume; 324 | int buff_len; 325 | int buff_x; 326 | int buff_sp; 327 | char *buff_e; 328 | 329 | TplEncode tokEncode; 330 | } TplData; 331 | 332 | int ICACHE_FLASH_ATTR 333 | tplSend(HttpdConnData *conn, const char *str, int len) 334 | { 335 | if (conn == NULL) return 0; 336 | TplData *tpd=conn->cgiData; 337 | 338 | if (tpd == NULL || tpd->tokEncode == ENCODE_PLAIN) return httpdSend(conn, str, len); 339 | if (tpd->tokEncode == ENCODE_HTML) return httpdSend_html(conn, str, len); 340 | if (tpd->tokEncode == ENCODE_JS) return httpdSend_js(conn, str, len); 341 | return 0; 342 | } 343 | 344 | CgiStatus ICACHE_FLASH_ATTR cgiEspFsTemplate(HttpdConnData *connData) { 345 | TplData *tpd=connData->cgiData; 346 | int len; 347 | int x, sp=0; 348 | char *e=NULL; 349 | int tokOfs; 350 | 351 | if (connData->isConnectionClosed) { 352 | //Connection aborted. Clean up. 353 | ((TplCallback)(connData->cgiArg2))(connData, NULL, &tpd->tplArg); 354 | espfs_fclose(tpd->file); 355 | free(tpd); 356 | return HTTPD_CGI_DONE; 357 | } 358 | 359 | if (tpd==NULL) { 360 | //First call to this cgi. Open the file so we can read it. 361 | tpd=(TplData *)malloc(sizeof(TplData)); 362 | if (tpd==NULL) { 363 | ESP_LOGE(TAG, "Failed to malloc tpl struct"); 364 | return HTTPD_CGI_NOTFOUND; 365 | } 366 | 367 | tpd->chunk_resume = false; 368 | 369 | char filepath[256]; 370 | getFilepath(connData, filepath, sizeof(filepath)); 371 | tpd->file = espfs_fopen(espfs, filepath); 372 | 373 | if (tpd->file == NULL) { 374 | // maybe a folder, look for index file 375 | tpd->file = tryOpenIndex(filepath); 376 | if (tpd->file == NULL) { 377 | free(tpd); 378 | return HTTPD_CGI_NOTFOUND; 379 | } 380 | } 381 | 382 | tpd->tplArg=NULL; 383 | tpd->tokenPos=-1; 384 | 385 | espfs_stat_t s = {0}; 386 | espfs_fstat(tpd->file, &s); 387 | 388 | if (s.flags & ESPFS_FLAG_GZIP) { 389 | ESP_LOGE(TAG, "cgiEspFsTemplate: Trying to use gzip-compressed file %s as template", connData->url); 390 | espfs_fclose(tpd->file); 391 | free(tpd); 392 | return HTTPD_CGI_NOTFOUND; 393 | } 394 | connData->cgiData=tpd; 395 | httpdStartResponse(connData, 200); 396 | 397 | const char *mimetype = NULL; 398 | bool sendContentType = false; 399 | bool sentHeaders = false; 400 | 401 | if (connData->cgiArg == &httpdCgiEx) { 402 | HttpdCgiExArg *ex = (HttpdCgiExArg *)connData->cgiArg2; 403 | if (ex->mimetype) { 404 | mimetype = ex->mimetype; 405 | sendContentType = true; 406 | } else if (!ex->headerCb) { 407 | sendContentType = true; 408 | } 409 | } else { 410 | sendContentType = true; 411 | } 412 | 413 | if (sendContentType) { 414 | if (!mimetype) { 415 | mimetype = httpdGetMimetype(connData->url); 416 | } 417 | httpdHeader(connData, "Content-Type", mimetype); 418 | } 419 | 420 | if (connData->cgiArg == &httpdCgiEx) { 421 | HttpdCgiExArg *ex = (HttpdCgiExArg *)connData->cgiArg2; 422 | if (ex->headerCb) { 423 | ex->headerCb(connData); 424 | sentHeaders = true; 425 | } 426 | } 427 | 428 | if (mimetype && !sentHeaders) { 429 | httpdAddCacheHeaders(connData, mimetype); 430 | sentHeaders = true; 431 | } 432 | httpdEndHeaders(connData); 433 | return HTTPD_CGI_MORE; 434 | } 435 | 436 | char *buff = tpd->buff; 437 | 438 | // resume the parser state from the last token, 439 | // if subst. func wants more data to be sent. 440 | if (tpd->chunk_resume) { 441 | //espfs_dbg("Resuming tpl parser for multi-part subst"); 442 | len = tpd->buff_len; 443 | e = tpd->buff_e; 444 | sp = tpd->buff_sp; 445 | x = tpd->buff_x; 446 | } else { 447 | len = espfs_fread(tpd->file, buff, FILE_CHUNK_LEN); 448 | tpd->buff_len = len; 449 | 450 | e = buff; 451 | sp = 0; 452 | x = 0; 453 | } 454 | 455 | if (len>0) { 456 | for (; xtokenPos==-1) { 458 | //Inside ordinary text. 459 | if (buff[x]=='%') { 460 | //Send raw data up to now 461 | if (sp!=0) httpdSend(connData, e, sp); 462 | sp=0; 463 | //Go collect token chars. 464 | tpd->tokenPos=0; 465 | } else { 466 | sp++; 467 | } 468 | } else { 469 | if (buff[x]=='%') { 470 | if (tpd->tokenPos==0) { 471 | //This is the second % of a %% escape string. 472 | //Send a single % and resume with the normal program flow. 473 | httpdSend(connData, "%", 1); 474 | } else { 475 | if (!tpd->chunk_resume) { 476 | //This is an actual token. 477 | tpd->token[tpd->tokenPos++] = 0; //zero-terminate token 478 | 479 | tokOfs = 0; 480 | tpd->tokEncode = ENCODE_PLAIN; 481 | if (strncmp(tpd->token, "html:", 5) == 0) { 482 | tokOfs = 5; 483 | tpd->tokEncode = ENCODE_HTML; 484 | } 485 | else if (strncmp(tpd->token, "h:", 2) == 0) { 486 | tokOfs = 2; 487 | tpd->tokEncode = ENCODE_HTML; 488 | } 489 | else if (strncmp(tpd->token, "js:", 3) == 0) { 490 | tokOfs = 3; 491 | tpd->tokEncode = ENCODE_JS; 492 | } 493 | else if (strncmp(tpd->token, "j:", 2) == 0) { 494 | tokOfs = 2; 495 | tpd->tokEncode = ENCODE_JS; 496 | } 497 | 498 | // do the shifting 499 | if (tokOfs > 0) { 500 | for(int i=tokOfs; i<=tpd->tokenPos; i++) { 501 | tpd->token[i-tokOfs] = tpd->token[i]; 502 | } 503 | } 504 | } 505 | 506 | tpd->chunk_resume = false; 507 | 508 | CgiStatus status = ((TplCallback)(connData->cgiArg2))(connData, tpd->token, &tpd->tplArg); 509 | if (status == HTTPD_CGI_MORE) { 510 | // espfs_dbg("Multi-part tpl subst, saving parser state"); 511 | // wants to send more in this token's place..... 512 | tpd->chunk_resume = true; 513 | tpd->buff_len = len; 514 | tpd->buff_e = e; 515 | tpd->buff_sp = sp; 516 | tpd->buff_x = x; 517 | break; 518 | } 519 | } 520 | //Go collect normal chars again. 521 | e=&buff[x+1]; 522 | tpd->tokenPos=-1; 523 | } 524 | else { 525 | // Add char to the token buf 526 | char c = buff[x]; 527 | bool outOfSpace = tpd->tokenPos >= (sizeof(tpd->token) - 1); 528 | if (outOfSpace || 529 | ( !(c >= 'a' && c <= 'z') && 530 | !(c >= 'A' && c <= 'Z') && 531 | !(c >= '0' && c <= '9') && 532 | c != '.' && c != '_' && c != '-' && c != ':' 533 | )) { 534 | // looks like we collected some garbage 535 | httpdSend(connData, "%", 1); 536 | if (tpd->tokenPos > 0) { 537 | httpdSend(connData, tpd->token, tpd->tokenPos); 538 | } 539 | // the bad char 540 | httpdSend(connData, &c, 1); 541 | 542 | //Go collect normal chars again. 543 | e=&buff[x+1]; 544 | tpd->tokenPos=-1; 545 | } 546 | else { 547 | // collect it 548 | tpd->token[tpd->tokenPos++] = c; 549 | } 550 | } 551 | } 552 | } 553 | } 554 | 555 | if (tpd->chunk_resume) { 556 | return HTTPD_CGI_MORE; 557 | } 558 | 559 | //Send remaining bit. 560 | if (sp!=0) httpdSend(connData, e, sp); 561 | if (len!=FILE_CHUNK_LEN) { 562 | //We're done. 563 | ((TplCallback)(connData->cgiArg2))(connData, NULL, &tpd->tplArg); 564 | ESP_LOGD(TAG, "Template sent"); 565 | espfs_fclose(tpd->file); 566 | free(tpd); 567 | return HTTPD_CGI_DONE; 568 | } else { 569 | //Ok, till next time. 570 | return HTTPD_CGI_MORE; 571 | } 572 | } 573 | #endif // CONFIG_ESPHTTPD_USE_ESPFS 574 | -------------------------------------------------------------------------------- /core/httpd-nonos.c: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | /* 6 | ESP8266 web server - platform-dependent routines, nonos version 7 | */ 8 | 9 | #include 10 | #include "libesphttpd/httpd.h" 11 | #include "libesphttpd/platform.h" 12 | #include "httpd-platform.h" 13 | 14 | #ifndef FREERTOS 15 | 16 | //Listening connection data 17 | static struct espconn httpdConn; 18 | static esp_tcp httpdTcp; 19 | 20 | //Set/clear global httpd lock. 21 | //Not needed on nonoos. 22 | void ICACHE_FLASH_ATTR httpdPlatLock() { 23 | } 24 | void ICACHE_FLASH_ATTR httpdPlatUnlock() { 25 | } 26 | 27 | 28 | static void ICACHE_FLASH_ATTR platReconCb(void *arg, sint8 err) { 29 | //From ESP8266 SDK 30 | //If still no response, considers it as TCP connection broke, goes into espconn_reconnect_callback. 31 | 32 | ConnTypePtr conn=arg; 33 | //Just call disconnect to clean up pool and close connection. 34 | httpdDisconCb(conn, (char*)conn->proto.tcp->remote_ip, conn->proto.tcp->remote_port); 35 | } 36 | 37 | static void ICACHE_FLASH_ATTR platDisconCb(void *arg) { 38 | ConnTypePtr conn=arg; 39 | httpdDisconCb(conn, (char*)conn->proto.tcp->remote_ip, conn->proto.tcp->remote_port); 40 | } 41 | 42 | static void ICACHE_FLASH_ATTR platRecvCb(void *arg, char *data, unsigned short len) { 43 | ConnTypePtr conn=arg; 44 | httpdRecvCb(conn, (char*)conn->proto.tcp->remote_ip, conn->proto.tcp->remote_port, data, len); 45 | } 46 | 47 | static void ICACHE_FLASH_ATTR platSentCb(void *arg) { 48 | ConnTypePtr conn=arg; 49 | httpdSentCb(conn, (char*)conn->proto.tcp->remote_ip, conn->proto.tcp->remote_port); 50 | } 51 | 52 | static void ICACHE_FLASH_ATTR platConnCb(void *arg) { 53 | ConnTypePtr conn=arg; 54 | if (httpdConnectCb(conn, (char*)conn->proto.tcp->remote_ip, conn->proto.tcp->remote_port)) { 55 | espconn_regist_recvcb(conn, platRecvCb); 56 | espconn_regist_reconcb(conn, platReconCb); 57 | espconn_regist_disconcb(conn, platDisconCb); 58 | espconn_regist_sentcb(conn, platSentCb); 59 | } else { 60 | espconn_disconnect(conn); 61 | } 62 | } 63 | 64 | 65 | int ICACHE_FLASH_ATTR httpdPlatSendData(ConnTypePtr conn, char *buff, int len) { 66 | int r; 67 | r=espconn_sent(conn, (uint8_t*)buff, len); 68 | return (r>=0); 69 | } 70 | 71 | void ICACHE_FLASH_ATTR httpdPlatDisconnect(ConnTypePtr conn) { 72 | espconn_disconnect(conn); 73 | } 74 | 75 | void ICACHE_FLASH_ATTR httpdPlatDisableTimeout(ConnTypePtr conn) { 76 | //Can't disable timeout; set to 2 hours instead. 77 | espconn_regist_time(conn, 7199, 1); 78 | } 79 | 80 | //Initialize listening socket, do general initialization 81 | HttpdInitStatus ICACHE_FLASH_ATTR httpdPlatInit(int port, int maxConnCt, uint32_t listenAddress, HttpdFlags flags) { 82 | // TODO: check flags 83 | // TODO: handle listenAddress 84 | 85 | httpdConn.type=ESPCONN_TCP; 86 | httpdConn.state=ESPCONN_NONE; 87 | httpdTcp.local_port=port; 88 | httpdConn.proto.tcp=&httpdTcp; 89 | espconn_regist_connectcb(&httpdConn, platConnCb); 90 | espconn_accept(&httpdConn); 91 | espconn_tcp_set_max_con_allow(&httpdConn, maxConnCt); 92 | 93 | return InitializationSuccess; 94 | } 95 | 96 | HttpdPlatTimerHandle httpdPlatTimerCreate(const char *name, int periodMs, int autoreload, void (*callback)(void *arg), void *ctx) 97 | { 98 | HttpdPlatTimerHandle newTimer = malloc(sizeof(HttpdPlatTimerHandle)); 99 | os_timer_setfn(&newTimer->timer, callback, ctx); 100 | 101 | // store the timer settings into the structure as we want to capture them here but 102 | // can't apply them until the timer is armed 103 | newTimer->autoReload = autoreload; 104 | newTimer->timerPeriodMS = periodMs; 105 | 106 | return newTimer; 107 | } 108 | 109 | void httpdPlatTimerStart(HttpdPlatTimerHandle timer) { 110 | os_timer_arm(&timer->timer, timer->timerPeriodMS, timer->autoReload); 111 | } 112 | 113 | void httpdPlatTimerStop(HttpdPlatTimerHandle timer) { 114 | os_timer_disarm(&timer->timer); 115 | } 116 | 117 | void httpdPlatTimerDelete(HttpdPlatTimerHandle timer) { 118 | os_timer_disarm(&timer->timer); 119 | free(timer); 120 | } 121 | 122 | #endif 123 | -------------------------------------------------------------------------------- /core/httpd-platform.h: -------------------------------------------------------------------------------- 1 | #ifndef HTTPD_PLATFORM_H 2 | #define HTTPD_PLATFORM_H 3 | 4 | #include "libesphttpd/platform.h" 5 | 6 | /** 7 | * @return number of bytes that were written 8 | */ 9 | int httpdPlatSendData(HttpdInstance *pInstance, HttpdConnData *pConn, char *buff, int len); 10 | 11 | void httpdPlatDisconnect(HttpdConnData *ponn); 12 | void httpdPlatDisableTimeout(HttpdConnData *pConn); 13 | 14 | void httpdPlatLock(HttpdInstance *pInstance); 15 | void httpdPlatUnlock(HttpdInstance *pInstance); 16 | 17 | HttpdPlatTimerHandle httpdPlatTimerCreate(const char *name, int periodMs, int autoreload, void (*callback)(void *arg), void *ctx); 18 | void httpdPlatTimerStart(HttpdPlatTimerHandle timer); 19 | void httpdPlatTimerStop(HttpdPlatTimerHandle timer); 20 | void httpdPlatTimerDelete(HttpdPlatTimerHandle timer); 21 | 22 | #ifdef CONFIG_ESPHTTPD_SHUTDOWN_SUPPORT 23 | void httpdPlatShutdown(HttpdInstance *pInstance); 24 | #endif 25 | 26 | #endif 27 | -------------------------------------------------------------------------------- /core/libesphttpd_base64.c: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | /* base64.c : base-64 / MIME encode/decode */ 6 | /* PUBLIC DOMAIN - Jon Mayo - November 13, 2003 */ 7 | #ifdef linux 8 | #include 9 | #else 10 | #include 11 | #endif 12 | 13 | #include "libesphttpd_base64.h" 14 | 15 | static const int base64dec_tab[256] ICACHE_RODATA_ATTR={ 16 | 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, 17 | 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, 18 | 255,255,255,255,255,255,255,255,255,255,255, 62,255,255,255, 63, 19 | 52, 53, 54, 55, 56, 57, 58, 59, 60, 61,255,255,255, 0,255,255, 20 | 255, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 21 | 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25,255,255,255,255,255, 22 | 255, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 23 | 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51,255,255,255,255,255, 24 | 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, 25 | 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, 26 | 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, 27 | 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, 28 | 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, 29 | 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, 30 | 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, 31 | 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, 32 | }; 33 | 34 | #if 0 35 | static int ICACHE_FLASH_ATTR base64decode(const char in[4], char out[3]) { 36 | uint8_t v[4]; 37 | 38 | v[0]=base64dec_tab[(unsigned)in[0]]; 39 | v[1]=base64dec_tab[(unsigned)in[1]]; 40 | v[2]=base64dec_tab[(unsigned)in[2]]; 41 | v[3]=base64dec_tab[(unsigned)in[3]]; 42 | 43 | out[0]=(v[0]<<2)|(v[1]>>4); 44 | out[1]=(v[1]<<4)|(v[2]>>2); 45 | out[2]=(v[2]<<6)|(v[3]); 46 | return (v[0]|v[1]|v[2]|v[3])!=255 ? in[3]=='=' ? in[2]=='=' ? 1 : 2 : 3 : 0; 47 | } 48 | #endif 49 | 50 | /* decode a base64 string in one shot */ 51 | int ICACHE_FLASH_ATTR __attribute__((weak)) libesphttpd_base64_decode(size_t in_len, const char *in, size_t out_len, unsigned char *out) { 52 | unsigned int ii, io; 53 | uint32_t v; 54 | unsigned int rem; 55 | 56 | for(io=0,ii=0,v=0,rem=0;ii=8) { 65 | rem-=8; 66 | if(io>=out_len) return -1; /* truncation is failure */ 67 | out[io++]=(v>>rem)&255; 68 | } 69 | } 70 | if(rem>=8) { 71 | rem-=8; 72 | if(io>=out_len) return -1; /* truncation is failure */ 73 | out[io++]=(v>>rem)&255; 74 | } 75 | return io; 76 | } 77 | 78 | static const uint8_t base64enc_tab[64]= "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; 79 | 80 | #if 0 81 | void base64encode(const unsigned char in[3], unsigned char out[4], int count) { 82 | out[0]=base64enc_tab[(in[0]>>2)]; 83 | out[1]=base64enc_tab[((in[0]&3)<<4)|(in[1]>>4)]; 84 | out[2]=count<2 ? '=' : base64enc_tab[((in[1]&15)<<2)|(in[2]>>6)]; 85 | out[3]=count<3 ? '=' : base64enc_tab[(in[2]&63)]; 86 | } 87 | #endif 88 | 89 | int ICACHE_FLASH_ATTR __attribute__((weak)) libesphttpd_base64_encode(size_t in_len, const unsigned char *in, size_t out_len, char *out) { 90 | unsigned ii, io; 91 | uint32_t v; 92 | unsigned rem; 93 | 94 | for(io=0,ii=0,v=0,rem=0;ii=6) { 100 | rem-=6; 101 | if(io>=out_len) return -1; /* truncation is failure */ 102 | out[io++]=base64enc_tab[(v>>rem)&63]; 103 | } 104 | } 105 | if(rem) { 106 | v<<=(6-rem); 107 | if(io>=out_len) return -1; /* truncation is failure */ 108 | out[io++]=base64enc_tab[v&63]; 109 | } 110 | while(io&3) { 111 | if(io>=out_len) return -1; /* truncation is failure */ 112 | out[io++]='='; 113 | } 114 | if(io>=out_len) return -1; /* no room for null terminator */ 115 | out[io]=0; 116 | return io; 117 | } 118 | -------------------------------------------------------------------------------- /core/libesphttpd_base64.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | int libesphttpd_base64_decode(size_t in_len, const char *in, size_t out_len, unsigned char *out); 4 | int libesphttpd_base64_encode(size_t in_len, const unsigned char *in, size_t out_len, char *out); 5 | -------------------------------------------------------------------------------- /core/linux/esp_log.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "esp_log.h" 4 | 5 | 6 | void esp_log_write(esp_log_level_t level, const char* tag, const char* format, ...) 7 | { 8 | va_list(args); 9 | va_start(args, format); 10 | vprintf(format, args); 11 | } 12 | 13 | uint32_t esp_log_timestamp(void) 14 | { 15 | return 0; 16 | } 17 | -------------------------------------------------------------------------------- /core/sha1.c: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | /* This code is public-domain - it is based on libcrypt 6 | * placed in the public domain by Wei Dai and other contributors. 7 | */ 8 | // gcc -Wall -DSHA1TEST -o sha1test sha1.c && ./sha1test 9 | 10 | #ifdef linux 11 | #include 12 | #else 13 | #include 14 | #endif 15 | 16 | #include 17 | #include 18 | 19 | #include "libesphttpd/sha1.h" 20 | 21 | //according to http://ip.cadence.com/uploads/pdf/xtensalx_overview_handbook.pdf 22 | // the cpu is normally defined as little ending, but can be big endian too. 23 | // for the esp this seems to work 24 | //#define SHA_BIG_ENDIAN 25 | 26 | 27 | 28 | /* code */ 29 | #define SHA1_K0 0x5a827999 30 | #define SHA1_K20 0x6ed9eba1 31 | #define SHA1_K40 0x8f1bbcdc 32 | #define SHA1_K60 0xca62c1d6 33 | 34 | void ICACHE_FLASH_ATTR sha1_init(sha1nfo *s) { 35 | s->state[0] = 0x67452301; 36 | s->state[1] = 0xefcdab89; 37 | s->state[2] = 0x98badcfe; 38 | s->state[3] = 0x10325476; 39 | s->state[4] = 0xc3d2e1f0; 40 | s->byteCount = 0; 41 | s->bufferOffset = 0; 42 | } 43 | 44 | uint32_t ICACHE_FLASH_ATTR sha1_rol32(uint32_t number, uint8_t bits) { 45 | return ((number << bits) | (number >> (32-bits))); 46 | } 47 | 48 | void ICACHE_FLASH_ATTR sha1_hashBlock(sha1nfo *s) { 49 | uint8_t i; 50 | uint32_t a,b,c,d,e,t; 51 | 52 | a=s->state[0]; 53 | b=s->state[1]; 54 | c=s->state[2]; 55 | d=s->state[3]; 56 | e=s->state[4]; 57 | for (i=0; i<80; i++) { 58 | if (i>=16) { 59 | t = s->buffer[(i+13)&15] ^ s->buffer[(i+8)&15] ^ s->buffer[(i+2)&15] ^ s->buffer[i&15]; 60 | s->buffer[i&15] = sha1_rol32(t,1); 61 | } 62 | if (i<20) { 63 | t = (d ^ (b & (c ^ d))) + SHA1_K0; 64 | } else if (i<40) { 65 | t = (b ^ c ^ d) + SHA1_K20; 66 | } else if (i<60) { 67 | t = ((b & c) | (d & (b | c))) + SHA1_K40; 68 | } else { 69 | t = (b ^ c ^ d) + SHA1_K60; 70 | } 71 | t+=sha1_rol32(a,5) + e + s->buffer[i&15]; 72 | e=d; 73 | d=c; 74 | c=sha1_rol32(b,30); 75 | b=a; 76 | a=t; 77 | } 78 | s->state[0] += a; 79 | s->state[1] += b; 80 | s->state[2] += c; 81 | s->state[3] += d; 82 | s->state[4] += e; 83 | } 84 | 85 | void ICACHE_FLASH_ATTR sha1_addUncounted(sha1nfo *s, uint8_t data) { 86 | uint8_t * const b = (uint8_t*) s->buffer; 87 | #ifdef SHA_BIG_ENDIAN 88 | b[s->bufferOffset] = data; 89 | #else 90 | b[s->bufferOffset ^ 3] = data; 91 | #endif 92 | s->bufferOffset++; 93 | if (s->bufferOffset == BLOCK_LENGTH) { 94 | sha1_hashBlock(s); 95 | s->bufferOffset = 0; 96 | } 97 | } 98 | 99 | void ICACHE_FLASH_ATTR sha1_writebyte(sha1nfo *s, uint8_t data) { 100 | ++s->byteCount; 101 | sha1_addUncounted(s, data); 102 | } 103 | 104 | void ICACHE_FLASH_ATTR sha1_write(sha1nfo *s, const char *data, size_t len) { 105 | for (;len--;) sha1_writebyte(s, (uint8_t) *data++); 106 | } 107 | 108 | void ICACHE_FLASH_ATTR sha1_pad(sha1nfo *s) { 109 | // Implement SHA-1 padding (fips180-2 §5.1.1) 110 | 111 | // Pad with 0x80 followed by 0x00 until the end of the block 112 | sha1_addUncounted(s, 0x80); 113 | while (s->bufferOffset != 56) sha1_addUncounted(s, 0x00); 114 | 115 | // Append length in the last 8 bytes 116 | sha1_addUncounted(s, 0); // We're only using 32 bit lengths 117 | sha1_addUncounted(s, 0); // But SHA-1 supports 64 bit lengths 118 | sha1_addUncounted(s, 0); // So zero pad the top bits 119 | sha1_addUncounted(s, s->byteCount >> 29); // Shifting to multiply by 8 120 | sha1_addUncounted(s, s->byteCount >> 21); // as SHA-1 supports bitstreams as well as 121 | sha1_addUncounted(s, s->byteCount >> 13); // byte. 122 | sha1_addUncounted(s, s->byteCount >> 5); 123 | sha1_addUncounted(s, s->byteCount << 3); 124 | } 125 | 126 | uint8_t* ICACHE_FLASH_ATTR sha1_result(sha1nfo *s) { 127 | // Pad to complete the last block 128 | sha1_pad(s); 129 | 130 | #ifndef SHA_BIG_ENDIAN 131 | // Swap byte order back 132 | int i; 133 | for (i=0; i<5; i++) { 134 | s->state[i]= 135 | (((s->state[i])<<24)& 0xff000000) 136 | | (((s->state[i])<<8) & 0x00ff0000) 137 | | (((s->state[i])>>8) & 0x0000ff00) 138 | | (((s->state[i])>>24)& 0x000000ff); 139 | } 140 | #endif 141 | 142 | // Return pointer to hash (20 characters) 143 | return (uint8_t*) s->state; 144 | } 145 | 146 | #define HMAC_IPAD 0x36 147 | #define HMAC_OPAD 0x5c 148 | 149 | void ICACHE_FLASH_ATTR sha1_initHmac(sha1nfo *s, const uint8_t* key, int keyLength) { 150 | uint8_t i; 151 | memset(s->keyBuffer, 0, BLOCK_LENGTH); 152 | if (keyLength > BLOCK_LENGTH) { 153 | // Hash long keys 154 | sha1_init(s); 155 | for (;keyLength--;) sha1_writebyte(s, *key++); 156 | memcpy(s->keyBuffer, sha1_result(s), HASH_LENGTH); 157 | } else { 158 | // Block length keys are used as is 159 | memcpy(s->keyBuffer, key, keyLength); 160 | } 161 | // Start inner hash 162 | sha1_init(s); 163 | for (i=0; ikeyBuffer[i] ^ HMAC_IPAD); 165 | } 166 | } 167 | 168 | uint8_t* ICACHE_FLASH_ATTR sha1_resultHmac(sha1nfo *s) { 169 | uint8_t i; 170 | // Complete inner hash 171 | memcpy(s->innerHash,sha1_result(s),HASH_LENGTH); 172 | // Calculate outer hash 173 | sha1_init(s); 174 | for (i=0; ikeyBuffer[i] ^ HMAC_OPAD); 175 | for (i=0; iinnerHash[i]); 176 | return sha1_result(s); 177 | } 178 | -------------------------------------------------------------------------------- /include/libesphttpd/auth.h: -------------------------------------------------------------------------------- 1 | #ifndef AUTH_H 2 | #define AUTH_H 3 | 4 | #include "httpd.h" 5 | 6 | #ifndef HTTP_AUTH_REALM 7 | #define HTTP_AUTH_REALM "Protected" 8 | #endif 9 | 10 | #define HTTPD_AUTH_SINGLE 0 11 | #define HTTPD_AUTH_CALLBACK 1 12 | 13 | #define AUTH_MAX_USER_LEN 32 14 | #define AUTH_MAX_PASS_LEN 32 15 | 16 | //Parameter given to authWhatever functions. This callback returns the usernames/passwords the device 17 | //has. 18 | typedef int (* AuthGetUserPw)(HttpdConnData *connData, int no, char *user, int userLen, char *pass, int passLen); 19 | 20 | CgiStatus ICACHE_FLASH_ATTR authBasic(HttpdConnData *connData); 21 | 22 | #endif 23 | -------------------------------------------------------------------------------- /include/libesphttpd/captdns.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // NOTE: hostnames that end in '.local' appear to conflict with mDNS resolution, 4 | // at least in MacOS. These hostnames will not be redirected to the captive 5 | // dns server. 6 | void captdnsInit(void); 7 | -------------------------------------------------------------------------------- /include/libesphttpd/cgi_common.h: -------------------------------------------------------------------------------- 1 | /* Functions commonly used in cgi handlers for libesphttpd */ 2 | 3 | #ifndef CGI_COMMON_H 4 | #define CGI_COMMON_H 5 | 6 | #include "libesphttpd/httpd.h" 7 | #include "cJSON.h" 8 | 9 | #ifdef __cplusplus 10 | extern "C" { 11 | #endif 12 | 13 | // this should be more efficient than httpdSend(,,-1) using strlen() at runtime on constant strings 14 | #define HTTP_SEND_CONST(connData, constString) httpdSend(connData, constString, sizeof(constString)-1) //-1 to skip sending the null-terminator 15 | 16 | enum { 17 | CGI_ARG_ERROR = -1, 18 | CGI_ARG_NOT_FOUND = 0, 19 | CGI_ARG_FOUND = 1 20 | }; 21 | int cgiGetArgDecS32(const char *allArgs, const char *argName, int *pvalue, char *buff, int buffLen); 22 | int cgiGetArgDecU32(const char *allArgs, const char *argName, uint32_t *pvalue, char *buff, int buffLen); 23 | int cgiGetArgHexU32(const char *allArgs, const char *argName, uint32_t *pvalue, char *buff, int buffLen); 24 | int cgiGetArgString(const char *allArgs, const char *argName, char *buff, int buffLen); 25 | 26 | void cgiJsonResponseHeaders(HttpdConnData *connData); 27 | void cgiJavascriptResponseHeaders(HttpdConnData *connData); 28 | 29 | CgiStatus cgiResponseCommonMulti(HttpdConnData *connData, void **statepp, char *toSendAndFree); 30 | CgiStatus cgiJsonResponseCommonMulti(HttpdConnData *connData, void **statepp, cJSON *jsroot); 31 | CgiStatus cgiJsonResponseCommonSingle(HttpdConnData *connData, cJSON *jsroot); 32 | CgiStatus cgiResponseCommonMultiCleanup(void **statepp); 33 | 34 | CgiStatus cgiJavascriptResponseCommon(HttpdConnData *connData, cJSON *jsroot, const char *jsObjName); 35 | 36 | #ifdef __cplusplus 37 | } 38 | #endif 39 | #endif //CGI_COMMON_H 40 | -------------------------------------------------------------------------------- /include/libesphttpd/cgiflash.h: -------------------------------------------------------------------------------- 1 | #ifndef CGIFLASH_H 2 | #define CGIFLASH_H 3 | 4 | #include "httpd.h" 5 | 6 | #define CGIFLASH_TYPE_FW 0 7 | #define CGIFLASH_TYPE_ESPFS 1 8 | 9 | #ifdef __cplusplus 10 | extern "C" { 11 | #endif 12 | 13 | typedef struct { 14 | int type; 15 | int fw1Pos; 16 | int fw2Pos; 17 | int fwSize; 18 | const char *tagName; 19 | } CgiUploadFlashDef; 20 | 21 | CgiStatus cgiGetFirmwareNext(HttpdConnData *connData); 22 | CgiStatus cgiUploadFirmware(HttpdConnData *connData); 23 | CgiStatus cgiRebootFirmware(HttpdConnData *connData); 24 | CgiStatus cgiSetBoot(HttpdConnData *connData); 25 | CgiStatus cgiEraseFlash(HttpdConnData *connData); 26 | CgiStatus cgiGetFlashInfo(HttpdConnData *connData); 27 | 28 | #ifdef __cplusplus 29 | } 30 | #endif 31 | #endif 32 | -------------------------------------------------------------------------------- /include/libesphttpd/cgiredirect.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "httpd.h" 4 | 5 | #ifdef __cplusplus 6 | extern "C" { 7 | #endif 8 | 9 | CgiStatus cgiRedirect(HttpdConnData *connData); 10 | 11 | // This CGI function redirects to a fixed url of http://[hostname]/ if hostname 12 | // field of request isn't already that hostname. Use this in combination with 13 | // a DNS server that redirects everything to the ESP in order to load a HTML 14 | // page as soon as a phone, tablet etc connects to the ESP. 15 | // 16 | // NOTE: If your httpd server is listening on all interfaces this will also 17 | // redirect connections when the ESP is in STA mode, potentially to a 18 | // hostname that is not in the 'official' DNS and so will fail. 19 | // 20 | // It is recommended to place this cgi route early or first in your route list 21 | // so the redirect occurs before other route processing 22 | // 23 | // The 'cgiArg' to this cgi function is the hostname that the client will be redirected 24 | // to. 25 | // 26 | // If the hostname matches the hostname specified in cgiArg then this cgi function 27 | // will return HTTPD_CGI_NOTFOUND, causing the libesphttpd server to skip 28 | // over this cgi function and to continue processing with the next route 29 | // 30 | // Example usage: 31 | // ROUTE_CGI_ARG("*", cgiRedirectToHostname, "HOSTNAME_HERE"), 32 | CgiStatus cgiRedirectToHostname(HttpdConnData *connData); 33 | 34 | CgiStatus cgiRedirectApClientToHostname(HttpdConnData *connData); 35 | 36 | #ifdef __cplusplus 37 | } 38 | #endif -------------------------------------------------------------------------------- /include/libesphttpd/cgiwebsocket.h: -------------------------------------------------------------------------------- 1 | #ifndef CGIWEBSOCKET_H 2 | #define CGIWEBSOCKET_H 3 | 4 | #include "httpd.h" 5 | #include "kref.h" 6 | 7 | #define WEBSOCK_FLAG_NONE 0 8 | #define WEBSOCK_FLAG_MORE (1<<0) //Set if the data is not the final data in the message; more follows 9 | #define WEBSOCK_FLAG_BIN (1<<1) //Set if the data is binary instead of text 10 | #define WEBSOCK_FLAG_CONT (1<<2) //set if this is a continuation frame (after WEBSOCK_FLAG_CONT) 11 | #define WEBSOCK_CLOSED -1 12 | 13 | #ifdef __cplusplus 14 | extern "C" { 15 | #endif 16 | 17 | typedef struct Websock Websock; 18 | typedef struct WebsockPriv WebsockPriv; 19 | 20 | typedef void(*WsConnectedCb)(Websock *ws); 21 | typedef void(*WsRecvCb)(Websock *ws, char *data, int len, int flags); 22 | typedef void(*WsSentCb)(Websock *ws); 23 | typedef void(*WsCloseCb)(Websock *ws); 24 | 25 | struct Websock { 26 | struct kref ref_cnt; // reference count to manage lifetime of this shared object 27 | void *userData; // optional user data to attach to a Websock object, not used by the library 28 | HttpdConnData *conn; // Stores a reference to the connData, but warning that we don't own it and it may be freed. 29 | WsRecvCb recvCb; // optional user callback on data recieved 30 | WsSentCb sentCb; // optional user callback on data sent 31 | WsCloseCb closeCb; // optional user callback on websocket close 32 | WebsockPriv *priv; 33 | }; 34 | 35 | CgiStatus ICACHE_FLASH_ATTR cgiWebsocket(HttpdConnData *connData); 36 | int ICACHE_FLASH_ATTR cgiWebsocketSend(HttpdInstance *pInstance, Websock *ws, const char *data, int len, int flags); 37 | void ICACHE_FLASH_ATTR cgiWebsocketClose(HttpdInstance *pInstance, Websock *ws, int reason); 38 | CgiStatus ICACHE_FLASH_ATTR cgiWebSocketRecv(HttpdInstance *pInstance, HttpdConnData *connData, char *data, int len); 39 | int ICACHE_FLASH_ATTR cgiWebsockBroadcast(HttpdInstance *pInstance, const char *resource, const char *data, int len, int flags); 40 | 41 | #ifdef __cplusplus 42 | } /* extern "C" */ 43 | #endif 44 | 45 | #endif 46 | -------------------------------------------------------------------------------- /include/libesphttpd/cgiwifi.h: -------------------------------------------------------------------------------- 1 | #ifndef CGIWIFI_H 2 | #define CGIWIFI_H 3 | 4 | #include "httpd.h" 5 | 6 | CgiStatus cgiWiFiScan(HttpdConnData *connData); 7 | CgiStatus tplWlan(HttpdConnData *connData, char *token, void **arg); 8 | CgiStatus cgiWiFi(HttpdConnData *connData); 9 | CgiStatus cgiWiFiConnect(HttpdConnData *connData); 10 | CgiStatus cgiWiFiSetMode(HttpdConnData *connData); 11 | CgiStatus cgiWiFiAPSettings(HttpdConnData *connData); 12 | CgiStatus cgiWiFiConnStatus(HttpdConnData *connData); 13 | 14 | #ifdef ESP32 15 | #include 16 | esp_err_t initCgiWifi(void); 17 | esp_err_t startCgiWifi(void); 18 | CgiStatus cgiWiFiStartWps(HttpdConnData *connData); 19 | #endif 20 | 21 | 22 | #endif 23 | -------------------------------------------------------------------------------- /include/libesphttpd/esp.h: -------------------------------------------------------------------------------- 1 | // Combined include file for esp8266 and esp32 2 | 3 | #ifdef ESP_PLATFORM //only set in esp-idf 4 | #define FREERTOS 1 5 | #define ESP32 1 6 | 7 | #include "sdkconfig.h" 8 | #define HTTPD_STACKSIZE CONFIG_ESPHTTPD_STACK_SIZE 9 | #include "stdint.h" 10 | typedef uint8_t uint8; 11 | typedef uint16_t uint16; 12 | typedef uint32_t uint32; 13 | typedef int8_t int8; 14 | typedef int16_t int16; 15 | typedef int32_t int32; 16 | 17 | #define ICACHE_RODATA_ATTR 18 | #endif 19 | 20 | 21 | #include 22 | #include 23 | #include 24 | #include 25 | 26 | #ifndef linux 27 | 28 | #ifdef FREERTOS 29 | #include 30 | #ifdef ESP32 31 | #include "esp_types.h" 32 | #include "esp_attr.h" 33 | #include "esp_spi_flash.h" 34 | #else 35 | #include 36 | #endif 37 | 38 | #else 39 | #include 40 | #include 41 | #include 42 | #include 43 | #include 44 | #include 45 | #include 46 | #include 47 | #include 48 | #endif 49 | 50 | #endif // #ifndef linux 51 | 52 | #include "platform.h" 53 | 54 | #ifndef linux 55 | #include "espmissingincludes.h" 56 | #endif 57 | -------------------------------------------------------------------------------- /include/libesphttpd/esp32_httpd_vfs.h: -------------------------------------------------------------------------------- 1 | #ifndef ESP32_HTTPD_VFS_H 2 | #define ESP32_HTTPD_VFS_H 3 | 4 | #include "httpd.h" 5 | 6 | #ifdef __cplusplus 7 | extern "C" { 8 | #endif 9 | 10 | 11 | //This is a catch-all cgi function. It takes the url passed to it, looks up the corresponding 12 | //path in the filesystem and if it exists, passes the file through. This simulates what a normal 13 | //webserver would do with static files. 14 | // If the file is not found, (or if http method is not GET) this cgi function returns NOT_FOUND, and then other cgi functions specified later in the routing table can try. 15 | // The cgiArg value is the base directory path, if specified. 16 | // 17 | // Usage: 18 | // ROUTE_CGI("*", cgiEspVfsGet) or 19 | // ROUTE_CGI_ARG("*", cgiEspVfsGet, "/base/directory/") or 20 | // ROUTE_CGI_ARG("*", cgiEspVfsGet, ".") to use the current working directory 21 | CgiStatus cgiEspVfsGet(HttpdConnData *connData); 22 | 23 | 24 | //This is a POST and PUT handler for uploading files to the VFS filesystem. 25 | // If http method is not PUT or POST, this cgi function returns NOT_FOUND, and then other cgi functions specified later in the routing table can try. 26 | // Specify base directory (with trailing slash) or single file as 1st cgiArg. 27 | // 28 | // Filename can be specified 3 ways, in order of priority lowest to highest: 29 | // 1. URL Path. i.e. PUT http://1.2.3.4/path/newfile.txt 30 | // 2. Inside multipart/form-data (todo not supported yet) 31 | // 3. URL Parameter. i.e. POST http://1.2.3.4/upload.cgi?filename=path%2Fnewfile.txt 32 | // 33 | // Usage: 34 | // ROUTE_CGI_ARG("*", cgiEspVfsUpload, "/base/directory/") 35 | // - Allows creating/replacing files anywhere under "/base/directory/". Don't forget to specify trailing slash in cgiArg! 36 | // - example: POST or PUT http://1.2.3.4/anydir/anyname.txt 37 | // 38 | // ROUTE_CGI_ARG("/filesystem/upload.cgi", cgiEspVfsUpload, "/base/directory/") 39 | // - Allows creating/replacing files anywhere under "/base/directory/". Don't forget to specify trailing slash in cgiArg! 40 | // - example: POST or PUT http://1.2.3.4/filesystem/upload.cgi?filename=newdir%2Fnewfile.txt 41 | // 42 | // ROUTE_CGI_ARG("/writeable_file.txt", cgiEspVfsUpload, "/base/directory/writeable_file.txt") 43 | // - Allows only replacing content of one file at "/base/directory/writeable_file.txt". 44 | // - example: POST or PUT http://1.2.3.4/writeable_file.txt 45 | CgiStatus cgiEspVfsUpload(HttpdConnData *connData); 46 | 47 | #ifdef __cplusplus 48 | } 49 | #endif 50 | 51 | #endif //ESP32_HTTPD_VFS_H 52 | -------------------------------------------------------------------------------- /include/libesphttpd/espmissingincludes.h: -------------------------------------------------------------------------------- 1 | #ifndef ESPMISSINGINCLUDES_H 2 | #define ESPMISSINGINCLUDES_H 3 | 4 | #include 5 | #ifndef ESP32 6 | #include 7 | #endif 8 | 9 | int strcasecmp(const char *a, const char *b); 10 | #ifndef FREERTOS 11 | #include 12 | #include 13 | 14 | //Missing function prototypes in include folders. Gcc will warn on these if we don't define 'em anywhere. 15 | //MOST OF THESE ARE GUESSED! but they seem to swork and shut up the compiler. 16 | typedef struct espconn espconn; 17 | 18 | int atoi(const char *nptr); 19 | void ets_install_putc1(void (*routine)(char c)); 20 | void ets_isr_attach(int intr, void (*handler)(void *), void *arg); 21 | void ets_isr_mask(unsigned intr); 22 | void ets_isr_unmask(unsigned intr); 23 | int ets_memcmp(const void *s1, const void *s2, size_t n); 24 | void *ets_memcpy(void *dest, const void *src, size_t n); 25 | void *ets_memset(void *s, int c, size_t n); 26 | int ets_sprintf(char *str, const char *format, ...) __attribute__ ((format (printf, 2, 3))); 27 | int ets_str2macaddr(void *, void *); 28 | int ets_strcmp(const char *s1, const char *s2); 29 | char *ets_strcpy(char *dest, const char *src); 30 | int ets_strlen(const char *s); 31 | int ets_strncmp(const char *s1, const char *s2, unsigned int len); 32 | char *ets_strncpy(char *dest, const char *src, size_t n); 33 | char *ets_strstr(const char *haystack, const char *needle); 34 | void ets_timer_arm_new(os_timer_t *a, uint32_t b, bool repeat, bool isMstimer); 35 | void ets_timer_disarm(os_timer_t *a); 36 | void ets_timer_setfn(os_timer_t *t, ETSTimerFunc *fn, void *parg); 37 | void ets_update_cpu_frequency(int freqmhz); 38 | void *os_memmove(void *dest, const void *src, size_t n); 39 | int os_snprintf(char *str, size_t size, const char *format, ...) __attribute__ ((format (printf, 3, 4))); 40 | int os_printf_plus(const char *format, ...) __attribute__ ((format (printf, 1, 2))); 41 | void uart_div_modify(uint8 no, uint32 freq); 42 | uint32 system_get_time(); 43 | int rand(void); 44 | void ets_bzero(void *s, size_t n); 45 | void ets_delay_us(uint16_t ms); 46 | 47 | //Hack: this is defined in SDK 1.4.0 and undefined in 1.3.0. It's only used for this, the symbol itself 48 | //has no meaning here. 49 | #ifndef RC_LIMIT_P2P_11N 50 | //Defs for SDK <1.4.0 51 | void *pvPortMalloc(size_t xWantedSize); 52 | void *pvPortZalloc(size_t); 53 | void vPortFree(void *ptr); 54 | void *vPortMalloc(size_t xWantedSize); 55 | void pvPortFree(void *ptr); 56 | #else 57 | void *pvPortMalloc(size_t xWantedSize, const char *file, unsigned line); 58 | void *pvPortZalloc(size_t, const char *file, unsigned line); 59 | void vPortFree(void *ptr, const char *file, unsigned line); 60 | void *vPortMalloc(size_t xWantedSize, const char *file, unsigned line); 61 | void pvPortFree(void *ptr, const char *file, unsigned line); 62 | #endif 63 | 64 | //Standard PIN_FUNC_SELECT gives a warning. Replace by a non-warning one. 65 | #ifdef PIN_FUNC_SELECT 66 | #undef PIN_FUNC_SELECT 67 | #define PIN_FUNC_SELECT(PIN_NAME, FUNC) do { \ 68 | WRITE_PERI_REG(PIN_NAME, \ 69 | (READ_PERI_REG(PIN_NAME) \ 70 | & (~(PERIPHS_IO_MUX_FUNC< 6 | #else 7 | #include // for sdkconfig.h 8 | #endif 9 | 10 | #ifdef __cplusplus 11 | extern "C" { 12 | #endif 13 | 14 | #ifdef CONFIG_ESPHTTPD_USE_ESPFS 15 | #include "libespfs/espfs.h" 16 | #include "httpd.h" 17 | /** 18 | * The template substitution callback. 19 | * Returns CGI_MORE if more should be sent within the token, CGI_DONE otherwise. 20 | */ 21 | typedef CgiStatus (* TplCallback)(HttpdConnData *connData, char *token, void **arg); 22 | 23 | void httpdRegisterEspfs(espfs_fs_t *fs); 24 | CgiStatus cgiEspFsHook(HttpdConnData *connData); 25 | CgiStatus ICACHE_FLASH_ATTR cgiEspFsTemplate(HttpdConnData *connData); 26 | 27 | /** 28 | * @return 1 upon success, 0 upon failure 29 | */ 30 | int tplSend(HttpdConnData *conn, const char *str, int len); 31 | 32 | #endif // CONFIG_ESPHTTPD_USE_ESPFS 33 | 34 | #ifdef __cplusplus 35 | } /* extern "C" */ 36 | #endif 37 | 38 | #endif // HTTPDESPFS_H 39 | -------------------------------------------------------------------------------- /include/libesphttpd/httpd-freertos.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "httpd.h" 4 | 5 | #ifdef FREERTOS 6 | #ifdef ESP32 7 | #include "lwip/sockets.h" 8 | #else 9 | #include "lwip/lwip/sockets.h" 10 | #endif 11 | #endif // #ifdef FREERTOS 12 | 13 | #ifdef CONFIG_ESPHTTPD_SSL_SUPPORT 14 | #include 15 | #ifdef linux 16 | #include 17 | #endif 18 | #endif 19 | 20 | #ifdef linux 21 | #include 22 | #endif 23 | 24 | 25 | #ifdef linux 26 | #define PLAT_RETURN void* 27 | #else 28 | #define PLAT_RETURN void 29 | #endif 30 | 31 | #ifdef __cplusplus 32 | extern "C" { 33 | #endif 34 | 35 | struct RtosConnType{ 36 | int fd; 37 | int needWriteDoneNotif; 38 | int needsClose; 39 | int port; 40 | char ip[4]; 41 | #ifdef CONFIG_ESPHTTPD_SSL_SUPPORT 42 | SSL *ssl; 43 | #endif 44 | 45 | // server connection data structure 46 | HttpdConnData connData; 47 | }; 48 | 49 | #define RECV_BUF_SIZE 2048 50 | 51 | typedef struct 52 | { 53 | RtosConnType *rconn; 54 | 55 | int httpPort; 56 | struct sockaddr_in httpListenAddress; 57 | HttpdFlags httpdFlags; 58 | 59 | #ifdef CONFIG_ESPHTTPD_SHUTDOWN_SUPPORT 60 | int udpShutdownPort; 61 | #endif 62 | 63 | bool isShutdown; 64 | 65 | // storage for data read in the main loop 66 | char precvbuf[RECV_BUF_SIZE]; 67 | 68 | #ifdef linux 69 | pthread_mutex_t httpdMux; 70 | #else 71 | xQueueHandle httpdMux; 72 | #endif 73 | 74 | #ifdef CONFIG_ESPHTTPD_SSL_SUPPORT 75 | SSL_CTX *ctx; 76 | #endif 77 | 78 | HttpdInstance httpdInstance; 79 | } HttpdFreertosInstance; 80 | 81 | typedef struct { 82 | bool shutdown; 83 | bool listeningForNewConnections; 84 | char serverStr[20]; 85 | struct timeval *selectTimeoutData; 86 | HttpdFreertosInstance *pInstance; 87 | int32 listenFd; 88 | int32 udpListenFd; 89 | int32 remoteFd; 90 | } ServerTaskContext; 91 | 92 | /** 93 | * Execute the server task in a loop, internally calls init, process and deinit 94 | */ 95 | PLAT_RETURN platHttpServerTask(void *pvParameters); 96 | 97 | /** 98 | * Manually init all data required for processing the server task 99 | */ 100 | void platHttpServerTaskInit(ServerTaskContext *ctx, HttpdFreertosInstance *pInstance); 101 | 102 | /** 103 | * Manually execute the server task loop function once 104 | */ 105 | void platHttpServerTaskProcess(ServerTaskContext *ctx); 106 | 107 | /** 108 | * Manually deinit all data required for processing the server task 109 | */ 110 | PLAT_RETURN platHttpServerTaskDeinit(ServerTaskContext *ctx); 111 | 112 | 113 | /* 114 | * connectionBuffer should be sized 'sizeof(RtosConnType) * maxConnections' 115 | */ 116 | HttpdInitStatus httpdFreertosInit(HttpdFreertosInstance *pInstance, 117 | const HttpdBuiltInUrl *fixedUrls, 118 | int port, 119 | void* connectionBuffer, int maxConnections, 120 | HttpdFlags flags); 121 | 122 | /* NOTE: listenAddress is in network byte order 123 | * 124 | * connectionBuffer should be sized 'sizeof(RtosConnType) * maxConnections' 125 | */ 126 | HttpdInitStatus httpdFreertosInitEx(HttpdFreertosInstance *pInstance, 127 | const HttpdBuiltInUrl *fixedUrls, 128 | int port, 129 | uint32_t listenAddress, 130 | void* connectionBuffer, int maxConnections, 131 | HttpdFlags flags); 132 | 133 | 134 | typedef enum 135 | { 136 | StartSuccess, 137 | StartFailedSslNotConfigured 138 | } HttpdStartStatus; 139 | 140 | /** 141 | * Call to start the server 142 | */ 143 | HttpdStartStatus httpdFreertosStart(HttpdFreertosInstance *pInstance); 144 | 145 | typedef enum 146 | { 147 | SslInitSuccess, 148 | SslInitContextCreationFailed 149 | } SslInitStatus; 150 | 151 | /** 152 | * Configure SSL 153 | * 154 | * NOTE: Must be called before starting the server if SSL mode is enabled 155 | * NOTE: Must be called again after each call to httpdShutdown() 156 | */ 157 | SslInitStatus httpdFreertosSslInit(HttpdFreertosInstance *pInstance); 158 | 159 | /** 160 | * Set the ssl certificate and private key (in DER format) 161 | * 162 | * NOTE: Must be called before starting the server if SSL mode is enabled 163 | */ 164 | void httpdFreertosSslSetCertificateAndKey(HttpdFreertosInstance *pInstance, 165 | const void *certificate, size_t certificate_size, 166 | const void *private_key, size_t private_key_size); 167 | 168 | typedef enum 169 | { 170 | SslClientVerifyNone, 171 | SslClientVerifyRequired 172 | } SslClientVerifySetting; 173 | 174 | /** 175 | * Enable / disable client certificate verification 176 | * 177 | * NOTE: Ssl defaults to SslClientVerifyNone 178 | */ 179 | void httpdFreertosSslSetClientValidation(HttpdFreertosInstance *pInstance, 180 | SslClientVerifySetting verifySetting); 181 | 182 | /** 183 | * Add a client certificate (in DER format) 184 | * 185 | * NOTE: Should use httpdFreertosSslSetClientValidation() to enable validation 186 | */ 187 | void httpdFreertosSslAddClientCertificate(HttpdFreertosInstance *pInstance, 188 | const void *certificate, size_t certificate_size); 189 | #ifdef __cplusplus 190 | } /* extern "C" */ 191 | #endif 192 | -------------------------------------------------------------------------------- /include/libesphttpd/httpd.h: -------------------------------------------------------------------------------- 1 | #ifndef HTTPD_H 2 | #define HTTPD_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #ifdef __cplusplus 9 | extern "C" { 10 | #endif 11 | 12 | #include "esp.h" 13 | 14 | /** 15 | * Design notes: 16 | * - The platform code owns the memory management of connections 17 | * - The platform embeds a HttpdConnData into its own structure, this enables 18 | * the platform code, through the use of the container_of approach, to 19 | * find its connection structure when given a HttpdConnData* 20 | */ 21 | 22 | 23 | #define HTTPDVER "0.5" 24 | 25 | //Max length of request head. This is statically allocated for each connection. 26 | #ifndef HTTPD_MAX_HEAD_LEN 27 | #define HTTPD_MAX_HEAD_LEN 1024 28 | #endif 29 | 30 | //Max post buffer len. This is dynamically malloc'ed if needed. 31 | #ifndef HTTPD_MAX_POST_LEN 32 | #define HTTPD_MAX_POST_LEN 2048 33 | #endif 34 | 35 | //Send buffer size 36 | #ifndef HTTPD_SENDBUFF_SIZE 37 | #define HTTPD_SENDBUFF_SIZE 2048 38 | #endif 39 | 40 | //Send buffer limit for httpdSend. 2 bytes are reserved for chunk termination ('\r\n'). 41 | #ifndef HTTPD_SENDBUFF_MAX_FILL 42 | #define HTTPD_SENDBUFF_MAX_FILL (HTTPD_SENDBUFF_SIZE - 2) 43 | #endif 44 | 45 | //Send buffer limit (for backward compatibility 46 | #ifndef HTTPD_MAX_SENDBUFF_LEN 47 | #define HTTPD_MAX_SENDBUFF_LEN HTTPD_SENDBUFF_MAX_FILL 48 | #endif 49 | 50 | //If some data can't be sent because the underlaying socket doesn't accept the data (like the nonos 51 | //layer is prone to do), we put it in a backlog that is dynamically malloc'ed. This defines the max 52 | //size of the backlog. 53 | #ifndef HTTPD_MAX_BACKLOG_SIZE 54 | #define HTTPD_MAX_BACKLOG_SIZE (4*1024) 55 | #endif 56 | 57 | //Max length of CORS token. This amount is allocated per connection. 58 | #define MAX_CORS_TOKEN_LEN 256 59 | 60 | typedef enum 61 | { 62 | HTTPD_CGI_MORE, 63 | HTTPD_CGI_DONE, 64 | HTTPD_CGI_NOTFOUND, 65 | HTTPD_CGI_AUTHENTICATED 66 | } CgiStatus; 67 | 68 | typedef enum 69 | { 70 | HTTPD_METHOD_GET, 71 | HTTPD_METHOD_POST, 72 | HTTPD_METHOD_OPTIONS, 73 | HTTPD_METHOD_PUT, 74 | HTTPD_METHOD_PATCH, 75 | HTTPD_METHOD_DELETE 76 | } RequestTypes; 77 | 78 | typedef enum 79 | { 80 | HTTPD_TRANSFER_CLOSE, 81 | HTTPD_TRANSFER_CHUNKED, 82 | HTTPD_TRANSFER_NONE 83 | } TransferModes; 84 | 85 | typedef struct HttpdPriv HttpdPriv; 86 | typedef struct HttpdConnData HttpdConnData; 87 | typedef struct HttpdPostData HttpdPostData; 88 | typedef struct HttpdInstance HttpdInstance; 89 | 90 | 91 | typedef CgiStatus (* cgiSendCallback)(HttpdConnData *connData); 92 | typedef CgiStatus (* cgiRecvHandler)(HttpdInstance *pInstance, HttpdConnData *connData, char *data, int len); 93 | 94 | #ifdef CONFIG_ESPHTTPD_BACKLOG_SUPPORT 95 | struct HttpSendBacklogItem { 96 | int len; 97 | HttpSendBacklogItem *next; 98 | char data[]; 99 | }; 100 | #endif 101 | 102 | //Private data for http connection 103 | struct HttpdPriv { 104 | char head[HTTPD_MAX_HEAD_LEN]; 105 | #ifdef CONFIG_ESPHTTPD_CORS_SUPPORT 106 | char corsToken[MAX_CORS_TOKEN_LEN]; 107 | #endif 108 | int headPos; 109 | char sendBuff[HTTPD_SENDBUFF_SIZE]; 110 | int sendBuffLen; 111 | 112 | /** NOTE: chunkHdr, if valid, points at memory assigned to sendBuff 113 | so it doesn't have to be freed */ 114 | char *chunkHdr; 115 | 116 | #ifdef CONFIG_ESPHTTPD_BACKLOG_SUPPORT 117 | HttpSendBacklogItem *sendBacklog; 118 | int sendBacklogSize; 119 | #endif 120 | int flags; 121 | }; 122 | 123 | //A struct describing the POST data sent inside the http connection. This is used by the CGI functions 124 | struct HttpdPostData { 125 | int len; // POST Content-Length 126 | int buffSize; // The maximum length of the post buffer 127 | int buffLen; // The amount of bytes in the current post buffer 128 | int received; // The total amount of bytes received so far 129 | char *buff; // Actual POST data buffer 130 | char *multipartBoundary; // Pointer to the start of the multipart boundary value in priv.head 131 | }; 132 | 133 | //A struct describing a http connection. This gets passed to cgi functions. 134 | struct HttpdConnData { 135 | RequestTypes requestType; 136 | char *url; // The URL requested, without hostname or GET arguments 137 | const char *route; // The route matched. 138 | char *getArgs; // The GET arguments for this request, if any. 139 | const void *cgiArg; // Argument to the CGI function, as stated as the 3rd argument of 140 | // the builtInUrls entry that referred to the CGI function. 141 | const void *cgiArg2; // 4th argument of the builtInUrls entries, used to pass template file to the tpl handler. 142 | void *cgiData; // Opaque data pointer for the CGI function 143 | char *hostName; // Host name field of request 144 | HttpdPriv priv; // Data for internal httpd housekeeping 145 | cgiSendCallback cgi; // CGI function pointer 146 | cgiRecvHandler recvHdl; // Handler for data received after headers, if any 147 | HttpdPostData post; // POST data structure 148 | bool isConnectionClosed; 149 | }; 150 | 151 | //A struct describing an url. This is the main struct that's used to send different URL requests to 152 | //different routines. 153 | typedef struct { 154 | const char *url; 155 | cgiSendCallback cgiCb; 156 | const void *cgiArg; 157 | const void *cgiArg2; 158 | } HttpdBuiltInUrl; 159 | 160 | extern const char *httpdCgiEx; /* Magic for use in CgiArgs to interpret CgiArgs2 as HttpdCgiExArg */ 161 | 162 | typedef struct { 163 | void (*headerCb)(HttpdConnData *connData); 164 | const char *mimetype; 165 | const char *basepath; 166 | } HttpdCgiExArg; 167 | 168 | void httpdRedirect(HttpdConnData *conn, const char *newUrl); 169 | 170 | // Decode a percent-encoded value. 171 | // Takes the valLen bytes stored in val, and converts it into at most retLen bytes that 172 | // are stored in the ret buffer. ret is always null terminated. 173 | // @return True if decoding fit into the ret buffer, false if not 174 | bool httpdUrlDecode(const char *val, int valLen, char *ret, int retLen, int* bytesWritten); 175 | 176 | int httpdFindArg(const char *line, const char *arg, char *buff, int buffLen); 177 | 178 | typedef enum 179 | { 180 | HTTPD_FLAG_NONE = (1 << 0), 181 | HTTPD_FLAG_SSL = (1 << 1) 182 | } HttpdFlags; 183 | 184 | typedef enum 185 | { 186 | InitializationSuccess, 187 | InitializationFailure 188 | } HttpdInitStatus; 189 | 190 | /** Common elements to the core server code */ 191 | typedef struct HttpdInstance 192 | { 193 | const HttpdBuiltInUrl *builtInUrls; 194 | 195 | int maxConnections; 196 | } HttpdInstance; 197 | 198 | typedef enum 199 | { 200 | CallbackSuccess, 201 | CallbackErrorOutOfConnections, 202 | CallbackErrorCannotFindConnection, 203 | CallbackErrorMemory, 204 | CallbackError 205 | } CallbackStatus; 206 | 207 | const char *httpdGetMimetype(const char *url); 208 | void httpdSetTransferMode(HttpdConnData *conn, TransferModes mode); 209 | void httpdStartResponse(HttpdConnData *conn, int code); 210 | void httpdHeader(HttpdConnData *conn, const char *field, const char *val); 211 | void httpdEndHeaders(HttpdConnData *conn); 212 | 213 | /** 214 | * Get the value of a certain header in the HTTP client head 215 | * Returns true when found, false when not found. 216 | * 217 | * NOTE: 'ret' will be null terminated 218 | * 219 | * @param retLen is the number of bytes available in 'ret' 220 | */ 221 | bool httpdGetHeader(HttpdConnData *conn, const char *header, char *ret, int retLen); 222 | 223 | int httpdSend(HttpdConnData *conn, const char *data, int len); 224 | int httpdSend_js(HttpdConnData *conn, const char *data, int len); 225 | int httpdSend_html(HttpdConnData *conn, const char *data, int len); 226 | void httpdFlushSendBuffer(HttpdInstance *pInstance, HttpdConnData *conn); 227 | CallbackStatus httpdContinue(HttpdInstance *pInstance, HttpdConnData *conn); 228 | CallbackStatus httpdConnSendStart(HttpdInstance *pInstance, HttpdConnData *conn); 229 | void httpdConnSendFinish(HttpdInstance *pInstance, HttpdConnData *conn); 230 | void httpdAddCacheHeaders(HttpdConnData *connData, const char *mime); 231 | 232 | //Platform dependent code should call these. 233 | CallbackStatus httpdSentCb(HttpdInstance *pInstance, HttpdConnData *pConn); 234 | CallbackStatus httpdRecvCb(HttpdInstance *pInstance, HttpdConnData *pConn, char *data, unsigned short len); 235 | CallbackStatus httpdDisconCb(HttpdInstance *pInstance, HttpdConnData *pConn); 236 | 237 | /** NOTE: httpdConnectCb() cannot fail */ 238 | void httpdConnectCb(HttpdInstance *pInstance, HttpdConnData *pConn); 239 | 240 | #define esp_container_of(ptr, type, member) ({ \ 241 | const typeof( ((type *)0)->member ) *__mptr = (ptr); \ 242 | (type *)( (char *)__mptr - offsetof(type,member) );}) 243 | 244 | #ifdef CONFIG_ESPHTTPD_SHUTDOWN_SUPPORT 245 | void httpdShutdown(HttpdInstance *pInstance); 246 | #endif 247 | 248 | #ifdef __cplusplus 249 | } 250 | #endif 251 | 252 | #endif 253 | -------------------------------------------------------------------------------- /include/libesphttpd/kref.h: -------------------------------------------------------------------------------- 1 | #ifndef _KREF_H_ 2 | #define _KREF_H_ 3 | 4 | #include 5 | #include 6 | 7 | #if !defined(koffsetof) 8 | #define koffsetof(type, member) ((size_t) &((type *)0)->member) 9 | #endif 10 | 11 | #if !defined(kcontainer_of) 12 | #define kcontainer_of(ptr, type, member) ({ \ 13 | const typeof( ((type *)0)->member ) *__mptr = (ptr); \ 14 | (type *)( (char *)__mptr - koffsetof(type,member) );}) 15 | #endif 16 | 17 | struct kref { 18 | atomic_int count; 19 | }; 20 | 21 | static inline void kref_init(struct kref *kref) 22 | { 23 | atomic_init(&(kref->count), 1); 24 | } 25 | 26 | static inline void kref_get(struct kref *kref) 27 | { 28 | int old; 29 | 30 | old = atomic_fetch_add(&(kref->count), 1); 31 | configASSERT(old >= 1); 32 | } 33 | 34 | static inline int kref_put(struct kref *kref, void (*release)(struct kref *kref)) 35 | { 36 | int result; 37 | int old; 38 | 39 | configASSERT(release != NULL); 40 | 41 | result = 0; 42 | 43 | old = atomic_fetch_sub(&(kref->count), 1); 44 | configASSERT(old >= 1); 45 | 46 | if(old == 1){ 47 | release(kref); 48 | result = 1; 49 | } 50 | 51 | return result; 52 | } 53 | 54 | #endif /* _KREF_H_ */ 55 | -------------------------------------------------------------------------------- /include/libesphttpd/linux.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "stdint.h" 4 | typedef uint8_t uint8; 5 | typedef uint16_t uint16; 6 | typedef uint32_t uint32; 7 | typedef int8_t int8; 8 | typedef int16_t int16; 9 | typedef int32_t int32; 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | #include 18 | 19 | #include "platform.h" 20 | -------------------------------------------------------------------------------- /include/libesphttpd/platform.h: -------------------------------------------------------------------------------- 1 | #ifndef PLATFORM_H 2 | #define PLATFORM_H 3 | 4 | #ifdef FREERTOS 5 | #include 6 | 7 | #ifdef ESP32 8 | #include "freertos/FreeRTOS.h" 9 | #include "freertos/timers.h" 10 | #else 11 | #include "FreeRTOS.h" 12 | #include "timers.h" 13 | #endif 14 | 15 | //#include "esp_timer.h" 16 | typedef struct RtosConnType RtosConnType; 17 | typedef RtosConnType* ConnTypePtr; 18 | 19 | #ifdef ESP32 20 | // freertos v8 api 21 | typedef TimerHandle_t HttpdPlatTimerHandle; 22 | #else 23 | // freertos v7 api 24 | typedef xTimerHandle HttpdPlatTimerHandle; 25 | #endif 26 | 27 | #ifdef ESP32 28 | #define ICACHE_FLASH_ATTR 29 | #endif 30 | 31 | #elif defined(linux) 32 | 33 | #include 34 | #include 35 | typedef struct RtosConnType RtosConnType; 36 | typedef RtosConnType* ConnTypePtr; 37 | 38 | #define vTaskDelay(milliseconds) usleep((milliseconds) * 1000) 39 | #define portTICK_RATE_MS 1 40 | #define portTICK_PERIOD_MS 1 41 | 42 | typedef struct 43 | { 44 | timer_t timer; 45 | int timerPeriodMS; 46 | bool autoReload; 47 | void (*callback)(void* arg); 48 | void* callbackArg; 49 | } HttpdPlatTimer; 50 | 51 | typedef HttpdPlatTimer* HttpdPlatTimerHandle; 52 | 53 | #define ICACHE_FLASH_ATTR 54 | #define ICACHE_RODATA_ATTR 55 | 56 | #else // no-os, map to os-specific versions that have to be defined 57 | 58 | typedef struct 59 | { 60 | os_timer_t timer; 61 | int timerPeriodMS; 62 | bool autoReload; 63 | } HttpdPlatTimer; 64 | 65 | #define printf(...) os_printf(__VA_ARGS__) 66 | #define sprintf(str, ...) os_sprintf(str, __VA_ARGS__) 67 | #define strcpy(a, b) os_strcpy(a, b) 68 | #define strncpy(a, b, c) os_strncpy(a, b, c) 69 | #define strcmp(a, b) os_strcmp(a, b) 70 | #define strncmp(a, b, c) os_strncmp(a, b, c) 71 | #define malloc(x) os_malloc(x) 72 | #define free(x) os_free(x) 73 | #define memset(x, a, b) os_memset(x, a, b) 74 | #define memcpy(x, a, b) os_memcpy(x, a, b) 75 | #define strcat(a, b) os_strcat(a, b) 76 | #define strstr(a, b) os_strstr(a, b) 77 | #define strlen(a) os_strlen(a) 78 | #define memcmp(a, b, c) os_memcmp(a, b, c) 79 | typedef struct espconn* ConnTypePtr; 80 | typedef HttpdPlatTimer* HttpdPlatTimerHandle; 81 | #define httpd_printf(format, ...) os_printf(format, ##__VA_ARGS__) 82 | #endif 83 | 84 | 85 | 86 | #endif 87 | -------------------------------------------------------------------------------- /include/libesphttpd/route.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "cgiredirect.h" 4 | 5 | // macros for defining HttpdBuiltInUrl's 6 | 7 | /** Route with a CGI handler and two arguments */ 8 | #define ROUTE_CGI_ARG2(path, handler, arg1, arg2) {(path), (handler), (void *)(arg1), (void *)(arg2)} 9 | 10 | /** Route with a CGI handler and one argument */ 11 | #define ROUTE_CGI_ARG(path, handler, arg1) ROUTE_CGI_ARG2((path), (handler), (arg1), NULL) 12 | 13 | /** Route with a CGI handler and an extended argument */ 14 | #define ROUTE_CGI_EX(path, handler, ex) ROUTE_CGI_ARG2((path), (handler), &httpdCgiEx, (ex)) 15 | 16 | /** Route with an argument-less CGI handler */ 17 | #define ROUTE_CGI(path, handler) ROUTE_CGI_ARG2((path), (handler), NULL, NULL) 18 | 19 | /** Static file route (file loaded from espfs) */ 20 | #define ROUTE_FILE(path, filepath) ROUTE_CGI_ARG((path), cgiEspFsHook, (const char*)(filepath)) 21 | 22 | /** Extended static file route (file loaded from espfs) */ 23 | #define ROUTE_FILE_EX(path, ex) ROUTE_CGI_EX((path), cgiEspFsHook, (HttpdCgiExArg*)(ex)) 24 | 25 | /** Static file as a template with a replacer function */ 26 | #define ROUTE_TPL(path, replacer) ROUTE_CGI_ARG2((path), cgiEspFsTemplate, NULL, (TplCallback)(replacer)) 27 | 28 | /** Static file as a template with a replacer function, taking additional argument connData->cgiArg2 */ 29 | #define ROUTE_TPL_FILE(path, replacer, filepath) ROUTE_CGI_ARG2((path), cgiEspFsTemplate, (const char*)(filepath), (TplCallback)(replacer)) 30 | 31 | /** Redirect to some URL */ 32 | #define ROUTE_REDIRECT(path, target) ROUTE_CGI_ARG((path), cgiRedirect, (const char*)(target)) 33 | 34 | /** Following routes are basic-auth protected */ 35 | #define ROUTE_AUTH(path, passwdFunc) ROUTE_CGI_ARG((path), authBasic, (AuthGetUserPw)(passwdFunc)) 36 | 37 | /** Websocket endpoint */ 38 | #define ROUTE_WS(path, callback) ROUTE_CGI_ARG((path), cgiWebsocket, (WsConnectedCb)(callback)) 39 | 40 | /** Catch-all filesystem route */ 41 | #define ROUTE_FILESYSTEM() ROUTE_CGI("*", cgiEspFsHook) 42 | 43 | #define ROUTE_END() {NULL, NULL, NULL, NULL} 44 | -------------------------------------------------------------------------------- /include/libesphttpd/sha1.h: -------------------------------------------------------------------------------- 1 | /* header */ 2 | 3 | #ifndef __SHA1_H__ 4 | #define __SHA1_H__ 5 | 6 | #define HASH_LENGTH 20 7 | #define BLOCK_LENGTH 64 8 | 9 | typedef struct sha1nfo { 10 | uint32_t buffer[BLOCK_LENGTH/4]; 11 | uint32_t state[HASH_LENGTH/4]; 12 | uint32_t byteCount; 13 | uint8_t bufferOffset; 14 | uint8_t keyBuffer[BLOCK_LENGTH]; 15 | uint8_t innerHash[HASH_LENGTH]; 16 | } sha1nfo; 17 | 18 | /* public API - prototypes - TODO: doxygen*/ 19 | 20 | /** 21 | */ 22 | void sha1_init(sha1nfo *s); 23 | /** 24 | */ 25 | void sha1_writebyte(sha1nfo *s, uint8_t data); 26 | /** 27 | */ 28 | void sha1_write(sha1nfo *s, const char *data, size_t len); 29 | /** 30 | */ 31 | uint8_t* sha1_result(sha1nfo *s); 32 | /** 33 | */ 34 | void sha1_initHmac(sha1nfo *s, const uint8_t* key, int keyLength); 35 | /** 36 | */ 37 | uint8_t* sha1_resultHmac(sha1nfo *s); 38 | 39 | #endif 40 | -------------------------------------------------------------------------------- /include/libesphttpd/user_config.h: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /include/linux/esp_log.h: -------------------------------------------------------------------------------- 1 | // Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #ifndef __ESP_LOG_H__ 16 | #define __ESP_LOG_H__ 17 | 18 | #include 19 | #include 20 | //#include "sdkconfig.h" 21 | //#include 22 | 23 | #ifdef __cplusplus 24 | extern "C" { 25 | #endif 26 | 27 | /** 28 | * @brief Log level 29 | * 30 | */ 31 | typedef enum { 32 | ESP_LOG_NONE, /*!< No log output */ 33 | ESP_LOG_ERROR, /*!< Critical errors, software module can not recover on its own */ 34 | ESP_LOG_WARN, /*!< Error conditions from which recovery measures have been taken */ 35 | ESP_LOG_INFO, /*!< Information messages which describe normal flow of events */ 36 | ESP_LOG_DEBUG, /*!< Extra information which is not necessary for normal use (values, pointers, sizes, etc). */ 37 | ESP_LOG_VERBOSE /*!< Bigger chunks of debugging information, or frequent messages which can potentially flood the output. */ 38 | } esp_log_level_t; 39 | 40 | typedef int (*vprintf_like_t)(const char *, va_list); 41 | 42 | /** 43 | * @brief Set log level for given tag 44 | * 45 | * If logging for given component has already been enabled, changes previous setting. 46 | * 47 | * @param tag Tag of the log entries to enable. Must be a non-NULL zero terminated string. 48 | * Value "*" resets log level for all tags to the given value. 49 | * 50 | * @param level Selects log level to enable. Only logs at this and lower levels will be shown. 51 | */ 52 | void esp_log_level_set(const char* tag, esp_log_level_t level); 53 | 54 | /** 55 | * @brief Set function used to output log entries 56 | * 57 | * By default, log output goes to UART0. This function can be used to redirect log 58 | * output to some other destination, such as file or network. 59 | * 60 | * @param func Function used for output. Must have same signature as vprintf. 61 | */ 62 | void esp_log_set_vprintf(vprintf_like_t func); 63 | 64 | /** 65 | * @brief Function which returns timestamp to be used in log output 66 | * 67 | * This function is used in expansion of ESP_LOGx macros. 68 | * In the 2nd stage bootloader, and at early application startup stage 69 | * this function uses CPU cycle counter as time source. Later when 70 | * FreeRTOS scheduler start running, it switches to FreeRTOS tick count. 71 | * 72 | * For now, we ignore millisecond counter overflow. 73 | * 74 | * @return timestamp, in milliseconds 75 | */ 76 | uint32_t esp_log_timestamp(void); 77 | 78 | /** 79 | * @brief Function which returns timestamp to be used in log output 80 | * 81 | * This function uses HW cycle counter and does not depend on OS, 82 | * so it can be safely used after application crash. 83 | * 84 | * @return timestamp, in milliseconds 85 | */ 86 | uint32_t esp_log_early_timestamp(void); 87 | 88 | /** 89 | * @brief Write message into the log 90 | * 91 | * This function is not intended to be used directly. Instead, use one of 92 | * ESP_LOGE, ESP_LOGW, ESP_LOGI, ESP_LOGD, ESP_LOGV macros. 93 | * 94 | * This function or these macros should not be used from an interrupt. 95 | */ 96 | void esp_log_write(esp_log_level_t level, const char* tag, const char* format, ...) __attribute__ ((format (printf, 3, 4))); 97 | 98 | //#include "esp_log_internal.h" 99 | 100 | /** 101 | * @brief Log a buffer of hex bytes at specified level, seprated into 16 bytes each line. 102 | * 103 | * @param tag description tag 104 | * 105 | * @param buffer Pointer to the buffer array 106 | * 107 | * @param buff_len length of buffer in bytes 108 | * 109 | * @param level level of the log 110 | * 111 | */ 112 | #define ESP_LOG_BUFFER_HEX_LEVEL( tag, buffer, buff_len, level ) do {\ 113 | if ( LOG_LOCAL_LEVEL >= level ) esp_log_buffer_hex_internal( tag, buffer, buff_len, level ); } while(0) 114 | 115 | /** 116 | * @brief Log a buffer of characters at specified level, seprated into 16 bytes each line. Buffer should contain only printable characters. 117 | * 118 | * @param tag description tag 119 | * 120 | * @param buffer Pointer to the buffer array 121 | * 122 | * @param buff_len length of buffer in bytes 123 | * 124 | * @param level level of the log 125 | * 126 | */ 127 | #define ESP_LOG_BUFFER_CHAR_LEVEL( tag, buffer, buff_len, level ) do {\ 128 | if ( LOG_LOCAL_LEVEL >= level ) esp_log_buffer_char_internal( tag, buffer, buff_len, level ); } while(0) 129 | 130 | /** 131 | * @brief Dump a buffer to the log at specified level. 132 | * 133 | * The dump log shows just like the one below: 134 | * 135 | * W (195) log_example: 0x3ffb4280 45 53 50 33 32 20 69 73 20 67 72 65 61 74 2c 20 |ESP32 is great, | 136 | * W (195) log_example: 0x3ffb4290 77 6f 72 6b 69 6e 67 20 61 6c 6f 6e 67 20 77 69 |working along wi| 137 | * W (205) log_example: 0x3ffb42a0 74 68 20 74 68 65 20 49 44 46 2e 00 |th the IDF..| 138 | * 139 | * It is highly recommend to use terminals with over 102 text width. 140 | * 141 | * @param tag description tag 142 | * 143 | * @param buffer Pointer to the buffer array 144 | * 145 | * @param buff_len length of buffer in bytes 146 | * 147 | * @param level level of the log 148 | */ 149 | #define ESP_LOG_BUFFER_HEXDUMP( tag, buffer, buff_len, level ) do {\ 150 | if ( LOG_LOCAL_LEVEL >= level ) esp_log_buffer_hexdump_internal( tag, buffer, buff_len, level); } while(0) 151 | 152 | 153 | #if (LOG_LOCAL_LEVEL >= ESP_LOG_INFO) 154 | /** 155 | * @brief Log a buffer of hex bytes at Info level 156 | * 157 | * @param tag description tag 158 | * 159 | * @param buffer Pointer to the buffer array 160 | * 161 | * @param buff_len length of buffer in bytes 162 | * 163 | * @see ``esp_log_buffer_hex_level`` 164 | * 165 | */ 166 | #define ESP_LOG_BUFFER_HEX(tag, buffer, buff_len) ESP_LOG_BUFFER_HEX_LEVEL( tag, buffer, buff_len, ESP_LOG_INFO ) 167 | 168 | /** 169 | * @brief Log a buffer of characters at Info level. Buffer should contain only printable characters. 170 | * 171 | * @param tag description tag 172 | * 173 | * @param buffer Pointer to the buffer array 174 | * 175 | * @param buff_len length of buffer in bytes 176 | * 177 | * @see ``esp_log_buffer_char_level`` 178 | * 179 | */ 180 | #define ESP_LOG_BUFFER_CHAR(tag, buffer, buff_len) ESP_LOG_BUFFER_CHAR_LEVEL( tag, buffer, buff_len, ESP_LOG_INFO ) 181 | 182 | #else 183 | #define ESP_LOG_BUFFER_HEX(tag, buffer, buff_len) {} 184 | #define ESP_LOG_BUFFER_CHAR(tag, buffer, buff_len) {} 185 | #endif 186 | 187 | //to be back compatible 188 | #define esp_log_buffer_hex ESP_LOG_BUFFER_HEX 189 | #define esp_log_buffer_char ESP_LOG_BUFFER_CHAR 190 | 191 | 192 | #if CONFIG_LOG_COLORS 193 | #define LOG_COLOR_BLACK "30" 194 | #define LOG_COLOR_RED "31" 195 | #define LOG_COLOR_GREEN "32" 196 | #define LOG_COLOR_BROWN "33" 197 | #define LOG_COLOR_BLUE "34" 198 | #define LOG_COLOR_PURPLE "35" 199 | #define LOG_COLOR_CYAN "36" 200 | #define LOG_COLOR(COLOR) "\033[0;" COLOR "m" 201 | #define LOG_BOLD(COLOR) "\033[1;" COLOR "m" 202 | #define LOG_RESET_COLOR "\033[0m" 203 | #define LOG_COLOR_E LOG_COLOR(LOG_COLOR_RED) 204 | #define LOG_COLOR_W LOG_COLOR(LOG_COLOR_BROWN) 205 | #define LOG_COLOR_I LOG_COLOR(LOG_COLOR_GREEN) 206 | #define LOG_COLOR_D 207 | #define LOG_COLOR_V 208 | #else //CONFIG_LOG_COLORS 209 | #define LOG_COLOR_E 210 | #define LOG_COLOR_W 211 | #define LOG_COLOR_I 212 | #define LOG_COLOR_D 213 | #define LOG_COLOR_V 214 | #define LOG_RESET_COLOR 215 | #endif //CONFIG_LOG_COLORS 216 | 217 | #define LOG_FORMAT(letter, format) LOG_COLOR_ ## letter #letter " (%d) %s: " format LOG_RESET_COLOR "\n" 218 | 219 | #ifndef LOG_LOCAL_LEVEL 220 | #ifndef BOOTLOADER_BUILD 221 | #define LOG_LOCAL_LEVEL ((esp_log_level_t) CONFIG_LOG_DEFAULT_LEVEL) 222 | #else 223 | #define LOG_LOCAL_LEVEL ((esp_log_level_t) CONFIG_LOG_BOOTLOADER_LEVEL) 224 | #endif 225 | #endif 226 | 227 | /// macro to output logs in startup code, before heap allocator and syscalls have been initialized. log at ``ESP_LOG_ERROR`` level. @see ``printf``,``ESP_LOGE`` 228 | #define ESP_EARLY_LOGE( tag, format, ... ) if (LOG_LOCAL_LEVEL >= ESP_LOG_ERROR) { ets_printf(LOG_FORMAT(E, format), esp_log_timestamp(), tag, ##__VA_ARGS__); } 229 | /// macro to output logs in startup code at ``ESP_LOG_WARN`` level. @see ``ESP_EARLY_LOGE``,``ESP_LOGE``, ``printf`` 230 | #define ESP_EARLY_LOGW( tag, format, ... ) if (LOG_LOCAL_LEVEL >= ESP_LOG_WARN) { ets_printf(LOG_FORMAT(W, format), esp_log_timestamp(), tag, ##__VA_ARGS__); } 231 | /// macro to output logs in startup code at ``ESP_LOG_INFO`` level. @see ``ESP_EARLY_LOGE``,``ESP_LOGE``, ``printf`` 232 | #define ESP_EARLY_LOGI( tag, format, ... ) if (LOG_LOCAL_LEVEL >= ESP_LOG_INFO) { ets_printf(LOG_FORMAT(I, format), esp_log_timestamp(), tag, ##__VA_ARGS__); } 233 | /// macro to output logs in startup code at ``ESP_LOG_DEBUG`` level. @see ``ESP_EARLY_LOGE``,``ESP_LOGE``, ``printf`` 234 | #define ESP_EARLY_LOGD( tag, format, ... ) if (LOG_LOCAL_LEVEL >= ESP_LOG_DEBUG) { ets_printf(LOG_FORMAT(D, format), esp_log_timestamp(), tag, ##__VA_ARGS__); } 235 | /// macro to output logs in startup code at ``ESP_LOG_VERBOSE`` level. @see ``ESP_EARLY_LOGE``,``ESP_LOGE``, ``printf`` 236 | #define ESP_EARLY_LOGV( tag, format, ... ) if (LOG_LOCAL_LEVEL >= ESP_LOG_VERBOSE) { ets_printf(LOG_FORMAT(V, format), esp_log_timestamp(), tag, ##__VA_ARGS__); } 237 | 238 | #ifndef BOOTLOADER_BUILD 239 | #define ESP_LOGE( tag, format, ... ) if (LOG_LOCAL_LEVEL >= ESP_LOG_ERROR) { esp_log_write(ESP_LOG_ERROR, tag, LOG_FORMAT(E, format), esp_log_timestamp(), tag, ##__VA_ARGS__); } 240 | #define ESP_LOGW( tag, format, ... ) if (LOG_LOCAL_LEVEL >= ESP_LOG_WARN) { esp_log_write(ESP_LOG_WARN, tag, LOG_FORMAT(W, format), esp_log_timestamp(), tag, ##__VA_ARGS__); } 241 | #define ESP_LOGI( tag, format, ... ) if (LOG_LOCAL_LEVEL >= ESP_LOG_INFO) { esp_log_write(ESP_LOG_INFO, tag, LOG_FORMAT(I, format), esp_log_timestamp(), tag, ##__VA_ARGS__); } 242 | #define ESP_LOGD( tag, format, ... ) if (LOG_LOCAL_LEVEL >= ESP_LOG_DEBUG) { esp_log_write(ESP_LOG_DEBUG, tag, LOG_FORMAT(D, format), esp_log_timestamp(), tag, ##__VA_ARGS__); } 243 | #define ESP_LOGV( tag, format, ... ) if (LOG_LOCAL_LEVEL >= ESP_LOG_VERBOSE) { esp_log_write(ESP_LOG_VERBOSE, tag, LOG_FORMAT(V, format), esp_log_timestamp(), tag, ##__VA_ARGS__); } 244 | #else 245 | /** 246 | * macro to output logs at ESP_LOG_ERROR level. 247 | * 248 | * @param tag tag of the log, which can be used to change the log level by ``esp_log_level_set`` at runtime. 249 | * 250 | * @see ``printf`` 251 | */ 252 | #define ESP_LOGE( tag, format, ... ) ESP_EARLY_LOGE(tag, format, ##__VA_ARGS__) 253 | /// macro to output logs at ``ESP_LOG_WARN`` level. @see ``ESP_LOGE`` 254 | #define ESP_LOGW( tag, format, ... ) ESP_EARLY_LOGW(tag, format, ##__VA_ARGS__) 255 | /// macro to output logs at ``ESP_LOG_INFO`` level. @see ``ESP_LOGE`` 256 | #define ESP_LOGI( tag, format, ... ) ESP_EARLY_LOGI(tag, format, ##__VA_ARGS__) 257 | /// macro to output logs at ``ESP_LOG_DEBUG`` level. @see ``ESP_LOGE`` 258 | #define ESP_LOGD( tag, format, ... ) ESP_EARLY_LOGD(tag, format, ##__VA_ARGS__) 259 | /// macro to output logs at ``ESP_LOG_VERBOSE`` level. @see ``ESP_LOGE`` 260 | #define ESP_LOGV( tag, format, ... ) ESP_EARLY_LOGV(tag, format, ##__VA_ARGS__) 261 | #endif // BOOTLOADER_BUILD 262 | 263 | /** runtime macro to output logs at a speicfied level. 264 | * 265 | * @param tag tag of the log, which can be used to change the log level by ``esp_log_level_set`` at runtime. 266 | * 267 | * @param level level of the output log. 268 | * 269 | * @param format format of the output log. see ``printf`` 270 | * 271 | * @param ... variables to be replaced into the log. see ``printf`` 272 | * 273 | * @see ``printf`` 274 | */ 275 | #define ESP_LOG_LEVEL(level, tag, format, ...) do {\ 276 | if (level==ESP_LOG_ERROR ) { esp_log_write(ESP_LOG_ERROR, tag, LOG_FORMAT(E, format), esp_log_timestamp(), tag, ##__VA_ARGS__); }\ 277 | else if (level==ESP_LOG_WARN ) { esp_log_write(ESP_LOG_WARN, tag, LOG_FORMAT(W, format), esp_log_timestamp(), tag, ##__VA_ARGS__); }\ 278 | else if (level==ESP_LOG_DEBUG ) { esp_log_write(ESP_LOG_DEBUG, tag, LOG_FORMAT(D, format), esp_log_timestamp(), tag, ##__VA_ARGS__); }\ 279 | else if (level==ESP_LOG_VERBOSE ) { esp_log_write(ESP_LOG_VERBOSE, tag, LOG_FORMAT(V, format), esp_log_timestamp(), tag, ##__VA_ARGS__); }\ 280 | else { esp_log_write(ESP_LOG_INFO, tag, LOG_FORMAT(I, format), esp_log_timestamp(), tag, ##__VA_ARGS__); }}while(0) 281 | 282 | /** runtime macro to output logs at a speicfied level. Also check the level with ``LOG_LOCAL_LEVEL``. 283 | * 284 | * @see ``printf``, ``ESP_LOG_LEVEL`` 285 | */ 286 | #define ESP_LOG_LEVEL_LOCAL(level, tag, format, ...) do {\ 287 | if ( LOG_LOCAL_LEVEL >= level ) ESP_LOG_LEVEL(level, tag, format, ##__VA_ARGS__); } while(0); 288 | 289 | #ifdef __cplusplus 290 | } 291 | #endif 292 | 293 | 294 | #endif /* __ESP_LOG_H__ */ 295 | -------------------------------------------------------------------------------- /mkupgimg/.gitignore: -------------------------------------------------------------------------------- 1 | mkupgimg 2 | -------------------------------------------------------------------------------- /mkupgimg/Makefile: -------------------------------------------------------------------------------- 1 | mkupgimg: mkupgimg.c 2 | $(CC) -o $@ $^ 3 | 4 | clean: 5 | rm -f mkupgimg 6 | -------------------------------------------------------------------------------- /mkupgimg/mkupgimg.c: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | /* 15 | Program to combine two OTA images into one combined image. 16 | Only needed for the ESP8266, which has a separately compiled 17 | image for each OTA region. The ESP32 can run the same OTA image 18 | independent of the flash position it is in, so it does not 19 | need multiple OTA images to be combined into one. 20 | */ 21 | 22 | //Cygwin e.a. needs O_BINARY. Don't miscompile if it's not set. 23 | #ifndef O_BINARY 24 | #define O_BINARY 0 25 | #endif 26 | 27 | typedef struct __attribute__((packed)) { 28 | char magic[4]; 29 | char tag[28]; 30 | int32_t len1; 31 | int32_t len2; 32 | } Header; 33 | 34 | 35 | int openFile(char *file) { 36 | int r=open(file, O_RDONLY|O_BINARY); 37 | if (r<=0) { 38 | perror(file); 39 | exit(1); 40 | } 41 | return r; 42 | } 43 | 44 | int32_t intToEsp(int32_t v) { 45 | int32_t ret; 46 | char *p=(char*)&ret; 47 | *p++=(v>>0)&0xff; 48 | *p++=(v>>8)&0xff; 49 | *p++=(v>>16)&0xff; 50 | *p++=(v>>24)&0xff; 51 | return ret; 52 | } 53 | 54 | size_t fileLen(int f) { 55 | size_t r; 56 | r=lseek(f, 0, SEEK_END); 57 | lseek(f, 0, SEEK_SET); 58 | return r; 59 | } 60 | 61 | 62 | int main(int argc, char **argv) { 63 | int u1, u2; 64 | size_t l1, l2; 65 | int of; 66 | char *fc1, *fc2; 67 | Header hdr; 68 | if (argc!=5) { 69 | printf("Usage: %s user1.bin user2.bin tagname outfile.bin\n", argv[0]); 70 | exit(1); 71 | } 72 | if (strlen(argv[3])>27) { 73 | printf("Error: Tag can't be longer than 27 characters.\n"); 74 | exit(1); 75 | } 76 | memset(&hdr, 0, sizeof(hdr)); 77 | memcpy(hdr.magic, "EHUG", 4); 78 | strcpy(hdr.tag, argv[3]); 79 | u1=openFile(argv[1]); 80 | u2=openFile(argv[2]); 81 | l1=fileLen(u1); 82 | l2=fileLen(u2); 83 | hdr.len1=intToEsp(l1); 84 | hdr.len2=intToEsp(l2); 85 | fc1=malloc(l1); 86 | fc2=malloc(l2); 87 | if (read(u1, fc1, l1)!=l1) { 88 | perror(argv[1]); 89 | exit(1); 90 | } 91 | if (read(u2, fc2, l2)!=l2) { 92 | perror(argv[2]); 93 | exit(1); 94 | } 95 | close(u1); 96 | close(u2); 97 | of=open(argv[4], O_WRONLY|O_CREAT|O_TRUNC|O_BINARY, 0666); 98 | if (of<=0) { 99 | perror(argv[4]); 100 | exit(1); 101 | } 102 | write(of, &hdr, sizeof(hdr)); 103 | write(of, fc1, l1); 104 | write(of, fc2, l2); 105 | printf("Header: %d bytes, user1: %d bytes, user2: %d bytes.\n", sizeof(hdr), (int)l1, (int)l2); 106 | close(of); 107 | exit(0); 108 | } 109 | 110 | -------------------------------------------------------------------------------- /standalone/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.2.2) 2 | 3 | include(CheckCCompilerFlag) 4 | 5 | function(enable_c_compiler_flag_if_supported flag) 6 | string(FIND "${CMAKE_C_FLAGS}" "${flag}" flag_already_set) 7 | if(flag_already_set EQUAL -1) 8 | check_c_compiler_flag("${flag}" flag_supported) 9 | if(flag_supported) 10 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${flag}" PARENT_SCOPE) 11 | endif() 12 | endif() 13 | endfunction() 14 | 15 | enable_c_compiler_flag_if_supported("-Wall") 16 | enable_c_compiler_flag_if_supported("-Werror") 17 | 18 | # Enable when heatshink fallthrough bug is fixed, see https://github.com/atomicobject/heatshrink/pull/46 19 | #enable_c_compiler_flag_if_supported("-Wextra") 20 | 21 | 22 | add_library(esphttpd 23 | ../core/auth.c 24 | ../core/libesphttpd_base64.c 25 | ../core/httpd-espfs.c 26 | ../core/httpd.c 27 | ../core/httpd-freertos.c 28 | ../core/sha1.c 29 | ../core/linux/esp_log.c 30 | ../util/cgiwebsocket.c 31 | ../util/cgiredirect.c 32 | ) 33 | 34 | set(ENABLE_SSL_SUPPORT 1) 35 | 36 | if(ENABLE_SSL_SUPPORT) 37 | target_compile_definitions(esphttpd PUBLIC "CONFIG_ESPHTTPD_SSL_SUPPORT=1") 38 | endif() 39 | 40 | target_compile_definitions(esphttpd PUBLIC "CONFIG_LOG_DEFAULT_LEVEL=ESP_LOG_INFO") 41 | 42 | target_compile_definitions(esphttpd PUBLIC "CONFIG_ESPHTTPD_SO_REUSEADDR") 43 | target_compile_definitions(esphttpd PUBLIC "CONFIG_ESPHTTPD_SHUTDOWN_SUPPORT") 44 | 45 | target_include_directories(esphttpd PUBLIC "../core") 46 | target_include_directories(esphttpd PUBLIC "../include") 47 | target_include_directories(esphttpd PUBLIC "../include/linux") 48 | 49 | if(ENABLE_SSL_SUPPORT) 50 | find_package(OpenSSL REQUIRED) 51 | target_link_libraries(esphttpd ${OPENSSL_LIBRARIES}) 52 | include_directories(${OPENSSL_INCLUDE_DIRS}) 53 | endif() 54 | 55 | install(TARGETS esphttpd DESTINATION lib) 56 | install(FILES ../include/libesphttpd/httpd.h DESTINATION include/libesphttpd) 57 | install(FILES ../include/libesphttpd/httpd-freertos.h DESTINATION include/libesphttpd) 58 | install(FILES ../include/libesphttpd/cgiwebsocket.h DESTINATION include/libesphttpd) 59 | install(FILES ../include/libesphttpd/cgiredirect.h DESTINATION include/libesphttpd) 60 | install(FILES ../include/libesphttpd/httpdespfs.h DESTINATION include/libesphttpd) 61 | install(FILES ../include/libesphttpd/linux.h DESTINATION include/libesphttpd) 62 | install(FILES ../include/libesphttpd/platform.h DESTINATION include/libesphttpd) 63 | install(FILES ../include/libesphttpd/route.h DESTINATION include/libesphttpd) 64 | install(FILES ../include/libesphttpd/espfs.h DESTINATION include/libesphttpd) 65 | install(FILES ../include/libesphttpd/webpages-espfs.h DESTINATION include/libesphttpd) 66 | install(FILES ../include/libesphttpd/esp.h DESTINATION include/libesphttpd) 67 | -------------------------------------------------------------------------------- /util/captdns.c: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | 6 | /* 7 | This is a 'captive portal' DNS server: it basically replies with a fixed IP (in this case: 8 | the one of the SoftAP interface of this ESP module) for any and all DNS queries. This can 9 | be used to send mobile phones, tablets etc which connect to the ESP in AP mode directly to 10 | the internal webserver. 11 | */ 12 | 13 | #include 14 | #include "esp_log.h" 15 | 16 | static const char* TAG = "captdns"; 17 | 18 | #ifdef FREERTOS 19 | 20 | #ifdef ESP32 21 | #include "freertos/FreeRTOS.h" 22 | #include "freertos/task.h" 23 | #include "freertos/queue.h" 24 | #include "esp_netif.h" 25 | #else 26 | #include "FreeRTOS.h" 27 | #include "task.h" 28 | #include "queue.h" 29 | #endif 30 | 31 | 32 | #include "lwip/sockets.h" 33 | #include "lwip/err.h" 34 | static int sockFd; 35 | #endif 36 | 37 | 38 | #define DNS_LEN 512 39 | 40 | typedef struct __attribute__ ((packed)) { 41 | uint16_t id; 42 | uint8_t flags; 43 | uint8_t rcode; 44 | uint16_t qdcount; 45 | uint16_t ancount; 46 | uint16_t nscount; 47 | uint16_t arcount; 48 | } DnsHeader; 49 | 50 | 51 | typedef struct __attribute__ ((packed)) { 52 | uint8_t len; 53 | uint8_t data; 54 | } DnsLabel; 55 | 56 | 57 | typedef struct __attribute__ ((packed)) { 58 | //before: label 59 | uint16_t type; 60 | uint16_t class; 61 | } DnsQuestionFooter; 62 | 63 | 64 | typedef struct __attribute__ ((packed)) { 65 | //before: label 66 | uint16_t type; 67 | uint16_t class; 68 | uint32_t ttl; 69 | uint16_t rdlength; 70 | //after: rdata 71 | } DnsResourceFooter; 72 | 73 | typedef struct __attribute__ ((packed)) { 74 | uint16_t prio; 75 | uint16_t weight; 76 | } DnsUriHdr; 77 | 78 | 79 | #define FLAG_QR (1<<7) 80 | #define FLAG_AA (1<<2) 81 | #define FLAG_TC (1<<1) 82 | #define FLAG_RD (1<<0) 83 | 84 | #define QTYPE_A 1 85 | #define QTYPE_NS 2 86 | #define QTYPE_CNAME 5 87 | #define QTYPE_SOA 6 88 | #define QTYPE_WKS 11 89 | #define QTYPE_PTR 12 90 | #define QTYPE_HINFO 13 91 | #define QTYPE_MINFO 14 92 | #define QTYPE_MX 15 93 | #define QTYPE_TXT 16 94 | #define QTYPE_URI 256 95 | 96 | #define QCLASS_IN 1 97 | #define QCLASS_ANY 255 98 | #define QCLASS_URI 256 99 | 100 | 101 | //Function to put unaligned 16-bit network values 102 | static void ICACHE_FLASH_ATTR setn16(void *pp, int16_t n) { 103 | char *p=pp; 104 | *p++=(n>>8); 105 | *p++=(n&0xff); 106 | } 107 | 108 | //Function to put unaligned 32-bit network values 109 | static void ICACHE_FLASH_ATTR setn32(void *pp, int32_t n) { 110 | char *p=pp; 111 | *p++=(n>>24)&0xff; 112 | *p++=(n>>16)&0xff; 113 | *p++=(n>>8)&0xff; 114 | *p++=(n&0xff); 115 | } 116 | 117 | //Parses a label into a C-string containing a dotted 118 | //Returns pointer to start of next fields in packet 119 | static char* ICACHE_FLASH_ATTR labelToStr(char *packet, char *labelPtr, int packetSz, char *res, int resMaxLen) { 120 | int i, j, k; 121 | char *endPtr=NULL; 122 | i=0; 123 | do { 124 | if ((*labelPtr&0xC0)==0) { 125 | j=*labelPtr++; //skip past length 126 | //Add separator period if there already is data in res 127 | if (ipacketSz) return NULL; 131 | if (ipacketSz) return NULL; 139 | labelPtr=&packet[offset]; 140 | } 141 | //check for out-of-bound-ness 142 | if ((labelPtr-packet)>packetSz) return NULL; 143 | } while (*labelPtr!=0); 144 | res[i]=0; //zero-terminate 145 | if (endPtr==NULL) endPtr=labelPtr+1; 146 | return endPtr; 147 | } 148 | 149 | 150 | //Converts a dotted hostname to the weird label form dns uses. 151 | static char ICACHE_FLASH_ATTR *strToLabel(char *str, char *label, int maxLen) { 152 | char *len=label; //ptr to len byte 153 | char *p=label+1; //ptr to next label byte to be written 154 | while (1) { 155 | if (*str=='.' || *str==0) { 156 | *len=((p-len)-1); //write len of label bit 157 | len=p; //pos of len for next part 158 | p++; //data ptr is one past len 159 | if (*str==0) break; //done 160 | str++; 161 | } else { 162 | *p++=*str++; //copy byte 163 | // if ((p-label)>maxLen) return NULL; //check out of bounds 164 | } 165 | } 166 | *len=0; 167 | return p; //ptr to first free byte in resp 168 | } 169 | 170 | 171 | //Receive a DNS packet and maybe send a response back 172 | static char buff[DNS_LEN]; 173 | static char reply[DNS_LEN]; 174 | 175 | #ifndef FREERTOS 176 | static void ICACHE_FLASH_ATTR captdnsRecv(void* arg, char *pusrdata, unsigned short length) { 177 | struct espconn *conn=(struct espconn *)arg; 178 | #else 179 | static void ICACHE_FLASH_ATTR captdnsRecv(struct sockaddr_in *premote_addr, char *pusrdata, unsigned short length) { 180 | #endif 181 | int i; 182 | char *rend=&reply[length]; 183 | char *p=pusrdata; 184 | DnsHeader *hdr=(DnsHeader*)p; 185 | DnsHeader *rhdr=(DnsHeader*)&reply[0]; 186 | p+=sizeof(DnsHeader); 187 | ESP_LOGD(TAG, "DNS packet: id 0x%X flags 0x%X rcode 0x%X qcnt %d ancnt %d nscount %d arcount %d len %d", 188 | ntohs(hdr->id), hdr->flags, hdr->rcode, ntohs(hdr->qdcount), ntohs(hdr->ancount), ntohs(hdr->nscount), ntohs(hdr->arcount), length); 189 | //Some sanity checks: 190 | if (length>DNS_LEN) // Packet is longer than DNS implementation allows 191 | { 192 | ESP_LOGD(TAG, "Packet length %d longer than DNS_LEN(%d)", length, DNS_LEN); 193 | return; 194 | } 195 | if (lengtharcount is non-zero as these could be extension 201 | // records, but we don't process them 202 | if (hdr->ancount || hdr->nscount) //this is a reply, don't know what to do with it 203 | { 204 | ESP_LOGD(TAG, "Ignoring reply"); 205 | return; 206 | } 207 | 208 | if (hdr->flags&FLAG_TC) //truncated, can't use this 209 | { 210 | ESP_LOGD(TAG, "Message truncated"); 211 | return; 212 | } 213 | 214 | //Reply is basically the request plus the needed data 215 | memcpy(reply, pusrdata, length); 216 | rhdr->flags|=FLAG_QR; 217 | for (i=0; iqdcount); i++) { 218 | //Grab the labels in the q string 219 | p=labelToStr(pusrdata, p, length, buff, sizeof(buff)); 220 | if (p==NULL) return; 221 | DnsQuestionFooter *qf=(DnsQuestionFooter*)p; 222 | p+=sizeof(DnsQuestionFooter); 223 | ESP_LOGD(TAG, "DNS: Q (type 0x%X class 0x%X) for %s", ntohs(qf->type), ntohs(qf->class), buff); 224 | if (ntohs(qf->type)==QTYPE_A) { 225 | //They want to know the IPv4 address of something. 226 | //Build the response. 227 | rend=strToLabel(buff, rend, sizeof(reply)-(rend-reply)); //Add the label 228 | if (rend==NULL) return; 229 | DnsResourceFooter *rf=(DnsResourceFooter *)rend; 230 | rend+=sizeof(DnsResourceFooter); 231 | setn16(&rf->type, QTYPE_A); 232 | setn16(&rf->class, QCLASS_IN); 233 | setn32(&rf->ttl, 0); 234 | setn16(&rf->rdlength, 4); //IPv4 addr is 4 bytes; 235 | //Grab the current IP of the softap interface 236 | #ifdef ESP32 237 | esp_netif_ip_info_t info; 238 | esp_netif_get_ip_info(esp_netif_get_handle_from_ifkey("WIFI_AP_DEF"), &info); 239 | #else 240 | struct ip_info info; 241 | wifi_get_ip_info(SOFTAP_IF, &info); 242 | #endif 243 | *rend++=ip4_addr1(&info.ip); 244 | *rend++=ip4_addr2(&info.ip); 245 | *rend++=ip4_addr3(&info.ip); 246 | *rend++=ip4_addr4(&info.ip); 247 | setn16(&rhdr->ancount, ntohs(rhdr->ancount)+1); 248 | ESP_LOGD(TAG, "Added A rec to resp. Resp len is %d", (rend-reply)); 249 | } else if (ntohs(qf->type)==QTYPE_NS) { 250 | //Give ns server. Basically can be whatever we want because it'll get resolved to our IP later anyway. 251 | rend=strToLabel(buff, rend, sizeof(reply)-(rend-reply)); //Add the label 252 | DnsResourceFooter *rf=(DnsResourceFooter *)rend; 253 | rend+=sizeof(DnsResourceFooter); 254 | setn16(&rf->type, QTYPE_NS); 255 | setn16(&rf->class, QCLASS_IN); 256 | setn16(&rf->ttl, 0); 257 | setn16(&rf->rdlength, 4); 258 | *rend++=2; 259 | *rend++='n'; 260 | *rend++='s'; 261 | *rend++=0; 262 | setn16(&rhdr->ancount, ntohs(rhdr->ancount)+1); 263 | ESP_LOGD(TAG, "Added NS rec to resp. Resp len is %d", (rend-reply)); 264 | } else if (ntohs(qf->type)==QTYPE_URI) { 265 | //Give uri to us 266 | rend=strToLabel(buff, rend, sizeof(reply)-(rend-reply)); //Add the label 267 | DnsResourceFooter *rf=(DnsResourceFooter *)rend; 268 | rend+=sizeof(DnsResourceFooter); 269 | DnsUriHdr *uh=(DnsUriHdr *)rend; 270 | rend+=sizeof(DnsUriHdr); 271 | setn16(&rf->type, QTYPE_URI); 272 | setn16(&rf->class, QCLASS_URI); 273 | setn16(&rf->ttl, 0); 274 | setn16(&rf->rdlength, 4+16); 275 | setn16(&uh->prio, 10); 276 | setn16(&uh->weight, 1); 277 | memcpy(rend, "http://esp.nonet", 16); 278 | rend+=16; 279 | setn16(&rhdr->ancount, ntohs(rhdr->ancount)+1); 280 | ESP_LOGD(TAG, "Added NS rec to resp. Resp len is %d", (rend-reply)); 281 | } 282 | } 283 | //Send the response 284 | #ifndef FREERTOS 285 | remot_info *remInfo=NULL; 286 | //Send data to port/ip it came from, not to the ip/port we listen on. 287 | if (espconn_get_connection_info(conn, &remInfo, 0)==ESPCONN_OK) { 288 | conn->proto.udp->remote_port=remInfo->remote_port; 289 | memcpy(conn->proto.udp->remote_ip, remInfo->remote_ip, sizeof(remInfo->remote_ip)); 290 | } 291 | espconn_sendto(conn, (uint8*)reply, rend-reply); 292 | #else 293 | sendto(sockFd,(uint8*)reply, rend-reply, 0, (struct sockaddr *)premote_addr, sizeof(struct sockaddr_in)); 294 | #endif 295 | } 296 | 297 | #ifdef FREERTOS 298 | char udp_msg[DNS_LEN]; 299 | static void captdnsTask(void *pvParameters) { 300 | struct sockaddr_in server_addr; 301 | int32 ret; 302 | struct sockaddr_in from; 303 | socklen_t fromlen; 304 | 305 | memset(&server_addr, 0, sizeof(server_addr)); 306 | server_addr.sin_family = AF_INET; 307 | server_addr.sin_addr.s_addr = INADDR_ANY; 308 | server_addr.sin_port = htons(53); 309 | server_addr.sin_len = sizeof(server_addr); 310 | 311 | do { 312 | sockFd=socket(AF_INET, SOCK_DGRAM, 0); 313 | if (sockFd==-1) { 314 | ESP_LOGE(TAG, "captdns_task failed to create sock"); 315 | vTaskDelay(1000/portTICK_RATE_MS); 316 | } 317 | } while (sockFd==-1); 318 | 319 | do { 320 | ret=bind(sockFd, (struct sockaddr *)&server_addr, sizeof(server_addr)); 321 | if (ret!=0) { 322 | ESP_LOGE(TAG, "captdns_task failed to bind sock"); 323 | vTaskDelay(1000/portTICK_RATE_MS); 324 | } 325 | } while (ret!=0); 326 | 327 | ESP_LOGI(TAG, "CaptDNS inited"); 328 | while(1) { 329 | memset(&from, 0, sizeof(from)); 330 | fromlen=sizeof(struct sockaddr_in); 331 | ret=recvfrom(sockFd, (uint8_t *)udp_msg, DNS_LEN, 0,(struct sockaddr *)&from,(socklen_t *)&fromlen); 332 | if (ret>0) captdnsRecv(&from,udp_msg,ret); 333 | } 334 | 335 | close(sockFd); 336 | vTaskDelete(NULL); 337 | } 338 | 339 | void captdnsInit(void) { 340 | #ifdef ESP32 341 | xTaskCreate(captdnsTask, (const char *)"captdns_task", 3000, NULL, 3, NULL); 342 | #else 343 | xTaskCreate(captdnsTask, (const signed char *)"captdns_task", 1200, NULL, 3, NULL); 344 | #endif 345 | } 346 | 347 | #else 348 | 349 | void ICACHE_FLASH_ATTR captdnsInit(void) { 350 | static struct espconn conn; 351 | static esp_udp udpconn; 352 | conn.type=ESPCONN_UDP; 353 | conn.proto.udp=&udpconn; 354 | conn.proto.udp->local_port = 53; 355 | espconn_regist_recvcb(&conn, captdnsRecv); 356 | espconn_create(&conn); 357 | } 358 | 359 | #endif 360 | -------------------------------------------------------------------------------- /util/cgi_common.c: -------------------------------------------------------------------------------- 1 | #include 2 | // #define LOG_LOCAL_LEVEL ESP_LOG_DEBUG 3 | #include "esp_log.h" 4 | #include 5 | #include "libesphttpd/httpd.h" 6 | #include "libesphttpd/cgi_common.h" 7 | 8 | #define SENDBUFSIZE (1024) 9 | #define MAGICNUM (0x12345678) // A magic number to validate the state object. The value doesn't really matter, as long as it's unlikely to occur elsewhere. 10 | 11 | typedef struct 12 | { 13 | void *toFree; // keep a pointer to the beginning for free() when finished 14 | const char *toSendPosition; 15 | int len_to_send; 16 | uint32_t magic; 17 | } cgiResp_state_t; 18 | 19 | /** 20 | * @brief Common routine for parsing GET or POST parameters. 21 | * Parses *allArgs (i.e. connData->getArgs or connData->post.buff) for a value 22 | * by name of *argName and then converts the found string value to the requested binary format (sscanf format string). 23 | * (Must supply a buffer and then string value will be available on return.) 24 | * 25 | * @param allArgs String to search in i.e. connData->getArgs or connData->post.buff 26 | * @param argName Name of argument to find 27 | * @param format sscanf format string 28 | * @param pvalue return value parsed 29 | * @param signd Should the return value be treated as signed? 30 | * @param buff Supply a buffer to parse the value found. The unparsed value will be available here upon return if found. 31 | * @param buffLen Length of supplied buffer 32 | * @return int 0: arg not found, 1: arg found and parsed, -1: found arg but error parsing value 33 | */ 34 | static int cgiGetArgCommon(const char *allArgs, const char *argName, const char *format, void *pvalue, bool signd, char *buff, int buffLen) 35 | { 36 | int retVal = 0; 37 | buff[0] = 0; // null terminate empty string 38 | int len = httpdFindArg(allArgs, argName, buff, buffLen); 39 | if (len > 0) 40 | { 41 | int n = 0; 42 | char ch; // dummy to test for malformed input 43 | if (signd) 44 | { 45 | n = sscanf(buff, format, (int32_t *)pvalue, &ch); 46 | } 47 | else 48 | { 49 | n = sscanf(buff, format, (uint32_t *)pvalue, &ch); 50 | } 51 | if (n == 1) 52 | { 53 | /* sscanf found a number to convert */ 54 | retVal = 1; 55 | } 56 | else 57 | { 58 | retVal = -1; // error parsing number 59 | } 60 | } 61 | return retVal; 62 | } 63 | 64 | /** 65 | * @brief Parses *allArgs (i.e. connData->getArgs or connData->post.buff) for a signed integer by name of *argName and returns int value at *pvalue. 66 | * 67 | * @param allArgs String to search in i.e. connData->getArgs or connData->post.buff 68 | * @param argName Name of argument to find 69 | * @param pvalue return value parsed 70 | * @param buff Supply a buffer to parse the value found. The unparsed value will be available here upon return if found. 71 | * @param buffLen Length of supplied buffer 72 | * @return int 0: arg not found, 1: arg found and parsed, -1: found arg but error parsing value 73 | */ 74 | int cgiGetArgDecS32(const char *allArgs, const char *argName, int *pvalue, char *buff, int buffLen) 75 | { 76 | return cgiGetArgCommon(allArgs, argName, "%" PRId32 "%c", (void *)pvalue, true, buff, buffLen); 77 | } 78 | 79 | /** 80 | * @brief Parses *allArgs (i.e. connData->getArgs or connData->post.buff) for a unsigned int (i.e. ?uintval=123) 81 | * 82 | * @param allArgs String to search in i.e. connData->getArgs or connData->post.buff 83 | * @param argName Name of argument to find 84 | * @param pvalue return value parsed 85 | * @param buff Supply a buffer to parse the value found. The unparsed value will be available here upon return if found. 86 | * @param buffLen Length of supplied buffer 87 | * @return int 0: arg not found, 1: arg found and parsed, -1: found arg but error parsing value 88 | */ 89 | int cgiGetArgDecU32(const char *allArgs, const char *argName, uint32_t *pvalue, char *buff, int buffLen) 90 | { 91 | return cgiGetArgCommon(allArgs, argName, "%" PRIu32 "%c", (void *)pvalue, false, buff, buffLen); 92 | } 93 | 94 | /** 95 | * @brief Parses *allArgs (i.e. connData->getArgs or connData->post.buff) for a uint32_t from a hexadecimal string (i.e. ?hexval=0123ABCD ) 96 | * 97 | * @param allArgs String to search in i.e. connData->getArgs or connData->post.buff 98 | * @param argName Name of argument to find 99 | * @param pvalue return value parsed 100 | * @param buff Supply a buffer to parse the value found. The unparsed value will be available here upon return if found. 101 | * @param buffLen Length of supplied buffer 102 | * @return int 0: arg not found, 1: arg found and parsed, -1: found arg but error parsing value 103 | */ 104 | int cgiGetArgHexU32(const char *allArgs, const char *argName, uint32_t *pvalue, char *buff, int buffLen) 105 | { 106 | return cgiGetArgCommon(allArgs, argName, "%" PRIx32 "%c", (void *)pvalue, false, buff, buffLen); 107 | } 108 | 109 | /** 110 | * @brief Parses *allArgs (i.e. connData->getArgs or connData->post.buff) for a string value. (just a wrapper for httpdFindArg()) 111 | * 112 | * @param allArgs String to search in i.e. connData->getArgs or connData->post.buff 113 | * @param argName Name of argument to find 114 | * @param buff Supply a buffer to copy the value found. 115 | * @param buffLen Length of supplied buffer. 116 | * @return int 0: arg not found, 1: arg found 117 | */ 118 | int cgiGetArgString(const char *allArgs, const char *argName, char *buff, int buffLen) 119 | { 120 | int retVal = 0; 121 | int len = httpdFindArg(allArgs, argName, buff, buffLen); 122 | if (len > 0) 123 | { 124 | retVal = 1; 125 | } 126 | return retVal; 127 | } 128 | 129 | void cgiJsonResponseHeaders(HttpdConnData *connData) 130 | { 131 | //// Generate the header 132 | // We want the header to start with HTTP code 200, which means the document is found. 133 | httpdStartResponse(connData, 200); 134 | httpdHeader(connData, "Cache-Control", "no-store, must-revalidate, no-cache, max-age=0"); 135 | httpdHeader(connData, "Expires", "Mon, 01 Jan 1990 00:00:00 GMT"); // This one might be redundant, since modern browsers look for "Cache-Control". 136 | httpdHeader(connData, "Content-Type", "application/json; charset=utf-8"); // We are going to send some JSON. 137 | httpdEndHeaders(connData); 138 | } 139 | 140 | void cgiJavascriptResponseHeaders(HttpdConnData *connData) 141 | { 142 | //// Generate the header 143 | // We want the header to start with HTTP code 200, which means the document is found. 144 | httpdStartResponse(connData, 200); 145 | httpdHeader(connData, "Cache-Control", "no-store, must-revalidate, no-cache, max-age=0"); 146 | httpdHeader(connData, "Expires", "Mon, 01 Jan 1990 00:00:00 GMT"); // This one might be redundant, since modern browsers look for "Cache-Control". 147 | httpdHeader(connData, "Content-Type", "application/javascript; charset=utf-8"); // We are going to send a file as javascript. 148 | httpdEndHeaders(connData); 149 | } 150 | 151 | CgiStatus cgiResponseCommonMultiCleanup(void **statepp) 152 | { 153 | cgiResp_state_t *statep = NULL; // statep is local pointer to state 154 | if (statepp != NULL) 155 | { 156 | statep = *(cgiResp_state_t **)statepp; // dereference statepp to get statep 157 | if (statep) 158 | { 159 | if (statep->toFree) 160 | { 161 | ESP_LOGD(__func__, "freeing"); 162 | free(statep->toFree); 163 | } 164 | free(statep); // clear state 165 | } 166 | *statepp = NULL; // clear external pointer 167 | } 168 | return HTTPD_CGI_DONE; 169 | } 170 | /** 171 | * @brief Send a string that is longer than can fit in a single call to httpdSend(). The string is freed when done. 172 | * 173 | * @param connData HttpdConnData 174 | * @param statepp Opaque pointer-pointer state 175 | * @param toSendAndFree String buffer. Note this function will call free() on this string when finished! 176 | * @return CgiStatus HTTPD_CGI_DONE: no need to call again. HTTPD_CGI_MORE: Need to call this again to finish sending. 177 | */ 178 | CgiStatus cgiResponseCommonMulti(HttpdConnData *connData, void **statepp, char *toSendAndFree) 179 | { 180 | cgiResp_state_t *statep = NULL; // statep is local pointer to state 181 | if (statepp != NULL) // If statepp is passed in (from previous call), set local pointer. 182 | { 183 | statep = *(cgiResp_state_t **)statepp; // dereference statepp to get statep 184 | } 185 | 186 | if (statep == NULL) // first call? 187 | { 188 | // statep is NULL, need to alloc memory for the state 189 | statep = calloc(1, sizeof(cgiResp_state_t)); // all members init to 0 190 | if (statep == NULL) 191 | { 192 | ESP_LOGE(__func__, "calloc failed!"); 193 | return HTTPD_CGI_DONE; 194 | } 195 | statep->magic = MAGICNUM; // write a magic number to the state to validate it later 196 | if (statepp != NULL) // caller passed in pointer to statep? 197 | { 198 | *statepp = statep; // set external pointer for later 199 | } 200 | 201 | statep->toFree = toSendAndFree; 202 | statep->toSendPosition = toSendAndFree; 203 | 204 | if (statep->toSendPosition) 205 | { 206 | statep->len_to_send = strlen(statep->toSendPosition); 207 | } 208 | ESP_LOGD(__func__, "tosendtotal: %d", statep->len_to_send); 209 | } 210 | 211 | if (statep->magic != MAGICNUM) // check state magic number. 212 | { 213 | // The state object which was referenced by statepp is invalid or corrupted! 214 | // The most likely reason for the magic mismatch is a programming mistake in the calling function. 215 | // Ensure statepp is preserved between calls to this function. 216 | ESP_LOGE(__func__, "state invalid!"); 217 | // Probably not safe to free memory, so just shout error and return. 218 | return HTTPD_CGI_DONE; 219 | } 220 | 221 | if (statep->len_to_send > 0) 222 | { 223 | size_t max_send_size = SENDBUFSIZE; 224 | size_t len_to_send_this_time = (statep->len_to_send < max_send_size) ? (statep->len_to_send) : (max_send_size); 225 | ESP_LOGD(__func__, "tosendthistime: %d", len_to_send_this_time); 226 | int success = httpdSend(connData, statep->toSendPosition, len_to_send_this_time); 227 | if (success) 228 | { 229 | statep->len_to_send -= len_to_send_this_time; 230 | statep->toSendPosition += len_to_send_this_time; 231 | } 232 | else 233 | { 234 | ESP_LOGE(__func__, "httpdSend out-of-memory"); 235 | statep->len_to_send = 0; 236 | } 237 | } 238 | 239 | // if called without state pointer (single send) 240 | if (NULL == statepp) 241 | { 242 | cgiResponseCommonMultiCleanup(statep); 243 | return HTTPD_CGI_DONE; 244 | } 245 | // else if finished sending 246 | if (statep->len_to_send <= 0) 247 | { 248 | cgiResponseCommonMultiCleanup(statepp); 249 | return HTTPD_CGI_DONE; 250 | } 251 | // else still sending 252 | return HTTPD_CGI_MORE; 253 | } 254 | 255 | /** 256 | * @brief Send a multipart json response (i.e. larger than 1kB). The json object is freed when done. 257 | * 258 | * @param connData HttpdConnData 259 | * @param statepp Opaque pointer-pointer state 260 | * @param jsroot cJSON object to send 261 | * @return CgiStatus HTTPD_CGI_DONE: no need to call again. HTTPD_CGI_MORE: Need to call this again to finish sending. 262 | * 263 | * @example 264 | CgiStatus cgiFn(HttpdConnData *connData) 265 | { 266 | if (connData->isConnectionClosed) 267 | { 268 | // Connection aborted. Clean up. 269 | cgiResponseCommonMultiCleanup(&connData->cgiData); 270 | return HTTPD_CGI_DONE; 271 | } 272 | cJSON *jsroot = NULL; 273 | if (connData->cgiData == NULL) 274 | { 275 | //First call to this cgi. 276 | jsroot = cJSON_CreateObject(); 277 | ... 278 | cgiJsonResponseHeaders(connData); 279 | } 280 | return cgiJsonResponseCommonMulti(connData, &connData->cgiData, jsroot); // Send the json response! 281 | } 282 | */ 283 | CgiStatus cgiJsonResponseCommonMulti(HttpdConnData *connData, void **statepp, cJSON *jsroot) 284 | { 285 | // This wrapper for cgiResponseCommonMulti() doesn't need it's own state. 286 | // We can determine if this is the first call by testing **statepp 287 | 288 | char *stringToSend = NULL; 289 | if (statepp == NULL || *statepp == NULL) // First call? 290 | { 291 | stringToSend = cJSON_PrintUnformatted(jsroot); 292 | cJSON_Delete(jsroot); // we're done with the json object, now that it is stringified 293 | } 294 | 295 | return cgiResponseCommonMulti(connData, statepp, stringToSend); // will free stringToSend when done 296 | } 297 | 298 | CgiStatus cgiJsonResponseCommonSingle(HttpdConnData *connData, cJSON *jsroot) 299 | { 300 | cgiJsonResponseHeaders(connData); 301 | return cgiJsonResponseCommonMulti(connData, NULL, jsroot); 302 | } 303 | 304 | CgiStatus cgiJavascriptResponseCommon(HttpdConnData *connData, cJSON *jsroot, const char *jsObjName) 305 | { 306 | char sendbuff[SENDBUFSIZE]; 307 | int sblen = 0; 308 | sendbuff[0] = 0; // null terminate empty string 309 | 310 | cgiJavascriptResponseHeaders(connData); 311 | sblen += snprintf(sendbuff + sblen, SENDBUFSIZE - sblen, "var %s = ", jsObjName); 312 | httpdSend(connData, sendbuff, sblen); 313 | 314 | return cgiJsonResponseCommonMulti(connData, NULL, jsroot); 315 | } 316 | -------------------------------------------------------------------------------- /util/cgiredirect.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "esp_log.h" 5 | 6 | const static char* TAG = "cgiredirect"; 7 | 8 | 9 | //Use this as a cgi function to redirect one url to another. 10 | CgiStatus ICACHE_FLASH_ATTR cgiRedirect(HttpdConnData *connData) { 11 | if (connData->isConnectionClosed) { 12 | //Connection aborted. Clean up. 13 | return HTTPD_CGI_DONE; 14 | } 15 | httpdRedirect(connData, (char*)connData->cgiArg); 16 | return HTTPD_CGI_DONE; 17 | } 18 | 19 | CgiStatus ICACHE_FLASH_ATTR cgiRedirectToHostname(HttpdConnData *connData) { 20 | static const char hostFmt[]="http://%s/"; 21 | char *buff; 22 | int isIP=0; 23 | int x; 24 | if (connData->isConnectionClosed) { 25 | // Connection closed. 26 | return HTTPD_CGI_DONE; 27 | } 28 | if (connData->hostName==NULL) { 29 | return HTTPD_CGI_NOTFOUND; 30 | } 31 | 32 | //Quick and dirty code to see if host is an IP 33 | if (strlen(connData->hostName)>8) 34 | { 35 | isIP=1; 36 | for (x=0; xhostName); x++) { 37 | if (connData->hostName[x]!='.' && (connData->hostName[x]<'0' || connData->hostName[x]>'9')) isIP=0; 38 | } 39 | } 40 | 41 | if (isIP) 42 | { 43 | return HTTPD_CGI_NOTFOUND; 44 | } 45 | 46 | //Check hostname; pass on if the same 47 | if (strcasecmp(connData->hostName, (char*)connData->cgiArg)==0) 48 | { 49 | ESP_LOGD(TAG, "connData->hostName:'%s', redirect hostname: '%s'", connData->hostName, 50 | (char*)connData->cgiArg); 51 | return HTTPD_CGI_NOTFOUND; 52 | } 53 | 54 | //Not the same. Redirect to real hostname. 55 | buff = malloc(strlen((char*)connData->cgiArg)+sizeof(hostFmt)); 56 | if (buff==NULL) { 57 | ESP_LOGE(TAG, "allocating memory"); 58 | //Bail out 59 | return HTTPD_CGI_DONE; 60 | } 61 | sprintf(buff, hostFmt, (char*)connData->cgiArg); 62 | ESP_LOGD(TAG, "Redirecting to hostname url %s", buff); 63 | httpdRedirect(connData, buff); 64 | free(buff); 65 | return HTTPD_CGI_DONE; 66 | } 67 | 68 | 69 | //Same as above, but will only redirect clients with an IP that is in the range of 70 | //the SoftAP interface. This should preclude clients connected to the STA interface 71 | //to be redirected to nowhere. 72 | CgiStatus ICACHE_FLASH_ATTR cgiRedirectApClientToHostname(HttpdConnData *connData) { 73 | #ifdef linux 74 | return HTTPD_CGI_NOTFOUND; 75 | #else 76 | #ifndef FREERTOS 77 | uint32 *remadr; 78 | struct ip_info apip; 79 | int x=wifi_get_opmode(); 80 | //Check if we have an softap interface; bail out if not 81 | if (x!=2 && x!=3) return HTTPD_CGI_NOTFOUND; 82 | remadr=(uint32 *)connData->remote_ip; 83 | wifi_get_ip_info(SOFTAP_IF, &apip); 84 | if ((*remadr & apip.netmask.addr) == (apip.ip.addr & apip.netmask.addr)) { 85 | return cgiRedirectToHostname(connData); 86 | } else { 87 | return HTTPD_CGI_NOTFOUND; 88 | } 89 | #else 90 | return HTTPD_CGI_NOTFOUND; 91 | #endif 92 | #endif 93 | } 94 | -------------------------------------------------------------------------------- /util/cgiwebsocket.c: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | /* 6 | Websocket support for esphttpd. Inspired by https://github.com/dangrie158/ESP-8266-WebSocket 7 | */ 8 | 9 | #ifdef linux 10 | #include 11 | #else 12 | #include 13 | #include "freertos/FreeRTOS.h" 14 | #include "freertos/semphr.h" 15 | #endif 16 | 17 | #include "libesphttpd/httpd.h" 18 | #include "httpd-platform.h" 19 | #include "libesphttpd/sha1.h" 20 | #include "libesphttpd_base64.h" 21 | #include "libesphttpd/cgiwebsocket.h" 22 | 23 | //#define LOG_LOCAL_LEVEL ESP_LOG_DEBUG 24 | #include "esp_log.h" 25 | const static char* TAG = "cgiwebsocket"; 26 | 27 | #define WS_KEY_IDENTIFIER "Sec-WebSocket-Key: " 28 | #define WS_GUID "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" 29 | 30 | /* from IEEE RFC6455 sec 5.2 31 | 0 1 2 3 32 | 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 33 | +-+-+-+-+-------+-+-------------+-------------------------------+ 34 | |F|R|R|R| opcode|M| Payload len | Extended payload length | 35 | |I|S|S|S| (4) |A| (7) | (16/64) | 36 | |N|V|V|V| |S| | (if payload len==126/127) | 37 | | |1|2|3| |K| | | 38 | +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - + 39 | | Extended payload length continued, if payload len == 127 | 40 | + - - - - - - - - - - - - - - - +-------------------------------+ 41 | | |Masking-key, if MASK set to 1 | 42 | +-------------------------------+-------------------------------+ 43 | | Masking-key (continued) | Payload Data | 44 | +-------------------------------- - - - - - - - - - - - - - - - + 45 | : Payload Data continued ... : 46 | + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + 47 | | Payload Data continued ... | 48 | +---------------------------------------------------------------+ 49 | */ 50 | 51 | #define FLAG_FIN (1 << 7) 52 | 53 | #define OPCODE_CONTINUE 0x0 54 | #define OPCODE_TEXT 0x1 55 | #define OPCODE_BINARY 0x2 56 | #define OPCODE_CLOSE 0x8 57 | #define OPCODE_PING 0x9 58 | #define OPCODE_PONG 0xA 59 | 60 | #define FLAGS_MASK ((uint8_t)0xF0) 61 | #define OPCODE_MASK ((uint8_t)0x0F) 62 | #define IS_MASKED ((uint8_t)(1<<7)) 63 | #define PAYLOAD_MASK ((uint8_t)0x7F) 64 | 65 | typedef struct WebsockFrame WebsockFrame; 66 | 67 | #define ST_FLAGS 0 68 | #define ST_LEN0 1 69 | #define ST_LEN1 2 70 | #define ST_LEN2 3 71 | //... 72 | #define ST_LEN8 9 73 | #define ST_MASK1 10 74 | #define ST_MASK4 13 75 | #define ST_PAYLOAD 14 76 | 77 | struct WebsockFrame { 78 | uint8_t flags; 79 | uint8_t len8; 80 | uint64_t len; 81 | uint8_t mask[4]; 82 | }; 83 | 84 | struct WebsockPriv { 85 | struct WebsockFrame fr; 86 | uint8_t maskCtr; 87 | uint8 frameCont; 88 | int wsStatus; 89 | }; 90 | 91 | 92 | #define WEBSOCK_LIST_SIZE (32) 93 | // List of active Websockets (TODO: not support multiple httpd instances yet) 94 | static Websock *wsList[WEBSOCK_LIST_SIZE] = {NULL}; 95 | // Recursive mutex to protect list 96 | static SemaphoreHandle_t wsListMutex=NULL; 97 | 98 | // Lock access to list 99 | static bool ICACHE_FLASH_ATTR wsListLock(void) 100 | { 101 | if (wsListMutex == NULL) { 102 | wsListMutex = xSemaphoreCreateRecursiveMutex(); 103 | } 104 | assert(wsListMutex != NULL); 105 | ESP_LOGD(TAG, "Lock list. Task: %p", xTaskGetCurrentTaskHandle()); 106 | // Take the mutex recursively 107 | if (pdTRUE != xSemaphoreTakeRecursive(wsListMutex, portMAX_DELAY)) { 108 | ESP_LOGE(TAG, "Failed to take mutex"); 109 | return false; 110 | } 111 | return true; 112 | } 113 | 114 | // Unlock list access 115 | static void ICACHE_FLASH_ATTR wsListUnlock(void) 116 | { 117 | assert(wsListMutex != NULL); 118 | ESP_LOGD(TAG, "Unlock list. Task: %p", xTaskGetCurrentTaskHandle()); 119 | // Give the mutex recursively 120 | xSemaphoreGiveRecursive(wsListMutex); 121 | } 122 | 123 | static bool check_websock_closed(Websock *ws) { 124 | return ((NULL == ws->conn) || (ws->conn->isConnectionClosed)); 125 | } 126 | 127 | /* Free data, must only be called by kref_put()! */ 128 | static void ICACHE_FLASH_ATTR free_websock(struct kref *ref) 129 | { 130 | ESP_LOGD(TAG, "websockFree"); 131 | Websock *ws; 132 | 133 | ws = kcontainer_of(ref, Websock, ref_cnt); 134 | if (ws->priv) free(ws->priv); 135 | // Note we don't free ws->conn, since it is managed by the httpd instance. 136 | free(ws); 137 | } 138 | 139 | /* Drop a reference to a Websock, possibly freeing it. */ 140 | static void put_websock(Websock *ws) 141 | { 142 | assert(ws != NULL); 143 | kref_put(&(ws->ref_cnt), free_websock); 144 | } 145 | 146 | /* Get a reference counted pointer to a Websock object by index. *\ 147 | \* Must be released via put_websock()! */ 148 | static struct Websock *get_websock(unsigned int index) 149 | { 150 | Websock *retWs = NULL; 151 | if ((index < WEBSOCK_LIST_SIZE) && 152 | (NULL != wsList[index])) // do an early NULL check to avoid locking an empty slot 153 | { 154 | if (wsListLock()) 155 | { 156 | if (wsList[index] != NULL) // double check for NULL now that we have the lock 157 | { 158 | // check if the websocket is closed and remove it from the list 159 | // (Optional to do it here, since it is also done in wsListAddAndGC) 160 | if (check_websock_closed(wsList[index])) { 161 | ESP_LOGD(TAG, "Cleaning up websocket %p", wsList[index]); 162 | put_websock(wsList[index]); // decrement reference count for the ref in the list 163 | wsList[index] = NULL; // remove from list 164 | // will return NULL to indicate closed 165 | } else { 166 | // ws is still open 167 | retWs = wsList[index]; 168 | // increment reference count, since we will be retuning a pointer to it 169 | kref_get(&(retWs->ref_cnt)); 170 | } 171 | } 172 | wsListUnlock(); 173 | } 174 | } 175 | return retWs; 176 | } 177 | 178 | // Add websocket to list, and garbage collect dead websockets 179 | static void ICACHE_FLASH_ATTR wsListAddAndGC(Websock *ws) 180 | { 181 | ESP_LOGD(TAG, "Adding to list. Task: %p", xTaskGetCurrentTaskHandle()); 182 | if (wsListLock()) // protect access to list, since it could be used/modified outside this thread 183 | { 184 | // Garbage collection 185 | for (int i = 0; i < WEBSOCK_LIST_SIZE; i++) { 186 | if (wsList[i] != NULL) { 187 | if (check_websock_closed(wsList[i])) { 188 | ESP_LOGD(TAG, "Cleaning up websocket %p", wsList[i]); 189 | put_websock(wsList[i]); // decrement reference count 190 | wsList[i] = NULL; 191 | } 192 | } 193 | } 194 | // Insert ws into list 195 | int j = 0; 196 | // Find first empty slot 197 | for (; j < WEBSOCK_LIST_SIZE; j++) { 198 | // Check if slot is empty 199 | if (wsList[j] == NULL) { 200 | ESP_LOGD(TAG, "Adding websocket %p to slot %d", ws, j); 201 | kref_get(&(ws->ref_cnt)); // increment reference count 202 | wsList[j] = ws; 203 | break; 204 | } 205 | } 206 | if (j == WEBSOCK_LIST_SIZE) { 207 | ESP_LOGE(TAG, "Websocket list full"); 208 | } 209 | wsListUnlock(); 210 | } 211 | } 212 | 213 | static int ICACHE_FLASH_ATTR sendFrameHead(Websock *ws, int opcode, int len) { 214 | char buf[14]; 215 | int i=0; 216 | buf[i++]=opcode; 217 | if (len>65535) { 218 | buf[i++]=127; 219 | buf[i++]=0; buf[i++]=0; buf[i++]=0; buf[i++]=0; 220 | buf[i++]=len>>24; 221 | buf[i++]=len>>16; 222 | buf[i++]=len>>8; 223 | buf[i++]=len; 224 | } else if (len>125) { 225 | buf[i++]=126; 226 | buf[i++]=len>>8; 227 | buf[i++]=len; 228 | } else { 229 | buf[i++]=len; 230 | } 231 | ESP_LOGD(TAG, "Sent frame head for payload of %d bytes", len); 232 | return httpdSend(ws->conn, buf, i); 233 | } 234 | 235 | int ICACHE_FLASH_ATTR cgiWebsocketSend(HttpdInstance *pInstance, Websock *ws, const char *data, int len, int flags) { 236 | int r=0; 237 | int fl=0; 238 | 239 | // Continuation frame has opcode 0 240 | if (!(flags&WEBSOCK_FLAG_CONT)) { 241 | if (flags & WEBSOCK_FLAG_BIN) 242 | fl = OPCODE_BINARY; 243 | else 244 | fl = OPCODE_TEXT; 245 | } 246 | // add FIN to last frame 247 | if (!(flags&WEBSOCK_FLAG_MORE)) fl|=FLAG_FIN; 248 | 249 | httpdPlatLock(pInstance); 250 | if (check_websock_closed(ws)) { 251 | ESP_LOGE(TAG, "Websocket closed, cannot send"); 252 | httpdPlatUnlock(pInstance); 253 | return WEBSOCK_CLOSED; 254 | } 255 | sendFrameHead(ws, fl, len); 256 | if (len!=0) r=httpdSend(ws->conn, data, len); 257 | httpdFlushSendBuffer(pInstance, ws->conn); 258 | httpdPlatUnlock(pInstance); 259 | return r; 260 | } 261 | 262 | //Broadcast data to all websockets at a specific url. Returns the amount of connections sent to. 263 | int ICACHE_FLASH_ATTR cgiWebsockBroadcast(HttpdInstance *pInstance, const char *resource, const char *data, int len, int flags) { 264 | int ret = 0; 265 | 266 | for (int i = 0; i < WEBSOCK_LIST_SIZE; i++) { 267 | Websock *ws = get_websock(i); // get a reference counted pointer to the Websock object from the list 268 | if (NULL != ws) { 269 | httpdPlatLock(pInstance); // lock needed so we can access the connData to check for routeMatch 270 | if (check_websock_closed(ws)) { 271 | ESP_LOGD(TAG, "Websocket %p closed", ws); 272 | httpdPlatUnlock(pInstance); 273 | continue; 274 | } 275 | // else ws is still open 276 | bool routeMatch = (strcmp(ws->conn->url, resource) == 0); 277 | httpdPlatUnlock(pInstance); 278 | 279 | if (routeMatch) { 280 | cgiWebsocketSend(pInstance, ws, data, len, flags); 281 | ret++; 282 | } 283 | put_websock(ws); // decrement reference count for local copy 284 | } 285 | } 286 | if (ret == 0) { 287 | ESP_LOGD(TAG, "No websockets found for resource %s", resource); 288 | } 289 | else { 290 | ESP_LOGD(TAG, "Broadcasted %d bytes to %d websockets", len, ret); 291 | } 292 | return ret; 293 | } 294 | 295 | 296 | void ICACHE_FLASH_ATTR cgiWebsocketClose(HttpdInstance *pInstance, Websock *ws, int reason) { 297 | char rs[2]={reason>>8, reason&0xff}; 298 | httpdPlatLock(pInstance); 299 | if (ws->conn != NULL) { 300 | sendFrameHead(ws, FLAG_FIN|OPCODE_CLOSE, 2); 301 | httpdSend(ws->conn, rs, 2); 302 | httpdFlushSendBuffer(pInstance, ws->conn); 303 | ws->conn = NULL; // mark as closed for shared references 304 | if (ws->closeCb) ws->closeCb(ws); 305 | } // else already closed 306 | httpdPlatUnlock(pInstance); 307 | } 308 | 309 | CgiStatus ICACHE_FLASH_ATTR cgiWebSocketRecv(HttpdInstance *pInstance, HttpdConnData *connData, char *data, int len) { 310 | int i, j, sl; 311 | int r=HTTPD_CGI_MORE; 312 | int wasHeaderByte; 313 | Websock *ws=(Websock*)connData->cgiData; 314 | assert(ws != NULL); 315 | kref_get(&(ws->ref_cnt)); // increment reference count 316 | for (i=0; ipriv->wsStatus, data[i]); 318 | wasHeaderByte=1; 319 | if (ws->priv->wsStatus==ST_FLAGS) { 320 | ws->priv->maskCtr=0; 321 | ws->priv->frameCont=0; 322 | ws->priv->fr.flags=(uint8_t)data[i]; 323 | ws->priv->wsStatus=ST_LEN0; 324 | } else if (ws->priv->wsStatus==ST_LEN0) { 325 | ws->priv->fr.len8=(uint8_t)data[i]; 326 | if ((ws->priv->fr.len8&127)>=126) { 327 | ws->priv->fr.len=0; 328 | ws->priv->wsStatus=ST_LEN1; 329 | } else { 330 | ws->priv->fr.len=ws->priv->fr.len8&127; 331 | ws->priv->wsStatus=(ws->priv->fr.len8&IS_MASKED)?ST_MASK1:ST_PAYLOAD; 332 | } 333 | } else if (ws->priv->wsStatus<=ST_LEN8) { 334 | ws->priv->fr.len=(ws->priv->fr.len<<8)|data[i]; 335 | if (((ws->priv->fr.len8&127)==126 && ws->priv->wsStatus==ST_LEN2) || ws->priv->wsStatus==ST_LEN8) { 336 | ws->priv->wsStatus=(ws->priv->fr.len8&IS_MASKED)?ST_MASK1:ST_PAYLOAD; 337 | } else { 338 | ws->priv->wsStatus++; 339 | } 340 | } else if (ws->priv->wsStatus<=ST_MASK4) { 341 | ws->priv->fr.mask[ws->priv->wsStatus-ST_MASK1]=data[i]; 342 | ws->priv->wsStatus++; 343 | } else { 344 | //Was a payload byte. 345 | wasHeaderByte=0; 346 | } 347 | 348 | if (ws->priv->wsStatus==ST_PAYLOAD && wasHeaderByte) { 349 | //We finished parsing the header, but i still is on the last header byte. Move one forward so 350 | //the payload code works as usual. 351 | i++; 352 | } 353 | //Also finish parsing frame if we haven't received any payload bytes yet, but the length of the frame 354 | //is zero. 355 | if (ws->priv->wsStatus==ST_PAYLOAD) { 356 | //Okay, header is in; this is a data byte. We're going to process all the data bytes we have 357 | //received here at the same time; no more byte iterations till the end of this frame. 358 | //First, unmask the data 359 | sl=len-i; 360 | ESP_LOGD(TAG, "Frame payload. wasHeaderByte %d fr.len %d sl %d cmd 0x%x", wasHeaderByte, (int)ws->priv->fr.len, (int)sl, ws->priv->fr.flags); 361 | if (sl > ws->priv->fr.len) sl=ws->priv->fr.len; 362 | for (j=0; jpriv->fr.mask[(ws->priv->maskCtr++)&3]); 363 | 364 | // httpd_printf("Unmasked: "); 365 | // for (j=0; jpriv->fr.flags&OPCODE_MASK)==OPCODE_PING) { 370 | if (ws->priv->fr.len>125) { 371 | if (!ws->priv->frameCont) cgiWebsocketClose(pInstance, ws, 1002); 372 | r=HTTPD_CGI_DONE; 373 | break; 374 | } else { 375 | if (!ws->priv->frameCont) sendFrameHead(ws, OPCODE_PONG|FLAG_FIN, ws->priv->fr.len); 376 | if (sl>0) httpdSend(ws->conn, data+i, sl); 377 | } 378 | } else if ((ws->priv->fr.flags&OPCODE_MASK)==OPCODE_TEXT || 379 | (ws->priv->fr.flags&OPCODE_MASK)==OPCODE_BINARY || 380 | (ws->priv->fr.flags&OPCODE_MASK)==OPCODE_CONTINUE) { 381 | if (sl>ws->priv->fr.len) sl=ws->priv->fr.len; 382 | if (!(ws->priv->fr.len8&IS_MASKED)) { 383 | //We're a server; client should send us masked packets. 384 | cgiWebsocketClose(pInstance, ws, 1002); 385 | r=HTTPD_CGI_DONE; 386 | break; 387 | } else { 388 | int flags=0; 389 | if ((ws->priv->fr.flags&OPCODE_MASK)==OPCODE_BINARY) flags|=WEBSOCK_FLAG_BIN; 390 | if ((ws->priv->fr.flags&FLAG_FIN)==0) flags|=WEBSOCK_FLAG_MORE; 391 | if (ws->recvCb) ws->recvCb(ws, data+i, sl, flags); 392 | } 393 | } else if ((ws->priv->fr.flags&OPCODE_MASK)==OPCODE_CLOSE) { 394 | ESP_LOGD(TAG, "Got close frame"); 395 | cgiWebsocketClose(pInstance, ws, ((data[i]<<8)&0xff00)+(data[i+1]&0xff)); 396 | r=HTTPD_CGI_DONE; 397 | break; 398 | } else { 399 | if (!ws->priv->frameCont) ESP_LOGE(TAG, "Unknown opcode 0x%X", ws->priv->fr.flags&OPCODE_MASK); 400 | } 401 | i+=sl-1; 402 | ws->priv->fr.len-=sl; 403 | if (ws->priv->fr.len==0) { 404 | ws->priv->wsStatus=ST_FLAGS; //go receive next frame 405 | } else { 406 | ws->priv->frameCont=1; //next payload is continuation of this frame. 407 | } 408 | } 409 | } 410 | if (r==HTTPD_CGI_DONE) { 411 | //We're going to tell the main webserver we're done. The webserver expects us to clean up by ourselves 412 | //we're chosing to be done. Do so. 413 | put_websock((Websock*)connData->cgiData); // drop reference for connData->cgiData 414 | connData->cgiData=NULL; 415 | } 416 | put_websock(ws); // drop reference for local ws, possibly free it if no one else is using it 417 | return r; 418 | } 419 | 420 | //Websocket 'cgi' implementation 421 | CgiStatus ICACHE_FLASH_ATTR cgiWebsocket(HttpdConnData *connData) { 422 | char buff[256]; 423 | int i; 424 | sha1nfo s; 425 | if (connData->isConnectionClosed) { 426 | //Connection aborted. Clean up. 427 | ESP_LOGD(TAG, "Cleanup"); 428 | if (connData->cgiData) { 429 | ((Websock*)connData->cgiData)->conn = NULL; // mark as closed for shared references 430 | put_websock((Websock*)connData->cgiData); // drop reference for connData->cgiData 431 | connData->cgiData=NULL; 432 | } 433 | return HTTPD_CGI_DONE; 434 | } 435 | 436 | if (connData->cgiData==NULL) { 437 | ESP_LOGV(TAG, "WS: First call"); 438 | //First call here. Check if client headers are OK, send server header. 439 | i=httpdGetHeader(connData, "Upgrade", buff, sizeof(buff)-1); 440 | ESP_LOGD(TAG, "Upgrade: %s", buff); 441 | if (i && strcasecmp(buff, "websocket")==0) { 442 | i=httpdGetHeader(connData, "Sec-WebSocket-Key", buff, sizeof(buff)-1); 443 | if (i) { 444 | ESP_LOGV(TAG, "WS: Key: %s", buff); 445 | //Seems like a WebSocket connection. 446 | // Alloc structs 447 | Websock *ws = calloc(1, sizeof(Websock)); 448 | if (ws == NULL) { 449 | ESP_LOGE(TAG, "Can't allocate mem for websocket"); 450 | return HTTPD_CGI_DONE; 451 | } 452 | kref_init(&(ws->ref_cnt)); // initialises ref_cnt to 1 453 | ws->priv = calloc(1, sizeof(WebsockPriv)); 454 | if (ws->priv==NULL) { 455 | ESP_LOGE(TAG, "Can't allocate mem for websocket priv"); 456 | if (ws != NULL) 457 | { 458 | put_websock(ws); // drop reference to local ws 459 | } 460 | return HTTPD_CGI_DONE; 461 | } 462 | 463 | // Store a reference to the connData, but note that ws doesn't own it. 464 | // We have to be careful using it because it can be freed by the httpd instance. 465 | ws->conn=connData; 466 | //Reply with the right headers. 467 | strcat(buff, WS_GUID); 468 | sha1_init(&s); 469 | sha1_write(&s, buff, strlen(buff)); 470 | httpdSetTransferMode(connData, HTTPD_TRANSFER_NONE); 471 | httpdStartResponse(connData, 101); 472 | httpdHeader(connData, "Upgrade", "websocket"); 473 | httpdHeader(connData, "Connection", "upgrade"); 474 | libesphttpd_base64_encode(20, sha1_result(&s), sizeof(buff), buff); 475 | httpdHeader(connData, "Sec-WebSocket-Accept", buff); 476 | httpdEndHeaders(connData); 477 | //Set data receive handler 478 | connData->recvHdl=cgiWebSocketRecv; 479 | //Inform CGI function we have a connection 480 | WsConnectedCb connCb=connData->cgiArg; 481 | connCb(ws); 482 | 483 | // Add a reference count, since it is stored on connData->cgiData until the connection is closed 484 | kref_get(&(ws->ref_cnt)); 485 | connData->cgiData = ws; 486 | 487 | wsListAddAndGC(ws); // add to list, will add another reference count 488 | 489 | put_websock(ws); // drop reference to local ws 490 | return HTTPD_CGI_MORE; 491 | } 492 | } 493 | //No valid websocket connection 494 | httpdStartResponse(connData, 500); 495 | httpdEndHeaders(connData); 496 | return HTTPD_CGI_DONE; 497 | } 498 | 499 | //Sending is done. Call the sent callback if we have one. 500 | Websock *ws=(Websock*)connData->cgiData; 501 | if (ws && ws->sentCb) ws->sentCb(ws); 502 | 503 | return HTTPD_CGI_MORE; 504 | } 505 | -------------------------------------------------------------------------------- /util/cgiwifi.c: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | /* 6 | Cgi/template routines for the /wifi url. 7 | */ 8 | 9 | 10 | #include 11 | #include "libesphttpd/cgiwifi.h" 12 | 13 | #if !defined(ESP32) 14 | 15 | #define SSID_SIZE 32 16 | #define BSSID_SIZE 6 17 | 18 | //Enable this to disallow any changes in AP settings 19 | //#define DEMO_MODE 20 | 21 | //WiFi access point data 22 | typedef struct { 23 | char ssid[SSID_SIZE + 1]; 24 | char bssid[BSSID_SIZE]; 25 | int channel; 26 | char rssi; 27 | char enc; 28 | } ApData; 29 | 30 | //Scan result 31 | typedef struct { 32 | char scanInProgress; //if 1, don't access the underlying stuff from the webpage. 33 | ApData **apData; 34 | int noAps; 35 | } ScanResultData; 36 | 37 | //Static scan status storage. 38 | static ScanResultData cgiWifiAps; 39 | 40 | 41 | 42 | #define CONNTRY_IDLE 0 43 | #define CONNTRY_WORKING 1 44 | #define CONNTRY_SUCCESS 2 45 | #define CONNTRY_FAIL 3 46 | //Connection result var 47 | static int connTryStatus=CONNTRY_IDLE; 48 | static os_timer_t resetTimer; 49 | 50 | //Callback the code calls when a wlan ap scan is done. Basically stores the result in 51 | //the cgiWifiAps struct. 52 | void ICACHE_FLASH_ATTR wifiScanDoneCb(void *arg, STATUS status) { 53 | int n; 54 | struct bss_info *bss_link = (struct bss_info *)arg; 55 | httpd_printf("wifiScanDoneCb %d\n", status); 56 | if (status!=OK) { 57 | cgiWifiAps.scanInProgress=0; 58 | return; 59 | } 60 | 61 | //Clear prev ap data if needed. 62 | if (cgiWifiAps.apData!=NULL) { 63 | for (n=0; nnext.stqe_next; 71 | n++; 72 | } 73 | //Allocate memory for access point data 74 | cgiWifiAps.apData=(ApData **)malloc(sizeof(ApData *)*n); 75 | if (cgiWifiAps.apData==NULL) { 76 | printf("Out of memory allocating apData\n"); 77 | return; 78 | } 79 | cgiWifiAps.noAps=n; 80 | httpd_printf("Scan done: found %d APs\n", n); 81 | 82 | //Copy access point data to the static struct 83 | n=0; 84 | bss_link = (struct bss_info *)arg; 85 | while (bss_link != NULL) { 86 | if (n>=cgiWifiAps.noAps) { 87 | //This means the bss_link changed under our nose. Shouldn't happen! 88 | //Break because otherwise we will write in unallocated memory. 89 | httpd_printf("Huh? I have more than the allocated %d aps!\n", cgiWifiAps.noAps); 90 | break; 91 | } 92 | //Save the ap data. 93 | cgiWifiAps.apData[n]=(ApData *)malloc(sizeof(ApData)); 94 | if (cgiWifiAps.apData[n]==NULL) { 95 | httpd_printf("Can't allocate mem for ap buff.\n"); 96 | cgiWifiAps.scanInProgress=0; 97 | return; 98 | } 99 | cgiWifiAps.apData[n]->rssi=bss_link->rssi; 100 | cgiWifiAps.apData[n]->channel=bss_link->channel; 101 | cgiWifiAps.apData[n]->enc=bss_link->authmode; 102 | strncpy(cgiWifiAps.apData[n]->ssid, (char*)bss_link->ssid, 32); 103 | strncpy(cgiWifiAps.apData[n]->bssid, (char*)bss_link->bssid, 6); 104 | 105 | bss_link = bss_link->next.stqe_next; 106 | n++; 107 | } 108 | //We're done. 109 | cgiWifiAps.scanInProgress=0; 110 | } 111 | 112 | //Routine to start a WiFi access point scan. 113 | static void ICACHE_FLASH_ATTR wifiStartScan() { 114 | if (cgiWifiAps.scanInProgress) return; 115 | cgiWifiAps.scanInProgress=1; 116 | wifi_station_scan(NULL, wifiScanDoneCb); 117 | } 118 | 119 | //This CGI is called from the bit of AJAX-code in wifi.tpl. It will initiate a 120 | //scan for access points and if available will return the result of an earlier scan. 121 | //The result is embedded in a bit of JSON parsed by the javascript in wifi.tpl. 122 | CgiStatus ICACHE_FLASH_ATTR cgiWiFiScan(HttpdConnData *connData) { 123 | int pos=(int)connData->cgiData; 124 | int len; 125 | char buff[1024]; 126 | 127 | if (!cgiWifiAps.scanInProgress && pos!=0) { 128 | //Fill in json code for an access point 129 | if (pos-1ssid, MAC2STR(cgiWifiAps.apData[pos-1]->bssid), cgiWifiAps.apData[pos-1]->rssi, 132 | cgiWifiAps.apData[pos-1]->enc, cgiWifiAps.apData[pos-1]->channel, (pos-1==cgiWifiAps.noAps-1)?"":","); 133 | httpdSend(connData, buff, len); 134 | } 135 | pos++; 136 | if ((pos-1)>=cgiWifiAps.noAps) { 137 | len=sprintf(buff, "]\n}\n}\n"); 138 | httpdSend(connData, buff, len); 139 | //Also start a new scan. 140 | wifiStartScan(); 141 | return HTTPD_CGI_DONE; 142 | } else { 143 | connData->cgiData=(void*)pos; 144 | return HTTPD_CGI_MORE; 145 | } 146 | } 147 | 148 | httpdStartResponse(connData, 200); 149 | httpdHeader(connData, "Content-Type", "text/json"); 150 | httpdEndHeaders(connData); 151 | 152 | if (cgiWifiAps.scanInProgress==1) { 153 | //We're still scanning. Tell Javascript code that. 154 | len=sprintf(buff, "{\n \"result\": { \n\"inProgress\": \"1\"\n }\n}\n"); 155 | httpdSend(connData, buff, len); 156 | return HTTPD_CGI_DONE; 157 | } else { 158 | //We have a scan result. Pass it on. 159 | len=sprintf(buff, "{\n \"result\": { \n\"inProgress\": \"0\", \n\"APs\": [\n"); 160 | httpdSend(connData, buff, len); 161 | if (cgiWifiAps.apData==NULL) cgiWifiAps.noAps=0; 162 | connData->cgiData=(void *)1; 163 | return HTTPD_CGI_MORE; 164 | } 165 | } 166 | 167 | //Temp store for new ap info. 168 | static struct station_config stconf; 169 | 170 | //This routine is ran some time after a connection attempt to an access point. If 171 | //the connect succeeds, this gets the module in STA-only mode. 172 | static void ICACHE_FLASH_ATTR resetTimerCb(void *arg) { 173 | int x=wifi_station_get_connect_status(); 174 | if (x==STATION_GOT_IP) { 175 | //Go to STA mode. This needs a reset, so do that. 176 | httpd_printf("Got IP. Going into STA mode..\n"); 177 | wifi_set_opmode(1); 178 | system_restart(); 179 | } else { 180 | connTryStatus=CONNTRY_FAIL; 181 | httpd_printf("Connect fail. Not going into STA-only mode.\n"); 182 | //Maybe also pass this through on the webpage? 183 | } 184 | } 185 | 186 | 187 | 188 | //Actually connect to a station. This routine is timed because I had problems 189 | //with immediate connections earlier. It probably was something else that caused it, 190 | //but I can't be arsed to put the code back :P 191 | static void ICACHE_FLASH_ATTR reassTimerCb(void *arg) { 192 | int x; 193 | httpd_printf("Try to connect to AP....\n"); 194 | wifi_station_disconnect(); 195 | wifi_station_set_config(&stconf); 196 | wifi_station_connect(); 197 | x=wifi_get_opmode(); 198 | connTryStatus=CONNTRY_WORKING; 199 | if (x!=1) { 200 | //Schedule disconnect/connect 201 | os_timer_disarm(&resetTimer); 202 | os_timer_setfn(&resetTimer, resetTimerCb, NULL); 203 | os_timer_arm(&resetTimer, 15000, 0); //time out after 15 secs of trying to connect 204 | } 205 | } 206 | 207 | //This cgi uses the routines above to connect to a specific access point with the 208 | //given ESSID using the given password. 209 | CgiStatus ICACHE_FLASH_ATTR cgiWiFiConnect(HttpdConnData *connData) { 210 | char essid[128]; 211 | char passwd[128]; 212 | static os_timer_t reassTimer; 213 | if (connData->isConnectionClosed) { 214 | //Connection aborted. Clean up. 215 | return HTTPD_CGI_DONE; 216 | } 217 | 218 | httpdFindArg(connData->post.buff, "essid", essid, sizeof(essid)); 219 | httpdFindArg(connData->post.buff, "passwd", passwd, sizeof(passwd)); 220 | strncpy((char*)stconf.ssid, essid, 32); 221 | strncpy((char*)stconf.password, passwd, 64); 222 | 223 | //Schedule disconnect/connect 224 | os_timer_disarm(&reassTimer); 225 | os_timer_setfn(&reassTimer, reassTimerCb, NULL); 226 | //Set to 0 if you want to disable the actual reconnecting bit 227 | connTryStatus = CONNTRY_WORKING; 228 | #ifdef DEMO_MODE 229 | httpdRedirect(connData, "/wifi"); 230 | #else 231 | os_timer_arm(&reassTimer, 500, 0); 232 | httpdRedirect(connData, "connecting.html"); 233 | #endif 234 | 235 | return HTTPD_CGI_DONE; 236 | } 237 | 238 | //This cgi uses the routines above to connect to a specific access point with the 239 | //given ESSID using the given password. 240 | CgiStatus ICACHE_FLASH_ATTR cgiWiFiSetMode(HttpdConnData *connData) { 241 | int len; 242 | char buff[1024]; 243 | 244 | if (connData->isConnectionClosed) { 245 | //Connection aborted. Clean up. 246 | return HTTPD_CGI_DONE; 247 | } 248 | 249 | len=httpdFindArg(connData->getArgs, "mode", buff, sizeof(buff)); 250 | if (len!=0) { 251 | #ifndef DEMO_MODE 252 | wifi_set_opmode(atoi(buff)); 253 | system_restart(); 254 | #endif 255 | } 256 | httpdRedirect(connData, "/wifi"); 257 | return HTTPD_CGI_DONE; 258 | } 259 | 260 | //Set wifi channel for AP mode 261 | CgiStatus ICACHE_FLASH_ATTR cgiWiFiSetChannel(HttpdConnData *connData) { 262 | 263 | int len; 264 | char buff[64]; 265 | 266 | if (connData->isConnectionClosed) { 267 | //Connection aborted. Clean up. 268 | return HTTPD_CGI_DONE; 269 | } 270 | 271 | len=httpdFindArg(connData->getArgs, "ch", buff, sizeof(buff)); 272 | if (len!=0) { 273 | int channel = atoi(buff); 274 | if (channel > 0 && channel < 15) { 275 | httpd_printf("cgiWifiSetChannel: %s\n", buff); 276 | struct softap_config wificfg; 277 | wifi_softap_get_config(&wificfg); 278 | wificfg.channel = (uint8)channel; 279 | wifi_softap_set_config(&wificfg); 280 | } 281 | } 282 | httpdRedirect(connData, "/wifi"); 283 | 284 | 285 | return HTTPD_CGI_DONE; 286 | } 287 | 288 | CgiStatus ICACHE_FLASH_ATTR cgiWiFiConnStatus(HttpdConnData *connData) { 289 | char buff[1024]; 290 | int len; 291 | struct ip_info info; 292 | int st = wifi_station_get_connect_status(); 293 | httpdStartResponse(connData, 200); 294 | httpdHeader(connData, "Content-Type", "text/json"); 295 | httpdEndHeaders(connData); 296 | if (connTryStatus==CONNTRY_IDLE) { 297 | len=sprintf(buff, "{\n \"status\": \"idle\"\n }\n"); 298 | } else if (connTryStatus==CONNTRY_WORKING || connTryStatus==CONNTRY_SUCCESS) { 299 | if (st==STATION_GOT_IP) { 300 | wifi_get_ip_info(0, &info); 301 | len=sprintf(buff, "{\n \"status\": \"success\",\n \"ip\": \"%d.%d.%d.%d\" }\n", 302 | (info.ip.addr>>0)&0xff, (info.ip.addr>>8)&0xff, 303 | (info.ip.addr>>16)&0xff, (info.ip.addr>>24)&0xff); 304 | //Reset into AP-only mode sooner. 305 | os_timer_disarm(&resetTimer); 306 | os_timer_setfn(&resetTimer, resetTimerCb, NULL); 307 | os_timer_arm(&resetTimer, 1000, 0); 308 | } else { 309 | len=sprintf(buff, "{\n \"status\": \"working\"\n }\n"); 310 | } 311 | } else { 312 | len=sprintf(buff, "{\n \"status\": \"fail\"\n }\n"); 313 | } 314 | 315 | httpdSend(connData, buff, len); 316 | return HTTPD_CGI_DONE; 317 | } 318 | 319 | //Template code for the WLAN page. 320 | CgiStatus ICACHE_FLASH_ATTR tplWlan(HttpdConnData *connData, char *token, void **arg) { 321 | char buff[1024]; 322 | if (token==NULL) return HTTPD_CGI_DONE; 323 | int x; 324 | static struct station_config stconf; 325 | wifi_station_get_config(&stconf); 326 | strcpy(buff, "Unknown"); 327 | if (strcmp(token, "WiFiMode")==0) { 328 | x=wifi_get_opmode(); 329 | if (x==1) strcpy(buff, "Client"); 330 | if (x==2) strcpy(buff, "SoftAP"); 331 | if (x==3) strcpy(buff, "STA+AP"); 332 | } else if (strcmp(token, "currSsid")==0) { 333 | strcpy(buff, (char*)stconf.ssid); 334 | } else if (strcmp(token, "WiFiPasswd")==0) { 335 | strcpy(buff, (char*)stconf.password); 336 | } else if (strcmp(token, "WiFiapwarn")==0) { 337 | x=wifi_get_opmode(); 338 | if (x==2) { 339 | strcpy(buff, "Can't scan in this mode. Click here to go to STA+AP mode."); 340 | } else { 341 | strcpy(buff, "Click here to go to standalone AP mode."); 342 | } 343 | } 344 | httpdSend(connData, buff, -1); 345 | return HTTPD_CGI_DONE; 346 | } 347 | #endif // !defined(ESP32) 348 | -------------------------------------------------------------------------------- /util/esp32_httpd_vfs.c: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 | 5 | /* 6 | Connector to let httpd use the vfs filesystem to serve the files in it. 7 | */ 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include "esp_err.h" 14 | #include "esp_log.h" 15 | #include "libesphttpd/esp.h" 16 | #include "libesphttpd/httpd.h" 17 | #include "httpd-platform.h" 18 | #include "cJSON.h" 19 | #include "libesphttpd/cgi_common.h" 20 | 21 | #define FILE_CHUNK_LEN (1024) 22 | #define MAX_FILENAME_LENGTH (1024) 23 | 24 | #define ESPFS_MAGIC (0x73665345) 25 | #define ESPFS_FLAG_GZIP (1<<1) 26 | 27 | // If the client does not advertise that he accepts GZIP send following warning message (telnet users for e.g.) 28 | static const char *gzipNonSupportedMessage = "HTTP/1.0 501 Not implemented\r\nServer: esp8266-httpd/"HTTPDVER"\r\nConnection: close\r\nContent-Type: text/plain\r\nContent-Length: 52\r\n\r\nYour browser does not accept gzip-compressed data.\r\n"; 29 | 30 | static size_t getFilepath(HttpdConnData *connData, char *filepath, size_t len) 31 | { 32 | struct stat s; 33 | int outlen; 34 | 35 | if (connData->cgiArg != &httpdCgiEx) { 36 | filepath[0] = '\0'; 37 | if (connData->cgiArg != NULL) { 38 | outlen = strlcpy(filepath, connData->cgiArg, len); 39 | if (stat(filepath, &s) == 0 && S_ISREG(s.st_mode)) { 40 | return outlen; 41 | } 42 | } 43 | return strlcat(filepath, connData->url, len); 44 | } 45 | 46 | HttpdCgiExArg *ex = (HttpdCgiExArg *)connData->cgiArg2; 47 | const char *route = connData->route; 48 | char *url = connData->url; 49 | while (*url && *route == *url) { 50 | route++; 51 | url++; 52 | } 53 | 54 | size_t basepathLen = strlen(ex->basepath); 55 | if (!ex->basepath || basepathLen == 0) { 56 | return strlcpy(filepath, url, len); 57 | } 58 | 59 | if (url[0] == '/') { 60 | url++; 61 | } 62 | 63 | outlen = strlcpy(filepath, ex->basepath, len); 64 | if (stat(ex->basepath, &s) != 0 || S_ISDIR(s.st_mode)) { 65 | if (ex->basepath[basepathLen - 1] != '/') { 66 | strlcat(filepath, "/", len); 67 | } 68 | outlen = strlcat(filepath, url, len); 69 | } 70 | return outlen; 71 | } 72 | 73 | CgiStatus ICACHE_FLASH_ATTR cgiEspVfsGet(HttpdConnData *connData) { 74 | FILE *file=connData->cgiData; 75 | int len; 76 | char buff[FILE_CHUNK_LEN]; 77 | char filename[MAX_FILENAME_LENGTH + 1]; 78 | char acceptEncodingBuffer[64]; 79 | int isGzip; 80 | bool isIndex = false; 81 | struct stat filestat; 82 | 83 | if (connData->isConnectionClosed) { 84 | //Connection aborted. Clean up. 85 | if(file != NULL){ 86 | fclose(file); 87 | ESP_LOGD(__func__, "fclose: %s, r", filename); 88 | } 89 | ESP_LOGE(__func__, "Connection aborted!"); 90 | return HTTPD_CGI_DONE; 91 | } 92 | 93 | //First call to this cgi. 94 | if (file==NULL) { 95 | isGzip = 0; 96 | if (connData->requestType!=HTTPD_METHOD_GET) { 97 | return HTTPD_CGI_NOTFOUND; // return and allow another cgi function to handle it 98 | } 99 | 100 | getFilepath(connData, filename, sizeof(filename)); 101 | 102 | if(filename[strlen(filename)-1]=='/') filename[strlen(filename)-1]='\0'; 103 | if(stat(filename, &filestat) == 0) { 104 | if((isIndex = S_ISDIR(filestat.st_mode))) { 105 | strncat(filename, "/index.html", MAX_FILENAME_LENGTH - strlen(filename)); 106 | } 107 | } 108 | 109 | file = fopen(filename, "r"); 110 | if (file != NULL) { 111 | struct stat st = {}; 112 | fstat(fileno(file), &st); 113 | isGzip = (st.st_spare4[0] == ESPFS_MAGIC && st.st_spare4[1] & ESPFS_FLAG_GZIP); 114 | ESP_LOGD(__func__, "fopen: %s, r", filename); 115 | } 116 | 117 | if (file==NULL) { 118 | // Check if requested file is available GZIP compressed ie. with file extension .gz 119 | 120 | strncat(filename, ".gz", MAX_FILENAME_LENGTH - strlen(filename)); 121 | ESP_LOGD(__func__, "GET: GZIPped file - %s", filename); 122 | file = fopen(filename, "r"); 123 | if (file != NULL) ESP_LOGD(__func__, "fopen: %s, r", filename); 124 | isGzip = 1; 125 | 126 | if (file==NULL) { 127 | return HTTPD_CGI_NOTFOUND; 128 | } 129 | 130 | // Check the browser's "Accept-Encoding" header. If the client does not 131 | // advertise that he accepts GZIP send a warning message (telnet users for e.g.) 132 | httpdGetHeader(connData, "Accept-Encoding", acceptEncodingBuffer, 64); 133 | if (strstr(acceptEncodingBuffer, "gzip") == NULL) { 134 | //No Accept-Encoding: gzip header present 135 | httpdSend(connData, gzipNonSupportedMessage, -1); 136 | fclose(file); 137 | ESP_LOGD(__func__, "fclose: %s, r", filename); 138 | ESP_LOGE(__func__, "client does not accept gzip!"); 139 | return HTTPD_CGI_DONE; 140 | } 141 | } 142 | 143 | connData->cgiData=file; 144 | httpdStartResponse(connData, 200); 145 | 146 | const char *mimetype = NULL; 147 | bool sendContentType = false; 148 | bool sentHeaders = false; 149 | 150 | if (connData->cgiArg == &httpdCgiEx) { 151 | HttpdCgiExArg *ex = (HttpdCgiExArg *)connData->cgiArg2; 152 | if (ex->mimetype) { 153 | mimetype = ex->mimetype; 154 | sendContentType = true; 155 | } else if (!ex->headerCb) { 156 | sendContentType = true; 157 | } 158 | } else { 159 | sendContentType = true; 160 | } 161 | 162 | if (sendContentType) { 163 | if (!mimetype) { 164 | mimetype = isIndex ? httpdGetMimetype("index.html") : httpdGetMimetype(connData->url); 165 | } 166 | httpdHeader(connData, "Content-Type", mimetype); 167 | } 168 | 169 | if (isGzip) { 170 | httpdHeader(connData, "Content-Encoding", "gzip"); 171 | } 172 | 173 | if (connData->cgiArg == &httpdCgiEx) { 174 | HttpdCgiExArg *ex = (HttpdCgiExArg *)connData->cgiArg2; 175 | if (ex->headerCb) { 176 | ex->headerCb(connData); 177 | sentHeaders = true; 178 | } 179 | } 180 | 181 | if (mimetype && !sentHeaders) { 182 | httpdAddCacheHeaders(connData, mimetype); 183 | } 184 | httpdEndHeaders(connData); 185 | return HTTPD_CGI_MORE; 186 | } 187 | 188 | len=fread(buff, 1, FILE_CHUNK_LEN, file); 189 | if (len>0) httpdSend(connData, buff, len); 190 | if (len!=FILE_CHUNK_LEN) { 191 | //We're done. 192 | fclose(file); 193 | ESP_LOGD(__func__, "fclose: %s, r", filename); 194 | 195 | return HTTPD_CGI_DONE; 196 | } else { 197 | //Ok, till next time. 198 | return HTTPD_CGI_MORE; 199 | } 200 | } 201 | 202 | typedef struct { 203 | FILE *file; 204 | void *tplArg; 205 | char token[64]; 206 | int tokenPos; 207 | } TplData; 208 | 209 | typedef void (* TplCallback)(HttpdConnData *connData, char *token, void **arg); 210 | 211 | CgiStatus ICACHE_FLASH_ATTR cgiEspVfsTemplate(HttpdConnData *connData) { 212 | TplData *tpd=connData->cgiData; 213 | int len; 214 | int x, sp=0; 215 | char *e=NULL; 216 | char buff[FILE_CHUNK_LEN +1]; 217 | char filename[MAX_FILENAME_LENGTH + 1]; 218 | 219 | 220 | if (connData->isConnectionClosed) { 221 | //Connection aborted. Clean up. 222 | ((TplCallback)(connData->cgiArg))(connData, NULL, &tpd->tplArg); 223 | if(tpd->file != NULL){ 224 | fclose(tpd->file); 225 | } 226 | free(tpd); 227 | return HTTPD_CGI_DONE; 228 | } 229 | 230 | if (tpd==NULL) { 231 | //First call to this cgi. Open the file so we can read it. 232 | struct stat s; 233 | if (stat(filename, &s) != 0 || S_ISDIR(s.st_mode)) { 234 | return HTTPD_CGI_NOTFOUND; 235 | } 236 | 237 | if (s.st_spare4[0] == ESPFS_MAGIC && s.st_spare4[1] & ESPFS_FLAG_GZIP) { 238 | ESP_LOGE(__func__, "Trying to use gzip-compressed file %s as template!", connData->url); 239 | return HTTPD_CGI_NOTFOUND; 240 | } 241 | 242 | getFilepath(connData, filename, sizeof(filename)); 243 | 244 | tpd=(TplData *)malloc(sizeof(TplData)); 245 | if (tpd==NULL) return HTTPD_CGI_NOTFOUND; 246 | tpd->file=fopen(connData->url, "r"); 247 | tpd->tplArg=NULL; 248 | tpd->tokenPos=-1; 249 | if (tpd->file==NULL) { 250 | fclose(tpd->file); 251 | free(tpd); 252 | return HTTPD_CGI_NOTFOUND; 253 | } 254 | 255 | connData->cgiData=tpd; 256 | httpdStartResponse(connData, 200); 257 | 258 | const char *mimetype = NULL; 259 | bool sendContentType = false; 260 | 261 | if (connData->cgiArg == &httpdCgiEx) { 262 | HttpdCgiExArg *ex = (HttpdCgiExArg *)connData->cgiArg2; 263 | if (ex->mimetype) { 264 | mimetype = ex->mimetype; 265 | sendContentType = true; 266 | } else if (!ex->headerCb) { 267 | sendContentType = true; 268 | } 269 | } else { 270 | sendContentType = true; 271 | } 272 | 273 | if (sendContentType) { 274 | if (!mimetype) { 275 | mimetype = httpdGetMimetype(connData->url); 276 | } 277 | httpdHeader(connData, "Content-Type", mimetype); 278 | } 279 | 280 | if (connData->cgiArg == &httpdCgiEx) { 281 | HttpdCgiExArg *ex = (HttpdCgiExArg *)connData->cgiArg2; 282 | if (ex->headerCb) { 283 | ex->headerCb(connData); 284 | } 285 | } 286 | httpdEndHeaders(connData); 287 | return HTTPD_CGI_MORE; 288 | } 289 | 290 | len=fread(buff, 1, FILE_CHUNK_LEN, tpd->file); 291 | if (len>0) { 292 | sp=0; 293 | e=buff; 294 | for (x=0; xtokenPos==-1) { 296 | //Inside ordinary text. 297 | if (buff[x]=='%') { 298 | //Send raw data up to now 299 | if (sp!=0) httpdSend(connData, e, sp); 300 | sp=0; 301 | //Go collect token chars. 302 | tpd->tokenPos=0; 303 | } else { 304 | sp++; 305 | } 306 | } else { 307 | if (buff[x]=='%') { 308 | if (tpd->tokenPos==0) { 309 | //This is the second % of a %% escape string. 310 | //Send a single % and resume with the normal program flow. 311 | httpdSend(connData, "%", 1); 312 | } else { 313 | //This is an actual token. 314 | tpd->token[tpd->tokenPos++]=0; //zero-terminate token 315 | ((TplCallback)(connData->cgiArg))(connData, tpd->token, &tpd->tplArg); 316 | } 317 | //Go collect normal chars again. 318 | e=&buff[x+1]; 319 | tpd->tokenPos=-1; 320 | } else { 321 | if (tpd->tokenPos<(sizeof(tpd->token)-1)) tpd->token[tpd->tokenPos++]=buff[x]; 322 | } 323 | } 324 | } 325 | } 326 | //Send remaining bit. 327 | if (sp!=0) httpdSend(connData, e, sp); 328 | if (len!=FILE_CHUNK_LEN) { 329 | //We're done. 330 | ((TplCallback)(connData->cgiArg))(connData, NULL, &tpd->tplArg); 331 | fclose(tpd->file); 332 | free(tpd); 333 | return HTTPD_CGI_DONE; 334 | } else { 335 | //Ok, till next time. 336 | return HTTPD_CGI_MORE; 337 | } 338 | } 339 | 340 | static esp_err_t createMissingDirectories(char *fullpath) { 341 | char *string, *tofree; 342 | int err = ESP_OK; 343 | tofree = string = strndup(fullpath, MAX_FILENAME_LENGTH); // make a copy because modifies input 344 | assert(string != NULL); 345 | 346 | int i; 347 | for(i=0; string[i] != '\0'; i++) // search over all chars in fullpath 348 | { 349 | if (i>0 && (string[i] == '\\' || string[i] == '/')) // stop when reached a slash 350 | { 351 | struct stat sb; 352 | char slash = string[i]; 353 | string[i] = '\0'; // replace slash with null terminator temporarily 354 | if (stat(string, &sb) != 0) { // stat() will tell us if it is a file or directory or neither. 355 | //printf("stat failed.\n"); 356 | if (errno == ENOENT) /* No such file or directory */ 357 | { 358 | // Create directory 359 | int e = mkdir(string, S_IRWXU); 360 | if (e != 0) 361 | { 362 | ESP_LOGE(__func__, "mkdir failed; errno=%d\n",errno); 363 | err = ESP_FAIL; 364 | break; 365 | } 366 | else 367 | { 368 | ESP_LOGI(__func__, "created the directory %s\n",string); 369 | } 370 | } 371 | } 372 | string[i] = slash; // replace the slash and keep searching fullpath 373 | } 374 | } 375 | free(tofree); // don't skip this or memory-leak! 376 | return err; 377 | } 378 | 379 | typedef struct { 380 | enum {UPSTATE_START, UPSTATE_WRITE, UPSTATE_DONE, UPSTATE_ERR} state; 381 | FILE *file; 382 | char filename[MAX_FILENAME_LENGTH + 1]; 383 | int b_written; 384 | const char *errtxt; 385 | } UploadState; 386 | 387 | CgiStatus cgiEspVfsUpload(HttpdConnData *connData) { 388 | UploadState *state=(UploadState *)connData->cgiData; 389 | 390 | if (connData->isConnectionClosed) { 391 | //Connection aborted. Clean up. 392 | if (state != NULL) 393 | { 394 | if(state->file != NULL){ 395 | fclose(state->file); 396 | ESP_LOGD(__func__, "fclose: %s, r", state->filename); 397 | } 398 | free(state); 399 | } 400 | ESP_LOGE(__func__, "Connection aborted!"); 401 | return HTTPD_CGI_DONE; 402 | } 403 | 404 | //First call to this cgi. 405 | if (state == NULL) { 406 | if (!(connData->requestType==HTTPD_METHOD_PUT || connData->requestType==HTTPD_METHOD_POST)) { 407 | return HTTPD_CGI_NOTFOUND; // return and allow another cgi function to handle it 408 | } 409 | //First call. Allocate and initialize state variable. 410 | state = malloc(sizeof(UploadState)); 411 | if (state==NULL) { 412 | ESP_LOGE(__func__, "Can't allocate upload struct"); 413 | //return HTTPD_CGI_NOTFOUND; // Let cgiNotFound() deal with extra post data. 414 | state->state=UPSTATE_ERR; 415 | goto error_first; 416 | } 417 | memset(state, 0, sizeof(UploadState)); // all members of state are initialized to 0 418 | state->state = UPSTATE_START; 419 | 420 | if (connData->post.multipartBoundary != NULL) 421 | { 422 | // todo: handle when uploaded file is POSTed in a multipart/form-data. For now, just upload the file by itself (i.e. xhr.send(file), not xhr.send(form)). 423 | ESP_LOGE(__func__, "Sorry! file upload in multipart/form-data is not supported yet."); 424 | //return HTTPD_CGI_NOTFOUND; // Let cgiNotFound() deal with extra post data. 425 | state->state=UPSTATE_ERR; 426 | goto error_first; 427 | } 428 | 429 | // cgiArg specifies where this function is allowed to write to. It must be specified and can't be empty. 430 | const char *basePath = connData->cgiArg; 431 | if ((basePath == NULL) || (*basePath == 0)) { 432 | state->state=UPSTATE_ERR; 433 | goto error_first; 434 | } 435 | int n = strlcpy(state->filename, basePath, MAX_FILENAME_LENGTH); 436 | if (n >= MAX_FILENAME_LENGTH) goto error_first; 437 | 438 | // Is cgiArg a single file or a directory (with trailing slash)? 439 | if (basePath[n - 1] == '\\' || basePath[n - 1] == '/') // check last char in cgiArg string for a slash 440 | { 441 | // Last char of cgiArg is a slash, assume it is a directory. 442 | 443 | // get queryParameter "filename" : string 444 | char* filenamebuf = state->filename + n; 445 | int arglen = httpdFindArg(connData->getArgs, "filename", filenamebuf, MAX_FILENAME_LENGTH - n); 446 | // 3. (highest priority) Filename to write to is cgiArg + "filename" as specified by url parameter 447 | if (arglen > 0) 448 | { 449 | // filename is already appended by httpdFindArg() above. 450 | } 451 | // 2. Filename to write to is cgiArg + "filename" as inside multipart/form-data --todo) 452 | else if (0) 453 | { 454 | // Beginning of POST data (after first headers): 455 | /* 456 | -----------------------------190192493010810\r\n 457 | Content-Disposition: form-data; name="file"; filename="/README.md"\r\n 458 | Content-Type: application/octet-stream\r\n 459 | \r\n 460 | datadatadatadata... 461 | */ 462 | } 463 | // 1: Filename to write to is cgiArg + url. 464 | else if(connData->url != NULL){ 465 | strncat(state->filename, connData->url, MAX_FILENAME_LENGTH - n); 466 | } 467 | } 468 | else 469 | { 470 | // Last char of cgiArg is not a slash, assume a single file. 471 | // Filename to write to is forced to cgiArg. The filename specified in the PUT url or in the POST filename is simply ignored. 472 | // (Anyway, a proper ROUTE entry should enforce the PUT url matches the cgiArg. i.e. 473 | // ROUTE_CGI_ARG("/writeable_file.txt", cgiEspVfsPut, FS_BASE_PATH "/html/writeable_file.txt")) 474 | // filename is already = basePath from strlcpy() above. 475 | } 476 | 477 | 478 | ESP_LOGI(__func__, "Uploading: %s", state->filename); 479 | 480 | // Create missing directories 481 | if (createMissingDirectories(state->filename) != ESP_OK) 482 | { 483 | state->errtxt="Error creating directory!"; 484 | state->state=UPSTATE_ERR; 485 | goto error_first; 486 | } 487 | 488 | // Open file for writing 489 | state->file = fopen(state->filename, "w"); 490 | if (state->file == NULL) 491 | { 492 | ESP_LOGE(__func__, "Can't open file for writing!"); 493 | state->errtxt="Can't open file for writing!"; 494 | state->state=UPSTATE_ERR; 495 | goto error_first; 496 | } 497 | 498 | state->state=UPSTATE_WRITE; 499 | ESP_LOGD(__func__, "fopen: %s, w", state->filename); 500 | 501 | error_first: 502 | connData->cgiData=state; 503 | } 504 | 505 | ESP_LOGD(__func__, "Chunk: %d bytes, ", connData->post.buffLen); 506 | 507 | if (state->state==UPSTATE_WRITE) { 508 | if(state->file != NULL){ 509 | int count = fwrite(connData->post.buff, 1, connData->post.buffLen, state->file); 510 | state->b_written += count; 511 | if (count != connData->post.buffLen) 512 | { 513 | state->state=UPSTATE_ERR; 514 | ESP_LOGE(__func__, "error writing to filesystem!"); 515 | } 516 | if (state->b_written >= connData->post.len){ 517 | state->state=UPSTATE_DONE; 518 | } 519 | } // else, Just eat up any bytes we receive. 520 | } else if (state->state==UPSTATE_DONE) { 521 | ESP_LOGE(__func__, "bogus bytes received after data received"); 522 | //Ignore those bytes. 523 | } else if (state->state==UPSTATE_ERR) { 524 | //Just eat up any bytes we receive. 525 | } 526 | 527 | if (connData->post.received == connData->post.len) { 528 | //We're done. 529 | cJSON *jsroot = cJSON_CreateObject(); 530 | if(state->file != NULL){ 531 | fclose(state->file); 532 | ESP_LOGD(__func__, "fclose: %s, r", state->filename); 533 | } 534 | ESP_LOGI(__func__, "Total: %d bytes written.", state->b_written); 535 | 536 | cJSON_AddStringToObject(jsroot, "filename", state->filename); 537 | cJSON_AddNumberToObject(jsroot, "bytes received", connData->post.received); 538 | cJSON_AddNumberToObject(jsroot, "bytes written", state->b_written); 539 | cJSON_AddBoolToObject(jsroot, "success", state->state==UPSTATE_DONE); 540 | free(state); 541 | 542 | cgiJsonResponseCommonSingle(connData, jsroot); // Send the json response! 543 | return HTTPD_CGI_DONE; 544 | } else { 545 | //Ok, till next time. 546 | return HTTPD_CGI_MORE; 547 | } 548 | } 549 | --------------------------------------------------------------------------------