├── .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 | }
--------------------------------------------------------------------------------