File: | bin/tools/mimegen.c |
Warning: | line 343, column 9 Potential leak of memory pointed to by 'output.ptr' |
Press '?' to see keyboard shortcuts
Keyboard shortcuts:
1 | /* | |||
2 | * lwan - web server | |||
3 | * Copyright (c) 2016 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, USA. | |||
18 | */ | |||
19 | ||||
20 | #include <assert.h> | |||
21 | #include <ctype.h> | |||
22 | #include <errno(*__errno_location ()).h> | |||
23 | #include <fcntl.h> | |||
24 | #include <stdint.h> | |||
25 | #include <stdio.h> | |||
26 | #include <stdlib.h> | |||
27 | #include <string.h> | |||
28 | ||||
29 | #if defined(LWAN_HAVE_BROTLI) | |||
30 | #include <brotli/encode.h> | |||
31 | #elif defined(LWAN_HAVE_ZSTD) | |||
32 | #include <zstd.h> | |||
33 | #elif defined(LWAN_HAVE_ZOPFLI) | |||
34 | #include <zopfli/zopfli.h> | |||
35 | #else | |||
36 | #include <zlib.h> | |||
37 | #endif | |||
38 | ||||
39 | #include "../../lib/hash.h" | |||
40 | ||||
41 | struct output { | |||
42 | char *ptr; | |||
43 | size_t used, capacity; | |||
44 | }; | |||
45 | ||||
46 | static int | |||
47 | output_append_full(struct output *output, const char *str, size_t str_len) | |||
48 | { | |||
49 | size_t total_size = output->used + str_len; | |||
50 | ||||
51 | if (total_size >= output->capacity) { | |||
52 | char *tmp; | |||
53 | ||||
54 | while (total_size
| |||
55 | output->capacity *= 2; | |||
56 | ||||
57 | tmp = realloc(output->ptr, output->capacity); | |||
58 | if (!tmp) | |||
59 | return -errno(*__errno_location ()); | |||
60 | ||||
61 | output->ptr = tmp; | |||
62 | } | |||
63 | ||||
64 | memcpy(output->ptr + output->used, str, str_len); | |||
65 | output->used = total_size; | |||
66 | ||||
67 | return 0; | |||
68 | } | |||
69 | ||||
70 | static int output_append_padded(struct output *output, const char *str) | |||
71 | { | |||
72 | size_t str_len = strlen(str); | |||
73 | ||||
74 | assert(str_len <= 8)((void) sizeof ((str_len <= 8) ? 1 : 0), __extension__ ({ if (str_len <= 8) ; else __assert_fail ("str_len <= 8", "/home/buildbot/lwan-worker/clang-analyze/build/src/bin/tools/mimegen.c" , 74, __extension__ __PRETTY_FUNCTION__); })); | |||
75 | ||||
76 | int r = output_append_full(output, str, str_len); | |||
77 | if (r
| |||
78 | return r; | |||
79 | ||||
80 | if (str_len != 8) | |||
81 | return output_append_full(output, "\0\0\0\0\0\0\0\0", 8 - str_len); | |||
82 | ||||
83 | return 0; | |||
84 | } | |||
85 | ||||
86 | static int output_append(struct output *output, const char *str) | |||
87 | { | |||
88 | return output_append_full(output, str, strlen(str) + 1); | |||
89 | } | |||
90 | ||||
91 | static int compare_ext(const void *a, const void *b) | |||
92 | { | |||
93 | const char **exta = (const char **)a; | |||
94 | const char **extb = (const char **)b; | |||
95 | ||||
96 | return strcasecmp(*exta, *extb); | |||
97 | } | |||
98 | ||||
99 | static char *strend(char *str, char ch) | |||
100 | { | |||
101 | str = strchr(str, ch); | |||
102 | if (str) { | |||
103 | *str = '\0'; | |||
104 | return str + 1; | |||
105 | } | |||
106 | return NULL((void*)0); | |||
107 | } | |||
108 | ||||
109 | static char *compress_output(const struct output *output, size_t *outlen) | |||
110 | { | |||
111 | char *compressed; | |||
112 | ||||
113 | #if defined(LWAN_HAVE_BROTLI) | |||
114 | *outlen = BrotliEncoderMaxCompressedSize(output->used); | |||
115 | ||||
116 | compressed = malloc(*outlen); | |||
117 | if (!compressed) { | |||
118 | fprintf(stderrstderr, "Could not allocate memory for compressed data\n"); | |||
119 | exit(1); | |||
120 | } | |||
121 | ||||
122 | if (BrotliEncoderCompress(BROTLI_MAX_QUALITY11, BROTLI_MAX_WINDOW_BITS24, | |||
123 | BROTLI_MODE_TEXT, output->used, | |||
124 | (const unsigned char *)output->ptr, outlen, | |||
125 | (unsigned char *)compressed) != BROTLI_TRUE1) { | |||
126 | fprintf(stderrstderr, "Could not compress mime type table with Brotli\n"); | |||
127 | exit(1); | |||
128 | } | |||
129 | #elif defined(LWAN_HAVE_ZSTD) | |||
130 | *outlen = ZSTD_compressBound(output->used); | |||
131 | ||||
132 | compressed = malloc(*outlen); | |||
133 | if (!compressed) { | |||
134 | fprintf(stderrstderr, "Could not allocate memory for compressed data\n"); | |||
135 | exit(1); | |||
136 | } | |||
137 | ||||
138 | *outlen = ZSTD_compress(compressed, *outlen, output->ptr, output->used, | |||
139 | ZSTD_maxCLevel()); | |||
140 | if (ZSTD_isError(*outlen)) { | |||
141 | fprintf(stderrstderr, "Could not compress mime type table with ZSTD\n"); | |||
142 | exit(1); | |||
143 | } | |||
144 | #elif defined(LWAN_HAVE_ZOPFLI) | |||
145 | ZopfliOptions opts; | |||
146 | ||||
147 | *outlen = 0; | |||
148 | ||||
149 | ZopfliInitOptions(&opts); | |||
150 | ZopfliCompress(&opts, ZOPFLI_FORMAT_ZLIB, | |||
151 | (const unsigned char *)output->ptr, output->used, | |||
152 | (unsigned char **)&compressed, outlen); | |||
153 | #else | |||
154 | *outlen = compressBound((uLong)output->used); | |||
155 | compressed = malloc(*outlen); | |||
156 | if (!compressed) { | |||
157 | fprintf(stderrstderr, "Could not allocate memory for compressed data\n"); | |||
158 | exit(1); | |||
159 | } | |||
160 | if (compress2((Bytef *)compressed, outlen, (const Bytef *)output->ptr, | |||
161 | output->used, 9) != Z_OK) { | |||
162 | fprintf(stderrstderr, "Could not compress data with zlib\n"); | |||
163 | exit(1); | |||
164 | } | |||
165 | #endif | |||
166 | if (!*outlen) { | |||
167 | free(compressed); | |||
168 | return NULL((void*)0); | |||
169 | } | |||
170 | ||||
171 | return compressed; | |||
172 | } | |||
173 | ||||
174 | static bool_Bool is_builtin_ext(const char *ext) | |||
175 | { | |||
176 | /* STRING_SWITCH_L() is not used here to not bring in lwan.h */ | |||
177 | /* FIXME: maybe use an X-macro to keep in sync with lwan-tables.c? */ | |||
178 | if (strcaseequal_neutral(ext, "css")) | |||
179 | return true1; | |||
180 | if (strcaseequal_neutral(ext, "gif")) | |||
181 | return true1; | |||
182 | if (strcaseequal_neutral(ext, "htm")) | |||
183 | return true1; | |||
184 | if (strcaseequal_neutral(ext, "html")) | |||
185 | return true1; | |||
186 | if (strcaseequal_neutral(ext, "jpg")) | |||
187 | return true1; | |||
188 | if (strcaseequal_neutral(ext, "js")) | |||
189 | return true1; | |||
190 | if (strcaseequal_neutral(ext, "png")) | |||
191 | return true1; | |||
192 | if (strcaseequal_neutral(ext, "txt")) | |||
193 | return true1; | |||
194 | return false0; | |||
195 | } | |||
196 | ||||
197 | int main(int argc, char *argv[]) | |||
198 | { | |||
199 | /* 32k is sufficient for the provided mime.types, but we can reallocate | |||
200 | * if necessary. This is just to avoid unneccessary reallocs. */ | |||
201 | struct output output = { .capacity = 32768 }; | |||
202 | FILE *fp; | |||
203 | char buffer[256]; | |||
204 | size_t compressed_size; | |||
205 | char *compressed, *ext; | |||
206 | struct hash *ext_mime; | |||
207 | struct hash_iter iter; | |||
208 | const char **exts, *key; | |||
209 | size_t i; | |||
210 | ||||
211 | if (argc < 2) { | |||
| ||||
212 | fprintf(stderrstderr, "Usage: %s /path/to/mime.types\n", argv[0]); | |||
213 | return 1; | |||
214 | } | |||
215 | ||||
216 | fp = fopen(argv[1], "re"); | |||
217 | if (!fp) { | |||
218 | fprintf(stderrstderr, "Could not open %s: %s\n", argv[1], strerror(errno(*__errno_location ()))); | |||
219 | return 1; | |||
220 | } | |||
221 | ||||
222 | ext_mime = hash_str_new(free, free); | |||
223 | if (!ext_mime) { | |||
224 | fprintf(stderrstderr, "Could not allocate hash table\n"); | |||
225 | fclose(fp); | |||
226 | return 1; | |||
227 | } | |||
228 | ||||
229 | while (fgets(buffer, sizeof(buffer), fp)) { | |||
230 | char *start = buffer, *end, *tab, *mime_type; | |||
231 | ||||
232 | while (*start && isspace(*start)((*__ctype_b_loc ())[(int) ((*start))] & (unsigned short int ) _ISspace)) /* Strip spaces at the start. */ | |||
233 | start++; | |||
234 | if (*start == '#') /* Ignore commented-out lines. */ | |||
235 | continue; | |||
236 | ||||
237 | strend(start, '\n'); /* Strip line endings. */ | |||
238 | strend(start, '#'); /* Strip comments from the middle. */ | |||
239 | tab = strend(start, '\t'); | |||
240 | if (!tab) /* Find mime-type/extension separator. */ | |||
241 | continue; | |||
242 | ||||
243 | mime_type = start; | |||
244 | /* "application/octet-stream" is the fallback, so no need to store | |||
245 | * it in the table. It's just one line, though, so maybe not really | |||
246 | * necessary? */ | |||
247 | if (streq(mime_type, "application/octet-stream")) | |||
248 | continue; | |||
249 | ||||
250 | while (*tab && *tab == '\t') /* Find first extension. */ | |||
251 | tab++; | |||
252 | ||||
253 | for (ext = tab; *ext; ext += end - ext + 1) { | |||
254 | char *k, *v; | |||
255 | int r; | |||
256 | ||||
257 | end = strchr(ext, ' '); /* Stop at next extension. */ | |||
258 | if (!end) | |||
259 | end = strchr(ext, '\0'); /* If not found, find last extension. */ | |||
260 | *end = '\0'; | |||
261 | ||||
262 | /* Check if we have empty extensions. Shouldn't happen with the provided | |||
263 | * mime.types file, but check on debug builds if this ever happens. */ | |||
264 | assert(end != ext)((void) sizeof ((end != ext) ? 1 : 0), __extension__ ({ if (end != ext) ; else __assert_fail ("end != ext", "/home/buildbot/lwan-worker/clang-analyze/build/src/bin/tools/mimegen.c" , 264, __extension__ __PRETTY_FUNCTION__); })); | |||
265 | ||||
266 | if (end - ext > 8) { | |||
267 | /* Truncate extensions over 8 characters. See commit 2050759297. */ | |||
268 | ext[8] = '\0'; | |||
269 | } | |||
270 | ||||
271 | /* Lwan has a fast-path for some common extensions, so don't bundle them | |||
272 | * in this table if not really needed. */ | |||
273 | if (is_builtin_ext(ext)) | |||
274 | continue; | |||
275 | ||||
276 | k = strdup(ext); | |||
277 | v = strdup(mime_type); | |||
278 | ||||
279 | if (!k || !v) { | |||
280 | fprintf(stderrstderr, "Could not allocate memory\n"); | |||
281 | fclose(fp); | |||
282 | return 1; | |||
283 | } | |||
284 | ||||
285 | r = hash_add_unique(ext_mime, k, v); | |||
286 | if (r < 0) { | |||
287 | free(k); | |||
288 | free(v); | |||
289 | ||||
290 | if (r != -EEXIST17) { | |||
291 | fprintf(stderrstderr, "Could not add extension to hash table\n"); | |||
292 | fclose(fp); | |||
293 | return 1; | |||
294 | } | |||
295 | } | |||
296 | } | |||
297 | } | |||
298 | ||||
299 | /* Get sorted list of extensions. */ | |||
300 | exts = calloc(hash_get_count(ext_mime), sizeof(char *)); | |||
301 | if (!exts) { | |||
302 | fprintf(stderrstderr, "Could not allocate extension array\n"); | |||
303 | fclose(fp); | |||
304 | return 1; | |||
305 | } | |||
306 | hash_iter_init(ext_mime, &iter); | |||
307 | for (i = 0; hash_iter_next(&iter, (const void **)&key, NULL((void*)0)); i++) | |||
308 | exts[i] = key; | |||
309 | qsort(exts, hash_get_count(ext_mime), sizeof(char *), compare_ext); | |||
310 | ||||
311 | /* Generate uncompressed blob. */ | |||
312 | output.ptr = malloc(output.capacity); | |||
313 | if (!output.ptr) { | |||
314 | fprintf(stderrstderr, "Could not allocate temporary memory\n"); | |||
315 | fclose(fp); | |||
316 | return 1; | |||
317 | } | |||
318 | for (i = 0; i < hash_get_count(ext_mime); i++) { | |||
319 | char ext_lower[9] = {0}; | |||
320 | ||||
321 | strncpy(ext_lower, exts[i], 8); | |||
322 | ||||
323 | for (char *p = ext_lower; *p; p++) | |||
324 | *p &= ~0x20; | |||
325 | ||||
326 | if (output_append_padded(&output, ext_lower) < 0) { | |||
327 | fprintf(stderrstderr, "Could not append to output\n"); | |||
328 | fclose(fp); | |||
329 | return 1; | |||
330 | } | |||
331 | } | |||
332 | for (i = 0; i < hash_get_count(ext_mime); i++) { | |||
333 | if (output_append(&output, hash_find(ext_mime, exts[i])) < 0) { | |||
334 | fprintf(stderrstderr, "Could not append to output\n"); | |||
335 | fclose(fp); | |||
336 | return 1; | |||
337 | } | |||
338 | } | |||
339 | ||||
340 | /* Compress blob. */ | |||
341 | compressed = compress_output(&output, &compressed_size); | |||
342 | if (!compressed
| |||
343 | fprintf(stderrstderr, "Could not compress data\n"); | |||
| ||||
344 | fclose(fp); | |||
345 | return 1; | |||
346 | } | |||
347 | ||||
348 | /* Print output. */ | |||
349 | #if defined(LWAN_HAVE_BROTLI) | |||
350 | printf("/* Compressed with brotli */\n"); | |||
351 | #elif defined(LWAN_HAVE_ZSTD) | |||
352 | printf("/* Compressed with zstd */\n"); | |||
353 | #elif defined(LWAN_HAVE_ZOPFLI) | |||
354 | printf("/* Compressed with zopfli (deflate) */\n"); | |||
355 | #else | |||
356 | printf("/* Compressed with zlib (deflate) */\n"); | |||
357 | #endif | |||
358 | printf("#pragma once\n"); | |||
359 | printf("#define MIME_UNCOMPRESSED_LEN %zu\n", output.used); | |||
360 | printf("#define MIME_COMPRESSED_LEN %lu\n", compressed_size); | |||
361 | printf("#define MIME_ENTRIES %d\n", hash_get_count(ext_mime)); | |||
362 | printf("static const unsigned char mime_entries_compressed[] = {\n"); | |||
363 | for (i = 1; compressed_size; compressed_size--, i++) | |||
364 | printf("0x%02x,%c", compressed[i - 1] & 0xff, " \n"[i % 13 == 0]); | |||
365 | printf("};\n"); | |||
366 | ||||
367 | free(compressed); | |||
368 | free(output.ptr); | |||
369 | free(exts); | |||
370 | hash_unref(ext_mime); | |||
371 | fclose(fp); | |||
372 | ||||
373 | return 0; | |||
374 | } |