From ea3ebef8f1f5f2b20775c143a89495efe43685fa Mon Sep 17 00:00:00 2001 From: hyung-hwan Date: Fri, 7 Sep 2012 15:13:55 +0000 Subject: [PATCH] added qse_awk_pushecb()/qse_awk_popecb()/qse_sed_pushecb()/qse_sed_popecb()/qse_httpd_pushecb()/qse_httpd_popecb(). started reorganizing samples/httpd01 to net/httpd-std.c --- qse/cmd/awk/awk.c | 22 +- qse/include/qse/awk/awk.h | 116 ++- qse/include/qse/macros.h | 9 + qse/include/qse/net/httpd.h | 61 +- qse/include/qse/sed/sed.h | 43 + qse/lib/awk/awk.c | 26 + qse/lib/awk/awk.h | 3 +- qse/lib/awk/run.c | 24 +- qse/lib/awk/std.c | 11 +- qse/lib/net/Makefile.am | 1 + qse/lib/net/Makefile.in | 6 +- qse/lib/net/httpd-std.c | 1627 +++++++++++++++++++++++++++++++++++ qse/lib/net/httpd.c | 22 + qse/lib/net/httpd.h | 3 +- qse/lib/sed/sed.c | 18 + qse/lib/sed/sed.h | 2 + 16 files changed, 1930 insertions(+), 64 deletions(-) create mode 100644 qse/lib/net/httpd-std.c diff --git a/qse/cmd/awk/awk.c b/qse/cmd/awk/awk.c index 7ce38686..66374492 100644 --- a/qse/cmd/awk/awk.c +++ b/qse/cmd/awk/awk.c @@ -350,8 +350,7 @@ static void dprint_return (qse_awk_rtx_t* rtx, qse_awk_val_t* ret) } #ifdef ENABLE_CALLBACK -static void on_statement ( - qse_awk_rtx_t* rtx, qse_awk_nde_t* nde, void* data) +static void on_statement (qse_awk_rtx_t* rtx, qse_awk_nde_t* nde) { dprint (QSE_T("running %d at line %d\n"), (int)nde->type, (int)nde->loc.line); } @@ -975,13 +974,18 @@ static int awk_main (int argc, qse_char_t* argv[]) qse_awk_t* awk = QSE_NULL; qse_awk_rtx_t* rtx = QSE_NULL; qse_awk_val_t* retv; -#ifdef ENABLE_CALLBACK - qse_awk_rcb_t rcb; -#endif int i; struct arg_t arg; int ret = -1; +#ifdef ENABLE_CALLBACK + static qse_awk_rtx_ecb_t rtx_ecb = + { + QSE_FV(.close, QSE_NULL), + QSE_FV(.stmt, on_statement) + }; +#endif + /* TODO: change it to support multiple source files */ qse_awk_parsestd_t psin; qse_awk_parsestd_t psout; @@ -1080,12 +1084,6 @@ static int awk_main (int argc, qse_char_t* argv[]) goto oops; } -#ifdef ENABLE_CALLBACK - qse_memset (&rcb, 0, QSE_SIZEOF(rcb)); - rcb.stmt = on_statement; - rcb.ctx = &arg; -#endif - rtx = qse_awk_rtx_openstd ( awk, 0, QSE_T("qseawk"), (const qse_char_t*const*)arg.icf.ptr, @@ -1104,7 +1102,7 @@ static int awk_main (int argc, qse_char_t* argv[]) app_rtx = rtx; #ifdef ENABLE_CALLBACK - qse_awk_rtx_pushrcb (rtx, &rcb); + qse_awk_rtx_pushecb (rtx, &rtx_ecb); #endif set_intr_run (); diff --git a/qse/include/qse/awk/awk.h b/qse/include/qse/awk/awk.h index e0e0c19a..8dcb1f70 100644 --- a/qse/include/qse/awk/awk.h +++ b/qse/include/qse/awk/awk.h @@ -617,6 +617,8 @@ struct qse_awk_prm_t }; typedef struct qse_awk_prm_t qse_awk_prm_t; +/* ------------------------------------------------------------------------ */ + /** * The qse_awk_sio_t type defines a script stream handler set. * The qse_awk_parse() function calls the input and output handler to parse @@ -664,6 +666,8 @@ struct qse_awk_sio_t }; typedef struct qse_awk_sio_t qse_awk_sio_t; +/* ------------------------------------------------------------------------ */ + /** * The qse_awk_rio_t type defines a runtime I/O handler set. * @sa qse_awk_rtx_t @@ -676,56 +680,93 @@ struct qse_awk_rio_t }; typedef struct qse_awk_rio_t qse_awk_rio_t; +/* ------------------------------------------------------------------------ */ + /** - * The qse_awk_rcb_close_t type defines the callback function + * The qse_awk_ecb_close_t type defines the callback function + * called when an awk object is closed. + */ +typedef void (*qse_awk_ecb_close_t) ( + qse_awk_t* awk /**< awk */ +); + +/** + * The qse_awk_ecb_clear_t type defines the callback function + * called when an awk object is cleared. + */ +typedef void (*qse_awk_ecb_clear_t) ( + qse_awk_t* awk /**< awk */ +); + +/** + * The qse_awk_ecb_t type defines an event callback set. + * You can register a callback function set with + * qse_awk_pushecb(). The callback functions in the registered + * set are called in the reverse order of registration. + */ +typedef struct qse_awk_ecb_t qse_awk_ecb_t; +struct qse_awk_ecb_t +{ + /** + * called by qse_awk_close(). + */ + qse_awk_ecb_close_t close; + + /** + * called by qse_awk_clear(). + */ + qse_awk_ecb_clear_t clear; + + /* internal use only. don't touch this field */ + qse_awk_ecb_t* next; +}; + +/* ------------------------------------------------------------------------ */ + +/** + * The qse_awk_rtx_ecb_close_t type defines the callback function * called when the runtime context is closed. */ -typedef void (*qse_awk_rcb_close_t) ( - qse_awk_rtx_t* rtx, /**< runtime context */ - void* ctx /**< user-defined data */ +typedef void (*qse_awk_rtx_ecb_close_t) ( + qse_awk_rtx_t* rtx /**< runtime context */ ); /** - * The qse_awk_rcb_stmt_t type defines the callback function for each + * The qse_awk_rtx_ecb_stmt_t type defines the callback function for each * statement. */ -typedef void (*qse_awk_rcb_stmt_t) ( +typedef void (*qse_awk_rtx_ecb_stmt_t) ( qse_awk_rtx_t* rtx, /**< runtime context */ - qse_awk_nde_t* nde, /**< node */ - void* ctx /**< user-defined data */ + qse_awk_nde_t* nde /**< node */ ); /** - * The qse_awk_rcb_t type defines a runtime callback set. You can - * register a callback function set with qse_awk_rtx_pushrcb(). - * The callback functions in the set registered are called in a - * proper context in the reverse order of registeration. + * The qse_awk_rtx_ecb_t type defines an event callback set for a + * runtime context. You can register a callback function set with + * qse_awk_rtx_pushecb(). The callback functions in the registered + * set are called in the reverse order of registration. */ -typedef struct qse_awk_rcb_t qse_awk_rcb_t; -struct qse_awk_rcb_t +typedef struct qse_awk_rtx_ecb_t qse_awk_rtx_ecb_t; +struct qse_awk_rtx_ecb_t { /** * called by qse_awk_rtx_close(). */ - qse_awk_rcb_close_t close; + qse_awk_rtx_ecb_close_t close; /** * called by qse_awk_rtx_loop() and qse_awk_rtx_call() for * each statement executed. */ - qse_awk_rcb_stmt_t stmt; - - /** - * A caller may store a user-defined data pointer into this field. This - * is passed to an actual callback. - */ - void* ctx; + qse_awk_rtx_ecb_stmt_t stmt; /* internal use only. don't touch this field */ - qse_awk_rcb_t* next; + qse_awk_rtx_ecb_t* next; }; +/* ------------------------------------------------------------------------ */ + /** * The qse_awk_option_t type defines various options to change the behavior * of #qse_awk_t. @@ -1381,6 +1422,23 @@ void qse_awk_setmaxdepth ( qse_size_t depth /**< maximum depth */ ); +/** + * The qse_awk_popecb() function pops an awk event callback set + * and returns the pointer to it. If no callback set can be popped, + * it returns #QSE_NULL. + */ +qse_awk_ecb_t* qse_awk_popecb ( + qse_awk_t* awk /**< awk */ +); + +/** + * The qse_awk_pushecb() function register a runtime callback set. + */ +void qse_awk_pushecb ( + qse_awk_t* awk, /**< awk */ + qse_awk_ecb_t* ecb /**< callback set */ +); + /** * The qse_awk_addgbl() function adds an intrinsic global variable. * @return the ID of the global variable added on success, -1 on failure. @@ -1717,20 +1775,20 @@ void qse_awk_rtx_setrio ( ); /** - * The qse_awk_rtx_poprcb() function pops a runtime callback set + * The qse_awk_rtx_popecb() function pops a runtime callback set * and returns the pointer to it. If no callback set can be popped, * it returns #QSE_NULL. */ -qse_awk_rcb_t* qse_awk_rtx_poprcb ( +qse_awk_rtx_ecb_t* qse_awk_rtx_popecb ( qse_awk_rtx_t* rtx /**< runtime context */ ); /** - * The qse_awk_rtx_pushrcb() function register a runtime callback set. + * The qse_awk_rtx_pushecb() function register a runtime callback set. */ -void qse_awk_rtx_pushrcb ( - qse_awk_rtx_t* rtx, /**< runtime context */ - qse_awk_rcb_t* rcb /**< callback set */ +void qse_awk_rtx_pushecb ( + qse_awk_rtx_t* rtx, /**< runtime context */ + qse_awk_rtx_ecb_t* ecb /**< callback set */ ); /** diff --git a/qse/include/qse/macros.h b/qse/include/qse/macros.h index d6038591..adc66126 100644 --- a/qse/include/qse/macros.h +++ b/qse/include/qse/macros.h @@ -216,6 +216,15 @@ } \ ) +/** + * The QSE_FV() macro is used to specify a initial value + * for a field of an aggregate type. + */ +#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901) +# define QSE_FV(field,value) field = value +#else +# define QSE_FV(field,value) value +#endif /* number of characters to number of bytes */ #define QSE_NCTONB(x) ((x)*sizeof(qse_char_t)) diff --git a/qse/include/qse/net/httpd.h b/qse/include/qse/net/httpd.h index 741e6953..f3bb3f5f 100644 --- a/qse/include/qse/net/httpd.h +++ b/qse/include/qse/net/httpd.h @@ -305,6 +305,32 @@ struct qse_httpd_client_t } task; }; +/** + * The qse_httpd_ecb_close_t type defines the callback function + * called when an httpd object is closed. + */ +typedef void (*qse_httpd_ecb_close_t) ( + qse_httpd_t* httpd /**< httpd */ +); + +/** + * The qse_httpd_ecb_t type defines an event callback set. + * You can register a callback function set with + * qse_httpd_pushecb(). The callback functions in the registered + * set are called in the reverse order of registration. + */ +typedef struct qse_httpd_ecb_t qse_httpd_ecb_t; +struct qse_httpd_ecb_t +{ + /** + * called by qse_httpd_close(). + */ + qse_httpd_ecb_close_t close; + + /* internal use only. don't touch this field */ + qse_httpd_ecb_t* next; +}; + #ifdef __cplusplus extern "C" { #endif @@ -344,6 +370,23 @@ void qse_httpd_setoption ( int option ); +/** + * The qse_httpd_popecb() function pops an httpd event callback set + * and returns the pointer to it. If no callback set can be popped, + * it returns #QSE_NULL. + */ +qse_httpd_ecb_t* qse_httpd_popecb ( + qse_httpd_t* httpd /**< httpd */ +); + +/** + * The qse_httpd_pushecb() function register a runtime callback set. + */ +void qse_httpd_pushecb ( + qse_httpd_t* httpd, /**< httpd */ + qse_httpd_ecb_t* ecb /**< callback set */ +); + /** * The qse_httpd_loop() function starts a httpd server loop. */ @@ -360,7 +403,6 @@ void qse_httpd_stop ( qse_httpd_t* httpd ); - int qse_httpd_addserver ( qse_httpd_t* httpd, const qse_char_t* uri @@ -502,6 +544,23 @@ void qse_httpd_freemem ( void* ptr ); +/* -------------------------------------------- */ + + +qse_httpd_t* qse_httpd_openstd ( + qse_size_t xtnsize +); + +qse_httpd_t* qse_httpd_openstdwithmmgr ( + qse_mmgr_t* mmgr, + qse_size_t xtnsize +); + +void* qse_httpd_getxtnstd ( + qse_httpd_t* httpd +); + + #ifdef __cplusplus } #endif diff --git a/qse/include/qse/sed/sed.h b/qse/include/qse/sed/sed.h index 0ca1f959..d4620b2b 100644 --- a/qse/include/qse/sed/sed.h +++ b/qse/include/qse/sed/sed.h @@ -345,6 +345,32 @@ typedef int (*qse_sed_lformatter_t) ( int (*cwriter) (qse_sed_t*, qse_char_t) ); +/** + * The qse_sed_ecb_close_t type defines the callback function + * called when an sed object is closed. + */ +typedef void (*qse_sed_ecb_close_t) ( + qse_sed_t* sed /**< sed */ +); + +/** + * The qse_sed_ecb_t type defines an event callback set. + * You can register a callback function set with + * qse_sed_pushecb(). The callback functions in the registered + * set are called in the reverse order of registration. + */ +typedef struct qse_sed_ecb_t qse_sed_ecb_t; +struct qse_sed_ecb_t +{ + /** + * called by qse_sed_close(). + */ + qse_sed_ecb_close_t close; + + /* internal use only. don't touch this field */ + qse_sed_ecb_t* next; +}; + #ifdef QSE_ENABLE_SEDTRACER enum qse_sed_exec_op_t { @@ -515,6 +541,23 @@ void qse_sed_seterror ( const qse_sed_loc_t* errloc /**< error location */ ); +/** + * The qse_sed_popecb() function pops an sed event callback set + * and returns the pointer to it. If no callback set can be popped, + * it returns #QSE_NULL. + */ +qse_sed_ecb_t* qse_sed_popecb ( + qse_sed_t* sed /**< sed */ +); + +/** + * The qse_sed_pushecb() function register a runtime callback set. + */ +void qse_sed_pushecb ( + qse_sed_t* sed, /**< sed */ + qse_sed_ecb_t* ecb /**< callback set */ +); + /** * The qse_sed_comp() function compiles editing commands into an internal form. * @return 0 on success, -1 on error diff --git a/qse/lib/awk/awk.c b/qse/lib/awk/awk.c index 23fc56e3..bd7be116 100644 --- a/qse/lib/awk/awk.c +++ b/qse/lib/awk/awk.c @@ -262,8 +262,14 @@ oops: int qse_awk_close (qse_awk_t* awk) { + qse_awk_ecb_t* ecb; + if (qse_awk_clear (awk) <= -1) return -1; /*qse_awk_clrfnc (awk);*/ + + for (ecb = awk->ecb; ecb; ecb = ecb->next) + if (ecb->close) ecb->close (awk); + qse_htb_close (awk->fnc.user); qse_lda_close (awk->parse.params); @@ -287,6 +293,11 @@ int qse_awk_close (qse_awk_t* awk) int qse_awk_clear (qse_awk_t* awk) { + qse_awk_ecb_t* ecb; + + for (ecb = awk->ecb; ecb; ecb = ecb->next) + if (ecb->clear) ecb->clear (awk); + awk->stopall = QSE_FALSE; clear_token (&awk->tok); @@ -439,3 +450,18 @@ void qse_awk_setmaxdepth (qse_awk_t* awk, int types, qse_size_t depth) awk->parse.depth.max.incl = depth; } } + + +qse_awk_ecb_t* qse_awk_popecb (qse_awk_t* awk) +{ + qse_awk_ecb_t* top = awk->ecb; + if (top) awk->ecb = top->next; + return top; +} + +void qse_awk_pushecb (qse_awk_t* awk, qse_awk_ecb_t* ecb) +{ + ecb->next = awk->ecb; + awk->ecb = ecb; +} + diff --git a/qse/lib/awk/awk.h b/qse/lib/awk/awk.h index 7edb51db..e0492e8f 100644 --- a/qse/lib/awk/awk.h +++ b/qse/lib/awk/awk.h @@ -235,6 +235,7 @@ struct qse_awk_t qse_awk_errinf_t errinf; qse_bool_t stopall; + qse_awk_ecb_t* ecb; }; struct qse_awk_chain_t @@ -371,7 +372,7 @@ struct qse_awk_rtx_t qse_awk_errinf_t errinf; qse_awk_t* awk; - qse_awk_rcb_t* rcb; + qse_awk_rtx_ecb_t* ecb; }; diff --git a/qse/lib/awk/run.c b/qse/lib/awk/run.c index 67e55c5f..ea83b127 100644 --- a/qse/lib/awk/run.c +++ b/qse/lib/awk/run.c @@ -744,10 +744,10 @@ qse_awk_rtx_t* qse_awk_rtx_open ( void qse_awk_rtx_close (qse_awk_rtx_t* rtx) { - qse_awk_rcb_t* rcb; + qse_awk_rtx_ecb_t* ecb; - for (rcb = rtx->rcb; rcb; rcb = rcb->next) - if (rcb->close) rcb->close (rtx, rcb->ctx); + for (ecb = rtx->ecb; ecb; ecb = ecb->next) + if (ecb->close) ecb->close (rtx); /* NOTE: * the close callbacks are called before data in rtx @@ -784,17 +784,17 @@ void qse_awk_rtx_setrio (qse_awk_rtx_t* rtx, const qse_awk_rio_t* rio) rtx->rio.handler[QSE_AWK_RIO_CONSOLE] = rio->console; } -qse_awk_rcb_t* qse_awk_rtx_poprcb (qse_awk_rtx_t* rtx) +qse_awk_rtx_ecb_t* qse_awk_rtx_popecb (qse_awk_rtx_t* rtx) { - qse_awk_rcb_t* top = rtx->rcb; - if (top) rtx->rcb = top->next; + qse_awk_rtx_ecb_t* top = rtx->ecb; + if (top) rtx->ecb = top->next; return top; } -void qse_awk_rtx_pushrcb (qse_awk_rtx_t* rtx, qse_awk_rcb_t* rcb) +void qse_awk_rtx_pushecb (qse_awk_rtx_t* rtx, qse_awk_rtx_ecb_t* ecb) { - rcb->next = rtx->rcb; - rtx->rcb = rcb; + ecb->next = rtx->ecb; + rtx->ecb = ecb; } static void free_namedval (qse_htb_t* map, void* dptr, qse_size_t dlen) @@ -1826,10 +1826,10 @@ static int run_block0 (qse_awk_rtx_t* rtx, qse_awk_nde_blk_t* nde) } #define ON_STATEMENT(rtx,nde) QSE_BLOCK ( \ - qse_awk_rcb_t* rcb; \ + qse_awk_rtx_ecb_t* ecb; \ if ((rtx)->awk->stopall) (rtx)->exit_level = EXIT_ABORT; \ - for (rcb = (rtx)->rcb; rcb; rcb = rcb->next) \ - if (rcb->stmt) rcb->stmt (rtx, nde, rcb->ctx); \ + for (ecb = (rtx)->ecb; ecb; ecb = ecb->next) \ + if (ecb->stmt) ecb->stmt (rtx, nde); \ ) static int run_statement (qse_awk_rtx_t* rtx, qse_awk_nde_t* nde) diff --git a/qse/lib/awk/std.c b/qse/lib/awk/std.c index 50a4c430..35706d9c 100644 --- a/qse/lib/awk/std.c +++ b/qse/lib/awk/std.c @@ -1478,7 +1478,7 @@ static qse_ssize_t awk_rio_console ( return -1; } -static void fini_rxtn (qse_awk_rtx_t* rtx, void* ctx) +static void fini_rxtn (qse_awk_rtx_t* rtx) { rxtn_t* rxtn = (rxtn_t*) QSE_XTN (rtx); @@ -1876,11 +1876,10 @@ qse_awk_rtx_t* qse_awk_rtx_openstd ( const qse_char_t*const ocf[], qse_cmgr_t* cmgr) { - static qse_awk_rcb_t rcb = + static qse_awk_rtx_ecb_t ecb = { - fini_rxtn, - QSE_NULL, - QSE_NULL + QSE_FV (.close, fini_rxtn), + QSE_FV (.stmt, QSE_NULL) }; qse_awk_rtx_t* rtx; @@ -1921,7 +1920,7 @@ qse_awk_rtx_t* qse_awk_rtx_openstd ( rxtn->cmgrtab_inited = 1; } - qse_awk_rtx_pushrcb (rtx, &rcb); + qse_awk_rtx_pushecb (rtx, &ecb); rxtn->seed = (qse_gettime (&now) <= -1)? 0u: (qse_long_t)now; /* i don't care if the seed becomes negative or overflows. diff --git a/qse/lib/net/Makefile.am b/qse/lib/net/Makefile.am index 9cbd51c2..49bf4013 100644 --- a/qse/lib/net/Makefile.am +++ b/qse/lib/net/Makefile.am @@ -16,6 +16,7 @@ libqsenet_la_SOURCES = \ httpd-cgi.c \ httpd-proxy.c \ httpd-resol.c \ + httpd-std.c \ httpd-task.c \ upxd.c diff --git a/qse/lib/net/Makefile.in b/qse/lib/net/Makefile.in index 1755e151..5b091304 100644 --- a/qse/lib/net/Makefile.in +++ b/qse/lib/net/Makefile.in @@ -79,8 +79,8 @@ am__installdirs = "$(DESTDIR)$(libdir)" LTLIBRARIES = $(lib_LTLIBRARIES) libqsenet_la_DEPENDENCIES = am_libqsenet_la_OBJECTS = http.lo htre.lo htrd.lo httpd.lo \ - httpd-cgi.lo httpd-proxy.lo httpd-resol.lo httpd-task.lo \ - upxd.lo + httpd-cgi.lo httpd-proxy.lo httpd-resol.lo httpd-std.lo \ + httpd-task.lo upxd.lo libqsenet_la_OBJECTS = $(am_libqsenet_la_OBJECTS) libqsenet_la_LINK = $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) \ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ @@ -273,6 +273,7 @@ libqsenet_la_SOURCES = \ httpd-cgi.c \ httpd-proxy.c \ httpd-resol.c \ + httpd-std.c \ httpd-task.c \ upxd.c @@ -358,6 +359,7 @@ distclean-compile: @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/httpd-cgi.Plo@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/httpd-proxy.Plo@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/httpd-resol.Plo@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/httpd-std.Plo@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/httpd-task.Plo@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/httpd.Plo@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/upxd.Plo@am__quote@ diff --git a/qse/lib/net/httpd-std.c b/qse/lib/net/httpd-std.c new file mode 100644 index 00000000..668f8689 --- /dev/null +++ b/qse/lib/net/httpd-std.c @@ -0,0 +1,1627 @@ +/* + * $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" + +#if defined(_WIN32) +# include +# include /* sockaddr_in6 */ +# include + +#elif defined(__OS2__) + /* TODO */ + +#elif defined(__DOS__) + /* TODO */ + +#else +# include "../cmn/syscall.h" +# include +# include +# include +# include +# if defined(HAVE_SYS_SENDFILE_H) +# include +# endif +# if defined(HAVE_EPOLL) && defined(HAVE_SYS_EPOLL_H) +# include +# endif +#endif + + +#if defined(HAVE_SSL) +# include +# include +# include +#endif + +#include /* TODO: remove this */ + +/* ------------------------------------------------------------------- */ + +#if defined(_WIN32) +static qse_httpd_errnum_t syserr_to_errnum (DWORD e) +{ + + switch (e) + { + case ERROR_NOT_ENOUGH_MEMORY: + case ERROR_OUTOFMEMORY: + return QSE_HTTPD_ENOMEM; + + case ERROR_INVALID_PARAMETER: + case ERROR_INVALID_HANDLE: + case ERROR_INVALID_NAME: + return QSE_HTTPD_EINVAL; + + case ERROR_ACCESS_DENIED: + return QSE_HTTPD_EACCES; + + case ERROR_FILE_NOT_FOUND: + case ERROR_PATH_NOT_FOUND: + return QSE_HTTPD_ENOENT; + + case ERROR_ALREADY_EXISTS: + case ERROR_FILE_EXISTS: + return QSE_HTTPD_EEXIST; + + default: + return QSE_HTTPD_ESYSERR; + } +} +#elif defined(__OS2__) +static qse_httpd_errnum_t syserr_to_errnum (APIRET e) +{ + switch (e) + { + case ERROR_NOT_ENOUGH_MEMORY: + return QSE_HTTPD_ENOMEM; + + case ERROR_INVALID_PARAMETER: + case ERROR_INVALID_HANDLE: + case ERROR_INVALID_NAME: + return QSE_HTTPD_EINVAL; + + case ERROR_ACCESS_DENIED: + return QSE_HTTPD_EACCES; + + case ERROR_FILE_NOT_FOUND: + case ERROR_PATH_NOT_FOUND: + return QSE_HTTPD_ENOENT; + + case ERROR_ALREADY_EXISTS: + return QSE_HTTPD_EEXIST; + + default: + return QSE_HTTPD_ESYSERR; + } +} +#elif defined(__DOS__) +static qse_httpd_errnum_t syserr_to_errnum (int e) +{ + switch (e) + { + case ENOMEM: + return QSE_HTTPD_ENOMEM; + + case EINVAL: + return QSE_HTTPD_EINVAL; + + case EACCES: + return QSE_HTTPD_EACCES; + + case ENOENT: + return QSE_HTTPD_ENOENT; + + case EEXIST: + return QSE_HTTPD_EEXIST; + + default: + return QSE_HTTPD_ESYSERR; + } +} + +#else +static qse_httpd_errnum_t syserr_to_errnum (int e) +{ + switch (e) + { + case ENOMEM: + return QSE_HTTPD_ENOMEM; + + case EINVAL: + return QSE_HTTPD_EINVAL; + + case EACCES: + case ECONNREFUSED: + return QSE_HTTPD_EACCES; + + case ENOENT: + return QSE_HTTPD_ENOENT; + + case EEXIST: + return QSE_HTTPD_EEXIST; + + case EINTR: + return QSE_HTTPD_EINTR; + + case EAGAIN: + /*case EWOULDBLOCK:*/ + return QSE_HTTPD_EAGAIN; + + default: + return QSE_HTTPD_ESYSERR; + } +} +#endif + +/* ------------------------------------------------------------------- */ + +#define MAX_SEND_SIZE 4096 + +#if defined(_WIN32) + /* TODO */ + /* TODO: WIN32 TransmitFile */ +#elif defined(__OS2__) + /* TODO */ +#elif defined(__DOS__) + /* TODO */ + +#elif defined(HAVE_SENDFILE) && defined(HAVE_SENDFILE64) +# if !defined(_LP64) && (QSE_SIZEOF_VOID_P<8) && defined(HAVE_SENDFILE64) +# define xsendfile(out,in,offset,count) sendfile64(out,in,offset,count) +# else +# define xsendfile(out,in,offset,count) sendfile(out,in,offset,count) +# endif + +#elif defined(HAVE_SENDFILE) +# define xsendfile(out,in,offset,count) sendfile(out,in,offset,count) + +#elif defined(HAVE_SENDFILE64) +# define xsendfile(out,in,offset,count) sendfile64(out,in,offset,count) + +#elif defined(HAVE_SENDFILEV) || defined(HAVE_SENDFILEV64) + +static qse_ssize_t xsendfile ( + int out_fd, int in_fd, qse_foff_t* offset, qse_size_t count) +{ +#if !defined(_LP64) && (QSE_SIZEOF_VOID_P<8) && defined(HAVE_SENDFILE64) + struct sendfilevec64 vec; +#else + struct sendfilevec vec; +#endif + size_t xfer; + ssize_t n; + + vec.sfv_fd = in_fd; + vec.sfv_flag = 0; + if (offset) + { + vec.sfv_off = *offset; + } + else + { + vec.sfv_off = QSE_LSEEK (in_fd, 0, SEEK_CUR); + if (vec.sfv_off == (off_t)-1) return (qse_ssize_t)-1; + } + vec.sfv_len = count; + +#if !defined(_LP64) && (QSE_SIZEOF_VOID_P<8) && defined(HAVE_SENDFILE64) + n = sendfilev64 (out_fd, &vec, 1, &xfer); +#else + n = sendfilev (out_fd, &vec, 1, &xfer); +#endif + if (offset) *offset = *offset + xfer; + +/* TODO: xfer contains number of byte written even on failure +on success xfer == n. +on failure xfer != n. + */ + return n; +} + +#else + +static qse_ssize_t xsendfile ( + int out_fd, int in_fd, qse_foff_t* offset, qse_size_t count) +{ + qse_mchar_t buf[MAX_SEND_SIZE]; + qse_ssize_t n; + + if (offset && QSE_LSEEK (in_fd, *offset, SEEK_SET) != *offset) + return (qse_ssize_t)-1; + + if (count > QSE_COUNTOF(buf)) count = QSE_COUNTOF(buf); + n = read (in_fd, buf, count); + if (n == (qse_ssize_t)-1 || n == 0) return n; + + n = send (out_fd, buf, n, 0); + if (n > 0 && offset) *offset = *offset + n; + + return n; +} + +#endif + +/* ------------------------------------------------------------------- */ + +#if defined(HAVE_SSL) +static qse_ssize_t xsendfile_ssl ( + SSL* out, int in_fd, qse_foff_t* offset, qse_size_t count) +{ + qse_mchar_t buf[MAX_SEND_SIZE]; + qse_ssize_t n; + + if (offset && QSE_LSEEK (in_fd, *offset, SEEK_SET) != *offset) + return (qse_ssize_t)-1; + + if (count > QSE_COUNTOF(buf)) count = QSE_COUNTOF(buf); + n = read (in_fd, buf, count); + if (n == (qse_ssize_t)-1 || n == 0) return n; + + n = SSL_write (out, buf, count); + if (n > 0 && offset) *offset = *offset + n; + + return n; +} +#endif + +/* ------------------------------------------------------------------- */ + +typedef struct httpd_xtn_t httpd_xtn_t; +struct httpd_xtn_t +{ +#if defined(HAVE_SSL) + SSL_CTX* ssl_ctx; +#endif +}; + +#if defined(HAVE_SSL) +static int init_xtn_ssl ( + httpd_xtn_t* xtn, + const qse_mchar_t* pemfile, + const qse_mchar_t* keyfile/*, + const qse_mchar_t* chainfile*/) +{ + SSL_CTX* ctx; + + SSL_library_init (); + SSL_load_error_strings (); + /*SSLeay_add_ssl_algorithms();*/ + + ctx = SSL_CTX_new (SSLv23_server_method()); + if (ctx == QSE_NULL) return -1; + + /*SSL_CTX_set_info_callback(ctx,ssl_info_callback);*/ + + if (SSL_CTX_use_certificate_file (ctx, pemfile, SSL_FILETYPE_PEM) == 0 || + SSL_CTX_use_PrivateKey_file (ctx, keyfile, SSL_FILETYPE_PEM) == 0 || + SSL_CTX_check_private_key (ctx) == 0 /*|| + SSL_CTX_use_certificate_chain_file (ctx, chainfile) == 0*/) + { + qse_mchar_t buf[128]; + ERR_error_string_n(ERR_get_error(), buf, QSE_COUNTOF(buf)); + qse_fprintf (QSE_STDERR, QSE_T("Error: %hs\n"), buf); + SSL_CTX_free (ctx); + return -1; + } + + +/* TODO: CRYPTO_set_id_callback (); + TODO: CRYPTO_set_locking_callback ();*/ + + xtn->ssl_ctx = ctx; + return 0; +} + +static void fini_xtn_ssl (httpd_xtn_t* xtn) +{ +/* TODO: CRYPTO_set_id_callback (QSE_NULL); + TODO: CRYPTO_set_locking_callback (QSE_NULL); */ + SSL_CTX_free (xtn->ssl_ctx); + + + /*ERR_remove_state ();*/ + ENGINE_cleanup (); + + ERR_free_strings (); + EVP_cleanup (); + CRYPTO_cleanup_all_ex_data (); +} +#endif + +/* ------------------------------------------------------------------- */ + +static void cleanup_standard_httpd (qse_httpd_t* httpd) +{ + httpd_xtn_t* xtn; + + xtn = (httpd_xtn_t*)qse_httpd_getxtn (httpd); + +#if defined(HAVE_SSL) + if (xtn->ssl_ctx) fini_xtn_ssl (xtn); +#endif +} + +qse_httpd_t* qse_httpd_openstd (qse_size_t xtnsize) +{ + return qse_httpd_openstdwithmmgr (QSE_MMGR_GETDFL(), xtnsize); +} + +qse_httpd_t* qse_httpd_openstdwithmmgr (qse_mmgr_t* mmgr, qse_size_t xtnsize) +{ + qse_httpd_t* httpd; + httpd_xtn_t* xtn; + + static qse_httpd_ecb_t std_ecb = + { + QSE_FV(.close, cleanup_standard_httpd) + }; + + httpd = qse_httpd_open (mmgr, QSE_SIZEOF(httpd_xtn_t) + xtnsize); + if (httpd == QSE_NULL) return QSE_NULL; + + xtn = (httpd_xtn_t*)qse_httpd_getxtn (httpd); + +#if defined(HAVE_SSL) + xtn->ssl_ctx = QSE_NULL; + init_xtn_ssl (xtn, "http01.pem", "http01.key"); +#endif + + qse_httpd_pushecb (httpd, &std_ecb); + return httpd; +} + +void* qse_httpd_getxtnstd (qse_httpd_t* httpd) +{ + return (void*)((httpd_xtn_t*)QSE_XTN(httpd) + 1); +} + +/* ------------------------------------------------------------------- */ + +static int sockaddr_to_nwad ( + const struct sockaddr_storage* addr, qse_nwad_t* nwad) +{ + int addrsize = -1; + + switch (addr->ss_family) + { + case AF_INET: + { + struct sockaddr_in* in; + in = (struct sockaddr_in*)addr; + addrsize = QSE_SIZEOF(*in); + + memset (nwad, 0, QSE_SIZEOF(*nwad)); + nwad->type = QSE_NWAD_IN4; + nwad->u.in4.addr.value = in->sin_addr.s_addr; + nwad->u.in4.port = in->sin_port; + break; + } + +#if defined(AF_INET6) + case AF_INET6: + { + struct sockaddr_in6* in; + in = (struct sockaddr_in6*)addr; + addrsize = QSE_SIZEOF(*in); + + memset (nwad, 0, QSE_SIZEOF(*nwad)); + nwad->type = QSE_NWAD_IN6; + memcpy (&nwad->u.in6.addr, &in->sin6_addr, QSE_SIZEOF(nwad->u.in6.addr)); + nwad->u.in6.scope = in->sin6_scope_id; + nwad->u.in6.port = in->sin6_port; + break; + } +#endif + } + + return addrsize; +} + +static int nwad_to_sockaddr ( + const qse_nwad_t* nwad, struct sockaddr_storage* addr) +{ + int addrsize = -1; + + switch (nwad->type) + { + case QSE_NWAD_IN4: + { + struct sockaddr_in* in; + + in = (struct sockaddr_in*)addr; + addrsize = QSE_SIZEOF(*in); + memset (in, 0, addrsize); + + in->sin_family = AF_INET; + in->sin_addr.s_addr = nwad->u.in4.addr.value; + in->sin_port = nwad->u.in4.port; + break; + } + + case QSE_NWAD_IN6: + { +#if defined(AF_INET6) + struct sockaddr_in6* in; + + in = (struct sockaddr_in6*)addr; + addrsize = QSE_SIZEOF(*in); + memset (in, 0, addrsize); + + in->sin6_family = AF_INET6; + memcpy (&in->sin6_addr, &nwad->u.in6.addr, QSE_SIZEOF(nwad->u.in6.addr)); + in->sin6_scope_id = nwad->u.in6.scope; + in->sin6_port = nwad->u.in6.port; +#endif + break; + } + } + + return addrsize; +} + +/* ------------------------------------------------------------------- */ + +static int server_open (qse_httpd_t* httpd, qse_httpd_server_t* server) +{ + int fd = -1, flag; +/* TODO: if AF_INET6 is not defined sockaddr_storage is not available... + * create your own union or somehting similar... */ + struct sockaddr_storage addr; + int addrsize; + + addrsize = nwad_to_sockaddr (&server->nwad, &addr); + if (addrsize <= -1) + { + qse_httpd_seterrnum (httpd, QSE_HTTPD_ENOIMPL); + return -1; + } + + fd = socket (addr.ss_family, SOCK_STREAM, IPPROTO_TCP); + if (fd <= -1) goto oops; + + flag = fcntl (fd, F_GETFD); + if (flag >= 0) fcntl (fd, F_SETFD, flag | FD_CLOEXEC); + + flag = 1; + setsockopt (fd, SOL_SOCKET, SO_REUSEADDR, &flag, QSE_SIZEOF(flag)); + +#if defined(IP_TRANSPARENT) + /* remove the ip routing restriction that a packet can only + * be sent using a local ip address. this option is useful + * if transparency is achieved with TPROXY */ + flag = 1; + setsockopt (fd, SOL_IP, IP_TRANSPARENT, &flag, QSE_SIZEOF(flag)); +#endif + + /* Solaris 8 returns EINVAL if QSE_SIZEOF(addr) is passed in as the + * address size for AF_INET. */ + /*if (bind (s, (struct sockaddr*)&addr, QSE_SIZEOF(addr)) <= -1) goto oops_esocket;*/ + if (bind (fd, (struct sockaddr*)&addr, addrsize) <= -1) + { +#if defined(IPV6_V6ONLY) + if (errno == EADDRINUSE && addr.ss_family == AF_INET6) + { + int on = 1; + setsockopt (fd, IPPROTO_IPV6, IPV6_V6ONLY, &on, sizeof(on)); + if (bind (fd, (struct sockaddr*)&addr, addrsize) <= -1) goto oops; + } + else goto oops; +#else + goto oops; +#endif + } + if (listen (fd, 10) <= -1) goto oops; + + flag = fcntl (fd, F_GETFL); + if (flag >= 0) fcntl (fd, F_SETFL, flag | O_NONBLOCK); + + server->handle.i = fd; + return 0; + +oops: + qse_httpd_seterrnum (httpd, syserr_to_errnum(errno)); + if (fd >= 0) close (fd); + return -1; +} + +static void server_close (qse_httpd_t* httpd, qse_httpd_server_t* server) +{ + close (server->handle.i); +} + +static int server_accept ( + qse_httpd_t* httpd, + qse_httpd_server_t* server, qse_httpd_client_t* client) +{ + struct sockaddr_storage addr; + +#ifdef HAVE_SOCKLEN_T + socklen_t addrlen; +#else + int addrlen; +#endif + int fd, flag; + + addrlen = QSE_SIZEOF(addr); + fd = accept (server->handle.i, (struct sockaddr*)&addr, &addrlen); + if (fd <= -1) + { + qse_httpd_seterrnum (httpd, syserr_to_errnum (errno)); + return -1; + } + +#if 0 + if (fd >= FD_SETSIZE) + { +qse_fprintf (QSE_STDERR, QSE_T("Error: too many client?\n")); + /*TODO: qse_httpd_seterrnum (httpd, QSE_HTTPD_EXXXXX);*/ + close (fd); + return -1; + } +#endif + flag = fcntl (fd, F_GETFD); + if (flag >= 0) fcntl (fd, F_SETFD, flag | FD_CLOEXEC); + + flag = fcntl (fd, F_GETFL); + if (flag >= 0) fcntl (fd, F_SETFL, flag | O_NONBLOCK); + + + if (sockaddr_to_nwad (&addr, &client->remote_addr) <= -1) + { +/* TODO: logging */ + client->remote_addr = server->nwad; + } + + addrlen = QSE_SIZEOF(addr); + if (getsockname (fd, (struct sockaddr*)&addr, &addrlen) <= -1 || + sockaddr_to_nwad (&addr, &client->local_addr) <= -1) + { +/* TODO: logging */ + client->local_addr = server->nwad; + } + +#if defined(SO_ORIGINAL_DST) + addrlen = QSE_SIZEOF(addr); + if (getsockopt (fd, SOL_IP, SO_ORIGINAL_DST, (char*)&addr, &addrlen) <= -1 || + sockaddr_to_nwad (&addr, &client->orgdst_addr) <= -1) + { + client->orgdst_addr = client->local_addr; + } +#endif + + client->handle.i = fd; + return 0; +} + +/* ------------------------------------------------------------------- */ + +static int peer_open (qse_httpd_t* httpd, qse_httpd_peer_t* peer) +{ + int fd = -1, flag; +/* TODO: if AF_INET6 is not defined sockaddr_storage is not available... + * create your own union or somehting similar... */ + struct sockaddr_storage addr; + int addrsize; + int connected = 1; + + addrsize = nwad_to_sockaddr (&peer->nwad, &addr); + if (addrsize <= -1) + { + qse_httpd_seterrnum (httpd, QSE_HTTPD_ENOIMPL); + return -1; + } + + fd = socket (addr.ss_family, SOCK_STREAM, IPPROTO_TCP); + if (fd <= -1) goto oops; + + flag = fcntl (fd, F_GETFD); + if (flag >= 0) fcntl (fd, F_SETFD, flag | FD_CLOEXEC); + + flag = fcntl (fd, F_GETFL); + if (flag >= 0) fcntl (fd, F_SETFL, flag | O_NONBLOCK); + + if (connect (fd, (struct sockaddr*)&addr, addrsize) <= -1) + { + if (errno != EINPROGRESS) goto oops; + connected = 0; + } + + /* restore flags */ + if (fcntl (fd, F_SETFL, flag) <= -1) goto oops; + + peer->handle.i = fd; + return connected; + +oops: + qse_httpd_seterrnum (httpd, syserr_to_errnum(errno)); + if (fd >= 0) close (fd); + return -1; +} + +static void peer_close (qse_httpd_t* httpd, qse_httpd_peer_t* peer) +{ + close (peer->handle.i); +} + +static int peer_connected (qse_httpd_t* httpd, qse_httpd_peer_t* peer) +{ +#ifdef HAVE_SOCKLEN_T + socklen_t len; +#else + int len; +#endif + int ret; + + len = QSE_SIZEOF(ret); + if (getsockopt (peer->handle.i, SOL_SOCKET, SO_ERROR, &ret, &len) <= -1) return -1; + + if (ret == EINPROGRESS) return 0; + if (ret != 0) + { + qse_httpd_seterrnum (httpd, syserr_to_errnum (ret)); + return -1; + } + + return 1; /* connection completed */ +} + +static qse_ssize_t peer_recv ( + qse_httpd_t* httpd, qse_httpd_peer_t* peer, + qse_mchar_t* buf, qse_size_t bufsize) +{ + ssize_t ret = read (peer->handle.i, buf, bufsize); + if (ret <= -1) qse_httpd_seterrnum (httpd, syserr_to_errnum(errno)); + return ret; +} + +static qse_ssize_t peer_send ( + qse_httpd_t* httpd, qse_httpd_peer_t* peer, + const qse_mchar_t* buf, qse_size_t bufsize) +{ + ssize_t ret = write (peer->handle.i, buf, bufsize); + if (ret <= -1) qse_httpd_seterrnum (httpd, syserr_to_errnum(errno)); + return ret; +} + +/* ------------------------------------------------------------------- */ + +struct mux_ev_t +{ + qse_ubi_t handle; + int reqmask; + qse_httpd_muxcb_t cbfun; + void* cbarg; +}; + +struct mux_t +{ + int fd; + + struct + { + struct epoll_event* ptr; + qse_size_t len; + qse_size_t capa; + } ee; + + struct + { + struct mux_ev_t** ptr; + qse_size_t capa; + } mev; +}; + +#define MUX_EV_ALIGN 64 + +static void* mux_open (qse_httpd_t* httpd) +{ + struct mux_t* mux; + + mux = qse_httpd_allocmem (httpd, QSE_SIZEOF(*mux)); + if (mux == QSE_NULL) return QSE_NULL; + + memset (mux, 0, QSE_SIZEOF(*mux)); + +#if defined(HAVE_EPOLL_CREATE1) && defined(O_CLOEXEC) + mux->fd = epoll_create1 (O_CLOEXEC); +#else + mux->fd = epoll_create (100); +#endif + if (mux->fd <= -1) + { + qse_httpd_freemem (httpd, mux); + qse_httpd_seterrnum (httpd, syserr_to_errnum(errno)); + return QSE_NULL; + } + +#if defined(HAVE_EPOLL_CREATE1) && defined(O_CLOEXEC) + /* nothing else to do */ +#else + { + int flag = fcntl (mux->fd, F_GETFD); + if (flag >= 0) fcntl (mux->fd, F_SETFD, flag | FD_CLOEXEC); + } +#endif + + return mux; +} + +static void mux_close (qse_httpd_t* httpd, void* vmux) +{ + struct mux_t* mux = (struct mux_t*)vmux; + if (mux->ee.ptr) qse_httpd_freemem (httpd, mux->ee.ptr); + if (mux->mev.ptr) + { + qse_size_t i; + for (i = 0; i < mux->mev.capa; i++) + if (mux->mev.ptr[i]) qse_httpd_freemem (httpd, mux->mev.ptr[i]); + qse_httpd_freemem (httpd, mux->mev.ptr); + } + close (mux->fd); + qse_httpd_freemem (httpd, mux); +} + +static int mux_addhnd ( + qse_httpd_t* httpd, void* vmux, qse_ubi_t handle, + int mask, qse_httpd_muxcb_t cbfun, void* cbarg) +{ + struct mux_t* mux = (struct mux_t*)vmux; + struct epoll_event ev; + struct mux_ev_t* mev; + + ev.events = 0; + if (mask & QSE_HTTPD_MUX_READ) ev.events |= EPOLLIN; + if (mask & QSE_HTTPD_MUX_WRITE) ev.events |= EPOLLOUT; + + if (ev.events == 0 || handle.i <= -1) + { + qse_httpd_seterrnum (httpd, QSE_HTTPD_EINVAL); + return -1; + } + + if (handle.i >= mux->mev.capa) + { + struct mux_ev_t** tmp; + qse_size_t tmpcapa, i; + + tmpcapa = (((handle.i + MUX_EV_ALIGN) / MUX_EV_ALIGN) * MUX_EV_ALIGN); + + tmp = (struct mux_ev_t**) qse_httpd_reallocmem ( + httpd, mux->mev.ptr, + QSE_SIZEOF(*mux->mev.ptr) * tmpcapa); + if (tmp == QSE_NULL) return -1; + + for (i = mux->mev.capa; i < tmpcapa; i++) tmp[i] = QSE_NULL; + mux->mev.ptr = tmp; + mux->mev.capa = tmpcapa; + } + + if (mux->mev.ptr[handle.i] == QSE_NULL) + { + /* the location of the data passed to epoll_ctl() + * must not change unless i update the info with epoll() + * whenever there is reallocation. so i simply + * make mux-mev.ptr reallocatable but auctual + * data fixed once allocated. */ + mux->mev.ptr[handle.i] = qse_httpd_allocmem ( + httpd, QSE_SIZEOF(*mux->mev.ptr[handle.i])); + if (mux->mev.ptr[handle.i] == QSE_NULL) return -1; + } + + if (mux->ee.len >= mux->ee.capa) + { + struct epoll_event* tmp; + + tmp = qse_httpd_reallocmem ( + httpd, mux->ee.ptr, + QSE_SIZEOF(*mux->ee.ptr) * (mux->ee.capa + 1) * 2); + if (tmp == QSE_NULL) return -1; + + mux->ee.ptr = tmp; + mux->ee.capa = (mux->ee.capa + 1) * 2; + } + + mev = mux->mev.ptr[handle.i]; + mev->handle = handle; + mev->reqmask = mask; + mev->cbfun = cbfun; + mev->cbarg = cbarg; + + ev.data.ptr = mev; + + if (epoll_ctl (mux->fd, EPOLL_CTL_ADD, handle.i, &ev) <= -1) + { + /* don't rollback ee.ptr */ + qse_httpd_seterrnum (httpd, syserr_to_errnum(errno)); + return -1; + } + + mux->ee.len++; + return 0; +} + +static int mux_delhnd (qse_httpd_t* httpd, void* vmux, qse_ubi_t handle) +{ + struct mux_t* mux = (struct mux_t*)vmux; + + if (epoll_ctl (mux->fd, EPOLL_CTL_DEL, handle.i, QSE_NULL) <= -1) + { + qse_httpd_seterrnum (httpd, syserr_to_errnum(errno)); + return -1; + } + + mux->ee.len--; + return 0; +} + +static int mux_poll (qse_httpd_t* httpd, void* vmux, qse_ntime_t timeout) +{ + struct mux_t* mux = (struct mux_t*)vmux; + struct mux_ev_t* mev; + int mask, nfds, i; + + nfds = epoll_wait (mux->fd, mux->ee.ptr, mux->ee.len, timeout); + if (nfds <= -1) + { + qse_httpd_seterrnum (httpd, syserr_to_errnum(errno)); + return -1; + } + + for (i = 0; i < nfds; i++) + { + mev = mux->ee.ptr[i].data.ptr; + + mask = 0; + + if (mux->ee.ptr[i].events & EPOLLIN) + mask |= QSE_HTTPD_MUX_READ; + if (mux->ee.ptr[i].events & EPOLLOUT) + mask |= QSE_HTTPD_MUX_WRITE; + + if (mux->ee.ptr[i].events & EPOLLHUP) + { + if (mev->reqmask & QSE_HTTPD_MUX_READ) + mask |= QSE_HTTPD_MUX_READ; + if (mev->reqmask & QSE_HTTPD_MUX_WRITE) + mask |= QSE_HTTPD_MUX_WRITE; + } + + mev->cbfun (httpd, mux, mev->handle, mask, mev->cbarg); + } + return 0; +} + +static int mux_readable (qse_httpd_t* httpd, qse_ubi_t handle, qse_ntoff_t msec) +{ + fd_set r; + struct timeval tv, * tvp; + + if (msec >= 0) + { + tv.tv_sec = (msec / 1000); + tv.tv_usec = ((msec % 1000) * 1000); + tvp = &tv; + } + else tvp = QSE_NULL; + + FD_ZERO (&r); + FD_SET (handle.i, &r); + + return select (handle.i + 1, &r, QSE_NULL, QSE_NULL, tvp); +} + +static int mux_writable (qse_httpd_t* httpd, qse_ubi_t handle, qse_ntoff_t msec) +{ + fd_set w; + struct timeval tv, * tvp; + + if (msec >= 0) + { + tv.tv_sec = (msec / 1000); + tv.tv_usec = ((msec % 1000) * 1000); + tvp = &tv; + } + else tvp = QSE_NULL; + + FD_ZERO (&w); + FD_SET (handle.i, &w); + + return select (handle.i + 1, QSE_NULL, &w, QSE_NULL, tvp); +} + +/* ------------------------------------------------------------------- */ + +static int file_executable (qse_httpd_t* httpd, const qse_mchar_t* path) +{ + if (access (path, X_OK) == -1) + return (errno == EACCES)? 0 /*no*/: -1 /*error*/; + return 1; /* yes */ +} + +static int file_stat ( + qse_httpd_t* httpd, const qse_mchar_t* path, qse_httpd_stat_t* hst) +{ + struct stat st; + +/* TODO: lstat? or stat? */ + if (stat (path, &st) <= -1) + { + qse_httpd_seterrnum (httpd, syserr_to_errnum(errno)); + return -1; + } + + /* stating for a file. it should be a regular file. + * i don't allow other file types. */ + if (!S_ISREG(st.st_mode)) + { + qse_httpd_seterrnum (httpd, QSE_HTTPD_EACCES); + return -1; + } + + memset (hst, 0, QSE_SIZEOF(*hst)); + + hst->size = st.st_size; +#if defined(HAVE_STRUCT_STAT_ST_MTIM_TV_NSEC) + hst->mtime = QSE_SECNSEC_TO_MSEC(st.st_mtim.tv_sec,st.st_mtim.tv_nsec); +#elif defined(HAVE_STRUCT_STAT_ST_MTIMESPEC_TV_NSEC) + hst->mtime = QSE_SECNSEC_TO_MSEC(st.st_mtimespec.tv_sec,st.st_mtimespec.tv_nsec); +#else + hst->mtime = st.st_mtime * QSE_MSECS_PER_SEC; +#endif + + hst->mime = qse_mbsend (path, QSE_MT(".html"))? QSE_MT("text/html"): + qse_mbsend (path, QSE_MT(".txt"))? QSE_MT("text/plain"): + qse_mbsend (path, QSE_MT(".jpg"))? QSE_MT("image/jpeg"): + qse_mbsend (path, QSE_MT(".mp4"))? QSE_MT("video/mp4"): + qse_mbsend (path, QSE_MT(".mp3"))? QSE_MT("audio/mpeg"): QSE_NULL; + return 0; +} + +static int file_ropen ( + qse_httpd_t* httpd, const qse_mchar_t* path, qse_ubi_t* handle) +{ + int fd; + int flags; + + flags = O_RDONLY; +#if defined(O_LARGEFILE) + flags |= O_LARGEFILE; +#endif + +qse_printf (QSE_T("opening file [%hs] for reading\n"), path); + fd = open (path, flags, 0); + if (fd <= -1) + { + qse_httpd_seterrnum (httpd, syserr_to_errnum(errno)); + return -1; + } + + flags = fcntl (fd, F_GETFD); + if (flags >= 0) fcntl (fd, F_SETFD, flags | FD_CLOEXEC); + + handle->i = fd; +qse_printf (QSE_T("opened file %hs\n"), path); + return 0; +} + +static int file_wopen ( + qse_httpd_t* httpd, const qse_mchar_t* path, + qse_ubi_t* handle) +{ + int fd; + int flags; + + flags = O_WRONLY | O_CREAT | O_TRUNC; +#if defined(O_LARGEFILE) + flags |= O_LARGEFILE; +#endif + +qse_printf (QSE_T("opening file [%hs] for writing\n"), path); + fd = open (path, flags, 0644); + if (fd <= -1) + { + qse_httpd_seterrnum (httpd, syserr_to_errnum(errno)); + return -1; + } + + handle->i = fd; + return 0; +} + +static void file_close (qse_httpd_t* httpd, qse_ubi_t handle) +{ +qse_printf (QSE_T("closing file %d\n"), handle.i); + close (handle.i); +} + +static qse_ssize_t file_read ( + qse_httpd_t* httpd, qse_ubi_t handle, + qse_mchar_t* buf, qse_size_t len) +{ + return read (handle.i, buf, len); +} + +static qse_ssize_t file_write ( + qse_httpd_t* httpd, qse_ubi_t handle, + const qse_mchar_t* buf, qse_size_t len) +{ + return write (handle.i, buf, len); +} + +/* ------------------------------------------------------------------- */ +static void client_close ( + qse_httpd_t* httpd, qse_httpd_client_t* client) +{ + close (client->handle.i); +} + +static void client_shutdown ( + qse_httpd_t* httpd, qse_httpd_client_t* client) +{ +#if defined(SHUT_RDWR) + shutdown (client->handle.i, SHUT_RDWR); +#else + shutdown (client->handle.i, 2); +#endif +} + +static qse_ssize_t client_recv ( + qse_httpd_t* httpd, qse_httpd_client_t* client, + qse_mchar_t* buf, qse_size_t bufsize) +{ + if (client->secure) + { +#if defined(HAVE_SSL) + int ret = SSL_read (client->handle2.ptr, buf, bufsize); + if (ret <= -1) + { + if (SSL_get_error(client->handle2.ptr,ret) == SSL_ERROR_WANT_READ) + qse_httpd_seterrnum (httpd, QSE_HTTPD_EAGAIN); + else + qse_httpd_seterrnum (httpd, QSE_HTTPD_ESYSERR); + } + return ret; +#else + return -1; +#endif + } + else + { + ssize_t ret = recv (client->handle.i, buf, bufsize, 0); + if (ret <= -1) qse_httpd_seterrnum (httpd, syserr_to_errnum(errno)); + return ret; + } +} + +static qse_ssize_t client_send ( + qse_httpd_t* httpd, qse_httpd_client_t* client, + const qse_mchar_t* buf, qse_size_t bufsize) +{ + if (client->secure) + { +#if defined(HAVE_SSL) + int ret = SSL_write (client->handle2.ptr, buf, bufsize); + if (ret <= -1) + { + if (SSL_get_error(client->handle2.ptr,ret) == SSL_ERROR_WANT_WRITE) + qse_httpd_seterrnum (httpd, QSE_HTTPD_EAGAIN); + else + qse_httpd_seterrnum (httpd, QSE_HTTPD_ESYSERR); + } + return ret; +#else + return -1; +#endif + } + else + { + ssize_t ret = send (client->handle.i, buf, bufsize, 0); + if (ret <= -1) qse_httpd_seterrnum (httpd, syserr_to_errnum(errno)); + return ret; + } +} + +static qse_ssize_t client_sendfile ( + qse_httpd_t* httpd, qse_httpd_client_t* client, + qse_ubi_t handle, qse_foff_t* offset, qse_size_t count) +{ + if (client->secure) + { +#if defined(HAVE_SSL) + return xsendfile_ssl (client->handle2.ptr, handle.i, offset, count); +#else + return -1; +#endif + } + else + { + return xsendfile (client->handle.i, handle.i, offset, count); + } +} + +static int client_accepted (qse_httpd_t* httpd, qse_httpd_client_t* client) +{ + httpd_xtn_t* xtn = (httpd_xtn_t*) qse_httpd_getxtn (httpd); + + if (client->secure) + { +#if defined(HAVE_SSL) + int ret; + SSL* ssl; + + if (client->handle2.ptr) + { + ssl = client->handle2.ptr; + } + else if (!xtn->ssl_ctx) + { + /* no ssl */ +qse_printf (QSE_T("NO SSL\n")); +qse_fflush (QSE_STDOUT); + return -1; + } + else + { + ssl = SSL_new (xtn->ssl_ctx); + if (ssl == QSE_NULL) return -1; + + client->handle2.ptr = ssl; + +qse_printf (QSE_T("SSL ACCEPTING %d\n"), client->handle.i); +qse_fflush (QSE_STDOUT); + if (SSL_set_fd (ssl, client->handle.i) == 0) + { + /* don't free ssl here since client_closed() + * will free it */ + return -1; + } + } + + ret = SSL_accept (ssl); + if (ret <= 0) + { + if (SSL_get_error(ssl,ret) == SSL_ERROR_WANT_READ) + { + /* handshaking isn't complete. */ + return 0; + } + + qse_fprintf (QSE_STDERR, QSE_T("Error: SSL ACCEPT ERROR\n")); + /* SSL_free (ssl); */ + return -1; + } +#else + return -1; +#endif + } + + return 1; /* accept completed */ +} + +static void client_closed (qse_httpd_t* httpd, qse_httpd_client_t* client) +{ + if (client->secure) + { +#if defined(HAVE_SSL) + if (client->handle2.ptr) + { + SSL_shutdown ((SSL*)client->handle2.ptr); /* is this needed? */ + SSL_free ((SSL*)client->handle2.ptr); + } +#endif + } +} + +/* ------------------------------------------------------------------- */ +static qse_htb_walk_t walk (qse_htb_t* htb, qse_htb_pair_t* pair, void* ctx) +{ + qse_htre_hdrval_t* val; + + val = QSE_HTB_VPTR(pair); + while (val) + { +qse_printf (QSE_T("HEADER OK %d[%hs] %d[%hs]\n"), (int)QSE_HTB_KLEN(pair), QSE_HTB_KPTR(pair), (int)val->len, val->ptr); + val = val->next; + } + return QSE_HTB_WALK_FORWARD; +} + +static int process_request ( + qse_httpd_t* httpd, qse_httpd_client_t* client, + qse_htre_t* req, int peek) +{ + int method; + qse_httpd_task_t* task; + int content_received; + + method = qse_htre_getqmethodtype(req); + content_received = (qse_htre_getcontentlen(req) > 0); + + /* percent-decode the query path to the original buffer + * since i'm not gonna need it in the original form + * any more */ + qse_perdechttpstr (qse_htre_getqpath(req), qse_htre_getqpath(req)); + +qse_printf (QSE_T("================================\n")); +qse_printf (QSE_T("[%lu] %hs REQUEST ==> [%hs] version[%d.%d %hs] method[%hs]\n"), + (unsigned long)time(NULL), + (peek? QSE_MT("PEEK"): QSE_MT("HANDLE")), + qse_htre_getqpath(req), + qse_htre_getmajorversion(req), + qse_htre_getminorversion(req), + qse_htre_getverstr(req), + qse_htre_getqmethodname(req) +); +if (qse_htre_getqparam(req)) + qse_printf (QSE_T("PARAMS ==> [%hs]\n"), qse_htre_getqparam(req)); + +qse_htb_walk (&req->hdrtab, walk, QSE_NULL); +if (qse_htre_getcontentlen(req) > 0) +{ + qse_printf (QSE_T("CONTENT before discard = [%.*S]\n"), (int)qse_htre_getcontentlen(req), qse_htre_getcontentptr(req)); +} + + if (peek) + { + if (method != QSE_HTTP_POST && method != QSE_HTTP_PUT) + { + /* i'll discard request contents if the method is none of + * post and put */ + qse_httpd_discardcontent (httpd, req); + } + + if ((req->attr.flags & QSE_HTRE_ATTR_EXPECT100) && + (req->version.major > 1 || + (req->version.major == 1 && req->version.minor >= 1)) && + !content_received) + { +/* TODO: check method.... */ + /* "expect" in the header, version 1.1 or higher, + * and no content received yet */ + + /* TODO: determine if to return 100-continue or other errors */ +{ +qse_ntime_t now; +qse_gettime (&now); +qse_printf (QSE_T("entasking continue at %lld\n"), (long long)now); +} + if (qse_httpd_entaskcontinue ( + httpd, client, QSE_NULL, req) == QSE_NULL) return -1; + } + } + +if (qse_htre_getcontentlen(req) > 0) +{ + qse_printf (QSE_T("CONTENT after discard = [%.*S]\n"), (int)qse_htre_getcontentlen(req), qse_htre_getcontentptr(req)); +} + + if (method == QSE_HTTP_GET || method == QSE_HTTP_POST) + { + const qse_mchar_t* qpath = qse_htre_getqpath(req); + const qse_mchar_t* dot = qse_mbsrchr (qpath, QSE_MT('.')); + + if (dot && qse_mbscmp (dot, QSE_MT(".cgi")) == 0) + { + if (peek) + { + /* cgi */ +#if 0 + if (req->attr.flags & QSE_HTRE_ATTR_CHUNKED) + { +qse_printf (QSE_T("chunked cgi... delaying until contents are received\n")); + #if 0 + req->attr.keepalive = 0; + task = qse_httpd_entaskerror ( + httpd, client, QSE_NULL, 411, req); + /* 411 can't keep alive */ + if (task) qse_httpd_entaskdisconnect (httpd, client, QSE_NULL); + #endif + } + else +#endif + + /*if (method == QSE_HTTP_POST && !(req->attr.flags & QSE_HTRE_ATTR_LENGTH))*/ + if (method == QSE_HTTP_POST && + !(req->attr.flags & QSE_HTRE_ATTR_LENGTH) && + !(req->attr.flags & QSE_HTRE_ATTR_CHUNKED)) + { + req->attr.flags &= ~QSE_HTRE_ATTR_KEEPALIVE; + task = qse_httpd_entaskerror ( + httpd, client, QSE_NULL, 411, req); + /* 411 can't keep alive */ + if (task) qse_httpd_entaskdisconnect (httpd, client, QSE_NULL); + } + else + { + task = qse_httpd_entaskcgi ( + httpd, client, QSE_NULL, qpath, req); + if (task == QSE_NULL) goto oops; + } + } +#if 0 + else + { + /* to support the chunked request, + * i need to wait until it's completed and invoke cgi */ + if (req->attr.flags & QSE_HTRE_ATTR_CHUNKED) + { +qse_printf (QSE_T("Entasking chunked CGI...\n")); + task = qse_httpd_entaskcgi ( + httpd, client, QSE_NULL, qpath, req); + if (task == QSE_NULL) goto oops; + } + } +#endif + return 0; + } + else if (dot && qse_mbscmp (dot, QSE_MT(".nph")) == 0) + { + if (peek) + { + const qse_htre_hdrval_t* auth; + int authorized = 0; + + auth = qse_htre_getheaderval (req, QSE_MT("Authorization")); + if (auth) + { + /* TODO: PERFORM authorization... */ + /* BASE64 decode... */ + while (auth->next) auth = auth->next; + authorized = 1; + } + + if (authorized) + { + /* nph-cgi */ + task = qse_httpd_entasknph ( + httpd, client, QSE_NULL, qpath, req); + } + else + { + task = qse_httpd_entaskauth ( + httpd, client, QSE_NULL, QSE_MT("Secure Area"), req); + } + if (task == QSE_NULL) goto oops; + } + return 0; + } + else + { +#if 0 + if (!peek) + { + /* file or directory */ + task = qse_httpd_entaskfile ( + httpd, client, QSE_NULL, qpath, req); + if (task == QSE_NULL) goto oops; + } +#else + if (peek) + { + qse_httpd_discardcontent (httpd, req); + task = qse_httpd_entaskfile ( + httpd, client, QSE_NULL, qpath, req); + if (task == QSE_NULL) goto oops; + } +#endif + } + } + else + { + if (!peek) + { + task = qse_httpd_entaskerror (httpd, client, QSE_NULL, 405, req); + if (task == QSE_NULL) goto oops; + } + } + + if (!(req->attr.flags & QSE_HTRE_ATTR_KEEPALIVE)) + { + if (!peek) + { + task = qse_httpd_entaskdisconnect (httpd, client, QSE_NULL); + if (task == QSE_NULL) goto oops; + } + } + + return 0; + +oops: + /*qse_httpd_markbadclient (httpd, client);*/ + return -1; +} + +static int proxy_request ( + qse_httpd_t* httpd, qse_httpd_client_t* client, qse_htre_t* req, int peek) +{ + qse_httpd_task_t* task; + +#if 0 + const qse_mchar_t* qpath; + + qpath = qse_htre_qpathptr (eq); + if (qpath[0] == QSE_MT('/')) + { + host = qse_htre_getheaderval (req, QSE_MT("Host")); + if (host == QSE_NULL) + { +qse_printf (QSE_T("Host not included....\n")); + goto oops; + } + } + else + { + const qse_mchar_t* host; + qse_parseuri (); + } +#endif + + +#if 0 + if (peek) + { + if (req->attr.expect && + (req->version.major > 1 || + (req->version.major == 1 && req->version.minor >= 1)) && + !content_received) + { +/* TODO: check method.... */ + /* "expect" in the header, version 1.1 or higher, + * and no content received yet */ + + if (qse_mbscasecmp(req->attr.expect, QSE_MT("100-continue")) != 0) + { + if (qse_httpd_entaskerror ( + httpd, client, QSE_NULL, 417, req) == QSE_NULL) return -1; + if (qse_httpd_entaskdisconnect ( + httpd, client, QSE_NULL) == QSE_NULL) return -1; + } + else + { + /* TODO: determine if to return 100-continue or other errors */ + if (qse_httpd_entaskcontinue ( + httpd, client, QSE_NULL, req) == QSE_NULL) return -1; + } + } + } +#endif + + if (peek) + { + qse_nwad_t nwad; + +#if 0 + if (qse_nwadequal (&client->local_addr, &client->orgdst_addr)) + { + //qse_strtonwad (QSE_T("192.168.1.55:9000"), &nwad); + //qse_strtonwad (QSE_T("1.234.53.142:80"), &nwad); + } + else + { +#endif + nwad = client->orgdst_addr; +#if 0 + } +#endif + task = qse_httpd_entaskproxy (httpd, client, QSE_NULL, &nwad, req); + if (task == QSE_NULL) goto oops; + } + + if (!(req->attr.flags & QSE_HTRE_ATTR_KEEPALIVE)) + { + if (!peek) + { + task = qse_httpd_entaskdisconnect (httpd, client, QSE_NULL); + if (task == QSE_NULL) goto oops; + } + } + + return 0; + +oops: + return -1; +} + +static int peek_request ( + qse_httpd_t* httpd, qse_httpd_client_t* client, qse_htre_t* req) +{ + if (memcmp (&client->local_addr, &client->orgdst_addr, sizeof(client->orgdst_addr)) == 0) + { + return process_request (httpd, client, req, 1); + } + else + { + return proxy_request (httpd, client, req, 1); + } +} + +static int handle_request ( + qse_httpd_t* httpd, qse_httpd_client_t* client, qse_htre_t* req) +{ + if (memcmp (&client->local_addr, &client->orgdst_addr, sizeof(client->orgdst_addr)) == 0) + { + return process_request (httpd, client, req, 0); + } + else + { + return proxy_request (httpd, client, req, 0); + } +} + +int list_directory (qse_httpd_t* httpd, const qse_mchar_t* path) +{ + return 404; +} + +static qse_httpd_cbs_t httpd_standard_callbacks = +{ + /* server */ + { server_open, server_close, server_accept }, + + { peer_open, + peer_close, + peer_connected, + peer_recv, + peer_send }, + + /* multiplexer */ + { mux_open, + mux_close, + mux_addhnd, + mux_delhnd, + mux_poll, + + mux_readable, + mux_writable + }, + + /* file operation */ + { file_executable, + file_stat, + file_ropen, + file_wopen, + file_close, + file_read, + file_write + }, + + /* client connection */ + { client_close, + client_shutdown, + client_recv, + client_send, + client_sendfile, + client_accepted, + client_closed }, + + /* http request */ + peek_request, + handle_request, + + list_directory +}; + +int qse_httpd_loopstd (qse_httpd_t* httpd, qse_ntime_t timeout) +{ + return qse_httpd_loop (httpd, &httpd_standard_callbacks, timeout); +} diff --git a/qse/lib/net/httpd.c b/qse/lib/net/httpd.c index c3312fff..682df6ae 100644 --- a/qse/lib/net/httpd.c +++ b/qse/lib/net/httpd.c @@ -84,6 +84,11 @@ qse_httpd_t* qse_httpd_open (qse_mmgr_t* mmgr, qse_size_t xtnsize) 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); } @@ -128,6 +133,23 @@ void qse_httpd_setoption (qse_httpd_t* httpd, int option) httpd->option = option; } +/* --------------------------------------------------- */ + +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); diff --git a/qse/lib/net/httpd.h b/qse/lib/net/httpd.h index 9f516769..a8264f2c 100644 --- a/qse/lib/net/httpd.h +++ b/qse/lib/net/httpd.h @@ -29,7 +29,8 @@ struct qse_httpd_t { QSE_DEFINE_COMMON_FIELDS (httpd) qse_httpd_errnum_t errnum; - qse_httpd_cbs_t* cbs; + qse_httpd_ecb_t* ecb; /* event callbacks */ + qse_httpd_cbs_t* cbs; int option; int stopreq; diff --git a/qse/lib/sed/sed.c b/qse/lib/sed/sed.c index e75566fa..5781049c 100644 --- a/qse/lib/sed/sed.c +++ b/qse/lib/sed/sed.c @@ -75,6 +75,11 @@ qse_sed_t* qse_sed_open (qse_mmgr_t* mmgr, qse_size_t xtnsize) void qse_sed_close (qse_sed_t* sed) { + qse_sed_ecb_t* ecb; + + for (ecb = sed->ecb; ecb; ecb = ecb->next) + if (ecb->close) ecb->close (sed); + qse_sed_fini (sed); QSE_MMGR_FREE (sed->mmgr, sed); } @@ -4142,6 +4147,19 @@ void qse_sed_setlinenum (qse_sed_t* sed, qse_size_t num) sed->e.in.num = num; } +qse_sed_ecb_t* qse_sed_popecb (qse_sed_t* sed) +{ + qse_sed_ecb_t* top = sed->ecb; + if (top) sed->ecb = top->next; + return top; +} + +void qse_sed_pushecb (qse_sed_t* sed, qse_sed_ecb_t* ecb) +{ + ecb->next = sed->ecb; + sed->ecb = ecb; +} + #ifdef QSE_ENABLE_SEDTRACER qse_sed_exec_tracer_t qse_sed_getexectracer (qse_sed_t* sed) { diff --git a/qse/lib/sed/sed.h b/qse/lib/sed/sed.h index 3eb7e31d..8012f1d3 100644 --- a/qse/lib/sed/sed.h +++ b/qse/lib/sed/sed.h @@ -99,6 +99,8 @@ struct qse_sed_t } rex; } depth; + qse_sed_ecb_t* ecb; + /** source text pointers */ struct {