annotate src/c/request.c @ 1339:43677a8a20e9

Bump year on LICENSE
author Adam Chlipala <adam@chlipala.net>
date Tue, 14 Dec 2010 10:35:52 -0500
parents a1aa62b472cf
children b106ca8200b1
rev   line source
adamc@1268 1 #include "config.h"
adamc@1268 2
adamc@853 3 #include <stdio.h>
adamc@853 4 #include <string.h>
adamc@853 5 #include <stdlib.h>
adamc@853 6 #include <sys/types.h>
adamc@853 7 #include <sys/socket.h>
adamc@853 8 #include <netinet/in.h>
adamc@853 9 #include <unistd.h>
adamc@853 10 #include <signal.h>
adamc@853 11
adamc@853 12 #include <pthread.h>
adamc@853 13
adamc@853 14 #include <mhash.h>
adamc@853 15
adamc@853 16 #include "urweb.h"
adamc@853 17
adamc@853 18 #define MAX_RETRIES 5
adamc@853 19
adamc@1152 20 void *memmem(const void *b1, size_t len1, const void *b2, size_t len2);
adamc@1152 21
adam@1327 22 static int try_rollback(uw_context ctx, int will_retry, void *logger_data, uw_logger log_error) {
adam@1327 23 int r = uw_rollback(ctx, will_retry);
adamc@853 24
adamc@853 25 if (r) {
adamc@856 26 log_error(logger_data, "Error running SQL ROLLBACK\n");
adamc@853 27 uw_reset(ctx);
adamc@1115 28 uw_write(ctx, "HTTP/1.1 500 Internal Server Error\r\n");
adamc@853 29 uw_write(ctx, "Content-type: text/plain\r\n\r\n");
adamc@853 30 uw_write(ctx, "Error running SQL ROLLBACK\n");
adamc@1115 31 uw_set_error_message(ctx, "Database error; you are probably out of storage space.");
adamc@853 32 }
adamc@853 33
adamc@853 34 return r;
adamc@853 35 }
adamc@853 36
adamc@1094 37 uw_context uw_request_new_context(uw_app *app, void *logger_data, uw_logger log_error, uw_logger log_debug) {
adam@1334 38 uw_context ctx = uw_init(logger_data, log_debug);
adamc@853 39 int retries_left = MAX_RETRIES;
adamc@1094 40 uw_set_app(ctx, app);
adamc@853 41
adamc@853 42 while (1) {
adamc@853 43 failure_kind fk = uw_begin_init(ctx);
adamc@853 44
adamc@853 45 if (fk == SUCCESS) {
adamc@856 46 log_debug(logger_data, "Database connection initialized.\n");
adamc@853 47 break;
adamc@853 48 } else if (fk == BOUNDED_RETRY) {
adamc@853 49 if (retries_left) {
adamc@856 50 log_debug(logger_data, "Initialization error triggers bounded retry: %s\n", uw_error_message(ctx));
adamc@853 51 --retries_left;
adamc@853 52 } else {
adamc@856 53 log_error(logger_data, "Fatal initialization error (out of retries): %s\n", uw_error_message(ctx));
adamc@853 54 uw_free(ctx);
adamc@853 55 return NULL;
adamc@853 56 }
adamc@853 57 } else if (fk == UNLIMITED_RETRY)
adamc@856 58 log_debug(logger_data, "Initialization error triggers unlimited retry: %s\n", uw_error_message(ctx));
adamc@853 59 else if (fk == FATAL) {
adamc@856 60 log_error(logger_data, "Fatal initialization error: %s\n", uw_error_message(ctx));
adamc@853 61 uw_free(ctx);
adamc@853 62 return NULL;
adamc@853 63 } else {
adamc@856 64 log_error(logger_data, "Unknown uw_begin_init return code!\n");
adamc@853 65 uw_free(ctx);
adamc@853 66 return NULL;
adamc@853 67 }
adamc@853 68 }
adamc@853 69
adamc@853 70 return ctx;
adamc@853 71 }
adamc@853 72
adam@1308 73 static void *ticker(void *data) {
adam@1308 74 while (1) {
adam@1308 75 usleep(100000);
adam@1308 76 ++uw_time;
adam@1308 77 }
adam@1308 78
adam@1308 79 return NULL;
adam@1308 80 }
adam@1308 81
adamc@1094 82 void uw_request_init(uw_app *app, void *logger_data, uw_logger log_error, uw_logger log_debug) {
adamc@853 83 uw_context ctx;
adamc@853 84 failure_kind fk;
adamc@853 85
adamc@853 86 uw_global_init();
adamc@1094 87 uw_app_init(app);
adamc@853 88
adam@1308 89 {
adam@1308 90 pthread_t thread;
adam@1308 91
adam@1308 92 if (uw_time_max && pthread_create(&thread, NULL, ticker, NULL)) {
adam@1308 93 fprintf(stderr, "Error creating ticker thread\n");
adam@1308 94 exit(1);
adam@1308 95 }
adam@1308 96 }
adam@1308 97
adamc@1094 98 ctx = uw_request_new_context(app, logger_data, log_error, log_debug);
adamc@853 99
adamc@853 100 if (!ctx)
adamc@853 101 exit(1);
adamc@853 102
adamc@853 103 for (fk = uw_initialize(ctx); fk == UNLIMITED_RETRY; fk = uw_initialize(ctx)) {
adamc@856 104 log_debug(logger_data, "Unlimited retry during init: %s\n", uw_error_message(ctx));
adam@1327 105 uw_rollback(ctx, 1);
adamc@853 106 uw_reset(ctx);
adamc@853 107 }
adamc@853 108
adamc@853 109 if (fk != SUCCESS) {
adamc@856 110 log_error(logger_data, "Failed to initialize database! %s\n", uw_error_message(ctx));
adam@1327 111 uw_rollback(ctx, 0);
adamc@853 112 exit(1);
adamc@853 113 }
adamc@853 114
adamc@853 115 uw_free(ctx);
adamc@853 116 }
adamc@853 117
adamc@853 118
adamc@853 119 typedef struct uw_rc {
adamc@853 120 size_t path_copy_size;
adamc@853 121 char *path_copy;
adamc@853 122 } *uw_request_context;
adamc@853 123
adamc@853 124 uw_request_context uw_new_request_context(void) {
adamc@853 125 uw_request_context r = malloc(sizeof(struct uw_rc));
adamc@853 126 r->path_copy_size = 0;
adamc@853 127 r->path_copy = malloc(0);
adamc@853 128 return r;
adamc@853 129 }
adamc@853 130
adamc@853 131 void uw_free_request_context(uw_request_context r) {
adamc@853 132 free(r->path_copy);
adamc@853 133 free(r);
adamc@853 134 }
adamc@853 135
adamc@854 136 request_result uw_request(uw_request_context rc, uw_context ctx,
adamc@854 137 char *method, char *path, char *query_string,
adamc@854 138 char *body, size_t body_len,
adamc@856 139 void (*on_success)(uw_context), void (*on_failure)(uw_context),
adamc@856 140 void *logger_data, uw_logger log_error, uw_logger log_debug,
adamc@863 141 int sock,
adamc@863 142 int (*send)(int sockfd, const void *buf, size_t len),
adamc@863 143 int (*close)(int fd)) {
adamc@853 144 int retries_left = MAX_RETRIES;
adamc@853 145 failure_kind fk;
adamc@1134 146 int is_post = 0;
adamc@853 147 char *boundary = NULL;
adamc@1134 148 size_t boundary_len = 0;
adamc@854 149 char *inputs;
adamc@1094 150 const char *prefix = uw_get_url_prefix(ctx);
adamc@1169 151 char *s;
adam@1294 152 int had_error = 0;
adam@1294 153 char errmsg[ERROR_BUF_LEN];
adamc@1169 154
adamc@1169 155 for (s = path; *s; ++s) {
adamc@1169 156 if (s[0] == '%' && s[1] == '2' && s[2] == '7') {
adamc@1169 157 s[0] = '\'';
adamc@1169 158 memmove(s+1, s+3, strlen(s+3)+1);
adamc@1169 159 }
adamc@1169 160 }
adamc@853 161
adamc@1066 162 uw_set_currentUrl(ctx, path);
adamc@1066 163
adamc@854 164 if (!strcmp(method, "POST")) {
adamc@853 165 char *clen_s = uw_Basis_requestHeader(ctx, "Content-length");
adamc@853 166 if (!clen_s) {
adamc@1037 167 clen_s = "0";
adamc@1037 168 /*log_error(logger_data, "No Content-length with POST\n");
adamc@1037 169 return FAILED;*/
adamc@853 170 }
adamc@853 171 int clen = atoi(clen_s);
adamc@853 172 if (clen < 0) {
adamc@856 173 log_error(logger_data, "Negative Content-length with POST\n");
adamc@853 174 return FAILED;
adamc@853 175 }
adamc@853 176
adamc@854 177 if (body_len < clen) {
adamc@856 178 log_error(logger_data, "Request doesn't contain all POST data (according to Content-Length)\n");
adamc@853 179 return FAILED;
adamc@853 180 }
adamc@853 181
adamc@853 182 is_post = 1;
adamc@853 183
adamc@853 184 clen_s = uw_Basis_requestHeader(ctx, "Content-type");
adamc@853 185 if (clen_s && !strncasecmp(clen_s, "multipart/form-data", 19)) {
adamc@853 186 if (strncasecmp(clen_s + 19, "; boundary=", 11)) {
adamc@856 187 log_error(logger_data, "Bad multipart boundary spec");
adamc@853 188 return FAILED;
adamc@853 189 }
adamc@853 190
adamc@853 191 boundary = clen_s + 28;
adamc@853 192 boundary[0] = '-';
adamc@853 193 boundary[1] = '-';
adamc@853 194 boundary_len = strlen(boundary);
adamc@853 195 }
adamc@854 196 } else if (strcmp(method, "GET")) {
adamc@856 197 log_error(logger_data, "Not ready for non-GET/POST command: %s\n", method);
adamc@853 198 return FAILED;
adamc@853 199 }
adamc@853 200
adamc@1094 201 if (!strncmp(path, prefix, strlen(prefix))
adamc@1094 202 && !strcmp(path + strlen(prefix), ".msgs")) {
adamc@853 203 char *id = uw_Basis_requestHeader(ctx, "UrWeb-Client");
adamc@853 204 char *pass = uw_Basis_requestHeader(ctx, "UrWeb-Pass");
adamc@853 205
adamc@853 206 if (sock < 0) {
adamc@856 207 log_error(logger_data, ".msgs requested, but not socket supplied\n");
adamc@853 208 return FAILED;
adamc@853 209 }
adamc@853 210
adamc@853 211 if (id && pass) {
adamc@853 212 unsigned idn = atoi(id);
adamc@864 213 uw_client_connect(idn, atoi(pass), sock, send, close, logger_data, log_error);
adam@1320 214 log_debug(logger_data, "Processed request for messages by client %u\n\n", idn);
adamc@853 215 return KEEP_OPEN;
adamc@853 216 }
adamc@853 217 else {
adamc@856 218 log_error(logger_data, "Missing fields in .msgs request: %s, %s\n\n", id, pass);
adamc@853 219 return FAILED;
adamc@853 220 }
adamc@853 221 }
adamc@853 222
adamc@853 223 if (boundary) {
adamc@854 224 char *part = body, *after_sub_headers, *header, *after_header;
adamc@853 225 size_t part_len;
adamc@853 226
adamc@853 227 part = strstr(part, boundary);
adamc@853 228 if (!part) {
adamc@856 229 log_error(logger_data, "Missing first multipart boundary\n");
adamc@853 230 return FAILED;
adamc@853 231 }
adamc@853 232 part += boundary_len;
adamc@853 233
adamc@853 234 while (1) {
adamc@853 235 char *name = NULL, *filename = NULL, *type = NULL;
adamc@853 236
adamc@853 237 if (part[0] == '-' && part[1] == '-')
adamc@853 238 break;
adamc@853 239
adamc@853 240 if (*part != '\r') {
adamc@856 241 log_error(logger_data, "No \\r after multipart boundary\n");
adamc@853 242 return FAILED;
adamc@853 243 }
adamc@853 244 ++part;
adamc@853 245 if (*part != '\n') {
adamc@856 246 log_error(logger_data, "No \\n after multipart boundary\n");
adamc@853 247 return FAILED;
adamc@853 248 }
adamc@853 249 ++part;
adamc@853 250
adamc@853 251 if (!(after_sub_headers = strstr(part, "\r\n\r\n"))) {
adamc@856 252 log_error(logger_data, "Missing end of headers after multipart boundary\n");
adamc@853 253 return FAILED;
adamc@853 254 }
adamc@853 255 after_sub_headers[2] = 0;
adamc@853 256 after_sub_headers += 4;
adamc@853 257
adamc@1134 258 for (header = part; (after_header = strstr(header, "\r\n")); header = after_header + 2) {
adamc@853 259 char *colon, *after_colon;
adamc@853 260
adamc@853 261 *after_header = 0;
adamc@853 262 if (!(colon = strchr(header, ':'))) {
adamc@856 263 log_error(logger_data, "Missing colon in multipart sub-header\n");
adamc@853 264 return FAILED;
adamc@853 265 }
adamc@853 266 *colon++ = 0;
adamc@853 267 if (*colon++ != ' ') {
adamc@856 268 log_error(logger_data, "No space after colon in multipart sub-header\n");
adamc@853 269 return FAILED;
adamc@853 270 }
adamc@853 271
adamc@853 272 if (!strcasecmp(header, "Content-Disposition")) {
adamc@853 273 if (strncmp(colon, "form-data; ", 11)) {
adamc@856 274 log_error(logger_data, "Multipart data is not \"form-data\"\n");
adamc@853 275 return FAILED;
adamc@853 276 }
adamc@853 277
adamc@1134 278 for (colon += 11; (after_colon = strchr(colon, '=')); colon = after_colon) {
adamc@853 279 char *data;
adamc@853 280 after_colon[0] = 0;
adamc@853 281 if (after_colon[1] != '"') {
adamc@856 282 log_error(logger_data, "Disposition setting is missing initial quote\n");
adamc@853 283 return FAILED;
adamc@853 284 }
adamc@853 285 data = after_colon+2;
adamc@853 286 if (!(after_colon = strchr(data, '"'))) {
adamc@856 287 log_error(logger_data, "Disposition setting is missing final quote\n");
adamc@853 288 return FAILED;
adamc@853 289 }
adamc@853 290 after_colon[0] = 0;
adamc@853 291 ++after_colon;
adamc@853 292 if (after_colon[0] == ';' && after_colon[1] == ' ')
adamc@853 293 after_colon += 2;
adamc@853 294
adamc@853 295 if (!strcasecmp(colon, "name"))
adamc@853 296 name = data;
adamc@853 297 else if (!strcasecmp(colon, "filename"))
adamc@853 298 filename = data;
adamc@853 299 }
adamc@853 300 } else if (!strcasecmp(header, "Content-Type")) {
adamc@853 301 type = colon;
adamc@853 302 }
adamc@853 303 }
adamc@853 304
adamc@854 305 part = memmem(after_sub_headers, body + body_len - after_sub_headers, boundary, boundary_len);
adamc@853 306 if (!part) {
adamc@856 307 log_error(logger_data, "Missing boundary after multipart payload\n");
adamc@853 308 return FAILED;
adamc@853 309 }
adamc@853 310 part[-2] = 0;
adamc@853 311 part_len = part - after_sub_headers - 2;
adamc@853 312 part[0] = 0;
adamc@853 313 part += boundary_len;
adamc@853 314
adamc@853 315 if (filename) {
adamc@853 316 uw_Basis_file f = {filename, type, {part_len, after_sub_headers}};
adamc@853 317
adamc@853 318 if (uw_set_file_input(ctx, name, f)) {
adamc@856 319 log_error(logger_data, "%s\n", uw_error_message(ctx));
adamc@853 320 return FAILED;
adamc@853 321 }
adamc@853 322 } else if (uw_set_input(ctx, name, after_sub_headers)) {
adamc@856 323 log_error(logger_data, "%s\n", uw_error_message(ctx));
adamc@853 324 return FAILED;
adamc@853 325 }
adamc@853 326 }
adamc@853 327 }
adamc@853 328 else {
adamc@854 329 inputs = is_post ? body : query_string;
adamc@853 330
adamc@853 331 if (inputs) {
adamc@853 332 char *name, *value;
adamc@853 333
adamc@853 334 while (*inputs) {
adamc@853 335 name = inputs;
adamc@1134 336 if ((inputs = strchr(inputs, '&')))
adamc@853 337 *inputs++ = 0;
adamc@853 338 else
adamc@853 339 inputs = strchr(name, 0);
adamc@853 340
adamc@1134 341 if ((value = strchr(name, '='))) {
adamc@853 342 *value++ = 0;
adamc@853 343 if (uw_set_input(ctx, name, value)) {
adamc@856 344 log_error(logger_data, "%s\n", uw_error_message(ctx));
adamc@853 345 return FAILED;
adamc@853 346 }
adamc@853 347 }
adamc@853 348 else if (uw_set_input(ctx, name, "")) {
adamc@856 349 log_error(logger_data, "%s\n", uw_error_message(ctx));
adamc@853 350 return FAILED;
adamc@853 351 }
adamc@853 352 }
adamc@853 353 }
adamc@853 354 }
adamc@853 355
adamc@856 356 log_debug(logger_data, "Serving URI %s....\n", path);
adamc@853 357
adamc@853 358 while (1) {
adam@1294 359 if (!had_error) {
adam@1294 360 size_t path_len = strlen(path);
adamc@853 361
adam@1294 362 on_success(ctx);
adamc@853 363
adam@1294 364 if (path_len + 1 > rc->path_copy_size) {
adam@1294 365 rc->path_copy_size = path_len + 1;
adam@1294 366 rc->path_copy = realloc(rc->path_copy, rc->path_copy_size);
adam@1294 367 }
adam@1294 368 strcpy(rc->path_copy, path);
adam@1308 369
adam@1308 370 uw_set_deadline(ctx, uw_time + uw_time_max);
adam@1294 371 fk = uw_begin(ctx, rc->path_copy);
adam@1308 372 } else {
adam@1308 373 uw_set_deadline(ctx, uw_time + uw_time_max);
adam@1294 374 fk = uw_begin_onError(ctx, errmsg);
adam@1308 375 }
adam@1294 376
adamc@1065 377 if (fk == SUCCESS || fk == RETURN_INDIRECTLY) {
adamc@853 378 uw_commit(ctx);
adam@1294 379 if (uw_has_error(ctx) && !had_error) {
adamc@1131 380 log_error(logger_data, "Fatal error: %s\n", uw_error_message(ctx));
adamc@1131 381 uw_reset_keep_error_message(ctx);
adamc@1131 382 on_failure(ctx);
adam@1294 383
adam@1294 384 if (uw_get_app(ctx)->on_error) {
adam@1294 385 had_error = 1;
adam@1294 386 strcpy(errmsg, uw_error_message(ctx));
adam@1294 387 } else {
adam@1294 388 uw_write_header(ctx, "Content-type: text/html\r\n");
adam@1294 389 uw_write(ctx, "<html><head><title>Fatal Error</title></head><body>");
adam@1294 390 uw_write(ctx, "Fatal error: ");
adam@1294 391 uw_write(ctx, uw_error_message(ctx));
adam@1294 392 uw_write(ctx, "\n</body></html>");
adamc@1131 393
adam@1294 394 return FAILED;
adam@1294 395 }
adamc@1131 396 } else
adam@1294 397 return had_error ? FAILED : SERVED;
adamc@853 398 } else if (fk == BOUNDED_RETRY) {
adamc@853 399 if (retries_left) {
adamc@856 400 log_debug(logger_data, "Error triggers bounded retry: %s\n", uw_error_message(ctx));
adamc@853 401 --retries_left;
adamc@853 402 }
adamc@853 403 else {
adamc@856 404 log_error(logger_data, "Fatal error (out of retries): %s\n", uw_error_message(ctx));
adamc@853 405
adam@1327 406 try_rollback(ctx, 0, logger_data, log_error);
adamc@853 407
adam@1294 408 if (!had_error && uw_get_app(ctx)->on_error) {
adam@1294 409 had_error = 1;
adam@1294 410 strcpy(errmsg, uw_error_message(ctx));
adam@1294 411 } else {
adam@1294 412 uw_reset_keep_error_message(ctx);
adam@1294 413 on_failure(ctx);
adam@1294 414 uw_write_header(ctx, "Content-type: text/plain\r\n");
adam@1294 415 uw_write(ctx, "Fatal error (out of retries): ");
adam@1294 416 uw_write(ctx, uw_error_message(ctx));
adam@1294 417 uw_write(ctx, "\n");
adam@1294 418
adam@1294 419 return FAILED;
adam@1294 420 }
adamc@853 421 }
adamc@853 422 } else if (fk == UNLIMITED_RETRY)
adamc@856 423 log_debug(logger_data, "Error triggers unlimited retry: %s\n", uw_error_message(ctx));
adamc@853 424 else if (fk == FATAL) {
adamc@856 425 log_error(logger_data, "Fatal error: %s\n", uw_error_message(ctx));
adamc@853 426
adam@1327 427 try_rollback(ctx, 0, logger_data, log_error);
adamc@853 428
adam@1294 429 if (uw_get_app(ctx)->on_error && !had_error) {
adam@1294 430 had_error = 1;
adam@1294 431 strcpy(errmsg, uw_error_message(ctx));
adam@1294 432 } else {
adam@1294 433 uw_reset_keep_error_message(ctx);
adam@1294 434 on_failure(ctx);
adam@1294 435 uw_write_header(ctx, "Content-type: text/html\r\n");
adam@1294 436 uw_write(ctx, "<html><head><title>Fatal Error</title></head><body>");
adam@1294 437 uw_write(ctx, "Fatal error: ");
adam@1294 438 uw_write(ctx, uw_error_message(ctx));
adam@1294 439 uw_write(ctx, "\n</body></html>");
adamc@853 440
adam@1294 441 return FAILED;
adam@1294 442 }
adamc@853 443 } else {
adamc@856 444 log_error(logger_data, "Unknown uw_handle return code!\n");
adamc@853 445
adam@1327 446 try_rollback(ctx, 0, logger_data, log_error);
adamc@853 447
adam@1294 448 if (uw_get_app(ctx)->on_error && !had_error) {
adam@1294 449 had_error = 1;
adam@1294 450 strcpy(errmsg, "Unknown uw_handle return code");
adam@1294 451 } else {
adam@1294 452 uw_reset_keep_request(ctx);
adam@1294 453 on_failure(ctx);
adam@1294 454 uw_write_header(ctx, "Content-type: text/plain\r\n");
adam@1294 455 uw_write(ctx, "Unknown uw_handle return code!\n");
adamc@853 456
adam@1294 457 return FAILED;
adam@1294 458 }
adamc@853 459 }
adamc@853 460
adam@1327 461 if (try_rollback(ctx, 1, logger_data, log_error))
adamc@853 462 return FAILED;
adamc@853 463
adamc@853 464 uw_reset_keep_request(ctx);
adamc@853 465 }
adamc@853 466 }
adamc@853 467
adamc@856 468 typedef struct {
adamc@1094 469 uw_app *app;
adamc@856 470 void *logger_data;
adamc@856 471 uw_logger log_error, log_debug;
adamc@856 472 } loggers;
adamc@856 473
adamc@853 474 void *client_pruner(void *data) {
adamc@856 475 loggers *ls = (loggers *)data;
adamc@1094 476 uw_context ctx = uw_request_new_context(ls->app, ls->logger_data, ls->log_error, ls->log_debug);
adamc@853 477
adamc@853 478 if (!ctx)
adamc@853 479 exit(1);
adamc@853 480
adamc@853 481 while (1) {
adamc@853 482 uw_prune_clients(ctx);
adamc@853 483 sleep(5);
adamc@853 484 }
adamc@853 485 }