diff --git a/qse/include/qse/cmn/time.h b/qse/include/qse/cmn/time.h index c696aba7..9aa2d4fd 100644 --- a/qse/include/qse/cmn/time.h +++ b/qse/include/qse/cmn/time.h @@ -63,6 +63,7 @@ #define QSE_SECNSEC_TO_MSEC(sec,nsec) \ (((qse_ntime_t)(sec) * QSE_MSECS_PER_SEC) + ((qse_ntime_t)(nsec) / QSE_NSECS_PER_MSEC)) +#define QSE_SEC_TO_MSEC(sec) ((sec) * QSE_MSECS_PER_SEC) /** * The qse_ntime_t type defines a numeric time type expressed in the * number of milliseconds since the Epoch (00:00:00 UTC, Jan 1, 1970). diff --git a/qse/include/qse/net/http.h b/qse/include/qse/net/http.h index 85162a8a..434172bc 100644 --- a/qse/include/qse/net/http.h +++ b/qse/include/qse/net/http.h @@ -181,12 +181,18 @@ int qse_parsehttprange ( ); /* -int qse_parsehttpdatetime ( +int qse_parsehttptime ( const qse_mchar_t* str, qse_ntime_t* t ); */ +qse_mchar_t* qse_fmthttptime ( + qse_ntime_t nt, + qse_mchar_t* buf, + qse_size_t bufsz +); + /* percent-decode a string */ qse_size_t qse_perdechttpstr ( const qse_mchar_t* str, diff --git a/qse/include/qse/net/httpd.h b/qse/include/qse/net/httpd.h index fa7c11c0..37d73670 100644 --- a/qse/include/qse/net/httpd.h +++ b/qse/include/qse/net/httpd.h @@ -422,6 +422,38 @@ void qse_httpd_completecontent ( qse_htre_t* req ); + + +/** + * The qse_httpd_setname() function changes the string + * to be used as the value for the server header. + */ +void qse_httpd_setname ( + qse_httpd_t* httpd, + const qse_mchar_t* name +); + + +/** + * The qse_httpd_getname() function returns the + * pointer to the string used as the value for the server + * header. + */ +qse_mchar_t* qse_httpd_getname ( + qse_httpd_t* httpd +); + +/** + * The qse_httpd_fmtgmtimetobb() function converts a numeric time @a nt + * to a string and stores it in a built-in buffer. + * If @a nt is QSE_NULL, the current time is used. + */ +const qse_mchar_t* qse_httpd_fmtgmtimetobb ( + qse_httpd_t* httpd, + const qse_ntime_t* nt, + int idx +); + #define qse_httpd_gettaskxtn(httpd,task) ((void*)(task+1)) qse_httpd_task_t* qse_httpd_entask ( @@ -506,6 +538,12 @@ qse_httpd_task_t* qse_httpd_entaskfile ( qse_htre_t* req ); +/** + * The qse_httpd_entaskphat() functions a dispatcher between + * qse_httpd_entaskdir() and qse_httpd_entaskfile(). It calls + * the former if @a name is a directory and calls the latter + * otherwise. + */ qse_httpd_task_t* qse_httpd_entaskpath ( qse_httpd_t* httpd, qse_httpd_client_t* client, diff --git a/qse/lib/cmn/time.c b/qse/lib/cmn/time.c index 9200e261..844246bc 100644 --- a/qse/lib/cmn/time.c +++ b/qse/lib/cmn/time.c @@ -23,19 +23,25 @@ #if defined(_WIN32) # include +# include #elif defined(__OS2__) # define INCL_DOSDATETIME # define INCL_DOSERRORS # include +# include #elif defined(__DOS__) # include +# include #else # include "syscall.h" -# include +# if defined(HAVE_SYS_TIME_H) +# include +# endif +# if defined(HAVE_TIME_H) +# include +# endif #endif -#include - #if defined(_WIN32) #define WIN_EPOCH_YEAR (1601) #define WIN_EPOCH_MON (1) @@ -479,4 +485,3 @@ int qse_timelocal (const qse_btime_t* bt, qse_ntime_t* nt) return 0; #endif } - diff --git a/qse/lib/net/http.c b/qse/lib/net/http.c index 663dd040..39210d8e 100644 --- a/qse/lib/net/http.c +++ b/qse/lib/net/http.c @@ -24,6 +24,8 @@ #include #include "../cmn/mem.h" +#include /* for snprintf. TODO: remove this. */ + int qse_comparehttpversions ( const qse_http_version_t* v1, const qse_http_version_t* v2) @@ -188,13 +190,59 @@ int qse_parsehttprange (const qse_mchar_t* str, qse_http_range_t* range) } #if 0 -int qse_parsehttpdatetime (const qse_mchar_t* str, qse_ntime_t* t) +int qse_parsehttptime (const qse_mchar_t* str, qse_ntime_t* t) { /* TODO: */ return -1; } #endif +qse_mchar_t* qse_fmthttptime ( + qse_ntime_t nt, qse_mchar_t* buf, qse_size_t bufsz) +{ + static const qse_mchar_t* wday_name[] = + { + QSE_MT("Sun"), + QSE_MT("Mon"), + QSE_MT("Tue"), + QSE_MT("Wed"), + QSE_MT("Thu"), + QSE_MT("Fri"), + QSE_MT("Sat") + }; + + static const qse_mchar_t* mon_name[] = + { + QSE_MT("Jan"), + QSE_MT("Feb"), + QSE_MT("Mar"), + QSE_MT("Apr"), + QSE_MT("May"), + QSE_MT("Jun"), + QSE_MT("Jul"), + QSE_MT("Aug"), + QSE_MT("Sep"), + QSE_MT("Oct"), + QSE_MT("Nov"), + QSE_MT("Dec") + }; + + qse_btime_t bt; + + qse_gmtime (nt, &bt); + +/* TODO: avoid using snprintf () */ + snprintf (buf, bufsz, + QSE_MT("%s, %d %s %d %02d:%02d:%02d GMT"), + wday_name[bt.wday], + bt.mday, + mon_name[bt.mon], + bt.year + QSE_BTIME_YEAR_BASE, + bt.hour, bt.min, bt.sec); + + return buf; +} + qse_size_t qse_perdechttpstr (const qse_mchar_t* str, qse_mchar_t* buf) { const qse_mchar_t* p = str; diff --git a/qse/lib/net/httpd-cgi.c b/qse/lib/net/httpd-cgi.c index 343e5196..2f3503d9 100644 --- a/qse/lib/net/httpd-cgi.c +++ b/qse/lib/net/httpd-cgi.c @@ -181,10 +181,11 @@ static int cgi_capture_script_header (qse_htre_t* req, const qse_mchar_t* key, c { task_cgi_t* cgi = (task_cgi_t*)ctx; - /* capture a header excluding Status and Connection */ + /* capture a header except Status, Connection, Transfer-Encoding, and Server */ if (qse_mbscasecmp (key, QSE_MT("Status")) != 0 && qse_mbscasecmp (key, QSE_MT("Connection")) != 0 && - qse_mbscasecmp (key, QSE_MT("Transfer-Encoding")) != 0) + qse_mbscasecmp (key, QSE_MT("Transfer-Encoding")) != 0 && + qse_mbscasecmp (key, QSE_MT("Server")) != 0) { return cgi_add_header_to_buffer (cgi, cgi->res, key, val); } @@ -268,6 +269,28 @@ static int cgi_htrd_peek_script_output (qse_htrd_t* htrd, qse_htre_t* req) } } + /* Add the server header. the server header in the cgi output will + * be ignored by cgi_capture_script_header() */ + if (qse_mbs_cat (cgi->res, QSE_MT("Server: ")) == (qse_size_t)-1 || + qse_mbs_cat (cgi->res, qse_httpd_getname (cgi->httpd)) == (qse_size_t)-1 || + qse_mbs_cat (cgi->res, QSE_MT("\r\n")) == (qse_size_t)-1) + { + cgi->httpd->errnum = QSE_HTTPD_ENOMEM; + return -1; + } + + if (qse_htre_getheaderval (req, QSE_MT("Date")) == QSE_NULL) + { + /* generate the Date header if it's not included in the script output */ + if (qse_mbs_cat (cgi->res, QSE_MT("Date: ")) == (qse_size_t)-1 || + qse_mbs_cat (cgi->res, qse_httpd_fmtgmtimetobb (cgi->httpd, QSE_NULL, 0)) == (qse_size_t)-1 || + qse_mbs_cat (cgi->res, QSE_MT("\r\n")) == (qse_size_t)-1) + { + cgi->httpd->errnum = QSE_HTTPD_ENOMEM; + return -1; + } + } + keepalive = cgi->keepalive; if (req->attr.flags & QSE_HTRE_ATTR_LENGTH) { diff --git a/qse/lib/net/httpd-dir.c b/qse/lib/net/httpd-dir.c index b9bdd404..970fa8ff 100644 --- a/qse/lib/net/httpd-dir.c +++ b/qse/lib/net/httpd-dir.c @@ -164,6 +164,7 @@ static int task_main_dseg ( } /* the buffer size is fixed to QSE_COUNTOF(ctx->buf). + * * the number of digits need to hold the the size converted to * a hexadecimal notation is roughly (log16(QSE_COUNTOF(ctx->buf) + 1). * it should be safter to use ceil(log16(QSE_COUNTOF(ctx->buf)) + 1 @@ -410,8 +411,10 @@ static QSE_INLINE int task_main_dir ( { x = qse_httpd_entaskformat ( httpd, client, x, - QSE_MT("HTTP/%d.%d 200 OK\r\nConnection: %s\r\nContent-Type: text/html\r\n%s\r\n"), + QSE_MT("HTTP/%d.%d 200 OK\r\nServer: %s\r\nDate: %s\r\nConnection: %s\r\nContent-Type: text/html\r\n%s\r\n"), dir->version.major, dir->version.minor, + qse_httpd_getname (httpd), + qse_httpd_fmtgmtimetobb (httpd, QSE_NULL, 0), (dir->keepalive? QSE_MT("keep-alive"): QSE_MT("close")), (dir->keepalive? QSE_MT("Transfer-Encoding: chunked\r\n"): QSE_MT("")) ); @@ -438,8 +441,10 @@ static QSE_INLINE int task_main_dir ( { x = qse_httpd_entaskformat ( httpd, client, x, - QSE_MT("HTTP/%d.%d 301 Moved Permanently\r\nContent-Length: 0\r\nConnection: %s\r\nLocation: %s/\r\n\r\n"), + QSE_MT("HTTP/%d.%d 301 Moved Permanently\r\nServer: %s\r\nDate: %s\r\nContent-Length: 0\r\nConnection: %s\r\nLocation: %s/\r\n\r\n"), dir->version.major, dir->version.minor, + qse_httpd_getname (httpd), + qse_httpd_fmtgmtimetobb (httpd, QSE_NULL, 0), (dir->keepalive? QSE_MT("keep-alive"): QSE_MT("close")), dir->path ); diff --git a/qse/lib/net/httpd-file.c b/qse/lib/net/httpd-file.c index 39b22389..275ce88b 100644 --- a/qse/lib/net/httpd-file.c +++ b/qse/lib/net/httpd-file.c @@ -22,7 +22,9 @@ #include "../cmn/mem.h" #include "../cmn/syscall.h" #include +#include #include +#include #include /* TODO: remove this */ typedef struct task_file_t task_file_t; @@ -30,6 +32,7 @@ struct task_file_t { const qse_mchar_t* path; qse_http_range_t range; + qse_ntime_t if_modified_since; qse_http_version_t version; int keepalive; }; @@ -174,6 +177,8 @@ qse_printf (QSE_T("opening file %hs\n"), file->path); if (file->range.type != QSE_HTTP_RANGE_NONE) { + qse_mchar_t tmp[4][64]; + if (file->range.type == QSE_HTTP_RANGE_SUFFIX) { if (file->range.to > st.size) file->range.to = st.size; @@ -191,37 +196,23 @@ qse_printf (QSE_T("opening file %hs\n"), file->path); if (file->range.to >= st.size) file->range.to = st.size - 1; -#if (QSE_SIZEOF_LONG_LONG > 0) + qse_fmtuintmaxtombs (tmp[0], QSE_COUNTOF(tmp[0]), (file->range.to - file->range.from + 1), 10, -1, QSE_MT('\0'), QSE_NULL); + qse_fmtuintmaxtombs (tmp[1], QSE_COUNTOF(tmp[1]), file->range.from, 10, -1, QSE_MT('\0'), QSE_NULL); + qse_fmtuintmaxtombs (tmp[2], QSE_COUNTOF(tmp[2]), file->range.to, 10, -1, QSE_MT('\0'), QSE_NULL); + qse_fmtuintmaxtombs (tmp[3], QSE_COUNTOF(tmp[3]), st.size, 10, -1, QSE_MT('\0'), QSE_NULL); + x = qse_httpd_entaskformat ( httpd, client, x, - QSE_MT("HTTP/%d.%d 206 Partial Content\r\nConnection: %s\r\n%s%s%sContent-Length: %llu\r\nContent-Range: bytes %llu-%llu/%llu\r\n\r\n"), - file->version.major, - file->version.minor, + QSE_MT("HTTP/%d.%d 206 Partial Content\r\nServer: %s\r\nDate: %s\r\nConnection: %s\r\n%s%s%sContent-Length: %s\r\nContent-Range: bytes %s-%s/%s\r\nAccept-Ranges: bytes\r\nLast-Modified: %s\r\n\r\n"), + file->version.major, file->version.minor, + qse_httpd_getname (httpd), + qse_httpd_fmtgmtimetobb (httpd, QSE_NULL, 0), (file->keepalive? QSE_MT("keep-alive"): QSE_MT("close")), (st.mime? QSE_MT("Content-Type: "): QSE_MT("")), (st.mime? st.mime: QSE_MT("")), (st.mime? QSE_MT("\r\n"): QSE_MT("")), - (unsigned long long)(file->range.to - file->range.from + 1), - (unsigned long long)file->range.from, - (unsigned long long)file->range.to, - (unsigned long long)st.size + tmp[0], tmp[1], tmp[2], tmp[3] ); -#else - x = qse_httpd_entaskformat ( - httpd, client, x, - QSE_MT("HTTP/%d.%d 206 Partial Content\r\nConnection: %s\r\n%s%s%sContent-Length: %lu\r\nContent-Range: bytes %lu-%lu/%lu\r\n\r\n"), - file->version.major, - file->version.minor, - (file->keepalive? QSE_MT("keep-alive"): QSE_MT("close")), - (st.mime? QSE_MT("Content-Type: "): QSE_MT("")), - (st.mime? st.mime: QSE_MT("")), - (st.mime? QSE_MT("\r\n"): QSE_MT("")), - (unsigned long)(file->range.to - file->range.from + 1), - (unsigned long)file->range.from, - (unsigned long)file->range.to, - (unsigned long)st.size - ); -#endif if (x) { x = entask_file_segment ( @@ -234,36 +225,28 @@ qse_printf (QSE_T("opening file %hs\n"), file->path); } else { -/* TODO: int64 format.... don't hard code it llu */ + qse_mchar_t b_fsize[64]; + + qse_fmtuintmaxtombs (b_fsize, QSE_COUNTOF(b_fsize), st.size, 10, -1, QSE_MT('\0'), QSE_NULL); + /* wget 1.8.2 set 'Connection: keep-alive' in the http 1.0 header. * if the reply doesn't contain 'Connection: keep-alive', it didn't * close connection.*/ -#if (QSE_SIZEOF_LONG_LONG > 0) - x = qse_httpd_entaskformat ( - httpd, client, x, - QSE_MT("HTTP/%d.%d 200 OK\r\nConnection: %s\r\n%s%s%sContent-Length: %llu\r\n\r\n"), - file->version.major, file->version.minor, - (file->keepalive? QSE_MT("keep-alive"): QSE_MT("close")), - (st.mime? QSE_MT("Content-Type: "): QSE_MT("")), - (st.mime? st.mime: QSE_MT("")), - (st.mime? QSE_MT("\r\n"): QSE_MT("")), - (unsigned long long)st.size - ); -#else - x = qse_httpd_entaskformat ( - httpd, client, x, - QSE_MT("HTTP/%d.%d 200 OK\r\nConnection: %s\r\n%s%s%sContent-Length: %lu\r\n\r\n"), - file->version.major, - file->version.minor, - (file->keepalive? QSE_MT("keep-alive"): QSE_MT("close")), - (st.mime? QSE_MT("Content-Type: "): QSE_MT("")), - (st.mime? st.mime: QSE_MT("")), - (st.mime? QSE_MT("\r\n"): QSE_MT("")), - (unsigned long)st.size - ); -#endif - if (x) x = entask_file_segment (httpd, client, x, handle, 0, st.size); + x = qse_httpd_entaskformat ( + httpd, client, x, + QSE_MT("HTTP/%d.%d 200 OK\r\nServer: %s\r\nDate: %s\r\nConnection: %s\r\n%s%s%sContent-Length: %s\r\nAccept-Ranges: bytes\r\nLast-Modified: %s\r\n\r\n"), + file->version.major, file->version.minor, + qse_httpd_getname (httpd), + qse_httpd_fmtgmtimetobb (httpd, QSE_NULL, 0), + (file->keepalive? QSE_MT("keep-alive"): QSE_MT("close")), + (st.mime? QSE_MT("Content-Type: "): QSE_MT("")), + (st.mime? st.mime: QSE_MT("")), + (st.mime? QSE_MT("\r\n"): QSE_MT("")), + b_fsize, + qse_httpd_fmtgmtimetobb (httpd, &st.mtime, 1) + ); + if (x) x = entask_file_segment (httpd, client, x, handle, 0, st.size); } if (x) return 0; @@ -305,11 +288,13 @@ qse_httpd_task_t* qse_httpd_entaskfile ( data.range.type = QSE_HTTP_RANGE_NONE; } + data.if_modified_since = QSE_TYPE_MIN(qse_ntime_t); /* TODO: If-Modified-Since... tmp = qse_htre_getheaderval(req, QSE_MT("If-Modified-Since")); if (tmp) { + qse_httpd_parsegmtime (tmp, & } */ diff --git a/qse/lib/net/httpd-std.c b/qse/lib/net/httpd-std.c index 95aa1795..7ef2f0c9 100644 --- a/qse/lib/net/httpd-std.c +++ b/qse/lib/net/httpd-std.c @@ -987,7 +987,7 @@ static int file_stat ( #elif defined(HAVE_STRUCT_STAT_ST_MTIMESPEC_TV_NSEC) hst->mtime = QSE_SECNSEC_TO_MSEC(st.st_mtimespec.tv_sec,st.st_mtimespec.tv_nsec); #else - hst->mtime = st.st_mtime * QSE_MSECS_PER_SEC; + hst->mtime = QSE_SEC_TO_MSEC(st.st_mtime); #endif hst->mime = qse_mbsend (path, QSE_MT(".html"))? QSE_MT("text/html"): diff --git a/qse/lib/net/httpd-task.c b/qse/lib/net/httpd-task.c index 71e6eca0..71b166d8 100644 --- a/qse/lib/net/httpd-task.c +++ b/qse/lib/net/httpd-task.c @@ -392,8 +392,10 @@ qse_httpd_task_t* qse_httpd_entask_error ( return qse_httpd_entaskformat ( httpd, client, pred, - QSE_MT("HTTP/%d.%d %d %s\r\nConnection: %s\r\nContent-Type: text/html\r\nContent-Length: %lu\r\n\r\n%s\r\n\r\n"), + QSE_MT("HTTP/%d.%d %d %s\r\nServer: %s\r\nDate: %s\r\nConnection: %s\r\nContent-Type: text/html\r\nContent-Length: %lu\r\n\r\n%s\r\n\r\n"), version->major, version->minor, code, smsg, + qse_httpd_getname (httpd), + qse_httpd_fmtgmtimetobb (httpd, QSE_NULL, 0), (keepalive? QSE_MT("keep-alive"): QSE_MT("close")), (unsigned long)qse_mbslen(lmsg) + 4, lmsg ); @@ -435,8 +437,10 @@ qse_httpd_task_t* qse_httpd_entaskauth ( return qse_httpd_entaskformat ( httpd, client, pred, - QSE_MT("HTTP/%d.%d 401 Unauthorized\r\nConnection: %s\r\nWWW-Authenticate: Basic realm=\"%s\"\r\nContent-Type: text/html\r\nContent-Length: %lu\r\n\r\n%s\r\n\r\n"), + QSE_MT("HTTP/%d.%d 401 Unauthorized\r\nServer: %s\r\nDate: %s\r\nConnection: %s\r\nWWW-Authenticate: Basic realm=\"%s\"\r\nContent-Type: text/html\r\nContent-Length: %lu\r\n\r\n%s\r\n\r\n"), version->major, version->minor, + qse_httpd_getname (httpd), + qse_httpd_fmtgmtimetobb (httpd, QSE_NULL, 0), ((req->attr.flags & QSE_HTRE_ATTR_KEEPALIVE)? QSE_MT("keep-alive"): QSE_MT("close")), realm, (unsigned long)qse_mbslen(lmsg) + 4, lmsg); } diff --git a/qse/lib/net/httpd.c b/qse/lib/net/httpd.c index 3d4c6b08..3152134f 100644 --- a/qse/lib/net/httpd.c +++ b/qse/lib/net/httpd.c @@ -18,14 +18,10 @@ 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 "../cmn/syscall.h" + #include #include #include @@ -97,6 +93,7 @@ int qse_httpd_init (qse_httpd_t* httpd, qse_mmgr_t* mmgr) { QSE_MEMSET (httpd, 0, QSE_SIZEOF(*httpd)); httpd->mmgr = mmgr; + qse_mbscpy (httpd->sname, QSE_MT("QSE-HTTPD " QSE_PACKAGE_VERSION)); return 0; } @@ -593,8 +590,7 @@ static void free_server_list (qse_httpd_t* httpd, qse_httpd_server_t* server) } } -static qse_httpd_server_t* parse_server_uri ( - qse_httpd_t* httpd, const qse_char_t* uri) +static qse_httpd_server_t* parse_server_uri (qse_httpd_t* httpd, const qse_char_t* uri) { qse_httpd_server_t* server; qse_uint16_t default_port; @@ -1234,4 +1230,31 @@ void qse_httpd_completecontent (qse_httpd_t* httpd, qse_htre_t* req) qse_htre_completecontent (req); } -#endif +/* --------------------------------------------------- */ + +void qse_httpd_setname (qse_httpd_t* httpd, const qse_mchar_t* name) +{ + qse_mbsxcpy (httpd->sname, QSE_COUNTOF(httpd->sname), name); +} + +qse_mchar_t* qse_httpd_getname (qse_httpd_t* httpd) +{ + return httpd->sname; +} + +const qse_mchar_t* qse_httpd_fmtgmtimetobb ( + qse_httpd_t* httpd, const qse_ntime_t* nt, int idx) +{ + qse_ntime_t now; + + QSE_ASSERT (idx >= 0 && idx < QSE_COUNTOF(httpd->gtbuf)); + + if (nt == QSE_NULL) + { + if (qse_gettime(&now) <= -1) now = 0; + nt = &now; + } + + qse_fmthttptime (*nt, httpd->gtbuf[idx], QSE_COUNTOF(httpd->gtbuf[idx])); + return httpd->gtbuf[idx]; +} diff --git a/qse/lib/net/httpd.h b/qse/lib/net/httpd.h index 66b1fdfa..4f73ee3c 100644 --- a/qse/lib/net/httpd.h +++ b/qse/lib/net/httpd.h @@ -36,6 +36,9 @@ struct qse_httpd_t int option; int stopreq; + qse_mchar_t sname[128]; /* server name for the server header */ + qse_mchar_t gtbuf[10][64]; /* GMT time buffers */ + struct { struct diff --git a/qse/samples/net/httpd02.c b/qse/samples/net/httpd02.c index afe52b2a..c0a81025 100644 --- a/qse/samples/net/httpd02.c +++ b/qse/samples/net/httpd02.c @@ -209,6 +209,8 @@ static int httpd_main (int argc, qse_char_t* argv[]) signal (SIGINT, sigint); signal (SIGPIPE, SIG_IGN); + qse_httpd_setname (httpd, QSE_MT("httpd02/qse 1.0")); + qse_httpd_setoption (httpd, QSE_HTTPD_CGIERRTONUL); ret = qse_httpd_loopstd (httpd, &rcb, 10000);