diff --git a/qse/configure b/qse/configure index 9dfcff8a..7c30e813 100755 --- a/qse/configure +++ b/qse/configure @@ -16057,7 +16057,7 @@ $as_echo "#define STDC_HEADERS 1" >>confdefs.h fi -for ac_header in stddef.h wchar.h wctype.h errno.h signal.h fcntl.h +for ac_header in stddef.h wchar.h wctype.h errno.h signal.h fcntl.h dirent.h do : as_ac_Header=`$as_echo "ac_cv_header_$ac_header" | $as_tr_sh` ac_fn_c_check_header_mongrel "$LINENO" "$ac_header" "$as_ac_Header" "$ac_includes_default" @@ -16177,7 +16177,7 @@ _ACEOF fi done -for ac_func in lseek64 stat64 fstat64 lstat64 ftruncate64 +for ac_func in lseek64 stat64 fstat64 lstat64 ftruncate64 readdir64 do : as_ac_var=`$as_echo "ac_cv_func_$ac_func" | $as_tr_sh` ac_fn_c_check_func "$LINENO" "$ac_func" "$as_ac_var" diff --git a/qse/configure.ac b/qse/configure.ac index 821d6fe0..ded6543f 100644 --- a/qse/configure.ac +++ b/qse/configure.ac @@ -82,7 +82,7 @@ AC_SUBST(LIBM, $LIBM) dnl check header files. AC_HEADER_STDC -AC_CHECK_HEADERS([stddef.h wchar.h wctype.h errno.h signal.h fcntl.h]) +AC_CHECK_HEADERS([stddef.h wchar.h wctype.h errno.h signal.h fcntl.h dirent.h]) AC_CHECK_HEADERS([time.h sys/time.h utime.h spawn.h execinfo.h]) AC_CHECK_HEADERS([sys/resource.h sys/wait.h sys/syscall.h sys/sendfile.h sys/epoll.h]) AC_CHECK_HEADERS([net/if.h]) @@ -100,7 +100,7 @@ AC_CHECK_FUNCS([mbrlen mbrtowc wcrtomb]) AC_CHECK_FUNCS([mbsnrtowcs mbsrtowcs wcsnrtombs wcsrtombs]) AC_CHECK_FUNCS([wctype iswctype wctrans towctrans]) AC_CHECK_FUNCS([isblank iswblank]) -AC_CHECK_FUNCS([lseek64 stat64 fstat64 lstat64 ftruncate64]) +AC_CHECK_FUNCS([lseek64 stat64 fstat64 lstat64 ftruncate64 readdir64]) AC_CHECK_FUNCS([timegm timelocal]) AC_CHECK_FUNCS([utime utimes]) AC_CHECK_FUNCS([sysconf]) diff --git a/qse/doc/page/awk.doc b/qse/doc/page/awk.doc index f896bd4f..d11624cf 100644 --- a/qse/doc/page/awk.doc +++ b/qse/doc/page/awk.doc @@ -199,16 +199,22 @@ QSEAWK implements the language described in the book The AWK Proramming Language with various @ref awk_ext "extensions". -An AWK program, at the top level, can composed of the following elements shown below. Each language element requires the option in the second column to be on. +An AWK program can be composed of the following elements shown below. +Each language element requires the option in the second column to be on. +
Element Option
Comment
Global variable declaration#QSE_AWK_EXPLICIT
Pattern-action block #QSE_AWK_PABLOCK
User-defined function
\@include #QSE_AWK_INCLUDE
+Single line comments begin with the '#' letter and end at the end of the +same line. The C style multi-line comments are supported as well. +Comments are ignored. + - pattern-action-block := pattern action-block - pattern := BEGIN | END | expression | expression-range - expression-range := expression , expression diff --git a/qse/include/qse/config.h.in b/qse/include/qse/config.h.in index c87cf217..69d453b6 100644 --- a/qse/include/qse/config.h.in +++ b/qse/include/qse/config.h.in @@ -202,6 +202,9 @@ /* Have PTHREAD_PRIO_INHERIT. */ #undef HAVE_PTHREAD_PRIO_INHERIT +/* Define to 1 if you have the `readdir64' function. */ +#undef HAVE_READDIR64 + /* Define to 1 if you have the `round' function. */ #undef HAVE_ROUND diff --git a/qse/include/qse/net/httpd.h b/qse/include/qse/net/httpd.h index 5ee9f601..85071d0e 100644 --- a/qse/include/qse/net/httpd.h +++ b/qse/include/qse/net/httpd.h @@ -104,6 +104,12 @@ typedef int (*qse_httpd_muxcb_t) ( void* cbarg ); +typedef struct qse_httpd_dirent_t qse_httpd_dirent_t; +struct qse_httpd_dirent_t +{ +}; + + typedef struct qse_httpd_cbs_t qse_httpd_cbs_t; struct qse_httpd_cbs_t { @@ -211,8 +217,6 @@ struct qse_httpd_cbs_t qse_httpd_t* httpd, qse_httpd_client_t* client, qse_htre_t* req); int (*handle_request) ( qse_httpd_t* httpd, qse_httpd_client_t* client, qse_htre_t* req); - - int (*listdir) (qse_httpd_t* httpd, const qse_mchar_t* path); }; typedef struct qse_httpd_task_t qse_httpd_task_t; @@ -502,6 +506,14 @@ qse_httpd_task_t* qse_httpd_entaskfile ( qse_htre_t* req ); +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* name, + qse_htre_t* req +); + qse_httpd_task_t* qse_httpd_entaskcgi ( qse_httpd_t* httpd, qse_httpd_client_t* client, diff --git a/qse/lib/awk/err.c b/qse/lib/awk/err.c index 1a0f9129..24831efe 100644 --- a/qse/lib/awk/err.c +++ b/qse/lib/awk/err.c @@ -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[] = { diff --git a/qse/lib/awk/err.h b/qse/lib/awk/err.h index 473ea402..cb8fd518 100644 --- a/qse/lib/awk/err.h +++ b/qse/lib/awk/err.h @@ -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 } diff --git a/qse/lib/awk/val.c b/qse/lib/awk/val.c index 3efe9a07..5cba0a50 100644 --- a/qse/lib/awk/val.c +++ b/qse/lib/awk/val.c @@ -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)); } diff --git a/qse/lib/cmn/glob.c b/qse/lib/cmn/glob.c index c1e63e6f..18dca22a 100644 --- a/qse/lib/cmn/glob.c +++ b/qse/lib/cmn/glob.c @@ -34,9 +34,7 @@ # include # include #else -# include -# include -# include +# 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) diff --git a/qse/lib/cmn/syscall.h b/qse/lib/cmn/syscall.h index 3fdfc1b4..9acb7625 100644 --- a/qse/lib/cmn/syscall.h +++ b/qse/lib/cmn/syscall.h @@ -23,39 +23,42 @@ /* This file defines unix/linux system calls */ -#ifdef HAVE_SYS_TYPES_H +#if defined(HAVE_SYS_TYPES_H) # include #endif -#ifdef HAVE_UNISTD_H +#if defined(HAVE_UNISTD_H) # include #endif -#ifdef HAVE_SYS_WAIT_H +#if defined(HAVE_SYS_WAIT_H) # include #endif -#ifdef HAVE_SIGNAL_H +#if defined(HAVE_SIGNAL_H) # include #endif -#ifdef HAVE_ERRNO_H +#if defined(HAVE_ERRNO_H) # include #endif -#ifdef HAVE_FCNTL_H +#if defined(HAVE_FCNTL_H) # include #endif -#ifdef HAVE_TIME_H +#if defined(HAVE_TIME_H) # include #endif -#ifdef HAVE_SYS_TIME_H +#if defined(HAVE_SYS_TIME_H) # include #endif -#ifdef HAVE_UTIME_H +#if defined(HAVE_UTIME_H) # include #endif -#ifdef HAVE_SYS_RESOURCE_H +#if defined(HAVE_SYS_RESOURCE_H) # include #endif -#ifdef HAVE_SYS_STAT_H +#if defined(HAVE_SYS_STAT_H) # include #endif +#if defined(HAVE_DIRENT_H) +# include +#endif #if defined(QSE_USE_SYSCALL) && defined(HAVE_SYS_SYSCALL_H) # include @@ -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) diff --git a/qse/lib/net/Makefile.am b/qse/lib/net/Makefile.am index 49bf4013..2d738541 100644 --- a/qse/lib/net/Makefile.am +++ b/qse/lib/net/Makefile.am @@ -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 \ diff --git a/qse/lib/net/Makefile.in b/qse/lib/net/Makefile.in index 5b091304..91b1406e 100644 --- a/qse/lib/net/Makefile.in +++ b/qse/lib/net/Makefile.in @@ -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@ diff --git a/qse/lib/net/httpd-dir.c b/qse/lib/net/httpd-dir.c new file mode 100644 index 00000000..de9b00e4 --- /dev/null +++ b/qse/lib/net/httpd-dir.c @@ -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 . + */ + +#include "httpd.h" +#include "../cmn/mem.h" +#include "../cmn/syscall.h" +#include +#include /* 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("\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("Directory Listing%s
  • ..
  • "), + 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("
\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("
  • %s%s
  • "), + 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, + ""); + 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("Directory Listing%s
    • ..
    • "), + 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, + "
    "); + 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, + "
  • %s%s
  • ", + 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); +} + diff --git a/qse/lib/net/httpd-file.c b/qse/lib/net/httpd-file.c new file mode 100644 index 00000000..84780682 --- /dev/null +++ b/qse/lib/net/httpd-file.c @@ -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 . + */ + +#include "httpd.h" +#include "../cmn/mem.h" +#include "../cmn/syscall.h" +#include +#include /* 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); +} + diff --git a/qse/lib/net/httpd-std.c b/qse/lib/net/httpd-std.c index 6fdd72b4..b8c6076e 100644 --- a/qse/lib/net/httpd-std.c +++ b/qse/lib/net/httpd-std.c @@ -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) diff --git a/qse/lib/net/httpd-task.c b/qse/lib/net/httpd-task.c index cf4a0b38..b3e72b90 100644 --- a/qse/lib/net/httpd-task.c +++ b/qse/lib/net/httpd-task.c @@ -32,7 +32,6 @@ #include #include -#include /* 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("\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("Directory Listingindex of xxxx
      ") - ); - 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("
    \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("
  • %s%s
  • "), - 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, - ""); - 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, - "Directory Listingindex of xxxx
      " - ); - 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, - "
    "); - if (x == -1 || x >= ctx->bufrem) - { - ctx->footer_pending = 1; - } - else - { - ctx->buflen += x; - } - break; - } - else - { - x = snprintf ( - &ctx->buf[ctx->buflen], - ctx->bufrem, - "
  • %s%s
  • ", - 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,