#include "response.h"
#ifdef __NetBSD__
#ifndef PATH_MAX
#include <sys/syslimits.h>
#endif
#endif
#if defined(__sun) && defined(__illumos__)
#ifndef PATH_MAX
#include <limits.h>
#endif
#endif
#ifdef __linux__
#ifndef PATH_MAX
#include <linux/limits.h> /* for PATH_MAX */
#endif
#endif
#include <sys/stat.h>
#include <sys/types.h>
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
#include "header.h"
#include "utils.h"
#define MAX_BUFFER_SIZE 1024
/*
* Sends a given status line and headers to the client, given the parameters
* of the original request, the main directory where the requested file is
* supposed to be stored, and the integer status to send over.
*/
void
send_status_and_headers(REQUEST* request, int status)
{
send_status(request, status);
if (status >= 400)
{
if (write_error_header(request) != 0)
{
fprintf(stderr,
"[Debug] An error occured trying to write headers: %s\n",
strerror(errno));
}
}
else if (status == 304)
{
if (write_304_header(request) != 0)
{
fprintf(stderr,
"[Debug] An error occured trying to write headers: %s\n",
strerror(errno));
}
}
else
{
if (write_header(request) != 0)
{
fprintf(stderr,
"[Debug] An error occured trying to write headers: %s\n",
strerror(errno));
}
}
if (write_crlf_to_request(request) != 0)
{
fprintf(stderr, "[Debug] Could not write CRLF to client.\n");
}
return;
}
void
send_status_and_headers_cgi(CGI* cgi, int status)
{
send_status(cgi->http_request, status);
if (write_cgi_header(cgi) != 0)
{
fprintf(stderr,
"[Debug] An error occured trying to write headers: %s\n",
strerror(errno));
}
if (write_crlf_to_request(cgi->http_request) != 0)
{
fprintf(stderr, "[Debug] Could not write CRLF to client.\n");
}
return;
}
/*
* Sends directory entries. Makes faithful assumption that directory exists.
*/
int
send_dir_entries(REQUEST* request, bool headers_only)
{
struct dirent** entries;
int number_of_files;
int status = 200;
// we sort the dirs here
number_of_files = scandir(request->parsed_path, &entries, NULL, alphasort);
char* buffer = (char*)calloc(MAX_BUFFER_SIZE, sizeof(char));
if (buffer == NULL)
{
status = 500;
send_status_and_headers(request, status);
fprintf(stderr, "[Debug] calloc failed: %s\n", strerror(errno));
return status;
}
size_t buffer_size = MAX_BUFFER_SIZE;
size_t buffer_len = 0;
for (int i = 0; i < number_of_files; i++)
{
if (entries[i]->d_name[0] == '.')
{
free(entries[i]);
continue;
}
printf("[Debug] dir: %s\n", entries[i]->d_name);
int dir_name_length = strlen(entries[i]->d_name) + 1; // add 1 for \n
if (buffer_len + dir_name_length > buffer_size)
{
/* see cgi.c, around line 300 for the explanation on this */
char* new_buffer = realloc(buffer, buffer_size + MAX_BUFFER_SIZE);
if (new_buffer == NULL)
{
status = 500;
send_status_and_headers(request, status);
fprintf(stderr, "[Debug] realloc failed: %s\n",
strerror(errno));
free(buffer);
for (int j = i; j < number_of_files; j++)
{
free(entries[i]);
}
return status;
}
buffer = new_buffer;
memset(buffer + buffer_size, 0, MAX_BUFFER_SIZE);
buffer_size += MAX_BUFFER_SIZE;
}
int len = snprintf(buffer + buffer_len, dir_name_length + 1, "%s\n",
entries[i]->d_name);
if (len < 0)
{
status = 500;
send_status_and_headers(request, status);
fprintf(stderr, "[Debug] realloc failed: %s\n", strerror(errno));
free(buffer);
for (int j = i; j < number_of_files; j++)
{
free(entries[i]);
}
return status;
}
buffer_len += len;
free(entries[i]);
}
send_status(request, status);
write_dir_header(request, buffer_len);
write_crlf_to_request(request);
if (!headers_only)
{
int bytes_written = safe_write(request->client_fd, buffer, buffer_len);
if (bytes_written == -1)
{
fprintf(stderr, "[Debug] safe_write failed: %s\n", strerror(errno));
}
}
free(buffer);
return status;
}
/*
* Attempts to serve a file located at the request's path in dir_arg. Sends
* the appropriate status code and headers if so.
*
* Returns the status code of the response sent to the client.
*/
int
attempt_serve_main(REQUEST* request, bool headers_only)
{
/*
* Much of the code here was originally written by Tristan, but re-adapted
* to ensure that status lines and header key-value pairs are written in the
* exact format expected by RFC 1945 (see Section 4).
*/
char buffer[4096]; /* Generally a good blocksize for most file systems. */
ssize_t bytes_read = 0;
errno = 0;
int source_fd = -1;
if (strlen(request->parsed_path) > PATH_MAX)
{
send_status_and_headers(request, 400);
return 400;
}
if ((source_fd = open(request->parsed_path, O_RDONLY)) == -1)
{
if (errno == ENOENT)
{
send_status_and_headers(request, 404);
return 404;
}
else
{
send_status_and_headers(request, 500);
return 500;
}
}
struct stat st;
if (fstat(source_fd, &st) == -1)
{
close(source_fd);
if (errno == ENOENT)
{
send_status_and_headers(request, 404);
return 404;
}
else
{
send_status_and_headers(request, 500);
return 500;
}
}
if (request->if_modified_since_time != 0 &&
request->if_modified_since_time > st.st_mtime)
{
send_status_and_headers(request, 304);
return 304;
}
if (S_ISDIR(st.st_mode))
{
(void)close(source_fd);
return send_dir_entries(request, headers_only);
}
send_status_and_headers(request, 200);
if (headers_only)
{
(void)close(source_fd);
return 200;
}
while ((bytes_read = read(source_fd, buffer, sizeof(buffer))) > 0)
{
ssize_t bytes_written_per_read = 0;
while (bytes_written_per_read < bytes_read)
{
/* this write is intentionally not our own safe_write */
ssize_t bytesWritten =
write(request->client_fd, (buffer + bytes_written_per_read),
(bytes_read - bytes_written_per_read));
if (errno == EINTR)
{
continue; /* try again */
}
if (bytesWritten < 0)
{
/* Short writes are fine, not much we can do */
return 200;
}
bytes_written_per_read += bytesWritten;
request->response_size += bytesWritten;
}
}
(void)close(source_fd);
if (bytes_read < 0)
{
/* also don't think there's much we can do here */
return 200;
}
return 200;
}