diff --git a/.travis.yml b/.travis.yml index e504f2ea..b61a7650 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,6 +13,8 @@ env: - LWS_METHOD=http2 CMAKE_ARGS="-DLWS_WITH_HTTP2=ON" - LWS_METHOD=nossl CMAKE_ARGS="-DLWS_WITH_SSL=OFF" - LWS_METHOD=nodaemon CMAKE_ARGS="-DLWS_WITHOUT_DAEMONIZE=ON" + - LWS_METHOD=cgi CMAKE_ARGS="-DLWS_WITH_CGI=ON" + os: - linux - osx diff --git a/CMakeLists.txt b/CMakeLists.txt index 490abd6a..7ea0944a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -344,6 +344,7 @@ CHECK_FUNCTION_EXISTS(realloc LWS_HAVE_REALLOC) CHECK_FUNCTION_EXISTS(socket LWS_HAVE_SOCKET) CHECK_FUNCTION_EXISTS(strerror LWS_HAVE_STRERROR) CHECK_FUNCTION_EXISTS(vfork LWS_HAVE_VFORK) +CHECK_FUNCTION_EXISTS(execvpe LWS_HAVE_EXECVPE) CHECK_FUNCTION_EXISTS(getifaddrs LWS_HAVE_GETIFADDRS) CHECK_FUNCTION_EXISTS(snprintf LWS_HAVE_SNPRINTF) CHECK_FUNCTION_EXISTS(_snprintf LWS_HAVE__SNPRINTF) diff --git a/changelog b/changelog index bc580d68..101f57a4 100644 --- a/changelog +++ b/changelog @@ -111,7 +111,8 @@ lws_adopt_socket_readbuf(struct lws_context *context, lws_sockfd_type accept_fd, a simple api. LWS_VISIBLE LWS_EXTERN int -lws_cgi(struct lws *wsi, char * const *exec_array, int timeout_secs); +lws_cgi(struct lws *wsi, char * const *exec_array, int script_uri_path_len, + int timeout_secs); LWS_VISIBLE LWS_EXTERN int lws_cgi_kill(struct lws *wsi); @@ -131,6 +132,9 @@ $ wget http://localhost:7681/cgitest --post-file=hello.txt -O- --quiet lwstest script read="hello" +The test script returns text/html table showing /proc/meminfo. But the cgi +support is complete enough to run cgit cgi. + 4) There is a helper api for forming logging timestamps LWS_VISIBLE int diff --git a/lib/client-handshake.c b/lib/client-handshake.c index b3419a1c..f4018b18 100644 --- a/lib/client-handshake.c +++ b/lib/client-handshake.c @@ -338,7 +338,7 @@ lws_client_reset(struct lws *wsi, int ssl, const char *address, int port, const return lws_client_connect_2(wsi); } - +#ifdef LWS_WITH_HTTP_PROXY static hubbub_error html_parser_cb(const hubbub_token *token, void *pw) { @@ -446,7 +446,7 @@ html_parser_cb(const hubbub_token *token, void *pw) return HUBBUB_OK; } - +#endif /** * lws_client_connect_via_info() - Connect to another websocket server * @i:pointer to lws_client_connect_info struct @@ -560,11 +560,12 @@ lws_client_connect_via_info(struct lws_client_connect_info *i) wsi->sibling_list = i->parent_wsi->child_list; i->parent_wsi->child_list = wsi; } - +#ifdef LWS_WITH_HTTP_PROXY if (i->uri_replace_to) wsi->rw = lws_rewrite_create(wsi, html_parser_cb, i->uri_replace_from, i->uri_replace_to); +#endif return wsi; diff --git a/lib/client.c b/lib/client.c index 77eb7f08..b8262f81 100644 --- a/lib/client.c +++ b/lib/client.c @@ -501,7 +501,7 @@ strtolower(char *s) * transaction if possible */ -LWS_VISIBLE int LWS_WARN_UNUSED_RESULT +int LWS_WARN_UNUSED_RESULT lws_http_transaction_completed_client(struct lws *wsi) { lwsl_debug("%s: wsi %p\n", __func__, wsi); @@ -622,7 +622,7 @@ lws_client_interpret_server_handshake(struct lws *wsi) goto bail2; } -#ifndef LWS_NO_CLIENT +#ifdef LWS_WITH_HTTP_PROXY wsi->perform_rewrite = 0; if (lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_CONTENT_TYPE)) { if (!strncmp(lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP_CONTENT_TYPE), diff --git a/lib/handshake.c b/lib/handshake.c index 6148ba54..d2dd4c76 100644 --- a/lib/handshake.c +++ b/lib/handshake.c @@ -171,7 +171,7 @@ http_postbody: wsi, LWS_CALLBACK_CGI_STDIN_DATA, wsi->user_space, (void *)&args, 0); - if (n < 0) + if ((int)n < 0) goto bail; } else { #endif diff --git a/lib/libwebsockets.c b/lib/libwebsockets.c index 7942ac1d..5e08b998 100644 --- a/lib/libwebsockets.c +++ b/lib/libwebsockets.c @@ -391,12 +391,12 @@ just_kill_connection: lwsl_info("%s: real just_kill_connection: %p (sockfd %d)\n", __func__, wsi, wsi->sock); - +#ifdef LWS_WITH_HTTP_PROXY if (wsi->rw) { lws_rewrite_destroy(wsi->rw); wsi->rw = NULL; } - +#endif /* * we won't be servicing or receiving anything further from this guy * delete socket from the internal poll list if still present @@ -1483,6 +1483,35 @@ lws_socket_bind(struct lws_context *context, int sockfd, int port, return port; } +LWS_VISIBLE LWS_EXTERN int +lws_urlencode(const char *in, int inlen, char *out, int outlen) +{ + const char *hex = "0123456789ABCDEF"; + char *start = out, *end = out + outlen; + + while (inlen-- && out > end - 4) { + if ((*in >= 'A' && *in <= 'Z') || + (*in >= 'a' && *in <= 'z') || + (*in >= '0' && *in <= '9') || + *in == '-' || + *in == '_' || + *in == '.' || + *in == '~') + *out++ = *in++; + else { + *out++ = '%'; + *out++ = hex[(*in) >> 4]; + *out++ = hex[(*in++) & 15]; + } + } + *out = '\0'; + + if (out >= end - 4) + return -1; + + return out - start; +} + LWS_VISIBLE LWS_EXTERN int lws_is_cgi(struct lws *wsi) { #ifdef LWS_WITH_CGI @@ -1546,12 +1575,14 @@ lws_create_basic_wsi(struct lws_context *context, int tsi) */ LWS_VISIBLE LWS_EXTERN int -lws_cgi(struct lws *wsi, char * const *exec_array, int timeout_secs) +lws_cgi(struct lws *wsi, char * const *exec_array, int script_uri_path_len, + int timeout_secs) { struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi]; - char *env_array[30], cgi_path[400]; + char *env_array[30], cgi_path[400], e[1024], *p = e, + *end = p + sizeof(e) - 1, tok[256]; struct lws_cgi *cgi; - int n; + int n, m, i; /* * give the master wsi a cgi struct @@ -1612,8 +1643,11 @@ lws_cgi(struct lws *wsi, char * const *exec_array, int timeout_secs) /* prepare his CGI env */ n = 0; + + if (lws_is_ssl(wsi)) + env_array[n++] = "HTTPS=ON"; if (wsi->u.hdr.ah) { - snprintf(cgi_path, sizeof(cgi_path) - 1, "PATH_INFO=%s", + snprintf(cgi_path, sizeof(cgi_path) - 1, "REQUEST_URI=%s", lws_hdr_simple_ptr(wsi, WSI_TOKEN_GET_URI)); cgi_path[sizeof(cgi_path) - 1] = '\0'; env_array[n++] = cgi_path; @@ -1621,18 +1655,66 @@ lws_cgi(struct lws *wsi, char * const *exec_array, int timeout_secs) env_array[n++] = "REQUEST_METHOD=POST"; else env_array[n++] = "REQUEST_METHOD=GET"; + + env_array[n++] = p; + p += snprintf(p, end - p, "QUERY_STRING="); + /* dump the individual URI Arg parameters */ + m = 0; + while (1) { + i = lws_hdr_copy_fragment(wsi, tok, sizeof(tok), + WSI_TOKEN_HTTP_URI_ARGS, m); + if (i < 0) + break; + i = lws_urlencode(tok, i, p, end - p); + p += i; + *p++ = '&'; + m++; + } + if (m) + p--; + *p++ = '\0'; + + env_array[n++] = p; + p += snprintf(p, end - p, "PATH_INFO=%s", + lws_hdr_simple_ptr(wsi, WSI_TOKEN_GET_URI) + + script_uri_path_len); + p++; } - if (lws_is_ssl(wsi)) - env_array[n++] = "HTTPS=ON"; + if (lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_REFERER)) { + env_array[n++] = p; + p += snprintf(p, end - p, "HTTP_REFERER=%s", + lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP_REFERER)); + p++; + } + if (lws_hdr_total_length(wsi, WSI_TOKEN_HOST)) { + env_array[n++] = p; + p += snprintf(p, end - p, "HTTP_HOST=%s", + lws_hdr_simple_ptr(wsi, WSI_TOKEN_HOST)); + p++; + } + if (lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_USER_AGENT)) { + env_array[n++] = p; + p += snprintf(p, end - p, "USER_AGENT=%s", + lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP_USER_AGENT)); + p++; + } + env_array[n++] = p; + p += snprintf(p, end - p, "SCRIPT_PATH=%s", exec_array[2]) + 1; + env_array[n++] = "SERVER_SOFTWARE=libwebsockets"; - env_array[n++] = "PATH=/bin:/usr/bin:/usrlocal/bin"; + env_array[n++] = "PATH=/bin:/usr/bin:/usr/local/bin:/var/www/cgi-bin"; env_array[n] = NULL; +#if 0 + for (m = 0; m < n; m++) + lwsl_err(" %s\n", env_array[m]); +#endif + /* we are ready with the redirection pipes... run the thing */ -#ifdef LWS_HAVE_VFORK - cgi->pid = vfork(); -#else +#if !defined(LWS_HAVE_VFORK) || !defined(LWS_HAVE_EXECVPE) cgi->pid = fork(); +#else + cgi->pid = vfork(); #endif if (cgi->pid < 0) { lwsl_err("fork failed, errno %d", errno); @@ -1658,7 +1740,17 @@ lws_cgi(struct lws *wsi, char * const *exec_array, int timeout_secs) close(cgi->pipe_fds[n][!(n == 0)]); } +#if !defined(LWS_HAVE_VFORK) || !defined(LWS_HAVE_EXECVPE) + for (m = 0; m < n; m++) { + p = strchr(env_array[m], '='); + *p++ = '\0'; + setenv(env_array[m], p, 1); + } + execvp(exec_array[0], &exec_array[0]); +#else execvpe(exec_array[0], &exec_array[0], &env_array[0]); +#endif + exit(1); bail3: @@ -1711,7 +1803,7 @@ lws_cgi_write_split_stdout_headers(struct lws *wsi) n = 0; } if (n) { - lwsl_err("-- %c\n", c); + //lwsl_err("-- %c\n", c); switch (wsi->hdr_state) { case LCHS_HEADER: *p++ = c; @@ -1799,7 +1891,7 @@ lws_cgi_kill(struct lws *wsi) if (!wsi->cgi) return 0; - lwsl_notice("%s: wsi %p\n", __func__, wsi); +// lwsl_notice("%s: wsi %p\n", __func__, wsi); assert(wsi->cgi); diff --git a/lib/libwebsockets.h b/lib/libwebsockets.h index 72b88657..91fadbc0 100644 --- a/lib/libwebsockets.h +++ b/lib/libwebsockets.h @@ -1890,7 +1890,8 @@ struct lws_cgi_args { }; LWS_VISIBLE LWS_EXTERN int -lws_cgi(struct lws *wsi, char * const *exec_array, int timeout_secs); +lws_cgi(struct lws *wsi, char * const *exec_array, int script_uri_path_len, + int timeout_secs); LWS_VISIBLE LWS_EXTERN int lws_cgi_write_split_stdout_headers(struct lws *wsi); diff --git a/lib/private-libwebsockets.h b/lib/private-libwebsockets.h index b70a31ba..f3d6f5c1 100644 --- a/lib/private-libwebsockets.h +++ b/lib/private-libwebsockets.h @@ -1124,7 +1124,7 @@ struct lws { BIO *client_bio; struct lws *pending_read_list_prev, *pending_read_list_next; #endif -#ifndef LWS_NO_CLIENT +#ifdef LWS_WITH_HTTP_PROXY struct lws_rewrite *rw; #endif #ifdef LWS_LATENCY @@ -1154,6 +1154,8 @@ struct lws { unsigned int do_ws:1; /* whether we are doing http or ws flow */ unsigned int chunked:1; /* if the clientside connection is chunked */ unsigned int client_rx_avail:1; +#endif +#ifdef LWS_WITH_HTTP_PROXY unsigned int perform_rewrite:1; #endif #ifndef LWS_NO_EXTENSIONS @@ -1530,9 +1532,6 @@ static LWS_INLINE int hstrcmp(hubbub_string *s, const char *p, int len) return strncmp((const char *)s->ptr, p, len); } typedef hubbub_error (*hubbub_callback_t)(const hubbub_token *token, void *pw); -LWS_EXTERN int lws_client_socket_service(struct lws_context *context, - struct lws *wsi, - struct lws_pollfd *pollfd); LWS_EXTERN struct lws_rewrite * lws_rewrite_create(struct lws *wsi, hubbub_callback_t cb, const char *from, const char *to); LWS_EXTERN void @@ -1542,6 +1541,11 @@ lws_rewrite_parse(struct lws_rewrite *r, const unsigned char *in, int in_len); #endif #ifndef LWS_NO_CLIENT +LWS_EXTERN int lws_client_socket_service(struct lws_context *context, + struct lws *wsi, + struct lws_pollfd *pollfd); +LWS_EXTERN int LWS_WARN_UNUSED_RESULT +lws_http_transaction_completed_client(struct lws *wsi); #ifdef LWS_OPENSSL_SUPPORT LWS_EXTERN int lws_context_init_client_ssl(struct lws_context_creation_info *info, diff --git a/lib/service.c b/lib/service.c index 368043dd..8ed26690 100644 --- a/lib/service.c +++ b/lib/service.c @@ -473,10 +473,8 @@ lws_service_flag_pending(struct lws_context *context, int tsi) return forced; } +#ifndef LWS_NO_CLIENT -/* - * - */ LWS_VISIBLE int lws_http_client_read(struct lws *wsi, char **buf, int *len) { @@ -550,7 +548,7 @@ spin_chunks: return 0; if (wsi->u.http.content_remain && - wsi->u.http.content_remain < *len) + (int)wsi->u.http.content_remain < *len) n = wsi->u.http.content_remain; else n = *len; @@ -559,11 +557,12 @@ spin_chunks: wsi->chunk_remaining < n) n = wsi->chunk_remaining; +#ifdef LWS_WITH_HTTP_PROXY /* hubbub */ if (wsi->perform_rewrite) lws_rewrite_parse(wsi->rw, (unsigned char *)*buf, n); else - +#endif if (user_callback_handle_rxflow(wsi->protocol->callback, wsi, LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ, wsi->user_space, *buf, n)) @@ -595,11 +594,12 @@ completed: wsi->user_space, NULL, 0)) return -1; - if (lws_http_transaction_completed(wsi)) + if (lws_http_transaction_completed_client(wsi)) return -1; return 0; } +#endif /** * lws_service_fd() - Service polled socket with something waiting @@ -878,6 +878,7 @@ read: } drain: +#ifndef LWS_NO_CLIENT if (wsi->mode == LWSCM_HTTP_CLIENT_ACCEPTED) { /* @@ -894,9 +895,8 @@ drain: wsi, LWS_CALLBACK_RECEIVE_CLIENT_HTTP, wsi->user_space, NULL, 0)) goto close_and_handled; - - } +#endif /* * give any active extensions a chance to munge the buffer * before parse. We pass in a pointer to an lws_tokens struct diff --git a/lws_config_private.h.in b/lws_config_private.h.in index 06da558c..2079e902 100644 --- a/lws_config_private.h.in +++ b/lws_config_private.h.in @@ -96,6 +96,9 @@ /* Define to 1 if `vfork' works. */ #cmakedefine LWS_HAVE_WORKING_VFORK +/* Define to 1 if execvpe() exists */ +#cmakedefine LWS_HAVE_EXECVPE + /* Define to 1 if you have the header file. */ #cmakedefine LWS_HAVE_ZLIB_H diff --git a/test-server/test-server-http.c b/test-server/test-server-http.c index 1535e437..a142b701 100644 --- a/test-server/test-server-http.c +++ b/test-server/test-server-http.c @@ -104,6 +104,9 @@ const char * get_mimetype(const char *file) if (!strcmp(&file[n - 5], ".html")) return "text/html"; + if (!strcmp(&file[n - 4], ".css")) + return "text/css"; + return NULL; } @@ -117,7 +120,7 @@ int callback_http(struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len) { struct per_session_data__http *pss = - (struct per_session_data__http *)user, *pss1; + (struct per_session_data__http *)user; unsigned char buffer[4096 + LWS_PRE]; unsigned long amount, file_len, sent; char leaf_path[1024]; @@ -126,7 +129,10 @@ int callback_http(struct lws *wsi, enum lws_callback_reasons reason, void *user, unsigned char *end; struct timeval tv; unsigned char *p; +#ifndef LWS_NO_CLIENT + struct per_session_data__http *pss1; struct lws *wsi1; +#endif char buf[256]; char b64[64]; int n, m; @@ -178,7 +184,7 @@ int callback_http(struct lws *wsi, enum lws_callback_reasons reason, void *user, i.port = 80; i.ssl_connection = 0; if (p[10]) - i.path = in + 10; + i.path = (char *)in + 10; else i.path = rootpath; i.host = "git.libwebsockets.org"; @@ -198,23 +204,26 @@ int callback_http(struct lws *wsi, enum lws_callback_reasons reason, void *user, } #endif +#if 1 /* this example server has no concept of directories */ if (strchr((const char *)in + 1, '/')) { lws_return_http_status(wsi, HTTP_STATUS_FORBIDDEN, NULL); goto try_to_reuse; } +#endif #ifdef LWS_WITH_CGI - if (!strcmp(in, "/cgitest")) { + if (!strncmp(in, "/cgitest", 8)) { static char *cmd[] = { "/bin/sh", "-c", INSTALL_DATADIR"/libwebsockets-test-server/lws-cgi-test.sh", +// "/var/www/cgi-bin/cgit", NULL }; lwsl_notice("%s: cgitest\n", __func__); - n = lws_cgi(wsi, cmd, 5); + n = lws_cgi(wsi, cmd, 8, 5); if (n) { lwsl_err("%s: cgi failed\n"); return -1; @@ -320,11 +329,16 @@ int callback_http(struct lws *wsi, enum lws_callback_reasons reason, void *user, } /* if not, send a file the easy way */ - strcpy(buf, resource_path); + if (!strncmp(in, "/cgit-data/", 11)) { + in = (char *)in + 11; + strcpy(buf, "/usr/share/cgit"); + } else + strcpy(buf, resource_path); + if (strcmp(in, "/")) { if (*((const char *)in) != '/') strcat(buf, "/"); - strncat(buf, in, sizeof(buf) - strlen(resource_path)); + strncat(buf, in, sizeof(buf) - strlen(buf) - 1); } else /* default file to serve */ strcat(buf, "/test.html"); buf[sizeof(buf) - 1] = '\0'; @@ -510,7 +524,7 @@ bail: /* if we returned non-zero from here, we kill the connection */ break; -#ifndef LWS_WITH_CLIENT +#ifndef LWS_NO_CLIENT case LWS_CALLBACK_ESTABLISHED_CLIENT_HTTP: { char ctype[64], ctlen = 0; lwsl_err("LWS_CALLBACK_ESTABLISHED_CLIENT_HTTP\n"); @@ -618,16 +632,16 @@ bail: break; case LWS_CALLBACK_CGI_TERMINATED: - lwsl_notice("LWS_CALLBACK_CGI_TERMINATED\n"); + //lwsl_notice("LWS_CALLBACK_CGI_TERMINATED\n"); /* because we sent on openended http, close the connection */ return -1; case LWS_CALLBACK_CGI_STDIN_DATA: /* POST body for stdin */ - lwsl_notice("LWS_CALLBACK_CGI_STDIN_DATA\n"); + //lwsl_notice("LWS_CALLBACK_CGI_STDIN_DATA\n"); pss->args = *((struct lws_cgi_args *)in); n = write(lws_get_socket_fd(pss->args.stdwsi[LWS_STDIN]), pss->args.data, pss->args.len); - lwsl_notice("LWS_CALLBACK_CGI_STDIN_DATA: write says %d", n); + //lwsl_notice("LWS_CALLBACK_CGI_STDIN_DATA: write says %d", n); if (n < pss->args.len) lwsl_notice("LWS_CALLBACK_CGI_STDIN_DATA: sent %d only %d went", n, pss->args.len); diff --git a/test-server/test-server.h b/test-server/test-server.h index a546904f..c468a69d 100644 --- a/test-server/test-server.h +++ b/test-server/test-server.h @@ -69,6 +69,8 @@ struct per_session_data__http { lws_filefd_type fd; #ifdef LWS_WITH_CGI struct lws_cgi_args args; +#endif +#if defined(LWS_WITH_CGI) || !defined(LWS_NO_CLIENT) int reason_bf; #endif unsigned int client_finished:1;