From adb9f387f9d3072275f0a0b01c5ff1ac52ed87b6 Mon Sep 17 00:00:00 2001 From: hyung-hwan Date: Wed, 12 Sep 2012 15:47:41 +0000 Subject: [PATCH] enhanced httpd a bit --- qse/include/qse/cmn/path.h | 112 ++++++- qse/include/qse/net/htrd.h | 11 +- qse/include/qse/net/http.h | 13 + qse/include/qse/net/httpd.h | 21 +- qse/lib/cmn/path-canon.c | 425 ++++++++++++++++++++---- qse/lib/net/htrd.c | 3 + qse/lib/net/http.c | 55 +++ qse/lib/net/httpd-cgi.c | 6 +- qse/lib/net/httpd-dir.c | 392 +++++++++++----------- qse/lib/net/httpd-file.c | 13 +- qse/lib/net/httpd-proxy.c | 22 +- qse/lib/net/httpd-std.c | 66 ++-- qse/lib/net/httpd-task.c | 42 ++- qse/lib/net/httpd.c | 86 +++-- qse/lib/net/httpd.h | 3 +- qse/samples/net/Makefile.am | 8 +- qse/samples/net/Makefile.in | 33 +- qse/samples/net/{http01.c => httpd01.c} | 4 +- qse/samples/net/httpd02.c | 249 ++++++++++++++ 19 files changed, 1144 insertions(+), 420 deletions(-) rename qse/samples/net/{http01.c => httpd01.c} (94%) create mode 100644 qse/samples/net/httpd02.c diff --git a/qse/include/qse/cmn/path.h b/qse/include/qse/cmn/path.h index 706852f8..50950a42 100644 --- a/qse/include/qse/cmn/path.h +++ b/qse/include/qse/cmn/path.h @@ -70,34 +70,34 @@ const qse_wchar_t* qse_wcsbasename ( ); /** - * The qse_isabspath() function determines if a path name is absolute. + * The qse_ismbsabspath() function determines if a path name is absolute. * A path name beginning with a segment separator is absolute. * On Win32/OS2/DOS, it also returns 1 if a path name begins with a drive * letter followed by a colon. * @return 1 if absolute, 0 if not. */ -int qse_isabspath ( - const qse_char_t* path +int qse_ismbsabspath ( + const qse_mchar_t* path ); /** - * The qse_isdrivepath() function determines if a path name begins with + * The qse_ismbsdrivepath() function determines if a path name begins with * a drive letter followed by a colon like A:. */ -int qse_isdrivepath ( - const qse_char_t* path +int qse_ismbsdrivepath ( + const qse_mchar_t* path ); /** - * The qse_isdrivecurpath() function determines if a path name is in the form + * The qse_ismbsdrivecurpath() function determines if a path name is in the form * of a drive letter followed by a colon like A:, without any trailing path. */ -int qse_isdrivecurpath ( - const qse_char_t* path +int qse_ismbsdrivecurpath ( + const qse_mchar_t* path ); /** - * The qse_canonpath() function canonicalizes a path name @a path by deleting + * The qse_canonmbspath() function canonicalizes a path name @a path by deleting * unnecessary path segments from it and stores the result to a memory buffer * pointed to by @a canon. Canonicalization is purely performed on the path * name without refering to actual file systems. It null-terminates the @@ -105,9 +105,9 @@ int qse_isdrivecurpath ( * the terminating null. * * @code - * qse_char_t buf[64]; - * qse_canonpath ("/usr/local/../bin/sh", buf); - * qse_printf (QSE_T("%s\n")); // prints /usr/bin/sh + * qse_mchar_t buf[64]; + * qse_canonmbspath (QSE_MT("/usr/local/../bin/sh"), buf); + * qse_printf (QSE_T("%hs\n")); // prints /usr/bin/sh * @endcode * * If #QSE_CANONPATH_EMPTYSINGLEDOT is clear in the @a flags, a single dot @@ -129,12 +129,90 @@ int qse_isdrivecurpath ( * @return number of characters in the resulting canonical path excluding * the terminating null. */ -qse_size_t qse_canonpath ( - const qse_char_t* path, - qse_char_t* canon, - int flags +qse_size_t qse_canonmbspath ( + const qse_mchar_t* path, + qse_mchar_t* canon, + int flags ); +/** + * The qse_iswcsabspath() function determines if a path name is absolute. + * A path name beginning with a segment separator is absolute. + * On Win32/OS2/DOS, it also returns 1 if a path name begins with a drive + * letter followed by a colon. + * @return 1 if absolute, 0 if not. + */ +int qse_iswcsabspath ( + const qse_wchar_t* path +); + +/** + * The qse_iswcsdrivepath() function determines if a path name begins with + * a drive letter followed by a colon like A:. + */ +int qse_iswcsdrivepath ( + const qse_wchar_t* path +); + +/** + * The qse_iswcsdrivecurpath() function determines if a path name is in the form + * of a drive letter followed by a colon like A:, without any trailing path. + */ +int qse_iswcsdrivecurpath ( + const qse_wchar_t* path +); + +/** + * The qse_canonwcspath() function canonicalizes a path name @a path by deleting + * unnecessary path segments from it and stores the result to a memory buffer + * pointed to by @a canon. Canonicalization is purely performed on the path + * name without refering to actual file systems. It null-terminates the + * canonical path in @a canon and returns the number of characters excluding + * the terminating null. + * + * @code + * qse_wchar_t buf[64]; + * qse_canonwcspath (QSE_WT("/usr/local/../bin/sh"), buf); + * qse_printf (QSE_T("%ls\n")); // prints /usr/bin/sh + * @endcode + * + * If #QSE_CANONPATH_EMPTYSINGLEDOT is clear in the @a flags, a single dot + * is produced if the input @path resolves to the current directory logically. + * For example, dir/.. is canonicalized to a single period; If it is set, + * an empty string is produced. Even a single period as an input produces + * an empty string if it is set. + * + * The output is empty returning 0 regardless of @a flags if the input + * @a path is empty. + * + * The caller must ensure that it is large enough to hold the resulting + * canonical path before calling because this function does not check the + * size of the memory buffer. Since the canonical path cannot be larger + * than the original path, you can simply ensure this by providing a memory + * buffer as long as the number of characters and a terminating null in + * the original path. + * + * @return number of characters in the resulting canonical path excluding + * the terminating null. + */ +qse_size_t qse_canonwcspath ( + const qse_wchar_t* path, + qse_wchar_t* canon, + int flags +); + +#if defined(QSE_CHAR_IS_MCHAR) +# define qse_isabspath(p) qse_ismbsabspath(p) +# define qse_isdrivepath(p) qse_ismbsdrivepath(p) +# define qse_isdrivecurpath(p) qse_ismbsdrivecurpath(p) +# define qse_canonpath(p,c,f) qse_canonmbspath(p,c,f) +#else +# define qse_isabspath(p) qse_iswcsabspath(p) +# define qse_isdrivepath(p) qse_iswcsdrivepath(p) +# define qse_isdrivecurpath(p) qse_iswcsdrivecurpath(p) +# define qse_canonpath(p,c,f) qse_canonwcspath(p,c,f) +#endif + #ifdef __cplusplus } #endif diff --git a/qse/include/qse/net/htrd.h b/qse/include/qse/net/htrd.h index 8a15a47d..9b52c91b 100644 --- a/qse/include/qse/net/htrd.h +++ b/qse/include/qse/net/htrd.h @@ -51,11 +51,12 @@ 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_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 */ - QSE_HTRD_TRAILERS = (1 << 5), /**< store trailers in a separate table */ - QSE_HTRD_STRICT = (1 << 6) /**< be more picky */ + QSE_HTRD_CANONQPATH = (1 << 2), /**< canonicalize the query path */ + QSE_HTRD_PEEKONLY = (1 << 3), /**< trigger a peek callback after headers without processing contents */ + QSE_HTRD_REQUEST = (1 << 4), /**< parse input as a request */ + QSE_HTRD_RESPONSE = (1 << 5), /**< parse input as a response */ + QSE_HTRD_TRAILERS = (1 << 6), /**< store trailers in a separate table */ + QSE_HTRD_STRICT = (1 << 7) /**< be more picky */ }; typedef enum qse_htrd_option_t qse_htrd_option_t; diff --git a/qse/include/qse/net/http.h b/qse/include/qse/net/http.h index 2719c159..85162a8a 100644 --- a/qse/include/qse/net/http.h +++ b/qse/include/qse/net/http.h @@ -187,11 +187,24 @@ int qse_parsehttpdatetime ( ); */ +/* percent-decode a string */ qse_size_t qse_perdechttpstr ( const qse_mchar_t* str, qse_mchar_t* buf ); + +/* percent-encode a string */ +qse_size_t qse_perenchttpstr ( + const qse_mchar_t* str, + qse_mchar_t* buf +); + +qse_mchar_t* qse_perenchttpstrdup ( + const qse_mchar_t* str, + qse_mmgr_t* mmgr +); + #ifdef __cplusplus } #endif diff --git a/qse/include/qse/net/httpd.h b/qse/include/qse/net/httpd.h index 85071d0e..fa7c11c0 100644 --- a/qse/include/qse/net/httpd.h +++ b/qse/include/qse/net/httpd.h @@ -104,14 +104,8 @@ typedef int (*qse_httpd_muxcb_t) ( void* cbarg ); -typedef struct qse_httpd_dirent_t qse_httpd_dirent_t; -struct qse_httpd_dirent_t -{ -}; - - -typedef struct qse_httpd_cbs_t qse_httpd_cbs_t; -struct qse_httpd_cbs_t +typedef struct qse_httpd_scb_t qse_httpd_scb_t; +struct qse_httpd_scb_t { struct { @@ -212,7 +206,11 @@ struct qse_httpd_cbs_t qse_httpd_t* httpd, qse_httpd_client_t* client); /* optional */ } client; +}; +typedef struct qse_httpd_rcb_t qse_httpd_rcb_t; +struct qse_httpd_rcb_t +{ int (*peek_request) ( qse_httpd_t* httpd, qse_httpd_client_t* client, qse_htre_t* req); int (*handle_request) ( @@ -335,6 +333,7 @@ struct qse_httpd_ecb_t qse_httpd_ecb_t* next; }; + #ifdef __cplusplus extern "C" { #endif @@ -396,7 +395,8 @@ void qse_httpd_pushecb ( */ int qse_httpd_loop ( qse_httpd_t* httpd, - qse_httpd_cbs_t* cbs, + qse_httpd_scb_t* scb, + qse_httpd_rcb_t* rcb, qse_ntime_t timeout ); @@ -506,7 +506,7 @@ qse_httpd_task_t* qse_httpd_entaskfile ( qse_htre_t* req ); -qse_httpd_task_t* qse_httpd_entaskdir ( +qse_httpd_task_t* qse_httpd_entaskpath ( qse_httpd_t* httpd, qse_httpd_client_t* client, qse_httpd_task_t* pred, @@ -574,6 +574,7 @@ void* qse_httpd_getxtnstd ( int qse_httpd_loopstd ( qse_httpd_t* httpd, + qse_httpd_rcb_t* rcb, qse_ntime_t timeout ); diff --git a/qse/lib/cmn/path-canon.c b/qse/lib/cmn/path-canon.c index 9869b658..58c4bbc5 100644 --- a/qse/lib/cmn/path-canon.c +++ b/qse/lib/cmn/path-canon.c @@ -20,63 +20,67 @@ #include +/* ------------------------------------------------------------------ */ +/* MBS IMPLEMENTATION */ +/* ------------------------------------------------------------------ */ + + #if defined(_WIN32) || defined(__OS2__) || defined(__DOS__) -# define IS_SEP(c) ((c) == QSE_T('/') || (c) == QSE_T('\\')) +# define IS_MSEP(c) ((c) == QSE_MT('/') || (c) == QSE_MT('\\')) #else -# define IS_SEP(c) ((c) == QSE_T('/')) +# define IS_MSEP(c) ((c) == QSE_MT('/')) #endif -#define IS_NIL(c) ((c) == QSE_T('\0')) -#define IS_SEP_OR_NIL(c) (IS_SEP(c) || IS_NIL(c)) +#define IS_MNIL(c) ((c) == QSE_MT('\0')) +#define IS_MSEP_OR_MNIL(c) (IS_MSEP(c) || IS_MNIL(c)) -#define ISDRIVE(s) \ - (((s[0] >= QSE_T('A') && s[0] <= QSE_T('Z')) || \ - (s[0] >= QSE_T('a') && s[0] <= QSE_T('z'))) && \ - s[1] == QSE_T(':')) +#define IS_MDRIVE(s) \ + (((s[0] >= QSE_MT('A') && s[0] <= QSE_MT('Z')) || \ + (s[0] >= QSE_MT('a') && s[0] <= QSE_MT('z'))) && \ + s[1] == QSE_MT(':')) -int qse_isabspath (const qse_char_t* path) +int qse_ismbsabspath (const qse_mchar_t* path) { - if (IS_SEP(path[0])) return 1; + if (IS_MSEP(path[0])) return 1; #if defined(_WIN32) || defined(__OS2__) || defined(__DOS__) /* a drive like c:tmp is absolute in positioning the drive. * but the path within the drive is kind of relative */ - if (ISDRIVE(path)) return 1; + if (IS_MDRIVE(path)) return 1; #endif return 0; - } -int qse_isdrivepath (const qse_char_t* path) +int qse_ismbsdrivepath (const qse_mchar_t* path) { #if defined(_WIN32) || defined(__OS2__) || defined(__DOS__) - if (ISDRIVE(path)) return 1; + if (IS_MDRIVE(path)) return 1; #endif return 0; } -int qse_isdrivecurpath (const qse_char_t* path) +int qse_ismbsdrivecurpath (const qse_mchar_t* path) { #if defined(_WIN32) || defined(__OS2__) || defined(__DOS__) - if (ISDRIVE(path) && path[2] == QSE_T('\0')) return 1; + if (IS_MDRIVE(path) && path[2] == QSE_MT('\0')) return 1; #endif return 0; } -qse_size_t qse_canonpath (const qse_char_t* path, qse_char_t* canon, int flags) +qse_size_t qse_canonmbspath (const qse_mchar_t* path, qse_mchar_t* canon, int flags) { - const qse_char_t* ptr; - qse_char_t* dst; - qse_char_t* non_root_start; + const qse_mchar_t* ptr; + qse_mchar_t* dst; + qse_mchar_t* non_root_start; int has_root = 0; #if defined(_WIN32) || defined(__OS2__) || defined(__DOS__) int is_drive = 0; #endif qse_size_t canon_len; - if (path[0] == QSE_T('\0')) + if (path[0] == QSE_MT('\0')) { /* if the source is empty, no translation is needed */ - canon[0] = QSE_T('\0'); + canon[0] = QSE_MT('\0'); return 0; } @@ -84,31 +88,31 @@ qse_size_t qse_canonpath (const qse_char_t* path, qse_char_t* canon, int flags) dst = canon; #if defined(_WIN32) || defined(__OS2__) || defined(__DOS__) - if (ISDRIVE(ptr)) + if (IS_MDRIVE(ptr)) { /* handle drive letter */ *dst++ = *ptr++; /* drive letter */ *dst++ = *ptr++; /* colon */ is_drive = 1; - if (IS_SEP(*ptr)) + if (IS_MSEP(*ptr)) { *dst++ = *ptr++; /* root directory */ has_root = 1; } } - else if (IS_SEP(*ptr)) + else if (IS_MSEP(*ptr)) { *dst++ = *ptr++; /* root directory */ has_root = 1; #if defined(_WIN32) /* handle UNC path for Windows */ - if (IS_SEP(*ptr)) + if (IS_MSEP(*ptr)) { *dst++ = *ptr++; - if (IS_SEP_OR_NIL(*ptr)) + if (IS_MSEP_OR_MNIL(*ptr)) { /* if there is another separator after \\, * it's not an UNC path. */ @@ -117,14 +121,14 @@ qse_size_t qse_canonpath (const qse_char_t* path, qse_char_t* canon, int flags) else { /* if it starts with \\, process host name */ - do { *dst++ = *ptr++; } while (!IS_SEP_OR_NIL(*ptr)); - if (IS_SEP(*ptr)) *dst++ = *ptr++; + do { *dst++ = *ptr++; } while (!IS_MSEP_OR_MNIL(*ptr)); + if (IS_MSEP(*ptr)) *dst++ = *ptr++; } } #endif } #else - if (IS_SEP(*ptr)) + if (IS_MSEP(*ptr)) { *dst++ = *ptr++; /* root directory */ has_root = 1; @@ -137,30 +141,30 @@ qse_size_t qse_canonpath (const qse_char_t* path, qse_char_t* canon, int flags) do { - const qse_char_t* seg; + const qse_mchar_t* seg; qse_size_t seglen; /* skip duplicate separators */ - while (IS_SEP(*ptr)) ptr++; + while (IS_MSEP(*ptr)) ptr++; /* end of path reached */ - if (*ptr == QSE_T('\0')) break; + if (*ptr == QSE_MT('\0')) break; /* find the next segment */ seg = ptr; - while (!IS_SEP_OR_NIL(*ptr)) ptr++; + while (!IS_MSEP_OR_MNIL(*ptr)) ptr++; seglen = ptr - seg; /* handle the segment */ - if (seglen == 1 && seg[0] == QSE_T('.')) + if (seglen == 1 && seg[0] == QSE_MT('.')) { /* eat up . */ } else if (!(flags & QSE_CANONPATH_KEEPDOUBLEDOTS) && - seglen == 2 && seg[0] == QSE_T('.') && seg[1] == QSE_T('.')) + seglen == 2 && seg[0] == QSE_MT('.') && seg[1] == QSE_MT('.')) { /* eat up the previous segment */ - qse_char_t* tmp; + qse_mchar_t* tmp; tmp = dst; if (tmp > non_root_start) @@ -173,7 +177,7 @@ qse_size_t qse_canonpath (const qse_char_t* path, qse_char_t* canon, int flags) while (tmp > non_root_start) { tmp--; - if (IS_SEP(*tmp)) + if (IS_MSEP(*tmp)) { tmp++; /* position it next to the separator */ break; @@ -212,7 +216,7 @@ qse_size_t qse_canonpath (const qse_char_t* path, qse_char_t* canon, int flags) goto normal; } - if (prevlen == 3 && tmp[0] == QSE_T('.') && tmp[1] == QSE_T('.')) + if (prevlen == 3 && tmp[0] == QSE_MT('.') && tmp[1] == QSE_MT('.')) { /* nothing to eat away because the previous segment is ../ * @@ -234,7 +238,7 @@ qse_size_t qse_canonpath (const qse_char_t* path, qse_char_t* canon, int flags) { normal: while (seg < ptr) *dst++ = *seg++; - if (IS_SEP(*ptr)) + if (IS_MSEP(*ptr)) { /* this segment ended with a separator */ *dst++ = *seg++; /* copy the separator */ @@ -244,8 +248,8 @@ qse_size_t qse_canonpath (const qse_char_t* path, qse_char_t* canon, int flags) } while (1); - if (dst > non_root_start && IS_SEP(dst[-1]) && - ((flags & QSE_CANONPATH_DROPTRAILINGSEP) || !IS_SEP(ptr[-1]))) + if (dst > non_root_start && IS_MSEP(dst[-1]) && + ((flags & QSE_CANONPATH_DROPTRAILINGSEP) || !IS_MSEP(ptr[-1]))) { /* if the canoncal path composed so far ends with a separator * and the original path didn't end with the separator, delete @@ -255,18 +259,18 @@ qse_size_t qse_canonpath (const qse_char_t* path, qse_char_t* canon, int flags) * dst > non_root_start: * there is at least 1 character after the root directory * part. - * IS_SEP(dst[-1]): + * IS_MSEP(dst[-1]): * the canonical path ends with a separator. - * IS_SEP(ptr[-1]): + * IS_MSEP(ptr[-1]): * the origial path ends with a separator. */ - dst[-1] = QSE_T('\0'); + dst[-1] = QSE_MT('\0'); canon_len = dst - canon - 1; } else { /* just null-terminate the canonical path normally */ - dst[0] = QSE_T('\0'); + dst[0] = QSE_MT('\0'); canon_len = dst - canon; } @@ -276,8 +280,8 @@ qse_size_t qse_canonpath (const qse_char_t* path, qse_char_t* canon, int flags) { /* when resolving to a single dot, a trailing separator is not * retained though the orignal path name contains it. */ - canon[0] = QSE_T('.'); - canon[1] = QSE_T('\0'); + canon[0] = QSE_MT('.'); + canon[1] = QSE_MT('\0'); canon_len = 1; } } @@ -302,21 +306,328 @@ qse_size_t qse_canonpath (const qse_char_t* path, qse_char_t* canon, int flags) /* i don't have to retain a trailing separator * if the last segment is double slashes because * the double slahses indicate a directory obviously */ - if (canon[canon_len-3] == QSE_T('.') && - canon[canon_len-2] == QSE_T('.') && - IS_SEP(canon[canon_len-1])) + if (canon[canon_len-3] == QSE_MT('.') && + canon[canon_len-2] == QSE_MT('.') && + IS_MSEP(canon[canon_len-1])) { - canon[--canon_len] = QSE_T('\0'); + canon[--canon_len] = QSE_MT('\0'); } } else if (canon_len > adj_base_len) { - if (IS_SEP(canon[canon_len-4]) && - canon[canon_len-3] == QSE_T('.') && - canon[canon_len-2] == QSE_T('.') && - IS_SEP(canon[canon_len-1])) + if (IS_MSEP(canon[canon_len-4]) && + canon[canon_len-3] == QSE_MT('.') && + canon[canon_len-2] == QSE_MT('.') && + IS_MSEP(canon[canon_len-1])) { - canon[--canon_len] = QSE_T('\0'); + canon[--canon_len] = QSE_MT('\0'); + } + } + } + + return canon_len; +} + + +/* ------------------------------------------------------------------ */ +/* WCS IMPLEMENTATION */ +/* ------------------------------------------------------------------ */ +#if defined(_WIN32) || defined(__OS2__) || defined(__DOS__) +# define IS_WSEP(c) ((c) == QSE_WT('/') || (c) == QSE_WT('\\')) +#else +# define IS_WSEP(c) ((c) == QSE_WT('/')) +#endif + +#define IS_WNIL(c) ((c) == QSE_WT('\0')) +#define IS_WSEP_OR_WNIL(c) (IS_WSEP(c) || IS_WNIL(c)) + +#define IS_WDRIVE(s) \ + (((s[0] >= QSE_WT('A') && s[0] <= QSE_WT('Z')) || \ + (s[0] >= QSE_WT('a') && s[0] <= QSE_WT('z'))) && \ + s[1] == QSE_WT(':')) + +int qse_iswcsabspath (const qse_wchar_t* path) +{ + if (IS_WSEP(path[0])) return 1; +#if defined(_WIN32) || defined(__OS2__) || defined(__DOS__) + /* a drive like c:tmp is absolute in positioning the drive. + * but the path within the drive is kind of relative */ + if (IS_WDRIVE(path)) return 1; +#endif + return 0; +} + +int qse_iswcsdrivepath (const qse_wchar_t* path) +{ +#if defined(_WIN32) || defined(__OS2__) || defined(__DOS__) + if (IS_WDRIVE(path)) return 1; +#endif + return 0; +} + +int qse_iswcsdrivecurpath (const qse_wchar_t* path) +{ +#if defined(_WIN32) || defined(__OS2__) || defined(__DOS__) + if (IS_WDRIVE(path) && path[2] == QSE_WT('\0')) return 1; +#endif + return 0; +} + +qse_size_t qse_canonwcspath (const qse_wchar_t* path, qse_wchar_t* canon, int flags) +{ + const qse_wchar_t* ptr; + qse_wchar_t* dst; + qse_wchar_t* non_root_start; + int has_root = 0; +#if defined(_WIN32) || defined(__OS2__) || defined(__DOS__) + int is_drive = 0; +#endif + qse_size_t canon_len; + + if (path[0] == QSE_WT('\0')) + { + /* if the source is empty, no translation is needed */ + canon[0] = QSE_WT('\0'); + return 0; + } + + ptr = path; + dst = canon; + +#if defined(_WIN32) || defined(__OS2__) || defined(__DOS__) + if (IS_WDRIVE(ptr)) + { + /* handle drive letter */ + *dst++ = *ptr++; /* drive letter */ + *dst++ = *ptr++; /* colon */ + + is_drive = 1; + if (IS_WSEP(*ptr)) + { + *dst++ = *ptr++; /* root directory */ + has_root = 1; + } + } + else if (IS_WSEP(*ptr)) + { + *dst++ = *ptr++; /* root directory */ + has_root = 1; + + #if defined(_WIN32) + /* handle UNC path for Windows */ + if (IS_WSEP(*ptr)) + { + *dst++ = *ptr++; + + if (IS_WSEP_OR_WNIL(*ptr)) + { + /* if there is another separator after \\, + * it's not an UNC path. */ + dst--; + } + else + { + /* if it starts with \\, process host name */ + do { *dst++ = *ptr++; } while (!IS_WSEP_OR_WNIL(*ptr)); + if (IS_WSEP(*ptr)) *dst++ = *ptr++; + } + } + #endif + } +#else + if (IS_WSEP(*ptr)) + { + *dst++ = *ptr++; /* root directory */ + has_root = 1; + } +#endif + + /* non_root_start points to the beginning of the canonicalized + * path excluding the root directory part. */ + non_root_start = dst; + + do + { + const qse_wchar_t* seg; + qse_size_t seglen; + + /* skip duplicate separators */ + while (IS_WSEP(*ptr)) ptr++; + + /* end of path reached */ + if (*ptr == QSE_WT('\0')) break; + + /* find the next segment */ + seg = ptr; + while (!IS_WSEP_OR_WNIL(*ptr)) ptr++; + seglen = ptr - seg; + + /* handle the segment */ + if (seglen == 1 && seg[0] == QSE_WT('.')) + { + /* eat up . */ + } + else if (!(flags & QSE_CANONPATH_KEEPDOUBLEDOTS) && + seglen == 2 && seg[0] == QSE_WT('.') && seg[1] == QSE_WT('.')) + { + /* eat up the previous segment */ + qse_wchar_t* tmp; + + tmp = dst; + if (tmp > non_root_start) + { + /* there is a previous segment. */ + + tmp--; /* skip the separator just before .. */ + + /* find the beginning of the previous segment */ + while (tmp > non_root_start) + { + tmp--; + if (IS_WSEP(*tmp)) + { + tmp++; /* position it next to the separator */ + break; + } + } + } + + if (has_root) + { + /* + * Eat up the previous segment if it exists. + * + * If it doesn't exist, tmp == dst so dst = tmp + * keeps dst unchanged. If it exists, + * tmp != dst. so dst = tmp changes dst. + * + * path /abc/def/.. + * ^ ^ + * seg ptr + * + * canon /abc/def/ + * ^ ^ + * tmp dst + */ + dst = tmp; + } + else + { + qse_size_t prevlen; + + prevlen = dst - tmp; + + if (/*tmp == non_root_start &&*/ prevlen == 0) + { + /* there is no previous segment */ + goto normal; + } + + if (prevlen == 3 && tmp[0] == QSE_WT('.') && tmp[1] == QSE_WT('.')) + { + /* nothing to eat away because the previous segment is ../ + * + * path ../../ + * ^ ^ + * seg ptr + * + * canon ../ + * ^ ^ + * tmp dst + */ + goto normal; + } + + dst = tmp; + } + } + else + { + normal: + while (seg < ptr) *dst++ = *seg++; + if (IS_WSEP(*ptr)) + { + /* this segment ended with a separator */ + *dst++ = *seg++; /* copy the separator */ + ptr++; /* move forward the pointer */ + } + } + } + while (1); + + if (dst > non_root_start && IS_WSEP(dst[-1]) && + ((flags & QSE_CANONPATH_DROPTRAILINGSEP) || !IS_WSEP(ptr[-1]))) + { + /* if the canoncal path composed so far ends with a separator + * and the original path didn't end with the separator, delete + * the ending separator. + * also delete it if QSE_CANONPATH_DROPTRAILINGSEP is set. + * + * dst > non_root_start: + * there is at least 1 character after the root directory + * part. + * IS_WSEP(dst[-1]): + * the canonical path ends with a separator. + * IS_WSEP(ptr[-1]): + * the origial path ends with a separator. + */ + dst[-1] = QSE_WT('\0'); + canon_len = dst - canon - 1; + } + else + { + /* just null-terminate the canonical path normally */ + dst[0] = QSE_WT('\0'); + canon_len = dst - canon; + } + + if (canon_len <= 0) + { + if (!(flags & QSE_CANONPATH_EMPTYSINGLEDOT)) + { + /* when resolving to a single dot, a trailing separator is not + * retained though the orignal path name contains it. */ + canon[0] = QSE_WT('.'); + canon[1] = QSE_WT('\0'); + canon_len = 1; + } + } + else + { + /* drop a traling separator if the last segment is + * double slashes */ + + int adj_base_len = 3; + +#if defined(_WIN32) || defined(__OS2__) || defined(__DOS__) + if (is_drive && !has_root) + { + /* A path like A:..\\\ need some adjustment for + * finalization below. */ + adj_base_len += 2; + } +#endif + + if (canon_len == adj_base_len) + { + /* i don't have to retain a trailing separator + * if the last segment is double slashes because + * the double slahses indicate a directory obviously */ + if (canon[canon_len-3] == QSE_WT('.') && + canon[canon_len-2] == QSE_WT('.') && + IS_WSEP(canon[canon_len-1])) + { + canon[--canon_len] = QSE_WT('\0'); + } + } + else if (canon_len > adj_base_len) + { + if (IS_WSEP(canon[canon_len-4]) && + canon[canon_len-3] == QSE_WT('.') && + canon[canon_len-2] == QSE_WT('.') && + IS_WSEP(canon[canon_len-1])) + { + canon[--canon_len] = QSE_WT('\0'); } } } diff --git a/qse/lib/net/htrd.c b/qse/lib/net/htrd.c index f0f895fa..57016c44 100644 --- a/qse/lib/net/htrd.c +++ b/qse/lib/net/htrd.c @@ -389,6 +389,9 @@ static qse_mchar_t* parse_initial_line ( htrd->re.u.q.param = QSE_NULL; } #endif + + if (htrd->option & QSE_HTRD_CANONQPATH) + qse_canonmbspath (htrd->re.u.q.path, htrd->re.u.q.path, 0); /* skip spaces after the url part */ do { p++; } while (is_space_octet(*p)); diff --git a/qse/lib/net/http.c b/qse/lib/net/http.c index 436bc7c8..663dd040 100644 --- a/qse/lib/net/http.c +++ b/qse/lib/net/http.c @@ -22,6 +22,7 @@ #include #include #include +#include "../cmn/mem.h" int qse_comparehttpversions ( const qse_http_version_t* v1, @@ -224,3 +225,57 @@ qse_size_t qse_perdechttpstr (const qse_mchar_t* str, qse_mchar_t* buf) *out = QSE_MT('\0'); return out - buf; } + +#define IS_UNRESERVED(c) \ + (((c) >= QSE_MT('A') && (c) <= QSE_MT('Z')) || \ + ((c) >= QSE_MT('a') && (c) <= QSE_MT('z')) || \ + (c) == QSE_MT('-') || (c) == QSE_T('_') || \ + (c) == QSE_MT('.') || (c) == QSE_T('~')) + +#define TO_HEX(v) (QSE_MT("0123456789ABCDEF")[(v) & 15]) + +qse_size_t qse_perenchttpstr (const qse_mchar_t* str, qse_mchar_t* buf) +{ + const qse_mchar_t* p = str; + qse_mchar_t* out = buf; + + while (*p != QSE_T('\0')) + { + if (IS_UNRESERVED(*p)) *out++ = *p; + else + { + *out++ = QSE_MT('%'); + *out++ = TO_HEX (*p >> 4); + *out++ = TO_HEX (*p & 15); + } + p++; + } + + *out = QSE_MT('\0'); + return out - buf; +} + +qse_mchar_t* qse_perenchttpstrdup (const qse_mchar_t* str, qse_mmgr_t* mmgr) +{ + qse_mchar_t* buf; + qse_size_t len = 0; + qse_size_t count = 0; + + /* count the number of characters that should be encoded */ + for (len = 0; str[len] != QSE_T('\0'); len++) + { + if (!IS_UNRESERVED(str[len])) count++; + } + + /* if there are no characters to escape, just return the original string */ + if (count <= 0) return (qse_mchar_t*)str; + + /* allocate a buffer of an optimal size for escaping, otherwise */ + buf = QSE_MMGR_ALLOC (mmgr, (len + (count * 2) + 1) * QSE_SIZEOF(*buf)); + if (buf == QSE_NULL) return QSE_NULL; + + /* perform actual escaping */ + qse_perenchttpstr (str, buf); + + return buf; +} diff --git a/qse/lib/net/httpd-cgi.c b/qse/lib/net/httpd-cgi.c index 2d2f904e..343e5196 100644 --- a/qse/lib/net/httpd-cgi.c +++ b/qse/lib/net/httpd-cgi.c @@ -577,7 +577,7 @@ qse_printf (QSE_T("FORWARD: CLEARING REQCON FOR ERROR\n")); if (writable) goto forward; - n = httpd->cbs->mux.writable ( + n = httpd->scb->mux.writable ( httpd, qse_pio_gethandleasubi (&cgi->pio, QSE_PIO_IN), 0); if (n >= 1) { @@ -886,7 +886,7 @@ static QSE_INLINE qse_ssize_t cgi_write_script_output_to_client ( qse_ssize_t n; httpd->errnum = QSE_HTTPD_ENOERR; - n = httpd->cbs->client.send (httpd, client, cgi->buf, cgi->buflen); + n = httpd->scb->client.send (httpd, client, cgi->buf, cgi->buflen); if (n > 0) { QSE_MEMCPY (&cgi->buf[0], &cgi->buf[n], cgi->buflen - n); @@ -1140,7 +1140,7 @@ qse_printf (QSE_T("[cgi_3 sending %d bytes]\n"), (int)count); if (count > 0) { httpd->errnum = QSE_HTTPD_ENOERR; - n = httpd->cbs->client.send (httpd, client, cgi->res_ptr, count); + n = httpd->scb->client.send (httpd, client, cgi->res_ptr, count); if (n <= -1) { diff --git a/qse/lib/net/httpd-dir.c b/qse/lib/net/httpd-dir.c index de9b00e4..b9bdd404 100644 --- a/qse/lib/net/httpd-dir.c +++ b/qse/lib/net/httpd-dir.c @@ -22,6 +22,7 @@ #include "../cmn/mem.h" #include "../cmn/syscall.h" #include +#include #include /* TODO: remove this */ typedef struct task_dir_t task_dir_t; @@ -35,15 +36,24 @@ struct task_dir_t typedef struct task_dseg_t task_dseg_t; struct task_dseg_t { + qse_http_version_t version; + int keepalive; + int chunked; + const qse_mchar_t* path; qse_dir_t* handle; qse_dirent_t* dent; - int header_added; - int footer_pending; +#define HEADER_ADDED (1 << 0) +#define FOOTER_ADDED (1 << 1) +#define FOOTER_PENDING (1 << 2) +#define DIRENT_PENDING (1 << 3) + int state; - /*qse_mchar_t buf[4096];*/ - qse_mchar_t buf[512]; /* TOOD: increate size */ + qse_size_t tcount; /* total directory entries */ + qse_size_t dcount; /* the number of items in the buffer */ + + qse_mchar_t buf[4096]; qse_size_t bufpos; qse_size_t buflen; qse_size_t bufrem; @@ -70,14 +80,88 @@ static void task_fini_dseg ( QSE_CLOSEDIR (ctx->handle); } -static int task_main_dseg_chunked ( +#define SIZE_CHLEN 4 /* the space size to hold the hexadecimal chunk length */ +#define SIZE_CHLENCRLF 2 /* the space size to hold CRLF after the chunk length */ +#define SIZE_CHENDCRLF 2 /* the sapce size to hold CRLF after the chunk data */ + +static QSE_INLINE void close_chunk_data (task_dseg_t* ctx, qse_size_t len) +{ + ctx->chunklen = len; + + /* CHENDCRLF - there is always space for these two. + * reserved with SIZE_CHENDCRLF */ + ctx->buf[ctx->buflen++] = QSE_MT('\r'); + ctx->buf[ctx->buflen++] = QSE_MT('\n'); +} + +static void fill_chunk_length (task_dseg_t* ctx) +{ + int x; + + /* right alignment with space padding on the left */ +/* TODO: change snprintf to qse_fmtuintmaxtombs() */ + x = snprintf ( + ctx->buf, (SIZE_CHLEN + SIZE_CHLENCRLF) - 1, + QSE_MT("%*lX"), (int)(SIZE_CHLEN + SIZE_CHLENCRLF - 2), + (unsigned long)(ctx->chunklen - (SIZE_CHLEN + SIZE_CHLENCRLF))); + + /* i don't check the error of snprintf because i've secured the + * suffient space for the chunk length at the beginning of the buffer */ + + /* CHLENCRLF */ + ctx->buf[x] = QSE_MT('\r'); + ctx->buf[x+1] = QSE_MT('\n'); + + /* skip leading space padding */ + QSE_ASSERT (ctx->bufpos == 0); + while (ctx->buf[ctx->bufpos] == QSE_MT(' ')) ctx->bufpos++; +} + +static int add_footer (task_dseg_t* ctx) +{ + int x; + + if (ctx->chunked) + { + x = snprintf ( + &ctx->buf[ctx->buflen], ctx->bufrem, + QSE_MT("Total %lu entries\r\n0\r\n"), (unsigned long)ctx->tcount); + } + else + { + x = snprintf ( + &ctx->buf[ctx->buflen], ctx->bufrem, + QSE_MT("Total %lu entries"), (unsigned long)ctx->tcount); + } + + if (x == -1 || x >= ctx->bufrem) + { + /* return an error if the buffer is too small to hold the + * trailing footer. you need to increate the buffer size */ + return -1; + } + + ctx->buflen += x; + ctx->bufrem -= x; + + /* -5 for \r\n0\r\n added above */ + if (ctx->chunked) close_chunk_data (ctx, ctx->buflen - 5); + + return 0; +} + +static int task_main_dseg ( qse_httpd_t* httpd, qse_httpd_client_t* client, qse_httpd_task_t* task) { task_dseg_t* ctx = (task_dseg_t*)task->ctx; qse_ssize_t n; int x; - if (ctx->bufpos < ctx->buflen) goto send_dirlist; + if (ctx->bufpos < ctx->buflen) + { + /* buffer contents not fully sent yet */ + goto send_dirlist; + } /* the buffer size is fixed to QSE_COUNTOF(ctx->buf). * the number of digits need to hold the the size converted to @@ -103,122 +187,149 @@ static int task_main_dseg_chunked ( * the size of the buffer arrray, you should check this size. */ -#define SIZE_CHLEN 4 -#define SIZE_CHLENCRLF 2 -#define SIZE_CHENDCRLF 2 - - /* reserve space to fill with the chunk length - * 4 for the actual chunk length and +2 for \r\n */ - ctx->buflen = SIZE_CHLEN + SIZE_CHLENCRLF; - - /* free space remaing in the buffer for the chunk data */ - ctx->bufrem = QSE_COUNTOF(ctx->buf) - ctx->buflen - SIZE_CHENDCRLF; - - if (ctx->footer_pending) + /* initialize buffer */ + ctx->dcount = 0; /* reset the entry counter */ + ctx->bufpos = 0; + if (ctx->chunked) { - x = snprintf ( - &ctx->buf[ctx->buflen], - ctx->bufrem, - QSE_MT("\r\n0\r\n")); - if (x == -1 || x >= ctx->bufrem) + /* reserve space to fill with the chunk length + * 4 for the actual chunk length and +2 for \r\n */ + ctx->buflen = SIZE_CHLEN + SIZE_CHLENCRLF; + /* free space remaing in the buffer for the chunk data */ + ctx->bufrem = QSE_COUNTOF(ctx->buf) - ctx->buflen - SIZE_CHENDCRLF; + } + else + { + ctx->buflen = 0; + ctx->bufrem = QSE_COUNTOF(ctx->buf); + } + + if (ctx->state & FOOTER_PENDING) + { + /* only footers yet to be sent */ + if (add_footer (ctx) <= -1) { /* return an error if the buffer is too small to hold the * trailing footer. you need to increate the buffer size */ + httpd->errnum = QSE_HTTPD_EINTERN; return -1; } - ctx->buflen += x; - ctx->chunklen = ctx->buflen - 5; /* -5 for \r\n0\r\n added above */ + ctx->state &= ~FOOTER_PENDING; + ctx->state |= FOOTER_ADDED; - /* CHENDCRLF */ - ctx->buf[ctx->buflen++] = QSE_MT('\r'); - ctx->buf[ctx->buflen++] = QSE_MT('\n'); - - goto set_chunklen; + if (ctx->chunked) fill_chunk_length (ctx); + goto send_dirlist; } - if (!ctx->header_added) + if (!(ctx->state & HEADER_ADDED)) { - /* compose the header since this is the first time. */ + int is_root; + is_root = (qse_mbscmp (ctx->path, QSE_MT("/")) == 0); + + /* compose the header since this is the first time. */ +/* TODO: page encoding?? utf-8??? or derive name from cmgr or current locale??? */ x = snprintf ( - &ctx->buf[ctx->buflen], - ctx->bufrem, - QSE_MT("Directory Listing%s
  • ..
  • "), - ctx->path + &ctx->buf[ctx->buflen], ctx->bufrem, + QSE_MT("%s
      %s"), + ctx->path, (is_root? QSE_MT(""): QSE_MT("
    • ..
    • ")) ); if (x == -1 || x >= ctx->bufrem) { /* return an error if the buffer is too small to hold the header. - * you need to increate the buffer size */ + * you need to increate the buffer size. or i have make the buffer + * dynamic. */ + httpd->errnum = QSE_HTTPD_EINTERN; return -1; } ctx->buflen += x; ctx->bufrem -= x; - ctx->header_added = 1; + ctx->state |= HEADER_ADDED; + ctx->dcount++; } - if (!ctx->dent) + /*if (!ctx->dent) ctx->dent = QSE_READDIR (ctx->handle); */ + if (ctx->state & DIRENT_PENDING) + ctx->state &= ~DIRENT_PENDING; + else ctx->dent = QSE_READDIR (ctx->handle); do { if (!ctx->dent) { - // TODO: check if errno has changed from before QSE_READDIR(). - // and return -1 if so. - x = snprintf ( - &ctx->buf[ctx->buflen], - ctx->bufrem, - QSE_MT("
    \r\n0\r\n")); - if (x == -1 || x >= ctx->bufrem) + /* TODO: check if errno has changed from before QSE_READDIR(). + and return -1 if so. */ + if (add_footer (ctx) <= -1) { - ctx->footer_pending = 1; - ctx->chunklen = ctx->buflen; - - /* CHENDCRLF */ - ctx->buf[ctx->buflen++] = QSE_MT('\r'); - ctx->buf[ctx->buflen++] = QSE_MT('\n'); - } - else - { - ctx->buflen += x; - ctx->chunklen = ctx->buflen - 5; - - /* CHENDCRLF */ - ctx->buf[ctx->buflen++] = QSE_MT('\r'); - ctx->buf[ctx->buflen++] = QSE_MT('\n'); + /* failed to add the footer part */ + if (ctx->chunked) + { + close_chunk_data (ctx, ctx->buflen); + fill_chunk_length (ctx); + } + ctx->state |= FOOTER_PENDING; } + else if (ctx->chunked) fill_chunk_length (ctx); break; } else if (qse_mbscmp (ctx->dent->d_name, QSE_MT(".")) != 0 && qse_mbscmp (ctx->dent->d_name, QSE_MT("..")) != 0) { + qse_mchar_t* encname; + + /* TODO: better buffer management in case there are + * a lot of file names to escape. */ + encname = qse_perenchttpstrdup (ctx->dent->d_name, httpd->mmgr); + if (encname == QSE_NULL) + { + httpd->errnum = QSE_HTTPD_ENOMEM; + return -1; + } + x = snprintf ( &ctx->buf[ctx->buflen], ctx->bufrem, QSE_MT("
  • %s%s
  • "), - ctx->dent->d_name, + encname, (ctx->dent->d_type == DT_DIR? QSE_MT("/"): QSE_MT("")), ctx->dent->d_name, (ctx->dent->d_type == DT_DIR? QSE_MT("/"): QSE_MT("")) ); + + if (encname != ctx->dent->d_name) QSE_MMGR_FREE (httpd->mmgr, encname); + if (x == -1 || x >= ctx->bufrem) { /* buffer not large enough to hold this entry */ - ctx->chunklen = ctx->buflen; + if (ctx->dcount <= 0) + { + /* neither directory entry nor the header + * has been added to the buffer so far. and + * this attempt has failed. the buffer size must + * be too small. you must increase it */ + httpd->errnum = QSE_HTTPD_EINTERN; + return -1; + } - /* CHENDCRLF */ - ctx->buf[ctx->buflen++] = QSE_MT('\r'); - ctx->buf[ctx->buflen++] = QSE_MT('\n'); + if (ctx->chunked) + { + close_chunk_data (ctx, ctx->buflen); + fill_chunk_length (ctx); + } + + ctx->state |= DIRENT_PENDING; break; } else { ctx->buflen += x; ctx->bufrem -= x; + ctx->dcount++; + ctx->tcount++; } } @@ -226,167 +337,41 @@ static int task_main_dseg_chunked ( } while (1); -set_chunklen: - /* right alignment with space padding on the left */ -/* TODO: change snprintf to qse_fmtuintmaxtombs() */ - x = snprintf ( - ctx->buf, (SIZE_CHLEN + SIZE_CHLENCRLF) - 1, - QSE_MT("%*lX"), (int)(SIZE_CHLEN + SIZE_CHLENCRLF - 2), - (unsigned long)(ctx->chunklen - (SIZE_CHLEN + SIZE_CHLENCRLF))); - - /* CHLENCRLF */ - ctx->buf[x] = QSE_MT('\r'); - ctx->buf[x+1] = QSE_MT('\n'); - - /* skip leading space padding */ - for (x = 0; ctx->buf[x] == QSE_MT(' '); x++) ctx->buflen--; - ctx->bufpos = x; send_dirlist: httpd->errnum = QSE_HTTPD_ENOERR; - n = httpd->cbs->client.send ( - httpd, client, &ctx->buf[ctx->bufpos], ctx->buflen); + n = httpd->scb->client.send ( + httpd, client, &ctx->buf[ctx->bufpos], ctx->buflen - ctx->bufpos); if (n <= -1) return -1; /* NOTE if (n == 0), it will enter an infinite loop */ ctx->bufpos += n; - ctx->buflen -= n; - return (ctx->bufpos < ctx->buflen || ctx->footer_pending || ctx->dent)? 1: 0; -} - -static int task_main_dseg_nochunk ( - qse_httpd_t* httpd, qse_httpd_client_t* client, qse_httpd_task_t* task) -{ - task_dseg_t* ctx = (task_dseg_t*)task->ctx; - qse_ssize_t n; - int x; - - if (ctx->bufpos < ctx->buflen) goto send_dirlist; - - ctx->bufpos = 0; - ctx->buflen = 0; - ctx->bufrem = QSE_COUNTOF(ctx->buf); - - if (ctx->footer_pending) - { - x = snprintf ( - &ctx->buf[ctx->buflen], - ctx->bufrem, - "
"); - if (x == -1 || x >= ctx->bufrem) - { - /* return an error if the buffer is too small to hold the - * trailing footer. you need to increate the buffer size */ - return -1; - } - - ctx->buflen += x; - goto send_dirlist; - } - - if (!ctx->header_added) - { - /* compose the header since this is the first time. */ - x = snprintf ( - &ctx->buf[ctx->buflen], - ctx->bufrem, - QSE_MT("Directory Listing%s
  • ..
  • "), - ctx->path - ); - if (x == -1 || x >= ctx->bufrem) - { - /* return an error if the buffer is too small to hold the header. - * you need to increate the buffer size */ - return -1; - } - - ctx->buflen += x; - ctx->bufrem -= x; - ctx->header_added = 1; - } - - if (ctx->dent == QSE_NULL) - ctx->dent = QSE_READDIR (ctx->handle); - - do - { - if (ctx->dent == QSE_NULL) - { - // TODO: check if errno has changed from before QSE_READDIR(). - // and return -1 if so. - x = snprintf ( - &ctx->buf[ctx->buflen], - ctx->bufrem, - "
"); - if (x == -1 || x >= ctx->bufrem) - { - ctx->footer_pending = 1; - } - else - { - ctx->buflen += x; - } - break; - } - else if (qse_mbscmp (ctx->dent->d_name, QSE_MT(".")) != 0 && - qse_mbscmp (ctx->dent->d_name, QSE_MT("..")) != 0) - { - x = snprintf ( - &ctx->buf[ctx->buflen], - ctx->bufrem, - "
  • %s%s
  • ", - ctx->dent->d_name, - (ctx->dent->d_type == DT_DIR? "/": ""), - ctx->dent->d_name, - (ctx->dent->d_type == DT_DIR? "/": "") - ); - if (x == -1 || x >= ctx->bufrem) - { - /* buffer not large enough to hold this entry */ - break; - } - else - { - ctx->buflen += x; - ctx->bufrem -= x; - } - } - - ctx->dent = QSE_READDIR (ctx->handle); - } - while (1); - -send_dirlist: - 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; - ctx->buflen -= n; - return (ctx->bufpos < ctx->buflen || ctx->footer_pending || ctx->dent)? 1: 0; + return (ctx->bufpos < ctx->buflen || (ctx->state & FOOTER_PENDING) || ctx->dent)? 1: 0; } static qse_httpd_task_t* entask_directory_segment ( qse_httpd_t* httpd, qse_httpd_client_t* client, - qse_httpd_task_t* pred, qse_dir_t* handle, const qse_mchar_t* path, int keepalive) + qse_httpd_task_t* pred, qse_dir_t* handle, task_dir_t* dir) { qse_httpd_task_t task; task_dseg_t data; QSE_MEMSET (&data, 0, QSE_SIZEOF(data)); data.handle = handle; - data.path = path; + data.version = dir->version; + data.keepalive = dir->keepalive; + data.chunked = dir->keepalive; + data.path = dir->path; QSE_MEMSET (&task, 0, QSE_SIZEOF(task)); task.init = task_init_dseg; - task.main = (keepalive)? task_main_dseg_chunked: task_main_dseg_nochunk; + task.main = task_main_dseg; task.fini = task_fini_dseg; task.ctx = &data; qse_printf (QSE_T("Debug: entasking directory segment (%d)\n"), client->handle.i); - return qse_httpd_entask (httpd, client, pred, &task, QSE_SIZEOF(data) + qse_mbslen(path) + 1); + return qse_httpd_entask (httpd, client, pred, &task, QSE_SIZEOF(data) + qse_mbslen(data.path) + 1); } /*------------------------------------------------------------------------*/ @@ -398,6 +383,7 @@ static int task_init_dir ( /* deep-copy the context data to the extension area */ QSE_MEMCPY (xtn, task->ctx, QSE_SIZEOF(*xtn)); + qse_mbscpy ((qse_mchar_t*)(xtn + 1), xtn->path); xtn->path = (qse_mchar_t*)(xtn + 1); @@ -429,7 +415,7 @@ static QSE_INLINE int task_main_dir ( (dir->keepalive? QSE_MT("keep-alive"): QSE_MT("close")), (dir->keepalive? QSE_MT("Transfer-Encoding: chunked\r\n"): QSE_MT("")) ); - if (x) x = entask_directory_segment (httpd, client, x, handle, dir->path, dir->keepalive); + if (x) x = entask_directory_segment (httpd, client, x, handle, dir); if (x) return 0; QSE_CLOSEDIR (handle); diff --git a/qse/lib/net/httpd-file.c b/qse/lib/net/httpd-file.c index 84780682..39b22389 100644 --- a/qse/lib/net/httpd-file.c +++ b/qse/lib/net/httpd-file.c @@ -22,6 +22,7 @@ #include "../cmn/mem.h" #include "../cmn/syscall.h" #include +#include #include /* TODO: remove this */ typedef struct task_file_t task_file_t; @@ -54,7 +55,7 @@ static void task_fini_fseg ( qse_httpd_t* httpd, qse_httpd_client_t* client, qse_httpd_task_t* task) { task_fseg_t* ctx = (task_fseg_t*)task->ctx; - httpd->cbs->file.close (httpd, ctx->handle); + httpd->scb->file.close (httpd, ctx->handle); } static int task_main_fseg ( @@ -68,7 +69,7 @@ static int task_main_fseg ( if (count >= ctx->left) count = ctx->left; /* TODO: more adjustment needed for OS with different sendfile semantics... */ - n = httpd->cbs->client.sendfile ( + n = httpd->scb->client.sendfile ( httpd, client, ctx->handle, &ctx->offset, count); if (n <= -1) { @@ -147,7 +148,7 @@ static QSE_INLINE int task_main_file ( qse_printf (QSE_T("opening file %hs\n"), file->path); httpd->errnum = QSE_HTTPD_ENOERR; - if (httpd->cbs->file.stat (httpd, file->path, &st) <= -1) + if (httpd->scb->file.stat (httpd, file->path, &st) <= -1) { int http_errnum; http_errnum = (httpd->errnum == QSE_HTTPD_ENOENT)? 404: @@ -159,7 +160,7 @@ qse_printf (QSE_T("opening file %hs\n"), file->path); } httpd->errnum = QSE_HTTPD_ENOERR; - if (httpd->cbs->file.ropen (httpd, file->path, &handle) <= -1) + if (httpd->scb->file.ropen (httpd, file->path, &handle) <= -1) { int http_errnum; http_errnum = (httpd->errnum == QSE_HTTPD_ENOENT)? 404: @@ -266,11 +267,11 @@ qse_printf (QSE_T("opening file %hs\n"), file->path); } if (x) return 0; - httpd->cbs->file.close (httpd, handle); + httpd->scb->file.close (httpd, handle); return -1; no_file_send: - if (fileopen) httpd->cbs->file.close (httpd, handle); + if (fileopen) httpd->scb->file.close (httpd, handle); return (x == QSE_NULL)? -1: 0; } diff --git a/qse/lib/net/httpd-proxy.c b/qse/lib/net/httpd-proxy.c index 37e6fc2a..5a4f3d4e 100644 --- a/qse/lib/net/httpd-proxy.c +++ b/qse/lib/net/httpd-proxy.c @@ -608,7 +608,7 @@ qse_printf (QSE_T("FORWARD: CLEARING REQCON FOR ERROR\n")); if (writable) goto forward; - n = httpd->cbs->mux.writable (httpd, proxy->peer.handle, 0); + n = httpd->scb->mux.writable (httpd, proxy->peer.handle, 0); if (n == 0) qse_printf (QSE_T("PROXY FORWARD: @@@@@@@@@NOT WRITABLE\n")); if (n >= 1) { @@ -617,7 +617,7 @@ if (n == 0) qse_printf (QSE_T("PROXY FORWARD: @@@@@@@@@NOT WRITABLE\n")); qse_printf (QSE_T("PROXY FORWARD: @@@@@@@@@@WRITING[%.*hs]\n"), (int)QSE_MBS_LEN(proxy->reqfwdbuf), QSE_MBS_PTR(proxy->reqfwdbuf)); - n = httpd->cbs->peer.send ( + n = httpd->scb->peer.send ( httpd, &proxy->peer, QSE_MBS_PTR(proxy->reqfwdbuf), QSE_MBS_LEN(proxy->reqfwdbuf) @@ -866,7 +866,7 @@ static void task_fini_proxy ( task_proxy_t* proxy = (task_proxy_t*)task->ctx; if (proxy->peer_status & PROXY_PEER_OPEN) - httpd->cbs->peer.close (httpd, &proxy->peer); + httpd->scb->peer.close (httpd, &proxy->peer); if (proxy->res) qse_mbs_close (proxy->res); if (proxy->peer_htrd) qse_htrd_close (proxy->peer_htrd); @@ -902,7 +902,7 @@ qse_printf (QSE_T("task_main_proxy_5 trigger[0].mask=%d trigger[1].mask=%d trigg { /* TODO: check if proxy outputs more than content-length if it is set... */ httpd->errnum = QSE_HTTPD_ENOERR; - n = httpd->cbs->client.send (httpd, client, proxy->buf, proxy->buflen); + n = httpd->scb->client.send (httpd, client, proxy->buf, proxy->buflen); if (n <= -1) { /* can't return internal server error any more... */ @@ -950,7 +950,7 @@ qse_printf (QSE_T("task_main_proxy_4 about to read from PEER...\n")); { qse_printf (QSE_T("task_main_proxy_4 reading from PEER... %d %d\n"), (int)proxy->peer_output_length, (int)proxy->peer_output_received); httpd->errnum = QSE_HTTPD_ENOERR; - n = httpd->cbs->peer.recv ( + n = httpd->scb->peer.recv ( httpd, &proxy->peer, &proxy->buf[proxy->buflen], QSE_SIZEOF(proxy->buf) - proxy->buflen @@ -1005,7 +1005,7 @@ qse_printf (QSE_T("task_main_proxy_4 read from PEER...%d\n"), (int)n); * side is writable. it should be safe to write whenever * this task function is called. */ httpd->errnum = QSE_HTTPD_ENOERR; - n = httpd->cbs->client.send (httpd, client, proxy->buf, proxy->buflen); + n = httpd->scb->client.send (httpd, client, proxy->buf, proxy->buflen); if (n <= -1) { /* can't return internal server error any more... */ @@ -1057,7 +1057,7 @@ qse_printf (QSE_T("[PROXY-----3 SENDING XXXXX]\n")); { qse_printf (QSE_T("[proxy_3 sending %d bytes]\n"), (int)count); httpd->errnum = QSE_HTTPD_ENOERR; - n = httpd->cbs->client.send ( + n = httpd->scb->client.send ( httpd, client, &QSE_MBS_CHAR(proxy->res,proxy->res_consumed), count @@ -1150,7 +1150,7 @@ for (i = 0; i < count; i++) qse_printf (QSE_T("%hc"), QSE_MBS_CHAR(proxy->res,pr qse_printf (QSE_T("]\n")); httpd->errnum = QSE_HTTPD_ENOERR; - n = httpd->cbs->client.send ( + n = httpd->scb->client.send ( httpd, client, QSE_MBS_CPTR(proxy->res,proxy->res_consumed), count @@ -1181,7 +1181,7 @@ qse_printf (QSE_T("[proxy-2 send failure....\n")); /* there is something to read from peer */ httpd->errnum = QSE_HTTPD_ENOERR; - n = httpd->cbs->peer.recv ( + n = httpd->scb->peer.recv ( httpd, &proxy->peer, &proxy->buf[proxy->buflen], QSE_SIZEOF(proxy->buf) - proxy->buflen @@ -1307,7 +1307,7 @@ qse_printf (QSE_T("task_main_proxy_1....\n")); int n; httpd->errnum = QSE_HTTPD_ENOERR; - n = httpd->cbs->peer.connected (httpd, &proxy->peer); + n = httpd->scb->peer.connected (httpd, &proxy->peer); if (n <= -1) { /* improve error conversion */ @@ -1378,7 +1378,7 @@ qse_printf (QSE_T("task_main_proxy....\n")); proxy->res_pending = 0; httpd->errnum = QSE_HTTPD_ENOERR; - n = httpd->cbs->peer.open (httpd, &proxy->peer); + n = httpd->scb->peer.open (httpd, &proxy->peer); if (n <= -1) { /* TODO: translate error code to http error... */ diff --git a/qse/lib/net/httpd-std.c b/qse/lib/net/httpd-std.c index b8c6076e..95aa1795 100644 --- a/qse/lib/net/httpd-std.c +++ b/qse/lib/net/httpd-std.c @@ -34,8 +34,6 @@ #else # include "../cmn/syscall.h" -# include -# include # include # include # if defined(HAVE_SYS_SENDFILE_H) @@ -49,8 +47,6 @@ # endif #endif - - #if defined(HAVE_SSL) # include # include @@ -543,13 +539,13 @@ static int server_open (qse_httpd_t* httpd, qse_httpd_server_t* server) oops: qse_httpd_seterrnum (httpd, syserr_to_errnum(errno)); - if (fd >= 0) close (fd); + if (fd >= 0) QSE_CLOSE (fd); return -1; } static void server_close (qse_httpd_t* httpd, qse_httpd_server_t* server) { - close (server->handle.i); + QSE_CLOSE (server->handle.i); } static int server_accept ( @@ -578,7 +574,7 @@ static int server_accept ( { qse_fprintf (QSE_STDERR, QSE_T("Error: too many client?\n")); /*TODO: qse_httpd_seterrnum (httpd, QSE_HTTPD_EXXXXX);*/ - close (fd); + QSE_CLOSE (fd); return -1; } #endif @@ -659,13 +655,13 @@ static int peer_open (qse_httpd_t* httpd, qse_httpd_peer_t* peer) oops: qse_httpd_seterrnum (httpd, syserr_to_errnum(errno)); - if (fd >= 0) close (fd); + if (fd >= 0) QSE_CLOSE (fd); return -1; } static void peer_close (qse_httpd_t* httpd, qse_httpd_peer_t* peer) { - close (peer->handle.i); + QSE_CLOSE (peer->handle.i); } static int peer_connected (qse_httpd_t* httpd, qse_httpd_peer_t* peer) @@ -782,7 +778,7 @@ static void mux_close (qse_httpd_t* httpd, void* vmux) if (mux->mev.ptr[i]) qse_httpd_freemem (httpd, mux->mev.ptr[i]); qse_httpd_freemem (httpd, mux->mev.ptr); } - close (mux->fd); + QSE_CLOSE (mux->fd); qse_httpd_freemem (httpd, mux); } @@ -998,7 +994,10 @@ static int file_stat ( 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; + qse_mbsend (path, QSE_MT(".mp3"))? QSE_MT("audio/mpeg"): + qse_mbsend (path, QSE_MT(".c"))? QSE_MT("text/plain"): + qse_mbsend (path, QSE_MT(".h"))? QSE_MT("text/plain"): + QSE_NULL; return 0; } @@ -1014,7 +1013,7 @@ static int file_ropen ( #endif qse_printf (QSE_T("opening file [%hs] for reading\n"), path); - fd = open (path, flags, 0); + fd = QSE_OPEN (path, flags, 0); if (fd <= -1) { qse_httpd_seterrnum (httpd, syserr_to_errnum(errno)); @@ -1042,7 +1041,7 @@ static int file_wopen ( #endif qse_printf (QSE_T("opening file [%hs] for writing\n"), path); - fd = open (path, flags, 0644); + fd = QSE_OPEN (path, flags, 0644); if (fd <= -1) { qse_httpd_seterrnum (httpd, syserr_to_errnum(errno)); @@ -1056,28 +1055,28 @@ qse_printf (QSE_T("opening file [%hs] for writing\n"), path); 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); + QSE_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); + return QSE_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); + return QSE_WRITE (handle.i, buf, len); } /* ------------------------------------------------------------------- */ static void client_close ( qse_httpd_t* httpd, qse_httpd_client_t* client) { - close (client->handle.i); + QSE_CLOSE (client->handle.i); } static void client_shutdown ( @@ -1264,8 +1263,10 @@ static int process_request ( /* 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)); + * any more. once it's decoded in the peek mode, + * the decoded query path is made available in the + * non-peek mode as well */ + if (peek) 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"), @@ -1423,14 +1424,8 @@ qse_printf (QSE_T("Entasking chunked CGI...\n")); #else if (peek) { - qse_stat_t st; - qse_httpd_discardcontent (httpd, req); - - if (QSE_LSTAT (qpath, &st) == 0 && S_ISDIR(st.st_mode)) - task = qse_httpd_entaskdir (httpd, client, QSE_NULL, qpath, req); - else - task = qse_httpd_entaskfile (httpd, client, QSE_NULL, qpath, req); + task = qse_httpd_entaskpath (httpd, client, QSE_NULL, qpath, req); if (task == QSE_NULL) goto oops; } #endif @@ -1578,7 +1573,7 @@ static int handle_request ( } } -static qse_httpd_cbs_t httpd_standard_callbacks = +static qse_httpd_scb_t httpd_system_callbacks = { /* server */ { server_open, server_close, server_accept }, @@ -1617,14 +1612,17 @@ static qse_httpd_cbs_t httpd_standard_callbacks = client_send, client_sendfile, client_accepted, - client_closed }, - - /* http request */ - peek_request, - handle_request, + client_closed } }; -int qse_httpd_loopstd (qse_httpd_t* httpd, qse_ntime_t timeout) +static qse_httpd_rcb_t httpd_request_callbacks = { - return qse_httpd_loop (httpd, &httpd_standard_callbacks, timeout); + peek_request, + handle_request +}; + +int qse_httpd_loopstd (qse_httpd_t* httpd, qse_httpd_rcb_t* rcb, qse_ntime_t timeout) +{ + if (rcb == QSE_NULL) rcb = &httpd_request_callbacks; + return qse_httpd_loop (httpd, &httpd_system_callbacks, rcb, timeout); } diff --git a/qse/lib/net/httpd-task.c b/qse/lib/net/httpd-task.c index b3e72b90..71e6eca0 100644 --- a/qse/lib/net/httpd-task.c +++ b/qse/lib/net/httpd-task.c @@ -18,20 +18,24 @@ License along with QSE. If not, see . */ -#if defined(_WIN32) || defined(__DOS__) || defined(__OS2__) -/* UNSUPPORTED YET.. */ -/* TODO: IMPLEMENT THIS */ -#else - #include "httpd.h" #include "../cmn/mem.h" #include #include -#include /* TODO: remove this */ +#if defined(_WIN32) + /* TODO */ +#elif defined(__DOS__) + /* TODO */ +#elif defined(__OS2__) + /* TODO */ +#else +# include "../cmn/syscall.h" +#endif #include -#include +#include /* TODO: remove this */ + /* TODO: * many functions in this file use qse_size_t. @@ -43,7 +47,7 @@ static int task_main_disconnect ( qse_httpd_t* httpd, qse_httpd_client_t* client, qse_httpd_task_t* task) { - httpd->cbs->client.shutdown (httpd, client); + httpd->scb->client.shutdown (httpd, client); return 0; } @@ -75,7 +79,7 @@ static int task_main_statictext ( } /* TODO: do i need to add code to skip this send if count is 0? */ - n = httpd->cbs->client.send (httpd, client, task->ctx, count); + n = httpd->scb->client.send (httpd, client, task->ctx, count); if (n <= -1) return -1; ptr = (const qse_mchar_t*)task->ctx + n; @@ -133,7 +137,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 = httpd->cbs->client.send (httpd, client, ctx->ptr, count); + n = httpd->scb->client.send (httpd, client, ctx->ptr, count); if (n <= -1) return -1; ctx->left -= n; @@ -206,7 +210,7 @@ static int task_main_format ( count = MAX_SEND_SIZE; if (count >= ctx->left) count = ctx->left; - n = httpd->cbs->client.send (httpd, client, ctx->ptr, count); + n = httpd->scb->client.send (httpd, client, ctx->ptr, count); if (n <= -1) return -1; ctx->left -= n; @@ -437,6 +441,21 @@ qse_httpd_task_t* qse_httpd_entaskauth ( realm, (unsigned long)qse_mbslen(lmsg) + 4, lmsg); } +/*------------------------------------------------------------------------*/ +qse_httpd_task_t* qse_httpd_entaskpath ( + qse_httpd_t* httpd, qse_httpd_client_t* client, + qse_httpd_task_t* pred, const qse_mchar_t* name, qse_htre_t* req) +{ + qse_stat_t st; + qse_httpd_task_t* task; + + if (QSE_LSTAT (name, &st) == 0 && S_ISDIR(st.st_mode)) + task = qse_httpd_entaskdir (httpd, client, pred, name, req); + else + task = qse_httpd_entaskfile (httpd, client, pred, name, req); + + return task; +} /*------------------------------------------------------------------------*/ #if 0 @@ -451,4 +470,3 @@ qse_httpd_task_t* qse_httpd_entaskconnect ( } #endif -#endif diff --git a/qse/lib/net/httpd.c b/qse/lib/net/httpd.c index 682df6ae..3d4c6b08 100644 --- a/qse/lib/net/httpd.c +++ b/qse/lib/net/httpd.c @@ -245,7 +245,7 @@ static QSE_INLINE int dequeue_task ( { if (client->status & CLIENT_TASK_TRIGGER_IN_MUX(i)) { - httpd->cbs->mux.delhnd (httpd, httpd->mux, task->trigger[i].handle); + httpd->scb->mux.delhnd (httpd, httpd->mux, task->trigger[i].handle); client->status &= ~CLIENT_TASK_TRIGGER_IN_MUX(i); } } @@ -281,13 +281,13 @@ static QSE_INLINE void purge_tasks ( static int htrd_peek_request (qse_htrd_t* htrd, qse_htre_t* req) { htrd_xtn_t* xtn = (htrd_xtn_t*) qse_htrd_getxtn (htrd); - return xtn->httpd->cbs->peek_request (xtn->httpd, xtn->client, req); + return xtn->httpd->rcb->peek_request (xtn->httpd, xtn->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); - return xtn->httpd->cbs->handle_request (xtn->httpd, xtn->client, req); + return xtn->httpd->rcb->handle_request (xtn->httpd, xtn->client, req); } static qse_htrd_recbs_t htrd_recbs = @@ -317,9 +317,9 @@ static qse_httpd_client_t* new_client ( return QSE_NULL; } - qse_htrd_setoption (client->htrd, QSE_HTRD_REQUEST | QSE_HTRD_TRAILERS); + qse_htrd_setoption (client->htrd, QSE_HTRD_REQUEST | QSE_HTRD_TRAILERS | QSE_HTRD_CANONQPATH); - if (httpd->cbs->client.accepted == QSE_NULL) + if (httpd->scb->client.accepted == QSE_NULL) client->status |= CLIENT_READY; client->status = tmpl->status; @@ -351,16 +351,16 @@ qse_printf (QSE_T("Debug: CLOSING SOCKET %d\n"), client->handle.i); if (client->status & CLIENT_HANDLE_IN_MUX) { - httpd->cbs->mux.delhnd (httpd, httpd->mux, client->handle); + httpd->scb->mux.delhnd (httpd, httpd->mux, client->handle); client->status &= ~CLIENT_HANDLE_IN_MUX; } /* note that client.closed is not a counterpart to client.accepted. * so it is called even if client.close() failed. */ - if (httpd->cbs->client.closed) - httpd->cbs->client.closed (httpd, client); + if (httpd->scb->client.closed) + httpd->scb->client.closed (httpd, client); - httpd->cbs->client.close (httpd, client); + httpd->scb->client.close (httpd, client); qse_httpd_freemem (httpd, client); } @@ -434,7 +434,7 @@ static int accept_client ( QSE_MEMSET (&clibuf, 0, QSE_SIZEOF(clibuf)); - if (httpd->cbs->server.accept (httpd, server, &clibuf) <= -1) + if (httpd->scb->server.accept (httpd, server, &clibuf) <= -1) { /* TODO: proper logging */ qse_char_t tmp[128]; @@ -451,12 +451,12 @@ qse_printf (QSE_T("failed to accept from server %s\n"), tmp); client = new_client (httpd, &clibuf); if (client == QSE_NULL) { - httpd->cbs->client.close (httpd, &clibuf); + httpd->scb->client.close (httpd, &clibuf); return -1; } qse_printf (QSE_T("MUX ADDHND CLIENT READ %d\n"), client->handle.i); - if (httpd->cbs->mux.addhnd ( + if (httpd->scb->mux.addhnd ( httpd, mux, client->handle, QSE_HTTPD_MUX_READ, perform_client_task, client) <= -1) { @@ -540,8 +540,8 @@ static void deactivate_servers (qse_httpd_t* httpd) { if (server->active) { - httpd->cbs->mux.delhnd (httpd, httpd->mux, server->handle); - httpd->cbs->server.close (httpd, server); + httpd->scb->mux.delhnd (httpd, httpd->mux, server->handle); + httpd->scb->server.close (httpd, server); server->active = 0; httpd->server.nactive--; } @@ -554,7 +554,7 @@ static int activate_servers (qse_httpd_t* httpd) for (server = httpd->server.list; server; server = server->next) { - if (httpd->cbs->server.open (httpd, server) <= -1) + if (httpd->scb->server.open (httpd, server) <= -1) { qse_char_t buf[64]; qse_nwadtostr (&server->nwad, buf, QSE_COUNTOF(buf), QSE_NWADTOSTR_ALL); @@ -563,12 +563,12 @@ qse_printf (QSE_T("FAILED TO ACTIVATE SERVER....[%s]\n"), buf); } qse_printf (QSE_T("MUX ADDHND SERVER %d\n"), server->handle.i); - if (httpd->cbs->mux.addhnd ( + if (httpd->scb->mux.addhnd ( httpd, httpd->mux, server->handle, QSE_HTTPD_MUX_READ, accept_client, server) <= -1) { qse_printf (QSE_T("FAILED TO ADD SERVER HANDLE TO MUX....\n")); - httpd->cbs->server.close (httpd, server); + httpd->scb->server.close (httpd, server); continue; } @@ -585,7 +585,7 @@ static void free_server_list (qse_httpd_t* httpd, qse_httpd_server_t* server) { qse_httpd_server_t* next = server->next; - httpd->cbs->server.close (httpd, server); + httpd->scb->server.close (httpd, server); qse_httpd_freemem (httpd, server); httpd->server.navail--; @@ -689,11 +689,11 @@ static int read_from_client (qse_httpd_t* httpd, qse_httpd_client_t* client) qse_mchar_t buf[4096]; /* TODO: adjust this buffer size */ qse_ssize_t m; - QSE_ASSERT (httpd->cbs->client.recv != QSE_NULL); + QSE_ASSERT (httpd->scb->client.recv != QSE_NULL); reread: httpd->errnum = QSE_HTTPD_ENOERR; - m = httpd->cbs->client.recv (httpd, client, buf, QSE_SIZEOF(buf)); + m = httpd->scb->client.recv (httpd, client, buf, QSE_SIZEOF(buf)); if (m <= -1) { if (httpd->errnum == QSE_HTTPD_EAGAIN) @@ -840,7 +840,7 @@ qse_printf (QSE_T("ERROR: mute client got no more task [%d] failed...\n"), (int) { /* the task is invoked for triggers. * check if the client handle is writable */ - if (httpd->cbs->mux.writable (httpd, client->handle, 0) <= 0) + if (httpd->scb->mux.writable (httpd, client->handle, 0) <= 0) { /* it is not writable yet. so just skip * performing the actual task */ @@ -891,12 +891,12 @@ qse_printf (QSE_T("REMOVING XXXXX FROM READING NO MORE TASK....\n")); if ((client->status & CLIENT_HANDLE_IN_MUX) != (mux_status & CLIENT_HANDLE_IN_MUX)) { - httpd->cbs->mux.delhnd (httpd, httpd->mux, client->handle); + httpd->scb->mux.delhnd (httpd, httpd->mux, client->handle); client->status &= ~CLIENT_HANDLE_IN_MUX; if (mux_status) { - if (httpd->cbs->mux.addhnd ( + if (httpd->scb->mux.addhnd ( httpd, httpd->mux, client->handle, mux_mask, perform_client_task, client) <= -1) { @@ -937,7 +937,7 @@ qse_printf (QSE_T("REMOVING XXXXX FROM READING NO MORE TASK....\n")); { if (client->status & CLIENT_TASK_TRIGGER_IN_MUX(i)) { - httpd->cbs->mux.delhnd (httpd, httpd->mux, client->trigger[i].handle); + httpd->scb->mux.delhnd (httpd, httpd->mux, client->trigger[i].handle); client->status &= ~CLIENT_TASK_TRIGGER_IN_MUX(i); } } @@ -983,7 +983,7 @@ qse_printf (QSE_T("REMOVING XXXXX FROM READING NO MORE TASK....\n")); } else { - if (httpd->cbs->mux.addhnd ( + if (httpd->scb->mux.addhnd ( httpd, httpd->mux, task->trigger[i].handle, trigger_mux_mask, perform_client_task, client) <= -1) { @@ -1012,12 +1012,12 @@ qse_printf (QSE_T("REMOVING XXXXX FROM READING NO MORE TASK....\n")); if ((client->status & CLIENT_HANDLE_IN_MUX) != (client_handle_mux_status & CLIENT_HANDLE_IN_MUX)) { - httpd->cbs->mux.delhnd (httpd, httpd->mux, client->handle); + httpd->scb->mux.delhnd (httpd, httpd->mux, client->handle); client->status &= ~CLIENT_HANDLE_IN_MUX; if (client_handle_mux_mask) { - if (httpd->cbs->mux.addhnd ( + if (httpd->scb->mux.addhnd ( httpd, httpd->mux, client->handle, client_handle_mux_mask, perform_client_task, client) <= -1) { @@ -1045,7 +1045,7 @@ static int perform_client_task ( if (!(client->status & CLIENT_READY)) { int x; - x = httpd->cbs->client.accepted (httpd, client); + x = httpd->scb->client.accepted (httpd, client); if (x <= -1) goto oops; if (x >= 1) { @@ -1139,11 +1139,11 @@ qse_httpd_task_t* qse_httpd_entask ( /* arrange to invokde this task so long as * the client-side handle is writable. */ QSE_ASSERT (client->status & CLIENT_HANDLE_IN_MUX); - httpd->cbs->mux.delhnd (httpd, httpd->mux, client->handle); + httpd->scb->mux.delhnd (httpd, httpd->mux, client->handle); client->status &= ~CLIENT_HANDLE_IN_MUX; qse_printf (QSE_T("MUX ADDHND CLIENT RW(ENTASK) %d\n"), client->handle.i); - if (httpd->cbs->mux.addhnd ( + if (httpd->scb->mux.addhnd ( httpd, httpd->mux, client->handle, QSE_HTTPD_MUX_READ | QSE_HTTPD_MUX_WRITE, perform_client_task, client) <= -1) @@ -1158,30 +1158,28 @@ qse_printf (QSE_T("MUX ADDHND CLIENT RW(ENTASK) %d\n"), client->handle.i); return new_task; } -int qse_httpd_loop (qse_httpd_t* httpd, qse_httpd_cbs_t* cbs, qse_ntime_t timeout) +int qse_httpd_loop (qse_httpd_t* httpd, qse_httpd_scb_t* scb, qse_httpd_rcb_t* rcb, qse_ntime_t timeout) { - httpd->stopreq = 0; - httpd->cbs = cbs; - QSE_ASSERTX (httpd->server.list != QSE_NULL, "Add listeners before calling qse_httpd_loop()"); QSE_ASSERTX (httpd->client.list.head == QSE_NULL, "No client should exist when this loop is started"); - QSE_ASSERTX (httpd->cbs != QSE_NULL, - "Set httpd callbacks before calling qse_httpd_loop()"); - - if (httpd->server.list == QSE_NULL) + if (scb == QSE_NULL || rcb == QSE_NULL || + httpd->server.list == QSE_NULL) { - /* no listener specified */ httpd->errnum = QSE_HTTPD_EINVAL; return -1; } + httpd->stopreq = 0; + httpd->scb = scb; + httpd->rcb = rcb; + QSE_ASSERT (httpd->server.navail > 0); - httpd->mux = httpd->cbs->mux.open (httpd); + httpd->mux = httpd->scb->mux.open (httpd); if (httpd->mux == QSE_NULL) { qse_printf (QSE_T("can't open mux....\n")); @@ -1190,13 +1188,13 @@ qse_printf (QSE_T("can't open mux....\n")); if (activate_servers (httpd) <= -1) { - httpd->cbs->mux.close (httpd, httpd->mux); + httpd->scb->mux.close (httpd, httpd->mux); return -1; } if (httpd->server.nactive <= 0) { qse_printf (QSE_T("no servers are active....\n")); - httpd->cbs->mux.close (httpd, httpd->mux); + httpd->scb->mux.close (httpd, httpd->mux); return -1; } @@ -1204,7 +1202,7 @@ qse_printf (QSE_T("no servers are active....\n")); { int count; - count = httpd->cbs->mux.poll (httpd, httpd->mux, timeout); + count = httpd->scb->mux.poll (httpd, httpd->mux, timeout); if (count <= -1) { httpd->errnum = QSE_HTTPD_EIOMUX; @@ -1220,7 +1218,7 @@ qse_fprintf (QSE_STDERR, QSE_T("Error: mux returned failure\n")); purge_client_list (httpd); deactivate_servers (httpd); - httpd->cbs->mux.close (httpd, httpd->mux); + httpd->scb->mux.close (httpd, httpd->mux); return 0; } diff --git a/qse/lib/net/httpd.h b/qse/lib/net/httpd.h index a8264f2c..66b1fdfa 100644 --- a/qse/lib/net/httpd.h +++ b/qse/lib/net/httpd.h @@ -30,7 +30,8 @@ struct qse_httpd_t QSE_DEFINE_COMMON_FIELDS (httpd) qse_httpd_errnum_t errnum; qse_httpd_ecb_t* ecb; /* event callbacks */ - qse_httpd_cbs_t* cbs; + qse_httpd_scb_t* scb; /* system callbacks */ + qse_httpd_rcb_t* rcb; /* request callbacks */ int option; int stopreq; diff --git a/qse/samples/net/Makefile.am b/qse/samples/net/Makefile.am index 5ff795e4..2b1c86b9 100644 --- a/qse/samples/net/Makefile.am +++ b/qse/samples/net/Makefile.am @@ -5,7 +5,7 @@ AM_CPPFLAGS = \ -I$(top_srcdir)/include \ -I$(includedir) -bin_PROGRAMS = http01 upxd01 +bin_PROGRAMS = httpd01 httpd02 upxd01 LDFLAGS += -L../../lib/cmn -L../../lib/net LDADD = -lqsenet -lqsecmn $(PTHREAD_LIBS) $(SOCKET_LIBS) $(SENDFILE_LIBS) @@ -16,7 +16,9 @@ LDADD += $(UNICOWS_LIBS) endif endif -http01_SOURCES = http01.c +httpd01_SOURCES = httpd01.c +httpd02_SOURCES = httpd02.c upxd01_SOURCES = upxd01.c -http01_LDADD = $(LDADD) $(SSL_LIBS) +httpd01_LDADD = $(LDADD) $(SSL_LIBS) +httpd02_LDADD = $(LDADD) $(SSL_LIBS) diff --git a/qse/samples/net/Makefile.in b/qse/samples/net/Makefile.in index 5354e97e..e28a2a9c 100644 --- a/qse/samples/net/Makefile.in +++ b/qse/samples/net/Makefile.in @@ -34,7 +34,7 @@ PRE_UNINSTALL = : POST_UNINSTALL = : build_triplet = @build@ host_triplet = @host@ -bin_PROGRAMS = http01$(EXEEXT) upxd01$(EXEEXT) +bin_PROGRAMS = httpd01$(EXEEXT) httpd02$(EXEEXT) upxd01$(EXEEXT) @WCHAR_TRUE@@WIN32_TRUE@am__append_1 = $(UNICOWS_LIBS) subdir = samples/net DIST_COMMON = $(srcdir)/Makefile.am $(srcdir)/Makefile.in @@ -52,13 +52,16 @@ CONFIG_CLEAN_FILES = CONFIG_CLEAN_VPATH_FILES = am__installdirs = "$(DESTDIR)$(bindir)" PROGRAMS = $(bin_PROGRAMS) -am_http01_OBJECTS = http01.$(OBJEXT) -http01_OBJECTS = $(am_http01_OBJECTS) +am_httpd01_OBJECTS = httpd01.$(OBJEXT) +httpd01_OBJECTS = $(am_httpd01_OBJECTS) am__DEPENDENCIES_1 = @WCHAR_TRUE@@WIN32_TRUE@am__DEPENDENCIES_2 = $(am__DEPENDENCIES_1) am__DEPENDENCIES_3 = $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \ $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_2) -http01_DEPENDENCIES = $(am__DEPENDENCIES_3) $(am__DEPENDENCIES_1) +httpd01_DEPENDENCIES = $(am__DEPENDENCIES_3) $(am__DEPENDENCIES_1) +am_httpd02_OBJECTS = httpd02.$(OBJEXT) +httpd02_OBJECTS = $(am_httpd02_OBJECTS) +httpd02_DEPENDENCIES = $(am__DEPENDENCIES_3) $(am__DEPENDENCIES_1) am_upxd01_OBJECTS = upxd01.$(OBJEXT) upxd01_OBJECTS = $(am_upxd01_OBJECTS) upxd01_LDADD = $(LDADD) @@ -77,8 +80,8 @@ CCLD = $(CC) LINK = $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) \ --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) \ $(LDFLAGS) -o $@ -SOURCES = $(http01_SOURCES) $(upxd01_SOURCES) -DIST_SOURCES = $(http01_SOURCES) $(upxd01_SOURCES) +SOURCES = $(httpd01_SOURCES) $(httpd02_SOURCES) $(upxd01_SOURCES) +DIST_SOURCES = $(httpd01_SOURCES) $(httpd02_SOURCES) $(upxd01_SOURCES) ETAGS = etags CTAGS = ctags DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) @@ -243,9 +246,11 @@ AM_CPPFLAGS = \ LDADD = -lqsenet -lqsecmn $(PTHREAD_LIBS) $(SOCKET_LIBS) \ $(SENDFILE_LIBS) $(am__append_1) -http01_SOURCES = http01.c +httpd01_SOURCES = httpd01.c +httpd02_SOURCES = httpd02.c upxd01_SOURCES = upxd01.c -http01_LDADD = $(LDADD) $(SSL_LIBS) +httpd01_LDADD = $(LDADD) $(SSL_LIBS) +httpd02_LDADD = $(LDADD) $(SSL_LIBS) all: all-am .SUFFIXES: @@ -323,9 +328,12 @@ clean-binPROGRAMS: list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ echo " rm -f" $$list; \ rm -f $$list -http01$(EXEEXT): $(http01_OBJECTS) $(http01_DEPENDENCIES) $(EXTRA_http01_DEPENDENCIES) - @rm -f http01$(EXEEXT) - $(LINK) $(http01_OBJECTS) $(http01_LDADD) $(LIBS) +httpd01$(EXEEXT): $(httpd01_OBJECTS) $(httpd01_DEPENDENCIES) $(EXTRA_httpd01_DEPENDENCIES) + @rm -f httpd01$(EXEEXT) + $(LINK) $(httpd01_OBJECTS) $(httpd01_LDADD) $(LIBS) +httpd02$(EXEEXT): $(httpd02_OBJECTS) $(httpd02_DEPENDENCIES) $(EXTRA_httpd02_DEPENDENCIES) + @rm -f httpd02$(EXEEXT) + $(LINK) $(httpd02_OBJECTS) $(httpd02_LDADD) $(LIBS) upxd01$(EXEEXT): $(upxd01_OBJECTS) $(upxd01_DEPENDENCIES) $(EXTRA_upxd01_DEPENDENCIES) @rm -f upxd01$(EXEEXT) $(LINK) $(upxd01_OBJECTS) $(upxd01_LDADD) $(LIBS) @@ -336,7 +344,8 @@ mostlyclean-compile: distclean-compile: -rm -f *.tab.c -@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/http01.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/httpd01.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/httpd02.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/upxd01.Po@am__quote@ .c.o: diff --git a/qse/samples/net/http01.c b/qse/samples/net/httpd01.c similarity index 94% rename from qse/samples/net/http01.c rename to qse/samples/net/httpd01.c index 9e3cb07f..0b54c513 100644 --- a/qse/samples/net/http01.c +++ b/qse/samples/net/httpd01.c @@ -33,7 +33,7 @@ static void sigint (int sig) if (g_httpd) qse_httpd_stop (g_httpd); } -int httpd_main (int argc, qse_char_t* argv[]) +static int httpd_main (int argc, qse_char_t* argv[]) { qse_httpd_t* httpd = QSE_NULL; int ret = -1, i; @@ -66,7 +66,7 @@ int httpd_main (int argc, qse_char_t* argv[]) signal (SIGPIPE, SIG_IGN); qse_httpd_setoption (httpd, QSE_HTTPD_CGIERRTONUL); - ret = qse_httpd_loopstd (httpd, 10000); + ret = qse_httpd_loopstd (httpd, QSE_NULL, 10000); signal (SIGINT, SIG_DFL); signal (SIGPIPE, SIG_DFL); diff --git a/qse/samples/net/httpd02.c b/qse/samples/net/httpd02.c new file mode 100644 index 00000000..afe52b2a --- /dev/null +++ b/qse/samples/net/httpd02.c @@ -0,0 +1,249 @@ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#if defined(_WIN32) +# include +# include +# include +#elif defined(__OS2__) +# define INCL_DOSPROCESS +# define INCL_DOSEXCEPTIONS +# define INCL_ERRORS +# include +#elif defined(__DOS__) +# include +#else +# include +# include +#endif + + +/* --------------------------------------------------------------------- */ + +typedef struct xtn_t xtn_t; +struct xtn_t +{ + qse_mchar_t basedir[4096]; +}; + +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; + xtn_t* xtn; + + method = qse_htre_getqmethodtype(req); + content_received = (qse_htre_getcontentlen(req) > 0); + + xtn = (xtn_t*) qse_httpd_getxtn (httpd); + + if (peek) 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)); + + 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 */ + if (qse_httpd_entaskcontinue ( + httpd, client, QSE_NULL, req) == QSE_NULL) return -1; + } + } + + 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 (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; + } + } + + return 0; + } + else + { + if (peek) + { +/* TODO: combine qpath with xtn->basedir */ + qse_httpd_discardcontent (httpd, req); + task = qse_httpd_entaskpath (httpd, client, QSE_NULL, qpath, req); + if (task == QSE_NULL) goto oops; + } + } + } + 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 peek_request ( + qse_httpd_t* httpd, qse_httpd_client_t* client, qse_htre_t* 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); +} + +/* --------------------------------------------------------------------- */ + +static qse_httpd_t* g_httpd = QSE_NULL; + +static void sigint (int sig) +{ + if (g_httpd) qse_httpd_stop (g_httpd); +} + +/* --------------------------------------------------------------------- */ +static int httpd_main (int argc, qse_char_t* argv[]) +{ + qse_httpd_t* httpd = QSE_NULL; + int ret = -1, i; + static qse_httpd_rcb_t rcb = { peek_request, handle_request }; + + if (argc <= 1) + { + qse_fprintf (QSE_STDERR, QSE_T("Usage: %s ...\n"), argv[0]); + goto oops; + } + + httpd = qse_httpd_openstd (QSE_SIZEOF(xtn_t)); + if (httpd == QSE_NULL) + { + qse_fprintf (QSE_STDERR, QSE_T("Cannot open httpd\n")); + goto oops; + } + + for (i = 1; i < argc; i++) + { + if (qse_httpd_addserver (httpd, argv[i]) <= -1) + { + qse_fprintf (QSE_STDERR, + QSE_T("Failed to add httpd listener - %s\n"), argv[i]); + goto oops; + } + } + + g_httpd = httpd; + signal (SIGINT, sigint); + signal (SIGPIPE, SIG_IGN); + + qse_httpd_setoption (httpd, QSE_HTTPD_CGIERRTONUL); + ret = qse_httpd_loopstd (httpd, &rcb, 10000); + + signal (SIGINT, SIG_DFL); + signal (SIGPIPE, SIG_DFL); + g_httpd = QSE_NULL; + + if (ret <= -1) qse_fprintf (QSE_STDERR, QSE_T("Httpd error\n")); + +oops: + if (httpd) qse_httpd_close (httpd); + return ret; +} + +int qse_main (int argc, qse_achar_t* argv[]) +{ +#if defined(_WIN32) + char locale[100]; + UINT codepage = GetConsoleOutputCP(); + if (codepage == CP_UTF8) + { + /*SetConsoleOUtputCP (CP_UTF8);*/ + qse_setdflcmgr (qse_utf8cmgr); + } + else + { + sprintf (locale, ".%u", (unsigned int)codepage); + setlocale (LC_ALL, locale); + qse_setdflcmgrbyid (QSE_CMGR_SLMB); + } +#else + setlocale (LC_ALL, ""); + qse_setdflcmgrbyid (QSE_CMGR_SLMB); +#endif + + return qse_runmain (argc, argv, httpd_main); +} +