Line data Source code
1 : /*
2 : * lwan - web server
3 : * Copyright (c) 2012, 2013 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 : #define _GNU_SOURCE
21 : #include <assert.h>
22 : #include <string.h>
23 : #include <stdlib.h>
24 :
25 : #if defined(LWAN_HAVE_BROTLI)
26 : #include <brotli/decode.h>
27 : #elif defined(LWAN_HAVE_ZSTD)
28 : #include <zstd.h>
29 : #else
30 : #include <zlib.h>
31 : #endif
32 :
33 : #include "lwan-private.h"
34 :
35 : #include "mime-types.h"
36 :
37 : static unsigned char uncompressed_mime_entries[MIME_UNCOMPRESSED_LEN];
38 : static char *mime_types[MIME_ENTRIES];
39 : static bool mime_entries_initialized = false;
40 :
41 92 : void lwan_tables_init(void)
42 : {
43 92 : if (mime_entries_initialized)
44 0 : return;
45 :
46 92 : lwan_status_debug("Uncompressing MIME type table: %u->%u bytes, %d entries",
47 : MIME_COMPRESSED_LEN, MIME_UNCOMPRESSED_LEN, MIME_ENTRIES);
48 :
49 : #if defined(LWAN_HAVE_BROTLI)
50 92 : size_t uncompressed_length = MIME_UNCOMPRESSED_LEN;
51 : BrotliDecoderResult ret;
52 :
53 92 : ret = BrotliDecoderDecompress(MIME_COMPRESSED_LEN, mime_entries_compressed,
54 : &uncompressed_length,
55 : uncompressed_mime_entries);
56 92 : if (ret != BROTLI_DECODER_RESULT_SUCCESS)
57 0 : lwan_status_critical("Error while uncompressing table with Brotli");
58 : #elif defined(LWAN_HAVE_ZSTD)
59 : size_t uncompressed_length =
60 : ZSTD_decompress(uncompressed_mime_entries, MIME_UNCOMPRESSED_LEN,
61 : mime_entries_compressed, MIME_COMPRESSED_LEN);
62 : if (ZSTD_isError(uncompressed_length))
63 : lwan_status_critical("Error while uncompressing table with Zstd");
64 : #else
65 : uLongf uncompressed_length = MIME_UNCOMPRESSED_LEN;
66 : int ret =
67 : uncompress((Bytef *)uncompressed_mime_entries, &uncompressed_length,
68 : (const Bytef *)mime_entries_compressed, MIME_COMPRESSED_LEN);
69 : if (ret != Z_OK) {
70 : lwan_status_critical("Error while uncompressing table: zlib error %d",
71 : ret);
72 : }
73 : #endif
74 :
75 92 : if (uncompressed_length != MIME_UNCOMPRESSED_LEN) {
76 0 : lwan_status_critical("Expected uncompressed length %d, got %ld",
77 : MIME_UNCOMPRESSED_LEN, uncompressed_length);
78 : }
79 :
80 92 : unsigned char *ptr = uncompressed_mime_entries + 8 * MIME_ENTRIES;
81 88964 : for (size_t i = 0; i < MIME_ENTRIES; i++) {
82 88872 : mime_types[i] = (char *)ptr;
83 88872 : ptr += strlen((const char *)ptr) + 1;
84 : }
85 :
86 92 : mime_entries_initialized = true;
87 :
88 92 : assert(streq(lwan_determine_mime_type_for_file_name(".mkv"),
89 : "video/x-matroska"));
90 92 : assert(streq(lwan_determine_mime_type_for_file_name(".xml"),
91 : "application/xml"));
92 92 : assert(streq(lwan_determine_mime_type_for_file_name(".nosuchext"),
93 : "application/octet-stream"));
94 92 : assert(streq(lwan_determine_mime_type_for_file_name("nodotinfilename"),
95 : "application/octet-stream"));
96 92 : assert(streq(lwan_determine_mime_type_for_file_name(""),
97 : "application/octet-stream"));
98 92 : assert(streq(lwan_determine_mime_type_for_file_name(".gif"),
99 : "image/gif"));
100 92 : assert(streq(lwan_determine_mime_type_for_file_name(".JS"),
101 : "text/javascript"));
102 92 : assert(streq(lwan_determine_mime_type_for_file_name(".BZ2"),
103 : "application/x-bzip2"));
104 : }
105 :
106 : void
107 0 : lwan_tables_shutdown(void)
108 : {
109 0 : }
110 :
111 : static int
112 3404 : compare_mime_entry(const void *a, const void *b)
113 : {
114 3404 : const char *exta = (const char *)a;
115 3404 : const char *extb = (const char *)b;
116 :
117 3404 : return strncmp(exta, extb, 8);
118 : }
119 :
120 : const char *
121 771 : lwan_determine_mime_type_for_file_name(const char *file_name)
122 : {
123 771 : char *last_dot = strrchr(file_name, '.');
124 771 : if (UNLIKELY(!last_dot))
125 194 : goto fallback;
126 :
127 577 : STRING_SWITCH_L(last_dot) {
128 0 : case STR4_INT_L('.','c','s','s'): return "text/css";
129 101 : case STR4_INT_L('.','g','i','f'): return "image/gif";
130 14 : case STR4_INT_L('.','h','t','m'): return "text/html";
131 0 : case STR4_INT_L('.','j','p','g'): return "image/jpeg";
132 92 : case STR4_INT_L('.','j','s',' '): return "text/javascript";
133 0 : case STR4_INT_L('.','p','n','g'): return "image/png";
134 2 : case STR4_INT_L('.','t','x','t'): return "text/plain";
135 : }
136 :
137 368 : if (LIKELY(*last_dot)) {
138 : uint64_t key;
139 : const unsigned char *extension;
140 :
141 : #pragma GCC diagnostic push
142 : #pragma GCC diagnostic ignored "-Wstringop-truncation"
143 : /* Data is stored with NULs on strings up to 7 chars, and no NULs
144 : * for 8-char strings, because that's implicit. So truncation is
145 : * intentional here: comparison in compare_mime_entry() uses
146 : * strncmp(..., 8), so even if NUL isn't present, it'll stop at the
147 : * right place. */
148 368 : strncpy((char *)&key, last_dot + 1, 8);
149 : #pragma GCC diagnostic pop
150 368 : key &= ~0x2020202020202020ull;
151 :
152 368 : extension = bsearch(&key, uncompressed_mime_entries, MIME_ENTRIES, 8,
153 : compare_mime_entry);
154 368 : if (LIKELY(extension))
155 276 : return mime_types[(extension - uncompressed_mime_entries) / 8];
156 : }
157 :
158 0 : fallback:
159 286 : return "application/octet-stream";
160 : }
161 :
162 : #define GENERATE_ENTRY(id, code, short, long) \
163 : [HTTP_ ## id] = {.status = #code " " short, .description = long},
164 : static const struct {
165 : const char *status;
166 : const char *description;
167 : } status_table[] = {
168 : FOR_EACH_HTTP_STATUS(GENERATE_ENTRY)
169 : };
170 : #undef GENERATE_ENTRY
171 :
172 : const char *
173 1231 : lwan_http_status_as_string_with_code(enum lwan_http_status status)
174 : {
175 1231 : if (LIKELY(status < N_ELEMENTS(status_table))) {
176 1231 : const char *ret = status_table[status].status;
177 :
178 1231 : if (LIKELY(ret))
179 1231 : return ret;
180 : }
181 :
182 0 : return "999 Invalid";
183 : }
184 :
185 : ALWAYS_INLINE const char *
186 41 : lwan_http_status_as_string(enum lwan_http_status status)
187 : {
188 41 : return lwan_http_status_as_string_with_code(status) + 4;
189 : }
190 :
191 : const char *
192 41 : lwan_http_status_as_descriptive_string(enum lwan_http_status status)
193 : {
194 41 : if (LIKELY(status < N_ELEMENTS(status_table))) {
195 41 : const char *ret = status_table[status].description;
196 :
197 41 : if (LIKELY(ret))
198 41 : return ret;
199 : }
200 :
201 0 : return "Invalid";
202 : }
203 :
204 : enum {
205 : CHAR_PROP_SPACE = 1<<0,
206 : CHAR_PROP_HEX = 1<<1,
207 : CHAR_PROP_DIG = 1<<2,
208 : };
209 :
210 : static const uint8_t char_prop_tbl[256] = {
211 : [' '] = CHAR_PROP_SPACE,
212 : ['\t'] = CHAR_PROP_SPACE,
213 : ['\n'] = CHAR_PROP_SPACE,
214 : ['\r'] = CHAR_PROP_SPACE,
215 : ['0'] = CHAR_PROP_HEX | CHAR_PROP_DIG,
216 : ['1'] = CHAR_PROP_HEX | CHAR_PROP_DIG,
217 : ['2'] = CHAR_PROP_HEX | CHAR_PROP_DIG,
218 : ['3'] = CHAR_PROP_HEX | CHAR_PROP_DIG,
219 : ['4'] = CHAR_PROP_HEX | CHAR_PROP_DIG,
220 : ['5'] = CHAR_PROP_HEX | CHAR_PROP_DIG,
221 : ['6'] = CHAR_PROP_HEX | CHAR_PROP_DIG,
222 : ['7'] = CHAR_PROP_HEX | CHAR_PROP_DIG,
223 : ['8'] = CHAR_PROP_HEX | CHAR_PROP_DIG,
224 : ['9'] = CHAR_PROP_HEX | CHAR_PROP_DIG,
225 : ['a'] = CHAR_PROP_HEX,
226 : ['b'] = CHAR_PROP_HEX,
227 : ['c'] = CHAR_PROP_HEX,
228 : ['d'] = CHAR_PROP_HEX,
229 : ['e'] = CHAR_PROP_HEX,
230 : ['f'] = CHAR_PROP_HEX,
231 : ['A'] = CHAR_PROP_HEX,
232 : ['B'] = CHAR_PROP_HEX,
233 : ['C'] = CHAR_PROP_HEX,
234 : ['D'] = CHAR_PROP_HEX,
235 : ['E'] = CHAR_PROP_HEX,
236 : ['F'] = CHAR_PROP_HEX,
237 : };
238 :
239 4668 : ALWAYS_INLINE uint8_t lwan_char_isspace(char ch)
240 : {
241 4668 : return char_prop_tbl[(unsigned char)ch] & CHAR_PROP_SPACE;
242 : }
243 :
244 0 : ALWAYS_INLINE uint8_t lwan_char_isxdigit(char ch)
245 : {
246 0 : return char_prop_tbl[(unsigned char)ch] & CHAR_PROP_HEX;
247 : }
248 :
249 5 : ALWAYS_INLINE uint8_t lwan_char_isdigit(char ch)
250 : {
251 5 : return char_prop_tbl[(unsigned char)ch] & CHAR_PROP_DIG;
252 : }
|