File: | samples/smolsite/main.c |
Warning: | line 157, column 13 Potential leak of memory pointed to by 'key' |
Press '?' to see keyboard shortcuts
Keyboard shortcuts:
1 | /* | |||
2 | * smolsite.zip | |||
3 | * Copyright (c) 2023 L. A. F. Pereira <l@tia.mat.br> | |||
4 | * | |||
5 | * This program is free software; you can redistribute it and/or | |||
6 | * modify it under the terms of the GNU General Public License | |||
7 | * as published by the Free Software Foundation; either version 2 | |||
8 | * of the License, or any later version. | |||
9 | * | |||
10 | * This program is distributed in the hope that it will be useful, | |||
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
13 | * GNU General Public License for more details. | |||
14 | * | |||
15 | * You should have received a copy of the GNU General Public License | |||
16 | * along with this program; if not, write to the Free Software | |||
17 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, | |||
18 | * USA. | |||
19 | */ | |||
20 | ||||
21 | #define _GNU_SOURCE | |||
22 | #include <ctype.h> | |||
23 | #include <errno(*__errno_location ()).h> | |||
24 | #include <fcntl.h> | |||
25 | #include <stdlib.h> | |||
26 | #include <string.h> | |||
27 | ||||
28 | #include "base64.h" | |||
29 | #include "hash.h" | |||
30 | #include "int-to-str.h" | |||
31 | #include "lwan.h" | |||
32 | #include "sha1.h" | |||
33 | #include "lwan-cache.h" | |||
34 | #include "lwan-private.h" | |||
35 | #include "lwan-template.h" | |||
36 | ||||
37 | #include "junzip.h" | |||
38 | #include "qrcodegen.h" | |||
39 | #include "../clock/gifenc.h" | |||
40 | ||||
41 | #include "smolsite.h" | |||
42 | ||||
43 | #define CACHE_FOR_MINUTES15 15 | |||
44 | ||||
45 | static struct cache *sites; | |||
46 | ||||
47 | struct file { | |||
48 | ptrdiff_t data_offset; | |||
49 | size_t size_compressed; | |||
50 | const char *mime_type; | |||
51 | bool_Bool deflated; | |||
52 | }; | |||
53 | ||||
54 | struct site { | |||
55 | struct cache_entry entry; | |||
56 | struct lwan_value zipped; | |||
57 | struct hash *files; | |||
58 | struct lwan_strbuf qr_code; | |||
59 | int has_qr_code; | |||
60 | }; | |||
61 | ||||
62 | struct iframe_tpl_vars { | |||
63 | const char *digest; | |||
64 | int has_qr_code; | |||
65 | }; | |||
66 | ||||
67 | #undef TPL_STRUCTstruct iframe_tpl_vars | |||
68 | #define TPL_STRUCTstruct iframe_tpl_vars struct iframe_tpl_vars | |||
69 | static const struct lwan_var_descriptor iframe_tpl_desc[] = { | |||
70 | TPL_VAR_STR(digest){ .name = "digest", .offset = __builtin_offsetof(struct iframe_tpl_vars , digest), .append_to_strbuf = lwan_append_str_to_strbuf, .get_is_empty = lwan_tpl_str_is_empty }, | |||
71 | TPL_VAR_INT(has_qr_code){ .name = "has_qr_code", .offset = __builtin_offsetof(struct iframe_tpl_vars , has_qr_code), .append_to_strbuf = lwan_append_int_to_strbuf , .get_is_empty = lwan_tpl_int_is_empty }, | |||
72 | TPL_VAR_SENTINEL{ .name = ((void*)0), .offset = 0, }, | |||
73 | }; | |||
74 | ||||
75 | static struct lwan_tpl *iframe_tpl; | |||
76 | static struct lwan_value smolsite_zip_base64; | |||
77 | ||||
78 | static void calc_hash(struct lwan_value value, | |||
79 | char digest_str[static 41]) | |||
80 | { | |||
81 | /* FIXME: Is SHA-1 overkill? */ | |||
82 | sha1_context ctx; | |||
83 | unsigned char digest[20]; | |||
84 | ||||
85 | sha1_init(&ctx); | |||
86 | sha1_update(&ctx, (const unsigned char *)value.value, value.len); | |||
87 | sha1_finalize(&ctx, digest); | |||
88 | ||||
89 | snprintf(digest_str, 41, | |||
90 | "%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x" | |||
91 | "%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x", | |||
92 | digest[0], digest[1], digest[2], digest[3], digest[4], digest[5], | |||
93 | digest[6], digest[7], digest[8], digest[9], digest[10], digest[11], | |||
94 | digest[12], digest[13], digest[14], digest[15], digest[16], | |||
95 | digest[17], digest[18], digest[19]); | |||
96 | } | |||
97 | ||||
98 | static struct hash *pending_sites(void) | |||
99 | { | |||
100 | /* This is kind of a hack: we can't have just a single thread-local | |||
101 | * for the current thread's pending site because a coroutine might | |||
102 | * yield while trying to obtain an item from the sites cache, which | |||
103 | * would override that value. Store these in a thread-local hash | |||
104 | * table instead, which can be consulted by the create_site() function. | |||
105 | * Items are removed from this table in a defer handler. */ | |||
106 | static __thread struct hash *pending_sites; | |||
107 | ||||
108 | if (!pending_sites) { | |||
109 | pending_sites = hash_str_new(free, NULL((void*)0)); | |||
110 | if (!pending_sites) { | |||
111 | lwan_status_critical("Could not allocate pending sites hash table")lwan_status_critical_debug("/home/buildbot/lwan-worker/clang-analyze/build/src/samples/smolsite/main.c" , 111, __FUNCTION__, "Could not allocate pending sites hash table" ); | |||
112 | } | |||
113 | } | |||
114 | ||||
115 | return pending_sites; | |||
116 | } | |||
117 | ||||
118 | static int file_cb( | |||
119 | JZFile *zip, int idx, JZFileHeader *header, char *filename, void *user_data) | |||
120 | { | |||
121 | struct site *site = user_data; | |||
122 | char filename_buf[1024]; | |||
123 | ptrdiff_t data_offset; | |||
124 | size_t cur_offset = zip->tell(zip); | |||
125 | ||||
126 | if (zip->seek(zip, header->offset, SEEK_SET0)) | |||
| ||||
127 | return 0; | |||
128 | ||||
129 | JZFileHeader local; | |||
130 | if (jzReadLocalFileHeader(zip, &local, filename_buf, sizeof(filename_buf))) | |||
131 | return 0; | |||
132 | if (__builtin_add_overflow(zip->tell(zip), local.offset, &data_offset)) | |||
133 | return 0; | |||
134 | if (data_offset < 0) | |||
135 | return 0; | |||
136 | if ((size_t)data_offset > site->zipped.len) | |||
137 | return 0; | |||
138 | ||||
139 | uint32_t last_data_offset; | |||
140 | if (__builtin_add_overflow(local.compressedSize, data_offset, &last_data_offset)) | |||
141 | return 0; | |||
142 | if (last_data_offset > site->zipped.len) | |||
143 | return 0; | |||
144 | ||||
145 | struct file *file = malloc(sizeof(*file)); | |||
146 | if (!file) | |||
147 | return 0; | |||
148 | ||||
149 | file->data_offset = data_offset; | |||
150 | file->deflated = local.compressionMethod == 8; | |||
151 | file->size_compressed = local.compressedSize; | |||
152 | file->mime_type = lwan_determine_mime_type_for_file_name(filename); | |||
153 | ||||
154 | char *key = strdup(filename); | |||
155 | if (key) { | |||
156 | if (!hash_add_unique(site->files, key, file)) { | |||
157 | zip->seek(zip, cur_offset, SEEK_SET0); | |||
| ||||
158 | return 1; | |||
159 | } | |||
160 | } | |||
161 | ||||
162 | free(key); | |||
163 | free(file); | |||
164 | return 0; | |||
165 | } | |||
166 | ||||
167 | static bool_Bool generate_qr_code_gif(const char *b64, struct lwan_strbuf *output) | |||
168 | { | |||
169 | uint8_t qrcode[qrcodegen_BUFFER_LEN_MAX((((40) * 4 + 17) * ((40) * 4 + 17) + 7) / 8 + 1)]; | |||
170 | uint8_t tempBuffer[qrcodegen_BUFFER_LEN_MAX((((40) * 4 + 17) * ((40) * 4 + 17) + 7) / 8 + 1)]; | |||
171 | char *url; | |||
172 | bool_Bool ok; | |||
173 | ||||
174 | if (!lwan_strbuf_init(output)) | |||
175 | return false0; | |||
176 | ||||
177 | if (asprintf(&url, "https://smolsite.zip/%s", b64) < 0) | |||
178 | return false0; | |||
179 | ||||
180 | ok = qrcodegen_encodeText(url, tempBuffer, qrcode, qrcodegen_Ecc_LOW, | |||
181 | qrcodegen_VERSION_MIN1, qrcodegen_VERSION_MAX40, | |||
182 | qrcodegen_Mask_AUTO, true1); | |||
183 | free(url); | |||
184 | if (!ok) | |||
185 | return false0; | |||
186 | ||||
187 | int size = qrcodegen_getSize(qrcode); | |||
188 | if ((int)(uint16_t)size != size) | |||
189 | return false0; | |||
190 | ||||
191 | ge_GIF *gif = | |||
192 | ge_new_gif(output, (uint16_t)size, (uint16_t)size, NULL((void*)0), 4, -1); | |||
193 | if (!gif) { | |||
194 | lwan_strbuf_free(output); | |||
195 | return false0; | |||
196 | } | |||
197 | ||||
198 | for (int y = 0; y < size; y++) { | |||
199 | for (int x = 0; x < size; x++) { | |||
200 | gif->frame[y * size + x] = qrcodegen_getModule(qrcode, x, y) ? 0 : 15; | |||
201 | } | |||
202 | } | |||
203 | ge_add_frame(gif, 0); | |||
204 | ge_close_gif(gif); | |||
205 | ||||
206 | return true1; | |||
207 | } | |||
208 | ||||
209 | static struct cache_entry *create_site(const void *key, void *context) | |||
210 | { | |||
211 | struct lwan_strbuf qr_code = LWAN_STRBUF_STATIC_INIT(struct lwan_strbuf) { .buffer = "" }; | |||
212 | const struct lwan_value *base64_encoded = | |||
213 | hash_find(pending_sites(), (const void *)key); | |||
214 | unsigned char *decoded = NULL((void*)0); | |||
215 | size_t decoded_len; | |||
216 | ||||
217 | if (!base64_encoded) | |||
218 | return NULL((void*)0); | |||
219 | ||||
220 | if (UNLIKELY(!base64_validate((unsigned char *)base64_encoded->value,__builtin_expect(((!base64_validate((unsigned char *)base64_encoded ->value, base64_encoded->len))), (0)) | |||
221 | base64_encoded->len))__builtin_expect(((!base64_validate((unsigned char *)base64_encoded ->value, base64_encoded->len))), (0))) { | |||
222 | return NULL((void*)0); | |||
223 | } | |||
224 | ||||
225 | decoded = base64_decode((unsigned char *)base64_encoded->value, | |||
226 | base64_encoded->len, &decoded_len); | |||
227 | if (UNLIKELY(!decoded)__builtin_expect(((!decoded)), (0))) | |||
228 | return NULL((void*)0); | |||
229 | ||||
230 | struct site *site = malloc(sizeof(*site)); | |||
231 | if (!site) | |||
232 | goto no_site; | |||
233 | ||||
234 | site->has_qr_code = | |||
235 | generate_qr_code_gif((const char *)base64_encoded->value, &qr_code); | |||
236 | ||||
237 | site->qr_code = qr_code; | |||
238 | site->zipped = (struct lwan_value) {.value = (char *)decoded, .len = decoded_len}; | |||
239 | ||||
240 | FILE *zip_mem = fmemopen(decoded, decoded_len, "rb"); | |||
241 | if (!zip_mem) | |||
242 | goto no_file; | |||
243 | ||||
244 | JZFile *zip = jzfile_from_stdio_file(zip_mem); | |||
245 | if (!zip) { | |||
246 | fclose(zip_mem); | |||
247 | goto no_file; | |||
248 | } | |||
249 | ||||
250 | JZEndRecord end_record; | |||
251 | if (jzReadEndRecord(zip, &end_record)) | |||
252 | goto no_end_record; | |||
253 | ||||
254 | site->files = hash_str_new(free, free); | |||
255 | if (!site->files) | |||
256 | goto no_hash; | |||
257 | ||||
258 | if (jzReadCentralDirectory(zip, &end_record, file_cb, site)) | |||
259 | goto no_central_dir; | |||
260 | ||||
261 | jzfile_free(zip); | |||
262 | ||||
263 | return (struct cache_entry *)site; | |||
264 | ||||
265 | no_central_dir: | |||
266 | hash_free(site->files); | |||
267 | no_hash: | |||
268 | no_end_record: | |||
269 | jzfile_free(zip); | |||
270 | no_file: | |||
271 | free(site); | |||
272 | no_site: | |||
273 | free(decoded); | |||
274 | lwan_strbuf_free(&qr_code); | |||
275 | return NULL((void*)0); | |||
276 | } | |||
277 | ||||
278 | static void destroy_site(struct cache_entry *entry, void *context) | |||
279 | { | |||
280 | struct site *site = (struct site *)entry; | |||
281 | lwan_strbuf_free(&site->qr_code); | |||
282 | hash_free(site->files); | |||
283 | free(site->zipped.value); | |||
284 | free(site); | |||
285 | } | |||
286 | ||||
287 | static void remove_from_pending_defer(void *data) | |||
288 | { | |||
289 | char *key = data; | |||
290 | hash_del(pending_sites(), key); | |||
291 | } | |||
292 | ||||
293 | LWAN_HANDLER_ROUTE(view_root, "/")static enum lwan_http_status lwan_handler_view_root( struct lwan_request *, struct lwan_response *, void *); static const struct lwan_handler_info __attribute__((used, section("lwan_handler"))) __attribute__ ((aligned(8))) lwan_handler_info_view_root = { .name = "view_root" , .route = "/", .handler = lwan_handler_view_root, }; __attribute__ ((used)) static enum lwan_http_status lwan_handler_view_root( struct lwan_request *request __attribute__((unused)), struct lwan_response *response __attribute__((unused)), void *data __attribute__ ((unused))) | |||
294 | { | |||
295 | char digest_str[41]; | |||
296 | struct site *site; | |||
297 | ||||
298 | if (!request->url.len) { | |||
299 | const struct lwan_key_value redir_headers[] = { | |||
300 | {"Location", smolsite_zip_base64.value}, | |||
301 | {"Cache-Control", "no-cache, max-age=0, private, no-transform"}, | |||
302 | {}, | |||
303 | }; | |||
304 | response->headers = coro_memdup(request->conn->coro, | |||
305 | redir_headers, | |||
306 | sizeof(redir_headers)); | |||
307 | return response->headers ? HTTP_TEMPORARY_REDIRECT : HTTP_INTERNAL_ERROR; | |||
308 | } | |||
309 | ||||
310 | /* Lwan gives us a percent-decoded URL, but '+' is part of the Base64 | |||
311 | * alphabet */ | |||
312 | for (char *p = strchr(request->url.value, ' '); p; p = strchr(p + 1, ' ')) | |||
313 | *p = '+'; | |||
314 | ||||
315 | calc_hash(request->url, digest_str); | |||
316 | ||||
317 | site = (struct site *)cache_coro_get_and_ref_entry( | |||
318 | sites, request->conn->coro, digest_str); | |||
319 | if (!site) { | |||
320 | char *key = strdup(digest_str); | |||
321 | if (!key) { | |||
322 | lwan_status_debug("a")lwan_status_debug_debug("/home/buildbot/lwan-worker/clang-analyze/build/src/samples/smolsite/main.c" , 322, __FUNCTION__, "a"); | |||
323 | return HTTP_INTERNAL_ERROR; | |||
324 | } | |||
325 | ||||
326 | if (UNLIKELY(hash_add_unique(pending_sites(), key, &request->url))__builtin_expect(((hash_add_unique(pending_sites(), key, & request->url))), (0))) { | |||
327 | lwan_status_debug("b")lwan_status_debug_debug("/home/buildbot/lwan-worker/clang-analyze/build/src/samples/smolsite/main.c" , 327, __FUNCTION__, "b"); | |||
328 | return HTTP_INTERNAL_ERROR; | |||
329 | } | |||
330 | ||||
331 | coro_defer(request->conn->coro, remove_from_pending_defer, key); | |||
332 | ||||
333 | site = (struct site *)cache_coro_get_and_ref_entry( | |||
334 | sites, request->conn->coro, key); | |||
335 | if (UNLIKELY(!site)__builtin_expect(((!site)), (0))) { | |||
336 | lwan_status_debug("c")lwan_status_debug_debug("/home/buildbot/lwan-worker/clang-analyze/build/src/samples/smolsite/main.c" , 336, __FUNCTION__, "c"); | |||
337 | return HTTP_INTERNAL_ERROR; | |||
338 | } | |||
339 | } | |||
340 | ||||
341 | response->mime_type = "text/html; charset=utf-8"; | |||
342 | ||||
343 | struct iframe_tpl_vars vars = { | |||
344 | .digest = digest_str, | |||
345 | .has_qr_code = site->has_qr_code, | |||
346 | }; | |||
347 | if (!lwan_tpl_apply_with_buffer(iframe_tpl, response->buffer, &vars)) | |||
348 | return HTTP_INTERNAL_ERROR; | |||
349 | ||||
350 | return HTTP_OK; | |||
351 | } | |||
352 | ||||
353 | LWAN_HANDLER_ROUTE(qr_code, "/q/")static enum lwan_http_status lwan_handler_qr_code( struct lwan_request *, struct lwan_response *, void *); static const struct lwan_handler_info __attribute__((used, section("lwan_handler"))) __attribute__ ((aligned(8))) lwan_handler_info_qr_code = { .name = "qr_code" , .route = "/q/", .handler = lwan_handler_qr_code, }; __attribute__ ((used)) static enum lwan_http_status lwan_handler_qr_code( struct lwan_request *request __attribute__((unused)), struct lwan_response *response __attribute__((unused)), void *data __attribute__( (unused))) | |||
354 | { | |||
355 | struct site *site = (struct site *)cache_coro_get_and_ref_entry( | |||
356 | sites, request->conn->coro, request->url.value); | |||
357 | if (!site) | |||
358 | return HTTP_NOT_FOUND; | |||
359 | if (!site->has_qr_code) | |||
360 | return HTTP_NOT_FOUND; | |||
361 | ||||
362 | lwan_strbuf_set_static(response->buffer, | |||
363 | lwan_strbuf_get_buffer(&site->qr_code), | |||
364 | lwan_strbuf_get_length(&site->qr_code)); | |||
365 | response->mime_type = "image/gif"; | |||
366 | return HTTP_OK; | |||
367 | } | |||
368 | ||||
369 | LWAN_HANDLER_ROUTE(view_site, "/s/")static enum lwan_http_status lwan_handler_view_site( struct lwan_request *, struct lwan_response *, void *); static const struct lwan_handler_info __attribute__((used, section("lwan_handler"))) __attribute__ ((aligned(8))) lwan_handler_info_view_site = { .name = "view_site" , .route = "/s/", .handler = lwan_handler_view_site, }; __attribute__ ((used)) static enum lwan_http_status lwan_handler_view_site( struct lwan_request *request __attribute__((unused)), struct lwan_response *response __attribute__((unused)), void *data __attribute__ ((unused))) | |||
370 | { | |||
371 | if (request->url.len < 40) | |||
372 | return HTTP_NOT_FOUND; | |||
373 | ||||
374 | char *slash = memchr(request->url.value, '/', request->url.len); | |||
375 | if (!slash) | |||
376 | return HTTP_NOT_FOUND; | |||
377 | if (slash - request->url.value < 40) | |||
378 | return HTTP_NOT_FOUND; | |||
379 | *slash = '\0'; | |||
380 | if (strcspn(request->url.value, "0123456789abcdefABCDEF")) | |||
381 | return HTTP_NOT_FOUND; | |||
382 | ||||
383 | const char *file_name = slash + 1; | |||
384 | ||||
385 | struct site *site = (struct site *)cache_coro_get_and_ref_entry( | |||
386 | sites, request->conn->coro, request->url.value); | |||
387 | if (!site) | |||
388 | return HTTP_NOT_FOUND; | |||
389 | ||||
390 | if (*file_name == '\0') | |||
391 | file_name = "index.html"; | |||
392 | ||||
393 | struct file *file = hash_find(site->files, file_name); | |||
394 | if (!file) | |||
395 | return HTTP_NOT_FOUND; | |||
396 | ||||
397 | if (file->deflated) { | |||
398 | enum lwan_request_flags accept = | |||
399 | lwan_request_get_accept_encoding(request); | |||
400 | ||||
401 | if (!(accept & REQUEST_ACCEPT_DEFLATE)) | |||
402 | return HTTP_NOT_ACCEPTABLE; | |||
403 | ||||
404 | static const struct lwan_key_value deflate_headers[] = { | |||
405 | {"Content-Encoding", "deflate"}, | |||
406 | {}, | |||
407 | }; | |||
408 | response->headers = deflate_headers; | |||
409 | } | |||
410 | ||||
411 | lwan_strbuf_set_static(response->buffer, | |||
412 | site->zipped.value + file->data_offset, | |||
413 | file->size_compressed); | |||
414 | response->mime_type = file->mime_type; | |||
415 | ||||
416 | return HTTP_OK; | |||
417 | } | |||
418 | ||||
419 | static struct lwan_value base64_encode_to_value(struct lwan_value input) | |||
420 | { | |||
421 | size_t len; | |||
422 | unsigned char *encoded = | |||
423 | base64_encode((unsigned char *)input.value, input.len, &len); | |||
424 | if (!encoded) | |||
425 | lwan_status_critical("Could not base64-encode smolsite.zip!")lwan_status_critical_debug("/home/buildbot/lwan-worker/clang-analyze/build/src/samples/smolsite/main.c" , 425, __FUNCTION__, "Could not base64-encode smolsite.zip!"); | |||
426 | return (struct lwan_value){.value = (char *)encoded, .len = len}; | |||
427 | } | |||
428 | ||||
429 | int main(void) | |||
430 | { | |||
431 | struct lwan l; | |||
432 | ||||
433 | lwan_init(&l); | |||
434 | lwan_detect_url_map(&l); | |||
435 | ||||
436 | smolsite_zip_base64 = base64_encode_to_value(smolsite_zip_value); | |||
437 | ||||
438 | iframe_tpl = lwan_tpl_compile_value_full(smolsite_html_value, | |||
439 | iframe_tpl_desc, | |||
440 | LWAN_TPL_FLAG_CONST_TEMPLATE); | |||
441 | if (!iframe_tpl) | |||
442 | lwan_status_critical("Could not compile template")lwan_status_critical_debug("/home/buildbot/lwan-worker/clang-analyze/build/src/samples/smolsite/main.c" , 442, __FUNCTION__, "Could not compile template"); | |||
443 | ||||
444 | sites = cache_create_full(create_site, destroy_site, hash_str_new, NULL((void*)0), | |||
445 | CACHE_FOR_MINUTES15 * 60); | |||
446 | if (!sites) | |||
447 | lwan_status_critical("Could not create site cache")lwan_status_critical_debug("/home/buildbot/lwan-worker/clang-analyze/build/src/samples/smolsite/main.c" , 447, __FUNCTION__, "Could not create site cache"); | |||
448 | ||||
449 | lwan_main_loop(&l); | |||
450 | lwan_shutdown(&l); | |||
451 | cache_destroy(sites); | |||
452 | lwan_tpl_free(iframe_tpl); | |||
453 | free(smolsite_zip_base64.value); | |||
454 | ||||
455 | return 0; | |||
456 | } |