/*
* libwebsockets - small server side websockets and web server implementation
*
- * Copyright (C) 2010-2013 Andy Green <andy@warmcat.com>
+ * Copyright (C) 2010-2015 Andy Green <andy@warmcat.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* Sec-WebSocket-Protocol: chat
*/
+#ifndef min
+#define min(a, b) ((a) < (b) ? (a) : (b))
+#endif
+
/*
* We have to take care about parsing because the headers may be split
* into multiple fragments. They may contain unknown headers with arbitrary
* argument lengths. So, we parse using a single-character at a time state
* machine that is completely independent of packet size.
+ *
+ * Returns <0 for error or length of chars consumed from buf (up to len)
*/
-int
-libwebsocket_read(struct libwebsocket_context *context,
- struct libwebsocket *wsi, unsigned char * buf, size_t len)
+LWS_VISIBLE int
+lws_read(struct lws *wsi, unsigned char *buf, lws_filepos_t len)
{
+ unsigned char *last_char, *oldbuf = buf;
+ lws_filepos_t body_chunk_len;
size_t n;
- switch (wsi->state) {
- case WSI_STATE_HTTP_ISSUING_FILE:
- case WSI_STATE_HTTP:
- wsi->state = WSI_STATE_HTTP_HEADERS;
- wsi->u.hdr.parser_state = WSI_TOKEN_NAME_PART;
- wsi->u.hdr.lextable_pos = 0;
- /* fallthru */
- case WSI_STATE_HTTP_HEADERS:
-
- lwsl_parser("issuing %d bytes to parser\n", (int)len);
-#ifdef _DEBUG
- //fwrite(buf, 1, len, stderr);
-#endif
+ lwsl_debug("%s: incoming len %d state %d\n", __func__, (int)len, wsi->state);
-#ifndef LWS_NO_CLIENT
-
-// lwsl_info("mode=%d\n", wsi->mode);
+ switch (wsi->state) {
+#ifdef LWS_USE_HTTP2
+ case LWSS_HTTP2_AWAIT_CLIENT_PREFACE:
+ case LWSS_HTTP2_ESTABLISHED_PRE_SETTINGS:
+ case LWSS_HTTP2_ESTABLISHED:
+ n = 0;
+ while (n < len) {
+ /*
+ * we were accepting input but now we stopped doing so
+ */
+ if (!(wsi->rxflow_change_to & LWS_RXFLOW_ALLOW)) {
+ lws_rxflow_cache(wsi, buf, n, len);
+
+ return 1;
+ }
- switch (wsi->mode) {
- case LWS_CONNMODE_WS_CLIENT_WAITING_PROXY_REPLY:
- case LWS_CONNMODE_WS_CLIENT_ISSUE_HANDSHAKE:
- case LWS_CONNMODE_WS_CLIENT_WAITING_SERVER_REPLY:
- case LWS_CONNMODE_WS_CLIENT_WAITING_EXTENSION_CONNECT:
- case LWS_CONNMODE_WS_CLIENT:
- for (n = 0; n < len; n++)
- libwebsocket_client_rx_sm(wsi, *buf++);
-
- return 0;
- default:
- break;
+ /* account for what we're using in rxflow buffer */
+ if (wsi->rxflow_buffer)
+ wsi->rxflow_pos++;
+ if (lws_http2_parser(wsi, buf[n++])) {
+ lwsl_debug("%s: http2_parser bailed\n", __func__);
+ goto bail;
+ }
}
+ break;
#endif
-#ifndef LWS_NO_SERVER
- /* LWS_CONNMODE_WS_SERVING */
-
- extern int handshake_0405(struct libwebsocket_context *context, struct libwebsocket *wsi);
- for (n = 0; n < len; n++)
- libwebsocket_parse(wsi, *buf++);
-
- if (wsi->u.hdr.parser_state != WSI_PARSING_COMPLETE)
- break;
+ case LWSS_HTTP_ISSUING_FILE:
+ return 0;
- lwsl_parser("seem to be serving, mode is %d\n", wsi->mode);
-
- lwsl_parser("libwebsocket_parse sees parsing complete\n");
+ case LWSS_CLIENT_HTTP_ESTABLISHED:
+ break;
- /* is this websocket protocol or normal http 1.0? */
+ case LWSS_HTTP:
+ wsi->hdr_parsing_completed = 0;
+ /* fallthru */
- if (!wsi->utf8_token[WSI_TOKEN_UPGRADE].token_len ||
- !wsi->utf8_token[WSI_TOKEN_CONNECTION].token_len) {
- wsi->state = WSI_STATE_HTTP;
- if (wsi->protocol->callback)
- if (wsi->protocol->callback(context, wsi,
- LWS_CALLBACK_HTTP, wsi->user_space,
- wsi->utf8_token[WSI_TOKEN_GET_URI].token,
- wsi->utf8_token[WSI_TOKEN_GET_URI].token_len)) {
- lwsl_info("LWS_CALLBACK_HTTP wanted to close\n");
- goto bail;
- }
- return 0;
+ case LWSS_HTTP_HEADERS:
+ if (!wsi->u.hdr.ah) {
+ lwsl_err("%s: LWSS_HTTP_HEADERS: NULL ah\n", __func__);
+ assert(0);
}
+ lwsl_parser("issuing %d bytes to parser\n", (int)len);
- if (!wsi->protocol)
- lwsl_err("NULL protocol at libwebsocket_read\n");
+ if (lws_handshake_client(wsi, &buf, (size_t)len))
+ goto bail;
+
+ last_char = buf;
+ if (lws_handshake_server(wsi, &buf, (size_t)len))
+ /* Handshake indicates this session is done. */
+ goto bail;
+ /* we might have transitioned to RAW */
+ if (wsi->mode == LWSCM_RAW)
+ /* we gave the read buffer to RAW handler already */
+ goto read_ok;
/*
- * It's websocket
- *
- * Make sure user side is happy about protocol
+ * It's possible that we've exhausted our data already, or
+ * rx flow control has stopped us dealing with this early,
+ * but lws_handshake_server doesn't update len for us.
+ * Figure out how much was read, so that we can proceed
+ * appropriately:
*/
-
- while (wsi->protocol->callback) {
-
- if (wsi->utf8_token[WSI_TOKEN_PROTOCOL].token == NULL) {
- if (wsi->protocol->name == NULL)
- break;
- } else
- if (wsi->protocol->name && strcmp(
- wsi->utf8_token[WSI_TOKEN_PROTOCOL].token,
- wsi->protocol->name) == 0)
- break;
-
- wsi->protocol++;
+ len -= (buf - last_char);
+ lwsl_debug("%s: thinks we have used %ld\n", __func__, (long)len);
+
+ if (!wsi->hdr_parsing_completed)
+ /* More header content on the way */
+ goto read_ok;
+
+ switch (wsi->state) {
+ case LWSS_HTTP:
+ case LWSS_HTTP_HEADERS:
+ goto read_ok;
+ case LWSS_HTTP_ISSUING_FILE:
+ goto read_ok;
+ case LWSS_HTTP_BODY:
+ wsi->u.http.content_remain =
+ wsi->u.http.content_length;
+ if (wsi->u.http.content_remain)
+ goto http_postbody;
+
+ /* there is no POST content */
+ goto postbody_completion;
+ default:
+ break;
}
+ break;
- /* we didn't find a protocol he wanted? */
-
- if (wsi->protocol->callback == NULL) {
- if (wsi->utf8_token[WSI_TOKEN_PROTOCOL].token == NULL) {
- lwsl_info("[no protocol] "
- "mapped to protocol 0 handler\n");
- wsi->protocol = &context->protocols[0];
+ case LWSS_HTTP_BODY:
+http_postbody:
+ while (len && wsi->u.http.content_remain) {
+ /* Copy as much as possible, up to the limit of:
+ * what we have in the read buffer (len)
+ * remaining portion of the POST body (content_remain)
+ */
+ body_chunk_len = min(wsi->u.http.content_remain,len);
+ wsi->u.http.content_remain -= body_chunk_len;
+ len -= body_chunk_len;
+#ifdef LWS_WITH_CGI
+ if (wsi->cgi) {
+ struct lws_cgi_args args;
+
+ args.ch = LWS_STDIN;
+ args.stdwsi = &wsi->cgi->stdwsi[0];
+ args.data = buf;
+ args.len = body_chunk_len;
+
+ /* returns how much used */
+ n = user_callback_handle_rxflow(
+ wsi->protocol->callback,
+ wsi, LWS_CALLBACK_CGI_STDIN_DATA,
+ wsi->user_space,
+ (void *)&args, 0);
+ if ((int)n < 0)
+ goto bail;
} else {
- lwsl_err("Requested protocol %s "
- "not supported\n",
- wsi->utf8_token[WSI_TOKEN_PROTOCOL].token);
- goto bail;
+#endif
+ n = wsi->protocol->callback(wsi,
+ LWS_CALLBACK_HTTP_BODY, wsi->user_space,
+ buf, (size_t)body_chunk_len);
+ if (n)
+ goto bail;
+ n = (size_t)body_chunk_len;
+#ifdef LWS_WITH_CGI
}
- }
-
- /*
- * find out which spec version the client is using
- * if this header is not given, we default to 00 (aka 76)
- */
-
- if (wsi->utf8_token[WSI_TOKEN_VERSION].token_len)
- wsi->ietf_spec_revision =
- atoi(wsi->utf8_token[WSI_TOKEN_VERSION].token);
-
- /*
- * Give the user code a chance to study the request and
- * have the opportunity to deny it
- */
-
- if ((wsi->protocol->callback)(wsi->protocol->owning_server, wsi,
- LWS_CALLBACK_FILTER_PROTOCOL_CONNECTION,
- &wsi->utf8_token[0], NULL, 0)) {
- lwsl_warn("User code denied connection\n");
- goto bail;
- }
-
-
- /*
- * Perform the handshake according to the protocol version the
- * client announced
- */
+#endif
+ buf += n;
- switch (wsi->ietf_spec_revision) {
- case 13:
- lwsl_parser("libwebsocket_parse calling handshake_04\n");
- if (handshake_0405(context, wsi)) {
- lwsl_info("handshake_0405 xor 05 has failed the connection\n");
- goto bail;
+ if (wsi->u.http.content_remain) {
+ lws_set_timeout(wsi, PENDING_TIMEOUT_HTTP_CONTENT,
+ wsi->context->timeout_secs);
+ break;
}
- break;
-
- default:
- lwsl_warn("Unknown client spec version %d\n",
- wsi->ietf_spec_revision);
- goto bail;
- }
-
- wsi->mode = LWS_CONNMODE_WS_SERVING;
-
- /* union transition */
- memset(&wsi->u, 0, sizeof wsi->u);
-
- lwsl_parser("accepted v%02d connection\n",
- wsi->ietf_spec_revision);
+ /* he sent all the content in time */
+postbody_completion:
+#ifdef LWS_WITH_CGI
+ /* if we're running a cgi, we can't let him off the hook just because he sent his POST data */
+ if (wsi->cgi)
+ lws_set_timeout(wsi, PENDING_TIMEOUT_CGI, wsi->context->timeout_secs);
+ else
#endif
- break;
-
- case WSI_STATE_AWAITING_CLOSE_ACK:
- case WSI_STATE_ESTABLISHED:
-#ifndef LWS_NO_CLIENT
- switch (wsi->mode) {
- case LWS_CONNMODE_WS_CLIENT:
- for (n = 0; n < len; n++)
- if (libwebsocket_client_rx_sm(wsi, *buf++) < 0) {
- lwsl_info("client rx has bailed\n");
+ lws_set_timeout(wsi, NO_PENDING_TIMEOUT, 0);
+#ifdef LWS_WITH_CGI
+ if (!wsi->cgi)
+#endif
+ {
+ n = wsi->protocol->callback(wsi,
+ LWS_CALLBACK_HTTP_BODY_COMPLETION,
+ wsi->user_space, NULL, 0);
+ if (n)
goto bail;
- }
+ }
- return 0;
- default:
break;
}
-#endif
-#ifndef LWS_NO_SERVER
- /* LWS_CONNMODE_WS_SERVING */
+ break;
- if (libwebsocket_interpret_incoming_packet(wsi, buf, len) < 0) {
- lwsl_info("interpret_incoming_packet has bailed\n");
+ case LWSS_ESTABLISHED:
+ case LWSS_AWAITING_CLOSE_ACK:
+ case LWSS_WAITING_TO_SEND_CLOSE_NOTIFICATION:
+ case LWSS_SHUTDOWN:
+ if (lws_handshake_client(wsi, &buf, (size_t)len))
goto bail;
+ switch (wsi->mode) {
+ case LWSCM_WS_SERVING:
+
+ if (lws_interpret_incoming_packet(wsi, &buf, (size_t)len) < 0) {
+ lwsl_info("interpret_incoming_packet has bailed\n");
+ goto bail;
+ }
+ break;
}
-#endif
break;
default:
- lwsl_err("libwebsocket_read: Unhandled state\n");
+ lwsl_err("%s: Unhandled state %d\n", __func__, wsi->state);
break;
}
- return 0;
+read_ok:
+ /* Nothing more to do for now */
+ lwsl_info("%s: read_ok, used %ld\n", __func__, (long)(buf - oldbuf));
+
+ return buf - oldbuf;
bail:
- lwsl_info("closing connection at libwebsocket_read bail:\n");
- libwebsocket_close_and_free_session(context, wsi,
- LWS_CLOSE_STATUS_NOSTATUS);
+ //lwsl_notice("closing connection at lws_read bail:\n");
+ lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS);
return -1;
}