websocket_bootstrap.c 35 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866
  1. /**
  2. * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
  3. * SPDX-License-Identifier: Apache-2.0.
  4. */
  5. #include <aws/cal/hash.h>
  6. #include <aws/common/encoding.h>
  7. #include <aws/common/logging.h>
  8. #include <aws/common/string.h>
  9. #include <aws/http/connection.h>
  10. #include <aws/http/private/http_impl.h>
  11. #include <aws/http/private/strutil.h>
  12. #include <aws/http/private/websocket_impl.h>
  13. #include <aws/http/request_response.h>
  14. #include <aws/http/status_code.h>
  15. #include <aws/io/uri.h>
  16. #include <inttypes.h>
  17. #ifdef _MSC_VER
  18. # pragma warning(disable : 4204) /* non-constant aggregate initializer */
  19. #endif
  20. /**
  21. * Allow unit-tests to mock interactions with external systems.
  22. */
  23. static const struct aws_websocket_client_bootstrap_system_vtable s_default_system_vtable = {
  24. .aws_http_client_connect = aws_http_client_connect,
  25. .aws_http_connection_release = aws_http_connection_release,
  26. .aws_http_connection_close = aws_http_connection_close,
  27. .aws_http_connection_get_channel = aws_http_connection_get_channel,
  28. .aws_http_connection_make_request = aws_http_connection_make_request,
  29. .aws_http_stream_activate = aws_http_stream_activate,
  30. .aws_http_stream_release = aws_http_stream_release,
  31. .aws_http_stream_get_connection = aws_http_stream_get_connection,
  32. .aws_http_stream_update_window = aws_http_stream_update_window,
  33. .aws_http_stream_get_incoming_response_status = aws_http_stream_get_incoming_response_status,
  34. .aws_websocket_handler_new = aws_websocket_handler_new,
  35. };
  36. static const struct aws_websocket_client_bootstrap_system_vtable *s_system_vtable = &s_default_system_vtable;
  37. void aws_websocket_client_bootstrap_set_system_vtable(
  38. const struct aws_websocket_client_bootstrap_system_vtable *system_vtable) {
  39. s_system_vtable = system_vtable;
  40. }
  41. /**
  42. * The websocket bootstrap brings a websocket connection into this world, and sees it out again.
  43. * Spins up an HTTP client, performs the opening handshake (HTTP Upgrade request),
  44. * creates the websocket handler, and inserts it into the channel.
  45. * The bootstrap is responsible for firing the on_connection_setup and on_connection_shutdown callbacks.
  46. */
  47. struct aws_websocket_client_bootstrap {
  48. /* Settings copied in from aws_websocket_client_connection_options */
  49. struct aws_allocator *alloc;
  50. size_t initial_window_size;
  51. bool manual_window_update;
  52. void *user_data;
  53. /* Setup callback will be set NULL once it's invoked.
  54. * This is used to determine whether setup or shutdown should be invoked
  55. * from the HTTP-shutdown callback. */
  56. aws_websocket_on_connection_setup_fn *websocket_setup_callback;
  57. aws_websocket_on_connection_shutdown_fn *websocket_shutdown_callback;
  58. aws_websocket_on_incoming_frame_begin_fn *websocket_frame_begin_callback;
  59. aws_websocket_on_incoming_frame_payload_fn *websocket_frame_payload_callback;
  60. aws_websocket_on_incoming_frame_complete_fn *websocket_frame_complete_callback;
  61. /* Handshake request data */
  62. struct aws_http_message *handshake_request;
  63. /* Given the "Sec-WebSocket-Key" from the request,
  64. * this is what we expect the response's "Sec-WebSocket-Accept" to be */
  65. struct aws_byte_buf expected_sec_websocket_accept;
  66. /* Comma-separated values from the request's "Sec-WebSocket-Protocol" (or NULL if none) */
  67. struct aws_string *expected_sec_websocket_protocols;
  68. /* Handshake response data */
  69. int response_status;
  70. struct aws_http_headers *response_headers;
  71. bool got_full_response_headers;
  72. struct aws_byte_buf response_body;
  73. bool got_full_response_body;
  74. int setup_error_code;
  75. struct aws_websocket *websocket;
  76. };
  77. static void s_ws_bootstrap_destroy(struct aws_websocket_client_bootstrap *ws_bootstrap);
  78. static int s_ws_bootstrap_calculate_sec_websocket_accept(
  79. struct aws_byte_cursor sec_websocket_key,
  80. struct aws_byte_buf *out_buf,
  81. struct aws_allocator *alloc);
  82. static void s_ws_bootstrap_cancel_setup_due_to_err(
  83. struct aws_websocket_client_bootstrap *ws_bootstrap,
  84. struct aws_http_connection *http_connection,
  85. int error_code);
  86. static void s_ws_bootstrap_on_http_setup(struct aws_http_connection *http_connection, int error_code, void *user_data);
  87. static void s_ws_bootstrap_on_http_shutdown(
  88. struct aws_http_connection *http_connection,
  89. int error_code,
  90. void *user_data);
  91. static int s_ws_bootstrap_on_handshake_response_headers(
  92. struct aws_http_stream *stream,
  93. enum aws_http_header_block header_block,
  94. const struct aws_http_header *header_array,
  95. size_t num_headers,
  96. void *user_data);
  97. static int s_ws_bootstrap_on_handshake_response_header_block_done(
  98. struct aws_http_stream *stream,
  99. enum aws_http_header_block header_block,
  100. void *user_data);
  101. static int s_ws_bootstrap_on_handshake_response_body(
  102. struct aws_http_stream *stream,
  103. const struct aws_byte_cursor *data,
  104. void *user_data);
  105. static void s_ws_bootstrap_on_stream_complete(struct aws_http_stream *stream, int error_code, void *user_data);
  106. int aws_websocket_client_connect(const struct aws_websocket_client_connection_options *options) {
  107. aws_http_fatal_assert_library_initialized();
  108. AWS_ASSERT(options);
  109. /* Validate options */
  110. struct aws_byte_cursor path;
  111. aws_http_message_get_request_path(options->handshake_request, &path);
  112. if (!options->allocator || !options->bootstrap || !options->socket_options || !options->host.len || !path.len ||
  113. !options->on_connection_setup) {
  114. AWS_LOGF_ERROR(AWS_LS_HTTP_WEBSOCKET_SETUP, "id=static: Missing required websocket connection options.");
  115. return aws_raise_error(AWS_ERROR_INVALID_ARGUMENT);
  116. }
  117. struct aws_byte_cursor method;
  118. aws_http_message_get_request_method(options->handshake_request, &method);
  119. if (aws_http_str_to_method(method) != AWS_HTTP_METHOD_GET) {
  120. AWS_LOGF_ERROR(AWS_LS_HTTP_WEBSOCKET_SETUP, "id=static: Websocket request must have method be 'GET'.");
  121. return aws_raise_error(AWS_ERROR_INVALID_ARGUMENT);
  122. }
  123. if (!options->handshake_request) {
  124. AWS_LOGF_ERROR(
  125. AWS_LS_HTTP_WEBSOCKET_SETUP,
  126. "id=static: Invalid connection options, missing required request for websocket client handshake.");
  127. return aws_raise_error(AWS_ERROR_INVALID_ARGUMENT);
  128. }
  129. const struct aws_http_headers *request_headers = aws_http_message_get_headers(options->handshake_request);
  130. struct aws_byte_cursor sec_websocket_key;
  131. if (aws_http_headers_get(request_headers, aws_byte_cursor_from_c_str("Sec-WebSocket-Key"), &sec_websocket_key)) {
  132. AWS_LOGF_ERROR(
  133. AWS_LS_HTTP_WEBSOCKET_SETUP,
  134. "id=static: Websocket handshake request is missing required 'Sec-WebSocket-Key' header");
  135. return aws_raise_error(AWS_ERROR_INVALID_ARGUMENT);
  136. }
  137. /* Extensions are not currently supported */
  138. if (aws_http_headers_has(request_headers, aws_byte_cursor_from_c_str("Sec-WebSocket-Extensions"))) {
  139. AWS_LOGF_ERROR(
  140. AWS_LS_HTTP_WEBSOCKET_SETUP, "id=static: 'Sec-WebSocket-Extensions' are not currently supported");
  141. return aws_raise_error(AWS_ERROR_INVALID_ARGUMENT);
  142. }
  143. /* Create bootstrap */
  144. struct aws_websocket_client_bootstrap *ws_bootstrap =
  145. aws_mem_calloc(options->allocator, 1, sizeof(struct aws_websocket_client_bootstrap));
  146. ws_bootstrap->alloc = options->allocator;
  147. ws_bootstrap->initial_window_size = options->initial_window_size;
  148. ws_bootstrap->manual_window_update = options->manual_window_management;
  149. ws_bootstrap->user_data = options->user_data;
  150. ws_bootstrap->websocket_setup_callback = options->on_connection_setup;
  151. ws_bootstrap->websocket_shutdown_callback = options->on_connection_shutdown;
  152. ws_bootstrap->websocket_frame_begin_callback = options->on_incoming_frame_begin;
  153. ws_bootstrap->websocket_frame_payload_callback = options->on_incoming_frame_payload;
  154. ws_bootstrap->websocket_frame_complete_callback = options->on_incoming_frame_complete;
  155. ws_bootstrap->handshake_request = aws_http_message_acquire(options->handshake_request);
  156. ws_bootstrap->response_status = AWS_HTTP_STATUS_CODE_UNKNOWN;
  157. ws_bootstrap->response_headers = aws_http_headers_new(ws_bootstrap->alloc);
  158. aws_byte_buf_init(&ws_bootstrap->response_body, ws_bootstrap->alloc, 0);
  159. if (s_ws_bootstrap_calculate_sec_websocket_accept(
  160. sec_websocket_key, &ws_bootstrap->expected_sec_websocket_accept, ws_bootstrap->alloc)) {
  161. goto error;
  162. }
  163. ws_bootstrap->expected_sec_websocket_protocols =
  164. aws_http_headers_get_all(request_headers, aws_byte_cursor_from_c_str("Sec-WebSocket-Protocol"));
  165. /* Initiate HTTP connection */
  166. struct aws_http_client_connection_options http_options = AWS_HTTP_CLIENT_CONNECTION_OPTIONS_INIT;
  167. http_options.allocator = ws_bootstrap->alloc;
  168. http_options.bootstrap = options->bootstrap;
  169. http_options.host_name = options->host;
  170. http_options.socket_options = options->socket_options;
  171. http_options.tls_options = options->tls_options;
  172. http_options.proxy_options = options->proxy_options;
  173. if (options->manual_window_management) {
  174. http_options.manual_window_management = true;
  175. /* Give HTTP handler enough window to comfortably receive the handshake response.
  176. *
  177. * If the upgrade is unsuccessful, the HTTP window will shrink as the response body is received.
  178. * In this case, we'll keep incrementing the window back to its original size so data keeps arriving.
  179. *
  180. * If the upgrade is successful, then the websocket handler is installed, and
  181. * the HTTP handler will take over its own window management. */
  182. http_options.initial_window_size = 1024;
  183. }
  184. http_options.user_data = ws_bootstrap;
  185. http_options.on_setup = s_ws_bootstrap_on_http_setup;
  186. http_options.on_shutdown = s_ws_bootstrap_on_http_shutdown;
  187. http_options.requested_event_loop = options->requested_event_loop;
  188. /* Infer port, if not explicitly specified in URI */
  189. http_options.port = options->port;
  190. if (!http_options.port) {
  191. http_options.port = options->tls_options ? 443 : 80;
  192. }
  193. if (s_system_vtable->aws_http_client_connect(&http_options)) {
  194. AWS_LOGF_ERROR(
  195. AWS_LS_HTTP_WEBSOCKET_SETUP,
  196. "id=static: Websocket failed to initiate HTTP connection, error %d (%s)",
  197. aws_last_error(),
  198. aws_error_name(aws_last_error()));
  199. goto error;
  200. }
  201. /* Success! (so far) */
  202. AWS_LOGF_TRACE(
  203. AWS_LS_HTTP_WEBSOCKET_SETUP,
  204. "id=%p: Websocket setup begun, connecting to " PRInSTR ":%" PRIu16 PRInSTR,
  205. (void *)ws_bootstrap,
  206. AWS_BYTE_CURSOR_PRI(options->host),
  207. options->port,
  208. AWS_BYTE_CURSOR_PRI(path));
  209. return AWS_OP_SUCCESS;
  210. error:
  211. s_ws_bootstrap_destroy(ws_bootstrap);
  212. return AWS_OP_ERR;
  213. }
  214. static void s_ws_bootstrap_destroy(struct aws_websocket_client_bootstrap *ws_bootstrap) {
  215. if (!ws_bootstrap) {
  216. return;
  217. }
  218. aws_http_message_release(ws_bootstrap->handshake_request);
  219. aws_http_headers_release(ws_bootstrap->response_headers);
  220. aws_byte_buf_clean_up(&ws_bootstrap->expected_sec_websocket_accept);
  221. aws_string_destroy(ws_bootstrap->expected_sec_websocket_protocols);
  222. aws_byte_buf_clean_up(&ws_bootstrap->response_body);
  223. aws_mem_release(ws_bootstrap->alloc, ws_bootstrap);
  224. }
  225. /* Given the handshake request's "Sec-WebSocket-Key" value,
  226. * calculate the expected value for the response's "Sec-WebSocket-Accept".
  227. * RFC-6455 Section 4.1:
  228. * base64-encoded SHA-1 of the concatenation of the |Sec-WebSocket-Key|
  229. * (as a string, not base64-decoded) with the string
  230. * "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" but ignoring any leading and
  231. * trailing whitespace
  232. */
  233. static int s_ws_bootstrap_calculate_sec_websocket_accept(
  234. struct aws_byte_cursor sec_websocket_key,
  235. struct aws_byte_buf *out_buf,
  236. struct aws_allocator *alloc) {
  237. AWS_ASSERT(out_buf && !out_buf->allocator && out_buf->len == 0); /* expect buf to be uninitialized */
  238. /* note: leading and trailing whitespace was already trimmed by aws_http_headers */
  239. /* optimization: skip concatenating Sec-WebSocket-Key and the magic string.
  240. * just run the SHA1 over the first string, and then the 2nd. */
  241. bool success = false;
  242. struct aws_byte_cursor magic_string = AWS_BYTE_CUR_INIT_FROM_STRING_LITERAL("258EAFA5-E914-47DA-95CA-C5AB0DC85B11");
  243. /* SHA-1 */
  244. struct aws_hash *sha1 = aws_sha1_new(alloc);
  245. if (!sha1) {
  246. AWS_LOGF_ERROR(
  247. AWS_LS_HTTP_WEBSOCKET_SETUP,
  248. "id=static: Failed to initiate SHA1, error %d (%s)",
  249. aws_last_error(),
  250. aws_error_name(aws_last_error()));
  251. goto cleanup;
  252. }
  253. if (aws_hash_update(sha1, &sec_websocket_key) || aws_hash_update(sha1, &magic_string)) {
  254. AWS_LOGF_ERROR(
  255. AWS_LS_HTTP_WEBSOCKET_SETUP,
  256. "id=static: Failed to update SHA1, error %d (%s)",
  257. aws_last_error(),
  258. aws_error_name(aws_last_error()));
  259. goto cleanup;
  260. }
  261. uint8_t sha1_storage[AWS_SHA1_LEN];
  262. struct aws_byte_buf sha1_buf = aws_byte_buf_from_empty_array(sha1_storage, sizeof(sha1_storage));
  263. if (aws_hash_finalize(sha1, &sha1_buf, 0)) {
  264. AWS_LOGF_ERROR(
  265. AWS_LS_HTTP_WEBSOCKET_SETUP,
  266. "id=static: Failed to finalize SHA1, error %d (%s)",
  267. aws_last_error(),
  268. aws_error_name(aws_last_error()));
  269. goto cleanup;
  270. }
  271. /* base64-encoded SHA-1 (clear out_buf, and write to it again) */
  272. size_t base64_encode_sha1_len;
  273. if (aws_base64_compute_encoded_len(sha1_buf.len, &base64_encode_sha1_len)) {
  274. AWS_LOGF_ERROR(
  275. AWS_LS_HTTP_WEBSOCKET_SETUP,
  276. "id=static: Failed to determine Base64-encoded length, error %d (%s)",
  277. aws_last_error(),
  278. aws_error_name(aws_last_error()));
  279. goto cleanup;
  280. }
  281. aws_byte_buf_init(out_buf, alloc, base64_encode_sha1_len);
  282. struct aws_byte_cursor sha1_cursor = aws_byte_cursor_from_buf(&sha1_buf);
  283. if (aws_base64_encode(&sha1_cursor, out_buf)) {
  284. AWS_LOGF_ERROR(
  285. AWS_LS_HTTP_WEBSOCKET_SETUP,
  286. "id=static: Failed to Base64-encode, error %d (%s)",
  287. aws_last_error(),
  288. aws_error_name(aws_last_error()));
  289. goto cleanup;
  290. }
  291. success = true;
  292. cleanup:
  293. if (sha1) {
  294. aws_hash_destroy(sha1);
  295. }
  296. return success ? AWS_OP_SUCCESS : AWS_OP_ERR;
  297. }
  298. /* Called if something goes wrong after an HTTP connection is established.
  299. * The HTTP connection is closed.
  300. * We must wait for its shutdown to complete before informing user of the failed websocket setup. */
  301. static void s_ws_bootstrap_cancel_setup_due_to_err(
  302. struct aws_websocket_client_bootstrap *ws_bootstrap,
  303. struct aws_http_connection *http_connection,
  304. int error_code) {
  305. AWS_ASSERT(error_code);
  306. AWS_ASSERT(http_connection);
  307. if (!ws_bootstrap->setup_error_code) {
  308. AWS_LOGF_ERROR(
  309. AWS_LS_HTTP_WEBSOCKET_SETUP,
  310. "id=%p: Canceling websocket setup due to error %d (%s).",
  311. (void *)ws_bootstrap,
  312. error_code,
  313. aws_error_name(error_code));
  314. ws_bootstrap->setup_error_code = error_code;
  315. s_system_vtable->aws_http_connection_close(http_connection);
  316. }
  317. }
  318. static void s_ws_bootstrap_invoke_setup_callback(struct aws_websocket_client_bootstrap *ws_bootstrap, int error_code) {
  319. /* sanity check: websocket XOR error_code is set. both cannot be set. both cannot be unset */
  320. AWS_FATAL_ASSERT((error_code != 0) ^ (ws_bootstrap->websocket != NULL));
  321. /* Report things about the response, if we received them */
  322. int *response_status_ptr = NULL;
  323. struct aws_http_header *response_header_array = NULL;
  324. size_t num_response_headers = 0;
  325. struct aws_byte_cursor *response_body_ptr = NULL;
  326. struct aws_byte_cursor response_body_cursor = {.len = 0};
  327. if (ws_bootstrap->got_full_response_headers) {
  328. response_status_ptr = &ws_bootstrap->response_status;
  329. num_response_headers = aws_http_headers_count(ws_bootstrap->response_headers);
  330. response_header_array =
  331. aws_mem_calloc(ws_bootstrap->alloc, aws_max_size(1, num_response_headers), sizeof(struct aws_http_header));
  332. for (size_t i = 0; i < num_response_headers; ++i) {
  333. aws_http_headers_get_index(ws_bootstrap->response_headers, i, &response_header_array[i]);
  334. }
  335. if (ws_bootstrap->got_full_response_body) {
  336. response_body_cursor = aws_byte_cursor_from_buf(&ws_bootstrap->response_body);
  337. response_body_ptr = &response_body_cursor;
  338. }
  339. }
  340. struct aws_websocket_on_connection_setup_data setup_data = {
  341. .error_code = error_code,
  342. .websocket = ws_bootstrap->websocket,
  343. .handshake_response_status = response_status_ptr,
  344. .handshake_response_header_array = response_header_array,
  345. .num_handshake_response_headers = num_response_headers,
  346. .handshake_response_body = response_body_ptr,
  347. };
  348. ws_bootstrap->websocket_setup_callback(&setup_data, ws_bootstrap->user_data);
  349. /* Clear setup callback so that we know that it's been invoked. */
  350. ws_bootstrap->websocket_setup_callback = NULL;
  351. if (response_header_array) {
  352. aws_mem_release(ws_bootstrap->alloc, response_header_array);
  353. }
  354. }
  355. /* Invoked when HTTP connection has been established (or failed to be established) */
  356. static void s_ws_bootstrap_on_http_setup(struct aws_http_connection *http_connection, int error_code, void *user_data) {
  357. struct aws_websocket_client_bootstrap *ws_bootstrap = user_data;
  358. /* Setup callback contract is: if error_code is non-zero then connection is NULL. */
  359. AWS_FATAL_ASSERT((error_code != 0) == (http_connection == NULL));
  360. /* If http connection failed, inform the user immediately and clean up the websocket bootstrapper. */
  361. if (error_code) {
  362. AWS_LOGF_ERROR(
  363. AWS_LS_HTTP_WEBSOCKET_SETUP,
  364. "id=%p: Websocket setup failed to establish HTTP connection, error %d (%s).",
  365. (void *)ws_bootstrap,
  366. error_code,
  367. aws_error_name(error_code));
  368. s_ws_bootstrap_invoke_setup_callback(ws_bootstrap, error_code);
  369. s_ws_bootstrap_destroy(ws_bootstrap);
  370. return;
  371. }
  372. /* Connection exists!
  373. * Note that if anything goes wrong with websocket setup from hereon out, we must close the http connection
  374. * first and wait for shutdown to complete before informing the user of setup failure. */
  375. /* Send the handshake request */
  376. struct aws_http_make_request_options options = {
  377. .self_size = sizeof(options),
  378. .request = ws_bootstrap->handshake_request,
  379. .user_data = ws_bootstrap,
  380. .on_response_headers = s_ws_bootstrap_on_handshake_response_headers,
  381. .on_response_header_block_done = s_ws_bootstrap_on_handshake_response_header_block_done,
  382. .on_response_body = s_ws_bootstrap_on_handshake_response_body,
  383. .on_complete = s_ws_bootstrap_on_stream_complete,
  384. };
  385. struct aws_http_stream *handshake_stream =
  386. s_system_vtable->aws_http_connection_make_request(http_connection, &options);
  387. if (!handshake_stream) {
  388. AWS_LOGF_ERROR(
  389. AWS_LS_HTTP_WEBSOCKET_SETUP,
  390. "id=%p: Failed to make websocket upgrade request, error %d (%s).",
  391. (void *)ws_bootstrap,
  392. aws_last_error(),
  393. aws_error_name(aws_last_error()));
  394. goto error;
  395. }
  396. if (s_system_vtable->aws_http_stream_activate(handshake_stream)) {
  397. AWS_LOGF_ERROR(
  398. AWS_LS_HTTP_WEBSOCKET_SETUP,
  399. "id=%p: Failed to activate websocket upgrade request, error %d (%s).",
  400. (void *)ws_bootstrap,
  401. aws_last_error(),
  402. aws_error_name(aws_last_error()));
  403. goto error;
  404. }
  405. /* Success! (so far) */
  406. AWS_LOGF_TRACE(
  407. AWS_LS_HTTP_WEBSOCKET_SETUP,
  408. "id=%p: HTTP connection established, sending websocket upgrade request.",
  409. (void *)ws_bootstrap);
  410. return;
  411. error:
  412. s_system_vtable->aws_http_stream_release(handshake_stream);
  413. s_ws_bootstrap_cancel_setup_due_to_err(ws_bootstrap, http_connection, aws_last_error());
  414. }
  415. /* Invoked when the HTTP connection has shut down.
  416. * This is never called if the HTTP connection failed its setup */
  417. static void s_ws_bootstrap_on_http_shutdown(
  418. struct aws_http_connection *http_connection,
  419. int error_code,
  420. void *user_data) {
  421. struct aws_websocket_client_bootstrap *ws_bootstrap = user_data;
  422. /* Inform user that connection has completely shut down.
  423. * If setup callback still hasn't fired, invoke it now and indicate failure.
  424. * Otherwise, invoke shutdown callback. */
  425. if (ws_bootstrap->websocket_setup_callback) {
  426. AWS_ASSERT(!ws_bootstrap->websocket);
  427. /* If there's already a setup_error_code, use that */
  428. if (ws_bootstrap->setup_error_code) {
  429. error_code = ws_bootstrap->setup_error_code;
  430. }
  431. /* Ensure non-zero error_code is passed */
  432. if (!error_code) {
  433. error_code = AWS_ERROR_UNKNOWN;
  434. }
  435. AWS_LOGF_ERROR(
  436. AWS_LS_HTTP_WEBSOCKET_SETUP,
  437. "id=%p: Websocket setup failed, error %d (%s).",
  438. (void *)ws_bootstrap,
  439. error_code,
  440. aws_error_name(error_code));
  441. s_ws_bootstrap_invoke_setup_callback(ws_bootstrap, error_code);
  442. } else if (ws_bootstrap->websocket_shutdown_callback) {
  443. AWS_ASSERT(ws_bootstrap->websocket);
  444. AWS_LOGF_DEBUG(
  445. AWS_LS_HTTP_WEBSOCKET,
  446. "id=%p: Websocket client connection shut down with error %d (%s).",
  447. (void *)ws_bootstrap->websocket,
  448. error_code,
  449. aws_error_name(error_code));
  450. ws_bootstrap->websocket_shutdown_callback(ws_bootstrap->websocket, error_code, ws_bootstrap->user_data);
  451. }
  452. /* Clean up HTTP connection and websocket-bootstrap.
  453. * It's still up to the user to release the websocket itself. */
  454. s_system_vtable->aws_http_connection_release(http_connection);
  455. s_ws_bootstrap_destroy(ws_bootstrap);
  456. }
  457. /* Invoked repeatedly as handshake response headers arrive */
  458. static int s_ws_bootstrap_on_handshake_response_headers(
  459. struct aws_http_stream *stream,
  460. enum aws_http_header_block header_block,
  461. const struct aws_http_header *header_array,
  462. size_t num_headers,
  463. void *user_data) {
  464. (void)stream;
  465. (void)header_block;
  466. struct aws_websocket_client_bootstrap *ws_bootstrap = user_data;
  467. /* Deep-copy headers into ws_bootstrap */
  468. aws_http_headers_add_array(ws_bootstrap->response_headers, header_array, num_headers);
  469. /* Don't report a partially-received response */
  470. ws_bootstrap->got_full_response_headers = false;
  471. return AWS_OP_SUCCESS;
  472. }
  473. static int s_ws_bootstrap_validate_header(
  474. struct aws_websocket_client_bootstrap *ws_bootstrap,
  475. const char *name,
  476. struct aws_byte_cursor expected_value,
  477. bool case_sensitive) {
  478. struct aws_byte_cursor actual_value;
  479. if (aws_http_headers_get(ws_bootstrap->response_headers, aws_byte_cursor_from_c_str(name), &actual_value)) {
  480. AWS_LOGF_ERROR(
  481. AWS_LS_HTTP_WEBSOCKET_SETUP, "id=%p: Response lacks required '%s' header", (void *)ws_bootstrap, name);
  482. return aws_raise_error(AWS_ERROR_HTTP_WEBSOCKET_UPGRADE_FAILURE);
  483. }
  484. bool matches = case_sensitive ? aws_byte_cursor_eq(&expected_value, &actual_value)
  485. : aws_byte_cursor_eq_ignore_case(&expected_value, &actual_value);
  486. if (!matches) {
  487. AWS_LOGF_ERROR(
  488. AWS_LS_HTTP_WEBSOCKET_SETUP,
  489. "id=%p: Response '%s' header has wrong value. Expected '" PRInSTR "'. Received '" PRInSTR "'",
  490. (void *)ws_bootstrap,
  491. name,
  492. AWS_BYTE_CURSOR_PRI(expected_value),
  493. AWS_BYTE_CURSOR_PRI(actual_value));
  494. return aws_raise_error(AWS_ERROR_HTTP_WEBSOCKET_UPGRADE_FAILURE);
  495. }
  496. return AWS_OP_SUCCESS;
  497. }
  498. static int s_ws_bootstrap_validate_sec_websocket_protocol(const struct aws_websocket_client_bootstrap *ws_bootstrap) {
  499. /* First handle the easy case:
  500. * If client requested no protocols, then the response should not pick any */
  501. if (ws_bootstrap->expected_sec_websocket_protocols == NULL) {
  502. if (aws_http_headers_has(
  503. ws_bootstrap->response_headers, aws_byte_cursor_from_c_str("Sec-WebSocket-Protocol"))) {
  504. AWS_LOGF_ERROR(
  505. AWS_LS_HTTP_WEBSOCKET_SETUP,
  506. "id=%p: Response has 'Sec-WebSocket-Protocol' header, no protocol was requested",
  507. (void *)ws_bootstrap);
  508. return aws_raise_error(AWS_ERROR_HTTP_WEBSOCKET_UPGRADE_FAILURE);
  509. } else {
  510. return AWS_OP_SUCCESS;
  511. }
  512. }
  513. /* Check that server has picked one of the protocols listed in the request */
  514. struct aws_byte_cursor response_protocol;
  515. if (aws_http_headers_get(
  516. ws_bootstrap->response_headers, aws_byte_cursor_from_c_str("Sec-WebSocket-Protocol"), &response_protocol)) {
  517. AWS_LOGF_ERROR(
  518. AWS_LS_HTTP_WEBSOCKET_SETUP,
  519. "id=%p: Response lacks required 'Sec-WebSocket-Protocol' header",
  520. (void *)ws_bootstrap);
  521. return aws_raise_error(AWS_ERROR_HTTP_WEBSOCKET_UPGRADE_FAILURE);
  522. }
  523. struct aws_byte_cursor request_protocols =
  524. aws_byte_cursor_from_string(ws_bootstrap->expected_sec_websocket_protocols);
  525. struct aws_byte_cursor request_protocol_i;
  526. AWS_ZERO_STRUCT(request_protocol_i);
  527. while (aws_byte_cursor_next_split(&request_protocols, ',', &request_protocol_i)) {
  528. struct aws_byte_cursor request_protocol = aws_strutil_trim_http_whitespace(request_protocol_i);
  529. if (aws_byte_cursor_eq(&response_protocol, &request_protocol)) {
  530. /* Success! */
  531. AWS_LOGF_DEBUG(
  532. AWS_LS_HTTP_WEBSOCKET_SETUP,
  533. "id=%p: Server selected Sec-WebSocket-Protocol: " PRInSTR,
  534. (void *)ws_bootstrap,
  535. AWS_BYTE_CURSOR_PRI(response_protocol));
  536. return AWS_OP_SUCCESS;
  537. }
  538. }
  539. AWS_LOGF_ERROR(
  540. AWS_LS_HTTP_WEBSOCKET_SETUP,
  541. "id=%p: Response 'Sec-WebSocket-Protocol' header has wrong value. Received '" PRInSTR
  542. "'. Expected one of '" PRInSTR "'",
  543. (void *)ws_bootstrap,
  544. AWS_BYTE_CURSOR_PRI(response_protocol),
  545. AWS_BYTE_CURSOR_PRI(request_protocols));
  546. return aws_raise_error(AWS_ERROR_HTTP_WEBSOCKET_UPGRADE_FAILURE);
  547. }
  548. /* OK, we've got all the headers for the 101 Switching Protocols response.
  549. * Validate the handshake response, install the websocket handler into the channel,
  550. * and invoke the on_connection_setup callback. */
  551. static int s_ws_bootstrap_validate_response_and_install_websocket_handler(
  552. struct aws_websocket_client_bootstrap *ws_bootstrap,
  553. struct aws_http_connection *http_connection) {
  554. /* RFC-6455 Section 4.1 - The client MUST validate the server's response as follows... */
  555. /* (we already checked step 1, that status code is 101) */
  556. AWS_FATAL_ASSERT(ws_bootstrap->response_status == AWS_HTTP_STATUS_CODE_101_SWITCHING_PROTOCOLS);
  557. /* 2. If the response lacks an |Upgrade| header field or the |Upgrade|
  558. * header field contains a value that is not an ASCII case-
  559. * insensitive match for the value "websocket", the client MUST
  560. * _Fail the WebSocket Connection_. */
  561. if (s_ws_bootstrap_validate_header(
  562. ws_bootstrap, "Upgrade", aws_byte_cursor_from_c_str("websocket"), false /*case_sensitive*/)) {
  563. goto error;
  564. }
  565. /* 3. If the response lacks a |Connection| header field or the
  566. * |Connection| header field doesn't contain a token that is an
  567. * ASCII case-insensitive match for the value "Upgrade", the client
  568. * MUST _Fail the WebSocket Connection_. */
  569. if (s_ws_bootstrap_validate_header(
  570. ws_bootstrap, "Connection", aws_byte_cursor_from_c_str("Upgrade"), false /*case_sensitive*/)) {
  571. goto error;
  572. }
  573. /* 4. If the response lacks a |Sec-WebSocket-Accept| header field or
  574. * the |Sec-WebSocket-Accept| contains a value other than the
  575. * base64-encoded SHA-1 of the concatenation of the |Sec-WebSocket-
  576. * Key| (as a string, not base64-decoded) with the string "258EAFA5-
  577. * E914-47DA-95CA-C5AB0DC85B11" but ignoring any leading and
  578. * trailing whitespace, the client MUST _Fail the WebSocket
  579. * Connection_. */
  580. if (s_ws_bootstrap_validate_header(
  581. ws_bootstrap,
  582. "Sec-WebSocket-Accept",
  583. aws_byte_cursor_from_buf(&ws_bootstrap->expected_sec_websocket_accept),
  584. true /*case_sensitive*/)) {
  585. goto error;
  586. }
  587. /* (step 5 is about validating Sec-WebSocket-Extensions, but we don't support extensions) */
  588. if (aws_http_headers_has(ws_bootstrap->response_headers, aws_byte_cursor_from_c_str("Sec-WebSocket-Extensions"))) {
  589. AWS_LOGF_ERROR(
  590. AWS_LS_HTTP_WEBSOCKET_SETUP,
  591. "id=%p: Response has 'Sec-WebSocket-Extensions' header, but client does not support extensions.",
  592. (void *)ws_bootstrap);
  593. aws_raise_error(AWS_ERROR_HTTP_WEBSOCKET_UPGRADE_FAILURE);
  594. goto error;
  595. }
  596. /* 6. If the response includes a |Sec-WebSocket-Protocol| header field
  597. * and this header field indicates the use of a subprotocol that was
  598. * not present in the client's handshake (the server has indicated a
  599. * subprotocol not requested by the client), the client MUST _Fail
  600. * the WebSocket Connection_. */
  601. if (s_ws_bootstrap_validate_sec_websocket_protocol(ws_bootstrap)) {
  602. goto error;
  603. }
  604. /* Insert websocket handler into channel */
  605. struct aws_channel *channel = s_system_vtable->aws_http_connection_get_channel(http_connection);
  606. AWS_ASSERT(channel);
  607. struct aws_websocket_handler_options ws_options = {
  608. .allocator = ws_bootstrap->alloc,
  609. .channel = channel,
  610. .initial_window_size = ws_bootstrap->initial_window_size,
  611. .user_data = ws_bootstrap->user_data,
  612. .on_incoming_frame_begin = ws_bootstrap->websocket_frame_begin_callback,
  613. .on_incoming_frame_payload = ws_bootstrap->websocket_frame_payload_callback,
  614. .on_incoming_frame_complete = ws_bootstrap->websocket_frame_complete_callback,
  615. .is_server = false,
  616. .manual_window_update = ws_bootstrap->manual_window_update,
  617. };
  618. ws_bootstrap->websocket = s_system_vtable->aws_websocket_handler_new(&ws_options);
  619. if (!ws_bootstrap->websocket) {
  620. AWS_LOGF_ERROR(
  621. AWS_LS_HTTP_WEBSOCKET_SETUP,
  622. "id=%p: Failed to create websocket handler, error %d (%s)",
  623. (void *)ws_bootstrap,
  624. aws_last_error(),
  625. aws_error_name(aws_last_error()));
  626. goto error;
  627. }
  628. /* Success! Setup complete! */
  629. AWS_LOGF_TRACE(/* Log for tracing setup id to websocket id. */
  630. AWS_LS_HTTP_WEBSOCKET_SETUP,
  631. "id=%p: Setup success, created websocket=%p",
  632. (void *)ws_bootstrap,
  633. (void *)ws_bootstrap->websocket);
  634. AWS_LOGF_DEBUG(/* Debug log about creation of websocket. */
  635. AWS_LS_HTTP_WEBSOCKET,
  636. "id=%p: Websocket client connection established.",
  637. (void *)ws_bootstrap->websocket);
  638. s_ws_bootstrap_invoke_setup_callback(ws_bootstrap, 0 /*error_code*/);
  639. return AWS_OP_SUCCESS;
  640. error:
  641. s_ws_bootstrap_cancel_setup_due_to_err(ws_bootstrap, http_connection, aws_last_error());
  642. /* Returning error stops HTTP from processing any further data */
  643. return AWS_OP_ERR;
  644. }
  645. /**
  646. * Invoked each time we reach the end of a block of response headers.
  647. * If we got a valid 101 Switching Protocols response, we insert the websocket handler.
  648. * Note:
  649. * In HTTP, 1xx responses are "interim" responses. So a 101 Switching Protocols
  650. * response does not "complete" the stream. Once the connection has switched
  651. * protocols, the stream does not end until the whole connection is closed.
  652. */
  653. static int s_ws_bootstrap_on_handshake_response_header_block_done(
  654. struct aws_http_stream *stream,
  655. enum aws_http_header_block header_block,
  656. void *user_data) {
  657. struct aws_websocket_client_bootstrap *ws_bootstrap = user_data;
  658. struct aws_http_connection *http_connection = s_system_vtable->aws_http_stream_get_connection(stream);
  659. AWS_ASSERT(http_connection);
  660. /* Get status code from stream */
  661. s_system_vtable->aws_http_stream_get_incoming_response_status(stream, &ws_bootstrap->response_status);
  662. ws_bootstrap->got_full_response_headers = true;
  663. if (header_block == AWS_HTTP_HEADER_BLOCK_INFORMATIONAL) {
  664. if (ws_bootstrap->response_status == AWS_HTTP_STATUS_CODE_101_SWITCHING_PROTOCOLS) {
  665. /* OK, got 101 response, proceed with upgrade! */
  666. return s_ws_bootstrap_validate_response_and_install_websocket_handler(ws_bootstrap, http_connection);
  667. } else {
  668. /* It would be weird to get any other kind of 1xx response, but anything is possible.
  669. * Another response should come eventually. Just ignore the headers from this one... */
  670. AWS_LOGF_DEBUG(
  671. AWS_LS_HTTP_WEBSOCKET_SETUP,
  672. "id=%p: Server sent interim response with status code %d",
  673. (void *)ws_bootstrap,
  674. ws_bootstrap->response_status);
  675. aws_http_headers_clear(ws_bootstrap->response_headers);
  676. ws_bootstrap->got_full_response_headers = false;
  677. return AWS_OP_SUCCESS;
  678. }
  679. }
  680. /* Otherwise, we got normal headers (from a non-1xx response), or trailing headers.
  681. * This can only happen if the handshake did not succeed. Keep the connection going.
  682. * We'll report failed setup to the user after we've received the complete response */
  683. ws_bootstrap->setup_error_code = AWS_ERROR_HTTP_WEBSOCKET_UPGRADE_FAILURE;
  684. return AWS_OP_SUCCESS;
  685. }
  686. /**
  687. * Invoked as we receive the body of a failed response.
  688. * This is never invoked if the handshake succeeds.
  689. */
  690. static int s_ws_bootstrap_on_handshake_response_body(
  691. struct aws_http_stream *stream,
  692. const struct aws_byte_cursor *data,
  693. void *user_data) {
  694. struct aws_websocket_client_bootstrap *ws_bootstrap = user_data;
  695. aws_byte_buf_append_dynamic(&ws_bootstrap->response_body, data);
  696. /* If we're managing the read window...
  697. * bump the HTTP window back to its starting size, so that we keep receiving the whole response. */
  698. if (ws_bootstrap->manual_window_update) {
  699. s_system_vtable->aws_http_stream_update_window(stream, data->len);
  700. }
  701. return AWS_OP_SUCCESS;
  702. }
  703. /**
  704. * Invoked when the stream completes.
  705. *
  706. * If the handshake succeeded and the websocket was installed,
  707. * then this is invoked at the end of the websocket connection.
  708. *
  709. * If the handshake response was not 101, then this is invoked
  710. * after we've received the whole response.
  711. *
  712. * Or this is invoked because the connection failed unexpectedly before the handshake could complete,
  713. * (or we killed the connection because the 101 response didn't pass validation).
  714. */
  715. static void s_ws_bootstrap_on_stream_complete(struct aws_http_stream *stream, int error_code, void *user_data) {
  716. struct aws_websocket_client_bootstrap *ws_bootstrap = user_data;
  717. struct aws_http_connection *http_connection = s_system_vtable->aws_http_stream_get_connection(stream);
  718. /* Only report the body if we received a complete response */
  719. if (error_code == 0) {
  720. ws_bootstrap->got_full_response_body = true;
  721. }
  722. /* Make sure the connection closes.
  723. * We'll deal with finishing setup or shutdown from the http-shutdown callback */
  724. s_system_vtable->aws_http_connection_close(http_connection);
  725. /* Done with stream, let it be cleaned up */
  726. s_system_vtable->aws_http_stream_release(stream);
  727. }