/* * $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 #include #include #include typedef struct htrd_xtn_t htrd_xtn_t; struct htrd_xtn_t { qse_httpd_t* httpd; qse_httpd_client_t* client; }; static void free_server_list (qse_httpd_t* httpd); static int perform_client_task ( qse_httpd_t* httpd, void* mux, qse_ubi_t handle, int mask, void* cbarg); 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_t* httpd; httpd = (qse_httpd_t*) QSE_MMGR_ALLOC ( mmgr, QSE_SIZEOF(*httpd) + xtnsize ); if (httpd == QSE_NULL) return QSE_NULL; if (qse_httpd_init (httpd, mmgr) <= -1) { QSE_MMGR_FREE (httpd->mmgr, httpd); return QSE_NULL; } QSE_MEMSET (httpd + 1, 0, xtnsize); return httpd; } void qse_httpd_close (qse_httpd_t* httpd) { qse_httpd_ecb_t* ecb; for (ecb = httpd->ecb; ecb; ecb = ecb->next) if (ecb->close) ecb->close (httpd); qse_httpd_fini (httpd); QSE_MMGR_FREE (httpd->mmgr, httpd); } int qse_httpd_init (qse_httpd_t* httpd, qse_mmgr_t* mmgr) { QSE_MEMSET (httpd, 0, QSE_SIZEOF(*httpd)); httpd->mmgr = mmgr; 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) { free_server_list (httpd); } 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; } qse_mmgr_t* qse_httpd_getmmgr (qse_httpd_t* httpd) { return httpd->mmgr; } void* qse_httpd_getxtn (qse_httpd_t* httpd) { return QSE_XTN (httpd); } 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_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; 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; } 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_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; 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; } 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; } /* --------------------------------------------------- */ QSE_INLINE void* qse_httpd_allocmem (qse_httpd_t* httpd, qse_size_t size) { void* ptr = QSE_MMGR_ALLOC (httpd->mmgr, size); if (ptr == QSE_NULL) httpd->errnum = QSE_HTTPD_ENOMEM; return ptr; } QSE_INLINE void* qse_httpd_callocmem (qse_httpd_t* httpd, qse_size_t size) { void* ptr = QSE_MMGR_ALLOC (httpd->mmgr, size); if (ptr == QSE_NULL) httpd->errnum = QSE_HTTPD_ENOMEM; else QSE_MEMSET (ptr, 0, size); return ptr; } QSE_INLINE void* qse_httpd_reallocmem ( qse_httpd_t* httpd, void* ptr, qse_size_t size) { void* nptr = QSE_MMGR_REALLOC (httpd->mmgr, ptr, size); if (nptr == QSE_NULL) httpd->errnum = QSE_HTTPD_ENOMEM; return nptr; } QSE_INLINE void qse_httpd_freemem (qse_httpd_t* httpd, void* ptr) { QSE_MMGR_FREE (httpd->mmgr, 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, httpd->mmgr); #else mptr = qse_wcstombsdup (str, QSE_NULL, httpd->mmgr); #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, httpd->mmgr); #else mptr = qse_wcsntombsdup (str, len, QSE_NULL, httpd->mmgr); #endif 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 = client->task.head; /* clear task triggers from mux if they are registered */ for (i = 0; i < QSE_COUNTOF(task->core.trigger); i++) { if (client->status & CLIENT_TASK_TRIGGER_IN_MUX(i)) { httpd->opt.scb.mux.delhnd (httpd, httpd->mux, task->core.trigger[i].handle); client->status &= ~CLIENT_TASK_TRIGGER_IN_MUX(i); } } /* --------------------------------------------------- */ if (task == 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, 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 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 qse_httpd_client_t* new_client ( qse_httpd_t* httpd, qse_httpd_client_t* tmpl) { qse_httpd_client_t* client; htrd_xtn_t* xtn; client = qse_httpd_allocmem (httpd, QSE_SIZEOF(*client)); if (client == QSE_NULL) return QSE_NULL; QSE_MEMSET (client, 0, QSE_SIZEOF(*client)); client->type = QSE_HTTPD_CLIENT; client->htrd = qse_htrd_open (httpd->mmgr, QSE_SIZEOF(*xtn)); if (client->htrd == QSE_NULL) { httpd->errnum = QSE_HTTPD_ENOMEM; qse_httpd_freemem (httpd, client); return QSE_NULL; } qse_htrd_setoption (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 |= 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; } static void free_client ( qse_httpd_t* httpd, qse_httpd_client_t* client) { QSE_ASSERT (client->htrd != QSE_NULL); purge_tasks (httpd, client); qse_htrd_close (client->htrd); #if 0 qse_printf (QSE_T("Debug: CLOSING SOCKET %d\n"), client->handle.i); #endif if (client->status & CLIENT_HANDLE_IN_MUX) { httpd->opt.scb.mux.delhnd (httpd, httpd->mux, client->handle); client->status &= ~CLIENT_HANDLE_IN_MUX; } /* 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 (httpd->opt.trait & QSE_HTTPD_LOGACT) { qse_httpd_act_t msg; msg.code = QSE_HTTPD_PURGE_CLIENT; msg.u.client = client; httpd->opt.rcb.logact (httpd, &msg); } 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; } 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; } } static int accept_client ( qse_httpd_t* httpd, void* mux, qse_ubi_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 0 /* TODO: proper logging */ qse_char_t tmp[128]; qse_nwadtostr (&server->dope.nwad, tmp, QSE_COUNTOF(tmp), QSE_NWADTOSTR_ALL); qse_printf (QSE_T("failed to accept from server [%s] [%d]\n"), tmp, server->handle.i); #endif return -1; } /* TODO: check maximum number of client. if exceed call client.close */ if (server->dope.flags & QSE_HTTPD_SERVER_SECURE) clibuf.status |= CLIENT_SECURE; clibuf.server = server; client = new_client (httpd, &clibuf); if (client == QSE_NULL) { httpd->opt.scb.client.close (httpd, &clibuf); return -1; } #if 0 qse_printf (QSE_T("MUX ADDHND CLIENT READ %d\n"), client->handle.i); #endif if (httpd->opt.scb.mux.addhnd ( httpd, mux, client->handle, QSE_HTTPD_MUX_READ, client) <= -1) { free_client (httpd, client); return -1; } client->status |= 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; } if (httpd->opt.trait & QSE_HTTPD_LOGACT) { qse_httpd_act_t msg; msg.code = QSE_HTTPD_ACCEPT_CLIENT; msg.u.client = client; httpd->opt.rcb.logact (httpd, &msg); } } return 0; } /* --------------------------------------------------- */ 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) { 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) { if (httpd->opt.scb.server.open (httpd, server) <= -1) { qse_char_t buf[64]; qse_nwadtostr (&server->dope.nwad, buf, QSE_COUNTOF(buf), QSE_NWADTOSTR_ALL); /* httpd->opt.rcb.log (httpd, 0, QSE_T("cannot activate %s"), buf); */ #if 0 qse_printf(QSE_T("cannot activate [%s]\n"), buf); #endif continue; } if (httpd->opt.scb.mux.addhnd ( httpd, httpd->mux, server->handle, QSE_HTTPD_MUX_READ, server) <= -1) { qse_char_t buf[64]; qse_nwadtostr (&server->dope.nwad, buf, QSE_COUNTOF(buf), QSE_NWADTOSTR_ALL); /* httpd->opt.rcb.log (httpd, 0, QSE_T("cannot activate %s - "), buf); */ #if 0 qse_printf(QSE_T("cannot add handle [%s]\n"), buf); #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->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; /* 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; } /* --------------------------------------------------- */ static int read_from_client (qse_httpd_t* httpd, qse_httpd_client_t* client) { qse_mchar_t buf[4096]; /* 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.i); #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 |= CLIENT_MUTE; #if 0 qse_printf (QSE_T(">>>>> Marking client %d as MUTE\n"), client->handle.i); #endif return 0; } else { #if 0 qse_printf (QSE_T(">>>>> Returning failure for client %d\n"), client->handle.i); #endif httpd->errnum = QSE_HTTPD_EDISCON; return -1; } } #if 0 qse_printf (QSE_T("!!!!!FEEDING %d from %d ["), (int)m, (int)client->handle.i); #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.i); { 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.i); #endif if (client->status & CLIENT_PENDING) { /* this 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 record a record but it's given a * smaller buffer than the actuaal 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 set CLIENT_PENDING. * * TODO: Investigate if there is any starvation issues. * What if a single client never stops sending? */ goto reread; } return 0; } static int invoke_client_task ( qse_httpd_t* httpd, qse_httpd_client_t* client, qse_ubi_t handle, int mask) { qse_httpd_task_t* task; qse_size_t i; int n, trigger_fired, client_handle_writable; /* TODO: handle comparison callback ... */ if (handle.i == client->handle.i && (mask & QSE_HTTPD_MUX_READ)) /* TODO: no direct comparision */ { if (!(client->status & 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; } } /* this client doesn't have any task */ task = client->task.head; if (task == QSE_NULL) { if (client->status & CLIENT_MUTE) { /* handle this delayed client disconnection */ return -1; } return 0; } trigger_fired = 0; client_handle_writable = 0; for (i = 0; i < QSE_COUNTOF(task->trigger); i++) { task->trigger[i].mask &= ~(QSE_HTTPD_TASK_TRIGGER_READABLE | QSE_HTTPD_TASK_TRIGGER_WRITABLE); if (task->trigger[i].handle.i == handle.i) /* TODO: no direct comparision */ { if (task->trigger[i].mask & QSE_HTTPD_TASK_TRIGGER_READ) { trigger_fired = 1; task->trigger[i].mask |= QSE_HTTPD_TASK_TRIGGER_READABLE; } if (task->trigger[i].mask & QSE_HTTPD_TASK_TRIGGER_WRITE) { trigger_fired = 1; task->trigger[i].mask |= QSE_HTTPD_TASK_TRIGGER_WRITABLE; if (handle.i == client->handle.i) client_handle_writable = 1; /* TODO: no direct comparison */ } } } if (trigger_fired && !client_handle_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; } } n = task->main (httpd, client, task); if (n <= -1) return -1; else if (n == 0) { int mux_mask; int mux_status; /* 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); mux_mask = QSE_HTTPD_MUX_READ; mux_status = CLIENT_HANDLE_READ_IN_MUX; if (client->task.head) { /* there is a pending task. arrange to * trigger it as if it is just entasked */ mux_mask |= QSE_HTTPD_MUX_WRITE; mux_status |= CLIENT_HANDLE_WRITE_IN_MUX; if (client->status & CLIENT_MUTE) { mux_mask &= ~QSE_HTTPD_MUX_READ; mux_status &= ~CLIENT_HANDLE_READ_IN_MUX; } } else { if (client->status & CLIENT_MUTE) { /* no more task. but this client * has closed connection previously */ return -1; } } if ((client->status & CLIENT_HANDLE_IN_MUX) != (mux_status & CLIENT_HANDLE_IN_MUX)) { httpd->opt.scb.mux.delhnd (httpd, httpd->mux, client->handle); client->status &= ~CLIENT_HANDLE_IN_MUX; if (mux_status) { if (httpd->opt.scb.mux.addhnd ( httpd, httpd->mux, client->handle, mux_mask, client) <= -1) { return -1; } client->status |= mux_status; } } QSE_MEMSET (client->trigger, 0, QSE_SIZEOF(client->trigger)); return 0; } else { /* 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. */ for (i = 0; i < QSE_COUNTOF(task->trigger); i++) { task->trigger[i].mask &= ~(QSE_HTTPD_TASK_TRIGGER_READABLE | QSE_HTTPD_TASK_TRIGGER_WRITABLE); } if (QSE_MEMCMP (client->trigger, task->trigger, QSE_SIZEOF(client->trigger)) != 0 || ((client->status & CLIENT_MUTE) && !(client->status & CLIENT_MUTE_DELETED))) { /* manipulate muxtiplexer settings if there are trigger changes */ int has_trigger; int trigger_mux_mask; int client_handle_mux_mask; int client_handle_mux_status; /* delete previous trigger handles */ for (i = 0; i < QSE_COUNTOF(task->trigger); i++) { if (client->status & CLIENT_TASK_TRIGGER_IN_MUX(i)) { httpd->opt.scb.mux.delhnd (httpd, httpd->mux, client->trigger[i].handle); client->status &= ~CLIENT_TASK_TRIGGER_IN_MUX(i); } } has_trigger = 0; client_handle_mux_mask = 0; client_handle_mux_status = 0; if (client->status & CLIENT_MUTE) { client->status |= CLIENT_MUTE_DELETED; } else { client_handle_mux_mask |= QSE_HTTPD_MUX_READ; client_handle_mux_status |= CLIENT_HANDLE_READ_IN_MUX; } /* add new trigger handles */ for (i = 0; i < QSE_COUNTOF(task->trigger); i++) { trigger_mux_mask = 0; if (task->trigger[i].mask & QSE_HTTPD_TASK_TRIGGER_READ) { if (task->trigger[i].handle.i != client->handle.i || !(client->status & CLIENT_MUTE)) { trigger_mux_mask |= QSE_HTTPD_MUX_READ; } } if (task->trigger[i].mask & QSE_HTTPD_TASK_TRIGGER_WRITE) trigger_mux_mask |= QSE_HTTPD_MUX_WRITE; if (trigger_mux_mask) { has_trigger = 1; if (task->trigger[i].handle.i == client->handle.i) /* TODO: no direct comparsion */ { /* if the client handle is included in the trigger, * delay its manipulation until the loop is over. * instead, just remember what mask is requested */ client_handle_mux_mask |= trigger_mux_mask; } else { if (httpd->opt.scb.mux.addhnd ( httpd, httpd->mux, task->trigger[i].handle, trigger_mux_mask, client) <= -1) { return -1; } client->status |= CLIENT_TASK_TRIGGER_IN_MUX(i); } } } if (client_handle_mux_mask) { /* if the client handle is included in the trigger * and writing is requested, arrange writing to be * enabled */ if (client_handle_mux_mask & QSE_HTTPD_MUX_WRITE) client_handle_mux_status |= CLIENT_HANDLE_WRITE_IN_MUX; } else if (!has_trigger) { /* if there is no trigger, writing should be enabled */ client_handle_mux_status |= CLIENT_HANDLE_WRITE_IN_MUX; client_handle_mux_mask |= QSE_HTTPD_MUX_WRITE; } if ((client->status & CLIENT_HANDLE_IN_MUX) != (client_handle_mux_status & CLIENT_HANDLE_IN_MUX)) { httpd->opt.scb.mux.delhnd (httpd, httpd->mux, client->handle); client->status &= ~CLIENT_HANDLE_IN_MUX; if (client_handle_mux_mask) { if (httpd->opt.scb.mux.addhnd ( httpd, httpd->mux, client->handle, client_handle_mux_mask, client) <= -1) { return -1; } client->status |= client_handle_mux_status; } } QSE_MEMCPY (client->trigger, task->trigger, QSE_SIZEOF(client->trigger)); } return 0; } } static int perform_client_task ( qse_httpd_t* httpd, void* mux, qse_ubi_t handle, int mask, void* cbarg) { qse_httpd_client_t* client; client = (qse_httpd_client_t*)cbarg; if (client->status & CLIENT_BAD) return 0; if (!(client->status & CLIENT_READY)) { int x; x = httpd->opt.scb.client.accepted (httpd, client); if (x <= -1) goto oops; if (x >= 1) { client->status |= 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);*/ client->status |= CLIENT_BAD; client->bad_next = httpd->client.bad; httpd->client.bad = client; return -1; } 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); } } static void purge_idle_clients (qse_httpd_t* httpd) { qse_httpd_client_t* client; qse_httpd_client_t* next_client; qse_ntime_t now; qse_gettime (&now); client = httpd->client.list.head; while (client) { next_client = client->next; /* TODO: check the nsec part */ if (now.sec <= client->last_active.sec) break; if (now.sec - client->last_active.sec < httpd->opt.idle_limit.sec) break; purge_client (httpd, client); client = next_client; } /* TODO: */ } 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 & CLIENT_BAD) return QSE_NULL; new_task = enqueue_task (httpd, client, pred, task, xtnsize); if (new_task == QSE_NULL) { /*purge_client (httpd, client);*/ client->status |= CLIENT_BAD; } else if (new_task->prev == QSE_NULL) { /* arrange to invokde this task so long as * the client-side handle is writable. */ QSE_ASSERT (client->status & CLIENT_HANDLE_IN_MUX); httpd->opt.scb.mux.delhnd (httpd, httpd->mux, client->handle); client->status &= ~CLIENT_HANDLE_IN_MUX; #if 0 qse_printf (QSE_T("MUX ADDHND CLIENT RW(ENTASK) %d\n"), client->handle.i); #endif if (httpd->opt.scb.mux.addhnd ( httpd, httpd->mux, client->handle, QSE_HTTPD_MUX_READ | QSE_HTTPD_MUX_WRITE, client) <= -1) { /*purge_client (httpd, client);*/ client->status |= CLIENT_BAD; new_task = QSE_NULL; } client->status |= CLIENT_HANDLE_IN_MUX; /* READ | WRITE */ } return (qse_httpd_task_t*)new_task; } static int dispatch_mux ( qse_httpd_t* httpd, void* mux, qse_ubi_t handle, int mask, void* cbarg) { return ((qse_httpd_mate_t*)cbarg)->type == QSE_HTTPD_SERVER? accept_client (httpd, mux, handle, mask, cbarg): perform_client_task (httpd, mux, handle, mask, cbarg); } int qse_httpd_loop (qse_httpd_t* httpd) { int xret; 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"); if (httpd->server.list.head == QSE_NULL) { httpd->errnum = QSE_HTTPD_EINVAL; return -1; } httpd->stopreq = 0; httpd->impedereq = 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_servers (httpd) <= -1) { httpd->opt.scb.mux.close (httpd, httpd->mux); return -1; } if (httpd->server.nactive <= 0) { httpd->errnum = QSE_HTTPD_ENOSVR; httpd->opt.scb.mux.close (httpd, httpd->mux); return -1; } xret = 0; while (!httpd->stopreq) { int count; count = httpd->opt.scb.mux.poll (httpd, httpd->mux, &httpd->opt.tmout); if (count <= -1) { if (httpd->errnum != QSE_HTTPD_EINTR) { xret = -1; break; } } purge_bad_clients (httpd); purge_idle_clients (httpd); if (httpd->impedereq) { httpd->impedereq = 0; httpd->opt.rcb.impede (httpd); } } purge_client_list (httpd); deactivate_servers (httpd); httpd->opt.scb.mux.close (httpd, httpd->mux); 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 = 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 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; }