Skip to content

Commit

Permalink
Support transfer of files up to 2 GB long
Browse files Browse the repository at this point in the history
  • Loading branch information
pascal-fb-martin committed Jun 2, 2020
1 parent 2bf02e1 commit 5298fee
Show file tree
Hide file tree
Showing 6 changed files with 114 additions and 46 deletions.
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ The arguments consumed by echttp_open are:
typedef const char *echttp_callback (const char *method, const char *uri,
const char *data, int length);
```
The profile for any HTTP request processing function.
The profile for any HTTP request processing function. The string returned contains the data to send back to the client; it must not be a local (stack) variable, since it is used after the callback returned.
```
int echttp_route_uri (const char *uri, echttp_callback *call);
```
Expand Down Expand Up @@ -135,6 +135,10 @@ void echttp_redirect (const char *url);
```
The HTTP response will return a redirect to the specified URL.
```
void echttp_transfer (int fd, int size);
```
Declare a file descriptor to transfer after the returned response. This function should be called from within an HTTP callback, while processing an HTTP request. Size defines how many bytes must be transferred from the file to the client. This transfer only happens after the HTTP preamble and the response string returned by the callback have been sent.
```
void echttp_islocal (void);
```
Return 1 if the HTTP client is on a local network, 0 otherwise. This is a crude protection mechanism that can be used to decide if private information should be concealed, or the command be rejected, with the assumption that local machine can be trusted. An application might also decide to not request user authentication if the client is on a local network. This function should be called from within an HTTP callback, while processing an HTTP request.
Expand Down
33 changes: 31 additions & 2 deletions echttp.c
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,12 @@ typedef struct {

int status;
const char *reason;

struct {
int fd;
int size;
} transfer;

} echttp_request;

static echttp_request *echttp_context = 0;
Expand Down Expand Up @@ -207,13 +213,17 @@ static void echttp_execute (int route, int client,
int i;
int keep;
char buffer[256];
int datalength;

echttp_current = &(echttp_context[client]);
echttp_current->client = client;
echttp_current->status = 200;
echttp_current->reason = "OK";
echttp_catalog_reset(&(echttp_current->out));

echttp_current->transfer.fd = -1;
echttp_current->transfer.size = 0;

if (echttp_routing.item[route].protect) {
echttp_routing.item[route].protect (action, uri);
}
Expand All @@ -226,7 +236,8 @@ static void echttp_execute (int route, int client,

data = echttp_routing.item[route].call (action, uri, data, length);
}
length = data?strlen(data):0;
datalength = data?strlen(data):0;
length = datalength + echttp_current->transfer.size;

snprintf (buffer, sizeof(buffer), "HTTP/1.1 %d %s\r\n",
echttp_current->status, echttp_current->reason);
Expand All @@ -250,7 +261,18 @@ static void echttp_execute (int route, int client,
}
echttp_raw_send (client, eol, sizeof(eol)-1, (data == 0 && keep == 0));
if (data != 0) {
echttp_raw_send (client, data, length, (keep == 0));
echttp_raw_send (client, data, datalength, (keep == 0));
}
if (echttp_current->transfer.size > 0) {
// This transfer must be submitted to the raw layer only after
// all the preamble was submitted. Otherwise the raw layer may
// start the file transfer before the HTTP preamble was sent..
//
echttp_raw_transfer (client,
echttp_current->transfer.fd,
echttp_current->transfer.size, (keep == 0));
echttp_current->transfer.fd = -1;
echttp_current->transfer.size = 0;
}

echttp_current = 0;
Expand Down Expand Up @@ -564,6 +586,13 @@ void echttp_content_type_css (void) {
echttp_catalog_set (&(echttp_current->out), "Content-Type", "text/css");
}

void echttp_transfer (int fd, int size) {
if (echttp_current->transfer.size <= 0) {
echttp_current->transfer.fd = fd;
echttp_current->transfer.size = size;
}
}

void echttp_error (int code, const char *message) {
echttp_current->status = code;
echttp_current->reason = message;
Expand Down
2 changes: 2 additions & 0 deletions echttp.h
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ void echttp_content_type_html (void);
void echttp_content_type_json (void);
void echttp_content_type_css (void);

void echttp_transfer (int fd, int size);

typedef void echttp_listener (int fd, int mode);
void echttp_listen (int fd, int mode, echttp_listener *listener, int premium);
void echttp_background (echttp_listener *listener);
Expand Down
79 changes: 63 additions & 16 deletions echttp_raw.c
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
#include <sys/select.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/sendfile.h>
#include <fcntl.h>
#include <arpa/inet.h>
#include <netdb.h>
Expand Down Expand Up @@ -75,7 +76,7 @@ static echttp_listener *echttp_raw_backgrounder = 0;
static int echttp_raw_serverport = 0;


#define ECHTTP_CLIENT_BUFFER 512000
#define ECHTTP_CLIENT_BUFFER 102400
typedef struct {
char data[ECHTTP_CLIENT_BUFFER];
int start;
Expand All @@ -90,6 +91,10 @@ static struct {
time_t deadline;
echttp_buffer in;
echttp_buffer out;
struct {
int fd;
int size;
} transfer;
} echttp_raw_client[ECHTTP_CLIENT_MAX];


Expand All @@ -110,6 +115,8 @@ static void echttp_raw_cleanup (int i) {
echttp_raw_client[i].out.end = 0;
echttp_raw_client[i].deadline = 0;
echttp_raw_client[i].hangup = 0;
echttp_raw_client[i].transfer.fd = -1;
echttp_raw_client[i].transfer.size = 0;
}

static const char *echttp_printip (long ip) {
Expand Down Expand Up @@ -268,9 +275,13 @@ int echttp_raw_open (const char *service, int debug) {
}

static void echttp_raw_close_client (int i, const char *reason) {

if (echttp_raw_client[i].socket >= 0) {
if (echttp_raw_debug) printf ("closing client %d: %s\n", i, reason);
close (echttp_raw_client[i].socket);
if (echttp_raw_client[i].transfer.size > 0) {
close (echttp_raw_client[i].transfer.fd);
}
echttp_raw_cleanup(i);
}
}
Expand All @@ -280,28 +291,56 @@ static int echttp_raw_consume (echttp_buffer *buffer, int length) {
if (buffer->start >= buffer->end) {
buffer->start = buffer->end = 0;
}
return buffer->start == 0;
return buffer->end == 0;
}

static void echttp_raw_transmit (int i) {

echttp_buffer *buffer = &(echttp_raw_client[i].out);

ssize_t length = buffer->end - buffer->start;
if (length > ETH_MAX_FRAME) length = ETH_MAX_FRAME;
if (length > 0) {

length = send (echttp_raw_client[i].socket,
buffer->data + buffer->start, (size_t)length, 0);
if (length <= 0) {
echttp_raw_close_client (i, strerror(errno));
return;
}
if (echttp_raw_debug) {
printf ("Transmit data at %d: %*.*s\n",
buffer->start, 0-length, length, buffer->data + buffer->start);
}
if (echttp_raw_consume (buffer, length)) {
echttp_raw_client[i].deadline = time(NULL) + 10;
if (length > ETH_MAX_FRAME) length = ETH_MAX_FRAME;

length = send (echttp_raw_client[i].socket,
buffer->data + buffer->start, (size_t)length, 0);
if (length <= 0) {
echttp_raw_close_client (i, strerror(errno));
return;
}
if (echttp_raw_debug) {
printf ("Transmit data at %d: %*.*s\n",
buffer->start, 0-length, length, buffer->data + buffer->start);
}
if (echttp_raw_consume (buffer, length)) {
if (echttp_raw_client[i].transfer.size <= 0) {
echttp_raw_client[i].deadline = time(NULL) + 10;
} else if (echttp_raw_debug) {
printf ("Initiating fiel transfer (%d bytes)\n",
echttp_raw_client[i].transfer.size);
}
}

} else if (echttp_raw_client[i].transfer.size > 0) {

length = echttp_raw_client[i].transfer.size;
if (length > ETH_MAX_FRAME) length = ETH_MAX_FRAME;

length = sendfile (echttp_raw_client[i].socket,
echttp_raw_client[i].transfer.fd, 0, length);
if (length <= 0) {
echttp_raw_close_client (i, strerror(errno));
return;
}
echttp_raw_client[i].transfer.size -= length;
if (echttp_raw_client[i].transfer.size <= 0) {
close (echttp_raw_client[i].transfer.fd);
echttp_raw_client[i].transfer.fd = -1;
echttp_raw_client[i].transfer.size = 0;

echttp_raw_client[i].deadline = time(NULL) + 10;
}
}
}

Expand Down Expand Up @@ -407,6 +446,13 @@ void echttp_raw_send (int client, const char *data, int length, int hangup) {
echttp_raw_client[client].hangup |= hangup;
}

void echttp_raw_transfer (int client, int fd, int length, int hangup) {
if (echttp_raw_invalid(client)) return;
echttp_raw_client[client].transfer.fd = fd;
echttp_raw_client[client].transfer.size = length;
echttp_raw_client[client].hangup |= hangup;
}

void echttp_raw_loop (echttp_raw_callback *received) {

struct timeval timeout;
Expand All @@ -425,7 +471,8 @@ void echttp_raw_loop (echttp_raw_callback *received) {
for (i = 0; i < ECHTTP_CLIENT_MAX; ++i) {
int socket = echttp_raw_client[i].socket;
if (socket >= 0) {
if (echttp_raw_client[i].out.end > 0) {
if (echttp_raw_client[i].out.end > 0 ||
echttp_raw_client[i].transfer.size > 0) {
FD_SET(socket, &writeset);
} else {
// Receive only after the previous response has been sent.
Expand Down
1 change: 1 addition & 0 deletions echttp_raw.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ int echttp_raw_open (const char *service, int debug);
int echttp_raw_server_port (int ip);

void echttp_raw_send (int client, const char *data, int length, int hangup);
void echttp_raw_transfer (int client, int fd, int length, int hangup);

void echttp_raw_register (int fd, int mode,
echttp_listener *listener, int premium);
Expand Down
39 changes: 12 additions & 27 deletions echttp_static.c
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,9 @@
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

#include "echttp.h"
#include "echttp_static.h"
Expand All @@ -45,9 +47,6 @@ static echttp_catalog echttp_static_roots;

static echttp_catalog echttp_static_type;

static char *echttp_static_buffer = 0;
static int echttp_static_buffer_size = 0;

/* Define default content type for the most frequent file extensions.
* Don't define too many: it would load the catalog with lot of unused items.
*/
Expand All @@ -71,42 +70,28 @@ static struct {
{0, 0}
};

static const char *echttp_static_file (FILE *page, const char *filename) {
static const char *echttp_static_file (int page, const char *filename) {

if (page == 0) {
if (page < 0) {
echttp_error (404, "Not found");
return "";
}
struct stat fileinfo;
if (fstat(fileno(page), &fileinfo) < 0) goto unsupported;
if (fstat(page, &fileinfo) < 0) goto unsupported;
if (fileinfo.st_mode & S_IFMT != S_IFREG) goto unsupported;
if (fileinfo.st_size < 0 ||
fileinfo.st_size >= 1024*1024*1024) goto unsupported;
if (fileinfo.st_size < 0) goto unsupported;

if (echttp_isdebug()) printf ("Serving static file: %s\n", filename);

size_t size = fileinfo.st_size + 1;
if (size > echttp_static_buffer_size) {
echttp_static_buffer_size = size;
echttp_static_buffer = realloc (echttp_static_buffer, (size_t)size);
if (echttp_static_buffer == 0) {
fprintf (stderr, "realloc failed for size %d\n", size);
exit(1);
}
}
echttp_static_buffer[0] = 0;
fread (echttp_static_buffer, 1, size, page);
echttp_static_buffer[size-1] = 0;
fclose (page);

const char *sep = strrchr (filename, '.');
if (sep) {
const char *content = echttp_catalog_get (&echttp_static_type, sep+1);
if (content) {
echttp_content_type_set (content);
}
}
return echttp_static_buffer;
echttp_transfer (page, fileinfo.st_size);
return "";

unsupported:
echttp_error (406, "Not Acceptable");
Expand Down Expand Up @@ -153,13 +138,13 @@ static const char *echttp_static_page (const char *action,
strncpy (filename+pathlen, uri+strlen(rooturi), sizeof(filename)-pathlen);
filename[sizeof(filename)-1] = 0;

return echttp_static_file (fopen (filename, "r"), filename);
return echttp_static_file (open (filename, O_RDONLY), filename);
}

static const char *echttp_static_root (const char *method, const char *uri,
const char *data, int length) {
char filename[1024];
FILE *page = 0;
int page = 0;
int i;

for (i = 1; i <= echttp_static_roots.count; ++i) {
Expand All @@ -169,8 +154,8 @@ static const char *echttp_static_root (const char *method, const char *uri,
filename[sizeof(filename)-1] = 0;
if (echttp_isdebug())
printf ("Trying static file %s\n", filename);
page = fopen (filename, "r");
if (page) break;
page = open (filename, O_RDONLY);
if (page >= 0) break;
}
return echttp_static_file (page, filename);
}
Expand Down

0 comments on commit 5298fee

Please sign in to comment.