tests: Add tests that just including the header works.
[nbdkit/ericb.git] / tests / web-server.c
blobd79924ff7faa37ae9125b6eaccd8547a60af2868
1 /* nbdkit
2 * Copyright (C) 2018 Red Hat Inc.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are
6 * met:
8 * * Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
11 * * Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
15 * * Neither the name of Red Hat nor the names of its contributors may be
16 * used to endorse or promote products derived from this software without
17 * specific prior written permission.
19 * THIS SOFTWARE IS PROVIDED BY RED HAT AND CONTRIBUTORS ''AS IS'' AND
20 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
21 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
22 * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL RED HAT OR
23 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
26 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
27 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
29 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
30 * SUCH DAMAGE.
33 /* See web-server.h */
35 #include <config.h>
37 #include <stdio.h>
38 #include <stdlib.h>
39 #include <stdbool.h>
40 #include <stdint.h>
41 #include <inttypes.h>
42 #include <string.h>
43 #include <fcntl.h>
44 #include <unistd.h>
45 #include <errno.h>
46 #include <sys/types.h>
47 #include <sys/stat.h>
48 #include <sys/socket.h>
49 #include <sys/un.h>
51 #include <pthread.h>
53 static char tmpdir[] = "/tmp/wsXXXXXX";
54 static char sockpath[] = "............./sock";
55 static int listen_sock = -1;
56 static int fd = -1;
57 static struct stat statbuf;
58 static char request[16384];
60 static void *start_web_server (void *arg);
61 static void handle_requests (int s);
62 static void handle_request (int s, bool headers_only);
63 static void xwrite (int s, const char *buf, size_t len);
64 static void xpread (int fd, char *buf, size_t count, off_t offset);
66 static void
67 cleanup (void)
69 if (fd >= 0)
70 close (fd);
71 if (listen_sock >= 0)
72 close (listen_sock);
73 unlink (sockpath);
74 rmdir (tmpdir);
77 const char *
78 web_server (const char *filename)
80 struct sockaddr_un addr;
81 pthread_t thread;
82 int err;
84 /* Open the file. */
85 fd = open (filename, O_RDONLY|O_CLOEXEC);
86 if (fd == -1) {
87 perror (filename);
88 return NULL;
90 if (fstat (fd, &statbuf) == -1) {
91 perror ("stat");
92 goto err1;
95 /* Create the temporary directory for the socket. */
96 if (mkdtemp (tmpdir) == NULL) {
97 perror ("mkdtemp");
98 goto err1;
101 /* Create the listening socket for the web server. */
102 memcpy (sockpath, tmpdir, strlen (tmpdir));
103 listen_sock = socket (AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0);
104 if (listen_sock == -1) {
105 perror ("socket");
106 goto err2;
109 addr.sun_family = AF_UNIX;
110 memcpy (addr.sun_path, sockpath, strlen (sockpath) + 1);
111 if (bind (listen_sock, (struct sockaddr *) &addr, sizeof addr) == -1) {
112 perror (sockpath);
113 goto err3;
116 if (listen (listen_sock, SOMAXCONN) == -1) {
117 perror ("listen");
118 goto err4;
121 /* Run the web server in a separate thread. */
122 err = pthread_create (&thread, NULL, start_web_server, NULL);
123 if (err) {
124 errno = err;
125 perror ("pthread_create");
126 goto err4;
128 err = pthread_detach (thread);
129 if (err) {
130 errno = err;
131 perror ("pthread_detach");
132 goto err4;
135 atexit (cleanup);
137 return sockpath;
139 err4:
140 unlink (sockpath);
141 err3:
142 close (listen_sock);
143 err2:
144 rmdir (tmpdir);
145 err1:
146 close (fd);
147 return NULL;
150 static void *
151 start_web_server (void *arg)
153 int s;
155 fprintf (stderr, "web server: listening on %s\n", sockpath);
157 for (;;) {
158 s = accept (listen_sock, NULL, NULL);
159 if (s == -1) {
160 perror ("accept");
161 exit (EXIT_FAILURE);
163 handle_requests (s);
167 static void
168 handle_requests (int s)
170 size_t r, n, sz;
171 bool eof = false;
173 fprintf (stderr, "web server: accepted connection\n");
175 while (!eof) {
176 /* Read request until we see "\r\n\r\n" (end of headers) or EOF. */
177 n = 0;
178 for (;;) {
179 if (n >= sizeof request - 1 /* allow one byte for \0 */) {
180 fprintf (stderr, "web server: request too long\n");
181 exit (EXIT_FAILURE);
183 sz = sizeof request - n - 1;
184 r = read (s, &request[n], sz);
185 if (r == -1) {
186 perror ("read");
187 exit (EXIT_FAILURE);
189 if (r == 0) {
190 eof = true;
191 break;
193 n += r;
194 request[n] = '\0';
195 if (strstr (request, "\r\n\r\n"))
196 break;
199 if (n == 0)
200 continue;
202 fprintf (stderr, "web server: request:\n%s", request);
204 /* HEAD or GET request? */
205 if (strncmp (request, "HEAD ", 5) == 0)
206 handle_request (s, true);
207 else if (strncmp (request, "GET ", 4) == 0)
208 handle_request (s, false);
209 else {
210 /* Return 405 Method Not Allowed. */
211 const char response[] =
212 "HTTP/1.1 405 Method Not Allowed\r\n"
213 "Content-Length: 0\r\n"
214 "Connection: close\r\n"
215 "\r\n";
216 xwrite (s, response, strlen (response));
217 eof = true;
218 break;
222 close (s);
225 static void
226 handle_request (int s, bool headers_only)
228 uint64_t offset, length, end;
229 const char *p;
230 const char response1_ok[] = "HTTP/1.1 200 OK\r\n";
231 const char response1_partial[] = "HTTP/1.1 206 Partial Content\r\n";
232 const char response2[] =
233 "Accept-Ranges: bytes\r\n"
234 "Connection: keep-alive\r\n"
235 "Content-Type: application/octet-stream\r\n";
236 char response3[64];
237 const char response4[] = "\r\n";
238 char *data;
240 /* If there's no Range request header then send the full size as the
241 * content-length.
243 p = strcasestr (request, "\r\nRange: bytes=");
244 if (p == NULL) {
245 offset = 0;
246 length = statbuf.st_size;
247 xwrite (s, response1_ok, strlen (response1_ok));
249 else {
250 p += 15;
251 if (sscanf (p, "%" SCNu64 "-%" SCNu64, &offset, &end) != 2) {
252 fprintf (stderr, "web server: could not parse "
253 "range request from curl client\n");
254 exit (EXIT_FAILURE);
256 /* Unclear but "Range: bytes=0-4" means bytes 0-3. '4' is the
257 * byte beyond the end of the range.
259 length = end - offset;
260 xwrite (s, response1_partial, strlen (response1_partial));
263 xwrite (s, response2, strlen (response2));
264 snprintf (response3, sizeof response3,
265 "Content-Length: %" PRIu64 "\r\n", length);
266 xwrite (s, response3, strlen (response3));
267 xwrite (s, response4, strlen (response4));
269 if (headers_only)
270 return;
272 /* Send the file content. */
273 data = malloc (length);
274 if (data == NULL) {
275 perror ("malloc");
276 exit (EXIT_FAILURE);
279 xpread (fd, data, length, offset);
280 xwrite (s, data, length);
282 free (data);
285 static void
286 xwrite (int s, const char *buf, size_t len)
288 ssize_t r;
290 while (len > 0) {
291 r = write (s, buf, len);
292 if (r == -1) {
293 perror ("write");
294 exit (EXIT_FAILURE);
296 buf += r;
297 len -= r;
301 static void
302 xpread (int fd, char *buf, size_t count, off_t offset)
304 ssize_t r;
306 while (count > 0) {
307 r = pread (fd, buf, count, offset);
308 if (r == -1) {
309 perror ("read");
310 exit (EXIT_FAILURE);
312 if (r == 0) {
313 fprintf (stderr, "pread: unexpected end of file\n");
314 exit (EXIT_FAILURE);
316 buf += r;
317 count -= r;
318 offset += r;