tests: Make all guestfish tests use runtime check.
[nbdkit/ericb.git] / tests / web-server.c
blobf27ee70d263cd47dee1ec1f6d39356ad0442f981
1 /* nbdkit
2 * Copyright (C) 2018-2019 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 #ifndef SOCK_CLOEXEC
54 /* For this file, we don't care if fds are marked cloexec; leaking is okay. */
55 #define SOCK_CLOEXEC 0
56 #endif
58 static char tmpdir[] = "/tmp/wsXXXXXX";
59 static char sockpath[] = "............./sock";
60 static int listen_sock = -1;
61 static int fd = -1;
62 static struct stat statbuf;
63 static char request[16384];
65 static void *start_web_server (void *arg);
66 static void handle_requests (int s);
67 static void handle_request (int s, bool headers_only);
68 static void xwrite (int s, const char *buf, size_t len);
69 static void xpread (char *buf, size_t count, off_t offset);
71 static void
72 cleanup (void)
74 if (fd >= 0)
75 close (fd);
76 if (listen_sock >= 0)
77 close (listen_sock);
78 unlink (sockpath);
79 rmdir (tmpdir);
82 const char *
83 web_server (const char *filename)
85 struct sockaddr_un addr;
86 pthread_t thread;
87 int err;
89 /* Open the file. */
90 fd = open (filename, O_RDONLY|O_CLOEXEC);
91 if (fd == -1) {
92 perror (filename);
93 return NULL;
95 if (fstat (fd, &statbuf) == -1) {
96 perror ("stat");
97 goto err1;
100 /* Create the temporary directory for the socket. */
101 if (mkdtemp (tmpdir) == NULL) {
102 perror ("mkdtemp");
103 goto err1;
106 /* Create the listening socket for the web server. */
107 memcpy (sockpath, tmpdir, strlen (tmpdir));
108 listen_sock = socket (AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0);
109 if (listen_sock == -1) {
110 perror ("socket");
111 goto err2;
114 addr.sun_family = AF_UNIX;
115 memcpy (addr.sun_path, sockpath, strlen (sockpath) + 1);
116 if (bind (listen_sock, (struct sockaddr *) &addr, sizeof addr) == -1) {
117 perror (sockpath);
118 goto err3;
121 if (listen (listen_sock, SOMAXCONN) == -1) {
122 perror ("listen");
123 goto err4;
126 /* Run the web server in a separate thread. */
127 err = pthread_create (&thread, NULL, start_web_server, NULL);
128 if (err) {
129 errno = err;
130 perror ("pthread_create");
131 goto err4;
133 err = pthread_detach (thread);
134 if (err) {
135 errno = err;
136 perror ("pthread_detach");
137 goto err4;
140 atexit (cleanup);
142 return sockpath;
144 err4:
145 unlink (sockpath);
146 err3:
147 close (listen_sock);
148 err2:
149 rmdir (tmpdir);
150 err1:
151 close (fd);
152 return NULL;
155 static void *
156 start_web_server (void *arg)
158 int s;
160 fprintf (stderr, "web server: listening on %s\n", sockpath);
162 for (;;) {
163 s = accept (listen_sock, NULL, NULL);
164 if (s == -1) {
165 perror ("accept");
166 exit (EXIT_FAILURE);
168 handle_requests (s);
172 static void
173 handle_requests (int s)
175 size_t r, n, sz;
176 bool eof = false;
178 fprintf (stderr, "web server: accepted connection\n");
180 while (!eof) {
181 /* Read request until we see "\r\n\r\n" (end of headers) or EOF. */
182 n = 0;
183 for (;;) {
184 if (n >= sizeof request - 1 /* allow one byte for \0 */) {
185 fprintf (stderr, "web server: request too long\n");
186 exit (EXIT_FAILURE);
188 sz = sizeof request - n - 1;
189 r = read (s, &request[n], sz);
190 if (r == -1) {
191 perror ("read");
192 exit (EXIT_FAILURE);
194 if (r == 0) {
195 eof = true;
196 break;
198 n += r;
199 request[n] = '\0';
200 if (strstr (request, "\r\n\r\n"))
201 break;
204 if (n == 0)
205 continue;
207 fprintf (stderr, "web server: request:\n%s", request);
209 /* HEAD or GET request? */
210 if (strncmp (request, "HEAD ", 5) == 0)
211 handle_request (s, true);
212 else if (strncmp (request, "GET ", 4) == 0)
213 handle_request (s, false);
214 else {
215 /* Return 405 Method Not Allowed. */
216 const char response[] =
217 "HTTP/1.1 405 Method Not Allowed\r\n"
218 "Content-Length: 0\r\n"
219 "Connection: close\r\n"
220 "\r\n";
221 xwrite (s, response, strlen (response));
222 eof = true;
223 break;
227 close (s);
230 static void
231 handle_request (int s, bool headers_only)
233 uint64_t offset, length, end;
234 const char *p;
235 const char response1_ok[] = "HTTP/1.1 200 OK\r\n";
236 const char response1_partial[] = "HTTP/1.1 206 Partial Content\r\n";
237 const char response2[] =
238 "Accept-Ranges: bytes\r\n"
239 "Connection: keep-alive\r\n"
240 "Content-Type: application/octet-stream\r\n";
241 char response3[64];
242 const char response4[] = "\r\n";
243 char *data;
245 /* If there's no Range request header then send the full size as the
246 * content-length.
248 p = strcasestr (request, "\r\nRange: bytes=");
249 if (p == NULL) {
250 offset = 0;
251 length = statbuf.st_size;
252 xwrite (s, response1_ok, strlen (response1_ok));
254 else {
255 p += 15;
256 if (sscanf (p, "%" SCNu64 "-%" SCNu64, &offset, &end) != 2) {
257 fprintf (stderr, "web server: could not parse "
258 "range request from curl client\n");
259 exit (EXIT_FAILURE);
261 /* Unclear but "Range: bytes=0-4" means bytes 0-3. '4' is the
262 * byte beyond the end of the range.
264 length = end - offset;
265 xwrite (s, response1_partial, strlen (response1_partial));
268 xwrite (s, response2, strlen (response2));
269 snprintf (response3, sizeof response3,
270 "Content-Length: %" PRIu64 "\r\n", length);
271 xwrite (s, response3, strlen (response3));
272 xwrite (s, response4, strlen (response4));
274 if (headers_only)
275 return;
277 /* Send the file content. */
278 data = malloc (length);
279 if (data == NULL) {
280 perror ("malloc");
281 exit (EXIT_FAILURE);
284 xpread (data, length, offset);
285 xwrite (s, data, length);
287 free (data);
290 static void
291 xwrite (int s, const char *buf, size_t len)
293 ssize_t r;
295 while (len > 0) {
296 r = write (s, buf, len);
297 if (r == -1) {
298 perror ("write");
299 exit (EXIT_FAILURE);
301 buf += r;
302 len -= r;
306 static void
307 xpread (char *buf, size_t count, off_t offset)
309 ssize_t r;
311 while (count > 0) {
312 r = pread (fd, buf, count, offset);
313 if (r == -1) {
314 perror ("read");
315 exit (EXIT_FAILURE);
317 if (r == 0) {
318 fprintf (stderr, "pread: unexpected end of file\n");
319 exit (EXIT_FAILURE);
321 buf += r;
322 count -= r;
323 offset += r;