#include <stdio.h>
#include <sys/file.h>
#include <sys/stat.h>
#include "service.h"
#include "objecttype.h"
#include "../util/io.h"
#include "../util/pblock.h"
#include "../util/util.h"
#include "../daemon/protocol.h"
#include "../daemon/vfs.h"
#include "../util/strbuf.h"
#include <cx/string.h>
#include <cx/utils.h>
#include <cx/printf.h>
#include <errno.h>
SYS_FILE prepare_service_file(Session *sn, Request *rq, VFSContext *vfs,
struct stat *s,
int *ret) {
char *path = pblock_findkeyval(pb_key_path, rq->vars);
SYS_FILE fd = vfs_open(vfs, path,
O_RDONLY);
if(!fd) {
*ret =
REQ_ABORTED;
return NULL;
}
if(vfs_fstat(vfs, fd, s) !=
0) {
protocol_status(sn, rq,
500,
NULL);
*ret =
REQ_ABORTED;
return NULL;
}
if(
S_ISDIR(s->st_mode)) {
pblock_removekey(pb_key_content_type, rq->srvhdrs);
char *uri = pblock_findkeyval(pb_key_uri, rq->reqpb);
size_t urilen = strlen(uri);
if(urilen >
0 && uri[urilen-
1] !=
'/') {
pblock_nvinsert(
"content-length",
"0", rq->srvhdrs);
char *location = pool_malloc(sn->pool, urilen +
2);
memcpy(location, uri, urilen);
location[urilen] =
'/';
location[urilen+
1] =
'\0';
pblock_kvinsert(pb_key_location, location, urilen +
1, rq->srvhdrs);
protocol_status(sn, rq,
302,
NULL);
http_start_response(sn, rq);
*ret =
REQ_PROCEED;
}
else {
pblock_kvinsert(
pb_key_content_type,
OBJTYPE_INTERNAL_DIRECTORY,
sizeof(
OBJTYPE_INTERNAL_DIRECTORY)-
1,
rq->srvhdrs);
*ret =
REQ_NOACTION;
}
vfs_close(fd);
return NULL;
}
const char *etag = vfs_getetag(fd);
if(http_set_finfo_etag(sn, rq, s, etag) !=
REQ_PROCEED) {
vfs_close(fd);
*ret =
REQ_ABORTED;
return NULL;
}
pblock_kvinsert(pb_key_accept_ranges,
"bytes",
5, rq->srvhdrs);
protocol_status(sn, rq,
200,
NULL);
return fd;
}
static void free_range(Session *sn, HttpRange *range) {
HttpRange *elm = range;
while(elm) {
HttpRange *next = elm->next;
pool_free(sn->pool, elm);
elm = next;
}
}
static HttpRange* parse_range(Session *sn,
char *header,
int *status) {
*status =
PROTOCOL_OK;
cxstring range = cx_strtrim(cx_str(header));
if(!cx_strprefix(range, (cxstring)
CX_STR(
"bytes="))) {
return NULL;
}
range = cx_strsubs(range,
6);
if(range.length <
1) {
return NULL;
}
HttpRange *range_list =
NULL;
HttpRange *last =
NULL;
off_t begin = -
1;
int start =
0;
int hasbegin =
0;
for(
int i=
0;i<=range.length;i++) {
char c = range.ptr[i];
if(c ==
'-') {
cxstring num = cx_strsubsl(range, start, i-start);
if(num.length ==
0) {
hasbegin =
1;
begin = -
1;
start = i+
1;
continue;
}
char *end;
errno =
0;
long long n = strtoll(num.ptr, &end,
10);
if(errno ==
0 && end == range.ptr + i && n >=
0) {
begin = n;
hasbegin =
1;
start = i+
1;
}
else {
free_range(sn, range_list);
return NULL;
}
}
else if(c ==
',' || c ==
'\0') {
cxstring num = cx_strsubsl(range, start, i-start);
if(hasbegin) {
long long n;
if(num.length ==
0) {
n = -
1;
}
else {
char *end;
errno =
0;
n = strtoll(num.ptr, &end,
10);
if(errno !=
0 || end != range.ptr + i || n <
0) {
free_range(sn, range_list);
return NULL;
}
}
if(!(begin <
0 && n <
0)) {
HttpRange *rangeelm = pool_malloc(sn->pool,
sizeof(HttpRange));
if(!rangeelm) {
free_range(sn, range_list);
*status =
PROTOCOL_SERVER_ERROR;
return NULL;
}
rangeelm->begin = begin;
rangeelm->end = n;
rangeelm->next =
NULL;
if(!last) {
range_list = rangeelm;
last = rangeelm;
}
else {
last->next = rangeelm;
last = rangeelm;
}
hasbegin =
0;
start = i+
1;
continue;
}
}
free_range(sn, range_list);
return NULL;
}
}
return range_list;
}
static int validate_range(HttpRange *range,
struct stat *finfo,
int *status) {
off_t max_len = finfo->st_size;
while(range) {
if(range->begin >
0 && range->end >
0) {
if(range->end < range->begin) {
*status =
PROTOCOL_REQUESTED_RANGE_NOT_SATISFIABLE;
return 0;
}
}
if(range->begin >= max_len) {
*status =
PROTOCOL_REQUESTED_RANGE_NOT_SATISFIABLE;
return 0;
}
if(range->end >= max_len) {
*status =
PROTOCOL_REQUESTED_RANGE_NOT_SATISFIABLE;
return 0;
}
range = range->next;
}
return 1;
}
static void range2off(HttpRange *range,
off_t filelen,
off_t *begin,
off_t *length) {
if(range->begin <
0) {
*begin = filelen - range->end;
*length = range->end;
}
else if(range->end <
0) {
*begin = range->begin;
*length = filelen - range->begin;
}
else {
*begin = range->begin;
*length = range->end +
1 - range->begin;
}
}
#define SF_MAX_LEN 0x8000000
static int send_range(Session *sn,
SYS_FILE fd,
off_t offset,
off_t length,
char *header,
int headerlen) {
off_t remaining = length;
sendfiledata sfd;
sfd.fd = fd;
sfd.header = header;
sfd.hlen = headerlen;
sfd.trailer =
NULL;
sfd.tlen =
0;
while(remaining >
0) {
size_t sflen = remaining <
SF_MAX_LEN ? remaining :
SF_MAX_LEN;
sfd.offset = offset;
sfd.len = sflen;
ssize_t r = net_sendfile(sn->csd, &sfd);
if(r <
0) {
return -
1;
}
sfd.header =
NULL;
offset += r;
remaining -= r;
}
return 0;
}
static void send_range_cleanup(AsyncSendRange *asr) {
WSBool error = asr->error;
Session *sn = asr->sn;
Request *rq = asr->rq;
pool_handle_t *pool = asr->sn->pool;
vfs_close(asr->in);
pool_free(pool, asr->aio->buf);
pool_free(pool, asr->aio);
pool_free(pool, asr->readev);
pool_free(pool, asr->writeev);
pool_free(pool, asr);
int ret =
REQ_PROCEED;
if(error) {
rq->rq_attr.keep_alive =
0;
ret =
REQ_ABORTED;
}
nsapi_function_return(sn, rq, ret);
}
static int send_buf(
SYS_NETFD out,
char *restrict buf,
size_t len,
size_t *restrict pos)
{
while(*pos < len) {
ssize_t w = net_write(out, buf + *pos, len - *pos);
if(w <=
0) {
return -
1;
}
*pos += w;
}
return 0;
}
static int send_bytes(AsyncSendRange *asr, WSBool *completed) {
*completed =
FALSE;
if(asr->header) {
if(send_buf(asr->out, asr->header, asr->headerlen, &asr->headerpos)) {
if(net_errno(asr->out) ==
EAGAIN) {
return 0;
}
else {
asr->error =
TRUE;
return 1;
}
}
if(asr->headerpos >= asr->headerlen) {
asr->header =
NULL;
}
}
if(send_buf(asr->out, asr->aio->buf, asr->aio->result, &asr->wpos)) {
if(net_errno(asr->out) ==
EAGAIN) {
return 0;
}
else {
asr->error =
TRUE;
return 1;
}
}
if(!asr->read_complete) {
asr->aio->offset += asr->aio->result;
size_t length = asr->end - asr->offset;
asr->aio->nbytes =
AIO_BUF_SIZE < length ?
AIO_BUF_SIZE : length;
asr->read_inprogress =
TRUE;
if(system_aio_read(asr->aio)) {
asr->error =
TRUE;
return 1;
}
}
*completed =
TRUE;
return 0;
}
static int send_range_readevent(EventHandler *ev, Event *event) {
AsyncSendRange *asr = event->cookie;
asr->read_inprogress =
FALSE;
asr->wpos =
0;
asr->offset += asr->aio->result;
if(asr->error || asr->aio->result <
0) {
return 0;
}
int ret =
1;
if(asr->aio->result ==
0 || asr->offset >= asr->end) {
asr->read_complete =
TRUE;
ret =
0;
}
WSBool completed;
if(send_bytes(asr, &completed)) {
return 0;
}
if(!completed && !asr->write_inprogress) {
asr->write_inprogress =
TRUE;
if(event_pollout(ev, asr->out, asr->writeev)) {
asr->error =
TRUE;
return 0;
}
}
return ret;
}
static int send_range_writeevent(EventHandler *ev, Event *event) {
AsyncSendRange *asr = event->cookie;
if(asr->error) {
return 1;
}
WSBool completed;
if(send_bytes(asr, &completed)) {
return 1;
}
if(completed) {
return 0;
}
return 1;
}
static int send_range_aio_finish(EventHandler *ev, Event *event) {
AsyncSendRange *asr = event->cookie;
if(!asr->write_inprogress) {
send_range_cleanup(asr);
}
asr->read_inprogress =
FALSE;
return 0;
}
static int send_range_poll_finish(EventHandler *ev, Event *event) {
AsyncSendRange *asr = event->cookie;
if(!asr->read_inprogress) {
send_range_cleanup(asr);
}
asr->write_inprogress =
FALSE;
return 0;
}
static int send_range_aio(Session *sn, Request *rq,
SYS_FILE fd,
off_t offset,
off_t length,
char *header,
int headerlen) {
net_setnonblock(sn->csd,
TRUE);
ssize_t hw = net_write(sn->csd, header, headerlen);
if(hw <
0) {
if(net_errno(sn->csd) ==
EAGAIN) {
hw =
0;
}
else {
return REQ_ABORTED;
}
}
AsyncSendRange *asr = pool_malloc(sn->pool,
sizeof(AsyncSendRange));
asr->sn = sn;
asr->rq = rq;
asr->in = fd;
asr->out = sn->csd;
asr->offset = offset;
asr->end = offset + length;
asr->pos = offset;
asr->read_complete =
FALSE;
asr->read_inprogress =
FALSE;
asr->write_inprogress =
FALSE;
asr->error =
FALSE;
if(hw == headerlen) {
asr->header =
NULL;
asr->headerlen =
0;
asr->headerpos =
0;
}
else {
asr->header = header;
asr->headerlen = headerlen;
asr->headerpos = hw;
}
Event *readev = pool_malloc(sn->pool,
sizeof(Event));
ZERO(readev,
sizeof(Event));
readev->cookie = asr;
readev->fn = send_range_readevent;
readev->finish = send_range_aio_finish;
Event *writeev = pool_malloc(sn->pool,
sizeof(Event));
ZERO(writeev,
sizeof(Event));
writeev->cookie = asr;
writeev->fn = send_range_writeevent;
writeev->finish = send_range_poll_finish;
asr->readev = readev;
asr->writeev = writeev;
aiocb_s *aio = pool_malloc(sn->pool,
sizeof(aiocb_s));
aio->buf = pool_malloc(sn->pool,
AIO_BUF_SIZE);
aio->nbytes =
AIO_BUF_SIZE < length ?
AIO_BUF_SIZE : length;
aio->filedes = fd;
aio->offset = offset;
aio->evhandler = sn->ev;
aio->event = readev;
asr->aio = aio;
asr->wpos =
0;
asr->read_inprogress =
TRUE;
if(system_aio_read(aio)) {
send_range_cleanup(asr);
return REQ_ABORTED;
}
asr->read_inprogress =
TRUE;
return REQ_PROCESSING;
}
struct multi_range_elm {
cxmutstr header;
off_t offset;
off_t length;
};
static int send_multi_range(Session *sn, Request *rq,
SYS_FILE fd,
off_t filelen, HttpRange *range) {
CxAllocator *a = pool_allocator(sn->pool);
pb_param *content_type = pblock_remove(
"content-type", rq->srvhdrs);
char sep[
64];
int seplen = util_mime_separator(sep);
cxmutstr newct = cx_asprintf_a(a,
"multipart/byteranges; boundary=%s", sep+
4);
pblock_kvinsert(
pb_key_content_type,
newct.ptr,
newct.length,
rq->srvhdrs);
cxFree(a, newct.ptr);
off_t response_len =
0;
int nrange =
0;
HttpRange *rangeelm = range;
while(rangeelm) {
nrange++;
rangeelm = rangeelm->next;
}
struct multi_range_elm *r = pool_calloc(sn->pool, nrange,
sizeof(
struct multi_range_elm));
rangeelm = range;
int i=
0;
while(rangeelm) {
range2off(rangeelm, filelen, &(r[i].offset), &(r[i].length));
r[i].header = cx_asprintf_a(
a,
"%s\r\nContent-Type: %s\r\nContent-Range: bytes %lld-%lld/%lld\r\n\r\n",
sep,
content_type->value,
(
long long)r[i].offset,
(
long long)r[i].offset+r[i].length -
1,
(
long long)filelen);
response_len += r[i].header.length + r[i].length;
rangeelm = rangeelm->next;
i++;
}
response_len += seplen +
4;
pblock_kllinsert(
pb_key_content_length,
(
long long)response_len,
rq->srvhdrs);
http_start_response(sn, rq);
rangeelm = range;
i =
0;
while(rangeelm) {
if(send_range(sn, fd, r[i].offset, r[i].length, r[i].header.ptr, r[i].header.length)) {
}
rangeelm = rangeelm->next;
i++;
}
net_printf(sn->csd,
"%s--\r\n", sep);
pool_free(sn->pool, r);
return 0;
}
int send_file(pblock *pb, Session *sn, Request *rq) {
int ret =
REQ_NOACTION;
struct stat s;
VFSContext *vfs = vfs_request_context(sn, rq);
SYS_FILE fd = prepare_service_file(sn, rq, vfs, &s, &ret);
if(!fd) {
return ret;
}
char *range_header = pblock_findkeyval(pb_key_range, rq->headers);
HttpRange *range =
NULL;
if(range_header) {
log_ereport(
LOG_DEBUG,
"send_file: range: %s", range_header);
int status;
range = parse_range(sn, range_header, &status);
if(status !=
PROTOCOL_OK) {
protocol_status(sn, rq, status,
NULL);
vfs_close(fd);
return REQ_ABORTED;
}
if(!validate_range(range, &s, &status)) {
protocol_status(sn, rq, status,
NULL);
free_range(sn, range);
vfs_close(fd);
return REQ_ABORTED;
}
}
int single_range =
1;
off_t offset;
off_t length;
if(range) {
protocol_status(sn, rq,
206,
NULL);
pblock_removekey(pb_key_content_length, rq->srvhdrs);
if(range->next) {
single_range =
0;
}
else {
range2off(range, s.st_size, &offset, &length);
pblock_kllinsert(
pb_key_content_length,
(
long long)length,
rq->srvhdrs);
cxmutstr content_range = cx_asprintf(
"%lld-%lld/%lld",
(
long long)offset,
(
long long)offset+length -
1,
(
long long)s.st_size);
pblock_kvinsert(
pb_key_content_range,
content_range.ptr,
content_range.length,
rq->srvhdrs);
free(content_range.ptr);
}
}
else {
offset =
0;
length = s.st_size;
}
if(single_range) {
http_start_response(sn, rq);
if(send_range(sn, fd, offset, length,
NULL,
0)) {
}
}
else {
ret = send_multi_range(sn, rq, fd, s.st_size, range);
}
vfs_close(fd);
free_range(sn, range);
return ret;
}
int service_hello(pblock *pb, Session *sn, Request *rq) {
pblock_removekey(pb_key_content_type, rq->srvhdrs);
pblock_nvinsert(
"content-type",
"text/plain", rq->srvhdrs);
pblock_nninsert(
"content-length",
13, rq->srvhdrs);
protocol_status(sn, rq,
200,
NULL);
http_start_response(sn, rq);
net_write(sn->csd,
"Hello World!\n",
13);
return REQ_PROCEED;
}
static int ws_msghandler(WebSocket *ws, WSMessage *msg) {
if(msg->type ==
1) {
printf(
"Message(text): %.*s\n", (
int)msg->length, msg->data);
websocket_send_text(ws->userdata,
"hello",
5);
}
else {
printf(
"Message: opcode: %d | length: %d\n", msg->type, (
int)msg->length);
}
return 0;
}
int service_ws_hello(pblock *pb, Session *sn, Request *rq) {
WebSocket ws;
ZERO(&ws,
sizeof(WebSocket));
ws.userdata = sn->csd;
ws.on_message = ws_msghandler;
return http_handle_websocket(sn, rq, &ws);
}
int service_index(pblock *pb, Session *sn, Request *rq) {
char *path = pblock_findkeyval(pb_key_path, rq->vars);
char *uri = pblock_findkeyval(pb_key_uri, rq->reqpb);
cxstring r_uri = cx_str(uri);
VFSContext *vfs = vfs_request_context(sn, rq);
VFS_DIR dir = vfs_opendir(vfs, path);
if(!dir) {
return REQ_ABORTED;
}
sbuf_t *out = sbuf_new(
1024);
sbuf_puts(out,
"<html>\n<head>\n<title>Index of ");
sbuf_puts(out, uri);
sbuf_puts(out,
"</title>\n</head><body>\n<h1>Index of ");
sbuf_puts(out, uri);
sbuf_puts(out,
"</h1><hr>\n\n");
VFS_ENTRY f;
while(vfs_readdir(dir, &f)) {
cxstring filename = cx_str(f.name);
sbuf_puts(out,
"<a href=\"");
sbuf_append(out, r_uri);
sbuf_append(out, filename);
sbuf_puts(out,
"\">");
sbuf_append(out, filename);
sbuf_puts(out,
"</a><br>\n");
}
sbuf_puts(out,
"\n</body>\n</html>\n");
pblock_removekey(pb_key_content_type, rq->srvhdrs);
pblock_kvinsert(pb_key_content_type,
"text/html",
9, rq->srvhdrs);
pblock_nninsert(
"content-length", out->length, rq->srvhdrs);
protocol_status(sn, rq,
200,
NULL);
http_start_response(sn, rq);
net_write(sn->csd, out->ptr, out->length);
vfs_closedir(dir);
sbuf_free(out);
return REQ_PROCEED;
}
int send_options(pblock *pb, Session *sn, Request *rq) {
char *allow =
"HEAD, GET, PUT, DELETE, TRACE, OPTIONS, MOVE, COPY, "
"PROPFIND, PROPPATCH, MKCOL, LOCK, UNLOCK, ACL, REPORT";
char *dav =
"1,2,access-control";
pblock_removekey(pb_key_content_type, rq->srvhdrs);
pblock_nvinsert(
"allow", allow, rq->srvhdrs);
pblock_nvinsert(
"dav", dav, rq->srvhdrs);
pblock_nninsert(
"content-length",
0, rq->srvhdrs);
protocol_status(sn, rq,
204,
NULL);
http_start_response(sn, rq);
return REQ_PROCEED;
}