added qse_httpd_entaskdir()

This commit is contained in:
2012-09-11 16:19:52 +00:00
parent 9cc8d6f76d
commit b9a0863fff
16 changed files with 905 additions and 880 deletions

View File

@ -20,7 +20,7 @@
#include "awk.h"
const qse_char_t* qse_awk_dflerrstr (qse_awk_t* awk, qse_awk_errnum_t errnum)
const qse_char_t* qse_awk_dflerrstr (const qse_awk_t* awk, qse_awk_errnum_t errnum)
{
static const qse_char_t* errstr[] =
{

View File

@ -25,7 +25,7 @@
extern "C" {
#endif
const qse_char_t* qse_awk_dflerrstr (qse_awk_t* awk, qse_awk_errnum_t errnum);
const qse_char_t* qse_awk_dflerrstr (const qse_awk_t* awk, qse_awk_errnum_t errnum);
#ifdef __cplusplus
}

View File

@ -1268,6 +1268,7 @@ qse_long_t qse_awk_rtx_hashval (qse_awk_rtx_t* rtx, qse_awk_val_t* v)
return -1;
}
/* turn off the sign bit */
return hv & ~(((qse_ulong_t)1) << ((QSE_SIZEOF(qse_ulong_t) * 8) - 1));
}

View File

@ -34,9 +34,7 @@
# include <dos.h>
# include <errno.h>
#else
# include <dirent.h>
# include <sys/stat.h>
# include <unistd.h>
# include "syscall.h"
#endif
#if defined(_WIN32) || defined(__OS2__) || defined(__DOS__)
@ -326,40 +324,41 @@ static int get_next_segment (glob_t* g, segment_t* seg)
#if defined(_WIN32)
struct DIR
struct qse_dir_t
{
HANDLE h;
WIN32_FIND_DATA wfd;
int done;
};
typedef struct DIR DIR;
typedef struct qse_dir_t qse_dir_t;
#elif defined(__OS2__)
struct DIR
struct qse_dir_t
{
HDIR h;
FILEFINDBUF3L ffb;
ULONG count;
};
typedef struct DIR DIR;
typedef struct qse_dir_t qse_dir_t;
#elif defined(__DOS__)
struct DIR
struct qse_dir_t
{
struct find_t f;
int done;
};
typedef struct DIR DIR;
typedef struct qse_dir_t qse_dir_t;
#endif
static DIR* xopendir (glob_t* g, const qse_cstr_t* path)
static qse_dir_t* xopendir (glob_t* g, const qse_cstr_t* path)
{
#if defined(_WIN32)
/* ------------------------------------------------------------------- */
DIR* dp;
qse_dir_t* dp;
dp = QSE_MMGR_ALLOC (g->mmgr, QSE_SIZEOF(*dp));
if (dp == QSE_NULL) return QSE_NULL;
@ -400,7 +399,7 @@ static DIR* xopendir (glob_t* g, const qse_cstr_t* path)
#elif defined(__OS2__)
/* ------------------------------------------------------------------- */
DIR* dp;
qse_dir_t* dp;
APIRET rc;
qse_mchar_t* mptr;
@ -463,7 +462,7 @@ static DIR* xopendir (glob_t* g, const qse_cstr_t* path)
#elif defined(__DOS__)
/* ------------------------------------------------------------------- */
DIR* dp;
qse_dir_t* dp;
unsigned int rc;
qse_mchar_t* mptr;
qse_size_t wl, ml;
@ -519,13 +518,12 @@ static DIR* xopendir (glob_t* g, const qse_cstr_t* path)
#else
/* ------------------------------------------------------------------- */
#if defined(QSE_CHAR_IS_MCHAR)
return opendir ((path->len <= 0)? QSE_T("."): path->ptr);
return QSE_OPENDIR ((path->len <= 0)? QSE_T("."): path->ptr);
#else
if (path->len <= 0)
{
return opendir (QSE_MT("."));
return QSE_OPENDIR (QSE_MT("."));
}
else
{
@ -534,7 +532,7 @@ static DIR* xopendir (glob_t* g, const qse_cstr_t* path)
mptr = wcs_to_mbuf (g, path->ptr, &g->mbuf);
if (mptr == QSE_NULL) return QSE_NULL;
return opendir (mptr);
return QSE_OPENDIR (mptr);
}
#endif
/* ------------------------------------------------------------------- */
@ -542,7 +540,7 @@ static DIR* xopendir (glob_t* g, const qse_cstr_t* path)
#endif
}
static int xreaddir (glob_t* g, DIR* dp, qse_str_t* path)
static int xreaddir (glob_t* g, qse_dir_t* dp, qse_str_t* path)
{
#if defined(_WIN32)
@ -615,14 +613,14 @@ static int xreaddir (glob_t* g, DIR* dp, qse_str_t* path)
#else
/* ------------------------------------------------------------------- */
struct dirent* de;
qse_dirent_t* de;
#if defined(QSE_CHAR_IS_MCHAR)
/* nothing */
#else
qse_size_t ml, wl, tmp;
#endif
de = readdir (dp);
de = QSE_READDIR (dp);
if (de == NULL) return 0;
#if defined(QSE_CHAR_IS_MCHAR)
@ -640,7 +638,7 @@ static int xreaddir (glob_t* g, DIR* dp, qse_str_t* path)
#endif
}
static void xclosedir (glob_t* g, DIR* dp)
static void xclosedir (glob_t* g, qse_dir_t* dp)
{
#if defined(_WIN32)
FindClose (dp->h);
@ -652,7 +650,7 @@ static void xclosedir (glob_t* g, DIR* dp)
_dos_findclose (&dp->f);
QSE_MMGR_FREE (g->mmgr, dp);
#else
closedir (dp);
QSE_CLOSEDIR (dp);
#endif
}
@ -722,7 +720,7 @@ struct stack_node_t
{
qse_size_t tmp;
qse_size_t tmp2;
DIR* dp;
qse_dir_t* dp;
segment_t seg;
stack_node_t* next;
@ -731,7 +729,7 @@ struct stack_node_t
static int search (glob_t* g, segment_t* seg)
{
DIR* dp;
qse_dir_t* dp;
qse_size_t tmp, tmp2;
#if defined(NO_RECURSION)

View File

@ -23,39 +23,42 @@
/* This file defines unix/linux system calls */
#ifdef HAVE_SYS_TYPES_H
#if defined(HAVE_SYS_TYPES_H)
# include <sys/types.h>
#endif
#ifdef HAVE_UNISTD_H
#if defined(HAVE_UNISTD_H)
# include <unistd.h>
#endif
#ifdef HAVE_SYS_WAIT_H
#if defined(HAVE_SYS_WAIT_H)
# include <sys/wait.h>
#endif
#ifdef HAVE_SIGNAL_H
#if defined(HAVE_SIGNAL_H)
# include <signal.h>
#endif
#ifdef HAVE_ERRNO_H
#if defined(HAVE_ERRNO_H)
# include <errno.h>
#endif
#ifdef HAVE_FCNTL_H
#if defined(HAVE_FCNTL_H)
# include <fcntl.h>
#endif
#ifdef HAVE_TIME_H
#if defined(HAVE_TIME_H)
# include <time.h>
#endif
#ifdef HAVE_SYS_TIME_H
#if defined(HAVE_SYS_TIME_H)
# include <sys/time.h>
#endif
#ifdef HAVE_UTIME_H
#if defined(HAVE_UTIME_H)
# include <utime.h>
#endif
#ifdef HAVE_SYS_RESOURCE_H
#if defined(HAVE_SYS_RESOURCE_H)
# include <sys/resource.h>
#endif
#ifdef HAVE_SYS_STAT_H
#if defined(HAVE_SYS_STAT_H)
# include <sys/stat.h>
#endif
#if defined(HAVE_DIRENT_H)
# include <dirent.h>
#endif
#if defined(QSE_USE_SYSCALL) && defined(HAVE_SYS_SYSCALL_H)
# include <sys/syscall.h>
@ -355,6 +358,19 @@
# define QSE_UTIMES(path,t) utimes(path,t)
#endif
/* ===== DIRECTORY - not really system calls ===== */
typedef DIR qse_dir_t;
#define QSE_OPENDIR(name) opendir(name)
#define QSE_CLOSEDIR(name) closedir(name)
#if defined(HAVE_READDIR64)
typedef struct dirent64 qse_dirent_t;
# define QSE_READDIR(x) readdir64(x)
#else
typedef struct dirent qse_dirent_t;
# define QSE_READDIR(x) readdir(x)
#endif
/* ------------------------------------------------------------------------ */
#if defined(__linux) && defined(__GNUC__) && defined(__x86_64)

View File

@ -14,6 +14,8 @@ libqsenet_la_SOURCES = \
htrd.c \
httpd.c \
httpd-cgi.c \
httpd-dir.c \
httpd-file.c \
httpd-proxy.c \
httpd-resol.c \
httpd-std.c \

View File

@ -79,8 +79,8 @@ am__installdirs = "$(DESTDIR)$(libdir)"
LTLIBRARIES = $(lib_LTLIBRARIES)
libqsenet_la_DEPENDENCIES =
am_libqsenet_la_OBJECTS = http.lo htre.lo htrd.lo httpd.lo \
httpd-cgi.lo httpd-proxy.lo httpd-resol.lo httpd-std.lo \
httpd-task.lo upxd.lo
httpd-cgi.lo httpd-dir.lo httpd-file.lo httpd-proxy.lo \
httpd-resol.lo httpd-std.lo httpd-task.lo upxd.lo
libqsenet_la_OBJECTS = $(am_libqsenet_la_OBJECTS)
libqsenet_la_LINK = $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) \
$(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
@ -271,6 +271,8 @@ libqsenet_la_SOURCES = \
htrd.c \
httpd.c \
httpd-cgi.c \
httpd-dir.c \
httpd-file.c \
httpd-proxy.c \
httpd-resol.c \
httpd-std.c \
@ -357,6 +359,8 @@ distclean-compile:
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/htre.Plo@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/http.Plo@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/httpd-cgi.Plo@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/httpd-dir.Plo@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/httpd-file.Plo@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/httpd-proxy.Plo@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/httpd-resol.Plo@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/httpd-std.Plo@am__quote@

487
qse/lib/net/httpd-dir.c Normal file
View File

@ -0,0 +1,487 @@
/*
* $Id$
*
Copyright 2006-2012 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/>.
*/
#include "httpd.h"
#include "../cmn/mem.h"
#include "../cmn/syscall.h"
#include <qse/cmn/str.h>
#include <qse/cmn/stdio.h> /* TODO: remove this */
typedef struct task_dir_t task_dir_t;
struct task_dir_t
{
const qse_mchar_t* path;
qse_http_version_t version;
int keepalive;
};
typedef struct task_dseg_t task_dseg_t;
struct task_dseg_t
{
const qse_mchar_t* path;
qse_dir_t* handle;
qse_dirent_t* dent;
int header_added;
int footer_pending;
/*qse_mchar_t buf[4096];*/
qse_mchar_t buf[512]; /* TOOD: increate size */
qse_size_t bufpos;
qse_size_t buflen;
qse_size_t bufrem;
qse_size_t chunklen;
};
static int task_init_dseg (
qse_httpd_t* httpd, qse_httpd_client_t* client, qse_httpd_task_t* task)
{
task_dseg_t* xtn = qse_httpd_gettaskxtn (httpd, task);
QSE_MEMCPY (xtn, task->ctx, QSE_SIZEOF(*xtn));
qse_mbscpy ((qse_mchar_t*)(xtn + 1), xtn->path);
xtn->path = (qse_mchar_t*)(xtn + 1);
task->ctx = xtn;
return 0;
}
static void task_fini_dseg (
qse_httpd_t* httpd, qse_httpd_client_t* client, qse_httpd_task_t* task)
{
task_dseg_t* ctx = (task_dseg_t*)task->ctx;
QSE_CLOSEDIR (ctx->handle);
}
static int task_main_dseg_chunked (
qse_httpd_t* httpd, qse_httpd_client_t* client, qse_httpd_task_t* task)
{
task_dseg_t* ctx = (task_dseg_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. */
x = snprintf (
&ctx->buf[ctx->buflen],
ctx->bufrem,
QSE_MT("<html><head><title>Directory Listing</title></head><body><b>%s</b><ul><li><a href='../'>..</a></li>"),
ctx->path
);
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)
ctx->dent = QSE_READDIR (ctx->handle);
do
{
if (!ctx->dent)
{
// TODO: check if errno has changed from before QSE_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 if (qse_mbscmp (ctx->dent->d_name, QSE_MT(".")) != 0 &&
qse_mbscmp (ctx->dent->d_name, QSE_MT("..")) != 0)
{
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 = QSE_READDIR (ctx->handle);
}
while (1);
set_chunklen:
/* right alignment with space padding on the left */
/* TODO: change snprintf to qse_fmtuintmaxtombs() */
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:
httpd->errnum = QSE_HTTPD_ENOERR;
n = httpd->cbs->client.send (
httpd, client, &ctx->buf[ctx->bufpos], ctx->buflen);
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_dseg_nochunk (
qse_httpd_t* httpd, qse_httpd_client_t* client, qse_httpd_task_t* task)
{
task_dseg_t* ctx = (task_dseg_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,
QSE_MT("<html><head><title>Directory Listing</title></head><body><b>%s</b><ul><li><a href='../'>..</a></li>"),
ctx->path
);
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 = QSE_READDIR (ctx->handle);
do
{
if (ctx->dent == QSE_NULL)
{
// TODO: check if errno has changed from before QSE_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 if (qse_mbscmp (ctx->dent->d_name, QSE_MT(".")) != 0 &&
qse_mbscmp (ctx->dent->d_name, QSE_MT("..")) != 0)
{
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 = QSE_READDIR (ctx->handle);
}
while (1);
send_dirlist:
httpd->errnum = QSE_HTTPD_ENOERR;
n = httpd->cbs->client.send (
httpd, client, &ctx->buf[ctx->bufpos], ctx->buflen);
if (n <= -1) return -1;
ctx->bufpos += n;
ctx->buflen -= n;
return (ctx->bufpos < ctx->buflen || ctx->footer_pending || ctx->dent)? 1: 0;
}
static qse_httpd_task_t* entask_directory_segment (
qse_httpd_t* httpd, qse_httpd_client_t* client,
qse_httpd_task_t* pred, qse_dir_t* handle, const qse_mchar_t* path, int keepalive)
{
qse_httpd_task_t task;
task_dseg_t data;
QSE_MEMSET (&data, 0, QSE_SIZEOF(data));
data.handle = handle;
data.path = path;
QSE_MEMSET (&task, 0, QSE_SIZEOF(task));
task.init = task_init_dseg;
task.main = (keepalive)? task_main_dseg_chunked: task_main_dseg_nochunk;
task.fini = task_fini_dseg;
task.ctx = &data;
qse_printf (QSE_T("Debug: entasking directory segment (%d)\n"), client->handle.i);
return qse_httpd_entask (httpd, client, pred, &task, QSE_SIZEOF(data) + qse_mbslen(path) + 1);
}
/*------------------------------------------------------------------------*/
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);
/* deep-copy the context data to the extension area */
QSE_MEMCPY (xtn, task->ctx, QSE_SIZEOF(*xtn));
qse_mbscpy ((qse_mchar_t*)(xtn + 1), xtn->path);
xtn->path = (qse_mchar_t*)(xtn + 1);
/* switch the context to the extension area */
task->ctx = xtn;
return 0;
}
static QSE_INLINE int task_main_dir (
qse_httpd_t* httpd, qse_httpd_client_t* client, qse_httpd_task_t* task)
{
task_dir_t* dir;
qse_httpd_task_t* x;
qse_dir_t* handle = QSE_NULL;
dir = (task_dir_t*)task->ctx;
x = task;
if (qse_mbsend (dir->path, QSE_MT("/")))
{
handle = QSE_OPENDIR (dir->path);
if (handle)
{
x = qse_httpd_entaskformat (
httpd, client, x,
QSE_MT("HTTP/%d.%d 200 OK\r\nConnection: %s\r\nContent-Type: text/html\r\n%s\r\n"),
dir->version.major, dir->version.minor,
(dir->keepalive? QSE_MT("keep-alive"): QSE_MT("close")),
(dir->keepalive? QSE_MT("Transfer-Encoding: chunked\r\n"): QSE_MT(""))
);
if (x) x = entask_directory_segment (httpd, client, x, handle, dir->path, dir->keepalive);
if (x) return 0;
QSE_CLOSEDIR (handle);
return -1;
}
else
{
int http_errnum;
http_errnum = (errno == ENOENT)? 404:
(errno == EACCES)? 403: 500;
x = qse_httpd_entask_error (
httpd, client, x, http_errnum,
&dir->version, dir->keepalive);
QSE_CLOSEDIR (handle);
return (x == QSE_NULL)? -1: 0;
}
}
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"),
dir->version.major, dir->version.minor,
(dir->keepalive? QSE_MT("keep-alive"): QSE_MT("close")),
dir->path
);
return (x == QSE_NULL)? -1: 0;
}
}
qse_httpd_task_t* qse_httpd_entaskdir (
qse_httpd_t* httpd,
qse_httpd_client_t* client,
qse_httpd_task_t* pred,
const qse_mchar_t* path,
qse_htre_t* req)
{
qse_httpd_task_t task;
task_dir_t data;
QSE_MEMSET (&data, 0, QSE_SIZEOF(data));
data.path = path;
data.version = *qse_htre_getversion(req);
data.keepalive = (req->attr.flags & QSE_HTRE_ATTR_KEEPALIVE);
QSE_MEMSET (&task, 0, QSE_SIZEOF(task));
task.init = task_init_dir;
task.main = task_main_dir;
task.ctx = &data;
return qse_httpd_entask (httpd, client, pred, &task,
QSE_SIZEOF(task_dir_t) + qse_mbslen(path) + 1);
}

323
qse/lib/net/httpd-file.c Normal file
View File

@ -0,0 +1,323 @@
/*
* $Id$
*
Copyright 2006-2012 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/>.
*/
#include "httpd.h"
#include "../cmn/mem.h"
#include "../cmn/syscall.h"
#include <qse/cmn/str.h>
#include <qse/cmn/stdio.h> /* TODO: remove this */
typedef struct task_file_t task_file_t;
struct task_file_t
{
const qse_mchar_t* path;
qse_http_range_t range;
qse_http_version_t version;
int keepalive;
};
typedef struct task_fseg_t task_fseg_t;
struct task_fseg_t
{
qse_ubi_t handle;
qse_foff_t left;
qse_foff_t offset;
};
static int task_init_fseg (
qse_httpd_t* httpd, qse_httpd_client_t* client, qse_httpd_task_t* task)
{
task_fseg_t* xtn = qse_httpd_gettaskxtn (httpd, task);
QSE_MEMCPY (xtn, task->ctx, QSE_SIZEOF(*xtn));
task->ctx = xtn;
return 0;
}
static void task_fini_fseg (
qse_httpd_t* httpd, qse_httpd_client_t* client, qse_httpd_task_t* task)
{
task_fseg_t* ctx = (task_fseg_t*)task->ctx;
httpd->cbs->file.close (httpd, ctx->handle);
}
static int task_main_fseg (
qse_httpd_t* httpd, qse_httpd_client_t* client, qse_httpd_task_t* task)
{
qse_ssize_t n;
qse_size_t count;
task_fseg_t* ctx = (task_fseg_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 = httpd->cbs->client.sendfile (
httpd, client, ctx->handle, &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 */
}
static qse_httpd_task_t* entask_file_segment (
qse_httpd_t* httpd, qse_httpd_client_t* client,
qse_httpd_task_t* pred,
qse_ubi_t handle, qse_foff_t offset, qse_foff_t size)
{
qse_httpd_task_t task;
task_fseg_t data;
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_fseg;
task.main = task_main_fseg;
task.fini = task_fini_fseg;
task.ctx = &data;
qse_printf (QSE_T("Debug: entasking file segment (%d)\n"), client->handle.i);
return qse_httpd_entask (httpd, client, pred, &task, QSE_SIZEOF(data));
}
/*------------------------------------------------------------------------*/
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));
qse_mbscpy ((qse_mchar_t*)(xtn + 1), xtn->path);
xtn->path = (qse_mchar_t*)(xtn + 1);
task->ctx = xtn;
return 0;
}
static QSE_INLINE int task_main_file (
qse_httpd_t* httpd, qse_httpd_client_t* client, qse_httpd_task_t* task)
{
task_file_t* file;
qse_httpd_task_t* x;
qse_ubi_t handle;
int fileopen = 0;
qse_httpd_stat_t st;
file = (task_file_t*)task->ctx;
x = task;
/* TODO: if you should deal with files on a network-mounted drive,
setting a trigger or non-blocking I/O are needed. */
qse_printf (QSE_T("opening file %hs\n"), file->path);
httpd->errnum = QSE_HTTPD_ENOERR;
if (httpd->cbs->file.stat (httpd, file->path, &st) <= -1)
{
int http_errnum;
http_errnum = (httpd->errnum == QSE_HTTPD_ENOENT)? 404:
(httpd->errnum == QSE_HTTPD_EACCES)? 403: 500;
x = qse_httpd_entask_error (
httpd, client, x, http_errnum,
&file->version, file->keepalive);
goto no_file_send;
}
httpd->errnum = QSE_HTTPD_ENOERR;
if (httpd->cbs->file.ropen (httpd, file->path, &handle) <= -1)
{
int http_errnum;
http_errnum = (httpd->errnum == QSE_HTTPD_ENOENT)? 404:
(httpd->errnum == QSE_HTTPD_EACCES)? 403: 500;
x = qse_httpd_entask_error (
httpd, client, x, http_errnum,
&file->version, file->keepalive);
goto no_file_send;
}
fileopen = 1;
if (file->range.type != QSE_HTTP_RANGE_NONE)
{
if (file->range.type == QSE_HTTP_RANGE_SUFFIX)
{
if (file->range.to > st.size) file->range.to = st.size;
file->range.from = st.size - file->range.to;
file->range.to = file->range.to + file->range.from;
if (st.size > 0) file->range.to--;
}
if (file->range.from >= st.size)
{
x = qse_httpd_entask_error (
httpd, client, x, 416, &file->version, file->keepalive);
goto no_file_send;
}
if (file->range.to >= st.size) file->range.to = st.size - 1;
#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"),
file->version.major,
file->version.minor,
(file->keepalive? QSE_MT("keep-alive"): QSE_MT("close")),
(st.mime? QSE_MT("Content-Type: "): QSE_MT("")),
(st.mime? st.mime: QSE_MT("")),
(st.mime? QSE_MT("\r\n"): QSE_MT("")),
(unsigned long long)(file->range.to - file->range.from + 1),
(unsigned long long)file->range.from,
(unsigned long long)file->range.to,
(unsigned long long)st.size
);
#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"),
file->version.major,
file->version.minor,
(file->keepalive? QSE_MT("keep-alive"): QSE_MT("close")),
(st.mime? QSE_MT("Content-Type: "): QSE_MT("")),
(st.mime? st.mime: QSE_MT("")),
(st.mime? QSE_MT("\r\n"): QSE_MT("")),
(unsigned long)(file->range.to - file->range.from + 1),
(unsigned long)file->range.from,
(unsigned long)file->range.to,
(unsigned long)st.size
);
#endif
if (x)
{
x = entask_file_segment (
httpd, client, x,
handle,
file->range.from,
(file->range.to - file->range.from + 1)
);
}
}
else
{
/* TODO: int64 format.... don't hard code it llu */
/* 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"),
file->version.major, file->version.minor,
(file->keepalive? QSE_MT("keep-alive"): QSE_MT("close")),
(st.mime? QSE_MT("Content-Type: "): QSE_MT("")),
(st.mime? st.mime: QSE_MT("")),
(st.mime? QSE_MT("\r\n"): QSE_MT("")),
(unsigned long long)st.size
);
#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"),
file->version.major,
file->version.minor,
(file->keepalive? QSE_MT("keep-alive"): QSE_MT("close")),
(st.mime? QSE_MT("Content-Type: "): QSE_MT("")),
(st.mime? st.mime: QSE_MT("")),
(st.mime? QSE_MT("\r\n"): QSE_MT("")),
(unsigned long)st.size
);
#endif
if (x) x = entask_file_segment (httpd, client, x, handle, 0, st.size);
}
if (x) return 0;
httpd->cbs->file.close (httpd, handle);
return -1;
no_file_send:
if (fileopen) httpd->cbs->file.close (httpd, handle);
return (x == QSE_NULL)? -1: 0;
}
qse_httpd_task_t* qse_httpd_entaskfile (
qse_httpd_t* httpd,
qse_httpd_client_t* client,
qse_httpd_task_t* pred,
const qse_mchar_t* path,
qse_htre_t* req)
{
qse_httpd_task_t task;
task_file_t data;
const qse_htre_hdrval_t* tmp;
QSE_MEMSET (&data, 0, QSE_SIZEOF(data));
data.path = path;
data.version = *qse_htre_getversion(req);
data.keepalive = (req->attr.flags & QSE_HTRE_ATTR_KEEPALIVE);
tmp = qse_htre_getheaderval(req, QSE_MT("Range"));
if (tmp)
{
while (tmp->next) tmp = tmp->next; /* get the last value */
if (qse_parsehttprange (tmp->ptr, &data.range) <= -1)
{
return qse_httpd_entaskerror (httpd, client, pred, 416, req);
}
}
else
{
data.range.type = QSE_HTTP_RANGE_NONE;
}
/*
TODO: If-Modified-Since...
tmp = qse_htre_getheaderval(req, QSE_MT("If-Modified-Since"));
if (tmp)
{
}
*/
QSE_MEMSET (&task, 0, QSE_SIZEOF(task));
task.init = task_init_file;
task.main = task_main_file;
task.ctx = &data;
return qse_httpd_entask (httpd, client, pred, &task,
QSE_SIZEOF(task_file_t) + qse_mbslen(path) + 1);
}

View File

@ -1423,9 +1423,14 @@ qse_printf (QSE_T("Entasking chunked CGI...\n"));
#else
if (peek)
{
qse_stat_t st;
qse_httpd_discardcontent (httpd, req);
task = qse_httpd_entaskfile (
httpd, client, QSE_NULL, qpath, req);
if (QSE_LSTAT (qpath, &st) == 0 && S_ISDIR(st.st_mode))
task = qse_httpd_entaskdir (httpd, client, QSE_NULL, qpath, req);
else
task = qse_httpd_entaskfile (httpd, client, QSE_NULL, qpath, req);
if (task == QSE_NULL) goto oops;
}
#endif
@ -1573,11 +1578,6 @@ static int handle_request (
}
}
int list_directory (qse_httpd_t* httpd, const qse_mchar_t* path)
{
return 404;
}
static qse_httpd_cbs_t httpd_standard_callbacks =
{
/* server */
@ -1622,8 +1622,6 @@ static qse_httpd_cbs_t httpd_standard_callbacks =
/* http request */
peek_request,
handle_request,
list_directory
};
int qse_httpd_loopstd (qse_httpd_t* httpd, qse_ntime_t timeout)

View File

@ -32,7 +32,6 @@
#include <stdarg.h>
#include <stdio.h>
#include <dirent.h>
/* TODO:
* many functions in this file use qse_size_t.
@ -440,830 +439,6 @@ qse_httpd_task_t* qse_httpd_entaskauth (
/*------------------------------------------------------------------------*/
#if 0
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[4096];*/
qse_mchar_t buf[512]; /* TOOD: increate size */
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 */
/* TODO: change snprintf to qse_fmtuintmaxtombs() */
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:
httpd->errnum = QSE_HTTPD_ENOERR;
n = httpd->cbs->client.send (
httpd, client, &ctx->buf[ctx->bufpos], ctx->buflen);
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:
httpd->errnum = QSE_HTTPD_ENOERR;
n = httpd->cbs->client.send (
httpd, client, &ctx->buf[ctx->bufpos], ctx->buflen);
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,
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));
}
/*------------------------------------------------------------------------*/
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_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\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\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 = qse_httpd_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;
qse_lstat_t st;
if (QSE_LSTAT (data->name, &st) <= -1)
{
return (qse_httpd_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,
qse_httpd_task_t* pred,
const qse_mchar_t* name,
qse_htre_t* req)
{
qse_httpd_task_t task;
task_path_t data;
const qse_htre_hdrval_t* tmp;
QSE_MEMSET (&data, 0, QSE_SIZEOF(data));
data.name = name;
data.version = *qse_htre_getversion(req);
data.keepalive = req->attr.keepalive;
tmp = qse_htre_getheaderval(req, QSE_MT("Range"));
if (tmp)
{
while (tmp->next) tmp = tmp->next; /* get the last value */
if (qse_parsehttprange (tmp->ptr, &data.range) <= -1)
{
return qse_httpd_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);
}
/*------------------------------------------------------------------------*/
#endif
typedef struct task_file_t task_file_t;
struct task_file_t
{
const qse_mchar_t* path;
qse_http_range_t range;
qse_http_version_t version;
int keepalive;
};
typedef struct task_fseg_t task_fseg_t;
struct task_fseg_t
{
qse_ubi_t handle;
qse_foff_t left;
qse_foff_t offset;
};
static int task_init_fseg (
qse_httpd_t* httpd, qse_httpd_client_t* client, qse_httpd_task_t* task)
{
task_fseg_t* xtn = qse_httpd_gettaskxtn (httpd, task);
QSE_MEMCPY (xtn, task->ctx, QSE_SIZEOF(*xtn));
task->ctx = xtn;
return 0;
}
static void task_fini_fseg (
qse_httpd_t* httpd, qse_httpd_client_t* client, qse_httpd_task_t* task)
{
task_fseg_t* ctx = (task_fseg_t*)task->ctx;
httpd->cbs->file.close (httpd, ctx->handle);
}
static int task_main_fseg (
qse_httpd_t* httpd, qse_httpd_client_t* client, qse_httpd_task_t* task)
{
qse_ssize_t n;
qse_size_t count;
task_fseg_t* ctx = (task_fseg_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 = httpd->cbs->client.sendfile (
httpd, client, ctx->handle, &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 */
}
static qse_httpd_task_t* entask_file_segment (
qse_httpd_t* httpd, qse_httpd_client_t* client,
qse_httpd_task_t* pred,
qse_ubi_t handle, qse_foff_t offset, qse_foff_t size)
{
qse_httpd_task_t task;
task_fseg_t data;
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_fseg;
task.main = task_main_fseg;
task.fini = task_fini_fseg;
task.ctx = &data;
qse_printf (QSE_T("Debug: entasking file segment (%d)\n"), client->handle.i);
return qse_httpd_entask (httpd, client, pred, &task, QSE_SIZEOF(data));
}
/*------------------------------------------------------------------------*/
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));
qse_mbscpy ((qse_mchar_t*)(xtn + 1), xtn->path);
xtn->path = (qse_mchar_t*)(xtn + 1);
task->ctx = xtn;
return 0;
}
static QSE_INLINE int task_main_file (
qse_httpd_t* httpd, qse_httpd_client_t* client, qse_httpd_task_t* task)
{
task_file_t* file;
qse_httpd_task_t* x;
qse_ubi_t handle;
int fileopen = 0;
qse_httpd_stat_t st;
file = (task_file_t*)task->ctx;
x = task;
/* TODO: if you should deal with files on a network-mounted drive,
setting a trigger or non-blocking I/O are needed. */
qse_printf (QSE_T("opening file %hs\n"), file->path);
httpd->errnum = QSE_HTTPD_ENOERR;
if (httpd->cbs->file.stat (httpd, file->path, &st) <= -1)
{
int http_errnum;
http_errnum = (httpd->errnum == QSE_HTTPD_ENOENT)? 404:
(httpd->errnum == QSE_HTTPD_EACCES)? 403: 500;
x = qse_httpd_entask_error (
httpd, client, x, http_errnum,
&file->version, file->keepalive);
goto no_file_send;
}
httpd->errnum = QSE_HTTPD_ENOERR;
if (httpd->cbs->file.ropen (httpd, file->path, &handle) <= -1)
{
int http_errnum;
http_errnum = (httpd->errnum == QSE_HTTPD_ENOENT)? 404:
(httpd->errnum == QSE_HTTPD_EACCES)? 403: 500;
x = qse_httpd_entask_error (
httpd, client, x, http_errnum,
&file->version, file->keepalive);
goto no_file_send;
}
fileopen = 1;
if (file->range.type != QSE_HTTP_RANGE_NONE)
{
if (file->range.type == QSE_HTTP_RANGE_SUFFIX)
{
if (file->range.to > st.size) file->range.to = st.size;
file->range.from = st.size - file->range.to;
file->range.to = file->range.to + file->range.from;
if (st.size > 0) file->range.to--;
}
if (file->range.from >= st.size)
{
x = qse_httpd_entask_error (
httpd, client, x, 416, &file->version, file->keepalive);
goto no_file_send;
}
if (file->range.to >= st.size) file->range.to = st.size - 1;
#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"),
file->version.major,
file->version.minor,
(file->keepalive? QSE_MT("keep-alive"): QSE_MT("close")),
(st.mime? QSE_MT("Content-Type: "): QSE_MT("")),
(st.mime? st.mime: QSE_MT("")),
(st.mime? QSE_MT("\r\n"): QSE_MT("")),
(unsigned long long)(file->range.to - file->range.from + 1),
(unsigned long long)file->range.from,
(unsigned long long)file->range.to,
(unsigned long long)st.size
);
#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"),
file->version.major,
file->version.minor,
(file->keepalive? QSE_MT("keep-alive"): QSE_MT("close")),
(st.mime? QSE_MT("Content-Type: "): QSE_MT("")),
(st.mime? st.mime: QSE_MT("")),
(st.mime? QSE_MT("\r\n"): QSE_MT("")),
(unsigned long)(file->range.to - file->range.from + 1),
(unsigned long)file->range.from,
(unsigned long)file->range.to,
(unsigned long)st.size
);
#endif
if (x)
{
x = entask_file_segment (
httpd, client, x,
handle,
file->range.from,
(file->range.to - file->range.from + 1)
);
}
}
else
{
/* TODO: int64 format.... don't hard code it llu */
/* 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"),
file->version.major, file->version.minor,
(file->keepalive? QSE_MT("keep-alive"): QSE_MT("close")),
(st.mime? QSE_MT("Content-Type: "): QSE_MT("")),
(st.mime? st.mime: QSE_MT("")),
(st.mime? QSE_MT("\r\n"): QSE_MT("")),
(unsigned long long)st.size
);
#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"),
file->version.major,
file->version.minor,
(file->keepalive? QSE_MT("keep-alive"): QSE_MT("close")),
(st.mime? QSE_MT("Content-Type: "): QSE_MT("")),
(st.mime? st.mime: QSE_MT("")),
(st.mime? QSE_MT("\r\n"): QSE_MT("")),
(unsigned long)st.size
);
#endif
if (x)
{
x = entask_file_segment (httpd, client, x, handle, 0, st.size);
}
}
return (x == QSE_NULL)? -1: 0;
no_file_send:
if (fileopen) httpd->cbs->file.close (httpd, handle);
return (x == QSE_NULL)? -1: 0;
}
qse_httpd_task_t* qse_httpd_entaskfile (
qse_httpd_t* httpd,
qse_httpd_client_t* client,
qse_httpd_task_t* pred,
const qse_mchar_t* path,
qse_htre_t* req)
{
qse_httpd_task_t task;
task_file_t data;
const qse_htre_hdrval_t* tmp;
QSE_MEMSET (&data, 0, QSE_SIZEOF(data));
data.path = path;
data.version = *qse_htre_getversion(req);
data.keepalive = (req->attr.flags & QSE_HTRE_ATTR_KEEPALIVE);
tmp = qse_htre_getheaderval(req, QSE_MT("Range"));
if (tmp)
{
while (tmp->next) tmp = tmp->next; /* get the last value */
if (qse_parsehttprange (tmp->ptr, &data.range) <= -1)
{
return qse_httpd_entaskerror (httpd, client, pred, 416, req);
}
}
else
{
data.range.type = QSE_HTTP_RANGE_NONE;
}
/*
TODO: If-Modified-Since...
tmp = qse_htre_getheaderval(req, QSE_MT("If-Modified-Since"));
if (tmp)
{
}
*/
QSE_MEMSET (&task, 0, QSE_SIZEOF(task));
task.init = task_init_file;
task.main = task_main_file;
task.ctx = &data;
return qse_httpd_entask (httpd, client, pred, &task,
QSE_SIZEOF(task_file_t) + qse_mbslen(path) + 1);
}
/*------------------------------------------------------------------------*/
#if 0
qse_httpd_task_t* qse_httpd_entaskresol (
qse_httpd_t* httpd,
qse_httpd_client_t* client,
qse_httpd_task_t* pred,
const qse_mchar_t* host,
qse_htre_t* req)
{
qse_httpd_task_t task;
task_resol_arg_t arg;
arg.peer_nwad = *nwad;
arg.req = req;
QSE_MEMSET (&task, 0, QSE_SIZEOF(task));
task.init = task_init_resol;
task.fini = task_fini_resol;
task.main = task_main_resol;
task.ctx = &arg;
return qse_httpd_entask (
httpd, client, pred, &task, QSE_SIZEOF(task_resol_t)
);
}
#endif
/*------------------------------------------------------------------------*/
#if 0
qse_httpd_task_t* qse_httpd_entaskconnect (
qse_httpd_t* httpd,