qse/qse/lib/net/httpd_task.c
hyung-hwan a782229fa2 added qse_httpd_entasknph()
enhanced qse_pio_t with posix_spawn()
2012-02-01 14:32:34 +00:00

1862 lines
46 KiB
C

/*
* $Id$
*
Copyright 2006-2011 Chung, Hyung-Hwan.
This file is part of QSE.
QSE is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as
published by the Free Software Foundation, either version 3 of
the License, or (at your option) any later version.
QSE is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with QSE. If not, see <htrd://www.gnu.org/licenses/>.
*/
#if defined(_WIN32) || defined(__DOS__) || defined(__OS2__)
/* UNSUPPORTED YET.. */
/* TODO: IMPLEMENT THIS */
#else
#include "httpd.h"
#include "../cmn/mem.h"
#include <qse/cmn/str.h>
#include <qse/cmn/chr.h>
#include <qse/cmn/pio.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdarg.h>
#include <stdio.h>
#include <dirent.h>
#define MAX_SEND_SIZE 4096
#if defined(HAVE_SYS_SENDFILE_H)
# include <sys/sendfile.h>
#endif
#if defined(HAVE_SENDFILE) && defined(HAVE_SENDFILE64)
# if !defined(_LP64) && (QSE_SIZEOF_VOID_P<8) && defined(HAVE_SENDFILE64)
# define xsendfile sendfile64
# else
# define xsendfile sendfile
# endif
#elif defined(HAVE_SENDFILE)
# define xsendfile sendfile
#elif defined(HAVE_SENDFILE64)
# define xsendfile sendfile64
#elif defined(HAVE_SENDFILEV) || defined(HAVE_SENDFILEV64)
static qse_ssize_t xsendfile (
int out_fd, int in_fd, qse_foff_t* offset, qse_size_t count)
{
#if !defined(_LP64) && (QSE_SIZEOF_VOID_P<8) && defined(HAVE_SENDFILE64)
struct sendfilevec64 vec;
#else
struct sendfilevec vec;
#endif
size_t xfer;
ssize_t n;
vec.sfv_fd = in_fd;
vec.sfv_flag = 0;
if (offset)
{
vec.sfv_off = *offset;
}
else
{
vec.sfv_off = lseek (in_fd, 0, SEEK_CUR); /* TODO: lseek64 or llseek.. */
if (vec.sfv_off == (off_t)-1) return (qse_ssize_t)-1;
}
vec.sfv_len = count;
#if !defined(_LP64) && (QSE_SIZEOF_VOID_P<8) && defined(HAVE_SENDFILE64)
n = sendfilev64 (out_fd, &vec, 1, &xfer);
#else
n = sendfilev (out_fd, &vec, 1, &xfer);
#endif
if (offset) *offset = *offset + xfer;
/* TODO: xfer contains number of byte written even on failure
on success xfer == n.
on failure xfer != n.
*/
return n;
}
#else
static qse_ssize_t xsendfile (
int out_fd, int in_fd, qse_foff_t* offset, qse_size_t count)
{
qse_mchar_t buf[MAX_SEND_SIZE];
qse_ssize_t n;
if (offset && lseek (in_fd, *offset, SEEK_SET) != *offset) //* 64bit version of lseek...
return (qse_ssize_t)-1;
if (count > QSE_COUNTOF(buf)) count = QSE_COUNTOF(buf);
n = read (in_fd, buf, count);
if (n == (qse_ssize_t)-1 || n == 0) return n;
n = send (out_fd, buf, n, 0);
if (n > 0 && offset) *offset = *offset + n;
return n;
}
#endif
/*------------------------------------------------------------------------*/
static int task_main_disconnect (
qse_httpd_t* httpd, qse_httpd_client_t* client, qse_httpd_task_t* task)
{
shutdown (client->handle.i, SHUT_RDWR);
return 0;
}
qse_httpd_task_t* qse_httpd_entaskdisconnect (
qse_httpd_t* httpd,
qse_httpd_client_t* client,
const qse_httpd_task_t* pred)
{
qse_httpd_task_t task;
QSE_MEMSET (&task, 0, QSE_SIZEOF(task));
task.main = task_main_disconnect;
return qse_httpd_entask (httpd, client, pred, &task, 0);
}
/*------------------------------------------------------------------------*/
static int task_main_statictext (
qse_httpd_t* httpd, qse_httpd_client_t* client, qse_httpd_task_t* task)
{
qse_ssize_t n;
qse_size_t count = 0;
const qse_mchar_t* ptr = (const qse_mchar_t*)task->ctx;
while (*ptr != QSE_MT('\0') && count < MAX_SEND_SIZE)
{
ptr++; count++;
}
/* TODO: do i need to add code to skip this send if count is 0? */
n = send (
client->handle.i,
task->ctx,
count,
0
);
if (n <= -1) return -1;
ptr = (const qse_mchar_t*)task->ctx + n;
if (*ptr == QSE_MT('\0')) return 0;
task->ctx = (void*)ptr;
return 1; /* more work to do */
}
qse_httpd_task_t* qse_httpd_entaskstatictext (
qse_httpd_t* httpd,
qse_httpd_client_t* client,
const qse_httpd_task_t* pred,
const qse_mchar_t* text)
{
qse_httpd_task_t task;
QSE_MEMSET (&task, 0, QSE_SIZEOF(task));
task.main = task_main_statictext;
task.ctx = (void*)text;
return qse_httpd_entask (httpd, client, pred, &task, 0);
}
/*------------------------------------------------------------------------*/
typedef struct task_text_t task_text_t;
struct task_text_t
{
const qse_mchar_t* ptr;
qse_size_t left;
};
static int task_init_text (
qse_httpd_t* httpd, qse_httpd_client_t* client, qse_httpd_task_t* task)
{
task_text_t* xtn = qse_httpd_gettaskxtn (httpd, task);
QSE_MEMCPY (xtn, task->ctx, QSE_SIZEOF(*xtn));
QSE_MEMCPY (xtn + 1, xtn->ptr, xtn->left);
xtn->ptr = (qse_mchar_t*)(xtn + 1);
task->ctx = xtn;
return 0;
}
static int task_main_text (
qse_httpd_t* httpd, qse_httpd_client_t* client, qse_httpd_task_t* task)
{
qse_ssize_t n;
qse_size_t count;
task_text_t* ctx = (task_text_t*)task->ctx;
count = MAX_SEND_SIZE;
if (count >= ctx->left) count = ctx->left;
/* TODO: do i need to add code to skip this send if count is 0? */
n = send (
client->handle.i,
ctx->ptr,
count,
0
);
if (n <= -1) return -1;
ctx->left -= n;
if (ctx->left <= 0) return 0;
ctx->ptr += n;
return 1; /* more work to do */
}
qse_httpd_task_t* qse_httpd_entasktext (
qse_httpd_t* httpd,
qse_httpd_client_t* client,
const qse_httpd_task_t* pred,
const qse_mchar_t* text)
{
qse_httpd_task_t task;
task_text_t data;
QSE_MEMSET (&data, 0, QSE_SIZEOF(data));
data.ptr = text;
data.left = qse_mbslen(text);
QSE_MEMSET (&task, 0, QSE_SIZEOF(task));
task.init = task_init_text;
task.main = task_main_text;
task.ctx = &data;
return qse_httpd_entask (
httpd, client, pred, &task, QSE_SIZEOF(data) + data.left);
}
/*------------------------------------------------------------------------*/
/* TODO: send wide character string when QSE_CHAR_IS_WCHAR */
/*------------------------------------------------------------------------*/
typedef struct task_format_t task_format_t;
struct task_format_t
{
qse_mchar_t* org;
const qse_mchar_t* ptr;
qse_size_t left;
};
static int task_init_format (
qse_httpd_t* httpd, qse_httpd_client_t* client, qse_httpd_task_t* task)
{
task_format_t* xtn = qse_httpd_gettaskxtn (httpd, task);
QSE_MEMCPY (xtn, task->ctx, QSE_SIZEOF(*xtn));
task->ctx = xtn;
return 0;
}
static void task_fini_format (
qse_httpd_t* httpd, qse_httpd_client_t* client, qse_httpd_task_t* task)
{
task_format_t* ctx = (task_format_t*)task->ctx;
qse_httpd_freemem (httpd, ctx->org);
}
static int task_main_format (
qse_httpd_t* httpd, qse_httpd_client_t* client, qse_httpd_task_t* task)
{
qse_ssize_t n;
qse_size_t count;
task_format_t* ctx = (task_format_t*)task->ctx;
count = MAX_SEND_SIZE;
if (count >= ctx->left) count = ctx->left;
n = send (
client->handle.i,
ctx->ptr,
count,
0
);
if (n <= -1) return -1;
ctx->left -= n;
if (ctx->left <= 0) return 0;
ctx->ptr += n;
return 1; /* more work to do */
}
qse_httpd_task_t* qse_httpd_entaskformat (
qse_httpd_t* httpd,
qse_httpd_client_t* client,
const qse_httpd_task_t* pred,
const qse_mchar_t* fmt, ...)
{
qse_httpd_task_t task;
task_format_t data;
va_list ap;
qse_mchar_t n[2];
qse_mchar_t* buf;
int bytes_req, l;
va_start (ap, fmt);
#if defined(_WIN32) && defined(_MSC_VER)
bytes_req = _vsnprintf (n, 1, fmt, ap);
#else
bytes_req = vsnprintf (n, 1, fmt, ap);
#endif
va_end (ap);
if (bytes_req == -1)
{
qse_size_t capa = 256;
buf = (qse_mchar_t*) qse_httpd_allocmem (httpd, (capa + 1) * QSE_SIZEOF(*buf));
if (buf == QSE_NULL) return QSE_NULL;
/* an old vsnprintf behaves differently from C99 standard.
* thus, it returns -1 when it can't write all the input given. */
for (;;)
{
va_start (ap, fmt);
#if defined(_WIN32) && defined(_MSC_VER)
l = _vsnprintf (buf, capa + 1, fmt, ap);
#else
l = vsnprintf (buf, capa + 1, fmt, ap);
#endif
va_end (ap);
if (l == -1)
{
qse_httpd_freemem (httpd, buf);
capa = capa * 2;
buf = (qse_mchar_t*) qse_httpd_allocmem (httpd, (capa + 1) * QSE_SIZEOF(*buf));
if (buf == QSE_NULL) return QSE_NULL;
}
else break;
}
}
else
{
/* vsnprintf returns the number of characters that would
* have been written not including the terminating '\0'
* if the _data buffer were large enough */
buf = (qse_mchar_t*) qse_httpd_allocmem (httpd, (bytes_req + 1) * QSE_SIZEOF(*buf));
if (buf == QSE_NULL) return QSE_NULL;
va_start (ap, fmt);
#if defined(_WIN32) && defined(_MSC_VER)
l = _vsnprintf (buf, bytes_req + 1, fmt, ap);
#else
l = vsnprintf (buf, bytes_req + 1, fmt, ap);
#endif
va_end (ap);
if (l != bytes_req)
{
/* something got wrong ... */
qse_httpd_freemem (httpd, buf);
httpd->errnum = QSE_HTTPD_EINTERN;
return QSE_NULL;
}
}
QSE_MEMSET (&data, 0, QSE_SIZEOF(data));
data.org = buf;
data.ptr = buf;
data.left = l;
QSE_MEMSET (&task, 0, QSE_SIZEOF(task));
task.init = task_init_format;
task.fini = task_fini_format;
task.main = task_main_format;
task.ctx = &data;
qse_printf (QSE_T("SEND: [%.*S]\n"), (int)l, buf);
return qse_httpd_entask (
httpd, client, pred, &task, QSE_SIZEOF(data));
}
/* TODO: send wide character string when QSE_CHAR_IS_WCHAR */
/*------------------------------------------------------------------------*/
static qse_httpd_task_t* entask_error (
qse_httpd_t* httpd, qse_httpd_client_t* client,
const qse_httpd_task_t* task, int code,
const qse_http_version_t* version, int keepalive)
{
const qse_mchar_t* smsg;
const qse_mchar_t* lmsg;
switch (code)
{
case 403:
smsg = QSE_MT("Forbidden");
lmsg = QSE_MT("<html><head><title>Forbidden</title></head><body><b>FORBIDDEN<b></body></html>");
break;
case 404:
smsg = QSE_MT("Not Found");
lmsg = QSE_MT("<html><head><title>Not Found</title></head><body><b>REQUESTED PATH NOT FOUND<b></body></html>");
break;
case 405:
smsg = QSE_MT("Method Not Allowed");
lmsg = QSE_MT("<html><head><title>Method Not Allowed</title></head><body><b>REQUESTED METHOD NOT ALLOWED<b></body></html>");
break;
case 416:
smsg = QSE_MT("Requested Range Not Satisfiable");
lmsg = QSE_MT("<html><head><title>Requested Range Not Satsfiable</title></head><body><b>REQUESTED RANGE NOT SATISFIABLE<b></body></html>");
break;
case 500:
smsg = QSE_MT("Internal Server Error");
lmsg = QSE_MT("<html><head><title>Internal Server Error</title></head><body><b>INTERNAL SERVER ERROR<b></body></html>");
break;
case 501:
smsg = QSE_MT("Not Implemented");
lmsg = QSE_MT("<html><head><title>Not Implemented</title></head><body><b>NOT IMPLEMENTED<b></body></html>");
break;
case 502:
smsg = QSE_MT("Bad Gateway");
lmsg = QSE_MT("<html><head><title>Bad Gateway</title></head><body><b>BAD GATEWAY<b></body></html>");
break;
case 503:
smsg = QSE_MT("Service Unavailable");
lmsg = QSE_MT("<html><head><title>Service Unavailable</title></head><body><b>SERVICE UNAVAILABLE<b></body></html>");
break;
default:
smsg = QSE_MT("Unknown");
lmsg = QSE_MT("<html><head><title>Unknown Error</title></head><body><b>UNKNOWN ERROR<b></body></html>");
break;
}
return qse_httpd_entaskformat (
httpd, client, task,
QSE_MT("HTTP/%d.%d %d %s\r\nConnection: %s\r\nContent-Type: text/html;charset=utf-8\r\nContent-Length: %d\r\n\r\n%s\r\n\r\n"),
version->major, version->minor, code, smsg,
(keepalive? QSE_MT("keep-alive"): QSE_MT("close")),
(int)qse_mbslen(lmsg) + 4, lmsg
);
}
qse_httpd_task_t* qse_httpd_entaskerror (
qse_httpd_t* httpd, qse_httpd_client_t* client,
const qse_httpd_task_t* task, int code, const qse_htre_t* req)
{
return entask_error (httpd, client, task, code, qse_htre_getversion(req), req->attr.keepalive);
}
/*------------------------------------------------------------------------*/
typedef struct task_file_t task_file_t;
struct task_file_t
{
qse_ubi_t handle;
qse_foff_t left;
qse_foff_t offset;
};
static int task_init_file (
qse_httpd_t* httpd, qse_httpd_client_t* client, qse_httpd_task_t* task)
{
task_file_t* xtn = qse_httpd_gettaskxtn (httpd, task);
QSE_MEMCPY (xtn, task->ctx, QSE_SIZEOF(*xtn));
task->ctx = xtn;
return 0;
}
static void task_fini_file (
qse_httpd_t* httpd, qse_httpd_client_t* client, qse_httpd_task_t* task)
{
task_file_t* ctx = (task_file_t*)task->ctx;
close (ctx->handle.i);
}
static int task_main_file (
qse_httpd_t* httpd, qse_httpd_client_t* client, qse_httpd_task_t* task)
{
qse_ssize_t n;
qse_size_t count;
task_file_t* ctx = (task_file_t*)task->ctx;
count = MAX_SEND_SIZE;
if (count >= ctx->left) count = ctx->left;
/* TODO: more adjustment needed for OS with different sendfile semantics... */
n = xsendfile (
client->handle.i,
ctx->handle.i,
&ctx->offset,
count
);
if (n <= -1)
{
// HANDLE EGAIN specially???
return -1; /* TODO: any logging */
}
if (n == 0 && count > 0)
{
/* The file could be truncated when this condition is set.
* The content-length sent in the header can't be fulfilled.
* So let's return an error here so that the main loop abort
* the connection. */
/* TODO: any logging....??? */
return -1;
}
ctx->left -= n;
if (ctx->left <= 0) return 0;
return 1; /* more work to do */
}
qse_httpd_task_t* qse_httpd_entaskfile (
qse_httpd_t* httpd,
qse_httpd_client_t* client,
const qse_httpd_task_t* pred,
qse_ubi_t handle,
qse_foff_t offset,
qse_foff_t size)
{
qse_httpd_task_t task;
task_file_t data;
qse_printf (QSE_T("Debug: sending file to %d\n"), client->handle.i);
QSE_MEMSET (&data, 0, QSE_SIZEOF(data));
data.handle = handle;
data.offset = offset;
data.left = size;
QSE_MEMSET (&task, 0, QSE_SIZEOF(task));
task.init = task_init_file;
task.main = task_main_file;
task.fini = task_fini_file;
task.ctx = &data;
return qse_httpd_entask (httpd, client, pred, &task, QSE_SIZEOF(data));
}
/*------------------------------------------------------------------------*/
typedef struct task_dir_t task_dir_t;
struct task_dir_t
{
qse_ubi_t handle;
int header_added;
int footer_pending;
struct dirent* dent;
qse_mchar_t buf[512];
qse_size_t bufpos;
qse_size_t buflen;
qse_size_t bufrem;
qse_size_t chunklen;
};
static int task_init_dir (
qse_httpd_t* httpd, qse_httpd_client_t* client, qse_httpd_task_t* task)
{
task_dir_t* xtn = qse_httpd_gettaskxtn (httpd, task);
QSE_MEMSET (xtn, 0, QSE_SIZEOF(*xtn));
xtn->handle = *(qse_ubi_t*)task->ctx;
task->ctx = xtn;
return 0;
}
static void task_fini_dir (
qse_httpd_t* httpd, qse_httpd_client_t* client, qse_httpd_task_t* task)
{
task_dir_t* ctx = (task_dir_t*)task->ctx;
closedir (ctx->handle.ptr);
}
static int task_main_dir (
qse_httpd_t* httpd, qse_httpd_client_t* client, qse_httpd_task_t* task)
{
task_dir_t* ctx = (task_dir_t*)task->ctx;
qse_ssize_t n;
int x;
if (ctx->bufpos < ctx->buflen) goto send_dirlist;
/* the buffer size is fixed to QSE_COUNTOF(ctx->buf).
* the number of digits need to hold the the size converted to
* a hexadecimal notation is roughly (log16(QSE_COUNTOF(ctx->buf) + 1).
* it should be safter to use ceil(log16(QSE_COUNTOF(ctx->buf)) + 1
* for precision issues.
*
* 16**X = QSE_COUNTOF(ctx->buf).
* X = log16(QSE_COUNTOF(ctx->buf).
* X + 1 is a required number of digits.
*
* Since log16 is not provided, we should use a natural log function
* whose base is the constant e (2.718).
*
* log16(n) = log(n) / log(16)
*
* The final fomula is here.
*
* X = ceil((log(QSE_COUNTOF(ctx->buf)) / log(16))) + 1;
*
* However, i won't use these floating-point opertions.
* instead i'll reserve a hardcoded size. so when you change
* the size of the buffer arrray, you should check this size.
*/
#define SIZE_CHLEN 4
#define SIZE_CHLENCRLF 2
#define SIZE_CHENDCRLF 2
/* reserve space to fill with the chunk length
* 4 for the actual chunk length and +2 for \r\n */
ctx->buflen = SIZE_CHLEN + SIZE_CHLENCRLF;
/* free space remaing in the buffer for the chunk data */
ctx->bufrem = QSE_COUNTOF(ctx->buf) - ctx->buflen - SIZE_CHENDCRLF;
if (ctx->footer_pending)
{
x = snprintf (
&ctx->buf[ctx->buflen],
ctx->bufrem,
QSE_MT("</ul></body></html>\r\n0\r\n"));
if (x == -1 || x >= ctx->bufrem)
{
/* return an error if the buffer is too small to hold the
* trailing footer. you need to increate the buffer size */
return -1;
}
ctx->buflen += x;
ctx->chunklen = ctx->buflen - 5; /* -5 for \r\n0\r\n added above */
/* CHENDCRLF */
ctx->buf[ctx->buflen++] = QSE_MT('\r');
ctx->buf[ctx->buflen++] = QSE_MT('\n');
goto set_chunklen;
}
if (!ctx->header_added)
{
/* compose the header since this is the first time. */
/* TODO: get the actual path ... and use it in the body or title. */
x = snprintf (
&ctx->buf[ctx->buflen],
ctx->bufrem,
QSE_MT("<html><head><title>Directory Listing</title></head><body>index of xxxx<ul>")
);
if (x == -1 || x >= ctx->bufrem)
{
/* return an error if the buffer is too small to hold the header.
* you need to increate the buffer size */
return -1;
}
ctx->buflen += x;
ctx->bufrem -= x;
ctx->header_added = 1;
}
if (ctx->dent == QSE_NULL)
ctx->dent = readdir (ctx->handle.ptr);
do
{
if (ctx->dent == QSE_NULL)
{
// TODO: check if errno has changed from before readdir().
// and return -1 if so.
x = snprintf (
&ctx->buf[ctx->buflen],
ctx->bufrem,
QSE_MT("</ul></body></html>\r\n0\r\n"));
if (x == -1 || x >= ctx->bufrem)
{
ctx->footer_pending = 1;
ctx->chunklen = ctx->buflen;
/* CHENDCRLF */
ctx->buf[ctx->buflen++] = QSE_MT('\r');
ctx->buf[ctx->buflen++] = QSE_MT('\n');
}
else
{
ctx->buflen += x;
ctx->chunklen = ctx->buflen - 5;
/* CHENDCRLF */
ctx->buf[ctx->buflen++] = QSE_MT('\r');
ctx->buf[ctx->buflen++] = QSE_MT('\n');
}
break;
}
else
{
x = snprintf (
&ctx->buf[ctx->buflen],
ctx->bufrem,
QSE_MT("<li><a href='%s%s'>%s%s</a></li>"),
ctx->dent->d_name,
(ctx->dent->d_type == DT_DIR? QSE_MT("/"): QSE_MT("")),
ctx->dent->d_name,
(ctx->dent->d_type == DT_DIR? QSE_MT("/"): QSE_MT(""))
);
if (x == -1 || x >= ctx->bufrem)
{
/* buffer not large enough to hold this entry */
ctx->chunklen = ctx->buflen;
/* CHENDCRLF */
ctx->buf[ctx->buflen++] = QSE_MT('\r');
ctx->buf[ctx->buflen++] = QSE_MT('\n');
break;
}
else
{
ctx->buflen += x;
ctx->bufrem -= x;
}
}
ctx->dent = readdir (ctx->handle.ptr);
}
while (1);
set_chunklen:
/* right alignment with space padding on the left */
x = snprintf (
ctx->buf, (SIZE_CHLEN + SIZE_CHLENCRLF) - 1,
QSE_MT("%*lX"), (int)(SIZE_CHLEN + SIZE_CHLENCRLF - 2),
(unsigned long)(ctx->chunklen - (SIZE_CHLEN + SIZE_CHLENCRLF)));
/* CHLENCRLF */
ctx->buf[x] = QSE_MT('\r');
ctx->buf[x+1] = QSE_MT('\n');
/* skip leading space padding */
for (x = 0; ctx->buf[x] == QSE_MT(' '); x++) ctx->buflen--;
ctx->bufpos = x;
send_dirlist:
n = send (client->handle.i, &ctx->buf[ctx->bufpos], ctx->buflen, 0);
if (n <= -1) return -1;
/* NOTE if (n == 0), it will enter an infinite loop */
ctx->bufpos += n;
ctx->buflen -= n;
return (ctx->bufpos < ctx->buflen || ctx->footer_pending || ctx->dent)? 1: 0;
}
static int task_main_dir_nochunk (
qse_httpd_t* httpd, qse_httpd_client_t* client, qse_httpd_task_t* task)
{
task_dir_t* ctx = (task_dir_t*)task->ctx;
qse_ssize_t n;
int x;
if (ctx->bufpos < ctx->buflen) goto send_dirlist;
ctx->bufpos = 0;
ctx->buflen = 0;
ctx->bufrem = QSE_COUNTOF(ctx->buf);
if (ctx->footer_pending)
{
x = snprintf (
&ctx->buf[ctx->buflen],
ctx->bufrem,
"</ul></body></html>");
if (x == -1 || x >= ctx->bufrem)
{
/* return an error if the buffer is too small to hold the
* trailing footer. you need to increate the buffer size */
return -1;
}
ctx->buflen += x;
goto send_dirlist;
}
if (!ctx->header_added)
{
/* compose the header since this is the first time. */
x = snprintf (
&ctx->buf[ctx->buflen],
ctx->bufrem,
"<html><head><title>Directory Listing</title></head><body>index of xxxx<ul>"
);
if (x == -1 || x >= ctx->bufrem)
{
/* return an error if the buffer is too small to hold the header.
* you need to increate the buffer size */
return -1;
}
ctx->buflen += x;
ctx->bufrem -= x;
ctx->header_added = 1;
}
if (ctx->dent == QSE_NULL)
ctx->dent = readdir (ctx->handle.ptr);
do
{
if (ctx->dent == QSE_NULL)
{
// TODO: check if errno has changed from before readdir().
// and return -1 if so.
x = snprintf (
&ctx->buf[ctx->buflen],
ctx->bufrem,
"</ul></body></html>");
if (x == -1 || x >= ctx->bufrem)
{
ctx->footer_pending = 1;
}
else
{
ctx->buflen += x;
}
break;
}
else
{
x = snprintf (
&ctx->buf[ctx->buflen],
ctx->bufrem,
"<li><a href='%s%s'>%s%s</a></li>",
ctx->dent->d_name,
(ctx->dent->d_type == DT_DIR? "/": ""),
ctx->dent->d_name,
(ctx->dent->d_type == DT_DIR? "/": "")
);
if (x == -1 || x >= ctx->bufrem)
{
/* buffer not large enough to hold this entry */
break;
}
else
{
ctx->buflen += x;
ctx->bufrem -= x;
}
}
ctx->dent = readdir (ctx->handle.ptr);
}
while (1);
send_dirlist:
n = send (client->handle.i, &ctx->buf[ctx->bufpos], ctx->buflen, 0);
if (n <= -1) return -1;
ctx->bufpos += n;
ctx->buflen -= n;
return (ctx->bufpos < ctx->buflen || ctx->footer_pending || ctx->dent)? 1: 0;
}
qse_httpd_task_t* qse_httpd_entaskdir (
qse_httpd_t* httpd,
qse_httpd_client_t* client,
const qse_httpd_task_t* pred,
qse_ubi_t handle, int chunked)
{
qse_httpd_task_t task;
QSE_MEMSET (&task, 0, QSE_SIZEOF(task));
task.init = task_init_dir;
task.main = chunked? task_main_dir: task_main_dir_nochunk;
task.fini = task_fini_dir;
task.ctx = &handle;
return qse_httpd_entask (httpd, client, pred, &task, QSE_SIZEOF(task_dir_t));
}
/*------------------------------------------------------------------------*/
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
typedef struct task_path_t task_path_t;
struct task_path_t
{
const qse_mchar_t* name;
qse_http_range_t range;
qse_http_version_t version;
int keepalive;
};
static int task_init_path (
qse_httpd_t* httpd, qse_httpd_client_t* client, qse_httpd_task_t* task)
{
task_path_t* xtn = qse_httpd_gettaskxtn (httpd, task);
QSE_MEMCPY (xtn, task->ctx, QSE_SIZEOF(*xtn));
qse_mbscpy ((qse_mchar_t*)(xtn + 1), xtn->name);
xtn->name = (qse_mchar_t*)(xtn + 1);
task->ctx = xtn;
return 0;
}
static QSE_INLINE int task_main_path_file (
qse_httpd_t* httpd, qse_httpd_client_t* client, qse_httpd_task_t* task, qse_foff_t filesize)
{
task_path_t* data = (task_path_t*)task->ctx;
qse_httpd_task_t* x = task;
qse_ubi_t handle;
/* when it comes to the file size, using fstat after opening
* can be more accurate. but this function uses information
* set into the task context before the call to this function */
qse_printf (QSE_T("opening file %hs\n"), data->name);
handle.i = open (data->name, O_RDONLY);
if (handle.i <= -1)
{
x = entask_error (httpd, client, x, 404, &data->version, data->keepalive);
goto no_file_send;
}
fcntl (handle.i, F_SETFD, FD_CLOEXEC);
if (data->range.type != QSE_HTTP_RANGE_NONE)
{
const qse_mchar_t* mime_type = QSE_NULL;
if (data->range.type == QSE_HTTP_RANGE_SUFFIX)
{
if (data->range.to > filesize) data->range.to = filesize;
data->range.from = filesize - data->range.to;
data->range.to = data->range.to + data->range.from;
if (filesize > 0) data->range.to--;
}
if (data->range.from >= filesize)
{
x = entask_error (httpd, client, x, 416, &data->version, data->keepalive);
goto no_file_send;
}
if (data->range.to >= filesize) data->range.to = filesize - 1;
if (httpd->cbs->getmimetype)
{
httpd->errnum = QSE_HTTPD_ENOERR;
mime_type = httpd->cbs->getmimetype (httpd, data->name);
/*TODO: how to handle an error... */
}
#if (QSE_SIZEOF_LONG_LONG > 0)
x = qse_httpd_entaskformat (
httpd, client, x,
QSE_MT("HTTP/%d.%d 206 Partial Content\r\nConnection: %s\r\n%s%s%sContent-Length: %llu\r\nContent-Range: bytes %llu-%llu/%llu\r\n\r\n"),
data->version.major,
data->version.minor,
(data->keepalive? QSE_MT("keep-alive"): QSE_MT("close")),
(mime_type? QSE_MT("Content-Type: "): QSE_MT("")),
(mime_type? mime_type: QSE_MT("")),
(mime_type? QSE_MT("\r\n"): QSE_MT("")),
(unsigned long long)(data->range.to - data->range.from + 1),
(unsigned long long)data->range.from,
(unsigned long long)data->range.to,
(unsigned long long)filesize
);
#else
x = qse_httpd_entaskformat (
httpd, client, x,
QSE_MT("HTTP/%d.%d 206 Partial Content\r\nConnection: %s\r\n%s%s%sContent-Length: %lu\r\nContent-Range: bytes %lu-%lu/%lu\r\n\r\n"),
data->version.major,
data->version.minor,
(data->keepalive? QSE_MT("keep-alive"): QSE_MT("close")),
(mime_type? QSE_MT("Content-Type: "): QSE_MT("")),
(mime_type? mime_type: QSE_MT("")),
(mime_type? QSE_MT("\r\n"): QSE_MT("")),
(unsigned long)(data->range.to - data->range.from + 1),
(unsigned long)data->range.from,
(unsigned long)data->range.to,
(unsigned long)filesize
);
#endif
if (x)
{
x = qse_httpd_entaskfile (
httpd, client, x,
handle,
data->range.from,
(data->range.to - data->range.from + 1)
);
}
}
else
{
/* TODO: int64 format.... don't hard code it llu */
const qse_mchar_t* mime_type = QSE_NULL;
if (httpd->cbs->getmimetype)
{
httpd->errnum = QSE_HTTPD_ENOERR;
mime_type = httpd->cbs->getmimetype (httpd, data->name);
/*TODO: how to handle an error... */
}
/* wget 1.8.2 set 'Connection: keep-alive' in the http 1.0 header.
* if the reply doesn't contain 'Connection: keep-alive', it didn't
* close connection.*/
#if (QSE_SIZEOF_LONG_LONG > 0)
x = qse_httpd_entaskformat (
httpd, client, x,
QSE_MT("HTTP/%d.%d 200 OK\r\nConnection: %s\r\n%s%s%sContent-Length: %llu\r\n\r\n"),
data->version.major, data->version.minor,
(data->keepalive? QSE_MT("keep-alive"): QSE_MT("close")),
(mime_type? QSE_MT("Content-Type: "): QSE_MT("")),
(mime_type? mime_type: QSE_MT("")),
(mime_type? QSE_MT("\r\n"): QSE_MT("")),
(unsigned long long)filesize
);
#else
x = qse_httpd_entaskformat (
httpd, client, x,
QSE_MT("HTTP/%d.%d 200 OK\r\nConnection: %s\r\n%s%s%sContent-Length: %lu\r\n\r\n"),
data->version.major,
data->version.minor,
(data->keepalive? QSE_MT("keep-alive"): QSE_MT("close")),
(mime_type? QSE_MT("Content-Type: "): QSE_MT("")),
(mime_type? mime_type: QSE_MT("")),
(mime_type? QSE_MT("\r\n"): QSE_MT("")),
(unsigned long)filesize
);
#endif
if (x)
{
x = qse_httpd_entaskfile (
httpd, client, x, handle, 0, filesize);
}
}
return (x == QSE_NULL)? -1: 0;
no_file_send:
if (handle.i >= 0) close (handle.i);
return (x == QSE_NULL)? -1: 0;
}
static QSE_INLINE int task_main_path_dir (
qse_httpd_t* httpd, qse_httpd_client_t* client, qse_httpd_task_t* task)
{
task_path_t* data = (task_path_t*)task->ctx;
qse_httpd_task_t* x = task;
qse_ubi_t handle;
if (qse_mbsend (data->name, QSE_MT("/")))
{
handle.ptr = opendir (data->name);
if (handle.ptr)
{
if (data->version.major < 1 ||
(data->version.major == 1 && data->version.minor == 0))
{
data->keepalive = 0;
}
if (data->keepalive)
{
x = qse_httpd_entaskformat (
httpd, client, x,
QSE_MT("HTTP/%d.%d 200 OK\r\nConnection: keep-alive\r\nContent-Type: text/html;charset=utf-8\r\nTransfer-Encoding: chunked\r\n\r\n"),
data->version.major, data->version.minor
);
if (x) x = qse_httpd_entaskdir (httpd, client, x, handle, data->keepalive);
}
else
{
x = qse_httpd_entaskformat (
httpd, client, x,
QSE_MT("HTTP/%d.%d 200 OK\r\nConnection: close\r\nContent-Type: text/html;charset=utf-8\r\n\r\n"),
data->version.major, data->version.minor
);
if (x)
{
x = qse_httpd_entaskdir (httpd, client, x, handle, data->keepalive);
if (x) x = qse_httpd_entaskdisconnect (httpd, client, x);
}
}
if (!x) closedir (handle.ptr);
}
else
{
x = entask_error (httpd, client, x, 403, &data->version, data->keepalive);
}
}
else
{
x = qse_httpd_entaskformat (
httpd, client, x,
QSE_MT("HTTP/%d.%d 301 Moved Permanently\r\nContent-Length: 0\r\nConnection: %s\r\nLocation: %s/\r\n\r\n"),
data->version.major, data->version.minor,
(data->keepalive? QSE_MT("keep-alive"): QSE_MT("close")),
data->name
);
}
return (x == QSE_NULL)? -1: 0;
}
static int task_main_path (
qse_httpd_t* httpd, qse_httpd_client_t* client, qse_httpd_task_t* task)
{
task_path_t* data = (task_path_t*)task->ctx;
struct stat st;
if (lstat (data->name, &st) <= -1)
{
return (entask_error (httpd, client, task, 404, &data->version, data->keepalive) == QSE_NULL)? -1: 0;
}
if (S_ISDIR(st.st_mode))
{
return task_main_path_dir (httpd, client, task);
}
else
{
if (st.st_size < 0) st.st_size = 0; /* can this happen? */
return task_main_path_file (httpd, client, task, st.st_size);
}
}
qse_httpd_task_t* qse_httpd_entaskpath (
qse_httpd_t* httpd,
qse_httpd_client_t* client,
const qse_httpd_task_t* pred,
const qse_mchar_t* name,
const qse_htre_t* req)
{
qse_httpd_task_t task;
task_path_t data;
const qse_mchar_t* range;
QSE_MEMSET (&data, 0, QSE_SIZEOF(data));
data.name = name;
data.version = *qse_htre_getversion(req);
data.keepalive = req->attr.keepalive;
range = qse_htre_getheaderval(req, QSE_MT("Range"));
if (range)
{
if (qse_parsehttprange (range, &data.range) <= -1)
{
return entask_error (httpd, client, pred, 416, &data.version, data.keepalive);
}
}
else
{
data.range.type = QSE_HTTP_RANGE_NONE;
}
QSE_MEMSET (&task, 0, QSE_SIZEOF(task));
task.init = task_init_path;
task.main = task_main_path;
task.ctx = &data;
return qse_httpd_entask (httpd, client, pred, &task,
QSE_SIZEOF(task_path_t) + qse_mbslen(name) + 1);
}
/*------------------------------------------------------------------------*/
typedef struct task_cgi_arg_t task_cgi_arg_t;
struct task_cgi_arg_t
{
const qse_mchar_t* path;
const qse_htre_t* req;
int nph;
};
typedef struct task_cgi_t task_cgi_t;
struct task_cgi_t
{
int init_failed;
const qse_mchar_t* path;
qse_http_version_t version;
int keepalive; /* taken from the request */
int nph;
qse_env_t* env;
qse_pio_t* pio;
qse_htrd_t* htrd;
qse_mbs_t* res;
qse_mchar_t* res_ptr;
qse_size_t res_left;
/* if true, close connection after response is sent out */
int disconnect;
/* if true, the content of response is chunked */
int content_chunked;
/* if true, content_length is set. */
int content_length_set;
/* content-length that CGI returned */
qse_size_t content_length;
/* the number of octets in the contents received */
qse_size_t content_received;
qse_mchar_t buf[MAX_SEND_SIZE];
qse_size_t buflen;
};
typedef struct cgi_htrd_xtn_t cgi_htrd_xtn_t;
struct cgi_htrd_xtn_t
{
task_cgi_t* cgi;
};
int walk_cgi_headers (qse_htre_t* req, const qse_mchar_t* key, const qse_mchar_t* val, void* ctx)
{
task_cgi_t* cgi = (task_cgi_t*)ctx;
if (qse_mbscmp (key, QSE_MT("Status")) != 0)
{
if (qse_mbs_cat (cgi->res, key) == (qse_size_t)-1) return -1;
if (qse_mbs_cat (cgi->res, QSE_MT(": ")) == (qse_size_t)-1) return -1;
if (qse_mbs_cat (cgi->res, val) == (qse_size_t)-1) return -1;
if (qse_mbs_cat (cgi->res, QSE_MT("\r\n")) == (qse_size_t)-1) return -1;
}
return 0;
}
static int cgi_htrd_handle_request (qse_htrd_t* htrd, qse_htre_t* req)
{
cgi_htrd_xtn_t* xtn = (cgi_htrd_xtn_t*) qse_htrd_getxtn (htrd);
task_cgi_t* cgi = xtn->cgi;
const qse_mchar_t* status;
static qse_http_version_t v11 = { 1, 1 };
QSE_ASSERT (req->attr.hurried);
status = qse_htre_getheaderval (req, QSE_MT("Status"));
if (status)
{
qse_mchar_t buf[128];
int nstatus;
qse_mchar_t* endptr;
/* TODO: check the syntax of status value??? */
QSE_MSTRTONUM (nstatus, status, &endptr, 10);
snprintf (buf, QSE_COUNTOF(buf),
QSE_MT("HTTP/%d.%d %d "),
cgi->version.major,
cgi->version.minor,
nstatus
);
/*
Would it need this kind of extra work?
while (QSE_ISMSPACE(*endptr)) endptr++;
if (*endptr == QSE_MT('\0')) ....
*/
if (qse_mbs_cat (cgi->res, buf) == (qse_size_t)-1) return -1;
if (qse_mbs_cat (cgi->res, endptr) == (qse_size_t)-1) return -1;
if (qse_mbs_cat (cgi->res, QSE_MT("\r\n")) == (qse_size_t)-1) return -1;
}
else
{
qse_mchar_t* location;
qse_mchar_t buf[128];
location = qse_htre_getheaderval (req, QSE_MT("Location"));
if (location)
{
snprintf (buf, QSE_COUNTOF(buf),
QSE_MT("HTTP/%d.%d 301 Moved Permanently\r\n"),
cgi->version.major, cgi->version.minor
);
if (qse_mbs_cat (cgi->res, buf) == (qse_size_t)-1) return -1;
/* the actual Location hearer is added by
* qse_htre_walkheaders() below */
}
else
{
snprintf (buf, QSE_COUNTOF(buf),
QSE_MT("HTTP/%d.%d 200 OK\r\n"),
cgi->version.major, cgi->version.minor
);
if (qse_mbs_cat (cgi->res, buf) == (qse_size_t)-1) return -1;
}
}
if (req->attr.content_length_set)
{
cgi->content_length_set = 1;
cgi->content_length = req->attr.content_length;
}
else
{
/* no Content-Length returned by CGI. */
if (qse_comparehttpversions (&cgi->version, &v11) >= 0)
{
cgi->content_chunked = 1;
}
else
{
/* If CGI doesn't specify Content-Length, i can't
* honor cgi->keepalive in HTTP/1.0 or earlier.
* Closing the connection is the only way to
* specify how long the content is */
cgi->disconnect = 1;
}
}
if (cgi->content_chunked &&
qse_mbs_cat (cgi->res, QSE_MT("Transfer-Encoding: chunked\r\n")) == (qse_size_t)-1) return -1;
if (qse_htre_walkheaders (req, walk_cgi_headers, cgi) <= -1) return -1;
/* end of header */
if (qse_mbs_cat (cgi->res, QSE_MT("\r\n")) == (qse_size_t)-1) return -1;
/* content body begins here */
cgi->content_received = qse_htre_getcontentlen(req);
if (cgi->content_length_set &&
cgi->content_received > cgi->content_length)
{
/* TODO: cgi returning too much data... something is wrong in CGI */
return -1;
}
if (cgi->content_received > 0)
{
if (cgi->content_chunked)
{
qse_mchar_t buf[64];
snprintf (buf, QSE_COUNTOF(buf), QSE_MT("%lX\r\n"), (unsigned long)cgi->content_received);
if (qse_mbs_cat (cgi->res, buf) == (qse_size_t)-1) return -1;
}
if (qse_mbs_ncat (cgi->res, qse_htre_getcontentptr(req), qse_htre_getcontentlen(req)) == (qse_size_t)-1) return -1;
if (cgi->content_chunked)
{
if (qse_mbs_ncat (cgi->res, QSE_MT("\r\n"), 2) == (qse_size_t)-1) return -1;
}
}
return 0;
}
static qse_htrd_recbs_t cgi_htrd_cbs =
{
cgi_htrd_handle_request,
QSE_NULL, /* not needed for CGI */
QSE_NULL /* not needed for CGI */
};
static qse_env_t* makecgienv (
qse_httpd_t* httpd, qse_httpd_client_t* client, const qse_htre_t* req)
{
/* TODO: error check */
qse_env_t* env;
env = qse_env_open (httpd->mmgr, 0, 0);
if (env == QSE_NULL) goto oops;
#ifdef _WIN32
qse_env_insertsys (env, QSE_T("PATH"));
{
qse_char_t proto[32];
qse_http_version_t* v = qse_htre_getversion(req);
snprintf (proto, QSE_COUNTOF(proto),
QSE_T("HTTP/%d.%d"), (int)v->major, (int)v->minor);
qse_env_insert (env, QSE_T("SERVER_PROTOCOL"), proto);
}
#else
qse_env_insertsysm (env, QSE_MT("LANG"));
qse_env_insertsysm (env, QSE_MT("PATH"));
//qse_env_insertm (env, QSE_MT("SERVER_PORT"), );
{
qse_mchar_t port[16];
snprintf (port, QSE_COUNTOF(port),
QSE_MT("%d"), (int)ntohs(client->addr.in4.sin_port));
qse_env_insertm (env, QSE_MT("REMOTE_PORT"), port);
}
{
qse_mchar_t proto[32];
qse_http_version_t* v = qse_htre_getversion(req);
snprintf (proto, QSE_COUNTOF(proto),
QSE_MT("HTTP/%d.%d"), (int)v->major, (int)v->minor);
qse_env_insertm (env, QSE_MT("SERVER_PROTOCOL"), proto);
}
//qse_env_insertm (env, QSE_MT("REMOTE_ADDR"), QSE_MT("what the hell"));
#endif
#if 0
qse_env_insertm (env, "SERVER_NAME",
qse_env_insertm (env, "SERVER_ROOT",
qse_env_insertm (env, "DOCUMENT_ROOT",
qse_env_insertm (env, "REMOTE_PORT",
qse_env_insertm (env, "REQUEST_URI",
#endif
return env;
oops:
if (env) qse_env_close (env);
return QSE_NULL;
}
static int task_init_cgi (
qse_httpd_t* httpd, qse_httpd_client_t* client, qse_httpd_task_t* task)
{
task_cgi_t* xtn = (task_cgi_t*)qse_httpd_gettaskxtn (httpd, task);
task_cgi_arg_t* arg = (task_cgi_arg_t*)task->ctx;
QSE_MEMSET (xtn, 0, QSE_SIZEOF(*xtn));
qse_mbscpy ((qse_mchar_t*)(xtn + 1), arg->path);
xtn->path = (qse_mchar_t*)(xtn + 1);
xtn->version = *qse_htre_getversion(arg->req);
xtn->keepalive = arg->req->attr.keepalive;
xtn->nph = arg->nph;
xtn->env = makecgienv (httpd, client, arg->req);
if (xtn->env == QSE_NULL) xtn->init_failed = 1;
task->ctx = xtn;
return 0;
}
static void task_fini_cgi (
qse_httpd_t* httpd, qse_httpd_client_t* client, qse_httpd_task_t* task)
{
task_cgi_t* cgi = (task_cgi_t*)task->ctx;
if (cgi->env) qse_env_close (cgi->env);
if (cgi->pio) qse_pio_close (cgi->pio);
if (cgi->res) qse_mbs_close (cgi->res);
if (cgi->htrd) qse_htrd_close (cgi->htrd);
qse_printf (QSE_T("task_fini_cgi\n"));
}
static int task_main_cgi_5 (
qse_httpd_t* httpd, qse_httpd_client_t* client, qse_httpd_task_t* task)
{
task_cgi_t* cgi = (task_cgi_t*)task->ctx;
qse_ssize_t n;
QSE_ASSERT (cgi->pio != QSE_NULL);
qse_printf (QSE_T("task_main_cgi_5\n"));
/* TODO: check if cgi outputs more than content-length if it is set... */
n = send (client->handle.i, cgi->buf, cgi->buflen, 0);
if (n <= -1)
{
/* can't return internal server error any more... */
/* TODO: logging ... */
return -1;
}
QSE_MEMCPY (&cgi->buf[0], &cgi->buf[n], cgi->buflen - n);
cgi->buflen -= n;
return (cgi->buflen > 0)? 1: 0;
}
static int task_main_cgi_4 (
qse_httpd_t* httpd, qse_httpd_client_t* client, qse_httpd_task_t* task)
{
task_cgi_t* cgi = (task_cgi_t*)task->ctx;
qse_ssize_t n;
QSE_ASSERT (cgi->pio != QSE_NULL);
/* this function assumes that the chunk length does not exceeded
* 4 hexadecimal digits. */
QSE_ASSERT (QSE_SIZEOF(cgi->buf) <= 0xFFFF);
qse_printf (QSE_T("task_main_cgi_4\n"));
if (cgi->content_chunked)
{
qse_size_t count, extra;
qse_mchar_t chunklen[7];
qse_printf (QSE_T("READING CHUNKED MODE...\n"));
extra = (QSE_SIZEOF(chunklen) - 1) + 2;
count = QSE_SIZEOF(cgi->buf) - cgi->buflen;
if (count > extra)
{
/* TODO: check if cgi outputs more than content-length if it is set... */
/* <- can i make it non-block?? or use select??? pio_tryread()? */
n = qse_pio_read (
cgi->pio, QSE_PIO_OUT,
&cgi->buf[cgi->buflen + QSE_SIZEOF(chunklen) - 1],
count - extra
);
if (n <= -1)
{
/* can't return internal server error any more... */
/* TODO: logging ... */
return -1;
}
if (n == 0)
{
cgi->buf[cgi->buflen++] = QSE_MT('0');
cgi->buf[cgi->buflen++] = QSE_MT('\r');
cgi->buf[cgi->buflen++] = QSE_MT('\n');
cgi->buf[cgi->buflen++] = QSE_MT('\r');
cgi->buf[cgi->buflen++] = QSE_MT('\n');
task->main = task_main_cgi_5;
return task_main_cgi_5 (httpd, client, task);
}
/* set the chunk length */
snprintf (chunklen, QSE_COUNTOF(chunklen),
QSE_MT("%-4lX\r\n"), (unsigned long)n);
QSE_MEMCPY (&cgi->buf[cgi->buflen],
chunklen, QSE_SIZEOF(chunklen) - 1);
cgi->buflen += QSE_SIZEOF(chunklen) - 1 + n;
/* set the trailing CR & LF for a chunk */
cgi->buf[cgi->buflen++] = QSE_MT('\r');
cgi->buf[cgi->buflen++] = QSE_MT('\n');
cgi->content_received += n;
}
}
else
{
qse_printf (QSE_T("READING IN NON-CHUNKED MODE...\n"));
n = qse_pio_read (
cgi->pio, QSE_PIO_OUT,
&cgi->buf[cgi->buflen],
QSE_SIZEOF(cgi->buf) - cgi->buflen
);
if (n <= -1)
{
/* can't return internal server error any more... */
/* TODO: loggig ... */
return -1;
}
if (n == 0)
{
task->main = task_main_cgi_5;
return task_main_cgi_5 (httpd, client, task);
}
cgi->buflen += n;
cgi->content_received += n;
}
if (cgi->content_length_set &&
cgi->content_received > cgi->content_length)
{
/* TODO: cgi returning too much data... something is wrong in CGI */
qse_printf (QSE_T("CGI FUCKED UP...RETURNING TOO MUCH DATA\n"));
return -1;
}
n = send (client->handle.i, cgi->buf, cgi->buflen, 0);
if (n <= -1)
{
/* can't return internal server error any more... */
/* TODO: logging ... */
return -1;
}
QSE_MEMCPY (&cgi->buf[0], &cgi->buf[n], cgi->buflen - n);
cgi->buflen -= n;
return 1;
}
static int task_main_cgi_3 (
qse_httpd_t* httpd, qse_httpd_client_t* client, qse_httpd_task_t* task)
{
/* send the http initial line and headers built using the headers
* returned by CGI. it may include some contents as well */
task_cgi_t* cgi = (task_cgi_t*)task->ctx;
qse_ssize_t n;
qse_size_t count;
qse_printf (QSE_T("cgi_3\n"));
count = MAX_SEND_SIZE;
if (count >= cgi->res_left) count = cgi->res_left;
n = send (
client->handle.i,
cgi->res_ptr,
count,
0
);
if (n <= -1) return -1;
cgi->res_left -= n;
if (cgi->res_left <= 0)
{
task->main = task_main_cgi_4;
return task_main_cgi_4 (httpd, client, task);
}
cgi->res_ptr += n;
return 1; /* more work to do */
}
static int task_main_cgi_2 (
qse_httpd_t* httpd, qse_httpd_client_t* client, qse_httpd_task_t* task)
{
/* several calls to this function will read output from the cgi
* until the end of header is reached. when the end is reached,
* it is possible that some contents are also read in.
* The callback function to qse_htrd_feed() handles this also.
*/
task_cgi_t* cgi = (task_cgi_t*)task->ctx;
qse_ssize_t n;
QSE_ASSERT (cgi->pio != QSE_NULL);
qse_printf (QSE_T("[cgi_2 ]\n"));
/* <- can i make it non-block?? or use select??? pio_tryread()? */
n = qse_pio_read (
cgi->pio, QSE_PIO_OUT,
&cgi->buf[cgi->buflen],
QSE_SIZEOF(cgi->buf) - cgi->buflen
);
if (n <= -1)
{
/* can't return internal server error any more... */
/* TODO: logging ... */
return -1;
}
if (n == 0)
{
/* end of output from cgi before it has seen a header.
* the cgi script must be crooked. */
/* TODO: logging */
qse_pio_kill (cgi->pio);
return -1;
}
cgi->buflen += n;
if (qse_htrd_feed (cgi->htrd, cgi->buf, cgi->buflen) <= -1)
{
/* TODO: logging */
return -1;
}
cgi->buflen = 0;
if (QSE_MBS_LEN(cgi->res) > 0)
{
/* the htrd handler composed some response.
* this means that at least it finished processing CGI headers.
* some contents could be in cgi->res, though.
*/
if (cgi->disconnect &&
qse_httpd_entaskdisconnect (httpd, client, task) == QSE_NULL) return -1;
cgi->res_ptr = QSE_MBS_PTR(cgi->res);
cgi->res_left = QSE_MBS_LEN(cgi->res);
task->main = task_main_cgi_3;
return task_main_cgi_3 (httpd, client, task);
}
/* complete headers not seen yet. i need to be called again */
return 1;
}
static int task_main_cgi (
qse_httpd_t* httpd, qse_httpd_client_t* client, qse_httpd_task_t* task)
{
task_cgi_t* cgi = (task_cgi_t*)task->ctx;
if (cgi->init_failed) goto oops;
if (cgi->nph)
{
/* i cannot know how long the content will be
* since i don't parse the header. so i have to close
* the connection regardless of content-length or transfer-encoding
* in the actual header. */
if (qse_httpd_entaskdisconnect (httpd, client, task) == QSE_NULL) return -1;
}
else
{
cgi_htrd_xtn_t* xtn;
cgi->htrd = qse_htrd_open (httpd->mmgr, QSE_SIZEOF(cgi_htrd_xtn_t));
if (cgi->htrd == QSE_NULL) goto oops;
xtn = (cgi_htrd_xtn_t*) qse_htrd_getxtn (cgi->htrd);
xtn->cgi = cgi;
qse_htrd_setrecbs (cgi->htrd, &cgi_htrd_cbs);
qse_htrd_setoption (
cgi->htrd,
QSE_HTRD_SKIPINITIALLINE |
QSE_HTRD_HURRIED |
QSE_HTRD_REQUEST
);
cgi->res = qse_mbs_open (httpd->mmgr, 0, 256);
if (cgi->res == QSE_NULL) goto oops;
}
cgi->pio = qse_pio_open (
httpd->mmgr, 0, (const qse_char_t*)cgi->path, cgi->env,
QSE_PIO_READOUT | QSE_PIO_WRITEIN | QSE_PIO_ERRTONUL | QSE_PIO_MBSCMD
);
if (cgi->pio == QSE_NULL) goto oops;
if (cgi->nph)
{
/* skip various header processing */
task->main = task_main_cgi_4;
return task_main_cgi_4 (httpd, client, task);
}
else
{
task->main = task_main_cgi_2; /* cause this function to be called subsequently */
return task_main_cgi_2 (httpd, client, task); /* let me call it here once */
}
oops:
if (cgi->res)
{
qse_mbs_close (cgi->res);
cgi->res = QSE_NULL;
}
if (cgi->htrd)
{
qse_htrd_close (cgi->htrd);
cgi->htrd = QSE_NULL;
}
return (entask_error (httpd, client, task, 500, &cgi->version, cgi->keepalive) == QSE_NULL)? -1: 0;
}
/* TODO: global option or individual paramter for max cgi lifetime
* non-blocking pio read ...
*/
qse_httpd_task_t* qse_httpd_entaskcgi (
qse_httpd_t* httpd,
qse_httpd_client_t* client,
const qse_httpd_task_t* pred,
const qse_mchar_t* path,
const qse_htre_t* req)
{
qse_httpd_task_t task;
task_cgi_arg_t arg;
arg.path = path;
arg.req = req;
arg.nph = 0;
QSE_MEMSET (&task, 0, QSE_SIZEOF(task));
task.init = task_init_cgi;
task.fini = task_fini_cgi;
task.main = task_main_cgi;
task.ctx = &arg;
return qse_httpd_entask (
httpd, client, pred, &task,
QSE_SIZEOF(task_cgi_t) + ((qse_mbslen(path) + 1) * QSE_SIZEOF(*path))
);
}
qse_httpd_task_t* qse_httpd_entasknph (
qse_httpd_t* httpd,
qse_httpd_client_t* client,
const qse_httpd_task_t* pred,
const qse_mchar_t* path,
const qse_htre_t* req)
{
qse_httpd_task_t task;
task_cgi_arg_t arg;
arg.path = path;
arg.req = req;
arg.nph = 1;
QSE_MEMSET (&task, 0, QSE_SIZEOF(task));
task.init = task_init_cgi;
task.fini = task_fini_cgi;
task.main = task_main_cgi;
task.ctx = &arg;
return qse_httpd_entask (
httpd, client, pred, &task,
QSE_SIZEOF(task_cgi_t) + ((qse_mbslen(path) + 1) * QSE_SIZEOF(*path))
);
}
/*------------------------------------------------------------------------*/
/*
typedef struct task_proxy_t task_proxy_t;
struct task_proxy_t
{
}
qse_httpd_task_t* qse_httpd_entaskproxy (...)
{
}
*/
/*------------------------------------------------------------------------*/
#endif