├── .gitignore ├── .gitmodules ├── Makefile ├── README.md ├── core ├── auth.c ├── base64.c ├── base64.h ├── httpd-freertos.c ├── httpd-nonos.c ├── httpd-platform.h ├── httpd.c ├── httpdespfs.c └── sha1.c ├── espfs ├── espfs.c ├── espfsformat.h ├── espfstest │ ├── Makefile │ └── main.c ├── heatshrink_config_custom.h ├── heatshrink_decoder.c └── mkespfsimage │ ├── Makefile │ ├── heatshrink_encoder.c │ └── main.c ├── include ├── auth.h ├── captdns.h ├── cgiflash.h ├── cgiwebsocket.h ├── cgiwifi.h ├── esp8266.h ├── espfs.h ├── espmissingincludes.h ├── httpd.h ├── httpdespfs.h ├── platform.h ├── sha1.h ├── user_config.h └── webpages-espfs.h ├── mkupgimg ├── .gitignore ├── Makefile └── mkupgimg.c ├── util ├── captdns.c ├── cgiflash.c ├── cgiwebsocket.c └── cgiwifi.c └── webpages.espfs.ld /.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 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "lib/heatshrink"] 2 | path = lib/heatshrink 3 | url = https://github.com/atomicobject/heatshrink.git 4 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | # Directory the Makefile is in. Please don't include other Makefiles before this. 3 | THISDIR:=$(dir $(abspath $(lastword $(MAKEFILE_LIST)))) 4 | 5 | #Include httpd config from lower level, if it exists 6 | -include ../esphttpdconfig.mk 7 | 8 | #Default options. If you want to change them, please create ../esphttpdconfig.mk with the options you want in it. 9 | GZIP_COMPRESSION ?= no 10 | COMPRESS_W_YUI ?= no 11 | YUI-COMPRESSOR ?= /usr/bin/yui-compressor 12 | USE_HEATSHRINK ?= yes 13 | HTTPD_WEBSOCKETS ?= yes 14 | USE_OPENSDK ?= no 15 | HTTPD_MAX_CONNECTIONS ?= 4 16 | #For FreeRTOS 17 | HTTPD_STACKSIZE ?= 2048 18 | #Auto-detect ESP32 build if not given. 19 | ifneq (,$(wildcard $(SDK_PATH)/include/esp32)) 20 | ESP32 ?= yes 21 | FREERTOS ?= yes 22 | else 23 | ESP32 ?= no 24 | FREERTOS ?= no 25 | endif 26 | 27 | # Output directors to store intermediate compiled files 28 | # relative to the project directory 29 | BUILD_BASE = build 30 | 31 | # Base directory for the compiler. Needs a / at the end; if not set it'll use the tools that are in 32 | # the PATH. 33 | XTENSA_TOOLS_ROOT ?= 34 | 35 | # base directory of the ESP8266 SDK package, absolute 36 | # Only used for the non-FreeRTOS build 37 | SDK_BASE ?= /opt/Espressif/ESP8266_SDK 38 | 39 | # Base directory of the ESP8266 FreeRTOS SDK package, absolute 40 | # Only used for the FreeRTOS build 41 | SDK_PATH ?= /opt/Espressif/ESP8266_RTOS_SDK 42 | 43 | 44 | # name for the target project 45 | LIB = libesphttpd.a 46 | 47 | # which modules (subdirectories) of the project to include in compiling 48 | MODULES = espfs core util 49 | EXTRA_INCDIR = ./include \ 50 | . \ 51 | lib/heatshrink/ 52 | 53 | 54 | # compiler flags using during compilation of source files 55 | CFLAGS = -Os -ggdb -std=c99 -Werror -Wpointer-arith -Wundef -Wall -Wl,-EL -fno-inline-functions \ 56 | -nostdlib -mlongcalls -mtext-section-literals -D__ets__ -DICACHE_FLASH \ 57 | -Wno-address -DHTTPD_MAX_CONNECTIONS=$(HTTPD_MAX_CONNECTIONS) -DHTTPD_STACKSIZE=$(HTTPD_STACKSIZE) \ 58 | 59 | 60 | # various paths from the SDK used in this project 61 | SDK_LIBDIR = lib 62 | SDK_LDDIR = ld 63 | 64 | 65 | ifeq ("$(FREERTOS)","yes") 66 | CFLAGS += -DFREERTOS -DLWIP_OPEN_SRC -ffunction-sections -fdata-sections 67 | ifeq ("$(ESP32)","yes") 68 | SDK_INCDIR = include \ 69 | include/esp32 \ 70 | driver_lib/include \ 71 | extra_include \ 72 | third_party/include \ 73 | third_party/include/cjson \ 74 | third_party/include/freertos \ 75 | third_party/include/lwip \ 76 | third_party/include/lwip/ipv4 \ 77 | third_party/include/lwip/ipv6 \ 78 | third_party/include/ssl 79 | CFLAGS += -DESP32 -DFREERTOS -DLWIP_OPEN_SRC -ffunction-sections -fdata-sections 80 | else 81 | SDK_INCDIR = include \ 82 | include/freertos \ 83 | include/espressif/esp8266 \ 84 | include/espressif \ 85 | extra_include \ 86 | include/lwip \ 87 | include/lwip/lwip \ 88 | include/lwip/ipv4 \ 89 | include/lwip/ipv6 90 | CFLAGS += -DFREERTOS -DLWIP_OPEN_SRC -ffunction-sections -fdata-sections 91 | endif 92 | SDK_INCDIR := $(addprefix -I$(SDK_PATH)/,$(SDK_INCDIR)) 93 | else 94 | SDK_INCDIR = include 95 | SDK_INCDIR := $(addprefix -I$(SDK_BASE)/,$(SDK_INCDIR)) 96 | endif 97 | 98 | 99 | ifeq ("$(ESP32)","yes") 100 | TOOLPREFIX=xtensa-esp108-elf- 101 | CFLAGS+=-DESP32 102 | else 103 | TOOLPREFIX=xtensa-lx106-elf- 104 | endif 105 | 106 | # select which tools to use as compiler, librarian and linker 107 | CC := $(XTENSA_TOOLS_ROOT)$(TOOLPREFIX)gcc 108 | AR := $(XTENSA_TOOLS_ROOT)$(TOOLPREFIX)ar 109 | LD := $(XTENSA_TOOLS_ROOT)$(TOOLPREFIX)gcc 110 | OBJCOPY := $(XTENSA_TOOLS_ROOT)$(TOOLPREFIX)objcopy 111 | 112 | #### 113 | #### no user configurable options below here 114 | #### 115 | SRC_DIR := $(MODULES) 116 | BUILD_DIR := $(addprefix $(BUILD_BASE)/,$(MODULES)) 117 | 118 | SRC := $(foreach sdir,$(SRC_DIR),$(wildcard $(sdir)/*.c)) 119 | OBJ := $(patsubst %.c,$(BUILD_BASE)/%.o,$(SRC)) 120 | 121 | INCDIR := $(addprefix -I,$(SRC_DIR)) 122 | EXTRA_INCDIR := $(addprefix -I,$(EXTRA_INCDIR)) 123 | MODULE_INCDIR := $(addsuffix /include,$(INCDIR)) 124 | 125 | V ?= $(VERBOSE) 126 | ifeq ("$(V)","1") 127 | Q := 128 | vecho := @true 129 | else 130 | Q := @ 131 | vecho := @echo 132 | endif 133 | 134 | 135 | ifneq ("$(FREERTOS)","yes") 136 | ifeq ("$(USE_OPENSDK)","yes") 137 | CFLAGS += -DUSE_OPENSDK 138 | else 139 | CFLAGS += -D_STDINT_H 140 | endif 141 | endif 142 | 143 | ifeq ("$(GZIP_COMPRESSION)","yes") 144 | CFLAGS += -DGZIP_COMPRESSION 145 | endif 146 | 147 | ifeq ("$(USE_HEATSHRINK)","yes") 148 | CFLAGS += -DESPFS_HEATSHRINK 149 | endif 150 | 151 | ifeq ("$(HTTPD_WEBSOCKETS)","yes") 152 | CFLAGS += -DHTTPD_WEBSOCKETS 153 | endif 154 | 155 | vpath %.c $(SRC_DIR) 156 | 157 | define compile-objects 158 | $1/%.o: %.c 159 | $(vecho) "CC $$<" 160 | $(Q) $(CC) $(INCDIR) $(MODULE_INCDIR) $(EXTRA_INCDIR) $(SDK_INCDIR) $(CFLAGS) -c $$< -o $$@ 161 | endef 162 | 163 | .PHONY: all checkdirs clean webpages.espfs submodules 164 | 165 | all: checkdirs $(LIB) webpages.espfs libwebpages-espfs.a 166 | 167 | submodules: lib/heatshrink/Makefile 168 | lib/heatshrink/Makefile: 169 | $(Q) echo "Heatshrink isn't found. Checking out submodules to fetch it." 170 | $(Q) git submodule init 171 | $(Q) git submodule update 172 | 173 | 174 | $(LIB): $(BUILD_DIR) submodules $(OBJ) 175 | $(vecho) "AR $@" 176 | $(Q) $(AR) cru $@ $(OBJ) 177 | 178 | checkdirs: $(BUILD_DIR) 179 | 180 | $(BUILD_DIR): 181 | $(Q) mkdir -p $@ 182 | 183 | #ignore vim swap files 184 | FIND_OPTIONS = -not -iname '*.swp' 185 | 186 | webpages.espfs: $(HTMLDIR) espfs/mkespfsimage/mkespfsimage 187 | ifeq ("$(COMPRESS_W_YUI)","yes") 188 | $(Q) rm -rf html_compressed; 189 | $(Q) cp -r ../html html_compressed; 190 | $(Q) echo "Compression assets with yui-compressor. This may take a while..." 191 | $(Q) for file in `find html_compressed -type f -name "*.js"`; do $(YUI-COMPRESSOR) --type js $$file -o $$file; done 192 | $(Q) for file in `find html_compressed -type f -name "*.css"`; do $(YUI-COMPRESSOR) --type css $$file -o $$file; done 193 | $(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}" 194 | # mkespfsimage will compress html, css, svg and js files with gzip by default if enabled 195 | # override with -g cmdline parameter 196 | $(Q) cd html_compressed; find . $(FIND_OPTIONS) | $(THISDIR)/espfs/mkespfsimage/mkespfsimage > $(THISDIR)/webpages.espfs; cd ..; 197 | else 198 | $(Q) cd ../html; find . $(FIND_OPTIONS) | $(THISDIR)/espfs/mkespfsimage/mkespfsimage > $(THISDIR)/webpages.espfs; cd .. 199 | endif 200 | 201 | libwebpages-espfs.a: webpages.espfs 202 | $(Q) $(OBJCOPY) -I binary -O elf32-xtensa-le -B xtensa --rename-section .data=.irom0.literal \ 203 | webpages.espfs build/webpages.espfs.o.tmp 204 | $(Q) $(LD) -nostdlib -Wl,-r build/webpages.espfs.o.tmp -o build/webpages.espfs.o -Wl,-T webpages.espfs.ld 205 | $(Q) $(AR) cru $@ build/webpages.espfs.o 206 | 207 | espfs/mkespfsimage/mkespfsimage: espfs/mkespfsimage/ 208 | $(Q) $(MAKE) -C espfs/mkespfsimage USE_HEATSHRINK="$(USE_HEATSHRINK)" GZIP_COMPRESSION="$(GZIP_COMPRESSION)" 209 | 210 | clean: 211 | $(Q) rm -f $(LIB) 212 | $(Q) find $(BUILD_BASE) -type f | xargs rm -f 213 | $(Q) make -C espfs/mkespfsimage/ clean 214 | $(Q) rm -rf $(FW_BASE) 215 | $(Q) rm -f webpages.espfs libwebpages-espfs.a 216 | ifeq ("$(COMPRESS_W_YUI)","yes") 217 | $(Q) rm -rf html_compressed 218 | endif 219 | 220 | $(foreach bdir,$(BUILD_DIR),$(eval $(call compile-objects,$(bdir)))) 221 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Libesphttpd intro 2 | 3 | Libesphttpd is a HTTP server library for the ESP8266. It supports integration in projects 4 | running under the non-os and FreeRTOS-based SDK. Its core is clean and small, but it provides an 5 | extensible architecture with plugins to handle a flash-based compressed read-only filesystem 6 | for static files, a tiny template engine, websockets, a captive portal, and more. 7 | 8 | # Examples 9 | 10 | There are two example projects that integrate this code, both a [non-os](http://git.spritesserver.nl/esphttpd.git/) 11 | as well as a [FreeRTOS-based](http://git.spritesserver.nl/esphttpd-freertos.git/) example. They show 12 | how to use libesphttpd to serve files from an ESP8266 and illustrate a way to make an user associate 13 | the ESP8266 with an access point from a standard webbrowser on a PC or mobile phone. 14 | 15 | # Programming guide 16 | 17 | Programming libesphttpd will require some knowledge of HTTP. Knowledge of the exact RFCs isn't needed, 18 | but it helps if you know the difference between a GET and a POST request, how HTTP headers work, 19 | what an mime-type is and so on. Furthermore, libesphttpd is written in the C language and uses the 20 | libraries available on the ESP8266 SDK. It is assumed the developer knows C and has some experience 21 | with the SDK. 22 | 23 | ## Initializing libesphttpd 24 | 25 | Initializing libesphttpd is usually done in the `user_main()` of your project, but it is not mandatory 26 | to place the call here. Initialization is done by the `httpdInit(builtInUrls, port)` call. The port 27 | is the TCP port the webserver will listen on; the builtInUrls is the CGI list. Only call the `httpdInit` 28 | once, calling it multiple times leads to undefined behaviour. 29 | 30 | (As an aside: CGI actually is an abbreviation for Common Gateway Interface, which is a specification 31 | to allow external processes to interface with a non-embedded webserver. The CGI functions mentioned here 32 | have nothing to do with the CGI protocol specification; the term 'CGI' is just used as a quick 33 | handle for a function interpreting headers and generating data to send to the web client.) 34 | 35 | The CGI list is an array of the HttpdBuiltInUrl type. Here's an example: 36 | ```c 37 | HttpdBuiltInUrl builtInUrls[]={ 38 | {"/", cgiRedirect, "/index.cgi"}, 39 | {"/index.cgi", cgiMyFunction, NULL}, 40 | {"*", cgiEspFsHook, NULL}, 41 | {NULL, NULL, NULL} 42 | }; 43 | ``` 44 | As you can see, the array consists of a number of entries, with the last entry filled with NULLs. When 45 | the webserver gets a request, it will run down the list and try to match the URL the browser sent to the 46 | pattern specified in the first argument in the list. If a match is detected, the corresponding CGI 47 | function is called. This function gets the opportunity to handle the request, but it also can pass 48 | on handling it; if this happens, the webserver will keep going down the list to look for a CGI 49 | with a matching pattern willing to handle the request; if there is none on the list, it will 50 | generate a 404 page itself. 51 | 52 | The patterns can also have wildcards: a * at the end of the pattern matches any text. For instance, 53 | the pattern `/wifi/*` will match requests for `/wifi/index.cgi` and `/wifi/picture.jpg`, but not 54 | for example `/settings/wifi/`. The cgiEspFsHook is used like that in the example: it will be called 55 | on any request that is not handled by the cgi functions earlier in the list. 56 | 57 | There also is a third entry in the list. This is an optional argument for the CGI function; its 58 | purpose differs per specific function. If this is not needed, it's okay to put NULL there instead. 59 | 60 | ### Sidenote: About the cgiEspFsHook call 61 | While `cgiEspFsHook` isn't handled any different than any other cgi function, it may be useful 62 | to shortly elaborate what its function is. `cgiEspFsHook` is responsible, on most implementations, 63 | for serving up the static files that are included in the project: static HTML pages, images, Javascript 64 | code etc. Esphttpd doesn't have a built-in method to serve static files: the code responsible 65 | for doing it is plugged into it the same way as any cgi function is. This allows the developer to 66 | leave away the ability to serve static files if it isn't needed, or use a different implementation 67 | that serves e.g. files off the FAT-partition of a SD-card. 68 | 69 | ## Built-in CGI functions 70 | The webserver provides a fair amount of general-use CGI functions. Because of the structure of 71 | libesphttpd works and some linker magic in the Makefiles of the SDKs, the compiler will only 72 | include them in the output binary if they're actually used. 73 | 74 | * __cgiRedirect__ (arg: URL to redirect to) 75 | This is a convenience function to redirect the browser requesting this URL to a different URL. For 76 | example, an entry like {"/google", cgiRedirect, "http://google.com"} would redirect 77 | all browsers requesting /google to the website of the search giant. 78 | 79 | * __cgiRedirectToHostname__ (arg: hostname to redirect to) 80 | If the host as requested by the browser isn't the hostname in the argument, the webserver will do a redirect 81 | to the host instead. If the hostname does match, it will pass on the request. 82 | 83 | * __cgiRedirectApClientToHostname__ (arg: hostname to redirect to) 84 | This does the same as `cgiRedirectToHostname` but only to clients connected to the SoftAP of the 85 | ESP8266. This and the former function are used with the captive portal mode. The captive portal consists 86 | of a DNS-server (started by calling `captdnsInit()`) resolving all hostnames into the IP of the 87 | ESP8266. These redirect functions can then be used to further redirect the client to the hostname of 88 | the ESP8266. 89 | 90 | * __cgiReadFlash__ (arg: none) 91 | Will serve up the SPI flash of the ESP8266 as a binary file. 92 | 93 | * __cgiGetFirmwareNext__ (arg: CgiUploadFlashDef flash description data) 94 | For OTA firmware upgrade: indicates if the user1 or user2 firmware needs to be sent to the ESP to do 95 | an OTA upgrade 96 | 97 | * __cgiUploadFirmware__ (arg: CgiUploadFlashDef flash description data) 98 | Accepts a POST request containing the user1 or user2 firmware binary and flashes it to the SPI flash 99 | 100 | * __cgiRebootFirmware__ (arg: none) 101 | Reboots the ESP8266 to the newly uploaded code after a firmware upload. 102 | 103 | * __cgiWiFi* functions__ (arg: various) 104 | These are used to change WiFi mode, scan for access points, associate to an access point etcetera. See 105 | the example projects for an implementation that uses these function calls. 106 | 107 | * __cgiWebsocket__ (arg: connect function) 108 | This CGI is used to set up a websocket. Websockets are described later in this document. 109 | 110 | * __cgiEspFsHook__ (arg: none) 111 | Serves files from the espfs filesystem. The espFsInit function should be called first, with as argument 112 | a pointer to the start of the espfs binary data in flash. The binary data can be both flashed separately 113 | to a free bit of SPI flash, as well as linked in with the binary. The nonos example project can be 114 | configured to do either. 115 | 116 | * __cgiEspFsTemplate__ (arg: template function) 117 | The espfs code comes with a small but efficient template routine, which can fill a template file stored on 118 | the espfs filesystem with user-defined data. 119 | 120 | 121 | ## Writing a CGI function 122 | 123 | A CGI function, in principle, is called when the HTTP headers have come in and the client is waiting for 124 | the response of the webserver. The CGI function is responsible for generating this response, including 125 | the correct headers and an appropriate body. To decide what response to generate and what other actions 126 | to take, the CGI function can inspect various information sources, like data passed as GET- or 127 | POST-arguments. 128 | 129 | A simple CGI function may, for example, greet the user with a name given as a GET argument: 130 | 131 | ```c 132 | int ICACHE_FLASH_ATTR cgiGreetUser(HttpdConnData *connData) { 133 | int len; //length of user name 134 | char name[128]; //Temporary buffer for name 135 | char output[256]; //Temporary buffer for HTML output 136 | 137 | //If the browser unexpectedly closes the connection, the CGI will be called 138 | //with connData->conn=NULL. We can use this to clean up any data. It's not really 139 | //used in this simple CGI function. 140 | if (connData->conn==NULL) { 141 | //Connection aborted. Clean up. 142 | return HTTPD_CGI_DONE; 143 | } 144 | 145 | if (connData->requestType!=HTTPD_METHOD_GET) { 146 | //Sorry, we only accept GET requests. 147 | httpdStartResponse(connData, 406); //http error code 'unacceptable' 148 | httpdEndHeaders(connData); 149 | return HTTPD_CGI_DONE; 150 | } 151 | 152 | //Look for the 'name' GET value. If found, urldecode it and return it into the 'name' var. 153 | len=httpdFindArg(connData->getArgs, "name", name, sizeof(name)); 154 | if (len==-1) { 155 | //If the result of httpdFindArg is -1, the variable isn't found in the data. 156 | strcpy(name, "unknown person"); 157 | } else { 158 | //If len isn't -1, the variable is found and is copied to the 'name' variable 159 | } 160 | 161 | //Generate the header 162 | //We want the header to start with HTTP code 200, which means the document is found. 163 | httpdStartResponse(connData, 200); 164 | //We are going to send some HTML. 165 | httpdHeader(connData, "Content-Type", "text/html"); 166 | //No more headers. 167 | httpdEndHeaders(connData); 168 | 169 | //We're going to send the HTML as two pieces: a head and a body. We could've also done 170 | //it in one go, but this demonstrates multiple ways of calling httpdSend. 171 | //Send the HTML head. Using -1 as the length will make httpdSend take the length 172 | //of the zero-terminated string it's passed as the amount of data to send. 173 | httpdSend(connData, "Page", -1) 174 | //Generate the HTML body. 175 | len=sprintf(output, "

Hello, %s!

", name); 176 | //Send HTML body to webbrowser. We use the length as calculated by sprintf here. 177 | //Using -1 again would also have worked, but this is more efficient. 178 | httpdSend(connData, output, len); 179 | 180 | //All done. 181 | return HTTPD_CGI_DONE; 182 | } 183 | ``` 184 | 185 | Putting this CGI function into the HttpdBuiltInUrl array, for example with pattern `"/hello.cgi"`, 186 | would allow an user to request the page `"http://192.168.4.1/hello.cgi?name=John+Doe"` and get a document 187 | saying *"Hello, John Doe!"*. 188 | 189 | A word of warning: while it may look like you could forego the entire 190 | httpdStartResponse/httpdHeader/httpdEndHeader structure and send all the HTTP headers using httpdSend, 191 | this will break a few things that need to know when the headers are finished, for example the 192 | HTTP 1.1 chunked transfer mode. 193 | 194 | The approach of parsing the arguments, building up a response and then sending it in one go is pretty 195 | simple and works just fine for small bits of data. The gotcha here is that all http data sent during the 196 | CGI function (headers and data) are temporarily stored in a buffer, which is sent to the client when 197 | the function returns. The size of this buffer is typically about 2K; if the CGI tries to send more than 198 | this, data will be lost. 199 | 200 | The way to get around this is to send part of the data using `httpdSend` and then return with `HTTPD_CGI_MORE` 201 | instead of `HTTPD_CGI_DONE`. The webserver will send the partial data and will call the CGI function 202 | again so it can send another part of the data, until the CGI function finally returns with `HTTPD_CGI_DONE`. 203 | The CGI can store it's state in connData->cgiData, which is a freely usable pointer that will persist across 204 | all calls in the request. It is NULL on the first call, and the standard way of doing things is to allocate 205 | a pointer to a struct that stores state here. Here's an example: 206 | 207 | ```c 208 | typedef struct { 209 | char *stringPos; 210 | } LongStringState; 211 | 212 | static char *longString="Please assume this is a very long string, way too long to be sent"\ 213 | "in one time because it won't fit in the send buffer in it's entirety; we have to"\ 214 | "break up sending it in multiple parts." 215 | 216 | int ICACHE_FLASH_ATTR cgiSendLongString(HttpdConnData *connData) { 217 | LongStringState *state=connData->cgiData; 218 | int len; 219 | 220 | //If the browser unexpectedly closes the connection, the CGI will be called 221 | //with connData->conn=NULL. We can use this to clean up any data. It's pretty relevant 222 | //here because otherwise we may leak memory when the browser aborts the connection. 223 | if (connData->conn==NULL) { 224 | //Connection aborted. Clean up. 225 | if (state!=NULL) free(state); 226 | return HTTPD_CGI_DONE; 227 | } 228 | 229 | if (state==NULL) { 230 | //This is the first call to the CGI for this webbrowser request. 231 | //Allocate a state structure. 232 | state=malloc(sizeof(LongStringState); 233 | //Save the ptr in connData so we get it passed the next time as well. 234 | connData->cgiData=state; 235 | //Set initial pointer to start of string 236 | state->stringPos=longString; 237 | //We need to send the headers before sending any data. Do that now. 238 | httpdStartResponse(connData, 200); 239 | httpdHeader(connData, "Content-Type", "text/plain"); 240 | httpdEndHeaders(connData); 241 | } 242 | 243 | //Figure out length of string to send. We will never send more than 128 bytes in this example. 244 | len=strlen(state->stringPos); //Get remaining length 245 | if (len>128) len=128; //Never send more than 128 bytes 246 | 247 | //Send that amount of data 248 | httpdSend(connData, state->stringPos, len); 249 | //Adjust stringPos to first byte we haven't sent yet 250 | state->stringPos+=len; 251 | //See if we need to send more 252 | if (strlen(state->stringPos)!=0) { 253 | //we have more to send; let the webserver call this function again. 254 | return HTTPD_CGI_MORE; 255 | } else { 256 | //We're done. Clean up here as well: if the CGI function returns HTTPD_CGI_DONE, it will 257 | //not be called again. 258 | free(state); 259 | return HTTPD_CGI_DONE; 260 | } 261 | } 262 | 263 | ``` 264 | In this case, the CGI is called again after each chunk of data has been sent over the socket. If you need to suspend the 265 | HTTP response and resume it asynchronously for some other reason, you may save the `HttpdConnData` pointer, return 266 | `HTTPD_CGI_MORE`, then later call `httpdContinue` with the saved connection pointer. For example, if you need to 267 | communicate with another device over a different connection, you could send data to that device in the initial CGI call, 268 | then return `HTTPD_CGI_MORE`, then, in the `espconn_recv_callback` for the response, you can call `httpdContinue` to 269 | resume the HTTP response with data retrieved from the other device. 270 | 271 | For POST data, a similar technique is used. For small amounts of POST data (smaller than MAX_POST, typically 272 | 1024 bytes) the entire thing will be stored in `connData->post->buff` and is accessible in its entirely 273 | on the first call to the CGI function. For example, when using POST to send form data, if the amount of expected 274 | data is low, it is acceptable to do a call like `len=httpdFindArg(connData->post->buff, "varname", buff, sizeof(buff));` 275 | to get the data for the individual form elements. 276 | 277 | In all cases, `connData->post->len` will contain the length of the entirety of the POST data, while 278 | `connData->post->buffLen` contains the length of the data in `connData->post->buff`. In the case where the 279 | total POST data is larger than the POST buffer, the latter will be less than the former. In this case, the 280 | CGI function is expected to not send any headers or data out yet, but to process the incoming bit of POST data and 281 | return with `HTTPD_CGI_MORE`. The next call will contain the next chunk of POST data. `connData->post->received` 282 | will always contain the total amount of POST data received for the request, including the data passed 283 | to the CGI. When that number equals `connData->post->len`, it means no more POST data is expected and 284 | the CGI function is free to send out the reply headers and data for the request. 285 | 286 | ## The template engine 287 | 288 | The espfs driver comes with a tiny template engine, which allows for runtime-calculated value changes in a static 289 | html page. It can be included in the builtInUrls variable like this: 290 | 291 | ```c 292 | {"/showname.tpl", cgiEspFsTemplate, tplShowName} 293 | ``` 294 | 295 | It requires two things. First of all, the template is needed, which specifically is a file on the espfs with the 296 | same name as the first argument of the builtInUrls value, in this case `showname.tpl`. It is a standard HTML file 297 | containing a number of %name% entries. For example: 298 | 299 | ```html 300 | 301 | Welcome 302 | 303 |

Welcome, %username%, to the %thing%!

304 | 305 | 306 | ``` 307 | 308 | When this URL is requested, the words between percent characters will invoke the `tplShowName` function, allowing 309 | it to output specific data. For example: 310 | 311 | ```c 312 | int ICACHE_FLASH_ATTR tplShowName(HttpdConnData *connData, char *token, void **arg) { 313 | if (token==NULL) return HTTPD_CGI_DONE; 314 | 315 | if (os_strcmp(token, "username")==0) httpdSend(connData, "John Doe", -1); 316 | if (os_strcmp(token, "thing")==0) httpdSend(connData, "ESP8266 webserver", -1); 317 | 318 | return HTTPD_CGI_DONE; 319 | } 320 | ``` 321 | 322 | This will result in a page stating *Welcome, John Doe, to the ESP8266 webserver!*. 323 | 324 | 325 | ## Websocket functionality 326 | 327 | ToDo: document this -------------------------------------------------------------------------------- /core/auth.c: -------------------------------------------------------------------------------- 1 | /* 2 | HTTP auth implementation. Only does basic authentication for now. 3 | */ 4 | 5 | /* 6 | * ---------------------------------------------------------------------------- 7 | * "THE BEER-WARE LICENSE" (Revision 42): 8 | * Jeroen Domburg wrote this file. As long as you retain 9 | * this notice you can do whatever you want with this stuff. If we meet some day, 10 | * and you think this stuff is worth it, you can buy me a beer in return. 11 | * ---------------------------------------------------------------------------- 12 | */ 13 | 14 | 15 | #include 16 | #include "auth.h" 17 | #include "base64.h" 18 | 19 | int ICACHE_FLASH_ATTR authBasic(HttpdConnData *connData) { 20 | const char *forbidden="401 Forbidden."; 21 | int no=0; 22 | int r; 23 | char hdr[(AUTH_MAX_USER_LEN+AUTH_MAX_PASS_LEN+2)*10]; 24 | char userpass[AUTH_MAX_USER_LEN+AUTH_MAX_PASS_LEN+2]; 25 | char user[AUTH_MAX_USER_LEN]; 26 | char pass[AUTH_MAX_PASS_LEN]; 27 | if (connData->conn==NULL) { 28 | //Connection aborted. Clean up. 29 | return HTTPD_CGI_DONE; 30 | } 31 | 32 | r=httpdGetHeader(connData, "Authorization", hdr, sizeof(hdr)); 33 | if (r && strncmp(hdr, "Basic", 5)==0) { 34 | r=base64_decode(strlen(hdr)-6, hdr+6, sizeof(userpass), (unsigned char *)userpass); 35 | if (r<0) r=0; //just clean out string on decode error 36 | userpass[r]=0; //zero-terminate user:pass string 37 | // printf("Auth: %s\n", userpass); 38 | while (((AuthGetUserPw)(connData->cgiArg))(connData, no, 39 | user, AUTH_MAX_USER_LEN, pass, AUTH_MAX_PASS_LEN)) { 40 | //Check user/pass against auth header 41 | if (strlen(userpass)==strlen(user)+strlen(pass)+1 && 42 | strncmp(userpass, user, strlen(user))==0 && 43 | userpass[strlen(user)]==':' && 44 | strcmp(userpass+strlen(user)+1, pass)==0) { 45 | //Authenticated. Yay! 46 | return HTTPD_CGI_AUTHENTICATED; 47 | } 48 | no++; //Not authenticated with this user/pass. Check next user/pass combo. 49 | } 50 | } 51 | 52 | //Not authenticated. Go bug user with login screen. 53 | httpdStartResponse(connData, 401); 54 | httpdHeader(connData, "Content-Type", "text/plain"); 55 | httpdHeader(connData, "WWW-Authenticate", "Basic realm=\""HTTP_AUTH_REALM"\""); 56 | httpdEndHeaders(connData); 57 | httpdSend(connData, forbidden, -1); 58 | //Okay, all done. 59 | return HTTPD_CGI_DONE; 60 | } 61 | 62 | -------------------------------------------------------------------------------- /core/base64.c: -------------------------------------------------------------------------------- 1 | /* base64.c : base-64 / MIME encode/decode */ 2 | /* PUBLIC DOMAIN - Jon Mayo - November 13, 2003 */ 3 | #include 4 | #include "base64.h" 5 | 6 | static const int base64dec_tab[256] ICACHE_RODATA_ATTR={ 7 | 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, 8 | 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, 9 | 255,255,255,255,255,255,255,255,255,255,255, 62,255,255,255, 63, 10 | 52, 53, 54, 55, 56, 57, 58, 59, 60, 61,255,255,255, 0,255,255, 11 | 255, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 12 | 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25,255,255,255,255,255, 13 | 255, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 14 | 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51,255,255,255,255,255, 15 | 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, 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,255,255,255,255,255, 19 | 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, 20 | 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, 21 | 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, 22 | 255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, 23 | }; 24 | 25 | #if 0 26 | static int ICACHE_FLASH_ATTR base64decode(const char in[4], char out[3]) { 27 | uint8_t v[4]; 28 | 29 | v[0]=base64dec_tab[(unsigned)in[0]]; 30 | v[1]=base64dec_tab[(unsigned)in[1]]; 31 | v[2]=base64dec_tab[(unsigned)in[2]]; 32 | v[3]=base64dec_tab[(unsigned)in[3]]; 33 | 34 | out[0]=(v[0]<<2)|(v[1]>>4); 35 | out[1]=(v[1]<<4)|(v[2]>>2); 36 | out[2]=(v[2]<<6)|(v[3]); 37 | return (v[0]|v[1]|v[2]|v[3])!=255 ? in[3]=='=' ? in[2]=='=' ? 1 : 2 : 3 : 0; 38 | } 39 | #endif 40 | 41 | /* decode a base64 string in one shot */ 42 | int ICACHE_FLASH_ATTR __attribute__((weak)) base64_decode(size_t in_len, const char *in, size_t out_len, unsigned char *out) { 43 | unsigned int ii, io; 44 | uint32_t v; 45 | unsigned int rem; 46 | 47 | for(io=0,ii=0,v=0,rem=0;ii=8) { 56 | rem-=8; 57 | if(io>=out_len) return -1; /* truncation is failure */ 58 | out[io++]=(v>>rem)&255; 59 | } 60 | } 61 | if(rem>=8) { 62 | rem-=8; 63 | if(io>=out_len) return -1; /* truncation is failure */ 64 | out[io++]=(v>>rem)&255; 65 | } 66 | return io; 67 | } 68 | 69 | static const uint8_t base64enc_tab[64]= "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; 70 | 71 | #if 0 72 | void base64encode(const unsigned char in[3], unsigned char out[4], int count) { 73 | out[0]=base64enc_tab[(in[0]>>2)]; 74 | out[1]=base64enc_tab[((in[0]&3)<<4)|(in[1]>>4)]; 75 | out[2]=count<2 ? '=' : base64enc_tab[((in[1]&15)<<2)|(in[2]>>6)]; 76 | out[3]=count<3 ? '=' : base64enc_tab[(in[2]&63)]; 77 | } 78 | #endif 79 | 80 | int ICACHE_FLASH_ATTR __attribute__((weak)) base64_encode(size_t in_len, const unsigned char *in, size_t out_len, char *out) { 81 | unsigned ii, io; 82 | uint32_t v; 83 | unsigned rem; 84 | 85 | for(io=0,ii=0,v=0,rem=0;ii=6) { 91 | rem-=6; 92 | if(io>=out_len) return -1; /* truncation is failure */ 93 | out[io++]=base64enc_tab[(v>>rem)&63]; 94 | } 95 | } 96 | if(rem) { 97 | v<<=(6-rem); 98 | if(io>=out_len) return -1; /* truncation is failure */ 99 | out[io++]=base64enc_tab[v&63]; 100 | } 101 | while(io&3) { 102 | if(io>=out_len) return -1; /* truncation is failure */ 103 | out[io++]='='; 104 | } 105 | if(io>=out_len) return -1; /* no room for null terminator */ 106 | out[io]=0; 107 | return io; 108 | } 109 | 110 | -------------------------------------------------------------------------------- /core/base64.h: -------------------------------------------------------------------------------- 1 | #ifndef BASE64_H 2 | #define BASE64_H 3 | 4 | int base64_decode(size_t in_len, const char *in, size_t out_len, unsigned char *out); 5 | int base64_encode(size_t in_len, const unsigned char *in, size_t out_len, char *out); 6 | #endif -------------------------------------------------------------------------------- /core/httpd-freertos.c: -------------------------------------------------------------------------------- 1 | /* 2 | ESP8266 web server - platform-dependent routines, FreeRTOS version 3 | 4 | 5 | Thanks to my collague at Espressif for writing the foundations of this code. 6 | */ 7 | #ifdef FREERTOS 8 | 9 | 10 | #include 11 | #include "httpd.h" 12 | #include "platform.h" 13 | #include "httpd-platform.h" 14 | 15 | #include "freertos/FreeRTOS.h" 16 | #include "freertos/task.h" 17 | #include "freertos/queue.h" 18 | #include "freertos/semphr.h" 19 | 20 | #include "lwip/lwip/sockets.h" 21 | 22 | 23 | static int httpPort; 24 | static int httpMaxConnCt; 25 | static xQueueHandle httpdMux; 26 | 27 | 28 | struct RtosConnType{ 29 | int fd; 30 | int needWriteDoneNotif; 31 | int needsClose; 32 | int port; 33 | char ip[4]; 34 | }; 35 | 36 | static RtosConnType rconn[HTTPD_MAX_CONNECTIONS]; 37 | 38 | int ICACHE_FLASH_ATTR httpdPlatSendData(ConnTypePtr conn, char *buff, int len) { 39 | conn->needWriteDoneNotif=1; 40 | return (write(conn->fd, buff, len)>=0); 41 | } 42 | 43 | void ICACHE_FLASH_ATTR httpdPlatDisconnect(ConnTypePtr conn) { 44 | conn->needsClose=1; 45 | conn->needWriteDoneNotif=1; //because the real close is done in the writable select code 46 | } 47 | 48 | void httpdPlatDisableTimeout(ConnTypePtr conn) { 49 | //Unimplemented for FreeRTOS 50 | } 51 | 52 | //Set/clear global httpd lock. 53 | void ICACHE_FLASH_ATTR httpdPlatLock() { 54 | xSemaphoreTakeRecursive(httpdMux, portMAX_DELAY); 55 | } 56 | 57 | void ICACHE_FLASH_ATTR httpdPlatUnlock() { 58 | xSemaphoreGiveRecursive(httpdMux); 59 | } 60 | 61 | 62 | #define RECV_BUF_SIZE 2048 63 | static void platHttpServerTask(void *pvParameters) { 64 | int32 listenfd; 65 | int32 remotefd; 66 | int32 len; 67 | int32 ret; 68 | int x; 69 | int maxfdp = 0; 70 | char *precvbuf; 71 | fd_set readset,writeset; 72 | struct sockaddr name; 73 | //struct timeval timeout; 74 | struct sockaddr_in server_addr; 75 | struct sockaddr_in remote_addr; 76 | 77 | httpdMux=xSemaphoreCreateRecursiveMutex(); 78 | 79 | for (x=0; xmaxfdp) maxfdp=rconn[x].fd; 133 | } else { 134 | socketsFull=0; 135 | } 136 | } 137 | 138 | if (!socketsFull) { 139 | FD_SET(listenfd, &readset); 140 | if (listenfd>maxfdp) maxfdp=listenfd; 141 | } 142 | 143 | //polling all exist client handle,wait until readable/writable 144 | ret = select(maxfdp+1, &readset, &writeset, NULL, NULL);//&timeout 145 | if(ret > 0){ 146 | //See if we need to accept a new connection 147 | if (FD_ISSET(listenfd, &readset)) { 148 | len=sizeof(struct sockaddr_in); 149 | remotefd = accept(listenfd, (struct sockaddr *)&remote_addr, (socklen_t *)&len); 150 | if (remotefd<0) { 151 | httpd_printf("platHttpServerTask: Huh? Accept failed.\n"); 152 | continue; 153 | } 154 | for(x=0; xsin_port; 178 | memcpy(&rconn[x].ip, &piname->sin_addr.s_addr, sizeof(rconn[x].ip)); 179 | 180 | httpdConnectCb(&rconn[x], rconn[x].ip, rconn[x].port); 181 | //os_timer_disarm(&connData[x].conn->stop_watch); 182 | //os_timer_setfn(&connData[x].conn->stop_watch, (os_timer_func_t *)httpserver_conn_watcher, connData[x].conn); 183 | //os_timer_arm(&connData[x].conn->stop_watch, STOP_TIMER, 0); 184 | // httpd_printf("httpserver acpt index %d sockfd %d!\n", x, remotefd); 185 | } 186 | 187 | //See if anything happened on the existing connections. 188 | for(x=0; x < HTTPD_MAX_CONNECTIONS; x++){ 189 | //Skip empty slots 190 | if (rconn[x].fd==-1) continue; 191 | 192 | //Check for write availability first: the read routines may write needWriteDoneNotif while 193 | //the select didn't check for that. 194 | if (rconn[x].needWriteDoneNotif && FD_ISSET(rconn[x].fd, &writeset)) { 195 | rconn[x].needWriteDoneNotif=0; //Do this first, httpdSentCb may write something making this 1 again. 196 | if (rconn[x].needsClose) { 197 | //Do callback and close fd. 198 | httpdDisconCb(&rconn[x], rconn[x].ip, rconn[x].port); 199 | close(rconn[x].fd); 200 | rconn[x].fd=-1; 201 | } else { 202 | httpdSentCb(&rconn[x], rconn[x].ip, rconn[x].port); 203 | } 204 | } 205 | 206 | if (FD_ISSET(rconn[x].fd, &readset)) { 207 | precvbuf=(char*)malloc(RECV_BUF_SIZE); 208 | if (precvbuf==NULL) { 209 | httpd_printf("platHttpServerTask: memory exhausted!\n"); 210 | httpdDisconCb(&rconn[x], rconn[x].ip, rconn[x].port); 211 | close(rconn[x].fd); 212 | rconn[x].fd=-1; 213 | } 214 | ret=recv(rconn[x].fd, precvbuf, RECV_BUF_SIZE,0); 215 | if (ret > 0) { 216 | //Data received. Pass to httpd. 217 | httpdRecvCb(&rconn[x], rconn[x].ip, rconn[x].port, precvbuf, ret); 218 | } else { 219 | //recv error,connection close 220 | httpdDisconCb(&rconn[x], rconn[x].ip, rconn[x].port); 221 | close(rconn[x].fd); 222 | rconn[x].fd=-1; 223 | } 224 | if (precvbuf) free(precvbuf); 225 | } 226 | } 227 | } 228 | } 229 | 230 | #if 0 231 | //Deinit code, not used here. 232 | /*release data connection*/ 233 | for(x=0; x < HTTPD_MAX_CONNECTIONS; x++){ 234 | //find all valid handle 235 | if(connData[x].conn == NULL) continue; 236 | if(connData[x].conn->sockfd >= 0){ 237 | os_timer_disarm((os_timer_t *)&connData[x].conn->stop_watch); 238 | close(connData[x].conn->sockfd); 239 | connData[x].conn->sockfd = -1; 240 | connData[x].conn = NULL; 241 | if(connData[x].cgi!=NULL) connData[x].cgi(&connData[x]); //flush cgi data 242 | httpdRetireConn(&connData[x]); 243 | } 244 | } 245 | /*release listen socket*/ 246 | close(listenfd); 247 | 248 | vTaskDelete(NULL); 249 | #endif 250 | } 251 | 252 | 253 | 254 | //Initialize listening socket, do general initialization 255 | void ICACHE_FLASH_ATTR httpdPlatInit(int port, int maxConnCt) { 256 | httpPort=port; 257 | httpMaxConnCt=maxConnCt; 258 | #ifdef ESP32 259 | xTaskCreate(platHttpServerTask, (const char *)"esphttpd", HTTPD_STACKSIZE, NULL, 4, NULL); 260 | #else 261 | xTaskCreate(platHttpServerTask, (const signed char *)"esphttpd", HTTPD_STACKSIZE, NULL, 4, NULL); 262 | #endif 263 | } 264 | 265 | 266 | #endif -------------------------------------------------------------------------------- /core/httpd-nonos.c: -------------------------------------------------------------------------------- 1 | /* 2 | ESP8266 web server - platform-dependent routines, nonos version 3 | */ 4 | 5 | #include 6 | #include "httpd.h" 7 | #include "platform.h" 8 | #include "httpd-platform.h" 9 | 10 | #ifndef FREERTOS 11 | 12 | //Listening connection data 13 | static struct espconn httpdConn; 14 | static esp_tcp httpdTcp; 15 | 16 | //Set/clear global httpd lock. 17 | //Not needed on nonoos. 18 | void ICACHE_FLASH_ATTR httpdPlatLock() { 19 | } 20 | void ICACHE_FLASH_ATTR httpdPlatUnlock() { 21 | } 22 | 23 | 24 | static void ICACHE_FLASH_ATTR platReconCb(void *arg, sint8 err) { 25 | //From ESP8266 SDK 26 | //If still no response, considers it as TCP connection broke, goes into espconn_reconnect_callback. 27 | 28 | ConnTypePtr conn=arg; 29 | //Just call disconnect to clean up pool and close connection. 30 | httpdDisconCb(conn, (char*)conn->proto.tcp->remote_ip, conn->proto.tcp->remote_port); 31 | } 32 | 33 | static void ICACHE_FLASH_ATTR platDisconCb(void *arg) { 34 | ConnTypePtr conn=arg; 35 | httpdDisconCb(conn, (char*)conn->proto.tcp->remote_ip, conn->proto.tcp->remote_port); 36 | } 37 | 38 | static void ICACHE_FLASH_ATTR platRecvCb(void *arg, char *data, unsigned short len) { 39 | ConnTypePtr conn=arg; 40 | httpdRecvCb(conn, (char*)conn->proto.tcp->remote_ip, conn->proto.tcp->remote_port, data, len); 41 | } 42 | 43 | static void ICACHE_FLASH_ATTR platSentCb(void *arg) { 44 | ConnTypePtr conn=arg; 45 | httpdSentCb(conn, (char*)conn->proto.tcp->remote_ip, conn->proto.tcp->remote_port); 46 | } 47 | 48 | static void ICACHE_FLASH_ATTR platConnCb(void *arg) { 49 | ConnTypePtr conn=arg; 50 | if (httpdConnectCb(conn, (char*)conn->proto.tcp->remote_ip, conn->proto.tcp->remote_port)) { 51 | espconn_regist_recvcb(conn, platRecvCb); 52 | espconn_regist_reconcb(conn, platReconCb); 53 | espconn_regist_disconcb(conn, platDisconCb); 54 | espconn_regist_sentcb(conn, platSentCb); 55 | } else { 56 | espconn_disconnect(conn); 57 | } 58 | } 59 | 60 | 61 | int ICACHE_FLASH_ATTR httpdPlatSendData(ConnTypePtr conn, char *buff, int len) { 62 | int r; 63 | r=espconn_sent(conn, (uint8_t*)buff, len); 64 | return (r>=0); 65 | } 66 | 67 | void ICACHE_FLASH_ATTR httpdPlatDisconnect(ConnTypePtr conn) { 68 | espconn_disconnect(conn); 69 | } 70 | 71 | void ICACHE_FLASH_ATTR httpdPlatDisableTimeout(ConnTypePtr conn) { 72 | //Can't disable timeout; set to 2 hours instead. 73 | espconn_regist_time(conn, 7199, 1); 74 | } 75 | 76 | //Initialize listening socket, do general initialization 77 | void ICACHE_FLASH_ATTR httpdPlatInit(int port, int maxConnCt) { 78 | httpdConn.type=ESPCONN_TCP; 79 | httpdConn.state=ESPCONN_NONE; 80 | httpdTcp.local_port=port; 81 | httpdConn.proto.tcp=&httpdTcp; 82 | espconn_regist_connectcb(&httpdConn, platConnCb); 83 | espconn_accept(&httpdConn); 84 | espconn_tcp_set_max_con_allow(&httpdConn, maxConnCt); 85 | } 86 | 87 | 88 | #endif -------------------------------------------------------------------------------- /core/httpd-platform.h: -------------------------------------------------------------------------------- 1 | #ifndef HTTPD_PLATFORM_H 2 | #define HTTPD_PLATFORM_H 3 | 4 | int httpdPlatSendData(ConnTypePtr conn, char *buff, int len); 5 | void httpdPlatDisconnect(ConnTypePtr conn); 6 | void httpdPlatDisableTimeout(ConnTypePtr conn); 7 | void httpdPlatInit(int port, int maxConnCt); 8 | void httpdPlatLock(); 9 | void httpdPlatUnlock(); 10 | 11 | #endif -------------------------------------------------------------------------------- /core/httpd.c: -------------------------------------------------------------------------------- 1 | /* 2 | Esp8266 http server - core routines 3 | */ 4 | 5 | /* 6 | * ---------------------------------------------------------------------------- 7 | * "THE BEER-WARE LICENSE" (Revision 42): 8 | * Jeroen Domburg wrote this file. As long as you retain 9 | * this notice you can do whatever you want with this stuff. If we meet some day, 10 | * and you think this stuff is worth it, you can buy me a beer in return. 11 | * ---------------------------------------------------------------------------- 12 | */ 13 | 14 | 15 | #include 16 | #include "httpd.h" 17 | #include "httpd-platform.h" 18 | 19 | //This gets set at init time. 20 | static HttpdBuiltInUrl *builtInUrls; 21 | 22 | typedef struct HttpSendBacklogItem HttpSendBacklogItem; 23 | 24 | struct HttpSendBacklogItem { 25 | int len; 26 | HttpSendBacklogItem *next; 27 | char data[]; 28 | }; 29 | 30 | //Flags 31 | #define HFL_HTTP11 (1<<0) 32 | #define HFL_CHUNKED (1<<1) 33 | #define HFL_SENDINGBODY (1<<2) 34 | #define HFL_DISCONAFTERSENT (1<<3) 35 | #define HFL_NOCONNECTIONSTR (1<<4) 36 | 37 | //Private data for http connection 38 | struct HttpdPriv { 39 | char head[HTTPD_MAX_HEAD_LEN]; 40 | int headPos; 41 | char *sendBuff; 42 | int sendBuffLen; 43 | char *chunkHdr; 44 | HttpSendBacklogItem *sendBacklog; 45 | int sendBacklogSize; 46 | int flags; 47 | }; 48 | 49 | 50 | //Connection pool 51 | static HttpdConnData *connData[HTTPD_MAX_CONNECTIONS]; 52 | 53 | //Struct to keep extension->mime data in 54 | typedef struct { 55 | const char *ext; 56 | const char *mimetype; 57 | } MimeMap; 58 | 59 | 60 | //#define RSTR(a) ((const char)(a)) 61 | 62 | //The mappings from file extensions to mime types. If you need an extra mime type, 63 | //add it here. 64 | static const ICACHE_RODATA_ATTR MimeMap mimeTypes[]={ 65 | {"htm", "text/htm"}, 66 | {"html", "text/html"}, 67 | {"css", "text/css"}, 68 | {"js", "text/javascript"}, 69 | {"txt", "text/plain"}, 70 | {"jpg", "image/jpeg"}, 71 | {"jpeg", "image/jpeg"}, 72 | {"png", "image/png"}, 73 | {"svg", "image/svg+xml"}, 74 | {"xml", "text/xml"}, 75 | {"json", "application/json"}, 76 | {NULL, "text/html"}, //default value 77 | }; 78 | 79 | //Returns a static char* to a mime type for a given url to a file. 80 | const char ICACHE_FLASH_ATTR *httpdGetMimetype(char *url) { 81 | int i=0; 82 | //Go find the extension 83 | char *ext=url+(strlen(url)-1); 84 | while (ext!=url && *ext!='.') ext--; 85 | if (*ext=='.') ext++; 86 | 87 | //ToDo: strcmp is case sensitive; we may want to do case-intensive matching here... 88 | while (mimeTypes[i].ext!=NULL && strcmp(ext, mimeTypes[i].ext)!=0) i++; 89 | return mimeTypes[i].mimetype; 90 | } 91 | 92 | //Looks up the connData info for a specific connection 93 | static HttpdConnData ICACHE_FLASH_ATTR *httpdFindConnData(ConnTypePtr conn, char *remIp, int remPort) { 94 | for (int i=0; iremote_port == remPort && 96 | memcmp(connData[i]->remote_ip, remIp, 4) == 0) { 97 | connData[i]->conn=conn; 98 | return connData[i]; 99 | } 100 | } 101 | //Shouldn't happen. 102 | httpd_printf("*** Unknown connection %d.%d.%d.%d:%d\n", remIp[0]&0xff, remIp[1]&0xff, remIp[2]&0xff, remIp[3]&0xff, remPort); 103 | httpdPlatDisconnect(conn); 104 | return NULL; 105 | } 106 | 107 | //Retires a connection for re-use 108 | static void ICACHE_FLASH_ATTR httpdRetireConn(HttpdConnData *conn) { 109 | if (conn->priv->sendBacklog!=NULL) { 110 | HttpSendBacklogItem *i, *j; 111 | i=conn->priv->sendBacklog; 112 | do { 113 | j=i; 114 | i=i->next; 115 | free(j); 116 | } while (i!=NULL); 117 | } 118 | if (conn->post->buff!=NULL) free(conn->post->buff); 119 | if (conn->post!=NULL) free(conn->post); 120 | if (conn->priv!=NULL) free(conn->priv); 121 | if (conn) free(conn); 122 | for (int i=0; i='0' && c<='9') return c-'0'; 130 | if (c>='A' && c<='F') return c-'A'+10; 131 | if (c>='a' && c<='f') return c-'a'+10; 132 | return 0; 133 | } 134 | 135 | //Decode a percent-encoded value. 136 | //Takes the valLen bytes stored in val, and converts it into at most retLen bytes that 137 | //are stored in the ret buffer. Returns the actual amount of bytes used in ret. Also 138 | //zero-terminates the ret buffer. 139 | int ICACHE_FLASH_ATTR httpdUrlDecode(char *val, int valLen, char *ret, int retLen) { 140 | int s=0, d=0; 141 | int esced=0, escVal=0; 142 | while (spriv->head; 192 | p=p+strlen(p)+1; //skip GET/POST part 193 | p=p+strlen(p)+1; //skip HTTP part 194 | while (p<(conn->priv->head+conn->priv->headPos)) { 195 | while(*p<=32 && *p!=0) p++; //skip crap at start 196 | //See if this is the header 197 | if (strncmp(p, header, strlen(header))==0 && p[strlen(header)]==':') { 198 | //Skip 'key:' bit of header line 199 | p=p+strlen(header)+1; 200 | //Skip past spaces after the colon 201 | while(*p==' ') p++; 202 | //Copy from p to end 203 | while (*p!=0 && *p!='\r' && *p!='\n' && retLen>1) { 204 | *ret++=*p++; 205 | retLen--; 206 | } 207 | //Zero-terminate string 208 | *ret=0; 209 | //All done :) 210 | return 1; 211 | } 212 | p+=strlen(p)+1; //Skip past end of string and \0 terminator 213 | } 214 | return 0; 215 | } 216 | 217 | void ICACHE_FLASH_ATTR httdSetTransferMode(HttpdConnData *conn, int mode) { 218 | if (mode==HTTPD_TRANSFER_CLOSE) { 219 | conn->priv->flags&=~HFL_CHUNKED; 220 | conn->priv->flags&=~HFL_NOCONNECTIONSTR; 221 | } else if (mode==HTTPD_TRANSFER_CHUNKED) { 222 | conn->priv->flags|=HFL_CHUNKED; 223 | conn->priv->flags&=~HFL_NOCONNECTIONSTR; 224 | } else if (mode==HTTPD_TRANSFER_NONE) { 225 | conn->priv->flags&=~HFL_CHUNKED; 226 | conn->priv->flags|=HFL_NOCONNECTIONSTR; 227 | } 228 | } 229 | 230 | //Start the response headers. 231 | void ICACHE_FLASH_ATTR httpdStartResponse(HttpdConnData *conn, int code) { 232 | char buff[256]; 233 | int l; 234 | const char *connStr="Connection: close\r\n"; 235 | if (conn->priv->flags&HFL_CHUNKED) connStr="Transfer-Encoding: chunked\r\n"; 236 | if (conn->priv->flags&HFL_NOCONNECTIONSTR) connStr=""; 237 | l=sprintf(buff, "HTTP/1.%d %d OK\r\nServer: esp8266-httpd/"HTTPDVER"\r\n%s", 238 | (conn->priv->flags&HFL_HTTP11)?1:0, 239 | code, 240 | connStr); 241 | httpdSend(conn, buff, l); 242 | } 243 | 244 | //Send a http header. 245 | void ICACHE_FLASH_ATTR httpdHeader(HttpdConnData *conn, const char *field, const char *val) { 246 | httpdSend(conn, field, -1); 247 | httpdSend(conn, ": ", -1); 248 | httpdSend(conn, val, -1); 249 | httpdSend(conn, "\r\n", -1); 250 | } 251 | 252 | //Finish the headers. 253 | void ICACHE_FLASH_ATTR httpdEndHeaders(HttpdConnData *conn) { 254 | httpdSend(conn, "\r\n", -1); 255 | conn->priv->flags|=HFL_SENDINGBODY; 256 | } 257 | 258 | //Redirect to the given URL. 259 | void ICACHE_FLASH_ATTR httpdRedirect(HttpdConnData *conn, char *newUrl) { 260 | httpdStartResponse(conn, 302); 261 | httpdHeader(conn, "Location", newUrl); 262 | httpdEndHeaders(conn); 263 | httpdSend(conn, "Moved to ", -1); 264 | httpdSend(conn, newUrl, -1); 265 | } 266 | 267 | //Use this as a cgi function to redirect one url to another. 268 | int ICACHE_FLASH_ATTR cgiRedirect(HttpdConnData *connData) { 269 | if (connData->conn==NULL) { 270 | //Connection aborted. Clean up. 271 | return HTTPD_CGI_DONE; 272 | } 273 | httpdRedirect(connData, (char*)connData->cgiArg); 274 | return HTTPD_CGI_DONE; 275 | } 276 | 277 | //Used to spit out a 404 error 278 | static int ICACHE_FLASH_ATTR cgiNotFound(HttpdConnData *connData) { 279 | if (connData->conn==NULL) return HTTPD_CGI_DONE; 280 | httpdStartResponse(connData, 404); 281 | httpdEndHeaders(connData); 282 | httpdSend(connData, "404 File not found.", -1); 283 | return HTTPD_CGI_DONE; 284 | } 285 | 286 | //This CGI function redirects to a fixed url of http://[hostname]/ if hostname field of request isn't 287 | //already that hostname. Use this in combination with a DNS server that redirects everything to the 288 | //ESP in order to load a HTML page as soon as a phone, tablet etc connects to the ESP. Watch out: 289 | //this will also redirect connections when the ESP is in STA mode, potentially to a hostname that is not 290 | //in the 'official' DNS and so will fail. 291 | int ICACHE_FLASH_ATTR cgiRedirectToHostname(HttpdConnData *connData) { 292 | static const char hostFmt[]="http://%s/"; 293 | char *buff; 294 | int isIP=0; 295 | int x; 296 | if (connData->conn==NULL) { 297 | //Connection aborted. Clean up. 298 | return HTTPD_CGI_DONE; 299 | } 300 | if (connData->hostName==NULL) { 301 | httpd_printf("Huh? No hostname.\n"); 302 | return HTTPD_CGI_NOTFOUND; 303 | } 304 | 305 | //Quick and dirty code to see if host is an IP 306 | if (strlen(connData->hostName)>8) { 307 | isIP=1; 308 | for (x=0; xhostName); x++) { 309 | if (connData->hostName[x]!='.' && (connData->hostName[x]<'0' || connData->hostName[x]>'9')) isIP=0; 310 | } 311 | } 312 | if (isIP) return HTTPD_CGI_NOTFOUND; 313 | //Check hostname; pass on if the same 314 | if (strcmp(connData->hostName, (char*)connData->cgiArg)==0) return HTTPD_CGI_NOTFOUND; 315 | //Not the same. Redirect to real hostname. 316 | buff=malloc(strlen((char*)connData->cgiArg)+sizeof(hostFmt)); 317 | if (buff==NULL) { 318 | //Bail out 319 | return HTTPD_CGI_DONE; 320 | } 321 | sprintf(buff, hostFmt, (char*)connData->cgiArg); 322 | httpd_printf("Redirecting to hostname url %s\n", buff); 323 | httpdRedirect(connData, buff); 324 | free(buff); 325 | return HTTPD_CGI_DONE; 326 | } 327 | 328 | 329 | //Same as above, but will only redirect clients with an IP that is in the range of 330 | //the SoftAP interface. This should preclude clients connected to the STA interface 331 | //to be redirected to nowhere. 332 | int ICACHE_FLASH_ATTR cgiRedirectApClientToHostname(HttpdConnData *connData) { 333 | #ifndef FREERTOS 334 | uint32 *remadr; 335 | struct ip_info apip; 336 | int x=wifi_get_opmode(); 337 | //Check if we have an softap interface; bail out if not 338 | if (x!=2 && x!=3) return HTTPD_CGI_NOTFOUND; 339 | remadr=(uint32 *)connData->remote_ip; 340 | wifi_get_ip_info(SOFTAP_IF, &apip); 341 | if ((*remadr & apip.netmask.addr) == (apip.ip.addr & apip.netmask.addr)) { 342 | return cgiRedirectToHostname(connData); 343 | } else { 344 | return HTTPD_CGI_NOTFOUND; 345 | } 346 | #else 347 | return HTTPD_CGI_NOTFOUND; 348 | #endif 349 | } 350 | 351 | 352 | //Add data to the send buffer. len is the length of the data. If len is -1 353 | //the data is seen as a C-string. 354 | //Returns 1 for success, 0 for out-of-memory. 355 | int ICACHE_FLASH_ATTR httpdSend(HttpdConnData *conn, const char *data, int len) { 356 | if (conn->conn==NULL) return 0; 357 | if (len<0) len=strlen(data); 358 | if (len==0) return 0; 359 | if (conn->priv->flags&HFL_CHUNKED && conn->priv->flags&HFL_SENDINGBODY && conn->priv->chunkHdr==NULL) { 360 | if (conn->priv->sendBuffLen+len+6>HTTPD_MAX_SENDBUFF_LEN) return 0; 361 | //Establish start of chunk 362 | conn->priv->chunkHdr=&conn->priv->sendBuff[conn->priv->sendBuffLen]; 363 | strcpy(conn->priv->chunkHdr, "0000\r\n"); 364 | conn->priv->sendBuffLen+=6; 365 | } 366 | if (conn->priv->sendBuffLen+len>HTTPD_MAX_SENDBUFF_LEN) return 0; 367 | memcpy(conn->priv->sendBuff+conn->priv->sendBuffLen, data, len); 368 | conn->priv->sendBuffLen+=len; 369 | return 1; 370 | } 371 | 372 | static char ICACHE_FLASH_ATTR httpdHexNibble(int val) { 373 | val&=0xf; 374 | if (val<10) return '0'+val; 375 | return 'A'+(val-10); 376 | } 377 | 378 | //Function to send any data in conn->priv->sendBuff. Do not use in CGIs unless you know what you 379 | //are doing! Also, if you do set conn->cgi to NULL to indicate the connection is closed, do it BEFORE 380 | //calling this. 381 | void ICACHE_FLASH_ATTR httpdFlushSendBuffer(HttpdConnData *conn) { 382 | int r, len; 383 | if (conn->conn==NULL) return; 384 | if (conn->priv->chunkHdr!=NULL) { 385 | //We're sending chunked data, and the chunk needs fixing up. 386 | //Finish chunk with cr/lf 387 | httpdSend(conn, "\r\n", 2); 388 | //Calculate length of chunk 389 | len=((&conn->priv->sendBuff[conn->priv->sendBuffLen])-conn->priv->chunkHdr)-8; 390 | //Fix up chunk header to correct value 391 | conn->priv->chunkHdr[0]=httpdHexNibble(len>>12); 392 | conn->priv->chunkHdr[1]=httpdHexNibble(len>>8); 393 | conn->priv->chunkHdr[2]=httpdHexNibble(len>>4); 394 | conn->priv->chunkHdr[3]=httpdHexNibble(len>>0); 395 | //Reset chunk hdr for next call 396 | conn->priv->chunkHdr=NULL; 397 | } 398 | if (conn->priv->flags&HFL_CHUNKED && conn->priv->flags&HFL_SENDINGBODY && conn->cgi==NULL) { 399 | //Connection finished sending whatever needs to be sent. Add NULL chunk to indicate this. 400 | strcpy(&conn->priv->sendBuff[conn->priv->sendBuffLen], "0\r\n\r\n"); 401 | conn->priv->sendBuffLen+=5; 402 | } 403 | if (conn->priv->sendBuffLen!=0) { 404 | r=httpdPlatSendData(conn->conn, conn->priv->sendBuff, conn->priv->sendBuffLen); 405 | if (!r) { 406 | //Can't send this for some reason. Dump packet in backlog, we can send it later. 407 | if (conn->priv->sendBacklogSize+conn->priv->sendBuffLen>HTTPD_MAX_BACKLOG_SIZE) { 408 | httpd_printf("Httpd: Backlog: Exceeded max backlog size, dropped %d bytes instead of sending them.\n", conn->priv->sendBuffLen); 409 | conn->priv->sendBuffLen=0; 410 | return; 411 | } 412 | HttpSendBacklogItem *i=malloc(sizeof(HttpSendBacklogItem)+conn->priv->sendBuffLen); 413 | if (i==NULL) { 414 | httpd_printf("Httpd: Backlog: malloc failed, out of memory!\n"); 415 | return; 416 | } 417 | memcpy(i->data, conn->priv->sendBuff, conn->priv->sendBuffLen); 418 | i->len=conn->priv->sendBuffLen; 419 | i->next=NULL; 420 | if (conn->priv->sendBacklog==NULL) { 421 | conn->priv->sendBacklog=i; 422 | } else { 423 | HttpSendBacklogItem *e=conn->priv->sendBacklog; 424 | while (e->next!=NULL) e=e->next; 425 | e->next=i; 426 | } 427 | conn->priv->sendBacklogSize+=conn->priv->sendBuffLen; 428 | } 429 | conn->priv->sendBuffLen=0; 430 | } 431 | } 432 | 433 | void ICACHE_FLASH_ATTR httpdCgiIsDone(HttpdConnData *conn) { 434 | conn->cgi=NULL; //no need to call this anymore 435 | if (conn->priv->flags&HFL_CHUNKED) { 436 | httpd_printf("Pool slot %d is done. Cleaning up for next req\n", conn->slot); 437 | httpdFlushSendBuffer(conn); 438 | //Note: Do not clean up sendBacklog, it may still contain data at this point. 439 | conn->priv->headPos=0; 440 | conn->post->len=-1; 441 | conn->priv->flags=0; 442 | if (conn->post->buff) free(conn->post->buff); 443 | conn->post->buff=NULL; 444 | conn->post->buffLen=0; 445 | conn->post->received=0; 446 | conn->hostName=NULL; 447 | } else { 448 | //Cannot re-use this connection. Mark to get it killed after all data is sent. 449 | conn->priv->flags|=HFL_DISCONAFTERSENT; 450 | } 451 | } 452 | 453 | //Callback called when the data on a socket has been successfully 454 | //sent. 455 | void ICACHE_FLASH_ATTR httpdSentCb(ConnTypePtr rconn, char *remIp, int remPort) { 456 | HttpdConnData *conn=httpdFindConnData(rconn, remIp, remPort); 457 | httpdContinue(conn); 458 | } 459 | 460 | //Can be called after a CGI function has returned HTTPD_CGI_MORE to 461 | //resume handling an open connection asynchronously 462 | void ICACHE_FLASH_ATTR httpdContinue(HttpdConnData * conn) { 463 | int r; 464 | httpdPlatLock(); 465 | 466 | char *sendBuff; 467 | 468 | if (conn==NULL) return; 469 | 470 | if (conn->priv->sendBacklog!=NULL) { 471 | //We have some backlog to send first. 472 | HttpSendBacklogItem *next=conn->priv->sendBacklog->next; 473 | httpdPlatSendData(conn->conn, conn->priv->sendBacklog->data, conn->priv->sendBacklog->len); 474 | conn->priv->sendBacklogSize-=conn->priv->sendBacklog->len; 475 | free(conn->priv->sendBacklog); 476 | conn->priv->sendBacklog=next; 477 | httpdPlatUnlock(); 478 | return; 479 | } 480 | 481 | if (conn->priv->flags&HFL_DISCONAFTERSENT) { //Marked for destruction? 482 | httpd_printf("Pool slot %d is done. Closing.\n", conn->slot); 483 | httpdPlatDisconnect(conn->conn); 484 | httpdPlatUnlock(); 485 | return; //No need to call httpdFlushSendBuffer. 486 | } 487 | 488 | //If we don't have a CGI function, there's nothing to do but wait for something from the client. 489 | if (conn->cgi==NULL) { 490 | httpdPlatUnlock(); 491 | return; 492 | } 493 | 494 | sendBuff=malloc(HTTPD_MAX_SENDBUFF_LEN); 495 | if (sendBuff==NULL) { 496 | httpd_printf("Malloc of sendBuff failed!\n"); 497 | httpdPlatUnlock(); 498 | return; 499 | } 500 | conn->priv->sendBuff=sendBuff; 501 | conn->priv->sendBuffLen=0; 502 | r=conn->cgi(conn); //Execute cgi fn. 503 | if (r==HTTPD_CGI_DONE) { 504 | httpdCgiIsDone(conn); 505 | } 506 | if (r==HTTPD_CGI_NOTFOUND || r==HTTPD_CGI_AUTHENTICATED) { 507 | httpd_printf("ERROR! CGI fn returns code %d after sending data! Bad CGI!\n", r); 508 | httpdCgiIsDone(conn); 509 | } 510 | httpdFlushSendBuffer(conn); 511 | free(sendBuff); 512 | httpdPlatUnlock(); 513 | } 514 | 515 | //This is called when the headers have been received and the connection is ready to send 516 | //the result headers and data. 517 | //We need to find the CGI function to call, call it, and dependent on what it returns either 518 | //find the next cgi function, wait till the cgi data is sent or close up the connection. 519 | static void ICACHE_FLASH_ATTR httpdProcessRequest(HttpdConnData *conn) { 520 | int r; 521 | int i=0; 522 | if (conn->url==NULL) { 523 | httpd_printf("WtF? url = NULL\n"); 524 | return; //Shouldn't happen 525 | } 526 | //See if we can find a CGI that's happy to handle the request. 527 | while (1) { 528 | //Look up URL in the built-in URL table. 529 | while (builtInUrls[i].url!=NULL) { 530 | int match=0; 531 | //See if there's a literal match 532 | if (strcmp(builtInUrls[i].url, conn->url)==0) match=1; 533 | //See if there's a wildcard match 534 | if (builtInUrls[i].url[strlen(builtInUrls[i].url)-1]=='*' && 535 | strncmp(builtInUrls[i].url, conn->url, strlen(builtInUrls[i].url)-1)==0) match=1; 536 | if (match) { 537 | httpd_printf("Is url index %d\n", i); 538 | conn->cgiData=NULL; 539 | conn->cgi=builtInUrls[i].cgiCb; 540 | conn->cgiArg=builtInUrls[i].cgiArg; 541 | break; 542 | } 543 | i++; 544 | } 545 | if (builtInUrls[i].url==NULL) { 546 | //Drat, we're at the end of the URL table. This usually shouldn't happen. Well, just 547 | //generate a built-in 404 to handle this. 548 | httpd_printf("%s not found. 404!\n", conn->url); 549 | conn->cgi=cgiNotFound; 550 | } 551 | 552 | //Okay, we have a CGI function that matches the URL. See if it wants to handle the 553 | //particular URL we're supposed to handle. 554 | r=conn->cgi(conn); 555 | if (r==HTTPD_CGI_MORE) { 556 | //Yep, it's happy to do so and has more data to send. 557 | if (conn->recvHdl) { 558 | //Seems the CGI is planning to do some long-term communications with the socket. 559 | //Disable the timeout on it, so we won't run into that. 560 | httpdPlatDisableTimeout(conn->conn); 561 | } 562 | httpdFlushSendBuffer(conn); 563 | return; 564 | } else if (r==HTTPD_CGI_DONE) { 565 | //Yep, it's happy to do so and already is done sending data. 566 | httpdCgiIsDone(conn); 567 | return; 568 | } else if (r==HTTPD_CGI_NOTFOUND || r==HTTPD_CGI_AUTHENTICATED) { 569 | //URL doesn't want to handle the request: either the data isn't found or there's no 570 | //need to generate a login screen. 571 | i++; //look at next url the next iteration of the loop. 572 | } 573 | } 574 | } 575 | 576 | //Parse a line of header data and modify the connection data accordingly. 577 | static void ICACHE_FLASH_ATTR httpdParseHeader(char *h, HttpdConnData *conn) { 578 | int i; 579 | char firstLine=0; 580 | 581 | if (strncmp(h, "GET ", 4)==0) { 582 | conn->requestType = HTTPD_METHOD_GET; 583 | firstLine=1; 584 | } else if (strncmp(h, "Host:", 5)==0) { 585 | i=5; 586 | while (h[i]==' ') i++; 587 | conn->hostName=&h[i]; 588 | } else if (strncmp(h, "POST ", 5)==0) { 589 | conn->requestType = HTTPD_METHOD_POST; 590 | firstLine=1; 591 | } 592 | 593 | if (firstLine) { 594 | char *e; 595 | 596 | //Skip past the space after POST/GET 597 | i=0; 598 | while (h[i]!=' ') i++; 599 | conn->url=h+i+1; 600 | 601 | //Figure out end of url. 602 | e=(char*)strstr(conn->url, " "); 603 | if (e==NULL) return; //wtf? 604 | *e=0; //terminate url part 605 | e++; //Skip to protocol indicator 606 | while (*e==' ') e++; //Skip spaces. 607 | //If HTTP/1.1, note that and set chunked encoding 608 | if (strcasecmp(e, "HTTP/1.1")==0) conn->priv->flags|=HFL_HTTP11|HFL_CHUNKED; 609 | 610 | httpd_printf("URL = %s\n", conn->url); 611 | //Parse out the URL part before the GET parameters. 612 | conn->getArgs=(char*)strstr(conn->url, "?"); 613 | if (conn->getArgs!=0) { 614 | *conn->getArgs=0; 615 | conn->getArgs++; 616 | httpd_printf("GET args = %s\n", conn->getArgs); 617 | } else { 618 | conn->getArgs=NULL; 619 | } 620 | } else if (strncmp(h, "Connection:", 11)==0) { 621 | i=11; 622 | //Skip trailing spaces 623 | while (h[i]==' ') i++; 624 | if (strncmp(&h[i], "close", 5)==0) conn->priv->flags&=~HFL_CHUNKED; //Don't use chunked conn 625 | } else if (strncmp(h, "Content-Length:", 15)==0) { 626 | i=15; 627 | //Skip trailing spaces 628 | while (h[i]==' ') i++; 629 | //Get POST data length 630 | conn->post->len=atoi(h+i); 631 | 632 | // Allocate the buffer 633 | if (conn->post->len > HTTPD_MAX_POST_LEN) { 634 | // we'll stream this in in chunks 635 | conn->post->buffSize = HTTPD_MAX_POST_LEN; 636 | } else { 637 | conn->post->buffSize = conn->post->len; 638 | } 639 | httpd_printf("Mallocced buffer for %d + 1 bytes of post data.\n", conn->post->buffSize); 640 | conn->post->buff=(char*)malloc(conn->post->buffSize + 1); 641 | if (conn->post->buff==NULL) { 642 | printf("...failed!\n"); 643 | return; 644 | } 645 | conn->post->buffLen=0; 646 | } else if (strncmp(h, "Content-Type: ", 14)==0) { 647 | if (strstr(h, "multipart/form-data")) { 648 | // It's multipart form data so let's pull out the boundary for future use 649 | char *b; 650 | if ((b = strstr(h, "boundary=")) != NULL) { 651 | conn->post->multipartBoundary = b + 7; // move the pointer 2 chars before boundary then fill them with dashes 652 | conn->post->multipartBoundary[0] = '-'; 653 | conn->post->multipartBoundary[1] = '-'; 654 | httpd_printf("boundary = %s\n", conn->post->multipartBoundary); 655 | } 656 | } 657 | } 658 | } 659 | 660 | //Make a connection 'live' so we can do all the things a cgi can do to it. 661 | //ToDo: Also make httpdRecvCb/httpdContinue use these? 662 | //ToDo: Fail if malloc fails? 663 | void ICACHE_FLASH_ATTR httpdConnSendStart(HttpdConnData *conn) { 664 | httpdPlatLock(); 665 | char *sendBuff=malloc(HTTPD_MAX_SENDBUFF_LEN); 666 | if (sendBuff==NULL) { 667 | printf("Malloc sendBuff failed!\n"); 668 | return; 669 | } 670 | conn->priv->sendBuff=sendBuff; 671 | conn->priv->sendBuffLen=0; 672 | } 673 | 674 | //Finish the live-ness of a connection. Always call this after httpdConnStart 675 | void ICACHE_FLASH_ATTR httpdConnSendFinish(HttpdConnData *conn) { 676 | if (conn->conn) httpdFlushSendBuffer(conn); 677 | free(conn->priv->sendBuff); 678 | httpdPlatUnlock(); 679 | } 680 | 681 | //Callback called when there's data available on a socket. 682 | void ICACHE_FLASH_ATTR httpdRecvCb(ConnTypePtr rconn, char *remIp, int remPort, char *data, unsigned short len) { 683 | int x, r; 684 | char *p, *e; 685 | httpdPlatLock(); 686 | 687 | HttpdConnData *conn=httpdFindConnData(rconn, remIp, remPort); 688 | if (conn==NULL) { 689 | httpdPlatUnlock(); 690 | return; 691 | } 692 | 693 | char *sendBuff=malloc(HTTPD_MAX_SENDBUFF_LEN); 694 | if (sendBuff==NULL) { 695 | printf("Malloc sendBuff failed!\n"); 696 | httpdPlatUnlock(); 697 | return; 698 | } 699 | conn->priv->sendBuff=sendBuff; 700 | conn->priv->sendBuffLen=0; 701 | 702 | //This is slightly evil/dirty: we abuse conn->post->len as a state variable for where in the http communications we are: 703 | //<0 (-1): Post len unknown because we're still receiving headers 704 | //==0: No post data 705 | //>0: Need to receive post data 706 | //ToDo: See if we can use something more elegant for this. 707 | 708 | for (x=0; xpost->len<0) { 710 | //This byte is a header byte. 711 | if (data[x]=='\n') { 712 | //Compatibility with clients that send \n only: fake a \r in front of this. 713 | if (conn->priv->headPos!=0 && conn->priv->head[conn->priv->headPos-1]!='\r') { 714 | conn->priv->head[conn->priv->headPos++]='\r'; 715 | } 716 | } 717 | //ToDo: return http error code 431 (request header too long) if this happens 718 | if (conn->priv->headPospriv->head[conn->priv->headPos++]=data[x]; 719 | conn->priv->head[conn->priv->headPos]=0; 720 | //Scan for /r/n/r/n. Receiving this indicate the headers end. 721 | if (data[x]=='\n' && (char *)strstr(conn->priv->head, "\r\n\r\n")!=NULL) { 722 | //Indicate we're done with the headers. 723 | conn->post->len=0; 724 | //Reset url data 725 | conn->url=NULL; 726 | //Iterate over all received headers and parse them. 727 | p=conn->priv->head; 728 | while(p<(&conn->priv->head[conn->priv->headPos-4])) { 729 | e=(char *)strstr(p, "\r\n"); //Find end of header line 730 | if (e==NULL) break; //Shouldn't happen. 731 | e[0]=0; //Zero-terminate header 732 | httpdParseHeader(p, conn); //and parse it. 733 | p=e+2; //Skip /r/n (now /0/n) 734 | } 735 | //If we don't need to receive post data, we can send the response now. 736 | if (conn->post->len==0) { 737 | httpdProcessRequest(conn); 738 | } 739 | } 740 | } else if (conn->post->len!=0) { 741 | //This byte is a POST byte. 742 | conn->post->buff[conn->post->buffLen++]=data[x]; 743 | conn->post->received++; 744 | conn->hostName=NULL; 745 | if (conn->post->buffLen >= conn->post->buffSize || conn->post->received == conn->post->len) { 746 | //Received a chunk of post data 747 | conn->post->buff[conn->post->buffLen]=0; //zero-terminate, in case the cgi handler knows it can use strings 748 | //Process the data 749 | if (conn->cgi) { 750 | r=conn->cgi(conn); 751 | if (r==HTTPD_CGI_DONE) { 752 | httpdCgiIsDone(conn); 753 | } 754 | } else { 755 | //No CGI fn set yet: probably first call. Allow httpdProcessRequest to choose CGI and 756 | //call it the first time. 757 | httpdProcessRequest(conn); 758 | } 759 | conn->post->buffLen = 0; 760 | } 761 | } else { 762 | //Let cgi handle data if it registered a recvHdl callback. If not, ignore. 763 | if (conn->recvHdl) { 764 | r=conn->recvHdl(conn, data+x, len-x); 765 | if (r==HTTPD_CGI_DONE) { 766 | httpd_printf("Recvhdl returned DONE\n"); 767 | httpdCgiIsDone(conn); 768 | //We assume the recvhdlr has sent something; we'll kill the sock in the sent callback. 769 | } 770 | break; //ignore rest of data, recvhdl has parsed it. 771 | } else { 772 | httpd_printf("Eh? Got unexpected data from client. %s\n", data); 773 | } 774 | } 775 | } 776 | if (conn->conn) httpdFlushSendBuffer(conn); 777 | free(sendBuff); 778 | httpdPlatUnlock(); 779 | } 780 | 781 | //The platform layer should ALWAYS call this function, regardless if the connection is closed by the server 782 | //or by the client. 783 | void ICACHE_FLASH_ATTR httpdDisconCb(ConnTypePtr rconn, char *remIp, int remPort) { 784 | httpdPlatLock(); 785 | HttpdConnData *hconn=httpdFindConnData(rconn, remIp, remPort); 786 | if (hconn==NULL) { 787 | httpdPlatUnlock(); 788 | return; 789 | } 790 | httpd_printf("Pool slot %d: socket closed.\n", hconn->slot); 791 | hconn->conn=NULL; //indicate cgi the connection is gone 792 | if (hconn->cgi) hconn->cgi(hconn); //Execute cgi fn if needed 793 | httpdRetireConn(hconn); 794 | httpdPlatUnlock(); 795 | } 796 | 797 | 798 | int ICACHE_FLASH_ATTR httpdConnectCb(ConnTypePtr conn, char *remIp, int remPort) { 799 | int i; 800 | httpdPlatLock(); 801 | //Find empty conndata in pool 802 | for (i=0; ipriv=malloc(sizeof(HttpdPriv)); 817 | memset(connData[i]->priv, 0, sizeof(HttpdPriv)); 818 | connData[i]->conn=conn; 819 | connData[i]->slot=i; 820 | connData[i]->priv->headPos=0; 821 | connData[i]->post=malloc(sizeof(HttpdPostData)); 822 | if (connData[i]->post==NULL) { 823 | printf("Out of memory allocating connData post struct!\n"); 824 | httpdPlatUnlock(); 825 | return 0; 826 | } 827 | memset(connData[i]->post, 0, sizeof(HttpdPostData)); 828 | connData[i]->post->buff=NULL; 829 | connData[i]->post->buffLen=0; 830 | connData[i]->post->received=0; 831 | connData[i]->post->len=-1; 832 | connData[i]->hostName=NULL; 833 | connData[i]->remote_port=remPort; 834 | connData[i]->priv->sendBacklog=NULL; 835 | connData[i]->priv->sendBacklogSize=0; 836 | memcpy(connData[i]->remote_ip, remIp, 4); 837 | 838 | httpdPlatUnlock(); 839 | return 1; 840 | } 841 | 842 | //Httpd initialization routine. Call this to kick off webserver functionality. 843 | void ICACHE_FLASH_ATTR httpdInit(HttpdBuiltInUrl *fixedUrls, int port) { 844 | int i; 845 | 846 | for (i=0; i wrote this file. As long as you retain 9 | * this notice you can do whatever you want with this stuff. If we meet some day, 10 | * and you think this stuff is worth it, you can buy me a beer in return. 11 | * ---------------------------------------------------------------------------- 12 | */ 13 | 14 | #include 15 | #include "httpdespfs.h" 16 | #include "espfs.h" 17 | #include "espfsformat.h" 18 | 19 | // The static files marked with FLAG_GZIP are compressed and will be served with GZIP compression. 20 | // If the client does not advertise that he accepts GZIP send following warning message (telnet users for e.g.) 21 | 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"; 22 | 23 | 24 | //This is a catch-all cgi function. It takes the url passed to it, looks up the corresponding 25 | //path in the filesystem and if it exists, passes the file through. This simulates what a normal 26 | //webserver would do with static files. 27 | int ICACHE_FLASH_ATTR cgiEspFsHook(HttpdConnData *connData) { 28 | EspFsFile *file=connData->cgiData; 29 | int len; 30 | char buff[1024]; 31 | char acceptEncodingBuffer[64]; 32 | int isGzip; 33 | 34 | if (connData->conn==NULL) { 35 | //Connection aborted. Clean up. 36 | espFsClose(file); 37 | return HTTPD_CGI_DONE; 38 | } 39 | 40 | //First call to this cgi. 41 | if (file==NULL) { 42 | if (connData->cgiArg != NULL) { 43 | //Open a different file than provided in http request. 44 | //Common usage: {"/", cgiEspFsHook, "/index.html"} will show content of index.html without actual redirect to that file if host root was requested 45 | file = espFsOpen((char*)connData->cgiArg); 46 | } else { 47 | //Open the file so we can read it. 48 | file = espFsOpen(connData->url); 49 | } 50 | 51 | if (file==NULL) { 52 | return HTTPD_CGI_NOTFOUND; 53 | } 54 | 55 | // The gzip checking code is intentionally without #ifdefs because checking 56 | // for FLAG_GZIP (which indicates gzip compressed file) is very easy, doesn't 57 | // mean additional overhead and is actually safer to be on at all times. 58 | // If there are no gzipped files in the image, the code bellow will not cause any harm. 59 | 60 | // Check if requested file was GZIP compressed 61 | isGzip = espFsFlags(file) & FLAG_GZIP; 62 | if (isGzip) { 63 | // Check the browser's "Accept-Encoding" header. If the client does not 64 | // advertise that he accepts GZIP send a warning message (telnet users for e.g.) 65 | httpdGetHeader(connData, "Accept-Encoding", acceptEncodingBuffer, 64); 66 | if (strstr(acceptEncodingBuffer, "gzip") == NULL) { 67 | //No Accept-Encoding: gzip header present 68 | httpdSend(connData, gzipNonSupportedMessage, -1); 69 | espFsClose(file); 70 | return HTTPD_CGI_DONE; 71 | } 72 | } 73 | 74 | connData->cgiData=file; 75 | httpdStartResponse(connData, 200); 76 | httpdHeader(connData, "Content-Type", httpdGetMimetype(connData->url)); 77 | if (isGzip) { 78 | httpdHeader(connData, "Content-Encoding", "gzip"); 79 | } 80 | httpdHeader(connData, "Cache-Control", "max-age=3600, must-revalidate"); 81 | httpdEndHeaders(connData); 82 | return HTTPD_CGI_MORE; 83 | } 84 | 85 | len=espFsRead(file, buff, 1024); 86 | if (len>0) httpdSend(connData, buff, len); 87 | if (len!=1024) { 88 | //We're done. 89 | espFsClose(file); 90 | return HTTPD_CGI_DONE; 91 | } else { 92 | //Ok, till next time. 93 | return HTTPD_CGI_MORE; 94 | } 95 | } 96 | 97 | 98 | //cgiEspFsTemplate can be used as a template. 99 | 100 | typedef struct { 101 | EspFsFile *file; 102 | void *tplArg; 103 | char token[64]; 104 | int tokenPos; 105 | } TplData; 106 | 107 | typedef void (* TplCallback)(HttpdConnData *connData, char *token, void **arg); 108 | 109 | int ICACHE_FLASH_ATTR cgiEspFsTemplate(HttpdConnData *connData) { 110 | TplData *tpd=connData->cgiData; 111 | int len; 112 | int x, sp=0; 113 | char *e=NULL; 114 | char buff[1025]; 115 | 116 | if (connData->conn==NULL) { 117 | //Connection aborted. Clean up. 118 | ((TplCallback)(connData->cgiArg))(connData, NULL, &tpd->tplArg); 119 | espFsClose(tpd->file); 120 | free(tpd); 121 | return HTTPD_CGI_DONE; 122 | } 123 | 124 | if (tpd==NULL) { 125 | //First call to this cgi. Open the file so we can read it. 126 | tpd=(TplData *)malloc(sizeof(TplData)); 127 | if (tpd==NULL) return HTTPD_CGI_NOTFOUND; 128 | tpd->file=espFsOpen(connData->url); 129 | tpd->tplArg=NULL; 130 | tpd->tokenPos=-1; 131 | if (tpd->file==NULL) { 132 | espFsClose(tpd->file); 133 | free(tpd); 134 | return HTTPD_CGI_NOTFOUND; 135 | } 136 | if (espFsFlags(tpd->file) & FLAG_GZIP) { 137 | httpd_printf("cgiEspFsTemplate: Trying to use gzip-compressed file %s as template!\n", connData->url); 138 | espFsClose(tpd->file); 139 | free(tpd); 140 | return HTTPD_CGI_NOTFOUND; 141 | } 142 | connData->cgiData=tpd; 143 | httpdStartResponse(connData, 200); 144 | httpdHeader(connData, "Content-Type", httpdGetMimetype(connData->url)); 145 | httpdEndHeaders(connData); 146 | return HTTPD_CGI_MORE; 147 | } 148 | 149 | len=espFsRead(tpd->file, buff, 1024); 150 | if (len>0) { 151 | sp=0; 152 | e=buff; 153 | for (x=0; xtokenPos==-1) { 155 | //Inside ordinary text. 156 | if (buff[x]=='%') { 157 | //Send raw data up to now 158 | if (sp!=0) httpdSend(connData, e, sp); 159 | sp=0; 160 | //Go collect token chars. 161 | tpd->tokenPos=0; 162 | } else { 163 | sp++; 164 | } 165 | } else { 166 | if (buff[x]=='%') { 167 | if (tpd->tokenPos==0) { 168 | //This is the second % of a %% escape string. 169 | //Send a single % and resume with the normal program flow. 170 | httpdSend(connData, "%", 1); 171 | } else { 172 | //This is an actual token. 173 | tpd->token[tpd->tokenPos++]=0; //zero-terminate token 174 | ((TplCallback)(connData->cgiArg))(connData, tpd->token, &tpd->tplArg); 175 | } 176 | //Go collect normal chars again. 177 | e=&buff[x+1]; 178 | tpd->tokenPos=-1; 179 | } else { 180 | if (tpd->tokenPos<(sizeof(tpd->token)-1)) tpd->token[tpd->tokenPos++]=buff[x]; 181 | } 182 | } 183 | } 184 | } 185 | //Send remaining bit. 186 | if (sp!=0) httpdSend(connData, e, sp); 187 | if (len!=1024) { 188 | //We're done. 189 | ((TplCallback)(connData->cgiArg))(connData, NULL, &tpd->tplArg); 190 | espFsClose(tpd->file); 191 | free(tpd); 192 | return HTTPD_CGI_DONE; 193 | } else { 194 | //Ok, till next time. 195 | return HTTPD_CGI_MORE; 196 | } 197 | } 198 | 199 | -------------------------------------------------------------------------------- /core/sha1.c: -------------------------------------------------------------------------------- 1 | /* This code is public-domain - it is based on libcrypt 2 | * placed in the public domain by Wei Dai and other contributors. 3 | */ 4 | // gcc -Wall -DSHA1TEST -o sha1test sha1.c && ./sha1test 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | #include "sha1.h" 11 | 12 | //according to http://ip.cadence.com/uploads/pdf/xtensalx_overview_handbook.pdf 13 | // the cpu is normally defined as little ending, but can be big endian too. 14 | // for the esp this seems to work 15 | //#define SHA_BIG_ENDIAN 16 | 17 | 18 | 19 | /* code */ 20 | #define SHA1_K0 0x5a827999 21 | #define SHA1_K20 0x6ed9eba1 22 | #define SHA1_K40 0x8f1bbcdc 23 | #define SHA1_K60 0xca62c1d6 24 | 25 | void ICACHE_FLASH_ATTR sha1_init(sha1nfo *s) { 26 | s->state[0] = 0x67452301; 27 | s->state[1] = 0xefcdab89; 28 | s->state[2] = 0x98badcfe; 29 | s->state[3] = 0x10325476; 30 | s->state[4] = 0xc3d2e1f0; 31 | s->byteCount = 0; 32 | s->bufferOffset = 0; 33 | } 34 | 35 | uint32_t ICACHE_FLASH_ATTR sha1_rol32(uint32_t number, uint8_t bits) { 36 | return ((number << bits) | (number >> (32-bits))); 37 | } 38 | 39 | void ICACHE_FLASH_ATTR sha1_hashBlock(sha1nfo *s) { 40 | uint8_t i; 41 | uint32_t a,b,c,d,e,t; 42 | 43 | a=s->state[0]; 44 | b=s->state[1]; 45 | c=s->state[2]; 46 | d=s->state[3]; 47 | e=s->state[4]; 48 | for (i=0; i<80; i++) { 49 | if (i>=16) { 50 | t = s->buffer[(i+13)&15] ^ s->buffer[(i+8)&15] ^ s->buffer[(i+2)&15] ^ s->buffer[i&15]; 51 | s->buffer[i&15] = sha1_rol32(t,1); 52 | } 53 | if (i<20) { 54 | t = (d ^ (b & (c ^ d))) + SHA1_K0; 55 | } else if (i<40) { 56 | t = (b ^ c ^ d) + SHA1_K20; 57 | } else if (i<60) { 58 | t = ((b & c) | (d & (b | c))) + SHA1_K40; 59 | } else { 60 | t = (b ^ c ^ d) + SHA1_K60; 61 | } 62 | t+=sha1_rol32(a,5) + e + s->buffer[i&15]; 63 | e=d; 64 | d=c; 65 | c=sha1_rol32(b,30); 66 | b=a; 67 | a=t; 68 | } 69 | s->state[0] += a; 70 | s->state[1] += b; 71 | s->state[2] += c; 72 | s->state[3] += d; 73 | s->state[4] += e; 74 | } 75 | 76 | void ICACHE_FLASH_ATTR sha1_addUncounted(sha1nfo *s, uint8_t data) { 77 | uint8_t * const b = (uint8_t*) s->buffer; 78 | #ifdef SHA_BIG_ENDIAN 79 | b[s->bufferOffset] = data; 80 | #else 81 | b[s->bufferOffset ^ 3] = data; 82 | #endif 83 | s->bufferOffset++; 84 | if (s->bufferOffset == BLOCK_LENGTH) { 85 | sha1_hashBlock(s); 86 | s->bufferOffset = 0; 87 | } 88 | } 89 | 90 | void ICACHE_FLASH_ATTR sha1_writebyte(sha1nfo *s, uint8_t data) { 91 | ++s->byteCount; 92 | sha1_addUncounted(s, data); 93 | } 94 | 95 | void ICACHE_FLASH_ATTR sha1_write(sha1nfo *s, const char *data, size_t len) { 96 | for (;len--;) sha1_writebyte(s, (uint8_t) *data++); 97 | } 98 | 99 | void ICACHE_FLASH_ATTR sha1_pad(sha1nfo *s) { 100 | // Implement SHA-1 padding (fips180-2 §5.1.1) 101 | 102 | // Pad with 0x80 followed by 0x00 until the end of the block 103 | sha1_addUncounted(s, 0x80); 104 | while (s->bufferOffset != 56) sha1_addUncounted(s, 0x00); 105 | 106 | // Append length in the last 8 bytes 107 | sha1_addUncounted(s, 0); // We're only using 32 bit lengths 108 | sha1_addUncounted(s, 0); // But SHA-1 supports 64 bit lengths 109 | sha1_addUncounted(s, 0); // So zero pad the top bits 110 | sha1_addUncounted(s, s->byteCount >> 29); // Shifting to multiply by 8 111 | sha1_addUncounted(s, s->byteCount >> 21); // as SHA-1 supports bitstreams as well as 112 | sha1_addUncounted(s, s->byteCount >> 13); // byte. 113 | sha1_addUncounted(s, s->byteCount >> 5); 114 | sha1_addUncounted(s, s->byteCount << 3); 115 | } 116 | 117 | uint8_t* ICACHE_FLASH_ATTR sha1_result(sha1nfo *s) { 118 | // Pad to complete the last block 119 | sha1_pad(s); 120 | 121 | #ifndef SHA_BIG_ENDIAN 122 | // Swap byte order back 123 | int i; 124 | for (i=0; i<5; i++) { 125 | s->state[i]= 126 | (((s->state[i])<<24)& 0xff000000) 127 | | (((s->state[i])<<8) & 0x00ff0000) 128 | | (((s->state[i])>>8) & 0x0000ff00) 129 | | (((s->state[i])>>24)& 0x000000ff); 130 | } 131 | #endif 132 | 133 | // Return pointer to hash (20 characters) 134 | return (uint8_t*) s->state; 135 | } 136 | 137 | #define HMAC_IPAD 0x36 138 | #define HMAC_OPAD 0x5c 139 | 140 | void ICACHE_FLASH_ATTR sha1_initHmac(sha1nfo *s, const uint8_t* key, int keyLength) { 141 | uint8_t i; 142 | memset(s->keyBuffer, 0, BLOCK_LENGTH); 143 | if (keyLength > BLOCK_LENGTH) { 144 | // Hash long keys 145 | sha1_init(s); 146 | for (;keyLength--;) sha1_writebyte(s, *key++); 147 | memcpy(s->keyBuffer, sha1_result(s), HASH_LENGTH); 148 | } else { 149 | // Block length keys are used as is 150 | memcpy(s->keyBuffer, key, keyLength); 151 | } 152 | // Start inner hash 153 | sha1_init(s); 154 | for (i=0; ikeyBuffer[i] ^ HMAC_IPAD); 156 | } 157 | } 158 | 159 | uint8_t* ICACHE_FLASH_ATTR sha1_resultHmac(sha1nfo *s) { 160 | uint8_t i; 161 | // Complete inner hash 162 | memcpy(s->innerHash,sha1_result(s),HASH_LENGTH); 163 | // Calculate outer hash 164 | sha1_init(s); 165 | for (i=0; ikeyBuffer[i] ^ HMAC_OPAD); 166 | for (i=0; iinnerHash[i]); 167 | return sha1_result(s); 168 | } 169 | -------------------------------------------------------------------------------- /espfs/espfs.c: -------------------------------------------------------------------------------- 1 | /* 2 | This is a simple read-only implementation of a file system. It uses a block of data coming from the 3 | mkespfsimg tool, and can use that block to do abstracted operations on the files that are in there. 4 | It's written for use with httpd, but doesn't need to be used as such. 5 | */ 6 | 7 | /* 8 | * ---------------------------------------------------------------------------- 9 | * "THE BEER-WARE LICENSE" (Revision 42): 10 | * Jeroen Domburg wrote this file. As long as you retain 11 | * this notice you can do whatever you want with this stuff. If we meet some day, 12 | * and you think this stuff is worth it, you can buy me a beer in return. 13 | * ---------------------------------------------------------------------------- 14 | */ 15 | 16 | 17 | //These routines can also be tested by comping them in with the espfstest tool. This 18 | //simplifies debugging, but needs some slightly different headers. The #ifdef takes 19 | //care of that. 20 | 21 | #ifdef __ets__ 22 | //esp build 23 | #include 24 | #else 25 | //Test build 26 | #include 27 | #include 28 | #include 29 | #include 30 | #define ICACHE_FLASH_ATTR 31 | #endif 32 | 33 | #include "espfsformat.h" 34 | #include "espfs.h" 35 | 36 | #ifdef ESPFS_HEATSHRINK 37 | #include "heatshrink_config_custom.h" 38 | #include "heatshrink_decoder.h" 39 | #endif 40 | 41 | static char* espFsData = NULL; 42 | 43 | 44 | struct EspFsFile { 45 | EspFsHeader *header; 46 | char decompressor; 47 | int32_t posDecomp; 48 | char *posStart; 49 | char *posComp; 50 | void *decompData; 51 | }; 52 | 53 | /* 54 | Available locations, at least in my flash, with boundaries partially guessed. This 55 | is using 0.9.1/0.9.2 SDK on a not-too-new module. 56 | 0x00000 (0x10000): Code/data (RAM data?) 57 | 0x10000 (0x02000): Gets erased by something? 58 | 0x12000 (0x2E000): Free (filled with zeroes) (parts used by ESPCloud and maybe SSL) 59 | 0x40000 (0x20000): Code/data (ROM data?) 60 | 0x60000 (0x1C000): Free 61 | 0x7c000 (0x04000): Param store 62 | 0x80000 - end of flash 63 | 64 | Accessing the flash through the mem emulation at 0x40200000 is a bit hairy: All accesses 65 | *must* be aligned 32-bit accesses. Reading a short, byte or unaligned word will result in 66 | a memory exception, crashing the program. 67 | */ 68 | 69 | #ifndef ESP32 70 | #define FLASH_BASE_ADDR 0x40200000 71 | #else 72 | //ESP32 IRAM addresses start at 0x40000000, but the IRAM segment actually is mapped 73 | //starting from SPI address 0x40000. 74 | #define FLASH_BASE_ADDR 0x40040000 75 | #endif 76 | 77 | EspFsInitResult ICACHE_FLASH_ATTR espFsInit(void *flashAddress) { 78 | if((uint32_t)flashAddress > 0x40000000) { 79 | flashAddress = (void*)((uint32_t)flashAddress-FLASH_BASE_ADDR); 80 | } 81 | 82 | // base address must be aligned to 4 bytes 83 | if (((int)flashAddress & 3) != 0) { 84 | return ESPFS_INIT_RESULT_BAD_ALIGN; 85 | } 86 | 87 | // check if there is valid header at address 88 | EspFsHeader testHeader; 89 | spi_flash_read((uint32)flashAddress, (uint32*)&testHeader, sizeof(EspFsHeader)); 90 | if (testHeader.magic != ESPFS_MAGIC) { 91 | return ESPFS_INIT_RESULT_NO_IMAGE; 92 | } 93 | 94 | espFsData = (char *)flashAddress; 95 | return ESPFS_INIT_RESULT_OK; 96 | } 97 | 98 | //Copies len bytes over from dst to src, but does it using *only* 99 | //aligned 32-bit reads. Yes, it's no too optimized but it's short and sweet and it works. 100 | 101 | //ToDo: perhaps memcpy also does unaligned accesses? 102 | #ifdef __ets__ 103 | void ICACHE_FLASH_ATTR readFlashUnaligned(char *dst, char *src, int len) { 104 | uint8_t src_offset = ((uint32_t)src) & 3; 105 | uint32_t src_address = ((uint32_t)src) - src_offset; 106 | 107 | uint32_t tmp_buf[len/4 + 2]; 108 | spi_flash_read((uint32)src_address, (uint32*)tmp_buf, len+src_offset); 109 | memcpy(dst, ((uint8_t*)tmp_buf)+src_offset, len); 110 | } 111 | #else 112 | #define readFlashUnaligned memcpy 113 | #endif 114 | 115 | // Returns flags of opened file. 116 | int ICACHE_FLASH_ATTR espFsFlags(EspFsFile *fh) { 117 | if (fh == NULL) { 118 | httpd_printf("File handle not ready\n"); 119 | return -1; 120 | } 121 | 122 | int8_t flags; 123 | readFlashUnaligned((char*)&flags, (char*)&fh->header->flags, 1); 124 | return (int)flags; 125 | } 126 | 127 | //Open a file and return a pointer to the file desc struct. 128 | EspFsFile ICACHE_FLASH_ATTR *espFsOpen(char *fileName) { 129 | if (espFsData == NULL) { 130 | httpd_printf("Call espFsInit first!\n"); 131 | return NULL; 132 | } 133 | char *p=espFsData; 134 | char *hpos; 135 | char namebuf[256]; 136 | EspFsHeader h; 137 | EspFsFile *r; 138 | //Strip initial slashes 139 | while(fileName[0]=='/') fileName++; 140 | //Go find that file! 141 | while(1) { 142 | hpos=p; 143 | //Grab the next file header. 144 | spi_flash_read((uint32)p, (uint32*)&h, sizeof(EspFsHeader)); 145 | 146 | if (h.magic!=ESPFS_MAGIC) { 147 | httpd_printf("Magic mismatch. EspFS image broken.\n"); 148 | return NULL; 149 | } 150 | if (h.flags&FLAG_LASTFILE) { 151 | httpd_printf("End of image.\n"); 152 | return NULL; 153 | } 154 | //Grab the name of the file. 155 | p+=sizeof(EspFsHeader); 156 | spi_flash_read((uint32)p, (uint32*)&namebuf, sizeof(namebuf)); 157 | // httpd_printf("Found file '%s'. Namelen=%x fileLenComp=%x, compr=%d flags=%d\n", 158 | // namebuf, (unsigned int)h.nameLen, (unsigned int)h.fileLenComp, h.compression, h.flags); 159 | if (strcmp(namebuf, fileName)==0) { 160 | //Yay, this is the file we need! 161 | p+=h.nameLen; //Skip to content. 162 | r=(EspFsFile *)malloc(sizeof(EspFsFile)); //Alloc file desc mem 163 | // httpd_printf("Alloc %p\n", r); 164 | if (r==NULL) return NULL; 165 | r->header=(EspFsHeader *)hpos; 166 | r->decompressor=h.compression; 167 | r->posComp=p; 168 | r->posStart=p; 169 | r->posDecomp=0; 170 | if (h.compression==COMPRESS_NONE) { 171 | r->decompData=NULL; 172 | #ifdef ESPFS_HEATSHRINK 173 | } else if (h.compression==COMPRESS_HEATSHRINK) { 174 | //File is compressed with Heatshrink. 175 | char parm; 176 | heatshrink_decoder *dec; 177 | //Decoder params are stored in 1st byte. 178 | readFlashUnaligned(&parm, r->posComp, 1); 179 | r->posComp++; 180 | httpd_printf("Heatshrink compressed file; decode parms = %x\n", parm); 181 | dec=heatshrink_decoder_alloc(16, (parm>>4)&0xf, parm&0xf); 182 | r->decompData=dec; 183 | #endif 184 | } else { 185 | httpd_printf("Invalid compression: %d\n", h.compression); 186 | free(r); 187 | return NULL; 188 | } 189 | return r; 190 | } 191 | //We don't need this file. Skip name and file 192 | p+=h.nameLen+h.fileLenComp; 193 | if ((int)p&3) p+=4-((int)p&3); //align to next 32bit val 194 | } 195 | } 196 | 197 | //Read len bytes from the given file into buff. Returns the actual amount of bytes read. 198 | int ICACHE_FLASH_ATTR espFsRead(EspFsFile *fh, char *buff, int len) { 199 | int flen; 200 | #ifdef ESPFS_HEATSHRINK 201 | int fdlen; 202 | #endif 203 | if (fh==NULL) return 0; 204 | 205 | readFlashUnaligned((char*)&flen, (char*)&fh->header->fileLenComp, 4); 206 | //Cache file length. 207 | //Do stuff depending on the way the file is compressed. 208 | if (fh->decompressor==COMPRESS_NONE) { 209 | int toRead; 210 | toRead=flen-(fh->posComp-fh->posStart); 211 | if (len>toRead) len=toRead; 212 | // httpd_printf("Reading %d bytes from %x\n", len, (unsigned int)fh->posComp); 213 | readFlashUnaligned(buff, fh->posComp, len); 214 | fh->posDecomp+=len; 215 | fh->posComp+=len; 216 | // httpd_printf("Done reading %d bytes, pos=%x\n", len, fh->posComp); 217 | return len; 218 | #ifdef ESPFS_HEATSHRINK 219 | } else if (fh->decompressor==COMPRESS_HEATSHRINK) { 220 | readFlashUnaligned((char*)&fdlen, (char*)&fh->header->fileLenDecomp, 4); 221 | int decoded=0; 222 | size_t elen, rlen; 223 | char ebuff[16]; 224 | heatshrink_decoder *dec=(heatshrink_decoder *)fh->decompData; 225 | // httpd_printf("Alloc %p\n", dec); 226 | if (fh->posDecomp == fdlen) { 227 | return 0; 228 | } 229 | 230 | // We must ensure that whole file is decompressed and written to output buffer. 231 | // This means even when there is no input data (elen==0) try to poll decoder until 232 | // posDecomp equals decompressed file length 233 | 234 | while(decodedposComp - fh->posStart); 238 | if (elen>0) { 239 | readFlashUnaligned(ebuff, fh->posComp, 16); 240 | heatshrink_decoder_sink(dec, (uint8_t *)ebuff, (elen>16)?16:elen, &rlen); 241 | fh->posComp+=rlen; 242 | } 243 | //Grab decompressed data and put into buff 244 | heatshrink_decoder_poll(dec, (uint8_t *)buff, len-decoded, &rlen); 245 | fh->posDecomp+=rlen; 246 | buff+=rlen; 247 | decoded+=rlen; 248 | 249 | // httpd_printf("Elen %d rlen %d d %d pd %ld fdl %d\n",elen,rlen,decoded, fh->posDecomp, fdlen); 250 | 251 | if (elen == 0) { 252 | if (fh->posDecomp == fdlen) { 253 | // httpd_printf("Decoder finish\n"); 254 | heatshrink_decoder_finish(dec); 255 | } 256 | return decoded; 257 | } 258 | } 259 | return len; 260 | #endif 261 | } 262 | return 0; 263 | } 264 | 265 | //Close the file. 266 | void ICACHE_FLASH_ATTR espFsClose(EspFsFile *fh) { 267 | if (fh==NULL) return; 268 | #ifdef ESPFS_HEATSHRINK 269 | if (fh->decompressor==COMPRESS_HEATSHRINK) { 270 | heatshrink_decoder *dec=(heatshrink_decoder *)fh->decompData; 271 | heatshrink_decoder_free(dec); 272 | // httpd_printf("Freed %p\n", dec); 273 | } 274 | #endif 275 | // httpd_printf("Freed %p\n", fh); 276 | free(fh); 277 | } 278 | 279 | 280 | 281 | -------------------------------------------------------------------------------- /espfs/espfsformat.h: -------------------------------------------------------------------------------- 1 | #ifndef ESPROFSFORMAT_H 2 | #define ESPROFSFORMAT_H 3 | 4 | /* 5 | Stupid cpio-like tool to make read-only 'filesystems' that live on the flash SPI chip of the module. 6 | Can (will) use lzf compression (when I come around to it) to make shit quicker. Aligns names, files, 7 | headers on 4-byte boundaries so the SPI abstraction hardware in the ESP8266 doesn't crap on itself 8 | when trying to do a <4byte or unaligned read. 9 | */ 10 | 11 | /* 12 | The idea 'borrows' from cpio: it's basically a concatenation of {header, filename, file} data. 13 | Header, filename and file data is 32-bit aligned. The last file is indicated by data-less header 14 | with the FLAG_LASTFILE flag set. 15 | */ 16 | 17 | 18 | #define FLAG_LASTFILE (1<<0) 19 | #define FLAG_GZIP (1<<1) 20 | #define COMPRESS_NONE 0 21 | #define COMPRESS_HEATSHRINK 1 22 | #define ESPFS_MAGIC 0x73665345 23 | 24 | typedef struct { 25 | int32_t magic; 26 | int8_t flags; 27 | int8_t compression; 28 | int16_t nameLen; 29 | int32_t fileLenComp; 30 | int32_t fileLenDecomp; 31 | } __attribute__((packed)) EspFsHeader; 32 | 33 | #endif -------------------------------------------------------------------------------- /espfs/espfstest/Makefile: -------------------------------------------------------------------------------- 1 | CFLAGS=-I../../lib/heatshrink -I.. -std=gnu99 -DESPFS_HEATSHRINK 2 | 3 | espfstest: main.o espfs.o heatshrink_decoder.o 4 | $(CC) -o $@ $^ 5 | 6 | espfs.o: ../espfs.c 7 | $(CC) $(CFLAGS) -c $^ -o $@ 8 | 9 | heatshrink_decoder.o: ../heatshrink_decoder.c 10 | $(CC) $(CFLAGS) -c $^ -o $@ 11 | 12 | clean: 13 | rm -f *.o espfstest 14 | -------------------------------------------------------------------------------- /espfs/espfstest/main.c: -------------------------------------------------------------------------------- 1 | /* 2 | Simple and stupid file decompressor for an espfs image. Mostly used as a testbed for espfs.c and 3 | the decompressors: code compiled natively is way easier to debug using gdb et all :) 4 | */ 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | 15 | #include "espfs.h" 16 | 17 | char *espFsData; 18 | 19 | int main(int argc, char **argv) { 20 | int f, out; 21 | int len; 22 | char buff[128]; 23 | EspFsFile *ef; 24 | off_t size; 25 | EspFsInitResult ir; 26 | 27 | if (argc!=3) { 28 | printf("Usage: %s espfs-image file\nExpands file from the espfs-image archive.\n", argv[0]); 29 | exit(0); 30 | } 31 | 32 | f=open(argv[1], O_RDONLY); 33 | if (f<=0) { 34 | perror(argv[1]); 35 | exit(1); 36 | } 37 | size=lseek(f, 0, SEEK_END); 38 | espFsData=mmap(NULL, size, PROT_READ, MAP_SHARED, f, 0); 39 | if (espFsData==MAP_FAILED) { 40 | perror("mmap"); 41 | exit(1); 42 | } 43 | 44 | ir=espFsInit(espFsData); 45 | if (ir != ESPFS_INIT_RESULT_OK) { 46 | printf("Couldn't init espfs filesystem (code %d)\n", ir); 47 | exit(1); 48 | } 49 | 50 | ef=espFsOpen(argv[2]); 51 | if (ef==NULL) { 52 | printf("Couldn't find %s in image.\n", argv[2]); 53 | exit(1); 54 | } 55 | 56 | out=open(argv[2], O_WRONLY|O_CREAT|O_TRUNC, 0644); 57 | if (out<=0) { 58 | perror(argv[2]); 59 | exit(1); 60 | } 61 | 62 | while ((len=espFsRead(ef, buff, 128))!=0) { 63 | write(out, buff, len); 64 | } 65 | espFsClose(ef); 66 | //munmap, close, ... I can't be bothered. 67 | } 68 | -------------------------------------------------------------------------------- /espfs/heatshrink_config_custom.h: -------------------------------------------------------------------------------- 1 | //Heatshrink config for the decompressor. 2 | #ifndef HEATSHRINK_CONFIG_H 3 | #define HEATSHRINK_CONFIG_H 4 | 5 | /* Should functionality assuming dynamic allocation be used? */ 6 | #define HEATSHRINK_DYNAMIC_ALLOC 1 7 | 8 | #if HEATSHRINK_DYNAMIC_ALLOC 9 | /* Optional replacement of malloc/free */ 10 | #define HEATSHRINK_MALLOC(SZ) malloc(SZ) 11 | #define HEATSHRINK_FREE(P, SZ) free(P) 12 | #else 13 | /* Required parameters for static configuration */ 14 | #define HEATSHRINK_STATIC_INPUT_BUFFER_SIZE 32 15 | #define HEATSHRINK_STATIC_WINDOW_BITS 8 16 | #define HEATSHRINK_STATIC_LOOKAHEAD_BITS 4 17 | #endif 18 | 19 | /* Turn on logging for debugging. */ 20 | #define HEATSHRINK_DEBUGGING_LOGS 0 21 | 22 | /* Use indexing for faster compression. (This requires additional space.) */ 23 | #define HEATSHRINK_USE_INDEX 1 24 | 25 | #endif 26 | -------------------------------------------------------------------------------- /espfs/heatshrink_decoder.c: -------------------------------------------------------------------------------- 1 | #include "espfs.h" 2 | #ifdef ESPFS_HEATSHRINK 3 | //Stupid wrapper so we don't have to move c-files around 4 | //Also loads httpd-specific config. 5 | 6 | #ifdef __ets__ 7 | //esp build 8 | 9 | #include 10 | 11 | #endif 12 | 13 | #include "heatshrink_config_custom.h" 14 | #include "../lib/heatshrink/heatshrink_decoder.c" 15 | 16 | 17 | #endif 18 | -------------------------------------------------------------------------------- /espfs/mkespfsimage/Makefile: -------------------------------------------------------------------------------- 1 | GZIP_COMPRESSION ?= no 2 | USE_HEATSHRINK ?= yes 3 | 4 | CFLAGS=-I../../lib/heatshrink -I../../include -I.. -std=gnu99 5 | ifeq ("$(GZIP_COMPRESSION)","yes") 6 | CFLAGS += -DESPFS_GZIP 7 | endif 8 | 9 | ifeq ("$(USE_HEATSHRINK)","yes") 10 | CFLAGS += -DESPFS_HEATSHRINK 11 | endif 12 | 13 | OBJS=main.o heatshrink_encoder.o 14 | TARGET=mkespfsimage 15 | 16 | $(TARGET): $(OBJS) 17 | ifeq ("$(GZIP_COMPRESSION)","yes") 18 | $(CC) -o $@ $^ -lz 19 | else 20 | $(CC) -o $@ $^ 21 | endif 22 | 23 | clean: 24 | rm -f $(TARGET) $(OBJS) -------------------------------------------------------------------------------- /espfs/mkespfsimage/heatshrink_encoder.c: -------------------------------------------------------------------------------- 1 | //Stupid wraparound include to make sure object file doesn't end up in heatshrink dir 2 | #ifdef ESPFS_HEATSHRINK 3 | #include "../lib/heatshrink/heatshrink_encoder.c" 4 | #endif -------------------------------------------------------------------------------- /espfs/mkespfsimage/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #ifdef __MINGW32__ 10 | #include 11 | #endif 12 | #include "espfs.h" 13 | #include "espfsformat.h" 14 | 15 | //Heatshrink 16 | #ifdef ESPFS_HEATSHRINK 17 | #include "heatshrink_common.h" 18 | #include "heatshrink_config.h" 19 | #include "heatshrink_encoder.h" 20 | #endif 21 | 22 | //Gzip 23 | #ifdef ESPFS_GZIP 24 | // If compiler complains about missing header, try running "sudo apt-get install zlib1g-dev" 25 | // to install missing package. 26 | #include 27 | #endif 28 | 29 | //Cygwin e.a. needs O_BINARY. Don't miscompile if it's not set. 30 | #ifndef O_BINARY 31 | #define O_BINARY 0 32 | #endif 33 | 34 | //Routines to convert host format to the endianness used in the xtensa 35 | short htoxs(short in) { 36 | char r[2]; 37 | r[0]=in; 38 | r[1]=in>>8; 39 | return *((short *)r); 40 | } 41 | 42 | int htoxl(int in) { 43 | unsigned char r[4]; 44 | r[0]=in; 45 | r[1]=in>>8; 46 | r[2]=in>>16; 47 | r[3]=in>>24; 48 | return *((int *)r); 49 | } 50 | 51 | #ifdef ESPFS_HEATSHRINK 52 | size_t compressHeatshrink(char *in, int insize, char *out, int outsize, int level) { 53 | char *inp=in; 54 | char *outp=out; 55 | size_t len; 56 | int ws[]={5, 6, 8, 11, 13}; 57 | int ls[]={3, 3, 4, 4, 4}; 58 | HSE_poll_res pres; 59 | HSE_sink_res sres; 60 | size_t r; 61 | if (level==-1) level=8; 62 | level=(level-1)/2; //level is now 0, 1, 2, 3, 4 63 | heatshrink_encoder *enc=heatshrink_encoder_alloc(ws[level], ls[level]); 64 | if (enc==NULL) { 65 | perror("allocating mem for heatshrink"); 66 | exit(1); 67 | } 68 | //Save encoder parms as first byte 69 | *outp=(ws[level]<<4)|ls[level]; 70 | outp++; outsize--; 71 | 72 | r=1; 73 | do { 74 | if (insize>0) { 75 | sres=heatshrink_encoder_sink(enc, inp, insize, &len); 76 | if (sres!=HSER_SINK_OK) break; 77 | inp+=len; insize-=len; 78 | if (insize==0) heatshrink_encoder_finish(enc); 79 | } 80 | do { 81 | pres=heatshrink_encoder_poll(enc, outp, outsize, &len); 82 | if (pres!=HSER_POLL_MORE && pres!=HSER_POLL_EMPTY) break; 83 | outp+=len; outsize-=len; 84 | r+=len; 85 | } while (pres==HSER_POLL_MORE); 86 | } while (insize!=0); 87 | 88 | if (insize!=0) { 89 | fprintf(stderr, "Heatshrink: Bug? insize is still %d. sres=%d pres=%d\n", insize, sres, pres); 90 | exit(1); 91 | } 92 | 93 | heatshrink_encoder_free(enc); 94 | return r; 95 | } 96 | #endif 97 | 98 | #ifdef ESPFS_GZIP 99 | size_t compressGzip(char *in, int insize, char *out, int outsize, int level) { 100 | z_stream stream; 101 | int zresult; 102 | 103 | stream.zalloc = Z_NULL; 104 | stream.zfree = Z_NULL; 105 | stream.opaque = Z_NULL; 106 | stream.next_in = in; 107 | stream.avail_in = insize; 108 | stream.next_out = out; 109 | stream.avail_out = outsize; 110 | // 31 -> 15 window bits + 16 for gzip 111 | zresult = deflateInit2 (&stream, level, Z_DEFLATED, 31, 8, Z_DEFAULT_STRATEGY); 112 | if (zresult != Z_OK) { 113 | fprintf(stderr, "DeflateInit2 failed with code %d\n", zresult); 114 | exit(1); 115 | } 116 | 117 | zresult = deflate(&stream, Z_FINISH); 118 | if (zresult != Z_STREAM_END) { 119 | fprintf(stderr, "Deflate failed with code %d\n", zresult); 120 | exit(1); 121 | } 122 | 123 | zresult = deflateEnd(&stream); 124 | if (zresult != Z_OK) { 125 | fprintf(stderr, "DeflateEnd failed with code %d\n", zresult); 126 | exit(1); 127 | } 128 | 129 | return stream.total_out; 130 | } 131 | 132 | char **gzipExtensions = NULL; 133 | 134 | int shouldCompressGzip(char *name) { 135 | char *ext = name + strlen(name); 136 | while (*ext != '.') { 137 | ext--; 138 | if (ext < name) { 139 | // no dot in file name -> no extension -> nothing to match against 140 | return 0; 141 | } 142 | } 143 | ext++; 144 | 145 | int i = 0; 146 | while (gzipExtensions[i] != NULL) { 147 | if (strcmp(ext,gzipExtensions[i]) == 0) { 148 | return 1; 149 | } 150 | i++; 151 | } 152 | 153 | return 0; 154 | } 155 | 156 | int parseGzipExtensions(char *input) { 157 | char *token; 158 | char *extList = input; 159 | int count = 2; // one for first element, second for terminator 160 | 161 | // count elements 162 | while (*extList != 0) { 163 | if (*extList == ',') count++; 164 | extList++; 165 | } 166 | 167 | // split string 168 | extList = input; 169 | gzipExtensions = malloc(count * sizeof(char*)); 170 | count = 0; 171 | token = strtok(extList, ","); 172 | while (token) { 173 | gzipExtensions[count++] = token; 174 | token = strtok(NULL, ","); 175 | } 176 | // terminate list 177 | gzipExtensions[count] = NULL; 178 | 179 | return 1; 180 | } 181 | #endif 182 | 183 | int handleFile(int f, char *name, int compression, int level, char **compName) { 184 | char *fdat, *cdat; 185 | off_t size, csize; 186 | EspFsHeader h; 187 | int nameLen; 188 | int8_t flags = 0; 189 | size=lseek(f, 0, SEEK_END); 190 | fdat=malloc(size); 191 | lseek(f, 0, SEEK_SET); 192 | read(f, fdat, size); 193 | 194 | 195 | #ifdef ESPFS_GZIP 196 | if (shouldCompressGzip(name)) { 197 | csize = size*3; 198 | if (csize<100) // gzip has some headers that do not fit when trying to compress small files 199 | csize = 100; // enlarge buffer if this is the case 200 | cdat=malloc(csize); 201 | csize=compressGzip(fdat, size, cdat, csize, level); 202 | compression = COMPRESS_NONE; 203 | flags = FLAG_GZIP; 204 | } else 205 | #endif 206 | if (compression==COMPRESS_NONE) { 207 | csize=size; 208 | cdat=fdat; 209 | #ifdef ESPFS_HEATSHRINK 210 | } else if (compression==COMPRESS_HEATSHRINK) { 211 | cdat=malloc(size*2); 212 | csize=compressHeatshrink(fdat, size, cdat, size*2, level); 213 | #endif 214 | } else { 215 | fprintf(stderr, "Unknown compression - %d\n", compression); 216 | exit(1); 217 | } 218 | 219 | if (csize>size) { 220 | //Compressing enbiggened this file. Revert to uncompressed store. 221 | compression=COMPRESS_NONE; 222 | csize=size; 223 | cdat=fdat; 224 | flags=0; 225 | } 226 | 227 | //Fill header data 228 | h.magic=('E'<<0)+('S'<<8)+('f'<<16)+('s'<<24); 229 | h.flags=flags; 230 | h.compression=compression; 231 | h.nameLen=nameLen=strlen(name)+1; 232 | if (h.nameLen&3) h.nameLen+=4-(h.nameLen&3); //Round to next 32bit boundary 233 | h.nameLen=htoxs(h.nameLen); 234 | h.fileLenComp=htoxl(csize); 235 | h.fileLenDecomp=htoxl(size); 236 | 237 | write(1, &h, sizeof(EspFsHeader)); 238 | write(1, name, nameLen); 239 | while (nameLen&3) { 240 | write(1, "\000", 1); 241 | nameLen++; 242 | } 243 | write(1, cdat, csize); 244 | //Pad out to 32bit boundary 245 | while (csize&3) { 246 | write(1, "\000", 1); 247 | csize++; 248 | } 249 | free(fdat); 250 | 251 | if (compName != NULL) { 252 | if (h.compression==COMPRESS_HEATSHRINK) { 253 | *compName = "heatshrink"; 254 | } else if (h.compression==COMPRESS_NONE) { 255 | if (h.flags & FLAG_GZIP) { 256 | *compName = "gzip"; 257 | } else { 258 | *compName = "none"; 259 | } 260 | } else { 261 | *compName = "unknown"; 262 | } 263 | } 264 | return size ? (csize*100)/size : 100; 265 | } 266 | 267 | //Write final dummy header with FLAG_LASTFILE set. 268 | void finishArchive() { 269 | EspFsHeader h; 270 | h.magic=('E'<<0)+('S'<<8)+('f'<<16)+('s'<<24); 271 | h.flags=FLAG_LASTFILE; 272 | h.compression=COMPRESS_NONE; 273 | h.nameLen=htoxs(0); 274 | h.fileLenComp=htoxl(0); 275 | h.fileLenDecomp=htoxl(0); 276 | write(1, &h, sizeof(EspFsHeader)); 277 | } 278 | 279 | int main(int argc, char **argv) { 280 | int f, x; 281 | char fileName[1024]; 282 | char *realName; 283 | struct stat statBuf; 284 | int serr; 285 | int rate; 286 | int err=0; 287 | int compType; //default compression type - heatshrink 288 | int compLvl=-1; 289 | 290 | #ifdef __MINGW32__ 291 | setmode(fileno(stdout), O_BINARY); 292 | #endif 293 | #ifdef ESPFS_HEATSHRINK 294 | compType = COMPRESS_HEATSHRINK; 295 | #else 296 | compType = COMPRESS_NONE; 297 | #endif 298 | 299 | for (x=1; x=x-2) { 301 | compType=atoi(argv[x+1]); 302 | x++; 303 | } else if (strcmp(argv[x], "-l")==0 && argc>=x-2) { 304 | compLvl=atoi(argv[x+1]); 305 | if (compLvl<1 || compLvl>9) err=1; 306 | x++; 307 | #ifdef ESPFS_GZIP 308 | } else if (strcmp(argv[x], "-g")==0 && argc>=x-2) { 309 | if (!parseGzipExtensions(argv[x+1])) err=1; 310 | x++; 311 | #endif 312 | } else { 313 | err=1; 314 | } 315 | } 316 | 317 | #ifdef ESPFS_GZIP 318 | if (gzipExtensions == NULL) { 319 | parseGzipExtensions(strdup("html,css,js,svg")); 320 | } 321 | #endif 322 | 323 | if (err) { 324 | fprintf(stderr, "%s - Program to create espfs images\n", argv[0]); 325 | fprintf(stderr, "Usage: \nfind | %s [-c compressor] [-l compression_level] ", argv[0]); 326 | #ifdef ESPFS_GZIP 327 | fprintf(stderr, "[-g gzipped_extensions] "); 328 | #endif 329 | fprintf(stderr, "> out.espfs\n"); 330 | fprintf(stderr, "Compressors:\n"); 331 | #ifdef ESPFS_HEATSHRINK 332 | fprintf(stderr, "0 - None\n1 - Heatshrink(default)\n"); 333 | #else 334 | fprintf(stderr, "0 - None(default)\n"); 335 | #endif 336 | fprintf(stderr, "\nCompression level: 1 is worst but low RAM usage, higher is better compression \nbut uses more ram on decompression. -1 = compressors default.\n"); 337 | #ifdef ESPFS_GZIP 338 | fprintf(stderr, "\nGzipped extensions: list of comma separated, case sensitive file extensions \nthat will be gzipped. Defaults to 'html,css,js'\n"); 339 | #endif 340 | exit(0); 341 | } 342 | 343 | while(fgets(fileName, sizeof(fileName), stdin)) { 344 | //Kill off '\n' at the end 345 | fileName[strlen(fileName)-1]=0; 346 | //Only include files 347 | serr=stat(fileName, &statBuf); 348 | if ((serr==0) && S_ISREG(statBuf.st_mode)) { 349 | //Strip off './' or '/' madness. 350 | realName=fileName; 351 | if (fileName[0]=='.') realName++; 352 | if (realName[0]=='/') realName++; 353 | f=open(fileName, O_RDONLY|O_BINARY); 354 | if (f>0) { 355 | char *compName = "unknown"; 356 | rate=handleFile(f, realName, compType, compLvl, &compName); 357 | fprintf(stderr, "%s (%d%%, %s)\n", realName, rate, compName); 358 | close(f); 359 | } else { 360 | perror(fileName); 361 | } 362 | } else { 363 | if (serr!=0) { 364 | perror(fileName); 365 | } 366 | } 367 | } 368 | finishArchive(); 369 | return 0; 370 | } 371 | 372 | -------------------------------------------------------------------------------- /include/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 | int ICACHE_FLASH_ATTR authBasic(HttpdConnData *connData); 21 | 22 | #endif -------------------------------------------------------------------------------- /include/captdns.h: -------------------------------------------------------------------------------- 1 | #ifndef CAPTDNS_H 2 | #define CAPTDNS_H 3 | void ICACHE_FLASH_ATTR captdnsInit(void); 4 | 5 | #endif -------------------------------------------------------------------------------- /include/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 | typedef struct { 10 | int type; 11 | int fw1Pos; 12 | int fw2Pos; 13 | int fwSize; 14 | char *tagName; 15 | } CgiUploadFlashDef; 16 | 17 | int cgiGetFirmwareNext(HttpdConnData *connData); 18 | int cgiUploadFirmware(HttpdConnData *connData); 19 | int cgiRebootFirmware(HttpdConnData *connData); 20 | 21 | #endif 22 | -------------------------------------------------------------------------------- /include/cgiwebsocket.h: -------------------------------------------------------------------------------- 1 | #ifndef CGIWEBSOCKET_H 2 | #define CGIWEBSOCKET_H 3 | 4 | #include "httpd.h" 5 | 6 | #define WEBSOCK_FLAG_NONE 0 7 | #define WEBSOCK_FLAG_CONT (1<<0) //Set if the data is not the final data in the message; more follows 8 | #define WEBSOCK_FLAG_BIN (1<<1) //Set if the data is binary instead of text 9 | 10 | 11 | 12 | typedef struct Websock Websock; 13 | typedef struct WebsockPriv WebsockPriv; 14 | 15 | typedef void(*WsConnectedCb)(Websock *ws); 16 | typedef void(*WsRecvCb)(Websock *ws, char *data, int len, int flags); 17 | typedef void(*WsSentCb)(Websock *ws); 18 | typedef void(*WsCloseCb)(Websock *ws); 19 | 20 | struct Websock { 21 | void *userData; 22 | HttpdConnData *conn; 23 | uint8_t status; 24 | WsRecvCb recvCb; 25 | WsSentCb sentCb; 26 | WsCloseCb closeCb; 27 | WebsockPriv *priv; 28 | }; 29 | 30 | int ICACHE_FLASH_ATTR cgiWebsocket(HttpdConnData *connData); 31 | int ICACHE_FLASH_ATTR cgiWebsocketSend(Websock *ws, char *data, int len, int flags); 32 | void ICACHE_FLASH_ATTR cgiWebsocketClose(Websock *ws, int reason); 33 | int ICACHE_FLASH_ATTR cgiWebSocketRecv(HttpdConnData *connData, char *data, int len); 34 | int ICACHE_FLASH_ATTR cgiWebsockBroadcast(char *resource, char *data, int len, int flags); 35 | 36 | 37 | #endif -------------------------------------------------------------------------------- /include/cgiwifi.h: -------------------------------------------------------------------------------- 1 | #ifndef CGIWIFI_H 2 | #define CGIWIFI_H 3 | 4 | #include "httpd.h" 5 | 6 | int cgiWiFiScan(HttpdConnData *connData); 7 | int tplWlan(HttpdConnData *connData, char *token, void **arg); 8 | int cgiWiFi(HttpdConnData *connData); 9 | int cgiWiFiConnect(HttpdConnData *connData); 10 | int cgiWiFiSetMode(HttpdConnData *connData); 11 | int cgiWiFiConnStatus(HttpdConnData *connData); 12 | 13 | #endif -------------------------------------------------------------------------------- /include/esp8266.h: -------------------------------------------------------------------------------- 1 | // Combined include file for esp8266 2 | // Actually misnamed, as it also works for ESP32. 3 | // ToDo: Figure out better name 4 | 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #ifdef FREERTOS 13 | #include 14 | 15 | #ifdef ESP32 16 | #include 17 | #else 18 | #include 19 | #endif 20 | 21 | #else 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #endif 31 | 32 | #include "platform.h" 33 | #include "espmissingincludes.h" 34 | 35 | -------------------------------------------------------------------------------- /include/espfs.h: -------------------------------------------------------------------------------- 1 | #ifndef ESPFS_H 2 | #define ESPFS_H 3 | 4 | // This define is done in Makefile. If you do not use default Makefile, uncomment 5 | // to be able to use Heatshrink-compressed espfs images. 6 | //#define ESPFS_HEATSHRINK 7 | 8 | typedef enum { 9 | ESPFS_INIT_RESULT_OK, 10 | ESPFS_INIT_RESULT_NO_IMAGE, 11 | ESPFS_INIT_RESULT_BAD_ALIGN, 12 | } EspFsInitResult; 13 | 14 | typedef struct EspFsFile EspFsFile; 15 | 16 | EspFsInitResult espFsInit(void *flashAddress); 17 | EspFsFile *espFsOpen(char *fileName); 18 | int espFsFlags(EspFsFile *fh); 19 | int espFsRead(EspFsFile *fh, char *buff, int len); 20 | void espFsClose(EspFsFile *fh); 21 | 22 | 23 | #endif -------------------------------------------------------------------------------- /include/espmissingincludes.h: -------------------------------------------------------------------------------- 1 | #ifndef ESPMISSINGINCLUDES_H 2 | #define ESPMISSINGINCLUDES_H 3 | 4 | #include 5 | #include 6 | 7 | 8 | int strcasecmp(const char *a, const char *b); 9 | #ifndef FREERTOS 10 | #include 11 | #include 12 | 13 | //Missing function prototypes in include folders. Gcc will warn on these if we don't define 'em anywhere. 14 | //MOST OF THESE ARE GUESSED! but they seem to swork and shut up the compiler. 15 | typedef struct espconn espconn; 16 | 17 | int atoi(const char *nptr); 18 | void ets_install_putc1(void (*routine)(char c)); 19 | void ets_isr_attach(int intr, void (*handler)(void *), void *arg); 20 | void ets_isr_mask(unsigned intr); 21 | void ets_isr_unmask(unsigned intr); 22 | int ets_memcmp(const void *s1, const void *s2, size_t n); 23 | void *ets_memcpy(void *dest, const void *src, size_t n); 24 | void *ets_memset(void *s, int c, size_t n); 25 | int ets_sprintf(char *str, const char *format, ...) __attribute__ ((format (printf, 2, 3))); 26 | int ets_str2macaddr(void *, void *); 27 | int ets_strcmp(const char *s1, const char *s2); 28 | char *ets_strcpy(char *dest, const char *src); 29 | int ets_strlen(const char *s); 30 | int ets_strncmp(const char *s1, const char *s2, unsigned int len); 31 | char *ets_strncpy(char *dest, const char *src, size_t n); 32 | char *ets_strstr(const char *haystack, const char *needle); 33 | void ets_timer_arm_new(os_timer_t *a, uint32_t b, bool repeat, bool isMstimer); 34 | void ets_timer_disarm(os_timer_t *a); 35 | void ets_timer_setfn(os_timer_t *t, ETSTimerFunc *fn, void *parg); 36 | void ets_update_cpu_frequency(int freqmhz); 37 | void *os_memmove(void *dest, const void *src, size_t n); 38 | int os_printf(const char *format, ...) __attribute__ ((format (printf, 1, 2))); 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 | uint8 wifi_get_opmode(void); 43 | uint32 system_get_time(); 44 | int rand(void); 45 | void ets_bzero(void *s, size_t n); 46 | void ets_delay_us(uint16_t ms); 47 | 48 | //Hack: this is defined in SDK 1.4.0 and undefined in 1.3.0. It's only used for this, the symbol itself 49 | //has no meaning here. 50 | #ifndef RC_LIMIT_P2P_11N 51 | //Defs for SDK <1.4.0 52 | void *pvPortMalloc(size_t xWantedSize); 53 | void *pvPortZalloc(size_t); 54 | void vPortFree(void *ptr); 55 | void *vPortMalloc(size_t xWantedSize); 56 | void pvPortFree(void *ptr); 57 | #else 58 | void *pvPortMalloc(size_t xWantedSize, const char *file, unsigned line); 59 | void *pvPortZalloc(size_t, const char *file, unsigned line); 60 | void vPortFree(void *ptr, const char *file, unsigned line); 61 | void *vPortMalloc(size_t xWantedSize, const char *file, unsigned line); 62 | void pvPortFree(void *ptr, const char *file, unsigned line); 63 | #endif 64 | 65 | //Standard PIN_FUNC_SELECT gives a warning. Replace by a non-warning one. 66 | #ifdef PIN_FUNC_SELECT 67 | #undef PIN_FUNC_SELECT 68 | #define PIN_FUNC_SELECT(PIN_NAME, FUNC) do { \ 69 | WRITE_PERI_REG(PIN_NAME, \ 70 | (READ_PERI_REG(PIN_NAME) \ 71 | & (~(PERIPHS_IO_MUX_FUNC< 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | //Cygwin e.a. needs O_BINARY. Don't miscompile if it's not set. 11 | #ifndef O_BINARY 12 | #define O_BINARY 0 13 | #endif 14 | 15 | typedef struct __attribute__((packed)) { 16 | char magic[4]; 17 | char tag[28]; 18 | int32_t len1; 19 | int32_t len2; 20 | } Header; 21 | 22 | 23 | int openFile(char *file) { 24 | int r=open(file, O_RDONLY|O_BINARY); 25 | if (r<=0) { 26 | perror(file); 27 | exit(1); 28 | } 29 | return r; 30 | } 31 | 32 | int32_t intToEsp(int32_t v) { 33 | int32_t ret; 34 | char *p=(char*)&ret; 35 | *p++=(v>>0)&0xff; 36 | *p++=(v>>8)&0xff; 37 | *p++=(v>>16)&0xff; 38 | *p++=(v>>24)&0xff; 39 | return ret; 40 | } 41 | 42 | size_t fileLen(int f) { 43 | size_t r; 44 | r=lseek(f, 0, SEEK_END); 45 | lseek(f, 0, SEEK_SET); 46 | return r; 47 | } 48 | 49 | 50 | int main(int argc, char **argv) { 51 | int u1, u2; 52 | size_t l1, l2; 53 | int of; 54 | char *fc1, *fc2; 55 | Header hdr; 56 | if (argc!=5) { 57 | printf("Usage: %s user1.bin user2.bin tagname outfile.bin\n", argv[0]); 58 | exit(1); 59 | } 60 | if (strlen(argv[3])>27) { 61 | printf("Error: Tag can't be longer than 27 characters.\n"); 62 | exit(1); 63 | } 64 | memset(&hdr, 0, sizeof(hdr)); 65 | memcpy(hdr.magic, "EHUG", 4); 66 | strcpy(hdr.tag, argv[3]); 67 | u1=openFile(argv[1]); 68 | u2=openFile(argv[2]); 69 | l1=fileLen(u1); 70 | l2=fileLen(u2); 71 | hdr.len1=intToEsp(l1); 72 | hdr.len2=intToEsp(l2); 73 | fc1=malloc(l1); 74 | fc2=malloc(l2); 75 | if (read(u1, fc1, l1)!=l1) { 76 | perror(argv[1]); 77 | exit(1); 78 | } 79 | if (read(u2, fc2, l2)!=l2) { 80 | perror(argv[2]); 81 | exit(1); 82 | } 83 | close(u1); 84 | close(u2); 85 | of=open(argv[4], O_WRONLY|O_CREAT|O_TRUNC|O_BINARY, 0666); 86 | if (of<=0) { 87 | perror(argv[4]); 88 | exit(1); 89 | } 90 | write(of, &hdr, sizeof(hdr)); 91 | write(of, fc1, l1); 92 | write(of, fc2, l2); 93 | printf("Header: %d bytes, user1: %d bytes, user2: %d bytes.\n", sizeof(hdr), (int)l1, (int)l2); 94 | close(of); 95 | exit(0); 96 | } 97 | 98 | -------------------------------------------------------------------------------- /util/captdns.c: -------------------------------------------------------------------------------- 1 | /* 2 | * ---------------------------------------------------------------------------- 3 | * "THE BEER-WARE LICENSE" (Revision 42): 4 | * Jeroen Domburg wrote this file. As long as you retain 5 | * this notice you can do whatever you want with this stuff. If we meet some day, 6 | * and you think this stuff is worth it, you can buy me a beer in return. 7 | * ---------------------------------------------------------------------------- 8 | */ 9 | 10 | 11 | /* 12 | This is a 'captive portal' DNS server: it basically replies with a fixed IP (in this case: 13 | the one of the SoftAP interface of this ESP module) for any and all DNS queries. This can 14 | be used to send mobile phones, tablets etc which connect to the ESP in AP mode directly to 15 | the internal webserver. 16 | */ 17 | 18 | #include 19 | #ifdef FREERTOS 20 | 21 | #include "FreeRTOS.h" 22 | #include "task.h" 23 | #include "queue.h" 24 | 25 | #include "lwip/sockets.h" 26 | #include "lwip/err.h" 27 | static int sockFd; 28 | #endif 29 | 30 | 31 | #define DNS_LEN 512 32 | 33 | typedef struct __attribute__ ((packed)) { 34 | uint16_t id; 35 | uint8_t flags; 36 | uint8_t rcode; 37 | uint16_t qdcount; 38 | uint16_t ancount; 39 | uint16_t nscount; 40 | uint16_t arcount; 41 | } DnsHeader; 42 | 43 | 44 | typedef struct __attribute__ ((packed)) { 45 | uint8_t len; 46 | uint8_t data; 47 | } DnsLabel; 48 | 49 | 50 | typedef struct __attribute__ ((packed)) { 51 | //before: label 52 | uint16_t type; 53 | uint16_t class; 54 | } DnsQuestionFooter; 55 | 56 | 57 | typedef struct __attribute__ ((packed)) { 58 | //before: label 59 | uint16_t type; 60 | uint16_t class; 61 | uint32_t ttl; 62 | uint16_t rdlength; 63 | //after: rdata 64 | } DnsResourceFooter; 65 | 66 | typedef struct __attribute__ ((packed)) { 67 | uint16_t prio; 68 | uint16_t weight; 69 | } DnsUriHdr; 70 | 71 | 72 | #define FLAG_QR (1<<7) 73 | #define FLAG_AA (1<<2) 74 | #define FLAG_TC (1<<1) 75 | #define FLAG_RD (1<<0) 76 | 77 | #define QTYPE_A 1 78 | #define QTYPE_NS 2 79 | #define QTYPE_CNAME 5 80 | #define QTYPE_SOA 6 81 | #define QTYPE_WKS 11 82 | #define QTYPE_PTR 12 83 | #define QTYPE_HINFO 13 84 | #define QTYPE_MINFO 14 85 | #define QTYPE_MX 15 86 | #define QTYPE_TXT 16 87 | #define QTYPE_URI 256 88 | 89 | #define QCLASS_IN 1 90 | #define QCLASS_ANY 255 91 | #define QCLASS_URI 256 92 | 93 | 94 | //Function to put unaligned 16-bit network values 95 | static void ICACHE_FLASH_ATTR setn16(void *pp, int16_t n) { 96 | char *p=pp; 97 | *p++=(n>>8); 98 | *p++=(n&0xff); 99 | } 100 | 101 | //Function to put unaligned 32-bit network values 102 | static void ICACHE_FLASH_ATTR setn32(void *pp, int32_t n) { 103 | char *p=pp; 104 | *p++=(n>>24)&0xff; 105 | *p++=(n>>16)&0xff; 106 | *p++=(n>>8)&0xff; 107 | *p++=(n&0xff); 108 | } 109 | 110 | static uint16_t ICACHE_FLASH_ATTR my_ntohs(uint16_t *in) { 111 | char *p=(char*)in; 112 | return ((p[0]<<8)&0xff00)|(p[1]&0xff); 113 | } 114 | 115 | 116 | //Parses a label into a C-string containing a dotted 117 | //Returns pointer to start of next fields in packet 118 | static char* ICACHE_FLASH_ATTR labelToStr(char *packet, char *labelPtr, int packetSz, char *res, int resMaxLen) { 119 | int i, j, k; 120 | char *endPtr=NULL; 121 | i=0; 122 | do { 123 | if ((*labelPtr&0xC0)==0) { 124 | j=*labelPtr++; //skip past length 125 | //Add separator period if there already is data in res 126 | if (ipacketSz) return NULL; 130 | if (ipacketSz) return NULL; 138 | labelPtr=&packet[offset]; 139 | } 140 | //check for out-of-bound-ness 141 | if ((labelPtr-packet)>packetSz) return NULL; 142 | } while (*labelPtr!=0); 143 | res[i]=0; //zero-terminate 144 | if (endPtr==NULL) endPtr=labelPtr+1; 145 | return endPtr; 146 | } 147 | 148 | 149 | //Converts a dotted hostname to the weird label form dns uses. 150 | static char ICACHE_FLASH_ATTR *strToLabel(char *str, char *label, int maxLen) { 151 | char *len=label; //ptr to len byte 152 | char *p=label+1; //ptr to next label byte to be written 153 | while (1) { 154 | if (*str=='.' || *str==0) { 155 | *len=((p-len)-1); //write len of label bit 156 | len=p; //pos of len for next part 157 | p++; //data ptr is one past len 158 | if (*str==0) break; //done 159 | str++; 160 | } else { 161 | *p++=*str++; //copy byte 162 | // if ((p-label)>maxLen) return NULL; //check out of bounds 163 | } 164 | } 165 | *len=0; 166 | return p; //ptr to first free byte in resp 167 | } 168 | 169 | 170 | //Receive a DNS packet and maybe send a response back 171 | #ifndef FREERTOS 172 | static void ICACHE_FLASH_ATTR captdnsRecv(void* arg, char *pusrdata, unsigned short length) { 173 | struct espconn *conn=(struct espconn *)arg; 174 | #else 175 | static void ICACHE_FLASH_ATTR captdnsRecv(struct sockaddr_in *premote_addr, char *pusrdata, unsigned short length) { 176 | #endif 177 | char buff[DNS_LEN]; 178 | char reply[DNS_LEN]; 179 | int i; 180 | char *rend=&reply[length]; 181 | char *p=pusrdata; 182 | DnsHeader *hdr=(DnsHeader*)p; 183 | DnsHeader *rhdr=(DnsHeader*)&reply[0]; 184 | p+=sizeof(DnsHeader); 185 | // httpd_printf("DNS packet: id 0x%X flags 0x%X rcode 0x%X qcnt %d ancnt %d nscount %d arcount %d len %d\n", 186 | // my_ntohs(&hdr->id), hdr->flags, hdr->rcode, my_ntohs(&hdr->qdcount), my_ntohs(&hdr->ancount), my_ntohs(&hdr->nscount), my_ntohs(&hdr->arcount), length); 187 | //Some sanity checks: 188 | if (length>DNS_LEN) return; //Packet is longer than DNS implementation allows 189 | if (lengthancount || hdr->nscount || hdr->arcount) return; //this is a reply, don't know what to do with it 191 | if (hdr->flags&FLAG_TC) return; //truncated, can't use this 192 | //Reply is basically the request plus the needed data 193 | memcpy(reply, pusrdata, length); 194 | rhdr->flags|=FLAG_QR; 195 | for (i=0; iqdcount); i++) { 196 | //Grab the labels in the q string 197 | p=labelToStr(pusrdata, p, length, buff, sizeof(buff)); 198 | if (p==NULL) return; 199 | DnsQuestionFooter *qf=(DnsQuestionFooter*)p; 200 | p+=sizeof(DnsQuestionFooter); 201 | httpd_printf("DNS: Q (type 0x%X class 0x%X) for %s\n", my_ntohs(&qf->type), my_ntohs(&qf->class), buff); 202 | if (my_ntohs(&qf->type)==QTYPE_A) { 203 | //They want to know the IPv4 address of something. 204 | //Build the response. 205 | rend=strToLabel(buff, rend, sizeof(reply)-(rend-reply)); //Add the label 206 | if (rend==NULL) return; 207 | DnsResourceFooter *rf=(DnsResourceFooter *)rend; 208 | rend+=sizeof(DnsResourceFooter); 209 | setn16(&rf->type, QTYPE_A); 210 | setn16(&rf->class, QCLASS_IN); 211 | setn32(&rf->ttl, 0); 212 | setn16(&rf->rdlength, 4); //IPv4 addr is 4 bytes; 213 | //Grab the current IP of the softap interface 214 | struct ip_info info; 215 | wifi_get_ip_info(SOFTAP_IF, &info); 216 | *rend++=ip4_addr1(&info.ip); 217 | *rend++=ip4_addr2(&info.ip); 218 | *rend++=ip4_addr3(&info.ip); 219 | *rend++=ip4_addr4(&info.ip); 220 | setn16(&rhdr->ancount, my_ntohs(&rhdr->ancount)+1); 221 | // httpd_printf("Added A rec to resp. Resp len is %d\n", (rend-reply)); 222 | } else if (my_ntohs(&qf->type)==QTYPE_NS) { 223 | //Give ns server. Basically can be whatever we want because it'll get resolved to our IP later anyway. 224 | rend=strToLabel(buff, rend, sizeof(reply)-(rend-reply)); //Add the label 225 | DnsResourceFooter *rf=(DnsResourceFooter *)rend; 226 | rend+=sizeof(DnsResourceFooter); 227 | setn16(&rf->type, QTYPE_NS); 228 | setn16(&rf->class, QCLASS_IN); 229 | setn16(&rf->ttl, 0); 230 | setn16(&rf->rdlength, 4); 231 | *rend++=2; 232 | *rend++='n'; 233 | *rend++='s'; 234 | *rend++=0; 235 | setn16(&rhdr->ancount, my_ntohs(&rhdr->ancount)+1); 236 | // httpd_printf("Added NS rec to resp. Resp len is %d\n", (rend-reply)); 237 | } else if (my_ntohs(&qf->type)==QTYPE_URI) { 238 | //Give uri to us 239 | rend=strToLabel(buff, rend, sizeof(reply)-(rend-reply)); //Add the label 240 | DnsResourceFooter *rf=(DnsResourceFooter *)rend; 241 | rend+=sizeof(DnsResourceFooter); 242 | DnsUriHdr *uh=(DnsUriHdr *)rend; 243 | rend+=sizeof(DnsUriHdr); 244 | setn16(&rf->type, QTYPE_URI); 245 | setn16(&rf->class, QCLASS_URI); 246 | setn16(&rf->ttl, 0); 247 | setn16(&rf->rdlength, 4+16); 248 | setn16(&uh->prio, 10); 249 | setn16(&uh->weight, 1); 250 | memcpy(rend, "http://esp.nonet", 16); 251 | rend+=16; 252 | setn16(&rhdr->ancount, my_ntohs(&rhdr->ancount)+1); 253 | // httpd_printf("Added NS rec to resp. Resp len is %d\n", (rend-reply)); 254 | } 255 | } 256 | //Send the response 257 | #ifndef FREERTOS 258 | remot_info *remInfo=NULL; 259 | //Send data to port/ip it came from, not to the ip/port we listen on. 260 | if (espconn_get_connection_info(conn, &remInfo, 0)==ESPCONN_OK) { 261 | conn->proto.udp->remote_port=remInfo->remote_port; 262 | memcpy(conn->proto.udp->remote_ip, remInfo->remote_ip, sizeof(remInfo->remote_ip)); 263 | } 264 | espconn_sendto(conn, (uint8*)reply, rend-reply); 265 | #else 266 | sendto(sockFd,(uint8*)reply, rend-reply, 0, (struct sockaddr *)premote_addr, sizeof(struct sockaddr_in)); 267 | #endif 268 | } 269 | 270 | #ifdef FREERTOS 271 | static void captdnsTask(void *pvParameters) { 272 | struct sockaddr_in server_addr; 273 | int32 ret; 274 | struct sockaddr_in from; 275 | socklen_t fromlen; 276 | struct ip_info ipconfig; 277 | char udp_msg[DNS_LEN]; 278 | 279 | memset(&ipconfig, 0, sizeof(ipconfig)); 280 | memset(&server_addr, 0, sizeof(server_addr)); 281 | server_addr.sin_family = AF_INET; 282 | server_addr.sin_addr.s_addr = INADDR_ANY; 283 | server_addr.sin_port = htons(53); 284 | server_addr.sin_len = sizeof(server_addr); 285 | 286 | do { 287 | sockFd=socket(AF_INET, SOCK_DGRAM, 0); 288 | if (sockFd==-1) { 289 | httpd_printf("captdns_task failed to create sock!\n"); 290 | vTaskDelay(1000/portTICK_RATE_MS); 291 | } 292 | } while (sockFd==-1); 293 | 294 | do { 295 | ret=bind(sockFd, (struct sockaddr *)&server_addr, sizeof(server_addr)); 296 | if (ret!=0) { 297 | httpd_printf("captdns_task failed to bind sock!\n"); 298 | vTaskDelay(1000/portTICK_RATE_MS); 299 | } 300 | } while (ret!=0); 301 | 302 | httpd_printf("CaptDNS inited.\n"); 303 | while(1) { 304 | memset(&from, 0, sizeof(from)); 305 | fromlen=sizeof(struct sockaddr_in); 306 | ret=recvfrom(sockFd, (u8 *)udp_msg, DNS_LEN, 0,(struct sockaddr *)&from,(socklen_t *)&fromlen); 307 | if (ret>0) captdnsRecv(&from,udp_msg,ret); 308 | } 309 | 310 | close(sockFd); 311 | vTaskDelete(NULL); 312 | } 313 | 314 | void captdnsInit(void) { 315 | #ifdef ESP32 316 | xTaskCreate(captdnsTask, (const char *)"captdns_task", 1200, NULL, 3, NULL); 317 | #else 318 | xTaskCreate(captdnsTask, (const signed char *)"captdns_task", 1200, NULL, 3, NULL); 319 | #endif 320 | } 321 | 322 | #else 323 | 324 | void ICACHE_FLASH_ATTR captdnsInit(void) { 325 | static struct espconn conn; 326 | static esp_udp udpconn; 327 | conn.type=ESPCONN_UDP; 328 | conn.proto.udp=&udpconn; 329 | conn.proto.udp->local_port = 53; 330 | espconn_regist_recvcb(&conn, captdnsRecv); 331 | espconn_create(&conn); 332 | } 333 | 334 | #endif -------------------------------------------------------------------------------- /util/cgiflash.c: -------------------------------------------------------------------------------- 1 | /* 2 | Some flash handling cgi routines. Used for updating the ESPFS/OTA image. 3 | */ 4 | 5 | /* 6 | * ---------------------------------------------------------------------------- 7 | * "THE BEER-WARE LICENSE" (Revision 42): 8 | * Jeroen Domburg wrote this file. As long as you retain 9 | * this notice you can do whatever you want with this stuff. If we meet some day, 10 | * and you think this stuff is worth it, you can buy me a beer in return. 11 | * ---------------------------------------------------------------------------- 12 | */ 13 | 14 | //This doesn't work yet on the ESP32. ToDo: figure out how to make it work. 15 | #ifndef ESP32 16 | 17 | #include 18 | #include "cgiflash.h" 19 | #include "espfs.h" 20 | #include "cgiflash.h" 21 | #include "espfs.h" 22 | 23 | //#include 24 | #include "cgiflash.h" 25 | #include "espfs.h" 26 | 27 | #ifndef UPGRADE_FLAG_FINISH 28 | #define UPGRADE_FLAG_FINISH 0x02 29 | #endif 30 | 31 | // Check that the header of the firmware blob looks like actual firmware... 32 | static int ICACHE_FLASH_ATTR checkBinHeader(void *buf) { 33 | uint8_t *cd = (uint8_t *)buf; 34 | if (cd[0] != 0xEA) return 0; 35 | if (cd[1] != 4 || cd[2] > 3 || cd[3] > 0x40) return 0; 36 | if (((uint16_t *)buf)[3] != 0x4010) return 0; 37 | if (((uint32_t *)buf)[2] != 0) return 0; 38 | return 1; 39 | } 40 | 41 | static int ICACHE_FLASH_ATTR checkEspfsHeader(void *buf) { 42 | if (memcmp(buf, "ESfs", 4)!=0) return 0; 43 | return 1; 44 | } 45 | 46 | 47 | // Cgi to query which firmware needs to be uploaded next 48 | int ICACHE_FLASH_ATTR cgiGetFirmwareNext(HttpdConnData *connData) { 49 | if (connData->conn==NULL) { 50 | //Connection aborted. Clean up. 51 | return HTTPD_CGI_DONE; 52 | } 53 | uint8 id = system_upgrade_userbin_check(); 54 | httpdStartResponse(connData, 200); 55 | httpdHeader(connData, "Content-Type", "text/plain"); 56 | httpdHeader(connData, "Content-Length", "9"); 57 | httpdEndHeaders(connData); 58 | char *next = id == 1 ? "user1.bin" : "user2.bin"; 59 | httpdSend(connData, next, -1); 60 | httpd_printf("Next firmware: %s (got %d)\n", next, id); 61 | return HTTPD_CGI_DONE; 62 | } 63 | 64 | 65 | //Cgi that allows the firmware to be replaced via http POST This takes 66 | //a direct POST from e.g. Curl or a Javascript AJAX call with either the 67 | //firmware given by cgiGetFirmwareNext or an OTA upgrade image. 68 | 69 | //Because we don't have the buffer to allocate an entire sector but will 70 | //have to buffer some data because the post buffer may be misaligned, we 71 | //write SPI data in pages. The page size is a software thing, not 72 | //a hardware one. 73 | #define PAGELEN 64 74 | 75 | #define FLST_START 0 76 | #define FLST_WRITE 1 77 | #define FLST_SKIP 2 78 | #define FLST_DONE 3 79 | #define FLST_ERROR 4 80 | 81 | #define FILETYPE_ESPFS 0 82 | #define FILETYPE_FLASH 1 83 | #define FILETYPE_OTA 2 84 | typedef struct { 85 | int state; 86 | int filetype; 87 | int flashPos; 88 | char pageData[PAGELEN]; 89 | int pagePos; 90 | int address; 91 | int len; 92 | int skip; 93 | char *err; 94 | } UploadState; 95 | 96 | typedef struct __attribute__((packed)) { 97 | char magic[4]; 98 | char tag[28]; 99 | int32_t len1; 100 | int32_t len2; 101 | } OtaHeader; 102 | 103 | 104 | int ICACHE_FLASH_ATTR cgiUploadFirmware(HttpdConnData *connData) { 105 | CgiUploadFlashDef *def=(CgiUploadFlashDef*)connData->cgiArg; 106 | UploadState *state=(UploadState *)connData->cgiData; 107 | int len; 108 | char buff[128]; 109 | 110 | if (connData->conn==NULL) { 111 | //Connection aborted. Clean up. 112 | if (state!=NULL) free(state); 113 | return HTTPD_CGI_DONE; 114 | } 115 | 116 | if (state==NULL) { 117 | //First call. Allocate and initialize state variable. 118 | httpd_printf("Firmware upload cgi start.\n"); 119 | state=malloc(sizeof(UploadState)); 120 | if (state==NULL) { 121 | httpd_printf("Can't allocate firmware upload struct!\n"); 122 | return HTTPD_CGI_DONE; 123 | } 124 | memset(state, 0, sizeof(UploadState)); 125 | state->state=FLST_START; 126 | connData->cgiData=state; 127 | state->err="Premature end"; 128 | } 129 | 130 | char *data=connData->post->buff; 131 | int dataLen=connData->post->buffLen; 132 | 133 | while (dataLen!=0) { 134 | if (state->state==FLST_START) { 135 | //First call. Assume the header of whatever we're uploading already is in the POST buffer. 136 | if (def->type==CGIFLASH_TYPE_FW && memcmp(data, "EHUG", 4)==0) { 137 | //Type is combined flash1/flash2 file 138 | OtaHeader *h=(OtaHeader*)data; 139 | strncpy(buff, h->tag, 27); 140 | buff[27]=0; 141 | if (strcmp(buff, def->tagName)!=0) { 142 | httpd_printf("OTA tag mismatch! Current=`%s` uploaded=`%s`.\n", 143 | def->tagName, buff); 144 | len=httpdFindArg(connData->getArgs, "force", buff, sizeof(buff)); 145 | if (len!=-1 && atoi(buff)) { 146 | httpd_printf("Forcing firmware flash.\n"); 147 | } else { 148 | state->err="Firmware not intended for this device!\n"; 149 | state->state=FLST_ERROR; 150 | } 151 | } 152 | if (state->state!=FLST_ERROR && connData->post->len > def->fwSize*2+sizeof(OtaHeader)) { 153 | state->err="Firmware image too large"; 154 | state->state=FLST_ERROR; 155 | } 156 | if (state->state!=FLST_ERROR) { 157 | //Flash header seems okay. 158 | dataLen-=sizeof(OtaHeader); //skip header when parsing data 159 | data+=sizeof(OtaHeader); 160 | if (system_upgrade_userbin_check()==1) { 161 | httpd_printf("Flashing user1.bin from ota image\n"); 162 | state->len=h->len1; 163 | state->skip=h->len2; 164 | state->state=FLST_WRITE; 165 | state->address=def->fw1Pos; 166 | } else { 167 | httpd_printf("Flashing user2.bin from ota image\n"); 168 | state->len=h->len2; 169 | state->skip=h->len1; 170 | state->state=FLST_SKIP; 171 | state->address=def->fw2Pos; 172 | } 173 | } 174 | } else if (def->type==CGIFLASH_TYPE_FW && checkBinHeader(connData->post->buff)) { 175 | if (connData->post->len > def->fwSize) { 176 | state->err="Firmware image too large"; 177 | state->state=FLST_ERROR; 178 | } else { 179 | state->len=connData->post->len; 180 | state->address=def->fw1Pos; 181 | state->state=FLST_WRITE; 182 | } 183 | } else if (def->type==CGIFLASH_TYPE_ESPFS && checkEspfsHeader(connData->post->buff)) { 184 | if (connData->post->len > def->fwSize) { 185 | state->err="Firmware image too large"; 186 | state->state=FLST_ERROR; 187 | } else { 188 | state->len=connData->post->len; 189 | state->address=def->fw1Pos; 190 | state->state=FLST_WRITE; 191 | } 192 | } else { 193 | state->err="Invalid flash image type!"; 194 | state->state=FLST_ERROR; 195 | httpd_printf("Did not recognize flash image type!\n"); 196 | } 197 | } else if (state->state==FLST_SKIP) { 198 | //Skip bytes without doing anything with them 199 | if (state->skip>dataLen) { 200 | //Skip entire buffer 201 | state->skip-=dataLen; 202 | dataLen=0; 203 | } else { 204 | //Only skip part of buffer 205 | dataLen-=state->skip; 206 | data+=state->skip; 207 | state->skip=0; 208 | if (state->len) state->state=FLST_WRITE; else state->state=FLST_DONE; 209 | } 210 | } else if (state->state==FLST_WRITE) { 211 | //Copy bytes to page buffer, and if page buffer is full, flash the data. 212 | //First, calculate the amount of bytes we need to finish the page buffer. 213 | int lenLeft=PAGELEN-state->pagePos; 214 | if (state->lenlen; //last buffer can be a cut-off one 215 | //See if we need to write the page. 216 | if (dataLenpageData[state->pagePos], data, dataLen); 219 | state->pagePos+=dataLen; 220 | state->len-=dataLen; 221 | dataLen=0; 222 | } else { 223 | //Finish page; take data we need from post buffer 224 | memcpy(&state->pageData[state->pagePos], data, lenLeft); 225 | data+=lenLeft; 226 | dataLen-=lenLeft; 227 | state->pagePos+=lenLeft; 228 | state->len-=lenLeft; 229 | //Erase sector, if needed 230 | if ((state->address&(SPI_FLASH_SEC_SIZE-1))==0) { 231 | spi_flash_erase_sector(state->address/SPI_FLASH_SEC_SIZE); 232 | } 233 | //Write page 234 | //httpd_printf("Writing %d bytes of data to SPI pos 0x%x...\n", state->pagePos, state->address); 235 | spi_flash_write(state->address, (uint32 *)state->pageData, state->pagePos); 236 | state->address+=PAGELEN; 237 | state->pagePos=0; 238 | if (state->len==0) { 239 | //Done. 240 | if (state->skip) state->state=FLST_SKIP; else state->state=FLST_DONE; 241 | } 242 | } 243 | } else if (state->state==FLST_DONE) { 244 | httpd_printf("Huh? %d bogus bytes received after data received.\n", dataLen); 245 | //Ignore those bytes. 246 | dataLen=0; 247 | } else if (state->state==FLST_ERROR) { 248 | //Just eat up any bytes we receive. 249 | dataLen=0; 250 | } 251 | } 252 | 253 | if (connData->post->len==connData->post->received) { 254 | //We're done! Format a response. 255 | httpd_printf("Upload done. Sending response.\n"); 256 | httpdStartResponse(connData, state->state==FLST_ERROR?400:200); 257 | httpdHeader(connData, "Content-Type", "text/plain"); 258 | httpdEndHeaders(connData); 259 | if (state->state!=FLST_DONE) { 260 | httpdSend(connData, "Firmware image error:", -1); 261 | httpdSend(connData, state->err, -1); 262 | httpdSend(connData, "\n", -1); 263 | } 264 | free(state); 265 | return HTTPD_CGI_DONE; 266 | } 267 | 268 | return HTTPD_CGI_MORE; 269 | } 270 | 271 | 272 | 273 | static os_timer_t resetTimer; 274 | 275 | static void ICACHE_FLASH_ATTR resetTimerCb(void *arg) { 276 | system_upgrade_flag_set(UPGRADE_FLAG_FINISH); 277 | system_upgrade_reboot(); 278 | } 279 | 280 | // Handle request to reboot into the new firmware 281 | int ICACHE_FLASH_ATTR cgiRebootFirmware(HttpdConnData *connData) { 282 | if (connData->conn==NULL) { 283 | //Connection aborted. Clean up. 284 | return HTTPD_CGI_DONE; 285 | } 286 | 287 | // TODO: sanity-check that the 'next' partition actually contains something that looks like 288 | // valid firmware 289 | 290 | //Do reboot in a timer callback so we still have time to send the response. 291 | os_timer_disarm(&resetTimer); 292 | os_timer_setfn(&resetTimer, resetTimerCb, NULL); 293 | os_timer_arm(&resetTimer, 200, 0); 294 | 295 | httpdStartResponse(connData, 200); 296 | httpdHeader(connData, "Content-Type", "text/plain"); 297 | httpdEndHeaders(connData); 298 | httpdSend(connData, "Rebooting...", -1); 299 | return HTTPD_CGI_DONE; 300 | } 301 | 302 | #endif -------------------------------------------------------------------------------- /util/cgiwebsocket.c: -------------------------------------------------------------------------------- 1 | /* 2 | Websocket support for esphttpd. Inspired by https://github.com/dangrie158/ESP-8266-WebSocket 3 | */ 4 | 5 | /* 6 | * ---------------------------------------------------------------------------- 7 | * "THE BEER-WARE LICENSE" (Revision 42): 8 | * Jeroen Domburg wrote this file. As long as you retain 9 | * this notice you can do whatever you want with this stuff. If we meet some day, 10 | * and you think this stuff is worth it, you can buy me a beer in return. 11 | * ---------------------------------------------------------------------------- 12 | */ 13 | 14 | 15 | #include 16 | #include "httpd.h" 17 | #include "sha1.h" 18 | #include "base64.h" 19 | #include "cgiwebsocket.h" 20 | 21 | #define WS_KEY_IDENTIFIER "Sec-WebSocket-Key: " 22 | #define WS_GUID "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" 23 | 24 | /* from IEEE RFC6455 sec 5.2 25 | 0 1 2 3 26 | 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 27 | +-+-+-+-+-------+-+-------------+-------------------------------+ 28 | |F|R|R|R| opcode|M| Payload len | Extended payload length | 29 | |I|S|S|S| (4) |A| (7) | (16/64) | 30 | |N|V|V|V| |S| | (if payload len==126/127) | 31 | | |1|2|3| |K| | | 32 | +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - + 33 | | Extended payload length continued, if payload len == 127 | 34 | + - - - - - - - - - - - - - - - +-------------------------------+ 35 | | |Masking-key, if MASK set to 1 | 36 | +-------------------------------+-------------------------------+ 37 | | Masking-key (continued) | Payload Data | 38 | +-------------------------------- - - - - - - - - - - - - - - - + 39 | : Payload Data continued ... : 40 | + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + 41 | | Payload Data continued ... | 42 | +---------------------------------------------------------------+ 43 | */ 44 | 45 | #define FLAG_FIN (1 << 7) 46 | 47 | #define OPCODE_CONTINUE 0x0 48 | #define OPCODE_TEXT 0x1 49 | #define OPCODE_BINARY 0x2 50 | #define OPCODE_CLOSE 0x8 51 | #define OPCODE_PING 0x9 52 | #define OPCODE_PONG 0xA 53 | 54 | #define FLAGS_MASK ((uint8_t)0xF0) 55 | #define OPCODE_MASK ((uint8_t)0x0F) 56 | #define IS_MASKED ((uint8_t)(1<<7)) 57 | #define PAYLOAD_MASK ((uint8_t)0x7F) 58 | 59 | typedef struct WebsockFrame WebsockFrame; 60 | 61 | #define ST_FLAGS 0 62 | #define ST_LEN0 1 63 | #define ST_LEN1 2 64 | #define ST_LEN2 3 65 | //... 66 | #define ST_LEN8 9 67 | #define ST_MASK1 10 68 | #define ST_MASK4 13 69 | #define ST_PAYLOAD 14 70 | 71 | struct WebsockFrame { 72 | uint8_t flags; 73 | uint8_t len8; 74 | uint64_t len; 75 | uint8_t mask[4]; 76 | }; 77 | 78 | struct WebsockPriv { 79 | struct WebsockFrame fr; 80 | uint8_t maskCtr; 81 | uint8 frameCont; 82 | uint8 closedHere; 83 | int wsStatus; 84 | Websock *next; //in linked list 85 | }; 86 | 87 | static Websock *llStart=NULL; 88 | 89 | static int ICACHE_FLASH_ATTR sendFrameHead(Websock *ws, int opcode, int len) { 90 | char buf[14]; 91 | int i=0; 92 | buf[i++]=opcode; 93 | if (len>65535) { 94 | buf[i++]=127; 95 | buf[i++]=0; buf[i++]=0; buf[i++]=0; buf[i++]=0; 96 | buf[i++]=len>>24; 97 | buf[i++]=len>>16; 98 | buf[i++]=len>>8; 99 | buf[i++]=len; 100 | } else if (len>125) { 101 | buf[i++]=126; 102 | buf[i++]=len>>8; 103 | buf[i++]=len; 104 | } else { 105 | buf[i++]=len; 106 | } 107 | httpd_printf("WS: Sent frame head for payload of %d bytes.\n", len); 108 | return httpdSend(ws->conn, buf, i); 109 | } 110 | 111 | int ICACHE_FLASH_ATTR cgiWebsocketSend(Websock *ws, char *data, int len, int flags) { 112 | int r=0; 113 | int fl=0; 114 | if (flags&WEBSOCK_FLAG_BIN) fl=OPCODE_BINARY; else fl=OPCODE_TEXT; 115 | if (!(flags&WEBSOCK_FLAG_CONT)) fl|=FLAG_FIN; 116 | sendFrameHead(ws, fl, len); 117 | if (len!=0) r=httpdSend(ws->conn, data, len); 118 | httpdFlushSendBuffer(ws->conn); 119 | return r; 120 | } 121 | 122 | //Broadcast data to all websockets at a specific url. Returns the amount of connections sent to. 123 | int ICACHE_FLASH_ATTR cgiWebsockBroadcast(char *resource, char *data, int len, int flags) { 124 | Websock *lw=llStart; 125 | int ret=0; 126 | while (lw!=NULL) { 127 | if (strcmp(lw->conn->url, resource)==0) { 128 | httpdConnSendStart(lw->conn); 129 | cgiWebsocketSend(lw, data, len, flags); 130 | httpdConnSendFinish(lw->conn); 131 | ret++; 132 | } 133 | lw=lw->priv->next; 134 | } 135 | return ret; 136 | } 137 | 138 | 139 | void ICACHE_FLASH_ATTR cgiWebsocketClose(Websock *ws, int reason) { 140 | char rs[2]={reason>>8, reason&0xff}; 141 | sendFrameHead(ws, FLAG_FIN|OPCODE_CLOSE, 2); 142 | httpdSend(ws->conn, rs, 2); 143 | ws->priv->closedHere=1; 144 | httpdFlushSendBuffer(ws->conn); 145 | } 146 | 147 | 148 | static void ICACHE_FLASH_ATTR websockFree(Websock *ws) { 149 | httpd_printf("Ws: Free\n"); 150 | if (ws->closeCb) ws->closeCb(ws); 151 | //Clean up linked list 152 | if (llStart==ws) { 153 | llStart=ws->priv->next; 154 | } else if (llStart) { 155 | Websock *lws=llStart; 156 | //Find ws that links to this one. 157 | while (lws!=NULL && lws->priv->next!=ws) lws=lws->priv->next; 158 | if (lws!=NULL) lws->priv->next=ws->priv->next; 159 | } 160 | if (ws->priv) free(ws->priv); 161 | } 162 | 163 | int ICACHE_FLASH_ATTR cgiWebSocketRecv(HttpdConnData *connData, char *data, int len) { 164 | int i, j, sl; 165 | int r=HTTPD_CGI_MORE; 166 | int wasHeaderByte; 167 | Websock *ws=(Websock*)connData->cgiData; 168 | for (i=0; ipriv->wsStatus, data[i]); 170 | wasHeaderByte=1; 171 | if (ws->priv->wsStatus==ST_FLAGS) { 172 | ws->priv->maskCtr=0; 173 | ws->priv->frameCont=0; 174 | ws->priv->fr.flags=(uint8_t)data[i]; 175 | ws->priv->wsStatus=ST_LEN0; 176 | } else if (ws->priv->wsStatus==ST_LEN0) { 177 | ws->priv->fr.len8=(uint8_t)data[i]; 178 | if ((ws->priv->fr.len8&127)>=126) { 179 | ws->priv->fr.len=0; 180 | ws->priv->wsStatus=ST_LEN1; 181 | } else { 182 | ws->priv->fr.len=ws->priv->fr.len8&127; 183 | ws->priv->wsStatus=(ws->priv->fr.len8&IS_MASKED)?ST_MASK1:ST_PAYLOAD; 184 | } 185 | } else if (ws->priv->wsStatus<=ST_LEN8) { 186 | ws->priv->fr.len=(ws->priv->fr.len<<8)|data[i]; 187 | if (((ws->priv->fr.len8&127)==126 && ws->priv->wsStatus==ST_LEN2) || ws->priv->wsStatus==ST_LEN8) { 188 | ws->priv->wsStatus=(ws->priv->fr.len8&IS_MASKED)?ST_MASK1:ST_PAYLOAD; 189 | } else { 190 | ws->priv->wsStatus++; 191 | } 192 | } else if (ws->priv->wsStatus<=ST_MASK4) { 193 | ws->priv->fr.mask[ws->priv->wsStatus-ST_MASK1]=data[i]; 194 | ws->priv->wsStatus++; 195 | } else { 196 | //Was a payload byte. 197 | wasHeaderByte=0; 198 | } 199 | 200 | if (ws->priv->wsStatus==ST_PAYLOAD && wasHeaderByte) { 201 | //We finished parsing the header, but i still is on the last header byte. Move one forward so 202 | //the payload code works as usual. 203 | i++; 204 | } 205 | //Also finish parsing frame if we haven't received any payload bytes yet, but the length of the frame 206 | //is zero. 207 | if (ws->priv->wsStatus==ST_PAYLOAD) { 208 | //Okay, header is in; this is a data byte. We're going to process all the data bytes we have 209 | //received here at the same time; no more byte iterations till the end of this frame. 210 | //First, unmask the data 211 | sl=len-i; 212 | httpd_printf("Ws: Frame payload. wasHeaderByte %d fr.len %d sl %d cmd 0x%x\n", wasHeaderByte, (int)ws->priv->fr.len, (int)sl, ws->priv->fr.flags); 213 | if (sl > ws->priv->fr.len) sl=ws->priv->fr.len; 214 | for (j=0; jpriv->fr.mask[(ws->priv->maskCtr++)&3]); 215 | 216 | // httpd_printf("Unmasked: "); 217 | // for (j=0; jpriv->fr.flags&OPCODE_MASK)==OPCODE_PING) { 222 | if (ws->priv->fr.len>125) { 223 | if (!ws->priv->frameCont) cgiWebsocketClose(ws, 1002); 224 | r=HTTPD_CGI_DONE; 225 | break; 226 | } else { 227 | if (!ws->priv->frameCont) sendFrameHead(ws, OPCODE_PONG|FLAG_FIN, ws->priv->fr.len); 228 | if (sl>0) httpdSend(ws->conn, data+i, sl); 229 | } 230 | } else if ((ws->priv->fr.flags&OPCODE_MASK)==OPCODE_TEXT || 231 | (ws->priv->fr.flags&OPCODE_MASK)==OPCODE_BINARY || 232 | (ws->priv->fr.flags&OPCODE_MASK)==OPCODE_CONTINUE) { 233 | if (sl>ws->priv->fr.len) sl=ws->priv->fr.len; 234 | if (!(ws->priv->fr.len8&IS_MASKED)) { 235 | //We're a server; client should send us masked packets. 236 | cgiWebsocketClose(ws, 1002); 237 | r=HTTPD_CGI_DONE; 238 | break; 239 | } else { 240 | int flags=0; 241 | if ((ws->priv->fr.flags&OPCODE_MASK)==OPCODE_BINARY) flags|=WEBSOCK_FLAG_BIN; 242 | if ((ws->priv->fr.flags&FLAG_FIN)==0) flags|=WEBSOCK_FLAG_CONT; 243 | if (ws->recvCb) ws->recvCb(ws, data+i, sl, flags); 244 | } 245 | } else if ((ws->priv->fr.flags&OPCODE_MASK)==OPCODE_CLOSE) { 246 | httpd_printf("WS: Got close frame\n"); 247 | if (!ws->priv->closedHere) { 248 | httpd_printf("WS: Sending response close frame\n"); 249 | cgiWebsocketClose(ws, ((data[i]<<8)&0xff00)+(data[i+1]&0xff)); 250 | } 251 | r=HTTPD_CGI_DONE; 252 | break; 253 | } else { 254 | if (!ws->priv->frameCont) httpd_printf("WS: Unknown opcode 0x%X\n", ws->priv->fr.flags&OPCODE_MASK); 255 | } 256 | i+=sl-1; 257 | ws->priv->fr.len-=sl; 258 | if (ws->priv->fr.len==0) { 259 | ws->priv->wsStatus=ST_FLAGS; //go receive next frame 260 | } else { 261 | ws->priv->frameCont=1; //next payload is continuation of this frame. 262 | } 263 | } 264 | } 265 | if (r==HTTPD_CGI_DONE) { 266 | //We're going to tell the main webserver we're done. The webserver expects us to clean up by ourselves 267 | //we're chosing to be done. Do so. 268 | websockFree(ws); 269 | free(connData->cgiData); 270 | connData->cgiData=NULL; 271 | } 272 | return r; 273 | } 274 | 275 | //Websocket 'cgi' implementation 276 | int ICACHE_FLASH_ATTR cgiWebsocket(HttpdConnData *connData) { 277 | char buff[256]; 278 | int i; 279 | sha1nfo s; 280 | if (connData->conn==NULL) { 281 | //Connection aborted. Clean up. 282 | httpd_printf("WS: Cleanup\n"); 283 | if (connData->cgiData) { 284 | Websock *ws=(Websock*)connData->cgiData; 285 | websockFree(ws); 286 | free(connData->cgiData); 287 | connData->cgiData=NULL; 288 | } 289 | return HTTPD_CGI_DONE; 290 | } 291 | 292 | if (connData->cgiData==NULL) { 293 | // httpd_printf("WS: First call\n"); 294 | //First call here. Check if client headers are OK, send server header. 295 | i=httpdGetHeader(connData, "Upgrade", buff, sizeof(buff)-1); 296 | httpd_printf("WS: Upgrade: %s\n", buff); 297 | if (i && strcasecmp(buff, "websocket")==0) { 298 | i=httpdGetHeader(connData, "Sec-WebSocket-Key", buff, sizeof(buff)-1); 299 | if (i) { 300 | // httpd_printf("WS: Key: %s\n", buff); 301 | //Seems like a WebSocket connection. 302 | // Alloc structs 303 | connData->cgiData=malloc(sizeof(Websock)); 304 | if (connData->cgiData==NULL) { 305 | httpd_printf("Can't allocate mem for websocket\n"); 306 | return HTTPD_CGI_DONE; 307 | } 308 | memset(connData->cgiData, 0, sizeof(Websock)); 309 | Websock *ws=(Websock*)connData->cgiData; 310 | ws->priv=malloc(sizeof(WebsockPriv)); 311 | if (ws->priv==NULL) { 312 | httpd_printf("Can't allocate mem for websocket priv\n"); 313 | free(connData->cgiData); 314 | connData->cgiData=NULL; 315 | return HTTPD_CGI_DONE; 316 | } 317 | memset(ws->priv, 0, sizeof(WebsockPriv)); 318 | ws->conn=connData; 319 | //Reply with the right headers. 320 | strcat(buff, WS_GUID); 321 | sha1_init(&s); 322 | sha1_write(&s, buff, strlen(buff)); 323 | httdSetTransferMode(connData, HTTPD_TRANSFER_NONE); 324 | httpdStartResponse(connData, 101); 325 | httpdHeader(connData, "Upgrade", "websocket"); 326 | httpdHeader(connData, "Connection", "upgrade"); 327 | base64_encode(20, sha1_result(&s), sizeof(buff), buff); 328 | httpdHeader(connData, "Sec-WebSocket-Accept", buff); 329 | httpdEndHeaders(connData); 330 | //Set data receive handler 331 | connData->recvHdl=cgiWebSocketRecv; 332 | //Inform CGI function we have a connection 333 | WsConnectedCb connCb=connData->cgiArg; 334 | connCb(ws); 335 | //Insert ws into linked list 336 | if (llStart==NULL) { 337 | llStart=ws; 338 | } else { 339 | Websock *lw=llStart; 340 | while (lw->priv->next) lw=lw->priv->next; 341 | lw->priv->next=ws; 342 | } 343 | return HTTPD_CGI_MORE; 344 | } 345 | } 346 | //No valid websocket connection 347 | httpdStartResponse(connData, 500); 348 | httpdEndHeaders(connData); 349 | return HTTPD_CGI_DONE; 350 | } 351 | 352 | //Sending is done. Call the sent callback if we have one. 353 | Websock *ws=(Websock*)connData->cgiData; 354 | if (ws && ws->sentCb) ws->sentCb(ws); 355 | 356 | return HTTPD_CGI_MORE; 357 | } 358 | 359 | -------------------------------------------------------------------------------- /util/cgiwifi.c: -------------------------------------------------------------------------------- 1 | /* 2 | Cgi/template routines for the /wifi url. 3 | */ 4 | 5 | /* 6 | * ---------------------------------------------------------------------------- 7 | * "THE BEER-WARE LICENSE" (Revision 42): 8 | * Jeroen Domburg wrote this file. As long as you retain 9 | * this notice you can do whatever you want with this stuff. If we meet some day, 10 | * and you think this stuff is worth it, you can buy me a beer in return. 11 | * ---------------------------------------------------------------------------- 12 | */ 13 | 14 | 15 | #include 16 | #include "cgiwifi.h" 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[32]; 24 | char bssid[8]; 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 | #define CONNTRY_IDLE 0 41 | #define CONNTRY_WORKING 1 42 | #define CONNTRY_SUCCESS 2 43 | #define CONNTRY_FAIL 3 44 | //Connection result var 45 | static int connTryStatus=CONNTRY_IDLE; 46 | static os_timer_t resetTimer; 47 | 48 | //Callback the code calls when a wlan ap scan is done. Basically stores the result in 49 | //the cgiWifiAps struct. 50 | void ICACHE_FLASH_ATTR wifiScanDoneCb(void *arg, STATUS status) { 51 | int n; 52 | struct bss_info *bss_link = (struct bss_info *)arg; 53 | httpd_printf("wifiScanDoneCb %d\n", status); 54 | if (status!=OK) { 55 | cgiWifiAps.scanInProgress=0; 56 | return; 57 | } 58 | 59 | //Clear prev ap data if needed. 60 | if (cgiWifiAps.apData!=NULL) { 61 | for (n=0; nnext.stqe_next; 69 | n++; 70 | } 71 | //Allocate memory for access point data 72 | cgiWifiAps.apData=(ApData **)malloc(sizeof(ApData *)*n); 73 | if (cgiWifiAps.apData==NULL) { 74 | printf("Out of memory allocating apData\n"); 75 | return; 76 | } 77 | cgiWifiAps.noAps=n; 78 | httpd_printf("Scan done: found %d APs\n", n); 79 | 80 | //Copy access point data to the static struct 81 | n=0; 82 | bss_link = (struct bss_info *)arg; 83 | while (bss_link != NULL) { 84 | if (n>=cgiWifiAps.noAps) { 85 | //This means the bss_link changed under our nose. Shouldn't happen! 86 | //Break because otherwise we will write in unallocated memory. 87 | httpd_printf("Huh? I have more than the allocated %d aps!\n", cgiWifiAps.noAps); 88 | break; 89 | } 90 | //Save the ap data. 91 | cgiWifiAps.apData[n]=(ApData *)malloc(sizeof(ApData)); 92 | if (cgiWifiAps.apData[n]==NULL) { 93 | httpd_printf("Can't allocate mem for ap buff.\n"); 94 | cgiWifiAps.scanInProgress=0; 95 | return; 96 | } 97 | cgiWifiAps.apData[n]->rssi=bss_link->rssi; 98 | cgiWifiAps.apData[n]->channel=bss_link->channel; 99 | cgiWifiAps.apData[n]->enc=bss_link->authmode; 100 | strncpy(cgiWifiAps.apData[n]->ssid, (char*)bss_link->ssid, 32); 101 | strncpy(cgiWifiAps.apData[n]->bssid, (char*)bss_link->bssid, 6); 102 | 103 | bss_link = bss_link->next.stqe_next; 104 | n++; 105 | } 106 | //We're done. 107 | cgiWifiAps.scanInProgress=0; 108 | } 109 | 110 | 111 | //Routine to start a WiFi access point scan. 112 | static void ICACHE_FLASH_ATTR wifiStartScan() { 113 | // int x; 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 | int 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 | 208 | //This cgi uses the routines above to connect to a specific access point with the 209 | //given ESSID using the given password. 210 | int ICACHE_FLASH_ATTR cgiWiFiConnect(HttpdConnData *connData) { 211 | char essid[128]; 212 | char passwd[128]; 213 | static os_timer_t reassTimer; 214 | 215 | if (connData->conn==NULL) { 216 | //Connection aborted. Clean up. 217 | return HTTPD_CGI_DONE; 218 | } 219 | 220 | httpdFindArg(connData->post->buff, "essid", essid, sizeof(essid)); 221 | httpdFindArg(connData->post->buff, "passwd", passwd, sizeof(passwd)); 222 | 223 | strncpy((char*)stconf.ssid, essid, 32); 224 | strncpy((char*)stconf.password, passwd, 64); 225 | httpd_printf("Try to connect to AP %s pw %s\n", essid, passwd); 226 | 227 | //Schedule disconnect/connect 228 | os_timer_disarm(&reassTimer); 229 | os_timer_setfn(&reassTimer, reassTimerCb, NULL); 230 | //Set to 0 if you want to disable the actual reconnecting bit 231 | #ifdef DEMO_MODE 232 | httpdRedirect(connData, "/wifi"); 233 | #else 234 | os_timer_arm(&reassTimer, 500, 0); 235 | httpdRedirect(connData, "connecting.html"); 236 | #endif 237 | return HTTPD_CGI_DONE; 238 | } 239 | 240 | //This cgi uses the routines above to connect to a specific access point with the 241 | //given ESSID using the given password. 242 | int ICACHE_FLASH_ATTR cgiWiFiSetMode(HttpdConnData *connData) { 243 | int len; 244 | char buff[1024]; 245 | 246 | if (connData->conn==NULL) { 247 | //Connection aborted. Clean up. 248 | return HTTPD_CGI_DONE; 249 | } 250 | 251 | len=httpdFindArg(connData->getArgs, "mode", buff, sizeof(buff)); 252 | if (len!=0) { 253 | httpd_printf("cgiWifiSetMode: %s\n", buff); 254 | #ifndef DEMO_MODE 255 | wifi_set_opmode(atoi(buff)); 256 | system_restart(); 257 | #endif 258 | } 259 | httpdRedirect(connData, "/wifi"); 260 | return HTTPD_CGI_DONE; 261 | } 262 | 263 | int ICACHE_FLASH_ATTR cgiWiFiConnStatus(HttpdConnData *connData) { 264 | char buff[1024]; 265 | int len; 266 | struct ip_info info; 267 | int st=wifi_station_get_connect_status(); 268 | httpdStartResponse(connData, 200); 269 | httpdHeader(connData, "Content-Type", "text/json"); 270 | httpdEndHeaders(connData); 271 | if (connTryStatus==CONNTRY_IDLE) { 272 | len=sprintf(buff, "{\n \"status\": \"idle\"\n }\n"); 273 | } else if (connTryStatus==CONNTRY_WORKING || connTryStatus==CONNTRY_SUCCESS) { 274 | if (st==STATION_GOT_IP) { 275 | wifi_get_ip_info(0, &info); 276 | len=sprintf(buff, "{\n \"status\": \"success\",\n \"ip\": \"%d.%d.%d.%d\" }\n", 277 | (info.ip.addr>>0)&0xff, (info.ip.addr>>8)&0xff, 278 | (info.ip.addr>>16)&0xff, (info.ip.addr>>24)&0xff); 279 | //Reset into AP-only mode sooner. 280 | os_timer_disarm(&resetTimer); 281 | os_timer_setfn(&resetTimer, resetTimerCb, NULL); 282 | os_timer_arm(&resetTimer, 1000, 0); 283 | } else { 284 | len=sprintf(buff, "{\n \"status\": \"working\"\n }\n"); 285 | } 286 | } else { 287 | len=sprintf(buff, "{\n \"status\": \"fail\"\n }\n"); 288 | } 289 | 290 | httpdSend(connData, buff, len); 291 | return HTTPD_CGI_DONE; 292 | } 293 | 294 | //Template code for the WLAN page. 295 | int ICACHE_FLASH_ATTR tplWlan(HttpdConnData *connData, char *token, void **arg) { 296 | char buff[1024]; 297 | int x; 298 | static struct station_config stconf; 299 | if (token==NULL) return HTTPD_CGI_DONE; 300 | wifi_station_get_config(&stconf); 301 | 302 | strcpy(buff, "Unknown"); 303 | if (strcmp(token, "WiFiMode")==0) { 304 | x=wifi_get_opmode(); 305 | if (x==1) strcpy(buff, "Client"); 306 | if (x==2) strcpy(buff, "SoftAP"); 307 | if (x==3) strcpy(buff, "STA+AP"); 308 | } else if (strcmp(token, "currSsid")==0) { 309 | strcpy(buff, (char*)stconf.ssid); 310 | } else if (strcmp(token, "WiFiPasswd")==0) { 311 | strcpy(buff, (char*)stconf.password); 312 | } else if (strcmp(token, "WiFiapwarn")==0) { 313 | x=wifi_get_opmode(); 314 | if (x==2) { 315 | strcpy(buff, "Can't scan in this mode. Click here to go to STA+AP mode."); 316 | } else { 317 | strcpy(buff, "Click here to go to standalone AP mode."); 318 | } 319 | } 320 | httpdSend(connData, buff, -1); 321 | return HTTPD_CGI_DONE; 322 | } 323 | 324 | -------------------------------------------------------------------------------- /webpages.espfs.ld: -------------------------------------------------------------------------------- 1 | OUTPUT_FORMAT("elf32-xtensa-le") 2 | 3 | 4 | SECTIONS 5 | { 6 | .irom0.literal : ALIGN(4) SUBALIGN(4) { 7 | webpages_espfs_start = .; 8 | *(*) 9 | webpages_espfs_end = .; 10 | webpages_espfs_size = webpages_espfs_end - webpages_espfs_start; 11 | } 12 | } --------------------------------------------------------------------------------