tiptorrent/src/handler.c

261 lines
5.6 KiB
C

#include "core.h"
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <syslog.h>
#include <sys/ioctl.h>
#include <netinet/in.h>
#include <ifaddrs.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <netinet/tcp.h>
#include <fcntl.h>
#include <time.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/sendfile.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <limits.h>
#include <errno.h>
static int tip_client_method_not_found(struct tip_client *cli)
{
/* To meet RFC 7231, this function MUST generate an Allow header field
* containing the correct methods. For example: "Allow: POST\r\n"
*/
char buf[] = "HTTP/1.1 405 Method Not Allowed\r\n"
"Content-Length: 0\r\n\r\n";
send(tip_client_socket(cli), buf, strlen(buf), 0);
return -1;
}
static int tip_client_file_not_found(struct tip_client *cli)
{
char buf[] = "HTTP/1.1 404 Not Found\r\n"
"Content-Length: 0\r\n\r\n";
send(tip_client_socket(cli), buf, strlen(buf), 0);
return -1;
}
static bool sanitize(const char *uri)
{
/* TODO: smarter sanitization. */
if (strstr(uri, ".."))
return false;
return true;
}
#define BLOCK 1024000
int tip_client_state_process_payload(struct tip_client *cli)
{
const char *trailer, *x_redirect;
bool allow_redirect = true;
char _uri[32], *uri = _uri;
char allow_redirect_str[5];
char path[PATH_MAX + 1];
char *chunk = NULL;
struct stat st;
int err;
/* syslog(LOG_DEBUG, "%s:rhu %.32s ...\n",
inet_ntoa(cli->addr.sin_addr),
ntohs(cli->addr.sin_port), cli->buf); */
if (!strncmp(cli->buf, "GET", strlen("GET"))) {
cli->method = TIP_METHOD_GET;
if (sscanf(cli->buf, "GET %31s HTTP/1.1", uri) != 1)
return tip_client_method_not_found(cli);
} else if (!strncmp(cli->buf, "HEAD", strlen("HEAD"))) {
cli->method = TIP_METHOD_HEAD;
if (sscanf(cli->buf, "HEAD %31s HTTP/1.1", uri) != 1)
return tip_client_method_not_found(cli);
} else if (!strncmp(cli->buf, "POST", strlen("POST"))) {
cli->method = TIP_METHOD_POST;
if (sscanf(cli->buf, "POST %31s HTTP/1.1", uri) != 1)
return tip_client_method_not_found(cli);
} else {
return tip_client_method_not_found(cli);
}
x_redirect = strstr(cli->buf, "X-Accept-Redirect: ");
if (x_redirect &&
sscanf(x_redirect, "X-Accept-Redirect: %4s", allow_redirect_str) == 1 &&
!strncmp(allow_redirect_str, "off", strlen("off")))
allow_redirect = false;
trailer = strstr(cli->buf, "\r\n\r\n");
if (!sanitize(uri))
return tip_client_method_not_found(cli);
/* skip initial / */
uri++;
switch (cli->method) {
case TIP_METHOD_GET:
case TIP_METHOD_POST:
/* get chunk number from file extension, e.g. FILE.0 */
chunk = strchr(uri, '.');
if (chunk) {
*chunk = '\0';
chunk++;
cli->chunk = atoi(chunk);
if (cli->chunk >= MAX_CHUNKS)
return tip_client_file_not_found(cli);
}
break;
case TIP_METHOD_HEAD:
break;
}
snprintf(path, PATH_MAX, "%s/%s", root, uri);
err = stat(path, &st);
if (err < 0)
return tip_client_file_not_found(cli);
/* restore the original uri that was mangled. */
if (chunk) {
chunk--;
*chunk = '.';
}
cli->uri = strdup(uri);
cli->path = strdup(path);
cli->size = st.st_size;
switch (cli->method) {
case TIP_METHOD_GET:
break;
case TIP_METHOD_HEAD:
cli->state = TIP_CLIENT_PROCESSING_REQUEST_2;
return 0;
case TIP_METHOD_POST:
cli->allow_redirect = true;
tip_client_redirect_create(cli);
tip_client_activate_pending(true);
cli->state = TIP_CLIENT_PROCESSING_REQUEST_2;
return 0;
}
if (tip_client_large_file(cli)) {
cli->allow_redirect = allow_redirect;
num_clients++;
if (num_clients > max_clients) {
if (!tip_client_redirect(cli)) {
tip_client_pending(cli);
return 1;
}
}
}
cli->state = TIP_CLIENT_PROCESSING_REQUEST_2;
return 0;
}
int tip_client_state_process_payload_reply(struct tip_client *cli)
{
uint64_t chunk_size;
off_t chunk_offset;
char buf[1024];
int fd;
if (cli->redirect) {
snprintf(buf, sizeof(buf),
"HTTP/1.1 301 Moves Permanently\r\nLocation: http://%s:%hu/%s\r\n\r\n",
inet_ntoa(cli->redirect_addr.sin_addr),
htons(cli->redirect_addr.sin_port), cli->uri);
send(tip_client_socket(cli), buf, strlen(buf), 0);
return 1;
}
fd = open(cli->path, O_RDONLY);
if (fd < 0)
return tip_client_file_not_found(cli);
switch (cli->method) {
case TIP_METHOD_GET:
if (cli->chunk < 0)
break;
chunk_size = cli->size / MAX_CHUNKS;
if (cli->size % MAX_CHUNKS) {
if (cli->chunk < MAX_CHUNKS - 1) {
chunk_size++;
chunk_offset = chunk_size * cli->chunk;
} else {
chunk_offset = chunk_size * cli->chunk;
chunk_offset += MAX_CHUNKS - 1;
chunk_size--;
}
} else {
chunk_offset = chunk_size * cli->chunk;
}
cli->size = chunk_size;
cli->offset = chunk_offset;
break;
case TIP_METHOD_POST:
cli->size = 0;
break;
case TIP_METHOD_HEAD:
break;
}
cli->left = cli->size;
snprintf(buf, sizeof(buf),
"HTTP/1.1 200 OK\r\nContent-Length: %lu\r\n\r\n",
cli->size);
send(tip_client_socket(cli), buf, strlen(buf), 0);
cli->fd = fd;
switch (cli->method) {
case TIP_METHOD_GET:
cli->state = TIP_CLIENT_PROCESSING_REQUEST_3;
break;
case TIP_METHOD_HEAD:
case TIP_METHOD_POST:
/* close connection. */
return 1;
}
return 0;
}
int tip_client_state_process_payload_bulk(struct tip_client *cli)
{
uint32_t bytes;
int ret;
if (cli->left < BLOCK)
bytes = cli->left;
else
bytes = BLOCK;
ret = sendfile(tip_client_socket(cli), cli->fd, &cli->offset, bytes);
if (ret < 0)
return -1;
cli->left -= ret;
if (cli->left <= 0) {
cli->state = TIP_CLIENT_CLOSE_WAIT;
return 1;
}
return 0;
}