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 @@
[](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