diff --git a/qse/include/qse/cmn/pio.h b/qse/include/qse/cmn/pio.h index b7b93414..056d0843 100644 --- a/qse/include/qse/cmn/pio.h +++ b/qse/include/qse/cmn/pio.h @@ -132,6 +132,8 @@ enum qse_pio_errnum_t QSE_PIO_ECHILD, /**< the child is not valid */ QSE_PIO_EINTR, /**< interrupted */ QSE_PIO_EPIPE, /**< broken pipe */ + QSE_PIO_EACCES, /**< access denied */ + QSE_PIO_ENOENT, /**< no such file */ QSE_PIO_ESUBSYS /**< subsystem(system call) error */ }; typedef enum qse_pio_errnum_t qse_pio_errnum_t; @@ -316,6 +318,16 @@ qse_pio_hnd_t qse_pio_gethandle ( qse_pio_hid_t hid /**< handle ID */ ); +/** + * The qse_pio_gethandleasubi() function gets a pipe handle wrapped + * in the #qse_ubi_t type. + * @return pipe handle + */ +qse_ubi_t qse_pio_gethandleasubi ( + qse_pio_t* pio, /**< pio object */ + qse_pio_hid_t hid /**< handle ID */ +); + /** * The qse_pio_getchild() function gets a process handle. * @return process handle diff --git a/qse/include/qse/net/htre.h b/qse/include/qse/net/htre.h index 5463ff70..8bd172b1 100644 --- a/qse/include/qse/net/htre.h +++ b/qse/include/qse/net/htre.h @@ -126,12 +126,6 @@ struct qse_htre_t #define qse_htre_setsmessagefromxstr(re,v) \ qse_htre_setstrfromxstr((re),qse_htre_getsmessage(re),(v)) -/* NOTE: setcontent() doesn't execute concb. use this with care */ -#define qse_htre_setcontentfromcstr(re,v) \ - qse_htre_setstrfromcstr((re),qse_htre_getcontent(re),(v)) -#define qse_htre_setcontentfromxstr(re,v) \ - qse_htre_setstrfromxstr((re),qse_htre_getcontent(re),(v)) - typedef int (*qse_htre_header_walker_t) ( qse_htre_t* re, const qse_mchar_t* key, diff --git a/qse/include/qse/net/httpd.h b/qse/include/qse/net/httpd.h index e397e17f..566e68a9 100644 --- a/qse/include/qse/net/httpd.h +++ b/qse/include/qse/net/httpd.h @@ -34,13 +34,15 @@ enum qse_httpd_errnum_t QSE_HTTPD_ENOERR, QSE_HTTPD_ENOMEM, QSE_HTTPD_EINVAL, + QSE_HTTPD_ENOENT, + QSE_HTTPD_EACCES, QSE_HTTPD_EINTERN, QSE_HTTPD_EIOMUX, + QSE_HTTPD_ESUBSYS, QSE_HTTPD_ESOCKET, QSE_HTTPD_EDISCON, /* client disconnnected */ QSE_HTTPD_EBADREQ, /* bad request */ - QSE_HTTPD_ETASK, - QSE_HTTPD_ECOMCBS + QSE_HTTPD_ETASK }; typedef enum qse_httpd_errnum_t qse_httpd_errnum_t; @@ -184,6 +186,15 @@ void qse_httpd_close ( qse_httpd_t* httpd ); +qse_httpd_errnum_t qse_httpd_geterrnum ( + qse_httpd_t* httpd +); + +void qse_httpd_seterrnum ( + qse_httpd_t* httpd, + qse_httpd_errnum_t errnum +); + int qse_httpd_getoption ( qse_httpd_t* httpd ); @@ -214,11 +225,6 @@ int qse_httpd_addlistener ( const qse_char_t* uri ); -void qse_httpd_markbadclient ( - qse_httpd_t* httpd, - qse_httpd_client_t* client -); - void qse_httpd_discardcontent ( qse_httpd_t* httpd, qse_htre_t* req diff --git a/qse/lib/cmn/pio.c b/qse/lib/cmn/pio.c index 5df82a85..dde8f3c4 100644 --- a/qse/lib/cmn/pio.c +++ b/qse/lib/cmn/pio.c @@ -288,6 +288,35 @@ oops: return -1; } +static int assert_executable (qse_pio_t* pio, const qse_mchar_t* path) +{ + qse_lstat_t st; + + if (QSE_ACCESS(path, X_OK) <= -1) + { + if (errno == EACCES) pio->errnum = QSE_PIO_EACCES; + else if (errno == ENOENT) pio->errnum = QSE_PIO_ENOENT; + else if (errno == ENOMEM) pio->errnum = QSE_PIO_ENOMEM; + return -1; + } + + if (QSE_LSTAT(path, &st) <= -1) + { + if (errno == EACCES) pio->errnum = QSE_PIO_EACCES; + else if (errno == ENOENT) pio->errnum = QSE_PIO_ENOENT; + else if (errno == ENOMEM) pio->errnum = QSE_PIO_ENOMEM; + return -1; + } + + if (!S_ISREG(st.st_mode)) + { + pio->errnum = QSE_PIO_EACCES; + return -1; + } + + return 0; +} + static QSE_INLINE int is_fd_valid (int fd) { return fcntl (fd, F_GETFD) != -1 || errno != EBADF; @@ -572,7 +601,26 @@ int qse_pio_init ( ); QSE_MMGR_FREE (mmgr, dupcmd); - if (x == FALSE) goto oops; + if (x == FALSE) + { + DWORD e = GetLastError (); + switch (e) + { + case ERROR_ACCESS_DENIED: + pio->errnum = QSE_PIO_EACCES; + break; + + case ERROR_FILE_NOT_FOUND: + case ERROR_PATH_NOT_FOUND: + pio->errnum = QSE_PIO_ENOENT; + break; + + case ERROR_NOT_ENOUGH_MEMORY: + case ERROR_OUTOFMEMORY: + pio->errnum = QSE_PIO_ENOMEM; + break; + } + } } if (windevnul != INVALID_HANDLE_VALUE) @@ -871,6 +919,8 @@ int qse_pio_init ( cmd_file ); +/* TODO: translate error code ... */ + QSE_MMGR_FREE (mmgr, cmd_line); cmd_line = QSE_NULL; @@ -994,6 +1044,16 @@ int qse_pio_init ( } if (make_param (pio, cmd, flags, ¶m) <= -1) goto oops; + + /* check if the command(the command requested or /bin/sh) is + * exectuable to return an error without trying to execute it + * though this check alone isn't sufficient */ + if (assert_executable (pio, param.argv[0]) <= -1) + { + free_param (pio, ¶m); + goto oops; + } + spawn_ret = posix_spawn( &pid, param.argv[0], &fa, QSE_NULL, param.argv, (env? qse_env_getarr(env): environ)); @@ -1050,8 +1110,23 @@ int qse_pio_init ( goto oops; } + if (make_param (pio, cmd, flags, ¶m) <= -1) goto oops; + + /* check if the command(the command requested or /bin/sh) is + * exectuable to return an error without trying to execute it + * though this check alone isn't sufficient */ + if (assert_executable (pio, param.argv[0]) <= -1) + { + free_param (pio, ¶m); + goto oops; + } + pid = QSE_FORK(); - if (pid <= -1) goto oops; + if (pid <= -1) + { + free_param (pio, ¶m); + goto oops; + } if (pid == 0) { @@ -1145,7 +1220,7 @@ int qse_pio_init ( if (flags & QSE_PIO_DROPOUT) QSE_CLOSE(1); if (flags & QSE_PIO_DROPERR) QSE_CLOSE(2); - if (make_param (pio, cmd, flags, ¶m) <= -1) goto child_oops; + /*if (make_param (pio, cmd, flags, ¶m) <= -1) goto child_oops;*/ QSE_EXECVE (param.argv[0], param.argv, (env? qse_env_getarr(env): environ)); free_param (pio, ¶m); @@ -1155,6 +1230,7 @@ int qse_pio_init ( } /* parent */ + free_param (pio, ¶m); pio->child = pid; if (flags & QSE_PIO_WRITEIN) @@ -1337,6 +1413,8 @@ const qse_char_t* qse_pio_geterrmsg (qse_pio_t* pio) QSE_T("child process not valid"), QSE_T("interruped"), QSE_T("broken pipe"), + QSE_T("access denied"), + QSE_T("no such file"), QSE_T("systeam call error"), QSE_T("unknown error") }; @@ -1362,6 +1440,23 @@ qse_pio_hnd_t qse_pio_gethandle (qse_pio_t* pio, qse_pio_hid_t hid) return pio->pin[hid].handle; } +qse_ubi_t qse_pio_gethandleasubi (qse_pio_t* pio, qse_pio_hid_t hid) +{ + qse_ubi_t handle; + +#if defined(_WIN32) + handle.ptr = pio->pin[hid].handle; +#elif defined(__OS2__) + handle.ul = pio->pin[hid].handle; +#elif defined(__DOS__) + handle.i = pio->pin[hid].handle; +#else + handle.i = pio->pin[hid].handle; +#endif + + return handle; +} + qse_pio_pid_t qse_pio_getchild (qse_pio_t* pio) { return pio->child; diff --git a/qse/lib/net/htre.c b/qse/lib/net/htre.c index 541769a0..b4b2269d 100644 --- a/qse/lib/net/htre.c +++ b/qse/lib/net/htre.c @@ -45,10 +45,14 @@ void qse_htre_fini (qse_htre_t* re) void qse_htre_clear (qse_htre_t* re) { - if (re->concb) + if (!(re->state & QSE_HTRE_COMPLETED) && + !(re->state & QSE_HTRE_DISCARDED)) { - re->concb (re, QSE_NULL, 0, re->concb_ctx); /* indicate end of content */ - qse_htre_unsetconcb (re); + if (re->concb) + { + re->concb (re, QSE_NULL, 0, re->concb_ctx); /* indicate end of content */ + qse_htre_unsetconcb (re); + } } QSE_MEMSET (&re->version, 0, QSE_SIZEOF(re->version)); @@ -117,25 +121,73 @@ int qse_htre_walkheaders ( int qse_htre_addcontent ( qse_htre_t* re, const qse_mchar_t* ptr, qse_size_t len) { - if (re->state & (QSE_HTRE_COMPLETED | QSE_HTRE_DISCARDED)) return 0; + /* see comments in qse_htre_discardcontent() */ - /* if the callback is set, the content goes to the callback. */ - if (re->concb) return re->concb (re, ptr, len, re->concb_ctx); - /* if the callback is not set, the contents goes to the internal buffer */ - if (qse_mbs_ncat (&re->content, ptr, len) == (qse_size_t)-1) return -1; + if (re->state & (QSE_HTRE_COMPLETED | QSE_HTRE_DISCARDED)) return 0; /* skipped */ + + if (re->concb) + { + /* if the callback is set, the content goes to the callback. */ + if (re->concb (re, ptr, len, re->concb_ctx) <= -1) return -1; + } + else + { + /* if the callback is not set, the contents goes to the internal buffer */ + if (qse_mbs_ncat (&re->content, ptr, len) == (qse_size_t)-1) + return -1; + } return 1; /* added successfully */ } void qse_htre_completecontent (qse_htre_t* re) { - re->state |= QSE_HTRE_COMPLETED; + /* see comments in qse_htre_discardcontent() */ + + if (!(re->state & QSE_HTRE_COMPLETED) && + !(re->state & QSE_HTRE_DISCARDED)) + { + re->state |= QSE_HTRE_COMPLETED; + if (re->concb) + { + /* indicate end of content */ + re->concb (re, QSE_NULL, 0, re->concb_ctx); + } + } } void qse_htre_discardcontent (qse_htre_t* re) { - re->state |= QSE_HTRE_DISCARDED; - qse_mbs_clear (&re->content); + /* you can't discard this if it's completed. + * you can't complete this if it's discarded + * you can't add contents to this if it's completed or discarded + */ + + if (!(re->state & QSE_HTRE_COMPLETED) && + !(re->state & QSE_HTRE_DISCARDED)) + { + re->state |= QSE_HTRE_DISCARDED; + + /* qse_htre_addcontent()... + * qse_thre_setconcb()... + * qse_htre_discardcontent()... <-- POINT A. + * + * at point A, the content must contain something + * and concb is also set. for simplicity, + * clear the content buffer and invoke the callback + * + * likewise, you may produce many weird combinations + * of these functions. however, these functions are + * designed to serve a certain usage pattern not including + * weird combinations. + */ + qse_mbs_clear (&re->content); + if (re->concb) + { + /* indicate end of content */ + re->concb (re, QSE_NULL, 0, re->concb_ctx); + } + } } void qse_htre_unsetconcb (qse_htre_t* re) diff --git a/qse/lib/net/httpd.c b/qse/lib/net/httpd.c index e687fd0f..7d81dcd0 100644 --- a/qse/lib/net/httpd.c +++ b/qse/lib/net/httpd.c @@ -97,6 +97,16 @@ void qse_httpd_stop (qse_httpd_t* httpd) httpd->stopreq = 1; } +qse_httpd_errnum_t qse_httpd_geterrnum (qse_httpd_t* httpd) +{ + return httpd->errnum; +} + +void qse_httpd_seterrnum (qse_httpd_t* httpd, qse_httpd_errnum_t errnum) +{ + httpd->errnum = errnum; +} + int qse_httpd_getoption (qse_httpd_t* httpd) { return httpd->option; @@ -665,7 +675,7 @@ static void perform_task (qse_httpd_t* httpd, qse_httpd_client_t* client) { dequeue_task (httpd, client); /*shutdown (client->handle.i, SHUT_RDWR);*/ - client->bad = 1; + client->bad = 1; } else if (n == 0) { @@ -675,7 +685,7 @@ static void perform_task (qse_httpd_t* httpd, qse_httpd_client_t* client) static int read_from_client (qse_httpd_t* httpd, qse_httpd_client_t* client) { - qse_mchar_t buf[1024]; + qse_mchar_t buf[2048]; /* TODO: adjust this buffer size */ qse_ssize_t m; QSE_ASSERT (httpd->cbs->client.recv != QSE_NULL); @@ -803,87 +813,87 @@ qse_fprintf (QSE_STDERR, QSE_T("Error: select returned failure\n")); QSE_ASSERT (httpd->cbs->client.accepted != QSE_NULL); int x = httpd->cbs->client.accepted (httpd, client); /* is this correct???? what if ssl handshaking got stalled because writing failed in SSL_accept()? */ if (x >= 1) client->ready = 1; - else if (x <= -1) goto bad_client; + else if (x <= -1) + { + delete_from_client_array (httpd, fd); + continue; + } } else { +// TODO: any way to suspend read while a request is being processed??? if (read_from_client (httpd, client) <= -1) { - bad_client: - if (httpd->threaded) - { - /* let the writing part handle it, - * probably in the next iteration */ - qse_httpd_markbadclient (httpd, client); - shutdown (client->handle.i, SHUT_RDWR); - } - else - { - delete_from_client_array (httpd, fd); - continue; /* don't need to go to the writing part */ - } + delete_from_client_array (httpd, fd); + continue; } } } - if (!httpd->threaded) + /* perform a client task enqued to a client */ + if (client->task.queue.head) { - if (client->bad) + qse_httpd_task_t* task; + int perform = 0; + +qse_printf (QSE_T(".....CLIENT %d HAS TASK\n"), fd); + task = &client->task.queue.head->task; + task->trigger_mask &= + ~(QSE_HTTPD_TASK_TRIGGER_READABLE | + QSE_HTTPD_TASK_TRIGGER_RELAYABLE | + QSE_HTTPD_TASK_TRIGGER_WRITABLE); + + if (!(task->trigger_mask & QSE_HTTPD_TASK_TRIGGER_READ) && + !(task->trigger_mask & QSE_HTTPD_TASK_TRIGGER_RELAY) && + !(task->trigger_mask & QSE_HTTPD_TASK_TRIGGER_WRITE)) { - /*shutdown (client->handle.i, SHUT_RDWR);*/ - delete_from_client_array (httpd, fd); +qse_printf (QSE_T(".....NO TRIGGER ACTION....\n")); + /* no trigger set. set the flag to + * non-readable and non-writable */ + perform = 1; } - else if (client->task.queue.head) + else { - qse_httpd_task_t* task; - int perform = 0; - - task = &client->task.queue.head->task; - task->trigger_mask &= - ~(QSE_HTTPD_TASK_TRIGGER_READABLE | - QSE_HTTPD_TASK_TRIGGER_RELAYABLE | - QSE_HTTPD_TASK_TRIGGER_WRITABLE); - - if (!(task->trigger_mask & QSE_HTTPD_TASK_TRIGGER_READ) && - !(task->trigger_mask & QSE_HTTPD_TASK_TRIGGER_RELAY) && - !(task->trigger_mask & QSE_HTTPD_TASK_TRIGGER_WRITE)) +if (task->trigger_mask & QSE_HTTPD_TASK_TRIGGER_READ) +qse_printf (QSE_T(".....CLIENT %d HAS READ TREIGGER %d\n"), fd, task->trigger[0].i); +if (task->trigger_mask & QSE_HTTPD_TASK_TRIGGER_RELAY) +qse_printf (QSE_T(".....CLIENT %d HAS RELAY TREIGGER %d\n"), fd, task->trigger[1].i); +if (task->trigger_mask & QSE_HTTPD_TASK_TRIGGER_WRITE) +qse_printf (QSE_T(".....CLIENT %d HAS WRITE TREIGGER %d\n"), fd, task->trigger[2].i); + if ((task->trigger_mask & QSE_HTTPD_TASK_TRIGGER_READ) && + FD_ISSET(task->trigger[0].i, &r)) { - /* no trigger set. set the flag to - * non-readable and non-writable */ + task->trigger_mask |= QSE_HTTPD_TASK_TRIGGER_READABLE; perform = 1; +qse_printf (QSE_T(".....TRIGGER READABLE....\n")); } - else - { - if ((task->trigger_mask & QSE_HTTPD_TASK_TRIGGER_READ) && - FD_ISSET(task->trigger[0].i, &r)) - { - task->trigger_mask |= QSE_HTTPD_TASK_TRIGGER_READABLE; - perform = 1; - } - if ((task->trigger_mask & QSE_HTTPD_TASK_TRIGGER_RELAY) && - FD_ISSET(task->trigger[1].i, &r)) - { - task->trigger_mask |= QSE_HTTPD_TASK_TRIGGER_RELAYABLE; - perform = 1; - } - - if ((task->trigger_mask & QSE_HTTPD_TASK_TRIGGER_RELAY) && - FD_ISSET(task->trigger[2].i, &w)) - { - task->trigger_mask |= QSE_HTTPD_TASK_TRIGGER_WRITABLE; - perform = 1; - } - } - - if (perform) + if ((task->trigger_mask & QSE_HTTPD_TASK_TRIGGER_RELAY) && + FD_ISSET(task->trigger[1].i, &r)) { - /* TODO: error handling -> writable() returns <= -1 */ - /* TODO: though the client side is not writable, can't i still exeucte the task? - * if the task needs to transfer anything yet.. it can do that. - * i probably need a new trigger type??? */ - if (httpd->cbs->mux.writable (httpd, client->handle, 0) >= 1) - perform_task (httpd, client); + task->trigger_mask |= QSE_HTTPD_TASK_TRIGGER_RELAYABLE; + perform = 1; +qse_printf (QSE_T(".....TRIGGER RELAYABLE....\n")); + } + + if ((task->trigger_mask & QSE_HTTPD_TASK_TRIGGER_WRITE) && + FD_ISSET(task->trigger[2].i, &w)) + { + task->trigger_mask |= QSE_HTTPD_TASK_TRIGGER_WRITABLE; + perform = 1; +qse_printf (QSE_T(".....TRIGGER WRITABLE....\n")); + } + } + + if (perform) + { + /* TODO: error handling -> writable() returns <= -1 */ + /* TODO: though the client side is not writable, can't i still exeucte the task? + * if the task needs to transfer anything yet.. it can do that. + * i probably need a new trigger type??? */ + if (httpd->cbs->mux.writable (httpd, client->handle, 0) >= 1) + { + perform_task (httpd, client); } } } @@ -1109,18 +1119,7 @@ qse_httpd_task_t* qse_httpd_entask ( const qse_httpd_task_t* pred, const qse_httpd_task_t* task, qse_size_t xtnsize) { - qse_httpd_task_t* ret; - ret = enqueue_task (httpd, client, pred, task, xtnsize); - if (ret == QSE_NULL) client->bad = 1; /* mark this client bad */ - return ret; -} - -void qse_httpd_markbadclient (qse_httpd_t* httpd, qse_httpd_client_t* client) -{ - /* mark that something is wrong in processing requests from this client. - * this client could be bad... or the system could encounter some errors - * like memory allocation failure */ - client->bad = 1; + return enqueue_task (httpd, client, pred, task, xtnsize); } void qse_httpd_discardcontent (qse_httpd_t* httpd, qse_htre_t* req) diff --git a/qse/lib/net/httpd.h b/qse/lib/net/httpd.h index 7b4ff384..74201470 100644 --- a/qse/lib/net/httpd.h +++ b/qse/lib/net/httpd.h @@ -30,10 +30,6 @@ #include #include -#if defined(HAVE_PTHREAD) -# include -#endif - #ifndef SHUT_RDWR # define SHUT_RDWR 2 #endif @@ -64,17 +60,14 @@ struct qse_httpd_client_t qse_ubi_t handle2; int ready; - int bad; int secure; + int bad; sockaddr_t local_addr; sockaddr_t remote_addr; qse_htrd_t* htrd; struct { -#if defined(HAVE_PTHREAD) - pthread_mutex_t mutex; -#endif struct { int count; @@ -120,23 +113,13 @@ struct qse_httpd_t int option; int stopreq; - int threaded; - struct { -#if defined(HAVE_PTHREAD) - int pfd[2]; - pthread_mutex_t mutex; - pthread_cond_t cond; -#endif client_array_t array; } client; struct { -#if defined(HAVE_PTHREAD) - pthread_mutex_t mutex; -#endif listener_t* list; fd_set set; int max; diff --git a/qse/lib/net/httpd_task.c b/qse/lib/net/httpd_task.c index 95dc8fab..b0e73032 100644 --- a/qse/lib/net/httpd_task.c +++ b/qse/lib/net/httpd_task.c @@ -262,7 +262,7 @@ qse_httpd_task_t* qse_httpd_entaskformat ( capa = capa * 2; buf = (qse_mchar_t*) qse_httpd_allocmem (httpd, (capa + 1) * QSE_SIZEOF(*buf)); - if (buf == QSE_NULL) return QSE_NULL; + if (buf == QSE_NULL) return QSE_NULL; } else break; } @@ -272,7 +272,8 @@ qse_httpd_task_t* qse_httpd_entaskformat ( /* vsnprintf returns the number of characters that would * have been written not including the terminating '\0' * if the _data buffer were large enough */ - buf = (qse_mchar_t*) qse_httpd_allocmem (httpd, (bytes_req + 1) * QSE_SIZEOF(*buf)); + buf = (qse_mchar_t*) qse_httpd_allocmem ( + httpd, (bytes_req + 1) * QSE_SIZEOF(*buf)); if (buf == QSE_NULL) return QSE_NULL; va_start (ap, fmt); @@ -287,7 +288,6 @@ qse_httpd_task_t* qse_httpd_entaskformat ( { /* something got wrong ... */ qse_httpd_freemem (httpd, buf); - httpd->errnum = QSE_HTTPD_EINTERN; return QSE_NULL; } @@ -1116,17 +1116,17 @@ qse_httpd_task_t* qse_httpd_entaskpath ( { qse_httpd_task_t task; task_path_t data; - const qse_mchar_t* range; + const qse_mchar_t* tmp; QSE_MEMSET (&data, 0, QSE_SIZEOF(data)); data.name = name; data.version = *qse_htre_getversion(req); data.keepalive = req->attr.keepalive; - range = qse_htre_getheaderval(req, QSE_MT("Range")); - if (range) + tmp = qse_htre_getheaderval(req, QSE_MT("Range")); + if (tmp) { - if (qse_parsehttprange (range, &data.range) <= -1) + if (qse_parsehttprange (tmp, &data.range) <= -1) { return entask_error (httpd, client, pred, 416, &data.version, data.keepalive); } @@ -1196,7 +1196,7 @@ static int task_main_fseg ( httpd, client, ctx->handle, &ctx->offset, count); if (n <= -1) { -// HANDLE EGAIN specially??? +/* HANDLE EGAIN specially??? */ return -1; /* TODO: any logging */ } @@ -1268,17 +1268,17 @@ static QSE_INLINE int task_main_file ( /* TODO: if you should deal with files on a network-mounted drive, setting a trigger or non-blocking I/O are needed. */ - /* when it comes to the file size, using fstat after opening - * can be more accurate. but this function uses information - * set into the task context before the call to this function */ - qse_printf (QSE_T("opening file %hs\n"), file->path); + httpd->errnum = QSE_HTTPD_ENOERR; if (httpd->cbs->file.ropen (httpd, file->path, &handle, &filesize) <= -1) { -/* TODO: depending on the error type, either 404 or 403??? */ + int http_errnum; + http_errnum = (httpd->errnum == QSE_HTTPD_ENOENT)? 404: + (httpd->errnum == QSE_HTTPD_EACCES)? 403: 500; x = entask_error ( - httpd, client, x, 404, &file->version, file->keepalive); + httpd, client, x, http_errnum, + &file->version, file->keepalive); goto no_file_send; } fileopen = 1; @@ -1414,17 +1414,17 @@ qse_httpd_task_t* qse_httpd_entaskfile ( { qse_httpd_task_t task; task_file_t data; - const qse_mchar_t* range; + const qse_mchar_t* tmp; QSE_MEMSET (&data, 0, QSE_SIZEOF(data)); data.path = path; data.version = *qse_htre_getversion(req); data.keepalive = req->attr.keepalive; - range = qse_htre_getheaderval(req, QSE_MT("Range")); - if (range) + tmp = qse_htre_getheaderval(req, QSE_MT("Range")); + if (tmp) { - if (qse_parsehttprange (range, &data.range) <= -1) + if (qse_parsehttprange (tmp, &data.range) <= -1) { return qse_httpd_entaskerror (httpd, client, pred, 416, req); } @@ -1433,6 +1433,14 @@ qse_httpd_task_t* qse_httpd_entaskfile ( { data.range.type = QSE_HTTP_RANGE_NONE; } + +/* +TODO: If-Modified-Since... + tmp = qse_htre_getheaderval(req, QSE_MT("If-Modified-Since")); + if (tmp) + { + } +*/ QSE_MEMSET (&task, 0, QSE_SIZEOF(task)); task.init = task_init_file; @@ -1463,12 +1471,13 @@ struct task_cgi_t int keepalive; /* taken from the request */ int nph; - qse_env_t* env; - qse_pio_t* pio; qse_htrd_t* htrd; + qse_env_t* env; + qse_pio_t pio; + int pio_inited; qse_htre_t* req; /* original request associated with this */ - qse_mbs_t* reqcon; /* content from the request */ + qse_mbs_t* reqfwdbuf; /* content from the request */ int reqfwderr; qse_mbs_t* res; @@ -1807,89 +1816,136 @@ oops: static int cgi_snatch_content ( qse_htre_t* req, const qse_mchar_t* ptr, qse_size_t len, void* ctx) { - task_cgi_t* cgi = (task_cgi_t*)ctx; + qse_httpd_task_t* task; + task_cgi_t* cgi; + + task = (qse_httpd_task_t*)ctx; + cgi = (task_cgi_t*)task->ctx; if (ptr) qse_printf (QSE_T("!!!SNATCHING [%.*hs]\n"), len, ptr); else qse_printf (QSE_T("!!!SNATCHING DONE\n")); if (ptr == QSE_NULL) { - /* request ended. this could be a real end or - * abortion for an error */ + /* + * this callback is called with ptr of QSE_NULL + * when the request is completed or discarded. + * and this indicates that there's nothing more to read + * from the client side. this can happen when the end of + * a request is seen or when an error occurs + */ QSE_ASSERT (len == 0); - cgi->req = QSE_NULL; -/* TODO: probably need to add a write trigger.... -until cgi->reqcon is emptied... -cannot clear triogters since there are jobs depending on readbility or realyableilityy -*/ + + /* mark the there's nothing to read form the client side */ + cgi->req = QSE_NULL; + + /* since there is no more to read from the client side. + * the relay trigger is not needed any more. */ + task->trigger_mask &= + ~(QSE_HTTPD_TASK_TRIGGER_RELAY | + QSE_HTTPD_TASK_TRIGGER_RELAYABLE); + + if (QSE_MBS_LEN(cgi->reqfwdbuf) > 0 && cgi->pio_inited && + !(task->trigger_mask & QSE_HTTPD_TASK_TRIGGER_WRITE)) + { + /* there's nothing more to read from the client side. + * there's something to forward in the forwarding buffer. + * but no write trigger is set. add the write trigger + * for task invocation. */ + task->trigger_mask |= QSE_HTTPD_TASK_TRIGGER_WRITE; + task->trigger_mask &= ~QSE_HTTPD_TASK_TRIGGER_WRITABLE; + task->trigger[2] = qse_pio_gethandleasubi (&cgi->pio, QSE_PIO_IN); + } } else if (!cgi->reqfwderr) { - /* push the contents to the own buffer */ - if (qse_mbs_ncat (cgi->reqcon, ptr, len) == (qse_size_t)-1) + /* we can write to the child process if a forwarding error + * didn't occur previously. we store data from the client side + * to the forwaring buffer only if there's no such previous + * error. if an error occurred, we simply drop the data. */ + if (qse_mbs_ncat (cgi->reqfwdbuf, ptr, len) == (qse_size_t)-1) { return -1; } -qse_printf (QSE_T("!!!SNACHED [%.*hs]\n"), len, ptr); +qse_printf (QSE_T("!!!SNATCHED [%.*hs]\n"), len, ptr); } return 0; } -static void cgi_forward_content (qse_httpd_t* httpd, qse_httpd_task_t* task) +static void cgi_forward_content ( + qse_httpd_t* httpd, qse_httpd_task_t* task, int writable) { task_cgi_t* cgi = (task_cgi_t*)task->ctx; - QSE_ASSERT (cgi->reqcon != QSE_NULL); + QSE_ASSERT (cgi->reqfwdbuf != QSE_NULL); - if (QSE_MBS_LEN(cgi->reqcon) > 0) + if (QSE_MBS_LEN(cgi->reqfwdbuf) > 0) { + /* there is something to forward in the forwarding buffer. */ + if (cgi->reqfwderr) { - qse_mbs_clear (cgi->reqcon); + /* a forwarding error has occurred previously. + * clear the forwarding buffer */ +qse_printf (QSE_T("FORWARD: CLEARING REQCON FOR ERROR\n")); + qse_mbs_clear (cgi->reqfwdbuf); } else { - qse_ubi_t handle; + /* normal forwarding */ qse_ssize_t n; - /* TODO: handle = qse_pio_getubihandle(); */ - handle.i = qse_pio_gethandle (cgi->pio, QSE_PIO_IN); + if (writable) goto forward; - n = httpd->cbs->mux.writable (httpd, handle, 0); + n = httpd->cbs->mux.writable ( + httpd, qse_pio_gethandleasubi (&cgi->pio, QSE_PIO_IN), 0); +if (n == 0) qse_printf (QSE_T("FORWARD: @@@@@@@@@NOT WRITABLE\n")); if (n >= 1) { + forward: /* writable */ -qse_printf (QSE_T("@@@@@@@@@@WRITING[%.*hs]\n"), - (int)QSE_MBS_LEN(cgi->reqcon), - QSE_MBS_PTR(cgi->reqcon)); +qse_printf (QSE_T("FORWARD: @@@@@@@@@@WRITING[%.*hs]\n"), + (int)QSE_MBS_LEN(cgi->reqfwdbuf), + QSE_MBS_PTR(cgi->reqfwdbuf)); n = qse_pio_write ( - cgi->pio, QSE_PIO_IN, - QSE_MBS_PTR(cgi->reqcon), - QSE_MBS_LEN(cgi->reqcon) + &cgi->pio, QSE_PIO_IN, + QSE_MBS_PTR(cgi->reqfwdbuf), + QSE_MBS_LEN(cgi->reqfwdbuf) ); -/* TODO: improve performance.. instead of copying the remaing part to the head all the time.. -grow the buffer to a certain limit. */ - if (n > 0) qse_mbs_del (cgi->reqcon, 0, n); +/* TODO: improve performance.. instead of copying the remaing part +to the head all the time.. grow the buffer to a certain limit. */ + if (n > 0) qse_mbs_del (cgi->reqfwdbuf, 0, n); } if (n <= -1) { -qse_printf (QSE_T("@@@@@@@@WRITE TO CGI FAILED\n")); +qse_printf (QSE_T("FORWARD: @@@@@@@@WRITE TO CGI FAILED\n")); /* TODO: logging ... */ cgi->reqfwderr = 1; - qse_mbs_clear (cgi->reqcon); - if (cgi->req) qse_htre_discardcontent (cgi->req); + qse_mbs_clear (cgi->reqfwdbuf); + if (cgi->req) + { + qse_htre_discardcontent (cgi->req); + /* NOTE: cgi->req may be set to QSE_NULL + * in cgi_snatch_content() triggered by + * qse_htre_discardcontent() */ + } } } } else if (cgi->req == QSE_NULL) { - /* no more request content */ -qse_printf (QSE_T("@@@@@@@@NOTHING MORE TO WRITE TO CGI\n")); + /* there is nothing to read from the client side and + * there is nothing more to forward in the forwarding buffer. + * clear the relay and write triggers. + */ +qse_printf (QSE_T("FORWARD: @@@@@@@@NOTHING MORE TO WRITE TO CGI\n")); task->trigger_mask &= ~(QSE_HTTPD_TASK_TRIGGER_RELAY | - QSE_HTTPD_TASK_TRIGGER_RELAYABLE); + QSE_HTTPD_TASK_TRIGGER_RELAYABLE | + QSE_HTTPD_TASK_TRIGGER_WRITE | + QSE_HTTPD_TASK_TRIGGER_WRITABLE); } } @@ -1950,14 +2006,14 @@ qse_printf (QSE_T("ZZZZZZZZZZZZZZZ\n")); { /* create a buffer to hold request content from the client * and copy content received already */ - cgi->reqcon = qse_mbs_open (httpd->mmgr, 0, (len < 512? 512: len)); - if (cgi->reqcon == QSE_NULL) goto oops; + cgi->reqfwdbuf = qse_mbs_open (httpd->mmgr, 0, (len < 512? 512: len)); + if (cgi->reqfwdbuf == QSE_NULL) goto oops; ptr = qse_htre_getcontentptr(arg->req); - if (qse_mbs_ncpy (cgi->reqcon, ptr, len) == (qse_size_t)-1) + if (qse_mbs_ncpy (cgi->reqfwdbuf, ptr, len) == (qse_size_t)-1) { - qse_mbs_close (cgi->reqcon); - cgi->reqcon = QSE_NULL; + qse_mbs_close (cgi->reqfwdbuf); + cgi->reqfwdbuf = QSE_NULL; goto oops; } @@ -1985,10 +2041,10 @@ qse_printf (QSE_T("HHHHHHHHHHHHHHHHhh %d\n"), (int)len); if the request is already set up with a callback, something will go wrong. */ /* set up a callback to be called when the request content - * is fed to the htrd reader. qse_htre_addcontent() called - * by htrd invokes this callback. */ + * is fed to the htrd reader. qse_htre_addcontent() that + * htrd calls invokes this callback. */ cgi->req = arg->req; - qse_htre_setconcb (cgi->req, cgi_snatch_content, cgi); + qse_htre_setconcb (cgi->req, cgi_snatch_content, task); QSE_ASSERT (arg->req->attr.content_length_set); content_length = arg->req->attr.content_length; @@ -2020,16 +2076,16 @@ static void task_fini_cgi ( { task_cgi_t* cgi = (task_cgi_t*)task->ctx; if (cgi->env) qse_env_close (cgi->env); - if (cgi->pio) + if (cgi->pio_inited) { /* kill cgi in case it is still alive. * qse_pio_wait() in qse_pio_close() can block. */ - qse_pio_kill (cgi->pio); - qse_pio_close (cgi->pio); + qse_pio_kill (&cgi->pio); + qse_pio_close (&cgi->pio); } if (cgi->res) qse_mbs_close (cgi->res); if (cgi->htrd) qse_htrd_close (cgi->htrd); - if (cgi->reqcon) qse_mbs_close (cgi->reqcon); + if (cgi->reqfwdbuf) qse_mbs_close (cgi->reqfwdbuf); if (cgi->req) { /* this task is destroyed but the request @@ -2047,31 +2103,39 @@ static int task_main_cgi_5 ( task_cgi_t* cgi = (task_cgi_t*)task->ctx; qse_ssize_t n; - QSE_ASSERT (cgi->pio != QSE_NULL); + QSE_ASSERT (cgi->pio_inited); if (task->trigger_mask & QSE_HTTPD_TASK_TRIGGER_RELAYABLE) { - cgi_forward_content (httpd, task); - /* if forwarding didn't finish, something is not really right... - * so long as the output from CGI is finished, no more forwarding - * is performed */ + cgi_forward_content (httpd, task, 0); + } + else if (task->trigger_mask & QSE_HTTPD_TASK_TRIGGER_WRITABLE) + { + cgi_forward_content (httpd, task, 1); } qse_printf (QSE_T("task_main_cgi_5\n")); -/* TODO: check if cgi outputs more than content-length if it is set... */ - httpd->errnum = QSE_HTTPD_ENOERR; - n = httpd->cbs->client.send (httpd, client, cgi->buf, cgi->buflen); - if (n <= -1) + if (cgi->buflen > 0) { +/* TODO: check if cgi outputs more than content-length if it is set... */ + httpd->errnum = QSE_HTTPD_ENOERR; + n = httpd->cbs->client.send (httpd, client, cgi->buf, cgi->buflen); + if (n <= -1) + { /* can't return internal server error any more... */ /* TODO: logging ... */ - return -1; + return -1; + } + + QSE_MEMCPY (&cgi->buf[0], &cgi->buf[n], cgi->buflen - n); + cgi->buflen -= n; } - QSE_MEMCPY (&cgi->buf[0], &cgi->buf[n], cgi->buflen - n); - cgi->buflen -= n; - - return (cgi->buflen > 0)? 1: 0; + /* if forwarding didn't finish, something is not really right... + * so long as the output from CGI is finished, no more forwarding + * is performed */ + return (cgi->buflen > 0 || cgi->req || + (cgi->reqfwdbuf && QSE_MBS_LEN(cgi->reqfwdbuf) > 0))? 1: 0; } static int task_main_cgi_4 ( @@ -2080,11 +2144,15 @@ static int task_main_cgi_4 ( task_cgi_t* cgi = (task_cgi_t*)task->ctx; qse_ssize_t n; - QSE_ASSERT (cgi->pio != QSE_NULL); + QSE_ASSERT (cgi->pio_inited); if (task->trigger_mask & QSE_HTTPD_TASK_TRIGGER_RELAYABLE) { - cgi_forward_content (httpd, task); + cgi_forward_content (httpd, task, 0); + } + else if (task->trigger_mask & QSE_HTTPD_TASK_TRIGGER_WRITABLE) + { + cgi_forward_content (httpd, task, 1); } if (task->trigger_mask & QSE_HTTPD_TASK_TRIGGER_READABLE) @@ -2110,7 +2178,7 @@ static int task_main_cgi_4 ( /* <- can i make it non-block?? or use select??? pio_tryread()? */ n = qse_pio_read ( - cgi->pio, QSE_PIO_OUT, + &cgi->pio, QSE_PIO_OUT, &cgi->buf[cgi->buflen + QSE_SIZEOF(chunklen) - 1], count - extra ); @@ -2157,7 +2225,7 @@ static int task_main_cgi_4 ( { qse_printf (QSE_T("READING IN NON-CHUNKED MODE...\n")); n = qse_pio_read ( - cgi->pio, QSE_PIO_OUT, + &cgi->pio, QSE_PIO_OUT, &cgi->buf[cgi->buflen], QSE_SIZEOF(cgi->buf) - cgi->buflen ); @@ -2226,7 +2294,11 @@ static int task_main_cgi_3 ( if (task->trigger_mask & QSE_HTTPD_TASK_TRIGGER_RELAYABLE) { - cgi_forward_content (httpd, task); + cgi_forward_content (httpd, task, 0); + } + else if (task->trigger_mask & QSE_HTTPD_TASK_TRIGGER_WRITABLE) + { + cgi_forward_content (httpd, task, 1); } if (task->trigger_mask & QSE_HTTPD_TASK_TRIGGER_READABLE) @@ -2273,13 +2345,17 @@ static int task_main_cgi_2 ( task_cgi_t* cgi = (task_cgi_t*)task->ctx; qse_ssize_t n; - QSE_ASSERT (cgi->pio != QSE_NULL); + QSE_ASSERT (cgi->pio_inited); qse_printf (QSE_T("[cgi_2 ]\n")); if (task->trigger_mask & QSE_HTTPD_TASK_TRIGGER_RELAYABLE) { qse_printf (QSE_T("[cgi_2 write]\n")); - cgi_forward_content (httpd, task); + cgi_forward_content (httpd, task, 0); + } + else if (task->trigger_mask & QSE_HTTPD_TASK_TRIGGER_WRITABLE) + { + cgi_forward_content (httpd, task, 1); } if (task->trigger_mask & QSE_HTTPD_TASK_TRIGGER_READABLE) @@ -2287,7 +2363,7 @@ qse_printf (QSE_T("[cgi_2 write]\n")); qse_printf (QSE_T("[cgi_2 read]\n")); /* <- can i make it non-block?? or use select??? pio_tryread()? */ n = qse_pio_read ( - cgi->pio, QSE_PIO_OUT, + &cgi->pio, QSE_PIO_OUT, &cgi->buf[cgi->buflen], QSE_SIZEOF(cgi->buf) - cgi->buflen ); @@ -2359,6 +2435,7 @@ static int task_main_cgi ( { task_cgi_t* cgi = (task_cgi_t*)task->ctx; int pio_options; + int http_errnum = 500; if (cgi->init_failed) goto oops; @@ -2398,39 +2475,67 @@ static int task_main_cgi ( if (httpd->option & QSE_HTTPD_CGINOCLOEXEC) pio_options |= QSE_PIO_NOCLOEXEC; - cgi->pio = qse_pio_open ( - httpd->mmgr, 0, (const qse_char_t*)cgi->path, - cgi->env, pio_options - ); - if (cgi->pio == QSE_NULL) goto oops; - -/* TODO: use a different field for different OS??? -HANDLE for win32??? -*/ - /* set the trigger that the main loop can use this - * handle for multiplexing */ - task->trigger_mask = QSE_HTTPD_TASK_TRIGGER_READ; - task->trigger[0].i = qse_pio_gethandle (cgi->pio, QSE_PIO_OUT); - if (cgi->reqcon) + if (qse_pio_init ( + &cgi->pio, httpd->mmgr, (const qse_char_t*)cgi->path, + cgi->env, pio_options) <= -1) { - /* not meaningful to check writability to the child process - * in the main loop. it is checked in cgi_forward_content(). - * so the following 2 lines are commented out. - task->trigger_mask |= QSE_HTTPD_TASK_TRIGGER_WRITE; - task->trigger[1].i = qse_pio_gethandle (cgi->pio, QSE_PIO_IN);*/ + qse_pio_errnum_t errnum; - task->trigger_mask |= QSE_HTTPD_TASK_TRIGGER_RELAY; - task->trigger[1].i = client->handle.i; + errnum = qse_pio_geterrnum (&cgi->pio); + + if (errnum == QSE_PIO_ENOENT) http_errnum = 404; + else if (errnum == QSE_PIO_EACCES) http_errnum = 403; + + goto oops; } - - if (cgi->reqcon) + cgi->pio_inited = 1; + + /* set the trigger that the main loop can use this + * handle for multiplexing + * + * it the output from the child is available, this task + * writes it back to the client. so add a trigger for + * checking the data availability from the child process */ + task->trigger_mask = QSE_HTTPD_TASK_TRIGGER_READ; + task->trigger[0] = qse_pio_gethandleasubi (&cgi->pio, QSE_PIO_OUT); + if (cgi->reqfwdbuf) { - /* since i didn't set triggers in the initializer (task_init_cgi()), - * it is possible that some contents has been read in already, - * forward them first. cgi_forward_content() is called after - * triggers are added above because cgi_forwrad_content() - * manipulates triggers when a forwarding error occurs. */ - cgi_forward_content (httpd, task); + /* the existence of the forwarding buffer leads to a trigger + * for checking data availiability from the client side. */ + + if (cgi->req) + { + /* there are still things to forward from the client-side. + * i can rely on this relay trigger for task invocation. */ + task->trigger_mask |= QSE_HTTPD_TASK_TRIGGER_RELAY; + task->trigger[1].i = client->handle.i; + } + else if (QSE_MBS_LEN(cgi->reqfwdbuf) > 0) + { + /* there's nothing more to read from the client side but + * some contents are already read into the forwarding buffer. + * this is possible because the main loop can still read + * between the initializer function (task_init_cgi()) and + * this function. so let's forward it initially. */ +qse_printf (QSE_T("FORWARDING INITIAL PART OF CONTENT...\n")); + cgi_forward_content (httpd, task, 0); + + /* if the initial forwarding clears the forwarding + * buffer, there is nothing more to forward. + * (nothing more to read from the client side, nothing + * left in the forwarding buffer). if not, this task should + * still be invoked for forwarding. + */ + if (QSE_MBS_LEN(cgi->reqfwdbuf) > 0) + { + /* since the buffer is not cleared, this task needs + * a trigger for invocation. ask the main loop to + * invoke this task so long as it is able to write + * to the child process */ + task->trigger_mask |= QSE_HTTPD_TASK_TRIGGER_WRITE; + task->trigger[2] = qse_pio_gethandleasubi (&cgi->pio, QSE_PIO_IN); + } + } } task->main = cgi->nph? task_main_cgi_4: task_main_cgi_2; @@ -2451,7 +2556,9 @@ oops: cgi->htrd = QSE_NULL; } - return (entask_error (httpd, client, task, 500, &cgi->version, cgi->keepalive) == QSE_NULL)? -1: 0; + return (entask_error ( + httpd, client, task, http_errnum, + &cgi->version, cgi->keepalive) == QSE_NULL)? -1: 0; } /* TODO: global option or individual paramter for max cgi lifetime diff --git a/qse/samples/net/http01.c b/qse/samples/net/http01.c index ce6ce6c7..647acf17 100644 --- a/qse/samples/net/http01.c +++ b/qse/samples/net/http01.c @@ -249,7 +249,13 @@ static int file_ropen ( qse_printf (QSE_T("opening file [%hs] for reading\n"), path); fd = open (path, flags, 0); - if (fd <= -1) return -1; + if (fd <= -1) + { + qse_httpd_seterrnum (httpd, + (errno == ENOENT? QSE_HTTPD_ENOENT: + errno == EACCES? QSE_HTTPD_EACCES: QSE_HTTPD_ESUBSYS)); + return -1; + } flags = fcntl (fd, F_GETFD); if (flags >= 0) fcntl (fd, F_SETFD, flags | FD_CLOEXEC); @@ -257,12 +263,16 @@ qse_printf (QSE_T("opening file [%hs] for reading\n"), path); /* TODO: fstat64??? */ if (fstat (fd, &st) <= -1) { + qse_httpd_seterrnum (httpd, + (errno == ENOENT? QSE_HTTPD_ENOENT: + errno == EACCES? QSE_HTTPD_EACCES: QSE_HTTPD_ESUBSYS)); close (fd); return -1; } - if (S_ISDIR(st.st_mode)) + if (!S_ISREG(st.st_mode)) { + qse_httpd_seterrnum (httpd, QSE_HTTPD_EACCES); close (fd); return -1; } @@ -287,7 +297,13 @@ static int file_wopen ( qse_printf (QSE_T("opening file [%hs] for writing\n"), path); fd = open (path, flags, 0644); - if (fd <= -1) return -1; + if (fd <= -1) + { + qse_httpd_seterrnum (httpd, + (errno == ENOENT? QSE_HTTPD_ENOENT: + errno == EACCES? QSE_HTTPD_EACCES: QSE_HTTPD_ESUBSYS)); + return -1; + } handle->i = fd; return 0; @@ -488,6 +504,7 @@ if (qse_htre_getcontentlen(req) > 0) qse_printf (QSE_T("CONTENT after discard = [%.*S]\n"), (int)qse_htre_getcontentlen(req), qse_htre_getcontentptr(req)); } + if (method == QSE_HTTP_GET || method == QSE_HTTP_POST) { const qse_mchar_t* qpath = qse_htre_getqpathptr(req); @@ -583,7 +600,7 @@ qse_printf (QSE_T("Entasking chunked CGI...\n")); oops: /*qse_httpd_markbadclient (httpd, client);*/ - return 0; /* TODO: return failure??? */ + return -1; } static int peek_request (