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
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
33 /* See web-server.h */
46 #include <sys/types.h>
48 #include <sys/socket.h>
54 /* For this file, we don't care if fds are marked cloexec; leaking is okay. */
55 #define SOCK_CLOEXEC 0
58 static char tmpdir
[] = "/tmp/wsXXXXXX";
59 static char sockpath
[] = "............./sock";
60 static int listen_sock
= -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
);
83 web_server (const char *filename
)
85 struct sockaddr_un addr
;
90 fd
= open (filename
, O_RDONLY
|O_CLOEXEC
);
95 if (fstat (fd
, &statbuf
) == -1) {
100 /* Create the temporary directory for the socket. */
101 if (mkdtemp (tmpdir
) == NULL
) {
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) {
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) {
121 if (listen (listen_sock
, SOMAXCONN
) == -1) {
126 /* Run the web server in a separate thread. */
127 err
= pthread_create (&thread
, NULL
, start_web_server
, NULL
);
130 perror ("pthread_create");
133 err
= pthread_detach (thread
);
136 perror ("pthread_detach");
156 start_web_server (void *arg
)
160 fprintf (stderr
, "web server: listening on %s\n", sockpath
);
163 s
= accept (listen_sock
, NULL
, NULL
);
173 handle_requests (int s
)
178 fprintf (stderr
, "web server: accepted connection\n");
181 /* Read request until we see "\r\n\r\n" (end of headers) or EOF. */
184 if (n
>= sizeof request
- 1 /* allow one byte for \0 */) {
185 fprintf (stderr
, "web server: request too long\n");
188 sz
= sizeof request
- n
- 1;
189 r
= read (s
, &request
[n
], sz
);
200 if (strstr (request
, "\r\n\r\n"))
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);
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"
221 xwrite (s
, response
, strlen (response
));
231 handle_request (int s
, bool headers_only
)
233 uint64_t offset
, length
, end
;
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";
242 const char response4
[] = "\r\n";
245 /* If there's no Range request header then send the full size as the
248 p
= strcasestr (request
, "\r\nRange: bytes=");
251 length
= statbuf
.st_size
;
252 xwrite (s
, response1_ok
, strlen (response1_ok
));
256 if (sscanf (p
, "%" SCNu64
"-%" SCNu64
, &offset
, &end
) != 2) {
257 fprintf (stderr
, "web server: could not parse "
258 "range request from curl client\n");
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
));
277 /* Send the file content. */
278 data
= malloc (length
);
284 xpread (data
, length
, offset
);
285 xwrite (s
, data
, length
);
291 xwrite (int s
, const char *buf
, size_t len
)
296 r
= write (s
, buf
, len
);
307 xpread (char *buf
, size_t count
, off_t offset
)
312 r
= pread (fd
, buf
, count
, offset
);
318 fprintf (stderr
, "pread: unexpected end of file\n");