diff options
Diffstat (limited to 'thirdparty/libwebsockets/roles/http/client')
-rw-r--r-- | thirdparty/libwebsockets/roles/http/client/client-handshake.c | 1284 | ||||
-rw-r--r-- | thirdparty/libwebsockets/roles/http/client/client.c | 1243 |
2 files changed, 2527 insertions, 0 deletions
diff --git a/thirdparty/libwebsockets/roles/http/client/client-handshake.c b/thirdparty/libwebsockets/roles/http/client/client-handshake.c new file mode 100644 index 0000000000..0095c79a69 --- /dev/null +++ b/thirdparty/libwebsockets/roles/http/client/client-handshake.c @@ -0,0 +1,1284 @@ +#include "core/private.h" + +static int +lws_getaddrinfo46(struct lws *wsi, const char *ads, struct addrinfo **result) +{ + struct addrinfo hints; + + memset(&hints, 0, sizeof(hints)); + *result = NULL; + +#ifdef LWS_WITH_IPV6 + if (wsi->ipv6) { + +#if !defined(__ANDROID__) + hints.ai_family = AF_INET6; + hints.ai_flags = AI_V4MAPPED; +#endif + } else +#endif + { + hints.ai_family = PF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + } + + return getaddrinfo(ads, NULL, &hints, result); +} + +struct lws * +lws_client_connect_2(struct lws *wsi) +{ +#if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2) + struct lws_context *context = wsi->context; + struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi]; + const char *adsin; + struct lws *wsi_piggyback = NULL; + struct lws_pollfd pfd; + ssize_t plen = 0; +#endif + struct addrinfo *result; + const char *ads; + sockaddr46 sa46; + int n, port; + const char *cce = "", *iface; + const char *meth = NULL; +#ifdef LWS_WITH_IPV6 + char ipv6only = lws_check_opt(wsi->vhost->options, + LWS_SERVER_OPTION_IPV6_V6ONLY_MODIFY | + LWS_SERVER_OPTION_IPV6_V6ONLY_VALUE); + +#if defined(__ANDROID__) + ipv6only = 0; +#endif +#endif + + lwsl_client("%s: %p\n", __func__, wsi); + +#if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2) + if (!wsi->http.ah) { + cce = "ah was NULL at cc2"; + lwsl_err("%s\n", cce); + goto oom4; + } + + /* we can only piggyback GET or POST */ + + meth = lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_METHOD); + if (meth && strcmp(meth, "GET") && strcmp(meth, "POST")) + goto create_new_conn; + + /* we only pipeline connections that said it was okay */ + + if (!wsi->client_pipeline) + goto create_new_conn; + + /* + * let's take a look first and see if there are any already-active + * client connections we can piggy-back on. + */ + + adsin = lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_PEER_ADDRESS); + + lws_vhost_lock(wsi->vhost); /* ----------------------------------- { */ + + lws_start_foreach_dll_safe(struct lws_dll_lws *, d, d1, + wsi->vhost->dll_active_client_conns.next) { + struct lws *w = lws_container_of(d, struct lws, + dll_active_client_conns); + + lwsl_debug("%s: check %s %s %d %d\n", __func__, adsin, + w->client_hostname_copy, wsi->c_port, w->c_port); + + if (w != wsi && w->client_hostname_copy && + !strcmp(adsin, w->client_hostname_copy) && +#if defined(LWS_WITH_TLS) + (wsi->tls.use_ssl & LCCSCF_USE_SSL) == + (w->tls.use_ssl & LCCSCF_USE_SSL) && +#endif + wsi->c_port == w->c_port) { + + /* someone else is already connected to the right guy */ + + /* do we know for a fact pipelining won't fly? */ + if (w->keepalive_rejected) { + lwsl_info("defeating pipelining due to no " + "keepalive on server\n"); + lws_vhost_unlock(wsi->vhost); /* } ---------- */ + goto create_new_conn; + } +#if defined (LWS_WITH_HTTP2) + /* + * h2: in usable state already: just use it without + * going through the queue + */ + if (w->client_h2_alpn && + (lwsi_state(w) == LRS_H2_WAITING_TO_SEND_HEADERS || + lwsi_state(w) == LRS_ESTABLISHED)) { + + lwsl_info("%s: just join h2 directly\n", + __func__); + + wsi->client_h2_alpn = 1; + lws_wsi_h2_adopt(w, wsi); + lws_vhost_unlock(wsi->vhost); /* } ---------- */ + + return wsi; + } +#endif + + lwsl_info("applying %p to txn queue on %p (wsistate 0x%x)\n", + wsi, w, w->wsistate); + /* + * ...let's add ourselves to his transaction queue... + * we are adding ourselves at the HEAD + */ + lws_dll_lws_add_front(&wsi->dll_client_transaction_queue, + &w->dll_client_transaction_queue_head); + + /* + * h1: pipeline our headers out on him, + * and wait for our turn at client transaction_complete + * to take over parsing the rx. + */ + + wsi_piggyback = w; + + lws_vhost_unlock(wsi->vhost); /* } ---------- */ + goto send_hs; + } + + } lws_end_foreach_dll_safe(d, d1); + + lws_vhost_unlock(wsi->vhost); /* } ---------------------------------- */ + +create_new_conn: +#endif + + /* + * clients who will create their own fresh connection keep a copy of + * the hostname they originally connected to, in case other connections + * want to use it too + */ + + if (!wsi->client_hostname_copy) + wsi->client_hostname_copy = + lws_strdup(lws_hdr_simple_ptr(wsi, + _WSI_TOKEN_CLIENT_PEER_ADDRESS)); + + /* + * If we made our own connection, and we're doing a method that can take + * a pipeline, we are an "active client connection". + * + * Add ourselves to the vhost list of those so that others can + * piggyback on our transaction queue + */ + + if (meth && (!strcmp(meth, "GET") || !strcmp(meth, "POST")) && + lws_dll_is_null(&wsi->dll_client_transaction_queue) && + lws_dll_is_null(&wsi->dll_active_client_conns)) { + lws_vhost_lock(wsi->vhost); + lws_dll_lws_add_front(&wsi->dll_active_client_conns, + &wsi->vhost->dll_active_client_conns); + lws_vhost_unlock(wsi->vhost); + } + + /* + * start off allowing ipv6 on connection if vhost allows it + */ + wsi->ipv6 = LWS_IPV6_ENABLED(wsi->vhost); + +#if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2) + + /* Decide what it is we need to connect to: + * + * Priority 1: connect to http proxy */ + + if (wsi->vhost->http.http_proxy_port) { + plen = sprintf((char *)pt->serv_buf, + "CONNECT %s:%u HTTP/1.0\x0d\x0a" + "User-agent: libwebsockets\x0d\x0a", + lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_PEER_ADDRESS), + wsi->c_port); + + if (wsi->vhost->proxy_basic_auth_token[0]) + plen += sprintf((char *)pt->serv_buf + plen, + "Proxy-authorization: basic %s\x0d\x0a", + wsi->vhost->proxy_basic_auth_token); + + plen += sprintf((char *)pt->serv_buf + plen, "\x0d\x0a"); + ads = wsi->vhost->http.http_proxy_address; + port = wsi->vhost->http.http_proxy_port; +#else + if (0) { +#endif + +#if defined(LWS_WITH_SOCKS5) + + /* Priority 2: Connect to SOCK5 Proxy */ + + } else if (wsi->vhost->socks_proxy_port) { + socks_generate_msg(wsi, SOCKS_MSG_GREETING, &plen); + lwsl_client("Sending SOCKS Greeting\n"); + ads = wsi->vhost->socks_proxy_address; + port = wsi->vhost->socks_proxy_port; +#endif + } else { + + /* Priority 3: Connect directly */ + + ads = lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_PEER_ADDRESS); + port = wsi->c_port; + } + + /* + * prepare the actual connection + * to whatever we decided to connect to + */ + + lwsl_info("%s: %p: address %s\n", __func__, wsi, ads); + + n = lws_getaddrinfo46(wsi, ads, &result); + +#ifdef LWS_WITH_IPV6 + if (wsi->ipv6) { + struct sockaddr_in6 *sa6 = + ((struct sockaddr_in6 *)result->ai_addr); + + if (n) { + /* lws_getaddrinfo46 failed, there is no usable result */ + lwsl_notice("%s: lws_getaddrinfo46 failed %d\n", + __func__, n); + cce = "ipv6 lws_getaddrinfo46 failed"; + goto oom4; + } + + memset(&sa46, 0, sizeof(sa46)); + + sa46.sa6.sin6_family = AF_INET6; + switch (result->ai_family) { + case AF_INET: + if (ipv6only) + break; + /* map IPv4 to IPv6 */ + bzero((char *)&sa46.sa6.sin6_addr, + sizeof(sa46.sa6.sin6_addr)); + sa46.sa6.sin6_addr.s6_addr[10] = 0xff; + sa46.sa6.sin6_addr.s6_addr[11] = 0xff; + memcpy(&sa46.sa6.sin6_addr.s6_addr[12], + &((struct sockaddr_in *)result->ai_addr)->sin_addr, + sizeof(struct in_addr)); + lwsl_notice("uplevelling AF_INET to AF_INET6\n"); + break; + + case AF_INET6: + memcpy(&sa46.sa6.sin6_addr, &sa6->sin6_addr, + sizeof(struct in6_addr)); + sa46.sa6.sin6_scope_id = sa6->sin6_scope_id; + sa46.sa6.sin6_flowinfo = sa6->sin6_flowinfo; + break; + default: + lwsl_err("Unknown address family\n"); + freeaddrinfo(result); + cce = "unknown address family"; + goto oom4; + } + } else +#endif /* use ipv6 */ + + /* use ipv4 */ + { + void *p = NULL; + + if (!n) { + struct addrinfo *res = result; + + /* pick the first AF_INET (IPv4) result */ + + while (!p && res) { + switch (res->ai_family) { + case AF_INET: + p = &((struct sockaddr_in *)res->ai_addr)->sin_addr; + break; + } + + res = res->ai_next; + } +#if defined(LWS_FALLBACK_GETHOSTBYNAME) + } else if (n == EAI_SYSTEM) { + struct hostent *host; + + lwsl_info("getaddrinfo (ipv4) failed, trying gethostbyname\n"); + host = gethostbyname(ads); + if (host) { + p = host->h_addr; + } else { + lwsl_err("gethostbyname failed\n"); + cce = "gethostbyname (ipv4) failed"; + goto oom4; + } +#endif + } else { + lwsl_err("getaddrinfo failed\n"); + cce = "getaddrinfo failed"; + goto oom4; + } + + if (!p) { + if (result) + freeaddrinfo(result); + lwsl_err("Couldn't identify address\n"); + cce = "unable to lookup address"; + goto oom4; + } + + sa46.sa4.sin_family = AF_INET; + sa46.sa4.sin_addr = *((struct in_addr *)p); + bzero(&sa46.sa4.sin_zero, 8); + } + + if (result) + freeaddrinfo(result); + + /* now we decided on ipv4 or ipv6, set the port */ + + if (!lws_socket_is_valid(wsi->desc.sockfd)) { + + if (wsi->context->event_loop_ops->check_client_connect_ok && + wsi->context->event_loop_ops->check_client_connect_ok(wsi)) { + cce = "waiting for event loop watcher to close"; + goto oom4; + } + +#ifdef LWS_WITH_IPV6 + if (wsi->ipv6) + wsi->desc.sockfd = socket(AF_INET6, SOCK_STREAM, 0); + else +#endif + wsi->desc.sockfd = socket(AF_INET, SOCK_STREAM, 0); + + if (!lws_socket_is_valid(wsi->desc.sockfd)) { + lwsl_warn("Unable to open socket\n"); + cce = "unable to open socket"; + goto oom4; + } + + if (lws_plat_set_socket_options(wsi->vhost, wsi->desc.sockfd)) { + lwsl_err("Failed to set wsi socket options\n"); + compatible_close(wsi->desc.sockfd); + cce = "set socket opts failed"; + goto oom4; + } + + lwsi_set_state(wsi, LRS_WAITING_CONNECT); + + if (wsi->context->event_loop_ops->accept) + wsi->context->event_loop_ops->accept(wsi); + + if (__insert_wsi_socket_into_fds(wsi->context, wsi)) { + compatible_close(wsi->desc.sockfd); + cce = "insert wsi failed"; + goto oom4; + } + + lws_change_pollfd(wsi, 0, LWS_POLLIN); + + /* + * past here, we can't simply free the structs as error + * handling as oom4 does. We have to run the whole close flow. + */ + + if (!wsi->protocol) + wsi->protocol = &wsi->vhost->protocols[0]; + + wsi->protocol->callback(wsi, LWS_CALLBACK_WSI_CREATE, + wsi->user_space, NULL, 0); + + lws_set_timeout(wsi, PENDING_TIMEOUT_AWAITING_CONNECT_RESPONSE, + AWAITING_TIMEOUT); + + iface = lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_IFACE); + + if (iface) { + n = lws_socket_bind(wsi->vhost, wsi->desc.sockfd, 0, iface); + if (n < 0) { + cce = "unable to bind socket"; + goto failed; + } + } + } + +#ifdef LWS_WITH_IPV6 + if (wsi->ipv6) { + sa46.sa6.sin6_port = htons(port); + n = sizeof(struct sockaddr_in6); + } else +#endif + { + sa46.sa4.sin_port = htons(port); + n = sizeof(struct sockaddr); + } + + if (connect(wsi->desc.sockfd, (const struct sockaddr *)&sa46, n) == -1 || + LWS_ERRNO == LWS_EISCONN) { + if (LWS_ERRNO == LWS_EALREADY || + LWS_ERRNO == LWS_EINPROGRESS || + LWS_ERRNO == LWS_EWOULDBLOCK +#ifdef _WIN32 + || LWS_ERRNO == WSAEINVAL +#endif + ) { + lwsl_client("nonblocking connect retry (errno = %d)\n", + LWS_ERRNO); + + if (lws_plat_check_connection_error(wsi)) { + cce = "socket connect failed"; + goto failed; + } + + /* + * must do specifically a POLLOUT poll to hear + * about the connect completion + */ + if (lws_change_pollfd(wsi, 0, LWS_POLLOUT)) { + cce = "POLLOUT set failed"; + goto failed; + } + + return wsi; + } + + if (LWS_ERRNO != LWS_EISCONN) { + lwsl_notice("Connect failed errno=%d\n", LWS_ERRNO); + cce = "connect failed"; + goto failed; + } + } + + lwsl_client("connected\n"); + +#if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2) + /* we are connected to server, or proxy */ + + /* http proxy */ + if (wsi->vhost->http.http_proxy_port) { + + /* + * OK from now on we talk via the proxy, so connect to that + * + * (will overwrite existing pointer, + * leaving old string/frag there but unreferenced) + */ + if (lws_hdr_simple_create(wsi, _WSI_TOKEN_CLIENT_PEER_ADDRESS, + wsi->vhost->http.http_proxy_address)) + goto failed; + wsi->c_port = wsi->vhost->http.http_proxy_port; + + n = send(wsi->desc.sockfd, (char *)pt->serv_buf, (int)plen, + MSG_NOSIGNAL); + if (n < 0) { + lwsl_debug("ERROR writing to proxy socket\n"); + cce = "proxy write failed"; + goto failed; + } + + lws_set_timeout(wsi, PENDING_TIMEOUT_AWAITING_PROXY_RESPONSE, + AWAITING_TIMEOUT); + + lwsi_set_state(wsi, LRS_WAITING_PROXY_REPLY); + + return wsi; + } +#endif +#if defined(LWS_WITH_SOCKS5) + /* socks proxy */ + else if (wsi->vhost->socks_proxy_port) { + n = send(wsi->desc.sockfd, (char *)pt->serv_buf, plen, + MSG_NOSIGNAL); + if (n < 0) { + lwsl_debug("ERROR writing socks greeting\n"); + cce = "socks write failed"; + goto failed; + } + + lws_set_timeout(wsi, PENDING_TIMEOUT_AWAITING_SOCKS_GREETING_REPLY, + AWAITING_TIMEOUT); + + lwsi_set_state(wsi, LRS_WAITING_SOCKS_GREETING_REPLY); + + return wsi; + } +#endif +#if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2) +send_hs: + + if (wsi_piggyback && + !lws_dll_is_null(&wsi->dll_client_transaction_queue)) { + /* + * We are pipelining on an already-established connection... + * we can skip tls establishment. + */ + + lwsi_set_state(wsi, LRS_H1C_ISSUE_HANDSHAKE2); + + /* + * we can't send our headers directly, because they have to + * be sent when the parent is writeable. The parent will check + * for anybody on his client transaction queue that is in + * LRS_H1C_ISSUE_HANDSHAKE2, and let them write. + * + * If we are trying to do this too early, before the master + * connection has written his own headers, then it will just + * wait in the queue until it's possible to send them. + */ + lws_callback_on_writable(wsi_piggyback); + lwsl_info("%s: wsi %p: waiting to send headers (parent state %x)\n", + __func__, wsi, lwsi_state(wsi_piggyback)); + } else { + lwsl_info("%s: wsi %p: client creating own connection\n", + __func__, wsi); + + /* we are making our own connection */ + lwsi_set_state(wsi, LRS_H1C_ISSUE_HANDSHAKE); + + /* + * provoke service to issue the handshake directly. + * + * we need to do it this way because in the proxy case, this is + * the next state and executed only if and when we get a good + * proxy response inside the state machine... but notice in + * SSL case this may not have sent anything yet with 0 return, + * and won't until many retries from main loop. To stop that + * becoming endless, cover with a timeout. + */ + + lws_set_timeout(wsi, PENDING_TIMEOUT_SENT_CLIENT_HANDSHAKE, + AWAITING_TIMEOUT); + + pfd.fd = wsi->desc.sockfd; + pfd.events = LWS_POLLIN; + pfd.revents = LWS_POLLIN; + + n = lws_service_fd(context, &pfd); + if (n < 0) { + cce = "first service failed"; + goto failed; + } + if (n) /* returns 1 on failure after closing wsi */ + return NULL; + } +#endif + return wsi; + +oom4: + if (lwsi_role_client(wsi) && lwsi_state_est(wsi)) { + wsi->protocol->callback(wsi, + LWS_CALLBACK_CLIENT_CONNECTION_ERROR, + wsi->user_space, (void *)cce, strlen(cce)); + wsi->already_did_cce = 1; + } + /* take care that we might be inserted in fds already */ + if (wsi->position_in_fds_table != LWS_NO_FDS_POS) + goto failed1; + lws_remove_from_timeout_list(wsi); +#if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2) + lws_header_table_detach(wsi, 0); +#endif + lws_client_stash_destroy(wsi); + lws_free_set_NULL(wsi->client_hostname_copy); + lws_free(wsi); + + return NULL; + +failed: + wsi->protocol->callback(wsi, + LWS_CALLBACK_CLIENT_CONNECTION_ERROR, + wsi->user_space, (void *)cce, strlen(cce)); + wsi->already_did_cce = 1; +failed1: + lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS, "client_connect2"); + + return NULL; +} + +#if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2) + +/** + * lws_client_reset() - retarget a connected wsi to start over with a new connection (ie, redirect) + * this only works if still in HTTP, ie, not upgraded yet + * wsi: connection to reset + * address: network address of the new server + * port: port to connect to + * path: uri path to connect to on the new server + * host: host header to send to the new server + */ +LWS_VISIBLE struct lws * +lws_client_reset(struct lws **pwsi, int ssl, const char *address, int port, + const char *path, const char *host) +{ + char origin[300] = "", protocol[300] = "", method[32] = "", + iface[16] = "", alpn[32] = "", *p; + struct lws *wsi = *pwsi; + + if (wsi->redirects == 3) { + lwsl_err("%s: Too many redirects\n", __func__); + return NULL; + } + wsi->redirects++; + + p = lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_ORIGIN); + if (p) + lws_strncpy(origin, p, sizeof(origin)); + + p = lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_SENT_PROTOCOLS); + if (p) + lws_strncpy(protocol, p, sizeof(protocol)); + + p = lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_METHOD); + if (p) + lws_strncpy(method, p, sizeof(method)); + + p = lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_IFACE); + if (p) + lws_strncpy(iface, p, sizeof(iface)); + + p = lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_ALPN); + if (p) + lws_strncpy(alpn, p, sizeof(alpn)); + + lwsl_info("redirect ads='%s', port=%d, path='%s', ssl = %d\n", + address, port, path, ssl); + + /* close the connection by hand */ + +#if defined(LWS_WITH_TLS) + lws_ssl_close(wsi); +#endif + + __remove_wsi_socket_from_fds(wsi); + + if (wsi->context->event_loop_ops->close_handle_manually) + wsi->context->event_loop_ops->close_handle_manually(wsi); + else + compatible_close(wsi->desc.sockfd); + +#if defined(LWS_WITH_TLS) + wsi->tls.use_ssl = ssl; +#else + if (ssl) { + lwsl_err("%s: not configured for ssl\n", __func__); + return NULL; + } +#endif + + wsi->desc.sockfd = LWS_SOCK_INVALID; + lwsi_set_state(wsi, LRS_UNCONNECTED); + wsi->protocol = NULL; + wsi->pending_timeout = NO_PENDING_TIMEOUT; + wsi->c_port = port; + wsi->hdr_parsing_completed = 0; + _lws_header_table_reset(wsi->http.ah); + + if (lws_hdr_simple_create(wsi, _WSI_TOKEN_CLIENT_PEER_ADDRESS, address)) + return NULL; + + if (lws_hdr_simple_create(wsi, _WSI_TOKEN_CLIENT_HOST, host)) + return NULL; + + if (origin[0]) + if (lws_hdr_simple_create(wsi, _WSI_TOKEN_CLIENT_ORIGIN, + origin)) + return NULL; + if (protocol[0]) + if (lws_hdr_simple_create(wsi, _WSI_TOKEN_CLIENT_SENT_PROTOCOLS, + protocol)) + return NULL; + if (method[0]) + if (lws_hdr_simple_create(wsi, _WSI_TOKEN_CLIENT_METHOD, + method)) + return NULL; + + if (iface[0]) + if (lws_hdr_simple_create(wsi, _WSI_TOKEN_CLIENT_IFACE, + iface)) + return NULL; + if (alpn[0]) + if (lws_hdr_simple_create(wsi, _WSI_TOKEN_CLIENT_ALPN, + alpn)) + return NULL; + + origin[0] = '/'; + strncpy(&origin[1], path, sizeof(origin) - 2); + if (lws_hdr_simple_create(wsi, _WSI_TOKEN_CLIENT_URI, origin)) + return NULL; + + *pwsi = lws_client_connect_2(wsi); + + return *pwsi; +} + +#ifdef LWS_WITH_HTTP_PROXY +hubbub_error +html_parser_cb(const hubbub_token *token, void *pw) +{ + struct lws_rewrite *r = (struct lws_rewrite *)pw; + char buf[1024], *start = buf + LWS_PRE, *p = start, + *end = &buf[sizeof(buf) - 1]; + size_t i; + + switch (token->type) { + case HUBBUB_TOKEN_DOCTYPE: + + p += lws_snprintf(p, end - p, "<!DOCTYPE %.*s %s ", + (int) token->data.doctype.name.len, + token->data.doctype.name.ptr, + token->data.doctype.force_quirks ? + "(force-quirks) " : ""); + + if (token->data.doctype.public_missing) + lwsl_debug("\tpublic: missing\n"); + else + p += lws_snprintf(p, end - p, "PUBLIC \"%.*s\"\n", + (int) token->data.doctype.public_id.len, + token->data.doctype.public_id.ptr); + + if (token->data.doctype.system_missing) + lwsl_debug("\tsystem: missing\n"); + else + p += lws_snprintf(p, end - p, " \"%.*s\">\n", + (int) token->data.doctype.system_id.len, + token->data.doctype.system_id.ptr); + + break; + case HUBBUB_TOKEN_START_TAG: + p += lws_snprintf(p, end - p, "<%.*s", (int)token->data.tag.name.len, + token->data.tag.name.ptr); + +/* (token->data.tag.self_closing) ? + "(self-closing) " : "", + (token->data.tag.n_attributes > 0) ? + "attributes:" : ""); +*/ + for (i = 0; i < token->data.tag.n_attributes; i++) { + if (!hstrcmp(&token->data.tag.attributes[i].name, "href", 4) || + !hstrcmp(&token->data.tag.attributes[i].name, "action", 6) || + !hstrcmp(&token->data.tag.attributes[i].name, "src", 3)) { + const char *pp = (const char *)token->data.tag.attributes[i].value.ptr; + int plen = (int) token->data.tag.attributes[i].value.len; + + if (strncmp(pp, "http:", 5) && strncmp(pp, "https:", 6)) { + + if (!hstrcmp(&token->data.tag.attributes[i].value, + r->from, r->from_len)) { + pp += r->from_len; + plen -= r->from_len; + } + p += lws_snprintf(p, end - p, " %.*s=\"%s/%.*s\"", + (int) token->data.tag.attributes[i].name.len, + token->data.tag.attributes[i].name.ptr, + r->to, plen, pp); + continue; + } + } + + p += lws_snprintf(p, end - p, " %.*s=\"%.*s\"", + (int) token->data.tag.attributes[i].name.len, + token->data.tag.attributes[i].name.ptr, + (int) token->data.tag.attributes[i].value.len, + token->data.tag.attributes[i].value.ptr); + } + p += lws_snprintf(p, end - p, ">"); + break; + case HUBBUB_TOKEN_END_TAG: + p += lws_snprintf(p, end - p, "</%.*s", (int) token->data.tag.name.len, + token->data.tag.name.ptr); +/* + (token->data.tag.self_closing) ? + "(self-closing) " : "", + (token->data.tag.n_attributes > 0) ? + "attributes:" : ""); +*/ + for (i = 0; i < token->data.tag.n_attributes; i++) { + p += lws_snprintf(p, end - p, " %.*s='%.*s'\n", + (int) token->data.tag.attributes[i].name.len, + token->data.tag.attributes[i].name.ptr, + (int) token->data.tag.attributes[i].value.len, + token->data.tag.attributes[i].value.ptr); + } + p += lws_snprintf(p, end - p, ">"); + break; + case HUBBUB_TOKEN_COMMENT: + p += lws_snprintf(p, end - p, "<!-- %.*s -->\n", + (int) token->data.comment.len, + token->data.comment.ptr); + break; + case HUBBUB_TOKEN_CHARACTER: + if (token->data.character.len == 1) { + if (*token->data.character.ptr == '<') { + p += lws_snprintf(p, end - p, "<"); + break; + } + if (*token->data.character.ptr == '>') { + p += lws_snprintf(p, end - p, ">"); + break; + } + if (*token->data.character.ptr == '&') { + p += lws_snprintf(p, end - p, "&"); + break; + } + } + + p += lws_snprintf(p, end - p, "%.*s", (int) token->data.character.len, + token->data.character.ptr); + break; + case HUBBUB_TOKEN_EOF: + p += lws_snprintf(p, end - p, "\n"); + break; + } + + if (user_callback_handle_rxflow(r->wsi->protocol->callback, + r->wsi, LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ, + r->wsi->user_space, start, p - start)) + return -1; + + return HUBBUB_OK; +} +#endif + +#endif + +char * +lws_strdup(const char *s) +{ + char *d = lws_malloc(strlen(s) + 1, "strdup"); + + if (d) + strcpy(d, s); + + return d; +} + +void +lws_client_stash_destroy(struct lws *wsi) +{ + if (!wsi || !wsi->stash) + return; + + lws_free_set_NULL(wsi->stash->address); + lws_free_set_NULL(wsi->stash->path); + lws_free_set_NULL(wsi->stash->host); + lws_free_set_NULL(wsi->stash->origin); + lws_free_set_NULL(wsi->stash->protocol); + lws_free_set_NULL(wsi->stash->method); + lws_free_set_NULL(wsi->stash->iface); + lws_free_set_NULL(wsi->stash->alpn); + + lws_free_set_NULL(wsi->stash); +} + +LWS_VISIBLE struct lws * +lws_client_connect_via_info(struct lws_client_connect_info *i) +{ + struct lws *wsi; + const struct lws_protocols *p; + const char *local = i->protocol; + + if (i->context->requested_kill) + return NULL; + + if (!i->context->protocol_init_done) + lws_protocol_init(i->context); + /* + * If we have .local_protocol_name, use it to select the + * local protocol handler to bind to. Otherwise use .protocol if + * http[s]. + */ + if (i->local_protocol_name) + local = i->local_protocol_name; + + wsi = lws_zalloc(sizeof(struct lws), "client wsi"); + if (wsi == NULL) + goto bail; + + wsi->context = i->context; +#if defined(LWS_ROLE_H1) + /* assert the mode and union status (hdr) clearly */ + lws_role_transition(wsi, LWSIFR_CLIENT, LRS_UNCONNECTED, &role_ops_h1); +#else + lws_role_transition(wsi, LWSIFR_CLIENT, LRS_UNCONNECTED, &role_ops_raw_skt); +#endif + wsi->desc.sockfd = LWS_SOCK_INVALID; + + /* 1) fill up the wsi with stuff from the connect_info as far as it + * can go. It's because not only is our connection async, we might + * not even be able to get ahold of an ah at this point. + */ + + if (!i->method) /* ie, ws */ +#if defined(LWS_ROLE_WS) + if (lws_create_client_ws_object(i, wsi)) + return NULL; +#else + return NULL; +#endif + + wsi->user_space = NULL; + wsi->pending_timeout = NO_PENDING_TIMEOUT; + wsi->position_in_fds_table = LWS_NO_FDS_POS; + wsi->c_port = i->port; + wsi->vhost = i->vhost; + if (!wsi->vhost) + wsi->vhost = i->context->vhost_list; + + if (!wsi->vhost) { + lwsl_err("At least one vhost in the context is required\n"); + + goto bail; + } + + wsi->protocol = &wsi->vhost->protocols[0]; + wsi->client_pipeline = !!(i->ssl_connection & LCCSCF_PIPELINE); + + /* reasonable place to start */ + lws_role_transition(wsi, LWSIFR_CLIENT, LRS_UNCONNECTED, +#if defined(LWS_ROLE_H1) + &role_ops_h1); +#else + &role_ops_raw_skt); +#endif + + /* + * 1) for http[s] connection, allow protocol selection by name + * 2) for ws[s], if local_protocol_name given also use it for + * local protocol binding... this defeats the server + * protocol negotiation if so + * + * Otherwise leave at protocols[0]... the server will tell us + * which protocol we are associated with since we can give it a + * list. + */ + if (/*(i->method || i->local_protocol_name) && */local) { + lwsl_info("binding to %s\n", local); + p = lws_vhost_name_to_protocol(wsi->vhost, local); + if (p) + wsi->protocol = p; + } + + if (wsi && !wsi->user_space && i->userdata) { + wsi->user_space_externally_allocated = 1; + wsi->user_space = i->userdata; + } else + /* if we stay in http, we can assign the user space now, + * otherwise do it after the protocol negotiated + */ + if (i->method) + if (lws_ensure_user_space(wsi)) + goto bail; + +#if defined(LWS_WITH_TLS) + wsi->tls.use_ssl = i->ssl_connection; +#else + if (i->ssl_connection & LCCSCF_USE_SSL) { + lwsl_err("libwebsockets not configured for ssl\n"); + goto bail; + } +#endif + + /* 2) stash the things from connect_info that we can't process without + * an ah. Because if no ah, we will go on the ah waiting list and + * process those things later (after the connect_info and maybe the + * things pointed to have gone out of scope. + */ + + wsi->stash = lws_zalloc(sizeof(*wsi->stash), "client stash"); + if (!wsi->stash) { + lwsl_err("%s: OOM\n", __func__); + goto bail1; + } + + wsi->stash->address = lws_strdup(i->address); + wsi->stash->path = lws_strdup(i->path); + wsi->stash->host = lws_strdup(i->host); + + if (!wsi->stash->address || !wsi->stash->path || !wsi->stash->host) + goto bail1; + + if (i->origin) { + wsi->stash->origin = lws_strdup(i->origin); + if (!wsi->stash->origin) + goto bail1; + } + if (i->protocol) { + wsi->stash->protocol = lws_strdup(i->protocol); + if (!wsi->stash->protocol) + goto bail1; + } + if (i->method) { + wsi->stash->method = lws_strdup(i->method); + if (!wsi->stash->method) + goto bail1; + } + if (i->iface) { + wsi->stash->iface = lws_strdup(i->iface); + if (!wsi->stash->iface) + goto bail1; + } + /* + * For ws, default to http/1.1 only. If i->alpn is set, defer to + * whatever he has set in there (eg, "h2"). + * + * The problem is he has to commit to h2 before he can find out if the + * server has the SETTINGS for ws-over-h2 enabled; if not then ws is + * not possible on that connection. So we only try it if he + * assertively said to use h2 alpn. + */ + if (!i->method && !i->alpn) { + wsi->stash->alpn = lws_strdup("http/1.1"); + if (!wsi->stash->alpn) + goto bail1; + } else + if (i->alpn) { + wsi->stash->alpn = lws_strdup(i->alpn); + if (!wsi->stash->alpn) + goto bail1; + } + + if (i->pwsi) + *i->pwsi = wsi; + +#if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2) + /* if we went on the waiting list, no probs just return the wsi + * when we get the ah, now or later, he will call + * lws_client_connect_via_info2() below. + */ + if (lws_header_table_attach(wsi, 0) < 0) { + /* + * if we failed here, the connection is already closed + * and freed. + */ + goto bail2; + } + +#endif + + if (i->parent_wsi) { + lwsl_info("%s: created child %p of parent %p\n", __func__, + wsi, i->parent_wsi); + wsi->parent = i->parent_wsi; + 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->http.rw = lws_rewrite_create(wsi, html_parser_cb, + i->uri_replace_from, + i->uri_replace_to); +#endif + + return wsi; + +bail1: + lws_client_stash_destroy(wsi); + +bail: + lws_free(wsi); +#if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2) +bail2: +#endif + if (i->pwsi) + *i->pwsi = NULL; + + return NULL; +} + +struct lws * +lws_client_connect_via_info2(struct lws *wsi) +{ + struct client_info_stash *stash = wsi->stash; + + if (!stash) + return wsi; + + /* + * we're not necessarily in a position to action these right away, + * stash them... we only need during connect phase so into a temp + * allocated stash + */ + if (lws_hdr_simple_create(wsi, _WSI_TOKEN_CLIENT_PEER_ADDRESS, + stash->address)) + goto bail1; + + if (lws_hdr_simple_create(wsi, _WSI_TOKEN_CLIENT_URI, stash->path)) + goto bail1; + + if (lws_hdr_simple_create(wsi, _WSI_TOKEN_CLIENT_HOST, stash->host)) + goto bail1; + + if (stash->origin) + if (lws_hdr_simple_create(wsi, _WSI_TOKEN_CLIENT_ORIGIN, + stash->origin)) + goto bail1; + /* + * this is a list of protocols we tell the server we're okay with + * stash it for later when we compare server response with it + */ + if (stash->protocol) + if (lws_hdr_simple_create(wsi, _WSI_TOKEN_CLIENT_SENT_PROTOCOLS, + stash->protocol)) + goto bail1; + if (stash->method) + if (lws_hdr_simple_create(wsi, _WSI_TOKEN_CLIENT_METHOD, + stash->method)) + goto bail1; + if (stash->iface) + if (lws_hdr_simple_create(wsi, _WSI_TOKEN_CLIENT_IFACE, + stash->iface)) + goto bail1; + if (stash->alpn) + if (lws_hdr_simple_create(wsi, _WSI_TOKEN_CLIENT_ALPN, + stash->alpn)) + goto bail1; + +#if defined(LWS_WITH_SOCKS5) + if (!wsi->vhost->socks_proxy_port) + lws_client_stash_destroy(wsi); +#endif + + wsi->context->count_wsi_allocated++; + + return lws_client_connect_2(wsi); + +bail1: +#if defined(LWS_WITH_SOCKS5) + if (!wsi->vhost->socks_proxy_port) + lws_free_set_NULL(wsi->stash); +#endif + + return NULL; +} + +LWS_VISIBLE struct lws * +lws_client_connect_extended(struct lws_context *context, const char *address, + int port, int ssl_connection, const char *path, + const char *host, const char *origin, + const char *protocol, int ietf_version_or_minus_one, + void *userdata) +{ + struct lws_client_connect_info i; + + memset(&i, 0, sizeof(i)); + + i.context = context; + i.address = address; + i.port = port; + i.ssl_connection = ssl_connection; + i.path = path; + i.host = host; + i.origin = origin; + i.protocol = protocol; + i.ietf_version_or_minus_one = ietf_version_or_minus_one; + i.userdata = userdata; + + return lws_client_connect_via_info(&i); +} + +LWS_VISIBLE struct lws * +lws_client_connect(struct lws_context *context, const char *address, + int port, int ssl_connection, const char *path, + const char *host, const char *origin, + const char *protocol, int ietf_version_or_minus_one) +{ + struct lws_client_connect_info i; + + memset(&i, 0, sizeof(i)); + + i.context = context; + i.address = address; + i.port = port; + i.ssl_connection = ssl_connection; + i.path = path; + i.host = host; + i.origin = origin; + i.protocol = protocol; + i.ietf_version_or_minus_one = ietf_version_or_minus_one; + i.userdata = NULL; + + return lws_client_connect_via_info(&i); +} + +#if defined(LWS_WITH_SOCKS5) +void socks_generate_msg(struct lws *wsi, enum socks_msg_type type, + ssize_t *msg_len) +{ + struct lws_context *context = wsi->context; + struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi]; + ssize_t len = 0, n, passwd_len; + short net_num; + char *p; + + switch (type) { + case SOCKS_MSG_GREETING: + /* socks version, version 5 only */ + pt->serv_buf[len++] = SOCKS_VERSION_5; + /* number of methods */ + pt->serv_buf[len++] = 2; + /* username password method */ + pt->serv_buf[len++] = SOCKS_AUTH_USERNAME_PASSWORD; + /* no authentication method */ + pt->serv_buf[len++] = SOCKS_AUTH_NO_AUTH; + break; + + case SOCKS_MSG_USERNAME_PASSWORD: + n = strlen(wsi->vhost->socks_user); + passwd_len = strlen(wsi->vhost->socks_password); + + /* the subnegotiation version */ + pt->serv_buf[len++] = SOCKS_SUBNEGOTIATION_VERSION_1; + /* length of the user name */ + pt->serv_buf[len++] = n; + /* user name */ + lws_strncpy((char *)&pt->serv_buf[len], wsi->vhost->socks_user, + context->pt_serv_buf_size - len + 1); + len += n; + /* length of the password */ + pt->serv_buf[len++] = passwd_len; + /* password */ + lws_strncpy((char *)&pt->serv_buf[len], wsi->vhost->socks_password, + context->pt_serv_buf_size - len + 1); + len += passwd_len; + break; + + case SOCKS_MSG_CONNECT: + p = (char*)&net_num; + + /* socks version */ + pt->serv_buf[len++] = SOCKS_VERSION_5; + /* socks command */ + pt->serv_buf[len++] = SOCKS_COMMAND_CONNECT; + /* reserved */ + pt->serv_buf[len++] = 0; + /* address type */ + pt->serv_buf[len++] = SOCKS_ATYP_DOMAINNAME; + /* skip length, we fill it in at the end */ + n = len++; + + /* the address we tell SOCKS proxy to connect to */ + lws_strncpy((char *)&(pt->serv_buf[len]), wsi->stash->address, + context->pt_serv_buf_size - len + 1); + len += strlen(wsi->stash->address); + net_num = htons(wsi->c_port); + + /* the port we tell SOCKS proxy to connect to */ + pt->serv_buf[len++] = p[0]; + pt->serv_buf[len++] = p[1]; + + /* the length of the address, excluding port */ + pt->serv_buf[n] = strlen(wsi->stash->address); + break; + + default: + return; + } + + *msg_len = len; +} +#endif diff --git a/thirdparty/libwebsockets/roles/http/client/client.c b/thirdparty/libwebsockets/roles/http/client/client.c new file mode 100644 index 0000000000..5645fa2b7a --- /dev/null +++ b/thirdparty/libwebsockets/roles/http/client/client.c @@ -0,0 +1,1243 @@ +/* + * libwebsockets - lib/client/client.c + * + * Copyright (C) 2010-2017 Andy Green <andy@warmcat.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation: + * version 2.1 of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + */ + +#include "core/private.h" + +LWS_VISIBLE LWS_EXTERN void +lws_client_http_body_pending(struct lws *wsi, int something_left_to_send) +{ + wsi->client_http_body_pending = !!something_left_to_send; +} + +/* + * return self, or queued client wsi we are acting on behalf of + * + * That is the TAIL of the queue (new queue elements are added at the HEAD) + */ + +struct lws * +lws_client_wsi_effective(struct lws *wsi) +{ + struct lws_dll_lws *tail = NULL; + + if (!wsi->transaction_from_pipeline_queue || + !wsi->dll_client_transaction_queue_head.next) + return wsi; + + lws_start_foreach_dll_safe(struct lws_dll_lws *, d, d1, + wsi->dll_client_transaction_queue_head.next) { + tail = d; + } lws_end_foreach_dll_safe(d, d1); + + return lws_container_of(tail, struct lws, + dll_client_transaction_queue); +} + +/* + * return self or the guy we are queued under + * + * REQUIRES VHOST LOCK HELD + */ + +static struct lws * +_lws_client_wsi_master(struct lws *wsi) +{ + struct lws *wsi_eff = wsi; + struct lws_dll_lws *d; + + d = wsi->dll_client_transaction_queue.prev; + while (d) { + wsi_eff = lws_container_of(d, struct lws, + dll_client_transaction_queue_head); + + d = d->prev; + } + + return wsi_eff; +} + +int +lws_client_socket_service(struct lws *wsi, struct lws_pollfd *pollfd, + struct lws *wsi_conn) +{ + struct lws_context *context = wsi->context; + struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi]; + char *p = (char *)&pt->serv_buf[0]; + struct lws *w; +#if defined(LWS_WITH_TLS) + char ebuf[128]; +#endif + const char *cce = NULL; +#if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2) + ssize_t len = 0; + unsigned char c; +#endif + char *sb = p; + int n = 0; +#if defined(LWS_WITH_SOCKS5) + int conn_mode = 0, pending_timeout = 0; +#endif + + if ((pollfd->revents & LWS_POLLOUT) && + wsi->keepalive_active && + wsi->dll_client_transaction_queue_head.next) { + struct lws *wfound = NULL; + + lwsl_debug("%s: pollout HANDSHAKE2\n", __func__); + + /* + * We have a transaction queued that wants to pipeline. + * + * We have to allow it to send headers strictly in the order + * that it was queued, ie, tail-first. + */ + lws_vhost_lock(wsi->vhost); + lws_start_foreach_dll_safe(struct lws_dll_lws *, d, d1, + wsi->dll_client_transaction_queue_head.next) { + struct lws *w = lws_container_of(d, struct lws, + dll_client_transaction_queue); + + lwsl_debug("%s: %p states 0x%x\n", __func__, w, w->wsistate); + if (lwsi_state(w) == LRS_H1C_ISSUE_HANDSHAKE2) + wfound = w; + } lws_end_foreach_dll_safe(d, d1); + + if (wfound) { + /* + * pollfd has the master sockfd in it... we + * need to use that in HANDSHAKE2 to understand + * which wsi to actually write on + */ + lws_client_socket_service(wfound, pollfd, wsi); + lws_callback_on_writable(wsi); + } else + lwsl_debug("%s: didn't find anything in txn q in HS2\n", + __func__); + + lws_vhost_unlock(wsi->vhost); + + return 0; + } + + switch (lwsi_state(wsi)) { + + case LRS_WAITING_CONNECT: + + /* + * we are under PENDING_TIMEOUT_SENT_CLIENT_HANDSHAKE + * timeout protection set in client-handshake.c + */ + + if (!lws_client_connect_2(wsi)) { + /* closed */ + lwsl_client("closed\n"); + return -1; + } + + /* either still pending connection, or changed mode */ + return 0; + +#if defined(LWS_WITH_SOCKS5) + /* SOCKS Greeting Reply */ + case LRS_WAITING_SOCKS_GREETING_REPLY: + case LRS_WAITING_SOCKS_AUTH_REPLY: + case LRS_WAITING_SOCKS_CONNECT_REPLY: + + /* handle proxy hung up on us */ + + if (pollfd->revents & LWS_POLLHUP) { + lwsl_warn("SOCKS connection %p (fd=%d) dead\n", + (void *)wsi, pollfd->fd); + goto bail3; + } + + n = recv(wsi->desc.sockfd, sb, context->pt_serv_buf_size, 0); + if (n < 0) { + if (LWS_ERRNO == LWS_EAGAIN) { + lwsl_debug("SOCKS read EAGAIN, retrying\n"); + return 0; + } + lwsl_err("ERROR reading from SOCKS socket\n"); + goto bail3; + } + + switch (lwsi_state(wsi)) { + + case LRS_WAITING_SOCKS_GREETING_REPLY: + if (pt->serv_buf[0] != SOCKS_VERSION_5) + goto socks_reply_fail; + + if (pt->serv_buf[1] == SOCKS_AUTH_NO_AUTH) { + lwsl_client("SOCKS GR: No Auth Method\n"); + socks_generate_msg(wsi, SOCKS_MSG_CONNECT, &len); + conn_mode = LRS_WAITING_SOCKS_CONNECT_REPLY; + pending_timeout = + PENDING_TIMEOUT_AWAITING_SOCKS_CONNECT_REPLY; + goto socks_send; + } + + if (pt->serv_buf[1] == SOCKS_AUTH_USERNAME_PASSWORD) { + lwsl_client("SOCKS GR: User/Pw Method\n"); + socks_generate_msg(wsi, + SOCKS_MSG_USERNAME_PASSWORD, + &len); + conn_mode = LRS_WAITING_SOCKS_AUTH_REPLY; + pending_timeout = + PENDING_TIMEOUT_AWAITING_SOCKS_AUTH_REPLY; + goto socks_send; + } + goto socks_reply_fail; + + case LRS_WAITING_SOCKS_AUTH_REPLY: + if (pt->serv_buf[0] != SOCKS_SUBNEGOTIATION_VERSION_1 || + pt->serv_buf[1] != SOCKS_SUBNEGOTIATION_STATUS_SUCCESS) + goto socks_reply_fail; + + lwsl_client("SOCKS password OK, sending connect\n"); + socks_generate_msg(wsi, SOCKS_MSG_CONNECT, &len); + conn_mode = LRS_WAITING_SOCKS_CONNECT_REPLY; + pending_timeout = + PENDING_TIMEOUT_AWAITING_SOCKS_CONNECT_REPLY; +socks_send: + n = send(wsi->desc.sockfd, (char *)pt->serv_buf, len, + MSG_NOSIGNAL); + if (n < 0) { + lwsl_debug("ERROR writing to socks proxy\n"); + goto bail3; + } + + lws_set_timeout(wsi, pending_timeout, AWAITING_TIMEOUT); + lwsi_set_state(wsi, conn_mode); + break; + +socks_reply_fail: + lwsl_notice("socks reply: v%d, err %d\n", + pt->serv_buf[0], pt->serv_buf[1]); + goto bail3; + + case LRS_WAITING_SOCKS_CONNECT_REPLY: + if (pt->serv_buf[0] != SOCKS_VERSION_5 || + pt->serv_buf[1] != SOCKS_REQUEST_REPLY_SUCCESS) + goto socks_reply_fail; + + lwsl_client("socks connect OK\n"); + + /* free stash since we are done with it */ + lws_client_stash_destroy(wsi); + if (lws_hdr_simple_create(wsi, + _WSI_TOKEN_CLIENT_PEER_ADDRESS, + wsi->vhost->socks_proxy_address)) + goto bail3; + + wsi->c_port = wsi->vhost->socks_proxy_port; + + /* clear his proxy connection timeout */ + lws_set_timeout(wsi, NO_PENDING_TIMEOUT, 0); + goto start_ws_handshake; + default: + break; + } + break; +#endif + + case LRS_WAITING_PROXY_REPLY: + + /* handle proxy hung up on us */ + + if (pollfd->revents & LWS_POLLHUP) { + + lwsl_warn("Proxy connection %p (fd=%d) dead\n", + (void *)wsi, pollfd->fd); + + goto bail3; + } + + n = recv(wsi->desc.sockfd, sb, context->pt_serv_buf_size, 0); + if (n < 0) { + if (LWS_ERRNO == LWS_EAGAIN) { + lwsl_debug("Proxy read EAGAIN... retrying\n"); + return 0; + } + lwsl_err("ERROR reading from proxy socket\n"); + goto bail3; + } + + pt->serv_buf[13] = '\0'; + if (strcmp(sb, "HTTP/1.0 200 ") && + strcmp(sb, "HTTP/1.1 200 ")) { + lwsl_err("ERROR proxy: %s\n", sb); + goto bail3; + } + + /* clear his proxy connection timeout */ + + lws_set_timeout(wsi, NO_PENDING_TIMEOUT, 0); + + /* fallthru */ + + case LRS_H1C_ISSUE_HANDSHAKE: + + /* + * we are under PENDING_TIMEOUT_SENT_CLIENT_HANDSHAKE + * timeout protection set in client-handshake.c + * + * take care of our lws_callback_on_writable + * happening at a time when there's no real connection yet + */ +#if defined(LWS_WITH_SOCKS5) +start_ws_handshake: +#endif + if (lws_change_pollfd(wsi, LWS_POLLOUT, 0)) + return -1; + +#if defined(LWS_WITH_TLS) + /* we can retry this... just cook the SSL BIO the first time */ + + if ((wsi->tls.use_ssl & LCCSCF_USE_SSL) && !wsi->tls.ssl && + lws_ssl_client_bio_create(wsi) < 0) { + cce = "bio_create failed"; + goto bail3; + } + + if (wsi->tls.use_ssl & LCCSCF_USE_SSL) { + n = lws_ssl_client_connect1(wsi); + if (!n) + return 0; + if (n < 0) { + cce = "lws_ssl_client_connect1 failed"; + goto bail3; + } + } else + wsi->tls.ssl = NULL; + + /* fallthru */ + + case LRS_WAITING_SSL: + + if (wsi->tls.use_ssl & LCCSCF_USE_SSL) { + n = lws_ssl_client_connect2(wsi, ebuf, sizeof(ebuf)); + if (!n) + return 0; + if (n < 0) { + cce = ebuf; + goto bail3; + } + } else + wsi->tls.ssl = NULL; +#endif +#if defined (LWS_WITH_HTTP2) + if (wsi->client_h2_alpn) { + /* + * We connected to the server and set up tls, and + * negotiated "h2". + * + * So this is it, we are an h2 master client connection + * now, not an h1 client connection. + */ + lws_tls_server_conn_alpn(wsi); + + /* send the H2 preface to legitimize the connection */ + if (lws_h2_issue_preface(wsi)) { + cce = "error sending h2 preface"; + goto bail3; + } + + break; + } +#endif + lwsi_set_state(wsi, LRS_H1C_ISSUE_HANDSHAKE2); + lws_set_timeout(wsi, PENDING_TIMEOUT_AWAITING_CLIENT_HS_SEND, + context->timeout_secs); + + /* fallthru */ + + case LRS_H1C_ISSUE_HANDSHAKE2: + p = lws_generate_client_handshake(wsi, p); + if (p == NULL) { + if (wsi->role_ops == &role_ops_raw_skt || + wsi->role_ops == &role_ops_raw_file) + return 0; + + lwsl_err("Failed to generate handshake for client\n"); + lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS, "chs"); + return 0; + } + + /* send our request to the server */ + lws_latency_pre(context, wsi); + + w = _lws_client_wsi_master(wsi); + lwsl_info("%s: HANDSHAKE2: %p: sending headers on %p (wsistate 0x%x 0x%x)\n", + __func__, wsi, w, wsi->wsistate, w->wsistate); + + n = lws_ssl_capable_write(w, (unsigned char *)sb, (int)(p - sb)); + lws_latency(context, wsi, "send lws_issue_raw", n, + n == p - sb); + switch (n) { + case LWS_SSL_CAPABLE_ERROR: + lwsl_debug("ERROR writing to client socket\n"); + lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS, "cws"); + return 0; + case LWS_SSL_CAPABLE_MORE_SERVICE: + lws_callback_on_writable(wsi); + break; + } + + if (wsi->client_http_body_pending) { + lwsi_set_state(wsi, LRS_ISSUE_HTTP_BODY); + lws_set_timeout(wsi, + PENDING_TIMEOUT_CLIENT_ISSUE_PAYLOAD, + context->timeout_secs); + /* user code must ask for writable callback */ + break; + } + + lwsi_set_state(wsi, LRS_WAITING_SERVER_REPLY); + wsi->hdr_parsing_completed = 0; + + if (lwsi_state(w) == LRS_IDLING) { + lwsi_set_state(w, LRS_WAITING_SERVER_REPLY); + w->hdr_parsing_completed = 0; +#if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2) + w->http.ah->parser_state = WSI_TOKEN_NAME_PART; + w->http.ah->lextable_pos = 0; + /* If we're (re)starting on headers, need other implied init */ + wsi->http.ah->ues = URIES_IDLE; +#endif + } + + lws_set_timeout(wsi, PENDING_TIMEOUT_AWAITING_SERVER_RESPONSE, + wsi->context->timeout_secs); + + lws_callback_on_writable(w); + + goto client_http_body_sent; + + case LRS_ISSUE_HTTP_BODY: + if (wsi->client_http_body_pending) { + //lws_set_timeout(wsi, + // PENDING_TIMEOUT_CLIENT_ISSUE_PAYLOAD, + // context->timeout_secs); + /* user code must ask for writable callback */ + break; + } +client_http_body_sent: +#if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2) + /* prepare ourselves to do the parsing */ + wsi->http.ah->parser_state = WSI_TOKEN_NAME_PART; + wsi->http.ah->lextable_pos = 0; +#endif + lwsi_set_state(wsi, LRS_WAITING_SERVER_REPLY); + lws_set_timeout(wsi, PENDING_TIMEOUT_AWAITING_SERVER_RESPONSE, + context->timeout_secs); + break; + + case LRS_WAITING_SERVER_REPLY: + /* + * handle server hanging up on us... + * but if there is POLLIN waiting, handle that first + */ + if ((pollfd->revents & (LWS_POLLIN | LWS_POLLHUP)) == + LWS_POLLHUP) { + + lwsl_debug("Server connection %p (fd=%d) dead\n", + (void *)wsi, pollfd->fd); + cce = "Peer hung up"; + goto bail3; + } + + if (!(pollfd->revents & LWS_POLLIN)) + break; + +#if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2) + /* interpret the server response + * + * HTTP/1.1 101 Switching Protocols + * Upgrade: websocket + * Connection: Upgrade + * Sec-WebSocket-Accept: me89jWimTRKTWwrS3aRrL53YZSo= + * Sec-WebSocket-Nonce: AQIDBAUGBwgJCgsMDQ4PEC== + * Sec-WebSocket-Protocol: chat + * + * we have to take some care here to only take from the + * socket bytewise. The browser may (and has been seen to + * in the case that onopen() performs websocket traffic) + * coalesce both handshake response and websocket traffic + * in one packet, since at that point the connection is + * definitively ready from browser pov. + */ + len = 1; + while (wsi->http.ah->parser_state != WSI_PARSING_COMPLETE && + len > 0) { + int plen = 1; + + n = lws_ssl_capable_read(wsi, &c, 1); + lws_latency(context, wsi, "send lws_issue_raw", n, + n == 1); + switch (n) { + case 0: + case LWS_SSL_CAPABLE_ERROR: + cce = "read failed"; + goto bail3; + case LWS_SSL_CAPABLE_MORE_SERVICE: + return 0; + } + + if (lws_parse(wsi, &c, &plen)) { + lwsl_warn("problems parsing header\n"); + goto bail3; + } + } + + /* + * hs may also be coming in multiple packets, there is a 5-sec + * libwebsocket timeout still active here too, so if parsing did + * not complete just wait for next packet coming in this state + */ + if (wsi->http.ah->parser_state != WSI_PARSING_COMPLETE) + break; + +#endif + + /* + * otherwise deal with the handshake. If there's any + * packet traffic already arrived we'll trigger poll() again + * right away and deal with it that way + */ + return lws_client_interpret_server_handshake(wsi); + +bail3: + lwsl_info("closing conn at LWS_CONNMODE...SERVER_REPLY\n"); + if (cce) + lwsl_info("reason: %s\n", cce); + wsi->protocol->callback(wsi, + LWS_CALLBACK_CLIENT_CONNECTION_ERROR, + wsi->user_space, (void *)cce, cce ? strlen(cce) : 0); + wsi->already_did_cce = 1; + lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS, "cbail3"); + return -1; + + default: + break; + } + + return 0; +} + +#if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2) + +int LWS_WARN_UNUSED_RESULT +lws_http_transaction_completed_client(struct lws *wsi) +{ + struct lws *wsi_eff = lws_client_wsi_effective(wsi); + + lwsl_info("%s: wsi: %p, wsi_eff: %p\n", __func__, wsi, wsi_eff); + + if (user_callback_handle_rxflow(wsi_eff->protocol->callback, + wsi_eff, LWS_CALLBACK_COMPLETED_CLIENT_HTTP, + wsi_eff->user_space, NULL, 0)) { + lwsl_debug("%s: Completed call returned nonzero (role 0x%x)\n", + __func__, lwsi_role(wsi_eff)); + return -1; + } + + /* + * Are we constitutionally capable of having a queue, ie, we are on + * the "active client connections" list? + * + * If not, that's it for us. + */ + + if (lws_dll_is_null(&wsi->dll_active_client_conns)) + return -1; + + /* if this was a queued guy, close him and remove from queue */ + + if (wsi->transaction_from_pipeline_queue) { + lwsl_debug("closing queued wsi %p\n", wsi_eff); + /* so the close doesn't trigger a CCE */ + wsi_eff->already_did_cce = 1; + __lws_close_free_wsi(wsi_eff, + LWS_CLOSE_STATUS_CLIENT_TRANSACTION_DONE, + "queued client done"); + } + + _lws_header_table_reset(wsi->http.ah); + + /* after the first one, they can only be coming from the queue */ + wsi->transaction_from_pipeline_queue = 1; + + wsi->http.rx_content_length = 0; + wsi->hdr_parsing_completed = 0; + + /* is there a new tail after removing that one? */ + wsi_eff = lws_client_wsi_effective(wsi); + + /* + * Do we have something pipelined waiting? + * it's OK if he hasn't managed to send his headers yet... he's next + * in line to do that... + */ + if (wsi_eff == wsi) { + /* + * Nothing pipelined... we should hang around a bit + * in case something turns up... + */ + lwsl_info("%s: nothing pipelined waiting\n", __func__); + lwsi_set_state(wsi, LRS_IDLING); + + lws_set_timeout(wsi, PENDING_TIMEOUT_CLIENT_CONN_IDLE, 5); + + return 0; + } + + /* + * H1: we can serialize the queued guys into the same ah + * H2: everybody needs their own ah until their own STREAM_END + */ + + /* otherwise set ourselves up ready to go again */ + lwsi_set_state(wsi, LRS_WAITING_SERVER_REPLY); + + wsi->http.ah->parser_state = WSI_TOKEN_NAME_PART; + wsi->http.ah->lextable_pos = 0; + + lws_set_timeout(wsi, PENDING_TIMEOUT_AWAITING_SERVER_RESPONSE, + wsi->context->timeout_secs); + + /* If we're (re)starting on headers, need other implied init */ + wsi->http.ah->ues = URIES_IDLE; + + lwsl_info("%s: %p: new queued transaction as %p\n", __func__, wsi, wsi_eff); + lws_callback_on_writable(wsi); + + return 0; +} + +LWS_VISIBLE LWS_EXTERN unsigned int +lws_http_client_http_response(struct lws *_wsi) +{ + struct lws *wsi; + unsigned int resp; + + if (_wsi->http.ah && _wsi->http.ah->http_response) + return _wsi->http.ah->http_response; + + lws_vhost_lock(_wsi->vhost); + wsi = _lws_client_wsi_master(_wsi); + resp = wsi->http.ah->http_response; + lws_vhost_unlock(_wsi->vhost); + + return resp; +} +#endif +#if defined(LWS_PLAT_OPTEE) +char * +strrchr(const char *s, int c) +{ + char *hit = NULL; + + while (*s) + if (*(s++) == (char)c) + hit = (char *)s - 1; + + return hit; +} + +#define atoll atoi +#endif + +#if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2) +int +lws_client_interpret_server_handshake(struct lws *wsi) +{ + int n, port = 0, ssl = 0; + int close_reason = LWS_CLOSE_STATUS_PROTOCOL_ERR; + const char *prot, *ads = NULL, *path, *cce = NULL; + struct allocated_headers *ah = NULL; + struct lws *w = lws_client_wsi_effective(wsi); + char *p, *q; + char new_path[300]; + + lws_client_stash_destroy(wsi); + + ah = wsi->http.ah; + if (!wsi->do_ws) { + /* we are being an http client... + */ +#if defined(LWS_ROLE_H2) + if (wsi->client_h2_alpn || wsi->client_h2_substream) { + lwsl_debug("%s: %p: transitioning to h2 client\n", __func__, wsi); + lws_role_transition(wsi, LWSIFR_CLIENT, + LRS_ESTABLISHED, &role_ops_h2); + } else +#endif + { +#if defined(LWS_ROLE_H1) + { + lwsl_debug("%s: %p: transitioning to h1 client\n", __func__, wsi); + lws_role_transition(wsi, LWSIFR_CLIENT, + LRS_ESTABLISHED, &role_ops_h1); + } +#else + return -1; +#endif + } + + wsi->http.ah = ah; + ah->http_response = 0; + } + + /* + * well, what the server sent looked reasonable for syntax. + * Now let's confirm it sent all the necessary headers + * + * http (non-ws) client will expect something like this + * + * HTTP/1.0.200 + * server:.libwebsockets + * content-type:.text/html + * content-length:.17703 + * set-cookie:.test=LWS_1456736240_336776_COOKIE;Max-Age=360000 + */ + + wsi->http.connection_type = HTTP_CONNECTION_KEEP_ALIVE; + if (!wsi->client_h2_substream) { + p = lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP); + if (wsi->do_ws && !p) { + lwsl_info("no URI\n"); + cce = "HS: URI missing"; + goto bail3; + } + if (!p) { + p = lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP1_0); + wsi->http.connection_type = HTTP_CONNECTION_CLOSE; + } + if (!p) { + cce = "HS: URI missing"; + lwsl_info("no URI\n"); + goto bail3; + } + } else { + p = lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP_COLON_STATUS); + if (!p) { + cce = "HS: :status missing"; + lwsl_info("no status\n"); + goto bail3; + } + } + n = atoi(p); + if (ah) + ah->http_response = n; + + if (n == 301 || n == 302 || n == 303 || n == 307 || n == 308) { + p = lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP_LOCATION); + if (!p) { + cce = "HS: Redirect code but no Location"; + goto bail3; + } + + /* Relative reference absolute path */ + if (p[0] == '/') { +#if defined(LWS_WITH_TLS) + ssl = wsi->tls.use_ssl & LCCSCF_USE_SSL; +#endif + ads = lws_hdr_simple_ptr(wsi, + _WSI_TOKEN_CLIENT_PEER_ADDRESS); + port = wsi->c_port; + /* +1 as lws_client_reset expects leading / omitted */ + path = p + 1; + } + /* Absolute (Full) URI */ + else if (strchr(p, ':')) { + if (lws_parse_uri(p, &prot, &ads, &port, &path)) { + cce = "HS: URI did not parse"; + goto bail3; + } + + if (!strcmp(prot, "wss") || !strcmp(prot, "https")) + ssl = 1; + } + /* Relative reference relative path */ + else { + /* This doesn't try to calculate an absolute path, + * that will be left to the server */ +#if defined(LWS_WITH_TLS) + ssl = wsi->tls.use_ssl & LCCSCF_USE_SSL; +#endif + ads = lws_hdr_simple_ptr(wsi, + _WSI_TOKEN_CLIENT_PEER_ADDRESS); + port = wsi->c_port; + /* +1 as lws_client_reset expects leading / omitted */ + path = new_path + 1; + lws_strncpy(new_path, lws_hdr_simple_ptr(wsi, + _WSI_TOKEN_CLIENT_URI), sizeof(new_path)); + q = strrchr(new_path, '/'); + if (q) + lws_strncpy(q + 1, p, sizeof(new_path) - + (q - new_path) - 1); + else + path = p; + } + +#if defined(LWS_WITH_TLS) + if ((wsi->tls.use_ssl & LCCSCF_USE_SSL) && !ssl) { + cce = "HS: Redirect attempted SSL downgrade"; + goto bail3; + } +#endif + + if (!lws_client_reset(&wsi, ssl, ads, port, path, ads)) { + /* there are two ways to fail out with NULL return... + * simple, early problem where the wsi is intact, or + * we went through with the reconnect attempt and the + * wsi is already closed. In the latter case, the wsi + * has beet set to NULL additionally. + */ + lwsl_err("Redirect failed\n"); + cce = "HS: Redirect failed"; + if (wsi) + goto bail3; + + return 1; + } + return 0; + } + + if (!wsi->do_ws) { + + /* if h1 KA is allowed, enable the queued pipeline guys */ + + if (!wsi->client_h2_alpn && !wsi->client_h2_substream && w == wsi) { /* ie, coming to this for the first time */ + if (wsi->http.connection_type == HTTP_CONNECTION_KEEP_ALIVE) + wsi->keepalive_active = 1; + else { + /* + * Ugh... now the main http connection has seen + * both sides, we learn the server doesn't + * support keepalive. + * + * That means any guys queued on us are going + * to have to be restarted from connect2 with + * their own connections. + */ + + /* + * stick around telling any new guys they can't + * pipeline to this server + */ + wsi->keepalive_rejected = 1; + + lws_vhost_lock(wsi->vhost); + lws_start_foreach_dll_safe(struct lws_dll_lws *, d, d1, + wsi->dll_client_transaction_queue_head.next) { + struct lws *ww = lws_container_of(d, struct lws, + dll_client_transaction_queue); + + /* remove him from our queue */ + lws_dll_lws_remove(&ww->dll_client_transaction_queue); + /* give up on pipelining */ + ww->client_pipeline = 0; + + /* go back to "trying to connect" state */ + lws_role_transition(ww, LWSIFR_CLIENT, + LRS_UNCONNECTED, +#if defined(LWS_ROLE_H1) + &role_ops_h1); +#else +#if defined (LWS_ROLE_H2) + &role_ops_h2); +#else + &role_ops_raw); +#endif +#endif + ww->user_space = NULL; + } lws_end_foreach_dll_safe(d, d1); + lws_vhost_unlock(wsi->vhost); + } + } + +#ifdef LWS_WITH_HTTP_PROXY + wsi->http.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), + "text/html", 9)) + wsi->http.perform_rewrite = 1; + } +#endif + + /* allocate the per-connection user memory (if any) */ + if (lws_ensure_user_space(wsi)) { + lwsl_err("Problem allocating wsi user mem\n"); + cce = "HS: OOM"; + goto bail2; + } + + /* he may choose to send us stuff in chunked transfer-coding */ + wsi->chunked = 0; + wsi->chunk_remaining = 0; /* ie, next thing is chunk size */ + if (lws_hdr_total_length(wsi, + WSI_TOKEN_HTTP_TRANSFER_ENCODING)) { + wsi->chunked = !strcmp(lws_hdr_simple_ptr(wsi, + WSI_TOKEN_HTTP_TRANSFER_ENCODING), + "chunked"); + /* first thing is hex, after payload there is crlf */ + wsi->chunk_parser = ELCP_HEX; + } + + if (lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_CONTENT_LENGTH)) { + wsi->http.rx_content_length = + atoll(lws_hdr_simple_ptr(wsi, + WSI_TOKEN_HTTP_CONTENT_LENGTH)); + lwsl_info("%s: incoming content length %llu\n", + __func__, (unsigned long long) + wsi->http.rx_content_length); + wsi->http.rx_content_remain = + wsi->http.rx_content_length; + } else /* can't do 1.1 without a content length or chunked */ + if (!wsi->chunked) + wsi->http.connection_type = + HTTP_CONNECTION_CLOSE; + + /* + * we seem to be good to go, give client last chance to check + * headers and OK it + */ + if (w->protocol->callback(w, + LWS_CALLBACK_CLIENT_FILTER_PRE_ESTABLISH, + w->user_space, NULL, 0)) { + + cce = "HS: disallowed by client filter"; + goto bail2; + } + + /* clear his proxy connection timeout */ + lws_set_timeout(wsi, NO_PENDING_TIMEOUT, 0); + + wsi->rxflow_change_to = LWS_RXFLOW_ALLOW; + + /* call him back to inform him he is up */ + if (w->protocol->callback(w, + LWS_CALLBACK_ESTABLISHED_CLIENT_HTTP, + w->user_space, NULL, 0)) { + cce = "HS: disallowed at ESTABLISHED"; + goto bail3; + } + + /* + * for pipelining, master needs to keep his ah... guys who + * queued on him can drop it now though. + */ + + if (w != wsi) + /* free up parsing allocations for queued guy */ + lws_header_table_detach(w, 0); + + lwsl_info("%s: client connection up\n", __func__); + + return 0; + } + +#if defined(LWS_ROLE_WS) + switch (lws_client_ws_upgrade(wsi, &cce)) { + case 2: + goto bail2; + case 3: + goto bail3; + } + + return 0; +#endif + +bail3: + close_reason = LWS_CLOSE_STATUS_NOSTATUS; + +bail2: + if (wsi->protocol) { + n = 0; + if (cce) + n = (int)strlen(cce); + w->protocol->callback(w, + LWS_CALLBACK_CLIENT_CONNECTION_ERROR, + w->user_space, (void *)cce, + (unsigned int)n); + } + wsi->already_did_cce = 1; + + lwsl_info("closing connection due to bail2 connection error\n"); + + /* closing will free up his parsing allocations */ + lws_close_free_wsi(wsi, close_reason, "c hs interp"); + + return 1; +} +#endif + +char * +lws_generate_client_handshake(struct lws *wsi, char *pkt) +{ + char *p = pkt; + const char *meth; + const char *pp = lws_hdr_simple_ptr(wsi, + _WSI_TOKEN_CLIENT_SENT_PROTOCOLS); + + meth = lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_METHOD); + if (!meth) { + meth = "GET"; + wsi->do_ws = 1; + } else { + wsi->do_ws = 0; + } + + if (!strcmp(meth, "RAW")) { + lws_set_timeout(wsi, NO_PENDING_TIMEOUT, 0); + lwsl_notice("client transition to raw\n"); + + if (pp) { + const struct lws_protocols *pr; + + pr = lws_vhost_name_to_protocol(wsi->vhost, pp); + + if (!pr) { + lwsl_err("protocol %s not enabled on vhost\n", + pp); + return NULL; + } + + lws_bind_protocol(wsi, pr); + } + + if ((wsi->protocol->callback)(wsi, LWS_CALLBACK_RAW_ADOPT, + wsi->user_space, NULL, 0)) + return NULL; + + lws_role_transition(wsi, 0, LRS_ESTABLISHED, &role_ops_raw_skt); + lws_header_table_detach(wsi, 1); + + return NULL; + } + + /* + * 04 example client handshake + * + * GET /chat HTTP/1.1 + * Host: server.example.com + * Upgrade: websocket + * Connection: Upgrade + * Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ== + * Sec-WebSocket-Origin: http://example.com + * Sec-WebSocket-Protocol: chat, superchat + * Sec-WebSocket-Version: 4 + */ + + p += sprintf(p, "%s %s HTTP/1.1\x0d\x0a", meth, + lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_URI)); + + p += sprintf(p, "Pragma: no-cache\x0d\x0a" + "Cache-Control: no-cache\x0d\x0a"); + + p += sprintf(p, "Host: %s\x0d\x0a", + lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_HOST)); + + if (lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_ORIGIN)) { + if (lws_check_opt(wsi->context->options, + LWS_SERVER_OPTION_JUST_USE_RAW_ORIGIN)) + p += sprintf(p, "Origin: %s\x0d\x0a", + lws_hdr_simple_ptr(wsi, + _WSI_TOKEN_CLIENT_ORIGIN)); + else + p += sprintf(p, "Origin: http://%s\x0d\x0a", + lws_hdr_simple_ptr(wsi, + _WSI_TOKEN_CLIENT_ORIGIN)); + } +#if defined(LWS_ROLE_WS) + if (wsi->do_ws) + p = lws_generate_client_ws_handshake(wsi, p); +#endif + + /* give userland a chance to append, eg, cookies */ + + if (wsi->protocol->callback(wsi, + LWS_CALLBACK_CLIENT_APPEND_HANDSHAKE_HEADER, + wsi->user_space, &p, + (pkt + wsi->context->pt_serv_buf_size) - p - 12)) + return NULL; + + p += sprintf(p, "\x0d\x0a"); + + return p; +} + +#if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2) + +LWS_VISIBLE int +lws_http_client_read(struct lws *wsi, char **buf, int *len) +{ + int rlen, n; + + rlen = lws_ssl_capable_read(wsi, (unsigned char *)*buf, *len); + *len = 0; + + // lwsl_notice("%s: rlen %d\n", __func__, rlen); + + /* allow the source to signal he has data again next time */ + lws_change_pollfd(wsi, 0, LWS_POLLIN); + + if (rlen == LWS_SSL_CAPABLE_ERROR) { + lwsl_notice("%s: SSL capable error\n", __func__); + return -1; + } + + if (rlen == 0) + return -1; + + if (rlen < 0) + return 0; + + *len = rlen; + wsi->client_rx_avail = 0; + + /* + * server may insist on transfer-encoding: chunked, + * so http client must deal with it + */ +spin_chunks: + while (wsi->chunked && (wsi->chunk_parser != ELCP_CONTENT) && *len) { + switch (wsi->chunk_parser) { + case ELCP_HEX: + if ((*buf)[0] == '\x0d') { + wsi->chunk_parser = ELCP_CR; + break; + } + n = char_to_hex((*buf)[0]); + if (n < 0) { + lwsl_debug("chunking failure\n"); + return -1; + } + wsi->chunk_remaining <<= 4; + wsi->chunk_remaining |= n; + break; + case ELCP_CR: + if ((*buf)[0] != '\x0a') { + lwsl_debug("chunking failure\n"); + return -1; + } + wsi->chunk_parser = ELCP_CONTENT; + lwsl_info("chunk %d\n", wsi->chunk_remaining); + if (wsi->chunk_remaining) + break; + lwsl_info("final chunk\n"); + goto completed; + + case ELCP_CONTENT: + break; + + case ELCP_POST_CR: + if ((*buf)[0] != '\x0d') { + lwsl_debug("chunking failure\n"); + + return -1; + } + + wsi->chunk_parser = ELCP_POST_LF; + break; + + case ELCP_POST_LF: + if ((*buf)[0] != '\x0a') + return -1; + + wsi->chunk_parser = ELCP_HEX; + wsi->chunk_remaining = 0; + break; + } + (*buf)++; + (*len)--; + } + + if (wsi->chunked && !wsi->chunk_remaining) + return 0; + + if (wsi->http.rx_content_remain && + wsi->http.rx_content_remain < (unsigned int)*len) + n = (int)wsi->http.rx_content_remain; + else + n = *len; + + if (wsi->chunked && wsi->chunk_remaining && + wsi->chunk_remaining < n) + n = wsi->chunk_remaining; + +#ifdef LWS_WITH_HTTP_PROXY + /* hubbub */ + if (wsi->http.perform_rewrite) + lws_rewrite_parse(wsi->http.rw, (unsigned char *)*buf, n); + else +#endif + { + struct lws *wsi_eff = lws_client_wsi_effective(wsi); + + if (user_callback_handle_rxflow(wsi_eff->protocol->callback, + wsi_eff, LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ, + wsi_eff->user_space, *buf, n)) { + lwsl_debug("%s: RECEIVE_CLIENT_HTTP_READ returned -1\n", + __func__); + + return -1; + } + } + + if (wsi->chunked && wsi->chunk_remaining) { + (*buf) += n; + wsi->chunk_remaining -= n; + *len -= n; + } + + if (wsi->chunked && !wsi->chunk_remaining) + wsi->chunk_parser = ELCP_POST_CR; + + if (wsi->chunked && *len) + goto spin_chunks; + + if (wsi->chunked) + return 0; + + /* if we know the content length, decrement the content remaining */ + if (wsi->http.rx_content_length > 0) + wsi->http.rx_content_remain -= n; + + // lwsl_notice("rx_content_remain %lld, rx_content_length %lld\n", + // wsi->http.rx_content_remain, wsi->http.rx_content_length); + + if (wsi->http.rx_content_remain || !wsi->http.rx_content_length) + return 0; + +completed: + + if (lws_http_transaction_completed_client(wsi)) { + lwsl_notice("%s: transaction completed says -1\n", __func__); + return -1; + } + + return 0; +} + +#endif |