diff --git a/qse/include/qse/net/httpd.h b/qse/include/qse/net/httpd.h index eeb4f94c..d1d341a4 100644 --- a/qse/include/qse/net/httpd.h +++ b/qse/include/qse/net/httpd.h @@ -65,6 +65,7 @@ enum qse_httpd_option_t typedef struct qse_httpd_stat_t qse_httpd_stat_t; struct qse_httpd_stat_t { + int isdir; qse_long_t dev; qse_long_t ino; qse_foff_t size; @@ -123,6 +124,14 @@ typedef int (*qse_httpd_muxcb_t) ( void* cbarg ); +typedef struct qse_httpd_dirent_t qse_httpd_dirent_t; + +struct qse_httpd_dirent_t +{ + qse_mchar_t* name; + qse_httpd_stat_t stat; +}; + typedef struct qse_httpd_scb_t qse_httpd_scb_t; struct qse_httpd_scb_t { @@ -191,6 +200,17 @@ struct qse_httpd_scb_t const qse_mchar_t* buf, qse_size_t len); } file; + struct + { + int (*open) ( + qse_httpd_t* httpd, const qse_mchar_t* path, + qse_ubi_t* handle); + void (*close) (qse_httpd_t* httpd, qse_ubi_t handle); + int (*read) ( + qse_httpd_t* httpd, qse_ubi_t handle, + qse_httpd_dirent_t* ent); + } dir; + struct { void (*close) ( @@ -225,6 +245,7 @@ struct qse_httpd_scb_t qse_httpd_t* httpd, qse_httpd_client_t* client); /* optional */ } client; + }; typedef struct qse_httpd_rcb_t qse_httpd_rcb_t; @@ -363,6 +384,7 @@ struct qse_httpd_rsrc_t struct { const qse_mchar_t* path; + const qse_mchar_t* css; } dir; struct @@ -459,6 +481,8 @@ enum qse_httpd_server_xtn_cfg_idx_t QSE_HTTPD_SERVER_XTN_CFG_USERNAME, QSE_HTTPD_SERVER_XTN_CFG_PASSWORD, QSE_HTTPD_SERVER_XTN_CFG_BASICAUTH, + QSE_HTTPD_SERVER_XTN_CFG_DIRCSS, /* can't be too long due to internal buffer size */ + QSE_HTTPD_SERVER_XTN_CFG_ERRORCSS, QSE_HTTPD_SERVER_XTN_CFG_MAX }; @@ -680,6 +704,7 @@ qse_httpd_task_t* qse_httpd_entaskdir ( qse_httpd_client_t* client, qse_httpd_task_t* pred, const qse_mchar_t* name, + const qse_mchar_t* css, qse_htre_t* req ); diff --git a/qse/lib/net/httpd-dir.c b/qse/lib/net/httpd-dir.c index a7692f2b..8253c713 100644 --- a/qse/lib/net/httpd-dir.c +++ b/qse/lib/net/httpd-dir.c @@ -24,22 +24,13 @@ #include #include -#if defined(_WIN32) - /* TODO: */ -#elif defined(__OS2__) - /* TODO: */ -#elif defined(__DOS__) - /* TODO: */ -#else -# include "../cmn/syscall.h" -#endif - #include /* TODO: remove this */ typedef struct task_dir_t task_dir_t; struct task_dir_t { qse_mcstr_t path; + qse_mcstr_t css; qse_mcstr_t qpath; qse_http_version_t version; int keepalive; @@ -53,19 +44,24 @@ struct task_dseg_t int chunked; qse_mcstr_t path; + qse_mcstr_t css; qse_mcstr_t qpath; - qse_dir_t* handle; - qse_dirent_t* dent; + qse_ubi_t handle; + qse_httpd_dirent_t dent; #define HEADER_ADDED (1 << 0) #define FOOTER_ADDED (1 << 1) #define FOOTER_PENDING (1 << 2) #define DIRENT_PENDING (1 << 3) +#define DIRENT_NOMORE (1 << 4) int state; qse_size_t tcount; /* total directory entries */ qse_size_t dcount; /* the number of items in the buffer */ + qse_mchar_t tmbuf[128]; + qse_mchar_t fszbuf[128]; + qse_mchar_t buf[4096]; qse_size_t bufpos; qse_size_t buflen; @@ -83,7 +79,9 @@ static int task_init_dseg ( xtn->path.ptr = (qse_mchar_t*)(xtn + 1); qse_mbscpy ((qse_mchar_t*)xtn->path.ptr, arg->path.ptr); - xtn->qpath.ptr = xtn->path.ptr + xtn->path.len + 1; + xtn->css.ptr = xtn->path.ptr + xtn->path.len + 1; + qse_mbscpy ((qse_mchar_t*)xtn->css.ptr, arg->css.ptr); + xtn->qpath.ptr = xtn->css.ptr + xtn->css.len + 1; qse_mbscpy ((qse_mchar_t*)xtn->qpath.ptr, arg->qpath.ptr); task->ctx = xtn; @@ -95,7 +93,7 @@ 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); + httpd->scb->dir.close (httpd, ctx->handle); } #define SIZE_CHLEN 4 /* the space size to hold the hexadecimal chunk length */ @@ -145,13 +143,13 @@ static int add_footer (task_dseg_t* ctx) { x = snprintf ( &ctx->buf[ctx->buflen], ctx->bufrem, - QSE_MT("Total %lu entries\r\n0\r\n"), (unsigned long)ctx->tcount); + QSE_MT("\r\n0\r\n")); } else { x = snprintf ( &ctx->buf[ctx->buflen], ctx->bufrem, - QSE_MT("Total %lu entries"), (unsigned long)ctx->tcount); + QSE_MT("")); } if (x == -1 || x >= ctx->bufrem) @@ -254,8 +252,12 @@ static int task_main_dseg ( /* TODO: html escaping of ctx->qpath.ptr */ x = snprintf ( &ctx->buf[ctx->buflen], ctx->bufrem, - QSE_MT("%s
    %s"), - ctx->qpath.ptr, (is_root? QSE_MT(""): QSE_MT("
  • ..
  • ")) + QSE_MT("%s%s%s%s%s"), + (ctx->css.len > 0? QSE_MT(""): QSE_MT("")), + ctx->qpath.ptr, + (is_root? QSE_MT(""): QSE_MT("")) ); if (x == -1 || x >= ctx->bufrem) { @@ -273,18 +275,22 @@ static int task_main_dseg ( ctx->dcount++; } - /*if (!ctx->dent) ctx->dent = QSE_READDIR (ctx->handle); */ if (ctx->state & DIRENT_PENDING) + { ctx->state &= ~DIRENT_PENDING; + } else - ctx->dent = QSE_READDIR (ctx->handle); + { + if (httpd->scb->dir.read (httpd, ctx->handle, &ctx->dent) <= 0) + ctx->state |= DIRENT_NOMORE; + } do { - if (!ctx->dent) + if (ctx->state & DIRENT_NOMORE) { - /* TODO: check if errno has changed from before QSE_READDIR(). - and return -1 if so. */ + /* no more directory entry */ + if (add_footer (ctx) <= -1) { /* failed to add the footer part */ @@ -298,31 +304,55 @@ static int task_main_dseg ( else if (ctx->chunked) fill_chunk_length (ctx); break; } - else if (qse_mbscmp (ctx->dent->d_name, QSE_MT(".")) != 0 && - qse_mbscmp (ctx->dent->d_name, QSE_MT("..")) != 0) + + + if (qse_mbscmp (ctx->dent.name, QSE_MT(".")) != 0 && + qse_mbscmp (ctx->dent.name, QSE_MT("..")) != 0) { qse_mchar_t* encname; + qse_btime_t bt; /* TODO: better buffer management in case there are * a lot of file names to escape. */ - encname = qse_perenchttpstrdup (ctx->dent->d_name, httpd->mmgr); + encname = qse_perenchttpstrdup (ctx->dent.name, httpd->mmgr); if (encname == QSE_NULL) { httpd->errnum = QSE_HTTPD_ENOMEM; return -1; } +qse_printf (QSE_T("ADDING [%hs]\n"), ctx->dent.name); + + qse_localtime (ctx->dent.stat.mtime, &bt); + snprintf (ctx->tmbuf, QSE_COUNTOF(ctx->tmbuf), + QSE_MT("%04d-%02d-%02d %02d:%02d:%02d"), + bt.year + QSE_BTIME_YEAR_BASE, bt.mon + 1, bt.mday, + bt.hour, bt.min, bt.sec); + + if (ctx->dent.stat.isdir) + { + ctx->fszbuf[0] = QSE_MT('\0'); + } + else + { + qse_fmtuintmaxtombs ( + ctx->fszbuf, QSE_COUNTOF(ctx->fszbuf), + ctx->dent.stat.size, 10, -1, QSE_MT('\0'), QSE_NULL + ); + } + x = snprintf ( &ctx->buf[ctx->buflen], ctx->bufrem, - QSE_MT("
  • %s%s
  • "), + QSE_MT(""), encname, - (ctx->dent->d_type == DT_DIR? QSE_MT("/"): QSE_MT("")), - ctx->dent->d_name, /* TODO: html escaping */ - (ctx->dent->d_type == DT_DIR? QSE_MT("/"): QSE_MT("")) + (ctx->dent.stat.isdir? QSE_MT("/"): QSE_MT("")), + ctx->dent.name, /* TODO: html escaping */ + (ctx->dent.stat.isdir? QSE_MT("/"): QSE_MT("")), + ctx->tmbuf, ctx->fszbuf ); - if (encname != ctx->dent->d_name) QSE_MMGR_FREE (httpd->mmgr, encname); + if (encname != ctx->dent.name) QSE_MMGR_FREE (httpd->mmgr, encname); if (x == -1 || x >= ctx->bufrem) { @@ -355,7 +385,8 @@ static int task_main_dseg ( } } - ctx->dent = QSE_READDIR (ctx->handle); + if (httpd->scb->dir.read (httpd, ctx->handle, &ctx->dent) <= 0) + ctx->state |= DIRENT_NOMORE; } while (1); @@ -369,12 +400,12 @@ send_dirlist: /* NOTE if (n == 0), it will enter an infinite loop */ ctx->bufpos += n; - return (ctx->bufpos < ctx->buflen || (ctx->state & FOOTER_PENDING) || ctx->dent)? 1: 0; + return (ctx->bufpos < ctx->buflen || (ctx->state & FOOTER_PENDING) || !(ctx->state & DIRENT_NOMORE))? 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, task_dir_t* dir) + qse_httpd_task_t* pred, qse_ubi_t handle, task_dir_t* dir) { qse_httpd_task_t task; task_dseg_t data; @@ -385,6 +416,7 @@ static qse_httpd_task_t* entask_directory_segment ( data.keepalive = dir->keepalive; data.chunked = dir->keepalive; data.path = dir->path; + data.css = dir->css; data.qpath = dir->qpath; QSE_MEMSET (&task, 0, QSE_SIZEOF(task)); @@ -393,7 +425,7 @@ static qse_httpd_task_t* entask_directory_segment ( task.fini = task_fini_dseg; task.ctx = &data; - return qse_httpd_entask (httpd, client, pred, &task, QSE_SIZEOF(data) + data.path.len + 1 + data.qpath.len + 1); + return qse_httpd_entask (httpd, client, pred, &task, QSE_SIZEOF(data) + data.path.len + 1 + data.css.len + 1 + data.qpath.len + 1); } /*------------------------------------------------------------------------*/ @@ -409,7 +441,9 @@ static int task_init_dir ( xtn->path.ptr = (qse_mchar_t*)(xtn + 1); qse_mbscpy ((qse_mchar_t*)xtn->path.ptr, arg->path.ptr); - xtn->qpath.ptr = xtn->path.ptr + xtn->path.len + 1; + xtn->css.ptr = xtn->path.ptr + xtn->path.len + 1; + qse_mbscpy ((qse_mchar_t*)xtn->css.ptr, arg->css.ptr); + xtn->qpath.ptr = xtn->css.ptr + xtn->css.len + 1; qse_mbscpy ((qse_mchar_t*)xtn->qpath.ptr, arg->qpath.ptr); /* switch the context to the extension area */ @@ -423,15 +457,25 @@ static QSE_INLINE int task_main_dir ( { task_dir_t* dir; qse_httpd_task_t* x; - qse_dir_t* handle = QSE_NULL; + qse_ubi_t handle; dir = (task_dir_t*)task->ctx; x = task; if (qse_mbsend (dir->path.ptr, QSE_MT("/"))) { - handle = QSE_OPENDIR (dir->path.ptr); - if (handle) + if (httpd->scb->dir.open (httpd, dir->path.ptr, &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, + &dir->version, dir->keepalive); + + return (x == QSE_NULL)? -1: 0; + } + else { x = qse_httpd_entaskformat ( httpd, client, x, @@ -445,21 +489,9 @@ static QSE_INLINE int task_main_dir ( if (x) x = entask_directory_segment (httpd, client, x, handle, dir); if (x) return 0; - QSE_CLOSEDIR (handle); + httpd->scb->dir.close (httpd, 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 { @@ -481,14 +513,19 @@ qse_httpd_task_t* qse_httpd_entaskdir ( qse_httpd_client_t* client, qse_httpd_task_t* pred, const qse_mchar_t* path, + const qse_mchar_t* css, qse_htre_t* req) { qse_httpd_task_t task; task_dir_t data; + if (css == QSE_NULL) css = QSE_MT(""); + QSE_MEMSET (&data, 0, QSE_SIZEOF(data)); data.path.ptr = path; data.path.len = qse_mbslen(data.path.ptr); + data.css.ptr = css; + data.css.len = qse_mbslen(data.css.ptr); data.qpath.ptr = qse_htre_getqpath(req); data.qpath.len = qse_mbslen(data.qpath.ptr); data.version = *qse_htre_getversion(req); @@ -500,6 +537,6 @@ qse_httpd_task_t* qse_httpd_entaskdir ( task.ctx = &data; return qse_httpd_entask (httpd, client, pred, &task, - QSE_SIZEOF(task_dir_t) + data.path.len + 1 + data.qpath.len + 1); + QSE_SIZEOF(task_dir_t) + data.path.len + 1 + data.css.len + 1 + data.qpath.len + 1); } diff --git a/qse/lib/net/httpd-std.c b/qse/lib/net/httpd-std.c index d76f7338..9b655780 100644 --- a/qse/lib/net/httpd-std.c +++ b/qse/lib/net/httpd-std.c @@ -75,6 +75,7 @@ #define SERVER_XTN_CFG_USERNAME QSE_HTTPD_SERVER_XTN_CFG_USERNAME #define SERVER_XTN_CFG_PASSWORD QSE_HTTPD_SERVER_XTN_CFG_PASSWORD #define SERVER_XTN_CFG_BASICAUTH QSE_HTTPD_SERVER_XTN_CFG_BASICAUTH +#define SERVER_XTN_CFG_DIRCSS QSE_HTTPD_SERVER_XTN_CFG_DIRCSS /* ------------------------------------------------------------------- */ #if defined(_WIN32) @@ -1042,20 +1043,14 @@ static int mux_writable (qse_httpd_t* httpd, qse_ubi_t handle, qse_ntoff_t msec) /* ------------------------------------------------------------------- */ -static int file_executable (qse_httpd_t* httpd, const qse_mchar_t* path) -{ - if (access (path, X_OK) == -1) - return (errno == EACCES)? 0 /*no*/: -1 /*error*/; - return 1; /* yes */ -} -static int file_stat ( - qse_httpd_t* httpd, const qse_mchar_t* path, qse_httpd_stat_t* hst) +static int stat_file ( + qse_httpd_t* httpd, const qse_mchar_t* path, qse_httpd_stat_t* hst, int regonly) { struct stat st; /* TODO: lstat? or stat? */ - if (stat (path, &st) <= -1) + if (QSE_STAT (path, &st) <= -1) { qse_httpd_seterrnum (httpd, syserr_to_errnum(errno)); return -1; @@ -1063,7 +1058,7 @@ static int file_stat ( /* stating for a file. it should be a regular file. * i don't allow other file types. */ - if (!S_ISREG(st.st_mode)) + if (regonly && !S_ISREG(st.st_mode)) { qse_httpd_seterrnum (httpd, QSE_HTTPD_EACCES); return -1; @@ -1071,6 +1066,7 @@ static int file_stat ( QSE_MEMSET (hst, 0, QSE_SIZEOF(*hst)); + hst->isdir = S_ISDIR(st.st_mode); hst->dev = st.st_dev; hst->ino = st.st_ino; hst->size = st.st_size; @@ -1085,6 +1081,21 @@ static int file_stat ( return 0; } +/* ------------------------------------------------------------------- */ + +static int file_executable (qse_httpd_t* httpd, const qse_mchar_t* path) +{ + if (access (path, X_OK) == -1) + return (errno == EACCES)? 0 /*no*/: -1 /*error*/; + return 1; /* yes */ +} + +static int file_stat ( + qse_httpd_t* httpd, const qse_mchar_t* path, qse_httpd_stat_t* hst) +{ + return stat_file (httpd, path, hst, 1); +} + static int file_ropen ( qse_httpd_t* httpd, const qse_mchar_t* path, qse_ubi_t* handle) { @@ -1156,6 +1167,93 @@ static qse_ssize_t file_write ( return QSE_WRITE (handle.i, buf, len); } +/* ------------------------------------------------------------------- */ + +typedef struct dir_t dir_t; +struct dir_t +{ + qse_mchar_t* path; + qse_dir_t* dp; +}; + +static int dir_open (qse_httpd_t* httpd, const qse_mchar_t* path, qse_ubi_t* handle) +{ + dir_t* d; + + d = QSE_MMGR_ALLOC (httpd->mmgr, QSE_SIZEOF(*d)); + if (d == QSE_NULL) + { + qse_httpd_seterrnum (httpd, QSE_HTTPD_ENOMEM); + return -1; + } + + d->path = qse_mbsdup (path, httpd->mmgr); + if (d->path == QSE_NULL) + { + qse_httpd_seterrnum (httpd, QSE_HTTPD_ENOMEM); + QSE_MMGR_FREE (httpd->mmgr, d); + return -1; + } + + d->dp = QSE_OPENDIR (path); + if (d->dp == QSE_NULL) + { + qse_httpd_seterrnum (httpd, syserr_to_errnum(errno)); + QSE_MMGR_FREE (httpd->mmgr, d->path); + QSE_MMGR_FREE (httpd->mmgr, d); + return -1; + } + + handle->ptr = d; + return 0; +} + +static void dir_close (qse_httpd_t* httpd, qse_ubi_t handle) +{ + dir_t* d; + + d = (dir_t*)handle.ptr; + + QSE_CLOSEDIR (d->dp); + + QSE_MMGR_FREE (httpd->mmgr, d->path); + QSE_MMGR_FREE (httpd->mmgr, d); +} + +static int dir_read (qse_httpd_t* httpd, qse_ubi_t handle, qse_httpd_dirent_t* dirent) +{ + dir_t* d; + qse_dirent_t* de; + qse_mchar_t* fpath; + int n; + + d = (dir_t*)handle.ptr; + + errno = 0; + de = QSE_READDIR (d->dp); + if (de == QSE_NULL) + { + if (errno == 0) return 0; + qse_httpd_seterrnum (httpd, syserr_to_errnum(errno)); + return -1; + } + + /* i assume that d->path ends with a slash */ + fpath = qse_mbsdup2 (d->path, de->d_name, httpd->mmgr); + if (fpath == QSE_NULL) + { + qse_httpd_seterrnum (httpd, QSE_HTTPD_ENOMEM); + return -1; + } + + n = stat_file (httpd, fpath, &dirent->stat, 0); + QSE_MMGR_FREE (httpd->mmgr, fpath); + if (n <= -1) QSE_MEMSET (dirent, 0, QSE_SIZEOF(*dirent)); + + dirent->name = de->d_name; + return 1; +} + /* ------------------------------------------------------------------- */ static void client_close ( qse_httpd_t* httpd, qse_httpd_client_t* client) @@ -1520,6 +1618,13 @@ static qse_httpd_scb_t httpd_system_callbacks = file_write }, + /* directory operation */ + { dir_open, + dir_close, + dir_read + }, + + /* client connection */ { client_close, client_shutdown, @@ -1749,6 +1854,7 @@ auth_ok: target->type = QSE_HTTPD_RSRC_DIR; target->u.dir.path = xpath; + target->u.dir.css = server_xtn->cfg[SERVER_XTN_CFG_DIRCSS].ptr; } else { diff --git a/qse/lib/net/httpd-task.c b/qse/lib/net/httpd-task.c index b537617f..d6d2b664 100644 --- a/qse/lib/net/httpd-task.c +++ b/qse/lib/net/httpd-task.c @@ -390,7 +390,7 @@ qse_httpd_task_t* qse_httpd_entaskrsrc ( case QSE_HTTPD_RSRC_DIR: qse_httpd_discardcontent (httpd, req); - task = qse_httpd_entaskdir (httpd, client, QSE_NULL, rsrc->u.dir.path, req); + task = qse_httpd_entaskdir (httpd, client, QSE_NULL, rsrc->u.dir.path, rsrc->u.dir.css, req); break; case QSE_HTTPD_RSRC_ERROR: diff --git a/qse/samples/net/httpd01.c b/qse/samples/net/httpd01.c index 4b383f4f..8b6e75a9 100644 --- a/qse/samples/net/httpd01.c +++ b/qse/samples/net/httpd01.c @@ -59,12 +59,20 @@ static int httpd_main (int argc, qse_char_t* argv[]) for (i = 1; i < argc; i++) { - if (qse_httpd_attachserverstd (httpd, argv[i], QSE_NULL, 0) == QSE_NULL) + qse_httpd_server_t* server; + qse_httpd_server_xtn_t* server_xtn; + + server = qse_httpd_attachserverstd (httpd, argv[i], QSE_NULL, 0); + if (server == QSE_NULL) { qse_fprintf (QSE_STDERR, QSE_T("Failed to add httpd listener - %s\n"), argv[i]); goto oops; } + + server_xtn = qse_httpd_getserverxtn (httpd, server); + server_xtn->cfg[QSE_HTTPD_SERVER_XTN_CFG_DIRCSS].ptr = QSE_MT("body { background-color:#d0e4fe; font-size: 0.9em; font-family: Ubuntu,'Trebuchet MS',sans-serif; }"); + server_xtn->cfg[QSE_HTTPD_SERVER_XTN_CFG_DIRCSS].len = qse_mbslen(server_xtn->cfg[QSE_HTTPD_SERVER_XTN_CFG_DIRCSS].ptr); } g_httpd = httpd;
    ..
    %s%s%s%s