annotate src/c/request.c @ 1347:b106ca8200b1

postBody type
author Adam Chlipala <adam@chlipala.net>
date Sat, 18 Dec 2010 10:56:31 -0500
parents a1aa62b472cf
children 87156c44824f
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);
adam@1347 195 } else if (clen_s && strcasecmp(clen_s, "application/x-www-form-urlencoded")) {
adam@1347 196 uw_Basis_postBody pb = {clen_s, body};
adam@1347 197 uw_postBody(ctx, pb);
adamc@853 198 }
adamc@854 199 } else if (strcmp(method, "GET")) {
adamc@856 200 log_error(logger_data, "Not ready for non-GET/POST command: %s\n", method);
adamc@853 201 return FAILED;
adamc@853 202 }
adamc@853 203
adamc@1094 204 if (!strncmp(path, prefix, strlen(prefix))
adamc@1094 205 && !strcmp(path + strlen(prefix), ".msgs")) {
adamc@853 206 char *id = uw_Basis_requestHeader(ctx, "UrWeb-Client");
adamc@853 207 char *pass = uw_Basis_requestHeader(ctx, "UrWeb-Pass");
adamc@853 208
adamc@853 209 if (sock < 0) {
adamc@856 210 log_error(logger_data, ".msgs requested, but not socket supplied\n");
adamc@853 211 return FAILED;
adamc@853 212 }
adamc@853 213
adamc@853 214 if (id && pass) {
adamc@853 215 unsigned idn = atoi(id);
adamc@864 216 uw_client_connect(idn, atoi(pass), sock, send, close, logger_data, log_error);
adam@1320 217 log_debug(logger_data, "Processed request for messages by client %u\n\n", idn);
adamc@853 218 return KEEP_OPEN;
adamc@853 219 }
adamc@853 220 else {
adamc@856 221 log_error(logger_data, "Missing fields in .msgs request: %s, %s\n\n", id, pass);
adamc@853 222 return FAILED;
adamc@853 223 }
adamc@853 224 }
adamc@853 225
adamc@853 226 if (boundary) {
adamc@854 227 char *part = body, *after_sub_headers, *header, *after_header;
adamc@853 228 size_t part_len;
adamc@853 229
adamc@853 230 part = strstr(part, boundary);
adamc@853 231 if (!part) {
adamc@856 232 log_error(logger_data, "Missing first multipart boundary\n");
adamc@853 233 return FAILED;
adamc@853 234 }
adamc@853 235 part += boundary_len;
adamc@853 236
adamc@853 237 while (1) {
adamc@853 238 char *name = NULL, *filename = NULL, *type = NULL;
adamc@853 239
adamc@853 240 if (part[0] == '-' && part[1] == '-')
adamc@853 241 break;
adamc@853 242
adamc@853 243 if (*part != '\r') {
adamc@856 244 log_error(logger_data, "No \\r after multipart boundary\n");
adamc@853 245 return FAILED;
adamc@853 246 }
adamc@853 247 ++part;
adamc@853 248 if (*part != '\n') {
adamc@856 249 log_error(logger_data, "No \\n after multipart boundary\n");
adamc@853 250 return FAILED;
adamc@853 251 }
adamc@853 252 ++part;
adamc@853 253
adamc@853 254 if (!(after_sub_headers = strstr(part, "\r\n\r\n"))) {
adamc@856 255 log_error(logger_data, "Missing end of headers after multipart boundary\n");
adamc@853 256 return FAILED;
adamc@853 257 }
adamc@853 258 after_sub_headers[2] = 0;
adamc@853 259 after_sub_headers += 4;
adamc@853 260
adamc@1134 261 for (header = part; (after_header = strstr(header, "\r\n")); header = after_header + 2) {
adamc@853 262 char *colon, *after_colon;
adamc@853 263
adamc@853 264 *after_header = 0;
adamc@853 265 if (!(colon = strchr(header, ':'))) {
adamc@856 266 log_error(logger_data, "Missing colon in multipart sub-header\n");
adamc@853 267 return FAILED;
adamc@853 268 }
adamc@853 269 *colon++ = 0;
adamc@853 270 if (*colon++ != ' ') {
adamc@856 271 log_error(logger_data, "No space after colon in multipart sub-header\n");
adamc@853 272 return FAILED;
adamc@853 273 }
adamc@853 274
adamc@853 275 if (!strcasecmp(header, "Content-Disposition")) {
adamc@853 276 if (strncmp(colon, "form-data; ", 11)) {
adamc@856 277 log_error(logger_data, "Multipart data is not \"form-data\"\n");
adamc@853 278 return FAILED;
adamc@853 279 }
adamc@853 280
adamc@1134 281 for (colon += 11; (after_colon = strchr(colon, '=')); colon = after_colon) {
adamc@853 282 char *data;
adamc@853 283 after_colon[0] = 0;
adamc@853 284 if (after_colon[1] != '"') {
adamc@856 285 log_error(logger_data, "Disposition setting is missing initial quote\n");
adamc@853 286 return FAILED;
adamc@853 287 }
adamc@853 288 data = after_colon+2;
adamc@853 289 if (!(after_colon = strchr(data, '"'))) {
adamc@856 290 log_error(logger_data, "Disposition setting is missing final quote\n");
adamc@853 291 return FAILED;
adamc@853 292 }
adamc@853 293 after_colon[0] = 0;
adamc@853 294 ++after_colon;
adamc@853 295 if (after_colon[0] == ';' && after_colon[1] == ' ')
adamc@853 296 after_colon += 2;
adamc@853 297
adamc@853 298 if (!strcasecmp(colon, "name"))
adamc@853 299 name = data;
adamc@853 300 else if (!strcasecmp(colon, "filename"))
adamc@853 301 filename = data;
adamc@853 302 }
adamc@853 303 } else if (!strcasecmp(header, "Content-Type")) {
adamc@853 304 type = colon;
adamc@853 305 }
adamc@853 306 }
adamc@853 307
adamc@854 308 part = memmem(after_sub_headers, body + body_len - after_sub_headers, boundary, boundary_len);
adamc@853 309 if (!part) {
adamc@856 310 log_error(logger_data, "Missing boundary after multipart payload\n");
adamc@853 311 return FAILED;
adamc@853 312 }
adamc@853 313 part[-2] = 0;
adamc@853 314 part_len = part - after_sub_headers - 2;
adamc@853 315 part[0] = 0;
adamc@853 316 part += boundary_len;
adamc@853 317
adamc@853 318 if (filename) {
adamc@853 319 uw_Basis_file f = {filename, type, {part_len, after_sub_headers}};
adamc@853 320
adamc@853 321 if (uw_set_file_input(ctx, name, f)) {
adamc@856 322 log_error(logger_data, "%s\n", uw_error_message(ctx));
adamc@853 323 return FAILED;
adamc@853 324 }
adamc@853 325 } else if (uw_set_input(ctx, name, after_sub_headers)) {
adamc@856 326 log_error(logger_data, "%s\n", uw_error_message(ctx));
adamc@853 327 return FAILED;
adamc@853 328 }
adamc@853 329 }
adamc@853 330 }
adam@1347 331 else if (!uw_hasPostBody(ctx)) {
adamc@854 332 inputs = is_post ? body : query_string;
adamc@853 333
adamc@853 334 if (inputs) {
adamc@853 335 char *name, *value;
adamc@853 336
adamc@853 337 while (*inputs) {
adamc@853 338 name = inputs;
adamc@1134 339 if ((inputs = strchr(inputs, '&')))
adamc@853 340 *inputs++ = 0;
adamc@853 341 else
adamc@853 342 inputs = strchr(name, 0);
adamc@853 343
adamc@1134 344 if ((value = strchr(name, '='))) {
adamc@853 345 *value++ = 0;
adamc@853 346 if (uw_set_input(ctx, name, value)) {
adamc@856 347 log_error(logger_data, "%s\n", uw_error_message(ctx));
adamc@853 348 return FAILED;
adamc@853 349 }
adamc@853 350 }
adamc@853 351 else if (uw_set_input(ctx, name, "")) {
adamc@856 352 log_error(logger_data, "%s\n", uw_error_message(ctx));
adamc@853 353 return FAILED;
adamc@853 354 }
adamc@853 355 }
adamc@853 356 }
adamc@853 357 }
adamc@853 358
adamc@856 359 log_debug(logger_data, "Serving URI %s....\n", path);
adamc@853 360
adamc@853 361 while (1) {
adam@1294 362 if (!had_error) {
adam@1294 363 size_t path_len = strlen(path);
adamc@853 364
adam@1294 365 on_success(ctx);
adamc@853 366
adam@1294 367 if (path_len + 1 > rc->path_copy_size) {
adam@1294 368 rc->path_copy_size = path_len + 1;
adam@1294 369 rc->path_copy = realloc(rc->path_copy, rc->path_copy_size);
adam@1294 370 }
adam@1294 371 strcpy(rc->path_copy, path);
adam@1308 372
adam@1308 373 uw_set_deadline(ctx, uw_time + uw_time_max);
adam@1294 374 fk = uw_begin(ctx, rc->path_copy);
adam@1308 375 } else {
adam@1308 376 uw_set_deadline(ctx, uw_time + uw_time_max);
adam@1294 377 fk = uw_begin_onError(ctx, errmsg);
adam@1308 378 }
adam@1294 379
adamc@1065 380 if (fk == SUCCESS || fk == RETURN_INDIRECTLY) {
adamc@853 381 uw_commit(ctx);
adam@1294 382 if (uw_has_error(ctx) && !had_error) {
adamc@1131 383 log_error(logger_data, "Fatal error: %s\n", uw_error_message(ctx));
adamc@1131 384 uw_reset_keep_error_message(ctx);
adamc@1131 385 on_failure(ctx);
adam@1294 386
adam@1294 387 if (uw_get_app(ctx)->on_error) {
adam@1294 388 had_error = 1;
adam@1294 389 strcpy(errmsg, uw_error_message(ctx));
adam@1294 390 } else {
adam@1294 391 uw_write_header(ctx, "Content-type: text/html\r\n");
adam@1294 392 uw_write(ctx, "<html><head><title>Fatal Error</title></head><body>");
adam@1294 393 uw_write(ctx, "Fatal error: ");
adam@1294 394 uw_write(ctx, uw_error_message(ctx));
adam@1294 395 uw_write(ctx, "\n</body></html>");
adamc@1131 396
adam@1294 397 return FAILED;
adam@1294 398 }
adamc@1131 399 } else
adam@1294 400 return had_error ? FAILED : SERVED;
adamc@853 401 } else if (fk == BOUNDED_RETRY) {
adamc@853 402 if (retries_left) {
adamc@856 403 log_debug(logger_data, "Error triggers bounded retry: %s\n", uw_error_message(ctx));
adamc@853 404 --retries_left;
adamc@853 405 }
adamc@853 406 else {
adamc@856 407 log_error(logger_data, "Fatal error (out of retries): %s\n", uw_error_message(ctx));
adamc@853 408
adam@1327 409 try_rollback(ctx, 0, logger_data, log_error);
adamc@853 410
adam@1294 411 if (!had_error && uw_get_app(ctx)->on_error) {
adam@1294 412 had_error = 1;
adam@1294 413 strcpy(errmsg, uw_error_message(ctx));
adam@1294 414 } else {
adam@1294 415 uw_reset_keep_error_message(ctx);
adam@1294 416 on_failure(ctx);
adam@1294 417 uw_write_header(ctx, "Content-type: text/plain\r\n");
adam@1294 418 uw_write(ctx, "Fatal error (out of retries): ");
adam@1294 419 uw_write(ctx, uw_error_message(ctx));
adam@1294 420 uw_write(ctx, "\n");
adam@1294 421
adam@1294 422 return FAILED;
adam@1294 423 }
adamc@853 424 }
adamc@853 425 } else if (fk == UNLIMITED_RETRY)
adamc@856 426 log_debug(logger_data, "Error triggers unlimited retry: %s\n", uw_error_message(ctx));
adamc@853 427 else if (fk == FATAL) {
adamc@856 428 log_error(logger_data, "Fatal error: %s\n", uw_error_message(ctx));
adamc@853 429
adam@1327 430 try_rollback(ctx, 0, logger_data, log_error);
adamc@853 431
adam@1294 432 if (uw_get_app(ctx)->on_error && !had_error) {
adam@1294 433 had_error = 1;
adam@1294 434 strcpy(errmsg, uw_error_message(ctx));
adam@1294 435 } else {
adam@1294 436 uw_reset_keep_error_message(ctx);
adam@1294 437 on_failure(ctx);
adam@1294 438 uw_write_header(ctx, "Content-type: text/html\r\n");
adam@1294 439 uw_write(ctx, "<html><head><title>Fatal Error</title></head><body>");
adam@1294 440 uw_write(ctx, "Fatal error: ");
adam@1294 441 uw_write(ctx, uw_error_message(ctx));
adam@1294 442 uw_write(ctx, "\n</body></html>");
adamc@853 443
adam@1294 444 return FAILED;
adam@1294 445 }
adamc@853 446 } else {
adamc@856 447 log_error(logger_data, "Unknown uw_handle return code!\n");
adamc@853 448
adam@1327 449 try_rollback(ctx, 0, logger_data, log_error);
adamc@853 450
adam@1294 451 if (uw_get_app(ctx)->on_error && !had_error) {
adam@1294 452 had_error = 1;
adam@1294 453 strcpy(errmsg, "Unknown uw_handle return code");
adam@1294 454 } else {
adam@1294 455 uw_reset_keep_request(ctx);
adam@1294 456 on_failure(ctx);
adam@1294 457 uw_write_header(ctx, "Content-type: text/plain\r\n");
adam@1294 458 uw_write(ctx, "Unknown uw_handle return code!\n");
adamc@853 459
adam@1294 460 return FAILED;
adam@1294 461 }
adamc@853 462 }
adamc@853 463
adam@1327 464 if (try_rollback(ctx, 1, logger_data, log_error))
adamc@853 465 return FAILED;
adamc@853 466
adamc@853 467 uw_reset_keep_request(ctx);
adamc@853 468 }
adamc@853 469 }
adamc@853 470
adamc@856 471 typedef struct {
adamc@1094 472 uw_app *app;
adamc@856 473 void *logger_data;
adamc@856 474 uw_logger log_error, log_debug;
adamc@856 475 } loggers;
adamc@856 476
adamc@853 477 void *client_pruner(void *data) {
adamc@856 478 loggers *ls = (loggers *)data;
adamc@1094 479 uw_context ctx = uw_request_new_context(ls->app, ls->logger_data, ls->log_error, ls->log_debug);
adamc@853 480
adamc@853 481 if (!ctx)
adamc@853 482 exit(1);
adamc@853 483
adamc@853 484 while (1) {
adamc@853 485 uw_prune_clients(ctx);
adamc@853 486 sleep(5);
adamc@853 487 }
adamc@853 488 }