diff --git a/.gitignore b/.gitignore index b02eb76..8a5458b 100644 --- a/.gitignore +++ b/.gitignore @@ -25,3 +25,6 @@ tests/wsserver_autobahn/report tests/fuzzy/out/ examples/echo/echo examples/ping/ping +extra/dir2statics/dir2statics +.vscode/launch.json +.vscode/settings.json diff --git a/Makefile b/Makefile index dc91f36..5aecb8c 100644 --- a/Makefile +++ b/Makefile @@ -17,7 +17,7 @@ CC ?= gcc AR = ar LIB_WS = libws.a INCLUDE = include -CFLAGS += -Wall -Wextra -O2 +CFLAGS += -Wall -Wextra -O2 -DWS_STATICS CFLAGS += -I $(INCLUDE) -std=c99 -pedantic LDLIBS = $(LIB_WS) -pthread ARFLAGS = cru @@ -27,6 +27,7 @@ AFL_FUZZ ?= no VERBOSE_EXAMPLES ?= yes VALIDATE_UTF8 ?= yes + # Prefix ifeq ($(PREFIX),) PREFIX := /usr/local @@ -71,11 +72,22 @@ PKGDIR = $(LIBDIR)/pkgconfig # Extra paths TOYWS = extra/toyws +DIR2STATICS_DIR = extra/dir2statics +DIR2STATICS_BIN = $(DIR2STATICS_DIR)/dir2statics +DIR2STATICS_SRC = $(DIR2STATICS_DIR)/dir2statics.c +# Echo example statics (generated) +ECHO_STATIC_DIR = examples/echo +ECHO_STATIC_HDR = $(ECHO_STATIC_DIR)/echo-static.h + +# Only top-level files with known extensions will be tracked as dependencies +ECHO_STATIC_EXTS = html htm js css json txt svg png jpg jpeg gif ico wasm +ECHO_STATIC_INPUTS = $(foreach e,$(ECHO_STATIC_EXTS),$(wildcard $(ECHO_STATIC_DIR)/*.$(e))) + # All ifeq ($(AFL_FUZZ),no) -all: Makefile libws.a examples $(TOYWS)/toyws_test +all: Makefile libws.a $(DIR2STATICS_BIN) examples $(TOYWS)/toyws_test else -all: Makefile libws.a fuzzy +all: Makefile libws.a fuzzy endif # @@ -106,14 +118,25 @@ $(LIB_WS): $(WS_OBJ) $(Q)$(AR) $(ARFLAGS) $(LIB_WS) $^ # Examples + +# Generate embedded statics header for the echo example +$(ECHO_STATIC_HDR): $(DIR2STATICS_BIN) $(ECHO_STATIC_INPUTS) + @echo " GEN $@" + $(Q)$(DIR2STATICS_BIN) $(ECHO_STATIC_DIR) $@ + examples: examples/echo/echo examples/ping/ping -examples/echo/echo: examples/echo/echo.o $(LIB_WS) + +# Ensure statics header exists before compiling echo.c +examples/echo/echo.o: $(ECHO_STATIC_HDR) +examples/echo/echo.o: CFLAGS += -I $(ECHO_STATIC_DIR) + +examples/echo/echo: examples/echo/echo.o $(LIB_WS) @echo " LINK $@" $(Q)$(CC) $(CFLAGS) $< -o $@ $(LDLIBS) examples/ping/ping: examples/ping/ping.o $(LIB_WS) @echo " LINK $@" $(Q)$(CC) $(CFLAGS) $< -o $@ $(LDLIBS) - + # Autobahn tests tests: examples $(MAKE) all -C tests/ VERBOSE_EXAMPLES="$(VERBOSE_EXAMPLES)" @@ -129,6 +152,11 @@ $(TOYWS)/toyws_test: $(TOYWS)/tws_test.o $(TOYWS)/toyws.o @echo " LINK $@" $(Q)$(CC) $(CFLAGS) $^ -o $@ +# dir2statics tool +$(DIR2STATICS_BIN): $(DIR2STATICS_DIR)/dir2statics.o src/sha1.o + @echo " LINK $@" + $(Q)$(CC) $(CFLAGS) $^ -lz -o $@ + # Install rules install: libws.a wsserver.pc @echo " INSTALL $@" @@ -138,6 +166,9 @@ install: libws.a wsserver.pc @#Headers install -d $(DESTDIR)$(INCDIR)/wsserver install -m 644 $(INCLUDE)/*.h $(DESTDIR)$(INCDIR)/wsserver + @#Tools + install -d $(DESTDIR)$(BINDIR) + install -m 0755 $(DIR2STATICS_BIN) $(DESTDIR)$(BINDIR)/ @#Manpages install -d $(DESTDIR)$(MANDIR)/man3 install -m 0644 $(MANPAGES)/*.3 $(DESTDIR)$(MANDIR)/man3/ @@ -147,6 +178,7 @@ uninstall: @echo " UNINSTALL $@" rm -f $(DESTDIR)$(LIBDIR)/$(LIB_WS) rm -rf $(DESTDIR)$(INCDIR)/wsserver + rm -f $(DESTDIR)$(BINDIR)/dir2statics rm -f $(DESTDIR)$(MANDIR)/man3/ws_getaddress.3 rm -f $(DESTDIR)$(MANDIR)/man3/ws_getport.3 rm -f $(DESTDIR)$(MANDIR)/man3/ws_sendframe.3 @@ -194,5 +226,8 @@ clean: @rm -f $(TOYWS)/toyws.o $(TOYWS)/tws_test.o $(TOYWS)toyws_test @rm -f examples/echo/{echo,echo.o} @rm -f examples/ping/{ping,ping.o} + @rm -f $(DIR2STATICS_DIR)/{dir2statics,dir2statics.o} @$(MAKE) clean -C tests/ @$(MAKE) clean -C tests/fuzzy + @rm -f $(ECHO_STATIC_HDR) + @rm -f $(DIR2STATICS_DIR)/*.o \ No newline at end of file diff --git a/README.md b/README.md index f5224bb..14e651b 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,16 @@ [![Build Status for Windows, Linux, and macOS](https://github.com/Theldus/wsServer/actions/workflows/c-cpp.yml/badge.svg)](https://github.com/Theldus/wsServer/actions/workflows/c-cpp.yml) wsServer - a very tiny WebSocket server library written in C - +--- +
+ + + +
+ + + +
## Library wsServer is a tiny, lightweight WebSocket server library written in C that intends @@ -200,3 +209,41 @@ comments [here](https://github.com/Theldus/wsServer/discussions/30). wsServer is licensed under GPLv3 License. Written by Davidson Francis and [others](https://github.com/Theldus/wsServer/graphs/contributors) contributors. + + +## Static Assets Integration + +Create a header file from a folder. + +```bash +dir2statics ./ echo-statics.h +``` + +The header will include the embedded assets and a function **initEmbeddedAssets**() + +```c + +void initEmbeddedAssets(void){ + ws_set_static_assets(&embedded_assets); +} + +``` + + +```c + +#include "echo-static.h" + + +int main(void) +{ + + initEmbeddedAssets(); + sprintf(static_root_alias,"%s","/echo.html"); + + // start your server as usual + ws_socket(&(struct ws_server){ + //... + + +``` diff --git a/examples/echo/echo-static.h b/examples/echo/echo-static.h new file mode 100644 index 0000000..5933bbc --- /dev/null +++ b/examples/echo/echo-static.h @@ -0,0 +1,185 @@ +#pragma once +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* Declarations compatible with wsServer statics.h expectations */ +void initEmbeddedAssets(void);typedef struct ws_static_asset_set { + uint32_t count; + const char * const *urls; + const char * const *contentType; + const uint8_t * const *content; + const uint32_t *sizes; +} ws_static_asset_set_t; + +extern char static_root_alias[32]; + +void ws_set_static_assets(const ws_static_asset_set_t *set); + +#ifdef WS_STATICS_DATA_IMPLEMENTATION + +const uint32_t static_count = 2; + +const char *static_urls[2] = { + "/CMakeLists.txt", + "/echo.html" +}; + +static const uint8_t ws_static_CMakeLists_txt[479] = { +/* 0000*/0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x85, 0x92, 0x41, 0x8f, 0xda, 0x30, +/* 0010*/0x10, 0x85, 0xef, 0xf9, 0x15, 0x4f, 0xe2, 0x02, 0x12, 0x0a, 0x15, 0xbd, 0x6d, 0x57, 0xab, 0xa6, +/* 0020*/0x14, 0x76, 0x23, 0x51, 0x40, 0x21, 0x68, 0xc5, 0x69, 0xe5, 0xc4, 0x93, 0xc4, 0xaa, 0x63, 0x47, +/* 0030*/0xb6, 0x03, 0xe5, 0xdf, 0x77, 0x02, 0x54, 0x5c, 0xaa, 0xf6, 0x62, 0xc5, 0xe3, 0x99, 0xcf, 0xef, +/* 0040*/0xf9, 0x65, 0x84, 0x85, 0xed, 0x2e, 0x4e, 0xd5, 0x4d, 0xc0, 0x78, 0x31, 0xc1, 0xfc, 0xd3, 0x7c, +/* 0050*/0x0e, 0x7c, 0x17, 0x27, 0x25, 0xbd, 0x35, 0x58, 0x39, 0x61, 0x4a, 0xe5, 0xf1, 0x2c, 0xef, 0x15, +/* 0060*/0x59, 0xd5, 0xfa, 0x6b, 0xdd, 0x0a, 0xa5, 0xe3, 0xd2, 0xb6, 0x2f, 0xd1, 0x28, 0x1a, 0x21, 0x6f, +/* 0070*/0xb8, 0xa3, 0x73, 0xb6, 0x76, 0xa2, 0x05, 0x7f, 0x56, 0x8e, 0x08, 0xde, 0x56, 0xe1, 0x2c, 0x1c, +/* 0080*/0x3d, 0xe1, 0x62, 0x7b, 0x94, 0xc2, 0xc0, 0x91, 0x54, 0x3e, 0x38, 0x55, 0xf4, 0x81, 0xa0, 0x02, +/* 0090*/0x84, 0x91, 0x33, 0xeb, 0xd0, 0x5a, 0xa9, 0xaa, 0x0b, 0x63, 0xb8, 0xd4, 0x1b, 0x49, 0x0e, 0xa1, +/* 00A0*/0x21, 0x04, 0x72, 0xad, 0x87, 0xad, 0xae, 0x9b, 0xd7, 0xcd, 0x01, 0xaf, 0x64, 0xc8, 0x09, 0x8d, +/* 00B0*/0x5d, 0x5f, 0x68, 0x55, 0x62, 0xad, 0x4a, 0x32, 0x9e, 0x20, 0xf8, 0xe6, 0xa1, 0xe2, 0x1b, 0x92, +/* 00C0*/0x28, 0x06, 0xcc, 0x30, 0xb0, 0x1a, 0x14, 0xec, 0xef, 0x0a, 0xb0, 0xb2, 0xcc, 0x15, 0x41, 0x59, +/* 00D0*/0x33, 0x05, 0x29, 0x3e, 0x77, 0x38, 0x91, 0xf3, 0xbc, 0xc7, 0xe7, 0x3f, 0x57, 0xdc, 0x79, 0x53, +/* 00E0*/0x58, 0xc7, 0x8c, 0xb1, 0x08, 0x83, 0x6c, 0x07, 0xdb, 0x0d, 0x63, 0x13, 0xd6, 0x7a, 0x81, 0x16, +/* 00F0*/0xe1, 0x31, 0x19, 0xff, 0xd5, 0xf9, 0xc3, 0xa0, 0x84, 0x32, 0x57, 0x70, 0x63, 0x3b, 0x76, 0xd3, +/* 0100*/0x30, 0x90, 0xfd, 0x9d, 0x95, 0xd6, 0x28, 0x08, 0xbd, 0xa7, 0xaa, 0xd7, 0x53, 0x26, 0x70, 0x2f, +/* 0110*/0xde, 0xd3, 0xfc, 0x6d, 0x7b, 0xc8, 0x91, 0x6c, 0x8e, 0x78, 0x4f, 0xb2, 0x2c, 0xd9, 0xe4, 0xc7, +/* 0120*/0x2f, 0xdc, 0x1b, 0x1a, 0xcb, 0xa7, 0x74, 0xa2, 0x1b, 0x49, 0xb5, 0x9d, 0x56, 0x0c, 0x66, 0x4f, +/* 0130*/0x9c, 0x4a, 0xb8, 0xb0, 0x74, 0x06, 0xfc, 0x58, 0x66, 0x8b, 0x37, 0x9e, 0x48, 0xbe, 0xa5, 0xeb, +/* 0140*/0x34, 0x3f, 0xb2, 0x7e, 0xac, 0xd2, 0x7c, 0xb3, 0xdc, 0xef, 0xb1, 0xda, 0x66, 0x48, 0xb0, 0x4b, +/* 0150*/0xb2, 0x3c, 0x5d, 0x1c, 0xd6, 0x49, 0x86, 0xdd, 0x21, 0xdb, 0x6d, 0xf7, 0xcb, 0x18, 0xd8, 0xd3, +/* 0160*/0x20, 0x8a, 0x78, 0xfe, 0x1f, 0x6f, 0x5b, 0x5d, 0xd3, 0xe1, 0x07, 0x94, 0x14, 0x38, 0x70, 0x7f, +/* 0170*/0xf3, 0x7c, 0xe4, 0x38, 0x3d, 0x2b, 0xd3, 0x12, 0x8d, 0x38, 0x11, 0xc7, 0x5a, 0x92, 0x3a, 0xb1, +/* 0180*/0x2e, 0x81, 0x92, 0x7f, 0xa5, 0xff, 0x67, 0xc6, 0x0c, 0xa1, 0xad, 0xa9, 0xaf, 0x0e, 0xb9, 0xf7, +/* 0190*/0xf1, 0x84, 0x2c, 0x2c, 0xad, 0x60, 0x6c, 0x98, 0xc2, 0xb3, 0xc0, 0xe7, 0x26, 0x84, 0xee, 0x69, +/* 01A0*/0x36, 0x3b, 0x9f, 0xcf, 0x71, 0x6d, 0xfa, 0xd8, 0xba, 0x7a, 0xa6, 0x6f, 0x0c, 0x3f, 0x7b, 0x89, +/* 01B0*/0x22, 0x21, 0xe5, 0x07, 0xfd, 0xa2, 0xb2, 0x0f, 0xa2, 0xd0, 0x34, 0xa6, 0xb2, 0xb1, 0x18, 0x96, +/* 01C0*/0xb8, 0x9c, 0x44, 0x41, 0xb8, 0x9a, 0xc2, 0x87, 0x56, 0xe6, 0x27, 0x2f, 0x85, 0x13, 0x4e, 0x91, +/* 01D0*/0xbf, 0xb5, 0x9c, 0xfd, 0x24, 0xfa, 0x0d, 0xf7, 0x2a, 0x63, 0xb8, 0xf7, 0x02, 0x00, 0x00 +}; + +static const uint8_t ws_static_echo_html[1402] = { +/* 0000*/0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xa5, 0x57, 0x6d, 0x6f, 0x1a, 0x39, +/* 0010*/0x10, 0xfe, 0xbc, 0xfb, 0x2b, 0xa6, 0xfb, 0x25, 0xd0, 0x26, 0x90, 0xa4, 0xbd, 0x53, 0x45, 0x00, +/* 0020*/0x1d, 0x25, 0xa4, 0x41, 0xca, 0x9b, 0x80, 0xa8, 0x8a, 0x74, 0x52, 0x65, 0xd6, 0xc3, 0xee, 0xaa, +/* 0030*/0x66, 0x8d, 0xd6, 0x06, 0x8a, 0xae, 0xf9, 0xef, 0x37, 0xe3, 0xdd, 0x05, 0x72, 0xa4, 0x21, 0xbd, +/* 0040*/0xe6, 0x43, 0xe2, 0xb5, 0x67, 0xc6, 0xcf, 0x3c, 0xf3, 0x8c, 0xed, 0x34, 0xdf, 0x1c, 0x1d, 0xf9, +/* 0050*/0x5d, 0x3d, 0x5b, 0x65, 0x49, 0x14, 0x5b, 0xa8, 0x74, 0xab, 0x70, 0x7a, 0x7c, 0xf2, 0xe7, 0xd1, +/* 0060*/0xe9, 0xf1, 0xe9, 0x31, 0xc0, 0xb9, 0x58, 0x24, 0xd2, 0xe8, 0x14, 0x2e, 0x32, 0x91, 0x86, 0x89, +/* 0070*/0x81, 0xa6, 0x2c, 0x66, 0xe4, 0x24, 0x52, 0x7f, 0x45, 0x53, 0x91, 0xa8, 0x5a, 0xa8, 0xa7, 0x6d, +/* 0080*/0xdf, 0x1f, 0xc5, 0xb4, 0x3c, 0xcb, 0x74, 0x94, 0x89, 0x29, 0xd0, 0x70, 0x92, 0x21, 0x82, 0xd1, +/* 0090*/0x13, 0xbb, 0x14, 0x19, 0x36, 0x60, 0xa5, 0xe7, 0x10, 0x8a, 0x14, 0x32, 0x94, 0x89, 0xb1, 0x59, +/* 00A0*/0x32, 0x9e, 0x5b, 0x84, 0xc4, 0x82, 0x48, 0x65, 0x5d, 0x67, 0x30, 0xd5, 0x32, 0x99, 0xac, 0x7c, +/* 00B0*/0x9a, 0x98, 0xa7, 0x12, 0x33, 0xb0, 0x31, 0x82, 0xc5, 0x6c, 0x6a, 0x40, 0x4f, 0xdc, 0xc7, 0xe7, +/* 00C0*/0x9b, 0x7b, 0xf8, 0x8c, 0x29, 0x66, 0x42, 0xc1, 0xdd, 0x7c, 0xac, 0x92, 0x10, 0xae, 0x92, 0x10, +/* 00D0*/0x53, 0x83, 0x20, 0x68, 0x5f, 0x9e, 0x31, 0x31, 0x4a, 0x18, 0xaf, 0x7c, 0x36, 0xbf, 0xe0, 0xdd, +/* 00E0*/0x87, 0xc5, 0xee, 0x70, 0xa1, 0x29, 0xaa, 0xb0, 0x89, 0x4e, 0x0f, 0x01, 0x13, 0x5a, 0xcf, 0x60, +/* 00F0*/0x81, 0x99, 0xa1, 0x6f, 0x78, 0x5f, 0x6e, 0x50, 0x44, 0x3b, 0x04, 0x9d, 0xf9, 0x15, 0x61, 0x19, +/* 0100*/0x70, 0x06, 0x7a, 0xc6, 0x4e, 0x55, 0x42, 0xb9, 0x02, 0x25, 0xec, 0xc6, 0xaf, 0xb6, 0x9b, 0xf0, +/* 0110*/0x26, 0x2f, 0x09, 0x49, 0xea, 0x62, 0xc6, 0x7a, 0x46, 0x69, 0xc4, 0x14, 0x8d, 0x12, 0x5b, 0x26, +/* 0120*/0x4a, 0xc1, 0x18, 0x61, 0x6e, 0x70, 0x32, 0x57, 0x87, 0x3e, 0x59, 0xc2, 0x97, 0xfe, 0xe8, 0xf2, +/* 0130*/0xf6, 0x7e, 0x04, 0x9d, 0x9b, 0x07, 0xf8, 0xd2, 0x19, 0x0c, 0x3a, 0x37, 0xa3, 0x87, 0x33, 0xb2, +/* 0140*/0xb4, 0xb1, 0xa6, 0x55, 0x5c, 0x60, 0x1e, 0x27, 0x99, 0xce, 0x54, 0x42, 0x61, 0x29, 0x19, 0xaa, +/* 0150*/0x83, 0x5d, 0x11, 0x66, 0xff, 0xba, 0x37, 0xe8, 0x5e, 0x92, 0x7d, 0xe7, 0x53, 0xff, 0xaa, 0x3f, +/* 0160*/0x7a, 0x20, 0xd8, 0x70, 0xd1, 0x1f, 0xdd, 0xf4, 0x86, 0x43, 0xb8, 0xb8, 0x1d, 0x40, 0x07, 0xee, +/* 0170*/0x3a, 0x83, 0x51, 0xbf, 0x7b, 0x7f, 0xd5, 0x19, 0xc0, 0xdd, 0xfd, 0xe0, 0xee, 0x76, 0xd8, 0xab, +/* 0180*/0x01, 0x0c, 0x91, 0x01, 0xa1, 0xff, 0x02, 0x9d, 0x13, 0x57, 0x0e, 0x62, 0x4d, 0xa2, 0xa5, 0xf2, +/* 0190*/0x1a, 0x4a, 0xf5, 0x81, 0x8a, 0x67, 0x08, 0x92, 0x92, 0x10, 0x8b, 0x05, 0x52, 0x11, 0x43, 0x4c, +/* 01A0*/0x16, 0x04, 0x48, 0x40, 0x48, 0xd2, 0xd9, 0x5f, 0x23, 0x5f, 0x28, 0x9d, 0x46, 0x2e, 0x31, 0xb2, +/* 01B0*/0xdc, 0xf0, 0x46, 0x88, 0xfa, 0x13, 0x48, 0xb5, 0x3d, 0x04, 0x43, 0xc8, 0x9a, 0xb1, 0xb5, 0xb3, +/* 01C0*/0x46, 0xbd, 0xbe, 0x5c, 0x2e, 0x6b, 0x51, 0x3a, 0xaf, 0xe9, 0x2c, 0xaa, 0xab, 0x3c, 0x82, 0xa9, +/* 01D0*/0xb7, 0xfd, 0xa3, 0xa3, 0xb6, 0x4f, 0x26, 0x53, 0xc5, 0x7f, 0x50, 0xc8, 0xb6, 0xef, 0x35, 0x4d, +/* 01E0*/0x98, 0x25, 0x33, 0x4b, 0xfa, 0xf3, 0xea, 0x6f, 0xe1, 0x0b, 0x8e, 0x87, 0x3a, 0xfc, 0x86, 0xb6, +/* 01F0*/0x06, 0x6f, 0xeb, 0xbe, 0xb7, 0x10, 0x19, 0x2c, 0xcd, 0x59, 0x3e, 0x08, 0x75, 0x9a, 0x62, 0xc8, +/* 0200*/0xc5, 0x69, 0xc1, 0x44, 0x28, 0x83, 0x67, 0xe4, 0x33, 0x99, 0xa7, 0x21, 0x57, 0x18, 0x4c, 0xa8, +/* 0210*/0x95, 0xfa, 0x6a, 0xf5, 0xd7, 0xb1, 0xb6, 0x56, 0x4f, 0x2b, 0x55, 0xdf, 0xfb, 0xc7, 0xf7, 0x9c, +/* 0220*/0xa3, 0xd2, 0xd1, 0x48, 0x90, 0x93, 0xd4, 0xe1, 0x7c, 0x8a, 0xa9, 0xad, 0x45, 0x68, 0x7b, 0x0a, +/* 0230*/0x79, 0xf8, 0x69, 0xd5, 0x97, 0x95, 0xc0, 0x8a, 0x2b, 0x1d, 0x05, 0xe4, 0xe1, 0x39, 0xd3, 0x1a, +/* 0240*/0x41, 0xa2, 0x60, 0x23, 0x3d, 0x23, 0xa7, 0xed, 0x99, 0x4b, 0xe4, 0x16, 0x3b, 0xf3, 0x81, 0x7e, +/* 0250*/0x1e, 0x73, 0xc0, 0x3d, 0x63, 0x85, 0x53, 0x6e, 0x09, 0x8f, 0xa5, 0xe5, 0xb0, 0xaf, 0x91, 0x49, +/* 0260*/0xdd, 0xcd, 0x97, 0x2a, 0x42, 0xca, 0xac, 0xc0, 0x45, 0xae, 0xd7, 0x68, 0x8c, 0x88, 0xa8, 0x9a, +/* 0270*/0x9a, 0x45, 0x65, 0x18, 0x98, 0x73, 0x74, 0x98, 0xa7, 0x26, 0xe2, 0xf4, 0xd8, 0xee, 0x5c, 0xef, +/* 0280*/0xc4, 0xf6, 0x96, 0x86, 0xa0, 0xa5, 0xb8, 0xdc, 0x10, 0x96, 0x07, 0x2f, 0x7d, 0x06, 0x18, 0x91, +/* 0290*/0x98, 0x49, 0xed, 0xac, 0x40, 0x6b, 0xd6, 0x5e, 0x35, 0x9d, 0x92, 0xa2, 0x53, 0x66, 0xb0, 0x80, +/* 02A0*/0xc7, 0x44, 0x39, 0x44, 0xde, 0x36, 0xbf, 0x36, 0x9b, 0x13, 0xbd, 0x34, 0xf9, 0x53, 0xce, 0xc6, +/* 02B0*/0x96, 0xb3, 0x0a, 0xaa, 0xb5, 0x85, 0x50, 0x73, 0x24, 0x97, 0xe0, 0x3c, 0x31, 0x45, 0x88, 0x37, +/* 02C0*/0xc1, 0xcb, 0xbe, 0x05, 0xdf, 0x85, 0xeb, 0xbb, 0x16, 0x54, 0x82, 0xee, 0x3a, 0x43, 0x60, 0x80, +/* 02D0*/0x28, 0xff, 0xa6, 0xd8, 0x2e, 0xca, 0x4e, 0x61, 0x79, 0xf6, 0x71, 0x4d, 0x0e, 0x92, 0x12, 0x72, +/* 02E0*/0x59, 0x4e, 0x73, 0x3e, 0x9f, 0x24, 0x5b, 0xcc, 0x6d, 0xe5, 0x0b, 0x15, 0x5c, 0xd8, 0x75, 0xce, +/* 02F0*/0xbf, 0x02, 0x71, 0x80, 0xe1, 0xa2, 0x01, 0x01, 0xbc, 0x23, 0x52, 0x6d, 0x8d, 0x4e, 0x23, 0x41, +/* 0300*/0xc3, 0xe0, 0x95, 0x38, 0xbb, 0x4a, 0x53, 0x67, 0xee, 0x56, 0x23, 0x74, 0xf3, 0x5b, 0xe5, 0x70, +/* 0310*/0x26, 0xfb, 0xf1, 0xed, 0xd2, 0xdf, 0xfd, 0x7f, 0xdc, 0xb3, 0xb1, 0xf7, 0x84, 0x7f, 0x07, 0x49, +/* 0320*/0x36, 0xe8, 0xac, 0x32, 0x5d, 0x85, 0x22, 0x2d, 0x73, 0xe6, 0x68, 0xe5, 0x1c, 0x67, 0x4e, 0x27, +/* 0330*/0xf1, 0xa2, 0xab, 0x25, 0x5d, 0x0d, 0x81, 0x0b, 0xe2, 0x95, 0x46, 0x21, 0x4d, 0xee, 0xa7, 0xc6, +/* 0340*/0x7b, 0xa6, 0x9f, 0x1d, 0x61, 0x1e, 0xb7, 0xd6, 0x1a, 0x3f, 0xc9, 0xba, 0xc7, 0x51, 0xaf, 0x58, +/* 0350*/0xcf, 0x74, 0x32, 0x55, 0x82, 0xf3, 0xdb, 0x6b, 0x42, 0x6b, 0x79, 0x4e, 0x0b, 0x89, 0x92, 0x80, +/* 0360*/0xec, 0xb0, 0x57, 0xb4, 0x58, 0x91, 0x15, 0xd0, 0x49, 0x4d, 0xfb, 0x16, 0xbc, 0xef, 0xe7, 0x94, +/* 0370*/0xcb, 0x92, 0x84, 0xdf, 0x9e, 0xed, 0x92, 0x64, 0x02, 0x95, 0x2d, 0xe4, 0x05, 0x74, 0x5e, 0xce, +/* 0380*/0xd7, 0x5d, 0xf7, 0xda, 0xef, 0xf6, 0xc5, 0xf3, 0xe6, 0xbb, 0x1d, 0x62, 0x46, 0x17, 0x51, 0x59, +/* 0390*/0x07, 0xc7, 0x87, 0xb7, 0x39, 0x28, 0xc8, 0x20, 0xe7, 0x88, 0xa9, 0xf0, 0x3c, 0xa4, 0x1d, 0x36, +/* 03A0*/0x1b, 0x90, 0x6e, 0x5c, 0x89, 0x0a, 0x1a, 0x9f, 0xe7, 0xf1, 0x37, 0xa4, 0xf3, 0xb8, 0xad, 0xdb, +/* 03B0*/0x7e, 0x3a, 0xa3, 0x7b, 0xcc, 0x22, 0x25, 0x54, 0xf4, 0xd2, 0x3e, 0x16, 0x09, 0xfb, 0xb5, 0x61, +/* 03C0*/0x85, 0xed, 0x54, 0xee, 0xe0, 0x1b, 0xae, 0xe6, 0xb3, 0x83, 0xed, 0x72, 0xad, 0x69, 0x65, 0xd6, +/* 03D0*/0x68, 0x99, 0xd0, 0x60, 0x6d, 0x19, 0x27, 0x61, 0x0c, 0x3f, 0x7e, 0xd0, 0x90, 0xa6, 0x58, 0x62, +/* 03E0*/0x67, 0x25, 0xf3, 0xce, 0xa4, 0x05, 0x27, 0xef, 0xab, 0xfb, 0x72, 0xcc, 0x31, 0xb8, 0x3a, 0x16, +/* 03F0*/0xad, 0xb8, 0x3e, 0x1c, 0x87, 0x98, 0xca, 0xd7, 0x66, 0x53, 0x06, 0xfa, 0x25, 0x49, 0xf0, 0xe9, +/* 0400*/0xf9, 0xcb, 0x8a, 0x70, 0xfb, 0x9c, 0xad, 0x1d, 0xe8, 0xde, 0x79, 0xc5, 0x95, 0xe5, 0x52, 0x72, +/* 0410*/0x92, 0xa0, 0xfb, 0x43, 0xb2, 0x6e, 0xf2, 0xaa, 0x16, 0x81, 0x28, 0xc8, 0xf6, 0x09, 0xc6, 0x89, +/* 0420*/0xe7, 0xdd, 0xbc, 0x36, 0x7c, 0xd2, 0xa7, 0xde, 0x66, 0x9a, 0x54, 0x91, 0xab, 0xe1, 0x27, 0xcd, +/* 0430*/0xfb, 0x58, 0xf6, 0x2a, 0x7f, 0x36, 0xeb, 0xe5, 0x25, 0xde, 0xac, 0xe7, 0xd7, 0x7a, 0x73, 0xac, +/* 0440*/0xe5, 0x8a, 0xef, 0xf4, 0xa6, 0x4c, 0x16, 0x90, 0xc8, 0x56, 0xc0, 0xf3, 0x24, 0x78, 0xba, 0xf1, +/* 0450*/0xbd, 0x66, 0x7c, 0x02, 0x42, 0x25, 0x51, 0xda, 0x0a, 0x14, 0x4e, 0x6c, 0xd0, 0x5e, 0x9a, 0xbc, +/* 0460*/0x1d, 0xc8, 0xfb, 0x84, 0x0d, 0xf2, 0xaf, 0x06, 0x34, 0x93, 0x5c, 0x7d, 0xab, 0x19, 0xb6, 0x02, +/* 0470*/0xd6, 0x60, 0xe0, 0x42, 0x6d, 0xda, 0x07, 0x1c, 0xd8, 0x56, 0xb0, 0x34, 0xf4, 0xe6, 0x50, 0x3a, +/* 0480*/0x14, 0x2a, 0xd6, 0xc6, 0x36, 0x3e, 0x1e, 0x7f, 0x3c, 0xce, 0x77, 0xda, 0x0e, 0xe0, 0x0e, 0x81, +/* 0490*/0x34, 0x0f, 0x51, 0xf4, 0x00, 0xa4, 0x62, 0x8a, 0x9b, 0xaf, 0x22, 0xda, 0xba, 0x1d, 0xda, 0xcd, +/* 04A0*/0x71, 0x06, 0xf5, 0xe2, 0x37, 0x33, 0xbd, 0x75, 0x4a, 0x72, 0xcc, 0xc6, 0x7f, 0xb7, 0xc8, 0x84, +/* 04B0*/0x4c, 0x74, 0x19, 0x75, 0xa6, 0xb3, 0x02, 0x30, 0x8f, 0x4e, 0x02, 0x08, 0x63, 0xa4, 0x9b, 0x9a, +/* 04C0*/0x13, 0x20, 0x7d, 0x3c, 0xc5, 0x0e, 0x15, 0xc6, 0x5c, 0x75, 0x07, 0x69, 0x21, 0xb5, 0x56, 0xf0, +/* 04D0*/0xb3, 0xe2, 0x1f, 0xac, 0x09, 0x38, 0x28, 0x9a, 0xb8, 0x75, 0xf0, 0x0c, 0x03, 0x07, 0x39, 0x05, +/* 04E0*/0x4a, 0x8c, 0x51, 0xf1, 0x9b, 0xb0, 0xc4, 0xd1, 0xde, 0xde, 0xb1, 0x59, 0x77, 0xeb, 0x2e, 0xbb, +/* 04F0*/0xd7, 0xa6, 0x72, 0xba, 0x85, 0x3d, 0x0f, 0xf5, 0xe1, 0xc3, 0xfb, 0xdf, 0xc2, 0xfe, 0x14, 0x3c, +/* 0500*/0x45, 0x7b, 0x1e, 0xfb, 0x29, 0x63, 0xdf, 0xec, 0xf8, 0x04, 0xbb, 0xab, 0x11, 0x6c, 0x4a, 0x55, +/* 0510*/0xbc, 0xae, 0x5e, 0x12, 0x11, 0x77, 0x5c, 0x99, 0x89, 0x43, 0x3f, 0x53, 0x22, 0xc4, 0x58, 0x2b, +/* 0520*/0x12, 0x6a, 0x2b, 0x18, 0x91, 0x43, 0xfe, 0x3f, 0x44, 0xf9, 0x88, 0xa8, 0xf4, 0x6e, 0x46, 0xbd, +/* 0530*/0x01, 0xbf, 0xd7, 0xb8, 0xd9, 0xaa, 0xfb, 0x14, 0xe6, 0xc2, 0x97, 0x02, 0xdb, 0xde, 0x8b, 0xdb, +/* 0540*/0x70, 0x57, 0x5b, 0x4d, 0xc6, 0x46, 0xff, 0xeb, 0x08, 0xc8, 0xf4, 0xd2, 0xb4, 0x82, 0x93, 0x63, +/* 0550*/0x52, 0x8c, 0x56, 0x34, 0xfa, 0xe3, 0xb8, 0x80, 0xec, 0x7a, 0xbe, 0x08, 0x99, 0x7f, 0xb4, 0x9b, +/* 0560*/0xf5, 0xd2, 0x8d, 0xdf, 0xd2, 0x75, 0xea, 0x36, 0x6e, 0xc2, 0xbc, 0xfb, 0xa8, 0x9d, 0xdc, 0x53, +/* 0570*/0xfb, 0x5f, 0x47, 0xc3, 0xf6, 0x34, 0x22, 0x0e, 0x00, 0x00 +}; + +const char *static_contentType[2] = { + // CMakeLists.txt + "Etag: \"BBAE7AA04C9B95749B042591BB63DF5425ED0697\"\r\nContent-Encoding: gzip\r\nContent-Type: text/plain; charset=utf-8\r\n", + // echo.html + "Etag: \"88AB1B59A02F3EB2DC290E850AAA179EC95AF0E2\"\r\nContent-Encoding: gzip\r\nContent-Type: text/html; charset=utf-8\r\n" +}; + +const uint8_t *static_content[2] = { + ws_static_CMakeLists_txt, + ws_static_echo_html +}; + +const uint32_t static_content_size[2] = { + (uint32_t)sizeof(ws_static_CMakeLists_txt), + (uint32_t)sizeof(ws_static_echo_html) +}; + +static const ws_static_asset_set_t embedded_assets = { + static_count, static_urls, static_contentType, static_content, static_content_size +}; + + +void initEmbeddedAssets(void){ + ws_set_static_assets(&embedded_assets); + +} + +#endif /* WS_STATICS_DATA_IMPLEMENTATION */ + +#ifdef __cplusplus +} +#endif diff --git a/examples/echo/echo.c b/examples/echo/echo.c index 058c3cc..ee21360 100644 --- a/examples/echo/echo.c +++ b/examples/echo/echo.c @@ -19,8 +19,13 @@ #include #include #include +#ifdef WS_STATICS +#define WS_STATICS_DATA_IMPLEMENTATION +#include "echo-static.h" +#endif #include + /** * @dir examples/ * @brief wsServer examples folder @@ -116,6 +121,11 @@ void onmessage(ws_cli_conn_t client, */ int main(void) { +#ifdef WS_STATICS + initEmbeddedAssets(); + sprintf(static_root_alias,"%s","/echo.html"); + +#endif ws_socket(&(struct ws_server){ /* * Bind host: diff --git a/extra/dir2statics/dir2statics.c b/extra/dir2statics/dir2statics.c new file mode 100644 index 0000000..1bbc395 --- /dev/null +++ b/extra/dir2statics/dir2statics.c @@ -0,0 +1,461 @@ +/* + * dir2statics.c + * + * Generate a C header containing embedded static files for wsServer statics.h. + * + * Usage: + * dir2statics [url_prefix] + * + * Example: + * dir2statics ./www ./generated_statics.h + * dir2statics ./www ./generated_statics.h / + * + * Integration: + * // in exactly ONE .c file: + * #define WS_STATICS_DATA_IMPLEMENTATION + * #include "generated_statics.h" + * + * // everywhere else: + * #include "generated_statics.h" + * + * Notes: + * - Non-recursive (top-level only) + * - URLs are url_prefix + filename (default "/") + * - Skips directories and non-regular files + */ + +#define _POSIX_C_SOURCE 200809L + + +#define GZIP + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#ifdef GZIP +#include +#define BUFFER_SIZE 8192 // Max line length or buffer size +#endif + +/* ---------- small helpers ---------- */ + +static void die(const char *msg) +{ + perror(msg); + exit(1); +} + +static int is_regular_file(const char *dir, const char *name) +{ + char path[4096]; + struct stat st; + int n = snprintf(path, sizeof(path), "%s/%s", dir, name); + if (n < 0 || (size_t)n >= sizeof(path)) + return 0; + if (stat(path, &st) != 0) + return 0; + return S_ISREG(st.st_mode) ? 1 : 0; +} + +static char *xstrdup(const char *s) +{ + size_t n = strlen(s); + char *p = (char *)malloc(n + 1); + if (!p) die("malloc"); + memcpy(p, s, n + 1); + return p; +} + +static int cmp_strptr(const void *a, const void *b) +{ + const char *sa = *(const char * const *)a; + const char *sb = *(const char * const *)b; + return strcmp(sa, sb); +} + +static const char *content_type_for_filename(const char *name) +{ + const char *dot = strrchr(name, '.'); + if (!dot || dot == name) + return 0; // only accept file types in the list below.... + + /* lower-case compare without allocating */ + if (!strcasecmp(dot, ".html") || !strcasecmp(dot, ".htm")) + return "text/html; charset=utf-8"; + if (!strcasecmp(dot, ".js")) + return "text/javascript; charset=utf-8"; + if (!strcasecmp(dot, ".css")) + return "text/css; charset=utf-8"; + if (!strcasecmp(dot, ".json")) + return "application/json; charset=utf-8"; + if (!strcasecmp(dot, ".txt")) + return "text/plain; charset=utf-8"; + if (!strcasecmp(dot, ".svg")) + return "image/svg+xml"; + if (!strcasecmp(dot, ".png")) + return "image/png"; + if (!strcasecmp(dot, ".jpg") || !strcasecmp(dot, ".jpeg")) + return "image/jpeg"; + if (!strcasecmp(dot, ".gif")) + return "image/gif"; + if (!strcasecmp(dot, ".ico")) + return "image/x-icon"; + if (!strcasecmp(dot, ".wasm")) + return "application/wasm"; + if (!strcasecmp(dot, ".woff")) + return "font/woff"; + if (!strcasecmp(dot, ".woff2")) + return "font/woff2"; + + return 0; // we don't accept this file type +} + +/* Turn a filename into a safe C identifier suffix. */ +static void ident_from_filename(const char *name, char *out, size_t out_sz) +{ + size_t j = 0; + for (size_t i = 0; name[i] && j + 2 < out_sz; i++) + { + unsigned char c = (unsigned char)name[i]; + if (isalnum(c)) + out[j++] = (char)c; + else + out[j++] = '_'; + } + out[j] = '\0'; + + if (j == 0) + { + strncpy(out, "file", out_sz); + out[out_sz - 1] = '\0'; + } +} + +/* Read whole file into memory */ +static uint8_t *read_file(const char *path, uint32_t *out_size) +{ + FILE *f = fopen(path, "rb"); + if (!f) return NULL; + + if (fseek(f, 0, SEEK_END) != 0) { fclose(f); return NULL; } + long sz = ftell(f); + if (sz < 0) { fclose(f); return NULL; } + if (fseek(f, 0, SEEK_SET) != 0) { fclose(f); return NULL; } + + uint8_t *buf = (uint8_t *)malloc((size_t)sz ? (size_t)sz : 1); + if (!buf) { fclose(f); return NULL; } + + size_t got = fread(buf, 1, (size_t)sz, f); + fclose(f); + if (got != (size_t)sz) + { + free(buf); + return NULL; + } + + *out_size = (uint32_t)sz; + return buf; +} + + +/* Emit bytes as hex array */ +static size_t emit_u8_array(FILE *out, const char *ident, const uint8_t *data, uint32_t size) +{ + fprintf(out, "static const uint8_t %s[%u] = {", ident, size); + for (uint32_t i = 0; i < size; i++) + { + if (i % 16 == 0) fprintf(out, "\n/* %04X*/",i); + fprintf(out, "0x%02x", (unsigned)data[i]); + if (i + 1 != size) fprintf(out, ", "); + } + if (size == 0) fprintf(out, "\n /* empty */"); + fprintf(out, "\n};\n\n"); + return size; +} + +#ifdef GZIP +/* Emit bytes as gzipped hex array */ +static size_t emit_u8_gz_array(FILE *out, const char *ident, const uint8_t *data, uint32_t size) +{ + z_stream strm = {0}; + + // 15 is the default window size; +16 enables GZIP format + if (deflateInit2(&strm, Z_DEFAULT_COMPRESSION, Z_DEFLATED, + 15 + 16, 8, Z_DEFAULT_STRATEGY) != Z_OK) { + return 0; + } + + size_t compressed_size = deflateBound(&strm, size); + + unsigned char *compressed_data = malloc (compressed_size); + if (!compressed_data) die("malloc"); + + + strm.next_in = (Bytef *)data; + strm.avail_in = (uInt) size; + strm.next_out = (Bytef *)compressed_data; + strm.avail_out = (uInt) compressed_size; + + + // Use Z_FINISH to complete compression in one step if the buffer is large enough + int ret = deflate(&strm, Z_FINISH); + + if (ret == Z_STREAM_END) { + compressed_size = strm.total_out; // Actual size of compressed data + deflateEnd(&strm); + emit_u8_array(out,ident, compressed_data, compressed_size); + free(compressed_data); + return compressed_size; + } + deflateEnd(&strm); + free(compressed_data); + return 0 ; +} +#endif + +/* ---------- main ---------- */ + +int main(int argc, char **argv) +{ + if (argc < 3) + { + fprintf(stderr, + "Usage: %s [url_prefix]\n", + argv[0]); + return 2; + } + + SHA1Context ctx; /* SHA-1 Context. */ + + const char hexEncode[16] = "0123456789ABCDEF"; + struct { + char Etag_Colon_Quote[8]; // 'Etag: \"' + char hexhash[SHA1HashSize]; // first 20 hex bytes + unsigned char hash[SHA1HashSize]; // second 20 hex bytes, and binary hash temp + char Quote_crlf[6]; // '\"\r\n" +#ifdef GZIP // '12345678901234567890123 4 + char ContentEncoding[26]; // 'Content-Encoding: gzip\r\n' +#endif + char contentTypeTag[14]; // 'Content-Type: ' + char contentType[64]; // + } CustomHeader, *cHdr; + + char * c; + c = &CustomHeader.Etag_Colon_Quote[0]; sprintf(c,"Etag: %c",'\\');c[7]='"'; + c = &CustomHeader.Quote_crlf[0];sprintf(c,"%c%c%cr%c",'\\','"','\\','\\');c[5]='n'; +#ifdef GZIP + c = &CustomHeader.ContentEncoding[0];sprintf(c,"Content-Encoding: %s\\r\\","gzip");c[25]='n'; +#endif + c = &CustomHeader.contentTypeTag[0];sprintf(c,"%s","Content-Type:");c[13]=' '; + + + + + const char *in_dir = argv[1]; + const char *out_path = argv[2]; + const char *url_prefix = (argc >= 4) ? argv[3] : "/"; + + /* normalize url_prefix: if empty, treat as "/" */ + if (!url_prefix || !*url_prefix) url_prefix = "/"; + + /* collect filenames */ + DIR *d = opendir(in_dir); + if (!d) die("opendir"); + + size_t cap = 32, count = 0; + char **names = (char **)malloc(cap * sizeof(char *)); + if (!names) die("malloc"); + + cHdr = malloc(cap * sizeof(CustomHeader)); + if (!cHdr) die("malloc"); + + + struct dirent *de; + while ((de = readdir(d)) != NULL) + { + const char *nm = de->d_name; + if (!strcmp(nm, ".") || !strcmp(nm, "..")) continue; + + if (!content_type_for_filename(nm)) continue; + + if (!is_regular_file(in_dir, nm)) + continue; + + if (count == cap) + { + cap *= 2; + char **tmp = (char **)realloc(names, cap * sizeof(char *)); + if (!tmp) die("realloc"); + names = tmp; + + tmp = (char **)realloc(cHdr, cap * sizeof(CustomHeader)); + if (!tmp) die("realloc"); + cHdr = (void*) tmp; + } + names[count++] = xstrdup(nm); + } + closedir(d); + + qsort(names, count, sizeof(char *), cmp_strptr); + + FILE *out = fopen(out_path, "w"); + if (!out) die("fopen(output)"); + + /* header prelude */ + fprintf(out, + "#pragma once\n" + "#include \n" + "\n" + "#ifdef __cplusplus\n" + "extern \"C\" {\n" + "#endif\n" + "\n" + "/* Declarations compatible with wsServer statics.h expectations */\n" + "void initEmbeddedAssets(void);" + + "typedef struct ws_static_asset_set {\n" + " uint32_t count;\n" + " const char * const *urls;\n" + " const char * const *contentType;\n" + " const uint8_t * const *content;\n" + " const uint32_t *sizes;\n" + "} ws_static_asset_set_t;\n\n" + "extern char static_root_alias[32];\n\n" + + + "void ws_set_static_assets(const ws_static_asset_set_t *set);\n" + "\n" + "#ifdef WS_STATICS_DATA_IMPLEMENTATION\n" + "\n"); + + /* emit tables */ + fprintf(out, "const uint32_t static_count = %u;\n\n", (unsigned)count); + + fprintf(out, "const char *static_urls[%u] = {\n", (unsigned)count); + for (size_t i = 0; i < count; i++) + fprintf(out, " \"%s%s\"%s\n", url_prefix, names[i], (i + 1 == count) ? "" : ","); + fprintf(out, "};\n\n"); + + + + /* emit embedded file arrays */ + for (size_t i = 0; i < count; i++) + { + char ident[512]; + char path[4096]; + uint32_t sz = 0; + + ident_from_filename(names[i], ident, sizeof(ident)); + + int n = snprintf(path, sizeof(path), "%s/%s", in_dir, names[i]); + if (n < 0 || (size_t)n >= sizeof(path)) + { + fprintf(stderr, "Path too long, skipping: %s\n", names[i]); + continue; + } + + uint8_t *data = read_file(path, &sz); + if (!data) + { + fprintf(stderr, "Failed to read: %s\n", path); + continue; + } + + + + SHA1Reset(&ctx); + SHA1Input(&ctx, data, sz); + SHA1Result(&ctx,CustomHeader.hash); + char * hexOut = &CustomHeader.hexhash[0]; + unsigned char *decIn = &CustomHeader.hash[0]; + for (int x = 0; x < SHA1HashSize; x ++ , decIn++) { + *(hexOut++) = hexEncode[ *decIn >> 4 ]; + *(hexOut++) = hexEncode[ *decIn & 0xf ]; + } + snprintf(CustomHeader.contentType,sizeof (CustomHeader.contentType),"%s\\r\\n", content_type_for_filename(names[i])); + + // copy the entire struct (maps to null termed string) + memmove( &cHdr[i], &CustomHeader, sizeof(CustomHeader)); + + char arrname[600]; + snprintf(arrname, sizeof(arrname), "ws_static_%s", ident); +#ifdef GZIP + emit_u8_gz_array(out, arrname, data, sz); +#else + emit_u8_array(out, arrname, data, sz); +#endif + + + + free(data); + + } + + fprintf(out, "const char *static_contentType[%u] = {\n", (unsigned)count); + for (size_t i = 0; i < count; i++) { + fprintf(out, " // %s\n", names[i]); + fprintf(out, " \"%s\"%s\n", (char *) &cHdr[i], (i + 1 == count) ? "" : ","); + } + fprintf(out, "};\n\n"); + + + + fprintf(out, "const uint8_t *static_content[%u] = {\n", (unsigned)count); + for (size_t i = 0; i < count; i++) + { + char ident[512]; + char arrname[600]; + ident_from_filename(names[i], ident, sizeof(ident)); + snprintf(arrname, sizeof(arrname), "ws_static_%s", ident); + fprintf(out, " %s%s\n", arrname, (i + 1 == count) ? "" : ","); + } + fprintf(out, "};\n\n"); + + fprintf(out, "const uint32_t static_content_size[%u] = {\n", (unsigned)count); + for (size_t i = 0; i < count; i++) + { + /* We can safely use sizeof() because the arrays are in this header under IMPLEMENTATION. */ + char ident[512]; + char arrname[600]; + ident_from_filename(names[i], ident, sizeof(ident)); + snprintf(arrname, sizeof(arrname), "ws_static_%s", ident); + fprintf(out, " (uint32_t)sizeof(%s)%s\n", arrname, (i + 1 == count) ? "" : ","); + } + fprintf(out, "};\n\n"); + + fprintf(out, + + + "static const ws_static_asset_set_t embedded_assets = {\n" + " static_count, static_urls, static_contentType, static_content, static_content_size\n" + "};\n\n" + "\n" + "void initEmbeddedAssets(void){\n" + " ws_set_static_assets(&embedded_assets);\n" + "\n" + "}\n\n" + "#endif /* WS_STATICS_DATA_IMPLEMENTATION */\n" + "\n" + "#ifdef __cplusplus\n" + "}\n" + "#endif\n"); + + fclose(out); + + for (size_t i = 0; i < count; i++) free(names[i]); + free(names); + + free(cHdr); + + return 0; +} diff --git a/extra/toyws/toyws.c b/extra/toyws/toyws.c index bb91e92..b4a2aa6 100644 --- a/extra/toyws/toyws.c +++ b/extra/toyws/toyws.c @@ -397,7 +397,7 @@ int tws_receiveframe(struct tws_ctx *ctx, char **buff, { cur_byte = next_byte(ctx, &ret); if (cur_byte < 0) - return (ret == 0 ? frame_length : ret); + return (ret == 0 ? (int) frame_length : ret); *buf = cur_byte; } @@ -406,5 +406,5 @@ int tws_receiveframe(struct tws_ctx *ctx, char **buff, /* Fill other infos. */ *frm_type = opcode; - return (ret == 0 ? frame_length : ret); + return (ret == 0 ? (int) frame_length : ret); } diff --git a/include/statics.h b/include/statics.h new file mode 100644 index 0000000..cc4d930 --- /dev/null +++ b/include/statics.h @@ -0,0 +1,413 @@ +#pragma once + +#define _GNU_SOURCE // Required for strcasestr on some Linux systems +#include +// belt and suspenders: strcasestr looks like this +extern char *strcasestr(const char *haystack, const char *needle); +// rename contentType for code readability: full header lines can be used to add more context +#define CUSTOM_HEADERS contentType +#include +#include +#include + +#if WS_STATICS + #pragma message("wsServer: WS_STATICS enabled (static HTTP assets compiled in)") +#endif + +typedef struct ws_static_asset_set { + uint32_t count; + const char * const *urls; + const char * const *contentType; + const uint8_t * const *content; + const uint32_t *sizes; +} ws_static_asset_set_t; + +const char default_static_html_text[] = + "WS STATIC OK" + "Success
" + "Built: " __DATE__ " " __TIME__ + ""; +static const char * const urls[] = { "/" }; +static const char * const types[] = { "text/html; charset=utf-8" }; +static const uint8_t * const bodies[] = { (const uint8_t*)default_static_html_text }; +static const uint32_t sizes[] = { (uint32_t)(sizeof(default_static_html_text)-1) }; + +static const ws_static_asset_set_t default_set = { + 1, urls, types, bodies, sizes +}; +char static_root_alias[32]="/index.html"; + +ws_static_asset_set_t *g_assets = ( ws_static_asset_set_t *) &default_set; + +void ws_set_static_assets(const ws_static_asset_set_t *set) { + g_assets = ( ws_static_asset_set_t *) set; +} + +const ws_static_asset_set_t *ws_get_static_assets(void) { + if (g_assets) { + return g_assets; + } + ws_set_static_assets(&default_set); + + return &default_set; +} + + + + +static const char *strstricase_local(const char *haystack, const char *needle) +{ + size_t nlen = strlen(needle); + if (!nlen) return haystack; + + for (; *haystack; haystack++) + { + size_t i; + for (i = 0; i < nlen; i++) + { + unsigned char a = (unsigned char)haystack[i]; + unsigned char b = (unsigned char)needle[i]; + if (!a) return NULL; + if (tolower(a) != tolower(b)) break; + } + if (i == nlen) return haystack; + } + return NULL; +} + +/* Match the library’s own handshake expectation: WS_HS_REQ is "Sec-WebSocket-Key". */ +static int looks_like_ws_upgrade(const char *req) +{ + return strstricase_local(req, WS_HS_REQ) != NULL; +} + +static const char *http_reason_phrase(int code) +{ + switch (code) + { + case 200: return "OK"; + case 400: return "Bad Request"; + case 404: return "Not Found"; + case 405: return "Method Not Allowed"; + default: return "OK"; + } +} +bool ends_with(const char *str, const char *suffix); +char* get_content_between(char *buffer, const char *start_term, const char *end_term, size_t *foundSize); +extern char *strstr(const char *haystack, const char *needle); + +static int http_send_response(struct ws_connection *client, + int code, const char *content_type, const uint8_t *body, uint32_t body_len) +{ + char hdr[512]; + char customHdrs[512]; + int hdr_len; + + if (!content_type) content_type = "text/plain; charset=utf-8"; + + if (content_type) { + if ( ends_with (content_type,"\r\n") ){ + size_t etagSize = 0; + if ( get_content_between((char *) content_type,"etag:","\r",&etagSize)) { + + snprintf(customHdrs,sizeof customHdrs,"%s",content_type); + } else { + if ( get_content_between((char *) content_type,"Cache-Control:","\r",&etagSize)) { + snprintf(customHdrs,sizeof customHdrs,"%s",content_type); + } else { + snprintf(customHdrs,sizeof customHdrs,"Cache-Control: no-cache\r\n%s",content_type); + } + + } + + } else { + // to supply more than just content_type, supply the full header lines, separated by \r\n, ending in \r\n + snprintf(customHdrs,sizeof(customHdrs),"Cache-Control: no-cache\r\nContent-Type: %s\r\n",content_type); + } + } + + + hdr_len = snprintf(hdr, sizeof(hdr), + "HTTP/1.1 %d %s\r\n" + "Connection: close\r\n" + "%s" + "Content-Length: %" PRIu32 "\r\n" + "\r\n", + code, http_reason_phrase(code), customHdrs, body_len); + + if (hdr_len < 0 || (size_t)hdr_len >= sizeof(hdr)) + return -1; + + if (SEND(client, hdr, (size_t)hdr_len) < 0) + return -1; + + if (body && body_len) + if (SEND(client, body, body_len) < 0) + return -1; + + return 0; +} + +static int http_parse_request_line(char *buf, char **out_method, char **out_path, char **headers) +{ + char *sp1 = strchr(buf, ' '); + if (!sp1) return -1; + *sp1 = '\0'; + + char *sp2 = strchr(sp1 + 1, ' '); + if (!sp2) return -1; + *sp2 = '\0'; + + char *sp3; + if (headers) { + sp3 = strchr(sp2 + 1, '\n'); + if (!sp3) return -1; + } + + *out_method = buf; + *out_path = sp1 + 1; + if (headers) { + *headers = sp3 + 1; + } + + return 0; +} + + + +char* get_content_between(char *buffer, const char *start_term, const char *end_term, size_t *foundSize) { + // 1. Find the first occurrence of start_term + char *start_ptr = strcasestr(buffer, start_term); + if (!start_ptr) return NULL; + + // 2. Move pointer to the first character AFTER the start_term + char *content_start = start_ptr + strlen(start_term); + + // 3. Find the first occurrence of end_term starting AFTER the start_term + char *end_ptr = strcasestr(content_start, end_term); + if (!end_ptr) return NULL; + + // 4. Null-terminate the buffer at the start of the end_term + if (foundSize) { + *foundSize = (size_t) end_ptr - (size_t) content_start; + } else { + *end_ptr = '\0'; + } + + return content_start; +} + + +bool ends_with(const char *str, const char *suffix) { + if (!str || !suffix) { + return false; // Handle potential null pointers, though standard C functions assume non-null + } + size_t str_len = strlen(str); + size_t suffix_len = strlen(suffix); + + if (suffix_len > str_len) { + return false; // A string cannot end with a suffix longer than itself + } + + // Compare the last 'suffix_len' characters of 'str' with 'suffix' + // memcmp is efficient for comparing blocks of memory + return memcmp(str + str_len - suffix_len, suffix, suffix_len) == 0; +} + +bool found_root_alias = false; +static void find_root_alias() { + if (found_root_alias) return; + + found_root_alias = true; // only call this function once, on first server "hit" + const char * index_html = "/index.html"; + const char * dot_html = ".html"; + const char * classic_root = "/"; + if (strcmp(static_root_alias,index_html)!=0) return;// if it has already been set to something, abort. + + // scan the files. if we find /index.html or html, use that entry. + // otherwise count the .html files. if there is exactly 1, use that + // otherwise just let it fall to a 404. + + // *note* the default data url is "/" so this loop will always find that. + const ws_static_asset_set_t *a = ws_get_static_assets(); + + uint32_t html_count = 0; + uint32_t i, gotIt=0; + for (i = 0; i < a->count; i++) { + if (a->urls[i]) { + if ( (strcmp(a->urls[i], index_html) == 0)|| + (strcmp(a->urls[i], classic_root) == 0) + ) { + snprintf (static_root_alias,sizeof (static_root_alias),"%s",a->urls[i]); + printf ("Will use [%s] for default root /\n",static_root_alias ); + + return; + } + if (ends_with(a->urls[i],dot_html)) { + gotIt = i; + html_count++; + } + } + } + + if ( html_count==1 ) { + snprintf (static_root_alias,sizeof (static_root_alias), "%s",a->urls[gotIt]); + printf ("Will use [%s] for default root /\n",static_root_alias ); + } +} + + +static int static_find_path(const char *path) +{ + if (!found_root_alias)find_root_alias(); + + char tmp[512]; + const char *q; + size_t n; + uint32_t i; + + if (!path || !*path) path = "/"; + + if (path[0]=='/' && path[1]==0) { + path = static_root_alias; + } + + q = strchr(path, '?'); + n = q ? (size_t)(q - path) : strlen(path); + if (n >= sizeof(tmp)) n = sizeof(tmp) - 1; + memcpy(tmp, path, n); + tmp[n] = '\0'; + + const ws_static_asset_set_t *a = ws_get_static_assets(); + + + for (i = 0; i < a->count; i++) + if (a->urls[i] && strcmp(a->urls[i], tmp) == 0) + return (int)i; + + return -1; +} + +static void serve_static_http(struct ws_frame_data *wfd) +{ + /* Must have full headers */ + if (!strstr((char *)wfd->frm, "\r\n\r\n")) + { + static const uint8_t msg[] = "Bad Request\n"; + http_send_response(wfd->client, 400, NULL, msg, (uint32_t)sizeof(msg) - 1); + return; + } + + char *method = NULL; + char *path = NULL; + char *headers = NULL; + + /* Parse request line in-place (fine: we’re about to close anyway) */ + if (http_parse_request_line((char *)wfd->frm, &method, &path, &headers) < 0) + { + static const uint8_t msg[] = "Bad Request\n"; + http_send_response(wfd->client, 400, NULL, msg, (uint32_t)sizeof(msg) - 1); + return; + } + + if (strcmp(method, "GET") != 0 && strcmp(method, "HEAD") != 0) + { + static const uint8_t msg[] = "Method Not Allowed\n"; + http_send_response(wfd->client, 405, NULL, msg, (uint32_t)sizeof(msg) - 1); + return; + } + const ws_static_asset_set_t *a = ws_get_static_assets(); + int idx = static_find_path(path); + if (idx < 0) + { + static const uint8_t msg[] = "Not Found\n"; + http_send_response(wfd->client, 404, NULL, msg, (uint32_t)sizeof(msg) - 1); + return; + } + + size_t etagSize = 0; + // do we normally send an etag for this item? if so it will be in our custom headers field + char *etag = get_content_between((char *) a->CUSTOM_HEADERS[idx],"etag: \"","\"",&etagSize); + if (etag && etagSize == 40) { + // make a searchable etag from the etag in the headers we normally send out + char findEtag[41]; memcpy(findEtag,etag,40); findEtag[40]=0; + size_t gap = 0; + const char * ifnonematch = get_content_between(headers,"if-none-match:",findEtag,&gap); + if (ifnonematch && gap < 5) { + http_send_response(wfd->client, 304, a->CUSTOM_HEADERS[idx], NULL, a->sizes[idx]); + return; + } + } + + + if (strcmp(method, "HEAD") == 0) + { + http_send_response(wfd->client, 200, a->CUSTOM_HEADERS[idx], NULL, a->sizes[idx]); + return; + } + + http_send_response(wfd->client, 200, a->CUSTOM_HEADERS[idx],a->content[idx],a->sizes[idx]); +} + + +static int do_handshake(struct ws_frame_data *wfd) +{ + char *response; + char *p; + ssize_t n; + + if ((n = RECV(wfd->client, wfd->frm, sizeof(wfd->frm) - 1)) < 0) + return (-1); + + wfd->frm[n] = '\0'; + wfd->amt_read = (size_t)n; + + + p = strstr((const char *)wfd->frm, "\r\n\r\n"); + if (p == NULL) + { + /* Could also just return -1, but a 400 is nicer */ + static const uint8_t msg[] = "Bad Request\n"; + http_send_response(wfd->client, 400, NULL, msg, (uint32_t)sizeof(msg) - 1); + return (-1); + } + + /* If it doesn’t even look like WS (per WS_HS_REQ), serve static and stop. */ + if (!looks_like_ws_upgrade((const char *)wfd->frm)) + { + serve_static_http(wfd); + return (-1); /* ws_establishconnection will proceed to close */ + } + + /* WS attempt: copy because get_handshake_response() mutates input via strtok_r */ + char *req_copy = malloc((size_t)n + 1); + if (!req_copy) + return (-1); + memcpy(req_copy, wfd->frm, (size_t)n + 1); + + /* Keep original buffer intact for next_byte() continuation */ + wfd->cur_pos = (size_t)((ptrdiff_t)(p - (char *)wfd->frm)) + 4; + + if (get_handshake_response(req_copy, &response) < 0) + { + free(req_copy); + /* Malformed WS attempt */ + static const uint8_t msg[] = "Bad WebSocket handshake\n"; + http_send_response(wfd->client, 400, NULL, msg, (uint32_t)sizeof(msg) - 1); + return (-1); + } + free(req_copy); + + if (SEND(wfd->client, response, strlen(response)) < 0) + { + free(response); + return (-1); + } + + set_client_state(wfd->client, WS_STATE_OPEN); + wfd->client->ws_srv.evs.onopen(wfd->client->client_id); + free(response); + return (0); +} + +#undef CUSTOM_HEADERS diff --git a/meow.jpg b/meow.jpg new file mode 100644 index 0000000..c8857d8 Binary files /dev/null and b/meow.jpg differ diff --git a/now-with-assets.png b/now-with-assets.png new file mode 100644 index 0000000..d20664c Binary files /dev/null and b/now-with-assets.png differ diff --git a/now-with-assets1.png b/now-with-assets1.png new file mode 100644 index 0000000..c8889c9 Binary files /dev/null and b/now-with-assets1.png differ diff --git a/now-with-assets2.png b/now-with-assets2.png new file mode 100644 index 0000000..69c1ce4 Binary files /dev/null and b/now-with-assets2.png differ diff --git a/src/ws.c b/src/ws.c index 7ee76a5..14445ff 100644 --- a/src/ws.c +++ b/src/ws.c @@ -1010,6 +1010,10 @@ static inline int is_valid_frame(int opcode) ); } +#ifdef WS_STATICS +#include +#else + /** * @brief Do the handshake process. * @@ -1070,6 +1074,7 @@ static int do_handshake(struct ws_frame_data *wfd) free(response); return (0); } +#endif /** * @brief Sends a close frame, accordingly with the @p close_code @@ -2098,4 +2103,4 @@ int ws_file(struct ws_events *evs, const char *file) ws_establishconnection(&client_socks[0]); return (0); } -#endif +#endif \ No newline at end of file