Line data Source code
1 : /*
2 : * lwan - web server
3 : * Copyright (c) 2012 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 <assert.h>
23 : #include <netinet/in.h>
24 : #include <string.h>
25 : #include <sys/socket.h>
26 : #include <sys/uio.h>
27 : #include <unistd.h>
28 :
29 : #include "lwan-private.h"
30 :
31 : #include "int-to-str.h"
32 : #include "lwan-io-wrappers.h"
33 : #include "lwan-template.h"
34 :
35 : static struct lwan_tpl *error_template = NULL;
36 :
37 : static const char *error_template_str = "<html><head><style>" \
38 : "body{" \
39 : "background:#627d4d;" \
40 : "background:-moz-radial-gradient(center,ellipse cover,#627d4d 15\x25,#1f3b08 100\x25);" \
41 : "background:-webkit-gradient(radial,center center,0px,center center,100\x25,color-stop(15\x25,#627d4d),color-stop(100\x25,#1f3b08));" \
42 : "background:-webkit-radial-gradient(center,ellipse cover,#627d4d 15\x25,#1f3b08 100\x25);" \
43 : "background:-o-radial-gradient(center,ellipse cover,#627d4d 15\x25,#1f3b08 100\x25);" \
44 : "background:-ms-radial-gradient(center,ellipse cover,#627d4d 15\x25,#1f3b08 100\x25);" \
45 : "background:radial-gradient(center,ellipse cover,#627d4d 15\x25,#1f3b08 100\x25);" \
46 : "height:100\x25;font-family:Arial,'Helvetica Neue',Helvetica,sans-serif;text-align:center;border:0;letter-spacing:-1px;margin:0;padding:0}.sorry{color:#244837;font-size:18px;line-height:24px;text-shadow:0" \
47 : "1px 1px rgba(255,255,255,0.33)}h1{color:#fff;font-size:30px;font-weight:700;text-shadow:0 1px 4px rgba(0,0,0,0.68);letter-spacing:-1px;margin:0}" \
48 : "</style>" \
49 : "</head>" \
50 : "<body>" \
51 : "<table height=\"100\x25\" width=\"100\x25\"><tr><td align=\"center\" valign=\"middle\">" \
52 : "<div>" \
53 : "<h1>{{short_message}}</h1>" \
54 : "<div class=\"sorry\">" \
55 : "<p>{{long_message}}</p>" \
56 : "</div>" \
57 : "</div>" \
58 : "</td></tr></table>" \
59 : "</body>" \
60 : "</html>";
61 :
62 : struct error_template {
63 : const char *short_message;
64 : const char *long_message;
65 : };
66 :
67 92 : void lwan_response_init(struct lwan *l)
68 : {
69 : #undef TPL_STRUCT
70 : #define TPL_STRUCT struct error_template
71 : static const struct lwan_var_descriptor error_descriptor[] = {
72 : TPL_VAR_STR(short_message), TPL_VAR_STR(long_message),
73 : TPL_VAR_SENTINEL};
74 :
75 92 : assert(!error_template);
76 :
77 92 : lwan_status_debug("Initializing default response");
78 :
79 92 : if (l->config.error_template) {
80 0 : error_template =
81 0 : lwan_tpl_compile_file(l->config.error_template, error_descriptor);
82 : } else {
83 92 : error_template = lwan_tpl_compile_string_full(
84 : error_template_str, error_descriptor, LWAN_TPL_FLAG_CONST_TEMPLATE);
85 : }
86 :
87 92 : if (UNLIKELY(!error_template))
88 0 : lwan_status_critical_perror("lwan_tpl_compile_string");
89 92 : }
90 :
91 0 : void lwan_response_shutdown(struct lwan *l __attribute__((unused)))
92 : {
93 0 : lwan_status_debug("Shutting down response");
94 0 : assert(error_template);
95 0 : lwan_tpl_free(error_template);
96 0 : }
97 :
98 499 : static inline bool has_response_body(enum lwan_request_flags method,
99 : enum lwan_http_status status)
100 : {
101 : /* See FOR_EACH_REQUEST_METHOD() in lwan.h */
102 499 : return (method & 1 << 0) || status != HTTP_NOT_MODIFIED;
103 : }
104 :
105 544 : void lwan_response(struct lwan_request *request, enum lwan_http_status status)
106 : {
107 544 : const struct lwan_response *response = &request->response;
108 : char headers[DEFAULT_HEADERS_SIZE];
109 :
110 544 : if (UNLIKELY(request->flags & RESPONSE_CHUNKED_ENCODING)) {
111 : /* Send last, 0-sized chunk */
112 2 : lwan_strbuf_reset(response->buffer);
113 2 : lwan_response_send_chunk(request);
114 2 : return;
115 : }
116 :
117 542 : if (UNLIKELY(request->flags & RESPONSE_SENT_HEADERS)) {
118 0 : lwan_status_debug("Headers already sent, ignoring call");
119 0 : return;
120 : }
121 :
122 542 : if (UNLIKELY(!response->mime_type)) {
123 : /* Requests without a MIME Type are errors from handlers that should
124 : just be handled by lwan_default_response(). */
125 33 : return lwan_default_response(request, status);
126 : }
127 :
128 509 : if (request->flags & RESPONSE_STREAM) {
129 10 : if (LIKELY(response->stream.callback)) {
130 10 : status = response->stream.callback(request, response->stream.data);
131 : } else {
132 0 : status = HTTP_INTERNAL_ERROR;
133 : }
134 :
135 9 : if (UNLIKELY(status >= HTTP_CLASS__CLIENT_ERROR)) {
136 3 : request->flags &= ~RESPONSE_STREAM;
137 3 : lwan_default_response(request, status);
138 : }
139 :
140 9 : return;
141 : }
142 :
143 : size_t header_len =
144 499 : lwan_prepare_response_header(request, status, headers, sizeof(headers));
145 499 : if (UNLIKELY(!header_len))
146 0 : return lwan_default_response(request, HTTP_INTERNAL_ERROR);
147 :
148 499 : if (!has_response_body(lwan_request_get_method(request), status))
149 0 : return (void)lwan_send(request, headers, header_len, 0);
150 :
151 499 : char *resp_buf = lwan_strbuf_get_buffer(response->buffer);
152 499 : const size_t resp_len = lwan_strbuf_get_length(response->buffer);
153 499 : if (sizeof(headers) - header_len > resp_len) {
154 : /* writev() has to allocate, copy, and validate the response vector,
155 : * so use send() for responses small enough to fit the headers
156 : * buffer. On Linux, this is ~10% faster. */
157 495 : memcpy(headers + header_len, resp_buf, resp_len);
158 495 : return (void)lwan_send(request, headers, header_len + resp_len, 0);
159 : }
160 :
161 4 : struct iovec response_vec[] = {
162 : {.iov_base = headers, .iov_len = header_len},
163 : {.iov_base = resp_buf, .iov_len = resp_len},
164 : };
165 :
166 4 : return (void)lwan_writev(request, response_vec, N_ELEMENTS(response_vec));
167 : }
168 :
169 41 : void lwan_fill_default_response(struct lwan_strbuf *buffer,
170 : enum lwan_http_status status)
171 : {
172 41 : lwan_tpl_apply_with_buffer(
173 : error_template, buffer,
174 41 : &(struct error_template){
175 41 : .short_message = lwan_http_status_as_string(status),
176 41 : .long_message = lwan_http_status_as_descriptive_string(status),
177 : });
178 41 : }
179 :
180 41 : void lwan_default_response(struct lwan_request *request,
181 : enum lwan_http_status status)
182 : {
183 41 : request->response.mime_type = "text/html";
184 :
185 41 : lwan_fill_default_response(request->response.buffer, status);
186 41 : lwan_response(request, status);
187 41 : }
188 :
189 : #define RETURN_0_ON_OVERFLOW(len_) \
190 : if (UNLIKELY(p_headers + (len_) >= p_headers_end)) \
191 : return 0
192 :
193 : #define APPEND_STRING_LEN(const_str_, len_) \
194 : do { \
195 : RETURN_0_ON_OVERFLOW(len_); \
196 : p_headers = mempcpy(p_headers, (const_str_), (len_)); \
197 : } while (0)
198 :
199 : #define APPEND_STRING(str_) \
200 : do { \
201 : size_t len = strlen(str_); \
202 : APPEND_STRING_LEN((str_), len); \
203 : } while (0)
204 :
205 : #define APPEND_CHAR(value_) \
206 : do { \
207 : RETURN_0_ON_OVERFLOW(1); \
208 : *p_headers++ = (value_); \
209 : } while (0)
210 :
211 : #define APPEND_CHAR_NOCHECK(value_) *p_headers++ = (value_)
212 :
213 : #define APPEND_UINT(value_) \
214 : do { \
215 : size_t len; \
216 : char *tmp = uint_to_string((value_), buffer, &len); \
217 : RETURN_0_ON_OVERFLOW(len); \
218 : APPEND_STRING_LEN(tmp, len); \
219 : } while (0)
220 :
221 : #define APPEND_CONSTANT(const_str_) \
222 : APPEND_STRING_LEN((const_str_), sizeof(const_str_) - 1)
223 :
224 : static ALWAYS_INLINE __attribute__((const)) bool
225 : flags_has_content_length(enum lwan_request_flags v)
226 : {
227 508 : return !(v & (RESPONSE_NO_CONTENT_LENGTH | RESPONSE_STREAM |
228 : RESPONSE_CHUNKED_ENCODING));
229 : }
230 :
231 : static ALWAYS_INLINE __attribute__((const)) bool
232 : has_uncommon_response_headers(enum lwan_request_flags v)
233 : {
234 508 : return v & (RESPONSE_INCLUDE_REQUEST_ID | REQUEST_ALLOW_CORS |
235 : RESPONSE_CHUNKED_ENCODING | REQUEST_WANTS_HSTS_HEADER);
236 : }
237 :
238 508 : size_t lwan_prepare_response_header_full(
239 : struct lwan_request *request,
240 : enum lwan_http_status status,
241 : char headers[],
242 : size_t headers_buf_size,
243 : const struct lwan_key_value *additional_headers)
244 : {
245 : /* NOTE: If new response headers are added here, update
246 : * can_override_header() in lwan.c */
247 :
248 : char *p_headers;
249 508 : char *p_headers_end = headers + headers_buf_size;
250 : char buffer[INT_TO_STR_BUFFER_SIZE];
251 508 : const enum lwan_request_flags request_flags = request->flags;
252 508 : const enum lwan_connection_flags conn_flags = request->conn->flags;
253 508 : bool expires_override = !!(request->flags & (RESPONSE_NO_EXPIRES | REQUEST_HAS_QUERY_STRING));
254 :
255 508 : assert(request->global_response_headers);
256 :
257 508 : p_headers = headers;
258 :
259 508 : if (UNLIKELY(request_flags & REQUEST_IS_HTTP_1_0))
260 2 : APPEND_CONSTANT("HTTP/1.0 ");
261 : else
262 506 : APPEND_CONSTANT("HTTP/1.1 ");
263 508 : APPEND_STRING(lwan_http_status_as_string_with_code(status));
264 :
265 508 : if (LIKELY(!additional_headers))
266 59 : goto skip_additional_headers;
267 :
268 449 : if (LIKELY((status < HTTP_CLASS__CLIENT_ERROR))) {
269 : const struct lwan_key_value *header;
270 443 : bool date_override = false;
271 :
272 895 : for (header = additional_headers; header->key; header++) {
273 904 : STRING_SWITCH_L (header->key) {
274 0 : case STR4_INT_L('S', 'e', 'r', 'v'):
275 0 : if (LIKELY(streq(header->key + 4, "er")))
276 0 : continue;
277 0 : break;
278 0 : case STR4_INT_L('D', 'a', 't', 'e'):
279 0 : if (LIKELY(*(header->key + 4) == '\0'))
280 0 : date_override = true;
281 0 : break;
282 0 : case STR4_INT_L('E', 'x', 'p', 'i'):
283 0 : if (LIKELY(streq(header->key + 4, "res")))
284 0 : expires_override = true;
285 0 : break;
286 : }
287 :
288 452 : RETURN_0_ON_OVERFLOW(4);
289 452 : APPEND_CHAR_NOCHECK('\r');
290 452 : APPEND_CHAR_NOCHECK('\n');
291 452 : APPEND_STRING(header->key);
292 452 : APPEND_CHAR_NOCHECK(':');
293 452 : APPEND_CHAR_NOCHECK(' ');
294 452 : APPEND_STRING(header->value);
295 : }
296 :
297 443 : if (date_override)
298 0 : goto skip_date_header;
299 6 : } else if (UNLIKELY(status == HTTP_NOT_AUTHORIZED)) {
300 : const struct lwan_key_value *header;
301 :
302 3 : for (header = additional_headers; header->key; header++) {
303 3 : if (streq(header->key, "WWW-Authenticate")) {
304 3 : APPEND_CONSTANT("\r\nWWW-Authenticate: ");
305 3 : APPEND_STRING(header->value);
306 3 : break;
307 : }
308 : }
309 : }
310 :
311 3 : skip_additional_headers:
312 508 : APPEND_CONSTANT("\r\nDate: ");
313 508 : APPEND_STRING_LEN(request->conn->thread->date.date, 29);
314 :
315 508 : skip_date_header:
316 508 : if (UNLIKELY(conn_flags & CONN_IS_UPGRADE)) {
317 1 : APPEND_CONSTANT("\r\nConnection: Upgrade");
318 : } else {
319 507 : if (!(conn_flags & CONN_SENT_CONNECTION_HEADER)) {
320 239 : if (LIKELY(conn_flags & CONN_IS_KEEP_ALIVE))
321 220 : APPEND_CONSTANT("\r\nConnection: keep-alive");
322 : else
323 19 : APPEND_CONSTANT("\r\nConnection: close");
324 239 : request->conn->flags |= CONN_SENT_CONNECTION_HEADER;
325 : }
326 :
327 507 : if (LIKELY(request->response.mime_type)) {
328 507 : APPEND_CONSTANT("\r\nContent-Type: ");
329 507 : APPEND_STRING(request->response.mime_type);
330 : }
331 :
332 507 : if (!expires_override) {
333 187 : APPEND_CONSTANT("\r\nExpires: ");
334 187 : APPEND_STRING_LEN(request->conn->thread->date.expires, 29);
335 : }
336 : }
337 :
338 508 : const bool has_content_length = flags_has_content_length(request_flags);
339 508 : if (LIKELY(has_content_length)) {
340 499 : APPEND_CONSTANT("\r\nContent-Length: ");
341 499 : APPEND_UINT(lwan_strbuf_get_length(request->response.buffer));
342 : }
343 508 : if (UNLIKELY(has_uncommon_response_headers(request_flags))) {
344 392 : if (request_flags & REQUEST_ALLOW_CORS) {
345 0 : APPEND_CONSTANT(
346 : "\r\nAccess-Control-Allow-Origin: *"
347 : "\r\nAccess-Control-Allow-Methods: GET, POST, PUT, OPTIONS"
348 : "\r\nAccess-Control-Allow-Credentials: true"
349 : "\r\nAccess-Control-Allow-Headers: Origin, Accept, "
350 : "Content-Type");
351 : }
352 392 : if (!has_content_length && (request_flags & RESPONSE_CHUNKED_ENCODING)) {
353 2 : APPEND_CONSTANT("\r\nTransfer-Encoding: chunked");
354 : }
355 392 : if (request_flags & RESPONSE_INCLUDE_REQUEST_ID) {
356 390 : APPEND_CONSTANT("\r\nX-Request-Id: ");
357 390 : RETURN_0_ON_OVERFLOW(16);
358 390 : uint64_t id = lwan_request_get_id(request);
359 6630 : for (int i = 60; i >= 0; i -= 4)
360 6240 : APPEND_CHAR_NOCHECK("0123456789abcdef"[(id >> i) & 0xf]);
361 : }
362 392 : if (request_flags & REQUEST_WANTS_HSTS_HEADER) {
363 0 : APPEND_CONSTANT("\r\nStrict-Transport-Security: "
364 : "max-age=31536000; includeSubdomains");
365 : }
366 : }
367 :
368 508 : APPEND_STRING_LEN(request->global_response_headers->value,
369 : request->global_response_headers->len);
370 :
371 508 : return (size_t)(p_headers - headers);
372 : }
373 :
374 : #undef APPEND_CHAR
375 : #undef APPEND_CHAR_NOCHECK
376 : #undef APPEND_CONSTANT
377 : #undef APPEND_STRING
378 : #undef APPEND_STRING_LEN
379 : #undef APPEND_UINT
380 : #undef RETURN_0_ON_OVERFLOW
381 :
382 0 : ALWAYS_INLINE size_t lwan_prepare_response_header(struct lwan_request *request,
383 : enum lwan_http_status status,
384 : char headers[],
385 : size_t headers_buf_size)
386 : {
387 499 : return lwan_prepare_response_header_full(
388 : request, status, headers, headers_buf_size, request->response.headers);
389 : }
390 :
391 2 : bool lwan_response_set_chunked_full(struct lwan_request *request,
392 : enum lwan_http_status status,
393 : const struct lwan_key_value *additional_headers)
394 : {
395 : char buffer[DEFAULT_BUFFER_SIZE];
396 : size_t buffer_len;
397 :
398 2 : if (request->flags & RESPONSE_SENT_HEADERS)
399 0 : return false;
400 :
401 2 : request->flags |= RESPONSE_CHUNKED_ENCODING;
402 2 : buffer_len = lwan_prepare_response_header_full(request, status, buffer,
403 : DEFAULT_BUFFER_SIZE,
404 : additional_headers);
405 2 : if (UNLIKELY(!buffer_len))
406 0 : return false;
407 :
408 2 : request->flags |= RESPONSE_SENT_HEADERS;
409 2 : lwan_send(request, buffer, buffer_len, MSG_MORE);
410 :
411 2 : return true;
412 : }
413 :
414 2 : inline bool lwan_response_set_chunked(struct lwan_request *request,
415 : enum lwan_http_status status)
416 : {
417 2 : return lwan_response_set_chunked_full(request, status,
418 : request->response.headers);
419 : }
420 :
421 16 : void lwan_response_send_chunk_full(struct lwan_request *request,
422 : struct lwan_strbuf *strbuf)
423 : {
424 16 : if (!(request->flags & RESPONSE_SENT_HEADERS)) {
425 2 : if (UNLIKELY(!lwan_response_set_chunked(request, HTTP_OK)))
426 2 : return;
427 : }
428 :
429 16 : size_t buffer_len = lwan_strbuf_get_length(strbuf);
430 16 : if (UNLIKELY(!buffer_len)) {
431 : static const char last_chunk[] = "0\r\n\r\n";
432 2 : lwan_send(request, last_chunk, sizeof(last_chunk) - 1, 0);
433 2 : return;
434 : }
435 :
436 : char chunk_size[3 * sizeof(size_t) + 2];
437 : int converted_len =
438 14 : snprintf(chunk_size, sizeof(chunk_size), "%zx\r\n", buffer_len);
439 14 : if (UNLIKELY(converted_len < 0 ||
440 : (size_t)converted_len >= sizeof(chunk_size))) {
441 0 : coro_yield(request->conn->coro, CONN_CORO_ABORT);
442 0 : __builtin_unreachable();
443 : }
444 14 : size_t chunk_size_len = (size_t)converted_len;
445 :
446 28 : struct iovec chunk_vec[] = {
447 : {.iov_base = chunk_size, .iov_len = chunk_size_len},
448 14 : {.iov_base = lwan_strbuf_get_buffer(strbuf), .iov_len = buffer_len},
449 : {.iov_base = "\r\n", .iov_len = 2},
450 : };
451 :
452 14 : lwan_writev(request, chunk_vec, N_ELEMENTS(chunk_vec));
453 :
454 14 : lwan_strbuf_reset(strbuf);
455 : }
456 :
457 16 : void lwan_response_send_chunk(struct lwan_request *request)
458 : {
459 16 : return lwan_response_send_chunk_full(request, request->response.buffer);
460 : }
461 :
462 0 : bool lwan_response_set_event_stream(struct lwan_request *request,
463 : enum lwan_http_status status)
464 : {
465 : char buffer[DEFAULT_BUFFER_SIZE];
466 : size_t buffer_len;
467 :
468 0 : if (request->flags & RESPONSE_SENT_HEADERS)
469 0 : return false;
470 :
471 0 : request->response.mime_type = "text/event-stream";
472 0 : request->flags |= RESPONSE_NO_CONTENT_LENGTH;
473 0 : buffer_len = lwan_prepare_response_header(request, status, buffer,
474 : DEFAULT_BUFFER_SIZE);
475 0 : if (UNLIKELY(!buffer_len))
476 0 : return false;
477 :
478 0 : request->flags |= RESPONSE_SENT_HEADERS;
479 0 : lwan_send(request, buffer, buffer_len, MSG_MORE);
480 :
481 0 : return true;
482 : }
483 :
484 0 : void lwan_response_send_event(struct lwan_request *request, const char *event)
485 : {
486 : struct iovec vec[6];
487 0 : int last = 0;
488 :
489 0 : if (!(request->flags & RESPONSE_SENT_HEADERS)) {
490 0 : if (UNLIKELY(!lwan_response_set_event_stream(request, HTTP_OK)))
491 0 : return;
492 : }
493 :
494 0 : if (event) {
495 0 : vec[last++] = (struct iovec){
496 : .iov_base = "event: ",
497 : .iov_len = sizeof("event: ") - 1,
498 : };
499 0 : vec[last++] = (struct iovec){
500 : .iov_base = (char *)event,
501 0 : .iov_len = strlen(event),
502 : };
503 0 : vec[last++] = (struct iovec){
504 : .iov_base = "\r\n",
505 : .iov_len = 2,
506 : };
507 : }
508 :
509 0 : size_t buffer_len = lwan_strbuf_get_length(request->response.buffer);
510 0 : if (buffer_len) {
511 0 : vec[last++] = (struct iovec){
512 : .iov_base = "data: ",
513 : .iov_len = sizeof("data: ") - 1,
514 : };
515 0 : vec[last++] = (struct iovec){
516 0 : .iov_base = lwan_strbuf_get_buffer(request->response.buffer),
517 : .iov_len = buffer_len,
518 : };
519 : }
520 :
521 0 : vec[last++] = (struct iovec){
522 : .iov_base = "\r\n\r\n",
523 : .iov_len = 4,
524 : };
525 :
526 0 : lwan_writev(request, vec, last);
527 :
528 0 : lwan_strbuf_reset(request->response.buffer);
529 0 : coro_yield(request->conn->coro, CONN_CORO_WANT_WRITE);
530 : }
|