2 * Copyright (C) 2018-2020 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>
53 #include "web-server.h"
56 /* For this file, we don't care if fds are marked cloexec; leaking is okay. */
57 #define SOCK_CLOEXEC 0
60 enum method
{ HEAD
, GET
};
62 static char tmpdir
[] = "/tmp/wsXXXXXX";
63 static char sockpath
[] = "............./sock";
64 static int listen_sock
= -1;
66 static struct stat statbuf
;
67 static char request
[16384];
68 static check_request_t check_request
;
70 static void *start_web_server (void *arg
);
71 static void handle_requests (int s
);
72 static void handle_file_request (int s
, enum method method
);
73 static void handle_mirror_redirect_request (int s
);
74 static void handle_mirror_data_request (int s
, enum method method
, char byte
);
75 static void send_404_not_found (int s
);
76 static void send_405_method_not_allowed (int s
);
77 static void send_500_internal_server_error (int s
);
78 static void xwrite (int s
, const char *buf
, size_t len
);
79 static void xpread (char *buf
, size_t count
, off_t offset
);
94 web_server (const char *filename
, check_request_t _check_request
)
96 struct sockaddr_un addr
;
100 check_request
= _check_request
;
103 fd
= open (filename
, O_RDONLY
|O_CLOEXEC
);
108 if (fstat (fd
, &statbuf
) == -1) {
113 /* Create the temporary directory for the socket. */
114 if (mkdtemp (tmpdir
) == NULL
) {
119 /* Create the listening socket for the web server. */
120 memcpy (sockpath
, tmpdir
, strlen (tmpdir
));
121 listen_sock
= socket (AF_UNIX
, SOCK_STREAM
|SOCK_CLOEXEC
, 0);
122 if (listen_sock
== -1) {
127 addr
.sun_family
= AF_UNIX
;
128 memcpy (addr
.sun_path
, sockpath
, strlen (sockpath
) + 1);
129 if (bind (listen_sock
, (struct sockaddr
*) &addr
, sizeof addr
) == -1) {
134 if (listen (listen_sock
, SOMAXCONN
) == -1) {
139 /* Run the web server in a separate thread. */
140 err
= pthread_create (&thread
, NULL
, start_web_server
, NULL
);
143 perror ("pthread_create");
146 err
= pthread_detach (thread
);
149 perror ("pthread_detach");
169 start_web_server (void *arg
)
171 fprintf (stderr
, "web server: listening on %s\n", sockpath
);
174 int s
= accept (listen_sock
, NULL
, NULL
);
176 /* This is not an error: The server has closed the socket in
177 * cleanup() because it is exiting, resulting in accept(2) above
178 * returning EBADF, so just exit the thread.
182 perror ("web server: accept");
190 handle_requests (int s
)
194 fprintf (stderr
, "web server: accepted connection\n");
201 /* Read request until we see "\r\n\r\n" (end of headers) or EOF. */
204 if (n
>= sizeof request
- 1 /* allow one byte for \0 */) {
205 fprintf (stderr
, "web server: request too long\n");
208 sz
= sizeof request
- n
- 1;
209 r
= read (s
, &request
[n
], sz
);
220 if (strstr (request
, "\r\n\r\n"))
227 fprintf (stderr
, "web server: request:\n%s", request
);
229 /* Call the optional user function to check the request. */
230 if (check_request
) check_request (request
);
232 /* Get the method and path fields from the first line. */
233 if (strncmp (request
, "HEAD ", 5) == 0) {
235 n
= strcspn (&request
[5], " \n\t");
236 if (n
>= sizeof path
) {
237 send_500_internal_server_error (s
);
241 memcpy (path
, &request
[5], n
);
244 else if (strncmp (request
, "GET ", 4) == 0) {
246 n
= strcspn (&request
[4], " \n\t");
247 if (n
>= sizeof path
) {
248 send_500_internal_server_error (s
);
252 memcpy (path
, &request
[4], n
);
256 send_405_method_not_allowed (s
);
261 fprintf (stderr
, "web server: requested path: %s\n", path
);
263 /* For testing retry-request + curl:
264 * /mirror redirects round-robin to /mirror1, /mirror2, /mirror3
265 * /mirror1 returns a file of \x01 bytes
266 * /mirror2 returns a file of \x02 bytes
267 * /mirror3 returns 404 errors
268 * Anything else returns a 500 error
270 if (strcmp (path
, "/mirror") == 0)
271 handle_mirror_redirect_request (s
);
272 else if (strcmp (path
, "/mirror1") == 0)
273 handle_mirror_data_request (s
, method
, 1);
274 else if (strcmp (path
, "/mirror2") == 0)
275 handle_mirror_data_request (s
, method
, 2);
276 else if (strcmp (path
, "/mirror3") == 0) {
277 send_404_not_found (s
);
280 else if (strncmp (path
, "/mirror", 7) == 0) {
281 send_500_internal_server_error (s
);
285 /* Otherwise it's a regular file request. 'path' is ignored, we
286 * only serve a single file passed to web_server().
289 handle_file_request (s
, method
);
291 fprintf (stderr
, "web server: completed request\n");
294 fprintf (stderr
, "web server: closing socket\n");
299 handle_file_request (int s
, enum method method
)
301 const bool headers_only
= method
== HEAD
;
302 uint64_t offset
, length
, end
;
304 const char response1_ok
[] = "HTTP/1.1 200 OK\r\n";
305 const char response1_partial
[] = "HTTP/1.1 206 Partial Content\r\n";
306 const char response2
[] =
307 "Accept-rANGES: bytes\r\n" /* See RHBZ#1837337 */
308 "Connection: close\r\n"
309 "Content-Type: application/octet-stream\r\n";
311 const char response4
[] = "\r\n";
314 /* If there's no Range request header then send the full size as the
317 p
= strcasestr (request
, "\r\nRange: bytes=");
320 length
= statbuf
.st_size
;
321 xwrite (s
, response1_ok
, strlen (response1_ok
));
325 if (sscanf (p
, "%" SCNu64
"-%" SCNu64
, &offset
, &end
) != 2) {
326 fprintf (stderr
, "web server: could not parse "
327 "range request from curl client\n");
330 /* Unclear but "Range: bytes=0-4" means bytes 0-3. '4' is the
331 * byte beyond the end of the range.
333 length
= end
- offset
;
334 xwrite (s
, response1_partial
, strlen (response1_partial
));
337 xwrite (s
, response2
, strlen (response2
));
338 snprintf (response3
, sizeof response3
,
339 "Content-Length: %" PRIu64
"\r\n", length
);
340 xwrite (s
, response3
, strlen (response3
));
341 xwrite (s
, response4
, strlen (response4
));
346 /* Send the file content. */
347 data
= malloc (length
);
353 xpread (data
, length
, offset
);
354 xwrite (s
, data
, length
);
359 /* Request for /mirror */
361 handle_mirror_redirect_request (int s
)
363 static char rr
= '1'; /* round robin '1', '2', '3' */
364 /* Note we send 302 (temporary redirect), same as Fedora's mirrorservice. */
365 const char found
[] = "HTTP/1.1 302 Found\r\nContent-Length: 0\r\n";
366 char location
[] = "Location: /mirrorX\r\n";
367 char close_
[] = "Connection: close\r\n";
368 const char eol
[] = "\r\n";
375 xwrite (s
, found
, strlen (found
));
376 xwrite (s
, location
, strlen (location
));
377 xwrite (s
, close_
, strlen (close_
));
378 xwrite (s
, eol
, strlen (eol
));
382 handle_mirror_data_request (int s
, enum method method
, char byte
)
384 const bool headers_only
= method
== HEAD
;
385 uint64_t offset
, length
, end
;
387 const char response1_ok
[] = "HTTP/1.1 200 OK\r\n";
388 const char response1_partial
[] = "HTTP/1.1 206 Partial Content\r\n";
389 const char response2
[] =
390 "Accept-rANGES: bytes\r\n" /* See RHBZ#1837337 */
391 "Connection: close\r\n"
392 "Content-Type: application/octet-stream\r\n";
394 const char response4
[] = "\r\n";
397 /* If there's no Range request header then send the full size as the
400 p
= strcasestr (request
, "\r\nRange: bytes=");
403 length
= statbuf
.st_size
;
404 xwrite (s
, response1_ok
, strlen (response1_ok
));
408 if (sscanf (p
, "%" SCNu64
"-%" SCNu64
, &offset
, &end
) != 2) {
409 fprintf (stderr
, "web server: could not parse "
410 "range request from curl client\n");
413 /* Unclear but "Range: bytes=0-4" means bytes 0-3. '4' is the
414 * byte beyond the end of the range.
416 length
= end
- offset
;
417 xwrite (s
, response1_partial
, strlen (response1_partial
));
420 xwrite (s
, response2
, strlen (response2
));
421 snprintf (response3
, sizeof response3
,
422 "Content-Length: %" PRIu64
"\r\n", length
);
423 xwrite (s
, response3
, strlen (response3
));
424 xwrite (s
, response4
, strlen (response4
));
429 /* Send the file content. */
430 data
= malloc (length
);
436 memset (data
, byte
, length
);
437 xwrite (s
, data
, length
);
443 send_404_not_found (int s
)
445 const char response
[] =
446 "HTTP/1.1 404 Not Found\r\n"
447 "Content-Length: 0\r\n"
448 "Connection: close\r\n"
450 xwrite (s
, response
, strlen (response
));
454 send_405_method_not_allowed (int s
)
456 const char response
[] =
457 "HTTP/1.1 405 Method Not Allowed\r\n"
458 "Content-Length: 0\r\n"
459 "Connection: close\r\n"
461 xwrite (s
, response
, strlen (response
));
465 send_500_internal_server_error (int s
)
467 const char response
[] =
468 "HTTP/1.1 500 Internal Server Error\r\n"
469 "Content-Length: 0\r\n"
470 "Connection: close\r\n"
472 xwrite (s
, response
, strlen (response
));
476 xwrite (int s
, const char *buf
, size_t len
)
481 r
= write (s
, buf
, len
);
492 xpread (char *buf
, size_t count
, off_t offset
)
497 r
= pread (fd
, buf
, count
, offset
);
503 fprintf (stderr
, "pread: unexpected end of file\n");