parent
98304bc9e5
commit
6acbb69765
@ -1,3 +0,0 @@ |
|||||||
[submodule "lib/heatshrink"] |
|
||||||
path = libesphttpd/lib/heatshrink |
|
||||||
url = https://github.com/atomicobject/heatshrink.git |
|
@ -1,10 +0,0 @@ |
|||||||
build/ |
|
||||||
espfs/mkespfsimage/*.o |
|
||||||
espfs/mkespfsimage/mkespfsimage |
|
||||||
webpages.espfs |
|
||||||
libesphttpd.a |
|
||||||
espfs/espfstest/*.o |
|
||||||
espfs/espfstest/espfstest |
|
||||||
*.DS_Store |
|
||||||
html_compressed/ |
|
||||||
libwebpages-espfs.a |
|
@ -1,145 +0,0 @@ |
|||||||
|
|
||||||
# Directory the Makefile is in. Please don't include other Makefiles before this.
|
|
||||||
THISDIR:=$(dir $(abspath $(lastword $(MAKEFILE_LIST))))
|
|
||||||
|
|
||||||
#Include httpd config from lower level, if it exists
|
|
||||||
-include ../esphttpdconfig.mk |
|
||||||
|
|
||||||
|
|
||||||
#Default options. If you want to change them, please create ../esphttpdconfig.mk with the options you want in it.
|
|
||||||
GZIP_COMPRESSION ?= no
|
|
||||||
COMPRESS_W_YUI ?= no
|
|
||||||
YUI-COMPRESSOR ?= /usr/bin/yui-compressor
|
|
||||||
USE_HEATSHRINK ?= yes
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Output directors to store intermediate compiled files
|
|
||||||
# relative to the project directory
|
|
||||||
BUILD_BASE = build
|
|
||||||
|
|
||||||
# Base directory for the compiler. Needs a / at the end; if not set it'll use the tools that are in
|
|
||||||
# the PATH.
|
|
||||||
XTENSA_TOOLS_ROOT ?=
|
|
||||||
|
|
||||||
# base directory of the ESP8266 SDK package, absolute
|
|
||||||
SDK_BASE ?= /opt/Espressif/ESP8266_SDK
|
|
||||||
|
|
||||||
# name for the target project
|
|
||||||
LIB = libesphttpd.a
|
|
||||||
|
|
||||||
# which modules (subdirectories) of the project to include in compiling
|
|
||||||
MODULES = espfs core util
|
|
||||||
EXTRA_INCDIR = ./include \
|
|
||||||
. \
|
|
||||||
lib/heatshrink/
|
|
||||||
|
|
||||||
|
|
||||||
# compiler flags using during compilation of source files
|
|
||||||
CFLAGS = -Os -ggdb -std=c99 -Werror -Wpointer-arith -Wundef -Wall -Wl,-EL -fno-inline-functions \
|
|
||||||
-nostdlib -mlongcalls -mtext-section-literals -D__ets__ -DICACHE_FLASH -D_STDINT_H \
|
|
||||||
-Wno-address
|
|
||||||
|
|
||||||
# various paths from the SDK used in this project
|
|
||||||
SDK_LIBDIR = lib
|
|
||||||
SDK_LDDIR = ld
|
|
||||||
SDK_INCDIR = include
|
|
||||||
|
|
||||||
# select which tools to use as compiler, librarian and linker
|
|
||||||
CC := $(XTENSA_TOOLS_ROOT)xtensa-lx106-elf-gcc
|
|
||||||
AR := $(XTENSA_TOOLS_ROOT)xtensa-lx106-elf-ar
|
|
||||||
LD := $(XTENSA_TOOLS_ROOT)xtensa-lx106-elf-gcc
|
|
||||||
OBJCOPY := $(XTENSA_TOOLS_ROOT)xtensa-lx106-elf-objcopy
|
|
||||||
|
|
||||||
####
|
|
||||||
#### no user configurable options below here
|
|
||||||
####
|
|
||||||
SRC_DIR := $(MODULES)
|
|
||||||
BUILD_DIR := $(addprefix $(BUILD_BASE)/,$(MODULES))
|
|
||||||
|
|
||||||
SDK_INCDIR := $(addprefix -I$(SDK_BASE)/,$(SDK_INCDIR))
|
|
||||||
|
|
||||||
SRC := $(foreach sdir,$(SRC_DIR),$(wildcard $(sdir)/*.c))
|
|
||||||
OBJ := $(patsubst %.c,$(BUILD_BASE)/%.o,$(SRC))
|
|
||||||
|
|
||||||
INCDIR := $(addprefix -I,$(SRC_DIR))
|
|
||||||
EXTRA_INCDIR := $(addprefix -I,$(EXTRA_INCDIR))
|
|
||||||
MODULE_INCDIR := $(addsuffix /include,$(INCDIR))
|
|
||||||
|
|
||||||
V ?= $(VERBOSE)
|
|
||||||
ifeq ("$(V)","1") |
|
||||||
Q :=
|
|
||||||
vecho := @true
|
|
||||||
else |
|
||||||
Q := @
|
|
||||||
vecho := @echo
|
|
||||||
endif |
|
||||||
|
|
||||||
ifeq ("$(GZIP_COMPRESSION)","yes") |
|
||||||
CFLAGS += -DGZIP_COMPRESSION
|
|
||||||
endif |
|
||||||
|
|
||||||
ifeq ("$(USE_HEATSHRINK)","yes") |
|
||||||
CFLAGS += -DESPFS_HEATSHRINK
|
|
||||||
endif |
|
||||||
|
|
||||||
vpath %.c $(SRC_DIR) |
|
||||||
|
|
||||||
define compile-objects |
|
||||||
$1/%.o: %.c |
|
||||||
$(vecho) "CC $$<"
|
|
||||||
$(Q) $(CC) $(INCDIR) $(MODULE_INCDIR) $(EXTRA_INCDIR) $(SDK_INCDIR) $(CFLAGS) -c $$< -o $$@
|
|
||||||
endef |
|
||||||
|
|
||||||
.PHONY: all checkdirs clean webpages.espfs |
|
||||||
|
|
||||||
all: checkdirs $(LIB) webpages.espfs libwebpages-espfs.a |
|
||||||
|
|
||||||
|
|
||||||
$(LIB): $(OBJ) |
|
||||||
$(vecho) "AR $@"
|
|
||||||
$(Q) $(AR) cru $@ $^
|
|
||||||
|
|
||||||
checkdirs: $(BUILD_DIR) |
|
||||||
|
|
||||||
$(BUILD_DIR): |
|
||||||
$(Q) mkdir -p $@
|
|
||||||
|
|
||||||
|
|
||||||
webpages.espfs: $(HTMLDIR) espfs/mkespfsimage/mkespfsimage |
|
||||||
ifeq ("$(COMPRESS_W_YUI)","yes") |
|
||||||
$(Q) rm -rf html_compressed;
|
|
||||||
$(Q) cp -r ../html html_compressed;
|
|
||||||
$(Q) echo "Compression assets with yui-compressor. This may take a while..."
|
|
||||||
$(Q) for file in `find html_compressed -type f -name "*.js"`; do $(YUI-COMPRESSOR) --type js $$file -o $$file; done
|
|
||||||
$(Q) for file in `find html_compressed -type f -name "*.css"`; do $(YUI-COMPRESSOR) --type css $$file -o $$file; done
|
|
||||||
$(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}"
|
|
||||||
# mkespfsimage will compress html, css and js files with gzip by default if enabled
|
|
||||||
# override with -g cmdline parameter
|
|
||||||
$(Q) cd html_compressed; find | $(THISDIR)/espfs/mkespfsimage/mkespfsimage > $(THISDIR)/webpages.espfs; cd ..;
|
|
||||||
else |
|
||||||
$(Q) cd ../html; find | $(THISDIR)/espfs/mkespfsimage/mkespfsimage > $(THISDIR)/webpages.espfs; cd ..
|
|
||||||
endif |
|
||||||
|
|
||||||
libwebpages-espfs.a: webpages.espfs |
|
||||||
$(Q) $(OBJCOPY) -I binary -O elf32-xtensa-le -B xtensa --rename-section .data=.irom0.literal \
|
|
||||||
--redefine-sym _binary_webpages_espfs_start=webpages_espfs_start \
|
|
||||||
--redefine-sym _binary_webpages_espfs_end=webpages_espfs_end \
|
|
||||||
--redefine-sym _binary_webpages_espfs_size=webpages_espfs_size \
|
|
||||||
webpages.espfs build/webpages.espfs.o
|
|
||||||
$(Q) $(AR) cru $@ build/webpages.espfs.o
|
|
||||||
|
|
||||||
espfs/mkespfsimage/mkespfsimage: espfs/mkespfsimage/ |
|
||||||
$(Q) $(MAKE) -C espfs/mkespfsimage USE_HEATSHRINK="$(USE_HEATSHRINK)" GZIP_COMPRESSION="$(GZIP_COMPRESSION)"
|
|
||||||
|
|
||||||
clean: |
|
||||||
$(Q) rm -f $(LIB)
|
|
||||||
$(Q) find $(BUILD_BASE) -type f | xargs rm -f
|
|
||||||
$(Q) make -C espfs/mkespfsimage/ clean
|
|
||||||
$(Q) rm -rf $(FW_BASE)
|
|
||||||
$(Q) rm -f webpages.espfs
|
|
||||||
ifeq ("$(COMPRESS_W_YUI)","yes") |
|
||||||
$(Q) rm -rf html_compressed
|
|
||||||
endif |
|
||||||
|
|
||||||
$(foreach bdir,$(BUILD_DIR),$(eval $(call compile-objects,$(bdir)))) |
|
@ -1,61 +0,0 @@ |
|||||||
/*
|
|
||||||
HTTP auth implementation. Only does basic authentication for now. |
|
||||||
*/ |
|
||||||
|
|
||||||
/*
|
|
||||||
* ---------------------------------------------------------------------------- |
|
||||||
* "THE BEER-WARE LICENSE" (Revision 42): |
|
||||||
* Jeroen Domburg <jeroen@spritesmods.com> wrote this file. As long as you retain
|
|
||||||
* this notice you can do whatever you want with this stuff. If we meet some day,
|
|
||||||
* and you think this stuff is worth it, you can buy me a beer in return.
|
|
||||||
* ---------------------------------------------------------------------------- |
|
||||||
*/ |
|
||||||
|
|
||||||
|
|
||||||
#include <esp8266.h> |
|
||||||
#include "auth.h" |
|
||||||
#include "base64.h" |
|
||||||
|
|
||||||
int ICACHE_FLASH_ATTR authBasic(HttpdConnData *connData) { |
|
||||||
const char *forbidden="401 Forbidden."; |
|
||||||
int no=0; |
|
||||||
int r; |
|
||||||
char hdr[(AUTH_MAX_USER_LEN+AUTH_MAX_PASS_LEN+2)*10]; |
|
||||||
char userpass[AUTH_MAX_USER_LEN+AUTH_MAX_PASS_LEN+2]; |
|
||||||
char user[AUTH_MAX_USER_LEN]; |
|
||||||
char pass[AUTH_MAX_PASS_LEN]; |
|
||||||
if (connData->conn==NULL) { |
|
||||||
//Connection aborted. Clean up.
|
|
||||||
return HTTPD_CGI_DONE; |
|
||||||
} |
|
||||||
|
|
||||||
r=httpdGetHeader(connData, "Authorization", hdr, sizeof(hdr)); |
|
||||||
if (r && strncmp(hdr, "Basic", 5)==0) { |
|
||||||
r=base64_decode(strlen(hdr)-6, hdr+6, sizeof(userpass), (unsigned char *)userpass); |
|
||||||
if (r<0) r=0; //just clean out string on decode error
|
|
||||||
userpass[r]=0; //zero-terminate user:pass string
|
|
||||||
// os_printf("Auth: %s\n", userpass);
|
|
||||||
while (((AuthGetUserPw)(connData->cgiArg))(connData, no, |
|
||||||
user, AUTH_MAX_USER_LEN, pass, AUTH_MAX_PASS_LEN)) { |
|
||||||
//Check user/pass against auth header
|
|
||||||
if (strlen(userpass)==strlen(user)+strlen(pass)+1 && |
|
||||||
os_strncmp(userpass, user, strlen(user))==0 && |
|
||||||
userpass[strlen(user)]==':' && |
|
||||||
os_strcmp(userpass+strlen(user)+1, pass)==0) { |
|
||||||
//Authenticated. Yay!
|
|
||||||
return HTTPD_CGI_AUTHENTICATED; |
|
||||||
} |
|
||||||
no++; //Not authenticated with this user/pass. Check next user/pass combo.
|
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
//Not authenticated. Go bug user with login screen.
|
|
||||||
httpdStartResponse(connData, 401); |
|
||||||
httpdHeader(connData, "Content-Type", "text/plain"); |
|
||||||
httpdHeader(connData, "WWW-Authenticate", "Basic realm=\""HTTP_AUTH_REALM"\""); |
|
||||||
httpdEndHeaders(connData); |
|
||||||
httpdSend(connData, forbidden, -1); |
|
||||||
//Okay, all done.
|
|
||||||
return HTTPD_CGI_DONE; |
|
||||||
} |
|
||||||
|
|
@ -1,112 +0,0 @@ |
|||||||
/* base64.c : base-64 / MIME encode/decode */ |
|
||||||
/* PUBLIC DOMAIN - Jon Mayo - November 13, 2003 */ |
|
||||||
#include <esp8266.h> |
|
||||||
#include "base64.h" |
|
||||||
|
|
||||||
static const uint8_t base64dec_tab[256]= { |
|
||||||
255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, |
|
||||||
255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, |
|
||||||
255,255,255,255,255,255,255,255,255,255,255, 62,255,255,255, 63, |
|
||||||
52, 53, 54, 55, 56, 57, 58, 59, 60, 61,255,255,255, 0,255,255, |
|
||||||
255, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, |
|
||||||
15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25,255,255,255,255,255, |
|
||||||
255, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, |
|
||||||
41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51,255,255,255,255,255, |
|
||||||
255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, |
|
||||||
255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, |
|
||||||
255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, |
|
||||||
255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, |
|
||||||
255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, |
|
||||||
255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, |
|
||||||
255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, |
|
||||||
255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255, |
|
||||||
}; |
|
||||||
|
|
||||||
#if 0 |
|
||||||
static int ICACHE_FLASH_ATTR base64decode(const char in[4], char out[3]) { |
|
||||||
uint8_t v[4]; |
|
||||||
|
|
||||||
v[0]=base64dec_tab[(unsigned)in[0]]; |
|
||||||
v[1]=base64dec_tab[(unsigned)in[1]]; |
|
||||||
v[2]=base64dec_tab[(unsigned)in[2]]; |
|
||||||
v[3]=base64dec_tab[(unsigned)in[3]]; |
|
||||||
|
|
||||||
out[0]=(v[0]<<2)|(v[1]>>4);
|
|
||||||
out[1]=(v[1]<<4)|(v[2]>>2);
|
|
||||||
out[2]=(v[2]<<6)|(v[3]);
|
|
||||||
return (v[0]|v[1]|v[2]|v[3])!=255 ? in[3]=='=' ? in[2]=='=' ? 1 : 2 : 3 : 0; |
|
||||||
} |
|
||||||
#endif |
|
||||||
|
|
||||||
/* decode a base64 string in one shot */ |
|
||||||
int ICACHE_FLASH_ATTR base64_decode(size_t in_len, const char *in, size_t out_len, unsigned char *out) { |
|
||||||
unsigned int ii, io; |
|
||||||
uint32_t v; |
|
||||||
unsigned int rem; |
|
||||||
|
|
||||||
for(io=0,ii=0,v=0,rem=0;ii<in_len;ii++) { |
|
||||||
unsigned char ch; |
|
||||||
if(isspace((int)in[ii])) continue; |
|
||||||
if(in[ii]=='=') break; /* stop at = */ |
|
||||||
ch=base64dec_tab[(unsigned int)in[ii]]; |
|
||||||
if(ch==255) break; /* stop at a parse error */ |
|
||||||
v=(v<<6)|ch; |
|
||||||
rem+=6; |
|
||||||
if(rem>=8) { |
|
||||||
rem-=8; |
|
||||||
if(io>=out_len) return -1; /* truncation is failure */ |
|
||||||
out[io++]=(v>>rem)&255; |
|
||||||
} |
|
||||||
} |
|
||||||
if(rem>=8) { |
|
||||||
rem-=8; |
|
||||||
if(io>=out_len) return -1; /* truncation is failure */ |
|
||||||
out[io++]=(v>>rem)&255; |
|
||||||
} |
|
||||||
return io; |
|
||||||
} |
|
||||||
|
|
||||||
//Only need decode functions for now.
|
|
||||||
#if 0 |
|
||||||
|
|
||||||
static const uint8_t base64enc_tab[64]= "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; |
|
||||||
|
|
||||||
void base64encode(const unsigned char in[3], unsigned char out[4], int count) { |
|
||||||
out[0]=base64enc_tab[(in[0]>>2)]; |
|
||||||
out[1]=base64enc_tab[((in[0]&3)<<4)|(in[1]>>4)]; |
|
||||||
out[2]=count<2 ? '=' : base64enc_tab[((in[1]&15)<<2)|(in[2]>>6)]; |
|
||||||
out[3]=count<3 ? '=' : base64enc_tab[(in[2]&63)]; |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
int base64_encode(size_t in_len, const unsigned char *in, size_t out_len, char *out) { |
|
||||||
unsigned ii, io; |
|
||||||
uint_least32_t v; |
|
||||||
unsigned rem; |
|
||||||
|
|
||||||
for(io=0,ii=0,v=0,rem=0;ii<in_len;ii++) { |
|
||||||
unsigned char ch; |
|
||||||
ch=in[ii]; |
|
||||||
v=(v<<8)|ch; |
|
||||||
rem+=8; |
|
||||||
while(rem>=6) { |
|
||||||
rem-=6; |
|
||||||
if(io>=out_len) return -1; /* truncation is failure */ |
|
||||||
out[io++]=base64enc_tab[(v>>rem)&63]; |
|
||||||
} |
|
||||||
} |
|
||||||
if(rem) { |
|
||||||
v<<=(6-rem); |
|
||||||
if(io>=out_len) return -1; /* truncation is failure */ |
|
||||||
out[io++]=base64enc_tab[v&63]; |
|
||||||
} |
|
||||||
while(io&3) { |
|
||||||
if(io>=out_len) return -1; /* truncation is failure */ |
|
||||||
out[io++]='='; |
|
||||||
} |
|
||||||
if(io>=out_len) return -1; /* no room for null terminator */ |
|
||||||
out[io]=0; |
|
||||||
return io; |
|
||||||
} |
|
||||||
|
|
||||||
#endif |
|
@ -1,6 +0,0 @@ |
|||||||
#ifndef BASE64_H |
|
||||||
#define BASE64_H |
|
||||||
|
|
||||||
int base64_decode(size_t in_len, const char *in, size_t out_len, unsigned char *out); |
|
||||||
|
|
||||||
#endif |
|
@ -1,560 +0,0 @@ |
|||||||
/*
|
|
||||||
Esp8266 http server - core routines |
|
||||||
*/ |
|
||||||
|
|
||||||
/*
|
|
||||||
* ---------------------------------------------------------------------------- |
|
||||||
* "THE BEER-WARE LICENSE" (Revision 42): |
|
||||||
* Jeroen Domburg <jeroen@spritesmods.com> wrote this file. As long as you retain
|
|
||||||
* this notice you can do whatever you want with this stuff. If we meet some day,
|
|
||||||
* and you think this stuff is worth it, you can buy me a beer in return.
|
|
||||||
* ---------------------------------------------------------------------------- |
|
||||||
*/ |
|
||||||
|
|
||||||
|
|
||||||
#include <esp8266.h> |
|
||||||
#include "httpd.h" |
|
||||||
|
|
||||||
|
|
||||||
//Max length of request head
|
|
||||||
#define MAX_HEAD_LEN 1024 |
|
||||||
//Max amount of connections
|
|
||||||
#define MAX_CONN 8 |
|
||||||
//Max post buffer len
|
|
||||||
#define MAX_POST 1024 |
|
||||||
//Max send buffer len
|
|
||||||
#define MAX_SENDBUFF_LEN 2048 |
|
||||||
|
|
||||||
|
|
||||||
//This gets set at init time.
|
|
||||||
static HttpdBuiltInUrl *builtInUrls; |
|
||||||
|
|
||||||
//Private data for http connection
|
|
||||||
struct HttpdPriv { |
|
||||||
char head[MAX_HEAD_LEN]; |
|
||||||
int headPos; |
|
||||||
char *sendBuff; |
|
||||||
int sendBuffLen; |
|
||||||
}; |
|
||||||
|
|
||||||
//Connection pool
|
|
||||||
static HttpdPriv connPrivData[MAX_CONN]; |
|
||||||
static HttpdConnData connData[MAX_CONN]; |
|
||||||
static HttpdPostData connPostData[MAX_CONN]; |
|
||||||
|
|
||||||
//Listening connection data
|
|
||||||
static struct espconn httpdConn; |
|
||||||
static esp_tcp httpdTcp; |
|
||||||
|
|
||||||
//Struct to keep extension->mime data in
|
|
||||||
typedef struct { |
|
||||||
const char *ext; |
|
||||||
const char *mimetype; |
|
||||||
} MimeMap; |
|
||||||
|
|
||||||
//The mappings from file extensions to mime types. If you need an extra mime type,
|
|
||||||
//add it here.
|
|
||||||
static const MimeMap mimeTypes[]={ |
|
||||||
{"htm", "text/htm"}, |
|
||||||
{"html", "text/html"}, |
|
||||||
{"css", "text/css"}, |
|
||||||
{"js", "text/javascript"}, |
|
||||||
{"txt", "text/plain"}, |
|
||||||
{"jpg", "image/jpeg"}, |
|
||||||
{"jpeg", "image/jpeg"}, |
|
||||||
{"png", "image/png"}, |
|
||||||
{NULL, "text/html"}, //default value
|
|
||||||
}; |
|
||||||
|
|
||||||
//Returns a static char* to a mime type for a given url to a file.
|
|
||||||
const char ICACHE_FLASH_ATTR *httpdGetMimetype(char *url) { |
|
||||||
int i=0; |
|
||||||
//Go find the extension
|
|
||||||
char *ext=url+(strlen(url)-1); |
|
||||||
while (ext!=url && *ext!='.') ext--; |
|
||||||
if (*ext=='.') ext++; |
|
||||||
|
|
||||||
//ToDo: os_strcmp is case sensitive; we may want to do case-intensive matching here...
|
|
||||||
while (mimeTypes[i].ext!=NULL && os_strcmp(ext, mimeTypes[i].ext)!=0) i++; |
|
||||||
return mimeTypes[i].mimetype; |
|
||||||
} |
|
||||||
|
|
||||||
//Looks up the connData info for a specific esp connection
|
|
||||||
static HttpdConnData ICACHE_FLASH_ATTR *httpdFindConnData(void *arg) { |
|
||||||
int i; |
|
||||||
for (i=0; i<MAX_CONN; i++) { |
|
||||||
if (connData[i].conn==(struct espconn *)arg) return &connData[i]; |
|
||||||
} |
|
||||||
//Shouldn't happen.
|
|
||||||
os_printf("FindConnData: Huh? Couldn't find connection for %p\n", arg); |
|
||||||
return NULL; |
|
||||||
} |
|
||||||
|
|
||||||
//Retires a connection for re-use
|
|
||||||
static void ICACHE_FLASH_ATTR httpdRetireConn(HttpdConnData *conn) { |
|
||||||
if (conn->post->buff!=NULL) os_free(conn->post->buff); |
|
||||||
conn->post->buff=NULL; |
|
||||||
conn->cgi=NULL; |
|
||||||
conn->conn=NULL; |
|
||||||
} |
|
||||||
|
|
||||||
//Stupid li'l helper function that returns the value of a hex char.
|
|
||||||
static int httpdHexVal(char c) { |
|
||||||
if (c>='0' && c<='9') return c-'0'; |
|
||||||
if (c>='A' && c<='F') return c-'A'+10; |
|
||||||
if (c>='a' && c<='f') return c-'a'+10; |
|
||||||
return 0; |
|
||||||
} |
|
||||||
|
|
||||||
//Decode a percent-encoded value.
|
|
||||||
//Takes the valLen bytes stored in val, and converts it into at most retLen bytes that
|
|
||||||
//are stored in the ret buffer. Returns the actual amount of bytes used in ret. Also
|
|
||||||
//zero-terminates the ret buffer.
|
|
||||||
int httpdUrlDecode(char *val, int valLen, char *ret, int retLen) { |
|
||||||
int s=0, d=0; |
|
||||||
int esced=0, escVal=0; |
|
||||||
while (s<valLen && d<retLen) { |
|
||||||
if (esced==1) { |
|
||||||
escVal=httpdHexVal(val[s])<<4; |
|
||||||
esced=2; |
|
||||||
} else if (esced==2) { |
|
||||||
escVal+=httpdHexVal(val[s]); |
|
||||||
ret[d++]=escVal; |
|
||||||
esced=0; |
|
||||||
} else if (val[s]=='%') { |
|
||||||
esced=1; |
|
||||||
} else if (val[s]=='+') { |
|
||||||
ret[d++]=' '; |
|
||||||
} else { |
|
||||||
ret[d++]=val[s]; |
|
||||||
} |
|
||||||
s++; |
|
||||||
} |
|
||||||
if (d<retLen) ret[d]=0; |
|
||||||
return d; |
|
||||||
} |
|
||||||
|
|
||||||
//Find a specific arg in a string of get- or post-data.
|
|
||||||
//Line is the string of post/get-data, arg is the name of the value to find. The
|
|
||||||
//zero-terminated result is written in buff, with at most buffLen bytes used. The
|
|
||||||
//function returns the length of the result, or -1 if the value wasn't found. The
|
|
||||||
//returned string will be urldecoded already.
|
|
||||||
int ICACHE_FLASH_ATTR httpdFindArg(char *line, char *arg, char *buff, int buffLen) { |
|
||||||
char *p, *e; |
|
||||||
if (line==NULL) return 0; |
|
||||||
p=line; |
|
||||||
while(p!=NULL && *p!='\n' && *p!='\r' && *p!=0) { |
|
||||||
os_printf("findArg: %s\n", p); |
|
||||||
if (os_strncmp(p, arg, os_strlen(arg))==0 && p[strlen(arg)]=='=') { |
|
||||||
p+=os_strlen(arg)+1; //move p to start of value
|
|
||||||
e=(char*)os_strstr(p, "&"); |
|
||||||
if (e==NULL) e=p+os_strlen(p); |
|
||||||
os_printf("findArg: val %s len %d\n", p, (e-p)); |
|
||||||
return httpdUrlDecode(p, (e-p), buff, buffLen); |
|
||||||
} |
|
||||||
p=(char*)os_strstr(p, "&"); |
|
||||||
if (p!=NULL) p+=1; |
|
||||||
} |
|
||||||
os_printf("Finding %s in %s: Not found :/\n", arg, line); |
|
||||||
return -1; //not found
|
|
||||||
} |
|
||||||
|
|
||||||
//Get the value of a certain header in the HTTP client head
|
|
||||||
int ICACHE_FLASH_ATTR httpdGetHeader(HttpdConnData *conn, char *header, char *ret, int retLen) { |
|
||||||
char *p=conn->priv->head; |
|
||||||
p=p+strlen(p)+1; //skip GET/POST part
|
|
||||||
p=p+strlen(p)+1; //skip HTTP part
|
|
||||||
while (p<(conn->priv->head+conn->priv->headPos)) { |
|
||||||
while(*p<=32 && *p!=0) p++; //skip crap at start
|
|
||||||
//See if this is the header
|
|
||||||
if (os_strncmp(p, header, strlen(header))==0 && p[strlen(header)]==':') { |
|
||||||
//Skip 'key:' bit of header line
|
|
||||||
p=p+strlen(header)+1; |
|
||||||
//Skip past spaces after the colon
|
|
||||||
while(*p==' ') p++; |
|
||||||
//Copy from p to end
|
|
||||||
while (*p!=0 && *p!='\r' && *p!='\n' && retLen>1) { |
|
||||||
*ret++=*p++; |
|
||||||
retLen--; |
|
||||||
} |
|
||||||
//Zero-terminate string
|
|
||||||
*ret=0; |
|
||||||
//All done :)
|
|
||||||
return 1; |
|
||||||
} |
|
||||||
p+=strlen(p)+1; //Skip past end of string and \0 terminator
|
|
||||||
} |
|
||||||
return 0; |
|
||||||
} |
|
||||||
|
|
||||||
//Start the response headers.
|
|
||||||
void ICACHE_FLASH_ATTR httpdStartResponse(HttpdConnData *conn, int code) { |
|
||||||
char buff[128]; |
|
||||||
int l; |
|
||||||
l=os_sprintf(buff, "HTTP/1.0 %d OK\r\nServer: esp8266-httpd/"HTTPDVER"\r\nConnection: close\r\n", code); |
|
||||||
httpdSend(conn, buff, l); |
|
||||||
} |
|
||||||
|
|
||||||
//Send a http header.
|
|
||||||
void ICACHE_FLASH_ATTR httpdHeader(HttpdConnData *conn, const char *field, const char *val) { |
|
||||||
char buff[256]; |
|
||||||
int l; |
|
||||||
|
|
||||||
l=os_sprintf(buff, "%s: %s\r\n", field, val); |
|
||||||
httpdSend(conn, buff, l); |
|
||||||
} |
|
||||||
|
|
||||||
//Finish the headers.
|
|
||||||
void ICACHE_FLASH_ATTR httpdEndHeaders(HttpdConnData *conn) { |
|
||||||
httpdSend(conn, "\r\n", -1); |
|
||||||
} |
|
||||||
|
|
||||||
//ToDo: sprintf->snprintf everywhere... esp doesn't have snprintf tho' :/
|
|
||||||
//Redirect to the given URL.
|
|
||||||
void ICACHE_FLASH_ATTR httpdRedirect(HttpdConnData *conn, char *newUrl) { |
|
||||||
char buff[1024]; |
|
||||||
int l; |
|
||||||
l=os_sprintf(buff, "HTTP/1.1 302 Found\r\nServer: esp8266-httpd/"HTTPDVER"\r\nConnection: close\r\nLocation: %s\r\n\r\nMoved to %s\r\n", newUrl, newUrl); |
|
||||||
httpdSend(conn, buff, l); |
|
||||||
} |
|
||||||
|
|
||||||
//Use this as a cgi function to redirect one url to another.
|
|
||||||
int ICACHE_FLASH_ATTR cgiRedirect(HttpdConnData *connData) { |
|
||||||
if (connData->conn==NULL) { |
|
||||||
//Connection aborted. Clean up.
|
|
||||||
return HTTPD_CGI_DONE; |
|
||||||
} |
|
||||||
httpdRedirect(connData, (char*)connData->cgiArg); |
|
||||||
return HTTPD_CGI_DONE; |
|
||||||
} |
|
||||||
|
|
||||||
//This CGI function redirects to a fixed url of http://[hostname]/ if hostname field of request isn't
|
|
||||||
//already that hostname. Use this in combination with a DNS server that redirects everything to the
|
|
||||||
//ESP in order to load a HTML page as soon as a phone connects to the ESP.
|
|
||||||
int ICACHE_FLASH_ATTR cgiCheckHostname(HttpdConnData *connData) { |
|
||||||
char buff[1024]; |
|
||||||
if (connData->conn==NULL) { |
|
||||||
//Connection aborted. Clean up.
|
|
||||||
return HTTPD_CGI_DONE; |
|
||||||
} |
|
||||||
//Check hostname; pass on if the same
|
|
||||||
if (connData->hostName==NULL || os_strcmp(connData->hostName, (char*)connData->cgiArg)==0) return HTTPD_CGI_NOTFOUND; |
|
||||||
//Not the same. Redirect to real hostname.
|
|
||||||
os_sprintf(buff, "http://%s/", (char*)connData->cgiArg); |
|
||||||
os_printf("Redirecting to hostname url %s\n", buff); |
|
||||||
httpdRedirect(connData, buff); |
|
||||||
return HTTPD_CGI_DONE; |
|
||||||
} |
|
||||||
|
|
||||||
//Add data to the send buffer. len is the length of the data. If len is -1
|
|
||||||
//the data is seen as a C-string.
|
|
||||||
//Returns 1 for success, 0 for out-of-memory.
|
|
||||||
int ICACHE_FLASH_ATTR httpdSend(HttpdConnData *conn, const char *data, int len) { |
|
||||||
if (len<0) len=strlen(data); |
|
||||||
if (conn->priv->sendBuffLen+len>MAX_SENDBUFF_LEN) return 0; |
|
||||||
os_memcpy(conn->priv->sendBuff+conn->priv->sendBuffLen, data, len); |
|
||||||
conn->priv->sendBuffLen+=len; |
|
||||||
return 1; |
|
||||||
} |
|
||||||
|
|
||||||
//Helper function to send any data in conn->priv->sendBuff
|
|
||||||
static void ICACHE_FLASH_ATTR xmitSendBuff(HttpdConnData *conn) { |
|
||||||
if (conn->priv->sendBuffLen!=0) { |
|
||||||
espconn_sent(conn->conn, (uint8_t*)conn->priv->sendBuff, conn->priv->sendBuffLen); |
|
||||||
conn->priv->sendBuffLen=0; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
//Callback called when the data on a socket has been successfully
|
|
||||||
//sent.
|
|
||||||
static void ICACHE_FLASH_ATTR httpdSentCb(void *arg) { |
|
||||||
int r; |
|
||||||
HttpdConnData *conn=httpdFindConnData(arg); |
|
||||||
char sendBuff[MAX_SENDBUFF_LEN]; |
|
||||||
|
|
||||||
if (conn==NULL) return; |
|
||||||
conn->priv->sendBuff=sendBuff; |
|
||||||
conn->priv->sendBuffLen=0; |
|
||||||
|
|
||||||
if (conn->cgi==NULL) { //Marked for destruction?
|
|
||||||
os_printf("Conn %p is done. Closing.\n", conn->conn); |
|
||||||
espconn_disconnect(conn->conn); |
|
||||||
httpdRetireConn(conn); |
|
||||||
return; //No need to call xmitSendBuff.
|
|
||||||
} |
|
||||||
|
|
||||||
r=conn->cgi(conn); //Execute cgi fn.
|
|
||||||
if (r==HTTPD_CGI_DONE) { |
|
||||||
conn->cgi=NULL; //mark for destruction.
|
|
||||||
} |
|
||||||
if (r==HTTPD_CGI_NOTFOUND || r==HTTPD_CGI_AUTHENTICATED) { |
|
||||||
os_printf("ERROR! CGI fn returns code %d after sending data! Bad CGI!\n", r); |
|
||||||
conn->cgi=NULL; //mark for destruction.
|
|
||||||
} |
|
||||||
xmitSendBuff(conn); |
|
||||||
} |
|
||||||
|
|
||||||
static const char *httpNotFoundHeader="HTTP/1.0 404 Not Found\r\nServer: esp8266-httpd/"HTTPDVER"\r\nConnection: close\r\nContent-Type: text/plain\r\nContent-Length: 12\r\n\r\nNot Found.\r\n"; |
|
||||||
|
|
||||||
//This is called when the headers have been received and the connection is ready to send
|
|
||||||
//the result headers and data.
|
|
||||||
//We need to find the CGI function to call, call it, and dependent on what it returns either
|
|
||||||
//find the next cgi function, wait till the cgi data is sent or close up the connection.
|
|
||||||
static void ICACHE_FLASH_ATTR httpdProcessRequest(HttpdConnData *conn) { |
|
||||||
int r; |
|
||||||
int i=0; |
|
||||||
if (conn->url==NULL) { |
|
||||||
os_printf("WtF? url = NULL\n"); |
|
||||||
return; //Shouldn't happen
|
|
||||||
} |
|
||||||
//See if we can find a CGI that's happy to handle the request.
|
|
||||||
while (1) { |
|
||||||
//Look up URL in the built-in URL table.
|
|
||||||
while (builtInUrls[i].url!=NULL) { |
|
||||||
int match=0; |
|
||||||
//See if there's a literal match
|
|
||||||
if (os_strcmp(builtInUrls[i].url, conn->url)==0) match=1; |
|
||||||
//See if there's a wildcard match
|
|
||||||
if (builtInUrls[i].url[os_strlen(builtInUrls[i].url)-1]=='*' && |
|
||||||
os_strncmp(builtInUrls[i].url, conn->url, os_strlen(builtInUrls[i].url)-1)==0) match=1; |
|
||||||
if (match) { |
|
||||||
os_printf("Is url index %d\n", i); |
|
||||||
conn->cgiData=NULL; |
|
||||||
conn->cgi=builtInUrls[i].cgiCb; |
|
||||||
conn->cgiArg=builtInUrls[i].cgiArg; |
|
||||||
break; |
|
||||||
} |
|
||||||
i++; |
|
||||||
} |
|
||||||
if (builtInUrls[i].url==NULL) { |
|
||||||
//Drat, we're at the end of the URL table. This usually shouldn't happen. Well, just
|
|
||||||
//generate a built-in 404 to handle this.
|
|
||||||
os_printf("%s not found. 404!\n", conn->url); |
|
||||||
httpdSend(conn, httpNotFoundHeader, -1); |
|
||||||
xmitSendBuff(conn); |
|
||||||
conn->cgi=NULL; //mark for destruction
|
|
||||||
return; |
|
||||||
} |
|
||||||
|
|
||||||
//Okay, we have a CGI function that matches the URL. See if it wants to handle the
|
|
||||||
//particular URL we're supposed to handle.
|
|
||||||
r=conn->cgi(conn); |
|
||||||
if (r==HTTPD_CGI_MORE) { |
|
||||||
//Yep, it's happy to do so and has more data to send.
|
|
||||||
xmitSendBuff(conn); |
|
||||||
return; |
|
||||||
} else if (r==HTTPD_CGI_DONE) { |
|
||||||
//Yep, it's happy to do so and already is done sending data.
|
|
||||||
xmitSendBuff(conn); |
|
||||||
conn->cgi=NULL; //mark conn for destruction
|
|
||||||
return; |
|
||||||
} else if (r==HTTPD_CGI_NOTFOUND || r==HTTPD_CGI_AUTHENTICATED) { |
|
||||||
//URL doesn't want to handle the request: either the data isn't found or there's no
|
|
||||||
//need to generate a login screen.
|
|
||||||
i++; //look at next url the next iteration of the loop.
|
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
//Parse a line of header data and modify the connection data accordingly.
|
|
||||||
static void ICACHE_FLASH_ATTR httpdParseHeader(char *h, HttpdConnData *conn) { |
|
||||||
int i; |
|
||||||
char first_line = false; |
|
||||||
|
|
||||||
if (os_strncmp(h, "GET ", 4)==0) { |
|
||||||
conn->requestType = HTTPD_METHOD_GET; |
|
||||||
first_line = true; |
|
||||||
} else if (os_strncmp(h, "Host:", 5)==0) { |
|
||||||
i=5; |
|
||||||
while (h[i]==' ') i++; |
|
||||||
conn->hostName=&h[i]; |
|
||||||
} else if (os_strncmp(h, "POST ", 5)==0) { |
|
||||||
conn->requestType = HTTPD_METHOD_POST; |
|
||||||
first_line = true; |
|
||||||
} |
|
||||||
|
|
||||||
if (first_line) { |
|
||||||
char *e; |
|
||||||
|
|
||||||
//Skip past the space after POST/GET
|
|
||||||
i=0; |
|
||||||
while (h[i]!=' ') i++; |
|
||||||
conn->url=h+i+1; |
|
||||||
|
|
||||||
//Figure out end of url.
|
|
||||||
e=(char*)os_strstr(conn->url, " "); |
|
||||||
if (e==NULL) return; //wtf?
|
|
||||||
*e=0; //terminate url part
|
|
||||||
|
|
||||||
os_printf("URL = %s\n", conn->url); |
|
||||||
//Parse out the URL part before the GET parameters.
|
|
||||||
conn->getArgs=(char*)os_strstr(conn->url, "?"); |
|
||||||
if (conn->getArgs!=0) { |
|
||||||
*conn->getArgs=0; |
|
||||||
conn->getArgs++; |
|
||||||
os_printf("GET args = %s\n", conn->getArgs); |
|
||||||
} else { |
|
||||||
conn->getArgs=NULL; |
|
||||||
} |
|
||||||
|
|
||||||
} else if (os_strncmp(h, "Content-Length:", 15)==0) { |
|
||||||
i=15; |
|
||||||
//Skip trailing spaces
|
|
||||||
while (h[i]==' ') i++; |
|
||||||
//Get POST data length
|
|
||||||
conn->post->len=atoi(h+i); |
|
||||||
|
|
||||||
// Allocate the buffer
|
|
||||||
if (conn->post->len > MAX_POST) { |
|
||||||
// we'll stream this in in chunks
|
|
||||||
conn->post->buffSize = MAX_POST; |
|
||||||
} else { |
|
||||||
conn->post->buffSize = conn->post->len; |
|
||||||
} |
|
||||||
os_printf("Mallocced buffer for %d + 1 bytes of post data.\n", conn->post->buffSize); |
|
||||||
conn->post->buff=(char*)os_malloc(conn->post->buffSize + 1); |
|
||||||
conn->post->buffLen=0; |
|
||||||
} else if (os_strncmp(h, "Content-Type: ", 14)==0) { |
|
||||||
if (os_strstr(h, "multipart/form-data")) { |
|
||||||
// It's multipart form data so let's pull out the boundary for future use
|
|
||||||
char *b; |
|
||||||
if ((b = os_strstr(h, "boundary=")) != NULL) { |
|
||||||
conn->post->multipartBoundary = b + 7; // move the pointer 2 chars before boundary then fill them with dashes
|
|
||||||
conn->post->multipartBoundary[0] = '-'; |
|
||||||
conn->post->multipartBoundary[1] = '-'; |
|
||||||
os_printf("boundary = %s\n", conn->post->multipartBoundary); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
//Callback called when there's data available on a socket.
|
|
||||||
static void ICACHE_FLASH_ATTR httpdRecvCb(void *arg, char *data, unsigned short len) { |
|
||||||
int x; |
|
||||||
char *p, *e; |
|
||||||
char sendBuff[MAX_SENDBUFF_LEN]; |
|
||||||
HttpdConnData *conn=httpdFindConnData(arg); |
|
||||||
if (conn==NULL) return; |
|
||||||
conn->priv->sendBuff=sendBuff; |
|
||||||
conn->priv->sendBuffLen=0; |
|
||||||
|
|
||||||
//This is slightly evil/dirty: we abuse conn->post->len as a state variable for where in the http communications we are:
|
|
||||||
//<0 (-1): Post len unknown because we're still receiving headers
|
|
||||||
//==0: No post data
|
|
||||||
//>0: Need to receive post data
|
|
||||||
//ToDo: See if we can use something more elegant for this.
|
|
||||||
|
|
||||||
for (x=0; x<len; x++) { |
|
||||||
if (conn->post->len<0) { |
|
||||||
//This byte is a header byte.
|
|
||||||
if (conn->priv->headPos!=MAX_HEAD_LEN) conn->priv->head[conn->priv->headPos++]=data[x]; |
|
||||||
conn->priv->head[conn->priv->headPos]=0; |
|
||||||
//Scan for /r/n/r/n. Receiving this indicate the headers end.
|
|
||||||
if (data[x]=='\n' && (char *)os_strstr(conn->priv->head, "\r\n\r\n")!=NULL) { |
|
||||||
//Indicate we're done with the headers.
|
|
||||||
conn->post->len=0; |
|
||||||
//Reset url data
|
|
||||||
conn->url=NULL; |
|
||||||
//Iterate over all received headers and parse them.
|
|
||||||
p=conn->priv->head; |
|
||||||
while(p<(&conn->priv->head[conn->priv->headPos-4])) { |
|
||||||
e=(char *)os_strstr(p, "\r\n"); //Find end of header line
|
|
||||||
if (e==NULL) break; //Shouldn't happen.
|
|
||||||
e[0]=0; //Zero-terminate header
|
|
||||||
httpdParseHeader(p, conn); //and parse it.
|
|
||||||
p=e+2; //Skip /r/n (now /0/n)
|
|
||||||
} |
|
||||||
//If we don't need to receive post data, we can send the response now.
|
|
||||||
if (conn->post->len==0) { |
|
||||||
httpdProcessRequest(conn); |
|
||||||
} |
|
||||||
} |
|
||||||
} else if (conn->post->len!=0) { |
|
||||||
//This byte is a POST byte.
|
|
||||||
conn->post->buff[conn->post->buffLen++]=data[x]; |
|
||||||
conn->post->received++; |
|
||||||
conn->hostName=NULL; |
|
||||||
if (conn->post->buffLen >= conn->post->buffSize || conn->post->received == conn->post->len) { |
|
||||||
//Received a chunk of post data
|
|
||||||
conn->post->buff[conn->post->buffLen]=0; //zero-terminate, in case the cgi handler knows it can use strings
|
|
||||||
//Send the response.
|
|
||||||
httpdProcessRequest(conn); |
|
||||||
conn->post->buffLen = 0; |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
static void ICACHE_FLASH_ATTR httpdReconCb(void *arg, sint8 err) { |
|
||||||
HttpdConnData *conn=httpdFindConnData(arg); |
|
||||||
os_printf("ReconCb\n"); |
|
||||||
if (conn==NULL) return; |
|
||||||
//Yeah... No idea what to do here. ToDo: figure something out.
|
|
||||||
} |
|
||||||
|
|
||||||
static void ICACHE_FLASH_ATTR httpdDisconCb(void *arg) { |
|
||||||
//The esp sdk passes through wrong arg here, namely the one of the *listening* socket.
|
|
||||||
//That is why we can't use (HttpdConnData)arg->sock here.
|
|
||||||
//Just look at all the sockets and kill the slot if needed.
|
|
||||||
int i; |
|
||||||
for (i=0; i<MAX_CONN; i++) { |
|
||||||
if (connData[i].conn!=NULL) { |
|
||||||
//Why the >=ESPCONN_CLOSE and not ==? Well, seems the stack sometimes de-allocates
|
|
||||||
//espconns under our noses, especially when connections are interrupted. The memory
|
|
||||||
//is then used for something else, and we can use that to capture *most* of the
|
|
||||||
//disconnect cases.
|
|
||||||
if (connData[i].conn->state==ESPCONN_NONE || connData[i].conn->state>=ESPCONN_CLOSE) { |
|
||||||
connData[i].conn=NULL; |
|
||||||
if (connData[i].cgi!=NULL) connData[i].cgi(&connData[i]); //flush cgi data
|
|
||||||
httpdRetireConn(&connData[i]); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
static void ICACHE_FLASH_ATTR httpdConnectCb(void *arg) { |
|
||||||
struct espconn *conn=arg; |
|
||||||
int i; |
|
||||||
//Find empty conndata in pool
|
|
||||||
for (i=0; i<MAX_CONN; i++) if (connData[i].conn==NULL) break; |
|
||||||
os_printf("Con req, conn=%p, pool slot %d\n", conn, i); |
|
||||||
if (i==MAX_CONN) { |
|
||||||
os_printf("Aiee, conn pool overflow!\n"); |
|
||||||
espconn_disconnect(conn); |
|
||||||
return; |
|
||||||
} |
|
||||||
connData[i].priv=&connPrivData[i]; |
|
||||||
connData[i].conn=conn; |
|
||||||
connData[i].priv->headPos=0; |
|
||||||
connData[i].post=&connPostData[i]; |
|
||||||
connData[i].post->buff=NULL; |
|
||||||
connData[i].post->buffLen=0; |
|
||||||
connData[i].post->received=0; |
|
||||||
connData[i].post->len=-1; |
|
||||||
connData[i].hostName=NULL; |
|
||||||
|
|
||||||
espconn_regist_recvcb(conn, httpdRecvCb); |
|
||||||
espconn_regist_reconcb(conn, httpdReconCb); |
|
||||||
espconn_regist_disconcb(conn, httpdDisconCb); |
|
||||||
espconn_regist_sentcb(conn, httpdSentCb); |
|
||||||
} |
|
||||||
|
|
||||||
//Httpd initialization routine. Call this to kick off webserver functionality.
|
|
||||||
void ICACHE_FLASH_ATTR httpdInit(HttpdBuiltInUrl *fixedUrls, int port) { |
|
||||||
int i; |
|
||||||
|
|
||||||
for (i=0; i<MAX_CONN; i++) { |
|
||||||
connData[i].conn=NULL; |
|
||||||
} |
|
||||||
httpdConn.type=ESPCONN_TCP; |
|
||||||
httpdConn.state=ESPCONN_NONE; |
|
||||||
httpdTcp.local_port=port; |
|
||||||
httpdConn.proto.tcp=&httpdTcp; |
|
||||||
builtInUrls=fixedUrls; |
|
||||||
|
|
||||||
os_printf("Httpd init, conn=%p\n", &httpdConn); |
|
||||||
espconn_regist_connectcb(&httpdConn, httpdConnectCb); |
|
||||||
espconn_accept(&httpdConn); |
|
||||||
} |
|
@ -1,189 +0,0 @@ |
|||||||
/*
|
|
||||||
Connector to let httpd use the espfs filesystem to serve the files in it. |
|
||||||
*/ |
|
||||||
|
|
||||||
/*
|
|
||||||
* ---------------------------------------------------------------------------- |
|
||||||
* "THE BEER-WARE LICENSE" (Revision 42): |
|
||||||
* Jeroen Domburg <jeroen@spritesmods.com> wrote this file. As long as you retain
|
|
||||||
* this notice you can do whatever you want with this stuff. If we meet some day,
|
|
||||||
* and you think this stuff is worth it, you can buy me a beer in return.
|
|
||||||
* ---------------------------------------------------------------------------- |
|
||||||
*/ |
|
||||||
|
|
||||||
#include <esp8266.h> |
|
||||||
#include "httpdespfs.h" |
|
||||||
#include "espfs.h" |
|
||||||
#include "espfsformat.h" |
|
||||||
|
|
||||||
// The static files marked with FLAG_GZIP are compressed and will be served with GZIP compression.
|
|
||||||
// If the client does not advertise that he accepts GZIP send following warning message (telnet users for e.g.)
|
|
||||||
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"; |
|
||||||
|
|
||||||
|
|
||||||
//This is a catch-all cgi function. It takes the url passed to it, looks up the corresponding
|
|
||||||
//path in the filesystem and if it exists, passes the file through. This simulates what a normal
|
|
||||||
//webserver would do with static files.
|
|
||||||
int ICACHE_FLASH_ATTR cgiEspFsHook(HttpdConnData *connData) { |
|
||||||
EspFsFile *file=connData->cgiData; |
|
||||||
int len; |
|
||||||
char buff[1024]; |
|
||||||
char acceptEncodingBuffer[64]; |
|
||||||
int isGzip; |
|
||||||
|
|
||||||
if (connData->conn==NULL) { |
|
||||||
//Connection aborted. Clean up.
|
|
||||||
espFsClose(file); |
|
||||||
return HTTPD_CGI_DONE; |
|
||||||
} |
|
||||||
|
|
||||||
if (file==NULL) { |
|
||||||
//First call to this cgi. Open the file so we can read it.
|
|
||||||
file=espFsOpen(connData->url); |
|
||||||
if (file==NULL) { |
|
||||||
return HTTPD_CGI_NOTFOUND; |
|
||||||
} |
|
||||||
|
|
||||||
// The gzip checking code is intentionally without #ifdefs because checking
|
|
||||||
// for FLAG_GZIP (which indicates gzip compressed file) is very easy, doesn't
|
|
||||||
// mean additional overhead and is actually safer to be on at all times.
|
|
||||||
// If there are no gzipped files in the image, the code bellow will not cause any harm.
|
|
||||||
|
|
||||||
// Check if requested file was GZIP compressed
|
|
||||||
isGzip = espFsFlags(file) & FLAG_GZIP; |
|
||||||
if (isGzip) { |
|
||||||
// Check the browser's "Accept-Encoding" header. If the client does not
|
|
||||||
// advertise that he accepts GZIP send a warning message (telnet users for e.g.)
|
|
||||||
httpdGetHeader(connData, "Accept-Encoding", acceptEncodingBuffer, 64); |
|
||||||
if (os_strstr(acceptEncodingBuffer, "gzip") == NULL) { |
|
||||||
//No Accept-Encoding: gzip header present
|
|
||||||
httpdSend(connData, gzipNonSupportedMessage, -1); |
|
||||||
espFsClose(file); |
|
||||||
return HTTPD_CGI_DONE; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
connData->cgiData=file; |
|
||||||
httpdStartResponse(connData, 200); |
|
||||||
httpdHeader(connData, "Content-Type", httpdGetMimetype(connData->url)); |
|
||||||
if (isGzip) { |
|
||||||
httpdHeader(connData, "Content-Encoding", "gzip"); |
|
||||||
} |
|
||||||
httpdHeader(connData, "Cache-Control", "max-age=3600, must-revalidate"); |
|
||||||
httpdEndHeaders(connData); |
|
||||||
return HTTPD_CGI_MORE; |
|
||||||
} |
|
||||||
|
|
||||||
len=espFsRead(file, buff, 1024); |
|
||||||
if (len>0) espconn_sent(connData->conn, (uint8 *)buff, len); |
|
||||||
if (len!=1024) { |
|
||||||
//We're done.
|
|
||||||
espFsClose(file); |
|
||||||
return HTTPD_CGI_DONE; |
|
||||||
} else { |
|
||||||
//Ok, till next time.
|
|
||||||
return HTTPD_CGI_MORE; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
//cgiEspFsTemplate can be used as a template.
|
|
||||||
|
|
||||||
typedef struct { |
|
||||||
EspFsFile *file; |
|
||||||
void *tplArg; |
|
||||||
char token[64]; |
|
||||||
int tokenPos; |
|
||||||
} TplData; |
|
||||||
|
|
||||||
typedef void (* TplCallback)(HttpdConnData *connData, char *token, void **arg); |
|
||||||
|
|
||||||
int ICACHE_FLASH_ATTR cgiEspFsTemplate(HttpdConnData *connData) { |
|
||||||
TplData *tpd=connData->cgiData; |
|
||||||
int len; |
|
||||||
int x, sp=0; |
|
||||||
char *e=NULL; |
|
||||||
char buff[1025]; |
|
||||||
|
|
||||||
if (connData->conn==NULL) { |
|
||||||
//Connection aborted. Clean up.
|
|
||||||
((TplCallback)(connData->cgiArg))(connData, NULL, &tpd->tplArg); |
|
||||||
espFsClose(tpd->file); |
|
||||||
os_free(tpd); |
|
||||||
return HTTPD_CGI_DONE; |
|
||||||
} |
|
||||||
|
|
||||||
if (tpd==NULL) { |
|
||||||
//First call to this cgi. Open the file so we can read it.
|
|
||||||
tpd=(TplData *)os_malloc(sizeof(TplData)); |
|
||||||
tpd->file=espFsOpen(connData->url); |
|
||||||
tpd->tplArg=NULL; |
|
||||||
tpd->tokenPos=-1; |
|
||||||
if (tpd->file==NULL) { |
|
||||||
espFsClose(tpd->file); |
|
||||||
os_free(tpd); |
|
||||||
return HTTPD_CGI_NOTFOUND; |
|
||||||
} |
|
||||||
if (espFsFlags(tpd->file) & FLAG_GZIP) { |
|
||||||
os_printf("cgiEspFsTemplate: Trying to use gzip-compressed file %s as template!\n", connData->url); |
|
||||||
espFsClose(tpd->file); |
|
||||||
os_free(tpd); |
|
||||||
return HTTPD_CGI_NOTFOUND; |
|
||||||
} |
|
||||||
connData->cgiData=tpd; |
|
||||||
httpdStartResponse(connData, 200); |
|
||||||
httpdHeader(connData, "Content-Type", httpdGetMimetype(connData->url)); |
|
||||||
httpdEndHeaders(connData); |
|
||||||
return HTTPD_CGI_MORE; |
|
||||||
} |
|
||||||
|
|
||||||
len=espFsRead(tpd->file, buff, 1024); |
|
||||||
if (len>0) { |
|
||||||
sp=0; |
|
||||||
e=buff; |
|
||||||
for (x=0; x<len; x++) { |
|
||||||
if (tpd->tokenPos==-1) { |
|
||||||
//Inside ordinary text.
|
|
||||||
if (buff[x]=='%') { |
|
||||||
//Send raw data up to now
|
|
||||||
if (sp!=0) httpdSend(connData, e, sp); |
|
||||||
sp=0; |
|
||||||
//Go collect token chars.
|
|
||||||
tpd->tokenPos=0; |
|
||||||
} else { |
|
||||||
sp++; |
|
||||||
} |
|
||||||
} else { |
|
||||||
if (buff[x]=='%') { |
|
||||||
if (tpd->tokenPos==0) { |
|
||||||
//This is the second % of a %% escape string.
|
|
||||||
//Send a single % and resume with the normal program flow.
|
|
||||||
httpdSend(connData, "%", 1); |
|
||||||
} else { |
|
||||||
//This is an actual token.
|
|
||||||
tpd->token[tpd->tokenPos++]=0; //zero-terminate token
|
|
||||||
((TplCallback)(connData->cgiArg))(connData, tpd->token, &tpd->tplArg); |
|
||||||
} |
|
||||||
//Go collect normal chars again.
|
|
||||||
e=&buff[x+1]; |
|
||||||
tpd->tokenPos=-1; |
|
||||||
} else { |
|
||||||
if (tpd->tokenPos<(sizeof(tpd->token)-1)) tpd->token[tpd->tokenPos++]=buff[x]; |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
//Send remaining bit.
|
|
||||||
if (sp!=0) httpdSend(connData, e, sp); |
|
||||||
if (len!=1024) { |
|
||||||
//We're done.
|
|
||||||
((TplCallback)(connData->cgiArg))(connData, NULL, &tpd->tplArg); |
|
||||||
espFsClose(tpd->file); |
|
||||||
os_free(tpd); |
|
||||||
return HTTPD_CGI_DONE; |
|
||||||
} else { |
|
||||||
//Ok, till next time.
|
|
||||||
return HTTPD_CGI_MORE; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
@ -1,274 +0,0 @@ |
|||||||
/*
|
|
||||||
This is a simple read-only implementation of a file system. It uses a block of data coming from the |
|
||||||
mkespfsimg tool, and can use that block to do abstracted operations on the files that are in there. |
|
||||||
It's written for use with httpd, but doesn't need to be used as such. |
|
||||||
*/ |
|
||||||
|
|
||||||
/*
|
|
||||||
* ---------------------------------------------------------------------------- |
|
||||||
* "THE BEER-WARE LICENSE" (Revision 42): |
|
||||||
* Jeroen Domburg <jeroen@spritesmods.com> wrote this file. As long as you retain
|
|
||||||
* this notice you can do whatever you want with this stuff. If we meet some day,
|
|
||||||
* and you think this stuff is worth it, you can buy me a beer in return.
|
|
||||||
* ---------------------------------------------------------------------------- |
|
||||||
*/ |
|
||||||
|
|
||||||
|
|
||||||
//These routines can also be tested by comping them in with the espfstest tool. This
|
|
||||||
//simplifies debugging, but needs some slightly different headers. The #ifdef takes
|
|
||||||
//care of that.
|
|
||||||
|
|
||||||
#ifdef __ets__ |
|
||||||
//esp build
|
|
||||||
#include <esp8266.h> |
|
||||||
#else |
|
||||||
//Test build
|
|
||||||
#include <stdio.h> |
|
||||||
#include <stdint.h> |
|
||||||
#include <stdlib.h> |
|
||||||
#include <string.h> |
|
||||||
#define os_malloc malloc |
|
||||||
#define os_free free |
|
||||||
#define os_memcpy memcpy |
|
||||||
#define os_strncmp strncmp |
|
||||||
#define os_strcmp strcmp |
|
||||||
#define os_strcpy strcpy |
|
||||||
#define os_printf printf |
|
||||||
#define ICACHE_FLASH_ATTR |
|
||||||
#endif |
|
||||||
|
|
||||||
#include "espfsformat.h" |
|
||||||
#include "espfs.h" |
|
||||||
|
|
||||||
#ifdef ESPFS_HEATSHRINK |
|
||||||
#include "heatshrink_config_custom.h" |
|
||||||
#include "heatshrink_decoder.h" |
|
||||||
#endif |
|
||||||
|
|
||||||
static char* espFsData = NULL; |
|
||||||
|
|
||||||
|
|
||||||
struct EspFsFile { |
|
||||||
EspFsHeader *header; |
|
||||||
char decompressor; |
|
||||||
int32_t posDecomp; |
|
||||||
char *posStart; |
|
||||||
char *posComp; |
|
||||||
void *decompData; |
|
||||||
}; |
|
||||||
|
|
||||||
/*
|
|
||||||
Available locations, at least in my flash, with boundaries partially guessed. This |
|
||||||
is using 0.9.1/0.9.2 SDK on a not-too-new module. |
|
||||||
0x00000 (0x10000): Code/data (RAM data?) |
|
||||||
0x10000 (0x02000): Gets erased by something? |
|
||||||
0x12000 (0x2E000): Free (filled with zeroes) (parts used by ESPCloud and maybe SSL) |
|
||||||
0x40000 (0x20000): Code/data (ROM data?) |
|
||||||
0x60000 (0x1C000): Free |
|
||||||
0x7c000 (0x04000): Param store |
|
||||||
0x80000 - end of flash |
|
||||||
|
|
||||||
Accessing the flash through the mem emulation at 0x40200000 is a bit hairy: All accesses |
|
||||||
*must* be aligned 32-bit accesses. Reading a short, byte or unaligned word will result in |
|
||||||
a memory exception, crashing the program. |
|
||||||
*/ |
|
||||||
|
|
||||||
EspFsInitResult ICACHE_FLASH_ATTR espFsInit(void *flashAddress) { |
|
||||||
// base address must be aligned to 4 bytes
|
|
||||||
if (((int)flashAddress & 3) != 0) { |
|
||||||
return ESPFS_INIT_RESULT_BAD_ALIGN; |
|
||||||
} |
|
||||||
|
|
||||||
// check if there is valid header at address
|
|
||||||
EspFsHeader testHeader; |
|
||||||
os_memcpy(&testHeader, flashAddress, sizeof(EspFsHeader)); |
|
||||||
if (testHeader.magic != ESPFS_MAGIC) { |
|
||||||
return ESPFS_INIT_RESULT_NO_IMAGE; |
|
||||||
} |
|
||||||
|
|
||||||
espFsData = (char *)flashAddress; |
|
||||||
return ESPFS_INIT_RESULT_OK; |
|
||||||
} |
|
||||||
|
|
||||||
//Copies len bytes over from dst to src, but does it using *only*
|
|
||||||
//aligned 32-bit reads. Yes, it's no too optimized but it's short and sweet and it works.
|
|
||||||
|
|
||||||
//ToDo: perhaps os_memcpy also does unaligned accesses?
|
|
||||||
#ifdef __ets__ |
|
||||||
void ICACHE_FLASH_ATTR memcpyAligned(char *dst, char *src, int len) { |
|
||||||
int x; |
|
||||||
int w, b; |
|
||||||
for (x=0; x<len; x++) { |
|
||||||
b=((int)src&3); |
|
||||||
w=*((int *)(src-b)); |
|
||||||
if (b==0) *dst=(w>>0); |
|
||||||
if (b==1) *dst=(w>>8); |
|
||||||
if (b==2) *dst=(w>>16); |
|
||||||
if (b==3) *dst=(w>>24); |
|
||||||
dst++; src++; |
|
||||||
} |
|
||||||
} |
|
||||||
#else |
|
||||||
#define memcpyAligned memcpy |
|
||||||
#endif |
|
||||||
|
|
||||||
// Returns flags of opened file.
|
|
||||||
int ICACHE_FLASH_ATTR espFsFlags(EspFsFile *fh) { |
|
||||||
if (fh == NULL) { |
|
||||||
os_printf("File handle not ready\n"); |
|
||||||
return -1; |
|
||||||
} |
|
||||||
|
|
||||||
int8_t flags; |
|
||||||
memcpyAligned((char*)&flags, (char*)&fh->header->flags, 1); |
|
||||||
return (int)flags; |
|
||||||
} |
|
||||||
|
|
||||||
//Open a file and return a pointer to the file desc struct.
|
|
||||||
EspFsFile ICACHE_FLASH_ATTR *espFsOpen(char *fileName) { |
|
||||||
if (espFsData == NULL) { |
|
||||||
os_printf("Call espFsInit first!\n"); |
|
||||||
return NULL; |
|
||||||
} |
|
||||||
char *p=espFsData; |
|
||||||
char *hpos; |
|
||||||
char namebuf[256]; |
|
||||||
EspFsHeader h; |
|
||||||
EspFsFile *r; |
|
||||||
//Strip initial slashes
|
|
||||||
while(fileName[0]=='/') fileName++; |
|
||||||
//Go find that file!
|
|
||||||
while(1) { |
|
||||||
hpos=p; |
|
||||||
//Grab the next file header.
|
|
||||||
os_memcpy(&h, p, sizeof(EspFsHeader)); |
|
||||||
if (h.magic!=ESPFS_MAGIC) { |
|
||||||
os_printf("Magic mismatch. EspFS image broken.\n"); |
|
||||||
return NULL; |
|
||||||
} |
|
||||||
if (h.flags&FLAG_LASTFILE) { |
|
||||||
os_printf("End of image.\n"); |
|
||||||
return NULL; |
|
||||||
} |
|
||||||
//Grab the name of the file.
|
|
||||||
p+=sizeof(EspFsHeader);
|
|
||||||
os_memcpy(namebuf, p, sizeof(namebuf)); |
|
||||||
// os_printf("Found file '%s'. Namelen=%x fileLenComp=%x, compr=%d flags=%d\n",
|
|
||||||
// namebuf, (unsigned int)h.nameLen, (unsigned int)h.fileLenComp, h.compression, h.flags);
|
|
||||||
if (os_strcmp(namebuf, fileName)==0) { |
|
||||||
//Yay, this is the file we need!
|
|
||||||
p+=h.nameLen; //Skip to content.
|
|
||||||
r=(EspFsFile *)os_malloc(sizeof(EspFsFile)); //Alloc file desc mem
|
|
||||||
// os_printf("Alloc %p\n", r);
|
|
||||||
if (r==NULL) return NULL; |
|
||||||
r->header=(EspFsHeader *)hpos; |
|
||||||
r->decompressor=h.compression; |
|
||||||
r->posComp=p; |
|
||||||
r->posStart=p; |
|
||||||
r->posDecomp=0; |
|
||||||
if (h.compression==COMPRESS_NONE) { |
|
||||||
r->decompData=NULL; |
|
||||||
#ifdef ESPFS_HEATSHRINK |
|
||||||
} else if (h.compression==COMPRESS_HEATSHRINK) { |
|
||||||
//File is compressed with Heatshrink.
|
|
||||||
char parm; |
|
||||||
heatshrink_decoder *dec; |
|
||||||
//Decoder params are stored in 1st byte.
|
|
||||||
memcpyAligned(&parm, r->posComp, 1); |
|
||||||
r->posComp++; |
|
||||||
os_printf("Heatshrink compressed file; decode parms = %x\n", parm); |
|
||||||
dec=heatshrink_decoder_alloc(16, (parm>>4)&0xf, parm&0xf); |
|
||||||
r->decompData=dec; |
|
||||||
#endif |
|
||||||
} else { |
|
||||||
os_printf("Invalid compression: %d\n", h.compression); |
|
||||||
return NULL; |
|
||||||
} |
|
||||||
return r; |
|
||||||
} |
|
||||||
//We don't need this file. Skip name and file
|
|
||||||
p+=h.nameLen+h.fileLenComp; |
|
||||||
if ((int)p&3) p+=4-((int)p&3); //align to next 32bit val
|
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
//Read len bytes from the given file into buff. Returns the actual amount of bytes read.
|
|
||||||
int ICACHE_FLASH_ATTR espFsRead(EspFsFile *fh, char *buff, int len) { |
|
||||||
int flen, fdlen; |
|
||||||
if (fh==NULL) return 0; |
|
||||||
//Cache file length.
|
|
||||||
memcpyAligned((char*)&flen, (char*)&fh->header->fileLenComp, 4); |
|
||||||
memcpyAligned((char*)&fdlen, (char*)&fh->header->fileLenDecomp, 4); |
|
||||||
//Do stuff depending on the way the file is compressed.
|
|
||||||
if (fh->decompressor==COMPRESS_NONE) { |
|
||||||
int toRead; |
|
||||||
toRead=flen-(fh->posComp-fh->posStart); |
|
||||||
if (len>toRead) len=toRead; |
|
||||||
// os_printf("Reading %d bytes from %x\n", len, (unsigned int)fh->posComp);
|
|
||||||
memcpyAligned(buff, fh->posComp, len); |
|
||||||
fh->posDecomp+=len; |
|
||||||
fh->posComp+=len; |
|
||||||
// os_printf("Done reading %d bytes, pos=%x\n", len, fh->posComp);
|
|
||||||
return len; |
|
||||||
#ifdef ESPFS_HEATSHRINK |
|
||||||
} else if (fh->decompressor==COMPRESS_HEATSHRINK) { |
|
||||||
int decoded=0; |
|
||||||
size_t elen, rlen; |
|
||||||
char ebuff[16]; |
|
||||||
heatshrink_decoder *dec=(heatshrink_decoder *)fh->decompData; |
|
||||||
// os_printf("Alloc %p\n", dec);
|
|
||||||
if (fh->posDecomp == fdlen) { |
|
||||||
return 0; |
|
||||||
} |
|
||||||
|
|
||||||
// We must ensure that whole file is decompressed and written to output buffer.
|
|
||||||
// This means even when there is no input data (elen==0) try to poll decoder until
|
|
||||||
// posDecomp equals decompressed file length
|
|
||||||
|
|
||||||
while(decoded<len) { |
|
||||||
//Feed data into the decompressor
|
|
||||||
//ToDo: Check ret val of heatshrink fns for errors
|
|
||||||
elen=flen-(fh->posComp - fh->posStart); |
|
||||||
if (elen>0) { |
|
||||||
memcpyAligned(ebuff, fh->posComp, 16); |
|
||||||
heatshrink_decoder_sink(dec, (uint8_t *)ebuff, (elen>16)?16:elen, &rlen); |
|
||||||
fh->posComp+=rlen; |
|
||||||
} |
|
||||||
//Grab decompressed data and put into buff
|
|
||||||
heatshrink_decoder_poll(dec, (uint8_t *)buff, len-decoded, &rlen); |
|
||||||
fh->posDecomp+=rlen; |
|
||||||
buff+=rlen; |
|
||||||
decoded+=rlen; |
|
||||||
|
|
||||||
// os_printf("Elen %d rlen %d d %d pd %ld fdl %d\n",elen,rlen,decoded, fh->posDecomp, fdlen);
|
|
||||||
|
|
||||||
if (elen == 0) { |
|
||||||
if (fh->posDecomp == fdlen) { |
|
||||||
// os_printf("Decoder finish\n");
|
|
||||||
heatshrink_decoder_finish(dec); |
|
||||||
} |
|
||||||
return decoded; |
|
||||||
} |
|
||||||
} |
|
||||||
return len; |
|
||||||
#endif |
|
||||||
} |
|
||||||
return 0; |
|
||||||
} |
|
||||||
|
|
||||||
//Close the file.
|
|
||||||
void ICACHE_FLASH_ATTR espFsClose(EspFsFile *fh) { |
|
||||||
if (fh==NULL) return; |
|
||||||
#ifdef ESPFS_HEATSHRINK |
|
||||||
if (fh->decompressor==COMPRESS_HEATSHRINK) { |
|
||||||
heatshrink_decoder *dec=(heatshrink_decoder *)fh->decompData; |
|
||||||
heatshrink_decoder_free(dec); |
|
||||||
// os_printf("Freed %p\n", dec);
|
|
||||||
} |
|
||||||
#endif |
|
||||||
// os_printf("Freed %p\n", fh);
|
|
||||||
os_free(fh); |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1,33 +0,0 @@ |
|||||||
#ifndef ESPROFSFORMAT_H |
|
||||||
#define ESPROFSFORMAT_H |
|
||||||
|
|
||||||
/*
|
|
||||||
Stupid cpio-like tool to make read-only 'filesystems' that live on the flash SPI chip of the module. |
|
||||||
Can (will) use lzf compression (when I come around to it) to make shit quicker. Aligns names, files, |
|
||||||
headers on 4-byte boundaries so the SPI abstraction hardware in the ESP8266 doesn't crap on itself
|
|
||||||
when trying to do a <4byte or unaligned read. |
|
||||||
*/ |
|
||||||
|
|
||||||
/*
|
|
||||||
The idea 'borrows' from cpio: it's basically a concatenation of {header, filename, file} data. |
|
||||||
Header, filename and file data is 32-bit aligned. The last file is indicated by data-less header |
|
||||||
with the FLAG_LASTFILE flag set. |
|
||||||
*/ |
|
||||||
|
|
||||||
|
|
||||||
#define FLAG_LASTFILE (1<<0) |
|
||||||
#define FLAG_GZIP (1<<1) |
|
||||||
#define COMPRESS_NONE 0 |
|
||||||
#define COMPRESS_HEATSHRINK 1 |
|
||||||
#define ESPFS_MAGIC 0x73665345 |
|
||||||
|
|
||||||
typedef struct { |
|
||||||
int32_t magic; |
|
||||||
int8_t flags; |
|
||||||
int8_t compression; |
|
||||||
int16_t nameLen; |
|
||||||
int32_t fileLenComp; |
|
||||||
int32_t fileLenDecomp; |
|
||||||
} __attribute__((packed)) EspFsHeader; |
|
||||||
|
|
||||||
#endif |
|
@ -1,13 +0,0 @@ |
|||||||
CFLAGS=-I../../lib/heatshrink -I.. -std=gnu99 -DESPFS_HEATSHRINK
|
|
||||||
|
|
||||||
espfstest: main.o espfs.o heatshrink_decoder.o |
|
||||||
$(CC) -o $@ $^
|
|
||||||
|
|
||||||
espfs.o: ../espfs.c |
|
||||||
$(CC) $(CFLAGS) -c $^ -o $@
|
|
||||||
|
|
||||||
heatshrink_decoder.o: ../heatshrink_decoder.c |
|
||||||
$(CC) $(CFLAGS) -c $^ -o $@
|
|
||||||
|
|
||||||
clean: |
|
||||||
rm -f *.o espfstest
|
|
@ -1,67 +0,0 @@ |
|||||||
/*
|
|
||||||
Simple and stupid file decompressor for an espfs image. Mostly used as a testbed for espfs.c and
|
|
||||||
the decompressors: code compiled natively is way easier to debug using gdb et all :) |
|
||||||
*/ |
|
||||||
#include <stdio.h> |
|
||||||
#include <stdint.h> |
|
||||||
#include <sys/mman.h> |
|
||||||
#include <sys/types.h> |
|
||||||
#include <sys/stat.h> |
|
||||||
#include <fcntl.h> |
|
||||||
#include <stdlib.h> |
|
||||||
#include <unistd.h> |
|
||||||
|
|
||||||
|
|
||||||
#include "espfs.h" |
|
||||||
|
|
||||||
char *espFsData; |
|
||||||
|
|
||||||
int main(int argc, char **argv) { |
|
||||||
int f, out; |
|
||||||
int len; |
|
||||||
char buff[128]; |
|
||||||
EspFsFile *ef; |
|
||||||
off_t size; |
|
||||||
EspFsInitResult ir; |
|
||||||
|
|
||||||
if (argc!=3) { |
|
||||||
printf("Usage: %s espfs-image file\nExpands file from the espfs-image archive.\n", argv[0]); |
|
||||||
exit(0); |
|
||||||
} |
|
||||||
|
|
||||||
f=open(argv[1], O_RDONLY); |
|
||||||
if (f<=0) { |
|
||||||
perror(argv[1]); |
|
||||||
exit(1); |
|
||||||
} |
|
||||||
size=lseek(f, 0, SEEK_END); |
|
||||||
espFsData=mmap(NULL, size, PROT_READ, MAP_SHARED, f, 0); |
|
||||||
if (espFsData==MAP_FAILED) { |
|
||||||
perror("mmap"); |
|
||||||
exit(1); |
|
||||||
} |
|
||||||
|
|
||||||
ir=espFsInit(espFsData); |
|
||||||
if (ir != ESPFS_INIT_RESULT_OK) { |
|
||||||
printf("Couldn't init espfs filesystem (code %d)\n", ir); |
|
||||||
exit(1); |
|
||||||
} |
|
||||||
|
|
||||||
ef=espFsOpen(argv[2]); |
|
||||||
if (ef==NULL) { |
|
||||||
printf("Couldn't find %s in image.\n", argv[2]); |
|
||||||
exit(1); |
|
||||||
} |
|
||||||
|
|
||||||
out=open(argv[2], O_WRONLY|O_CREAT|O_TRUNC, 0644); |
|
||||||
if (out<=0) { |
|
||||||
perror(argv[2]); |
|
||||||
exit(1); |
|
||||||
} |
|
||||||
|
|
||||||
while ((len=espFsRead(ef, buff, 128))!=0) { |
|
||||||
write(out, buff, len); |
|
||||||
} |
|
||||||
espFsClose(ef); |
|
||||||
//munmap, close, ... I can't be bothered.
|
|
||||||
} |
|
@ -1,30 +0,0 @@ |
|||||||
//Heatshrink config for the decompressor.
|
|
||||||
#ifndef HEATSHRINK_CONFIG_H |
|
||||||
#define HEATSHRINK_CONFIG_H |
|
||||||
|
|
||||||
/* Should functionality assuming dynamic allocation be used? */ |
|
||||||
#define HEATSHRINK_DYNAMIC_ALLOC 1 |
|
||||||
|
|
||||||
#if HEATSHRINK_DYNAMIC_ALLOC |
|
||||||
/* Optional replacement of malloc/free */ |
|
||||||
#ifdef __ets__ |
|
||||||
#define HEATSHRINK_MALLOC(SZ) os_malloc(SZ) |
|
||||||
#define HEATSHRINK_FREE(P, SZ) os_free(P) |
|
||||||
#else |
|
||||||
#define HEATSHRINK_MALLOC(SZ) malloc(SZ) |
|
||||||
#define HEATSHRINK_FREE(P, SZ) free(P) |
|
||||||
#endif |
|
||||||
#else |
|
||||||
/* Required parameters for static configuration */ |
|
||||||
#define HEATSHRINK_STATIC_INPUT_BUFFER_SIZE 32 |
|
||||||
#define HEATSHRINK_STATIC_WINDOW_BITS 8 |
|
||||||
#define HEATSHRINK_STATIC_LOOKAHEAD_BITS 4 |
|
||||||
#endif |
|
||||||
|
|
||||||
/* Turn on logging for debugging. */ |
|
||||||
#define HEATSHRINK_DEBUGGING_LOGS 0 |
|
||||||
|
|
||||||
/* Use indexing for faster compression. (This requires additional space.) */ |
|
||||||
#define HEATSHRINK_USE_INDEX 1 |
|
||||||
|
|
||||||
#endif |
|
@ -1,19 +0,0 @@ |
|||||||
#include "espfs.h" |
|
||||||
#ifdef ESPFS_HEATSHRINK |
|
||||||
//Stupid wrapper so we don't have to move c-files around
|
|
||||||
//Also loads httpd-specific config.
|
|
||||||
|
|
||||||
#ifdef __ets__ |
|
||||||
//esp build
|
|
||||||
|
|
||||||
#include <esp8266.h> |
|
||||||
|
|
||||||
#define memset(x,y,z) os_memset(x,y,z) |
|
||||||
#define memcpy(x,y,z) os_memcpy(x,y,z) |
|
||||||
#endif |
|
||||||
|
|
||||||
#include "heatshrink_config_custom.h" |
|
||||||
#include "../lib/heatshrink/heatshrink_decoder.c" |
|
||||||
|
|
||||||
|
|
||||||
#endif |
|
@ -1,24 +0,0 @@ |
|||||||
GZIP_COMPRESSION ?= no
|
|
||||||
USE_HEATSHRINK ?= yes
|
|
||||||
|
|
||||||
CFLAGS=-I../../lib/heatshrink -I../../include -I.. -std=gnu99
|
|
||||||
ifeq ("$(GZIP_COMPRESSION)","yes") |
|
||||||
CFLAGS += -DESPFS_GZIP
|
|
||||||
endif |
|
||||||
|
|
||||||
ifeq ("$(USE_HEATSHRINK)","yes") |
|
||||||
CFLAGS += -DESPFS_HEATSHRINK
|
|
||||||
endif |
|
||||||
|
|
||||||
OBJS=main.o heatshrink_encoder.o
|
|
||||||
TARGET=mkespfsimage
|
|
||||||
|
|
||||||
$(TARGET): $(OBJS) |
|
||||||
ifeq ("$(GZIP_COMPRESSION)","yes") |
|
||||||
$(CC) -o $@ $^ -lz
|
|
||||||
else |
|
||||||
$(CC) -o $@ $^
|
|
||||||
endif |
|
||||||
|
|
||||||
clean: |
|
||||||
rm -f $(TARGET) $(OBJS)
|
|
@ -1,4 +0,0 @@ |
|||||||
//Stupid wraparound include to make sure object file doesn't end up in heatshrink dir
|
|
||||||
#ifdef ESPFS_HEATSHRINK |
|
||||||
#include "../lib/heatshrink/heatshrink_encoder.c" |
|
||||||
#endif |
|
@ -1,364 +0,0 @@ |
|||||||
#include <stdint.h> |
|
||||||
#include <unistd.h> |
|
||||||
#include <sys/types.h> |
|
||||||
#include <sys/stat.h> |
|
||||||
#include <fcntl.h> |
|
||||||
#include <stdio.h> |
|
||||||
#include <stdlib.h> |
|
||||||
#include <sys/mman.h> |
|
||||||
#include <arpa/inet.h> |
|
||||||
#include <string.h> |
|
||||||
#include "espfs.h" |
|
||||||
#include "espfsformat.h" |
|
||||||
|
|
||||||
//Heatshrink
|
|
||||||
#ifdef ESPFS_HEATSHRINK |
|
||||||
#include "heatshrink_common.h" |
|
||||||
#include "heatshrink_config.h" |
|
||||||
#include "heatshrink_encoder.h" |
|
||||||
#endif |
|
||||||
|
|
||||||
//Gzip
|
|
||||||
#ifdef ESPFS_GZIP |
|
||||||
// If compiler complains about missing header, try running "sudo apt-get install zlib1g-dev"
|
|
||||||
// to install missing package.
|
|
||||||
#include <zlib.h> |
|
||||||
#endif |
|
||||||
|
|
||||||
|
|
||||||
//Routines to convert host format to the endianness used in the xtensa
|
|
||||||
short htoxs(short in) { |
|
||||||
char r[2]; |
|
||||||
r[0]=in; |
|
||||||
r[1]=in>>8; |
|
||||||
return *((short *)r); |
|
||||||
} |
|
||||||
|
|
||||||
int htoxl(int in) { |
|
||||||
unsigned char r[4]; |
|
||||||
r[0]=in; |
|
||||||
r[1]=in>>8; |
|
||||||
r[2]=in>>16; |
|
||||||
r[3]=in>>24; |
|
||||||
return *((int *)r); |
|
||||||
} |
|
||||||
|
|
||||||
#ifdef ESPFS_HEATSHRINK |
|
||||||
size_t compressHeatshrink(char *in, int insize, char *out, int outsize, int level) { |
|
||||||
char *inp=in; |
|
||||||
char *outp=out; |
|
||||||
size_t len; |
|
||||||
int ws[]={5, 6, 8, 11, 13}; |
|
||||||
int ls[]={3, 3, 4, 4, 4}; |
|
||||||
HSE_poll_res pres; |
|
||||||
HSE_sink_res sres; |
|
||||||
size_t r; |
|
||||||
if (level==-1) level=8; |
|
||||||
level=(level-1)/2; //level is now 0, 1, 2, 3, 4
|
|
||||||
heatshrink_encoder *enc=heatshrink_encoder_alloc(ws[level], ls[level]); |
|
||||||
if (enc==NULL) { |
|
||||||
perror("allocating mem for heatshrink"); |
|
||||||
exit(1); |
|
||||||
} |
|
||||||
//Save encoder parms as first byte
|
|
||||||
*outp=(ws[level]<<4)|ls[level]; |
|
||||||
outp++; outsize--; |
|
||||||
|
|
||||||
r=1; |
|
||||||
do { |
|
||||||
if (insize>0) { |
|
||||||
sres=heatshrink_encoder_sink(enc, inp, insize, &len); |
|
||||||
if (sres!=HSER_SINK_OK) break; |
|
||||||
inp+=len; insize-=len; |
|
||||||
if (insize==0) heatshrink_encoder_finish(enc); |
|
||||||
} |
|
||||||
do { |
|
||||||
pres=heatshrink_encoder_poll(enc, outp, outsize, &len); |
|
||||||
if (pres!=HSER_POLL_MORE && pres!=HSER_POLL_EMPTY) break; |
|
||||||
outp+=len; outsize-=len; |
|
||||||
r+=len; |
|
||||||
} while (pres==HSER_POLL_MORE); |
|
||||||
} while (insize!=0); |
|
||||||
|
|
||||||
if (insize!=0) { |
|
||||||
fprintf(stderr, "Heatshrink: Bug? insize is still %d. sres=%d pres=%d\n", insize, sres, pres); |
|
||||||
exit(1); |
|
||||||
} |
|
||||||
|
|
||||||
heatshrink_encoder_free(enc); |
|
||||||
return r; |
|
||||||
} |
|
||||||
#endif |
|
||||||
|
|
||||||
#ifdef ESPFS_GZIP |
|
||||||
size_t compressGzip(char *in, int insize, char *out, int outsize, int level) { |
|
||||||
z_stream stream; |
|
||||||
int zresult; |
|
||||||
|
|
||||||
stream.zalloc = Z_NULL; |
|
||||||
stream.zfree = Z_NULL; |
|
||||||
stream.opaque = Z_NULL; |
|
||||||
stream.next_in = in; |
|
||||||
stream.avail_in = insize; |
|
||||||
stream.next_out = out; |
|
||||||
stream.avail_out = outsize; |
|
||||||
// 31 -> 15 window bits + 16 for gzip
|
|
||||||
zresult = deflateInit2 (&stream, level, Z_DEFLATED, 31, 8, Z_DEFAULT_STRATEGY); |
|
||||||
if (zresult != Z_OK) { |
|
||||||
fprintf(stderr, "DeflateInit2 failed with code %d\n", zresult); |
|
||||||
exit(1); |
|
||||||
} |
|
||||||
|
|
||||||
zresult = deflate(&stream, Z_FINISH); |
|
||||||
if (zresult != Z_STREAM_END) { |
|
||||||
fprintf(stderr, "Deflate failed with code %d\n", zresult); |
|
||||||
exit(1); |
|
||||||
} |
|
||||||
|
|
||||||
zresult = deflateEnd(&stream); |
|
||||||
if (zresult != Z_OK) { |
|
||||||
fprintf(stderr, "DeflateEnd failed with code %d\n", zresult); |
|
||||||
exit(1); |
|
||||||
} |
|
||||||
|
|
||||||
return stream.total_out; |
|
||||||
} |
|
||||||
|
|
||||||
char **gzipExtensions = NULL; |
|
||||||
|
|
||||||
int shouldCompressGzip(char *name) { |
|
||||||
char *ext = name + strlen(name); |
|
||||||
while (*ext != '.') { |
|
||||||
ext--; |
|
||||||
if (ext < name) { |
|
||||||
// no dot in file name -> no extension -> nothing to match against
|
|
||||||
return 0; |
|
||||||
} |
|
||||||
} |
|
||||||
ext++; |
|
||||||
|
|
||||||
int i = 0; |
|
||||||
while (gzipExtensions[i] != NULL) { |
|
||||||
if (strcmp(ext,gzipExtensions[i]) == 0) { |
|
||||||
return 1; |
|
||||||
} |
|
||||||
i++; |
|
||||||
} |
|
||||||
|
|
||||||
return 0; |
|
||||||
} |
|
||||||
|
|
||||||
int parseGzipExtensions(char *input) { |
|
||||||
char *token; |
|
||||||
char *extList = input; |
|
||||||
int count = 2; // one for first element, second for terminator
|
|
||||||
|
|
||||||
// count elements
|
|
||||||
while (*extList != 0) { |
|
||||||
if (*extList == ',') count++; |
|
||||||
extList++; |
|
||||||
} |
|
||||||
|
|
||||||
// split string
|
|
||||||
extList = input; |
|
||||||
gzipExtensions = malloc(count * sizeof(char*)); |
|
||||||
count = 0; |
|
||||||
token = strtok(extList, ","); |
|
||||||
while (token) { |
|
||||||
gzipExtensions[count++] = token; |
|
||||||
token = strtok(NULL, ","); |
|
||||||
} |
|
||||||
// terminate list
|
|
||||||
gzipExtensions[count] = NULL; |
|
||||||
|
|
||||||
return 1; |
|
||||||
} |
|
||||||
#endif |
|
||||||
|
|
||||||
int handleFile(int f, char *name, int compression, int level, char **compName) { |
|
||||||
char *fdat, *cdat; |
|
||||||
off_t size, csize; |
|
||||||
EspFsHeader h; |
|
||||||
int nameLen; |
|
||||||
int8_t flags = 0; |
|
||||||
size=lseek(f, 0, SEEK_END); |
|
||||||
fdat=mmap(NULL, size, PROT_READ, MAP_SHARED, f, 0); |
|
||||||
if (fdat==MAP_FAILED) { |
|
||||||
perror("mmap"); |
|
||||||
return 0; |
|
||||||
} |
|
||||||
|
|
||||||
#ifdef ESPFS_GZIP |
|
||||||
if (shouldCompressGzip(name)) { |
|
||||||
csize = size*3; |
|
||||||
if (csize<100) // gzip has some headers that do not fit when trying to compress small files
|
|
||||||
csize = 100; // enlarge buffer if this is the case
|
|
||||||
cdat=malloc(csize); |
|
||||||
csize=compressGzip(fdat, size, cdat, csize, level); |
|
||||||
compression = COMPRESS_NONE; |
|
||||||
flags = FLAG_GZIP; |
|
||||||
} else |
|
||||||
#endif |
|
||||||
if (compression==COMPRESS_NONE) { |
|
||||||
csize=size; |
|
||||||
cdat=fdat; |
|
||||||
#ifdef ESPFS_HEATSHRINK |
|
||||||
} else if (compression==COMPRESS_HEATSHRINK) { |
|
||||||
cdat=malloc(size*2); |
|
||||||
csize=compressHeatshrink(fdat, size, cdat, size*2, level); |
|
||||||
#endif |
|
||||||
} else { |
|
||||||
fprintf(stderr, "Unknown compression - %d\n", compression); |
|
||||||
exit(1); |
|
||||||
} |
|
||||||
|
|
||||||
if (csize>size) { |
|
||||||
//Compressing enbiggened this file. Revert to uncompressed store.
|
|
||||||
compression=COMPRESS_NONE; |
|
||||||
csize=size; |
|
||||||
cdat=fdat; |
|
||||||
flags=0; |
|
||||||
} |
|
||||||
|
|
||||||
//Fill header data
|
|
||||||
h.magic=('E'<<0)+('S'<<8)+('f'<<16)+('s'<<24); |
|
||||||
h.flags=flags; |
|
||||||
h.compression=compression; |
|
||||||
h.nameLen=nameLen=strlen(name)+1; |
|
||||||
if (h.nameLen&3) h.nameLen+=4-(h.nameLen&3); //Round to next 32bit boundary
|
|
||||||
h.nameLen=htoxs(h.nameLen); |
|
||||||
h.fileLenComp=htoxl(csize); |
|
||||||
h.fileLenDecomp=htoxl(size); |
|
||||||
|
|
||||||
write(1, &h, sizeof(EspFsHeader)); |
|
||||||
write(1, name, nameLen); |
|
||||||
while (nameLen&3) { |
|
||||||
write(1, "\000", 1); |
|
||||||
nameLen++; |
|
||||||
} |
|
||||||
write(1, cdat, csize); |
|
||||||
//Pad out to 32bit boundary
|
|
||||||
while (csize&3) { |
|
||||||
write(1, "\000", 1); |
|
||||||
csize++; |
|
||||||
} |
|
||||||
munmap(fdat, size); |
|
||||||
|
|
||||||
if (compName != NULL) { |
|
||||||
if (h.compression==COMPRESS_HEATSHRINK) { |
|
||||||
*compName = "heatshrink"; |
|
||||||
} else if (h.compression==COMPRESS_NONE) { |
|
||||||
if (h.flags & FLAG_GZIP) { |
|
||||||
*compName = "gzip"; |
|
||||||
} else { |
|
||||||
*compName = "none"; |
|
||||||
} |
|
||||||
} else { |
|
||||||
*compName = "unknown"; |
|
||||||
} |
|
||||||
} |
|
||||||
return (csize*100)/size; |
|
||||||
} |
|
||||||
|
|
||||||
//Write final dummy header with FLAG_LASTFILE set.
|
|
||||||
void finishArchive() { |
|
||||||
EspFsHeader h; |
|
||||||
h.magic=('E'<<0)+('S'<<8)+('f'<<16)+('s'<<24); |
|
||||||
h.flags=FLAG_LASTFILE; |
|
||||||
h.compression=COMPRESS_NONE; |
|
||||||
h.nameLen=htoxs(0); |
|
||||||
h.fileLenComp=htoxl(0); |
|
||||||
h.fileLenDecomp=htoxl(0); |
|
||||||
write(1, &h, sizeof(EspFsHeader)); |
|
||||||
} |
|
||||||
|
|
||||||
int main(int argc, char **argv) { |
|
||||||
int f, x; |
|
||||||
char fileName[1024]; |
|
||||||
char *realName; |
|
||||||
struct stat statBuf; |
|
||||||
int serr; |
|
||||||
int rate; |
|
||||||
int err=0; |
|
||||||
int compType; //default compression type - heatshrink
|
|
||||||
int compLvl=-1; |
|
||||||
|
|
||||||
#ifdef ESPFS_HEATSHRINK |
|
||||||
compType = COMPRESS_HEATSHRINK; |
|
||||||
#else |
|
||||||
compType = COMPRESS_NONE; |
|
||||||
#endif |
|
||||||
|
|
||||||
for (x=1; x<argc; x++) { |
|
||||||
if (strcmp(argv[x], "-c")==0 && argc>=x-2) { |
|
||||||
compType=atoi(argv[x+1]); |
|
||||||
x++; |
|
||||||
} else if (strcmp(argv[x], "-l")==0 && argc>=x-2) { |
|
||||||
compLvl=atoi(argv[x+1]); |
|
||||||
if (compLvl<1 || compLvl>9) err=1; |
|
||||||
x++; |
|
||||||
#ifdef ESPFS_GZIP |
|
||||||
} else if (strcmp(argv[x], "-g")==0 && argc>=x-2) { |
|
||||||
if (!parseGzipExtensions(argv[x+1])) err=1; |
|
||||||
x++; |
|
||||||
#endif |
|
||||||
} else { |
|
||||||
err=1; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
#ifdef ESPFS_GZIP |
|
||||||
if (gzipExtensions == NULL) { |
|
||||||
parseGzipExtensions(strdup("html,css,js")); |
|
||||||
} |
|
||||||
#endif |
|
||||||
|
|
||||||
if (err) { |
|
||||||
fprintf(stderr, "%s - Program to create espfs images\n", argv[0]); |
|
||||||
fprintf(stderr, "Usage: \nfind | %s [-c compressor] [-l compression_level] ", argv[0]); |
|
||||||
#ifdef ESPFS_GZIP |
|
||||||
fprintf(stderr, "[-g gzipped_extensions] "); |
|
||||||
#endif |
|
||||||
fprintf(stderr, "> out.espfs\n"); |
|
||||||
fprintf(stderr, "Compressors:\n"); |
|
||||||
#ifdef ESPFS_HEATSHRINK |
|
||||||
fprintf(stderr, "0 - None\n1 - Heatshrink(default)\n"); |
|
||||||
#else |
|
||||||
fprintf(stderr, "0 - None(default)\n"); |
|
||||||
#endif |
|
||||||
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"); |
|
||||||
#ifdef ESPFS_GZIP |
|
||||||
fprintf(stderr, "\nGzipped extensions: list of comma separated, case sensitive file extensions \nthat will be gzipped. Defaults to 'html,css,js'\n"); |
|
||||||
#endif |
|
||||||
exit(0); |
|
||||||
} |
|
||||||
|
|
||||||
while(fgets(fileName, sizeof(fileName), stdin)) { |
|
||||||
//Kill off '\n' at the end
|
|
||||||
fileName[strlen(fileName)-1]=0; |
|
||||||
//Only include files
|
|
||||||
serr=stat(fileName, &statBuf); |
|
||||||
if ((serr==0) && S_ISREG(statBuf.st_mode)) { |
|
||||||
//Strip off './' or '/' madness.
|
|
||||||
realName=fileName; |
|
||||||
if (fileName[0]=='.') realName++; |
|
||||||
if (realName[0]=='/') realName++; |
|
||||||
f=open(fileName, O_RDONLY); |
|
||||||
if (f>0) { |
|
||||||
char *compName = "unknown"; |
|
||||||
rate=handleFile(f, realName, compType, compLvl, &compName); |
|
||||||
fprintf(stderr, "%s (%d%%, %s)\n", realName, rate, compName); |
|
||||||
close(f); |
|
||||||
} else { |
|
||||||
perror(fileName); |
|
||||||
} |
|
||||||
} else { |
|
||||||
if (serr!=0) { |
|
||||||
perror(fileName); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
finishArchive(); |
|
||||||
return 0; |
|
||||||
} |
|
||||||
|
|
@ -1,22 +0,0 @@ |
|||||||
#ifndef AUTH_H |
|
||||||
#define AUTH_H |
|
||||||
|
|
||||||
#include "httpd.h" |
|
||||||
|
|
||||||
#ifndef HTTP_AUTH_REALM |
|
||||||
#define HTTP_AUTH_REALM "Protected" |
|
||||||
#endif |
|
||||||
|
|
||||||
#define HTTPD_AUTH_SINGLE 0 |
|
||||||
#define HTTPD_AUTH_CALLBACK 1 |
|
||||||
|
|
||||||
#define AUTH_MAX_USER_LEN 32 |
|
||||||
#define AUTH_MAX_PASS_LEN 32 |
|
||||||
|
|
||||||
//Parameter given to authWhatever functions. This callback returns the usernames/passwords the device
|
|
||||||
//has.
|
|
||||||
typedef int (* AuthGetUserPw)(HttpdConnData *connData, int no, char *user, int userLen, char *pass, int passLen); |
|
||||||
|
|
||||||
int ICACHE_FLASH_ATTR authBasic(HttpdConnData *connData); |
|
||||||
|
|
||||||
#endif |
|
@ -1,5 +0,0 @@ |
|||||||
#ifndef CAPTDNS_H |
|
||||||
#define CAPTDNS_H |
|
||||||
void ICACHE_FLASH_ATTR captdnsInit(void); |
|
||||||
|
|
||||||
#endif |
|
@ -1,15 +0,0 @@ |
|||||||
#ifndef CGIFLASH_H |
|
||||||
#define CGIFLASH_H |
|
||||||
|
|
||||||
#include "httpd.h" |
|
||||||
|
|
||||||
typedef struct { |
|
||||||
int espFsPos; |
|
||||||
int espFsSize; |
|
||||||
} CgiUploadEspfsParams; |
|
||||||
|
|
||||||
|
|
||||||
int cgiReadFlash(HttpdConnData *connData); |
|
||||||
int cgiUploadEspfs(HttpdConnData *connData); |
|
||||||
|
|
||||||
#endif |
|
@ -1,13 +0,0 @@ |
|||||||
#ifndef CGIWIFI_H |
|
||||||
#define CGIWIFI_H |
|
||||||
|
|
||||||
#include "httpd.h" |
|
||||||
|
|
||||||
int cgiWiFiScan(HttpdConnData *connData); |
|
||||||
int tplWlan(HttpdConnData *connData, char *token, void **arg); |
|
||||||
int cgiWiFi(HttpdConnData *connData); |
|
||||||
int cgiWiFiConnect(HttpdConnData *connData); |
|
||||||
int cgiWiFiSetMode(HttpdConnData *connData); |
|
||||||
int cgiWiFiConnStatus(HttpdConnData *connData); |
|
||||||
|
|
||||||
#endif |
|
@ -1,18 +0,0 @@ |
|||||||
// Combined include file for esp8266
|
|
||||||
|
|
||||||
#include <ctype.h> |
|
||||||
#include <stdio.h> |
|
||||||
#include <stdlib.h> |
|
||||||
#include <string.h> |
|
||||||
|
|
||||||
#include <c_types.h> |
|
||||||
#include <ip_addr.h> |
|
||||||
#include <espconn.h> |
|
||||||
#include <ets_sys.h> |
|
||||||
#include <gpio.h> |
|
||||||
#include <mem.h> |
|
||||||
#include <osapi.h> |
|
||||||
#include <user_interface.h> |
|
||||||
|
|
||||||
#include "espmissingincludes.h" |
|
||||||
|
|
@ -1,23 +0,0 @@ |
|||||||
#ifndef ESPFS_H |
|
||||||
#define ESPFS_H |
|
||||||
|
|
||||||
// This define is done in Makefile. If you do not use default Makefile, uncomment
|
|
||||||
// to be able to use Heatshrink-compressed espfs images.
|
|
||||||
//#define ESPFS_HEATSHRINK
|
|
||||||
|
|
||||||
typedef enum { |
|
||||||
ESPFS_INIT_RESULT_OK, |
|
||||||
ESPFS_INIT_RESULT_NO_IMAGE, |
|
||||||
ESPFS_INIT_RESULT_BAD_ALIGN, |
|
||||||
} EspFsInitResult; |
|
||||||
|
|
||||||
typedef struct EspFsFile EspFsFile; |
|
||||||
|
|
||||||
EspFsInitResult espFsInit(void *flashAddress); |
|
||||||
EspFsFile *espFsOpen(char *fileName); |
|
||||||
int espFsFlags(EspFsFile *fh); |
|
||||||
int espFsRead(EspFsFile *fh, char *buff, int len); |
|
||||||
void espFsClose(EspFsFile *fh); |
|
||||||
|
|
||||||
|
|
||||||
#endif |
|
@ -1,60 +0,0 @@ |
|||||||
#ifndef ESPMISSINGINCLUDES_H |
|
||||||
#define ESPMISSINGINCLUDES_H |
|
||||||
|
|
||||||
#include <stdint.h> |
|
||||||
#include <c_types.h> |
|
||||||
#include <ets_sys.h> |
|
||||||
#include <eagle_soc.h> |
|
||||||
|
|
||||||
//Missing function prototypes in include folders. Gcc will warn on these if we don't define 'em anywhere.
|
|
||||||
//MOST OF THESE ARE GUESSED! but they seem to swork and shut up the compiler.
|
|
||||||
typedef struct espconn espconn; |
|
||||||
|
|
||||||
int atoi(const char *nptr); |
|
||||||
void ets_install_putc1(void *routine); |
|
||||||
void ets_isr_attach(int intr, void *handler, void *arg); |
|
||||||
void ets_isr_mask(unsigned intr); |
|
||||||
void ets_isr_unmask(unsigned intr); |
|
||||||
int ets_memcmp(const void *s1, const void *s2, size_t n); |
|
||||||
void *ets_memcpy(void *dest, const void *src, size_t n); |
|
||||||
void *ets_memset(void *s, int c, size_t n); |
|
||||||
int ets_sprintf(char *str, const char *format, ...) __attribute__ ((format (printf, 2, 3))); |
|
||||||
int ets_str2macaddr(void *, void *); |
|
||||||
int ets_strcmp(const char *s1, const char *s2); |
|
||||||
char *ets_strcpy(char *dest, const char *src); |
|
||||||
size_t ets_strlen(const char *s); |
|
||||||
int ets_strncmp(const char *s1, const char *s2, int len); |
|
||||||
char *ets_strncpy(char *dest, const char *src, size_t n); |
|
||||||
char *ets_strstr(const char *haystack, const char *needle); |
|
||||||
void ets_timer_arm_new(ETSTimer *a, int b, int c, int isMstimer); |
|
||||||
void ets_timer_disarm(ETSTimer *a); |
|
||||||
void ets_timer_setfn(ETSTimer *t, ETSTimerFunc *fn, void *parg); |
|
||||||
void ets_update_cpu_frequency(int freqmhz); |
|
||||||
int os_printf(const char *format, ...) __attribute__ ((format (printf, 1, 2))); |
|
||||||
int os_snprintf(char *str, size_t size, const char *format, ...) __attribute__ ((format (printf, 3, 4))); |
|
||||||
int os_printf_plus(const char *format, ...) __attribute__ ((format (printf, 1, 2))); |
|
||||||
void pvPortFree(void *ptr); |
|
||||||
void *pvPortMalloc(size_t xWantedSize); |
|
||||||
void *pvPortZalloc(size_t); |
|
||||||
void uart_div_modify(int no, unsigned int freq); |
|
||||||
void vPortFree(void *ptr); |
|
||||||
void *vPortMalloc(size_t xWantedSize); |
|
||||||
uint8 wifi_get_opmode(void); |
|
||||||
uint32 system_get_time(); |
|
||||||
int rand(void); |
|
||||||
void ets_bzero(void *s, size_t n); |
|
||||||
void ets_delay_us(int ms); |
|
||||||
|
|
||||||
|
|
||||||
//Standard PIN_FUNC_SELECT gives a warning. Replace by a non-warning one.
|
|
||||||
#ifdef PIN_FUNC_SELECT |
|
||||||
#undef PIN_FUNC_SELECT |
|
||||||
#define PIN_FUNC_SELECT(PIN_NAME, FUNC) do { \ |
|
||||||
WRITE_PERI_REG(PIN_NAME, \
|
|
||||||
(READ_PERI_REG(PIN_NAME) \
|
|
||||||
& (~(PERIPHS_IO_MUX_FUNC<<PERIPHS_IO_MUX_FUNC_S))) \
|
|
||||||
|( (((FUNC&BIT2)<<2)|(FUNC&0x3))<<PERIPHS_IO_MUX_FUNC_S) ); \
|
|
||||||
} while (0) |
|
||||||
#endif |
|
||||||
|
|
||||||
#endif |
|
@ -1,67 +0,0 @@ |
|||||||
#ifndef HTTPD_H |
|
||||||
#define HTTPD_H |
|
||||||
|
|
||||||
#define HTTPDVER "0.3" |
|
||||||
|
|
||||||
#define HTTPD_CGI_MORE 0 |
|
||||||
#define HTTPD_CGI_DONE 1 |
|
||||||
#define HTTPD_CGI_NOTFOUND 2 |
|
||||||
#define HTTPD_CGI_AUTHENTICATED 3 |
|
||||||
|
|
||||||
#define HTTPD_METHOD_GET 1 |
|
||||||
#define HTTPD_METHOD_POST 2 |
|
||||||
|
|
||||||
|
|
||||||
typedef struct HttpdPriv HttpdPriv; |
|
||||||
typedef struct HttpdConnData HttpdConnData; |
|
||||||
typedef struct HttpdPostData HttpdPostData; |
|
||||||
|
|
||||||
typedef int (* cgiSendCallback)(HttpdConnData *connData); |
|
||||||
|
|
||||||
//A struct describing a http connection. This gets passed to cgi functions.
|
|
||||||
struct HttpdConnData { |
|
||||||
struct espconn *conn; |
|
||||||
char requestType; |
|
||||||
char *url; |
|
||||||
char *getArgs; |
|
||||||
const void *cgiArg; |
|
||||||
void *cgiData; |
|
||||||
void *cgiPrivData; // Used for streaming handlers storing state between requests
|
|
||||||
char *hostName; |
|
||||||
HttpdPriv *priv; |
|
||||||
cgiSendCallback cgi; |
|
||||||
HttpdPostData *post; |
|
||||||
}; |
|
||||||
|
|
||||||
//A struct describing the POST data sent inside the http connection. This is used by the CGI functions
|
|
||||||
struct HttpdPostData { |
|
||||||
int len; // POST Content-Length
|
|
||||||
int buffSize; // The maximum length of the post buffer
|
|
||||||
int buffLen; // The amount of bytes in the current post buffer
|
|
||||||
int received; // The total amount of bytes received so far
|
|
||||||
char *buff; // Actual POST data buffer
|
|
||||||
char *multipartBoundary; |
|
||||||
}; |
|
||||||
|
|
||||||
//A struct describing an url. This is the main struct that's used to send different URL requests to
|
|
||||||
//different routines.
|
|
||||||
typedef struct { |
|
||||||
const char *url; |
|
||||||
cgiSendCallback cgiCb; |
|
||||||
const void *cgiArg; |
|
||||||
} HttpdBuiltInUrl; |
|
||||||
|
|
||||||
int ICACHE_FLASH_ATTR cgiRedirect(HttpdConnData *connData); |
|
||||||
int ICACHE_FLASH_ATTR cgiCheckHostname(HttpdConnData *connData); |
|
||||||
void ICACHE_FLASH_ATTR httpdRedirect(HttpdConnData *conn, char *newUrl); |
|
||||||
int httpdUrlDecode(char *val, int valLen, char *ret, int retLen); |
|
||||||
int ICACHE_FLASH_ATTR httpdFindArg(char *line, char *arg, char *buff, int buffLen); |
|
||||||
void ICACHE_FLASH_ATTR httpdInit(HttpdBuiltInUrl *fixedUrls, int port); |
|
||||||
const char *httpdGetMimetype(char *url); |
|
||||||
void ICACHE_FLASH_ATTR httpdStartResponse(HttpdConnData *conn, int code); |
|
||||||
void ICACHE_FLASH_ATTR httpdHeader(HttpdConnData *conn, const char *field, const char *val); |
|
||||||
void ICACHE_FLASH_ATTR httpdEndHeaders(HttpdConnData *conn); |
|
||||||
int ICACHE_FLASH_ATTR httpdGetHeader(HttpdConnData *conn, char *header, char *ret, int retLen); |
|
||||||
int ICACHE_FLASH_ATTR httpdSend(HttpdConnData *conn, const char *data, int len); |
|
||||||
|
|
||||||
#endif |
|
@ -1,9 +0,0 @@ |
|||||||
#ifndef HTTPDESPFS_H |
|
||||||
#define HTTPDESPFS_H |
|
||||||
|
|
||||||
#include "httpd.h" |
|
||||||
|
|
||||||
int cgiEspFsHook(HttpdConnData *connData); |
|
||||||
int ICACHE_FLASH_ATTR cgiEspFsTemplate(HttpdConnData *connData); |
|
||||||
|
|
||||||
#endif |
|
@ -1 +0,0 @@ |
|||||||
|
|
@ -1,3 +0,0 @@ |
|||||||
extern char webpages_espfs_start[]; |
|
||||||
extern char webpages_espfs_end[]; |
|
||||||
extern int webpages_espfs_size; |
|
@ -1 +0,0 @@ |
|||||||
Subproject commit 555f7cf0b0a508c2f804d4fdf6c1fd0d92f9a798 |
|
@ -1,252 +0,0 @@ |
|||||||
#include <esp8266.h> |
|
||||||
|
|
||||||
/*
|
|
||||||
* ---------------------------------------------------------------------------- |
|
||||||
* "THE BEER-WARE LICENSE" (Revision 42): |
|
||||||
* Jeroen Domburg <jeroen@spritesmods.com> wrote this file. As long as you retain
|
|
||||||
* this notice you can do whatever you want with this stuff. If we meet some day,
|
|
||||||
* and you think this stuff is worth it, you can buy me a beer in return.
|
|
||||||
* ---------------------------------------------------------------------------- |
|
||||||
*/ |
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
This is a 'captive portal' DNS server: it basically replies with a fixed IP (in this case: |
|
||||||
the one of the SoftAP interface of this ESP module) for any and all DNS queries. This can
|
|
||||||
be used to send mobile phones, tablets etc which connect to the ESP in AP mode directly to |
|
||||||
the internal webserver. |
|
||||||
*/ |
|
||||||
|
|
||||||
|
|
||||||
typedef struct __attribute__ ((packed)) { |
|
||||||
uint16_t id; |
|
||||||
uint8_t flags; |
|
||||||
uint8_t rcode; |
|
||||||
uint16_t qdcount; |
|
||||||
uint16_t ancount; |
|
||||||
uint16_t nscount; |
|
||||||
uint16_t arcount; |
|
||||||
} DnsHeader; |
|
||||||
|
|
||||||
|
|
||||||
typedef struct __attribute__ ((packed)) { |
|
||||||
uint8_t len; |
|
||||||
uint8_t data; |
|
||||||
} DnsLabel; |
|
||||||
|
|
||||||
|
|
||||||
typedef struct __attribute__ ((packed)) { |
|
||||||
//before: label
|
|
||||||
uint16_t type; |
|
||||||
uint16_t class; |
|
||||||
} DnsQuestionFooter; |
|
||||||
|
|
||||||
|
|
||||||
typedef struct __attribute__ ((packed)) { |
|
||||||
//before: label
|
|
||||||
uint16_t type; |
|
||||||
uint16_t class; |
|
||||||
uint32_t ttl; |
|
||||||
uint16_t rdlength; |
|
||||||
//after: rdata
|
|
||||||
} DnsResourceFooter; |
|
||||||
|
|
||||||
typedef struct __attribute__ ((packed)) { |
|
||||||
uint16_t prio; |
|
||||||
uint16_t weight; |
|
||||||
} DnsUriHdr; |
|
||||||
|
|
||||||
|
|
||||||
#define FLAG_QR (1<<7) |
|
||||||
#define FLAG_AA (1<<2) |
|
||||||
#define FLAG_TC (1<<1) |
|
||||||
#define FLAG_RD (1<<0) |
|
||||||
|
|
||||||
#define QTYPE_A 1 |
|
||||||
#define QTYPE_NS 2 |
|
||||||
#define QTYPE_CNAME 5 |
|
||||||
#define QTYPE_SOA 6 |
|
||||||
#define QTYPE_WKS 11 |
|
||||||
#define QTYPE_PTR 12 |
|
||||||
#define QTYPE_HINFO 13 |
|
||||||
#define QTYPE_MINFO 14 |
|
||||||
#define QTYPE_MX 15 |
|
||||||
#define QTYPE_TXT 16 |
|
||||||
#define QTYPE_URI 256 |
|
||||||
|
|
||||||
#define QCLASS_IN 1 |
|
||||||
#define QCLASS_ANY 255 |
|
||||||
#define QCLASS_URI 256 |
|
||||||
|
|
||||||
|
|
||||||
//Function to put unaligned 16-bit network values
|
|
||||||
static void ICACHE_FLASH_ATTR setn16(void *pp, int16_t n) { |
|
||||||
char *p=pp; |
|
||||||
*p++=(n>>8); |
|
||||||
*p++=(n&0xff); |
|
||||||
} |
|
||||||
|
|
||||||
//Function to put unaligned 32-bit network values
|
|
||||||
static void ICACHE_FLASH_ATTR setn32(void *pp, int32_t n) { |
|
||||||
char *p=pp; |
|
||||||
*p++=(n>>24)&0xff; |
|
||||||
*p++=(n>>16)&0xff; |
|
||||||
*p++=(n>>8)&0xff; |
|
||||||
*p++=(n&0xff); |
|
||||||
} |
|
||||||
|
|
||||||
static uint16_t ICACHE_FLASH_ATTR ntohs(uint16_t *in) { |
|
||||||
char *p=(char*)in; |
|
||||||
return ((p[0]<<8)&0xff00)|(p[1]&0xff); |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
//Parses a label into a C-string containing a dotted
|
|
||||||
//Returns pointer to start of next fields in packet
|
|
||||||
static char* ICACHE_FLASH_ATTR labelToStr(char *packet, char *labelPtr, int packetSz, char *res, int resMaxLen) { |
|
||||||
int i, j, k; |
|
||||||
char *endPtr=NULL; |
|
||||||
i=0; |
|
||||||
do { |
|
||||||
if ((*labelPtr&0xC0)==0) { |
|
||||||
j=*labelPtr++; //skip past length
|
|
||||||
//Add separator period if there already is data in res
|
|
||||||
if (i<resMaxLen && i!=0) res[i++]='.'; |
|
||||||
//Copy label to res
|
|
||||||
for (k=0; k<j; k++) { |
|
||||||
if ((labelPtr-packet)>packetSz) return NULL; |
|
||||||
if (i<resMaxLen) res[i++]=*labelPtr++; |
|
||||||
} |
|
||||||
} else if ((*labelPtr&0xC0)==0xC0) { |
|
||||||
//Compressed label pointer
|
|
||||||
endPtr=labelPtr+2; |
|
||||||
int offset=ntohs(((uint16_t *)labelPtr))&0x3FFF; |
|
||||||
//Check if offset points to somewhere outside of the packet
|
|
||||||
if (offset>packetSz) return NULL; |
|
||||||
labelPtr=&packet[offset]; |
|
||||||
} |
|
||||||
//check for out-of-bound-ness
|
|
||||||
if ((labelPtr-packet)>packetSz) return NULL; |
|
||||||
} while (*labelPtr!=0); |
|
||||||
res[i]=0; //zero-terminate
|
|
||||||
if (endPtr==NULL) endPtr=labelPtr+1; |
|
||||||
return endPtr; |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
//Converts a dotted hostname to the weird label form dns uses.
|
|
||||||
static char ICACHE_FLASH_ATTR *strToLabel(char *str, char *label, int maxLen) { |
|
||||||
char *len=label; //ptr to len byte
|
|
||||||
char *p=label+1; //ptr to next label byte to be written
|
|
||||||
while (1) { |
|
||||||
if (*str=='.' || *str==0) { |
|
||||||
*len=((p-len)-1); //write len of label bit
|
|
||||||
len=p; //pos of len for next part
|
|
||||||
p++; //data ptr is one past len
|
|
||||||
if (*str==0) break; //done
|
|
||||||
str++; |
|
||||||
} else { |
|
||||||
*p++=*str++; //copy byte
|
|
||||||
// if ((p-label)>maxLen) return NULL; //check out of bounds
|
|
||||||
} |
|
||||||
} |
|
||||||
*len=0; |
|
||||||
return p; //ptr to first free byte in resp
|
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
//Receive a DNS packet and maybe send a response back
|
|
||||||
static void ICACHE_FLASH_ATTR captdnsRecv(void* arg, char *pusrdata, unsigned short length) { |
|
||||||
struct espconn *conn=(struct espconn *)arg; |
|
||||||
char buff[512]; |
|
||||||
char reply[512]; |
|
||||||
int i; |
|
||||||
char *rend=&reply[length]; |
|
||||||
char *p=pusrdata; |
|
||||||
DnsHeader *hdr=(DnsHeader*)p; |
|
||||||
DnsHeader *rhdr=(DnsHeader*)&reply[0]; |
|
||||||
p+=sizeof(DnsHeader); |
|
||||||
// os_printf("DNS packet: id 0x%X flags 0x%X rcode 0x%X qcnt %d ancnt %d nscount %d arcount %d len %d\n",
|
|
||||||
// ntohs(&hdr->id), hdr->flags, hdr->rcode, ntohs(&hdr->qdcount), ntohs(&hdr->ancount), ntohs(&hdr->nscount), ntohs(&hdr->arcount), length);
|
|
||||||
//Some sanity checks:
|
|
||||||
if (length>512) return; //Packet is longer than DNS implementation allows
|
|
||||||
if (length<sizeof(DnsHeader)) return; //Packet is too short
|
|
||||||
if (hdr->ancount || hdr->nscount || hdr->arcount) return; //this is a reply, don't know what to do with it
|
|
||||||
if (hdr->flags&FLAG_TC) return; //truncated, can't use this
|
|
||||||
//Reply is basically the request plus the needed data
|
|
||||||
os_memcpy(reply, pusrdata, length); |
|
||||||
rhdr->flags|=FLAG_QR; |
|
||||||
for (i=0; i<ntohs(&hdr->qdcount); i++) { |
|
||||||
//Grab the labels in the q string
|
|
||||||
p=labelToStr(pusrdata, p, length, buff, sizeof(buff)); |
|
||||||
if (p==NULL) return; |
|
||||||
DnsQuestionFooter *qf=(DnsQuestionFooter*)p; |
|
||||||
p+=sizeof(DnsQuestionFooter); |
|
||||||
os_printf("DNS: Q (type 0x%X class 0x%X) for %s\n", ntohs(&qf->type), ntohs(&qf->class), buff); |
|
||||||
if (ntohs(&qf->type)==QTYPE_A) { |
|
||||||
//They want to know the IPv4 address of something.
|
|
||||||
//Build the response.
|
|
||||||
rend=strToLabel(buff, rend, sizeof(reply)-(rend-reply)); //Add the label
|
|
||||||
if (rend==NULL) return; |
|
||||||
DnsResourceFooter *rf=(DnsResourceFooter *)rend; |
|
||||||
rend+=sizeof(DnsResourceFooter); |
|
||||||
setn16(&rf->type, QTYPE_A); |
|
||||||
setn16(&rf->class, QCLASS_IN); |
|
||||||
setn32(&rf->ttl, 1); |
|
||||||
setn16(&rf->rdlength, 4); //IPv4 addr is 4 bytes;
|
|
||||||
//Grab the current IP of the softap interface
|
|
||||||
struct ip_info info; |
|
||||||
wifi_get_ip_info(SOFTAP_IF, &info); |
|
||||||
*rend++=ip4_addr1(&info.ip); |
|
||||||
*rend++=ip4_addr2(&info.ip); |
|
||||||
*rend++=ip4_addr3(&info.ip); |
|
||||||
*rend++=ip4_addr4(&info.ip); |
|
||||||
setn16(&rhdr->ancount, ntohs(&rhdr->ancount)+1); |
|
||||||
// os_printf("Added A rec to resp. Resp len is %d\n", (rend-reply));
|
|
||||||
} else if (ntohs(&qf->type)==QTYPE_NS) { |
|
||||||
//Give ns server. Basically can be whatever we want because it'll get resolved to our IP later anyway.
|
|
||||||
rend=strToLabel(buff, rend, sizeof(reply)-(rend-reply)); //Add the label
|
|
||||||
DnsResourceFooter *rf=(DnsResourceFooter *)rend; |
|
||||||
rend+=sizeof(DnsResourceFooter); |
|
||||||
setn16(&rf->type, QTYPE_NS); |
|
||||||
setn16(&rf->class, QCLASS_IN); |
|
||||||
setn16(&rf->ttl, 1); |
|
||||||
setn16(&rf->rdlength, 4); |
|
||||||
*rend++=2; |
|
||||||
*rend++='n'; |
|
||||||
*rend++='s'; |
|
||||||
*rend++=0; |
|
||||||
setn16(&rhdr->ancount, ntohs(&rhdr->ancount)+1); |
|
||||||
// os_printf("Added NS rec to resp. Resp len is %d\n", (rend-reply));
|
|
||||||
} else if (ntohs(&qf->type)==QTYPE_URI) { |
|
||||||
//Give uri to us
|
|
||||||
rend=strToLabel(buff, rend, sizeof(reply)-(rend-reply)); //Add the label
|
|
||||||
DnsResourceFooter *rf=(DnsResourceFooter *)rend; |
|
||||||
rend+=sizeof(DnsResourceFooter); |
|
||||||
DnsUriHdr *uh=(DnsUriHdr *)rend; |
|
||||||
rend+=sizeof(DnsUriHdr); |
|
||||||
setn16(&rf->type, QTYPE_URI); |
|
||||||
setn16(&rf->class, QCLASS_URI); |
|
||||||
setn16(&rf->ttl, 1); |
|
||||||
setn16(&rf->rdlength, 4+16); |
|
||||||
setn16(&uh->prio, 10); |
|
||||||
setn16(&uh->weight, 1); |
|
||||||
memcpy(rend, "http://esp.local", 16); |
|
||||||
rend+=16; |
|
||||||
setn16(&rhdr->ancount, ntohs(&rhdr->ancount)+1); |
|
||||||
// os_printf("Added NS rec to resp. Resp len is %d\n", (rend-reply));
|
|
||||||
} |
|
||||||
} |
|
||||||
//Send the response
|
|
||||||
espconn_sent(conn, (uint8*)reply, rend-reply); |
|
||||||
} |
|
||||||
|
|
||||||
void ICACHE_FLASH_ATTR captdnsInit(void) { |
|
||||||
static struct espconn conn; |
|
||||||
static esp_udp udpconn; |
|
||||||
conn.type=ESPCONN_UDP; |
|
||||||
conn.proto.udp=&udpconn; |
|
||||||
conn.proto.udp->local_port = 53; |
|
||||||
espconn_regist_recvcb(&conn, captdnsRecv); |
|
||||||
espconn_create(&conn); |
|
||||||
} |
|
@ -1,77 +0,0 @@ |
|||||||
/*
|
|
||||||
Some flash handling cgi routines. Used for reading the existing flash and updating the ESPFS image. |
|
||||||
*/ |
|
||||||
|
|
||||||
/*
|
|
||||||
* ---------------------------------------------------------------------------- |
|
||||||
* "THE BEER-WARE LICENSE" (Revision 42): |
|
||||||
* Jeroen Domburg <jeroen@spritesmods.com> wrote this file. As long as you retain
|
|
||||||
* this notice you can do whatever you want with this stuff. If we meet some day,
|
|
||||||
* and you think this stuff is worth it, you can buy me a beer in return.
|
|
||||||
* ---------------------------------------------------------------------------- |
|
||||||
*/ |
|
||||||
|
|
||||||
|
|
||||||
#include <esp8266.h> |
|
||||||
#include "cgiflash.h" |
|
||||||
#include "espfs.h" |
|
||||||
|
|
||||||
|
|
||||||
//Cgi that reads the SPI flash. Assumes 512KByte flash.
|
|
||||||
int ICACHE_FLASH_ATTR cgiReadFlash(HttpdConnData *connData) { |
|
||||||
int *pos=(int *)&connData->cgiData; |
|
||||||
if (connData->conn==NULL) { |
|
||||||
//Connection aborted. Clean up.
|
|
||||||
return HTTPD_CGI_DONE; |
|
||||||
} |
|
||||||
|
|
||||||
if (*pos==0) { |
|
||||||
os_printf("Start flash download.\n"); |
|
||||||
httpdStartResponse(connData, 200); |
|
||||||
httpdHeader(connData, "Content-Type", "application/bin"); |
|
||||||
httpdEndHeaders(connData); |
|
||||||
*pos=0x40200000; |
|
||||||
return HTTPD_CGI_MORE; |
|
||||||
} |
|
||||||
//Send 1K of flash per call. We will get called again if we haven't sent 512K yet.
|
|
||||||
espconn_sent(connData->conn, (uint8 *)(*pos), 1024); |
|
||||||
*pos+=1024; |
|
||||||
if (*pos>=0x40200000+(512*1024)) return HTTPD_CGI_DONE; else return HTTPD_CGI_MORE; |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
//Cgi that allows the ESPFS image to be replaced via http POST
|
|
||||||
int ICACHE_FLASH_ATTR cgiUploadEspfs(HttpdConnData *connData) { |
|
||||||
const CgiUploadEspfsParams *up=(CgiUploadEspfsParams*)connData->cgiArg; |
|
||||||
|
|
||||||
if (connData->conn==NULL) { |
|
||||||
//Connection aborted. Clean up.
|
|
||||||
return HTTPD_CGI_DONE; |
|
||||||
} |
|
||||||
|
|
||||||
if(connData->post->len > up->espFsSize){ |
|
||||||
// The uploaded file is too large
|
|
||||||
os_printf("ESPFS file too large\n"); |
|
||||||
httpdSend(connData, "HTTP/1.0 500 Internal Server Error\r\nServer: esp8266-httpd/0.3\r\nConnection: close\r\nContent-Type: text/plain\r\nContent-Length: 24\r\n\r\nESPFS image loo large.\r\n", -1); |
|
||||||
return HTTPD_CGI_DONE; |
|
||||||
} |
|
||||||
|
|
||||||
// The source should be 4byte aligned, so go ahead and flash whatever we have
|
|
||||||
int address = up->espFsPos + connData->post->received - connData->post->buffLen; |
|
||||||
if(address % SPI_FLASH_SEC_SIZE == 0){ |
|
||||||
// We need to erase this block
|
|
||||||
os_printf("Erasing flash at %d\n", address/SPI_FLASH_SEC_SIZE); |
|
||||||
spi_flash_erase_sector(address/SPI_FLASH_SEC_SIZE); |
|
||||||
} |
|
||||||
// Write the data
|
|
||||||
os_printf("Writing at: 0x%x\n", address); |
|
||||||
spi_flash_write(address, (uint32 *)connData->post->buff, connData->post->buffLen); |
|
||||||
os_printf("Wrote %d bytes (%dB of %d)\n", connData->post->buffSize, connData->post->received, connData->post->len);//&connData->postBuff));
|
|
||||||
|
|
||||||
if (connData->post->received == connData->post->len){ |
|
||||||
httpdSend(connData, "Finished uploading", -1); |
|
||||||
return HTTPD_CGI_DONE; |
|
||||||
} else { |
|
||||||
return HTTPD_CGI_MORE; |
|
||||||
} |
|
||||||
} |
|
@ -1,311 +0,0 @@ |
|||||||
/*
|
|
||||||
Cgi/template routines for the /wifi url. |
|
||||||
*/ |
|
||||||
|
|
||||||
/*
|
|
||||||
* ---------------------------------------------------------------------------- |
|
||||||
* "THE BEER-WARE LICENSE" (Revision 42): |
|
||||||
* Jeroen Domburg <jeroen@spritesmods.com> wrote this file. As long as you retain
|
|
||||||
* this notice you can do whatever you want with this stuff. If we meet some day,
|
|
||||||
* and you think this stuff is worth it, you can buy me a beer in return.
|
|
||||||
* ---------------------------------------------------------------------------- |
|
||||||
*/ |
|
||||||
|
|
||||||
|
|
||||||
#include <esp8266.h> |
|
||||||
#include "cgiwifi.h" |
|
||||||
|
|
||||||
//Enable this to disallow any changes in AP settings
|
|
||||||
//#define DEMO_MODE
|
|
||||||
|
|
||||||
//WiFi access point data
|
|
||||||
typedef struct { |
|
||||||
char ssid[32]; |
|
||||||
char rssi; |
|
||||||
char enc; |
|
||||||
} ApData; |
|
||||||
|
|
||||||
//Scan result
|
|
||||||
typedef struct { |
|
||||||
char scanInProgress; //if 1, don't access the underlying stuff from the webpage.
|
|
||||||
ApData **apData; |
|
||||||
int noAps; |
|
||||||
} ScanResultData; |
|
||||||
|
|
||||||
//Static scan status storage.
|
|
||||||
static ScanResultData cgiWifiAps; |
|
||||||
|
|
||||||
#define CONNTRY_IDLE 0 |
|
||||||
#define CONNTRY_WORKING 1 |
|
||||||
#define CONNTRY_SUCCESS 2 |
|
||||||
#define CONNTRY_FAIL 3 |
|
||||||
//Connection result var
|
|
||||||
static int connTryStatus=CONNTRY_IDLE; |
|
||||||
static ETSTimer resetTimer; |
|
||||||
|
|
||||||
//Callback the code calls when a wlan ap scan is done. Basically stores the result in
|
|
||||||
//the cgiWifiAps struct.
|
|
||||||
void ICACHE_FLASH_ATTR wifiScanDoneCb(void *arg, STATUS status) { |
|
||||||
int n; |
|
||||||
struct bss_info *bss_link = (struct bss_info *)arg; |
|
||||||
os_printf("wifiScanDoneCb %d\n", status); |
|
||||||
if (status!=OK) { |
|
||||||
cgiWifiAps.scanInProgress=0; |
|
||||||
return; |
|
||||||
} |
|
||||||
|
|
||||||
//Clear prev ap data if needed.
|
|
||||||
if (cgiWifiAps.apData!=NULL) { |
|
||||||
for (n=0; n<cgiWifiAps.noAps; n++) os_free(cgiWifiAps.apData[n]); |
|
||||||
os_free(cgiWifiAps.apData); |
|
||||||
} |
|
||||||
|
|
||||||
//Count amount of access points found.
|
|
||||||
n=0; |
|
||||||
while (bss_link != NULL) { |
|
||||||
bss_link = bss_link->next.stqe_next; |
|
||||||
n++; |
|
||||||
} |
|
||||||
//Allocate memory for access point data
|
|
||||||
cgiWifiAps.apData=(ApData **)os_malloc(sizeof(ApData *)*n); |
|
||||||
cgiWifiAps.noAps=n; |
|
||||||
os_printf("Scan done: found %d APs\n", n); |
|
||||||
|
|
||||||
//Copy access point data to the static struct
|
|
||||||
n=0; |
|
||||||
bss_link = (struct bss_info *)arg; |
|
||||||
while (bss_link != NULL) { |
|
||||||
if (n>=cgiWifiAps.noAps) { |
|
||||||
//This means the bss_link changed under our nose. Shouldn't happen!
|
|
||||||
//Break because otherwise we will write in unallocated memory.
|
|
||||||
os_printf("Huh? I have more than the allocated %d aps!\n", cgiWifiAps.noAps); |
|
||||||
break; |
|
||||||
} |
|
||||||
//Save the ap data.
|
|
||||||
cgiWifiAps.apData[n]=(ApData *)os_malloc(sizeof(ApData)); |
|
||||||
cgiWifiAps.apData[n]->rssi=bss_link->rssi; |
|
||||||
cgiWifiAps.apData[n]->enc=bss_link->authmode; |
|
||||||
strncpy(cgiWifiAps.apData[n]->ssid, (char*)bss_link->ssid, 32); |
|
||||||
|
|
||||||
bss_link = bss_link->next.stqe_next; |
|
||||||
n++; |
|
||||||
} |
|
||||||
//We're done.
|
|
||||||
cgiWifiAps.scanInProgress=0; |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
//Routine to start a WiFi access point scan.
|
|
||||||
static void ICACHE_FLASH_ATTR wifiStartScan() { |
|
||||||
// int x;
|
|
||||||
if (cgiWifiAps.scanInProgress) return; |
|
||||||
cgiWifiAps.scanInProgress=1; |
|
||||||
wifi_station_scan(NULL, wifiScanDoneCb); |
|
||||||
} |
|
||||||
|
|
||||||
//This CGI is called from the bit of AJAX-code in wifi.tpl. It will initiate a
|
|
||||||
//scan for access points and if available will return the result of an earlier scan.
|
|
||||||
//The result is embedded in a bit of JSON parsed by the javascript in wifi.tpl.
|
|
||||||
int ICACHE_FLASH_ATTR cgiWiFiScan(HttpdConnData *connData) { |
|
||||||
int pos=(int)connData->cgiData; |
|
||||||
int len; |
|
||||||
char buff[1024]; |
|
||||||
|
|
||||||
if (!cgiWifiAps.scanInProgress && pos!=0) { |
|
||||||
//Fill in json code for an access point
|
|
||||||
if (pos-1<cgiWifiAps.noAps) { |
|
||||||
len=os_sprintf(buff, "{\"essid\": \"%s\", \"rssi\": \"%d\", \"enc\": \"%d\"}%s\n",
|
|
||||||
cgiWifiAps.apData[pos-1]->ssid, cgiWifiAps.apData[pos-1]->rssi,
|
|
||||||
cgiWifiAps.apData[pos-1]->enc, (pos-1==cgiWifiAps.noAps-1)?"":","); |
|
||||||
httpdSend(connData, buff, len); |
|
||||||
} |
|
||||||
pos++; |
|
||||||
if ((pos-1)>=cgiWifiAps.noAps) { |
|
||||||
len=os_sprintf(buff, "]\n}\n}\n"); |
|
||||||
httpdSend(connData, buff, len); |
|
||||||
//Also start a new scan.
|
|
||||||
wifiStartScan(); |
|
||||||
return HTTPD_CGI_DONE; |
|
||||||
} else { |
|
||||||
connData->cgiData=(void*)pos; |
|
||||||
return HTTPD_CGI_MORE; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
httpdStartResponse(connData, 200); |
|
||||||
httpdHeader(connData, "Content-Type", "text/json"); |
|
||||||
httpdEndHeaders(connData); |
|
||||||
|
|
||||||
if (cgiWifiAps.scanInProgress==1) { |
|
||||||
//We're still scanning. Tell Javascript code that.
|
|
||||||
len=os_sprintf(buff, "{\n \"result\": { \n\"inProgress\": \"1\"\n }\n}\n"); |
|
||||||
httpdSend(connData, buff, len); |
|
||||||
return HTTPD_CGI_DONE; |
|
||||||
} else { |
|
||||||
//We have a scan result. Pass it on.
|
|
||||||
len=os_sprintf(buff, "{\n \"result\": { \n\"inProgress\": \"0\", \n\"APs\": [\n"); |
|
||||||
httpdSend(connData, buff, len); |
|
||||||
if (cgiWifiAps.apData==NULL) cgiWifiAps.noAps=0; |
|
||||||
connData->cgiData=(void *)1; |
|
||||||
return HTTPD_CGI_MORE; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
//Temp store for new ap info.
|
|
||||||
static struct station_config stconf; |
|
||||||
|
|
||||||
//This routine is ran some time after a connection attempt to an access point. If
|
|
||||||
//the connect succeeds, this gets the module in STA-only mode.
|
|
||||||
static void ICACHE_FLASH_ATTR resetTimerCb(void *arg) { |
|
||||||
int x=wifi_station_get_connect_status(); |
|
||||||
if (x==STATION_GOT_IP) { |
|
||||||
//Go to STA mode. This needs a reset, so do that.
|
|
||||||
os_printf("Got IP. Going into STA mode..\n"); |
|
||||||
wifi_set_opmode(1); |
|
||||||
system_restart(); |
|
||||||
} else { |
|
||||||
connTryStatus=CONNTRY_FAIL; |
|
||||||
os_printf("Connect fail. Not going into STA-only mode.\n"); |
|
||||||
//Maybe also pass this through on the webpage?
|
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
//Actually connect to a station. This routine is timed because I had problems
|
|
||||||
//with immediate connections earlier. It probably was something else that caused it,
|
|
||||||
//but I can't be arsed to put the code back :P
|
|
||||||
static void ICACHE_FLASH_ATTR reassTimerCb(void *arg) { |
|
||||||
int x; |
|
||||||
os_printf("Try to connect to AP....\n"); |
|
||||||
wifi_station_disconnect(); |
|
||||||
wifi_station_set_config(&stconf); |
|
||||||
wifi_station_connect(); |
|
||||||
x=wifi_get_opmode(); |
|
||||||
connTryStatus=CONNTRY_WORKING; |
|
||||||
if (x!=1) { |
|
||||||
//Schedule disconnect/connect
|
|
||||||
os_timer_disarm(&resetTimer); |
|
||||||
os_timer_setfn(&resetTimer, resetTimerCb, NULL); |
|
||||||
os_timer_arm(&resetTimer, 15000, 0); //time out after 15 secs of trying to connect
|
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
|
|
||||||
//This cgi uses the routines above to connect to a specific access point with the
|
|
||||||
//given ESSID using the given password.
|
|
||||||
int ICACHE_FLASH_ATTR cgiWiFiConnect(HttpdConnData *connData) { |
|
||||||
char essid[128]; |
|
||||||
char passwd[128]; |
|
||||||
static ETSTimer reassTimer; |
|
||||||
|
|
||||||
if (connData->conn==NULL) { |
|
||||||
//Connection aborted. Clean up.
|
|
||||||
return HTTPD_CGI_DONE; |
|
||||||
} |
|
||||||
|
|
||||||
httpdFindArg(connData->post->buff, "essid", essid, sizeof(essid)); |
|
||||||
httpdFindArg(connData->post->buff, "passwd", passwd, sizeof(passwd)); |
|
||||||
|
|
||||||
os_strncpy((char*)stconf.ssid, essid, 32); |
|
||||||
os_strncpy((char*)stconf.password, passwd, 64); |
|
||||||
os_printf("Try to connect to AP %s pw %s\n", essid, passwd); |
|
||||||
|
|
||||||
//Schedule disconnect/connect
|
|
||||||
os_timer_disarm(&reassTimer); |
|
||||||
os_timer_setfn(&reassTimer, reassTimerCb, NULL); |
|
||||||
//Set to 0 if you want to disable the actual reconnecting bit
|
|
||||||
#ifdef DEMO_MODE |
|
||||||
httpdRedirect(connData, "/wifi"); |
|
||||||
#else |
|
||||||
os_timer_arm(&reassTimer, 500, 0); |
|
||||||
httpdRedirect(connData, "connecting.html"); |
|
||||||
#endif |
|
||||||
return HTTPD_CGI_DONE; |
|
||||||
} |
|
||||||
|
|
||||||
//This cgi uses the routines above to connect to a specific access point with the
|
|
||||||
//given ESSID using the given password.
|
|
||||||
int ICACHE_FLASH_ATTR cgiWiFiSetMode(HttpdConnData *connData) { |
|
||||||
int len; |
|
||||||
char buff[1024]; |
|
||||||
|
|
||||||
if (connData->conn==NULL) { |
|
||||||
//Connection aborted. Clean up.
|
|
||||||
return HTTPD_CGI_DONE; |
|
||||||
} |
|
||||||
|
|
||||||
len=httpdFindArg(connData->getArgs, "mode", buff, sizeof(buff)); |
|
||||||
if (len!=0) { |
|
||||||
os_printf("cgiWifiSetMode: %s\n", buff); |
|
||||||
#ifndef DEMO_MODE |
|
||||||
wifi_set_opmode(atoi(buff)); |
|
||||||
system_restart(); |
|
||||||
#endif |
|
||||||
} |
|
||||||
httpdRedirect(connData, "/wifi"); |
|
||||||
return HTTPD_CGI_DONE; |
|
||||||
} |
|
||||||
|
|
||||||
int ICACHE_FLASH_ATTR cgiWiFiConnStatus(HttpdConnData *connData) { |
|
||||||
char buff[1024]; |
|
||||||
int len; |
|
||||||
struct ip_info info; |
|
||||||
int st=wifi_station_get_connect_status(); |
|
||||||
httpdStartResponse(connData, 200); |
|
||||||
httpdHeader(connData, "Content-Type", "text/json"); |
|
||||||
httpdEndHeaders(connData); |
|
||||||
if (connTryStatus==CONNTRY_IDLE) { |
|
||||||
len=os_sprintf(buff, "{\n \"status\": \"idle\"\n }\n"); |
|
||||||
} else if (connTryStatus==CONNTRY_WORKING || connTryStatus==CONNTRY_SUCCESS) { |
|
||||||
if (st==STATION_GOT_IP) { |
|
||||||
wifi_get_ip_info(0, &info); |
|
||||||
len=os_sprintf(buff, "{\n \"status\": \"success\",\n \"ip\": \"%d.%d.%d.%d\" }\n",
|
|
||||||
(info.ip.addr>>0)&0xff, (info.ip.addr>>8)&0xff,
|
|
||||||
(info.ip.addr>>16)&0xff, (info.ip.addr>>24)&0xff); |
|
||||||
//Reset into AP-only mode sooner.
|
|
||||||
os_timer_disarm(&resetTimer); |
|
||||||
os_timer_setfn(&resetTimer, resetTimerCb, NULL); |
|
||||||
os_timer_arm(&resetTimer, 1000, 0); |
|
||||||
} else { |
|
||||||
len=os_sprintf(buff, "{\n \"status\": \"working\"\n }\n"); |
|
||||||
} |
|
||||||
} else { |
|
||||||
len=os_sprintf(buff, "{\n \"status\": \"fail\"\n }\n"); |
|
||||||
} |
|
||||||
|
|
||||||
httpdSend(connData, buff, len); |
|
||||||
return HTTPD_CGI_DONE; |
|
||||||
} |
|
||||||
|
|
||||||
//Template code for the WLAN page.
|
|
||||||
int ICACHE_FLASH_ATTR tplWlan(HttpdConnData *connData, char *token, void **arg) { |
|
||||||
char buff[1024]; |
|
||||||
int x; |
|
||||||
static struct station_config stconf; |
|
||||||
if (token==NULL) return HTTPD_CGI_DONE; |
|
||||||
wifi_station_get_config(&stconf); |
|
||||||
|
|
||||||
os_strcpy(buff, "Unknown"); |
|
||||||
if (os_strcmp(token, "WiFiMode")==0) { |
|
||||||
x=wifi_get_opmode(); |
|
||||||
if (x==1) os_strcpy(buff, "Client"); |
|
||||||
if (x==2) os_strcpy(buff, "SoftAP"); |
|
||||||
if (x==3) os_strcpy(buff, "STA+AP"); |
|
||||||
} else if (os_strcmp(token, "currSsid")==0) { |
|
||||||
os_strcpy(buff, (char*)stconf.ssid); |
|
||||||
} else if (os_strcmp(token, "WiFiPasswd")==0) { |
|
||||||
os_strcpy(buff, (char*)stconf.password); |
|
||||||
} else if (os_strcmp(token, "WiFiapwarn")==0) { |
|
||||||
x=wifi_get_opmode(); |
|
||||||
if (x==2) { |
|
||||||
os_strcpy(buff, "<b>Can't scan in this mode.</b> Click <a href=\"setmode.cgi?mode=3\">here</a> to go to STA+AP mode."); |
|
||||||
} else { |
|
||||||
os_strcpy(buff, "Click <a href=\"setmode.cgi?mode=2\">here</a> to go to standalone AP mode."); |
|
||||||
} |
|
||||||
} |
|
||||||
httpdSend(connData, buff, -1); |
|
||||||
return HTTPD_CGI_DONE; |
|
||||||
} |
|
||||||
|
|
||||||
|
|
Loading…
Reference in new issue