aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChristian Grothoff <christian@grothoff.org>2016-08-27 19:01:52 +0000
committerChristian Grothoff <christian@grothoff.org>2016-08-27 19:01:52 +0000
commitcfbd80d1e127a98175c0276b7ffbcfb2475fea3b (patch)
tree531f11157d4f040720b446fa777a2879ab380f9a
parente0a43cb194fd22ceaed6905d964f2fcd829b933d (diff)
downloadlibmicrohttpd-cfbd80d1e127a98175c0276b7ffbcfb2475fea3b.tar.gz
libmicrohttpd-cfbd80d1e127a98175c0276b7ffbcfb2475fea3b.zip
add testcase for HTTP Upgrade
-rw-r--r--ChangeLog4
-rw-r--r--src/microhttpd/Makefile.am8
-rw-r--r--src/microhttpd/connection.c18
-rw-r--r--src/microhttpd/daemon.c4
-rw-r--r--src/microhttpd/response.c20
-rw-r--r--src/microhttpd/test_upgrade.c325
6 files changed, 376 insertions, 3 deletions
diff --git a/ChangeLog b/ChangeLog
index 2033ed4a..a606d2fe 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,7 @@
1Sat 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
1Sat Aug 27 20:07:53 CEST 2016 5Sat 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
148if HAVE_POSTPROCESSOR 149if HAVE_POSTPROCESSOR
149check_PROGRAMS += \ 150check_PROGRAMS += \
@@ -165,6 +166,11 @@ test_daemon_SOURCES = \
165test_daemon_LDADD = \ 166test_daemon_LDADD = \
166 $(top_builddir)/src/microhttpd/libmicrohttpd.la 167 $(top_builddir)/src/microhttpd/libmicrohttpd.la
167 168
169test_upgrade_SOURCES = \
170 test_upgrade.c
171test_upgrade_LDADD = \
172 $(top_builddir)/src/microhttpd/libmicrohttpd.la
173
168test_postprocessor_SOURCES = \ 174test_postprocessor_SOURCES = \
169 test_postprocessor.c 175 test_postprocessor.c
170test_postprocessor_CPPFLAGS = \ 176test_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
43static void
44send_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 */
72static void
73recv_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
119static void
120recv_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 */
198static void
199upgrade_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 */
253static int
254ahc_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
279static int
280test_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
316int
317main (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}