261 lines
5.6 KiB
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;
|
|
}
|