static int http_uri_finalize(struct http_uri *uri) { if (!uri->port && uri->scheme) { if (strcasecmp(uri->scheme, "http") == 0) { uri->port = http_strdup("80"); } else if (strcasecmp(uri->scheme, "https") == 0) { uri->port = http_strdup("443"); } } return 0; }
void http_uri_set_fragment(struct http_uri *uri, const char *fragment) { if (uri->fragment) http_free(uri->fragment); uri->fragment = http_strdup(fragment); }
void http_uri_set_port(struct http_uri *uri, const char *port) { if (uri->port) http_free(uri->port); uri->port = http_strdup(port); }
void http_uri_set_path(struct http_uri *uri, const char *path) { if (uri->path) http_free(uri->path); uri->path = http_strdup(path); }
void http_uri_set_password(struct http_uri *uri, const char *password) { if (uri->password) http_free(uri->password); uri->password = http_strdup(password); }
void http_uri_set_host(struct http_uri *uri, const char *host) { if (uri->host) http_free(uri->host); uri->host = http_strdup(host); }
void http_uri_set_user(struct http_uri *uri, const char *user) { if (uri->user) http_free(uri->user); uri->user = http_strdup(user); }
void http_uri_set_scheme(struct http_uri *uri, const char *scheme) { if (uri->scheme) http_free(uri->scheme); uri->scheme = http_strdup(scheme); }
void http_uri_add_query_parameter(struct http_uri *uri, const char *name, const char *value) { struct http_query_parameter *parameters, *parameter; if (uri->nb_query_parameters == 0) { parameters = http_malloc(sizeof(struct http_query_parameter)); } else { size_t nsz; nsz = (uri->nb_query_parameters + 1) * sizeof(struct http_query_parameter); parameters = http_realloc(uri->query_parameters, nsz); } parameter = parameters + uri->nb_query_parameters; parameter->name = http_strdup(name); parameter->value = http_strdup(value); uri->query_parameters = parameters; uri->nb_query_parameters++; }
struct http_route * http_route_new(enum http_method method, const char *path, http_msg_handler msg_handler) { struct http_route *route; route = http_malloc0(sizeof(struct http_route)); route->method = method; route->path = http_strdup(path); route->msg_handler = msg_handler; if (http_route_components_parse(path, &route->components, &route->nb_components) == -1) { http_route_delete(route); return NULL; } return route; }
static int http_uri_parse(const char *str, struct http_uri *uri) { const char *ptr, *start, *colon; size_t toklen; memset(uri, 0, sizeof(struct http_uri)); ptr = str; if (*ptr == '\0') { http_set_error("empty string"); return -1; } /* Scheme */ start = ptr; if (*ptr == '/') { goto path; } else if (!(*ptr >= 'a' && *ptr <= 'z') && !(*ptr >= 'A' && *ptr <= 'Z') && *ptr != '%') { http_set_error("invalid first character \\%hhu in scheme", (unsigned char)*ptr); return -1; } for (;;) { if (*ptr == ':' || *ptr == '\0') { toklen = (size_t)(ptr - start); uri->scheme = http_uri_decode_component(start, toklen); if (!uri->scheme) return -1; break; } else if (!http_uri_is_scheme_char((unsigned char)*ptr) && *ptr != '%') { http_set_error("invalid character \\%hhu in scheme", (unsigned char)*ptr); return -1; } ptr++; } /* Skip '://' */ if (ptr[0] != ':' || ptr[1] != '/' || ptr[2] != '/') { http_set_error("invalid characters after scheme"); return -1; } ptr += 3; /* User (optional) */ start = ptr; colon = NULL; while (*ptr != '\0') { if (*ptr == ':') { colon = ptr; } else if (*ptr == '@') { if (colon) { toklen = (size_t)(colon - start); } else { toklen = (size_t)(ptr - start); } uri->user = http_uri_decode_component(start, toklen); if (!uri->user) return -1; if (colon) ptr = colon; break; } else if (*ptr == '/') { /* End of authority, no user found */ break; } ptr++; } if (!uri->user) { /* Since we did not find a username, we backtrack to read the host. */ ptr = start; } /* Password (optional) */ if (uri->user && *ptr == ':') { start = ptr; for (;;) { if (*ptr == '@' || *ptr == '\0') { toklen = (size_t)(ptr - start - 1); if (toklen == 0) { http_set_error("empty password"); return -1; } uri->password = http_uri_decode_component(start + 1, toklen); if (!uri->password) return -1; break; } else if (*ptr == '/') { /* End of authority, no password found */ break; } ptr++; } if (!uri->password) { http_set_error("empty password"); return -1; } } if (uri->user) { /* Skip '@' */ ptr++; } /* Host */ start = ptr; if (*start >= '0' && *start <= '9') { /* IPv4 address */ for (;;) { if (*ptr == '/' || *ptr == ':' || *ptr == '\0') { toklen = (size_t)(ptr - start); if (toklen == 0) { http_set_error("empty host"); return -1; } uri->host = http_uri_decode_component(start, toklen); if (!uri->host) return -1; break; } else if (!http_uri_is_ipv4_addr_char((unsigned char)*ptr)) { http_set_error("invalid character \\%hhu in ipv4 address", (unsigned char)*ptr); return -1; } ptr++; } } else if (*start == '[') { ptr++; /* '[' */ start = ptr; /* IPv6 address */ for (;;) { if (*ptr == ']') { toklen = (size_t)(ptr - start); if (toklen == 0) { http_set_error("empty host"); return -1; } uri->host = http_uri_decode_component(start, toklen); if (!uri->host) return -1; ptr++; /* ']' */ break; } else if (*ptr == '\0') { http_set_error("truncated ipv6 address"); return -1; } else if (!http_uri_is_ipv6_addr_char((unsigned char)*ptr)) { http_set_error("invalid character \\%hhu in ipv6 address", (unsigned char)*ptr); return -1; } ptr++; } } else { /* Hostname */ for (;;) { if (*ptr == '/' || *ptr == ':' || *ptr == '#' || *ptr == '\0') { toklen = (size_t)(ptr - start); if (toklen == 0) { http_set_error("empty host"); return -1; } uri->host = http_uri_decode_component(start, toklen); if (!uri->host) return -1; break; } ptr++; } } /* Port (optional) */ if (*ptr == ':') { ptr++; start = ptr; for (;;) { if (*ptr == '/' || *ptr == '#' || *ptr == '\0') { toklen = (size_t)(ptr - start); if (toklen == 0) { http_set_error("empty port"); return -1; } uri->port = http_uri_decode_component(start, toklen); if (!uri->port) return -1; break; } else if (!http_uri_is_port_char((unsigned char)*ptr)) { http_set_error("invalid character \\%hhu in port", (unsigned char)*ptr); return -1; } ptr++; } } /* Path (optional, default '/') */ path: if (*ptr == '/') { start = ptr; for (;;) { if (*ptr == '?' || *ptr == '#' || *ptr == '\0') { toklen = (size_t)(ptr - start); uri->path = http_uri_decode_component(start, toklen); if (!uri->path) return -1; break; } ptr++; } } else { uri->path = http_strdup("/"); } /* Query (optional) */ if (*ptr == '?') { char *query; ptr++; start = ptr; while (*ptr != '#' && *ptr != '\0') ptr++; toklen = (size_t)(ptr - start); query = http_strndup(start, toklen); if (http_query_parameters_parse(query, &uri->query_parameters, &uri->nb_query_parameters) == -1) { http_free(query); return -1; } http_free(query); } /* Fragment (optional) */ if (*ptr == '#') { ptr++; start = ptr; while (*ptr != '\0') ptr++; toklen = (size_t)(ptr - start); uri->fragment = http_uri_decode_component(start, toklen); if (!uri->fragment) return -1; } if (http_uri_finalize(uri) == -1) return -1; return 1; }
int http_route_base_find_route(struct http_route_base *base, enum http_method method, const char *path, const struct http_route **proute, enum http_route_match_result *p_match_result, struct http_named_parameter **p_named_parameters, size_t *p_nb_named_parameters) { struct http_route *route; struct http_named_parameter *named_parameters; size_t nb_named_parameters; enum http_route_match_result match_result; char **path_components; size_t nb_path_components, idx; if (!base->sorted) http_route_base_sort_routes(base); if (*path != '/') { *proute = NULL; *p_match_result = HTTP_ROUTE_MATCH_WRONG_PATH; return 0; } if (http_path_parse(path, &path_components, &nb_path_components) == -1) return -1; route = NULL; match_result = HTTP_ROUTE_MATCH_PATH_NOT_FOUND; for (size_t i = 0; i < base->nb_routes; i++) { enum http_route_match_result result; if (http_route_matches_request(base->routes[i], method, path_components, nb_path_components, &result)) { route = base->routes[i]; match_result = HTTP_ROUTE_MATCH_OK; break; } if (result == HTTP_ROUTE_MATCH_METHOD_NOT_FOUND) { match_result = HTTP_ROUTE_MATCH_METHOD_NOT_FOUND; } else if (result == HTTP_ROUTE_MATCH_PATH_NOT_FOUND && match_result != HTTP_ROUTE_MATCH_METHOD_NOT_FOUND) { match_result = HTTP_ROUTE_MATCH_PATH_NOT_FOUND; } } if (!route) { *proute = NULL; *p_match_result = match_result; http_path_free(path_components, nb_path_components); return 0; } if (route->nb_components != nb_path_components) { /* We made a mistake somewhere, it should not happen */ http_set_error("route/path size mismatch"); return -1; } /* Copy named parameters */ nb_named_parameters = 0; for (size_t i = 0; i < route->nb_components; i++) { if (route->components[i].type == HTTP_ROUTE_COMPONENT_NAMED) nb_named_parameters++; } if (p_named_parameters && nb_named_parameters > 0) { named_parameters = http_calloc(nb_named_parameters, sizeof(struct http_named_parameter)); idx = 0; for (size_t i = 0; i < route->nb_components; i++) { struct http_route_component *component; component = route->components + i; if (component->type != HTTP_ROUTE_COMPONENT_NAMED) continue; named_parameters[idx].name = http_strdup(component->value); named_parameters[idx].value = http_strdup(path_components[i]); idx++; } } else { named_parameters = NULL; } http_path_free(path_components, nb_path_components); *proute = route; *p_match_result = match_result; if (p_named_parameters) *p_named_parameters = named_parameters; if (p_nb_named_parameters) *p_nb_named_parameters = nb_named_parameters; return 0; }
int http_header_parse(http_header_t *http, char *buf, size_t len) { char *ptr, *end=buf+len, *p, *q, *word=0; int is_space, is_nl; int line; char *line_ptr; http_keyval_t *hdr = 0; http_state_t state, state_next; http_header_clear(http); line = 0; line_ptr = buf; state = state_next = HTTP_STATE_INIT; #define HTTP_HEADER_PARSE_ERROR \ http->error_state = state; \ http->error_line = line; \ http->error_col = ptr-line_ptr; \ state = HTTP_STATE_ERROR; \ break; for(ptr=buf; ptr<end && state!=HTTP_STATE_DONE && state!=HTTP_STATE_ERROR; ptr++) { /* ignore '\r' */ if( *ptr == '\r' ) { continue; } is_nl = *ptr == '\n'; is_space = *ptr == ' ' || *ptr == '\t'; if( is_nl ) { line++; line_ptr = ptr; } /* debug(DEBUG_INFO, ("http_header_parse: state=%d line=%d col=%d\n", state, line, ptr-line_ptr)); */ switch(state) { case HTTP_STATE_INIT: { /* skip spaces and newlines */ if( !is_space && !is_nl ) { state = HTTP_STATE_OP; word = ptr; ptr--; } break; } case HTTP_STATE_SPACE: { /* skip spaces, not newlines */ if( !is_space ) { state = state_next; word = ptr; ptr--; } break; } case HTTP_STATE_OP: { if( is_nl ) { HTTP_HEADER_PARSE_ERROR; } if( is_space ) { http->op = http_strdup(http, word, ptr-word); if( strncmp(http->op, "HTTP/", 5)==0 ) { http->http_version = http->op; http->op = 0; http->op_code = HTTP_OP_RESPONSE; state = HTTP_STATE_SPACE; state_next = HTTP_STATE_RESPONSE_CODE; } else { if( strcmp(http->op, "GET")==0 ) { http->op_code = HTTP_OP_GET; } else if( strcmp(http->op, "PUT")==0 ) { http->op_code = HTTP_OP_PUT; } else if( strcmp(http->op, "POST")==0 ) { http->op_code = HTTP_OP_POST; } else { http->op_code = HTTP_OP_UNKNOWN; } state = HTTP_STATE_SPACE; state_next = HTTP_STATE_URI; } } break; } case HTTP_STATE_RESPONSE_CODE: { if( is_space || is_nl ) { http->response_code = strtoul(word, &q, 0); if( q<=word ) { HTTP_HEADER_PARSE_ERROR; } state = HTTP_STATE_SPACE; state_next = HTTP_STATE_RESPONSE_MESSAGE; if( is_nl ) { /* no message */ state = HTTP_STATE_HEADER; } } break; } case HTTP_STATE_RESPONSE_MESSAGE: { if( is_nl ) { http->response_message = http_strdup(http, word, ptr-word); state = HTTP_STATE_HEADER; } break; } case HTTP_STATE_URI: { if( is_space || is_nl ) { http->uri = http_strdup(http, word, ptr-word); state = HTTP_STATE_SPACE; state_next = HTTP_STATE_VERSION; } if( is_nl ) { state_next = HTTP_STATE_HEADER; } break; } case HTTP_STATE_VERSION: { if( is_space || is_nl ) { http->http_version = http_strdup(http, word, ptr-word); if( is_nl ) { state = HTTP_STATE_HEADER; } else { state = HTTP_STATE_SPACE; state_next = HTTP_STATE_HEADER_BOL; } } break; } case HTTP_STATE_HEADER_BOL: { if( !is_nl ) { HTTP_HEADER_PARSE_ERROR; } state = HTTP_STATE_HEADER; break; } case HTTP_STATE_HEADER: { if( is_space && !is_nl ) { /* add to previous header */ state = HTTP_STATE_HEADER_VAL; } else { /* finish off the last header */ if( http->headers_last ) { http->headers_last->val = http_strdup(http, word, ptr-word); } if( is_nl ) { /* done! */ state = HTTP_STATE_DONE; } else { /* start new header */ hdr = pool_malloc(http->pool, sizeof(*hdr)); if( http->headers_last ) { http->headers_last->next = hdr; } else { http->headers = hdr; } http->headers_last = hdr; word = ptr; state = HTTP_STATE_HEADER_NAME; } } break; } case HTTP_STATE_HEADER_NAME: { if( !(isalnum(*ptr) || *ptr == '-') ) { if( *ptr != ':' ) { HTTP_HEADER_PARSE_ERROR; } http->headers_last->key = http_strdup(http, word, ptr-word); state = HTTP_STATE_SPACE; state_next = HTTP_STATE_HEADER_VAL; } break; } case HTTP_STATE_HEADER_VAL: { if( is_nl ) { state = HTTP_STATE_HEADER; } break; } case HTTP_STATE_DONE: { break; } default: break; } } if( state == HTTP_STATE_ERROR ) { return -http->error_state; } if( state != HTTP_STATE_DONE ) { http_header_clear(http); return 0; } http->header_len = ptr-buf; http->total_len = http->header_len; /* done! for convenience, set up content length etc */ http->content_ptr = ptr; p = http_keyval_get(http->headers, "content-length", 0); http->content_len = 0; if( p ) { http->content_len = strtoul(p, &q, 0); http->total_len += http->content_len; } http->content_type = http_keyval_get(http->headers, "content-type", 0); return 1; }