adamc@1268: #include "config.h" adamc@1268: adamc@853: #include adamc@853: #include adamc@853: #include adamc@853: #include adamc@853: #include adamc@853: #include adamc@853: #include adamc@853: #include adamc@853: adamc@853: #include adamc@853: adamc@853: #include adamc@853: adamc@853: #include "urweb.h" adamc@853: adamc@853: #define MAX_RETRIES 5 adamc@853: adamc@1152: void *memmem(const void *b1, size_t len1, const void *b2, size_t len2); adamc@1152: adamc@856: static int try_rollback(uw_context ctx, void *logger_data, uw_logger log_error) { adamc@853: int r = uw_rollback(ctx); adamc@853: adamc@853: if (r) { adamc@856: log_error(logger_data, "Error running SQL ROLLBACK\n"); adamc@853: uw_reset(ctx); adamc@1115: uw_write(ctx, "HTTP/1.1 500 Internal Server Error\r\n"); adamc@853: uw_write(ctx, "Content-type: text/plain\r\n\r\n"); adamc@853: uw_write(ctx, "Error running SQL ROLLBACK\n"); adamc@1115: uw_set_error_message(ctx, "Database error; you are probably out of storage space."); adamc@853: } adamc@853: adamc@853: return r; adamc@853: } adamc@853: adamc@1094: uw_context uw_request_new_context(uw_app *app, void *logger_data, uw_logger log_error, uw_logger log_debug) { adamc@853: uw_context ctx = uw_init(); adamc@853: int retries_left = MAX_RETRIES; adamc@1094: uw_set_app(ctx, app); adamc@853: adamc@853: while (1) { adamc@853: failure_kind fk = uw_begin_init(ctx); adamc@853: adamc@853: if (fk == SUCCESS) { adamc@856: log_debug(logger_data, "Database connection initialized.\n"); adamc@853: break; adamc@853: } else if (fk == BOUNDED_RETRY) { adamc@853: if (retries_left) { adamc@856: log_debug(logger_data, "Initialization error triggers bounded retry: %s\n", uw_error_message(ctx)); adamc@853: --retries_left; adamc@853: } else { adamc@856: log_error(logger_data, "Fatal initialization error (out of retries): %s\n", uw_error_message(ctx)); adamc@853: uw_free(ctx); adamc@853: return NULL; adamc@853: } adamc@853: } else if (fk == UNLIMITED_RETRY) adamc@856: log_debug(logger_data, "Initialization error triggers unlimited retry: %s\n", uw_error_message(ctx)); adamc@853: else if (fk == FATAL) { adamc@856: log_error(logger_data, "Fatal initialization error: %s\n", uw_error_message(ctx)); adamc@853: uw_free(ctx); adamc@853: return NULL; adamc@853: } else { adamc@856: log_error(logger_data, "Unknown uw_begin_init return code!\n"); adamc@853: uw_free(ctx); adamc@853: return NULL; adamc@853: } adamc@853: } adamc@853: adamc@853: return ctx; adamc@853: } adamc@853: adam@1308: static void *ticker(void *data) { adam@1308: while (1) { adam@1308: usleep(100000); adam@1308: ++uw_time; adam@1308: } adam@1308: adam@1308: return NULL; adam@1308: } adam@1308: adamc@1094: void uw_request_init(uw_app *app, void *logger_data, uw_logger log_error, uw_logger log_debug) { adamc@853: uw_context ctx; adamc@853: failure_kind fk; adamc@853: adamc@853: uw_global_init(); adamc@1094: uw_app_init(app); adamc@853: adam@1308: { adam@1308: pthread_t thread; adam@1308: adam@1308: if (uw_time_max && pthread_create(&thread, NULL, ticker, NULL)) { adam@1308: fprintf(stderr, "Error creating ticker thread\n"); adam@1308: exit(1); adam@1308: } adam@1308: } adam@1308: adamc@1094: ctx = uw_request_new_context(app, logger_data, log_error, log_debug); adamc@853: adamc@853: if (!ctx) adamc@853: exit(1); adamc@853: adamc@853: for (fk = uw_initialize(ctx); fk == UNLIMITED_RETRY; fk = uw_initialize(ctx)) { adamc@856: log_debug(logger_data, "Unlimited retry during init: %s\n", uw_error_message(ctx)); adamc@1094: uw_rollback(ctx); adamc@853: uw_reset(ctx); adamc@853: } adamc@853: adamc@853: if (fk != SUCCESS) { adamc@856: log_error(logger_data, "Failed to initialize database! %s\n", uw_error_message(ctx)); adamc@1094: uw_rollback(ctx); adamc@853: exit(1); adamc@853: } adamc@853: adamc@853: uw_free(ctx); adamc@853: } adamc@853: adamc@853: adamc@853: typedef struct uw_rc { adamc@853: size_t path_copy_size; adamc@853: char *path_copy; adamc@853: } *uw_request_context; adamc@853: adamc@853: uw_request_context uw_new_request_context(void) { adamc@853: uw_request_context r = malloc(sizeof(struct uw_rc)); adamc@853: r->path_copy_size = 0; adamc@853: r->path_copy = malloc(0); adamc@853: return r; adamc@853: } adamc@853: adamc@853: void uw_free_request_context(uw_request_context r) { adamc@853: free(r->path_copy); adamc@853: free(r); adamc@853: } adamc@853: adamc@854: request_result uw_request(uw_request_context rc, uw_context ctx, adamc@854: char *method, char *path, char *query_string, adamc@854: char *body, size_t body_len, adamc@856: void (*on_success)(uw_context), void (*on_failure)(uw_context), adamc@856: void *logger_data, uw_logger log_error, uw_logger log_debug, adamc@863: int sock, adamc@863: int (*send)(int sockfd, const void *buf, size_t len), adamc@863: int (*close)(int fd)) { adamc@853: int retries_left = MAX_RETRIES; adamc@853: failure_kind fk; adamc@1134: int is_post = 0; adamc@853: char *boundary = NULL; adamc@1134: size_t boundary_len = 0; adamc@854: char *inputs; adamc@1094: const char *prefix = uw_get_url_prefix(ctx); adamc@1169: char *s; adam@1294: int had_error = 0; adam@1294: char errmsg[ERROR_BUF_LEN]; adamc@1169: adamc@1169: for (s = path; *s; ++s) { adamc@1169: if (s[0] == '%' && s[1] == '2' && s[2] == '7') { adamc@1169: s[0] = '\''; adamc@1169: memmove(s+1, s+3, strlen(s+3)+1); adamc@1169: } adamc@1169: } adamc@853: adamc@1066: uw_set_currentUrl(ctx, path); adamc@1066: adamc@854: if (!strcmp(method, "POST")) { adamc@853: char *clen_s = uw_Basis_requestHeader(ctx, "Content-length"); adamc@853: if (!clen_s) { adamc@1037: clen_s = "0"; adamc@1037: /*log_error(logger_data, "No Content-length with POST\n"); adamc@1037: return FAILED;*/ adamc@853: } adamc@853: int clen = atoi(clen_s); adamc@853: if (clen < 0) { adamc@856: log_error(logger_data, "Negative Content-length with POST\n"); adamc@853: return FAILED; adamc@853: } adamc@853: adamc@854: if (body_len < clen) { adamc@856: log_error(logger_data, "Request doesn't contain all POST data (according to Content-Length)\n"); adamc@853: return FAILED; adamc@853: } adamc@853: adamc@853: is_post = 1; adamc@853: adamc@853: clen_s = uw_Basis_requestHeader(ctx, "Content-type"); adamc@853: if (clen_s && !strncasecmp(clen_s, "multipart/form-data", 19)) { adamc@853: if (strncasecmp(clen_s + 19, "; boundary=", 11)) { adamc@856: log_error(logger_data, "Bad multipart boundary spec"); adamc@853: return FAILED; adamc@853: } adamc@853: adamc@853: boundary = clen_s + 28; adamc@853: boundary[0] = '-'; adamc@853: boundary[1] = '-'; adamc@853: boundary_len = strlen(boundary); adamc@853: } adamc@854: } else if (strcmp(method, "GET")) { adamc@856: log_error(logger_data, "Not ready for non-GET/POST command: %s\n", method); adamc@853: return FAILED; adamc@853: } adamc@853: adamc@1094: if (!strncmp(path, prefix, strlen(prefix)) adamc@1094: && !strcmp(path + strlen(prefix), ".msgs")) { adamc@853: char *id = uw_Basis_requestHeader(ctx, "UrWeb-Client"); adamc@853: char *pass = uw_Basis_requestHeader(ctx, "UrWeb-Pass"); adamc@853: adamc@853: if (sock < 0) { adamc@856: log_error(logger_data, ".msgs requested, but not socket supplied\n"); adamc@853: return FAILED; adamc@853: } adamc@853: adamc@853: if (id && pass) { adamc@853: unsigned idn = atoi(id); adamc@864: uw_client_connect(idn, atoi(pass), sock, send, close, logger_data, log_error); adamc@856: log_error(logger_data, "Processed request for messages by client %u\n\n", idn); adamc@853: return KEEP_OPEN; adamc@853: } adamc@853: else { adamc@856: log_error(logger_data, "Missing fields in .msgs request: %s, %s\n\n", id, pass); adamc@853: return FAILED; adamc@853: } adamc@853: } adamc@853: adamc@853: if (boundary) { adamc@854: char *part = body, *after_sub_headers, *header, *after_header; adamc@853: size_t part_len; adamc@853: adamc@853: part = strstr(part, boundary); adamc@853: if (!part) { adamc@856: log_error(logger_data, "Missing first multipart boundary\n"); adamc@853: return FAILED; adamc@853: } adamc@853: part += boundary_len; adamc@853: adamc@853: while (1) { adamc@853: char *name = NULL, *filename = NULL, *type = NULL; adamc@853: adamc@853: if (part[0] == '-' && part[1] == '-') adamc@853: break; adamc@853: adamc@853: if (*part != '\r') { adamc@856: log_error(logger_data, "No \\r after multipart boundary\n"); adamc@853: return FAILED; adamc@853: } adamc@853: ++part; adamc@853: if (*part != '\n') { adamc@856: log_error(logger_data, "No \\n after multipart boundary\n"); adamc@853: return FAILED; adamc@853: } adamc@853: ++part; adamc@853: adamc@853: if (!(after_sub_headers = strstr(part, "\r\n\r\n"))) { adamc@856: log_error(logger_data, "Missing end of headers after multipart boundary\n"); adamc@853: return FAILED; adamc@853: } adamc@853: after_sub_headers[2] = 0; adamc@853: after_sub_headers += 4; adamc@853: adamc@1134: for (header = part; (after_header = strstr(header, "\r\n")); header = after_header + 2) { adamc@853: char *colon, *after_colon; adamc@853: adamc@853: *after_header = 0; adamc@853: if (!(colon = strchr(header, ':'))) { adamc@856: log_error(logger_data, "Missing colon in multipart sub-header\n"); adamc@853: return FAILED; adamc@853: } adamc@853: *colon++ = 0; adamc@853: if (*colon++ != ' ') { adamc@856: log_error(logger_data, "No space after colon in multipart sub-header\n"); adamc@853: return FAILED; adamc@853: } adamc@853: adamc@853: if (!strcasecmp(header, "Content-Disposition")) { adamc@853: if (strncmp(colon, "form-data; ", 11)) { adamc@856: log_error(logger_data, "Multipart data is not \"form-data\"\n"); adamc@853: return FAILED; adamc@853: } adamc@853: adamc@1134: for (colon += 11; (after_colon = strchr(colon, '=')); colon = after_colon) { adamc@853: char *data; adamc@853: after_colon[0] = 0; adamc@853: if (after_colon[1] != '"') { adamc@856: log_error(logger_data, "Disposition setting is missing initial quote\n"); adamc@853: return FAILED; adamc@853: } adamc@853: data = after_colon+2; adamc@853: if (!(after_colon = strchr(data, '"'))) { adamc@856: log_error(logger_data, "Disposition setting is missing final quote\n"); adamc@853: return FAILED; adamc@853: } adamc@853: after_colon[0] = 0; adamc@853: ++after_colon; adamc@853: if (after_colon[0] == ';' && after_colon[1] == ' ') adamc@853: after_colon += 2; adamc@853: adamc@853: if (!strcasecmp(colon, "name")) adamc@853: name = data; adamc@853: else if (!strcasecmp(colon, "filename")) adamc@853: filename = data; adamc@853: } adamc@853: } else if (!strcasecmp(header, "Content-Type")) { adamc@853: type = colon; adamc@853: } adamc@853: } adamc@853: adamc@854: part = memmem(after_sub_headers, body + body_len - after_sub_headers, boundary, boundary_len); adamc@853: if (!part) { adamc@856: log_error(logger_data, "Missing boundary after multipart payload\n"); adamc@853: return FAILED; adamc@853: } adamc@853: part[-2] = 0; adamc@853: part_len = part - after_sub_headers - 2; adamc@853: part[0] = 0; adamc@853: part += boundary_len; adamc@853: adamc@853: if (filename) { adamc@853: uw_Basis_file f = {filename, type, {part_len, after_sub_headers}}; adamc@853: adamc@853: if (uw_set_file_input(ctx, name, f)) { adamc@856: log_error(logger_data, "%s\n", uw_error_message(ctx)); adamc@853: return FAILED; adamc@853: } adamc@853: } else if (uw_set_input(ctx, name, after_sub_headers)) { adamc@856: log_error(logger_data, "%s\n", uw_error_message(ctx)); adamc@853: return FAILED; adamc@853: } adamc@853: } adamc@853: } adamc@853: else { adamc@854: inputs = is_post ? body : query_string; adamc@853: adamc@853: if (inputs) { adamc@853: char *name, *value; adamc@853: adamc@853: while (*inputs) { adamc@853: name = inputs; adamc@1134: if ((inputs = strchr(inputs, '&'))) adamc@853: *inputs++ = 0; adamc@853: else adamc@853: inputs = strchr(name, 0); adamc@853: adamc@1134: if ((value = strchr(name, '='))) { adamc@853: *value++ = 0; adamc@853: if (uw_set_input(ctx, name, value)) { adamc@856: log_error(logger_data, "%s\n", uw_error_message(ctx)); adamc@853: return FAILED; adamc@853: } adamc@853: } adamc@853: else if (uw_set_input(ctx, name, "")) { adamc@856: log_error(logger_data, "%s\n", uw_error_message(ctx)); adamc@853: return FAILED; adamc@853: } adamc@853: } adamc@853: } adamc@853: } adamc@853: adamc@856: log_debug(logger_data, "Serving URI %s....\n", path); adamc@853: adamc@853: while (1) { adam@1294: if (!had_error) { adam@1294: size_t path_len = strlen(path); adamc@853: adam@1294: on_success(ctx); adamc@853: adam@1294: if (path_len + 1 > rc->path_copy_size) { adam@1294: rc->path_copy_size = path_len + 1; adam@1294: rc->path_copy = realloc(rc->path_copy, rc->path_copy_size); adam@1294: } adam@1294: strcpy(rc->path_copy, path); adam@1308: adam@1308: uw_set_deadline(ctx, uw_time + uw_time_max); adam@1294: fk = uw_begin(ctx, rc->path_copy); adam@1308: } else { adam@1308: uw_set_deadline(ctx, uw_time + uw_time_max); adam@1294: fk = uw_begin_onError(ctx, errmsg); adam@1308: } adam@1294: adamc@1065: if (fk == SUCCESS || fk == RETURN_INDIRECTLY) { adamc@853: uw_commit(ctx); adam@1294: if (uw_has_error(ctx) && !had_error) { adamc@1131: log_error(logger_data, "Fatal error: %s\n", uw_error_message(ctx)); adamc@1131: uw_reset_keep_error_message(ctx); adamc@1131: on_failure(ctx); adam@1294: adam@1294: if (uw_get_app(ctx)->on_error) { adam@1294: had_error = 1; adam@1294: strcpy(errmsg, uw_error_message(ctx)); adam@1294: } else { adam@1294: uw_write_header(ctx, "Content-type: text/html\r\n"); adam@1294: uw_write(ctx, "Fatal Error"); adam@1294: uw_write(ctx, "Fatal error: "); adam@1294: uw_write(ctx, uw_error_message(ctx)); adam@1294: uw_write(ctx, "\n"); adamc@1131: adam@1294: return FAILED; adam@1294: } adamc@1131: } else adam@1294: return had_error ? FAILED : SERVED; adamc@853: } else if (fk == BOUNDED_RETRY) { adamc@853: if (retries_left) { adamc@856: log_debug(logger_data, "Error triggers bounded retry: %s\n", uw_error_message(ctx)); adamc@853: --retries_left; adamc@853: } adamc@853: else { adamc@856: log_error(logger_data, "Fatal error (out of retries): %s\n", uw_error_message(ctx)); adamc@853: adamc@856: try_rollback(ctx, logger_data, log_error); adamc@853: adam@1294: if (!had_error && uw_get_app(ctx)->on_error) { adam@1294: had_error = 1; adam@1294: strcpy(errmsg, uw_error_message(ctx)); adam@1294: } else { adam@1294: uw_reset_keep_error_message(ctx); adam@1294: on_failure(ctx); adam@1294: uw_write_header(ctx, "Content-type: text/plain\r\n"); adam@1294: uw_write(ctx, "Fatal error (out of retries): "); adam@1294: uw_write(ctx, uw_error_message(ctx)); adam@1294: uw_write(ctx, "\n"); adam@1294: adam@1294: return FAILED; adam@1294: } adamc@853: } adamc@853: } else if (fk == UNLIMITED_RETRY) adamc@856: log_debug(logger_data, "Error triggers unlimited retry: %s\n", uw_error_message(ctx)); adamc@853: else if (fk == FATAL) { adamc@856: log_error(logger_data, "Fatal error: %s\n", uw_error_message(ctx)); adamc@853: adamc@856: try_rollback(ctx, logger_data, log_error); adamc@853: adam@1294: if (uw_get_app(ctx)->on_error && !had_error) { adam@1294: had_error = 1; adam@1294: strcpy(errmsg, uw_error_message(ctx)); adam@1294: } else { adam@1294: uw_reset_keep_error_message(ctx); adam@1294: on_failure(ctx); adam@1294: uw_write_header(ctx, "Content-type: text/html\r\n"); adam@1294: uw_write(ctx, "Fatal Error"); adam@1294: uw_write(ctx, "Fatal error: "); adam@1294: uw_write(ctx, uw_error_message(ctx)); adam@1294: uw_write(ctx, "\n"); adamc@853: adam@1294: return FAILED; adam@1294: } adamc@853: } else { adamc@856: log_error(logger_data, "Unknown uw_handle return code!\n"); adamc@853: adamc@856: try_rollback(ctx, logger_data, log_error); adamc@853: adam@1294: if (uw_get_app(ctx)->on_error && !had_error) { adam@1294: had_error = 1; adam@1294: strcpy(errmsg, "Unknown uw_handle return code"); adam@1294: } else { adam@1294: uw_reset_keep_request(ctx); adam@1294: on_failure(ctx); adam@1294: uw_write_header(ctx, "Content-type: text/plain\r\n"); adam@1294: uw_write(ctx, "Unknown uw_handle return code!\n"); adamc@853: adam@1294: return FAILED; adam@1294: } adamc@853: } adamc@853: adamc@856: if (try_rollback(ctx, logger_data, log_error)) adamc@853: return FAILED; adamc@853: adamc@853: uw_reset_keep_request(ctx); adamc@853: } adamc@853: } adamc@853: adamc@856: typedef struct { adamc@1094: uw_app *app; adamc@856: void *logger_data; adamc@856: uw_logger log_error, log_debug; adamc@856: } loggers; adamc@856: adamc@853: void *client_pruner(void *data) { adamc@856: loggers *ls = (loggers *)data; adamc@1094: uw_context ctx = uw_request_new_context(ls->app, ls->logger_data, ls->log_error, ls->log_debug); adamc@853: adamc@853: if (!ctx) adamc@853: exit(1); adamc@853: adamc@853: while (1) { adamc@853: uw_prune_clients(ctx); adamc@853: sleep(5); adamc@853: } adamc@853: }