int lws_http_action(struct lws *wsi) { struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi]; enum http_connection_type connection_type; enum http_version request_version; char content_length_str[32]; struct lws_http_mount *hm, *hit = NULL; unsigned int n, count = 0; char http_version_str[10]; char http_conn_str[20]; int http_version_len; char *uri_ptr = NULL; int uri_len = 0, best = 0; int meth = -1; static const unsigned char methods[] = { WSI_TOKEN_GET_URI, WSI_TOKEN_POST_URI, WSI_TOKEN_OPTIONS_URI, WSI_TOKEN_PUT_URI, WSI_TOKEN_PATCH_URI, WSI_TOKEN_DELETE_URI, #ifdef LWS_USE_HTTP2 WSI_TOKEN_HTTP_COLON_PATH, #endif }; #if defined(_DEBUG) || defined(LWS_WITH_ACCESS_LOG) static const char * const method_names[] = { "GET", "POST", "OPTIONS", "PUT", "PATCH", "DELETE", #ifdef LWS_USE_HTTP2 ":path", #endif }; #endif /* it's not websocket.... shall we accept it as http? */ for (n = 0; n < ARRAY_SIZE(methods); n++) if (lws_hdr_total_length(wsi, methods[n])) count++; if (!count) { lwsl_warn("Missing URI in HTTP request\n"); goto bail_nuke_ah; } if (count != 1) { lwsl_warn("multiple methods?\n"); goto bail_nuke_ah; } if (lws_ensure_user_space(wsi)) goto bail_nuke_ah; for (n = 0; n < ARRAY_SIZE(methods); n++) if (lws_hdr_total_length(wsi, methods[n])) { uri_ptr = lws_hdr_simple_ptr(wsi, methods[n]); uri_len = lws_hdr_total_length(wsi, methods[n]); lwsl_info("Method: %s request for '%s'\n", method_names[n], uri_ptr); meth = n; break; } (void)meth; /* we insist on absolute paths */ if (uri_ptr[0] != '/') { lws_return_http_status(wsi, HTTP_STATUS_FORBIDDEN, NULL); goto bail_nuke_ah; } /* HTTP header had a content length? */ wsi->u.http.content_length = 0; if (lws_hdr_total_length(wsi, WSI_TOKEN_POST_URI) || lws_hdr_total_length(wsi, WSI_TOKEN_PATCH_URI) || lws_hdr_total_length(wsi, WSI_TOKEN_PUT_URI)) wsi->u.http.content_length = 100 * 1024 * 1024; if (lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_CONTENT_LENGTH)) { lws_hdr_copy(wsi, content_length_str, sizeof(content_length_str) - 1, WSI_TOKEN_HTTP_CONTENT_LENGTH); wsi->u.http.content_length = atoi(content_length_str); } if (wsi->http2_substream) { wsi->u.http.request_version = HTTP_VERSION_2; } else { /* http_version? Default to 1.0, override with token: */ request_version = HTTP_VERSION_1_0; /* Works for single digit HTTP versions. : */ http_version_len = lws_hdr_total_length(wsi, WSI_TOKEN_HTTP); if (http_version_len > 7) { lws_hdr_copy(wsi, http_version_str, sizeof(http_version_str) - 1, WSI_TOKEN_HTTP); if (http_version_str[5] == '1' && http_version_str[7] == '1') request_version = HTTP_VERSION_1_1; } wsi->u.http.request_version = request_version; /* HTTP/1.1 defaults to "keep-alive", 1.0 to "close" */ if (request_version == HTTP_VERSION_1_1) connection_type = HTTP_CONNECTION_KEEP_ALIVE; else connection_type = HTTP_CONNECTION_CLOSE; /* Override default if http "Connection:" header: */ if (lws_hdr_total_length(wsi, WSI_TOKEN_CONNECTION)) { lws_hdr_copy(wsi, http_conn_str, sizeof(http_conn_str) - 1, WSI_TOKEN_CONNECTION); http_conn_str[sizeof(http_conn_str) - 1] = '\0'; if (!strcasecmp(http_conn_str, "keep-alive")) connection_type = HTTP_CONNECTION_KEEP_ALIVE; else if (!strcasecmp(http_conn_str, "close")) connection_type = HTTP_CONNECTION_CLOSE; } wsi->u.http.connection_type = connection_type; } n = wsi->protocol->callback(wsi, LWS_CALLBACK_FILTER_HTTP_CONNECTION, wsi->user_space, uri_ptr, uri_len); if (n) { lwsl_info("LWS_CALLBACK_HTTP closing\n"); return 1; } /* * if there is content supposed to be coming, * put a timeout on it having arrived */ lws_set_timeout(wsi, PENDING_TIMEOUT_HTTP_CONTENT, wsi->context->timeout_secs); #ifdef LWS_OPENSSL_SUPPORT if (wsi->redirect_to_https) { /* * we accepted http:// only so we could redirect to * https://, so issue the redirect. Create the redirection * URI from the host: header and ignore the path part */ unsigned char *start = pt->serv_buf + LWS_PRE, *p = start, *end = p + 512; if (!lws_hdr_total_length(wsi, WSI_TOKEN_HOST)) goto bail_nuke_ah; n = sprintf((char *)end, "https://%s/", lws_hdr_simple_ptr(wsi, WSI_TOKEN_HOST)); n = lws_http_redirect(wsi, end, n, &p, end); if ((int)n < 0) goto bail_nuke_ah; return lws_http_transaction_completed(wsi); } #endif #ifdef LWS_WITH_ACCESS_LOG /* * Produce Apache-compatible log string for wsi, like this: * * 2.31.234.19 - - [27/Mar/2016:03:22:44 +0800] * "GET /aep-screen.png HTTP/1.1" * 200 152987 "https://libwebsockets.org/index.html" * "Mozilla/5.0 (Macint... Chrome/49.0.2623.87 Safari/537.36" * */ { static const char * const hver[] = { "http/1.0", "http/1.1", "http/2" }; #ifdef LWS_USE_IPV6 char ads[INET6_ADDRSTRLEN]; #else char ads[INET_ADDRSTRLEN]; #endif char da[64]; const char *pa, *me; struct tm *tmp; time_t t = time(NULL); int l = 256; if (wsi->access_log_pending) lws_access_log(wsi); wsi->access_log.header_log = lws_malloc(l); tmp = localtime(&t); if (tmp) strftime(da, sizeof(da), "%d/%b/%Y:%H:%M:%S %z", tmp); else strcpy(da, "01/Jan/1970:00:00:00 +0000"); pa = lws_get_peer_simple(wsi, ads, sizeof(ads)); if (!pa) pa = "(unknown)"; if (meth >= 0) me = method_names[meth]; else me = "unknown"; snprintf(wsi->access_log.header_log, l, "%s - - [%s] \"%s %s %s\"", pa, da, me, uri_ptr, hver[wsi->u.http.request_version]); l = lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_USER_AGENT); if (l) { wsi->access_log.user_agent = lws_malloc(l + 2); lws_hdr_copy(wsi, wsi->access_log.user_agent, l + 1, WSI_TOKEN_HTTP_USER_AGENT); } wsi->access_log_pending = 1; } #endif /* can we serve it from the mount list? */ hm = wsi->vhost->mount_list; while (hm) { if (uri_len >= hm->mountpoint_len && !strncmp(uri_ptr, hm->mountpoint, hm->mountpoint_len) && (uri_ptr[hm->mountpoint_len] == '\0' || uri_ptr[hm->mountpoint_len] == '/' || hm->mountpoint_len == 1) ) { if ((hm->origin_protocol == LWSMPRO_CGI || lws_hdr_total_length(wsi, WSI_TOKEN_GET_URI)) && hm->mountpoint_len > best) { best = hm->mountpoint_len; hit = hm; } } hm = hm->mount_next; } if (hit) { char *s = uri_ptr + hit->mountpoint_len; lwsl_debug("*** hit %d %d %s\n", hit->mountpoint_len, hit->origin_protocol , hit->origin); /* * if we have a mountpoint like https://xxx.com/yyy * there is an implied / at the end for our purposes since * we can only mount on a "directory". * * But if we just go with that, the browser cannot understand * that he is actually looking down one "directory level", so * even though we give him /yyy/abc.html he acts like the * current directory level is /. So relative urls like "x.png" * wrongly look outside the mountpoint. * * Therefore if we didn't come in on a url with an explicit * / at the end, we must redirect to add it so the browser * understands he is one "directory level" down. */ if ((hit->mountpoint_len > 1 || (hit->origin_protocol & 4)) && (*s != '/' || (hit->origin_protocol & 4)) && (hit->origin_protocol != LWSMPRO_CGI)) { unsigned char *start = pt->serv_buf + LWS_PRE, *p = start, *end = p + 512; static const char *oprot[] = { "http://", "https://" }; lwsl_notice("Doing 301 '%s' org %s\n", s, hit->origin); if (!lws_hdr_total_length(wsi, WSI_TOKEN_HOST)) goto bail_nuke_ah; /* > at start indicates deal with by redirect */ if (hit->origin_protocol & 4) n = snprintf((char *)end, 256, "%s%s", oprot[hit->origin_protocol & 1], hit->origin); else n = snprintf((char *)end, 256, "https://%s/%s/", lws_hdr_simple_ptr(wsi, WSI_TOKEN_HOST), uri_ptr); n = lws_http_redirect(wsi, end, n, &p, end); if ((int)n < 0) goto bail_nuke_ah; return lws_http_transaction_completed(wsi); } #ifdef LWS_WITH_CGI /* did we hit something with a cgi:// origin? */ if (hit->origin_protocol == LWSMPRO_CGI) { const char *cmd[] = { NULL, /* replace with cgi path */ NULL }; unsigned char *p, *end, buffer[256]; lwsl_debug("%s: cgi\n", __func__); cmd[0] = hit->origin; n = 5; if (hit->cgi_timeout) n = hit->cgi_timeout; n = lws_cgi(wsi, cmd, hit->mountpoint_len, n, hit->cgienv); if (n) { lwsl_err("%s: cgi failed\n"); return -1; } p = buffer + LWS_PRE; end = p + sizeof(buffer) - LWS_PRE; if (lws_add_http_header_status(wsi, 200, &p, end)) return 1; if (lws_add_http_header_by_token(wsi, WSI_TOKEN_CONNECTION, (unsigned char *)"close", 5, &p, end)) return 1; n = lws_write(wsi, buffer + LWS_PRE, p - (buffer + LWS_PRE), LWS_WRITE_HTTP_HEADERS); goto deal_body; } #endif n = strlen(s); if (s[0] == '\0' || (n == 1 && s[n - 1] == '/')) s = (char *)hit->def; if (!s) s = "index.html"; // lwsl_err("okok\n"); n = lws_http_serve(wsi, s, hit->origin); } else n = wsi->protocol->callback(wsi, LWS_CALLBACK_HTTP, wsi->user_space, uri_ptr, uri_len); if (n) { lwsl_info("LWS_CALLBACK_HTTP closing\n"); return 1; } #ifdef LWS_WITH_CGI deal_body: #endif /* * If we're not issuing a file, check for content_length or * HTTP keep-alive. No keep-alive header allocation for * ISSUING_FILE, as this uses HTTP/1.0. * * In any case, return 0 and let lws_read decide how to * proceed based on state */ if (wsi->state != LWSS_HTTP_ISSUING_FILE) /* Prepare to read body if we have a content length: */ if (wsi->u.http.content_length > 0) wsi->state = LWSS_HTTP_BODY; return 0; bail_nuke_ah: /* we're closing, losing some rx is OK */ wsi->u.hdr.ah->rxpos = wsi->u.hdr.ah->rxlen; lws_header_table_detach(wsi, 1); return 1; }
int lws_http_action(struct lws *wsi) { #ifdef LWS_OPENSSL_SUPPORT struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi]; #endif enum http_connection_type connection_type; enum http_version request_version; char content_length_str[32]; struct lws_http_mount *hm; unsigned int n, count = 0; char http_version_str[10]; char http_conn_str[20]; int http_version_len; char *uri_ptr = NULL; int uri_len = 0; static const unsigned char methods[] = { WSI_TOKEN_GET_URI, WSI_TOKEN_POST_URI, WSI_TOKEN_OPTIONS_URI, WSI_TOKEN_PUT_URI, WSI_TOKEN_PATCH_URI, WSI_TOKEN_DELETE_URI, #ifdef LWS_USE_HTTP2 WSI_TOKEN_HTTP_COLON_PATH, #endif }; #ifdef _DEBUG static const char * const method_names[] = { "GET", "POST", "OPTIONS", "PUT", "PATCH", "DELETE", #ifdef LWS_USE_HTTP2 ":path", #endif }; #endif /* it's not websocket.... shall we accept it as http? */ for (n = 0; n < ARRAY_SIZE(methods); n++) if (lws_hdr_total_length(wsi, methods[n])) count++; if (!count) { lwsl_warn("Missing URI in HTTP request\n"); goto bail_nuke_ah; } if (count != 1) { lwsl_warn("multiple methods?\n"); goto bail_nuke_ah; } if (lws_ensure_user_space(wsi)) goto bail_nuke_ah; for (n = 0; n < ARRAY_SIZE(methods); n++) if (lws_hdr_total_length(wsi, methods[n])) { uri_ptr = lws_hdr_simple_ptr(wsi, methods[n]); uri_len = lws_hdr_total_length(wsi, methods[n]); lwsl_info("Method: %s request for '%s'\n", method_names[n], uri_ptr); break; } /* HTTP header had a content length? */ wsi->u.http.content_length = 0; if (lws_hdr_total_length(wsi, WSI_TOKEN_POST_URI) || lws_hdr_total_length(wsi, WSI_TOKEN_PATCH_URI) || lws_hdr_total_length(wsi, WSI_TOKEN_PUT_URI)) wsi->u.http.content_length = 100 * 1024 * 1024; if (lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_CONTENT_LENGTH)) { lws_hdr_copy(wsi, content_length_str, sizeof(content_length_str) - 1, WSI_TOKEN_HTTP_CONTENT_LENGTH); wsi->u.http.content_length = atoi(content_length_str); } /* http_version? Default to 1.0, override with token: */ request_version = HTTP_VERSION_1_0; /* Works for single digit HTTP versions. : */ http_version_len = lws_hdr_total_length(wsi, WSI_TOKEN_HTTP); if (http_version_len > 7) { lws_hdr_copy(wsi, http_version_str, sizeof(http_version_str) - 1, WSI_TOKEN_HTTP); if (http_version_str[5] == '1' && http_version_str[7] == '1') request_version = HTTP_VERSION_1_1; } wsi->u.http.request_version = request_version; /* HTTP/1.1 defaults to "keep-alive", 1.0 to "close" */ if (request_version == HTTP_VERSION_1_1) connection_type = HTTP_CONNECTION_KEEP_ALIVE; else connection_type = HTTP_CONNECTION_CLOSE; /* Override default if http "Connection:" header: */ if (lws_hdr_total_length(wsi, WSI_TOKEN_CONNECTION)) { lws_hdr_copy(wsi, http_conn_str, sizeof(http_conn_str) - 1, WSI_TOKEN_CONNECTION); http_conn_str[sizeof(http_conn_str) - 1] = '\0'; if (!strcasecmp(http_conn_str, "keep-alive")) connection_type = HTTP_CONNECTION_KEEP_ALIVE; else if (!strcasecmp(http_conn_str, "close")) connection_type = HTTP_CONNECTION_CLOSE; } wsi->u.http.connection_type = connection_type; n = wsi->protocol->callback(wsi, LWS_CALLBACK_FILTER_HTTP_CONNECTION, wsi->user_space, uri_ptr, uri_len); if (n) { lwsl_info("LWS_CALLBACK_HTTP closing\n"); return 1; } /* * if there is content supposed to be coming, * put a timeout on it having arrived */ lws_set_timeout(wsi, PENDING_TIMEOUT_HTTP_CONTENT, wsi->context->timeout_secs); #ifdef LWS_OPENSSL_SUPPORT if (wsi->redirect_to_https) { /* * we accepted http:// only so we could redirect to * https://, so issue the redirect. Create the redirection * URI from the host: header and ignore the path part */ unsigned char *start = pt->serv_buf + LWS_PRE, *p = start, *end = p + 512; if (!lws_hdr_total_length(wsi, WSI_TOKEN_HOST)) goto bail_nuke_ah; if (lws_add_http_header_status(wsi, 301, &p, end)) goto bail_nuke_ah; n = sprintf((char *)end, "htt struct lws_http_mount *hm;ps://%s/", lws_hdr_simple_ptr(wsi, WSI_TOKEN_HOST)); if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_LOCATION, end, n, &p, end)) goto bail_nuke_ah; if (lws_finalize_http_header(wsi, &p, end)) goto bail_nuke_ah; n = lws_write(wsi, start, p - start, LWS_WRITE_HTTP_HEADERS); if ((int)n < 0) goto bail_nuke_ah; return lws_http_transaction_completed(wsi); } #endif /* can we serve it from the mount list? */ hm = wsi->vhost->mount_list; while (hm) { char *s = uri_ptr + hm->mountpoint_len; if (s[0] == '\0') s = (char *)hm->def; if (!s) s = "index.html"; if (uri_len >= hm->mountpoint_len && !strncmp(uri_ptr, hm->mountpoint, hm->mountpoint_len)) { n = lws_http_serve(wsi, s, hm->origin); break; } hm = hm->mount_next; } if (!hm) n = wsi->protocol->callback(wsi, LWS_CALLBACK_HTTP, wsi->user_space, uri_ptr, uri_len); if (n) { lwsl_info("LWS_CALLBACK_HTTP closing\n"); return 1; } /* * If we're not issuing a file, check for content_length or * HTTP keep-alive. No keep-alive header allocation for * ISSUING_FILE, as this uses HTTP/1.0. * * In any case, return 0 and let lws_read decide how to * proceed based on state */ if (wsi->state != LWSS_HTTP_ISSUING_FILE) /* Prepare to read body if we have a content length: */ if (wsi->u.http.content_length > 0) wsi->state = LWSS_HTTP_BODY; return 0; bail_nuke_ah: /* we're closing, losing some rx is OK */ wsi->u.hdr.ah->rxpos = wsi->u.hdr.ah->rxlen; lws_header_table_detach(wsi, 1); return 1; }