/* * This function allow the core to invoke the closing connection process * when some connection was not proceesed due to a premature close or similar * exception, it also take care of invoke the STAGE_40 and STAGE_50 plugins events */ static void mk_request_premature_close(int http_status, struct mk_http_session *cs) { struct mk_http_request *sr; struct mk_list *sr_list = &cs->request_list; struct mk_list *host_list = &mk_config->hosts; /* * If the connection is too premature, we need to allocate a temporal session_request * to do not break the plugins stages */ if (mk_list_is_empty(sr_list) == 0) { sr = &cs->sr_fixed; memset(sr, 0, sizeof(struct mk_http_request)); mk_http_request_init(cs, sr); mk_list_add(&sr->_head, &cs->request_list); } else { sr = mk_list_entry_first(sr_list, struct mk_http_request, _head); } /* Raise error */ if (http_status > 0) { if (!sr->host_conf) { sr->host_conf = mk_list_entry_first(host_list, struct host, _head); } mk_http_error(http_status, cs, sr); /* STAGE_40, request has ended */ mk_plugin_stage_run_40(cs, sr); }
/* * This function is invoked everytime the parser evaluate the request is * OK. Here we perform some extra validations mostly based on some logic * and protocol requirements according to the data received. */ static inline int mk_http_parser_ok(struct mk_http_request *req, struct mk_http_parser *p) { /* Validate HTTP Version */ if (req->protocol == MK_HTTP_PROTOCOL_UNKNOWN) { mk_http_error(MK_SERVER_HTTP_VERSION_UNSUP, req->session, req); return MK_HTTP_PARSER_ERROR; } /* POST checks */ if (req->method == MK_METHOD_POST || req->method == MK_METHOD_PUT) { /* validate Content-Length exists */ if (p->headers[MK_HEADER_CONTENT_LENGTH].type == 0) { mk_http_error(MK_CLIENT_LENGTH_REQUIRED, req->session, req); return MK_HTTP_PARSER_ERROR; } } return MK_HTTP_PARSER_OK; }
static int mk_http_request_prepare(struct mk_http_session *cs, struct mk_http_request *sr) { int status = 0; char *temp; struct mk_list *hosts = &mk_config->hosts; struct mk_list *alias; struct mk_http_header *header; /* * Process URI, if it contains ASCII encoded strings like '%20', * it will return a new memory buffer with the decoded string, otherwise * it returns NULL */ temp = mk_utils_url_decode(sr->uri); if (temp) { sr->uri_processed.data = temp; sr->uri_processed.len = strlen(temp); } else { sr->uri_processed.data = sr->uri.data; sr->uri_processed.len = sr->uri.len; } /* Always assign the default vhost' */ sr->host_conf = mk_list_entry_first(hosts, struct host, _head); sr->user_home = MK_FALSE; /* Valid request URI? */ if (sr->uri_processed.data[0] != '/') { mk_http_error(MK_CLIENT_BAD_REQUEST, cs, sr); return MK_EXIT_OK; } /* Check if we have a Host header: Hostname ; port */ mk_http_point_header(&sr->host, &cs->parser, MK_HEADER_HOST); /* Header: Connection */ mk_http_point_header(&sr->connection, &cs->parser, MK_HEADER_CONNECTION); /* Header: Range */ mk_http_point_header(&sr->range, &cs->parser, MK_HEADER_RANGE); /* Header: If-Modified-Since */ mk_http_point_header(&sr->if_modified_since, &cs->parser, MK_HEADER_IF_MODIFIED_SINCE); /* HTTP/1.1 needs Host header */ if (!sr->host.data && sr->protocol == MK_HTTP_PROTOCOL_11) { mk_http_error(MK_CLIENT_BAD_REQUEST, cs, sr); return MK_EXIT_OK; } /* Should we close the session after this request ? */ mk_http_keepalive_check(cs, sr); /* Content Length */ header = &cs->parser.headers[MK_HEADER_CONTENT_LENGTH]; if (header->type == MK_HEADER_CONTENT_LENGTH) { sr->_content_length.data = header->val.data; sr->_content_length.len = header->val.len; } else { sr->_content_length.data = NULL; } /* Assign the first node alias */ alias = &sr->host_conf->server_names; sr->host_alias = mk_list_entry_first(alias, struct host_alias, _head); if (sr->host.data) { /* Set the given port */ if (cs->parser.header_host_port > 0) { sr->port = cs->parser.header_host_port; } /* Match the virtual host */ mk_vhost_get(sr->host, &sr->host_conf, &sr->host_alias); /* Check if this virtual host have some redirection */ if (sr->host_conf->header_redirect.data) { mk_header_set_http_status(sr, MK_REDIR_MOVED); sr->headers.location = mk_string_dup(sr->host_conf->header_redirect.data); sr->headers.content_length = 0; sr->headers.location = NULL; mk_header_prepare(cs, sr); return 0; } } /* Is requesting an user home directory ? */ if (mk_config->user_dir && sr->uri_processed.len > 2 && sr->uri_processed.data[1] == MK_USER_HOME) { if (mk_user_init(cs, sr) != 0) { mk_http_error(MK_CLIENT_NOT_FOUND, cs, sr); return MK_EXIT_ABORT; } } /* Plugins Stage 20 */ int ret; ret = mk_plugin_stage_run_20(cs, sr); if (ret == MK_PLUGIN_RET_CLOSE_CONX) { MK_TRACE("STAGE 20 requested close conexion"); return MK_EXIT_ABORT; } /* Normal HTTP process */ status = mk_http_init(cs, sr); MK_TRACE("[FD %i] HTTP Init returning %i", cs->socket, status); return status; }
/* * Parse the protocol and point relevant fields, don't take logic decisions * based on this, just parse to locate things. */ int mk_http_parser(struct mk_http_request *req, struct mk_http_parser *p, char *buffer, int buf_len, struct mk_server *server) { int s; int tmp; int ret; int len; /* lazy test printf("p->i=%i buf_len=%i\n", p->i, buf_len); for (s = p->i; s < buf_len; s++) { if (buffer[s] == '\r') { printf("CR"); } else if (buffer[s] == '\n') { printf("LF"); } else { printf("%c", buffer[s]); } } printf("\n"); */ len = buf_len; for (; p->i < len; p->i++, p->chars++) { /* FIRST LINE LEVEL: Method, URI & Protocol */ if (p->level == REQ_LEVEL_FIRST) { switch (p->status) { case MK_ST_REQ_METHOD: /* HTTP Method */ if (p->chars == -1) { switch (buffer[p->i]) { case 'G': p->method = MK_METHOD_GET; break; case 'P': p->method = MK_METHOD_POST; break; case 'H': p->method = MK_METHOD_HEAD; break; case 'D': p->method = MK_METHOD_DELETE; break; case 'O': p->method = MK_METHOD_OPTIONS; break; } continue; } if (buffer[p->i] == ' ') { mark_end(); p->status = MK_ST_REQ_URI; if (p->end < 2) { return MK_HTTP_PARSER_ERROR; } method_lookup(req, p, buffer); start_next(); } else { if ((p->i - p->start) > 10) { return MK_HTTP_PARSER_ERROR; } } break; case MK_ST_REQ_URI: /* URI */ if (buffer[p->i] == ' ') { mark_end(); p->status = MK_ST_REQ_PROT_VERSION; if (field_len() < 1) { return MK_HTTP_PARSER_ERROR; } request_set(&req->uri, p, buffer); start_next(); } else if (buffer[p->i] == '?') { mark_end(); request_set(&req->uri, p, buffer); p->status = MK_ST_REQ_QUERY_STRING; start_next(); } else if (buffer[p->i] == '\r' || buffer[p->i] == '\n') { mk_http_error(MK_CLIENT_BAD_REQUEST, req->session, req, server); return MK_HTTP_PARSER_ERROR; } break; case MK_ST_REQ_QUERY_STRING: /* Query string */ char_lookup(buffer, ' ', len, p); if (buffer[p->i] == ' ') { mark_end(); request_set(&req->query_string, p, buffer); p->status = MK_ST_REQ_PROT_VERSION; start_next(); } else if (buffer[p->i] == '\r' || buffer[p->i] == '\n') { mk_http_error(MK_CLIENT_BAD_REQUEST, req->session, req, server); return MK_HTTP_PARSER_ERROR; } break; case MK_ST_REQ_PROT_VERSION: /* Protocol Version */ /* * Most of the time we already have the string version in our * buffer, for that case try to match the version and avoid * loop rounds. */ if (p->start + 6 >= p->i) { continue; } tmp = p->start; if (buffer[tmp] == 'H' && buffer[tmp + 1] == 'T' && buffer[tmp + 2] == 'T' && buffer[tmp + 3] == 'P' && buffer[tmp + 4] == '/' && buffer[tmp + 5] == '1' && buffer[tmp + 6] == '.') { request_set(&req->protocol_p, p, buffer); req->protocol_p.len = 8; mk_http_set_minor_version(buffer[tmp + 7]); } else { mk_http_error(MK_SERVER_HTTP_VERSION_UNSUP, req->session, req, server); return MK_HTTP_PARSER_ERROR; } p->status = MK_ST_FIRST_CONTINUE; break; case MK_ST_FIRST_CONTINUE: if (buffer[p->i] == '\r') { p->status = MK_ST_FIRST_FINALIZING; } else { return MK_HTTP_PARSER_ERROR; } break; case MK_ST_FIRST_FINALIZING: /* New Line */ if (buffer[p->i] == '\n') { p->level = REQ_LEVEL_CONTINUE; start_next(); } else { return MK_HTTP_PARSER_ERROR; } break; case MK_ST_BLOCK_END: if (buffer[p->i] == '\n') { return mk_http_parser_ok(req, p, server); } else { return MK_HTTP_PARSER_ERROR; } break; }; } else if (p->level == REQ_LEVEL_CONTINUE) { if (buffer[p->i] == '\r') { p->level = REQ_LEVEL_FIRST; p->status = MK_ST_BLOCK_END; } else { p->level = REQ_LEVEL_HEADERS; p->status = MK_ST_HEADER_KEY; p->chars = 0; } } /* HEADERS: all headers stuff */ if (p->level == REQ_LEVEL_HEADERS) { /* Expect a Header key */ if (p->status == MK_ST_HEADER_KEY) { if (buffer[p->i] == '\r') { if (p->chars == 0) { p->level = REQ_LEVEL_END; start_next(); } else { return MK_HTTP_PARSER_ERROR; } } if (p->chars == 0) { /* * We reach the start of a Header row, lets catch the most * probable header. * * The goal of this 'first row character lookup', is to define a * small range set of probable headers comparison once we catch * a header end. */ s = tolower(buffer[p->i]); switch (s) { case 'a': p->header_min = MK_HEADER_ACCEPT; p->header_max = MK_HEADER_AUTHORIZATION; break; case 'c': p->header_min = MK_HEADER_CACHE_CONTROL; p->header_max = MK_HEADER_CONTENT_TYPE; break; case 'h': p->header_min = MK_HEADER_HOST; p->header_max = MK_HEADER_HTTP2_SETTINGS; break; case 'i': header_scope_eq(p, MK_HEADER_IF_MODIFIED_SINCE); break; case 'l': p->header_min = MK_HEADER_LAST_MODIFIED; p->header_max = MK_HEADER_LAST_MODIFIED_SINCE; break; case 'r': p->header_min = MK_HEADER_RANGE; p->header_max = MK_HEADER_REFERER; break; case 'u': p->header_min = MK_HEADER_UPGRADE; p->header_max = MK_HEADER_USER_AGENT; break; default: p->header_key = -1; p->header_sep = -1; p->header_min = -1; p->header_max = -1; }; p->header_key = p->i; continue; } /* Found key/value separator */ char_lookup(buffer, ':', len, p); if (buffer[p->i] == ':') { /* Set the key/value middle point */ p->header_sep = p->i; /* validate length */ mark_end(); if (field_len() < 1) { return MK_HTTP_PARSER_ERROR; } /* Wait for a value */ p->status = MK_ST_HEADER_VALUE; start_next(); } } /* Parsing the header value */ else if (p->status == MK_ST_HEADER_VALUE) { /* Trim left, set starts only when found something != ' ' */ if (buffer[p->i] == '\r' || buffer[p->i] == '\n') { return MK_HTTP_PARSER_ERROR; } else if (buffer[p->i] != ' ') { p->status = MK_ST_HEADER_VAL_STARTS; p->start = p->header_val = p->i; } continue; } /* New header row starts */ else if (p->status == MK_ST_HEADER_VAL_STARTS) { /* Maybe there is no more headers and we reach the end ? */ if (buffer[p->i] == '\r') { mark_end(); if (field_len() <= 0) { return MK_HTTP_PARSER_ERROR; } /* * A header row has ended, lets lookup the header and populate * our headers table index. */ ret = header_lookup(p, buffer); if (ret != 0) { if (ret < -1) { mk_http_error(-ret, req->session, req, server); } return MK_HTTP_PARSER_ERROR; } /* Try to catch next LF */ if (p->i + 1 < len) { if (buffer[p->i + 1] == '\n') { p->i++; p->status = MK_ST_HEADER_KEY; p->chars = -1; start_next(); } } p->status = MK_ST_HEADER_END; start_next(); } else if (buffer[p->i] == '\n' && buffer[p->i - 1] != '\r') { return MK_HTTP_PARSER_ERROR; } } else if (p->status == MK_ST_HEADER_END) { if (buffer[p->i] == '\n') { p->status = MK_ST_HEADER_KEY; p->chars = -1; start_next(); } else { return MK_HTTP_PARSER_ERROR; } } } else if (p->level == REQ_LEVEL_END) { if (buffer[p->i] == '\n') { if (p->header_content_length > 0) { p->level = REQ_LEVEL_BODY; p->chars = -1; start_next(); } else { return mk_http_parser_ok(req, p, server); } } else { return MK_HTTP_PARSER_ERROR; } } else if (p->level == REQ_LEVEL_BODY) { /* * Reaching this level can means two things: * * - A Pipeline Request * - A Body content (POST/PUT methods) */ if (p->header_content_length > 0) { p->body_received = len - p->start; if ((len - p->start) < p->header_content_length) { return MK_HTTP_PARSER_PENDING; } /* Cut off */ p->i += p->body_received; req->data.len = p->body_received; req->data.data = (buffer + p->start); } return mk_http_parser_ok(req, p, server); } } return MK_HTTP_PARSER_PENDING; }