diff --git a/mongoose.c b/mongoose.c index ad792350729065d40b99151ab05b00a01410b5aa..ad058a7f4cc206c0a43e06a647625155c9af691d 100644 --- a/mongoose.c +++ b/mongoose.c @@ -799,7 +799,7 @@ static void ns_write_to_socket(struct ns_connection *conn) { #endif { n = send(conn->sock, io->buf, io->len, 0); } - DBG(("%p -> %d bytes [%.*s%s]", conn, n, io->len < 40 ? io->len : 40, + DBG(("%p -> %d bytes [%.*s%s]", conn, n, io->len < 40 ? (int) io->len : 40, io->buf, io->len < 40 ? "" : "...")); ns_call(conn, NS_SEND, &n); @@ -1244,15 +1244,18 @@ struct mg_server { // Local endpoint representation union endpoint { - int fd; // Opened regular local file - struct ns_connection *cgi_conn; // CGI socket + int fd; // Opened regular local file + struct ns_connection *nc; // CGI or proxy->target connection }; -enum endpoint_type { EP_NONE, EP_FILE, EP_CGI, EP_USER, EP_PUT, EP_CLIENT }; +enum endpoint_type { + EP_NONE, EP_FILE, EP_CGI, EP_USER, EP_PUT, EP_CLIENT, EP_PROXY +}; #define MG_HEADERS_SENT NSF_USER_1 #define MG_LONG_RUNNING NSF_USER_2 #define MG_CGI_CONN NSF_USER_3 +#define MG_PROXY_CONN NSF_USER_4 struct connection { struct ns_connection *ns_conn; // NOTE(lsm): main.c depends on this order @@ -1265,8 +1268,6 @@ struct connection { int64_t num_bytes_sent; // Total number of bytes sent int64_t cl; // Reply content length, for Range support int request_len; // Request length, including last \r\n after last header - //int flags; // CONN_* flags: CONN_CLOSE, CONN_SPOOL_DONE, etc - //mg_handler_t handler; // Callback for HTTP client }; #define MG_CONN_2_CONN(c) ((struct connection *) ((char *) (c) - \ @@ -1600,6 +1601,11 @@ int mg_printf(struct mg_connection *conn, const char *fmt, ...) { return len; } +static void ns_forward(struct ns_connection *from, struct ns_connection *to) { + ns_send(to, from->recv_iobuf.buf, from->recv_iobuf.len); + iobuf_remove(&from->recv_iobuf, from->recv_iobuf.len); +} + #ifndef MONGOOSE_NO_CGI #ifdef _WIN32 struct threadparam { @@ -1953,14 +1959,14 @@ static void open_cgi_endpoint(struct connection *conn, const char *prog) { if (start_process(conn->server->config_options[CGI_INTERPRETER], prog, blk.buf, blk.vars, dir, fds[1]) > 0) { conn->endpoint_type = EP_CGI; - conn->endpoint.cgi_conn = ns_add_sock(&conn->server->ns_server, + conn->endpoint.nc = ns_add_sock(&conn->server->ns_server, fds[0], conn); - conn->endpoint.cgi_conn->flags |= MG_CGI_CONN; + conn->endpoint.nc->flags |= MG_CGI_CONN; ns_send(conn->ns_conn, cgi_status, sizeof(cgi_status) - 1); conn->mg_conn.status_code = 200; conn->ns_conn->flags |= NSF_BUFFER_BUT_DONT_SEND; // Pass POST data to the CGI process - conn->endpoint.cgi_conn->send_iobuf = conn->ns_conn->recv_iobuf; + conn->endpoint.nc->send_iobuf = conn->ns_conn->recv_iobuf; iobuf_init(&conn->ns_conn->recv_iobuf, 0); } else { closesocket(fds[0]); @@ -1980,8 +1986,7 @@ static void on_cgi_data(struct ns_connection *nc) { if (!conn) return; // Copy CGI data from CGI socket to the client send buffer - ns_send(conn->ns_conn, nc->recv_iobuf.buf, nc->recv_iobuf.len); - iobuf_remove(&nc->recv_iobuf, nc->recv_iobuf.len); + ns_forward(nc, conn->ns_conn); // If reply has not been parsed yet, parse it if (conn->ns_conn->flags & NSF_BUFFER_BUT_DONT_SEND) { @@ -2013,14 +2018,6 @@ static void on_cgi_data(struct ns_connection *nc) { conn->ns_conn->flags &= ~NSF_BUFFER_BUT_DONT_SEND; } } - -static void forward_post_data(struct connection *conn) { - struct iobuf *io = &conn->ns_conn->recv_iobuf; - if (conn->endpoint.cgi_conn != NULL) { - ns_send(conn->endpoint.cgi_conn, io->buf, io->len); - iobuf_remove(io, io->len); - } -} #endif // !MONGOOSE_NO_CGI static char *mg_strdup(const char *str) { @@ -2164,7 +2161,9 @@ static int parse_http_message(char *buf, int len, struct mg_connection *ri) { } n = (int) strlen(ri->uri); mg_url_decode(ri->uri, n, (char *) ri->uri, n + 1, 0); - remove_double_dots_and_double_slashes((char *) ri->uri); + if (*ri->uri == '/' || *ri->uri == '.') { + remove_double_dots_and_double_slashes((char *) ri->uri); + } } return len; @@ -3397,7 +3396,7 @@ static void handle_put(struct connection *conn, const char *path) { #endif send_http_error(conn, 500, "open(%s): %s", path, strerror(errno)); } else { - DBG(("PUT [%s] %d", path, conn->ns_conn->recv_iobuf.len)); + DBG(("PUT [%s] %zu", path, conn->ns_conn->recv_iobuf.len)); conn->endpoint_type = EP_PUT; ns_set_close_on_exec(conn->endpoint.fd); range = mg_get_header(&conn->mg_conn, "Content-Range"); @@ -3980,6 +3979,62 @@ static void handle_ssi_request(struct connection *conn, const char *path) { } #endif +static int parse_url(const char *url, char *proto, size_t plen, + char *host, size_t hlen, unsigned short *port) { + int n; + char fmt1[100], fmt2[100], fmt3[100]; + + *port = 80; + proto[0] = host[0] = '\0'; + + snprintf(fmt1, sizeof(fmt1), "%%%zu[a-z]://%%%zu[^: ]:%%hu%%n", plen, hlen); + snprintf(fmt2, sizeof(fmt2), "%%%zu[a-z]://%%%zu[^/ ]%%n", plen, hlen); + snprintf(fmt3, sizeof(fmt3), "%%%zu[^: ]:%%hu%%n", hlen); + + if (sscanf(url, fmt1, proto, host, port, &n) == 3 || + sscanf(url, fmt2, proto, host, &n) == 2 || + sscanf(url, fmt3, host, port, &n) == 2) { + return n; + } + + return 0; +} + +static void proxify_connection(struct connection *conn) { + char proto[10], host[500]; + unsigned short port; + struct mg_connection *c = &conn->mg_conn; + struct ns_server *server = &conn->server->ns_server; + struct ns_connection *pc; + int i; + + if (parse_url(c->uri, proto, sizeof(proto), host, sizeof(host), &port) && + (pc = ns_connect(server, host, port, 0, conn)) != NULL) { + // Interlink two connections + pc->flags |= MG_PROXY_CONN; + conn->endpoint_type = EP_PROXY; + conn->endpoint.nc = pc; + DBG(("%p [%s] -> %p", conn, c->uri, pc)); + + if (strcmp(c->request_method, "CONNECT") == 0) { + // For CONNECT request, reply with 200 OK. Tunnel is established. + mg_printf(c, "%s", "HTTP/1.1 200 OK\r\n\r\n"); + } else { + // For other methods, forward the request to the target host. + ns_printf(pc, "%s %s HTTP/%s\r\n", c->request_method, c->uri, + c->http_version); + for (i = 0; i < c->num_headers; i++) { + ns_printf(pc, "%s: %s\r\n", c->http_headers[i].name, + c->http_headers[i].value); + } + ns_printf(pc, "%s", "\r\n"); + ns_send(pc, c->content, c->content_len); + } + } else { + conn->ns_conn->flags |= NSF_CLOSE_IMMEDIATELY; + } +} + static void open_local_endpoint(struct connection *conn, int skip_user) { #ifndef MONGOOSE_NO_FILESYSTEM file_stat_t st; @@ -4024,6 +4079,12 @@ static void open_local_endpoint(struct connection *conn, int skip_user) { return; } + if (strcmp(conn->mg_conn.request_method, "CONNECT") == 0 || + memcmp(conn->mg_conn.uri, "http", 4) == 0) { + proxify_connection(conn); + return; + } + #ifdef MONGOOSE_NO_FILESYSTEM if (!strcmp(conn->mg_conn.request_method, "OPTIONS")) { send_options(conn); @@ -4105,10 +4166,13 @@ static void send_continue_if_expected(struct connection *conn) { } } +// Conform to http://www.w3.org/Protocols/rfc2616/rfc2616-sec5.html#sec5.1.2 static int is_valid_uri(const char *uri) { - // Conform to http://www.w3.org/Protocols/rfc2616/rfc2616-sec5.html#sec5.1.2 - // URI can be an asterisk (*) or should start with slash. - return uri[0] == '/' || (uri[0] == '*' && uri[1] == '\0'); + unsigned short n; + return uri[0] == '/' || + strcmp(uri, "*") == 0 || // OPTIONS method can use asterisk URI + memcmp(uri, "http", 4) == 0 || // Naive check for the absolute URI + sscanf(uri, "%*[^ :]:%hu", &n) > 0; // CONNECT method can use host:port } static void try_parse(struct connection *conn) { @@ -4133,12 +4197,12 @@ static void try_parse(struct connection *conn) { } } -static void process_request(struct connection *conn) { +static void on_recv_data(struct connection *conn) { struct iobuf *io = &conn->ns_conn->recv_iobuf; try_parse(conn); - DBG(("%p %d %d %d [%.*s]", conn, conn->request_len, io->len, - conn->ns_conn->flags, io->len, io->buf)); + DBG(("%p %d %zu %d [%.*s]", conn, conn->request_len, io->len, + conn->ns_conn->flags, (int) io->len, io->buf)); if (conn->request_len < 0 || (conn->request_len > 0 && !is_valid_uri(conn->mg_conn.uri))) { send_http_error(conn, 400, NULL); @@ -4156,9 +4220,12 @@ static void process_request(struct connection *conn) { open_local_endpoint(conn, 0); } + if (conn->endpoint_type == EP_PROXY && conn->endpoint.nc != NULL) { + ns_forward(conn->ns_conn, conn->endpoint.nc); + } #ifndef MONGOOSE_NO_CGI - if (conn->endpoint_type == EP_CGI && io->len > 0) { - forward_post_data(conn); + if (conn->endpoint_type == EP_CGI && conn->endpoint.nc != NULL) { + ns_forward(conn->ns_conn, conn->endpoint.nc); } #endif if (conn->endpoint_type == EP_USER) { @@ -4192,8 +4259,8 @@ static void process_response(struct connection *conn) { struct iobuf *io = &conn->ns_conn->recv_iobuf; try_parse(conn); - DBG(("%p %d %d [%.*s]", conn, conn->request_len, io->len, - io->len > 40 ? 40 : io->len, io->buf)); + DBG(("%p %d %zu [%.*s]", conn, conn->request_len, io->len, + io->len > 40 ? 40 : (int) io->len, io->buf)); if (conn->request_len < 0 || (conn->request_len == 0 && io->len > MAX_REQUEST_SIZE)) { call_http_client_handler(conn); @@ -4283,9 +4350,10 @@ static void close_local_endpoint(struct connection *conn) { close(conn->endpoint.fd); break; case EP_CGI: - if (conn->endpoint.cgi_conn != NULL) { - conn->endpoint.cgi_conn->flags |= NSF_CLOSE_IMMEDIATELY; - conn->endpoint.cgi_conn->connection_data = NULL; + case EP_PROXY: + if (conn->endpoint.nc != NULL) { + conn->endpoint.nc->flags |= NSF_CLOSE_IMMEDIATELY; + conn->endpoint.nc->connection_data = NULL; } break; default: break; @@ -4311,7 +4379,7 @@ static void close_local_endpoint(struct connection *conn) { free(conn->path_info); conn->path_info = NULL; if (keep_alive) { - process_request(conn); // Can call us recursively if pipelining is used + on_recv_data(conn); // Can call us recursively if pipelining is used } else { conn->ns_conn->flags |= conn->ns_conn->send_iobuf.len == 0 ? NSF_CLOSE_IMMEDIATELY : NSF_FINISHED_SENDING_DATA; @@ -4570,14 +4638,16 @@ const char *mg_set_option(struct mg_server *server, const char *name, return error_msg; } -static void set_ips(struct connection *conn, int is_rem) { +static void set_ips(struct ns_connection *nc, int is_rem) { + struct connection *conn = (struct connection *) nc->connection_data; struct mg_connection *c = &conn->mg_conn; char buf[100]; - ns_sock_to_str(conn->ns_conn->sock, buf, sizeof(buf), is_rem ? 7 : 3); + ns_sock_to_str(nc->sock, buf, sizeof(buf), is_rem ? 7 : 3); sscanf(buf, "%47[^:]:%hu", is_rem ? c->remote_ip : c->local_ip, is_rem ? &c->remote_port : &c->local_port); + //DBG(("%p %s %s", conn, is_rem ? "rem" : "loc", buf)); } static void on_accept(struct ns_connection *nc, union socket_address *sa) { @@ -4596,27 +4666,32 @@ static void on_accept(struct ns_connection *nc, union socket_address *sa) { // Initialize the rest of connection attributes conn->server = server; conn->mg_conn.server_param = nc->server->server_data; - set_ips(conn, 1); - set_ips(conn, 0); + set_ips(nc, 1); + set_ips(nc, 0); } } #ifndef MONGOOSE_NO_FILESYSTEM +static void print_hexdump_header(FILE *fp, struct ns_connection *nc, + int num_bytes, const char *marker) { + struct connection *mc = (struct connection *) nc->connection_data; + fprintf(fp, "%lu %s:%d %s %s:%d %d\n", (unsigned long) time(NULL), + mc->mg_conn.local_ip, mc->mg_conn.local_port, marker, + mc->mg_conn.remote_ip, mc->mg_conn.remote_port, num_bytes); +} + static void hexdump(struct ns_connection *nc, const char *path, int num_bytes, int is_sent) { - struct connection *mc = (struct connection *) nc->connection_data; const struct iobuf *io = is_sent ? &nc->send_iobuf : &nc->recv_iobuf; FILE *fp; char *buf; int buf_size = num_bytes * 5 + 100; - if (path != NULL && num_bytes > 0 && (fp = fopen(path, "a")) != NULL) { - fprintf(fp, "%lu %s:%d %s %s:%d %d\n", (unsigned long) time(NULL), - mc->mg_conn.local_ip, mc->mg_conn.local_port, - is_sent ? "->" : "<-", - mc->mg_conn.remote_ip, mc->mg_conn.remote_port, - num_bytes); - if ((buf = (char *) malloc(buf_size)) != NULL) { + if (path != NULL && (fp = fopen(path, "a")) != NULL) { + print_hexdump_header(fp, nc, num_bytes, + is_sent == 0 ? "<-" : is_sent == 1 ? "->" : + is_sent == 2 ? "<A" : "C>"); + if (num_bytes > 0 && (buf = (char *) malloc(buf_size)) != NULL) { ns_hexdump(io->buf + (is_sent ? 0 : io->len) - (is_sent ? 0 : num_bytes), num_bytes, buf, buf_size); fprintf(fp, "%s", buf); @@ -4644,6 +4719,9 @@ static void mg_ev_handler(struct ns_connection *nc, enum ns_event ev, void *p) { switch (ev) { case NS_ACCEPT: on_accept(nc, (union socket_address *) p); +#ifndef MONGOOSE_NO_FILESYSTEM + hexdump(nc, server->config_options[HEXDUMP_FILE], 0, 2); +#endif #ifdef MONGOOSE_SEND_NS_EVENTS { struct connection *conn = (struct connection *) nc->connection_data; @@ -4654,9 +4732,15 @@ static void mg_ev_handler(struct ns_connection *nc, enum ns_event ev, void *p) { break; case NS_CONNECT: + set_ips(nc, 1); + set_ips(nc, 0); +#ifndef MONGOOSE_NO_FILESYSTEM + hexdump(nc, server->config_options[HEXDUMP_FILE], 0, 3); +#endif conn->mg_conn.status_code = * (int *) p; if (conn->mg_conn.status_code != 0 || - call_user(conn, MG_CONNECT) == MG_FALSE) { + (!(nc->flags & MG_PROXY_CONN) && + call_user(conn, MG_CONNECT) == MG_FALSE)) { nc->flags |= NSF_CLOSE_IMMEDIATELY; } break; @@ -4666,11 +4750,15 @@ static void mg_ev_handler(struct ns_connection *nc, enum ns_event ev, void *p) { hexdump(nc, server->config_options[HEXDUMP_FILE], * (int *) p, 0); #endif if (nc->flags & NSF_ACCEPTED) { - process_request(conn); + on_recv_data(conn); #ifndef MONGOOSE_NO_CGI } else if (nc->flags & MG_CGI_CONN) { on_cgi_data(nc); #endif + } else if (nc->flags & MG_PROXY_CONN) { + if (conn != NULL) { + ns_forward(nc, conn->ns_conn); + } } else { process_response(conn); } @@ -4684,11 +4772,14 @@ static void mg_ev_handler(struct ns_connection *nc, enum ns_event ev, void *p) { case NS_CLOSE: nc->connection_data = NULL; - if ((nc->flags & MG_CGI_CONN) && conn && conn->ns_conn) { - conn->ns_conn->flags &= ~NSF_BUFFER_BUT_DONT_SEND; - conn->ns_conn->flags |= conn->ns_conn->send_iobuf.len > 0 ? - NSF_FINISHED_SENDING_DATA : NSF_CLOSE_IMMEDIATELY; - conn->endpoint.cgi_conn = NULL; + if ((nc->flags & MG_CGI_CONN) || (nc->flags & MG_PROXY_CONN)) { + DBG(("%p %d closing cgi/proxy conn", conn, conn->endpoint_type)); + if (conn && conn->ns_conn) { + conn->ns_conn->flags &= ~NSF_BUFFER_BUT_DONT_SEND; + conn->ns_conn->flags |= conn->ns_conn->send_iobuf.len > 0 ? + NSF_FINISHED_SENDING_DATA : NSF_CLOSE_IMMEDIATELY; + conn->endpoint.nc = NULL; + } } else if (conn != NULL) { DBG(("%p %d closing", conn, conn->endpoint_type));