diff --git a/qse/cmd/http/httpd.c b/qse/cmd/http/httpd.c index bf9385d7..733fb25d 100644 --- a/qse/cmd/http/httpd.c +++ b/qse/cmd/http/httpd.c @@ -466,6 +466,17 @@ static int query_server ( return 0; case QSE_HTTPD_SERVERSTD_ROOT: + #if 0 + if (qse_mbscmp (qse_htre_getqpath(req), QSE_MT("/version")) == 0) + { + /* return static text without inspecting further */ + ((qse_httpd_serverstd_root_t*)result)->type = QSE_HTTPD_SERVERSTD_ROOT_TEXT; + ((qse_httpd_serverstd_root_t*)result)->u.text.ptr = QSE_MT(QSE_PACKAGE_NAME " " QSE_PACKAGE_VERSION); + ((qse_httpd_serverstd_root_t*)result)->u.text.mime = QSE_MT("text/plain"); + } + else + #endif + if (loccfg->root_is_nwad) { ((qse_httpd_serverstd_root_t*)result)->type = QSE_HTTPD_SERVERSTD_ROOT_NWAD; @@ -574,30 +585,47 @@ static int query_server ( case QSE_HTTPD_SERVERSTD_DIRACC: case QSE_HTTPD_SERVERSTD_FILEACC: { - qse_size_t i; - const qse_mchar_t* xpath_base; - int id; - - id = (code == QSE_HTTPD_SERVERSTD_DIRACC)? 0: 1; - - xpath_base = qse_mbsbasename (xpath); - - *(int*)result = 200; - for (i = 0; i < QSE_COUNTOF(loccfg->access[id]); i++) + switch (qse_htre_getqmethodtype(req)) { - struct access_t* access; - for (access = loccfg->access[id][i].head; access; access = access->next) + case QSE_HTTP_OPTIONS: + case QSE_HTTP_HEAD: + case QSE_HTTP_GET: + case QSE_HTTP_POST: + case QSE_HTTP_PUT: + case QSE_HTTP_DELETE: { - if ((access->type == ACCESS_PREFIX && qse_mbsbeg (xpath_base, access->spec)) || - (access->type == ACCESS_SUFFIX && qse_mbsend (xpath_base, access->spec)) || - (access->type == ACCESS_NAME && qse_mbscmp (xpath_base, access->spec) == 0) || - access->type == ACCESS_OTHER) + qse_size_t i; + const qse_mchar_t* xpath_base; + int id; + + id = (code == QSE_HTTPD_SERVERSTD_DIRACC)? 0: 1; + + xpath_base = qse_mbsbasename (xpath); + + *(int*)result = 200; + for (i = 0; i < QSE_COUNTOF(loccfg->access[id]); i++) { - *(int*)result = access->value; - return 0; + struct access_t* access; + for (access = loccfg->access[id][i].head; access; access = access->next) + { + if ((access->type == ACCESS_PREFIX && qse_mbsbeg (xpath_base, access->spec)) || + (access->type == ACCESS_SUFFIX && qse_mbsend (xpath_base, access->spec)) || + (access->type == ACCESS_NAME && qse_mbscmp (xpath_base, access->spec) == 0) || + access->type == ACCESS_OTHER) + { + *(int*)result = access->value; + return 0; + } + } } + break; } + + default: + *(int*)result = 405; /* method not allowed */ + break; } + return 0; } } diff --git a/qse/include/qse/http/httpd.h b/qse/include/qse/http/httpd.h index e25089ff..995c702d 100644 --- a/qse/include/qse/http/httpd.h +++ b/qse/include/qse/http/httpd.h @@ -166,6 +166,7 @@ struct qse_httpd_scb_t int (*stat) ( qse_httpd_t* httpd, const qse_mchar_t* path, qse_httpd_stat_t* stat); + int (*purge) (qse_httpd_t* httpd, const qse_mchar_t* path); int (*ropen) ( qse_httpd_t* httpd, const qse_mchar_t* path, @@ -185,6 +186,12 @@ struct qse_httpd_scb_t struct { + int (*stat) ( + qse_httpd_t* httpd, const qse_mchar_t* path, + qse_httpd_stat_t* stat); + int (*make) (qse_httpd_t* httpd, const qse_mchar_t* path); + int (*purge) (qse_httpd_t* httpd, const qse_mchar_t* path); + int (*open) ( qse_httpd_t* httpd, const qse_mchar_t* path, qse_ubi_t* handle); @@ -808,6 +815,14 @@ QSE_EXPORT qse_httpd_task_t* qse_httpd_entasknomod ( qse_htre_t* req ); +QSE_EXPORT qse_httpd_task_t* qse_httpd_entaskallow ( + qse_httpd_t* httpd, + qse_httpd_client_t* client, + qse_httpd_task_t* pred, + const qse_mchar_t* allow, + qse_htre_t* req +); + QSE_EXPORT qse_httpd_task_t* qse_httpd_entaskdir ( qse_httpd_t* httpd, qse_httpd_client_t* client, diff --git a/qse/include/qse/http/stdhttpd.h b/qse/include/qse/http/stdhttpd.h index 1ec357b8..63a47ec2 100644 --- a/qse/include/qse/http/stdhttpd.h +++ b/qse/include/qse/http/stdhttpd.h @@ -41,7 +41,8 @@ typedef void (*qse_httpd_serverstd_freersrc_t) ( enum qse_httpd_serverstd_root_type_t { QSE_HTTPD_SERVERSTD_ROOT_PATH, - QSE_HTTPD_SERVERSTD_ROOT_NWAD + QSE_HTTPD_SERVERSTD_ROOT_NWAD, + QSE_HTTPD_SERVERSTD_ROOT_TEXT }; typedef enum qse_httpd_serverstd_root_type_t qse_httpd_serverstd_root_type_t; @@ -57,6 +58,11 @@ struct qse_httpd_serverstd_root_t qse_size_t rpl; /* replacement length */ } path; qse_nwad_t nwad; + struct + { + const qse_mchar_t* ptr; + const qse_mchar_t* mime; + } text; } u; }; diff --git a/qse/lib/cmn/syscall.h b/qse/lib/cmn/syscall.h index 289e1bd7..26471326 100644 --- a/qse/lib/cmn/syscall.h +++ b/qse/lib/cmn/syscall.h @@ -318,6 +318,12 @@ # define QSE_RENAME(oldpath,newpath) rename(oldpath,newpath) #endif +#if defined(SYS_mkdir) && defined(QSE_USE_SYSCALL) +# define QSE_MKDIR(path,mode) syscall(SYS_mkdir,path,mode) +#else +# define QSE_MKDIR(path,mode) mkdir(path,mode) +#endif + #if defined(SYS_rmdir) && defined(QSE_USE_SYSCALL) # define QSE_RMDIR(path) syscall(SYS_rmdir,path) #else diff --git a/qse/lib/cmn/syserr.h b/qse/lib/cmn/syserr.h index ed4cd8cd..a396184b 100644 --- a/qse/lib/cmn/syserr.h +++ b/qse/lib/cmn/syserr.h @@ -128,6 +128,7 @@ { \ case ENOMEM: return __SYSERRNUM__ (obj2, ENOMEM); \ case EINVAL: return __SYSERRNUM__ (obj2, EINVAL); \ + case EBUSY: \ case EACCES: return __SYSERRNUM__ (obj2, EACCES); \ case ENOTDIR: \ case ENOENT: return __SYSERRNUM__ (obj2, ENOENT); \ diff --git a/qse/lib/http/httpd-cgi.c b/qse/lib/http/httpd-cgi.c index d1b4f0e3..7819e21b 100644 --- a/qse/lib/http/httpd-cgi.c +++ b/qse/lib/http/httpd-cgi.c @@ -50,6 +50,8 @@ struct task_cgi_t const qse_mchar_t* suffix; const qse_mchar_t* root; const qse_mchar_t* shebang; + + int method; qse_http_version_t version; int keepalive; /* taken from the request */ int nph; @@ -746,6 +748,7 @@ static int task_init_cgi ( qse_mbscpy ((qse_mchar_t*)cgi->root, arg->root.ptr); qse_mbscpy ((qse_mchar_t*)cgi->shebang, arg->shebang.ptr); + cgi->method = qse_htre_getqmethodtype(arg->req); cgi->version = *qse_htre_getversion(arg->req); cgi->keepalive = (arg->req->attr.flags & QSE_HTRE_ATTR_KEEPALIVE); cgi->nph = arg->nph; @@ -1355,7 +1358,7 @@ qse_printf (QSE_T("TRAILING DATA=[%.*hs]\n"), (int)QSE_MBS_LEN(cgi->res), QSE_MB return 1; oops: - return (qse_httpd_entask_err (httpd, client, task, 500, &cgi->version, cgi->keepalive) == QSE_NULL)? -1: 0; + return (qse_httpd_entask_err (httpd, client, task, 500, cgi->method, &cgi->version, cgi->keepalive) == QSE_NULL)? -1: 0; } static int task_main_cgi ( @@ -1502,7 +1505,7 @@ oops: return (qse_httpd_entask_err ( httpd, client, task, http_errnum, - &cgi->version, cgi->keepalive) == QSE_NULL)? -1: 0; + cgi->method, &cgi->version, cgi->keepalive) == QSE_NULL)? -1: 0; } /* TODO: global option or individual paramter for max cgi lifetime diff --git a/qse/lib/http/httpd-dir.c b/qse/lib/http/httpd-dir.c index 86199c25..827382fb 100644 --- a/qse/lib/http/httpd-dir.c +++ b/qse/lib/http/httpd-dir.c @@ -30,7 +30,7 @@ struct task_dir_t qse_mcstr_t qpath; qse_http_version_t version; int keepalive; - int headonly; + int method; }; typedef struct task_dseg_t task_dseg_t; @@ -370,7 +370,7 @@ static qse_httpd_task_t* entask_directory_segment ( /*------------------------------------------------------------------------*/ -static int task_init_dir ( +static int task_init_getdir ( qse_httpd_t* httpd, qse_httpd_client_t* client, qse_httpd_task_t* task) { task_dir_t* xtn = qse_httpd_gettaskxtn (httpd, task); @@ -390,7 +390,7 @@ static int task_init_dir ( return 0; } -static QSE_INLINE int task_main_dir ( +static QSE_INLINE int task_main_getdir ( qse_httpd_t* httpd, qse_httpd_client_t* client, qse_httpd_task_t* task) { task_dir_t* dir; @@ -415,26 +415,46 @@ static QSE_INLINE int task_main_dir ( (httpd->errnum == QSE_HTTPD_EACCES)? 403: 500; x = qse_httpd_entask_err ( httpd, client, x, http_errnum, - &dir->version, dir->keepalive); + dir->method, &dir->version, dir->keepalive); return (x == QSE_NULL)? -1: 0; } else { - x = qse_httpd_entaskformat ( - httpd, client, x, - 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("")) - ); - if (x) x = entask_directory_segment (httpd, client, x, handle, dir); - if (x) return 0; + if (dir->method == QSE_HTTP_HEAD) + { + x = qse_httpd_entaskformat ( + httpd, client, x, + QSE_MT("HTTP/%d.%d 200 OK\r\nServer: %s\r\nDate: %s\r\nConnection: %s\r\nContent-Type: text/html\r\nContent-Length: 0\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")) + ); - httpd->opt.scb.dir.close (httpd, handle); - return -1; + httpd->opt.scb.dir.close (httpd, handle); + return (x == QSE_NULL)? -1: 0; + } + else + { + x = qse_httpd_entaskformat ( + httpd, client, x, + 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("")) + ); + if (x) + { + x = entask_directory_segment (httpd, client, x, handle, dir); + if (x) return 0; + } + + httpd->opt.scb.dir.close (httpd, handle); + return -1; + } } /* } @@ -458,9 +478,6 @@ qse_httpd_task_t* qse_httpd_entaskdir ( { qse_httpd_task_t task; task_dir_t data; - int meth; - - meth = qse_htre_getqmethodtype(req); QSE_MEMSET (&data, 0, QSE_SIZEOF(data)); data.path.ptr = path; @@ -469,28 +486,71 @@ qse_httpd_task_t* qse_httpd_entaskdir ( data.qpath.len = qse_mbslen(data.qpath.ptr); data.version = *qse_htre_getversion(req); data.keepalive = (req->attr.flags & QSE_HTRE_ATTR_KEEPALIVE); + data.method = qse_htre_getqmethodtype(req); + QSE_MEMSET (&task, 0, QSE_SIZEOF(task)); - qse_htre_discardcontent (req); /* TODO: don't discard for put??? */ - switch (meth) + /* i don't need contents for directories */ + qse_htre_discardcontent (req); + + switch (data.method) { + case QSE_HTTP_OPTIONS: + return qse_httpd_entaskallow (httpd, client, pred, + QSE_MT("OPTIONS,GET,HEAD,POST,PUT,DELETE"), req); + case QSE_HTTP_HEAD: - data.headonly = 1; - break; - case QSE_HTTP_GET: case QSE_HTTP_POST: - case QSE_HTTP_PUT: + task.init = task_init_getdir; + task.main = task_main_getdir; break; + + case QSE_HTTP_PUT: + { + int status = 201; /* 201 Created */ + + + if (httpd->opt.scb.dir.make (httpd, path) <= -1) + { + if (httpd->errnum == QSE_HTTPD_EEXIST) + { + /* an entry with the same name exists. + * if it is a directory, it's considered ok. + * if not, send '403 forbidden' indicating you can't + * change a file to a directory */ + qse_httpd_stat_t st; + status = (httpd->opt.scb.dir.stat (httpd, path, &st) <= -1)? 403: 204; + } + else + { + status = (httpd->errnum == QSE_HTTPD_ENOENT)? 404: + (httpd->errnum == QSE_HTTPD_EACCES)? 403: 500; + } + } + + return qse_httpd_entaskerr (httpd, client, pred, status, req); + } + + + case QSE_HTTP_DELETE: + { + int status = 200; + + if (httpd->opt.scb.dir.purge (httpd, path) <= -1) + { + status = (httpd->errnum == QSE_HTTPD_ENOENT)? 404: + (httpd->errnum == QSE_HTTPD_EACCES)? 403: 500; + } + + return qse_httpd_entaskerr (httpd, client, pred, status, req); + } default: /* Method not allowed */ return qse_httpd_entaskerr (httpd, client, pred, 405, req); } - QSE_MEMSET (&task, 0, QSE_SIZEOF(task)); - task.init = task_init_dir; - task.main = task_main_dir; task.ctx = &data; return qse_httpd_entask (httpd, client, pred, &task, diff --git a/qse/lib/http/httpd-file.c b/qse/lib/http/httpd-file.c index 7ab70430..53c0c670 100644 --- a/qse/lib/http/httpd-file.c +++ b/qse/lib/http/httpd-file.c @@ -25,30 +25,32 @@ #define ETAG_LEN_MAX 127 -#define PUTFILE_INIT_FAILED (1 << 0) +#define PUTFILE_INIT_FAILED (1 << 0) +#define PUTFILE_WRITE_FAILED (1 << 1) typedef struct task_file_t task_file_t; struct task_file_t { qse_mcstr_t path; - qse_http_range_t range; - qse_mchar_t if_none_match[ETAG_LEN_MAX + 1]; - qse_ntime_t if_modified_since; qse_http_version_t version; int keepalive; int method; - /* only for put file... */ union { struct { qse_mcstr_t mime; + qse_mchar_t if_none_match[ETAG_LEN_MAX + 1]; + qse_ntime_t if_modified_since; + qse_http_range_t range; } get; struct { + qse_httpd_t* httpd; int flags; + int status; qse_htre_t* req; qse_ubi_t handle; } put; @@ -91,7 +93,6 @@ static int task_main_getfseg ( count = MAX_SEND_SIZE; if (count >= ctx->left) count = ctx->left; -/* TODO: more adjustment needed for OS with different sendfile semantics... */ n = httpd->opt.scb.client.sendfile ( httpd, client, ctx->handle, &ctx->offset, count); if (n <= -1) @@ -184,7 +185,7 @@ static QSE_INLINE int task_main_getfile ( (httpd->errnum == QSE_HTTPD_EACCES)? 403: 500; x = qse_httpd_entask_err ( httpd, client, x, http_errnum, - &file->version, file->keepalive); + file->method, &file->version, file->keepalive); goto no_file_send; } @@ -196,37 +197,37 @@ static QSE_INLINE int task_main_getfile ( (httpd->errnum == QSE_HTTPD_EACCES)? 403: 500; x = qse_httpd_entask_err ( httpd, client, x, http_errnum, - &file->version, file->keepalive); + file->method, &file->version, file->keepalive); goto no_file_send; } fileopen = 1; - if (file->range.type != QSE_HTTP_RANGE_NONE) + if (file->u.get.range.type != QSE_HTTP_RANGE_NONE) { qse_mchar_t tmp[4][64]; qse_mchar_t etag[ETAG_LEN_MAX + 1]; qse_size_t etag_len; - if (file->range.type == QSE_HTTP_RANGE_SUFFIX) + if (file->u.get.range.type == QSE_HTTP_RANGE_SUFFIX) { - if (file->range.to > st.size) file->range.to = st.size; - file->range.from = st.size - file->range.to; - file->range.to = file->range.to + file->range.from; - if (st.size > 0) file->range.to--; + if (file->u.get.range.to > st.size) file->u.get.range.to = st.size; + file->u.get.range.from = st.size - file->u.get.range.to; + file->u.get.range.to = file->u.get.range.to + file->u.get.range.from; + if (st.size > 0) file->u.get.range.to--; } - if (file->range.from >= st.size) + if (file->u.get.range.from >= st.size) { x = qse_httpd_entask_err ( - httpd, client, x, 416, &file->version, file->keepalive); + httpd, client, x, 416, file->method, &file->version, file->keepalive); goto no_file_send; } - if (file->range.to >= st.size) file->range.to = st.size - 1; + if (file->u.get.range.to >= st.size) file->u.get.range.to = st.size - 1; - 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[0], QSE_COUNTOF(tmp[0]), (file->u.get.range.to - file->u.get.range.from + 1), 10, -1, QSE_MT('\0'), QSE_NULL); + qse_fmtuintmaxtombs (tmp[1], QSE_COUNTOF(tmp[1]), file->u.get.range.from, 10, -1, QSE_MT('\0'), QSE_NULL); + qse_fmtuintmaxtombs (tmp[2], QSE_COUNTOF(tmp[2]), file->u.get.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); etag_len = qse_fmtuintmaxtombs (&etag[0], QSE_COUNTOF(etag), st.mtime.sec, 16, -1, QSE_MT('\0'), QSE_NULL); @@ -249,7 +250,10 @@ static QSE_INLINE int task_main_getfile ( (file->u.get.mime.len > 0? QSE_MT("Content-Type: "): QSE_MT("")), (file->u.get.mime.len > 0? file->u.get.mime.ptr: QSE_MT("")), (file->u.get.mime.len > 0? QSE_MT("\r\n"): QSE_MT("")), - tmp[0], tmp[1], tmp[2], tmp[3], etag + ((file->method == QSE_HTTP_HEAD)? QSE_MT("0"): tmp[0]), + tmp[1], tmp[2], tmp[3], + qse_httpd_fmtgmtimetobb (httpd, &st.mtime, 1), + etag ); if (x) { @@ -257,8 +261,8 @@ static QSE_INLINE int task_main_getfile ( x = entask_getfseg ( httpd, client, x, handle, - file->range.from, - (file->range.to - file->range.from + 1) + file->u.get.range.from, + (file->u.get.range.to - file->u.get.range.from + 1) ); } } @@ -278,13 +282,13 @@ static QSE_INLINE int task_main_getfile ( etag[etag_len++] = QSE_MT('-'); etag_len += qse_fmtuintmaxtombs (&etag[etag_len], QSE_COUNTOF(etag) - etag_len, st.dev, 16, -1, QSE_MT('\0'), QSE_NULL); - if ((file->if_none_match[0] != QSE_MT('\0') && qse_mbscmp (etag, file->if_none_match) == 0) || - (file->if_modified_since.sec > 0 && st.mtime.sec <= file->if_modified_since.sec)) + if ((file->u.get.if_none_match[0] != QSE_MT('\0') && qse_mbscmp (etag, file->u.get.if_none_match) == 0) || + (file->u.get.if_modified_since.sec > 0 && st.mtime.sec <= file->u.get.if_modified_since.sec)) { /* i've converted milliseconds to seconds before timestamp comparison * because st.mtime has the actual milliseconds less than 1 second * while if_modified_since doesn't have such small milliseconds */ - x = qse_httpd_entask_nomod (httpd, client, x, &file->version, file->keepalive); + x = qse_httpd_entask_nomod (httpd, client, x, file->method, &file->version, file->keepalive); goto no_file_send; } @@ -304,7 +308,7 @@ static QSE_INLINE int task_main_getfile ( (file->u.get.mime.len > 0? QSE_MT("Content-Type: "): QSE_MT("")), (file->u.get.mime.len > 0? file->u.get.mime.ptr: QSE_MT("")), (file->u.get.mime.len > 0? QSE_MT("\r\n"): QSE_MT("")), - b_fsize, + ((file->method == QSE_HTTP_HEAD)? QSE_MT("0"): b_fsize), qse_httpd_fmtgmtimetobb (httpd, &st.mtime, 1), etag ); @@ -326,6 +330,24 @@ no_file_send: /*------------------------------------------------------------------------*/ +static int write_file (qse_httpd_t* httpd, qse_ubi_t handle, const qse_mchar_t* ptr, qse_size_t len) +{ + qse_ssize_t n; + qse_size_t pos = 0; + + /* this implementation assumes that file writing will never get + * blocked in practice. so no i/o multiplexing is performed over + * file descriptors */ + while (pos < len) + { + n = httpd->opt.scb.file.write (httpd, handle, &ptr[pos], len - pos); + if (n <= 0) return -1; + pos += n; + } + + return 0; +} + static int putfile_snatch_client_input ( qse_htre_t* req, const qse_mchar_t* ptr, qse_size_t len, void* ctx) { @@ -354,10 +376,13 @@ static int putfile_snatch_client_input ( * the trigger is not needed any more. */ task->trigger[0].mask = 0; } - else /*if (!(file->reqflags & PROXY_REQ_FWDERR))*/ + else if (!(file->u.put.flags & PUTFILE_WRITE_FAILED)) { - /* TODO: write to file */ -qse_printf (QSE_T("WRITING 4 [%.*hs]\n"), (int)len, ptr); + if (write_file (file->u.put.httpd, file->u.put.handle, ptr, len) <= -1) + { + file->u.put.flags |= PUTFILE_WRITE_FAILED; + file->u.put.status = 500; + } } return 0; @@ -368,7 +393,7 @@ static int task_init_putfile ( { task_file_t* file = qse_httpd_gettaskxtn (httpd, task); task_file_t* arg = (task_file_t*)task->ctx; - int snatch_needed; + qse_httpd_stat_t st; /* zero out the task's extension area */ QSE_MEMCPY (file, arg, QSE_SIZEOF(*file)); @@ -377,87 +402,36 @@ static int task_init_putfile ( /* copy in the path name to the area */ file->path.ptr = (qse_mchar_t*)(file + 1); qse_mbscpy ((qse_mchar_t*)file->path.ptr, arg->path.ptr); + file->u.put.status = 204; /* 200 should also be ok to indicate success. */ + task->ctx = file; /* switch the task context to the extension area */ - snatch_needed = 0; - -#if 0 httpd->errnum = QSE_HTTPD_ENOERR; if (httpd->opt.scb.file.stat (httpd, file->path.ptr, &st) <= -1) { - int http_errnum = 500; - - switch (httpd->errnum) + if (httpd->errnum == QSE_HTTPD_ENOENT) { - case QSE_HTTPD_ENOENT: - /* nothing to do */ - break; - - case QSE_HTTPD_EACCES: - http_errnum = 403; - default: - - goto no_file_write; + /* stat found no such file. so if the request is achived + * successfully, it should send '201 Created' */ + file->u.put.status = 201; } } -#endif - if (httpd->opt.scb.file.wopen (httpd, file->path.ptr, &file->u.put.handle) <= -1) goto oops; - - if (arg->u.put.req->state & QSE_HTRE_DISCARDED) + httpd->errnum = QSE_HTTPD_ENOERR; + if (httpd->opt.scb.file.wopen (httpd, file->path.ptr, &file->u.put.handle) <= -1) { - /* no content to add */ -/* TODO: return what??? */ -qse_printf (QSE_T("ALL DISCARDED...\n")); - } - else if (arg->u.put.req->state & QSE_HTRE_COMPLETED) - { -#if 0 - len = qse_htre_getcontentlen(arg->u.put.req); - if (len > 0) - { - ptr = qse_htre_getcontentptr(arg->u.put.req); - /* TODO: write this to a file */ - } -#endif -qse_printf (QSE_T("WRITING 1 [%.*hs]\n"), (int)qse_htre_getcontentlen(arg->u.put.req), qse_htre_getcontentptr(arg->u.put.req)); - } - else if (arg->u.put.req->attr.flags & QSE_HTRE_ATTR_LENGTH) - { - /* Content-Length is included and the content - * has been received partially so far */ - -#if 0 - len = qse_htre_getcontentlen(arg->u.put.req); - if (len > 0) - { - ptr = qse_htre_getcontentptr(arg->u.put.req); - /* TODO: write to a file */ - } - -#endif -qse_printf (QSE_T("WRITING 2 [%.*hs]\n"), (int)qse_htre_getcontentlen(arg->u.put.req), qse_htre_getcontentptr(arg->u.put.req)); - snatch_needed = 1; - } - else - { - /* if this request is not chunked nor not length based, - * the state should be QSE_HTRE_COMPLETED. so only a - * chunked request should reach here */ - QSE_ASSERT (arg->u.put.req->attr.flags & QSE_HTRE_ATTR_CHUNKED); - -qse_printf (QSE_T("WRITING 3 [%.*hs]\n"), (int)qse_htre_getcontentlen(arg->u.put.req), qse_htre_getcontentptr(arg->u.put.req)); -#if 0 - len = qse_htre_getcontentlen(arg->u.put.req); - if (len > 0) - { - ptr = qse_htre_getcontentptr(arg->u.put.req); - } -#endif - - snatch_needed = 1; + file->u.put.status = (httpd->errnum == QSE_HTTPD_EACCES)? 403: 500; + goto oops; } - if (snatch_needed) + if (write_file (httpd, file->u.put.handle, qse_htre_getcontentptr(arg->u.put.req), qse_htre_getcontentlen(arg->u.put.req)) <= -1) + { + httpd->opt.scb.file.close (httpd, file->u.put.handle); + file->u.put.status = 500; + goto oops; + } + + if (!(arg->u.put.req->state & QSE_HTRE_DISCARDED) && + !(arg->u.put.req->state & QSE_HTRE_COMPLETED)) { /* set up a callback to be called when the request content * is fed to the htrd reader. qse_htre_addcontent() that @@ -470,16 +444,14 @@ qse_printf (QSE_T("WRITING 3 [%.*hs]\n"), (int)qse_htre_getcontentlen(arg->u.put * triggers in the task initializer. however the main task handler * will be invoked so long as the client handle is writable by * the main loop. */ - - task->ctx = file; /* switch the task context to the extension area */ return 0; oops: /* since a new task can't be added in the initializer, * i mark that initialization failed and let task_main_putfile() * add an error task */ + qse_htre_discardcontent (arg->u.put.req); file->u.put.flags |= PUTFILE_INIT_FAILED; - task->ctx = file; return 0; } @@ -488,37 +460,58 @@ static void task_fini_putfile ( { task_file_t* file = (task_file_t*)task->ctx; -qse_printf (QSE_T("put fini....\n")); if (!(file->u.put.flags & PUTFILE_INIT_FAILED)) httpd->opt.scb.file.close (httpd, file->u.put.handle); } +static int task_main_putfile_2 ( + qse_httpd_t* httpd, qse_httpd_client_t* client, qse_httpd_task_t* task) +{ + task_file_t* file = (task_file_t*)task->ctx; + + if (file->u.put.req) + { + /* file->u.put.req is set to a non-NULL value if snatching + * is needed in the task_init_putfile(). and it's reset to + * QSE_NULL when snatching is over in putfile_snatch_client_input(). + * i set a trigger so that the task is executed + * while there is input from the client side */ + task->trigger[0].mask = QSE_HTTPD_TASK_TRIGGER_READ; + task->trigger[0].handle = client->handle; + return 1; /* trigger me when a client sends data */ + } + + /* snatching is over. writing error may have occurred as well. + * file->u.put.status should hold a proper status code */ + if (qse_httpd_entask_err ( + httpd, client, task, file->u.put.status, + file->method, &file->version, file->keepalive) == QSE_NULL) return -1; + return 0; /* no more data to read. task over */ +} + static int task_main_putfile ( qse_httpd_t* httpd, qse_httpd_client_t* client, qse_httpd_task_t* task) { task_file_t* file = (task_file_t*)task->ctx; -qse_printf (QSE_T("put main....\n")); - if (file->u.put.req) + if (!(file->u.put.flags & PUTFILE_INIT_FAILED) && file->u.put.req) { -qse_printf (QSE_T("put xxxxx....\n")); - /* still snatching the content body */ - task->trigger[0].mask = QSE_HTTPD_TASK_TRIGGER_READ; - task->trigger[0].handle = client->handle; - return 1; + /* initialization was successful and snatching is required. + * switch to the next phase. */ + task->main = task_main_putfile_2; + return task_main_putfile_2 (httpd, client, task); } -qse_printf (QSE_T("put what....\n")); - return 0; -} - -/*------------------------------------------------------------------------*/ - -static QSE_INLINE int task_main_delfile ( - qse_httpd_t* httpd, qse_httpd_client_t* client, qse_httpd_task_t* task) -{ - /* TODO: implement this */ - return -1; + /* snatching is not required or initialization error has occurred. + * file->u.put.status should hold a proper status code. + * + * note: if initialization error occurred and there is contents for the + * client to send, this reply may get to the client before it finishes + * sending the contents. */ + if (qse_httpd_entask_err ( + httpd, client, task, file->u.put.status, + file->method, &file->version, file->keepalive) == QSE_NULL) return -1; + return 0; /* task over */ } /*------------------------------------------------------------------------*/ @@ -549,6 +542,11 @@ qse_httpd_task_t* qse_httpd_entaskfile ( switch (data.method) { + case QSE_HTTP_OPTIONS: + qse_htre_discardcontent (req); + return qse_httpd_entaskallow (httpd, client, pred, + QSE_MT("OPTIONS,GET,HEAD,POST,PUT,DELETE"), req); + case QSE_HTTP_HEAD: case QSE_HTTP_GET: case QSE_HTTP_POST: @@ -561,72 +559,84 @@ qse_httpd_task_t* qse_httpd_entaskfile ( xtnsize += data.u.get.mime.len + 1; } + tmp = qse_htre_getheaderval(req, QSE_MT("If-None-Match")); + if (tmp) + { + /*while (tmp->next) tmp = tmp->next;*/ /* get the last value */ + qse_mbsxcpy (data.u.get.if_none_match, QSE_COUNTOF(data.u.get.if_none_match), tmp->ptr); + } + if (data.u.get.if_none_match[0] == QSE_MT('\0')) + { + /* Both ETag and Last-Modified are included in the reply. + * If the client understand ETag, it can choose to include + * If-None-Match in the request. If it understands Last-Modified, + * it can choose to include If-Modified-Since. I don't care + * the client understands both and include both of them + * in the request. + * + * I check If-None-Match if it's included. + * I check If-Modified-Since if If-None-Match is not included. + */ + tmp = qse_htre_getheaderval(req, QSE_MT("If-Modified-Since")); + if (tmp) + { + /*while (tmp->next) tmp = tmp->next;*/ /* get the last value */ + if (qse_parsehttptime (tmp->ptr, &data.u.get.if_modified_since) <= -1) + { + data.u.get.if_modified_since.sec = 0; + data.u.get.if_modified_since.nsec = 0; + } + } + } + + tmp = qse_htre_getheaderval(req, QSE_MT("Range")); + if (tmp) + { + /*while (tmp->next) tmp = tmp->next;*/ /* get the last value */ + if (qse_parsehttprange (tmp->ptr, &data.u.get.range) <= -1) + { + return qse_httpd_entaskerr (httpd, client, pred, 416, req); + } + } + else + { + data.u.get.range.type = QSE_HTTP_RANGE_NONE; + } + task.init = task_init_getfile; task.main = task_main_getfile; break; case QSE_HTTP_PUT: + /* note that no partial update is supported for PUT */ + data.u.put.httpd = httpd; data.u.put.req = req; task.init = task_init_putfile; task.main = task_main_putfile; task.fini = task_fini_putfile; break; -#if 0 case QSE_HTTP_DELETE: - task.main = task_main_delfile; - break; -#endif + { + int status = 200; + + qse_htre_discardcontent (req); + if (httpd->opt.scb.file.purge (httpd, path) <= -1) + { + status = (httpd->errnum == QSE_HTTPD_ENOENT)? 404: + (httpd->errnum == QSE_HTTPD_EACCES)? 403: 500; + } + + return qse_httpd_entaskerr (httpd, client, pred, status, req); + } default: /* Method not allowed */ + qse_htre_discardcontent (req); return qse_httpd_entaskerr (httpd, client, pred, 405, req); } - tmp = qse_htre_getheaderval(req, QSE_MT("Range")); - if (tmp) - { - while (tmp->next) tmp = tmp->next; /* get the last value */ - if (qse_parsehttprange (tmp->ptr, &data.range) <= -1) - { - return qse_httpd_entaskerr (httpd, client, pred, 416, req); - } - } - else - { - data.range.type = QSE_HTTP_RANGE_NONE; - } - tmp = qse_htre_getheaderval(req, QSE_MT("If-None-Match")); - if (tmp) - { - while (tmp->next) tmp = tmp->next; /* get the last value */ - qse_mbsxcpy (data.if_none_match, QSE_COUNTOF(data.if_none_match), tmp->ptr); - } - if (data.if_none_match[0] == QSE_MT('\0')) - { - /* Both ETag and Last-Modified are included in the reply. - * If the client understand ETag, it can choose to include - * If-None-Match in the request. If it understands Last-Modified, - * it can choose to include If-Modified-Since. I don't care - * the client understands both and include both of them - * in the request. - * - * I check If-None-Match if it's included. - * I check If-Modified-Since if If-None-Match is not included. - */ - tmp = qse_htre_getheaderval(req, QSE_MT("If-Modified-Since")); - if (tmp) - { - while (tmp->next) tmp = tmp->next; /* get the last value */ - if (qse_parsehttptime (tmp->ptr, &data.if_modified_since) <= -1) - { - data.if_modified_since.sec = 0; - data.if_modified_since.nsec = 0; - } - } - } - task.ctx = &data; return qse_httpd_entask (httpd, client, pred, &task, xtnsize); } diff --git a/qse/lib/http/httpd-proxy.c b/qse/lib/http/httpd-proxy.c index d3a8a8b2..83cd53d9 100644 --- a/qse/lib/http/httpd-proxy.c +++ b/qse/lib/http/httpd-proxy.c @@ -39,6 +39,8 @@ struct task_proxy_t qse_httpd_t* httpd; const qse_mchar_t* host; + + int method; qse_http_version_t version; int keepalive; /* taken from the request */ @@ -714,6 +716,7 @@ static int task_init_proxy ( QSE_MEMSET (proxy, 0, QSE_SIZEOF(*proxy)); proxy->httpd = httpd; + proxy->method = qse_htre_getqmethodtype(arg->req); proxy->version = *qse_htre_getversion(arg->req); proxy->keepalive = (arg->req->attr.flags & QSE_HTRE_ATTR_KEEPALIVE); proxy->peer.nwad = *arg->peer_nwad; @@ -1356,7 +1359,7 @@ qse_printf (QSE_T("TRAILING DATA=%d, [%hs]\n"), (int)QSE_MBS_LEN(proxy->res), QS oops: if (proxy->resflags & PROXY_RES_EVER_SENTBACK) return -1; - return (qse_httpd_entask_err (httpd, client, task, http_errnum, &proxy->version, proxy->keepalive) == QSE_NULL)? -1: 0; + return (qse_httpd_entask_err (httpd, client, task, http_errnum, proxy->method, &proxy->version, proxy->keepalive) == QSE_NULL)? -1: 0; } static int task_main_proxy_1 ( @@ -1419,7 +1422,7 @@ static int task_main_proxy_1 ( return 1; oops: - return (qse_httpd_entask_err (httpd, client, task, http_errnum, &proxy->version, proxy->keepalive) == QSE_NULL)? -1: 0; + return (qse_httpd_entask_err (httpd, client, task, http_errnum, proxy->method, &proxy->version, proxy->keepalive) == QSE_NULL)? -1: 0; } static int task_main_proxy ( @@ -1509,7 +1512,7 @@ oops: return (qse_httpd_entask_err ( httpd, client, task, http_errnum, - &proxy->version, proxy->keepalive) == QSE_NULL)? -1: 0; + proxy->method, &proxy->version, proxy->keepalive) == QSE_NULL)? -1: 0; } qse_httpd_task_t* qse_httpd_entaskproxy ( diff --git a/qse/lib/http/httpd-std.c b/qse/lib/http/httpd-std.c index fc45cc30..bf49c79a 100644 --- a/qse/lib/http/httpd-std.c +++ b/qse/lib/http/httpd-std.c @@ -33,6 +33,9 @@ #include #include +#define STAT_REG 1 +#define STAT_DIR 2 + #if defined(_WIN32) # include # include /* sockaddr_in6 */ @@ -1250,7 +1253,7 @@ static int mux_writable (qse_httpd_t* httpd, qse_ubi_t handle, const qse_ntime_t static int stat_file ( qse_httpd_t* httpd, const qse_mchar_t* path, - qse_httpd_stat_t* hst, int regonly) + qse_httpd_stat_t* hst, int filter) { #if defined(_WIN32) @@ -1360,9 +1363,8 @@ static int stat_file ( return -1; } - /* stating for a file. it should be a regular file. - * i don't allow other file types. */ - if (regonly && !S_ISREG(st.st_mode)) + if ((filter == STAT_REG && !S_ISREG(st.st_mode)) || + (filter == STAT_DIR && !S_ISDIR(st.st_mode))) { qse_httpd_seterrnum (httpd, QSE_HTTPD_EACCES); return -1; @@ -1397,8 +1399,47 @@ static int file_stat ( /* this callback is not required to be a general stat function * for a file. it is mainly used to get a file size and timestamps * of a regular file. so it should fail for a non-regular file. - * note that 1 passes 1 to stat_file for it */ - return stat_file (httpd, path, hst, 1); + * note that STAT_REG is passed to stat_file for it */ + return stat_file (httpd, path, hst, STAT_REG); +} + +static int file_purge (qse_httpd_t* httpd, const qse_mchar_t* path) +{ +#if defined(_WIN32) + if (DeleteFileA (path) == FALSE) + { + qse_httpd_seterrnum (httpd, syserr_to_errnum(GetLastError())); + return -1; + } + return 0; + +#elif defined(__OS2__) + + APIRET rc; + + rc = DosDelete (path); /* TODO: is DosForceDelete better? */ + if (rc != NO_ERROR) + { + qse_httpd_seterrnum (httpd, syserr_to_errnum(rc)); + return -1; + } + return 0; + +#elif defined(__DOS__) + /* TODO: */ + qse_httpd_seterrnum (httpd, QSE_HTTPD_ENOIMPL); + return -1; + +#else + + if (QSE_UNLINK (path) <= -1) + { + qse_httpd_seterrnum (httpd, syserr_to_errnum(errno)); + return -1; + } + return 0; + +#endif } static int file_ropen ( @@ -1503,6 +1544,90 @@ static qse_ssize_t file_write ( /* ------------------------------------------------------------------- */ +static int dir_stat ( + qse_httpd_t* httpd, const qse_mchar_t* path, qse_httpd_stat_t* hst) +{ + /* this callback is not required to be a general stat function + * for a file. it is mainly used to get a file size and timestamps + * of a regular file. so it should fail for a non-regular file. + * note that STAT_REG is passed to stat_file for it */ + return stat_file (httpd, path, hst, STAT_DIR); +} + +static int dir_make (qse_httpd_t* httpd, const qse_mchar_t* path) +{ +#if defined(_WIN32) + + if (CreateDirectoryA (path, QSE_NULL) == FALSE) + { + qse_httpd_seterrnum (httpd, syserr_to_errnum(GetLastError())); + return -1; + } + + return 0; + +#elif defined(__OS2__) + + APIRET rc; + + rc = DosCreateDir (path, QSE_NULL); /* TODO: is DosForceDelete better? */ + if (rc != NO_ERROR) + { + qse_httpd_seterrnum (httpd, syserr_to_errnum(rc)); + return -1; + } + return 0; + +#elif defined(__DOS__) + qse_httpd_seterrnum (httpd, QSE_HTTPD_ENOIMPL); + return -1; +#else + + if (QSE_MKDIR (path, 0755) <= -1) + { + qse_httpd_seterrnum (httpd, syserr_to_errnum(errno)); + return -1; + } + return 0; + +#endif +} + +static int dir_purge (qse_httpd_t* httpd, const qse_mchar_t* path) +{ +#if defined(_WIN32) + if (RemoveDirectoryA (path) == FALSE) + { + qse_httpd_seterrnum (httpd, syserr_to_errnum(GetLastError())); + return -1; + } +#elif defined(__OS2__) + + APIRET rc; + + rc = DosDeleteDir (path); /* TODO: is DosForceDelete better? */ + if (rc != NO_ERROR) + { + qse_httpd_seterrnum (httpd, syserr_to_errnum(rc)); + return -1; + } + return 0; + +#elif defined(__DOS__) + qse_httpd_seterrnum (httpd, QSE_HTTPD_ENOIMPL); + return -1; +#else + + if (QSE_RMDIR (path) <= -1) + { + qse_httpd_seterrnum (httpd, syserr_to_errnum(errno)); + return -1; + } + return 0; + +#endif +} + typedef struct dir_t dir_t; struct dir_t { @@ -1868,6 +1993,19 @@ if (qse_htre_getcontentlen(req) > 0) * if you don't like this behavior, you must implement your own * callback function for request handling. */ +#if 0 +/* TODO support X-HTTP-Method-Override */ + if (data.method == QSE_HTTP_POST) + { + tmp = qse_htre_getheaderval(req, QSE_MT("X-HTTP-Method-Override")); + if (tmp) + { + /*while (tmp->next) tmp = tmp->next;*/ /* get the last value */ + data.method = qse_mbstohttpmethod (tmp->ptr); + } + } +#endif + if (qse_htre_getqmethodtype(req) == QSE_HTTP_POST && !(req->attr.flags & QSE_HTRE_ATTR_LENGTH) && !(req->attr.flags & QSE_HTRE_ATTR_CHUNKED)) @@ -1908,7 +2046,6 @@ if (qse_htre_getcontentlen(req) > 0) * discard the contents since i won't return them */ if (rsrc.type == QSE_HTTPD_RSRC_ERR) { -qse_printf (QSE_T("DISCARD 3\n")); qse_httpd_discardcontent (httpd, req); } } @@ -2122,6 +2259,7 @@ static qse_httpd_scb_t httpd_system_callbacks = /* file operation */ { file_stat, + file_purge, file_ropen, file_wopen, file_close, @@ -2130,7 +2268,10 @@ static qse_httpd_scb_t httpd_system_callbacks = }, /* directory operation */ - { dir_open, + { dir_stat, + dir_make, + dir_purge, + dir_open, dir_close, dir_read }, @@ -2418,7 +2559,7 @@ static int make_resource ( struct rsrc_tmp_t tmp; qse_httpd_stat_t st; - int n, stx, method; + int n, stx, acc; QSE_MEMSET (&tmp, 0, QSE_SIZEOF(tmp)); tmp.qpath = qse_htre_getqpath(req); @@ -2429,41 +2570,27 @@ static int make_resource ( server_xtn = qse_httpd_getserverxtn (httpd, client->server); if (server_xtn->query (httpd, client->server, req, QSE_NULL, QSE_HTTPD_SERVERSTD_ROOT, &tmp.root) <= -1) return -1; - if (tmp.root.type == QSE_HTTPD_SERVERSTD_ROOT_NWAD) + switch (tmp.root.type) { - /* proxy the request */ - target->type = QSE_HTTPD_RSRC_PROXY; - /*target->u.proxy.dst = client->orgdst_addr;*/ - target->u.proxy.dst = tmp.root.u.nwad; - target->u.proxy.src = client->remote_addr; + case QSE_HTTPD_SERVERSTD_ROOT_NWAD: + /* proxy the request */ + target->type = QSE_HTTPD_RSRC_PROXY; + /*target->u.proxy.dst = client->orgdst_addr;*/ + target->u.proxy.dst = tmp.root.u.nwad; + target->u.proxy.src = client->remote_addr; - /* mark that this request is going to be proxied. */ - req->attr.flags |= QSE_HTRE_ATTR_PROXIED; - return 0; + /* mark that this request is going to be proxied. */ + req->attr.flags |= QSE_HTRE_ATTR_PROXIED; + return 0; + + case QSE_HTTPD_SERVERSTD_ROOT_TEXT: + target->type = QSE_HTTPD_RSRC_TEXT; + target->u.text.ptr = tmp.root.u.text.ptr; + target->u.text.mime = tmp.root.u.text.mime; + return 0; } /* handle the request locally */ -#if 0 - method = qse_htre_getqmethodtype(req); - switch (method) - { - case QSE_HTTP_HEAD: - case QSE_HTTP_GET: - case QSE_HTTP_POST: - case QSE_HTTP_PUT: - case QSE_HTTP_DELETE: - case QSE_HTTP_OPTIONS: - /* let these methods be handled locally */ - break; - - default: - /* method not allowed */ - target->type = QSE_HTTPD_RSRC_ERR; - target->u.err.code = 405; - return 0; - } -#endif - QSE_ASSERT (tmp.root.type == QSE_HTTPD_SERVERSTD_ROOT_PATH); if (server_xtn->query (httpd, client->server, req, QSE_NULL, QSE_HTTPD_SERVERSTD_REALM, &tmp.realm) <= -1 || @@ -2543,6 +2670,7 @@ auth_ok: else { /* Expectation Failed */ + qse_htre_discardcontent (req); target->type = QSE_HTTPD_RSRC_ERR; target->u.err.code = 417; return 0; @@ -2611,8 +2739,9 @@ auth_ok: /* it is a directory - should i allow it? */ if (server_xtn->query (httpd, client->server, req, tmp.xpath, QSE_HTTPD_SERVERSTD_DIRACC, &target->u.err.code) <= -1) target->u.err.code = 500; - if (target->u.err.code != 200) + if (target->u.err.code < 200 || target->u.err.code > 299) { + qse_htre_discardcontent (req); target->type = QSE_HTTPD_RSRC_ERR; /* free xpath since it won't be used */ QSE_MMGR_FREE (httpd->mmgr, tmp.xpath); @@ -2620,6 +2749,7 @@ auth_ok: else if (tmp.qpath[tmp.qpath_len - 1] != QSE_MT('/')) { /* the query path doesn't end with a slash. so redirect it */ + qse_htre_discardcontent (req); target->type = QSE_HTTPD_RSRC_REDIR; target->u.redir.dst = tmp.qpath; /* free xpath since it won't be used */ @@ -2651,11 +2781,16 @@ auth_ok: } if (n >= 1) return 0; + acc = (tmp.idxfile || !qse_mbsend(tmp.qpath, QSE_MT("/")))? + QSE_HTTPD_SERVERSTD_FILEACC: QSE_HTTPD_SERVERSTD_DIRACC; + /* check file's access permission */ - if (server_xtn->query (httpd, client->server, req, tmp.xpath, QSE_HTTPD_SERVERSTD_FILEACC, &target->u.err.code) <= -1) target->u.err.code = 500; - if (target->u.err.code != 200) + if (server_xtn->query (httpd, client->server, req, tmp.xpath, acc, &target->u.err.code) <= -1) target->u.err.code = 500; + + if (target->u.err.code < 200 || target->u.err.code > 299) { /* free xpath since it won't be used */ + qse_htre_discardcontent (req); QSE_MMGR_FREE (httpd->mmgr, tmp.xpath); target->type = QSE_HTTPD_RSRC_ERR; } @@ -2664,6 +2799,8 @@ auth_ok: /* fall back to a normal file. */ if (tmp.idxfile) { + qse_htre_discardcontent (req); + /* free xpath since it won't be used */ QSE_MMGR_FREE (httpd->mmgr, tmp.xpath); @@ -2672,6 +2809,11 @@ auth_ok: target->u.reloc.dst = merge_paths (httpd, tmp.qpath, tmp.idxfile); if (target->u.reloc.dst == QSE_NULL) return -1; } + else if (acc == QSE_HTTPD_SERVERSTD_DIRACC) + { + target->type = QSE_HTTPD_RSRC_DIR; + target->u.dir.path = tmp.xpath; + } else { target->type = QSE_HTTPD_RSRC_FILE; @@ -2802,8 +2944,26 @@ static int query_server ( case QSE_HTTPD_SERVERSTD_DIRACC: case QSE_HTTPD_SERVERSTD_FILEACC: - *(int*)result = 200; + { + /* i don't allow PUT or DELET by default. + * override this query result if you want to change + * the behavior. */ + switch (qse_htre_getqmethodtype(req)) + { + case QSE_HTTP_OPTIONS: + case QSE_HTTP_HEAD: + case QSE_HTTP_GET: + case QSE_HTTP_POST: + *(int*)result = 200; + break; + + default: + /* method not allowed */ + *(int*)result = 405; + break; + } return 0; + } } qse_httpd_seterrnum (httpd, QSE_HTTPD_EINVAL); diff --git a/qse/lib/http/httpd-task.c b/qse/lib/http/httpd-task.c index b116052d..4222e6d1 100644 --- a/qse/lib/http/httpd-task.c +++ b/qse/lib/http/httpd-task.c @@ -201,7 +201,8 @@ struct status_reloc_t static qse_httpd_task_t* entask_status ( qse_httpd_t* httpd, qse_httpd_client_t* client, qse_httpd_task_t* pred, int code, void* extra, - const qse_http_version_t* version, int keepalive) + qse_http_method_t method, const qse_http_version_t* version, + int keepalive) { const qse_mchar_t* msg; @@ -209,34 +210,44 @@ static qse_httpd_task_t* entask_status ( const qse_mchar_t* extrapst = QSE_MT(""); const qse_mchar_t* extraval = QSE_MT(""); - qse_mchar_t text[1024]; /* TODO: make this buffer dynamic or scalable */ + qse_mchar_t text[1024] = QSE_MT(""); /* TODO: make this buffer dynamic or scalable */ msg = qse_httpstatustombs (code); - if (code == 301 || code == 307) + switch (code) { - status_reloc_t* reloc; - - reloc = (status_reloc_t*)extra; - extrapre = QSE_MT("Location: "); - extrapst = reloc->redir? QSE_MT("/\r\n"): QSE_MT("\r\n"); - extraval = reloc->dst; - - text[0] = QSE_MT('\0'); - } - else if (code == 304) - { - text[0] = QSE_MT('\0'); - } - else - { - if (httpd->opt.rcb.fmterr (httpd, client, code, text, QSE_COUNTOF(text)) <= -1) return QSE_NULL; - - if (code == 401) + case 301: + case 307: { - extrapre = QSE_MT("WWW-Authenticate: Basic realm=\""); - extrapst = QSE_MT("\"\r\n"); - extraval = (const qse_mchar_t*)extra; + status_reloc_t* reloc; + reloc = (status_reloc_t*)extra; + extrapre = QSE_MT("Location: "); + extrapst = reloc->redir? QSE_MT("/\r\n"): QSE_MT("\r\n"); + extraval = reloc->dst; + break; } + + case 304: + case 200: + case 201: + case 202: + case 203: + case 204: + case 205: + case 206: + /* nothing to do */ + break; + + default: + if (method != QSE_HTTP_HEAD && + httpd->opt.rcb.fmterr (httpd, client, code, text, QSE_COUNTOF(text)) <= -1) return QSE_NULL; + + if (code == 401) + { + extrapre = QSE_MT("WWW-Authenticate: Basic realm=\""); + extrapst = QSE_MT("\"\r\n"); + extraval = (const qse_mchar_t*)extra; + } + break; } return qse_httpd_entaskformat ( @@ -254,19 +265,21 @@ static qse_httpd_task_t* entask_status ( qse_httpd_task_t* qse_httpd_entask_err ( qse_httpd_t* httpd, qse_httpd_client_t* client, qse_httpd_task_t* pred, int code, - const qse_http_version_t* version, int keepalive) + qse_http_method_t method, const qse_http_version_t* version, int keepalive) { - return entask_status (httpd, client, pred, code, QSE_NULL, version, keepalive); + return entask_status (httpd, client, pred, code, QSE_NULL, method, version, keepalive); } qse_httpd_task_t* qse_httpd_entaskerr ( qse_httpd_t* httpd, qse_httpd_client_t* client, qse_httpd_task_t* pred, int code, qse_htre_t* req) { - qse_htre_discardcontent (req); return entask_status ( httpd, client, pred, code, QSE_NULL, - qse_htre_getversion(req), (req->attr.flags & QSE_HTRE_ATTR_KEEPALIVE)); + qse_htre_getqmethodtype(req), + qse_htre_getversion(req), + (req->attr.flags & QSE_HTRE_ATTR_KEEPALIVE) + ); } /*------------------------------------------------------------------------*/ @@ -289,6 +302,7 @@ qse_httpd_task_t* qse_httpd_entaskauth ( { return entask_status ( httpd, client, pred, 401, (void*)realm, + qse_htre_getqmethodtype(req), qse_htre_getversion(req), (req->attr.flags & QSE_HTRE_ATTR_KEEPALIVE)); } @@ -296,21 +310,6 @@ qse_httpd_task_t* qse_httpd_entaskauth ( /*------------------------------------------------------------------------*/ -qse_httpd_task_t* qse_httpd_entask_reloc ( - qse_httpd_t* httpd, qse_httpd_client_t* client, - qse_httpd_task_t* pred, const qse_mchar_t* dst, - const qse_http_version_t* version, int keepalive) -{ - status_reloc_t reloc; - - reloc.dst = dst; - reloc.redir = 0; - - return entask_status ( - httpd, client, pred, 301, (void*)&reloc, - version, keepalive); -} - qse_httpd_task_t* qse_httpd_entaskreloc ( qse_httpd_t* httpd, qse_httpd_client_t* client, qse_httpd_task_t* pred, const qse_mchar_t* dst, qse_htre_t* req) @@ -322,25 +321,11 @@ qse_httpd_task_t* qse_httpd_entaskreloc ( return entask_status ( httpd, client, pred, 301, (void*)&reloc, + qse_htre_getqmethodtype(req), qse_htre_getversion(req), (req->attr.flags & QSE_HTRE_ATTR_KEEPALIVE)); } -qse_httpd_task_t* qse_httpd_entask_redir ( - qse_httpd_t* httpd, qse_httpd_client_t* client, - qse_httpd_task_t* pred, const qse_mchar_t* dst, - const qse_http_version_t* version, int keepalive) -{ - status_reloc_t reloc; - - reloc.dst = dst; - reloc.redir = 1; - - return entask_status ( - httpd, client, pred, 301, (void*)&reloc, - version, keepalive); -} - qse_httpd_task_t* qse_httpd_entaskredir ( qse_httpd_t* httpd, qse_httpd_client_t* client, qse_httpd_task_t* pred, const qse_mchar_t* dst, qse_htre_t* req) @@ -352,6 +337,7 @@ qse_httpd_task_t* qse_httpd_entaskredir ( return entask_status ( httpd, client, pred, 301, (void*)&reloc, + qse_htre_getqmethodtype(req), qse_htre_getversion(req), (req->attr.flags & QSE_HTRE_ATTR_KEEPALIVE)); } @@ -359,12 +345,11 @@ qse_httpd_task_t* qse_httpd_entaskredir ( /*------------------------------------------------------------------------*/ qse_httpd_task_t* qse_httpd_entask_nomod ( - qse_httpd_t* httpd, qse_httpd_client_t* client, - qse_httpd_task_t* pred, const qse_http_version_t* version, int keepalive) + qse_httpd_t* httpd, qse_httpd_client_t* client, qse_httpd_task_t* pred, + qse_http_method_t method, const qse_http_version_t* version, int keepalive) { return entask_status ( - httpd, client, pred, 304, - QSE_NULL, version, keepalive); + httpd, client, pred, 304, QSE_NULL, method, version, keepalive); } qse_httpd_task_t* qse_httpd_entasknomod ( @@ -372,13 +357,39 @@ qse_httpd_task_t* qse_httpd_entasknomod ( qse_httpd_task_t* pred, qse_htre_t* req) { return entask_status ( - httpd, client, pred, 304, - QSE_NULL, qse_htre_getversion(req), + httpd, client, pred, 304, QSE_NULL, + qse_htre_getqmethodtype(req), + qse_htre_getversion(req), (req->attr.flags & QSE_HTRE_ATTR_KEEPALIVE)); } /*------------------------------------------------------------------------*/ +qse_httpd_task_t* qse_httpd_entaskallow ( + qse_httpd_t* httpd, qse_httpd_client_t* client, + qse_httpd_task_t* pred, const qse_mchar_t* allow, qse_htre_t* req) +{ + int code = 200; + const qse_mchar_t* msg; + const qse_http_version_t* version; + int keepalive; + + msg = qse_httpstatustombs (code); + version = qse_htre_getversion(req); + keepalive = (req->attr.flags & QSE_HTRE_ATTR_KEEPALIVE); + return qse_httpd_entaskformat ( + httpd, client, pred, + QSE_MT("HTTP/%d.%d %d %s\r\nServer: %s\r\nDate: %s\r\nConnection: %s\r\nAllow: %s\r\nContent-Length: 0\r\n\r\n"), + version->major, version->minor, + code, msg, qse_httpd_getname (httpd), + qse_httpd_fmtgmtimetobb (httpd, QSE_NULL, 0), + (keepalive? QSE_MT("keep-alive"): QSE_MT("close")), + allow + ); +} + +/*------------------------------------------------------------------------*/ + #if 0 qse_httpd_task_t* qse_httpd_entaskconnect ( qse_httpd_t* httpd, diff --git a/qse/lib/http/httpd-text.c b/qse/lib/http/httpd-text.c index 19b39535..a15ba1e5 100644 --- a/qse/lib/http/httpd-text.c +++ b/qse/lib/http/httpd-text.c @@ -63,31 +63,6 @@ static int task_main_text ( return 1; /* more work to do */ } - -qse_httpd_task_t* qse_httpd_entask_text ( - qse_httpd_t* httpd, - qse_httpd_client_t* client, - qse_httpd_task_t* pred, - const qse_mchar_t* ptr, - qse_size_t len) -{ - qse_httpd_task_t task; - task_text_t data; - - QSE_MEMSET (&data, 0, QSE_SIZEOF(data)); - data.ptr = ptr; - data.left = len; - - QSE_MEMSET (&task, 0, QSE_SIZEOF(task)); - task.init = task_init_text; - task.main = task_main_text; - task.ctx = &data; - - return qse_httpd_entask ( - httpd, client, pred, - &task, QSE_SIZEOF(data) + data.left); -} - qse_httpd_task_t* qse_httpd_entasktext ( qse_httpd_t* httpd, qse_httpd_client_t* client, @@ -98,13 +73,33 @@ qse_httpd_task_t* qse_httpd_entasktext ( { qse_size_t tlen; qse_mchar_t b_tlen[64]; + qse_http_method_t method; qse_http_version_t* version; + qse_httpd_task_t task; + task_text_t data; + + method = qse_htre_getqmethodtype (req); version = qse_htre_getversion (req); - tlen = qse_mbslen(text); - qse_fmtuintmaxtombs (b_tlen, QSE_COUNTOF(b_tlen), tlen, 10, -1, QSE_MT('\0'), QSE_NULL); + qse_htre_discardcontent (req); + switch (method) + { + case QSE_HTTP_HEAD: + tlen = 0; + break; + case QSE_HTTP_GET: + case QSE_HTTP_POST: + tlen = qse_mbslen(text); + break; + + default: + /* Method not allowed */ + return qse_httpd_entaskerr (httpd, client, pred, 405, req); + } + + qse_fmtuintmaxtombs (b_tlen, QSE_COUNTOF(b_tlen), tlen, 10, -1, QSE_MT('\0'), QSE_NULL); pred = qse_httpd_entaskformat ( httpd, client, pred, QSE_MT("HTTP/%d.%d 200 OK\r\nServer: %s\r\nDate: %s\r\nConnection: %s\r\nContent-Type: %s\r\nContent-Length: %s\r\n\r\n"), @@ -116,5 +111,14 @@ qse_httpd_task_t* qse_httpd_entasktext ( ); if (pred == QSE_NULL) return QSE_NULL; - return qse_httpd_entask_text (httpd, client, pred, text, tlen); + QSE_MEMSET (&data, 0, QSE_SIZEOF(data)); + data.ptr = text; + data.left = tlen; + + QSE_MEMSET (&task, 0, QSE_SIZEOF(task)); + task.init = task_init_text; + task.main = task_main_text; + task.ctx = &data; + + return qse_httpd_entask (httpd, client, pred, &task, QSE_SIZEOF(data) + data.left); } diff --git a/qse/lib/http/httpd.h b/qse/lib/http/httpd.h index 46a2f180..ab9b2103 100644 --- a/qse/lib/http/httpd.h +++ b/qse/lib/http/httpd.h @@ -121,6 +121,7 @@ qse_httpd_task_t* qse_httpd_entask_err ( qse_httpd_client_t* client, qse_httpd_task_t* pred, int code, + qse_http_method_t method, const qse_http_version_t* version, int keepalive ); @@ -129,37 +130,11 @@ qse_httpd_task_t* qse_httpd_entask_nomod ( qse_httpd_t* httpd, qse_httpd_client_t* client, qse_httpd_task_t* pred, + qse_http_method_t method, const qse_http_version_t* version, int keepalive ); -qse_httpd_task_t* qse_httpd_entask_reloc ( - qse_httpd_t* httpd, - qse_httpd_client_t* client, - qse_httpd_task_t* pred, - const qse_mchar_t* dst, - const qse_http_version_t* version, - int keepalive -); - -qse_httpd_task_t* qse_httpd_entask_redir ( - qse_httpd_t* httpd, - qse_httpd_client_t* client, - qse_httpd_task_t* pred, - const qse_mchar_t* dst, - const qse_http_version_t* version, - int keepalive -); - - -qse_httpd_task_t* qse_httpd_entask_text ( - qse_httpd_t* httpd, - qse_httpd_client_t* client, - qse_httpd_task_t* pred, - const qse_mchar_t* ptr, - qse_size_t len -); - #ifdef __cplusplus } #endif