diff options
-rw-r--r-- | ChangeLog | 4 | ||||
-rw-r--r-- | src/microhttpd/Makefile.am | 8 | ||||
-rw-r--r-- | src/microhttpd/connection.c | 18 | ||||
-rw-r--r-- | src/microhttpd/daemon.c | 4 | ||||
-rw-r--r-- | src/microhttpd/response.c | 20 | ||||
-rw-r--r-- | src/microhttpd/test_upgrade.c | 325 |
6 files changed, 376 insertions, 3 deletions
@@ -1,3 +1,7 @@ | |||
1 | Sat Aug 27 21:01:43 CEST 2016 | ||
2 | Adding a few extra safety checks around HTTP "Upgrade" | ||
3 | (against wrong uses of API), and a testcase. -CG | ||
4 | |||
1 | Sat Aug 27 20:07:53 CEST 2016 | 5 | Sat Aug 27 20:07:53 CEST 2016 |
2 | Adding completely *untested* logic for HTTP "Upgrade" | 6 | Adding completely *untested* logic for HTTP "Upgrade" |
3 | handling. -CG | 7 | handling. -CG |
diff --git a/src/microhttpd/Makefile.am b/src/microhttpd/Makefile.am index 34a89984..6973af8a 100644 --- a/src/microhttpd/Makefile.am +++ b/src/microhttpd/Makefile.am | |||
@@ -143,7 +143,8 @@ check_PROGRAMS = \ | |||
143 | test_str_to_value \ | 143 | test_str_to_value \ |
144 | test_shutdown_select \ | 144 | test_shutdown_select \ |
145 | test_shutdown_poll \ | 145 | test_shutdown_poll \ |
146 | test_daemon | 146 | test_daemon \ |
147 | test_upgrade | ||
147 | 148 | ||
148 | if HAVE_POSTPROCESSOR | 149 | if HAVE_POSTPROCESSOR |
149 | check_PROGRAMS += \ | 150 | check_PROGRAMS += \ |
@@ -165,6 +166,11 @@ test_daemon_SOURCES = \ | |||
165 | test_daemon_LDADD = \ | 166 | test_daemon_LDADD = \ |
166 | $(top_builddir)/src/microhttpd/libmicrohttpd.la | 167 | $(top_builddir)/src/microhttpd/libmicrohttpd.la |
167 | 168 | ||
169 | test_upgrade_SOURCES = \ | ||
170 | test_upgrade.c | ||
171 | test_upgrade_LDADD = \ | ||
172 | $(top_builddir)/src/microhttpd/libmicrohttpd.la | ||
173 | |||
168 | test_postprocessor_SOURCES = \ | 174 | test_postprocessor_SOURCES = \ |
169 | test_postprocessor.c | 175 | test_postprocessor.c |
170 | test_postprocessor_CPPFLAGS = \ | 176 | test_postprocessor_CPPFLAGS = \ |
diff --git a/src/microhttpd/connection.c b/src/microhttpd/connection.c index b1b34cef..739f3c0f 100644 --- a/src/microhttpd/connection.c +++ b/src/microhttpd/connection.c | |||
@@ -3164,6 +3164,24 @@ MHD_queue_response (struct MHD_Connection *connection, | |||
3164 | ( (MHD_CONNECTION_HEADERS_PROCESSED != connection->state) && | 3164 | ( (MHD_CONNECTION_HEADERS_PROCESSED != connection->state) && |
3165 | (MHD_CONNECTION_FOOTERS_RECEIVED != connection->state) ) ) | 3165 | (MHD_CONNECTION_FOOTERS_RECEIVED != connection->state) ) ) |
3166 | return MHD_NO; | 3166 | return MHD_NO; |
3167 | if ( (MHD_HTTP_SWITCHING_PROTOCOLS != status_code) && | ||
3168 | (NULL != response->upgrade_handler) ) | ||
3169 | { | ||
3170 | #ifdef HAVE_MESSAGES | ||
3171 | MHD_DLOG (connection->daemon, | ||
3172 | "Application used invalid status code for 'upgrade' response!\n"); | ||
3173 | #endif | ||
3174 | return MHD_NO; | ||
3175 | } | ||
3176 | if ( (NULL != response->upgrade_handler) && | ||
3177 | (0 == (connection->daemon->options & MHD_USE_SUSPEND_RESUME)) ) | ||
3178 | { | ||
3179 | #ifdef HAVE_MESSAGES | ||
3180 | MHD_DLOG (connection->daemon, | ||
3181 | "Application attempted 'upgrade' without setting MHD_USE_SUSPEND_RESUME!\n"); | ||
3182 | #endif | ||
3183 | return MHD_NO; | ||
3184 | } | ||
3167 | MHD_increment_response_rc (response); | 3185 | MHD_increment_response_rc (response); |
3168 | connection->response = response; | 3186 | connection->response = response; |
3169 | connection->responseCode = status_code; | 3187 | connection->responseCode = status_code; |
diff --git a/src/microhttpd/daemon.c b/src/microhttpd/daemon.c index 19cd9a7c..3afeda69 100644 --- a/src/microhttpd/daemon.c +++ b/src/microhttpd/daemon.c | |||
@@ -1656,7 +1656,7 @@ MHD_resume_connection (struct MHD_Connection *connection) | |||
1656 | if (MHD_USE_SUSPEND_RESUME != (daemon->options & MHD_USE_SUSPEND_RESUME)) | 1656 | if (MHD_USE_SUSPEND_RESUME != (daemon->options & MHD_USE_SUSPEND_RESUME)) |
1657 | MHD_PANIC ("Cannot resume connections without enabling MHD_USE_SUSPEND_RESUME!\n"); | 1657 | MHD_PANIC ("Cannot resume connections without enabling MHD_USE_SUSPEND_RESUME!\n"); |
1658 | if ( (0 != (daemon->options & MHD_USE_THREAD_PER_CONNECTION)) && | 1658 | if ( (0 != (daemon->options & MHD_USE_THREAD_PER_CONNECTION)) && |
1659 | (!MHD_mutex_lock_ (&daemon->cleanup_connection_mutex)) ) | 1659 | (! MHD_mutex_lock_ (&daemon->cleanup_connection_mutex)) ) |
1660 | MHD_PANIC ("Failed to acquire cleanup mutex\n"); | 1660 | MHD_PANIC ("Failed to acquire cleanup mutex\n"); |
1661 | connection->resuming = MHD_YES; | 1661 | connection->resuming = MHD_YES; |
1662 | daemon->resuming = MHD_YES; | 1662 | daemon->resuming = MHD_YES; |
@@ -1669,7 +1669,7 @@ MHD_resume_connection (struct MHD_Connection *connection) | |||
1669 | #endif | 1669 | #endif |
1670 | } | 1670 | } |
1671 | if ( (0 != (daemon->options & MHD_USE_THREAD_PER_CONNECTION)) && | 1671 | if ( (0 != (daemon->options & MHD_USE_THREAD_PER_CONNECTION)) && |
1672 | (!MHD_mutex_unlock_ (&daemon->cleanup_connection_mutex)) ) | 1672 | (! MHD_mutex_unlock_ (&daemon->cleanup_connection_mutex)) ) |
1673 | MHD_PANIC ("Failed to release cleanup mutex\n"); | 1673 | MHD_PANIC ("Failed to release cleanup mutex\n"); |
1674 | } | 1674 | } |
1675 | 1675 | ||
diff --git a/src/microhttpd/response.c b/src/microhttpd/response.c index 66a1650d..fa45f5f5 100644 --- a/src/microhttpd/response.c +++ b/src/microhttpd/response.c | |||
@@ -669,6 +669,17 @@ MHD_response_execute_upgrade_ (struct MHD_Response *response, | |||
669 | int sv[2]; | 669 | int sv[2]; |
670 | size_t rbo; | 670 | size_t rbo; |
671 | 671 | ||
672 | if (NULL == | ||
673 | MHD_get_response_header (response, | ||
674 | MHD_HTTP_HEADER_UPGRADE)) | ||
675 | { | ||
676 | #ifdef HAVE_MESSAGES | ||
677 | MHD_DLOG (connection->daemon, | ||
678 | "Invalid response for upgrade: application failed to set the 'Upgrade' header!\n"); | ||
679 | #endif | ||
680 | return MHD_NO; | ||
681 | } | ||
682 | |||
672 | urh = malloc (sizeof (struct MHD_UpgradeResponseHandle)); | 683 | urh = malloc (sizeof (struct MHD_UpgradeResponseHandle)); |
673 | if (NULL == urh) | 684 | if (NULL == urh) |
674 | return MHD_NO; | 685 | return MHD_NO; |
@@ -704,6 +715,7 @@ MHD_response_execute_upgrade_ (struct MHD_Response *response, | |||
704 | return MHD_YES; | 715 | return MHD_YES; |
705 | } | 716 | } |
706 | #endif | 717 | #endif |
718 | urh->connection = connection; | ||
707 | urh->app_socket = MHD_INVALID_SOCKET; | 719 | urh->app_socket = MHD_INVALID_SOCKET; |
708 | urh->mhd_socket = MHD_INVALID_SOCKET; | 720 | urh->mhd_socket = MHD_INVALID_SOCKET; |
709 | rbo = connection->read_buffer_offset; | 721 | rbo = connection->read_buffer_offset; |
@@ -772,6 +784,14 @@ MHD_create_response_for_upgrade (MHD_UpgradeHandler upgrade_handler, | |||
772 | response->upgrade_handler_cls = upgrade_handler_cls; | 784 | response->upgrade_handler_cls = upgrade_handler_cls; |
773 | response->total_size = MHD_SIZE_UNKNOWN; | 785 | response->total_size = MHD_SIZE_UNKNOWN; |
774 | response->reference_count = 1; | 786 | response->reference_count = 1; |
787 | if (MHD_NO == | ||
788 | MHD_add_response_header (response, | ||
789 | MHD_HTTP_HEADER_CONNECTION, | ||
790 | "Upgrade")) | ||
791 | { | ||
792 | MHD_destroy_response (response); | ||
793 | return NULL; | ||
794 | } | ||
775 | return response; | 795 | return response; |
776 | } | 796 | } |
777 | 797 | ||
diff --git a/src/microhttpd/test_upgrade.c b/src/microhttpd/test_upgrade.c new file mode 100644 index 00000000..5251c6bc --- /dev/null +++ b/src/microhttpd/test_upgrade.c | |||
@@ -0,0 +1,325 @@ | |||
1 | /* | ||
2 | This file is part of libmicrohttpd | ||
3 | Copyright (C) 2016 Christian Grothoff | ||
4 | |||
5 | libmicrohttpd is free software; you can redistribute it and/or modify | ||
6 | it under the terms of the GNU General Public License as published | ||
7 | by the Free Software Foundation; either version 2, or (at your | ||
8 | option) any later version. | ||
9 | |||
10 | libmicrohttpd is distributed in the hope that it will be useful, but | ||
11 | WITHOUT ANY WARRANTY; without even the implied warranty of | ||
12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | ||
13 | General Public License for more details. | ||
14 | |||
15 | You should have received a copy of the GNU General Public License | ||
16 | along with libmicrohttpd; see the file COPYING. If not, write to the | ||
17 | Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, | ||
18 | Boston, MA 02110-1301, USA. | ||
19 | */ | ||
20 | |||
21 | /** | ||
22 | * @file test_upgrade.c | ||
23 | * @brief Testcase for libmicrohttpd upgrading a connection | ||
24 | * @author Christian Grothoff | ||
25 | */ | ||
26 | |||
27 | #include "platform.h" | ||
28 | #include "microhttpd.h" | ||
29 | #include <stdlib.h> | ||
30 | #include <string.h> | ||
31 | #include <stdio.h> | ||
32 | |||
33 | #ifndef WINDOWS | ||
34 | #include <unistd.h> | ||
35 | #endif | ||
36 | |||
37 | #include <sys/socket.h> | ||
38 | #include <netinet/in.h> | ||
39 | #include <netinet/ip.h> | ||
40 | #include "mhd_sockets.h" | ||
41 | |||
42 | |||
43 | static void | ||
44 | send_all (MHD_socket sock, | ||
45 | const char *text) | ||
46 | { | ||
47 | size_t len = strlen (text); | ||
48 | ssize_t ret; | ||
49 | |||
50 | for (size_t off = 0; off < len; off += ret) | ||
51 | { | ||
52 | ret = write (sock, | ||
53 | &text[off], | ||
54 | len - off); | ||
55 | if (-1 == ret) | ||
56 | { | ||
57 | if (EAGAIN == errno) | ||
58 | { | ||
59 | ret = 0; | ||
60 | continue; | ||
61 | } | ||
62 | abort (); | ||
63 | } | ||
64 | } | ||
65 | } | ||
66 | |||
67 | |||
68 | /** | ||
69 | * Read character-by-character until we | ||
70 | * get '\r\n\r\n'. | ||
71 | */ | ||
72 | static void | ||
73 | recv_hdr (MHD_socket sock) | ||
74 | { | ||
75 | unsigned int i; | ||
76 | char next; | ||
77 | char c; | ||
78 | ssize_t ret; | ||
79 | |||
80 | next = '\r'; | ||
81 | i = 0; | ||
82 | while (i < 4) | ||
83 | { | ||
84 | ret = read (sock, | ||
85 | &c, | ||
86 | 1); | ||
87 | if (-1 == ret) | ||
88 | { | ||
89 | if (EAGAIN == errno) | ||
90 | { | ||
91 | ret = 0; | ||
92 | continue; | ||
93 | } | ||
94 | abort (); | ||
95 | } | ||
96 | if (0 == ret) | ||
97 | continue; | ||
98 | if (c == next) | ||
99 | { | ||
100 | i++; | ||
101 | if (next == '\r') | ||
102 | next = '\n'; | ||
103 | else | ||
104 | next = '\r'; | ||
105 | continue; | ||
106 | } | ||
107 | if (c == '\r') | ||
108 | { | ||
109 | i = 1; | ||
110 | next = '\n'; | ||
111 | continue; | ||
112 | } | ||
113 | i = 0; | ||
114 | next = '\r'; | ||
115 | } | ||
116 | } | ||
117 | |||
118 | |||
119 | static void | ||
120 | recv_all (MHD_socket sock, | ||
121 | const char *text) | ||
122 | { | ||
123 | size_t len = strlen (text); | ||
124 | char buf[len]; | ||
125 | ssize_t ret; | ||
126 | |||
127 | for (size_t off = 0; off < len; off += ret) | ||
128 | { | ||
129 | ret = read (sock, | ||
130 | &buf[off], | ||
131 | len - off); | ||
132 | if (-1 == ret) | ||
133 | { | ||
134 | if (EAGAIN == errno) | ||
135 | { | ||
136 | ret = 0; | ||
137 | continue; | ||
138 | } | ||
139 | abort (); | ||
140 | } | ||
141 | } | ||
142 | if (0 != strncmp (text, buf, len)) | ||
143 | abort(); | ||
144 | } | ||
145 | |||
146 | |||
147 | /** | ||
148 | * Function called after a protocol "upgrade" response was sent | ||
149 | * successfully and the socket should now be controlled by some | ||
150 | * protocol other than HTTP. | ||
151 | * | ||
152 | * Any data received on the socket will be made available in | ||
153 | * 'data_in'. The function should update 'data_in_size' to | ||
154 | * reflect the number of bytes consumed from 'data_in' (the remaining | ||
155 | * bytes will be made available in the next call to the handler). | ||
156 | * | ||
157 | * Any data that should be transmitted on the socket should be | ||
158 | * stored in 'data_out'. '*data_out_size' is initially set to | ||
159 | * the available buffer space in 'data_out'. It should be set to | ||
160 | * the number of bytes stored in 'data_out' (which can be zero). | ||
161 | * | ||
162 | * The return value is a BITMASK that indicates how the function | ||
163 | * intends to interact with the event loop. It can request to be | ||
164 | * notified for reading, writing, request to UNCORK the send buffer | ||
165 | * (which MHD is allowed to ignore, if it is not possible to uncork on | ||
166 | * the local platform), to wait for the 'external' select loop to | ||
167 | * trigger another round. It is also possible to specify "no events" | ||
168 | * to terminate the connection; in this case, the | ||
169 | * #MHD_RequestCompletedCallback will be called and all resources of | ||
170 | * the connection will be released. | ||
171 | * | ||
172 | * Except when in 'thread-per-connection' mode, implementations | ||
173 | * of this function should never block (as it will still be called | ||
174 | * from within the main event loop). | ||
175 | * | ||
176 | * @param cls closure, whatever was given to #MHD_create_response_for_upgrade(). | ||
177 | * @param connection original HTTP connection handle, | ||
178 | * giving the function a last chance | ||
179 | * to inspect the original HTTP request | ||
180 | * @param extra_in if we happened to have read bytes after the | ||
181 | * HTTP header already (because the client sent | ||
182 | * more than the HTTP header of the request before | ||
183 | * we sent the upgrade response), | ||
184 | * these are the extra bytes already read from @a sock | ||
185 | * by MHD. The application should treat these as if | ||
186 | * it had read them from @a sock. | ||
187 | * @param extra_in_size number of bytes in @a extra_in | ||
188 | * @param sock socket to use for bi-directional communication | ||
189 | * with the client. For HTTPS, this may not be a socket | ||
190 | * that is directly connected to the client and thus certain | ||
191 | * operations (TCP-specific setsockopt(), getsockopt(), etc.) | ||
192 | * may not work as expected (as the socket could be from a | ||
193 | * socketpair() or a TCP-loopback) | ||
194 | * @param urh argument for #MHD_upgrade_action()s on this @a connection. | ||
195 | * Applications must eventually use this callback to perform the | ||
196 | * close() action on the @a sock. | ||
197 | */ | ||
198 | static void | ||
199 | upgrade_cb (void *cls, | ||
200 | struct MHD_Connection *connection, | ||
201 | const char *extra_in, | ||
202 | size_t extra_in_size, | ||
203 | MHD_socket sock, | ||
204 | struct MHD_UpgradeResponseHandle *urh) | ||
205 | { | ||
206 | send_all (sock, "Hello"); | ||
207 | recv_all (sock, "World"); | ||
208 | send_all (sock, "Finished"); | ||
209 | MHD_upgrade_action (urh, | ||
210 | MHD_UPGRADE_ACTION_CLOSE); | ||
211 | } | ||
212 | |||
213 | |||
214 | /** | ||
215 | * A client has requested the given url using the given method | ||
216 | * (#MHD_HTTP_METHOD_GET, #MHD_HTTP_METHOD_PUT, | ||
217 | * #MHD_HTTP_METHOD_DELETE, #MHD_HTTP_METHOD_POST, etc). The callback | ||
218 | * must call MHD callbacks to provide content to give back to the | ||
219 | * client and return an HTTP status code (i.e. #MHD_HTTP_OK, | ||
220 | * #MHD_HTTP_NOT_FOUND, etc.). | ||
221 | * | ||
222 | * @param cls argument given together with the function | ||
223 | * pointer when the handler was registered with MHD | ||
224 | * @param url the requested url | ||
225 | * @param method the HTTP method used (#MHD_HTTP_METHOD_GET, | ||
226 | * #MHD_HTTP_METHOD_PUT, etc.) | ||
227 | * @param version the HTTP version string (i.e. | ||
228 | * #MHD_HTTP_VERSION_1_1) | ||
229 | * @param upload_data the data being uploaded (excluding HEADERS, | ||
230 | * for a POST that fits into memory and that is encoded | ||
231 | * with a supported encoding, the POST data will NOT be | ||
232 | * given in upload_data and is instead available as | ||
233 | * part of #MHD_get_connection_values; very large POST | ||
234 | * data *will* be made available incrementally in | ||
235 | * @a upload_data) | ||
236 | * @param upload_data_size set initially to the size of the | ||
237 | * @a upload_data provided; the method must update this | ||
238 | * value to the number of bytes NOT processed; | ||
239 | * @param con_cls pointer that the callback can set to some | ||
240 | * address and that will be preserved by MHD for future | ||
241 | * calls for this request; since the access handler may | ||
242 | * be called many times (i.e., for a PUT/POST operation | ||
243 | * with plenty of upload data) this allows the application | ||
244 | * to easily associate some request-specific state. | ||
245 | * If necessary, this state can be cleaned up in the | ||
246 | * global #MHD_RequestCompletedCallback (which | ||
247 | * can be set with the #MHD_OPTION_NOTIFY_COMPLETED). | ||
248 | * Initially, `*con_cls` will be NULL. | ||
249 | * @return #MHD_YES if the connection was handled successfully, | ||
250 | * #MHD_NO if the socket must be closed due to a serios | ||
251 | * error while handling the request | ||
252 | */ | ||
253 | static int | ||
254 | ahc_upgrade (void *cls, | ||
255 | struct MHD_Connection *connection, | ||
256 | const char *url, | ||
257 | const char *method, | ||
258 | const char *version, | ||
259 | const char *upload_data, | ||
260 | size_t *upload_data_size, | ||
261 | void **con_cls) | ||
262 | { | ||
263 | struct MHD_Response *resp; | ||
264 | int ret; | ||
265 | |||
266 | resp = MHD_create_response_for_upgrade (&upgrade_cb, | ||
267 | NULL); | ||
268 | MHD_add_response_header (resp, | ||
269 | MHD_HTTP_HEADER_UPGRADE, | ||
270 | "Hello World Protocol"); | ||
271 | ret = MHD_queue_response (connection, | ||
272 | MHD_HTTP_SWITCHING_PROTOCOLS, | ||
273 | resp); | ||
274 | MHD_destroy_response (resp); | ||
275 | return ret; | ||
276 | } | ||
277 | |||
278 | |||
279 | static int | ||
280 | test_upgrade_internal_select () | ||
281 | { | ||
282 | struct MHD_Daemon *d; | ||
283 | MHD_socket sock; | ||
284 | struct sockaddr_in sa; | ||
285 | |||
286 | d = MHD_start_daemon (MHD_USE_SELECT_INTERNALLY | MHD_USE_DEBUG | MHD_USE_SUSPEND_RESUME, | ||
287 | 1080, | ||
288 | NULL, NULL, | ||
289 | &ahc_upgrade, NULL, | ||
290 | MHD_OPTION_END); | ||
291 | if (NULL == d) | ||
292 | return 2; | ||
293 | sock = socket (AF_INET, SOCK_STREAM, IPPROTO_TCP); | ||
294 | if (MHD_INVALID_SOCKET == sock) | ||
295 | abort (); | ||
296 | sa.sin_family = AF_INET; | ||
297 | sa.sin_port = htons (1080); | ||
298 | sa.sin_addr.s_addr = htonl (INADDR_LOOPBACK); | ||
299 | if (0 != connect (sock, (struct sockaddr *) &sa, sizeof (sa))) | ||
300 | abort (); | ||
301 | send_all (sock, | ||
302 | "GET / HTTP/1.1\r\nConnection: Upgrade\r\n\r\n"); | ||
303 | recv_hdr (sock); | ||
304 | recv_all (sock, | ||
305 | "Hello"); | ||
306 | send_all (sock, | ||
307 | "World"); | ||
308 | recv_all (sock, | ||
309 | "Finished"); | ||
310 | MHD_socket_close_ (sock); | ||
311 | MHD_stop_daemon (d); | ||
312 | return 0; | ||
313 | } | ||
314 | |||
315 | |||
316 | int | ||
317 | main (int argc, char *const *argv) | ||
318 | { | ||
319 | int errorCount = 0; | ||
320 | |||
321 | errorCount += test_upgrade_internal_select (); | ||
322 | if (errorCount != 0) | ||
323 | fprintf (stderr, "Error (code: %u)\n", errorCount); | ||
324 | return errorCount != 0; /* 0 == pass */ | ||
325 | } | ||