19#define ASSET_HASH_HEX 16
29 struct cwist_asset *next;
34 _Atomic(
struct cwist_asset *)
head;
45static char *join3(
const char *
a,
const char *b,
const char *
c) {
48 if (!out)
return NULL;
56static char *normalize_prefix(
const char *url_prefix) {
57 if (!url_prefix || url_prefix[0] !=
'/')
return NULL;
58 size_t len =
strlen(url_prefix);
59 while (len > 0 && url_prefix[len - 1] ==
'/') len--;
61 for (
size_t i = 0;
i < len;
i++) {
62 unsigned char c = (
unsigned char)url_prefix[
i];
63 if (
c <= 0x20 ||
c == 0x7f ||
c ==
'?' ||
c ==
'#' ||
c ==
'%')
return NULL;
67 if (!out)
return NULL;
69 memcpy(out, url_prefix, len);
75static bool name_is_valid(
const char *name) {
76 if (!name || !*name || name[0] ==
'/')
return false;
77 const char *
seg = name;
78 for (
const char *
p = name;;
p++) {
80 if (
c ==
'/' ||
c ==
'\0') {
83 if (
seg_len == 1 &&
seg[0] ==
'.')
return false;
84 if (
seg_len == 2 &&
seg[0] ==
'.' &&
seg[1] ==
'.')
return false;
85 if (
c ==
'\0')
return true;
89 bool ok = (
c >=
'a' &&
c <=
'z')
90 || (
c >=
'A' &&
c <=
'Z')
91 || (
c >=
'0' &&
c <=
'9')
92 ||
c ==
'.' ||
c ==
'-'
93 ||
c ==
'_' ||
c ==
'~';
94 if (!ok)
return false;
99static size_t extension_offset(
const char *name) {
100 const char *base =
strrchr(name,
'/');
101 base = base ? base + 1 : name;
105 return (
size_t)(
dot - name);
109static const char *guess_content_type(
const char *name) {
110 static const struct {
114 {
".css",
"text/css; charset=utf-8"},
115 {
".js",
"text/javascript; charset=utf-8"},
116 {
".mjs",
"text/javascript; charset=utf-8"},
117 {
".json",
"application/json"},
118 {
".map",
"application/json"},
119 {
".html",
"text/html; charset=utf-8"},
120 {
".htm",
"text/html; charset=utf-8"},
121 {
".txt",
"text/plain; charset=utf-8"},
122 {
".svg",
"image/svg+xml"},
123 {
".png",
"image/png"},
124 {
".jpg",
"image/jpeg"},
125 {
".jpeg",
"image/jpeg"},
126 {
".gif",
"image/gif"},
127 {
".webp",
"image/webp"},
128 {
".ico",
"image/x-icon"},
129 {
".woff",
"font/woff"},
130 {
".woff2",
"font/woff2"},
131 {
".wasm",
"application/wasm"},
133 const char *ext = name + extension_offset(name);
134 for (
size_t i = 0;
i <
sizeof(
table) /
sizeof(
table[0]);
i++) {
135 const char *
a = ext, *b =
table[
i].ext;
136 while (*
a && *b && (*
a | 0x20) == *b) {
140 if (*
a ==
'\0' && *b ==
'\0')
return table[
i].type;
142 return "application/octet-stream";
146static void content_hash(
const void *data,
size_t len,
char out[
ASSET_HASH_HEX + 1]) {
147 static const char hex[] =
"0123456789abcdef";
150 cwist_sha256_init(&ctx);
151 if (len > 0) cwist_sha256_update(&ctx, (
const uint8_t *)data, len);
152 cwist_sha256_final(&ctx,
digest);
161static void asset_free(
struct cwist_asset *asset) {
198 if (!app)
return asset_result(-1);
199 char *prefix = normalize_prefix(url_prefix);
200 if (!prefix)
return asset_result(-1);
204 return asset_result(-1);
207 reg->prefix = prefix;
208 return asset_result(0);
212static struct cwist_asset *asset_build(
const cwist_asset_registry *
reg,
const char *name,
213 unsigned char *data,
size_t len,
const char *content_type,
215 struct cwist_asset *asset = (
struct cwist_asset *)
cwist_alloc(
sizeof(*asset));
223 size_t ext = extension_offset(name);
235 asset->url = asset->hashed_name ? join3(
reg->prefix,
"/", asset->hashed_name) :
NULL;
236 asset->content_type =
cwist_strdup(content_type ? content_type : guess_content_type(name));
237 snprintf(asset->etag,
sizeof(asset->etag),
"\"%s\"",
hash);
239 if (!asset->name || !asset->hashed_name || !asset->url || !asset->content_type) {
247static bool header_value_is_valid(
const char *value) {
248 for (
const unsigned char *
p = (
const unsigned char *)value; *
p;
p++) {
249 if (*
p < 0x20 || *
p == 0x7f)
return false;
256 size_t len,
const char *content_type) {
260 return asset_result(-1);
264 content_hash(data, len,
hash);
269 const char *type = content_type ? content_type : guess_content_type(name);
272 if (
strcmp(
a->name, name) != 0)
continue;
274 (len == 0 ||
memcmp(
a->data, data, len) == 0) &&
strcmp(
a->content_type, type) == 0) {
276 return asset_result(0);
281 struct cwist_asset *asset = asset_build(
reg, name, data, len, content_type,
hash);
282 if (!asset)
return asset_result(-1);
289 return asset_result(0);
307 const char *content_type) {
308 if (!app || !name_is_valid(name)
309 || (!data && len > 0))
310 return asset_result(-1);
313 && (!*content_type || !header_value_is_valid(content_type))) {
314 return asset_result(-1);
316 unsigned char *copy = (
unsigned char *)
cwist_alloc(len ? len : 1);
318 if (!copy)
return asset_result(-1);
319 if (len > 0)
memcpy(copy, data, len);
320 return asset_publish(app, name, copy, len, content_type);
337 const char *content_type) {
338 if (!app || !name_is_valid(name) || !path)
return asset_result(-1);
339 if (content_type && (!*content_type || !header_value_is_valid(content_type))) {
340 return asset_result(-1);
344 if (!
f)
return asset_result(-1);
346 size_t cap = 4096, len = 0;
348 bool ok = buf !=
NULL;
371 return asset_result(-1);
374 return asset_publish(app, name, buf, len, content_type);
387 if (!app || !name)
return NULL;
394 if (
strcmp(
a->name, name) == 0)
return a->url;
439static bool etag_matches(
const char *
if_none_match,
const char *etag) {
443 while (*
p ==
' ' || *
p ==
'\t' || *
p ==
',')
p++;
446 while (*
p && *
p !=
',')
p++;
457static bool assets_add_header(
cwist_http_response *res,
const char *name,
const char *value) {
459 bool ok = cwist_error_is_ok(&
err);
477 if (!req || !res)
return;
482 const struct cwist_asset *asset =
match->asset;
487 bool ok = assets_add_header(res,
"ETag", asset->etag) && assets_add_header(res,
"Cache-Control", cache);
488 if (ok && !
not_modified) ok = assets_add_header(res,
"Content-Type", asset->content_type);
533 for (
struct cwist_asset *
a =
535 a && ok;
a =
a->next) {
536 unsigned char *data = (
unsigned char *)
cwist_alloc(
a->len ?
a->len : 1);
542 if (
a->len)
memcpy(data,
a->data,
a->len);
547 struct cwist_asset *copy = asset_build(
reg,
a->name, data,
a->len,
a->content_type,
hash);
576 struct cwist_asset *next =
a->next;
Memory allocation wrappers backed by libttak.
void cwist_free(void *ptr)
Free memory obtained via cwist_alloc/cwist_strdup/etc.
void * cwist_realloc(void *ptr, size_t new_size)
Resize an existing allocation (libttak tracked).
char * cwist_strdup(const char *src)
Duplicate a string using libttak-backed storage.
void * cwist_alloc(size_t size)
Allocate zeroed memory tracked by libttak.
void cwist_assets_destroy(void *registry)
const char * cwist_app_asset_url(cwist_app *app, const char *name)
Return the URL of the newest asset registered under name.
void cwist_assets_respond(cwist_http_request *req, cwist_http_response *res, const cwist_asset_match *match)
Fill a response for a matched asset.
cwist_error_t cwist_app_asset_add_file(cwist_app *app, const char *name, const char *path, const char *content_type)
Read a file from disk and publish it as an asset.
cwist_error_t cwist_app_asset_prefix(cwist_app *app, const char *url_prefix)
Set the URL prefix under which assets are served.
struct cwist_asset_registry cwist_asset_registry
int cwist_assets_clone(void **dst, const void *src)
Deep-copy a registry (used when an app is cloned).
bool cwist_assets_match(cwist_app *app, const cwist_http_request *req, cwist_asset_match *out)
Match an HTTP request to a registered asset.
cwist_error_t cwist_app_asset_add(cwist_app *app, const char *name, const void *data, size_t len, const char *content_type)
Publish an in-memory buffer as an asset.
Content-hashed, in-memory static assets served by a cwist_app.
#define CWIST_ASSET_DEFAULT_PREFIX
#define CWIST_ASSET_IMMUTABLE_CACHE
Private wiring between app.c and the asset registry (assets.c).
cwist_error_t cwist_sstring_assign(cwist_sstring *str, const char *data)
Assign a C string.
@ CWIST_ERR_INT16
used when checking common errcodes in Unix/Linux
cwist_error_t make_error(cwist_errtype_t type)
Creates an error object initialized with the specified type.
char * cwist_http_header_get(cwist_http_header_node *head, const char *key)
Find a header value by key.
@ CWIST_HTTP_INTERNAL_ERROR
cwist_error_t cwist_http_header_add(cwist_http_header_node **head, const char *key, const char *value)
Add a header to the list.
void cwist_http_response_set_body_ptr(cwist_http_response *res, const void *ptr, size_t len)
Sets a direct pointer for the response body (Zero Copy). Use this when serving large files from memor...
@ CWIST_HTTP_NOT_MODIFIED
Minimal self-contained SHA-256 and HMAC-SHA256 (RFC 4634 style).
#define CWIST_SHA256_DIGEST_LEN
Main Application Context.
void * assets
Content-hashed in-memory assets (see <cwist/sys/app/assets.h>).
const struct cwist_asset * asset
Integrated error structure used throughout the system.
cwist_sstring * path
e.g., "/users/1"
cwist_http_header_node * headers
cwist_http_method_t method
HTTP Response Object. Supports standard string body or Zero-Copy pointer body.
cwist_http_status_t status_code
cwist_http_header_node * headers
char * data
Access directly only when raw handling is necessary.
void cwist_error_dispose(cwist_error_t *err)
Release resources owned by an error value. Currently only CWIST_ERR_JSON owns memory (the cJSON paylo...