diff --git a/qse/include/qse/cmn/env.h b/qse/include/qse/cmn/env.h index 44a1e8e9..c2160373 100644 --- a/qse/include/qse/cmn/env.h +++ b/qse/include/qse/cmn/env.h @@ -106,46 +106,41 @@ qse_env_char_t** qse_env_getarr ( qse_env_t* env ); -int qse_env_insertw ( - qse_env_t* env, +/** + * The qse_env_insertwcs() function adds a new environment variable + * @a name with the @a value. If the @a value is #QSE_NULL, it takes + * the actual value from the system environment + * + * @return 0 on success, -1 on failure + */ +int qse_env_insertwcs ( + qse_env_t* env, const qse_wchar_t* name, const qse_wchar_t* value ); -int qse_env_insertm ( - qse_env_t* env, +int qse_env_insertmbs ( + qse_env_t* env, const qse_mchar_t* name, const qse_mchar_t* value ); -int qse_env_deletew ( - qse_env_t* env, +int qse_env_deletewcs ( + qse_env_t* env, const qse_wchar_t* name ); -int qse_env_deletem ( - qse_env_t* env, - const qse_mchar_t* name -); - -int qse_env_insertsysw ( - qse_env_t* env, - const qse_wchar_t* name -); - -int qse_env_insertsysm ( - qse_env_t* env, +int qse_env_deletembs ( + qse_env_t* env, const qse_mchar_t* name ); #if defined(QSE_CHAR_IS_WCHAR) -# define qse_env_insert(env,name,value) qse_env_insertw(env,name,value) -# define qse_env_delete(env,name) qse_env_deletew(env,name) -# define qse_env_insertsys(env,name) qse_env_insertsysw(env,name) +# define qse_env_insert(env,name,value) qse_env_insertwcs(env,name,value) +# define qse_env_delete(env,name) qse_env_deletewcs(env,name) #else -# define qse_env_insert(env,name,value) qse_env_insertm(env,name,value) -# define qse_env_delete(env,name) qse_env_deletem(env,name) -# define qse_env_insertsys(env,name) qse_env_insertsysm(env,name) +# define qse_env_insert(env,name,value) qse_env_insertmbs(env,name,value) +# define qse_env_delete(env,name) qse_env_deletembs(env,name) #endif #ifdef __cplusplus diff --git a/qse/include/qse/net/htrd.h b/qse/include/qse/net/htrd.h index 67fc8c02..ff510bf2 100644 --- a/qse/include/qse/net/htrd.h +++ b/qse/include/qse/net/htrd.h @@ -32,7 +32,8 @@ enum qse_htrd_errnum_t QSE_HTRD_ENOMEM, QSE_HTRD_EBADRE, QSE_HTRD_EBADHDR, - QSE_HTRD_ERECBS + QSE_HTRD_ERECBS, + QSE_HTRD_ECONCB }; typedef enum qse_htrd_errnum_t qse_htrd_errnum_t; @@ -45,7 +46,7 @@ enum qse_htrd_option_t { QSE_HTRD_SKIPEMPTYLINES = (1 << 0), /**< skip leading empty lines before the initial line */ QSE_HTRD_SKIPINITIALLINE = (1 << 1), /**< skip processing an initial line */ - QSE_HTRD_HURRIED = (1 << 2), /**< trigger a callback also after headers without processing contents */ + QSE_HTRD_PEEKONLY = (1 << 2), /**< trigger a peek callback after headers without processing contents */ QSE_HTRD_REQUEST = (1 << 3), /**< parse input as a request */ QSE_HTRD_RESPONSE = (1 << 4) /**< parse input as a response */ }; @@ -56,9 +57,8 @@ typedef struct qse_htrd_recbs_t qse_htrd_recbs_t; struct qse_htrd_recbs_t { - int (*request) (qse_htrd_t* htrd, qse_htre_t* req); - int (*expect_continue) (qse_htrd_t* htrd, qse_htre_t* req); - int (*response) (qse_htrd_t* htrd, qse_htre_t* res); + int (*peek) (qse_htrd_t* htrd, qse_htre_t* re); + int (*handle) (qse_htrd_t* htrd, qse_htre_t* re); }; struct qse_htrd_t diff --git a/qse/include/qse/net/htre.h b/qse/include/qse/net/htre.h index fc9c3ad9..6a2ce277 100644 --- a/qse/include/qse/net/htre.h +++ b/qse/include/qse/net/htre.h @@ -27,6 +27,20 @@ /* header and contents of request/response */ typedef struct qse_htre_t qse_htre_t; + +enum qse_htre_flag_t +{ + QSE_HTRE_DISCARDED = (1 << 0), /** content has been discarded */ + QSE_HTRE_COMPLETED = (1 << 1) /** complete content has been seen */ +}; + +typedef int (*qse_htre_concb_t) ( + qse_htre_t* re, + const qse_mchar_t* ptr, + qse_size_t len, + void* ctx +); + struct qse_htre_t { qse_mmgr_t* mmgr; @@ -45,10 +59,7 @@ struct qse_htre_t int content_length_set; qse_size_t content_length; int keepalive; - int expect_continue; - - /* indicates if the content has been filled */ - int hurried; + const qse_mchar_t* expect; } attr; /* header table */ @@ -57,8 +68,12 @@ struct qse_htre_t /* content octets */ qse_mbs_t content; - /* if set, the rest of the contents are discarded */ - int discard; + /* content callback */ + qse_htre_concb_t concb; + void* concb_ctx; + + /* ORed of qse_htre_flag_t */ + int flags; }; #define qse_htre_getversion(re) (&((re)->version)) @@ -111,13 +126,12 @@ struct qse_htre_t #define qse_htre_setsmessagefromxstr(re,v) \ qse_htre_setstrfromxstr((re),qse_htre_getsmessage(re),(v)) +/* NOTE: setcontent() doesn't execute concb. use this with care */ #define qse_htre_setcontentfromcstr(re,v) \ qse_htre_setstrfromcstr((re),qse_htre_getcontent(re),(v)) #define qse_htre_setcontentfromxstr(re,v) \ qse_htre_setstrfromxstr((re),qse_htre_getcontent(re),(v)) -#define qse_htre_setdiscard(re,v) QSE_BLOCK((re)->discard = (v);) - typedef int (*qse_htre_header_walker_t) ( qse_htre_t* re, const qse_mchar_t* key, @@ -165,6 +179,26 @@ int qse_htre_walkheaders ( void* ctx ); +int qse_htre_addcontent ( + qse_htre_t* re, + const qse_mchar_t* ptr, + qse_size_t len +); + +void qse_htre_unsetconcb ( + qse_htre_t* re +); + +void qse_htre_setconcb ( + qse_htre_t* re, + qse_htre_concb_t concb, + void* ctx +); + +const qse_mchar_t* qse_htre_getqmethodname ( + qse_htre_t* re +); + #ifdef __cplusplus } #endif diff --git a/qse/include/qse/net/httpd.h b/qse/include/qse/net/httpd.h index 9a8b0f3e..f6ee8108 100644 --- a/qse/include/qse/net/httpd.h +++ b/qse/include/qse/net/httpd.h @@ -51,9 +51,33 @@ enum qse_httpd_option_t typedef struct qse_httpd_cbs_t qse_httpd_cbs_t; struct qse_httpd_cbs_t { - int (*handle_request) ( + struct + { + /* action */ + int (*recv) (qse_httpd_t* httpd, + qse_httpd_client_t* client, + qse_mchar_t* buf, qse_size_t bufsize); + + int (*send) (qse_httpd_t* httpd, + qse_httpd_client_t* client, + const qse_mchar_t* buf, qse_size_t bufsize); + + int (*sendfile) (qse_httpd_t* httpd, + qse_httpd_client_t* client, + qse_ubi_t handle, qse_foff_t* offset, qse_size_t count); + + /* event notification */ + int (*accepted) ( + qse_httpd_t* httpd, + qse_httpd_client_t* client); /* optional */ + void (*closed) ( + qse_httpd_t* httpd, + qse_httpd_client_t* client); /* optional */ + } client; + + int (*peek_request) ( qse_httpd_t* httpd, qse_httpd_client_t* client, qse_htre_t* req); - int (*handle_expect_continue) ( + int (*handle_request) ( qse_httpd_t* httpd, qse_httpd_client_t* client, qse_htre_t* req); const qse_mchar_t* (*getmimetype) (qse_httpd_t* httpd, const qse_mchar_t* path); @@ -80,6 +104,15 @@ typedef int (*qse_httpd_task_main_t) ( qse_httpd_task_t* task ); + +enum qse_httpd_task_trigger_mask_t +{ + QSE_HTTPD_TASK_TRIGGER_READ = (1 << 0), + QSE_HTTPD_TASK_TRIGGER_WRITE = (1 << 1), + QSE_HTTPD_TASK_TRIGGER_READABLE = (1 << 2), + QSE_HTTPD_TASK_TRIGGER_WRITABLE = (1 << 3) +}; + struct qse_httpd_task_t { /* you must not call another entask functions from within @@ -89,7 +122,8 @@ struct qse_httpd_task_t qse_httpd_task_fini_t fini; qse_httpd_task_main_t main; - qse_ubi_t trigger; + int trigger_mask; + qse_ubi_t trigger[2]; void* ctx; }; @@ -143,8 +177,9 @@ void qse_httpd_setcbs ( * specify the number of output threads. */ int qse_httpd_loop ( - qse_httpd_t* httpd, - int threaded + qse_httpd_t* httpd, + qse_httpd_cbs_t* cbs, + int threaded ); /** @@ -161,11 +196,16 @@ int qse_httpd_addlistener ( ); -void qse_httpd_markclientbad ( +void qse_httpd_markbadclient ( qse_httpd_t* httpd, qse_httpd_client_t* client ); +void qse_httpd_discardcontent ( + qse_httpd_t* httpd, + qse_htre_t* req +); + #define qse_httpd_gettaskxtn(httpd,task) ((void*)(task+1)) qse_httpd_task_t* qse_httpd_entask ( @@ -235,6 +275,13 @@ qse_httpd_task_t* qse_httpd_entaskerror ( const qse_htre_t* req ); +qse_httpd_task_t* qse_httpd_entaskcontinue ( + qse_httpd_t* httpd, + qse_httpd_client_t* client, + const qse_httpd_task_t* task, + const qse_htre_t* req +); + qse_httpd_task_t* qse_httpd_entaskpath ( qse_httpd_t* httpd, qse_httpd_client_t* client, diff --git a/qse/lib/cmn/env.c b/qse/lib/cmn/env.c index 20e0fb2e..d8da553c 100644 --- a/qse/lib/cmn/env.c +++ b/qse/lib/cmn/env.c @@ -34,6 +34,8 @@ QSE_IMPLEMENT_COMMON_FUNCTIONS(env) static int load_curenv (qse_env_t* env); +static int insert_sys_wcs (qse_env_t* env, const qse_wchar_t* name); +static int insert_sys_mbs (qse_env_t* env, const qse_mchar_t* name); qse_env_t* qse_env_open (qse_mmgr_t* mmgr, qse_size_t xtnsize, int fromcurenv) { @@ -314,10 +316,9 @@ static int deletem (qse_env_t* env, const qse_mchar_t* name) return -1; } - #endif -int qse_env_insertw ( +static QSE_INLINE int insert_wcs ( qse_env_t* env, const qse_wchar_t* name, const qse_wchar_t* value) { #if defined(QSE_ENV_CHAR_IS_WCHAR) @@ -328,9 +329,9 @@ int qse_env_insertw ( qse_mchar_t* namedup, * valuedup; int n; - namedup = qse_wcstombsdup (name, env->mmgr); + namedup = qse_wcstombsdup (name, env->mmgr); /* TODO: ignore mbwcerr */ if (namedup == QSE_NULL) return -1; - valuedup = qse_wcstombsdup (value, env->mmgr); + valuedup = qse_wcstombsdup (value, env->mmgr); /* TODO: ignore mbwcerr */ if (valuedup == QSE_NULL) { QSE_MMGR_FREE (env->mmgr, namedup); @@ -344,7 +345,7 @@ int qse_env_insertw ( #endif } -int qse_env_insertm ( +static QSE_INLINE int insert_mbs ( qse_env_t* env, const qse_mchar_t* name, const qse_mchar_t* value) { #if defined(QSE_ENV_CHAR_IS_WCHAR) @@ -352,9 +353,9 @@ int qse_env_insertm ( qse_wchar_t* namedup, * valuedup; int n; - namedup = qse_mbstowcsdup (name, env->mmgr); /* TODO: ignroe mbwcerr */ + namedup = qse_mbstowcsalldup (name, env->mmgr); if (namedup == QSE_NULL) return -1; - valuedup = qse_mbstowcsdup (value, env->mmgr); /* TODO: ignroe mbwcerr */ + valuedup = qse_mbstowcsalldup (value, env->mmgr); if (valuedup == QSE_NULL) { QSE_MMGR_FREE (env->mmgr, namedup); @@ -372,44 +373,6 @@ int qse_env_insertm ( } -int qse_env_deletew (qse_env_t* env, const qse_wchar_t* name) -{ -#if defined(QSE_ENV_CHAR_IS_WCHAR) - return deletew (env, name); -#else - /* convert wchar to mchar */ - qse_mchar_t* namedup; - int n; - - namedup = qse_wcstombsdup (name, env->mmgr); - if (namedup == QSE_NULL) return -1; - - n = deletem (env, namedup); - - QSE_MMGR_FREE (env->mmgr, namedup); - return n; -#endif -} - -int qse_env_deletem (qse_env_t* env, const qse_mchar_t* name) -{ -#if defined(QSE_ENV_CHAR_IS_WCHAR) - /* convert mchar to wchar */ - qse_wchar_t* namedup; - int n; - - namedup = qse_mbstowcsdup (name, env->mmgr); /* TODO: ignroe mbwcerr */ - if (namedup == QSE_NULL) return -1; - - n = deletew (env, namedup); - - QSE_MMGR_FREE (env->mmgr, namedup); - return n; -#else - return deletem (env, name); -#endif -} - #if defined(_WIN32) static qse_char_t* get_env (qse_env_t* env, const qse_char_t* name, int* free) { @@ -507,7 +470,7 @@ static qse_mchar_t* get_env (qse_env_t* env, const qse_mchar_t* name, int* free) } #endif -int qse_env_insertsysw (qse_env_t* env, const qse_wchar_t* name) +static int insert_sys_wcs (qse_env_t* env, const qse_wchar_t* name) { #if defined(QSE_ENV_CHAR_IS_WCHAR) qse_wchar_t* v; @@ -526,10 +489,10 @@ int qse_env_insertsysw (qse_env_t* env, const qse_wchar_t* name) qse_mchar_t* namedup; int ret = -1; - namedup = qse_wcstombsdup (name, env->mmgr); + namedup = qse_wcstombsdup (name, env->mmgr); /* TODO: ignore mbwcerr */ if (namedup) { - ret = qse_env_insertsysm (env, namedup); + ret = insert_sys_mbs (env, namedup); QSE_MMGR_FREE (env->mmgr, namedup); } @@ -537,7 +500,7 @@ int qse_env_insertsysw (qse_env_t* env, const qse_wchar_t* name) #endif } -int qse_env_insertsysm (qse_env_t* env, const qse_mchar_t* name) +static insert_sys_mbs (qse_env_t* env, const qse_mchar_t* name) { #if defined(QSE_ENV_CHAR_IS_WCHAR) /* convert mchar to wchar */ @@ -547,7 +510,7 @@ int qse_env_insertsysm (qse_env_t* env, const qse_mchar_t* name) namedup = qse_mbstowcsdup (name, env->mmgr); /* TODO: ignroe mbwcerr */ if (namedup) { - ret = qse_env_insertsysw (env, namedup); + ret = insert_sys_wcs (env, namedup); QSE_MMGR_FREE (env->mmgr, namedup); } @@ -653,3 +616,56 @@ done: #endif } +/* ------------------------------------------------------------------- */ + +int qse_env_insertwcs ( + qse_env_t* env, const qse_wchar_t* name, const qse_wchar_t* value) +{ + return value? insert_wcs (env, name, value): insert_sys_wcs (env, name); +} + +int qse_env_insertmbs ( + qse_env_t* env, const qse_mchar_t* name, const qse_mchar_t* value) +{ + return value? insert_mbs (env, name, value): insert_sys_mbs (env, name); +} + + +int qse_env_deletewcs (qse_env_t* env, const qse_wchar_t* name) +{ +#if defined(QSE_ENV_CHAR_IS_WCHAR) + return deletew (env, name); +#else + /* convert wchar to mchar */ + qse_mchar_t* namedup; + int n; + + namedup = qse_wcstombsdup (name, env->mmgr); /* TODO: ignore mbwcerr */ + if (namedup == QSE_NULL) return -1; + + n = deletem (env, namedup); + + QSE_MMGR_FREE (env->mmgr, namedup); + return n; +#endif +} + +int qse_env_deletembs (qse_env_t* env, const qse_mchar_t* name) +{ +#if defined(QSE_ENV_CHAR_IS_WCHAR) + /* convert mchar to wchar */ + qse_wchar_t* namedup; + int n; + + namedup = qse_mbstowcsalldup (name, env->mmgr); + if (namedup == QSE_NULL) return -1; + + n = deletew (env, namedup); + + QSE_MMGR_FREE (env->mmgr, namedup); + return n; +#else + return deletem (env, name); +#endif +} + diff --git a/qse/lib/net/htrd.c b/qse/lib/net/htrd.c index af830d25..54d7e5fd 100644 --- a/qse/lib/net/htrd.c +++ b/qse/lib/net/htrd.c @@ -93,6 +93,17 @@ static QSE_INLINE int push_to_buffer ( return 0; } +static QSE_INLINE int push_content ( + qse_htrd_t* htrd, const qse_mchar_t* ptr, qse_size_t len) +{ + if (qse_htre_addcontent (&htrd->re, ptr, len) <= -1) + { + htrd->errnum = QSE_HTRD_ENOMEM; + return -1; + } + return 0; +} + struct hdr_cmb_t { struct hdr_cmb_t* next; @@ -515,18 +526,7 @@ static int capture_content_length (qse_htrd_t* htrd, qse_htb_pair_t* pair) static int capture_expect (qse_htrd_t* htrd, qse_htb_pair_t* pair) { - int n; - - n = qse_mbsxncasecmp ( - QSE_HTB_VPTR(pair), QSE_HTB_VLEN(pair), "100-continue", 12); - if (n == 0) - { - - htrd->re.attr.expect_continue = 1; - return 0; - } - - /* don't care about other values */ + htrd->re.attr.expect = QSE_HTB_VPTR(pair); return 0; } @@ -998,6 +998,8 @@ int qse_htrd_feed (qse_htrd_t* htrd, const qse_mchar_t* req, qse_size_t len) { const qse_mchar_t* end = req + len; const qse_mchar_t* ptr = req; + int header_completed_during_this_feed = 0; + qse_size_t avail; /* does this goto drop code maintainability? */ if (htrd->fed.s.need > 0) @@ -1067,7 +1069,7 @@ int qse_htrd_feed (qse_htrd_t* htrd, const qse_mchar_t* req, qse_size_t len) * => 2nd CR before LF */ - /* we got a complete request. */ + /* we got a complete request header. */ QSE_ASSERT (htrd->fed.s.crlf <= 3); /* reset the crlf state */ @@ -1078,86 +1080,41 @@ int qse_htrd_feed (qse_htrd_t* htrd, const qse_mchar_t* req, qse_size_t len) if (parse_initial_line_and_headers (htrd, req, ptr - req) <= -1) return -1; - if (htrd->option & QSE_HTRD_HURRIED) + /* compelete request header is received */ + header_completed_during_this_feed = 1; + if (htrd->option & QSE_HTRD_PEEKONLY) { - int n; - - /* it pushes any trailing data into the content in this mode. - * so the handler knows if there is contents fed to this reader. */ - if (push_to_buffer (htrd, &htrd->re.content, ptr, end - ptr) <= -1) - return -1; - - - htrd->re.attr.hurried = 1; - htrd->errnum = QSE_HTRD_ENOERR; - if (htrd->retype == QSE_HTRD_RETYPE_S) - { - QSE_ASSERTX ( - htrd->recbs->response != QSE_NULL, - "set response callback before feeding" - ); - n = htrd->recbs->response (htrd, &htrd->re); - } - else - { - QSE_ASSERTX ( - htrd->recbs->request != QSE_NULL, - "set request callback before feeding" - ); - n = htrd->recbs->request (htrd, &htrd->re); - } - - /* qse_mbs_clear (&htrd->re.content); */ - - if (n <= -1) - { - if (htrd->errnum == QSE_HTRD_ENOERR) - htrd->errnum = QSE_HTRD_ERECBS; - - /* need to clear request on error? - clear_feed (htrd); */ - return -1; - } - - /* if QSE_HTRD_HURRIED is set, we do not handle expect_continue */ - /* if QSE_HTRD_HURRIED is set, we handle a single request only */ - - return 0; - } - - if (htrd->retype == QSE_HTRD_RETYPE_Q && - htrd->re.attr.expect_continue && - htrd->recbs->expect_continue && ptr >= end) - { - int n; - - /* the 'ptr >= end' check is for not executing the - * callback if the message body has been seen already. - * however, this is not perfect as it is based on - * the current feed. what if it has been received but - * not fed here? - */ - - htrd->re.attr.hurried = 0; - htrd->errnum = QSE_HTRD_ENOERR; - n = htrd->recbs->expect_continue (htrd, &htrd->re); - - if (n <= -1) - { - if (htrd->errnum == QSE_HTRD_ENOERR) - htrd->errnum = QSE_HTRD_ERECBS; - - /* need to clear request on error? - clear_feed (htrd); */ - return -1; - } - - /* the expect_continue handler can set discard to non-zero - * to force discard the contents body - htrd->re.discard = 1; + /* when QSE_HTRD_PEEKONCE is set, + * the peek callback is invoked once + * a complete header is seen. the caller + * should not feed more data by calling + * this function again once the callback is + * invoked. the trailing data is appended + * to the content buffer. + * + * NOTE: if the current feed that completed + * the header contains the next request, + * the next request is treated as if it + * belongs to the current request. + * + * In priciple, this option was added for + * reading CGI outputs. So it comes with + * awkwardity described above. */ + if (!(htrd->re.flags & QSE_HTRE_DISCARDED) && + push_content (htrd, ptr, end - ptr) <= -1) return -1; + /* this jump is only to invoke the peek + * callback. this function should not be fed + * more. */ + + /* i don't really know if it is really completed + * with content. QSE_HTRD_PEEKONLY is not compatible + * with the completed flag. */ + htrd->re.flags &= QSE_HTRE_COMPLETED; + goto feedme_more; } - + + /* carry on processing content body fed together with the header */ if (htrd->re.attr.chunked) { /* transfer-encoding: chunked */ @@ -1203,16 +1160,14 @@ int qse_htrd_feed (qse_htrd_t* htrd, const qse_mchar_t* req, qse_size_t len) { /* content-length or chunked data length * specified */ - - qse_size_t avail; - content_resume: avail = end - ptr; if (avail < htrd->fed.s.need) { /* the data is not as large as needed */ - if (push_to_buffer (htrd, &htrd->re.content, ptr, avail) <= -1) return -1; + if (!(htrd->re.flags & QSE_HTRE_DISCARDED) && + push_content (htrd, ptr, avail) <= -1) return -1; htrd->fed.s.need -= avail; /* we didn't get a complete content yet */ goto feedme_more; @@ -1220,9 +1175,8 @@ int qse_htrd_feed (qse_htrd_t* htrd, const qse_mchar_t* req, qse_size_t len) else { /* we got all or more than needed */ - if (push_to_buffer ( - htrd, &htrd->re.content, ptr, - htrd->fed.s.need) <= -1) return -1; + if (!(htrd->re.flags & QSE_HTRE_DISCARDED) && + push_content (htrd, ptr, htrd->fed.s.need) <= -1) return -1; ptr += htrd->fed.s.need; htrd->fed.s.need = 0; } @@ -1272,43 +1226,51 @@ int qse_htrd_feed (qse_htrd_t* htrd, const qse_mchar_t* req, qse_size_t len) } } - if (!htrd->re.discard) + if (header_completed_during_this_feed && htrd->recbs->peek) { + /* the peek handler has not been executed. + * this can happen if this function is fed with + * at least the ending part of a complete header + * plus complete content body and the header + * of the next request. */ int n; - - htrd->re.attr.hurried = 0; - htrd->errnum = QSE_HTRD_ENOERR; - - if (htrd->retype == QSE_HTRD_RETYPE_S) - { - QSE_ASSERTX ( - htrd->recbs->response != QSE_NULL, - "set response callback before feeding" - ); - - n = htrd->recbs->response (htrd, &htrd->re); - } - else - { - QSE_ASSERTX ( - htrd->recbs->request != QSE_NULL, - "set request callback before feeding" - ); - - n = htrd->recbs->request (htrd, &htrd->re); - } - + htrd->re.flags |= QSE_HTRE_COMPLETED; + htrd->errnum = QSE_HTRD_ENOERR; + n = htrd->recbs->peek (htrd, &htrd->re); + if (n <= -1) + { + if (htrd->errnum == QSE_HTRD_ENOERR) + htrd->errnum = QSE_HTRD_ERECBS; + /* need to clear request on error? + clear_feed (htrd); */ + return -1; + } + + header_completed_during_this_feed = 0; + } + + if (htrd->recbs->handle) + { + int n; + htrd->re.flags |= QSE_HTRE_COMPLETED; + htrd->errnum = QSE_HTRD_ENOERR; + n = htrd->recbs->handle (htrd, &htrd->re); if (n <= -1) { if (htrd->errnum == QSE_HTRD_ENOERR) htrd->errnum = QSE_HTRD_ERECBS; - /* need to clear request on error? clear_feed (htrd); */ return -1; } } +#if 0 +qse_printf (QSE_T("CONTENT_LENGTH %d, RAW HEADER LENGTH %d\n"), + (int)QSE_MBS_LEN(&htrd->re.content), + (int)QSE_MBS_LEN(&htrd->fed.b.raw)); +#endif + clear_feed (htrd); /* let ptr point to the next character to LF or @@ -1340,6 +1302,21 @@ int qse_htrd_feed (qse_htrd_t* htrd, const qse_mchar_t* req, qse_size_t len) } feedme_more: + if (header_completed_during_this_feed && htrd->recbs->peek) + { + int n; + htrd->errnum = QSE_HTRD_ENOERR; + n = htrd->recbs->peek (htrd, &htrd->re); + if (n <= -1) + { + if (htrd->errnum == QSE_HTRD_ENOERR) + htrd->errnum = QSE_HTRD_ERECBS; + /* need to clear request on error? + clear_feed (htrd); */ + return -1; + } + } + return 0; } diff --git a/qse/lib/net/htre.c b/qse/lib/net/htre.c index 518025f6..fdadc5d1 100644 --- a/qse/lib/net/htre.c +++ b/qse/lib/net/htre.c @@ -45,6 +45,12 @@ void qse_htre_fini (qse_htre_t* re) void qse_htre_clear (qse_htre_t* re) { + if (re->concb) + { + re->concb (re, QSE_NULL, 0, re->concb_ctx); /* indicate end of content */ + qse_htre_unsetconcb (re); + } + QSE_MEMSET (&re->version, 0, QSE_SIZEOF(re->version)); QSE_MEMSET (&re->attr, 0, QSE_SIZEOF(re->attr)); @@ -53,8 +59,7 @@ void qse_htre_clear (qse_htre_t* re) qse_mbs_clear (&re->content); qse_mbs_clear (&re->qpath_or_smesg); qse_mbs_clear (&re->qparam); - - re->discard = 0; + re->flags = 0; } int qse_htre_setstrfromcstr ( @@ -109,3 +114,29 @@ int qse_htre_walkheaders ( return hwctx.ret; } +int qse_htre_addcontent ( + qse_htre_t* re, const qse_mchar_t* ptr, qse_size_t len) +{ + /* if the callback is set, the content goes to the callback. */ + if (re->concb) return re->concb (re, ptr, len, re->concb_ctx); + /* if the callback is not set, the contents goes to the internal buffer */ + if (qse_mbs_ncat (&re->content, ptr, len) == (qse_size_t)-1) return -1; + return 0; +} + +void qse_htre_unsetconcb (qse_htre_t* re) +{ + re->concb = QSE_NULL; + re->concb_ctx = QSE_NULL; +} + +void qse_htre_setconcb (qse_htre_t* re, qse_htre_concb_t concb, void* ctx) +{ + re->concb = concb; + re->concb_ctx = ctx; +} + +const qse_mchar_t* qse_htre_getqmethodname (qse_htre_t* re) +{ + return qse_gethttpmethodname (re->qmethod_or_sstatus); +} diff --git a/qse/lib/net/httpd.c b/qse/lib/net/httpd.c index 562367ff..cfd5b739 100644 --- a/qse/lib/net/httpd.c +++ b/qse/lib/net/httpd.c @@ -296,27 +296,26 @@ static void purge_tasks_locked (qse_httpd_t* httpd, qse_httpd_client_t* client) #endif } +static int htrd_peek_request (qse_htrd_t* htrd, qse_htre_t* req) +{ + htrd_xtn_t* xtn = (htrd_xtn_t*) qse_htrd_getxtn (htrd); + qse_httpd_client_t* client = + &xtn->httpd->client.array.data[xtn->client_index]; + return xtn->httpd->cbs->peek_request (xtn->httpd, client, req); +} + static int htrd_handle_request (qse_htrd_t* htrd, qse_htre_t* req) { htrd_xtn_t* xtn = (htrd_xtn_t*) qse_htrd_getxtn (htrd); - qse_httpd_client_t* client = &xtn->httpd->client.array.data[xtn->client_index]; + qse_httpd_client_t* client = + &xtn->httpd->client.array.data[xtn->client_index]; return xtn->httpd->cbs->handle_request (xtn->httpd, client, req); } -static int htrd_handle_expect_continue (qse_htrd_t* htrd, qse_htre_t* req) -{ - htrd_xtn_t* xtn = (htrd_xtn_t*) qse_htrd_getxtn (htrd); - qse_httpd_client_t* client = &xtn->httpd->client.array.data[xtn->client_index]; - return xtn->httpd->cbs->handle_expect_continue (xtn->httpd, client, req); -} - static qse_htrd_recbs_t htrd_recbs = { - htrd_handle_request, - htrd_handle_expect_continue, - - /* The response handler is not needed as QSE_HTRD_RESPONSE is truned off */ - QSE_NULL + htrd_peek_request, + htrd_handle_request }; static void deactivate_listener (qse_httpd_t* httpd, listener_t* l) @@ -329,41 +328,31 @@ static void deactivate_listener (qse_httpd_t* httpd, listener_t* l) l->handle = -1; } -static int activate_listener (qse_httpd_t* httpd, listener_t* l) +static int get_listener_sockaddr (const listener_t* l, sockaddr_t* addr) { -/* TODO: suport https... */ - sockaddr_t addr; - int s = -1, flag; int addrsize; - QSE_ASSERT (l->handle <= -1); + QSE_MEMSET (addr, 0, QSE_SIZEOF(*addr)); - s = socket (l->family, SOCK_STREAM, IPPROTO_TCP); - if (s <= -1) goto oops_esocket; - - flag = 1; - setsockopt (s, SOL_SOCKET, SO_REUSEADDR, &flag, QSE_SIZEOF(flag)); - - QSE_MEMSET (&addr, 0, QSE_SIZEOF(addr)); switch (l->family) { case AF_INET: { - addr.in4.sin_family = l->family; - addr.in4.sin_addr = l->addr.in4; - addr.in4.sin_port = htons (l->port); - addrsize = QSE_SIZEOF(addr.in4); + addr->in4.sin_family = l->family; + addr->in4.sin_addr = l->addr.in4; + addr->in4.sin_port = htons (l->port); + addrsize = QSE_SIZEOF(addr->in4); break; } #ifdef AF_INET6 case AF_INET6: { - addr.in6.sin6_family = l->family; - addr.in6.sin6_addr = l->addr.in6; - addr.in6.sin6_port = htons (l->port); + addr->in6.sin6_family = l->family; + addr->in6.sin6_addr = l->addr.in6; + addr->in6.sin6_port = htons (l->port); /* TODO: addr.in6.sin6_scope_id */ - addrsize = QSE_SIZEOF(addr.in6); + addrsize = QSE_SIZEOF(addr->in6); break; } #endif @@ -374,6 +363,26 @@ static int activate_listener (qse_httpd_t* httpd, listener_t* l) } } + return addrsize; +} + +static int activate_listener (qse_httpd_t* httpd, listener_t* l) +{ +/* TODO: suport https... */ + int s = -1, flag; + sockaddr_t addr; + int addrsize; + + QSE_ASSERT (l->handle <= -1); + + s = socket (l->family, SOCK_STREAM, IPPROTO_TCP); + if (s <= -1) goto oops_esocket; + + flag = 1; + setsockopt (s, SOL_SOCKET, SO_REUSEADDR, &flag, QSE_SIZEOF(flag)); + + addrsize = get_listener_sockaddr (l, &addr); + /* 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;*/ @@ -384,18 +393,6 @@ static int activate_listener (qse_httpd_t* httpd, listener_t* l) if (flag >= 0) fcntl (s, F_SETFL, flag | O_NONBLOCK); fcntl (s, F_SETFD, FD_CLOEXEC); -#if 0 -/* TODO: */ - if (l->secure) - { - SSL_CTX* ctx; - SSL* ssl; - - ctx = SSL_ctx_new (SSLv3_method()); - ssl = SSL_new (ctx); - } -#endif - l->handle = s; s = -1; @@ -470,6 +467,12 @@ static void delete_from_client_array (qse_httpd_t* httpd, int fd) qse_htrd_close (array->data[fd].htrd); array->data[fd].htrd = QSE_NULL; qse_fprintf (QSE_STDERR, QSE_T("Debug: closing socket %d\n"), array->data[fd].handle.i); + + /* note that client.closed is not a counterpart to client.accepted. + * so it is called even if client.closed failed. */ + if (httpd->cbs->client.closed) + httpd->cbs->client.closed (httpd, &array->data[fd]); + close (array->data[fd].handle.i); array->size--; } @@ -492,19 +495,23 @@ static void fini_client_array (qse_httpd_t* httpd) } } -static qse_httpd_client_t* insert_into_client_array (qse_httpd_t* httpd, int fd, sockaddr_t* addr) +static qse_httpd_client_t* insert_into_client_array ( + qse_httpd_t* httpd, qse_httpd_client_t* client) { htrd_xtn_t* xtn; client_array_t* array = &httpd->client.array; - int opt; + int opt, fd = client->handle.i; +/* TODO: is an array is the best??? + * i do use an array for direct access by fd. */ if (fd >= array->capa) { #define ALIGN 512 qse_httpd_client_t* tmp; qse_size_t capa = ((fd + ALIGN) / ALIGN) * ALIGN; - tmp = qse_httpd_reallocmem (httpd, array->data, capa * QSE_SIZEOF(*tmp)); + tmp = qse_httpd_reallocmem ( + httpd, array->data, capa * QSE_SIZEOF(*tmp)); if (tmp == QSE_NULL) return QSE_NULL; QSE_MEMSET (&tmp[array->capa], 0, @@ -523,9 +530,13 @@ static qse_httpd_client_t* insert_into_client_array (qse_httpd_t* httpd, int fd, opt &= ~QSE_HTRD_RESPONSE; qse_htrd_setoption (array->data[fd].htrd, opt); + array->data[fd].ready = httpd->cbs->client.accepted? 0 : 1; array->data[fd].bad = 0; - array->data[fd].handle.i = fd; - array->data[fd].addr = *addr; + array->data[fd].secure = client->secure; + array->data[fd].handle = client->handle; + array->data[fd].handle2 = client->handle2; + array->data[fd].local_addr = client->local_addr; + array->data[fd].remote_addr = client->remote_addr; #if defined(HAVE_PTHREAD) if (httpd->threaded) @@ -543,25 +554,23 @@ static qse_httpd_client_t* insert_into_client_array (qse_httpd_t* httpd, int fd, static int accept_client_from_listener (qse_httpd_t* httpd, listener_t* l) { - int flag, c; - sockaddr_t addr; + int flag; + #ifdef HAVE_SOCKLEN_T - socklen_t addrlen = QSE_SIZEOF(addr); + socklen_t addrlen; #else - int addrlen = QSE_SIZEOF(addr); + int addrlen; #endif + qse_httpd_client_t clibuf; qse_httpd_client_t* client; -/* TODO: - if (l->secure) - { -SSL_Accept - .... - } -*/ + QSE_MEMSET (&clibuf, 0, QSE_SIZEOF(clibuf)); + clibuf.secure = l->secure; - c = accept (l->handle, (struct sockaddr*)&addr, &addrlen); - if (c <= -1) + addrlen = QSE_SIZEOF(clibuf.remote_addr); + clibuf.handle.i = accept ( + l->handle, (struct sockaddr*)&clibuf.remote_addr, &addrlen); + if (clibuf.handle.i <= -1) { httpd->errnum = QSE_HTTPD_ESOCKET; qse_fprintf (QSE_STDERR, QSE_T("Error: accept returned failure\n")); @@ -570,39 +579,42 @@ qse_fprintf (QSE_STDERR, QSE_T("Error: accept returned failure\n")); /* select() uses a fixed-size array so the file descriptor can not * exceeded FD_SETSIZE */ - if (c >= FD_SETSIZE) + if (clibuf.handle.i >= FD_SETSIZE) { - close (c); - + close (clibuf.handle.i); qse_fprintf (QSE_STDERR, QSE_T("Error: too many client?\n")); /* httpd->errnum = QSE_HTTPD_EOVERFLOW; */ goto oops; } + addrlen = QSE_SIZEOF(clibuf.local_addr); + if (getsockname (clibuf.handle.i, (struct sockaddr*)&clibuf.local_addr, &addrlen) <= -1) + get_listener_sockaddr (l, &clibuf.local_addr); + /* set the nonblock flag in case read() after select() blocks * for various reasons - data received may be dropped after * arrival for wrong checksum, for example. */ - flag = fcntl (c, F_GETFL); - if (flag >= 0) fcntl (c, F_SETFL, flag | O_NONBLOCK); + flag = fcntl (clibuf.handle.i, F_GETFL); + if (flag >= 0) fcntl (clibuf.handle.i, F_SETFL, flag | O_NONBLOCK); - flag = fcntl (c, F_GETFD); - if (flag >= 0) fcntl (c, F_SETFD, flag | FD_CLOEXEC); + flag = fcntl (clibuf.handle.i, F_GETFD); + if (flag >= 0) fcntl (clibuf.handle.i, F_SETFD, flag | FD_CLOEXEC); #if defined(HAVE_PTHREAD) if (httpd->threaded) pthread_mutex_lock (&httpd->client.mutex); #endif - client = insert_into_client_array (httpd, c, &addr); + client = insert_into_client_array (httpd, &clibuf); #if defined(HAVE_PTHREAD) if (httpd->threaded) pthread_mutex_unlock (&httpd->client.mutex); #endif if (client == QSE_NULL) { - close (c); + close (clibuf.handle.i); goto oops; } -qse_printf (QSE_T("connection %d accepted\n"), c); +qse_printf (QSE_T("connection %d accepted\n"), clibuf.handle.i); return 0; @@ -683,26 +695,39 @@ static int make_fd_set_from_client_array ( if (!httpd->threaded || !for_rdwr) { - /* a trigger is a handle to monitor to check - * if there is data avaiable to write back to the client. - * if it is not threaded, qse_httpd_loop() needs to - * monitor trigger handles. if it is threaded, - * response_thread() needs to monitor these handles */ - - if (ca->data[fd].task.queue.head && - ca->data[fd].task.queue.head->task.trigger.i >= 0) + /* trigger[0] is a handle to monitor to check + * if there is data avaiable to read to write back to + * the client. if it is not threaded, qse_httpd_loop() + * needs to monitor trigger handles. if it is threaded, + * response_thread() needs to monitor these handles. + * + * trigger[1] is a user-defined handle to monitor to + * check if httpd can post data to. but this is not + * a client-side handle. + */ + if (ca->data[fd].task.queue.head) { - /* if a trigger is available, add it to the read set also. */ - FD_SET (ca->data[fd].task.queue.head->task.trigger.i, r); - if (ca->data[fd].task.queue.head->task.trigger.i > max) - max = ca->data[fd].task.queue.head->task.trigger.i; + qse_httpd_task_t* task = &ca->data[fd].task.queue.head->task; + if (task->trigger_mask & QSE_HTTPD_TASK_TRIGGER_READ) + { + /* if a trigger is available, add it to the read set also. */ +qse_printf (QSE_T(">>>>ADDING TRIGGER[0] %d\n"), task->trigger[0].i); + FD_SET (task->trigger[0].i, r); + if (task->trigger[0].i > max) max = task->trigger[0].i; + } + if (task->trigger_mask & QSE_HTTPD_TASK_TRIGGER_WRITE) + { + /* if a trigger is available, add it to the read set also. */ +qse_printf (QSE_T(">>>>ADDING TRIGGER[1] %d\n"), task->trigger[1].i); + FD_SET (task->trigger[1].i, w); + if (task->trigger[1].i > max) max = task->trigger[1].i; + } } } } if (ca->data[fd].bad || - (ca->data[fd].task.queue.head && - ca->data[fd].task.queue.head->task.trigger.i <= -1)) + (ca->data[fd].task.queue.head && !(ca->data[fd].task.queue.head->task.trigger_mask & QSE_HTTPD_TASK_TRIGGER_READ))) { /* add a client-side handle to the write set * if the client is already marked bad or @@ -815,8 +840,42 @@ qse_fprintf (QSE_STDERR, QSE_T("Error: select returned failure - %hs\n"), strerr } else if (client->task.queue.head) { - if (client->task.queue.head->task.trigger.i <= -1 || - FD_ISSET(client->task.queue.head->task.trigger.i, &r)) + qse_httpd_task_t* task; + int perform = 0; + + task = &client->task.queue.head->task; + + task->trigger_mask &= + ~(QSE_HTTPD_TASK_TRIGGER_READABLE | + QSE_HTTPD_TASK_TRIGGER_WRITABLE); + + if (!(task->trigger_mask & QSE_HTTPD_TASK_TRIGGER_READ) && + !(task->trigger_mask & QSE_HTTPD_TASK_TRIGGER_WRITE)) + { + /* no trigger set. set the flag to + * non-readable and non-writable */ + perform = 1; + } + else + { + if ((task->trigger_mask & QSE_HTTPD_TASK_TRIGGER_READ) && + FD_ISSET(task->trigger[0].i, &r)) + { + /* set the flag to readable */ + task->trigger_mask |= QSE_HTTPD_TASK_TRIGGER_READABLE; + perform = 1; + } + + if ((task->trigger_mask & QSE_HTTPD_TASK_TRIGGER_WRITE) && + FD_ISSET(task->trigger[1].i, &w)) + { + /* set the flag to writable */ + task->trigger_mask |= QSE_HTTPD_TASK_TRIGGER_WRITABLE; + perform = 1; + } + } + + if (perform) { tv.tv_sec = 0; tv.tv_usec = 0; @@ -842,10 +901,16 @@ static int read_from_client (qse_httpd_t* httpd, qse_httpd_client_t* client) qse_mchar_t buf[1024]; qse_ssize_t m; + QSE_ASSERT (httpd->cbs->client.recv != QSE_NULL); + reread: - m = read (client->handle.i, buf, QSE_SIZEOF(buf)); + httpd->errnum = QSE_HTTPD_ENOERR; + m = httpd->cbs->client.recv (httpd, client, buf, QSE_SIZEOF(buf)); if (m <= -1) { +// TODO: handle errno in the callback... and devise a new return value +// to indicate no data at this momemnt (EAGAIN, EWOULDBLOCK)... +// EINTR to be hnalded inside callback if needed... if (errno == EAGAIN || errno == EWOULDBLOCK) { /* nothing to read yet. */ @@ -871,6 +936,7 @@ qse_fprintf (QSE_STDERR, QSE_T("Debug: connection closed %d\n"), client->handle. * that's because we don't know how many valid requests * are included in 'buf' */ qse_fprintf (QSE_STDERR, QSE_T("Debug: read from a client %d\n"), client->handle.i); + httpd->errnum = QSE_HTTPD_ENOERR; if (qse_htrd_feed (client->htrd, buf, m) <= -1) { @@ -889,14 +955,15 @@ qse_fprintf (QSE_STDERR, QSE_T("Error: http error while processing \n")); return 0; } -int qse_httpd_loop (qse_httpd_t* httpd, int threaded) +int qse_httpd_loop (qse_httpd_t* httpd, qse_httpd_cbs_t* cbs, int threaded) { #if defined(HAVE_PTHREAD) pthread_t response_thread_id; #endif - httpd->stopreq = 0; httpd->threaded = 0; + httpd->stopreq = 0; + httpd->cbs = cbs; QSE_ASSERTX (httpd->listener.list != QSE_NULL, "Add listeners before calling qse_httpd_loop()" @@ -934,16 +1001,22 @@ int qse_httpd_loop (qse_httpd_t* httpd, int threaded) pthread_mutex_init (&httpd->client.mutex, QSE_NULL); pthread_cond_init (&httpd->client.cond, QSE_NULL); + /* set this before creating a thread + * because this is accessed in a thread. + * if i set this after pthread_create, a thread + * function may still see 0. */ + httpd->threaded = 1; + if (pthread_create ( &response_thread_id, QSE_NULL, response_thread, httpd) != 0) { + httpd->threaded = 0; pthread_cond_destroy (&httpd->client.cond); pthread_mutex_destroy (&httpd->client.mutex); QSE_CLOSE (httpd->client.pfd[1]); QSE_CLOSE (httpd->client.pfd[0]); } - else httpd->threaded = 1; } } #endif @@ -994,26 +1067,38 @@ qse_fprintf (QSE_STDERR, QSE_T("Error: select returned failure\n")); if (FD_ISSET(client->handle.i, &r)) { /* got input */ - if (read_from_client (httpd, client) <= -1) + if (!client->ready) { - if (httpd->threaded) + /* if client.accepted() returns 0, it is called + * again next time. */ + QSE_ASSERT (httpd->cbs->client.accepted != QSE_NULL); + int x = httpd->cbs->client.accepted (httpd, client); /* is this correct???? what if ssl handshaking got stalled because writing failed in SSL_accept()? */ + if (x >= 1) client->ready = 1; + else if (x <= -1) goto bad_client; + } + else + { + if (read_from_client (httpd, client) <= -1) { - /* let the writing part handle it, - * probably in the next iteration */ - qse_httpd_markclientbad (httpd, client); - shutdown (client->handle.i, SHUT_RDWR); - } - else - { - /*pthread_mutex_lock (&httpd->client.mutex);*/ - delete_from_client_array (httpd, fd); - /*pthread_mutex_unlock (&httpd->client.mutex);*/ - continue; /* don't need to go to the writing part */ + bad_client: + if (httpd->threaded) + { + /* let the writing part handle it, + * probably in the next iteration */ + qse_httpd_markbadclient (httpd, client); + shutdown (client->handle.i, SHUT_RDWR); + } + else + { + /*pthread_mutex_lock (&httpd->client.mutex);*/ + delete_from_client_array (httpd, fd); + /*pthread_mutex_unlock (&httpd->client.mutex);*/ + continue; /* don't need to go to the writing part */ + } } } } - if (!httpd->threaded) { if (client->bad) @@ -1025,8 +1110,41 @@ qse_fprintf (QSE_STDERR, QSE_T("Error: select returned failure\n")); } else if (client->task.queue.head) { - if (client->task.queue.head->task.trigger.i <= -1 || - FD_ISSET(client->task.queue.head->task.trigger.i, &r)) + qse_httpd_task_t* task; + int perform = 0; + + task = &client->task.queue.head->task; + task->trigger_mask &= + ~(QSE_HTTPD_TASK_TRIGGER_READABLE | + QSE_HTTPD_TASK_TRIGGER_WRITABLE); + + if (!(task->trigger_mask & QSE_HTTPD_TASK_TRIGGER_READ) && + !(task->trigger_mask & QSE_HTTPD_TASK_TRIGGER_WRITE)) + { + /* no trigger set. set the flag to + * non-readable and non-writable */ + perform = 1; + } + else + { + if ((task->trigger_mask & QSE_HTTPD_TASK_TRIGGER_READ) && + FD_ISSET(task->trigger[0].i, &r)) + { + /* set the flag to readable */ + task->trigger_mask |= QSE_HTTPD_TASK_TRIGGER_READABLE; + perform = 1; + } + + if ((task->trigger_mask & QSE_HTTPD_TASK_TRIGGER_WRITE) && + FD_ISSET(task->trigger[1].i, &w)) + { + /* set the flag to writable */ + task->trigger_mask |= QSE_HTTPD_TASK_TRIGGER_WRITABLE; + perform = 1; + } + } + + if (perform) { tv.tv_sec = 0; tv.tv_usec = 0; @@ -1305,7 +1423,7 @@ qse_httpd_task_t* qse_httpd_entask ( return ret; } -void qse_httpd_markclientbad (qse_httpd_t* httpd, qse_httpd_client_t* client) +void qse_httpd_markbadclient (qse_httpd_t* httpd, qse_httpd_client_t* client) { /* mark that something is wrong in processing requests from this client. * this client could be bad... or the system could encounter some errors @@ -1313,4 +1431,12 @@ void qse_httpd_markclientbad (qse_httpd_t* httpd, qse_httpd_client_t* client) client->bad = 1; } +void qse_httpd_discardcontent (qse_httpd_t* httpd, qse_htre_t* req) +{ + req->flags |= QSE_HTRE_DISCARDED; + /* clear the content buffer in case it has received contents + * partially already */ + qse_mbs_clear (&req->content); +} + #endif diff --git a/qse/lib/net/httpd.h b/qse/lib/net/httpd.h index 8b3ce9f3..7b4ff384 100644 --- a/qse/lib/net/httpd.h +++ b/qse/lib/net/httpd.h @@ -61,8 +61,13 @@ struct task_queue_node_t struct qse_httpd_client_t { qse_ubi_t handle; + qse_ubi_t handle2; + + int ready; int bad; - sockaddr_t addr; + int secure; + sockaddr_t local_addr; + sockaddr_t remote_addr; qse_htrd_t* htrd; struct diff --git a/qse/lib/net/httpd_task.c b/qse/lib/net/httpd_task.c index f3b59db8..761bcd72 100644 --- a/qse/lib/net/httpd_task.c +++ b/qse/lib/net/httpd_task.c @@ -29,6 +29,7 @@ #include #include #include +#include #include #include @@ -37,82 +38,6 @@ #define MAX_SEND_SIZE 4096 -#if defined(HAVE_SYS_SENDFILE_H) -# include -#endif - -#if defined(HAVE_SENDFILE) && defined(HAVE_SENDFILE64) -# if !defined(_LP64) && (QSE_SIZEOF_VOID_P<8) && defined(HAVE_SENDFILE64) -# define xsendfile sendfile64 -# else -# define xsendfile sendfile -# endif -#elif defined(HAVE_SENDFILE) -# define xsendfile sendfile -#elif defined(HAVE_SENDFILE64) -# define xsendfile sendfile64 -#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 = lseek (in_fd, 0, SEEK_CUR); /* TODO: lseek64 or llseek.. */ - 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 && lseek (in_fd, *offset, SEEK_SET) != *offset) //* 64bit version of lseek... - 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 - /*------------------------------------------------------------------------*/ static int task_main_disconnect ( @@ -131,7 +56,6 @@ qse_httpd_task_t* qse_httpd_entaskdisconnect ( QSE_MEMSET (&task, 0, QSE_SIZEOF(task)); task.main = task_main_disconnect; - task.trigger.i = -1; return qse_httpd_entask (httpd, client, pred, &task, 0); } @@ -151,13 +75,7 @@ static int task_main_statictext ( } /* TODO: do i need to add code to skip this send if count is 0? */ - n = send ( - client->handle.i, - task->ctx, - count, - 0 - ); - + n = httpd->cbs->client.send (httpd, client, task->ctx, count); if (n <= -1) return -1; ptr = (const qse_mchar_t*)task->ctx + n; @@ -178,7 +96,6 @@ qse_httpd_task_t* qse_httpd_entaskstatictext ( QSE_MEMSET (&task, 0, QSE_SIZEOF(task)); task.main = task_main_statictext; task.ctx = (void*)text; - task.trigger.i = -1; return qse_httpd_entask (httpd, client, pred, &task, 0); } @@ -216,13 +133,7 @@ static int task_main_text ( if (count >= ctx->left) count = ctx->left; /* TODO: do i need to add code to skip this send if count is 0? */ - n = send ( - client->handle.i, - ctx->ptr, - count, - 0 - ); - + n = httpd->cbs->client.send (httpd, client, ctx->ptr, count); if (n <= -1) return -1; ctx->left -= n; @@ -249,7 +160,6 @@ qse_httpd_task_t* qse_httpd_entasktext ( task.init = task_init_text; task.main = task_main_text; task.ctx = &data; - task.trigger.i = -1; return qse_httpd_entask ( httpd, client, pred, &task, QSE_SIZEOF(data) + data.left); @@ -296,13 +206,7 @@ static int task_main_format ( count = MAX_SEND_SIZE; if (count >= ctx->left) count = ctx->left; - n = send ( - client->handle.i, - ctx->ptr, - count, - 0 - ); - + n = httpd->cbs->client.send (httpd, client, ctx->ptr, count); if (n <= -1) return -1; ctx->left -= n; @@ -400,7 +304,6 @@ qse_httpd_task_t* qse_httpd_entaskformat ( task.fini = task_fini_format; task.main = task_main_format; task.ctx = &data; - task.trigger.i = -1; qse_printf (QSE_T("SEND: [%.*hs]\n"), (int)l, buf); return qse_httpd_entask ( @@ -436,11 +339,21 @@ static qse_httpd_task_t* entask_error ( lmsg = QSE_MT("Method Not AllowedREQUESTED METHOD NOT ALLOWED"); break; + case 411: + smsg = QSE_MT("Length Required"); + lmsg = QSE_MT("Length RequiredLENGTH REQUIRED"); + break; + case 416: smsg = QSE_MT("Requested Range Not Satisfiable"); lmsg = QSE_MT("Requested Range Not SatsfiableREQUESTED RANGE NOT SATISFIABLE"); break; + case 417: + smsg = QSE_MT("Expectation Failed"); + lmsg = QSE_MT("Expectation FailedEXPECTATION FAILED"); + break; + case 500: smsg = QSE_MT("Internal Server Error"); lmsg = QSE_MT("Internal Server ErrorINTERNAL SERVER ERROR"); @@ -483,6 +396,18 @@ qse_httpd_task_t* qse_httpd_entaskerror ( return entask_error (httpd, client, task, code, qse_htre_getversion(req), req->attr.keepalive); } +/*------------------------------------------------------------------------*/ +qse_httpd_task_t* qse_httpd_entaskcontinue ( + qse_httpd_t* httpd, qse_httpd_client_t* client, + const qse_httpd_task_t* task, const qse_htre_t* req) +{ + qse_http_version_t* version = qse_htre_getversion(req); + return qse_httpd_entaskformat ( + httpd, client, task, + QSE_MT("HTTP/%d.%d 100 Continue\r\n\r\n"), + version->major, version->minor); +} + /*------------------------------------------------------------------------*/ typedef struct task_file_t task_file_t; @@ -506,6 +431,7 @@ static void task_fini_file ( qse_httpd_t* httpd, qse_httpd_client_t* client, qse_httpd_task_t* task) { task_file_t* ctx = (task_file_t*)task->ctx; +qse_printf (QSE_T("closing file %d\n"), ctx->handle.i); QSE_CLOSE (ctx->handle.i); } @@ -520,13 +446,8 @@ static int task_main_file ( if (count >= ctx->left) count = ctx->left; /* TODO: more adjustment needed for OS with different sendfile semantics... */ - n = xsendfile ( - client->handle.i, - ctx->handle.i, - &ctx->offset, - count - ); - + n = httpd->cbs->client.sendfile ( + httpd, client, ctx->handle, &ctx->offset, count); if (n <= -1) { // HANDLE EGAIN specially??? @@ -560,7 +481,6 @@ qse_httpd_task_t* qse_httpd_entaskfile ( qse_httpd_task_t task; task_file_t data; -qse_printf (QSE_T("Debug: sending file to %d\n"), client->handle.i); QSE_MEMSET (&data, 0, QSE_SIZEOF(data)); data.handle = handle; data.offset = offset; @@ -571,8 +491,8 @@ qse_printf (QSE_T("Debug: sending file to %d\n"), client->handle.i); task.main = task_main_file; task.fini = task_fini_file; task.ctx = &data; - task.trigger.i = -1; +qse_printf (QSE_T("Debug: entasking file (%d)\n"), client->handle.i); return qse_httpd_entask (httpd, client, pred, &task, QSE_SIZEOF(data)); } @@ -783,7 +703,9 @@ set_chunklen: ctx->bufpos = x; send_dirlist: - n = send (client->handle.i, &ctx->buf[ctx->bufpos], ctx->buflen, 0); + httpd->errnum = QSE_HTTPD_ENOERR; + n = httpd->cbs->client.send ( + httpd, client, &ctx->buf[ctx->bufpos], ctx->buflen); if (n <= -1) return -1; /* NOTE if (n == 0), it will enter an infinite loop */ @@ -894,7 +816,9 @@ static int task_main_dir_nochunk ( while (1); send_dirlist: - n = send (client->handle.i, &ctx->buf[ctx->bufpos], ctx->buflen, 0); + httpd->errnum = QSE_HTTPD_ENOERR; + n = httpd->cbs->client.send ( + httpd, client, &ctx->buf[ctx->bufpos], ctx->buflen); if (n <= -1) return -1; ctx->bufpos += n; @@ -915,7 +839,6 @@ qse_httpd_task_t* qse_httpd_entaskdir ( task.main = chunked? task_main_dir: task_main_dir_nochunk; task.fini = task_fini_dir; task.ctx = &handle; - task.trigger.i = -1; return qse_httpd_entask (httpd, client, pred, &task, QSE_SIZEOF(task_dir_t)); } @@ -1209,7 +1132,6 @@ qse_httpd_task_t* qse_httpd_entaskpath ( task.init = task_init_path; task.main = task_main_path; task.ctx = &data; - task.trigger.i = -1; return qse_httpd_entask (httpd, client, pred, &task, QSE_SIZEOF(task_path_t) + qse_mbslen(name) + 1); @@ -1239,6 +1161,10 @@ struct task_cgi_t qse_pio_t* pio; qse_htrd_t* htrd; + qse_htre_t* req; /* original request associated with this */ + qse_mbs_t* reqcon; /* content from the request */ + int reqfwderr; + qse_mbs_t* res; qse_mchar_t* res_ptr; qse_size_t res_left; @@ -1264,6 +1190,35 @@ struct cgi_htrd_xtn_t task_cgi_t* cgi; }; +typedef struct cgi_req_hdr_ctx_t cgi_req_hdr_ctx_t; +struct cgi_req_hdr_ctx_t +{ + qse_httpd_t* httpd; + qse_env_t* env; +}; + + +int walk_req_headers (qse_htre_t* req, const qse_mchar_t* key, const qse_mchar_t* val, void* ctx) +{ + cgi_req_hdr_ctx_t* cgi; + qse_mchar_t* http_key; + int ret; + + cgi = (cgi_req_hdr_ctx_t*)ctx; + +/* convert value to uppercase, change - to _ */ + http_key = qse_mbsdup2 (QSE_MT("HTTP_"), key, req->mmgr); + if (http_key == QSE_NULL) + { + cgi->httpd->errnum = QSE_HTTPD_ENOMEM; + return -1; + } + + ret = qse_env_insertmbs (cgi->env, http_key, val); + QSE_MMGR_FREE (req->mmgr, http_key); + return ret; +} + int walk_cgi_headers (qse_htre_t* req, const qse_mchar_t* key, const qse_mchar_t* val, void* ctx) { task_cgi_t* cgi = (task_cgi_t*)ctx; @@ -1279,17 +1234,14 @@ int walk_cgi_headers (qse_htre_t* req, const qse_mchar_t* key, const qse_mchar_t return 0; } -static int cgi_htrd_handle_request (qse_htrd_t* htrd, qse_htre_t* req) +static int cgi_htrd_peek_request (qse_htrd_t* htrd, qse_htre_t* req) { cgi_htrd_xtn_t* xtn = (cgi_htrd_xtn_t*) qse_htrd_getxtn (htrd); task_cgi_t* cgi = xtn->cgi; const qse_mchar_t* status; static qse_http_version_t v11 = { 1, 1 }; - QSE_ASSERT (req->attr.hurried); - status = qse_htre_getheaderval (req, QSE_MT("Status")); - if (status) { qse_mchar_t buf[128]; @@ -1383,6 +1335,9 @@ static int cgi_htrd_handle_request (qse_htrd_t* htrd, qse_htre_t* req) if (cgi->content_received > 0) { + /* the initial part of the content body has been received + * along with the header. it need to be added to the result + * buffer. */ if (cgi->content_chunked) { qse_mchar_t buf[64]; @@ -1398,64 +1353,148 @@ static int cgi_htrd_handle_request (qse_htrd_t* htrd, qse_htre_t* req) } } - return 0; + return 0; } static qse_htrd_recbs_t cgi_htrd_cbs = { - cgi_htrd_handle_request, - QSE_NULL, /* not needed for CGI */ - QSE_NULL /* not needed for CGI */ + cgi_htrd_peek_request, + QSE_NULL /* not needed for CGI */ }; static qse_env_t* makecgienv ( - qse_httpd_t* httpd, qse_httpd_client_t* client, const qse_htre_t* req) + qse_httpd_t* httpd, qse_httpd_client_t* client, + const qse_htre_t* req, const qse_mchar_t* path) { /* TODO: error check */ qse_env_t* env; + cgi_req_hdr_ctx_t ctx; env = qse_env_open (httpd->mmgr, 0, 0); if (env == QSE_NULL) goto oops; #ifdef _WIN32 - qse_env_insertsys (env, QSE_T("PATH")); + qse_env_insert (env, QSE_T("PATH"), QSE_NULL); +#else + qse_env_insertmbs (env, QSE_MT("LANG"), QSE_NULL); + qse_env_insertmbs (env, QSE_MT("PATH"), QSE_NULL); +#endif + + qse_env_insertmbs (env, QSE_MT("GATEWAY_INTERFACE"), QSE_MT("CGI/1.1")); + +{ +/* TODO: corrent all these name??? */ +qse_mchar_t tmp[1024]; +qse_mbsxncpy (tmp, QSE_COUNTOF(tmp), qse_htre_getqpathptr(req), qse_htre_getqpathlen(req)); + //qse_env_insertmbs (env, QSE_MT("SCRIPT_NAME"), tmp); + //qse_env_insertmbs (env, QSE_MT("PATH_INFO"), tmp); + //qse_env_insertmbs (env, QSE_MT("PATH_TRANSLATED"), tmp); + //qse_env_insertmbs (env, QSE_MT("DOCUMENT_ROOT"), QSE_MT("/")); +} + + + //qse_env_insertmbs (env, QSE_MT("SCRIPT_FILENAME"), path); + qse_env_insertmbs (env, QSE_MT("REQUEST_URI"), qse_htre_getqpathptr(req)); { - qse_char_t proto[32]; - qse_http_version_t* v = qse_htre_getversion(req); - snprintf (proto, QSE_COUNTOF(proto), - QSE_T("HTTP/%d.%d"), (int)v->major, (int)v->minor); - qse_env_insert (env, QSE_T("SERVER_PROTOCOL"), proto); + qse_mchar_t* tmp = qse_htre_getqparamptr(req); + if (tmp) qse_env_insertmbs (env, QSE_MT("QUERY_STRING"), tmp); } -#else - qse_env_insertsysm (env, QSE_MT("LANG")); - qse_env_insertsysm (env, QSE_MT("PATH")); - //qse_env_insertm (env, QSE_MT("SERVER_PORT"), ); + qse_env_insertmbs ( + env, QSE_MT("REQUEST_METHOD"), qse_htre_getqmethodname(req)); + + + if (req->attr.content_length_set) + { + qse_mchar_t tmp[64]; + qse_fmtuintmaxtombs ( + tmp, QSE_COUNTOF(tmp), + req->attr.content_length, 10, + -1, QSE_MT('\0'), QSE_NULL); + qse_env_insertmbs (env, QSE_MT("CONTENT_LENGTH"), tmp); + } + + // TODO: SERVER_HOST, REMOTE_HOST, { qse_mchar_t port[16]; snprintf (port, QSE_COUNTOF(port), - QSE_MT("%d"), (int)ntohs(client->addr.in4.sin_port)); - qse_env_insertm (env, QSE_MT("REMOTE_PORT"), port); + QSE_MT("%d"), (int)ntohs(client->local_addr.in4.sin_port)); + qse_env_insertmbs (env, QSE_MT("SERVER_PORT"), port); + + snprintf (port, QSE_COUNTOF(port), + QSE_MT("%d"), (int)ntohs(client->remote_addr.in4.sin_port)); + qse_env_insertmbs (env, QSE_MT("REMOTE_PORT"), port); + } + + if (client->local_addr.in4.sin_family == AF_INET) + { + qse_mchar_t ipaddr[128]; + inet_ntop (client->local_addr.in4.sin_family, &client->local_addr.in4.sin_addr, ipaddr, QSE_COUNTOF(ipaddr)); + qse_env_insertmbs (env, QSE_MT("SERVER_ADDR"), ipaddr); + } + else + { + qse_mchar_t ipaddr[128]; + inet_ntop (client->local_addr.in6.sin6_family, &client->local_addr.in6.sin6_addr, ipaddr, QSE_COUNTOF(ipaddr)); + qse_env_insertmbs (env, QSE_MT("SERVER_ADDR"), ipaddr); + } + + if (client->remote_addr.in4.sin_family == AF_INET) + { + qse_mchar_t ipaddr[128]; + inet_ntop (client->remote_addr.in4.sin_family, &client->remote_addr.in4.sin_addr, ipaddr, QSE_COUNTOF(ipaddr)); + qse_env_insertmbs (env, QSE_MT("REMOTE_ADDR"), ipaddr); + } + else + { + qse_mchar_t ipaddr[128]; + inet_ntop (client->remote_addr.in6.sin6_family, &client->remote_addr.in6.sin6_addr, ipaddr, QSE_COUNTOF(ipaddr)); + qse_env_insertmbs (env, QSE_MT("REMOTE_ADDR"), ipaddr); } { qse_mchar_t proto[32]; - qse_http_version_t* v = qse_htre_getversion(req); + const qse_http_version_t* v = qse_htre_getversion(req); snprintf (proto, QSE_COUNTOF(proto), QSE_MT("HTTP/%d.%d"), (int)v->major, (int)v->minor); - qse_env_insertm (env, QSE_MT("SERVER_PROTOCOL"), proto); + qse_env_insertmbs (env, QSE_MT("SERVER_PROTOCOL"), proto); } - //qse_env_insertm (env, QSE_MT("REMOTE_ADDR"), QSE_MT("what the hell")); +// TODO: HTTP_ headers. + +#if 0 + qse_env_insertmbs (env, "SERVER_NAME", + qse_env_insertmbs (env, "SERVER_ROOT", + qse_env_insertmbs (env, "DOCUMENT_ROOT", + qse_env_insertmbs (env, "REMOTE_PORT", + qse_env_insertmbs (env, "REQUEST_URI", #endif #if 0 - qse_env_insertm (env, "SERVER_NAME", - qse_env_insertm (env, "SERVER_ROOT", - qse_env_insertm (env, "DOCUMENT_ROOT", - qse_env_insertm (env, "REMOTE_PORT", - qse_env_insertm (env, "REQUEST_URI", + ctx.httpd = httpd; + ctx.env = env; + if (qse_htre_walkheaders (req, walk_req_headers, &ctx) <= -1) return -1; #endif + + { + const qse_mchar_t* tmp; + + tmp = qse_htre_getheaderval(req, QSE_MT("Content-Type")); + if (tmp) qse_env_insertmbs (env, QSE_MT("CONTENT_TYPE"), tmp); + + tmp = qse_htre_getheaderval(req, QSE_MT("Cookie")); + if (tmp) qse_env_insertmbs (env, QSE_MT("HTTP_COOKIE"), tmp); + + tmp = qse_htre_getheaderval(req, QSE_MT("Host")); + if (tmp) qse_env_insertmbs (env, QSE_MT("HTTP_HOST"), tmp); + + tmp = qse_htre_getheaderval(req, QSE_MT("Referer")); + if (tmp) qse_env_insertmbs (env, QSE_MT("HTTP_REFERER"), tmp); + + tmp = qse_htre_getheaderval(req, QSE_MT("User-Agent")); + if (tmp) qse_env_insertmbs (env, QSE_MT("HTTP_USER_AGENT"), tmp); + } + return env; oops: @@ -1463,23 +1502,126 @@ oops: return QSE_NULL; } +static int cgi_snatch_content ( + qse_htre_t* req, const qse_mchar_t* ptr, qse_size_t len, void* ctx) +{ + task_cgi_t* cgi = (task_cgi_t*)ctx; + + if (ptr == QSE_NULL) + { + /* request ended. this could be a real end or + * abortion for an error */ + QSE_ASSERT (len == 0); + cgi->req = QSE_NULL; + } + else + { + /* push the contents to the own buffer */ + if (qse_mbs_ncat (cgi->reqcon, ptr, len) == (qse_size_t)-1) + { + return -1; + } + } + + return 0; +} + +static int cgi_forward_content (qse_httpd_task_t* task) +{ + task_cgi_t* cgi = (task_cgi_t*)task->ctx; + + QSE_ASSERT (cgi->reqcon != QSE_NULL); + + if (QSE_MBS_LEN(cgi->reqcon) > 0) + { + qse_ssize_t n; + + if (!cgi->reqfwderr) + { +qse_printf (QSE_T("@@@@@@@WRITING %d bytes TO CGI\n"), (int)QSE_MBS_LEN(cgi->reqcon)); + n = qse_pio_write ( + cgi->pio, QSE_PIO_IN, + QSE_MBS_PTR(cgi->reqcon), + QSE_MBS_LEN(cgi->reqcon) + ); + if (n <= -1) + { +qse_printf (QSE_T("@@@@@@@@WRITE TO CGI FAILED\n")); +/* TODO: logging ... */ + cgi->reqfwderr = 1; + if (cgi->req) cgi->req->flags |= QSE_HTRE_DISCARDED; + } + } + +/* TODO: performance improvement... +deleting keeps on moving contents to the head... +can't we grow the buffer to a certain limit? +if the limit is reached, copy the tail to the head... */ + /* can write return 0? */ + qse_mbs_del (cgi->reqcon, 0, n); + } + else if (cgi->req == QSE_NULL) + { + /* no more request content */ +qse_printf (QSE_T("@@@@@@@@NOTHING MORE TO WRITE TO CGI\n")); + task->trigger_mask &= + ~(QSE_HTTPD_TASK_TRIGGER_WRITE | + QSE_HTTPD_TASK_TRIGGER_WRITABLE); + } + + return 0; +} + + static int task_init_cgi ( qse_httpd_t* httpd, qse_httpd_client_t* client, qse_httpd_task_t* task) { - task_cgi_t* xtn = (task_cgi_t*)qse_httpd_gettaskxtn (httpd, task); + task_cgi_t* cgi = (task_cgi_t*)qse_httpd_gettaskxtn (httpd, task); task_cgi_arg_t* arg = (task_cgi_arg_t*)task->ctx; - QSE_MEMSET (xtn, 0, QSE_SIZEOF(*xtn)); - qse_mbscpy ((qse_mchar_t*)(xtn + 1), arg->path); - xtn->path = (qse_mchar_t*)(xtn + 1); - xtn->version = *qse_htre_getversion(arg->req); - xtn->keepalive = arg->req->attr.keepalive; - xtn->nph = arg->nph; + QSE_MEMSET (cgi, 0, QSE_SIZEOF(*cgi)); + qse_mbscpy ((qse_mchar_t*)(cgi + 1), arg->path); + cgi->path = (qse_mchar_t*)(cgi + 1); + cgi->version = *qse_htre_getversion(arg->req); + cgi->keepalive = arg->req->attr.keepalive; + cgi->nph = arg->nph; - xtn->env = makecgienv (httpd, client, arg->req); - if (xtn->env == QSE_NULL) xtn->init_failed = 1; + if (!(arg->req->flags & QSE_HTRE_DISCARDED)) + { + /* CGI entasking is invoked probably from the peek handler + * that was triggered after the request header is received + * in principle. In that case, arrange to forward content + * bypassing the buffer in the request object itself. */ + const qse_mchar_t* ptr = qse_htre_getcontentptr(arg->req); + qse_size_t len = qse_htre_getcontentlen(arg->req); - task->ctx = xtn; + /* create a buffer to hold request content from the client + * and copy content received already */ + cgi->reqcon = qse_mbs_open (httpd->mmgr, 0, (len < 512? 512: len)); + if (cgi->reqcon == QSE_NULL || + qse_mbs_ncpy (cgi->reqcon, ptr, len) == (qse_size_t)-1) + { + cgi->init_failed = 1; + } + else if (!(arg->req->flags & QSE_HTRE_COMPLETED)) + { +/* TODO: callback chain instead of a single pointer??? +if the request is already set up with a callback, something will go wrong. + */ + /* set up a callback to be called when the request content + * is fed to the htrd reader */ + cgi->req = (qse_htre_t*)arg->req; + qse_htre_setconcb (cgi->req, cgi_snatch_content, cgi); + } + } + + if (!cgi->init_failed) + { + cgi->env = makecgienv (httpd, client, arg->req, arg->path); + if (cgi->env == QSE_NULL) cgi->init_failed = 1; + } + + task->ctx = cgi; return 0; } @@ -1497,6 +1639,15 @@ static void task_fini_cgi ( } if (cgi->res) qse_mbs_close (cgi->res); if (cgi->htrd) qse_htrd_close (cgi->htrd); + if (cgi->reqcon) qse_mbs_close (cgi->reqcon); + if (cgi->req) + { + /* this task is destroyed but the request + * associated is still alive. so clear the + * callback to prevent the callback call. */ + qse_htre_unsetconcb (cgi->req); + } + qse_printf (QSE_T("task_fini_cgi\n")); } @@ -1508,10 +1659,18 @@ static int task_main_cgi_5 ( QSE_ASSERT (cgi->pio != QSE_NULL); -qse_printf (QSE_T("task_main_cgi_5\n")); + if (task->trigger_mask & QSE_HTTPD_TASK_TRIGGER_WRITABLE) + { + if (cgi_forward_content (task) <= -1) return -1; + /* if forwarding didn't finish, something is not really right... + * so long as the output from CGI is finished, no more forwarding + * is performed */ + } +qse_printf (QSE_T("task_main_cgi_5\n")); /* TODO: check if cgi outputs more than content-length if it is set... */ - n = send (client->handle.i, cgi->buf, cgi->buflen, 0); + httpd->errnum = QSE_HTTPD_ENOERR; + n = httpd->cbs->client.send (httpd, client, cgi->buf, cgi->buflen); if (n <= -1) { /* can't return internal server error any more... */ @@ -1533,112 +1692,130 @@ static int task_main_cgi_4 ( QSE_ASSERT (cgi->pio != QSE_NULL); - /* this function assumes that the chunk length does not exceeded - * 4 hexadecimal digits. */ - QSE_ASSERT (QSE_SIZEOF(cgi->buf) <= 0xFFFF); - -qse_printf (QSE_T("task_main_cgi_4\n")); - - if (cgi->content_chunked) + if (task->trigger_mask & QSE_HTTPD_TASK_TRIGGER_WRITABLE) { - qse_size_t count, extra; - qse_mchar_t chunklen[7]; + if (cgi_forward_content (task) <= -1) return -1; + } -qse_printf (QSE_T("READING CHUNKED MODE...\n")); - extra = (QSE_SIZEOF(chunklen) - 1) + 2; - count = QSE_SIZEOF(cgi->buf) - cgi->buflen; - if (count > extra) + if (task->trigger_mask & QSE_HTTPD_TASK_TRIGGER_READABLE) + { + /* this function assumes that the chunk length does not exceeded + * 4 hexadecimal digits. */ + QSE_ASSERT (QSE_SIZEOF(cgi->buf) <= 0xFFFF); + + qse_printf (QSE_T("task_main_cgi_4\n")); + + if (cgi->content_chunked) { + qse_size_t count, extra; + qse_mchar_t chunklen[7]; + + qse_printf (QSE_T("READING CHUNKED MODE...\n")); + extra = (QSE_SIZEOF(chunklen) - 1) + 2; + count = QSE_SIZEOF(cgi->buf) - cgi->buflen; + if (count > extra) + { + + /* TODO: check if cgi outputs more than content-length if it is set... */ + /* <- can i make it non-block?? or use select??? pio_tryread()? */ + + n = qse_pio_read ( + cgi->pio, QSE_PIO_OUT, + &cgi->buf[cgi->buflen + QSE_SIZEOF(chunklen) - 1], + count - extra + ); + if (n <= -1) + { + /* can't return internal server error any more... */ + /* TODO: logging ... */ + return -1; + } + if (n == 0) + { + /* the cgi script closed the output */ + task->trigger_mask &= + ~(QSE_HTTPD_TASK_TRIGGER_READ | + QSE_HTTPD_TASK_TRIGGER_READABLE); -/* TODO: check if cgi outputs more than content-length if it is set... */ - /* <- can i make it non-block?? or use select??? pio_tryread()? */ - + cgi->buf[cgi->buflen++] = QSE_MT('0'); + cgi->buf[cgi->buflen++] = QSE_MT('\r'); + cgi->buf[cgi->buflen++] = QSE_MT('\n'); + cgi->buf[cgi->buflen++] = QSE_MT('\r'); + cgi->buf[cgi->buflen++] = QSE_MT('\n'); + + task->main = task_main_cgi_5; + return task_main_cgi_5 (httpd, client, task); + } + + /* set the chunk length */ + snprintf (chunklen, QSE_COUNTOF(chunklen), + QSE_MT("%-4lX\r\n"), (unsigned long)n); + QSE_MEMCPY (&cgi->buf[cgi->buflen], + chunklen, QSE_SIZEOF(chunklen) - 1); + cgi->buflen += QSE_SIZEOF(chunklen) - 1 + n; + + /* set the trailing CR & LF for a chunk */ + cgi->buf[cgi->buflen++] = QSE_MT('\r'); + cgi->buf[cgi->buflen++] = QSE_MT('\n'); + + cgi->content_received += n; + } + } + else + { + qse_printf (QSE_T("READING IN NON-CHUNKED MODE...\n")); n = qse_pio_read ( cgi->pio, QSE_PIO_OUT, - &cgi->buf[cgi->buflen + QSE_SIZEOF(chunklen) - 1], - count - extra + &cgi->buf[cgi->buflen], + QSE_SIZEOF(cgi->buf) - cgi->buflen ); if (n <= -1) { /* can't return internal server error any more... */ -/* TODO: logging ... */ + /* TODO: loggig ... */ return -1; } - if (n == 0) + if (n == 0) { - cgi->buf[cgi->buflen++] = QSE_MT('0'); - cgi->buf[cgi->buflen++] = QSE_MT('\r'); - cgi->buf[cgi->buflen++] = QSE_MT('\n'); - cgi->buf[cgi->buflen++] = QSE_MT('\r'); - cgi->buf[cgi->buflen++] = QSE_MT('\n'); - + task->trigger_mask &= + ~(QSE_HTTPD_TASK_TRIGGER_READ | + QSE_HTTPD_TASK_TRIGGER_READABLE); task->main = task_main_cgi_5; return task_main_cgi_5 (httpd, client, task); } - - /* set the chunk length */ - snprintf (chunklen, QSE_COUNTOF(chunklen), - QSE_MT("%-4lX\r\n"), (unsigned long)n); - QSE_MEMCPY (&cgi->buf[cgi->buflen], - chunklen, QSE_SIZEOF(chunklen) - 1); - cgi->buflen += QSE_SIZEOF(chunklen) - 1 + n; - /* set the trailing CR & LF for a chunk */ - cgi->buf[cgi->buflen++] = QSE_MT('\r'); - cgi->buf[cgi->buflen++] = QSE_MT('\n'); - + cgi->buflen += n; cgi->content_received += n; } - } - else - { -qse_printf (QSE_T("READING IN NON-CHUNKED MODE...\n")); - n = qse_pio_read ( - cgi->pio, QSE_PIO_OUT, - &cgi->buf[cgi->buflen], - QSE_SIZEOF(cgi->buf) - cgi->buflen - ); + + if (cgi->content_length_set && + cgi->content_received > cgi->content_length) + { + /* TODO: cgi returning too much data... something is wrong in CGI */ + qse_printf (QSE_T("CGI FUCKED UP...RETURNING TOO MUCH DATA\n")); + return -1; + } + + #if 0 + qse_printf (QSE_T("CGI_4 SEND [%.*hs]\n"), (int)cgi->buflen, cgi->buf); + #endif + httpd->errnum = QSE_HTTPD_ENOERR; + n = httpd->cbs->client.send (httpd, client, cgi->buf, cgi->buflen); if (n <= -1) { /* can't return internal server error any more... */ -/* TODO: loggig ... */ + /* TODO: logging ... */ return -1; } - if (n == 0) - { - task->main = task_main_cgi_5; - return task_main_cgi_5 (httpd, client, task); - } - - cgi->buflen += n; - cgi->content_received += n; + + QSE_MEMCPY (&cgi->buf[0], &cgi->buf[n], cgi->buflen - n); + cgi->buflen -= n; + + #if 0 + qse_printf (QSE_T("CGI SEND DONE\n")); + #endif } - if (cgi->content_length_set && - cgi->content_received > cgi->content_length) - { -/* TODO: cgi returning too much data... something is wrong in CGI */ -qse_printf (QSE_T("CGI FUCKED UP...RETURNING TOO MUCH DATA\n")); - return -1; - } - -#if 0 -qse_printf (QSE_T("CGI SEND [%.*hs]\n"), (int)cgi->buflen, cgi->buf); -#endif - n = send (client->handle.i, cgi->buf, cgi->buflen, 0); - if (n <= -1) - { - /* can't return internal server error any more... */ -/* TODO: logging ... */ - return -1; - } - - QSE_MEMCPY (&cgi->buf[0], &cgi->buf[n], cgi->buflen - n); - cgi->buflen -= n; - -#if 0 -qse_printf (QSE_T("CGI SEND DONE\n")); -#endif return 1; } @@ -1652,27 +1829,36 @@ static int task_main_cgi_3 ( qse_ssize_t n; qse_size_t count; -qse_printf (QSE_T("cgi_3\n")); - count = MAX_SEND_SIZE; - if (count >= cgi->res_left) count = cgi->res_left; - - n = send ( - client->handle.i, - cgi->res_ptr, - count, - 0 - ); - - if (n <= -1) return -1; - - cgi->res_left -= n; - if (cgi->res_left <= 0) + if (task->trigger_mask & QSE_HTTPD_TASK_TRIGGER_WRITABLE) { - task->main = task_main_cgi_4; - return task_main_cgi_4 (httpd, client, task); + if (cgi_forward_content (task) <= -1) return -1; + } + + if (task->trigger_mask & QSE_HTTPD_TASK_TRIGGER_READABLE) + { + count = MAX_SEND_SIZE; + if (count >= cgi->res_left) count = cgi->res_left; + +qse_printf (QSE_T("[cgi_3 sending %d bytes]\n"), (int)count); + httpd->errnum = QSE_HTTPD_ENOERR; + n = httpd->cbs->client.send (httpd, client, cgi->res_ptr, count); + + if (n <= -1) return -1; + + cgi->res_left -= n; + if (cgi->res_left <= 0) + { + task->main = task_main_cgi_4; + /* don't chain-call task_main_cgi_4 since it has another send + * and it has already been sent here. so the writability must + * be checked again in the main loop. + * => return task_main_cgi_4 (httpd, client, task);*/ + return 1; + } + + cgi->res_ptr += n; } - cgi->res_ptr += n; return 1; /* more work to do */ } @@ -1691,51 +1877,71 @@ static int task_main_cgi_2 ( QSE_ASSERT (cgi->pio != QSE_NULL); qse_printf (QSE_T("[cgi_2 ]\n")); - /* <- can i make it non-block?? or use select??? pio_tryread()? */ - n = qse_pio_read ( - cgi->pio, QSE_PIO_OUT, - &cgi->buf[cgi->buflen], - QSE_SIZEOF(cgi->buf) - cgi->buflen - ); - if (n <= -1) + if (task->trigger_mask & QSE_HTTPD_TASK_TRIGGER_WRITABLE) { - /* can't return internal server error any more... */ +qse_printf (QSE_T("[cgi_2 write]\n")); + if (cgi_forward_content (task) <= -1) return -1; + } + + if (task->trigger_mask & QSE_HTTPD_TASK_TRIGGER_READABLE) + { +qse_printf (QSE_T("[cgi_2 read]\n")); + /* <- can i make it non-block?? or use select??? pio_tryread()? */ + n = qse_pio_read ( + cgi->pio, QSE_PIO_OUT, + &cgi->buf[cgi->buflen], + QSE_SIZEOF(cgi->buf) - cgi->buflen + ); + if (n <= -1) + { + /* can't return internal server error any more... */ /* TODO: logging ... */ - return -1; - } - if (n == 0) - { - /* end of output from cgi before it has seen a header. - * the cgi script must be crooked. */ + return -1; + } + if (n == 0) + { + /* end of output from cgi before it has seen a header. + * the cgi script must be crooked. */ /* TODO: logging */ - return -1; - } + task->trigger_mask &= + ~(QSE_HTTPD_TASK_TRIGGER_READ | + QSE_HTTPD_TASK_TRIGGER_READABLE); + return -1; + } - cgi->buflen += n; + cgi->buflen += n; - if (qse_htrd_feed (cgi->htrd, cgi->buf, cgi->buflen) <= -1) - { + if (qse_htrd_feed (cgi->htrd, cgi->buf, cgi->buflen) <= -1) + { /* TODO: logging */ - return -1; - } + return -1; + } - cgi->buflen = 0; + cgi->buflen = 0; - if (QSE_MBS_LEN(cgi->res) > 0) - { - /* the htrd handler composed some response. - * this means that at least it finished processing CGI headers. - * some contents could be in cgi->res, though. - */ + if (QSE_MBS_LEN(cgi->res) > 0) + { + /* the htrd handler composed some response. + * this means that at least it finished processing CGI headers. + * some contents could be in cgi->res, though. + * + * qse_htrd_feed() must have executed the peek handler + * (cgi_htrd_peek_request()) which handled the request header. + * so i won't call qse_htrd_feed() any more. intead, i'll + * simply read directly from the pipe. + */ - if (cgi->disconnect && - qse_httpd_entaskdisconnect (httpd, client, task) == QSE_NULL) return -1; + if (cgi->disconnect && + qse_httpd_entaskdisconnect (httpd, client, task) == QSE_NULL) return -1; - cgi->res_ptr = QSE_MBS_PTR(cgi->res); - cgi->res_left = QSE_MBS_LEN(cgi->res); + cgi->res_ptr = QSE_MBS_PTR(cgi->res); + cgi->res_left = QSE_MBS_LEN(cgi->res); + +qse_printf (QSE_T("TRAILING DATA=[%.*hs]\n"), (int)QSE_MBS_LEN(cgi->res), QSE_MBS_PTR(cgi->res)); + task->main = task_main_cgi_3; + return task_main_cgi_3 (httpd, client, task); + } - task->main = task_main_cgi_3; - return task_main_cgi_3 (httpd, client, task); } /* complete headers not seen yet. i need to be called again */ @@ -1776,7 +1982,7 @@ static int task_main_cgi ( qse_htrd_setoption ( cgi->htrd, QSE_HTRD_SKIPINITIALLINE | - QSE_HTRD_HURRIED | + QSE_HTRD_PEEKONLY | QSE_HTRD_REQUEST ); @@ -1795,21 +2001,24 @@ static int task_main_cgi ( ); if (cgi->pio == QSE_NULL) goto oops; +/* TODO: use a different field for different OS??? +HANDLE for win32??? +*/ /* set the trigger that the main loop can use this * handle for multiplexing */ - task->trigger.i = qse_pio_gethandle (cgi->pio, QSE_PIO_OUT); + task->trigger_mask = QSE_HTTPD_TASK_TRIGGER_READ; + task->trigger[0].i = qse_pio_gethandle (cgi->pio, QSE_PIO_OUT); + if (cgi->reqcon) + { + task->trigger_mask |= QSE_HTTPD_TASK_TRIGGER_WRITE; + task->trigger[1].i = qse_pio_gethandle (cgi->pio, QSE_PIO_IN); + } - if (cgi->nph) - { - /* skip various header processing */ - task->main = task_main_cgi_4; - return task_main_cgi_4 (httpd, client, task); - } - else - { - task->main = task_main_cgi_2; /* cause this function to be called subsequently */ - return task_main_cgi_2 (httpd, client, task); /* let me call it here once */ - } + task->main = cgi->nph? task_main_cgi_4: task_main_cgi_2; + /* no chain call since readability and writability needs + * to be checked in the main loop + return task->main (httpd, client, task); */ + return 1; oops: if (cgi->res) @@ -1849,7 +2058,6 @@ qse_httpd_task_t* qse_httpd_entaskcgi ( task.fini = task_fini_cgi; task.main = task_main_cgi; task.ctx = &arg; - task.trigger.i = -1; return qse_httpd_entask ( httpd, client, pred, &task, @@ -1876,7 +2084,6 @@ qse_httpd_task_t* qse_httpd_entasknph ( task.fini = task_fini_cgi; task.main = task_main_cgi; task.ctx = &arg; - task.trigger.i = -1; return qse_httpd_entask ( httpd, client, pred, &task, diff --git a/qse/samples/cmn/env.c b/qse/samples/cmn/env.c index b800ed52..7ca905b3 100644 --- a/qse/samples/cmn/env.c +++ b/qse/samples/cmn/env.c @@ -106,11 +106,11 @@ static int test3 (void) env = qse_env_open (QSE_MMGR_GETDFL(), 0, 0); - qse_printf (QSE_T("inserting PATH => %d\n"), qse_env_insertsys (env, QSE_T("PATH"))); - qse_printf (QSE_T("inserting HOME => %d\n"), qse_env_insertsysm (env, QSE_MT("HOME"))); - qse_printf (QSE_T("inserting USER => %d\n"), qse_env_insertsysw (env, QSE_WT("USER"))); - qse_printf (QSE_T("inserting WHAT => %d\n"), qse_env_insertsys (env, QSE_T("WHAT"))); - qse_printf (QSE_T("inserting an empty string => %d\n"), qse_env_insertsys (env, QSE_T(""))); + qse_printf (QSE_T("inserting PATH => %d\n"), qse_env_insert (env, QSE_T("PATH"), QSE_NULL)); + qse_printf (QSE_T("inserting HOME => %d\n"), qse_env_insertmbs (env, QSE_MT("HOME"), QSE_NULL)); + qse_printf (QSE_T("inserting USER => %d\n"), qse_env_insertwcs (env, QSE_WT("USER"), QSE_NULL)); + qse_printf (QSE_T("inserting WHAT => %d\n"), qse_env_insert (env, QSE_T("WHAT"), QSE_NULL)); + qse_printf (QSE_T("inserting an empty string => %d\n"), qse_env_insert (env, QSE_T(""), QSE_NULL)); dump (env); diff --git a/qse/samples/cmn/pio.c b/qse/samples/cmn/pio.c index 4a0360a8..cc207a17 100644 --- a/qse/samples/cmn/pio.c +++ b/qse/samples/cmn/pio.c @@ -304,7 +304,7 @@ static int test12 (void) env = qse_env_open (QSE_MMGR_GETDFL(), 0, 0); if (env == QSE_NULL) return -1; - qse_env_insertsys (env, QSE_T("PATH")); + qse_env_insert (env, QSE_T("PATH"), QSE_NULL); qse_env_insert (env, QSE_T("HELLO"), QSE_T("WORLD")); n = pio1 ( diff --git a/qse/samples/net/Makefile.am b/qse/samples/net/Makefile.am index c4e78a7b..b59ae0ae 100644 --- a/qse/samples/net/Makefile.am +++ b/qse/samples/net/Makefile.am @@ -8,7 +8,7 @@ AM_CPPFLAGS = \ bin_PROGRAMS = http01 LDFLAGS += -L../../lib/cmn -L../../lib/net -LDADD = -lqsenet -lqsecmn $(PTHREAD_LIBS) $(SOCKET_LIBS) $(SENDFILE_LIBS) +LDADD = -lqsenet -lqsecmn $(PTHREAD_LIBS) $(SOCKET_LIBS) $(SENDFILE_LIBS) -lssl http01_SOURCES = http01.c diff --git a/qse/samples/net/Makefile.in b/qse/samples/net/Makefile.in index b83e763d..d0970107 100644 --- a/qse/samples/net/Makefile.in +++ b/qse/samples/net/Makefile.in @@ -226,7 +226,7 @@ AM_CPPFLAGS = \ -I$(top_srcdir)/include \ -I$(includedir) -LDADD = -lqsenet -lqsecmn $(PTHREAD_LIBS) $(SOCKET_LIBS) $(SENDFILE_LIBS) +LDADD = -lqsenet -lqsecmn $(PTHREAD_LIBS) $(SOCKET_LIBS) $(SENDFILE_LIBS) -lssl http01_SOURCES = http01.c all: all-am diff --git a/qse/samples/net/http01.c b/qse/samples/net/http01.c index 7d0f0ef6..47b105ff 100644 --- a/qse/samples/net/http01.c +++ b/qse/samples/net/http01.c @@ -12,52 +12,347 @@ # include #endif -#define MAX_SENDFILE_SIZE 4096 +#include + + +// TODO: remove this and export structured needed like qse_httpd_client_t +#include "../../lib/net/httpd.h" + +/* ------------------------------------------------------------------- */ + +#define MAX_SEND_SIZE 4096 +#if defined(HAVE_SYS_SENDFILE_H) +# include +#endif +#include + +#if defined(HAVE_SENDFILE) && defined(HAVE_SENDFILE64) +# if !defined(_LP64) && (QSE_SIZEOF_VOID_P<8) && defined(HAVE_SENDFILE64) +# define xsendfile sendfile64 +# else +# define xsendfile sendfile +# endif +#elif defined(HAVE_SENDFILE) +# define xsendfile sendfile +#elif defined(HAVE_SENDFILE64) +# define xsendfile sendfile64 +#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 = lseek (in_fd, 0, SEEK_CUR); /* TODO: lseek64 or llseek.. */ + 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 && lseek (in_fd, *offset, SEEK_SET) != *offset) //* 64bit version of lseek... + 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 + +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 && lseek (in_fd, *offset, SEEK_SET) != *offset) //* 64bit version of lseek... + 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; +} + +/* ------------------------------------------------------------------- */ typedef struct httpd_xtn_t httpd_xtn_t; struct httpd_xtn_t { - const qse_httpd_cbs_t* orgcbs; + SSL_CTX* ssl_ctx; }; +/* ------------------------------------------------------------------- */ + +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 (); +} + +/* ------------------------------------------------------------------- */ +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) + { + return SSL_read (client->handle2.ptr, buf, bufsize); + } + else + { + return read (client->handle.i, buf, bufsize); + } +} + +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) + { + return SSL_write (client->handle2.ptr, buf, bufsize); + } + else + { + return write (client->handle.i, buf, bufsize); + } +} + +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) + { + return xsendfile_ssl (client->handle2.ptr, handle.i, offset, count); + } + 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) + { + int ret; + SSL* ssl; + + if (client->handle2.ptr) + { + ssl = client->handle2.ptr; + } + 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 be closed */ + 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; + } + } + + return 1; /* accept completed */ +} + +static void client_closed (qse_httpd_t* httpd, qse_httpd_client_t* client) +{ + if (client->secure) + { + if (client->handle2.ptr) + { + SSL_shutdown ((SSL*)client->handle2.ptr); /* is this needed? */ + SSL_free ((SSL*)client->handle2.ptr); + } + } +} + +/* ------------------------------------------------------------------- */ static qse_htb_walk_t walk (qse_htb_t* htb, qse_htb_pair_t* pair, void* ctx) { qse_printf (QSE_T("HEADER OK %d[%hs] %d[%hs]\n"), (int)QSE_HTB_KLEN(pair), QSE_HTB_KPTR(pair), (int)QSE_HTB_VLEN(pair), QSE_HTB_VPTR(pair)); return QSE_HTB_WALK_FORWARD; } -static int handle_request ( - qse_httpd_t* httpd, qse_httpd_client_t* client, qse_htre_t* req) +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* x; + qse_httpd_task_t* task; + int content_received; -#if 0 - httpd_xtn_t* xtn = (httpd_xtn_t*) qse_httpd_getxtn (httpd); - return xtn->orgcbs->handle_request (httpd, client, req); -#endif + method = qse_htre_getqmethod(req); + content_received = (qse_htre_getcontentlen(req) > 0); qse_printf (QSE_T("================================\n")); -qse_printf (QSE_T("[%lu] REQUEST ==> [%hs] version[%d.%d] method[%d]\n"), +qse_printf (QSE_T("[%lu] %hs REQUEST ==> [%hs] version[%d.%d] method[%d]\n"), (unsigned long)time(NULL), + (peek? QSE_MT("PEEK"): QSE_MT("HANDLE")), qse_htre_getqpathptr(req), qse_htre_getmajorversion(req), qse_htre_getminorversion(req), - qse_htre_getqmethod(req) + method ); -if (qse_htre_getqparamlen(req) > 0) -{ -qse_printf (QSE_T("PARAMS ==> [%hs]\n"), qse_htre_getqparamptr(req)); -} - +if (qse_htre_getqparamlen(req) > 0) qse_printf (QSE_T("PARAMS ==> [%hs]\n"), qse_htre_getqparamptr(req)); qse_htb_walk (&req->hdrtab, walk, QSE_NULL); -if (QSE_MBS_LEN(&req->content) > 0) +if (qse_htre_getcontentlen(req) > 0) { -qse_printf (QSE_T("content = [%.*S]\n"), - (int)QSE_MBS_LEN(&req->content), - QSE_MBS_PTR(&req->content)); + qse_printf (QSE_T("CONTENT before discard = [%.*S]\n"), (int)qse_htre_getcontentlen(req), qse_htre_getcontentptr(req)); } - method = qse_htre_getqmethod (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.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; + } + } + } + +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) { @@ -66,50 +361,72 @@ qse_printf (QSE_T("content = [%.*S]\n"), if (dot && qse_mbscmp (dot, QSE_MT(".cgi")) == 0) { - /* cgi */ - x = qse_httpd_entaskcgi ( - httpd, client, QSE_NULL, qpath, req); - if (x == QSE_NULL) goto oops; + if (peek) + { + /* cgi */ + task = qse_httpd_entaskcgi ( + httpd, client, QSE_NULL, qpath, req); + if (task == QSE_NULL) goto oops; + } + return 0; } else if (dot && qse_mbscmp (dot, QSE_MT(".nph")) == 0) { - /* nph-cgi */ - x = qse_httpd_entasknph ( - httpd, client, QSE_NULL, qpath, req); - if (x == QSE_NULL) goto oops; + if (peek) + { + /* nph-cgi */ + task = qse_httpd_entasknph ( + httpd, client, QSE_NULL, qpath, req); + if (task == QSE_NULL) goto oops; + } + return 0; } else { - /* file or directory */ - x = qse_httpd_entaskpath ( - httpd, client, QSE_NULL, qpath, req); - if (x == QSE_NULL) goto oops; + if (!peek) + { + /* file or directory */ + task = qse_httpd_entaskpath ( + httpd, client, QSE_NULL, qpath, req); + if (task == QSE_NULL) goto oops; + } } } else { - x = qse_httpd_entaskerror (httpd, client, QSE_NULL, 405, req); - if (x == QSE_NULL) goto oops; + if (!peek) + { + task = qse_httpd_entaskerror (httpd, client, QSE_NULL, 405, req); + if (task == QSE_NULL) goto oops; + } } if (!req->attr.keepalive) { - x = qse_httpd_entaskdisconnect (httpd, client, QSE_NULL); - if (x == QSE_NULL) goto oops; + if (!peek) + { + task = qse_httpd_entaskdisconnect (httpd, client, QSE_NULL); + if (task == QSE_NULL) goto oops; + } } return 0; oops: - /*qse_httpd_markclientbad (httpd, client);*/ - return 0; + /*qse_httpd_markbadclient (httpd, client);*/ + return 0; /* TODO: return failure??? */ } -static int handle_expect_continue ( +static int peek_request ( qse_httpd_t* httpd, qse_httpd_client_t* client, qse_htre_t* req) { - httpd_xtn_t* xtn = (httpd_xtn_t*) qse_httpd_getxtn (httpd); - return xtn->orgcbs->handle_expect_continue (httpd, client, req); + return process_request (httpd, client, req, 1); +} + +static int handle_request ( + qse_httpd_t* httpd, qse_httpd_client_t* client, qse_htre_t* req) +{ + return process_request (httpd, client, req, 0); } const qse_mchar_t* get_mime_type (qse_httpd_t* httpd, const qse_mchar_t* path) @@ -127,70 +444,82 @@ int list_directory (qse_httpd_t* httpd, const qse_mchar_t* path) return 404; } -static qse_httpd_t* httpd = NULL; - -static void sigint (int sig) -{ - qse_httpd_stop (httpd); -} - static qse_httpd_cbs_t httpd_cbs = { + /* client connection */ + { client_recv, client_send, client_sendfile, + client_accepted, client_closed }, + + /* http request */ + peek_request, handle_request, - handle_expect_continue, + get_mime_type, list_directory }; +static qse_httpd_t* g_httpd = QSE_NULL; + +static void sigint (int sig) +{ + if (g_httpd) qse_httpd_stop (g_httpd); +} + int httpd_main (int argc, qse_char_t* argv[]) { - int n; + qse_httpd_t* httpd = QSE_NULL; httpd_xtn_t* xtn; + int ret = -1, i; + int ssl_xtn_inited = 0; if (argc <= 1) { qse_fprintf (QSE_STDERR, QSE_T("Usage: %s ...\n"), argv[0]); - return -1; + goto oops; } httpd = qse_httpd_open (QSE_MMGR_GETDFL(), QSE_SIZEOF(httpd_xtn_t)); if (httpd == QSE_NULL) { qse_fprintf (QSE_STDERR, QSE_T("Cannot open httpd\n")); - return -1; + goto oops; } xtn = (httpd_xtn_t*)qse_httpd_getxtn (httpd); - xtn->orgcbs = qse_httpd_getcbs (httpd); - for (n = 1; n < argc; n++) + if (init_xtn_ssl (xtn, "http01.pem", "http01.key") <= -1) { - if (qse_httpd_addlistener (httpd, argv[n]) <= -1) + qse_fprintf (QSE_STDERR, QSE_T("Cannot open httpd\n")); + goto oops; + } + ssl_xtn_inited = 1; + + for (i = 1; i < argc; i++) + { + if (qse_httpd_addlistener (httpd, argv[i]) <= -1) { qse_fprintf (QSE_STDERR, - QSE_T("Failed to add httpd listener - %s\n"), argv[n]); - qse_httpd_close (httpd); - return -1; + QSE_T("Failed to add httpd listener - %s\n"), argv[i]); + goto oops; } } - qse_httpd_setcbs (httpd, &httpd_cbs); - + g_httpd = httpd; signal (SIGINT, sigint); signal (SIGPIPE, SIG_IGN); - n = qse_httpd_loop (httpd, 1); + ret = qse_httpd_loop (httpd, &httpd_cbs, 0); signal (SIGINT, SIG_DFL); signal (SIGPIPE, SIG_DFL); + g_httpd = QSE_NULL; - if (n <= -1) - { - qse_fprintf (QSE_STDERR, QSE_T("Httpd error\n")); - } + if (ret <= -1) qse_fprintf (QSE_STDERR, QSE_T("Httpd error\n")); - qse_httpd_close (httpd); - return n; +oops: + if (ssl_xtn_inited) fini_xtn_ssl (xtn); + if (httpd) qse_httpd_close (httpd); + return ret; } int qse_main (int argc, qse_achar_t* argv[])