LWS_VISIBLE int lws_http_redirect(struct lws *wsi, int code, const unsigned char *loc, int len, unsigned char **p, unsigned char *end) { unsigned char *start = *p; if (lws_add_http_header_status(wsi, code, p, end)) return -1; if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_LOCATION, loc, len, p, end)) return -1; /* * if we're going with http/1.1 and keepalive, we have to give fake * content metadata so the client knows we completed the transaction and * it can do the redirect... */ if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_CONTENT_TYPE, (unsigned char *)"text/html", 9, p, end)) return -1; if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_CONTENT_LENGTH, (unsigned char *)"0", 1, p, end)) return -1; if (lws_finalize_http_header(wsi, p, end)) return -1; return lws_write(wsi, start, *p - start, LWS_WRITE_HTTP_HEADERS | LWS_WRITE_H2_STREAM_END); }
/** * libwebsockets_return_http_status() - Return simple http status * @context: libwebsockets context * @wsi: Websocket instance (available from user callback) * @code: Status index, eg, 404 * @html_body: User-readable HTML description < 1KB, or NULL * * Helper to report HTTP errors back to the client cleanly and * consistently */ LWS_VISIBLE int libwebsockets_return_http_status( struct libwebsocket_context *context, struct libwebsocket *wsi, unsigned int code, const char *html_body) { int n, m; unsigned char *p = context->service_buffer + LWS_SEND_BUFFER_PRE_PADDING; unsigned char *start = p; unsigned char *end = p + sizeof(context->service_buffer) - LWS_SEND_BUFFER_PRE_PADDING; if (!html_body) html_body = ""; if (lws_add_http_header_status(context, wsi, code, &p, end)) return 1; if (lws_add_http_header_by_token(context, wsi, WSI_TOKEN_HTTP_SERVER, (unsigned char *)"libwebsockets", 13, &p, end)) return 1; if (lws_add_http_header_by_token(context, wsi, WSI_TOKEN_HTTP_CONTENT_TYPE, (unsigned char *)"text/html", 9, &p, end)) return 1; if (lws_finalize_http_header(context, wsi, &p, end)) return 1; m = libwebsocket_write(wsi, start, p - start, LWS_WRITE_HTTP_HEADERS); if (m) return m; n = sprintf((char *)start, "<html><body><h1>%u</h1>%s</body></html>", code, html_body); m = libwebsocket_write(wsi, start, n, LWS_WRITE_HTTP); return m; }
LWS_VISIBLE int lws_serve_http_file(struct lws_context *context, struct lws *wsi, const char *file, const char *content_type, const char *other_headers, int other_headers_len) { unsigned char *response = context->service_buffer + LWS_SEND_BUFFER_PRE_PADDING; unsigned char *p = response; unsigned char *end = p + sizeof(context->service_buffer) - LWS_SEND_BUFFER_PRE_PADDING; int ret = 0; wsi->u.http.fd = lws_plat_open_file(file, &wsi->u.http.filelen); if (wsi->u.http.fd == LWS_INVALID_FILE) { lwsl_err("Unable to open '%s'\n", file); lws_return_http_status(context, wsi, HTTP_STATUS_NOT_FOUND, NULL); return -1; } if (lws_add_http_header_status(context, wsi, 200, &p, end)) return -1; if (lws_add_http_header_by_token(context, wsi, WSI_TOKEN_HTTP_SERVER, (unsigned char *)"libwebsockets", 13, &p, end)) return -1; if (lws_add_http_header_by_token(context, wsi, WSI_TOKEN_HTTP_CONTENT_TYPE, (unsigned char *)content_type, strlen(content_type), &p, end)) return -1; if (lws_add_http_header_content_length(context, wsi, wsi->u.http.filelen, &p, end)) return -1; if (other_headers) { if ((end - p) < other_headers_len) return -1; memcpy(p, other_headers, other_headers_len); p += other_headers_len; } if (lws_finalize_http_header(context, wsi, &p, end)) return -1; ret = lws_write(wsi, response, p - response, LWS_WRITE_HTTP_HEADERS); if (ret != (p - response)) { lwsl_err("_write returned %d from %d\n", ret, (p - response)); return -1; } wsi->u.http.filepos = 0; wsi->state = WSI_STATE_HTTP_ISSUING_FILE; return lws_serve_http_file_fragment(context, wsi); }
LWS_VISIBLE int lws_serve_http_file(struct lws *wsi, const char *file, const char *content_type, const char *other_headers, int other_headers_len) { struct lws_context *context = lws_get_context(wsi); struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi]; unsigned char *response = pt->serv_buf + LWS_PRE; unsigned char *p = response; unsigned char *end = p + LWS_MAX_SOCKET_IO_BUF - LWS_PRE; int ret = 0; wsi->u.http.fd = lws_plat_file_open(wsi, file, &wsi->u.http.filelen, O_RDONLY); if (wsi->u.http.fd == LWS_INVALID_FILE) { lwsl_err("Unable to open '%s'\n", file); lws_return_http_status(wsi, HTTP_STATUS_NOT_FOUND, NULL); return -1; } if (lws_add_http_header_status(wsi, 200, &p, end)) return -1; if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_SERVER, (unsigned char *)"libwebsockets", 13, &p, end)) return -1; if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_CONTENT_TYPE, (unsigned char *)content_type, strlen(content_type), &p, end)) return -1; if (lws_add_http_header_content_length(wsi, wsi->u.http.filelen, &p, end)) return -1; if (other_headers) { if ((end - p) < other_headers_len) return -1; memcpy(p, other_headers, other_headers_len); p += other_headers_len; } if (lws_finalize_http_header(wsi, &p, end)) return -1; ret = lws_write(wsi, response, p - response, LWS_WRITE_HTTP_HEADERS); if (ret != (p - response)) { lwsl_err("_write returned %d from %d\n", ret, (p - response)); return -1; } wsi->u.http.filepos = 0; wsi->state = LWSS_HTTP_ISSUING_FILE; return lws_serve_http_file_fragment(wsi); }
/** * lws_return_http_status() - Return simple http status * @wsi: Websocket instance (available from user callback) * @code: Status index, eg, 404 * @html_body: User-readable HTML description < 1KB, or NULL * * Helper to report HTTP errors back to the client cleanly and * consistently */ LWS_VISIBLE int lws_return_http_status(struct lws *wsi, unsigned int code, const char *html_body) { struct lws_context *context = lws_get_context(wsi); struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi]; unsigned char *p = pt->serv_buf + LWS_PRE; unsigned char *start = p, *body = p + 512; unsigned char *end = p + LWS_MAX_SOCKET_IO_BUF - LWS_PRE; int n, m, len; char slen[20]; if (!html_body) html_body = ""; len = sprintf((char *)body, "<html><body><h1>%u</h1>%s</body></html>", code, html_body); if (lws_add_http_header_status(wsi, code, &p, end)) return 1; if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_CONTENT_TYPE, (unsigned char *)"text/html", 9, &p, end)) return 1; n = sprintf(slen, "%d", len); if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_CONTENT_LENGTH, (unsigned char *)slen, n, &p, end)) return 1; if (lws_finalize_http_header(wsi, &p, end)) return 1; m = lws_write(wsi, start, p - start, LWS_WRITE_HTTP_HEADERS); if (m != (int)(p - start)) return 1; m = lws_write(wsi, body, len, LWS_WRITE_HTTP); return m != n; }
static int callback_messageboard(struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len) { struct per_session_data__gs_mb *pss = (struct per_session_data__gs_mb *)user; const struct lws_protocol_vhost_options *pvo; struct per_vhost_data__gs_mb *vhd = (struct per_vhost_data__gs_mb *) lws_protocol_vh_priv_get(lws_get_vhost(wsi), lws_get_protocol(wsi)); unsigned char *p, *start, *end, buffer[LWS_PRE + 256]; char s[512]; int n; switch (reason) { case LWS_CALLBACK_PROTOCOL_INIT: /* per vhost */ vhd = lws_protocol_vh_priv_zalloc(lws_get_vhost(wsi), lws_get_protocol(wsi), sizeof(struct per_vhost_data__gs_mb)); if (!vhd) return 1; vhd->vh = lws_get_vhost(wsi); vhd->gsp = lws_vhost_name_to_protocol(vhd->vh, "protocol-generic-sessions"); if (!vhd->gsp) { lwsl_err("messageboard: requires generic-sessions\n"); return 1; } pvo = (const struct lws_protocol_vhost_options *)in; while (pvo) { if (!strcmp(pvo->name, "message-db")) strncpy(vhd->message_db, pvo->value, sizeof(vhd->message_db) - 1); pvo = pvo->next; } if (!vhd->message_db[0]) { lwsl_err("messageboard: \"message-db\" pvo missing\n"); return 1; } if (sqlite3_open_v2(vhd->message_db, &vhd->pdb, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, NULL) != SQLITE_OK) { lwsl_err("Unable to open message db %s: %s\n", vhd->message_db, sqlite3_errmsg(vhd->pdb)); return 1; } if (sqlite3_exec(vhd->pdb, "create table if not exists msg (" " idx integer primary key, time integer," " username varchar(32), email varchar(100)," " ip varchar(80), content blob);", NULL, NULL, NULL) != SQLITE_OK) { lwsl_err("Unable to create msg table: %s\n", sqlite3_errmsg(vhd->pdb)); return 1; } vhd->last_idx = get_last_idx(vhd); break; case LWS_CALLBACK_PROTOCOL_DESTROY: if (vhd->pdb) sqlite3_close(vhd->pdb); goto passthru; case LWS_CALLBACK_ESTABLISHED: vhd->gsp->callback(wsi, LWS_CALLBACK_SESSION_INFO, pss->pss_gs, &pss->sinfo, 0); if (!pss->sinfo.username[0]) { lwsl_notice("messageboard ws attempt with no session\n"); return -1; } lws_callback_on_writable(wsi); break; case LWS_CALLBACK_SERVER_WRITEABLE: { struct message m; char j[MAX_MSG_LEN + 512], e[MAX_MSG_LEN + 512], *p = j + LWS_PRE, *start = p, *end = j + sizeof(j) - LWS_PRE; if (pss->last_idx == vhd->last_idx) break; /* restrict to last 10 */ if (!pss->last_idx) if (vhd->last_idx >= 10) pss->last_idx = vhd->last_idx - 10; sprintf(s, "select idx, time, username, email, ip, content " "from msg where idx > %lu order by idx limit 1;", pss->last_idx); if (sqlite3_exec(vhd->pdb, s, lookup_cb, &m, NULL) != SQLITE_OK) { lwsl_err("Unable to lookup msg: %s\n", sqlite3_errmsg(vhd->pdb)); return 0; } /* format in JSON */ p += snprintf(p, end - p, "{\"idx\":\"%lu\",\"time\":\"%lu\",", m.idx, m.time); p += snprintf(p, end - p, " \"username\":\"%s\",", lws_json_purify(e, m.username, sizeof(e))); p += snprintf(p, end - p, " \"email\":\"%s\",", lws_json_purify(e, m.email, sizeof(e))); p += snprintf(p, end - p, " \"ip\":\"%s\",", lws_json_purify(e, m.ip, sizeof(e))); p += snprintf(p, end - p, " \"content\":\"%s\"}", lws_json_purify(e, m.content, sizeof(e))); if (lws_write(wsi, (unsigned char *)start, p - start, LWS_WRITE_TEXT) < 0) return -1; pss->last_idx = m.idx; if (pss->last_idx == vhd->last_idx) break; lws_callback_on_writable(wsi); /* more to do */ } break; case LWS_CALLBACK_HTTP: pss->our_form = 0; /* ie, it's our messageboard new message form */ if (!strcmp((const char *)in, "/msg")) { pss->our_form = 1; break; } goto passthru; case LWS_CALLBACK_HTTP_BODY: if (!pss->our_form) goto passthru; if (len < 2) break; if (!pss->spa) { pss->spa = lws_spa_create(wsi, param_names, ARRAY_SIZE(param_names), MAX_MSG_LEN + 1024, NULL, NULL); if (!pss->spa) return -1; } if (lws_spa_process(pss->spa, in, len)) { lwsl_notice("spa process blew\n"); return -1; } break; case LWS_CALLBACK_HTTP_BODY_COMPLETION: if (!pss->our_form) goto passthru; if (post_message(wsi, vhd, pss)) return -1; p = buffer + LWS_PRE; start = p; 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_HTTP_CONTENT_TYPE, (unsigned char *)"text/plain", 10, &p, end)) return -1; if (lws_add_http_header_content_length(wsi, 1, &p, end)) return -1; if (lws_finalize_http_header(wsi, &p, end)) return -1; n = lws_write(wsi, start, p - start, LWS_WRITE_HTTP_HEADERS); if (n != (p - start)) { lwsl_err("_write returned %d from %d\n", n, (p - start)); return -1; } s[0] = '0'; n = lws_write(wsi, (unsigned char *)s, 1, LWS_WRITE_HTTP); if (n != 1) return -1; goto try_to_reuse; case LWS_CALLBACK_HTTP_BIND_PROTOCOL: if (!pss || pss->pss_gs) break; pss->pss_gs = malloc(vhd->gsp->per_session_data_size); if (!pss->pss_gs) return -1; memset(pss->pss_gs, 0, vhd->gsp->per_session_data_size); break; case LWS_CALLBACK_HTTP_DROP_PROTOCOL: if (vhd->gsp->callback(wsi, reason, pss ? pss->pss_gs : NULL, in, len)) return -1; if (pss && pss->spa) { lws_spa_destroy(pss->spa); pss->spa = NULL; } if (pss && pss->pss_gs) { free(pss->pss_gs); pss->pss_gs = NULL; } break; default: passthru: return vhd->gsp->callback(wsi, reason, pss ? pss->pss_gs : NULL, in, len); } return 0; try_to_reuse: if (lws_http_transaction_completed(wsi)) return -1; return 0; }
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 0 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, "https://%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 #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_err("Doing 301 '%s' org %s\n", s, hit->origin); 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; lwsl_debug("**** %s", hit->origin); /* > 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); 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); } #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 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; unsigned char buffer[4096 + LWS_PRE]; unsigned long amount, file_len, sent; char leaf_path[1024]; const char *mimetype; char *other_headers; 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; #ifdef EXTERNAL_POLL struct lws_pollargs *pa = (struct lws_pollargs *)in; #endif // lwsl_err("%s: reason %d\n", __func__, reason); switch (reason) { case LWS_CALLBACK_HTTP: { char name[100], rip[50]; lws_get_peer_addresses(wsi, lws_get_socket_fd(wsi), name, sizeof(name), rip, sizeof(rip)); sprintf(buf, "%s (%s)", name, rip); lwsl_notice("HTTP connect from %s\n", buf); } if (len < 1) { lws_return_http_status(wsi, HTTP_STATUS_BAD_REQUEST, NULL); goto try_to_reuse; } #ifndef LWS_NO_CLIENT if (!strncmp(in, "/proxytest", 10)) { struct lws_client_connect_info i; char *rootpath = "/"; const char *p = (const char *)in; if (lws_get_child(wsi)) break; pss->client_finished = 0; memset(&i,0, sizeof(i)); i.context = lws_get_context(wsi); i.address = "git.libwebsockets.org"; i.port = 80; i.ssl_connection = 0; if (p[10]) i.path = (char *)in + 10; else i.path = rootpath; i.host = "git.libwebsockets.org"; i.origin = NULL; i.method = "GET"; i.parent_wsi = wsi; i.uri_replace_from = "git.libwebsockets.org/"; i.uri_replace_to = "/proxytest/"; if (!lws_client_connect_via_info(&i)) { lwsl_err("proxy connect fail\n"); break; } break; } #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 /* if a legal POST URL, let it continue and accept data */ if (lws_hdr_total_length(wsi, WSI_TOKEN_POST_URI)) return 0; /* check for the "send a big file by hand" example case */ lwsl_notice("%s\n", in); if (!strcmp((const char *)in, "/leaf.jpg")) { if (strlen(resource_path) > sizeof(leaf_path) - 10) return -1; sprintf(leaf_path, "%s/leaf.jpg", resource_path); /* well, let's demonstrate how to send the hard way */ p = buffer + LWS_PRE; end = p + sizeof(buffer) - LWS_PRE; pss->fd = lws_plat_file_open(wsi, leaf_path, &file_len, LWS_O_RDONLY); if (pss->fd == LWS_INVALID_FILE) { lwsl_err("faild to open file %s\n", leaf_path); return -1; } /* * we will send a big jpeg file, but it could be * anything. Set the Content-Type: appropriately * so the browser knows what to do with it. * * Notice we use the APIs to build the header, which * will do the right thing for HTTP 1/1.1 and HTTP2 * depending on what connection it happens to be working * on */ if (lws_add_http_header_status(wsi, 200, &p, end)) return 1; if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_SERVER, (unsigned char *)"libwebsockets", 13, &p, end)) return 1; if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_CONTENT_TYPE, (unsigned char *)"image/jpeg", 10, &p, end)) return 1; if (lws_add_http_header_content_length(wsi, file_len, &p, end)) return 1; if (lws_finalize_http_header(wsi, &p, end)) return 1; /* * send the http headers... * this won't block since it's the first payload sent * on the connection since it was established * (too small for partial) * * Notice they are sent using LWS_WRITE_HTTP_HEADERS * which also means you can't send body too in one step, * this is mandated by changes in HTTP2 */ *p = '\0'; lwsl_info("%s\n", buffer + LWS_PRE); n = lws_write(wsi, buffer + LWS_PRE, p - (buffer + LWS_PRE), LWS_WRITE_HTTP_HEADERS); if (n < 0) { lws_plat_file_close(wsi, pss->fd); return -1; } /* * book us a LWS_CALLBACK_HTTP_WRITEABLE callback */ lws_callback_on_writable(wsi); break; } /* if not, send a file the easy way */ 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(buf) - 1); } else /* default file to serve */ strcat(buf, "/test.html"); buf[sizeof(buf) - 1] = '\0'; /* refuse to serve files we don't understand */ mimetype = get_mimetype(buf); if (!mimetype) { lwsl_err("Unknown mimetype for %s\n", buf); lws_return_http_status(wsi, HTTP_STATUS_UNSUPPORTED_MEDIA_TYPE, NULL); return -1; } /* demonstrates how to set a cookie on / */ other_headers = leaf_path; p = (unsigned char *)leaf_path; if (!strcmp((const char *)in, "/") && !lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_COOKIE)) { /* this isn't very unguessable but it'll do for us */ gettimeofday(&tv, NULL); n = sprintf(b64, "test=LWS_%u_%u_COOKIE;Max-Age=360000", (unsigned int)tv.tv_sec, (unsigned int)tv.tv_usec); if (lws_add_http_header_by_name(wsi, (unsigned char *)"set-cookie:", (unsigned char *)b64, n, &p, (unsigned char *)leaf_path + sizeof(leaf_path))) return 1; } if (lws_is_ssl(wsi) && lws_add_http_header_by_name(wsi, (unsigned char *) "Strict-Transport-Security:", (unsigned char *) "max-age=15768000 ; " "includeSubDomains", 36, &p, (unsigned char *)leaf_path + sizeof(leaf_path))) return 1; n = (char *)p - leaf_path; n = lws_serve_http_file(wsi, buf, mimetype, other_headers, n); if (n < 0 || ((n > 0) && lws_http_transaction_completed(wsi))) return -1; /* error or can't reuse connection: close the socket */ /* * notice that the sending of the file completes asynchronously, * we'll get a LWS_CALLBACK_HTTP_FILE_COMPLETION callback when * it's done */ break; case LWS_CALLBACK_HTTP_BODY: strncpy(buf, in, 20); buf[20] = '\0'; if (len < 20) buf[len] = '\0'; lwsl_notice("LWS_CALLBACK_HTTP_BODY: %s... len %d\n", (const char *)buf, (int)len); break; case LWS_CALLBACK_HTTP_BODY_COMPLETION: lwsl_notice("LWS_CALLBACK_HTTP_BODY_COMPLETION\n"); /* the whole of the sent body arrived, close or reuse the connection */ lws_return_http_status(wsi, HTTP_STATUS_OK, NULL); goto try_to_reuse; case LWS_CALLBACK_HTTP_FILE_COMPLETION: goto try_to_reuse; case LWS_CALLBACK_HTTP_WRITEABLE: lwsl_info("LWS_CALLBACK_HTTP_WRITEABLE\n"); if (pss->client_finished) return -1; if (pss->fd == LWS_INVALID_FILE) goto try_to_reuse; #ifdef LWS_WITH_CGI if (pss->reason_bf & 1) { if (lws_cgi_write_split_stdout_headers(wsi) < 0) goto bail; pss->reason_bf &= ~1; break; } #endif #ifndef LWS_NO_CLIENT if (pss->reason_bf & 2) { char *px = buf + LWS_PRE; int lenx = sizeof(buf) - LWS_PRE; /* * our sink is writeable and our source has something * to read. So read a lump of source material of * suitable size to send or what's available, whichever * is the smaller. */ pss->reason_bf &= ~2; wsi1 = lws_get_child(wsi); if (!wsi1) break; if (lws_http_client_read(wsi1, &px, &lenx) < 0) goto bail; if (pss->client_finished) return -1; break; } #endif /* * we can send more of whatever it is we were sending */ sent = 0; do { /* we'd like the send this much */ n = sizeof(buffer) - LWS_PRE; /* but if the peer told us he wants less, we can adapt */ m = lws_get_peer_write_allowance(wsi); /* -1 means not using a protocol that has this info */ if (m == 0) /* right now, peer can't handle anything */ goto later; if (m != -1 && m < n) /* he couldn't handle that much */ n = m; n = lws_plat_file_read(wsi, pss->fd, &amount, buffer + LWS_PRE, n); /* problem reading, close conn */ if (n < 0) { lwsl_err("problem reading file\n"); goto bail; } n = (int)amount; /* sent it all, close conn */ if (n == 0) goto penultimate; /* * To support HTTP2, must take care about preamble space * * identification of when we send the last payload frame * is handled by the library itself if you sent a * content-length header */ m = lws_write(wsi, buffer + LWS_PRE, n, LWS_WRITE_HTTP); if (m < 0) { lwsl_err("write failed\n"); /* write failed, close conn */ goto bail; } if (m) /* while still active, extend timeout */ lws_set_timeout(wsi, PENDING_TIMEOUT_HTTP_CONTENT, 5); sent += m; } while (!lws_send_pipe_choked(wsi) && (sent < 1024 * 1024)); later: lws_callback_on_writable(wsi); break; penultimate: lws_plat_file_close(wsi, pss->fd); pss->fd = LWS_INVALID_FILE; goto try_to_reuse; bail: lws_plat_file_close(wsi, pss->fd); return -1; /* * callback for confirming to continue with client IP appear in * protocol 0 callback since no websocket protocol has been agreed * yet. You can just ignore this if you won't filter on client IP * since the default unhandled callback return is 0 meaning let the * connection continue. */ case LWS_CALLBACK_FILTER_NETWORK_CONNECTION: /* if we returned non-zero from here, we kill the connection */ break; #ifndef LWS_NO_CLIENT case LWS_CALLBACK_ESTABLISHED_CLIENT_HTTP: { char ctype[64], ctlen = 0; lwsl_err("LWS_CALLBACK_ESTABLISHED_CLIENT_HTTP\n"); p = buffer + LWS_PRE; end = p + sizeof(buffer) - LWS_PRE; if (lws_add_http_header_status(lws_get_parent(wsi), 200, &p, end)) return 1; if (lws_add_http_header_by_token(lws_get_parent(wsi), WSI_TOKEN_HTTP_SERVER, (unsigned char *)"libwebsockets", 13, &p, end)) return 1; ctlen = lws_hdr_copy(wsi, ctype, sizeof(ctype), WSI_TOKEN_HTTP_CONTENT_TYPE); if (ctlen > 0) { if (lws_add_http_header_by_token(lws_get_parent(wsi), WSI_TOKEN_HTTP_CONTENT_TYPE, (unsigned char *)ctype, ctlen, &p, end)) return 1; } #if 0 if (lws_add_http_header_content_length(lws_get_parent(wsi), file_len, &p, end)) return 1; #endif if (lws_finalize_http_header(lws_get_parent(wsi), &p, end)) return 1; *p = '\0'; lwsl_info("%s\n", buffer + LWS_PRE); n = lws_write(lws_get_parent(wsi), buffer + LWS_PRE, p - (buffer + LWS_PRE), LWS_WRITE_HTTP_HEADERS); if (n < 0) return -1; break; } case LWS_CALLBACK_CLOSED_CLIENT_HTTP: //lwsl_err("LWS_CALLBACK_CLOSED_CLIENT_HTTP\n"); return -1; break; case LWS_CALLBACK_RECEIVE_CLIENT_HTTP: //lwsl_err("LWS_CALLBACK_RECEIVE_CLIENT_HTTP: wsi %p\n", wsi); assert(lws_get_parent(wsi)); if (!lws_get_parent(wsi)) break; // lwsl_err("LWS_CALLBACK_RECEIVE_CLIENT_HTTP: wsi %p: sock: %d, parent_wsi: %p, parent_sock:%d, len %d\n", // wsi, lws_get_socket_fd(wsi), // lws_get_parent(wsi), // lws_get_socket_fd(lws_get_parent(wsi)), len); pss1 = lws_wsi_user(lws_get_parent(wsi)); pss1->reason_bf |= 2; lws_callback_on_writable(lws_get_parent(wsi)); break; case LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ: //lwsl_err("LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ len %d\n", len); assert(lws_get_parent(wsi)); m = lws_write(lws_get_parent(wsi), (unsigned char *)in, len, LWS_WRITE_HTTP); if (m < 0) return -1; break; case LWS_CALLBACK_COMPLETED_CLIENT_HTTP: //lwsl_err("LWS_CALLBACK_COMPLETED_CLIENT_HTTP\n"); assert(lws_get_parent(wsi)); if (!lws_get_parent(wsi)) break; pss1 = lws_wsi_user(lws_get_parent(wsi)); pss1->client_finished = 1; break; #endif #ifdef LWS_WITH_CGI /* CGI IO events (POLLIN/OUT) appear here our demo user code policy is * * - POST data goes on subprocess stdin * - subprocess stdout goes on http via writeable callback * - subprocess stderr goes to the logs */ case LWS_CALLBACK_CGI: pss->args = *((struct lws_cgi_args *)in); //lwsl_notice("LWS_CALLBACK_CGI: ch %d\n", pss->args.ch); switch (pss->args.ch) { /* which of stdin/out/err ? */ case LWS_STDIN: /* TBD stdin rx flow control */ break; case LWS_STDOUT: pss->reason_bf |= 1; /* when writing to MASTER would not block */ lws_callback_on_writable(wsi); break; case LWS_STDERR: n = read(lws_get_socket_fd(pss->args.stdwsi[LWS_STDERR]), buf, 127); //lwsl_notice("stderr reads %d\n", n); if (n > 0) { if (buf[n - 1] != '\n') buf[n++] = '\n'; buf[n] = '\0'; lwsl_notice("CGI-stderr: %s\n", buf); } break; } break; case LWS_CALLBACK_CGI_TERMINATED: //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"); 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); if (n < pss->args.len) lwsl_notice("LWS_CALLBACK_CGI_STDIN_DATA: sent %d only %d went", n, pss->args.len); return n; #endif /* * callbacks for managing the external poll() array appear in * protocol 0 callback */ case LWS_CALLBACK_LOCK_POLL: /* * lock mutex to protect pollfd state * called before any other POLL related callback * if protecting wsi lifecycle change, len == 1 */ test_server_lock(len); break; case LWS_CALLBACK_UNLOCK_POLL: /* * unlock mutex to protect pollfd state when * called after any other POLL related callback * if protecting wsi lifecycle change, len == 1 */ test_server_unlock(len); break; #ifdef EXTERNAL_POLL case LWS_CALLBACK_ADD_POLL_FD: if (count_pollfds >= max_poll_elements) { lwsl_err("LWS_CALLBACK_ADD_POLL_FD: too many sockets to track\n"); return 1; } fd_lookup[pa->fd] = count_pollfds; pollfds[count_pollfds].fd = pa->fd; pollfds[count_pollfds].events = pa->events; pollfds[count_pollfds++].revents = 0; break; case LWS_CALLBACK_DEL_POLL_FD: if (!--count_pollfds) break; m = fd_lookup[pa->fd]; /* have the last guy take up the vacant slot */ pollfds[m] = pollfds[count_pollfds]; fd_lookup[pollfds[count_pollfds].fd] = m; break; case LWS_CALLBACK_CHANGE_MODE_POLL_FD: pollfds[fd_lookup[pa->fd]].events = pa->events; break; #endif case LWS_CALLBACK_GET_THREAD_ID: /* * if you will call "lws_callback_on_writable" * from a different thread, return the caller thread ID * here so lws can use this information to work out if it * should signal the poll() loop to exit and restart early */ /* return pthread_getthreadid_np(); */ break; default: break; } return 0; /* if we're on HTTP1.1 or 2.0, will keep the idle connection alive */ try_to_reuse: if (lws_http_transaction_completed(wsi)) return -1; return 0; }
static int callback_http(struct libwebsocket_context *context, struct libwebsocket *wsi, enum libwebsocket_callback_reasons reason, void *user, void *in, size_t len) { #if 0 char client_name[128]; char client_ip[128]; #endif char buf[256]; char leaf_path[1024]; char b64[64]; struct timeval tv; int n, m; unsigned char *p; char *other_headers; static unsigned char buffer[4096]; struct stat stat_buf; struct per_session_data__http *pss = (struct per_session_data__http *)user; const char *mimetype; #ifdef EXTERNAL_POLL struct libwebsocket_pollargs *pa = (struct libwebsocket_pollargs *)in; #endif unsigned char *end; switch (reason) { case LWS_CALLBACK_HTTP: dump_handshake_info(wsi); if (len < 1) { libwebsockets_return_http_status(context, wsi, HTTP_STATUS_BAD_REQUEST, NULL); goto try_to_reuse; } /* this example server has no concept of directories */ if (strchr((const char *)in + 1, '/')) { libwebsockets_return_http_status(context, wsi, HTTP_STATUS_FORBIDDEN, NULL); goto try_to_reuse; } /* if a legal POST URL, let it continue and accept data */ if (lws_hdr_total_length(wsi, WSI_TOKEN_POST_URI)) return 0; /* check for the "send a big file by hand" example case */ if (!strcmp((const char *)in, "/leaf.jpg")) { if (strlen(resource_path) > sizeof(leaf_path) - 10) return -1; sprintf(leaf_path, "%s/leaf.jpg", resource_path); /* well, let's demonstrate how to send the hard way */ p = buffer + LWS_SEND_BUFFER_PRE_PADDING; end = p + sizeof(buffer) - LWS_SEND_BUFFER_PRE_PADDING; #ifdef WIN32 pss->fd = open(leaf_path, O_RDONLY | _O_BINARY); #else pss->fd = open(leaf_path, O_RDONLY); #endif if (pss->fd < 0) return -1; fstat(pss->fd, &stat_buf); /* * we will send a big jpeg file, but it could be * anything. Set the Content-Type: appropriately * so the browser knows what to do with it. * * Notice we use the APIs to build the header, which * will do the right thing for HTTP 1/1.1 and HTTP2 * depending on what connection it happens to be working * on */ if (lws_add_http_header_status(context, wsi, 200, &p, end)) return 1; if (lws_add_http_header_by_token(context, wsi, WSI_TOKEN_HTTP_SERVER, (unsigned char *)"libwebsockets", 13, &p, end)) return 1; if (lws_add_http_header_by_token(context, wsi, WSI_TOKEN_HTTP_CONTENT_TYPE, (unsigned char *)"image/jpeg", 10, &p, end)) return 1; if (lws_add_http_header_content_length(context, wsi,stat_buf.st_size, &p, end)) return 1; if (lws_finalize_http_header(context, wsi, &p, end)) return 1; /* * send the http headers... * this won't block since it's the first payload sent * on the connection since it was established * (too small for partial) * * Notice they are sent using LWS_WRITE_HTTP_HEADERS * which also means you can't send body too in one step, * this is mandated by changes in HTTP2 */ n = libwebsocket_write(wsi, buffer + LWS_SEND_BUFFER_PRE_PADDING, p - (buffer + LWS_SEND_BUFFER_PRE_PADDING), LWS_WRITE_HTTP_HEADERS); if (n < 0) { close(pss->fd); return -1; } /* * book us a LWS_CALLBACK_HTTP_WRITEABLE callback */ libwebsocket_callback_on_writable(context, wsi); break; } /* if not, send a file the easy way */ strcpy(buf, resource_path); if (strcmp(in, "/")) { if (*((const char *)in) != '/') strcat(buf, "/"); strncat(buf, in, sizeof(buf) - strlen(resource_path)); } else /* default file to serve */ strcat(buf, "/test.html"); buf[sizeof(buf) - 1] = '\0'; /* refuse to serve files we don't understand */ mimetype = get_mimetype(buf); if (!mimetype) { lwsl_err("Unknown mimetype for %s\n", buf); libwebsockets_return_http_status(context, wsi, HTTP_STATUS_UNSUPPORTED_MEDIA_TYPE, NULL); return -1; } /* demostrates how to set a cookie on / */ other_headers = NULL; n = 0; if (!strcmp((const char *)in, "/") && !lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_COOKIE)) { /* this isn't very unguessable but it'll do for us */ gettimeofday(&tv, NULL); n = sprintf(b64, "test=LWS_%u_%u_COOKIE;Max-Age=360000", (unsigned int)tv.tv_sec, (unsigned int)tv.tv_usec); p = (unsigned char *)leaf_path; if (lws_add_http_header_by_name(context, wsi, (unsigned char *)"set-cookie:", (unsigned char *)b64, n, &p, (unsigned char *)leaf_path + sizeof(leaf_path))) return 1; n = (char *)p - leaf_path; other_headers = leaf_path; } n = libwebsockets_serve_http_file(context, wsi, buf, mimetype, other_headers, n); if (n < 0 || ((n > 0) && lws_http_transaction_completed(wsi))) return -1; /* error or can't reuse connection: close the socket */ /* * notice that the sending of the file completes asynchronously, * we'll get a LWS_CALLBACK_HTTP_FILE_COMPLETION callback when * it's done */ break; case LWS_CALLBACK_HTTP_BODY: strncpy(buf, in, 20); buf[20] = '\0'; if (len < 20) buf[len] = '\0'; lwsl_notice("LWS_CALLBACK_HTTP_BODY: %s... len %d\n", (const char *)buf, (int)len); break; case LWS_CALLBACK_HTTP_BODY_COMPLETION: lwsl_notice("LWS_CALLBACK_HTTP_BODY_COMPLETION\n"); /* the whole of the sent body arrived, close or reuse the connection */ libwebsockets_return_http_status(context, wsi, HTTP_STATUS_OK, NULL); goto try_to_reuse; case LWS_CALLBACK_HTTP_FILE_COMPLETION: // lwsl_info("LWS_CALLBACK_HTTP_FILE_COMPLETION seen\n"); /* kill the connection after we sent one file */ goto try_to_reuse; case LWS_CALLBACK_HTTP_WRITEABLE: /* * we can send more of whatever it is we were sending */ lwsl_info("LWS_CALLBACK_HTTP_WRITEABLE\n"); do { lwsl_info("a\n"); n = read(pss->fd, buffer + LWS_SEND_BUFFER_PRE_PADDING, sizeof (buffer) - LWS_SEND_BUFFER_PRE_PADDING); /* problem reading, close conn */ if (n < 0) goto bail; /* sent it all, close conn */ if (n == 0) goto flush_bail; lwsl_info("b\n"); /* * To support HTTP2, must take care about preamble space * and identify when we send the last frame */ m = libwebsocket_write(wsi, buffer + LWS_SEND_BUFFER_PRE_PADDING, n, LWS_WRITE_HTTP); if (m < 0) /* write failed, close conn */ goto bail; lwsl_info("c\n"); /* * http2 won't do this */ if (m != n) /* partial write, adjust */ lseek(pss->fd, m - n, SEEK_CUR); lwsl_info("d\n"); if (m) /* while still active, extend timeout */ libwebsocket_set_timeout(wsi, PENDING_TIMEOUT_HTTP_CONTENT, 5); /* if he has indigestion, let him clear it before eating more */ if (lws_partial_buffered(wsi)) break; } while (!lws_send_pipe_choked(wsi)); lwsl_info("e\n"); libwebsocket_callback_on_writable(context, wsi); lwsl_info("f\n"); break; flush_bail: /* true if still partial pending */ if (lws_partial_buffered(wsi)) { libwebsocket_callback_on_writable(context, wsi); break; } close(pss->fd); goto try_to_reuse; bail: close(pss->fd); return -1; /* * callback for confirming to continue with client IP appear in * protocol 0 callback since no websocket protocol has been agreed * yet. You can just ignore this if you won't filter on client IP * since the default uhandled callback return is 0 meaning let the * connection continue. */ case LWS_CALLBACK_FILTER_NETWORK_CONNECTION: #if 0 libwebsockets_get_peer_addresses(context, wsi, (int)(long)in, client_name, sizeof(client_name), client_ip, sizeof(client_ip)); fprintf(stderr, "Received network connect from %s (%s)\n", client_name, client_ip); #endif /* if we returned non-zero from here, we kill the connection */ break; #ifdef EXTERNAL_POLL /* * callbacks for managing the external poll() array appear in * protocol 0 callback */ case LWS_CALLBACK_LOCK_POLL: /* * lock mutex to protect pollfd state * called before any other POLL related callback */ break; case LWS_CALLBACK_UNLOCK_POLL: /* * unlock mutex to protect pollfd state when * called after any other POLL related callback */ break; case LWS_CALLBACK_ADD_POLL_FD: if (count_pollfds >= max_poll_elements) { lwsl_err("LWS_CALLBACK_ADD_POLL_FD: too many sockets to track\n"); return 1; } fd_lookup[pa->fd] = count_pollfds; pollfds[count_pollfds].fd = pa->fd; pollfds[count_pollfds].events = pa->events; pollfds[count_pollfds++].revents = 0; break; case LWS_CALLBACK_DEL_POLL_FD: if (!--count_pollfds) break; m = fd_lookup[pa->fd]; /* have the last guy take up the vacant slot */ pollfds[m] = pollfds[count_pollfds]; fd_lookup[pollfds[count_pollfds].fd] = m; break; case LWS_CALLBACK_CHANGE_MODE_POLL_FD: pollfds[fd_lookup[pa->fd]].events = pa->events; break; #endif case LWS_CALLBACK_GET_THREAD_ID: /* * if you will call "libwebsocket_callback_on_writable" * from a different thread, return the caller thread ID * here so lws can use this information to work out if it * should signal the poll() loop to exit and restart early */ /* return pthread_getthreadid_np(); */ break; default: break; } return 0; try_to_reuse: if (lws_http_transaction_completed(wsi)) return -1; return 0; }
int lws_http_serve(struct lws *wsi, char *uri, const char *origin) { const char *mimetype; #ifndef _WIN32_WCE struct stat st; #endif char path[256], sym[256]; unsigned char *p = (unsigned char *)sym + 32 + LWS_PRE, *start = p; unsigned char *end = p + sizeof(sym) - 32 - LWS_PRE; #if !defined(WIN32) size_t len; #endif int n, spin = 0; snprintf(path, sizeof(path) - 1, "%s/%s", origin, uri); #ifndef _WIN32_WCE do { spin++; if (stat(path, &st)) { lwsl_info("unable to stat %s\n", path); goto bail; } lwsl_debug(" %s mode %d\n", path, S_IFMT & st.st_mode); #if !defined(WIN32) if ((S_IFMT & st.st_mode) == S_IFLNK) { len = readlink(path, sym, sizeof(sym) - 1); if (len) { lwsl_err("Failed to read link %s\n", path); goto bail; } sym[len] = '\0'; lwsl_debug("symlink %s -> %s\n", path, sym); snprintf(path, sizeof(path) - 1, "%s", sym); } #endif if ((S_IFMT & st.st_mode) == S_IFDIR) { lwsl_debug("default filename append to dir\n"); snprintf(path, sizeof(path) - 1, "%s/%s/index.html", origin, uri); } } while ((S_IFMT & st.st_mode) != S_IFREG && spin < 5); if (spin == 5) lwsl_err("symlink loop %s \n", path); n = sprintf(sym, "%08lX%08lX", (unsigned long)st.st_size, (unsigned long)st.st_mtime); if (lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_IF_NONE_MATCH)) { /* * he thinks he has some version of it already, * check if the tag matches */ if (!strcmp(sym, lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP_IF_NONE_MATCH))) { lwsl_debug("%s: ETAG match %s %s\n", __func__, uri, origin); /* we don't need to send the payload */ if (lws_add_http_header_status(wsi, 304, &p, end)) return -1; if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_ETAG, (unsigned char *)sym, n, &p, end)) return -1; if (lws_finalize_http_header(wsi, &p, end)) return -1; n = lws_write(wsi, start, p - start, LWS_WRITE_HTTP_HEADERS); if (n != (p - start)) { lwsl_err("_write returned %d from %d\n", n, p - start); return -1; } return lws_http_transaction_completed(wsi); } } if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_ETAG, (unsigned char *)sym, n, &p, end)) return -1; #endif mimetype = get_mimetype(path); if (!mimetype) { lwsl_err("unknown mimetype for %s", path); goto bail; } n = lws_serve_http_file(wsi, path, mimetype, (char *)start, p - start); if (n < 0 || ((n > 0) && lws_http_transaction_completed(wsi))) return -1; /* error or can't reuse connection: close the socket */ return 0; bail: return -1; }
LWS_VISIBLE int lws_serve_http_file(struct lws *wsi, const char *file, const char *content_type, const char *other_headers, int other_headers_len) { static const char * const intermediates[] = { "private", "public" }; struct lws_context *context = lws_get_context(wsi); struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi]; char cache_control[50], *cc = "no-store"; unsigned char *response = pt->serv_buf + LWS_PRE; unsigned char *p = response; unsigned char *end = p + LWS_MAX_SOCKET_IO_BUF - LWS_PRE; int ret = 0, cclen = 8; wsi->u.http.fd = lws_plat_file_open(wsi, file, &wsi->u.http.filelen, O_RDONLY); if (wsi->u.http.fd == LWS_INVALID_FILE) { lwsl_err("Unable to open '%s'\n", file); lws_return_http_status(wsi, HTTP_STATUS_NOT_FOUND, NULL); return -1; } if (lws_add_http_header_status(wsi, 200, &p, end)) return -1; if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_CONTENT_TYPE, (unsigned char *)content_type, strlen(content_type), &p, end)) return -1; if (lws_add_http_header_content_length(wsi, wsi->u.http.filelen, &p, end)) return -1; if (wsi->cache_secs && wsi->cache_reuse) { if (wsi->cache_revalidate) { cc = cache_control; cclen = sprintf(cache_control, "%s max-age: %u", intermediates[wsi->cache_intermediaries], wsi->cache_secs); } else { cc = "no-cache"; cclen = 8; } } if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_CACHE_CONTROL, (unsigned char *)cc, cclen, &p, end)) return -1; if (other_headers) { if ((end - p) < other_headers_len) return -1; memcpy(p, other_headers, other_headers_len); p += other_headers_len; } if (lws_finalize_http_header(wsi, &p, end)) return -1; ret = lws_write(wsi, response, p - response, LWS_WRITE_HTTP_HEADERS); if (ret != (p - response)) { lwsl_err("_write returned %d from %d\n", ret, (p - response)); return -1; } wsi->u.http.filepos = 0; wsi->state = LWSS_HTTP_ISSUING_FILE; return lws_serve_http_file_fragment(wsi); }
LWS_VISIBLE int lws_callback_http_dummy(struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len) { struct lws_ssl_info *si; #ifdef LWS_WITH_CGI struct lws_cgi_args *args; #endif #if defined(LWS_WITH_CGI) || defined(LWS_WITH_HTTP_PROXY) char buf[8192]; int n; #endif #if defined(LWS_WITH_HTTP_PROXY) unsigned char **p, *end; struct lws *parent; #endif switch (reason) { #if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2) case LWS_CALLBACK_HTTP: #ifndef LWS_NO_SERVER if (lws_return_http_status(wsi, HTTP_STATUS_NOT_FOUND, NULL)) return -1; if (lws_http_transaction_completed(wsi)) #endif return -1; break; #if !defined(LWS_NO_SERVER) case LWS_CALLBACK_HTTP_BODY_COMPLETION: case LWS_CALLBACK_HTTP_FILE_COMPLETION: if (lws_http_transaction_completed(wsi)) return -1; break; #endif case LWS_CALLBACK_HTTP_WRITEABLE: #ifdef LWS_WITH_CGI if (wsi->reason_bf & (LWS_CB_REASON_AUX_BF__CGI_HEADERS | LWS_CB_REASON_AUX_BF__CGI)) { n = lws_cgi_write_split_stdout_headers(wsi); if (n < 0) { lwsl_debug("AUX_BF__CGI forcing close\n"); return -1; } if (!n) lws_rx_flow_control( wsi->http.cgi->stdwsi[LWS_STDOUT], 1); if (wsi->reason_bf & LWS_CB_REASON_AUX_BF__CGI_HEADERS) wsi->reason_bf &= ~LWS_CB_REASON_AUX_BF__CGI_HEADERS; else wsi->reason_bf &= ~LWS_CB_REASON_AUX_BF__CGI; if (wsi->http.cgi && wsi->http.cgi->cgi_transaction_over) return -1; break; } if (wsi->reason_bf & LWS_CB_REASON_AUX_BF__CGI_CHUNK_END) { if (!wsi->http2_substream) { memcpy(buf + LWS_PRE, "0\x0d\x0a\x0d\x0a", 5); lwsl_debug("writing chunk term and exiting\n"); n = lws_write(wsi, (unsigned char *)buf + LWS_PRE, 5, LWS_WRITE_HTTP); } else n = lws_write(wsi, (unsigned char *)buf + LWS_PRE, 0, LWS_WRITE_HTTP_FINAL); /* always close after sending it */ return -1; } #endif #if defined(LWS_WITH_HTTP_PROXY) if (wsi->reason_bf & LWS_CB_REASON_AUX_BF__PROXY_HEADERS) { wsi->reason_bf &= ~LWS_CB_REASON_AUX_BF__PROXY_HEADERS; lwsl_debug("%s: %p: issuing proxy headers\n", __func__, wsi); n = lws_write(wsi, wsi->http.pending_return_headers + LWS_PRE, wsi->http.pending_return_headers_len, LWS_WRITE_HTTP_HEADERS); lws_free_set_NULL(wsi->http.pending_return_headers); if (n < 0) { lwsl_err("%s: EST_CLIENT_HTTP: write failed\n", __func__); return -1; } lws_callback_on_writable(wsi); break; } if (wsi->reason_bf & LWS_CB_REASON_AUX_BF__PROXY) { char *px = buf + LWS_PRE; int lenx = sizeof(buf) - LWS_PRE - 32; /* * our sink is writeable and our source has something * to read. So read a lump of source material of * suitable size to send or what's available, whichever * is the smaller. */ wsi->reason_bf &= ~LWS_CB_REASON_AUX_BF__PROXY; if (!lws_get_child(wsi)) break; /* this causes LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ */ if (lws_http_client_read(lws_get_child(wsi), &px, &lenx) < 0) { lwsl_info("%s: LWS_CB_REASON_AUX_BF__PROXY: " "client closed\n", __func__); stream_close(wsi); return -1; } break; } if (wsi->reason_bf & LWS_CB_REASON_AUX_BF__PROXY_TRANS_END) { lwsl_info("%s: LWS_CB_REASON_AUX_BF__PROXY_TRANS_END\n", __func__); wsi->reason_bf &= ~LWS_CB_REASON_AUX_BF__PROXY_TRANS_END; if (stream_close(wsi)) return -1; if (lws_http_transaction_completed(wsi)) return -1; } #endif break; #if defined(LWS_WITH_HTTP_PROXY) case LWS_CALLBACK_RECEIVE_CLIENT_HTTP: assert(lws_get_parent(wsi)); if (!lws_get_parent(wsi)) break; lws_get_parent(wsi)->reason_bf |= LWS_CB_REASON_AUX_BF__PROXY; lws_callback_on_writable(lws_get_parent(wsi)); break; case LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ: { char *out = buf + LWS_PRE; assert(lws_get_parent(wsi)); if (wsi->http.proxy_parent_chunked) { if (len > sizeof(buf) - LWS_PRE - 16) { lwsl_err("oversize buf %d %d\n", (int)len, (int)sizeof(buf) - LWS_PRE - 16); return -1; } /* * this only needs dealing with on http/1.1 to allow * pipelining */ n = lws_snprintf(out, 14, "%X\x0d\x0a", (int)len); out += n; memcpy(out, in, len); out += len; *out++ = '\x0d'; *out++ = '\x0a'; n = lws_write(lws_get_parent(wsi), (unsigned char *)buf + LWS_PRE, len + n + 2, LWS_WRITE_HTTP); } else n = lws_write(lws_get_parent(wsi), (unsigned char *)in, len, LWS_WRITE_HTTP); if (n < 0) return -1; break; } /* this handles the proxy case... */ case LWS_CALLBACK_ESTABLISHED_CLIENT_HTTP: { unsigned char *start, *p, *end; /* * We want to proxy these headers, but we are being called * at the point the onward client was established, which is * unrelated to the state or writability of our proxy * connection. * * Therefore produce the headers using the onward client ah * while we have it, and stick them on the output buflist to be * written on the proxy connection as soon as convenient. */ parent = lws_get_parent(wsi); if (!parent) return 0; start = p = (unsigned char *)buf + LWS_PRE; end = p + sizeof(buf) - LWS_PRE - 256; if (lws_add_http_header_status(lws_get_parent(wsi), lws_http_client_http_response(wsi), &p, end)) return 1; /* * copy these headers from the client connection to the parent */ proxy_header(parent, wsi, end, 256, WSI_TOKEN_HTTP_CONTENT_LENGTH, &p, end); proxy_header(parent, wsi, end, 256, WSI_TOKEN_HTTP_CONTENT_TYPE, &p, end); proxy_header(parent, wsi, end, 256, WSI_TOKEN_HTTP_ETAG, &p, end); proxy_header(parent, wsi, end, 256, WSI_TOKEN_HTTP_ACCEPT_LANGUAGE, &p, end); proxy_header(parent, wsi, end, 256, WSI_TOKEN_HTTP_CONTENT_ENCODING, &p, end); proxy_header(parent, wsi, end, 256, WSI_TOKEN_HTTP_CACHE_CONTROL, &p, end); if (!parent->http2_substream) if (lws_add_http_header_by_token(parent, WSI_TOKEN_CONNECTION, (unsigned char *)"close", 5, &p, end)) return -1; /* * We proxy using h1 only atm, and strip any chunking so it * can go back out on h2 just fine. * * However if we are actually going out on h1, we need to add * our own chunking since we still don't know the size. */ if (!parent->http2_substream && !lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_CONTENT_LENGTH)) { lwsl_debug("downstream parent chunked\n"); if (lws_add_http_header_by_token(parent, WSI_TOKEN_HTTP_TRANSFER_ENCODING, (unsigned char *)"chunked", 7, &p, end)) return -1; wsi->http.proxy_parent_chunked = 1; } if (lws_finalize_http_header(parent, &p, end)) return 1; parent->http.pending_return_headers_len = lws_ptr_diff(p, start); parent->http.pending_return_headers = lws_malloc(parent->http.pending_return_headers_len + LWS_PRE, "return proxy headers"); if (!parent->http.pending_return_headers) return -1; memcpy(parent->http.pending_return_headers + LWS_PRE, start, parent->http.pending_return_headers_len); parent->reason_bf |= LWS_CB_REASON_AUX_BF__PROXY_HEADERS; lwsl_debug("%s: LWS_CALLBACK_ESTABLISHED_CLIENT_HTTP: " "prepared headers\n", __func__); lws_callback_on_writable(parent); break; } case LWS_CALLBACK_COMPLETED_CLIENT_HTTP: lwsl_info("%s: COMPLETED_CLIENT_HTTP: %p (parent %p)\n", __func__, wsi, lws_get_parent(wsi)); if (!lws_get_parent(wsi)) break; lws_get_parent(wsi)->reason_bf |= LWS_CB_REASON_AUX_BF__PROXY_TRANS_END; lws_callback_on_writable(lws_get_parent(wsi)); break; case LWS_CALLBACK_CLOSED_CLIENT_HTTP: if (!lws_get_parent(wsi)) break; lwsl_err("%s: LWS_CALLBACK_CLOSED_CLIENT_HTTP\n", __func__); lws_set_timeout(lws_get_parent(wsi), LWS_TO_KILL_ASYNC, PENDING_TIMEOUT_KILLED_BY_PROXY_CLIENT_CLOSE); break; case LWS_CALLBACK_CLIENT_APPEND_HANDSHAKE_HEADER: parent = lws_get_parent(wsi); if (!parent) break; p = (unsigned char **)in; end = (*p) + len; /* * copy these headers from the parent request to the client * connection's request */ proxy_header(wsi, parent, (unsigned char *)buf, sizeof(buf), WSI_TOKEN_HOST, p, end); proxy_header(wsi, parent, (unsigned char *)buf, sizeof(buf), WSI_TOKEN_HTTP_ETAG, p, end); proxy_header(wsi, parent, (unsigned char *)buf, sizeof(buf), WSI_TOKEN_HTTP_IF_MODIFIED_SINCE, p, end); proxy_header(wsi, parent, (unsigned char *)buf, sizeof(buf), WSI_TOKEN_HTTP_ACCEPT_LANGUAGE, p, end); proxy_header(wsi, parent, (unsigned char *)buf, sizeof(buf), WSI_TOKEN_HTTP_ACCEPT_ENCODING, p, end); proxy_header(wsi, parent, (unsigned char *)buf, sizeof(buf), WSI_TOKEN_HTTP_CACHE_CONTROL, p, end); buf[0] = '\0'; lws_get_peer_simple(parent, buf, sizeof(buf)); if (lws_add_http_header_by_token(wsi, WSI_TOKEN_X_FORWARDED_FOR, (unsigned char *)buf, (int)strlen(buf), p, end)) return -1; break; #endif #ifdef LWS_WITH_CGI /* CGI IO events (POLLIN/OUT) appear here, our default policy is: * * - POST data goes on subprocess stdin * - subprocess stdout goes on http via writeable callback * - subprocess stderr goes to the logs */ case LWS_CALLBACK_CGI: args = (struct lws_cgi_args *)in; switch (args->ch) { /* which of stdin/out/err ? */ case LWS_STDIN: /* TBD stdin rx flow control */ break; case LWS_STDOUT: /* quench POLLIN on STDOUT until MASTER got writeable */ lws_rx_flow_control(args->stdwsi[LWS_STDOUT], 0); wsi->reason_bf |= LWS_CB_REASON_AUX_BF__CGI; /* when writing to MASTER would not block */ lws_callback_on_writable(wsi); break; case LWS_STDERR: n = lws_get_socket_fd(args->stdwsi[LWS_STDERR]); if (n < 0) break; n = read(n, buf, sizeof(buf) - 2); if (n > 0) { if (buf[n - 1] != '\n') buf[n++] = '\n'; buf[n] = '\0'; lwsl_notice("CGI-stderr: %s\n", buf); } break; } break; case LWS_CALLBACK_CGI_TERMINATED: lwsl_debug("LWS_CALLBACK_CGI_TERMINATED: %d %" PRIu64 "\n", wsi->http.cgi->explicitly_chunked, (uint64_t)wsi->http.cgi->content_length); if (!wsi->http.cgi->explicitly_chunked && !wsi->http.cgi->content_length) { /* send terminating chunk */ lwsl_debug("LWS_CALLBACK_CGI_TERMINATED: ending\n"); wsi->reason_bf |= LWS_CB_REASON_AUX_BF__CGI_CHUNK_END; lws_callback_on_writable(wsi); lws_set_timeout(wsi, PENDING_TIMEOUT_CGI, 3); break; } return -1; case LWS_CALLBACK_CGI_STDIN_DATA: /* POST body for stdin */ args = (struct lws_cgi_args *)in; args->data[args->len] = '\0'; if (!args->stdwsi[LWS_STDIN]) return -1; n = lws_get_socket_fd(args->stdwsi[LWS_STDIN]); if (n < 0) return -1; #if defined(LWS_WITH_ZLIB) if (wsi->http.cgi->gzip_inflate) { /* gzip handling */ if (!wsi->http.cgi->gzip_init) { lwsl_info("inflating gzip\n"); memset(&wsi->http.cgi->inflate, 0, sizeof(wsi->http.cgi->inflate)); if (inflateInit2(&wsi->http.cgi->inflate, 16 + 15) != Z_OK) { lwsl_err("%s: iniflateInit failed\n", __func__); return -1; } wsi->http.cgi->gzip_init = 1; } wsi->http.cgi->inflate.next_in = args->data; wsi->http.cgi->inflate.avail_in = args->len; do { wsi->http.cgi->inflate.next_out = wsi->http.cgi->inflate_buf; wsi->http.cgi->inflate.avail_out = sizeof(wsi->http.cgi->inflate_buf); n = inflate(&wsi->http.cgi->inflate, Z_SYNC_FLUSH); switch (n) { case Z_NEED_DICT: case Z_STREAM_ERROR: case Z_DATA_ERROR: case Z_MEM_ERROR: inflateEnd(&wsi->http.cgi->inflate); wsi->http.cgi->gzip_init = 0; lwsl_err("zlib error inflate %d\n", n); return -1; } if (wsi->http.cgi->inflate.avail_out != sizeof(wsi->http.cgi->inflate_buf)) { int written; written = write(args->stdwsi[LWS_STDIN]->desc.filefd, wsi->http.cgi->inflate_buf, sizeof(wsi->http.cgi->inflate_buf) - wsi->http.cgi->inflate.avail_out); if (written != (int)( sizeof(wsi->http.cgi->inflate_buf) - wsi->http.cgi->inflate.avail_out)) { lwsl_notice("LWS_CALLBACK_CGI_STDIN_DATA: " "sent %d only %d went", n, args->len); } if (n == Z_STREAM_END) { lwsl_err("gzip inflate end\n"); inflateEnd(&wsi->http.cgi->inflate); wsi->http.cgi->gzip_init = 0; break; } } else break; if (wsi->http.cgi->inflate.avail_out) break; } while (1); return args->len; } #endif /* WITH_ZLIB */ n = write(n, args->data, args->len); // lwsl_hexdump_notice(args->data, args->len); if (n < args->len) lwsl_notice("LWS_CALLBACK_CGI_STDIN_DATA: " "sent %d only %d went", n, args->len); if (wsi->http.cgi->post_in_expected && args->stdwsi[LWS_STDIN] && args->stdwsi[LWS_STDIN]->desc.filefd > 0) { wsi->http.cgi->post_in_expected -= n; if (!wsi->http.cgi->post_in_expected) { struct lws *siwsi = args->stdwsi[LWS_STDIN]; lwsl_debug("%s: expected POST in end: " "closing stdin wsi %p, fd %d\n", __func__, siwsi, siwsi->desc.sockfd); __remove_wsi_socket_from_fds(siwsi); lwsi_set_state(siwsi, LRS_DEAD_SOCKET); siwsi->socket_is_permanently_unusable = 1; lws_remove_child_from_any_parent(siwsi); if (wsi->context->event_loop_ops-> close_handle_manually) { wsi->context->event_loop_ops-> close_handle_manually(siwsi); siwsi->told_event_loop_closed = 1; } else { compatible_close(siwsi->desc.sockfd); __lws_free_wsi(siwsi); } wsi->http.cgi->pipe_fds[LWS_STDIN][1] = -1; args->stdwsi[LWS_STDIN] = NULL; } } return n; #endif /* WITH_CGI */ #endif /* ROLE_ H1 / H2 */ case LWS_CALLBACK_SSL_INFO: si = in; (void)si; lwsl_notice("LWS_CALLBACK_SSL_INFO: where: 0x%x, ret: 0x%x\n", si->where, si->ret); break; #if LWS_MAX_SMP > 1 case LWS_CALLBACK_GET_THREAD_ID: return (int)(unsigned long long)pthread_self(); #endif default: break; } return 0; }
LWS_VISIBLE LWS_EXTERN int lws_cgi_write_split_stdout_headers(struct lws *wsi) { int n, m, cmd; unsigned char buf[LWS_PRE + 4096], *start = &buf[LWS_PRE], *p = start, *end = &buf[sizeof(buf) - 1 - LWS_PRE], *name, *value = NULL; char c, hrs; if (!wsi->http.cgi) return -1; while (wsi->hdr_state != LHCS_PAYLOAD) { /* * We have to separate header / finalize and payload chunks, * since they need to be handled separately */ switch (wsi->hdr_state) { case LHCS_RESPONSE: lwsl_debug("LHCS_RESPONSE: issuing response %d\n", wsi->http.cgi->response_code); if (lws_add_http_header_status(wsi, wsi->http.cgi->response_code, &p, end)) return 1; if (!wsi->http.cgi->explicitly_chunked && !wsi->http.cgi->content_length && lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_TRANSFER_ENCODING, (unsigned char *)"chunked", 7, &p, end)) return 1; if (!(wsi->http2_substream)) if (lws_add_http_header_by_token(wsi, WSI_TOKEN_CONNECTION, (unsigned char *)"close", 5, &p, end)) return 1; n = lws_write(wsi, start, p - start, LWS_WRITE_HTTP_HEADERS | LWS_WRITE_NO_FIN); /* * so we have a bunch of http/1 style ascii headers * starting from wsi->http.cgi->headers_buf through * wsi->http.cgi->headers_pos. These are OK for http/1 * connections, but they're no good for http/2 conns. * * Let's redo them at headers_pos forward using the * correct coding for http/1 or http/2 */ if (!wsi->http2_substream) goto post_hpack_recode; p = wsi->http.cgi->headers_start; wsi->http.cgi->headers_start = wsi->http.cgi->headers_pos; wsi->http.cgi->headers_dumped = wsi->http.cgi->headers_start; hrs = HR_NAME; name = buf; while (p < wsi->http.cgi->headers_start) { switch (hrs) { case HR_NAME: /* * in http/2 upper-case header names * are illegal. So convert to lower- * case. */ if (name - buf > 64) return -1; if (*p != ':') { if (*p >= 'A' && *p <= 'Z') *name++ = (*p++) + ('a' - 'A'); else *name++ = *p++; } else { p++; *name++ = '\0'; value = name; hrs = HR_WHITESPACE; } break; case HR_WHITESPACE: if (*p == ' ') { p++; break; } hrs = HR_ARG; /* fallthru */ case HR_ARG: if (name > end - 64) return -1; if (*p != '\x0a' && *p != '\x0d') { *name++ = *p++; break; } hrs = HR_CRLF; /* fallthru */ case HR_CRLF: if ((*p != '\x0a' && *p != '\x0d') || p + 1 == wsi->http.cgi->headers_start) { *name = '\0'; if ((strcmp((const char *)buf, "transfer-encoding") )) { lwsl_debug("+ %s: %s\n", buf, value); if ( lws_add_http_header_by_name(wsi, buf, (unsigned char *)value, name - value, (unsigned char **)&wsi->http.cgi->headers_pos, (unsigned char *)wsi->http.cgi->headers_end)) return 1; hrs = HR_NAME; name = buf; break; } } p++; break; } } post_hpack_recode: /* finalize cached headers before dumping them */ if (lws_finalize_http_header(wsi, (unsigned char **)&wsi->http.cgi->headers_pos, (unsigned char *)wsi->http.cgi->headers_end)) { lwsl_notice("finalize failed\n"); return -1; } wsi->hdr_state = LHCS_DUMP_HEADERS; wsi->reason_bf |= LWS_CB_REASON_AUX_BF__CGI_HEADERS; lws_callback_on_writable(wsi); /* back to the loop for writeability again */ return 0; case LHCS_DUMP_HEADERS: n = wsi->http.cgi->headers_pos - wsi->http.cgi->headers_dumped; if (n > 512) n = 512; lwsl_debug("LHCS_DUMP_HEADERS: %d\n", n); cmd = LWS_WRITE_HTTP_HEADERS_CONTINUATION; if (wsi->http.cgi->headers_dumped + n != wsi->http.cgi->headers_pos) { lwsl_notice("adding no fin flag\n"); cmd |= LWS_WRITE_NO_FIN; } m = lws_write(wsi, (unsigned char *)wsi->http.cgi->headers_dumped, n, cmd); if (m < 0) { lwsl_debug("%s: write says %d\n", __func__, m); return -1; } wsi->http.cgi->headers_dumped += n; if (wsi->http.cgi->headers_dumped == wsi->http.cgi->headers_pos) { wsi->hdr_state = LHCS_PAYLOAD; lws_free_set_NULL(wsi->http.cgi->headers_buf); lwsl_debug("freed cgi headers\n"); } else { wsi->reason_bf |= LWS_CB_REASON_AUX_BF__CGI_HEADERS; lws_callback_on_writable(wsi); } /* writeability becomes uncertain now we wrote * something, we must return to the event loop */ return 0; } if (!wsi->http.cgi->headers_buf) { /* if we don't already have a headers buf, cook one */ n = 2048; if (wsi->http2_substream) n = 4096; wsi->http.cgi->headers_buf = lws_malloc(n + LWS_PRE, "cgi hdr buf"); if (!wsi->http.cgi->headers_buf) { lwsl_err("OOM\n"); return -1; } lwsl_debug("allocated cgi hdrs\n"); wsi->http.cgi->headers_start = wsi->http.cgi->headers_buf + LWS_PRE; wsi->http.cgi->headers_pos = wsi->http.cgi->headers_start; wsi->http.cgi->headers_dumped = wsi->http.cgi->headers_pos; wsi->http.cgi->headers_end = wsi->http.cgi->headers_buf + n - 1; for (n = 0; n < SIGNIFICANT_HDR_COUNT; n++) { wsi->http.cgi->match[n] = 0; wsi->http.cgi->lp = 0; } } n = lws_get_socket_fd(wsi->http.cgi->stdwsi[LWS_STDOUT]); if (n < 0) return -1; n = read(n, &c, 1); if (n < 0) { if (errno != EAGAIN) { lwsl_debug("%s: read says %d\n", __func__, n); return -1; } else n = 0; if (wsi->http.cgi->headers_pos >= wsi->http.cgi->headers_end - 4) { lwsl_notice("CGI hdrs > buf size\n"); return -1; } } if (!n) goto agin; lwsl_debug("-- 0x%02X %c %d %d\n", (unsigned char)c, c, wsi->http.cgi->match[1], wsi->hdr_state); if (!c) return -1; switch (wsi->hdr_state) { case LCHS_HEADER: hdr: for (n = 0; n < SIGNIFICANT_HDR_COUNT; n++) { /* * significant headers with * numeric decimal payloads */ if (!significant_hdr[n][wsi->http.cgi->match[n]] && (c >= '0' && c <= '9') && wsi->http.cgi->lp < (int)sizeof(wsi->http.cgi->l) - 1) { wsi->http.cgi->l[wsi->http.cgi->lp++] = c; wsi->http.cgi->l[wsi->http.cgi->lp] = '\0'; switch (n) { case SIGNIFICANT_HDR_CONTENT_LENGTH: wsi->http.cgi->content_length = atoll(wsi->http.cgi->l); break; case SIGNIFICANT_HDR_STATUS: wsi->http.cgi->response_code = atol(wsi->http.cgi->l); lwsl_debug("Status set to %d\n", wsi->http.cgi->response_code); break; default: break; } } /* hits up to the NUL are sticky until next hdr */ if (significant_hdr[n][wsi->http.cgi->match[n]]) { if (tolower(c) == significant_hdr[n][wsi->http.cgi->match[n]]) wsi->http.cgi->match[n]++; else wsi->http.cgi->match[n] = 0; } } /* some cgi only send us \x0a for EOL */ if (c == '\x0a') { wsi->hdr_state = LCHS_SINGLE_0A; *wsi->http.cgi->headers_pos++ = '\x0d'; } *wsi->http.cgi->headers_pos++ = c; if (c == '\x0d') wsi->hdr_state = LCHS_LF1; if (wsi->hdr_state != LCHS_HEADER && !significant_hdr[SIGNIFICANT_HDR_TRANSFER_ENCODING] [wsi->http.cgi->match[ SIGNIFICANT_HDR_TRANSFER_ENCODING]]) { lwsl_info("cgi produced chunked\n"); wsi->http.cgi->explicitly_chunked = 1; } /* presence of Location: mandates 302 retcode */ if (wsi->hdr_state != LCHS_HEADER && !significant_hdr[SIGNIFICANT_HDR_LOCATION][ wsi->http.cgi->match[SIGNIFICANT_HDR_LOCATION]]) { lwsl_debug("CGI: Location hdr seen\n"); wsi->http.cgi->response_code = 302; } break; case LCHS_LF1: *wsi->http.cgi->headers_pos++ = c; if (c == '\x0a') { wsi->hdr_state = LCHS_CR2; break; } /* we got \r[^\n]... it's unreasonable */ lwsl_debug("%s: funny CRLF 0x%02X\n", __func__, (unsigned char)c); return -1; case LCHS_CR2: if (c == '\x0d') { /* drop the \x0d */ wsi->hdr_state = LCHS_LF2; break; } wsi->hdr_state = LCHS_HEADER; for (n = 0; n < SIGNIFICANT_HDR_COUNT; n++) wsi->http.cgi->match[n] = 0; wsi->http.cgi->lp = 0; goto hdr; case LCHS_LF2: case LCHS_SINGLE_0A: m = wsi->hdr_state; if (c == '\x0a') { lwsl_debug("Content-Length: %lld\n", (unsigned long long) wsi->http.cgi->content_length); wsi->hdr_state = LHCS_RESPONSE; /* * drop the \0xa ... finalize * will add it if needed (HTTP/1) */ break; } if (m == LCHS_LF2) /* we got \r\n\r[^\n]... unreasonable */ return -1; /* we got \x0anext header, it's reasonable */ *wsi->http.cgi->headers_pos++ = c; wsi->hdr_state = LCHS_HEADER; for (n = 0; n < SIGNIFICANT_HDR_COUNT; n++) wsi->http.cgi->match[n] = 0; wsi->http.cgi->lp = 0; break; case LHCS_PAYLOAD: break; } agin: /* ran out of input, ended the hdrs, or filled up the hdrs buf */ if (!n || wsi->hdr_state == LHCS_PAYLOAD) return 0; } /* payload processing */ m = !wsi->http.cgi->implied_chunked && !wsi->http2_substream && !wsi->http.cgi->explicitly_chunked && !wsi->http.cgi->content_length; n = lws_get_socket_fd(wsi->http.cgi->stdwsi[LWS_STDOUT]); if (n < 0) return -1; if (m) { uint8_t term[LWS_PRE + 6]; lwsl_info("%s: zero chunk\n", __func__); memcpy(term + LWS_PRE, (uint8_t *)"0\x0d\x0a\x0d\x0a", 5); if (lws_write(wsi, term + LWS_PRE, 5, LWS_WRITE_HTTP_FINAL) != 5) return -1; wsi->http.cgi->cgi_transaction_over = 1; return 0; } n = read(n, start, sizeof(buf) - LWS_PRE); if (n < 0 && errno != EAGAIN) { lwsl_debug("%s: stdout read says %d\n", __func__, n); return -1; } if (n > 0) { /* if (!wsi->http2_substream && m) { char chdr[LWS_HTTP_CHUNK_HDR_SIZE]; m = lws_snprintf(chdr, LWS_HTTP_CHUNK_HDR_SIZE - 3, "%X\x0d\x0a", n); memmove(start + m, start, n); memcpy(start, chdr, m); memcpy(start + m + n, "\x0d\x0a", 2); n += m + 2; } */ #if defined(LWS_WITH_HTTP2) if (wsi->http2_substream) { struct lws *nwsi = lws_get_network_wsi(wsi); __lws_set_timeout(wsi, PENDING_TIMEOUT_HTTP_KEEPALIVE_IDLE, 31); if (!nwsi->immortal_substream_count) __lws_set_timeout(nwsi, PENDING_TIMEOUT_HTTP_KEEPALIVE_IDLE, 31); } #endif cmd = LWS_WRITE_HTTP; if (wsi->http.cgi->content_length_seen + n == wsi->http.cgi->content_length) cmd = LWS_WRITE_HTTP_FINAL; m = lws_write(wsi, (unsigned char *)start, n, cmd); //lwsl_notice("write %d\n", m); if (m < 0) { lwsl_debug("%s: stdout write says %d\n", __func__, m); return -1; } wsi->http.cgi->content_length_seen += n; } else { if (wsi->cgi_stdout_zero_length) { lwsl_debug("%s: stdout is POLLHUP'd\n", __func__); if (wsi->http2_substream) m = lws_write(wsi, (unsigned char *)start, 0, LWS_WRITE_HTTP_FINAL); else return -1; return 1; } wsi->cgi_stdout_zero_length = 1; } return 0; }
static int callback_post_demo(struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len) { struct per_session_data__post_demo *pss = (struct per_session_data__post_demo *)user; unsigned char *buffer; unsigned char *p, *start, *end; int n; switch (reason) { case LWS_CALLBACK_HTTP_BODY: /* create the POST argument parser if not already existing */ if (!pss->spa) { pss->spa = lws_spa_create(wsi, param_names, ARRAY_SIZE(param_names), 1024, file_upload_cb, pss); if (!pss->spa) return -1; pss->filename[0] = '\0'; pss->file_length = 0; } /* let it parse the POST data */ if (lws_spa_process(pss->spa, in, (int)len)) return -1; break; case LWS_CALLBACK_HTTP_BODY_COMPLETION: lwsl_debug("LWS_CALLBACK_HTTP_BODY_COMPLETION\n"); /* call to inform no more payload data coming */ lws_spa_finalize(pss->spa); p = (unsigned char *)pss->result + LWS_PRE; end = p + sizeof(pss->result) - LWS_PRE - 1; p += sprintf((char *)p, "<html><body><h1>Form results (after urldecoding)</h1>" "<table><tr><td>Name</td><td>Length</td><td>Value</td></tr>"); for (n = 0; n < (int)ARRAY_SIZE(param_names); n++) { if (!lws_spa_get_string(pss->spa, n)) p += lws_snprintf((char *)p, end - p, "<tr><td><b>%s</b></td><td>0</td><td>NULL</td></tr>", param_names[n]); else p += lws_snprintf((char *)p, end - p, "<tr><td><b>%s</b></td><td>%d</td><td>%s</td></tr>", param_names[n], lws_spa_get_length(pss->spa, n), lws_spa_get_string(pss->spa, n)); } p += lws_snprintf((char *)p, end - p, "</table><br><b>filename:</b> %s, <b>length</b> %ld", pss->filename, pss->file_length); p += lws_snprintf((char *)p, end - p, "</body></html>"); pss->result_len = lws_ptr_diff(p, pss->result + LWS_PRE); n = LWS_PRE + 1024; buffer = malloc(n); p = buffer + LWS_PRE; start = p; end = p + n - LWS_PRE - 1; if (lws_add_http_header_status(wsi, HTTP_STATUS_OK, &p, end)) goto bail; if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_CONTENT_TYPE, (unsigned char *)"text/html", 9, &p, end)) goto bail; if (lws_add_http_header_content_length(wsi, pss->result_len, &p, end)) goto bail; if (lws_finalize_http_header(wsi, &p, end)) goto bail; n = lws_write(wsi, start, p - start, LWS_WRITE_HTTP_HEADERS); if (n < 0) goto bail; free(buffer); lws_callback_on_writable(wsi); break; case LWS_CALLBACK_HTTP_WRITEABLE: if (!pss->result_len) break; lwsl_debug("LWS_CALLBACK_HTTP_WRITEABLE: sending %d\n", pss->result_len); n = lws_write(wsi, (unsigned char *)pss->result + LWS_PRE, pss->result_len, LWS_WRITE_HTTP); if (n < 0) return 1; goto try_to_reuse; case LWS_CALLBACK_HTTP_DROP_PROTOCOL: /* called when our wsi user_space is going to be destroyed */ if (pss->spa) { lws_spa_destroy(pss->spa); pss->spa = NULL; } break; default: break; } return 0; bail: free(buffer); return 1; try_to_reuse: if (lws_http_transaction_completed(wsi)) return -1; return 0; }
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; unsigned char buffer[4096 + LWS_PRE]; unsigned long amount, file_len, sent; char leaf_path[1024]; const char *mimetype; char *other_headers; unsigned char *end, *start; 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; #ifdef EXTERNAL_POLL struct lws_pollargs *pa = (struct lws_pollargs *)in; #endif switch (reason) { case LWS_CALLBACK_HTTP: lwsl_info("lws_http_serve: %s\n",in); if (debug_level & LLL_INFO) { dump_handshake_info(wsi); /* dump the individual URI Arg parameters */ n = 0; while (lws_hdr_copy_fragment(wsi, buf, sizeof(buf), WSI_TOKEN_HTTP_URI_ARGS, n) > 0) { lwsl_notice("URI Arg %d: %s\n", ++n, buf); } } { lws_get_peer_simple(wsi, buf, sizeof(buf)); lwsl_info("HTTP connect from %s\n", buf); } if (len < 1) { lws_return_http_status(wsi, HTTP_STATUS_BAD_REQUEST, NULL); goto try_to_reuse; } #ifndef LWS_NO_CLIENT if (!strncmp(in, "/proxytest", 10)) { struct lws_client_connect_info i; char *rootpath = "/"; const char *p = (const char *)in; if (lws_get_child(wsi)) break; pss->client_finished = 0; memset(&i,0, sizeof(i)); i.context = lws_get_context(wsi); i.address = "git.libwebsockets.org"; i.port = 80; i.ssl_connection = 0; if (p[10]) i.path = (char *)in + 10; else i.path = rootpath; i.host = "git.libwebsockets.org"; i.origin = NULL; i.method = "GET"; i.parent_wsi = wsi; i.uri_replace_from = "git.libwebsockets.org/"; i.uri_replace_to = "/proxytest/"; if (!lws_client_connect_via_info(&i)) { lwsl_err("proxy connect fail\n"); break; } break; } #endif #if 1 /* this example server has no concept of directories */ if (strchr((const char *)in + 1, '/')) { lws_return_http_status(wsi, HTTP_STATUS_NOT_ACCEPTABLE, NULL); goto try_to_reuse; } #endif /* if a legal POST URL, let it continue and accept data */ if (lws_hdr_total_length(wsi, WSI_TOKEN_POST_URI)) return 0; /* check for the "send a big file by hand" example case */ if (!strcmp((const char *)in, "/leaf.jpg")) { if (strlen(resource_path) > sizeof(leaf_path) - 10) return -1; sprintf(leaf_path, "%s/leaf.jpg", resource_path); /* well, let's demonstrate how to send the hard way */ p = buffer + LWS_PRE; end = p + sizeof(buffer) - LWS_PRE; pss->fd = lws_plat_file_open(wsi, leaf_path, &file_len, LWS_O_RDONLY); if (pss->fd == LWS_INVALID_FILE) { lwsl_err("failed to open file %s\n", leaf_path); return -1; } /* * we will send a big jpeg file, but it could be * anything. Set the Content-Type: appropriately * so the browser knows what to do with it. * * Notice we use the APIs to build the header, which * will do the right thing for HTTP 1/1.1 and HTTP2 * depending on what connection it happens to be working * on */ if (lws_add_http_header_status(wsi, 200, &p, end)) return 1; if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_SERVER, (unsigned char *)"libwebsockets", 13, &p, end)) return 1; if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_CONTENT_TYPE, (unsigned char *)"image/jpeg", 10, &p, end)) return 1; if (lws_add_http_header_content_length(wsi, file_len, &p, end)) return 1; if (lws_finalize_http_header(wsi, &p, end)) return 1; /* * send the http headers... * this won't block since it's the first payload sent * on the connection since it was established * (too small for partial) * * Notice they are sent using LWS_WRITE_HTTP_HEADERS * which also means you can't send body too in one step, * this is mandated by changes in HTTP2 */ *p = '\0'; lwsl_info("%s\n", buffer + LWS_PRE); n = lws_write(wsi, buffer + LWS_PRE, p - (buffer + LWS_PRE), LWS_WRITE_HTTP_HEADERS); if (n < 0) { lws_plat_file_close(wsi, pss->fd); return -1; } /* * book us a LWS_CALLBACK_HTTP_WRITEABLE callback */ lws_callback_on_writable(wsi); break; } /* if not, send a file the easy way */ 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(buf) - 1); } else /* default file to serve */ strcat(buf, "/test.html"); buf[sizeof(buf) - 1] = '\0'; /* refuse to serve files we don't understand */ mimetype = get_mimetype(buf); if (!mimetype) { lwsl_err("Unknown mimetype for %s\n", buf); lws_return_http_status(wsi, HTTP_STATUS_UNSUPPORTED_MEDIA_TYPE, NULL); return -1; } /* demonstrates how to set a cookie on / */ other_headers = leaf_path; p = (unsigned char *)leaf_path; if (!strcmp((const char *)in, "/") && !lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_COOKIE)) { /* this isn't very unguessable but it'll do for us */ gettimeofday(&tv, NULL); n = sprintf(b64, "test=LWS_%u_%u_COOKIE;Max-Age=360000", (unsigned int)tv.tv_sec, (unsigned int)tv.tv_usec); if (lws_add_http_header_by_name(wsi, (unsigned char *)"set-cookie:", (unsigned char *)b64, n, &p, (unsigned char *)leaf_path + sizeof(leaf_path))) return 1; } if (lws_is_ssl(wsi) && lws_add_http_header_by_name(wsi, (unsigned char *) "Strict-Transport-Security:", (unsigned char *) "max-age=15768000 ; " "includeSubDomains", 36, &p, (unsigned char *)leaf_path + sizeof(leaf_path))) return 1; n = (char *)p - leaf_path; n = lws_serve_http_file(wsi, buf, mimetype, other_headers, n); if (n < 0 || ((n > 0) && lws_http_transaction_completed(wsi))) return -1; /* error or can't reuse connection: close the socket */ /* * notice that the sending of the file completes asynchronously, * we'll get a LWS_CALLBACK_HTTP_FILE_COMPLETION callback when * it's done */ break; case LWS_CALLBACK_HTTP_BODY: /* create the POST argument parser if not already existing */ if (!pss->spa) { pss->spa = lws_spa_create(wsi, param_names, ARRAY_SIZE(param_names), 1024, file_upload_cb, pss); if (!pss->spa) return -1; pss->filename[0] = '\0'; pss->file_length = 0; } /* let it parse the POST data */ if (lws_spa_process(pss->spa, in, len)) return -1; break; case LWS_CALLBACK_HTTP_BODY_COMPLETION: lwsl_debug("LWS_CALLBACK_HTTP_BODY_COMPLETION\n"); /* * the whole of the sent body arrived, * respond to the client with a redirect to show the * results */ /* call to inform no more payload data coming */ lws_spa_finalize(pss->spa); p = (unsigned char *)pss->result + LWS_PRE; end = p + sizeof(pss->result) - LWS_PRE - 1; p += sprintf((char *)p, "<html><body><h1>Form results (after urldecoding)</h1>" "<table><tr><td>Name</td><td>Length</td><td>Value</td></tr>"); for (n = 0; n < ARRAY_SIZE(param_names); n++) p += lws_snprintf((char *)p, end - p, "<tr><td><b>%s</b></td><td>%d</td><td>%s</td></tr>", param_names[n], lws_spa_get_length(pss->spa, n), lws_spa_get_string(pss->spa, n)); p += lws_snprintf((char *)p, end - p, "</table><br><b>filename:</b> %s, <b>length</b> %ld", pss->filename, pss->file_length); p += lws_snprintf((char *)p, end - p, "</body></html>"); pss->result_len = p - (unsigned char *)(pss->result + LWS_PRE); p = buffer + LWS_PRE; start = p; 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_HTTP_CONTENT_TYPE, (unsigned char *)"text/html", 9, &p, end)) return 1; if (lws_add_http_header_content_length(wsi, pss->result_len, &p, end)) return 1; if (lws_finalize_http_header(wsi, &p, end)) return 1; n = lws_write(wsi, start, p - start, LWS_WRITE_HTTP_HEADERS); if (n < 0) return 1; n = lws_write(wsi, (unsigned char *)pss->result + LWS_PRE, pss->result_len, LWS_WRITE_HTTP); if (n < 0) return 1; goto try_to_reuse; case LWS_CALLBACK_HTTP_DROP_PROTOCOL: lwsl_debug("LWS_CALLBACK_HTTP_DROP_PROTOCOL\n"); /* called when our wsi user_space is going to be destroyed */ if (pss->spa) { lws_spa_destroy(pss->spa); pss->spa = NULL; } break; case LWS_CALLBACK_HTTP_FILE_COMPLETION: goto try_to_reuse; case LWS_CALLBACK_HTTP_WRITEABLE: lwsl_info("LWS_CALLBACK_HTTP_WRITEABLE\n"); if (pss->client_finished) return -1; if (pss->fd == LWS_INVALID_FILE) goto try_to_reuse; #ifndef LWS_NO_CLIENT if (pss->reason_bf & 2) { char *px = buf + LWS_PRE; int lenx = sizeof(buf) - LWS_PRE; /* * our sink is writeable and our source has something * to read. So read a lump of source material of * suitable size to send or what's available, whichever * is the smaller. */ pss->reason_bf &= ~2; wsi1 = lws_get_child(wsi); if (!wsi1) break; if (lws_http_client_read(wsi1, &px, &lenx) < 0) goto bail; if (pss->client_finished) return -1; break; } #endif /* * we can send more of whatever it is we were sending */ sent = 0; do { /* we'd like the send this much */ n = sizeof(buffer) - LWS_PRE; /* but if the peer told us he wants less, we can adapt */ m = lws_get_peer_write_allowance(wsi); /* -1 means not using a protocol that has this info */ if (m == 0) /* right now, peer can't handle anything */ goto later; if (m != -1 && m < n) /* he couldn't handle that much */ n = m; n = lws_plat_file_read(wsi, pss->fd, &amount, buffer + LWS_PRE, n); /* problem reading, close conn */ if (n < 0) { lwsl_err("problem reading file\n"); goto bail; } n = (int)amount; /* sent it all, close conn */ if (n == 0) goto penultimate; /* * To support HTTP2, must take care about preamble space * * identification of when we send the last payload frame * is handled by the library itself if you sent a * content-length header */ m = lws_write(wsi, buffer + LWS_PRE, n, LWS_WRITE_HTTP); if (m < 0) { lwsl_err("write failed\n"); /* write failed, close conn */ goto bail; } if (m) /* while still active, extend timeout */ lws_set_timeout(wsi, PENDING_TIMEOUT_HTTP_CONTENT, 5); sent += m; } while (!lws_send_pipe_choked(wsi) && (sent < 1024 * 1024)); later: lws_callback_on_writable(wsi); break; penultimate: lws_plat_file_close(wsi, pss->fd); pss->fd = LWS_INVALID_FILE; goto try_to_reuse; bail: lws_plat_file_close(wsi, pss->fd); return -1; /* * callback for confirming to continue with client IP appear in * protocol 0 callback since no websocket protocol has been agreed * yet. You can just ignore this if you won't filter on client IP * since the default unhandled callback return is 0 meaning let the * connection continue. */ case LWS_CALLBACK_FILTER_NETWORK_CONNECTION: /* if we returned non-zero from here, we kill the connection */ break; #ifndef LWS_NO_CLIENT case LWS_CALLBACK_ESTABLISHED_CLIENT_HTTP: { char ctype[64], ctlen = 0; lwsl_err("LWS_CALLBACK_ESTABLISHED_CLIENT_HTTP\n"); p = buffer + LWS_PRE; end = p + sizeof(buffer) - LWS_PRE; if (lws_add_http_header_status(lws_get_parent(wsi), 200, &p, end)) return 1; if (lws_add_http_header_by_token(lws_get_parent(wsi), WSI_TOKEN_HTTP_SERVER, (unsigned char *)"libwebsockets", 13, &p, end)) return 1; ctlen = lws_hdr_copy(wsi, ctype, sizeof(ctype), WSI_TOKEN_HTTP_CONTENT_TYPE); if (ctlen > 0) { if (lws_add_http_header_by_token(lws_get_parent(wsi), WSI_TOKEN_HTTP_CONTENT_TYPE, (unsigned char *)ctype, ctlen, &p, end)) return 1; } #if 0 if (lws_add_http_header_content_length(lws_get_parent(wsi), file_len, &p, end)) return 1; #endif if (lws_finalize_http_header(lws_get_parent(wsi), &p, end)) return 1; *p = '\0'; lwsl_info("%s\n", buffer + LWS_PRE); n = lws_write(lws_get_parent(wsi), buffer + LWS_PRE, p - (buffer + LWS_PRE), LWS_WRITE_HTTP_HEADERS); if (n < 0) return -1; break; } case LWS_CALLBACK_CLOSED_CLIENT_HTTP: //lwsl_err("LWS_CALLBACK_CLOSED_CLIENT_HTTP\n"); return -1; break; case LWS_CALLBACK_RECEIVE_CLIENT_HTTP: //lwsl_err("LWS_CALLBACK_RECEIVE_CLIENT_HTTP: wsi %p\n", wsi); assert(lws_get_parent(wsi)); if (!lws_get_parent(wsi)) break; // lwsl_err("LWS_CALLBACK_RECEIVE_CLIENT_HTTP: wsi %p: sock: %d, parent_wsi: %p, parent_sock:%d, len %d\n", // wsi, lws_get_socket_fd(wsi), // lws_get_parent(wsi), // lws_get_socket_fd(lws_get_parent(wsi)), len); pss1 = lws_wsi_user(lws_get_parent(wsi)); pss1->reason_bf |= 2; lws_callback_on_writable(lws_get_parent(wsi)); break; case LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ: //lwsl_err("LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ len %d\n", len); assert(lws_get_parent(wsi)); m = lws_write(lws_get_parent(wsi), (unsigned char *)in, len, LWS_WRITE_HTTP); if (m < 0) return -1; break; case LWS_CALLBACK_COMPLETED_CLIENT_HTTP: //lwsl_err("LWS_CALLBACK_COMPLETED_CLIENT_HTTP\n"); assert(lws_get_parent(wsi)); if (!lws_get_parent(wsi)) break; pss1 = lws_wsi_user(lws_get_parent(wsi)); pss1->client_finished = 1; break; #endif /* * callbacks for managing the external poll() array appear in * protocol 0 callback */ case LWS_CALLBACK_LOCK_POLL: /* * lock mutex to protect pollfd state * called before any other POLL related callback * if protecting wsi lifecycle change, len == 1 */ test_server_lock(len); break; case LWS_CALLBACK_UNLOCK_POLL: /* * unlock mutex to protect pollfd state when * called after any other POLL related callback * if protecting wsi lifecycle change, len == 1 */ test_server_unlock(len); break; #ifdef EXTERNAL_POLL case LWS_CALLBACK_ADD_POLL_FD: if (count_pollfds >= max_poll_elements) { lwsl_err("LWS_CALLBACK_ADD_POLL_FD: too many sockets to track\n"); return 1; } fd_lookup[pa->fd] = count_pollfds; pollfds[count_pollfds].fd = pa->fd; pollfds[count_pollfds].events = pa->events; pollfds[count_pollfds++].revents = 0; break; case LWS_CALLBACK_DEL_POLL_FD: if (!--count_pollfds) break; m = fd_lookup[pa->fd]; /* have the last guy take up the vacant slot */ pollfds[m] = pollfds[count_pollfds]; fd_lookup[pollfds[count_pollfds].fd] = m; break; case LWS_CALLBACK_CHANGE_MODE_POLL_FD: pollfds[fd_lookup[pa->fd]].events = pa->events; break; #endif case LWS_CALLBACK_GET_THREAD_ID: /* * if you will call "lws_callback_on_writable" * from a different thread, return the caller thread ID * here so lws can use this information to work out if it * should signal the poll() loop to exit and restart early */ /* return pthread_getthreadid_np(); */ break; #if defined(LWS_USE_POLARSSL) #else #if defined(LWS_USE_MBEDTLS) #else #if defined(LWS_OPENSSL_SUPPORT) case LWS_CALLBACK_OPENSSL_PERFORM_CLIENT_CERT_VERIFICATION: /* Verify the client certificate */ if (!len || (SSL_get_verify_result((SSL*)in) != X509_V_OK)) { int err = X509_STORE_CTX_get_error((X509_STORE_CTX*)user); int depth = X509_STORE_CTX_get_error_depth((X509_STORE_CTX*)user); const char* msg = X509_verify_cert_error_string(err); lwsl_err("LWS_CALLBACK_OPENSSL_PERFORM_CLIENT_CERT_VERIFICATION: SSL error: %s (%d), depth: %d\n", msg, err, depth); return 1; } break; #if defined(LWS_HAVE_SSL_CTX_set1_param) case LWS_CALLBACK_OPENSSL_LOAD_EXTRA_SERVER_VERIFY_CERTS: if (crl_path[0]) { /* Enable CRL checking */ X509_VERIFY_PARAM *param = X509_VERIFY_PARAM_new(); X509_VERIFY_PARAM_set_flags(param, X509_V_FLAG_CRL_CHECK); SSL_CTX_set1_param((SSL_CTX*)user, param); X509_STORE *store = SSL_CTX_get_cert_store((SSL_CTX*)user); X509_LOOKUP *lookup = X509_STORE_add_lookup(store, X509_LOOKUP_file()); n = X509_load_cert_crl_file(lookup, crl_path, X509_FILETYPE_PEM); X509_VERIFY_PARAM_free(param); if (n != 1) { char errbuf[256]; n = ERR_get_error(); lwsl_err("LWS_CALLBACK_OPENSSL_LOAD_EXTRA_SERVER_VERIFY_CERTS: SSL error: %s (%d)\n", ERR_error_string(n, errbuf), n); return 1; } } break; #endif #endif #endif #endif default: break; } return 0; /* if we're on HTTP1.1 or 2.0, will keep the idle connection alive */ try_to_reuse: if (lws_http_transaction_completed(wsi)) return -1; return 0; }
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; }
static int callback_esplws_ota(struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len) { struct per_session_data__esplws_ota *pss = (struct per_session_data__esplws_ota *)user; struct per_vhost_data__esplws_ota *vhd = (struct per_vhost_data__esplws_ota *) lws_protocol_vh_priv_get(lws_get_vhost(wsi), lws_get_protocol(wsi)); unsigned char buf[LWS_PRE + 384], *start = buf + LWS_PRE - 1, *p = start, *end = buf + sizeof(buf) - 1; int n; switch (reason) { case LWS_CALLBACK_PROTOCOL_INIT: vhd = lws_protocol_vh_priv_zalloc(lws_get_vhost(wsi), lws_get_protocol(wsi), sizeof(struct per_vhost_data__esplws_ota)); vhd->context = lws_get_context(wsi); vhd->protocol = lws_get_protocol(wsi); vhd->vhost = lws_get_vhost(wsi); break; case LWS_CALLBACK_PROTOCOL_DESTROY: if (!vhd) break; break; /* OTA POST handling */ case LWS_CALLBACK_HTTP_BODY: /* create the POST argument parser if not already existing */ // lwsl_notice("LWS_CALLBACK_HTTP_BODY (ota) %d %d %p\n", (int)pss->file_length, (int)len, pss->spa); lws_set_timeout(wsi, PENDING_TIMEOUT_HTTP_CONTENT, 30); if (!pss->spa) { pss->spa = lws_spa_create(wsi, ota_param_names, ARRAY_SIZE(ota_param_names), 4096, ota_file_upload_cb, pss); if (!pss->spa) return -1; pss->filename[0] = '\0'; pss->file_length = 0; } /* let it parse the POST data */ if (lws_spa_process(pss->spa, in, len)) return -1; break; case LWS_CALLBACK_HTTP_BODY_COMPLETION: lwsl_notice("LWS_CALLBACK_HTTP_BODY_COMPLETION (ota)\n"); /* call to inform no more payload data coming */ lws_spa_finalize(pss->spa); pss->result_len = snprintf(pss->result + LWS_PRE, sizeof(pss->result) - LWS_PRE - 1, "Rebooting after OTA update"); if (lws_add_http_header_status(wsi, HTTP_STATUS_OK, &p, end)) goto bail; if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_CONTENT_TYPE, (unsigned char *)"text/html", 9, &p, end)) goto bail; if (lws_add_http_header_content_length(wsi, pss->result_len, &p, end)) goto bail; if (lws_finalize_http_header(wsi, &p, end)) goto bail; n = lws_write(wsi, start, p - start, LWS_WRITE_HTTP_HEADERS | LWS_WRITE_H2_STREAM_END); if (n < 0) goto bail; lws_callback_on_writable(wsi); break; case LWS_CALLBACK_HTTP_WRITEABLE: if (!pss->result_len) break; lwsl_debug("LWS_CALLBACK_HTTP_WRITEABLE: sending %d\n", pss->result_len); n = lws_write(wsi, (unsigned char *)pss->result + LWS_PRE, pss->result_len, LWS_WRITE_HTTP); if (n < 0) return 1; if (lws_http_transaction_completed(wsi)) return 1; /* stop further service so we don't serve the probe GET to see if we rebooted */ while (1); break; case LWS_CALLBACK_HTTP_DROP_PROTOCOL: /* called when our wsi user_space is going to be destroyed */ if (pss->spa) { lws_spa_destroy(pss->spa); pss->spa = NULL; } break; default: break; } return 0; bail: return 1; }
static int callback_post_demo(struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len) { struct per_session_data__post_demo *pss = (struct per_session_data__post_demo *)user; unsigned char buffer[LWS_PRE + 512]; unsigned char *p, *start, *end; int n; switch (reason) { case LWS_CALLBACK_HTTP_BODY: /* create the POST argument parser if not already existing */ if (!pss->spa) { pss->spa = lws_urldecode_spa_create(wsi, param_names, ARRAY_SIZE(param_names), 1024, file_upload_cb, pss); if (!pss->spa) return -1; pss->filename[0] = '\0'; pss->file_length = 0; } /* let it parse the POST data */ if (lws_urldecode_spa_process(pss->spa, in, len)) return -1; break; case LWS_CALLBACK_HTTP_BODY_COMPLETION: lwsl_debug("LWS_CALLBACK_HTTP_BODY_COMPLETION\n"); /* * the whole of the sent body arrived, * respond to the client with a redirect to show the * results */ /* call to inform no more payload data coming */ lws_urldecode_spa_finalize(pss->spa); p = (unsigned char *)pss->result + LWS_PRE; end = p + sizeof(pss->result) - LWS_PRE - 1; p += sprintf((char *)p, "<html><body><h1>Form results (after urldecoding)</h1>" "<table><tr><td>Name</td><td>Length</td><td>Value</td></tr>"); for (n = 0; n < ARRAY_SIZE(param_names); n++) p += snprintf((char *)p, end - p, "<tr><td><b>%s</b></td><td>%d</td><td>%s</td></tr>", param_names[n], lws_urldecode_spa_get_length(pss->spa, n), lws_urldecode_spa_get_string(pss->spa, n)); p += snprintf((char *)p, end - p, "</table><br><b>filename:</b> %s, <b>length</b> %ld", pss->filename, pss->file_length); p += snprintf((char *)p, end - p, "</body></html>"); pss->result_len = p - (unsigned char *)(pss->result + LWS_PRE); p = buffer + LWS_PRE; start = p; 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_HTTP_CONTENT_TYPE, (unsigned char *)"text/html", 9, &p, end)) return 1; if (lws_add_http_header_content_length(wsi, pss->result_len, &p, end)) return 1; if (lws_finalize_http_header(wsi, &p, end)) return 1; n = lws_write(wsi, start, p - start, LWS_WRITE_HTTP_HEADERS); if (n < 0) return 1; /* * send the payload next time, in case would block after * headers */ lws_callback_on_writable(wsi); break; case LWS_CALLBACK_HTTP_WRITEABLE: lwsl_debug("LWS_CALLBACK_HTTP_WRITEABLE: sending %d\n", pss->result_len); n = lws_write(wsi, (unsigned char *)pss->result + LWS_PRE, pss->result_len, LWS_WRITE_HTTP); if (n < 0) return 1; goto try_to_reuse; case LWS_CALLBACK_HTTP_DROP_PROTOCOL: if (pss->spa) { lws_urldecode_spa_destroy(pss->spa); pss->spa = NULL; } break; default: break; } return 0; try_to_reuse: if (lws_http_transaction_completed(wsi)) return -1; return 0; }
int lws_context_init_server(struct lws_context_creation_info *info, struct lws_context *context) { #ifdef LWS_POSIX int n, opt = 1, limit = 1; #endif lws_sockfd_type sockfd; struct lws *wsi; int m = 0; /* set up our external listening socket we serve on */ if (info->port == CONTEXT_PORT_NO_LISTEN) return 0; #if LWS_POSIX #if defined(__linux__) limit = context->count_threads; #endif for (m = 0; m < limit; m++) { #ifdef LWS_USE_IPV6 if (LWS_IPV6_ENABLED(context)) sockfd = socket(AF_INET6, SOCK_STREAM, 0); else #endif sockfd = socket(AF_INET, SOCK_STREAM, 0); if (sockfd == -1) { #else sockfd = mbed3_create_tcp_stream_socket(); if (!lws_sockfd_valid(sockfd)) { #endif lwsl_err("ERROR opening socket\n"); return 1; } #if LWS_POSIX /* * allow us to restart even if old sockets in TIME_WAIT */ if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, (const void *)&opt, sizeof(opt)) < 0) { compatible_close(sockfd); return 1; } #if defined(__linux__) && defined(SO_REUSEPORT) && LWS_MAX_SMP > 1 if (context->count_threads > 1) if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEPORT, (const void *)&opt, sizeof(opt)) < 0) { compatible_close(sockfd); return 1; } #endif #endif lws_plat_set_socket_options(context, sockfd); #if LWS_POSIX n = lws_socket_bind(context, sockfd, info->port, info->iface); if (n < 0) goto bail; info->port = n; #endif context->listen_port = info->port; wsi = lws_zalloc(sizeof(struct lws)); if (wsi == NULL) { lwsl_err("Out of mem\n"); goto bail; } wsi->context = context; wsi->sock = sockfd; wsi->mode = LWSCM_SERVER_LISTENER; wsi->protocol = context->protocols; wsi->tsi = m; context->pt[m].wsi_listening = wsi; if (insert_wsi_socket_into_fds(context, wsi)) goto bail; context->count_wsi_allocated++; context->pt[m].lserv_fd = sockfd; #if LWS_POSIX listen(wsi->sock, LWS_SOMAXCONN); } /* for each thread able to independently lister */ #else mbed3_tcp_stream_bind(wsi->sock, info->port, wsi); #endif lwsl_notice(" Listening on port %d\n", info->port); return 0; bail: compatible_close(sockfd); return 1; } int _lws_server_listen_accept_flow_control(struct lws *twsi, int on) { struct lws_context_per_thread *pt = &twsi->context->pt[(int)twsi->tsi]; struct lws *wsi = pt->wsi_listening; int n; if (!wsi || twsi->context->being_destroyed) return 0; lwsl_debug("%s: Thr %d: LISTEN wsi %p: state %d\n", __func__, twsi->tsi, (void *)wsi, on); if (on) n = lws_change_pollfd(wsi, 0, LWS_POLLIN); else n = lws_change_pollfd(wsi, LWS_POLLIN, 0); return n; } 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]; 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); 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, "https://%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 (n < 0) goto bail_nuke_ah; return lws_http_transaction_completed(wsi); } 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; } int lws_handshake_server(struct lws *wsi, unsigned char **buf, size_t len) { struct lws_context *context = lws_get_context(wsi); struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi]; struct _lws_header_related hdr; struct allocated_headers *ah; int protocol_len, n, hit; char protocol_list[128]; char protocol_name[32]; char *p; assert(len < 10000000); assert(wsi->u.hdr.ah); while (len--) { wsi->more_rx_waiting = !!len; assert(wsi->mode == LWSCM_HTTP_SERVING); if (lws_parse(wsi, *(*buf)++)) { lwsl_info("lws_parse failed\n"); goto bail_nuke_ah; } if (wsi->u.hdr.parser_state != WSI_PARSING_COMPLETE) continue; lwsl_parser("%s: lws_parse sees parsing complete\n", __func__); lwsl_debug("%s: wsi->more_rx_waiting=%d\n", __func__, wsi->more_rx_waiting); wsi->mode = LWSCM_PRE_WS_SERVING_ACCEPT; lws_set_timeout(wsi, NO_PENDING_TIMEOUT, 0); /* is this websocket protocol or normal http 1.0? */ if (lws_hdr_total_length(wsi, WSI_TOKEN_UPGRADE)) { if (!strcasecmp(lws_hdr_simple_ptr(wsi, WSI_TOKEN_UPGRADE), "websocket")) { lwsl_info("Upgrade to ws\n"); goto upgrade_ws; } #ifdef LWS_USE_HTTP2 if (!strcasecmp(lws_hdr_simple_ptr(wsi, WSI_TOKEN_UPGRADE), "h2c-14")) { lwsl_info("Upgrade to h2c-14\n"); goto upgrade_h2c; } #endif lwsl_err("Unknown upgrade\n"); /* dunno what he wanted to upgrade to */ goto bail_nuke_ah; } /* no upgrade ack... he remained as HTTP */ lwsl_info("No upgrade\n"); ah = wsi->u.hdr.ah; lws_union_transition(wsi, LWSCM_HTTP_SERVING_ACCEPTED); wsi->state = LWSS_HTTP; wsi->u.http.fd = LWS_INVALID_FILE; /* expose it at the same offset as u.hdr */ wsi->u.http.ah = ah; lwsl_debug("%s: wsi %p: ah %p\n", __func__, (void *)wsi, (void *)wsi->u.hdr.ah); n = lws_http_action(wsi); return n; #ifdef LWS_USE_HTTP2 upgrade_h2c: if (!lws_hdr_total_length(wsi, WSI_TOKEN_HTTP2_SETTINGS)) { lwsl_err("missing http2_settings\n"); goto bail_nuke_ah; } lwsl_err("h2c upgrade...\n"); p = lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP2_SETTINGS); /* convert the peer's HTTP-Settings */ n = lws_b64_decode_string(p, protocol_list, sizeof(protocol_list)); if (n < 0) { lwsl_parser("HTTP2_SETTINGS too long\n"); return 1; } /* adopt the header info */ ah = wsi->u.hdr.ah; lws_union_transition(wsi, LWSCM_HTTP2_SERVING); /* http2 union member has http union struct at start */ wsi->u.http.ah = ah; lws_http2_init(&wsi->u.http2.peer_settings); lws_http2_init(&wsi->u.http2.my_settings); /* HTTP2 union */ lws_http2_interpret_settings_payload(&wsi->u.http2.peer_settings, (unsigned char *)protocol_list, n); strcpy(protocol_list, "HTTP/1.1 101 Switching Protocols\x0d\x0a" "Connection: Upgrade\x0d\x0a" "Upgrade: h2c\x0d\x0a\x0d\x0a"); n = lws_issue_raw(wsi, (unsigned char *)protocol_list, strlen(protocol_list)); if (n != strlen(protocol_list)) { lwsl_debug("http2 switch: ERROR writing to socket\n"); return 1; } wsi->state = LWSS_HTTP2_AWAIT_CLIENT_PREFACE; return 0; #endif upgrade_ws: if (!wsi->protocol) lwsl_err("NULL protocol at lws_read\n"); /* * It's websocket * * Select the first protocol we support from the list * the client sent us. * * Copy it to remove header fragmentation */ if (lws_hdr_copy(wsi, protocol_list, sizeof(protocol_list) - 1, WSI_TOKEN_PROTOCOL) < 0) { lwsl_err("protocol list too long"); goto bail_nuke_ah; } protocol_len = lws_hdr_total_length(wsi, WSI_TOKEN_PROTOCOL); protocol_list[protocol_len] = '\0'; p = protocol_list; hit = 0; while (*p && !hit) { unsigned int n = 0; while (n < sizeof(protocol_name) - 1 && *p && *p !=',') protocol_name[n++] = *p++; protocol_name[n] = '\0'; if (*p) p++; lwsl_info("checking %s\n", protocol_name); n = 0; while (context->protocols[n].callback) { if (context->protocols[n].name && !strcmp(context->protocols[n].name, protocol_name)) { lwsl_info("prot match %d\n", n); wsi->protocol = &context->protocols[n]; hit = 1; break; } n++; } } /* we didn't find a protocol he wanted? */ if (!hit) { if (lws_hdr_simple_ptr(wsi, WSI_TOKEN_PROTOCOL)) { lwsl_err("No protocol from \"%s\" supported\n", protocol_list); goto bail_nuke_ah; } /* * some clients only have one protocol and * do not sent the protocol list header... * allow it and match to protocol 0 */ lwsl_info("defaulting to prot 0 handler\n"); wsi->protocol = &context->protocols[0]; } /* allocate wsi->user storage */ if (lws_ensure_user_space(wsi)) goto bail_nuke_ah; /* * Give the user code a chance to study the request and * have the opportunity to deny it */ if ((wsi->protocol->callback)(wsi, LWS_CALLBACK_FILTER_PROTOCOL_CONNECTION, wsi->user_space, lws_hdr_simple_ptr(wsi, WSI_TOKEN_PROTOCOL), 0)) { lwsl_warn("User code denied connection\n"); goto bail_nuke_ah; } /* * Perform the handshake according to the protocol version the * client announced */ switch (wsi->ietf_spec_revision) { case 13: lwsl_parser("lws_parse calling handshake_04\n"); if (handshake_0405(context, wsi)) { lwsl_info("hs0405 has failed the connection\n"); goto bail_nuke_ah; } break; default: lwsl_warn("Unknown client spec version %d\n", wsi->ietf_spec_revision); goto bail_nuke_ah; } /* we are upgrading to ws, so http/1.1 and keepalive + * pipelined header considerations about keeping the ah around * no longer apply. However it's common for the first ws * protocol data to have been coalesced with the browser * upgrade request and to already be in the ah rx buffer. */ lwsl_info("%s: %p: inheriting ah in ws mode (rxpos:%d, rxlen:%d)\n", __func__, wsi, wsi->u.hdr.ah->rxpos, wsi->u.hdr.ah->rxlen); lws_pt_lock(pt); hdr = wsi->u.hdr; lws_union_transition(wsi, LWSCM_WS_SERVING); /* * first service is WS mode will notice this, use the RX and * then detach the ah (caution: we are not in u.hdr union * mode any more then... ah_temp member is at start the same * though) * * Because rxpos/rxlen shows something in the ah, we will get * service guaranteed next time around the event loop * * All union members begin with hdr, so we can use it even * though we transitioned to ws union mode (the ah detach * code uses it anyway). */ wsi->u.hdr = hdr; lws_pt_unlock(pt); /* * create the frame buffer for this connection according to the * size mentioned in the protocol definition. If 0 there, use * a big default for compatibility */ n = wsi->protocol->rx_buffer_size; if (!n) n = LWS_MAX_SOCKET_IO_BUF; n += LWS_PRE; wsi->u.ws.rx_ubuf = lws_malloc(n + 4 /* 0x0000ffff zlib */); if (!wsi->u.ws.rx_ubuf) { lwsl_err("Out of Mem allocating rx buffer %d\n", n); return 1; } wsi->u.ws.rx_ubuf_alloc = n; lwsl_info("Allocating RX buffer %d\n", n); #if LWS_POSIX if (setsockopt(wsi->sock, SOL_SOCKET, SO_SNDBUF, (const char *)&n, sizeof n)) { lwsl_warn("Failed to set SNDBUF to %d", n); return 1; } #endif lwsl_parser("accepted v%02d connection\n", wsi->ietf_spec_revision); return 0; } /* while all chars are handled */ return 0; bail_nuke_ah: /* drop the header info */ /* 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; } static int lws_get_idlest_tsi(struct lws_context *context) { unsigned int lowest = ~0; int n = 0, hit = -1; for (; n < context->count_threads; n++) { if ((unsigned int)context->pt[n].fds_count != context->fd_limit_per_thread - 1 && (unsigned int)context->pt[n].fds_count < lowest) { lowest = context->pt[n].fds_count; hit = n; } } return hit; }
static int callback_post_demo(struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len) { struct per_session_data__post_demo *pss = (struct per_session_data__post_demo *)user; unsigned char buffer[LWS_PRE + 512]; unsigned char *p, *start, *end; int n; switch (reason) { case LWS_CALLBACK_HTTP_BODY: lwsl_debug("LWS_CALLBACK_HTTP_BODY: len %d\n", (int)len); strncpy(pss->post_string, in, sizeof (pss->post_string) -1); pss->post_string[sizeof(pss->post_string) - 1] = '\0'; if (len < sizeof(pss->post_string) - 1) pss->post_string[len] = '\0'; break; case LWS_CALLBACK_HTTP_WRITEABLE: lwsl_debug("LWS_CALLBACK_HTTP_WRITEABLE: sending %d\n", pss->result_len); n = lws_write(wsi, (unsigned char *)pss->result + LWS_PRE, pss->result_len, LWS_WRITE_HTTP); if (n < 0) return 1; goto try_to_reuse; case LWS_CALLBACK_HTTP_BODY_COMPLETION: lwsl_debug("LWS_CALLBACK_HTTP_BODY_COMPLETION\n"); /* * the whole of the sent body arrived, * respond to the client with a redirect to show the * results */ pss->result_len = sprintf((char *)pss->result + LWS_PRE, "<html><body><h1>Form results</h1>'%s'<br>" "</body></html>", pss->post_string); p = buffer + LWS_PRE; start = p; 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_HTTP_CONTENT_TYPE, (unsigned char *)"text/html", 9, &p, end)) return 1; if (lws_add_http_header_content_length(wsi, pss->result_len, &p, end)) return 1; if (lws_finalize_http_header(wsi, &p, end)) return 1; n = lws_write(wsi, start, p - start, LWS_WRITE_HTTP_HEADERS); if (n < 0) return 1; /* * send the payload next time, in case would block after * headers */ lws_callback_on_writable(wsi); break; default: break; } return 0; try_to_reuse: if (lws_http_transaction_completed(wsi)) return -1; return 0; }
LWS_VISIBLE int lws_return_http_status(struct lws *wsi, unsigned int code, const char *html_body) { struct lws_context *context = lws_get_context(wsi); struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi]; unsigned char *p = pt->serv_buf + LWS_PRE; unsigned char *start = p; unsigned char *end = p + context->pt_serv_buf_size - LWS_PRE; int n = 0, m = 0, len; char slen[20]; if (!html_body) html_body = ""; if (lws_add_http_header_status(wsi, code, &p, end)) return 1; if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_CONTENT_TYPE, (unsigned char *)"text/html", 9, &p, end)) return 1; len = 35 + (int)strlen(html_body) + sprintf(slen, "%d", code); n = sprintf(slen, "%d", len); if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_CONTENT_LENGTH, (unsigned char *)slen, n, &p, end)) return 1; if (lws_finalize_http_header(wsi, &p, end)) return 1; #if defined(LWS_WITH_HTTP2) if (wsi->http2_substream) { unsigned char *body = p + 512; /* * for HTTP/2, the headers must be sent separately, since they * go out in their own frame. That puts us in a bind that * we won't always be able to get away with two lws_write()s in * sequence, since the first may use up the writability due to * the pipe being choked or SSL_WANT_. * * However we do need to send the human-readable body, and the * END_STREAM. * * Solve it by writing the headers now... */ m = lws_write(wsi, start, p - start, LWS_WRITE_HTTP_HEADERS); if (m != lws_ptr_diff(p, start)) return 1; /* * ... but stash the body and send it as a priority next * handle_POLLOUT */ len = sprintf((char *)body, "<html><body><h1>%u</h1>%s</body></html>", code, html_body); wsi->http.tx_content_length = len; wsi->http.tx_content_remain = len; wsi->h2.pending_status_body = lws_malloc(len + LWS_PRE + 1, "pending status body"); if (!wsi->h2.pending_status_body) return -1; strcpy(wsi->h2.pending_status_body + LWS_PRE, (const char *)body); lws_callback_on_writable(wsi); return 0; } else #endif { /* * for http/1, we can just append the body after the finalized * headers and send it all in one go. */ p += lws_snprintf((char *)p, end - p - 1, "<html><body><h1>%u</h1>%s</body></html>", code, html_body); n = lws_ptr_diff(p, start); m = lws_write(wsi, start, n, LWS_WRITE_HTTP); if (m != n) return 1; } return m != n; }
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; unsigned char buffer[4096 + LWS_PRE]; unsigned long amount, file_len, sent; char leaf_path[1024]; const char *mimetype; char *other_headers; unsigned char *end; struct timeval tv; unsigned char *p; char buf[256]; char b64[64]; int n, m; #ifdef EXTERNAL_POLL struct lws_pollargs *pa = (struct lws_pollargs *)in; #endif switch (reason) { case LWS_CALLBACK_HTTP: if (debug_level & LLL_INFO) { dump_handshake_info(wsi); /* dump the individual URI Arg parameters */ n = 0; while (lws_hdr_copy_fragment(wsi, buf, sizeof(buf), WSI_TOKEN_HTTP_URI_ARGS, n) > 0) { lwsl_notice("URI Arg %d: %s\n", ++n, buf); } } { char name[100], rip[50]; lws_get_peer_addresses(wsi, lws_get_socket_fd(wsi), name, sizeof(name), rip, sizeof(rip)); sprintf(buf, "%s (%s)", name, rip); lwsl_notice("HTTP connect from %s\n", buf); } if (len < 1) { lws_return_http_status(wsi, HTTP_STATUS_BAD_REQUEST, NULL); goto try_to_reuse; } /* 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; } /* if a legal POST URL, let it continue and accept data */ if (lws_hdr_total_length(wsi, WSI_TOKEN_POST_URI)) return 0; /* check for the "send a big file by hand" example case */ if (!strcmp((const char *)in, "/leaf.jpg")) { if (strlen(resource_path) > sizeof(leaf_path) - 10) return -1; sprintf(leaf_path, "%s/leaf.jpg", resource_path); /* well, let's demonstrate how to send the hard way */ p = buffer + LWS_PRE; end = p + sizeof(buffer) - LWS_PRE; pss->fd = lws_plat_file_open(wsi, leaf_path, &file_len, LWS_O_RDONLY); if (pss->fd == LWS_INVALID_FILE) { lwsl_err("faild to open file %s\n", leaf_path); return -1; } /* * we will send a big jpeg file, but it could be * anything. Set the Content-Type: appropriately * so the browser knows what to do with it. * * Notice we use the APIs to build the header, which * will do the right thing for HTTP 1/1.1 and HTTP2 * depending on what connection it happens to be working * on */ if (lws_add_http_header_status(wsi, 200, &p, end)) return 1; if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_SERVER, (unsigned char *)"libwebsockets", 13, &p, end)) return 1; if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_CONTENT_TYPE, (unsigned char *)"image/jpeg", 10, &p, end)) return 1; if (lws_add_http_header_content_length(wsi, file_len, &p, end)) return 1; if (lws_finalize_http_header(wsi, &p, end)) return 1; /* * send the http headers... * this won't block since it's the first payload sent * on the connection since it was established * (too small for partial) * * Notice they are sent using LWS_WRITE_HTTP_HEADERS * which also means you can't send body too in one step, * this is mandated by changes in HTTP2 */ *p = '\0'; lwsl_info("%s\n", buffer + LWS_PRE); n = lws_write(wsi, buffer + LWS_PRE, p - (buffer + LWS_PRE), LWS_WRITE_HTTP_HEADERS); if (n < 0) { lws_plat_file_close(wsi, pss->fd); return -1; } /* * book us a LWS_CALLBACK_HTTP_WRITEABLE callback */ lws_callback_on_writable(wsi); break; } /* if not, send a file the easy way */ strcpy(buf, resource_path); if (strcmp(in, "/")) { if (*((const char *)in) != '/') strcat(buf, "/"); strncat(buf, in, sizeof(buf) - strlen(resource_path)); } else /* default file to serve */ strcat(buf, "/test.html"); buf[sizeof(buf) - 1] = '\0'; /* refuse to serve files we don't understand */ mimetype = get_mimetype(buf); if (!mimetype) { lwsl_err("Unknown mimetype for %s\n", buf); lws_return_http_status(wsi, HTTP_STATUS_UNSUPPORTED_MEDIA_TYPE, NULL); return -1; } /* demonstrates how to set a cookie on / */ other_headers = leaf_path; p = (unsigned char *)leaf_path; if (!strcmp((const char *)in, "/") && !lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_COOKIE)) { /* this isn't very unguessable but it'll do for us */ gettimeofday(&tv, NULL); n = sprintf(b64, "test=LWS_%u_%u_COOKIE;Max-Age=360000", (unsigned int)tv.tv_sec, (unsigned int)tv.tv_usec); if (lws_add_http_header_by_name(wsi, (unsigned char *)"set-cookie:", (unsigned char *)b64, n, &p, (unsigned char *)leaf_path + sizeof(leaf_path))) return 1; } if (lws_is_ssl(wsi) && lws_add_http_header_by_name(wsi, (unsigned char *) "Strict-Transport-Security:", (unsigned char *) "max-age=15768000 ; " "includeSubDomains", 36, &p, (unsigned char *)leaf_path + sizeof(leaf_path))) return 1; n = (char *)p - leaf_path; n = lws_serve_http_file(wsi, buf, mimetype, other_headers, n); if (n < 0 || ((n > 0) && lws_http_transaction_completed(wsi))) return -1; /* error or can't reuse connection: close the socket */ /* * notice that the sending of the file completes asynchronously, * we'll get a LWS_CALLBACK_HTTP_FILE_COMPLETION callback when * it's done */ break; case LWS_CALLBACK_HTTP_BODY: strncpy(buf, in, 20); buf[20] = '\0'; if (len < 20) buf[len] = '\0'; lwsl_notice("LWS_CALLBACK_HTTP_BODY: %s... len %d\n", (const char *)buf, (int)len); break; case LWS_CALLBACK_HTTP_BODY_COMPLETION: lwsl_notice("LWS_CALLBACK_HTTP_BODY_COMPLETION\n"); /* the whole of the sent body arrived, close or reuse the connection */ lws_return_http_status(wsi, HTTP_STATUS_OK, NULL); goto try_to_reuse; case LWS_CALLBACK_HTTP_FILE_COMPLETION: goto try_to_reuse; case LWS_CALLBACK_HTTP_WRITEABLE: lwsl_info("LWS_CALLBACK_HTTP_WRITEABLE\n"); if (pss->fd == LWS_INVALID_FILE) goto try_to_reuse; /* * we can send more of whatever it is we were sending */ sent = 0; do { /* we'd like the send this much */ n = sizeof(buffer) - LWS_PRE; /* but if the peer told us he wants less, we can adapt */ m = lws_get_peer_write_allowance(wsi); /* -1 means not using a protocol that has this info */ if (m == 0) /* right now, peer can't handle anything */ goto later; if (m != -1 && m < n) /* he couldn't handle that much */ n = m; n = lws_plat_file_read(wsi, pss->fd, &amount, buffer + LWS_PRE, n); /* problem reading, close conn */ if (n < 0) { lwsl_err("problem reading file\n"); goto bail; } n = (int)amount; /* sent it all, close conn */ if (n == 0) goto penultimate; /* * To support HTTP2, must take care about preamble space * * identification of when we send the last payload frame * is handled by the library itself if you sent a * content-length header */ m = lws_write(wsi, buffer + LWS_PRE, n, LWS_WRITE_HTTP); if (m < 0) { lwsl_err("write failed\n"); /* write failed, close conn */ goto bail; } if (m) /* while still active, extend timeout */ lws_set_timeout(wsi, PENDING_TIMEOUT_HTTP_CONTENT, 5); sent += m; } while (!lws_send_pipe_choked(wsi) && (sent < 1024 * 1024)); later: lws_callback_on_writable(wsi); break; penultimate: lws_plat_file_close(wsi, pss->fd); pss->fd = LWS_INVALID_FILE; goto try_to_reuse; bail: lws_plat_file_close(wsi, pss->fd); return -1; /* * callback for confirming to continue with client IP appear in * protocol 0 callback since no websocket protocol has been agreed * yet. You can just ignore this if you won't filter on client IP * since the default uhandled callback return is 0 meaning let the * connection continue. */ case LWS_CALLBACK_FILTER_NETWORK_CONNECTION: /* if we returned non-zero from here, we kill the connection */ break; /* * callbacks for managing the external poll() array appear in * protocol 0 callback */ case LWS_CALLBACK_LOCK_POLL: /* * lock mutex to protect pollfd state * called before any other POLL related callback * if protecting wsi lifecycle change, len == 1 */ test_server_lock(len); break; case LWS_CALLBACK_UNLOCK_POLL: /* * unlock mutex to protect pollfd state when * called after any other POLL related callback * if protecting wsi lifecycle change, len == 1 */ test_server_unlock(len); break; #ifdef EXTERNAL_POLL case LWS_CALLBACK_ADD_POLL_FD: if (count_pollfds >= max_poll_elements) { lwsl_err("LWS_CALLBACK_ADD_POLL_FD: too many sockets to track\n"); return 1; } fd_lookup[pa->fd] = count_pollfds; pollfds[count_pollfds].fd = pa->fd; pollfds[count_pollfds].events = pa->events; pollfds[count_pollfds++].revents = 0; break; case LWS_CALLBACK_DEL_POLL_FD: if (!--count_pollfds) break; m = fd_lookup[pa->fd]; /* have the last guy take up the vacant slot */ pollfds[m] = pollfds[count_pollfds]; fd_lookup[pollfds[count_pollfds].fd] = m; break; case LWS_CALLBACK_CHANGE_MODE_POLL_FD: pollfds[fd_lookup[pa->fd]].events = pa->events; break; #endif case LWS_CALLBACK_GET_THREAD_ID: /* * if you will call "lws_callback_on_writable" * from a different thread, return the caller thread ID * here so lws can use this information to work out if it * should signal the poll() loop to exit and restart early */ /* return pthread_getthreadid_np(); */ break; default: break; } return 0; /* if we're on HTTP1.1 or 2.0, will keep the idle connection alive */ try_to_reuse: if (lws_http_transaction_completed(wsi)) return -1; return 0; }
int callback_http(struct lws *webSocketInstance, enum lws_callback_reasons reason, void *user, void *in, size_t len) { struct per_session_data__http *pss = (struct per_session_data__http *) user; struct timeval tv; unsigned long amount, file_len; const char *contentType; static unsigned char buffer4096[4096]; char buffer256[256]; char buffer64[64]; char leaf_path[1024]; char *other_headers; unsigned char *end; unsigned char *p; int n, m, headerIndex = 0; #ifdef EXTERNAL_POLL struct lws_pollargs *pa = (struct lws_pollargs *)in; #endif switch (reason) { case LWS_CALLBACK_HTTP: /* SS_Server_DumpHandShake(webSocketInstance); */ /* dump the individual URI Arg parameters */ while (lws_hdr_copy_fragment(webSocketInstance, buffer256, sizeof(buffer256), WSI_TOKEN_HTTP_URI_ARGS, headerIndex) > 0) { lwsl_info("URI Arg %d: %s\n", ++headerIndex, buffer256); } if (len < 1) { lws_return_http_status(webSocketInstance, HTTP_STATUS_BAD_REQUEST, NULL); goto try_to_reuse; } /* if a legal POST URL, let it continue and accept data */ if (lws_hdr_total_length(webSocketInstance, WSI_TOKEN_POST_URI)) { return 0; } if(strchr((const char*)in + 1, '/')) { if (strcmp(in, "/")) { if (*((const char *)in) != '/') { strcat(buffer256, "/"); } strncat(buffer256, in, sizeof(buffer256) - strlen(RESOURCE_PATH)); } else { /* default file to serve */ strcat(buffer256, in); } buffer256[sizeof(buffer256) - 1] = '\0'; sprintf(leaf_path, "%s%s", RESOURCE_PATH, buffer256); p = buffer4096 + LWS_PRE; end = p + sizeof(buffer4096) - LWS_PRE; pss->fd = lws_plat_file_open(webSocketInstance, leaf_path, &file_len, LWS_O_RDONLY); contentType = get_mimetype(buffer256); if (!contentType) { lwsl_err("Unknown content-type for %s\n", buffer256); lws_return_http_status(webSocketInstance, HTTP_STATUS_UNSUPPORTED_MEDIA_TYPE, NULL); return -1; } if (pss->fd == LWS_INVALID_FILE) { return -1; } if (lws_add_http_header_status(webSocketInstance, 200, &p, end)) { return 1; } if (lws_add_http_header_by_token(webSocketInstance, WSI_TOKEN_HTTP_SERVER, (unsigned char *)"libwebsockets", 13, &p, end)) { return 1; } if(strncmp(contentType, "*/*", 3) == 0) { if (lws_add_http_header_by_token(webSocketInstance, WSI_TOKEN_HTTP_CONTENT_TYPE, (unsigned char *)contentType, 4, &p, end)) { return 1; } } else { if(strncmp(contentType, "application/x-javascript", 24) == 0) { if (lws_add_http_header_by_token(webSocketInstance, WSI_TOKEN_HTTP_CONTENT_TYPE, (unsigned char *)contentType, 24, &p, end)) { return 1; } } else { if (lws_add_http_header_by_token(webSocketInstance, WSI_TOKEN_HTTP_CONTENT_TYPE, (unsigned char *)contentType, 9, &p, end)) { return 1; } } } if (lws_add_http_header_content_length(webSocketInstance, file_len, &p, end)) { return 1; } if (lws_finalize_http_header(webSocketInstance, &p, end)) { return 1; } n = lws_write(webSocketInstance, buffer4096 + LWS_PRE, p - (buffer4096 + LWS_PRE), LWS_WRITE_HTTP_HEADERS); if (n < 0) { lws_plat_file_close(webSocketInstance, pss->fd); return -1; } lws_callback_on_writable(webSocketInstance); break; printf("%s",buffer256); other_headers = NULL; n = 0; if (!strcmp((const char *)in, "/") && !lws_hdr_total_length(webSocketInstance, WSI_TOKEN_HTTP_COOKIE)) { /* this isn't very unguessable but it'll do for us */ gettimeofday(&tv, NULL); n = sprintf(buffer64, "test=SOUNDSHIELD_%u_%u_COOKIE;Max-Age=360000", (unsigned int)tv.tv_sec, (unsigned int)tv.tv_usec); p = (unsigned char *)buffer256; if (lws_add_http_header_by_name(webSocketInstance, (unsigned char *)"set-cookie:", (unsigned char *)buffer64, n, &p, (unsigned char *)buffer256 + sizeof(buffer256))) { return 1; } n = (char *)p - buffer256; other_headers = buffer256; } n = lws_serve_http_file(webSocketInstance, buffer256, contentType, other_headers, n); if (n < 0 || ((n > 0) && lws_http_transaction_completed(webSocketInstance))) { return -1; /* error or can't reuse connection: close the socket */ } break; } /* if not, send a file the easy way */ strcpy(buffer256, RESOURCE_PATH); if (strcmp(in, "/")) { if (*((const char *)in) != '/') { strcat(buffer256, "/"); } strncat(buffer256, in, sizeof(buffer256) - strlen(RESOURCE_PATH)); } else { /* default file to serve */ strcat(buffer256, "/index.html"); } buffer256[sizeof(buffer256) - 1] = '\0'; /* refuse to serve files we don't understand */ contentType = get_mimetype(buffer256); if (!contentType) { lwsl_err("Unknown content-type for %s\n", buffer256); lws_return_http_status(webSocketInstance, HTTP_STATUS_UNSUPPORTED_MEDIA_TYPE, NULL); return -1; } /* demonstrates how to set a cookie on / */ other_headers = NULL; n = 0; if (!strcmp((const char *)in, "/") && !lws_hdr_total_length(webSocketInstance, WSI_TOKEN_HTTP_COOKIE)) { /* this isn't very unguessable but it'll do for us */ gettimeofday(&tv, NULL); n = sprintf(buffer64, "test=LWS_%u_%u_COOKIE;Max-Age=360000", (unsigned int)tv.tv_sec, (unsigned int)tv.tv_usec); p = (unsigned char *)leaf_path; if (lws_add_http_header_by_name(webSocketInstance, (unsigned char *)"set-cookie:", (unsigned char *)buffer64, n, &p, (unsigned char *)leaf_path + sizeof(leaf_path))) { return 1; } n = (char *)p - leaf_path; other_headers = leaf_path; } n = lws_serve_http_file(webSocketInstance, buffer256, contentType, other_headers, n); if (n < 0 || ((n > 0) && lws_http_transaction_completed(webSocketInstance))) { return -1; /* error or can't reuse connection: close the socket */ } /* * notice that the sending of the file completes asynchronously, * we'll get a LWS_CALLBACK_HTTP_FILE_COMPLETION callback when * it's done */ break; case LWS_CALLBACK_HTTP_BODY: strncpy(buffer256, in, 20); buffer256[20] = '\0'; if (len < 20) { buffer256[len] = '\0'; } lwsl_notice("LWS_CALLBACK_HTTP_BODY: %s... len %d\n", (const char *)buffer256, (int)len); break; case LWS_CALLBACK_HTTP_BODY_COMPLETION: lwsl_notice("LWS_CALLBACK_HTTP_BODY_COMPLETION\n"); /* the whole of the sent body arrived, close or reuse the connection */ lws_return_http_status(webSocketInstance, HTTP_STATUS_OK, NULL); goto try_to_reuse; case LWS_CALLBACK_HTTP_FILE_COMPLETION: goto try_to_reuse; case LWS_CALLBACK_HTTP_WRITEABLE: /* * we can send more of whatever it is we were sending */ do { /* we'd like the send this much */ n = sizeof(buffer4096) - LWS_PRE; /* but if the peer told us he wants less, we can adapt */ m = lws_get_peer_write_allowance(webSocketInstance); /* -1 means not using a protocol that has this info */ if (m == 0) { /* right now, peer can't handle anything */ goto later; } if (m != -1 && m < n) { /* he couldn't handle that much */ n = m; } n = lws_plat_file_read(webSocketInstance, pss->fd, &amount, buffer4096 + LWS_PRE, n); /* problem reading, close conn */ if (n < 0) { goto bail; } n = (int)amount; /* sent it all, close conn */ if (n == 0) { goto flush_bail; } /* * To support HTTP2, must take care about preamble space * * identification of when we send the last payload frame * is handled by the library itself if you sent a * content-length header */ m = lws_write(webSocketInstance, buffer4096 + LWS_PRE, n, LWS_WRITE_HTTP); if (m < 0) { /* write failed, close conn */ goto bail; } /* * http2 won't do this */ if (m != n) { /* partial write, adjust */ if (lws_plat_file_seek_cur(webSocketInstance, pss->fd, m - n) == (unsigned long)-1) goto bail; } if (m) { /* while still active, extend timeout */ lws_set_timeout(webSocketInstance, PENDING_TIMEOUT_HTTP_CONTENT, 5); } /* if we have indigestion, let him clear it * before eating more */ if (lws_partial_buffered(webSocketInstance)) break; } while (!lws_send_pipe_choked(webSocketInstance)); later: lws_callback_on_writable(webSocketInstance); break; flush_bail: /* true if still partial pending */ if (lws_partial_buffered(webSocketInstance)) { lws_callback_on_writable(webSocketInstance); break; } lws_plat_file_close(webSocketInstance, pss->fd); goto try_to_reuse; bail: lws_plat_file_close(webSocketInstance, pss->fd); return -1; /* * callback for confirming to continue with client IP appear in * protocol 0 callback since no websocket protocol has been agreed * yet. You can just ignore this if you won't filter on client IP * since the default uhandled callback return is 0 meaning let the * connection continue. */ case LWS_CALLBACK_FILTER_NETWORK_CONNECTION: /* if we returned non-zero from here, we kill the connection */ break; /* * callbacks for managing the external poll() array appear in * protocol 0 callback */ case LWS_CALLBACK_LOCK_POLL: /* * lock mutex to protect pollfd state * called before any other POLL related callback * if protecting wsi lifecycle change, len == 1 */ test_server_lock(len); break; case LWS_CALLBACK_UNLOCK_POLL: /* * unlock mutex to protect pollfd state when * called after any other POLL related callback * if protecting wsi lifecycle change, len == 1 */ test_server_unlock(len); break; #ifdef EXTERNAL_POLL case LWS_CALLBACK_ADD_POLL_FD: if (count_pollfds >= max_poll_elements) { lwsl_err("LWS_CALLBACK_ADD_POLL_FD: too many sockets to track\n"); return 1; } fd_lookup[pa->fd] = count_pollfds; pollfds[count_pollfds].fd = pa->fd; pollfds[count_pollfds].events = pa->events; pollfds[count_pollfds++].revents = 0; break; case LWS_CALLBACK_DEL_POLL_FD: if (!--count_pollfds) { break; } m = fd_lookup[pa->fd]; /* have the last guy take up the vacant slot */ pollfds[m] = pollfds[count_pollfds]; fd_lookup[pollfds[count_pollfds].fd] = m; break; case LWS_CALLBACK_CHANGE_MODE_POLL_FD: pollfds[fd_lookup[pa->fd]].events = pa->events; break; #endif case LWS_CALLBACK_GET_THREAD_ID: /* * if you will call "lws_callback_on_writable" * from a different thread, return the caller thread ID * here so lws can use this information to work out if it * should signal the poll() loop to exit and restart early */ /* return pthread_getthreadid_np(); */ break; default: break; } return 0; /* if we're on HTTP1.1 or 2.0, will keep the idle connection alive */ try_to_reuse: if (lws_http_transaction_completed(webSocketInstance)) { return -1; } return 0; }