2370 lines
62 KiB
C
2370 lines
62 KiB
C
/*
|
|
* $Id$
|
|
*
|
|
Copyright (c) 2006-2019 Chung, Hyung-Hwan. All rights reserved.
|
|
|
|
Redistribution and use in source and binary forms, with or without
|
|
modification, are permitted provided that the following conditions
|
|
are met:
|
|
1. Redistributions of source code must retain the above copyright
|
|
notice, this list of conditions and the following disclaimer.
|
|
2. Redistributions in binary form must reproduce the above copyright
|
|
notice, this list of conditions and the following disclaimer in the
|
|
documentation and/or other materials provided with the distribution.
|
|
|
|
THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR
|
|
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
|
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
|
IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
|
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
|
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
|
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
|
|
#include "httpd.h"
|
|
#include "../cmn/mem-prv.h"
|
|
#include <qse/cmn/chr.h>
|
|
#include <qse/cmn/str.h>
|
|
#include <qse/cmn/mbwc.h>
|
|
#include <qse/si/sio.h>
|
|
|
|
#if !defined(QSE_HTTPD_DEFAULT_MODPREFIX)
|
|
# if defined(_WIN32)
|
|
# define QSE_HTTPD_DEFAULT_MODPREFIX "qsehttpd-"
|
|
# elif defined(__OS2__)
|
|
# define QSE_HTTPD_DEFAULT_MODPREFIX "htd-"
|
|
# elif defined(__DOS__)
|
|
# define QSE_HTTPD_DEFAULT_MODPREFIX "htd-"
|
|
# else
|
|
# define QSE_HTTPD_DEFAULT_MODPREFIX "libqsehttpd-"
|
|
# endif
|
|
#endif
|
|
|
|
#if !defined(QSE_HTTPD_DEFAULT_MODPOSTFIX)
|
|
# define QSE_HTTPD_DEFAULT_MODPOSTFIX ""
|
|
#endif
|
|
|
|
typedef struct htrd_xtn_t htrd_xtn_t;
|
|
typedef struct tmr_xtn_t tmr_xtn_t;
|
|
|
|
struct htrd_xtn_t
|
|
{
|
|
qse_httpd_t* httpd;
|
|
qse_httpd_client_t* client;
|
|
};
|
|
|
|
struct tmr_xtn_t
|
|
{
|
|
qse_httpd_t* httpd;
|
|
};
|
|
|
|
static void free_server_list (qse_httpd_t* httpd);
|
|
static int perform_client_task (
|
|
qse_httpd_t* httpd, void* mux, qse_httpd_hnd_t handle, int mask, void* cbarg);
|
|
static void unload_all_modules (qse_httpd_t* httpd);
|
|
|
|
qse_http_version_t qse_http_v11 = { 1, 1 };
|
|
|
|
qse_httpd_t* qse_httpd_open (qse_mmgr_t* mmgr, qse_size_t xtnsize, qse_httpd_errnum_t* errnum)
|
|
{
|
|
qse_httpd_t* httpd;
|
|
|
|
httpd = (qse_httpd_t*)QSE_MMGR_ALLOC(mmgr, QSE_SIZEOF(qse_httpd_t) + xtnsize);
|
|
if (httpd)
|
|
{
|
|
if (qse_httpd_init (httpd, mmgr) <= -1)
|
|
{
|
|
if (errnum) *errnum = qse_httpd_geterrnum(httpd);
|
|
QSE_MMGR_FREE (mmgr, httpd);
|
|
return QSE_NULL;
|
|
}
|
|
else QSE_MEMSET (QSE_XTN(httpd), 0, xtnsize);
|
|
}
|
|
else if (errnum) *errnum = QSE_HTTPD_ENOMEM;
|
|
|
|
return httpd;
|
|
}
|
|
|
|
void qse_httpd_close (qse_httpd_t* httpd)
|
|
{
|
|
qse_httpd_fini (httpd);
|
|
QSE_MMGR_FREE (httpd->_mmgr, httpd);
|
|
}
|
|
|
|
int qse_httpd_init (qse_httpd_t* httpd, qse_mmgr_t* mmgr)
|
|
{
|
|
tmr_xtn_t* tmr_xtn;
|
|
|
|
QSE_MEMSET (httpd, 0, QSE_SIZEOF(*httpd));
|
|
|
|
httpd->_instsize = QSE_SIZEOF(*httpd);
|
|
httpd->_mmgr = mmgr;
|
|
|
|
httpd->tmr = qse_tmr_open (mmgr, QSE_SIZEOF(tmr_xtn_t), 2048);
|
|
if (httpd->tmr == QSE_NULL) return -1;
|
|
|
|
tmr_xtn = qse_tmr_getxtn (httpd->tmr);
|
|
QSE_MEMSET (tmr_xtn, 0, QSE_SIZEOF(*tmr_xtn));
|
|
tmr_xtn->httpd = httpd;
|
|
|
|
qse_mbscpy (httpd->sname, QSE_MT("QSE-HTTPD " QSE_PACKAGE_VERSION));
|
|
|
|
httpd->opt.tmout.sec = 3;
|
|
httpd->opt.idle_limit.sec = 30;
|
|
|
|
return 0;
|
|
}
|
|
|
|
void qse_httpd_fini (qse_httpd_t* httpd)
|
|
{
|
|
qse_httpd_ecb_t* ecb;
|
|
qse_size_t i;
|
|
|
|
unload_all_modules (httpd);
|
|
|
|
for (ecb = httpd->ecb; ecb; ecb = ecb->next)
|
|
if (ecb->close) ecb->close (httpd);
|
|
|
|
free_server_list (httpd);
|
|
qse_tmr_close (httpd->tmr);
|
|
|
|
for (i = 0; i < QSE_COUNTOF(httpd->opt.mod); i++)
|
|
{
|
|
if (httpd->opt.mod[i].ptr)
|
|
{
|
|
qse_httpd_freemem (httpd, httpd->opt.mod[i].ptr);
|
|
httpd->opt.mod[i].ptr = QSE_NULL;
|
|
httpd->opt.mod[i].len = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
void qse_httpd_stop (qse_httpd_t* httpd)
|
|
{
|
|
httpd->stopreq = 1;
|
|
}
|
|
|
|
void qse_httpd_impede (qse_httpd_t* httpd)
|
|
{
|
|
httpd->impedereq = 1;
|
|
}
|
|
|
|
qse_httpd_errnum_t qse_httpd_geterrnum (qse_httpd_t* httpd)
|
|
{
|
|
return httpd->errnum;
|
|
}
|
|
|
|
void qse_httpd_seterrnum (qse_httpd_t* httpd, qse_httpd_errnum_t errnum)
|
|
{
|
|
httpd->errnum = errnum;
|
|
}
|
|
|
|
static int dup_str_opt (qse_httpd_t* httpd, const void* value, qse_cstr_t* tmp)
|
|
{
|
|
if (value)
|
|
{
|
|
tmp->ptr = qse_strdup(value, qse_httpd_getmmgr(httpd));
|
|
if (tmp->ptr == QSE_NULL)
|
|
{
|
|
qse_httpd_seterrnum (httpd, QSE_HTTPD_ENOMEM);
|
|
return -1;
|
|
}
|
|
tmp->len = qse_strlen (tmp->ptr);
|
|
}
|
|
else
|
|
{
|
|
tmp->ptr = QSE_NULL;
|
|
tmp->len = 0;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int qse_httpd_getopt (qse_httpd_t* httpd, qse_httpd_opt_t id, void* value)
|
|
{
|
|
switch (id)
|
|
{
|
|
case QSE_HTTPD_TRAIT:
|
|
*(int*)value = httpd->opt.trait;
|
|
return 0;
|
|
|
|
case QSE_HTTPD_TMOUT:
|
|
*(qse_ntime_t*)value = httpd->opt.tmout;
|
|
return 0;
|
|
|
|
case QSE_HTTPD_IDLELIMIT:
|
|
*(qse_ntime_t*)value = httpd->opt.idle_limit;
|
|
return 0;
|
|
|
|
case QSE_HTTPD_MODPREFIX:
|
|
case QSE_HTTPD_MODPOSTFIX:
|
|
*(const qse_char_t**)value = httpd->opt.mod[id - QSE_HTTPD_MODPREFIX].ptr;
|
|
return 0;
|
|
|
|
case QSE_HTTPD_SCB:
|
|
*(qse_httpd_scb_t*)value = httpd->opt.scb;
|
|
return 0;
|
|
|
|
case QSE_HTTPD_RCB:
|
|
*(qse_httpd_rcb_t*)value = httpd->opt.rcb;
|
|
return 0;
|
|
}
|
|
|
|
httpd->errnum = QSE_HTTPD_EINVAL;
|
|
return -1;
|
|
}
|
|
|
|
int qse_httpd_setopt (qse_httpd_t* httpd, qse_httpd_opt_t id, const void* value)
|
|
{
|
|
switch (id)
|
|
{
|
|
case QSE_HTTPD_TRAIT:
|
|
httpd->opt.trait = *(const int*)value;
|
|
return 0;
|
|
|
|
case QSE_HTTPD_TMOUT:
|
|
httpd->opt.tmout = *(qse_ntime_t*)value;
|
|
return 0;
|
|
|
|
case QSE_HTTPD_IDLELIMIT:
|
|
httpd->opt.idle_limit = *(qse_ntime_t*)value;
|
|
return 0;
|
|
|
|
case QSE_HTTPD_MODPREFIX:
|
|
case QSE_HTTPD_MODPOSTFIX:
|
|
{
|
|
qse_cstr_t tmp;
|
|
int idx;
|
|
|
|
if (dup_str_opt(httpd, value, &tmp) <= -1) return -1;
|
|
|
|
idx = id - QSE_HTTPD_MODPREFIX;
|
|
if (httpd->opt.mod[idx].ptr) qse_httpd_freemem (httpd, httpd->opt.mod[idx].ptr);
|
|
|
|
httpd->opt.mod[idx] = tmp;
|
|
return 0;
|
|
}
|
|
|
|
case QSE_HTTPD_SCB:
|
|
httpd->opt.scb = *(qse_httpd_scb_t*)value;
|
|
return 0;
|
|
|
|
case QSE_HTTPD_RCB:
|
|
httpd->opt.rcb = *(qse_httpd_rcb_t*)value;
|
|
return 0;
|
|
|
|
}
|
|
|
|
httpd->errnum = QSE_HTTPD_EINVAL;
|
|
return -1;
|
|
}
|
|
|
|
/* ----------------------------------------------------------------------- */
|
|
|
|
qse_httpd_ecb_t* qse_httpd_popecb (qse_httpd_t* httpd)
|
|
{
|
|
qse_httpd_ecb_t* top = httpd->ecb;
|
|
if (top) httpd->ecb = top->next;
|
|
return top;
|
|
}
|
|
|
|
void qse_httpd_pushecb (qse_httpd_t* httpd, qse_httpd_ecb_t* ecb)
|
|
{
|
|
ecb->next = httpd->ecb;
|
|
httpd->ecb = ecb;
|
|
}
|
|
|
|
/* ----------------------------------------------------------------------- */
|
|
|
|
void* qse_httpd_allocmem (qse_httpd_t* httpd, qse_size_t size)
|
|
{
|
|
void* ptr = QSE_MMGR_ALLOC (qse_httpd_getmmgr(httpd), size);
|
|
if (ptr == QSE_NULL) httpd->errnum = QSE_HTTPD_ENOMEM;
|
|
return ptr;
|
|
}
|
|
|
|
void* qse_httpd_callocmem (qse_httpd_t* httpd, qse_size_t size)
|
|
{
|
|
void* ptr = QSE_MMGR_ALLOC (qse_httpd_getmmgr(httpd), size);
|
|
if (ptr == QSE_NULL) httpd->errnum = QSE_HTTPD_ENOMEM;
|
|
else QSE_MEMSET (ptr, 0, size);
|
|
return ptr;
|
|
}
|
|
|
|
void* qse_httpd_reallocmem (
|
|
qse_httpd_t* httpd, void* ptr, qse_size_t size)
|
|
{
|
|
void* nptr = QSE_MMGR_REALLOC (qse_httpd_getmmgr(httpd), ptr, size);
|
|
if (nptr == QSE_NULL) httpd->errnum = QSE_HTTPD_ENOMEM;
|
|
return nptr;
|
|
}
|
|
|
|
void qse_httpd_freemem (qse_httpd_t* httpd, void* ptr)
|
|
{
|
|
QSE_MMGR_FREE (qse_httpd_getmmgr(httpd), ptr);
|
|
}
|
|
|
|
qse_mchar_t* qse_httpd_strtombsdup (qse_httpd_t* httpd, const qse_char_t* str)
|
|
{
|
|
qse_mchar_t* mptr;
|
|
|
|
#if defined(QSE_CHAR_IS_MCHAR)
|
|
mptr = qse_mbsdup (str, qse_httpd_getmmgr(httpd));
|
|
#else
|
|
mptr = qse_wcstombsdup (str, QSE_NULL, qse_httpd_getmmgr(httpd));
|
|
#endif
|
|
|
|
if (mptr == QSE_NULL) httpd->errnum = QSE_HTTPD_ENOMEM;
|
|
return mptr;
|
|
}
|
|
|
|
qse_mchar_t* qse_httpd_strntombsdup (qse_httpd_t* httpd, const qse_char_t* str, qse_size_t len)
|
|
{
|
|
qse_mchar_t* mptr;
|
|
|
|
#if defined(QSE_CHAR_IS_MCHAR)
|
|
mptr = qse_mbsxdup (str, len, qse_httpd_getmmgr(httpd));
|
|
#else
|
|
mptr = qse_wcsntombsdup (str, len, QSE_NULL, qse_httpd_getmmgr(httpd));
|
|
#endif
|
|
|
|
if (mptr == QSE_NULL) httpd->errnum = QSE_HTTPD_ENOMEM;
|
|
return mptr;
|
|
}
|
|
|
|
qse_mchar_t* qse_httpd_mbsdup (qse_httpd_t* httpd, const qse_mchar_t* str)
|
|
{
|
|
qse_mchar_t* mptr;
|
|
|
|
mptr = qse_mbsdup (str, qse_httpd_getmmgr(httpd));
|
|
if (mptr == QSE_NULL) httpd->errnum = QSE_HTTPD_ENOMEM;
|
|
|
|
return mptr;
|
|
}
|
|
|
|
qse_mchar_t* qse_httpd_mbsxdup (qse_httpd_t* httpd, const qse_mchar_t* str, qse_size_t len)
|
|
{
|
|
qse_mchar_t* mptr;
|
|
|
|
mptr = qse_mbsxdup (str, len, qse_httpd_getmmgr(httpd));
|
|
if (mptr == QSE_NULL) httpd->errnum = QSE_HTTPD_ENOMEM;
|
|
|
|
return mptr;
|
|
}
|
|
/* ----------------------------------------------------------------------- */
|
|
|
|
static qse_httpd_real_task_t* enqueue_task (
|
|
qse_httpd_t* httpd, qse_httpd_client_t* client,
|
|
qse_httpd_task_t* pred, const qse_httpd_task_t* task,
|
|
qse_size_t xtnsize)
|
|
{
|
|
qse_httpd_real_task_t* new_task;
|
|
qse_httpd_real_task_t* real_pred;
|
|
|
|
/* TODO: limit check
|
|
if (client->task.count >= httpd->limit.client_task_queue)
|
|
{
|
|
httpd->errnum = QSE_HTTPD_ETASK;
|
|
return -1;
|
|
}
|
|
*/
|
|
new_task = (qse_httpd_real_task_t*)
|
|
qse_httpd_allocmem (httpd, QSE_SIZEOF(*new_task) + xtnsize);
|
|
if (new_task == QSE_NULL) return QSE_NULL;
|
|
|
|
QSE_MEMSET (new_task, 0, QSE_SIZEOF(*new_task) + xtnsize);
|
|
new_task->core = *task;
|
|
|
|
if (new_task->core.init)
|
|
{
|
|
httpd->errnum = QSE_HTTPD_ENOERR;
|
|
if (new_task->core.init (httpd, client, (qse_httpd_task_t*)new_task) <= -1)
|
|
{
|
|
if (httpd->errnum == QSE_HTTPD_ENOERR)
|
|
httpd->errnum = QSE_HTTPD_ETASK;
|
|
qse_httpd_freemem (httpd, new_task);
|
|
return QSE_NULL;
|
|
}
|
|
}
|
|
|
|
real_pred = (qse_httpd_real_task_t*)pred;
|
|
if (pred)
|
|
{
|
|
new_task->next = real_pred->next;
|
|
new_task->prev = real_pred;
|
|
|
|
if (real_pred->next) real_pred->next->prev = new_task;
|
|
else client->task.tail = (qse_httpd_task_t*)new_task;
|
|
real_pred->next = new_task;
|
|
}
|
|
else
|
|
{
|
|
new_task->next = QSE_NULL;
|
|
new_task->prev = (qse_httpd_real_task_t*)client->task.tail;
|
|
|
|
if (client->task.tail)
|
|
((qse_httpd_real_task_t*)client->task.tail)->next = new_task;
|
|
else
|
|
client->task.head = (qse_httpd_task_t*)new_task;
|
|
client->task.tail = (qse_httpd_task_t*)new_task;
|
|
}
|
|
client->task.count++;
|
|
|
|
return new_task;
|
|
}
|
|
|
|
static QSE_INLINE int dequeue_task (qse_httpd_t* httpd, qse_httpd_client_t* client)
|
|
{
|
|
qse_httpd_real_task_t* task;
|
|
qse_size_t i;
|
|
|
|
if (client->task.count <= 0) return -1;
|
|
|
|
task = (qse_httpd_real_task_t*)client->task.head;
|
|
|
|
/* clear task triggers from mux if they are registered */
|
|
for (i = 0; i < QSE_COUNTOF(task->core.trigger.v); i++)
|
|
{
|
|
if (client->status & QSE_HTTPD_CLIENT_TASK_TRIGGER_RW_IN_MUX(i))
|
|
{
|
|
QSE_ASSERT (task->core.trigger.v[i].handle != client->handle);
|
|
httpd->opt.scb.mux.delhnd (httpd, httpd->mux, task->core.trigger.v[i].handle);
|
|
client->status &= ~QSE_HTTPD_CLIENT_TASK_TRIGGER_RW_IN_MUX(i);
|
|
}
|
|
}
|
|
|
|
/* --------------------------------------------------- */
|
|
|
|
if (task == (qse_httpd_real_task_t*)client->task.tail)
|
|
{
|
|
client->task.head = QSE_NULL;
|
|
client->task.tail = QSE_NULL;
|
|
}
|
|
else
|
|
{
|
|
task->next->prev = QSE_NULL;
|
|
client->task.head = (qse_httpd_task_t*)task->next;
|
|
}
|
|
client->task.count--;
|
|
|
|
if (task->core.fini) task->core.fini (httpd, client, (qse_httpd_task_t*)task);
|
|
qse_httpd_freemem (httpd, task);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static QSE_INLINE void purge_tasks (qse_httpd_t* httpd, qse_httpd_client_t* client)
|
|
{
|
|
while (dequeue_task (httpd, client) == 0);
|
|
}
|
|
|
|
/* ----------------------------------------------------------------------- */
|
|
|
|
static QSE_INLINE void unchain_cached_proxy_peer (qse_httpd_t* httpd, qse_httpd_client_t* client, qse_httpd_peer_t* peer)
|
|
{
|
|
QSE_ASSERT (peer->flags & QSE_HTTPD_PEER_CACHED);
|
|
|
|
if (peer->next) peer->next->prev = peer->prev;
|
|
else client->peer.last = peer->prev;
|
|
|
|
if (peer->prev) peer->prev->next = peer->next;
|
|
else client->peer.first = peer->next;
|
|
}
|
|
|
|
static void purge_cached_proxy_peer (qse_httpd_t* httpd, qse_httpd_client_t* client, qse_httpd_peer_t* peer)
|
|
{
|
|
unchain_cached_proxy_peer (httpd, client, peer);
|
|
|
|
#if defined(QSE_HTTPD_DEBUG)
|
|
{
|
|
qse_mchar_t tmp[128];
|
|
|
|
qse_nwadtombs (&peer->nwad, tmp, QSE_COUNTOF(tmp), QSE_NWADTOMBS_ALL);
|
|
HTTPD_DBGOUT2 ("Closing cached peer [%hs] - %zd\n", tmp, (qse_size_t)peer->handle);
|
|
}
|
|
#endif
|
|
|
|
httpd->opt.scb.peer.close (httpd, peer);
|
|
qse_httpd_freemem (httpd, peer);
|
|
}
|
|
|
|
static void purge_cached_proxy_peers (qse_httpd_t* httpd, qse_httpd_client_t* client)
|
|
{
|
|
while (client->peer.first)
|
|
{
|
|
purge_cached_proxy_peer (httpd, client, client->peer.first);
|
|
}
|
|
}
|
|
|
|
qse_httpd_peer_t* qse_httpd_cacheproxypeer (qse_httpd_t* httpd, qse_httpd_client_t* client, qse_httpd_peer_t* tmpl)
|
|
{
|
|
qse_httpd_peer_t* peer;
|
|
|
|
if (tmpl->flags & QSE_HTTPD_PEER_CACHED)
|
|
{
|
|
/* If QSE_HTTPD_PEER_CACHED is set, tmpl points to a block allocated
|
|
* here previously. Link such a block to the cache list */
|
|
peer = tmpl;
|
|
}
|
|
else
|
|
{
|
|
/* Clone the peer object if it's not previsouly allocated here */
|
|
peer = qse_httpd_allocmem (httpd, QSE_SIZEOF(*peer));
|
|
if (peer == QSE_NULL) goto oops;
|
|
|
|
QSE_MEMCPY (peer, tmpl, QSE_SIZEOF(*peer));
|
|
peer->flags |= QSE_HTTPD_PEER_CACHED;
|
|
}
|
|
|
|
/* place the peer at the back of the peer list of a client */
|
|
if (client->peer.last)
|
|
{
|
|
peer->next = QSE_NULL;
|
|
peer->prev = client->peer.last;
|
|
client->peer.last->next = peer;
|
|
client->peer.last = peer;
|
|
}
|
|
else
|
|
{
|
|
peer->next = QSE_NULL;
|
|
peer->prev = QSE_NULL;
|
|
client->peer.first = peer;
|
|
client->peer.last = peer;
|
|
}
|
|
|
|
qse_gettime (&peer->timestamp);
|
|
return peer;
|
|
|
|
oops:
|
|
if (peer) qse_httpd_freemem (httpd, peer);
|
|
return QSE_NULL;
|
|
}
|
|
|
|
qse_httpd_peer_t* qse_httpd_decacheproxypeer (
|
|
qse_httpd_t* httpd, qse_httpd_client_t* client,
|
|
const qse_nwad_t* nwad, const qse_nwad_t* local, int secure)
|
|
{
|
|
qse_httpd_peer_t* peer, * next;
|
|
qse_ntime_t now, diff;
|
|
static qse_ntime_t diff_limit = { 5, 0 }; /* TODO: make this configurable */
|
|
|
|
qse_gettime (&now);
|
|
|
|
peer = client->peer.first;
|
|
while (peer)
|
|
{
|
|
next = peer->next;
|
|
|
|
qse_sub_ntime (&diff, &now, &peer->timestamp);
|
|
if (qse_cmp_ntime(&diff, &diff_limit) >= 0)
|
|
{
|
|
/* the entry is too old */
|
|
purge_cached_proxy_peer (httpd, client, peer);
|
|
}
|
|
else if (qse_nwadequal(nwad, &peer->nwad) && qse_nwadequal (local, &peer->local))
|
|
{
|
|
if ((secure && (peer->flags & QSE_HTTPD_PEER_SECURE)) ||
|
|
(!secure && !(peer->flags & QSE_HTTPD_PEER_SECURE)))
|
|
{
|
|
unchain_cached_proxy_peer (httpd, client, peer);
|
|
peer->next = QSE_NULL;
|
|
peer->prev = QSE_NULL;
|
|
return peer;
|
|
}
|
|
}
|
|
|
|
peer = next;
|
|
}
|
|
|
|
return QSE_NULL;
|
|
}
|
|
|
|
|
|
/* ----------------------------------------------------------------------- */
|
|
static int htrd_peek_request (qse_htrd_t* htrd, qse_htre_t* req)
|
|
{
|
|
htrd_xtn_t* xtn = (htrd_xtn_t*)qse_htrd_getxtn(htrd);
|
|
return xtn->httpd->opt.rcb.peekreq(xtn->httpd, xtn->client, req);
|
|
}
|
|
|
|
static int htrd_poke_request (qse_htrd_t* htrd, qse_htre_t* req)
|
|
{
|
|
htrd_xtn_t* xtn = (htrd_xtn_t*)qse_htrd_getxtn(htrd);
|
|
return xtn->httpd->opt.rcb.pokereq(xtn->httpd, xtn->client, req);
|
|
}
|
|
|
|
static qse_htrd_recbs_t htrd_recbs =
|
|
{
|
|
QSE_STRUCT_FIELD (peek, htrd_peek_request),
|
|
QSE_STRUCT_FIELD (poke, htrd_poke_request)
|
|
};
|
|
|
|
/* ----------------------------------------------------------------------- */
|
|
static void tmr_idle_update (qse_tmr_t* tmr, qse_tmr_index_t old_index, qse_tmr_index_t new_index, qse_tmr_event_t* evt);
|
|
static void tmr_idle_handle (qse_tmr_t* tmr, const qse_ntime_t* now, qse_tmr_event_t* evt);
|
|
|
|
static void mark_bad_client (qse_httpd_client_t* client)
|
|
{
|
|
/* you can call this function multiple times */
|
|
if (!(client->status & QSE_HTTPD_CLIENT_BAD))
|
|
{
|
|
client->status |= QSE_HTTPD_CLIENT_BAD;
|
|
client->bad_next = client->server->httpd->client.bad;
|
|
client->server->httpd->client.bad = client;
|
|
}
|
|
}
|
|
|
|
static qse_httpd_client_t* new_client (qse_httpd_t* httpd, qse_httpd_client_t* tmpl)
|
|
{
|
|
qse_httpd_client_t* client = QSE_NULL;
|
|
qse_tmr_event_t idle_event;
|
|
htrd_xtn_t* xtn;
|
|
|
|
client = qse_httpd_allocmem(httpd, QSE_SIZEOF(*client));
|
|
if (client == QSE_NULL) goto oops;
|
|
|
|
QSE_MEMSET (client, 0, QSE_SIZEOF(*client));
|
|
client->tmr_idle = QSE_TMR_INVALID_INDEX;
|
|
|
|
client->_instsize = QSE_SIZEOF(*client);
|
|
client->type = QSE_HTTPD_CLIENT;
|
|
client->htrd = qse_htrd_open (qse_httpd_getmmgr(httpd), QSE_SIZEOF(*xtn));
|
|
if (client->htrd == QSE_NULL)
|
|
{
|
|
httpd->errnum = QSE_HTTPD_ENOMEM;
|
|
goto oops;
|
|
}
|
|
|
|
if (httpd->opt.idle_limit.sec > 0 ||
|
|
(httpd->opt.idle_limit.sec == 0 && httpd->opt.idle_limit.nsec > 0))
|
|
{
|
|
/* idle limit is enabled when the limit is greater than 0.0 */
|
|
QSE_MEMSET (&idle_event, 0, QSE_SIZEOF(idle_event));
|
|
qse_gettime (&idle_event.when);
|
|
qse_add_ntime (&idle_event.when, &httpd->opt.idle_limit, &idle_event.when);
|
|
idle_event.ctx = client;
|
|
idle_event.handler = tmr_idle_handle;
|
|
idle_event.updater = tmr_idle_update;
|
|
|
|
if (qse_httpd_insert_timer_event (httpd, &idle_event, &client->tmr_idle) <= -1) goto oops;
|
|
}
|
|
|
|
qse_htrd_setopt (client->htrd, QSE_HTRD_REQUEST | QSE_HTRD_TRAILERS | QSE_HTRD_CANONQPATH);
|
|
|
|
/* copy the public fields,
|
|
* keep the private fields initialized at 0 */
|
|
client->status = tmpl->status;
|
|
if (httpd->opt.scb.client.accepted == QSE_NULL)
|
|
client->status |= QSE_HTTPD_CLIENT_READY;
|
|
client->handle = tmpl->handle;
|
|
client->handle2 = tmpl->handle2;
|
|
client->remote_addr = tmpl->remote_addr;
|
|
client->local_addr = tmpl->local_addr;
|
|
client->orgdst_addr = tmpl->orgdst_addr;
|
|
client->server = tmpl->server;
|
|
client->initial_ifindex = tmpl->initial_ifindex;
|
|
|
|
xtn = (htrd_xtn_t*)qse_htrd_getxtn(client->htrd);
|
|
xtn->httpd = httpd;
|
|
xtn->client = client;
|
|
|
|
qse_htrd_setrecbs (client->htrd, &htrd_recbs);
|
|
|
|
return client;
|
|
|
|
oops:
|
|
if (client)
|
|
{
|
|
if (client->tmr_idle != QSE_TMR_INVALID_INDEX)
|
|
{
|
|
qse_httpd_remove_timer_event (httpd, client->tmr_idle);
|
|
client->tmr_idle = QSE_TMR_INVALID_INDEX;
|
|
}
|
|
if (client->htrd) qse_htrd_close (client->htrd);
|
|
qse_httpd_freemem (httpd, client);
|
|
}
|
|
|
|
return QSE_NULL;
|
|
}
|
|
|
|
static void free_client (qse_httpd_t* httpd, qse_httpd_client_t* client)
|
|
{
|
|
QSE_ASSERT (client->htrd != QSE_NULL);
|
|
|
|
purge_tasks (httpd, client);
|
|
purge_cached_proxy_peers (httpd, client);
|
|
|
|
qse_htrd_close (client->htrd);
|
|
|
|
#if defined(QSE_HTTPD_DEBUG)
|
|
{
|
|
qse_mchar_t tmp[128];
|
|
qse_nwadtombs (&client->remote_addr, tmp, QSE_COUNTOF(tmp), QSE_NWADTOMBS_ALL);
|
|
HTTPD_DBGOUT2 ("Closing client [%hs] - %zd\n", tmp, (qse_size_t)client->handle);
|
|
}
|
|
#endif
|
|
|
|
if (client->status & QSE_HTTPD_CLIENT_HANDLE_RW_IN_MUX)
|
|
{
|
|
httpd->opt.scb.mux.delhnd (httpd, httpd->mux, client->handle);
|
|
client->status &= ~QSE_HTTPD_CLIENT_HANDLE_RW_IN_MUX;
|
|
}
|
|
|
|
if (client->tmr_idle != QSE_TMR_INVALID_INDEX)
|
|
{
|
|
qse_httpd_remove_timer_event (httpd, client->tmr_idle);
|
|
client->tmr_idle = QSE_TMR_INVALID_INDEX;
|
|
}
|
|
|
|
/* note that client.closed is not a counterpart to client.accepted.
|
|
* so it is called even if client.close() failed. */
|
|
if (httpd->opt.scb.client.closed)
|
|
httpd->opt.scb.client.closed (httpd, client);
|
|
|
|
httpd->opt.scb.client.close (httpd, client);
|
|
|
|
qse_httpd_freemem (httpd, client);
|
|
}
|
|
|
|
static void purge_client (qse_httpd_t* httpd, qse_httpd_client_t* client)
|
|
{
|
|
qse_httpd_client_t* prev;
|
|
qse_httpd_client_t* next;
|
|
|
|
prev = client->prev;
|
|
next = client->next;
|
|
|
|
#if defined(QSE_HTTPD_DEBUG)
|
|
{
|
|
qse_mchar_t tmp[128];
|
|
qse_nwadtombs (&client->remote_addr, tmp, QSE_COUNTOF(tmp), QSE_NWADTOMBS_ALL);
|
|
HTTPD_DBGOUT2 ("Purging client [%hs] - %zd\n", tmp, (qse_size_t)client->handle);
|
|
}
|
|
#endif
|
|
|
|
free_client (httpd, client);
|
|
|
|
if (prev) prev->next = next;
|
|
else httpd->client.list.head = next;
|
|
if (next) next->prev = prev;
|
|
else httpd->client.list.tail = prev;
|
|
|
|
httpd->client.list.count--;
|
|
}
|
|
|
|
static void purge_client_list (qse_httpd_t* httpd)
|
|
{
|
|
while (httpd->client.list.tail)
|
|
purge_client (httpd, httpd->client.list.tail);
|
|
}
|
|
|
|
static void move_client_to_tail (qse_httpd_t* httpd, qse_httpd_client_t* client)
|
|
{
|
|
if (httpd->client.list.tail != client)
|
|
{
|
|
qse_httpd_client_t* prev;
|
|
qse_httpd_client_t* next;
|
|
|
|
prev = client->prev;
|
|
next = client->next;
|
|
|
|
if (prev) prev->next = next;
|
|
else httpd->client.list.head = next;
|
|
if (next) next->prev = prev;
|
|
else httpd->client.list.tail = prev;
|
|
|
|
client->next = QSE_NULL;
|
|
client->prev = httpd->client.list.tail;
|
|
httpd->client.list.tail->next = client;
|
|
httpd->client.list.tail = client;
|
|
}
|
|
}
|
|
|
|
#if 0
|
|
static int is_client_allowed (qse_httpd_t* httpd, qse_httpd_client_t* client)
|
|
{
|
|
qse_httpd_mod_t* mod;
|
|
|
|
/* TODO: no sequential search. speed up */
|
|
for (mod = httpd->modlist; mod; mod = mod->next)
|
|
{
|
|
if (mod->new_client) mod->new_client (httpd, client);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
static int accept_client (
|
|
qse_httpd_t* httpd, void* mux, qse_httpd_hnd_t handle, int mask, void* cbarg)
|
|
{
|
|
qse_httpd_server_t* server;
|
|
qse_httpd_client_t clibuf;
|
|
qse_httpd_client_t* client;
|
|
|
|
if (mask & QSE_HTTPD_MUX_READ)
|
|
{
|
|
server = (qse_httpd_server_t*)cbarg;
|
|
|
|
/*QSE_ASSERT (handle == server->handle);*/
|
|
|
|
QSE_MEMSET (&clibuf, 0, QSE_SIZEOF(clibuf));
|
|
|
|
if (httpd->opt.scb.server.accept (httpd, server, &clibuf) <= -1)
|
|
{
|
|
#if QSE_HTTPD_DEBUG
|
|
qse_mchar_t tmp[128];
|
|
qse_nwadtombs (&server->dope.nwad, tmp, QSE_COUNTOF(tmp), QSE_NWADTOMBS_ALL);
|
|
HTTPD_DBGOUT2 ("Failed to accept from server [%hs] [%d]\n", tmp, (int)server->handle);
|
|
#endif
|
|
return -1;
|
|
}
|
|
|
|
/* TODO: check maximum number of client. if exceed call client.close */
|
|
|
|
if (server->dope.flags & QSE_HTTPD_SERVER_SECURE)
|
|
clibuf.status |= QSE_HTTPD_CLIENT_SECURE;
|
|
|
|
clibuf.server = server;
|
|
|
|
#if 0
|
|
if (is_client_allowed (httpd, &clibuf) <= -1)
|
|
{
|
|
httpd->opt.scb.client.close (httpd, &clibuf);
|
|
return -1;
|
|
}
|
|
#endif
|
|
|
|
client = new_client (httpd, &clibuf);
|
|
if (client == QSE_NULL)
|
|
{
|
|
httpd->opt.scb.client.close (httpd, &clibuf);
|
|
return -1;
|
|
}
|
|
|
|
if (httpd->opt.scb.mux.addhnd (httpd, mux, client->handle, QSE_HTTPD_MUX_READ, client) <= -1)
|
|
{
|
|
free_client (httpd, client);
|
|
return -1;
|
|
}
|
|
client->status |= QSE_HTTPD_CLIENT_HANDLE_READ_IN_MUX;
|
|
|
|
qse_gettime (&client->last_active); /* TODO: error check */
|
|
/* link the new client to the tail of the client list. */
|
|
if (httpd->client.list.tail)
|
|
{
|
|
QSE_ASSERT (httpd->client.list.head);
|
|
client->prev = httpd->client.list.tail;
|
|
httpd->client.list.tail->next = client;
|
|
httpd->client.list.tail = client;
|
|
}
|
|
else
|
|
{
|
|
httpd->client.list.head = client;
|
|
httpd->client.list.tail = client;
|
|
}
|
|
httpd->client.list.count++;
|
|
|
|
#if defined(QSE_HTTPD_DEBUG)
|
|
{
|
|
qse_mchar_t tmp1[128], tmp2[128], tmp3[128];
|
|
qse_nwadtombs (&client->local_addr, tmp1, QSE_COUNTOF(tmp1), QSE_NWADTOMBS_ALL);
|
|
qse_nwadtombs (&client->orgdst_addr, tmp2, QSE_COUNTOF(tmp2), QSE_NWADTOMBS_ALL);
|
|
qse_nwadtombs (&client->remote_addr, tmp3, QSE_COUNTOF(tmp3), QSE_NWADTOMBS_ALL);
|
|
HTTPD_DBGOUT5 ("Accepted client %hs(%hs) on %zd from %hs - %zd\n",
|
|
tmp1, tmp2, (qse_size_t)server->handle, tmp3, (qse_size_t)client->handle);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void tmr_idle_update (qse_tmr_t* tmr, qse_tmr_index_t old_index, qse_tmr_index_t new_index, qse_tmr_event_t* evt)
|
|
{
|
|
qse_httpd_client_t* client = (qse_httpd_client_t*)evt->ctx;
|
|
QSE_ASSERT (client->tmr_idle == old_index);
|
|
client->tmr_idle = new_index;
|
|
}
|
|
|
|
static void tmr_idle_handle (qse_tmr_t* tmr, const qse_ntime_t* now, qse_tmr_event_t* evt)
|
|
{
|
|
qse_httpd_client_t* client = (qse_httpd_client_t*)evt->ctx;
|
|
|
|
if (qse_cmp_ntime(now, &client->last_active) >= 0)
|
|
{
|
|
qse_ntime_t diff;
|
|
qse_sub_ntime (&diff, now, &client->last_active);
|
|
if (qse_cmp_ntime(&diff, &client->server->httpd->opt.idle_limit) >= 0)
|
|
{
|
|
/* this client is idle */
|
|
HTTPD_DBGOUT1 ("Purging idle client %zd\n", (qse_size_t)client->handle);
|
|
purge_client (client->server->httpd, client);
|
|
}
|
|
else
|
|
{
|
|
qse_tmr_event_t idle_event;
|
|
|
|
QSE_ASSERT (client->server->httpd->tmr == tmr);
|
|
|
|
/*qse_gettime (&idle_event.when);*/
|
|
QSE_MEMSET (&idle_event, 0, QSE_SIZEOF(idle_event));
|
|
idle_event.when = *now;
|
|
qse_add_ntime (&idle_event.when, &client->server->httpd->opt.idle_limit, &idle_event.when);
|
|
idle_event.ctx = client;
|
|
idle_event.handler = tmr_idle_handle;
|
|
idle_event.updater = tmr_idle_update;
|
|
|
|
/* the timer must have been deleted when this callback is called. */
|
|
QSE_ASSERT (client->tmr_idle == QSE_TMR_INVALID_INDEX);
|
|
if (qse_httpd_insert_timer_event (client->server->httpd, &idle_event, &client->tmr_idle) <= -1)
|
|
{
|
|
HTTPD_DBGOUT1 ("Cannot update idle timer for client %d. Marking it bad", (int)client->handle);
|
|
mark_bad_client (client);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* ----------------------------------------------------------------------- */
|
|
|
|
static void deactivate_servers (qse_httpd_t* httpd)
|
|
{
|
|
qse_httpd_server_t* server;
|
|
|
|
for (server = httpd->server.list.head; server; server = server->next)
|
|
{
|
|
if (server->dope.flags & QSE_HTTPD_SERVER_ACTIVE)
|
|
{
|
|
#if defined(QSE_HTTPD_DEBUG)
|
|
{
|
|
qse_mchar_t tmp[128];
|
|
qse_nwadtombs (&server->dope.nwad, tmp, QSE_COUNTOF(tmp), QSE_NWADTOMBS_ALL);
|
|
HTTPD_DBGOUT2 ("Closing server [%hs] %zd to mux\n", tmp, (qse_size_t)server->handle);
|
|
}
|
|
#endif
|
|
|
|
httpd->opt.scb.mux.delhnd (httpd, httpd->mux, server->handle);
|
|
httpd->opt.scb.server.close (httpd, server);
|
|
server->dope.flags &= ~QSE_HTTPD_SERVER_ACTIVE;
|
|
httpd->server.nactive--;
|
|
}
|
|
}
|
|
}
|
|
|
|
static int activate_servers (qse_httpd_t* httpd)
|
|
{
|
|
qse_httpd_server_t* server;
|
|
|
|
for (server = httpd->server.list.head; server; server = server->next)
|
|
{
|
|
QSE_ASSERT (server->httpd == httpd);
|
|
|
|
if (httpd->opt.scb.server.open (httpd, server) <= -1)
|
|
{
|
|
#if defined(QSE_HTTPD_DEBUG)
|
|
{
|
|
qse_mchar_t tmp[128];
|
|
qse_nwadtombs (&server->dope.nwad, tmp, QSE_COUNTOF(tmp), QSE_NWADTOMBS_ALL);
|
|
HTTPD_DBGOUT1 ("Cannot open server [%hs]\n", tmp);
|
|
}
|
|
#endif
|
|
|
|
continue;
|
|
}
|
|
else
|
|
{
|
|
#if defined(QSE_HTTPD_DEBUG)
|
|
{
|
|
qse_mchar_t tmp[128], tmp2[128];
|
|
qse_nwadtombs (&server->dope.nwad, tmp, QSE_COUNTOF(tmp), QSE_NWADTOMBS_ALL);
|
|
qse_nwadtombs (&server->nwad, tmp2, QSE_COUNTOF(tmp2), QSE_NWADTOMBS_ALL);
|
|
HTTPD_DBGOUT3 ("Opened server [%hs] actual address [%hs] - %zd\n", tmp, tmp2, (qse_size_t)server->handle);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
if (httpd->opt.scb.mux.addhnd (
|
|
httpd, httpd->mux, server->handle, QSE_HTTPD_MUX_READ, server) <= -1)
|
|
{
|
|
#if defined(QSE_HTTPD_DEBUG)
|
|
{
|
|
qse_mchar_t tmp[128];
|
|
qse_nwadtombs (&server->dope.nwad, tmp, QSE_COUNTOF(tmp), QSE_NWADTOMBS_ALL);
|
|
HTTPD_DBGOUT2 ("Cannot add server [%hs] %zd to mux. Closing\n", tmp, (qse_size_t)server->handle);
|
|
}
|
|
#endif
|
|
|
|
httpd->opt.scb.server.close (httpd, server);
|
|
continue;
|
|
}
|
|
|
|
server->dope.flags |= QSE_HTTPD_SERVER_ACTIVE;
|
|
httpd->server.nactive++;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void free_server_list (qse_httpd_t* httpd)
|
|
{
|
|
qse_httpd_server_t* server;
|
|
|
|
server = httpd->server.list.head;
|
|
|
|
while (server)
|
|
{
|
|
qse_httpd_server_t* next = server->next;
|
|
qse_httpd_detachserver (httpd, server);
|
|
server = next;
|
|
}
|
|
|
|
QSE_ASSERT (httpd->server.navail == 0);
|
|
QSE_ASSERT (httpd->server.list.head == QSE_NULL);
|
|
QSE_ASSERT (httpd->server.list.tail == QSE_NULL);
|
|
}
|
|
|
|
qse_httpd_server_t* qse_httpd_attachserver (
|
|
qse_httpd_t* httpd, const qse_httpd_server_dope_t* dope, qse_size_t xtnsize)
|
|
{
|
|
qse_httpd_server_t* server;
|
|
|
|
server = qse_httpd_callocmem (httpd, QSE_SIZEOF(*server) + xtnsize);
|
|
if (server == QSE_NULL) return QSE_NULL;
|
|
|
|
server->_instsize = QSE_SIZEOF(*server);
|
|
server->type = QSE_HTTPD_SERVER;
|
|
/* copy the server dope */
|
|
server->dope = *dope;
|
|
/* and correct some fields in case the dope contains invalid stuffs */
|
|
server->dope.flags &= ~QSE_HTTPD_SERVER_ACTIVE;
|
|
|
|
server->httpd = httpd;
|
|
/* chain the server to the tail of the list */
|
|
server->prev = httpd->server.list.tail;
|
|
server->next = QSE_NULL;
|
|
if (httpd->server.list.tail)
|
|
httpd->server.list.tail->next = server;
|
|
else
|
|
httpd->server.list.head = server;
|
|
httpd->server.list.tail = server;
|
|
httpd->server.navail++;
|
|
|
|
return server;
|
|
}
|
|
|
|
void qse_httpd_detachserver (qse_httpd_t* httpd, qse_httpd_server_t* server)
|
|
{
|
|
qse_httpd_server_t* prev, * next;
|
|
|
|
prev = server->prev;
|
|
next = server->next;
|
|
|
|
QSE_ASSERT (!(server->dope.flags & QSE_HTTPD_SERVER_ACTIVE));
|
|
|
|
if (server->dope.detach) server->dope.detach (httpd, server);
|
|
|
|
qse_httpd_freemem (httpd, server);
|
|
httpd->server.navail--;
|
|
|
|
if (prev) prev->next = next;
|
|
else httpd->server.list.head = next;
|
|
if (next) next->prev = prev;
|
|
else httpd->server.list.tail = prev;
|
|
}
|
|
|
|
qse_httpd_server_t* qse_httpd_getfirstserver (qse_httpd_t* httpd)
|
|
{
|
|
return httpd->server.list.head;
|
|
}
|
|
|
|
qse_httpd_server_t* qse_httpd_getlastserver (qse_httpd_t* httpd)
|
|
{
|
|
return httpd->server.list.tail;
|
|
}
|
|
|
|
qse_httpd_server_t* qse_httpd_getnextserver (qse_httpd_t* httpd, qse_httpd_server_t* server)
|
|
{
|
|
return server->next;
|
|
}
|
|
|
|
qse_httpd_server_t* qse_httpd_getprevserver (qse_httpd_t* httpd, qse_httpd_server_t* server)
|
|
{
|
|
return server->prev;
|
|
}
|
|
|
|
|
|
int qse_httpd_addhnd (qse_httpd_t* httpd, qse_httpd_hnd_t handle, int mask, qse_httpd_custom_t* mate)
|
|
{
|
|
/* qse_httpd_loop() opens the multiplexer. you can call this function from
|
|
* preloop/postloop hooks only. but calling it from postloop hooks is
|
|
* useless. */
|
|
return httpd->opt.scb.mux.addhnd (httpd, httpd->mux, handle, mask, mate);
|
|
}
|
|
|
|
int qse_httpd_delhnd (qse_httpd_t* httpd, qse_httpd_hnd_t handle)
|
|
{
|
|
return httpd->opt.scb.mux.delhnd (httpd, httpd->mux, handle);
|
|
}
|
|
|
|
/* ----------------------------------------------------------------------- */
|
|
|
|
static int activate_dns (qse_httpd_t* httpd)
|
|
{
|
|
int i;
|
|
|
|
QSE_MEMSET (&httpd->dns, 0, QSE_SIZEOF(httpd->dns));
|
|
|
|
if (!httpd->opt.scb.dns.open)
|
|
{
|
|
httpd->errnum = QSE_HTTPD_ENOIMPL;
|
|
return -1;
|
|
}
|
|
|
|
if (httpd->opt.scb.dns.open (httpd, &httpd->dns) <= -1) return -1;
|
|
|
|
httpd->dns.type = QSE_HTTPD_DNS;
|
|
|
|
for (i = 0; i < httpd->dns.handle_count; i++)
|
|
{
|
|
if (httpd->dns.handle_mask & (1 << i))
|
|
{
|
|
if (httpd->opt.scb.mux.addhnd (httpd, httpd->mux, httpd->dns.handle[i], QSE_HTTPD_MUX_READ, &httpd->dns) <= -1)
|
|
{
|
|
while (i > 0)
|
|
{
|
|
--i;
|
|
if (httpd->dns.handle_mask & (1 << i))
|
|
httpd->opt.scb.mux.delhnd (httpd, httpd->mux, httpd->dns.handle[i]);
|
|
}
|
|
httpd->opt.scb.dns.close (httpd, &httpd->dns);
|
|
return -1;
|
|
}
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void deactivate_dns (qse_httpd_t* httpd)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < httpd->dns.handle_count; i++)
|
|
{
|
|
if (httpd->dns.handle_mask & (1 << i))
|
|
httpd->opt.scb.mux.delhnd (httpd, httpd->mux, httpd->dns.handle[i]);
|
|
}
|
|
|
|
httpd->opt.scb.dns.close (httpd, &httpd->dns);
|
|
}
|
|
/* ----------------------------------------------------------------------- */
|
|
|
|
static int activate_urs (qse_httpd_t* httpd)
|
|
{
|
|
int i;
|
|
|
|
QSE_MEMSET (&httpd->urs, 0, QSE_SIZEOF(httpd->urs));
|
|
|
|
if (!httpd->opt.scb.urs.open)
|
|
{
|
|
httpd->errnum = QSE_HTTPD_ENOIMPL;
|
|
return -1;
|
|
}
|
|
|
|
if (httpd->opt.scb.urs.open (httpd, &httpd->urs) <= -1) return -1;
|
|
|
|
httpd->urs.type = QSE_HTTPD_URS;
|
|
|
|
for (i = 0; i < httpd->urs.handle_count; i++)
|
|
{
|
|
if (httpd->urs.handle_mask & (1 << i))
|
|
{
|
|
if (httpd->opt.scb.mux.addhnd (httpd, httpd->mux, httpd->urs.handle[i], QSE_HTTPD_MUX_READ, &httpd->urs) <= -1)
|
|
{
|
|
while (i > 0)
|
|
{
|
|
--i;
|
|
if (httpd->urs.handle_mask & (1 << i))
|
|
httpd->opt.scb.mux.delhnd (httpd, httpd->mux, httpd->urs.handle[i]);
|
|
}
|
|
httpd->opt.scb.urs.close (httpd, &httpd->urs);
|
|
return -1;
|
|
}
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void deactivate_urs (qse_httpd_t* httpd)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < httpd->urs.handle_count; i++)
|
|
{
|
|
if (httpd->urs.handle_mask & (1 << i))
|
|
httpd->opt.scb.mux.delhnd (httpd, httpd->mux, httpd->urs.handle[i]);
|
|
}
|
|
|
|
httpd->opt.scb.urs.close (httpd, &httpd->urs);
|
|
}
|
|
|
|
/* ----------------------------------------------------------------------- */
|
|
|
|
static int read_from_client (qse_httpd_t* httpd, qse_httpd_client_t* client)
|
|
{
|
|
qse_mchar_t buf[MAX_RECV_SIZE]; /* TODO: adjust this buffer size */
|
|
qse_ssize_t m;
|
|
|
|
QSE_ASSERT (httpd->opt.scb.client.recv != QSE_NULL);
|
|
|
|
reread:
|
|
httpd->errnum = QSE_HTTPD_ENOERR;
|
|
m = httpd->opt.scb.client.recv (httpd, client, buf, QSE_SIZEOF(buf));
|
|
if (m <= -1)
|
|
{
|
|
if (httpd->errnum == QSE_HTTPD_EAGAIN)
|
|
{
|
|
/* nothing to read yet. */
|
|
return 0; /* return ok */
|
|
}
|
|
else if (httpd->errnum == QSE_HTTPD_EINTR)
|
|
{
|
|
goto reread;
|
|
}
|
|
else
|
|
{
|
|
/* TOOD: if (httpd->errnum == QSE_HTTPD_ENOERR) httpd->errnum = QSE_HTTPD_ECALLBACK; */
|
|
if (httpd->opt.trait & QSE_HTTPD_LOGACT)
|
|
{
|
|
qse_httpd_act_t msg;
|
|
msg.code = QSE_HTTPD_READERR_CLIENT;
|
|
msg.u.client = client;
|
|
httpd->opt.rcb.logact (httpd, &msg);
|
|
}
|
|
/* TODO: find a way to disconnect */
|
|
return -1;
|
|
}
|
|
}
|
|
else if (m == 0)
|
|
{
|
|
#if 0
|
|
qse_printf (QSE_T("Debug: connection closed %d\n"), client->handle);
|
|
#endif
|
|
/* reading from the client returned 0. this typically
|
|
* happens when the client closes the connection or
|
|
* shutdown the writing half of the socket. it's
|
|
* not really easy to determine one from the other.
|
|
* if QSE_HTTPD_MUTECLIENT is on, attempt to handle
|
|
* it as a half-close under a certain condition. */
|
|
|
|
if (httpd->opt.trait & QSE_HTTPD_MUTECLIENT &&
|
|
client->task.head && client->htrd->clean)
|
|
{
|
|
/* there is still more tasks to finish and
|
|
* http reader is not waiting for any more feeds. */
|
|
client->status |= QSE_HTTPD_CLIENT_MUTE;
|
|
#if 0
|
|
qse_printf (QSE_T(">>>>> Marking client %d as MUTE\n"), client->handle);
|
|
#endif
|
|
return 0;
|
|
}
|
|
else
|
|
{
|
|
#if 0
|
|
qse_printf (QSE_T(">>>>> Returning failure for client %d\n"), client->handle);
|
|
#endif
|
|
httpd->errnum = QSE_HTTPD_EDISCON;
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
#if 0
|
|
qse_printf (QSE_T("!!!!!FEEDING %d from %d ["), (int)m, (int)client->handle);
|
|
#if !defined(__WATCOMC__)
|
|
{
|
|
int i;
|
|
for (i = 0; i < m; i++) qse_printf (QSE_T("%hc"), buf[i]);
|
|
}
|
|
#endif
|
|
qse_printf (QSE_T("]\n"));
|
|
#endif
|
|
|
|
/* qse_htrd_feed() may call the request callback
|
|
* multiple times. that's because we don't know
|
|
* how many valid requests are included in 'buf'. */
|
|
httpd->errnum = QSE_HTTPD_ENOERR;
|
|
if (qse_htrd_feed (client->htrd, buf, m) <= -1)
|
|
{
|
|
if (httpd->errnum == QSE_HTTPD_ENOERR)
|
|
{
|
|
if (client->htrd->errnum == QSE_HTRD_EBADRE ||
|
|
client->htrd->errnum == QSE_HTRD_EBADHDR)
|
|
httpd->errnum = QSE_HTTPD_EBADREQ;
|
|
else httpd->errnum = QSE_HTTPD_ENOMEM; /* TODO: better translate error code */
|
|
}
|
|
|
|
#if 0
|
|
qse_printf (QSE_T("Error: http error while processing %d ["), (int)client->handle);
|
|
{
|
|
int i;
|
|
for (i = 0; i < m; i++) qse_printf (QSE_T("%hc"), buf[i]);
|
|
}
|
|
qse_printf (QSE_T("]\n"));
|
|
#endif
|
|
return -1;
|
|
}
|
|
|
|
|
|
#if 0
|
|
qse_printf (QSE_T("!!!!!FEEDING OK OK OK OK %d from %d\n"), (int)m, (int)client->handle);
|
|
#endif
|
|
|
|
if (client->status & QSE_HTTPD_CLIENT_PENDING)
|
|
{
|
|
/* this QSE_HTTPD_CLIENT_PENDING thing is a dirty hack for SSL.
|
|
* In SSL, data is transmitted in a record. a record can be
|
|
* as large as 16K bytes since its length field is 2 bytes.
|
|
* If SSL_read() has a record but it's given a smaller buffer
|
|
* than the actual record, the next call to select() won't return.
|
|
* there is no data to read at the socket layer. SSL_pending() can
|
|
* tell you the amount of data in the SSL buffer. I try to consume
|
|
* the pending data if the client.recv handler has set QSE_HTTPD_CLIENT_PENDING.
|
|
*
|
|
* TODO: Investigate if there is any starvation issues.
|
|
* What if a single client never stops sending?
|
|
*/
|
|
goto reread;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void clear_trigger_mask_result (qse_httpd_task_t* task)
|
|
{
|
|
qse_size_t i;
|
|
|
|
task->trigger.cmask &= ~(QSE_HTTPD_TASK_TRIGGER_READABLE |
|
|
QSE_HTTPD_TASK_TRIGGER_WRITABLE);
|
|
for (i = 0; i < QSE_COUNTOF(task->trigger.v); i++)
|
|
{
|
|
task->trigger.v[i].mask &= ~(QSE_HTTPD_TASK_TRIGGER_READABLE |
|
|
QSE_HTTPD_TASK_TRIGGER_WRITABLE);
|
|
}
|
|
}
|
|
|
|
static int update_mux_for_current_task (qse_httpd_t* httpd, qse_httpd_client_t* client, qse_httpd_task_t* task)
|
|
{
|
|
qse_size_t i;
|
|
|
|
/* the code here is pretty fragile. there is a high chance
|
|
* that something can go wrong if the task handler plays
|
|
* with the trigger field in an unexpected manner.
|
|
*/
|
|
|
|
clear_trigger_mask_result (task);
|
|
|
|
/*printf ("update_mux_for_current_task..............\n");*/
|
|
if (QSE_MEMCMP (&client->trigger, &task->trigger, QSE_SIZEOF(client->trigger)) != 0 ||
|
|
((client->status & QSE_HTTPD_CLIENT_MUTE) && !(client->status & QSE_HTTPD_CLIENT_MUTE_DELETED)))
|
|
{
|
|
/* manipulate muxtiplexer settings if there are trigger changes */
|
|
|
|
int has_trigger = 0;
|
|
int expected_client_handle_mux_mask;
|
|
int expected_client_handle_mux_status;
|
|
|
|
if ((client->trigger.flags & QSE_HTTPD_TASK_TRIGGER_INACTIVE) !=
|
|
(task->trigger.flags & QSE_HTTPD_TASK_TRIGGER_INACTIVE))
|
|
{
|
|
if (task->trigger.flags & QSE_HTTPD_TASK_TRIGGER_INACTIVE)
|
|
{
|
|
/* active to inactive */
|
|
|
|
/*printf ("ACTIVE TO INACTIVE....\n");*/
|
|
for (i = 0; i < QSE_COUNTOF(task->trigger.v); i++)
|
|
{
|
|
if (client->status & QSE_HTTPD_CLIENT_TASK_TRIGGER_RW_IN_MUX(i))
|
|
{
|
|
httpd->opt.scb.mux.delhnd (httpd, httpd->mux, client->trigger.v[i].handle);
|
|
client->status &= ~QSE_HTTPD_CLIENT_TASK_TRIGGER_RW_IN_MUX(i);
|
|
}
|
|
}
|
|
|
|
if (client->status & QSE_HTTPD_CLIENT_HANDLE_RW_IN_MUX)
|
|
{
|
|
httpd->opt.scb.mux.delhnd (httpd, httpd->mux, client->handle);
|
|
client->status &= ~QSE_HTTPD_CLIENT_HANDLE_RW_IN_MUX;
|
|
}
|
|
|
|
/* save the task trigger information */
|
|
client->trigger = task->trigger;
|
|
return 0;
|
|
}
|
|
|
|
/*printf ("INACTIVE TO ACTIVE....\n");*/
|
|
/* inactive to active . go on*/
|
|
}
|
|
else
|
|
{
|
|
if (task->trigger.flags & QSE_HTTPD_TASK_TRIGGER_INACTIVE)
|
|
{
|
|
/*printf ("INACTIVE TO INACTIVE....\n");*/
|
|
/* inactive to inactive.
|
|
* save the trigger as the trigger handle and masks could change */
|
|
client->trigger = task->trigger;
|
|
return 0;
|
|
}
|
|
|
|
/*printf ("ACTIVE TO ACTIVE....\n");*/
|
|
/* active to active. go on */
|
|
}
|
|
|
|
/* delete previous trigger handles */
|
|
for (i = 0; i < QSE_COUNTOF(task->trigger.v); i++)
|
|
{
|
|
if (client->status & QSE_HTTPD_CLIENT_TASK_TRIGGER_RW_IN_MUX(i))
|
|
{
|
|
httpd->opt.scb.mux.delhnd (httpd, httpd->mux, client->trigger.v[i].handle);
|
|
client->status &= ~QSE_HTTPD_CLIENT_TASK_TRIGGER_RW_IN_MUX(i);
|
|
}
|
|
}
|
|
|
|
/* add new trigger handles */
|
|
for (i = 0; i < QSE_COUNTOF(task->trigger.v); i++)
|
|
{
|
|
int expected_trigger_mux_mask = 0;
|
|
int expected_trigger_mux_status = 0;
|
|
|
|
if (task->trigger.v[i].handle == client->handle) continue;
|
|
|
|
if (task->trigger.v[i].mask & QSE_HTTPD_TASK_TRIGGER_READ)
|
|
{
|
|
expected_trigger_mux_mask |= QSE_HTTPD_MUX_READ;
|
|
expected_trigger_mux_status |= QSE_HTTPD_CLIENT_TASK_TRIGGER_READ_IN_MUX(i);
|
|
}
|
|
if (task->trigger.v[i].mask & QSE_HTTPD_TASK_TRIGGER_WRITE)
|
|
{
|
|
expected_trigger_mux_mask |= QSE_HTTPD_MUX_WRITE;
|
|
expected_trigger_mux_status |= QSE_HTTPD_CLIENT_TASK_TRIGGER_WRITE_IN_MUX(i);
|
|
}
|
|
|
|
if (expected_trigger_mux_mask)
|
|
{
|
|
has_trigger = 1;
|
|
if (httpd->opt.scb.mux.addhnd (httpd, httpd->mux, task->trigger.v[i].handle, expected_trigger_mux_mask, client) <= -1) return -1;
|
|
client->status |= expected_trigger_mux_status;
|
|
}
|
|
}
|
|
|
|
/* client-side handle is registered for both reading and
|
|
* writing when the task is executed for the first time.
|
|
* see update_mux_for_next_task() for this.
|
|
*
|
|
* starting from the second call, the client-side handle
|
|
* is registered for writing if it's explicitly requested.
|
|
* it's always registered for reading if not for QSE_HTTPD_CLIENT_MUTE.
|
|
*
|
|
* this means that QSE_HTTP_TASK_TRIGGER_READ set or clear
|
|
* in task->trigger.cmask is not honored.
|
|
*/
|
|
|
|
expected_client_handle_mux_mask = 0;
|
|
expected_client_handle_mux_status = 0;
|
|
if (task->trigger.cmask & QSE_HTTPD_TASK_TRIGGER_WRITE)
|
|
{
|
|
expected_client_handle_mux_mask |= QSE_HTTPD_MUX_WRITE;
|
|
expected_client_handle_mux_status |= QSE_HTTPD_CLIENT_HANDLE_WRITE_IN_MUX;
|
|
}
|
|
|
|
if (client->status & QSE_HTTPD_CLIENT_MUTE)
|
|
{
|
|
/* reading should be excluded from mux if the client-side has
|
|
* been closed */
|
|
client->status |= QSE_HTTPD_CLIENT_MUTE_DELETED;
|
|
}
|
|
else
|
|
{
|
|
if (task->trigger.cmask & QSE_HTTPD_TASK_TRIGGER_READ)
|
|
{
|
|
expected_client_handle_mux_mask |= QSE_HTTPD_MUX_READ;
|
|
expected_client_handle_mux_status |= QSE_HTTPD_CLIENT_HANDLE_READ_IN_MUX;
|
|
}
|
|
}
|
|
|
|
if (!expected_client_handle_mux_mask && !has_trigger)
|
|
{
|
|
/* if there is no trigger and the client handle is to be excluded
|
|
* from reading and writing, writing should be enabled. */
|
|
expected_client_handle_mux_mask |= QSE_HTTPD_MUX_WRITE;
|
|
expected_client_handle_mux_status |= QSE_HTTPD_CLIENT_HANDLE_WRITE_IN_MUX;
|
|
}
|
|
|
|
if ((client->status & QSE_HTTPD_CLIENT_HANDLE_RW_IN_MUX) != expected_client_handle_mux_status)
|
|
{
|
|
httpd->opt.scb.mux.delhnd (httpd, httpd->mux, client->handle);
|
|
client->status &= ~QSE_HTTPD_CLIENT_HANDLE_RW_IN_MUX;
|
|
|
|
if (expected_client_handle_mux_mask)
|
|
{
|
|
if (httpd->opt.scb.mux.addhnd (httpd, httpd->mux, client->handle, expected_client_handle_mux_mask, client) <= -1) return -1;
|
|
client->status |= expected_client_handle_mux_status;
|
|
}
|
|
}
|
|
|
|
/* save the task trigger information */
|
|
client->trigger = task->trigger;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int update_mux_for_next_task (qse_httpd_t* httpd, qse_httpd_client_t* client)
|
|
{
|
|
/* A new task should be invoked at least once regardless of the
|
|
* data availablility(read) on the client side. Arrange to invoke
|
|
* this task so long as the client-side handle is readable or writable. */
|
|
|
|
qse_httpd_task_t* task;
|
|
int expected_mux_mask;
|
|
int expected_mux_status;
|
|
int expected_trigger_cmask;
|
|
|
|
/*printf ("update_mux_for_next_task\n");*/
|
|
expected_mux_mask = QSE_HTTPD_MUX_READ;
|
|
expected_mux_status = QSE_HTTPD_CLIENT_HANDLE_READ_IN_MUX;
|
|
expected_trigger_cmask = QSE_HTTPD_TASK_TRIGGER_READ;
|
|
|
|
task = client->task.head;
|
|
if (task)
|
|
{
|
|
/* there is a pending task. arrange to trigger it as if it is
|
|
* just entasked. */
|
|
expected_mux_mask |= QSE_HTTPD_MUX_WRITE;
|
|
expected_mux_status |= QSE_HTTPD_CLIENT_HANDLE_WRITE_IN_MUX;
|
|
expected_trigger_cmask |= QSE_HTTPD_TASK_TRIGGER_WRITE;
|
|
|
|
if (client->status & QSE_HTTPD_CLIENT_MUTE)
|
|
{
|
|
/* when client-side has been disconnected, it can't read
|
|
* from the side any more. so exclude reading */
|
|
expected_mux_mask &= ~QSE_HTTPD_MUX_READ;
|
|
expected_mux_status &= ~QSE_HTTPD_CLIENT_HANDLE_READ_IN_MUX;
|
|
expected_trigger_cmask &= ~QSE_HTTPD_TASK_TRIGGER_READ;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* there is no pending task to invoke. */
|
|
|
|
if (client->status & QSE_HTTPD_CLIENT_MUTE)
|
|
{
|
|
/* and this client has closed connection previously.
|
|
* if not, reading would be the only clue to mux for
|
|
* invocation. return failure as reading from the client-side
|
|
* is not possible */
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
if ((client->status & QSE_HTTPD_CLIENT_HANDLE_RW_IN_MUX) != expected_mux_status)
|
|
{
|
|
httpd->opt.scb.mux.delhnd (httpd, httpd->mux, client->handle);
|
|
client->status &= ~QSE_HTTPD_CLIENT_HANDLE_RW_IN_MUX;
|
|
|
|
QSE_ASSERT (expected_mux_status & QSE_HTTPD_CLIENT_HANDLE_RW_IN_MUX);
|
|
if (httpd->opt.scb.mux.addhnd (httpd, httpd->mux, client->handle, expected_mux_mask, client) <= -1) return -1;
|
|
client->status |= expected_mux_status;
|
|
}
|
|
|
|
if (task) task->trigger.cmask = expected_trigger_cmask;
|
|
return 0;
|
|
}
|
|
|
|
static int invoke_client_task (
|
|
qse_httpd_t* httpd, qse_httpd_client_t* client,
|
|
qse_httpd_hnd_t handle, int mask)
|
|
{
|
|
qse_httpd_task_t* task;
|
|
qse_size_t i;
|
|
int n, trigger_fired;
|
|
|
|
if (handle == client->handle && (mask & QSE_HTTPD_MUX_READ))
|
|
{
|
|
/* keep reading from the client-side as long as
|
|
* it's readable. */
|
|
if (!(client->status & QSE_HTTPD_CLIENT_MUTE) &&
|
|
read_from_client (httpd, client) <= -1)
|
|
{
|
|
/* return failure on disconnection also in order to
|
|
* purge the client in perform_client_task().
|
|
* thus the following line isn't necessary.
|
|
*if (httpd->errnum == QSE_HTTPD_EDISCON) return 0;*/
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
task = client->task.head;
|
|
if (task == QSE_NULL)
|
|
{
|
|
/* this client doesn't have any task */
|
|
if (client->status & QSE_HTTPD_CLIENT_MUTE)
|
|
{
|
|
/* handle the delayed client disconnection */
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
trigger_fired = 0;
|
|
|
|
clear_trigger_mask_result (task);
|
|
if (handle == client->handle)
|
|
{
|
|
if (mask & QSE_HTTPD_MUX_READ)
|
|
{
|
|
/*QSE_ASSERT (task->trigger.cmask & QSE_HTTPD_TASK_TRIGGER_READ);*/
|
|
task->trigger.cmask |= QSE_HTTPD_TASK_TRIGGER_READABLE;
|
|
trigger_fired = 1;
|
|
}
|
|
if (mask & QSE_HTTPD_MUX_WRITE)
|
|
{
|
|
/*QSE_ASSERT (task->trigger.cmask & QSE_HTTPD_TASK_TRIGGER_WRITE);*/
|
|
task->trigger.cmask |= QSE_HTTPD_TASK_TRIGGER_WRITABLE;
|
|
trigger_fired = 1;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (i = 0; i < QSE_COUNTOF(task->trigger.v); i++)
|
|
{
|
|
if (task->trigger.v[i].handle == handle)
|
|
{
|
|
if (mask & QSE_HTTPD_MUX_READ)
|
|
{
|
|
/*QSE_ASSERT (task->trigger.v[i].mask & QSE_HTTPD_TASK_TRIGGER_READ);*/
|
|
/* the assertion above may be false if a task for the same
|
|
* trigger set was called earlier by a qse_mux_poll() call.
|
|
* and the task has changed some masks.
|
|
*
|
|
* for instance, you put handle A and B in to a trigger.
|
|
* if the task is triggered for A but the task may change
|
|
* the mask for B. the task may get executed for B by
|
|
* the same qse_mux_poll() call.
|
|
*/
|
|
|
|
task->trigger.v[i].mask |= QSE_HTTPD_TASK_TRIGGER_READABLE;
|
|
trigger_fired = 1;
|
|
}
|
|
if (mask & QSE_HTTPD_MUX_WRITE)
|
|
{
|
|
/*QSE_ASSERT (task->trigger.v[i].mask & QSE_HTTPD_TASK_TRIGGER_WRITE);*/
|
|
task->trigger.v[i].mask |= QSE_HTTPD_TASK_TRIGGER_WRITABLE;
|
|
trigger_fired = 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (trigger_fired && !(task->trigger.cmask & QSE_HTTPD_TASK_TRIGGER_WRITABLE))
|
|
{
|
|
/* the task is invoked for triggers.
|
|
* check if the client handle is writable */
|
|
qse_ntime_t tmout;
|
|
tmout.sec = 0;
|
|
tmout.nsec = 0;
|
|
|
|
if (httpd->opt.scb.mux.writable (httpd, client->handle, &tmout) <= 0)
|
|
{
|
|
/* it is not writable yet. so just skip
|
|
* performing the actual task */
|
|
return 0;
|
|
}
|
|
|
|
/* WRITABLE can be set without WRITE as this is the result of
|
|
* the additional writability check. */
|
|
task->trigger.cmask |= QSE_HTTPD_TASK_TRIGGER_WRITABLE;
|
|
}
|
|
|
|
n = task->main (httpd, client, task);
|
|
if (n == 0)
|
|
{
|
|
/* the current task is over. remove the task
|
|
* from the queue. dequeue_task() clears task triggers
|
|
* from the mux. so i don't clear them explicitly here */
|
|
dequeue_task (httpd, client);
|
|
|
|
/* update the multiplexer settings */
|
|
n = update_mux_for_next_task (httpd, client);
|
|
|
|
/* reset the task trigger remembered */
|
|
QSE_MEMSET (&client->trigger, 0, QSE_SIZEOF(client->trigger));
|
|
}
|
|
else if (n > 0)
|
|
{
|
|
n = update_mux_for_current_task (httpd, client, task);
|
|
}
|
|
|
|
return n;
|
|
}
|
|
|
|
static int perform_client_task (
|
|
qse_httpd_t* httpd, void* mux, qse_httpd_hnd_t handle, int mask, void* cbarg)
|
|
{
|
|
qse_httpd_client_t* client;
|
|
|
|
client = (qse_httpd_client_t*)cbarg;
|
|
|
|
if (client->status & QSE_HTTPD_CLIENT_BAD) return 0;
|
|
|
|
if (!(client->status & QSE_HTTPD_CLIENT_READY))
|
|
{
|
|
int x;
|
|
x = httpd->opt.scb.client.accepted (httpd, client);
|
|
if (x <= -1) goto oops;
|
|
if (x >= 1)
|
|
{
|
|
client->status |= QSE_HTTPD_CLIENT_READY;
|
|
|
|
qse_gettime (&client->last_active);
|
|
move_client_to_tail (httpd, client);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* locate an active client to the tail of the client list */
|
|
qse_gettime (&client->last_active); /* TODO: error check??? */
|
|
move_client_to_tail (httpd, client);
|
|
|
|
if (invoke_client_task (httpd, client, handle, mask) <= -1) goto oops;
|
|
}
|
|
|
|
return 0;
|
|
|
|
oops:
|
|
/*purge_client (httpd, client);*/
|
|
mark_bad_client (client);
|
|
return -1;
|
|
}
|
|
|
|
static int perform_dns (qse_httpd_t* httpd, void* mux, qse_httpd_hnd_t handle, int mask, void* cbarg)
|
|
{
|
|
qse_httpd_dns_t* dns = (qse_httpd_dns_t*)cbarg;
|
|
|
|
QSE_ASSERT (mask & QSE_HTTPD_MUX_READ);
|
|
QSE_ASSERT (&httpd->dns == dns);
|
|
|
|
return httpd->opt.scb.dns.recv (httpd, dns, handle);
|
|
}
|
|
|
|
static int perform_urs (qse_httpd_t* httpd, void* mux, qse_httpd_hnd_t handle, int mask, void* cbarg)
|
|
{
|
|
qse_httpd_urs_t* urs = (qse_httpd_urs_t*)cbarg;
|
|
|
|
QSE_ASSERT (mask & QSE_HTTPD_MUX_READ);
|
|
QSE_ASSERT (&httpd->urs == urs);
|
|
|
|
return httpd->opt.scb.urs.recv (httpd, urs, handle);
|
|
}
|
|
|
|
static void purge_bad_clients (qse_httpd_t* httpd)
|
|
{
|
|
qse_httpd_client_t* client;
|
|
|
|
while (httpd->client.bad)
|
|
{
|
|
client = httpd->client.bad;
|
|
httpd->client.bad = client->bad_next;
|
|
purge_client (httpd, client);
|
|
}
|
|
}
|
|
|
|
void* qse_httpd_gettaskxtn (qse_httpd_t* httpd, qse_httpd_task_t* task)
|
|
{
|
|
return (void*)((qse_httpd_real_task_t*)task + 1);
|
|
}
|
|
|
|
qse_httpd_task_t* qse_httpd_entask (
|
|
qse_httpd_t* httpd, qse_httpd_client_t* client,
|
|
qse_httpd_task_t* pred, const qse_httpd_task_t* task,
|
|
qse_size_t xtnsize)
|
|
{
|
|
qse_httpd_real_task_t* new_task;
|
|
|
|
if (client->status & QSE_HTTPD_CLIENT_BAD) return QSE_NULL;
|
|
|
|
new_task = enqueue_task (httpd, client, pred, task, xtnsize);
|
|
if (new_task == QSE_NULL)
|
|
{
|
|
/*purge_client (httpd, client);*/
|
|
mark_bad_client (client);
|
|
}
|
|
else if (new_task->prev == QSE_NULL)
|
|
{
|
|
/* this task is the first task to activate. */
|
|
|
|
if (update_mux_for_next_task (httpd, client) <= -1)
|
|
{
|
|
/*purge_client (httpd, client);*/
|
|
mark_bad_client (client);
|
|
|
|
/* call dequeue_task() to get new_task removed. this works
|
|
* because it is the only task. the task finalizer is also
|
|
* called when it's dequeued. */
|
|
dequeue_task (httpd, client);
|
|
|
|
new_task = QSE_NULL;
|
|
}
|
|
}
|
|
|
|
return (qse_httpd_task_t*)new_task;
|
|
}
|
|
|
|
static int dispatch_mux (qse_httpd_t* httpd, void* mux, qse_httpd_hnd_t handle, int mask, void* cbarg)
|
|
{
|
|
switch (((qse_httpd_mate_t*)cbarg)->type)
|
|
{
|
|
case QSE_HTTPD_SERVER:
|
|
return accept_client (httpd, mux, handle, mask, cbarg);
|
|
|
|
case QSE_HTTPD_CLIENT:
|
|
return perform_client_task (httpd, mux, handle, mask, cbarg);
|
|
|
|
case QSE_HTTPD_DNS:
|
|
return perform_dns (httpd, mux, handle, mask, cbarg);
|
|
|
|
case QSE_HTTPD_URS:
|
|
return perform_urs (httpd, mux, handle, mask, cbarg);
|
|
|
|
case QSE_HTTPD_CUSTOM:
|
|
return ((qse_httpd_custom_t*)cbarg)->dispatch (httpd, mux, handle, mask);
|
|
}
|
|
|
|
httpd->errnum = QSE_HTTPD_EINTERN;
|
|
return -1;
|
|
}
|
|
|
|
int qse_httpd_loop (qse_httpd_t* httpd)
|
|
{
|
|
int xret, count;
|
|
qse_ntime_t tmout;
|
|
qse_httpd_ecb_t* ecb;
|
|
|
|
QSE_ASSERTX (httpd->server.list.head != QSE_NULL,
|
|
"Add listeners before calling qse_httpd_loop()");
|
|
|
|
QSE_ASSERTX (httpd->client.list.head == QSE_NULL,
|
|
"No client should exist when this loop is started");
|
|
|
|
QSE_ASSERT (QSE_TMR_SIZE(httpd->tmr) == 0);
|
|
|
|
if (httpd->server.list.head == QSE_NULL)
|
|
{
|
|
httpd->errnum = QSE_HTTPD_ENOSVR;
|
|
return -1;
|
|
}
|
|
|
|
httpd->stopreq = 0;
|
|
httpd->impedereq = 0;
|
|
httpd->dnsactive = 0;
|
|
|
|
/* system callbacks and request callbacks must be set before the call to this function */
|
|
QSE_ASSERT (httpd->opt.scb.mux.open && httpd->opt.scb.mux.close && httpd->opt.scb.mux.poll);
|
|
QSE_ASSERT (httpd->opt.rcb.peekreq && httpd->opt.rcb.pokereq);
|
|
|
|
/* a server must be attached before the call to this function */
|
|
QSE_ASSERT (httpd->server.navail > 0);
|
|
|
|
httpd->mux = httpd->opt.scb.mux.open (httpd, dispatch_mux);
|
|
if (httpd->mux == QSE_NULL) return -1;
|
|
|
|
if (activate_dns (httpd) <= -1)
|
|
{
|
|
if (httpd->opt.trait & QSE_HTTPD_LOGACT)
|
|
{
|
|
qse_httpd_act_t msg;
|
|
msg.code = QSE_HTTPD_CATCH_MWARNMSG;
|
|
qse_mbscpy (msg.u.mwarnmsg, QSE_MT("cannot activate dns"));
|
|
httpd->opt.rcb.logact (httpd, &msg);
|
|
}
|
|
|
|
HTTPD_DBGOUT0 ("Failed to activate DNS\n");
|
|
}
|
|
else httpd->dnsactive = 1;
|
|
|
|
if (activate_urs (httpd) <= -1)
|
|
{
|
|
if (httpd->opt.trait & QSE_HTTPD_LOGACT)
|
|
{
|
|
qse_httpd_act_t msg;
|
|
msg.code = QSE_HTTPD_CATCH_MWARNMSG;
|
|
qse_mbscpy (msg.u.mwarnmsg, QSE_MT("cannot activate urs"));
|
|
httpd->opt.rcb.logact (httpd, &msg);
|
|
}
|
|
HTTPD_DBGOUT0 ("Failed to activate URS\n");
|
|
}
|
|
else httpd->ursactive = 1;
|
|
|
|
if (activate_servers (httpd) <= -1)
|
|
{
|
|
if (httpd->dnsactive) deactivate_dns (httpd);
|
|
if (httpd->ursactive) deactivate_urs (httpd);
|
|
httpd->opt.scb.mux.close (httpd, httpd->mux);
|
|
return -1;
|
|
}
|
|
if (httpd->server.nactive <= 0)
|
|
{
|
|
HTTPD_DBGOUT0 ("No servers are active. aborting\n");
|
|
|
|
if (httpd->dnsactive) deactivate_dns (httpd);
|
|
if (httpd->ursactive) deactivate_urs (httpd);
|
|
httpd->opt.scb.mux.close (httpd, httpd->mux);
|
|
|
|
httpd->errnum = QSE_HTTPD_ENOSVR;
|
|
return -1;
|
|
}
|
|
|
|
/* call preloop hooks */
|
|
for (ecb = httpd->ecb; ecb; ecb = ecb->next)
|
|
{
|
|
if (ecb->preloop) ecb->preloop (httpd);
|
|
}
|
|
|
|
xret = 0;
|
|
while (!httpd->stopreq)
|
|
{
|
|
if (qse_tmr_gettmout (httpd->tmr, QSE_NULL, &tmout) <= -1) tmout = httpd->opt.tmout;
|
|
count = httpd->opt.scb.mux.poll (httpd, httpd->mux, &tmout);
|
|
/*HTTPD_DBGOUT4 ("Multiplexer returned %d client count = %d tmout = %d.%d\n", (int)count, (int)httpd->client.list.count, (int)tmout.sec, (int)tmout.nsec);*/
|
|
if (count <= -1)
|
|
{
|
|
if (httpd->errnum != QSE_HTTPD_EINTR)
|
|
{
|
|
xret = -1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
qse_tmr_fire (httpd->tmr, QSE_NULL, QSE_NULL);
|
|
purge_bad_clients (httpd);
|
|
|
|
if (httpd->impedereq)
|
|
{
|
|
httpd->impedereq = 0;
|
|
httpd->opt.rcb.impede (httpd);
|
|
}
|
|
}
|
|
|
|
for (ecb = httpd->ecb; ecb; ecb = ecb->next)
|
|
{
|
|
if (ecb->postloop) ecb->postloop (httpd);
|
|
}
|
|
|
|
purge_client_list (httpd);
|
|
deactivate_servers (httpd);
|
|
|
|
if (httpd->ursactive) deactivate_urs (httpd);
|
|
if (httpd->dnsactive) deactivate_dns (httpd);
|
|
|
|
httpd->opt.scb.mux.close (httpd, httpd->mux);
|
|
|
|
QSE_ASSERT (QSE_TMR_SIZE(httpd->tmr) == 0);
|
|
return xret;
|
|
}
|
|
|
|
/* ----------------------------------------------------------------------- */
|
|
|
|
void qse_httpd_discardcontent (qse_httpd_t* httpd, qse_htre_t* req)
|
|
{
|
|
qse_htre_discardcontent (req);
|
|
}
|
|
|
|
void qse_httpd_completecontent (qse_httpd_t* httpd, qse_htre_t* req)
|
|
{
|
|
qse_htre_completecontent (req);
|
|
}
|
|
|
|
/* ----------------------------------------------------------------------- */
|
|
void qse_httpd_setname (qse_httpd_t* httpd, const qse_mchar_t* name)
|
|
{
|
|
qse_mbsxcpy (httpd->sname, QSE_COUNTOF(httpd->sname), name);
|
|
}
|
|
|
|
const qse_mchar_t* qse_httpd_getname (qse_httpd_t* httpd)
|
|
{
|
|
return httpd->sname;
|
|
}
|
|
|
|
const qse_mchar_t* qse_httpd_fmtgmtimetobb (
|
|
qse_httpd_t* httpd, const qse_ntime_t* nt, int idx)
|
|
{
|
|
qse_ntime_t now;
|
|
|
|
QSE_ASSERT (idx >= 0 && idx < QSE_COUNTOF(httpd->gtbuf));
|
|
|
|
if (nt == QSE_NULL)
|
|
{
|
|
if (qse_gettime(&now) <= -1)
|
|
{
|
|
now.sec = 0;
|
|
now.nsec = 0;
|
|
}
|
|
nt = &now;
|
|
}
|
|
|
|
qse_fmthttptime (nt, httpd->gtbuf[idx], QSE_COUNTOF(httpd->gtbuf[idx]));
|
|
return httpd->gtbuf[idx];
|
|
}
|
|
|
|
/* ----------------------------------------------------------------------- */
|
|
|
|
qse_mchar_t* qse_httpd_escapehtml (qse_httpd_t* httpd, const qse_mchar_t* str)
|
|
{
|
|
qse_mchar_t* ptr, * buf;
|
|
qse_size_t reqlen = 0;
|
|
|
|
for (ptr = (qse_mchar_t*)str; *ptr != QSE_MT('\0'); ptr++)
|
|
{
|
|
switch (*ptr)
|
|
{
|
|
case QSE_MT('<'):
|
|
case QSE_MT('>'):
|
|
reqlen += 4;
|
|
break;
|
|
|
|
case QSE_MT('&'):
|
|
reqlen += 5;
|
|
break;
|
|
|
|
default:
|
|
reqlen++;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (ptr - str == reqlen) return (qse_mchar_t*)str; /* no escaping is needed */
|
|
|
|
buf = qse_httpd_allocmem (httpd, QSE_SIZEOF(*buf) * (reqlen + 1));
|
|
if (buf == QSE_NULL) return QSE_NULL;
|
|
|
|
ptr = buf;
|
|
while (*str != QSE_MT('\0'))
|
|
{
|
|
switch (*str)
|
|
{
|
|
case QSE_MT('<'):
|
|
*ptr++ = QSE_MT('&');
|
|
*ptr++ = QSE_MT('l');
|
|
*ptr++ = QSE_MT('t');
|
|
*ptr++ = QSE_MT(';');
|
|
break;
|
|
|
|
case QSE_MT('>'):
|
|
*ptr++ = QSE_MT('&');
|
|
*ptr++ = QSE_MT('g');
|
|
*ptr++ = QSE_MT('t');
|
|
*ptr++ = QSE_MT(';');
|
|
break;
|
|
|
|
case QSE_MT('&'):
|
|
*ptr++ = QSE_MT('&');
|
|
*ptr++ = QSE_MT('a');
|
|
*ptr++ = QSE_MT('m');
|
|
*ptr++ = QSE_MT('p');
|
|
*ptr++ = QSE_MT(';');
|
|
break;
|
|
|
|
default:
|
|
*ptr++ = *str;
|
|
break;
|
|
}
|
|
str++;
|
|
}
|
|
*ptr = QSE_MT('\0');
|
|
return buf;
|
|
}
|
|
|
|
/* ----------------------------------------------------------------------- */
|
|
|
|
int qse_httpd_resolvename (qse_httpd_t* httpd, const qse_mchar_t* name, qse_httpd_resolve_t resol, const qse_httpd_dns_server_t* dns_server, void* ctx)
|
|
{
|
|
if (!httpd->dnsactive)
|
|
{
|
|
qse_httpd_seterrnum (httpd, QSE_HTTPD_ENODNS);
|
|
return -1;
|
|
}
|
|
|
|
#if defined(QSE_HTTPD_DEBUG)
|
|
{
|
|
qse_mchar_t tmp[128];
|
|
if (dns_server)
|
|
qse_nwadtombs (&dns_server->nwad, tmp, QSE_COUNTOF(tmp), QSE_NWADTOMBS_ALL);
|
|
else
|
|
qse_mbscpy (tmp, QSE_MT("default server"));
|
|
|
|
HTTPD_DBGOUT2 ("Sending DNS request [%hs] to [%hs]\n", name, tmp);
|
|
}
|
|
#endif
|
|
|
|
return httpd->opt.scb.dns.send (httpd, &httpd->dns, name, resol, dns_server, ctx);
|
|
}
|
|
|
|
int qse_httpd_rewriteurl (qse_httpd_t* httpd, const qse_mchar_t* url, qse_httpd_rewrite_t rewrite, const qse_httpd_urs_server_t* urs_server, void* ctx)
|
|
{
|
|
if (!httpd->ursactive)
|
|
{
|
|
qse_httpd_seterrnum (httpd, QSE_HTTPD_ENOURS);
|
|
return -1;
|
|
}
|
|
|
|
HTTPD_DBGOUT1 ("Sending URS request [%hs]\n", url);
|
|
return httpd->opt.scb.urs.send (httpd, &httpd->urs, url, rewrite, urs_server, ctx);
|
|
}
|
|
|
|
int qse_httpd_activatetasktrigger (qse_httpd_t* httpd, qse_httpd_client_t* client, qse_httpd_task_t* task)
|
|
{
|
|
int x, org_cmask;
|
|
|
|
/* don't do anything if it's active */
|
|
if (!(task->trigger.flags & QSE_HTTPD_TASK_TRIGGER_INACTIVE)) return 0;
|
|
|
|
/* when task trigger is inactive, no handle are registered
|
|
* into mux. update_mux_for_current_task adds the client handle
|
|
* to mux for reading only if writing is not requested explicitly.
|
|
* if no data is available for reading, the task can never be
|
|
* called after activation. so let's request writing here.
|
|
*/
|
|
QSE_ASSERT (!(client->status & QSE_HTTPD_CLIENT_HANDLE_RW_IN_MUX));
|
|
org_cmask = task->trigger.cmask;
|
|
task->trigger.cmask |= QSE_HTTPD_TASK_TRIGGER_WRITE;
|
|
|
|
task->trigger.flags &= ~QSE_HTTPD_TASK_TRIGGER_INACTIVE;
|
|
|
|
x = update_mux_for_current_task (httpd, client, task);
|
|
|
|
task->trigger.cmask = org_cmask;
|
|
return x;
|
|
}
|
|
|
|
int qse_httpd_inactivatetasktrigger (qse_httpd_t* httpd, qse_httpd_client_t* client, qse_httpd_task_t* task)
|
|
{
|
|
if (task->trigger.flags & QSE_HTTPD_TASK_TRIGGER_INACTIVE) return 0;
|
|
|
|
task->trigger.flags |= QSE_HTTPD_TASK_TRIGGER_INACTIVE;
|
|
return update_mux_for_current_task (httpd, client, task);
|
|
}
|
|
|
|
|
|
/* ------------------------------------------------------------------- */
|
|
|
|
|
|
static void update_timer_event (qse_tmr_t* tmr, qse_tmr_index_t old_index, qse_tmr_index_t new_index, qse_tmr_event_t* evt)
|
|
{
|
|
tmr_xtn_t* tmr_xtn;
|
|
qse_httpd_t* httpd;
|
|
qse_httpd_timer_updater_t updater;
|
|
|
|
tmr_xtn = qse_tmr_getxtn (tmr);
|
|
httpd = tmr_xtn->httpd;
|
|
updater = evt->ctx2;
|
|
updater (httpd, old_index, new_index, evt->ctx);
|
|
}
|
|
|
|
static void handle_timer_event (qse_tmr_t* tmr, const qse_ntime_t* now, qse_tmr_event_t* evt)
|
|
{
|
|
tmr_xtn_t* tmr_xtn;
|
|
qse_httpd_t* httpd;
|
|
qse_httpd_timer_handler_t handler;
|
|
|
|
tmr_xtn = qse_tmr_getxtn (tmr);
|
|
httpd = tmr_xtn->httpd;
|
|
handler = evt->ctx3;
|
|
handler (httpd, now, evt->ctx);
|
|
}
|
|
|
|
int qse_httpd_inserttimerevent (qse_httpd_t* httpd, const qse_httpd_timer_event_t* event, qse_httpd_timer_index_t* index)
|
|
{
|
|
qse_tmr_event_t timer_event;
|
|
qse_tmr_index_t timer_index;
|
|
|
|
QSE_MEMSET (&timer_event, 0, QSE_SIZEOF(timer_event));
|
|
timer_event.ctx = event->ctx;
|
|
timer_event.ctx2 = event->updater;
|
|
timer_event.ctx3 = event->handler;
|
|
timer_event.when = event->when;
|
|
timer_event.updater = update_timer_event;
|
|
timer_event.handler = handle_timer_event;
|
|
|
|
timer_index = qse_tmr_insert (httpd->tmr, &timer_event);
|
|
if (timer_index == QSE_TMR_INVALID_INDEX)
|
|
{
|
|
qse_httpd_seterrnum (httpd, QSE_HTTPD_ENOMEM);
|
|
return -1;
|
|
}
|
|
|
|
*index = timer_index;
|
|
return 0;
|
|
}
|
|
|
|
void qse_httpd_removetimerevent (qse_httpd_t* httpd, qse_httpd_timer_index_t index)
|
|
{
|
|
qse_tmr_delete (httpd->tmr, index);
|
|
}
|
|
|
|
/* qse_httpd_insert_timer_event() is a lighter-weight version of
|
|
* qse_httpd_inserttimerevent() and intended for internal use only */
|
|
|
|
int qse_httpd_insert_timer_event (qse_httpd_t* httpd, const qse_tmr_event_t* event, qse_tmr_index_t* index)
|
|
{
|
|
qse_tmr_index_t tmp = qse_tmr_insert (httpd->tmr, event);
|
|
if (tmp == QSE_TMR_INVALID_INDEX)
|
|
{
|
|
qse_httpd_seterrnum (httpd, QSE_HTTPD_ENOMEM);
|
|
return -1;
|
|
}
|
|
|
|
*index = tmp;
|
|
return 0;
|
|
}
|
|
void qse_httpd_remove_timer_event (qse_httpd_t* httpd, qse_tmr_index_t index)
|
|
{
|
|
qse_tmr_delete (httpd->tmr, index);
|
|
}
|
|
|
|
/* ----------------------------------------------------------------------- */
|
|
|
|
static void unload_all_modules (qse_httpd_t* httpd)
|
|
{
|
|
qse_httpd_mod_t* mod;
|
|
|
|
while (httpd->modlist)
|
|
{
|
|
mod = httpd->modlist;
|
|
httpd->modlist = mod->next;
|
|
|
|
if (mod->unload) mod->unload (mod);
|
|
httpd->opt.scb.mod.close (httpd, mod->handle );
|
|
qse_httpd_freemem (httpd, mod);
|
|
}
|
|
}
|
|
|
|
qse_httpd_mod_t* qse_httpd_loadmod (qse_httpd_t* httpd, const qse_char_t* name)
|
|
{
|
|
qse_httpd_mod_t* mod;
|
|
qse_size_t name_len, prefix_len, postfix_len, fullname_len;
|
|
const qse_char_t* prefix, * postfix;
|
|
qse_char_t* entry_point_name;
|
|
qse_httpd_mod_load_t load;
|
|
|
|
/* TODO: no singly linked list speed up */
|
|
|
|
name_len = qse_strlen(name);
|
|
|
|
if (httpd->opt.mod[0].len > 0)
|
|
{
|
|
prefix = httpd->opt.mod[0].ptr;
|
|
prefix_len = httpd->opt.mod[0].len;
|
|
}
|
|
else
|
|
{
|
|
prefix = QSE_T(QSE_HTTPD_DEFAULT_MODPREFIX);
|
|
prefix_len = qse_strlen(prefix);
|
|
}
|
|
|
|
if (httpd->opt.mod[1].len > 0)
|
|
{
|
|
postfix = httpd->opt.mod[1].ptr;
|
|
postfix_len = httpd->opt.mod[1].len;
|
|
}
|
|
else
|
|
{
|
|
postfix = QSE_T(QSE_HTTPD_DEFAULT_MODPOSTFIX);
|
|
postfix_len = qse_strlen(postfix);
|
|
}
|
|
|
|
/*
|
|
* +15: length of _qse_httpd_mod_
|
|
* +2: _\0
|
|
*/
|
|
fullname_len = prefix_len + name_len + postfix_len;
|
|
mod = qse_httpd_callocmem (httpd, QSE_SIZEOF(*mod) + (name_len + 1 + fullname_len + 1 + 15 + name_len + 2) * QSE_SIZEOF(qse_char_t));
|
|
if (mod == QSE_NULL) return QSE_NULL;
|
|
|
|
mod->httpd = httpd;
|
|
mod->name = (qse_char_t*)(mod + 1);
|
|
mod->fullname = mod->name + name_len + 1;
|
|
entry_point_name = mod->fullname + fullname_len + 1;
|
|
qse_strcpy (mod->name, name);
|
|
qse_strjoin (mod->fullname, prefix, name, postfix, QSE_NULL);
|
|
qse_strjoin (entry_point_name, QSE_T("_qse_httpd_mod_"), name, QSE_NULL);
|
|
|
|
mod->handle = httpd->opt.scb.mod.open (httpd, mod->fullname);
|
|
if (!mod->handle)
|
|
{
|
|
qse_httpd_freemem (httpd, mod);
|
|
return QSE_NULL;
|
|
}
|
|
|
|
/* attempt qse_httpd_mod_xxx */
|
|
load = httpd->opt.scb.mod.symbol (httpd, mod->handle, &entry_point_name[1]);
|
|
if (!load)
|
|
{
|
|
/* attempt _qse_awk_mod_xxx */
|
|
load = httpd->opt.scb.mod.symbol (httpd, mod->handle, &entry_point_name[0]);
|
|
if (!load)
|
|
{
|
|
/* attempt qse_awk_mod_xxx_ */
|
|
entry_point_name[15 + name_len] = QSE_T('_');
|
|
entry_point_name[15 + name_len + 1] = QSE_T('\0');
|
|
load = httpd->opt.scb.mod.symbol (httpd, mod->handle, &entry_point_name[1]);
|
|
}
|
|
}
|
|
|
|
if (!load || load (mod) <= -1)
|
|
{
|
|
httpd->opt.scb.mod.close (httpd, mod->handle);
|
|
qse_httpd_freemem (httpd, mod);
|
|
return QSE_NULL;
|
|
}
|
|
|
|
/* link the loaded module to the module list */
|
|
mod->next = httpd->modlist;
|
|
httpd->modlist = mod;
|
|
|
|
return mod;
|
|
}
|
|
|
|
int qse_httpd_configmod (qse_httpd_t* httpd, qse_httpd_mod_t* mod, const qse_char_t* key, const qse_char_t* value)
|
|
{
|
|
QSE_ASSERT (httpd == mod->httpd);
|
|
|
|
if (mod->config)
|
|
{
|
|
return mod->config (mod, key, value);
|
|
}
|
|
else
|
|
{
|
|
/* not allowed to set the module configuration
|
|
* without the 'config' handler */
|
|
httpd->errnum = QSE_HTTPD_EACCES;
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
qse_httpd_mod_t* qse_httpd_findmod (qse_httpd_t* httpd, const qse_char_t* name)
|
|
{
|
|
qse_httpd_mod_t* mod;
|
|
|
|
/* TODO: no sequential search. speed up */
|
|
for (mod = httpd->modlist; mod; mod = mod->next)
|
|
{
|
|
if (qse_strcmp (mod->name, name) == 0) return mod;
|
|
}
|
|
|
|
return QSE_NULL;
|
|
}
|