761 lines
21 KiB
C
761 lines
21 KiB
C
/*
|
|
* A partial implementation of HTTP/1.0
|
|
*
|
|
* This code is mainly intended as a replacement for the book's 'tiny.c' server
|
|
* It provides a *partial* implementation of HTTP/1.0 which can form a basis for
|
|
* the assignment.
|
|
*
|
|
* @author G. Back for CS 3214 Spring 2018
|
|
*/
|
|
#include <sys/types.h>
|
|
#include <sys/socket.h>
|
|
#include <sys/stat.h>
|
|
#include <string.h>
|
|
#include <stdarg.h>
|
|
#include <stdio.h>
|
|
#include <stdbool.h>
|
|
#include <errno.h>
|
|
#include <unistd.h>
|
|
#include <time.h>
|
|
#include <fcntl.h>
|
|
#include <linux/limits.h>
|
|
#include <dirent.h>
|
|
#include <jansson.h>
|
|
|
|
#include "http.h"
|
|
#include "hexdump.h"
|
|
#include "socket.h"
|
|
#include "bufio.h"
|
|
#include "main.h"
|
|
|
|
static const char * NEVER_EMBED_A_SECRET_IN_CODE = "supa secret";
|
|
|
|
// Need macros here because of the sizeof
|
|
#define CRLF "\r\n"
|
|
#define CR "\r"
|
|
#define STARTS_WITH(field_name, header) \
|
|
(!strncasecmp(field_name, header, sizeof(header) - 1))
|
|
|
|
/* Parse HTTP request line, setting req_method, req_path, and req_version. */
|
|
static bool
|
|
http_parse_request(struct http_transaction *ta)
|
|
{
|
|
size_t req_offset;
|
|
ssize_t len = bufio_readline(ta->client->bufio, &req_offset);
|
|
if (len < 2) // error, EOF, or less than 2 characters
|
|
return false;
|
|
|
|
char *request = bufio_offset2ptr(ta->client->bufio, req_offset);
|
|
request[len-2] = '\0'; // replace LF with 0 to ensure zero-termination
|
|
char *endptr;
|
|
char *method = strtok_r(request, " ", &endptr);
|
|
if (method == NULL)
|
|
return false;
|
|
|
|
if (!strcmp(method, "GET"))
|
|
ta->req_method = HTTP_GET;
|
|
else if (!strcmp(method, "POST"))
|
|
ta->req_method = HTTP_POST;
|
|
else
|
|
ta->req_method = HTTP_UNKNOWN;
|
|
|
|
char *req_path = strtok_r(NULL, " ", &endptr);
|
|
if (req_path == NULL)
|
|
return false;
|
|
|
|
ta->req_path = bufio_ptr2offset(ta->client->bufio, req_path);
|
|
|
|
char *http_version = strtok_r(NULL, CR, &endptr);
|
|
if (http_version == NULL) // would be HTTP 0.9
|
|
return false;
|
|
|
|
// record client's HTTP version in request
|
|
if (!strcmp(http_version, "HTTP/1.1"))
|
|
ta->req_version = HTTP_1_1;
|
|
else if (!strcmp(http_version, "HTTP/1.0"))
|
|
ta->req_version = HTTP_1_0;
|
|
else
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
/* Process HTTP headers. */
|
|
static bool
|
|
http_process_headers(struct http_transaction *ta)
|
|
{
|
|
ta->from = -1;
|
|
ta->to = -1;
|
|
for (;;) {
|
|
size_t header_offset;
|
|
ssize_t len = bufio_readline(ta->client->bufio, &header_offset);
|
|
if (len <= 0)
|
|
return false;
|
|
|
|
char *header = bufio_offset2ptr(ta->client->bufio, header_offset);
|
|
if (len == 2 && STARTS_WITH(header, CRLF)) // empty CRLF
|
|
return true;
|
|
|
|
header[len-2] = '\0';
|
|
/* Each header field consists of a name followed by a
|
|
* colon (":") and the field value. Field names are
|
|
* case-insensitive. The field value MAY be preceded by
|
|
* any amount of LWS, though a single SP is preferred.
|
|
*/
|
|
char *endptr;
|
|
char *field_name = strtok_r(header, ":", &endptr);
|
|
if (field_name == NULL)
|
|
return false;
|
|
|
|
// skip white space
|
|
char *field_value = endptr;
|
|
while (*field_value == ' ' || *field_value == '\t')
|
|
field_value++;
|
|
|
|
// you may print the header like so
|
|
// printf("Header: %s: %s\n", field_name, field_value);
|
|
if (!strcasecmp(field_name, "Content-Length")) {
|
|
ta->req_content_len = atoi(field_value);
|
|
}
|
|
|
|
ta->valid_token = false;
|
|
/* Handle other headers here. Both field_value and field_name
|
|
* are zero-terminated strings.
|
|
*/
|
|
|
|
// Cookie header
|
|
if (!strcasecmp(field_name, "Cookie")) {
|
|
|
|
// Gets the cookies.
|
|
char* cookie2;
|
|
char* cookie1 = strtok_r(field_value, ";", &cookie2);
|
|
cookie2++;
|
|
|
|
// Stores the 2nd cookie.
|
|
ta->extra_cookie = cookie2;
|
|
|
|
// Determines if the 1st cookie is invalid.
|
|
if ((cookie1 == NULL) || (strlen(cookie1) <= 11))
|
|
{
|
|
ta->resp_status = HTTP_PERMISSION_DENIED;
|
|
return false;
|
|
}
|
|
|
|
// If not, find token.
|
|
ta->token = cookie1 + 11; // + 11 gets rid of "auth_token=" heading.
|
|
ta->valid_token = validate_token_exp(ta, ta->token);
|
|
}
|
|
|
|
// Range header
|
|
if (!strcasecmp(field_name, "Range")){
|
|
// Gets token.
|
|
char *endp;
|
|
char *token = strtok_r(field_value, "= ", &endp);
|
|
while (token != NULL && strcasecmp(token, "bytes")) {
|
|
token = strtok_r(NULL, "= ", &endp);
|
|
}
|
|
token = strtok_r(NULL, "= ", &endp);
|
|
|
|
// Determines if the token exist.
|
|
if (token != NULL) {
|
|
if (token[0] == '-') {
|
|
ta->to = atol(token + 1);
|
|
}
|
|
else if (token[strlen(token) - 1] == '-') {
|
|
token[strlen(token) - 1] = '\0';
|
|
ta->from = atol(token);
|
|
}
|
|
else {
|
|
char *to;
|
|
char *from = strtok_r(token, "- ", &to);
|
|
ta->from = atol(from);
|
|
ta->to = atol(to);
|
|
}
|
|
}
|
|
ta->range_request = true;
|
|
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
const int MAX_HEADER_LEN = 2048;
|
|
|
|
/* add a formatted header to the response buffer. */
|
|
void
|
|
http_add_header(buffer_t * resp, char* key, char* fmt, ...)
|
|
{
|
|
va_list ap;
|
|
|
|
buffer_appends(resp, key);
|
|
buffer_appends(resp, ": ");
|
|
|
|
va_start(ap, fmt);
|
|
char *error = buffer_ensure_capacity(resp, MAX_HEADER_LEN);
|
|
int len = vsnprintf(error, MAX_HEADER_LEN, fmt, ap);
|
|
resp->len += len > MAX_HEADER_LEN ? MAX_HEADER_LEN - 1 : len;
|
|
va_end(ap);
|
|
|
|
buffer_appends(resp, "\r\n");
|
|
}
|
|
|
|
/* add a content-length header. */
|
|
static void
|
|
add_content_length(buffer_t *res, size_t len)
|
|
{
|
|
http_add_header(res, "Content-Length", "%ld", len);
|
|
}
|
|
|
|
/* start the response by writing the first line of the response
|
|
* to the response buffer. Used in send_response_header */
|
|
static void
|
|
start_response(struct http_transaction * ta, buffer_t *res)
|
|
{
|
|
switch (ta->req_version) {
|
|
case HTTP_1_0:
|
|
buffer_appends(res, "HTTP/1.0 ");
|
|
break;
|
|
case HTTP_1_1:
|
|
default:
|
|
buffer_appends(res, "HTTP/1.1 ");
|
|
break;
|
|
}
|
|
|
|
switch (ta->resp_status) {
|
|
case HTTP_OK:
|
|
buffer_appends(res, "200 OK");
|
|
break;
|
|
case HTTP_PARTIAL_CONTENT:
|
|
buffer_appends(res, "206 Partial Content");
|
|
break;
|
|
case HTTP_BAD_REQUEST:
|
|
buffer_appends(res, "400 Bad Request");
|
|
break;
|
|
case HTTP_PERMISSION_DENIED:
|
|
buffer_appends(res, "403 Permission Denied");
|
|
break;
|
|
case HTTP_NOT_FOUND:
|
|
buffer_appends(res, "404 Not Found");
|
|
break;
|
|
case HTTP_METHOD_NOT_ALLOWED:
|
|
buffer_appends(res, "405 Method Not Allowed");
|
|
break;
|
|
case HTTP_REQUEST_TIMEOUT:
|
|
buffer_appends(res, "408 Request Timeout");
|
|
break;
|
|
case HTTP_REQUEST_TOO_LONG:
|
|
buffer_appends(res, "414 Request Too Long");
|
|
break;
|
|
case HTTP_NOT_IMPLEMENTED:
|
|
buffer_appends(res, "501 Not Implemented");
|
|
break;
|
|
case HTTP_SERVICE_UNAVAILABLE:
|
|
buffer_appends(res, "503 Service Unavailable");
|
|
break;
|
|
case HTTP_INTERNAL_ERROR:
|
|
default:
|
|
buffer_appends(res, "500 Internal Server Error");
|
|
break;
|
|
}
|
|
buffer_appends(res, CRLF);
|
|
}
|
|
|
|
/* Send response headers to client */
|
|
static bool
|
|
send_response_header(struct http_transaction *ta)
|
|
{
|
|
buffer_t response;
|
|
buffer_init(&response, 80);
|
|
|
|
start_response(ta, &response);
|
|
if (bufio_sendbuffer(ta->client->bufio, &response) == -1)
|
|
return false;
|
|
|
|
buffer_appends(&ta->resp_headers, CRLF);
|
|
if (bufio_sendbuffer(ta->client->bufio, &ta->resp_headers) == -1)
|
|
return false;
|
|
|
|
buffer_delete(&response);
|
|
return true;
|
|
}
|
|
|
|
/* Send a full response to client with the content in resp_body. */
|
|
static bool
|
|
send_response(struct http_transaction *ta)
|
|
{
|
|
// add content-length. All other headers must have already been set.
|
|
add_content_length(&ta->resp_headers, ta->resp_body.len);
|
|
|
|
if (!send_response_header(ta))
|
|
return false;
|
|
|
|
return bufio_sendbuffer(ta->client->bufio, &ta->resp_body) != -1;
|
|
}
|
|
|
|
const int MAX_ERROR_LEN = 2048;
|
|
|
|
/* Send an error response. */
|
|
static bool
|
|
send_error(struct http_transaction * ta, enum http_response_status status, const char *fmt, ...)
|
|
{
|
|
va_list ap;
|
|
|
|
va_start(ap, fmt);
|
|
char *error = buffer_ensure_capacity(&ta->resp_body, MAX_ERROR_LEN);
|
|
int len = vsnprintf(error, MAX_ERROR_LEN, fmt, ap);
|
|
ta->resp_body.len += len > MAX_ERROR_LEN ? MAX_ERROR_LEN - 1 : len;
|
|
va_end(ap);
|
|
ta->resp_status = status;
|
|
http_add_header(&ta->resp_headers, "Content-Type", "text/plain");
|
|
return send_response(ta);
|
|
}
|
|
|
|
/* Send Not Found response. */
|
|
static bool
|
|
send_not_found(struct http_transaction *ta)
|
|
{
|
|
return send_error(ta, HTTP_NOT_FOUND, "File %s not found",
|
|
bufio_offset2ptr(ta->client->bufio, ta->req_path));
|
|
}
|
|
|
|
/* A start at assigning an appropriate mime type. Real-world
|
|
* servers use more extensive lists such as /etc/mime.types
|
|
*/
|
|
static const char *
|
|
guess_mime_type(char *filename)
|
|
{
|
|
char *suffix = strrchr(filename, '.');
|
|
if (suffix == NULL)
|
|
return "text/plain";
|
|
|
|
if (!strcasecmp(suffix, ".html"))
|
|
return "text/html";
|
|
|
|
if (!strcasecmp(suffix, ".gif"))
|
|
return "image/gif";
|
|
|
|
if (!strcasecmp(suffix, ".png"))
|
|
return "image/png";
|
|
|
|
if (!strcasecmp(suffix, ".jpg"))
|
|
return "image/jpeg";
|
|
|
|
if (!strcasecmp(suffix, ".js"))
|
|
return "text/javascript";
|
|
|
|
if (!strcasecmp(suffix, ".mp4"))
|
|
return "video/mp4";
|
|
|
|
return "text/plain";
|
|
}
|
|
|
|
/* Check a token is not expired. */
|
|
bool validate_token_exp(struct http_transaction *ta, char* token) {
|
|
jwt_t* cookie;
|
|
|
|
// Decode token.
|
|
int rc = jwt_decode(&cookie, ta->token, (unsigned char *)NEVER_EMBED_A_SECRET_IN_CODE, strlen(NEVER_EMBED_A_SECRET_IN_CODE));
|
|
if (rc)
|
|
return false;
|
|
|
|
// Get claim (formatted grants).
|
|
char* grants = jwt_get_grants_json(cookie, NULL);
|
|
if (grants == NULL)
|
|
return false;
|
|
ta->grants = grants;
|
|
|
|
// Get expiration time.
|
|
json_error_t error;
|
|
json_t *jgrants = json_loadb(grants, strlen(grants), 0, &error);
|
|
json_int_t exp, iat;
|
|
const char *sub;
|
|
json_unpack(jgrants, "{s:I, s:I, s:s}", "exp", &exp, "iat", &iat, "sub", &sub);
|
|
|
|
// Check expiration time.
|
|
if (time(NULL) >= exp)
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
/* Handle HTTP transaction for static files. */
|
|
static bool
|
|
handle_static_asset(struct http_transaction *ta, char *basedir)
|
|
{
|
|
char fname[PATH_MAX];
|
|
|
|
char *req_path = bufio_offset2ptr(ta->client->bufio, ta->req_path);
|
|
// The code below is vulnerable to an attack. Can you see
|
|
// which? Fix it to avoid indirect object reference (IDOR) attacks.
|
|
snprintf(fname, sizeof fname, "%s%s", basedir, req_path);
|
|
|
|
char *endptr;
|
|
char *p = calloc(strlen(fname) + 1, sizeof(char));
|
|
memcpy(p, fname, strlen(fname) + 1);
|
|
char *dir = strtok_r(p, "/", &endptr);
|
|
dir = strtok_r(NULL, "/", &endptr); // initial ".." is okay
|
|
while (dir != NULL) {
|
|
if (!strcmp(dir, "..")) {
|
|
return send_not_found(ta);
|
|
}
|
|
dir = strtok_r(NULL, "/", &endptr);
|
|
}
|
|
|
|
if (!strcmp(req_path, "/")) {
|
|
memset(fname, 0, PATH_MAX);
|
|
snprintf(fname, sizeof fname, "%s%s", server_root, "/index.html");
|
|
}
|
|
|
|
|
|
if (access(fname, R_OK)) {
|
|
if (errno == EACCES)
|
|
return send_error(ta, HTTP_PERMISSION_DENIED, "Permission denied.");
|
|
else {
|
|
if (html5_fallback) {
|
|
memset(fname, 0, PATH_MAX);
|
|
snprintf(fname, sizeof fname, "%s%s", server_root, "/index.html");
|
|
} else {
|
|
return send_not_found(ta);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
// Determine file size
|
|
struct stat st;
|
|
int rc = stat(fname, &st);
|
|
if (rc == -1)
|
|
return send_error(ta, HTTP_INTERNAL_ERROR, "Could not stat file.");
|
|
|
|
int filefd = open(fname, O_RDONLY);
|
|
if (filefd == -1) {
|
|
return send_not_found(ta);
|
|
}
|
|
|
|
ta->resp_status = HTTP_OK;
|
|
http_add_header(&ta->resp_headers, "Content-Type", "%s", guess_mime_type(fname));
|
|
off_t from = 0, to = st.st_size - 1;
|
|
|
|
if (ta->range_request) {
|
|
if (ta->from >= 0) {
|
|
from = ta->from;
|
|
}
|
|
if (ta->to >= 0) {
|
|
to = ta->to;
|
|
}
|
|
}
|
|
|
|
|
|
off_t content_length = to + 1 - from;
|
|
add_content_length(&ta->resp_headers, content_length);
|
|
|
|
http_add_header(&ta->resp_headers, "Accept-Ranges", "bytes");
|
|
|
|
if (ta->range_request) {
|
|
ta->resp_status = HTTP_PARTIAL_CONTENT;
|
|
http_add_header(&ta->resp_headers, "Content-Range",
|
|
"bytes %ld-%ld/%ld", from, to, st.st_size);
|
|
}
|
|
|
|
|
|
bool success = send_response_header(ta);
|
|
if (!success)
|
|
goto out;
|
|
|
|
// sendfile may send fewer bytes than requested, hence the loop
|
|
while (success && from <= to)
|
|
success = bufio_sendfile(ta->client->bufio, filefd, &from, to + 1 - from) > 0;
|
|
|
|
out:
|
|
close(filefd);
|
|
return success;
|
|
}
|
|
|
|
/* Handles api/login POST */
|
|
static bool
|
|
post_handle_login(struct http_transaction *ta)
|
|
{
|
|
http_add_header(&ta->resp_headers, "Content-Type", "application/json");
|
|
char* req_body = bufio_offset2ptr(ta->client->bufio, ta->req_body);
|
|
|
|
// Gets the username and password.
|
|
req_body[ta->req_content_len] = '\0';
|
|
json_error_t error;
|
|
json_t *jgrants = json_loadb(req_body, strlen(req_body), 0, &error);
|
|
const char *username;
|
|
const char *password;
|
|
int rc = json_unpack(jgrants, "{s:s, s:s}", "username", &username, "password", &password);
|
|
if (rc == -1)
|
|
{
|
|
return send_error(ta, HTTP_PERMISSION_DENIED, "???");
|
|
}
|
|
|
|
// Authenticates the username and password.
|
|
if (!strcmp(username, "user0") && !strcmp(password, "thepassword"))
|
|
{
|
|
int iat = time(NULL);
|
|
int exp = iat + token_expiration_time;
|
|
// Create cookie.
|
|
jwt_t* cookie;
|
|
int rc = jwt_new(&cookie);
|
|
if (rc)
|
|
return send_error(ta, HTTP_INTERNAL_ERROR, "jwt_new");
|
|
|
|
// Add grants.
|
|
rc = jwt_add_grant(cookie, "sub", username);
|
|
if (rc)
|
|
return send_error(ta, HTTP_INTERNAL_ERROR, "jwt_add_grant");
|
|
|
|
//time_t age = time(NULL);
|
|
rc = jwt_add_grant_int(cookie, "iat", iat);
|
|
if (rc)
|
|
return send_error(ta, HTTP_INTERNAL_ERROR, "jwt_add_grant");
|
|
|
|
//time_t max_age = age + 3600 * 24;
|
|
rc = jwt_add_grant_int(cookie, "exp", exp);
|
|
if (rc)
|
|
return send_error(ta, HTTP_INTERNAL_ERROR, "jwt_add_grant");
|
|
|
|
// Set algorithm.
|
|
rc = jwt_set_alg(cookie, JWT_ALG_HS256, (unsigned char *)NEVER_EMBED_A_SECRET_IN_CODE, strlen(NEVER_EMBED_A_SECRET_IN_CODE));
|
|
if (rc)
|
|
return send_error(ta, HTTP_INTERNAL_ERROR, "jwt_set_alg");
|
|
|
|
// Create token.
|
|
char* token = jwt_encode_str(cookie);
|
|
if (token == NULL)
|
|
return send_error(ta, HTTP_INTERNAL_ERROR, "jwt_encode_str");
|
|
|
|
// Add Set-Cookie header.
|
|
http_add_header(&ta->resp_headers, "Set-Cookie", "auth_token=%s; Path=%s; Max-Age=%d; HttpOnly", token, "/", token_expiration_time);
|
|
|
|
// Get claim (formatted grants).
|
|
char *grants = jwt_get_grants_json(cookie, NULL);
|
|
buffer_appends(&ta->resp_body, grants);
|
|
|
|
// Send claim.
|
|
ta->resp_status = HTTP_OK;
|
|
return send_response(ta);
|
|
}
|
|
|
|
return send_error(ta, HTTP_PERMISSION_DENIED, "Permission denied");
|
|
}
|
|
|
|
/* Handles api/login GET */
|
|
static bool get_handle_login(struct http_transaction *ta) {
|
|
http_add_header(&ta->resp_headers, "Content-Type", "application/json");
|
|
|
|
// Determines if the token is invalid.
|
|
if (!ta->valid_token) {
|
|
buffer_appends(&ta->resp_body, "{}");
|
|
ta->resp_status = HTTP_OK;
|
|
return send_response(ta);
|
|
}
|
|
|
|
// If not, send response.
|
|
buffer_appends(&ta->resp_body, ta->grants);
|
|
ta->resp_status = HTTP_OK;
|
|
return send_response(ta);
|
|
}
|
|
|
|
/* Lists the .mp4 files in the given directory in the proper format. */
|
|
static char *list_videos(DIR* dir) {
|
|
|
|
json_t *list = json_array();
|
|
|
|
struct dirent *file;
|
|
while ((file = readdir(dir)) != NULL) {
|
|
char *suffix = strrchr(file->d_name, '.');
|
|
if (suffix != NULL && !strcmp(suffix, ".mp4")) {
|
|
|
|
// stat the file
|
|
char fname[PATH_MAX];
|
|
struct stat st;
|
|
snprintf(fname, sizeof(fname), "%s/%s", server_root, file->d_name);
|
|
int rc = stat(fname, &st);
|
|
if (rc == -1)
|
|
perror("Could not list videos.");
|
|
|
|
// json components
|
|
json_t *entry = json_object();
|
|
json_t *size = json_integer(st.st_size);
|
|
json_t *name = json_string(file->d_name);
|
|
rc = json_object_set(entry, "size", size);
|
|
if (rc)
|
|
perror("JSON append failed.");
|
|
rc = json_object_set(entry, "name", name);
|
|
if (rc)
|
|
perror("JSON append failed.");
|
|
|
|
// add entry
|
|
rc = json_array_append_new(list, entry);
|
|
if (rc)
|
|
perror("JSON append failed.");
|
|
}
|
|
}
|
|
|
|
return json_dumps(list, 0);
|
|
}
|
|
|
|
/* Determines what the API request is. */
|
|
static int val_api_url(struct http_transaction *ta) {
|
|
char *req_path = bufio_offset2ptr(ta->client->bufio, ta->req_path);
|
|
if (!strcmp(req_path, "/api/login")) {
|
|
return 0;
|
|
}
|
|
if (!strcmp(req_path, "/api/video")) {
|
|
return 1;
|
|
}
|
|
if (!strcmp(req_path, "/api/logout")) {
|
|
return 2;
|
|
}
|
|
return -1;
|
|
|
|
}
|
|
|
|
/* Handles API requests like login/logout/video. */
|
|
static bool
|
|
handle_api(struct http_transaction *ta)
|
|
{
|
|
int val = val_api_url(ta);
|
|
|
|
if (val == -1){
|
|
return send_not_found(ta);
|
|
}
|
|
|
|
// API login
|
|
if (val == 0) {
|
|
if (ta->req_method == HTTP_POST) {
|
|
// Handle login post
|
|
return post_handle_login(ta);
|
|
}
|
|
|
|
else if (ta->req_method == HTTP_GET){
|
|
// Handle login get
|
|
return get_handle_login(ta);
|
|
}
|
|
|
|
else{
|
|
return send_error(ta, HTTP_METHOD_NOT_ALLOWED, "Unknown method.\n");
|
|
}
|
|
|
|
}
|
|
// API video
|
|
if (val == 1) {
|
|
http_add_header(&ta->resp_headers, "Accept-Ranges", "bytes");
|
|
|
|
DIR* dir = opendir(server_root);
|
|
char *json = list_videos(dir); // handling
|
|
fprintf(stderr, "json: %s\n", json);
|
|
|
|
http_add_header(&ta->resp_headers, "Content-Type", "application/json");
|
|
|
|
// Ensures response can be sent and sends it.
|
|
char *body = buffer_ensure_capacity(&ta->resp_body, MAX_HEADER_LEN);
|
|
int len = snprintf(body, strlen(json) + 2, "%s\n", json);
|
|
int length = len > MAX_HEADER_LEN ? MAX_HEADER_LEN - 1 : len;
|
|
ta->resp_body.len += length;
|
|
ta->resp_status = HTTP_OK;
|
|
return send_response(ta);
|
|
}
|
|
// API logout
|
|
if (val == 2)
|
|
{
|
|
// Add Set-Cookie header.
|
|
http_add_header(&ta->resp_headers, "Set-Cookie", "auth_token=; Path=/; Max-Age=0; HttpOnly");
|
|
|
|
// Send message.
|
|
buffer_appends(&ta->resp_body, "{\"message\":\"logging out\"}");
|
|
ta->resp_status = HTTP_OK;
|
|
return send_response(ta);
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
/* Set up an http client, associating it with a bufio buffer. */
|
|
void
|
|
http_setup_client(struct http_client *self, struct bufio *bufio)
|
|
{
|
|
self->bufio = bufio;
|
|
}
|
|
|
|
/* Handles attempts to access private files. */
|
|
static bool
|
|
handle_private(struct http_transaction *ta)
|
|
{
|
|
// Determines if the token from the 1st cookie is valid.
|
|
if (ta->valid_token) {
|
|
ta->resp_status = HTTP_OK;
|
|
return handle_static_asset(ta, server_root);
|
|
}
|
|
|
|
// Determines if there's a 2nd cookie.
|
|
if ((ta->extra_cookie == NULL) || (strlen(ta->extra_cookie) <= 11)) {
|
|
return send_error(ta, HTTP_PERMISSION_DENIED, "Permission denied.");
|
|
}
|
|
|
|
// If so, find its token.
|
|
ta->token = ta->extra_cookie + 11; // + 11 gets rid of "auth_token=" heading.
|
|
ta->valid_token = validate_token_exp(ta, ta->token);
|
|
|
|
// valid
|
|
if (ta->valid_token) {
|
|
ta->resp_status = HTTP_OK;
|
|
return handle_static_asset(ta, server_root);
|
|
}
|
|
|
|
// invalid
|
|
return send_error(ta, HTTP_PERMISSION_DENIED, "Permission denied.");
|
|
}
|
|
|
|
/* Handle a single HTTP transaction. Returns true on success. */
|
|
bool
|
|
http_handle_transaction(struct http_client *self)
|
|
{
|
|
struct http_transaction ta;
|
|
memset(&ta, 0, sizeof ta);
|
|
ta.client = self;
|
|
|
|
if (!http_parse_request(&ta))
|
|
return false;
|
|
|
|
bool is_http_1_1 = false; // false
|
|
if (ta.req_version == HTTP_1_1)
|
|
is_http_1_1 = true; // true
|
|
|
|
if (!http_process_headers(&ta))
|
|
return false;
|
|
|
|
if (ta.req_content_len > 0) {
|
|
int rc = bufio_read(self->bufio, ta.req_content_len, &ta.req_body);
|
|
if (rc != ta.req_content_len)
|
|
return false;
|
|
|
|
// To see the body, use this:
|
|
// char *body = bufio_offset2ptr(ta.client->bufio, ta.req_body);
|
|
// hexdump(body, ta.req_content_len);
|
|
}
|
|
|
|
buffer_init(&ta.resp_headers, 1024);
|
|
http_add_header(&ta.resp_headers, "Server", "CS3214-Personal-Server");
|
|
buffer_init(&ta.resp_body, 0);
|
|
|
|
bool rc = false;
|
|
char *req_path = bufio_offset2ptr(ta.client->bufio, ta.req_path);
|
|
if (STARTS_WITH(req_path, "/api")) {
|
|
rc = handle_api(&ta);
|
|
} else
|
|
if (STARTS_WITH(req_path, "/private")) {
|
|
rc = handle_private(&ta);
|
|
} else {
|
|
rc = handle_static_asset(&ta, server_root);
|
|
}
|
|
|
|
buffer_delete(&ta.resp_headers);
|
|
buffer_delete(&ta.resp_body);
|
|
|
|
return is_http_1_1 && rc;
|
|
}
|