#include "http_log.h"
#include "util_script.h"
#include "mod_core.h"
+#include "apr_lib.h"
#include "apr_strings.h"
#include "apr_portable.h"
#include "apr_buckets.h"
/* Our "Connection ID" structure */
typedef struct isapi_cid {
EXTENSION_CONTROL_BLOCK *ecb;
- isapi_dir_conf dconf;
- isapi_loaded *isa;
- request_rec *r;
+ isapi_dir_conf dconf;
+ isapi_loaded *isa;
+ request_rec *r;
+ int headers_sent;
#ifdef FAKE_ASYNC
- PFN_HSE_IO_COMPLETION completion;
- void *completion_arg;
- apr_thread_mutex_t *completed;
+ PFN_HSE_IO_COMPLETION completion;
+ void *completion_arg;
+ apr_thread_mutex_t *completed;
#endif
} isapi_cid;
if ((flags & HSE_IO_ASYNC) && cid->completion) {
if (rv == OK) {
cid->completion(cid->ecb, cid->completion_arg,
- ERROR_SUCCESS, *buf_size);
+ *buf_size, ERROR_SUCCESS);
}
else {
cid->completion(cid->ecb, cid->completion_arg,
- ERROR_WRITE_FAULT, *buf_size);
+ *buf_size, ERROR_WRITE_FAULT);
}
}
#endif
apr_size_t statlen,
apr_size_t headlen)
{
+ int head_present = 1;
int termarg;
char *termch;
+ apr_size_t ate = 0;
+
+ if (!head || headlen == 0 || !*head) {
+ head = stat;
+ stat = NULL;
+ headlen = statlen;
+ statlen = 0;
+ head_present = 0; /* Don't eat the header */
+ }
if (!stat || statlen == 0 || !*stat) {
- stat = "Status: 200 OK";
+ if (head && headlen && *head && ((stat = memchr(head, '\r', headlen))
+ || (stat = memchr(head, '\n', headlen))
+ || (stat = memchr(head, '\0', headlen))
+ || (stat = head + headlen))) {
+ statlen = stat - head;
+ if (memchr(head, ':', statlen)) {
+ stat = "Status: 200 OK";
+ statlen = strlen(stat);
+ }
+ else {
+ char *flip = head;
+ head = stat;
+ stat = flip;
+ headlen -= statlen;
+ ate += statlen;
+ if (*head == '\r' && headlen)
+ ++head, --headlen, ++ate;
+ if (*head == '\n' && headlen)
+ ++head, --headlen, ++ate;
+ }
+ }
}
- else {
- char *newstat;
- newstat = apr_palloc(cid->r->pool, statlen + 9);
+
+ if (stat && (statlen > 0) && *stat) {
+ char *newstat = apr_palloc(cid->r->pool, statlen + 9);
+ char *stattok = (char*)memchr(stat, ' ', statlen - 1) + 1;
strcpy(newstat, "Status: ");
+ /* Now decide if we follow the xxx message
+ * or the http/x.x xxx message format
+ */
+ if (!apr_isdigit(*stat) && stattok && apr_isdigit(*stattok)) {
+ statlen -= stattok - (char*)stat;
+ stat = stattok;
+ }
apr_cpystrn(newstat + 8, stat, statlen + 1);
stat = newstat;
}
if (!head || headlen == 0 || !*head) {
head = "\r\n";
+ headlen = 2;
}
else
{
- if (head[headlen]) {
+ if (head[headlen - 1] && head[headlen]) {
/* Whoops... not NULL terminated */
head = apr_pstrndup(cid->r->pool, head, headlen);
}
*
* Parse them out, or die trying
*/
- cid->r->status= ap_scan_script_header_err_strs(cid->r, NULL, &termch,
- &termarg, stat, head, NULL);
+ if (stat) {
+ cid->r->status = ap_scan_script_header_err_strs(cid->r, NULL,
+ &termch, &termarg, stat, head, NULL);
+ }
+ else {
+ cid->r->status = ap_scan_script_header_err_strs(cid->r, NULL,
+ &termch, &termarg, head, NULL);
+ }
cid->ecb->dwHttpStatusCode = cid->r->status;
if (cid->r->status == HTTP_INTERNAL_SERVER_ERROR)
return -1;
-
- /* Headers will actually go when they are good and ready */
- /* If all went well, tell the caller we consumed the headers complete */
+ /* If only Status was passed, we consumed nothing
+ */
+ if (!head_present)
+ return 0;
+
+ cid->headers_sent = 1;
+
+ /* If all went well, tell the caller we consumed the headers complete
+ */
if (!termch)
- return(headlen);
+ return(ate + headlen);
- /* Any data left is sent directly by the caller, all we
- * give back is the size of the headers we consumed
+ /* Any data left must be sent directly by the caller, all we
+ * give back is the size of the headers we consumed (which only
+ * happens if the parser got to the head arg, which varies based
+ * on whether we passed stat+head to scan, or only head.
*/
- if (termch && (termarg == 1) && head + headlen > termch) {
- return termch - head;
+ if (termch && (termarg == (stat ? 1 : 0))
+ && head_present && head + headlen > termch) {
+ return ate + termch - head;
}
- return 0;
+ return ate;
}
int APR_THREAD_FUNC ServerSupportFunction(isapi_cid *cid,
request_rec *subreq;
switch (HSE_code) {
- case 1: /* HSE_REQ_SEND_URL_REDIRECT_RESP */
+ case HSE_REQ_SEND_URL_REDIRECT_RESP:
/* Set the status to be returned when the HttpExtensionProc()
* is done.
* WARNING: Microsoft now advertises HSE_REQ_SEND_URL_REDIRECT_RESP
* They most definately are not, even in their own samples.
*/
apr_table_set (r->headers_out, "Location", buf_data);
- cid->r->status = cid->ecb->dwHttpStatusCode
- = HTTP_MOVED_TEMPORARILY;
+ cid->r->status = cid->ecb->dwHttpStatusCode = HTTP_MOVED_TEMPORARILY;
return 1;
case HSE_REQ_SEND_URL:
#ifdef FAKE_ASYNC
if (cid->completed) {
apr_thread_mutex_unlock(cid->completed);
- cid->completed = NULL;
}
#endif
return 1;
case HSE_REQ_TRANSMIT_FILE:
{
HSE_TF_INFO *tf = (HSE_TF_INFO*)buf_data;
+ apr_uint32_t sent = 0;
+ apr_ssize_t ate = 0;
apr_status_t rv;
apr_bucket_brigade *bb;
apr_bucket *b;
apr_file_t *fd;
+ apr_off_t fsize;
#ifdef FAKE_ASYNC
- if ((tf->dwFlags & HSE_IO_ASYNC) && cid->isa->fakeasync) {
- /* TBD */
- }
- else /* if (!cid->isa->fakeasync && ... */
+ if (!cid->isa->fakeasync)
#endif
if (tf->dwFlags & HSE_IO_ASYNC)
{
return 0;
}
- if ((rv = apr_os_file_put(&fd, tf->hFile, 0, r->pool)) != APR_SUCCESS) {
+ if ((rv = apr_os_file_put(&fd, &tf->hFile, 0, r->pool)) != APR_SUCCESS) {
return 0;
}
+ if (tf->BytesToWrite) {
+ fsize = tf->BytesToWrite;
+ }
+ else {
+ apr_finfo_t fi;
+ if (apr_file_info_get(&fi, APR_FINFO_SIZE, fd) != APR_SUCCESS) {
+ SetLastError(ERROR_INVALID_PARAMETER);
+ return 0;
+ }
+ fsize = fi.size - tf->Offset;
+ }
/* apr_dupfile_oshandle (&fd, tf->hFile, r->pool); */
bb = apr_brigade_create(r->pool, c->bucket_alloc);
- if (tf->dwFlags & HSE_IO_SEND_HEADERS)
- {
- /* According to MS: if calling HSE_REQ_TRANSMIT_FILE with the
- * HSE_IO_SEND_HEADERS flag, then you can't otherwise call any
- * HSE_SEND_RESPONSE_HEADERS* fn, but if you don't use the flag,
- * you must have done so. They document that the pHead headers
- * option is valid only for HSE_IO_SEND_HEADERS - we are a bit
- * more flexible and assume with the flag, pHead are the
- * response headers, and without, pHead simply contains text
- * (handled after this case).
- */
- apr_ssize_t ate = send_response_header(cid, tf->pszStatusCode,
- (char*)tf->pHead,
- strlen(tf->pszStatusCode),
- tf->HeadLength);
+ /* According to MS: if calling HSE_REQ_TRANSMIT_FILE with the
+ * HSE_IO_SEND_HEADERS flag, then you can't otherwise call any
+ * HSE_SEND_RESPONSE_HEADERS* fn, but if you don't use the flag,
+ * you must have done so. They document that the pHead headers
+ * option is valid only for HSE_IO_SEND_HEADERS - we are a bit
+ * more flexible and assume with the flag, pHead are the
+ * response headers, and without, pHead simply contains text
+ * (handled after this case).
+ */
+ if ((tf->dwFlags & HSE_IO_SEND_HEADERS) && tf->pszStatusCode) {
+ ate = send_response_header(cid, tf->pszStatusCode,
+ (char*)tf->pHead,
+ strlen(tf->pszStatusCode),
+ tf->HeadLength);
+ }
+ else if (!cid->headers_sent && tf->pHead && tf->HeadLength
+ && *(char*)tf->pHead) {
+ ate = send_response_header(cid, NULL, (char*)tf->pHead,
+ 0, tf->HeadLength);
if (ate < 0)
{
apr_brigade_destroy(bb);
SetLastError(ERROR_INVALID_PARAMETER);
return 0;
}
- if ((apr_size_t)ate < tf->HeadLength)
- {
- b = apr_bucket_transient_create((char*)tf->pHead + ate,
- tf->HeadLength - ate,
- c->bucket_alloc);
- APR_BRIGADE_INSERT_TAIL(bb, b);
- }
}
- else if (tf->pHead && tf->HeadLength) {
- b = apr_bucket_transient_create((char*)tf->pHead,
- tf->HeadLength,
- c->bucket_alloc);
+
+ if (tf->pHead && (apr_size_t)ate < tf->HeadLength) {
+ sent = tf->HeadLength - ate;
+ b = apr_bucket_transient_create((char*)tf->pHead + ate,
+ sent, c->bucket_alloc);
APR_BRIGADE_INSERT_TAIL(bb, b);
}
- b = apr_bucket_file_create(fd, tf->Offset,
- tf->BytesToWrite, r->pool, c->bucket_alloc);
+ sent += (apr_uint32_t)fsize;
+#if APR_HAS_LARGE_FILES
+ if (r->finfo.size > AP_MAX_SENDFILE) {
+ /* APR_HAS_LARGE_FILES issue; must split into mutiple buckets,
+ * no greater than MAX(apr_size_t), and more granular than that
+ * in case the brigade code/filters attempt to read it directly.
+ */
+ b = apr_bucket_file_create(fd, tf->Offset, AP_MAX_SENDFILE,
+ r->pool, c->bucket_alloc);
+ while (fsize > AP_MAX_SENDFILE) {
+ apr_bucket *bc;
+ apr_bucket_copy(b, &bc);
+ APR_BRIGADE_INSERT_TAIL(bb, bc);
+ b->start += AP_MAX_SENDFILE;
+ fsize -= AP_MAX_SENDFILE;
+ }
+ b->length = (apr_size_t)fsize; /* Resize just the last bucket */
+ }
+ else
+#endif
+ b = apr_bucket_file_create(fd, tf->Offset, (apr_size_t)fsize,
+ r->pool, c->bucket_alloc);
APR_BRIGADE_INSERT_TAIL(bb, b);
if (tf->pTail && tf->TailLength) {
+ sent += tf->TailLength;
b = apr_bucket_transient_create((char*)tf->pTail,
tf->TailLength, c->bucket_alloc);
APR_BRIGADE_INSERT_TAIL(bb, b);
if (tf->pfnHseIO) {
if (rv == OK) {
tf->pfnHseIO(cid->ecb, tf->pContext,
- ERROR_SUCCESS, *buf_size);
+ ERROR_SUCCESS, sent);
}
else {
tf->pfnHseIO(cid->ecb, tf->pContext,
- ERROR_WRITE_FAULT, *buf_size);
+ ERROR_WRITE_FAULT, sent);
}
}
- else {
+ else if (cid->completion) {
if (rv == OK) {
cid->completion(cid->ecb, cid->completion_arg,
- ERROR_SUCCESS, *buf_size);
+ sent, ERROR_SUCCESS);
}
else {
cid->completion(cid->ecb, cid->completion_arg,
- ERROR_WRITE_FAULT, *buf_size);
+ sent, ERROR_WRITE_FAULT);
}
}
}
#endif
- return 1;
+ return (rv == OK);
}
case HSE_REQ_REFRESH_ISAPI_ACL:
return 1;
case HSE_REQ_ASYNC_READ_CLIENT:
+ {
+ apr_uint32_t read = 0;
#ifdef FAKE_ASYNC
- /* TBD: Fake it */
-#else
- if (cid->dconf.log_unsupported)
- ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_WARNING, 0, r,
- "ISAPI: asynchronous I/O not supported: %s",
- r->filename);
- SetLastError(ERROR_INVALID_PARAMETER);
- return 0;
+ int res;
+ if (!cid->isa->fakeasync)
+#endif
+ {
+ if (cid->dconf.log_unsupported)
+ ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_WARNING, 0, r,
+ "ISAPI: asynchronous I/O not supported: %s",
+ r->filename);
+ SetLastError(ERROR_INVALID_PARAMETER);
+ return 0;
+ }
+
+#ifdef FAKE_ASYNC
+ if (r->remaining < *buf_size) {
+ *buf_size = (apr_size_t)r->remaining;
+ }
+
+ while (read < *buf_size &&
+ ((res = ap_get_client_block(r, (char*)buf_data + read,
+ *buf_size - read)) > 0)) {
+ read += res;
+ }
+
+ if ((*data_type & HSE_IO_ASYNC) && cid->completion) {
+ if (res >= 0) {
+ cid->completion(cid->ecb, cid->completion_arg,
+ read, ERROR_SUCCESS);
+ }
+ else {
+ cid->completion(cid->ecb, cid->completion_arg,
+ read, ERROR_READ_FAULT);
+ }
+ }
+ return (res >= 0);
#endif
+ }
case HSE_REQ_GET_IMPERSONATION_TOKEN: /* Added in ISAPI 4.0 */
if (cid->dconf.log_unsupported)
{
HSE_SEND_HEADER_EX_INFO *shi = (HSE_SEND_HEADER_EX_INFO*)buf_data;
- /* XXX: ignore shi->fKeepConn? We shouldn't need the advise
- * r->connection->keepalive = shi->fKeepConn;
- */
+ /* Ignore shi->fKeepConn - we don't want the advise
+ */
apr_ssize_t ate = send_response_header(cid, shi->pszStatus,
shi->pszHeader,
shi->cchStatus,
apr_table_setn(e, "SERVER_PORT_SECURE", "0");
apr_table_setn(e, "URL", r->uri);
- /* Set up connection structure and ecb */
+ /* Set up connection structure and ecb,
+ * NULL or zero out most fields.
+ */
cid = apr_pcalloc(r->pool, sizeof(isapi_cid));
/* Fixup defaults for dconf */
cid->isa = isa;
cid->r = r;
cid->r->status = 0;
-#ifdef FAKE_ASYNC
- cid->completed = NULL;
- cid->completion = NULL;
-#endif
cid->ecb->cbSize = sizeof(EXTENSION_CONTROL_BLOCK);
cid->ecb->dwVersion = isa->report_version;
cid->ecb->dwHttpStatusCode = 0;
strcpy(cid->ecb->lpszLogData, "");
- // TODO: are copies really needed here?
- cid->ecb->lpszMethod = apr_pstrdup(r->pool, (char*) r->method);
- cid->ecb->lpszQueryString = apr_pstrdup(r->pool,
- (char*) apr_table_get(e, "QUERY_STRING"));
- cid->ecb->lpszPathInfo = apr_pstrdup(r->pool,
- (char*) apr_table_get(e, "PATH_INFO"));
- cid->ecb->lpszPathTranslated = apr_pstrdup(r->pool,
- (char*) apr_table_get(e, "PATH_TRANSLATED"));
- cid->ecb->lpszContentType = apr_pstrdup(r->pool,
- (char*) apr_table_get(e, "CONTENT_TYPE"));
+ /* TODO: are copies really needed here?
+ */
+ cid->ecb->lpszMethod = (char*) r->method;
+ cid->ecb->lpszQueryString = (char*) apr_table_get(e, "QUERY_STRING");
+ cid->ecb->lpszPathInfo = (char*) apr_table_get(e, "PATH_INFO");
+ cid->ecb->lpszPathTranslated = (char*) apr_table_get(e, "PATH_TRANSLATED");
+ cid->ecb->lpszContentType = (char*) apr_table_get(e, "CONTENT_TYPE");
+
+ /* Based on some examples I've noticed, NULL is expected here.
+ */
+ if (!*cid->ecb->lpszQueryString) {
+ cid->ecb->lpszQueryString = NULL;
+ }
+
/* Set up the callbacks */
cid->ecb->GetServerVariable = GetServerVariable;
cid->ecb->WriteClient = WriteClient;
cid->ecb->ReadClient = ReadClient;
cid->ecb->ServerSupportFunction = ServerSupportFunction;
-
/* Set up client input */
res = ap_setup_client_block(r, REQUEST_CHUNKED_ERROR);
if (res) {
#ifdef FAKE_ASYNC
/* emulating async behavior...
*
- * Create a cid->completed event and wait on it for some timeout
+ * Create a cid->completed mutex and wait on it for some timeout
* so that the app thinks is it running async.
*
* All async ServerSupportFunction calls will be handled through
*/
if (isa->fakeasync)
{
+ apr_thread_mutex_t *comp;
+
rv = apr_thread_mutex_create(&cid->completed,
APR_THREAD_MUTEX_DEFAULT,
r->pool);
- if (rv != APR_SUCCESS
- || (rv = apr_thread_mutex_lock(cid->completed))
- != APR_SUCCESS) {
- /* TODO: If this gets hung, then do we kill our own
- * thread to force its death? No timeout options for
- * thread_mutex locks??? The module is still hanging
- * on to our ecb and cid gook, and it won't be good to
- * just destroy that pool. Outch.
- */
+ comp = cid->completed;
+ if (cid->completed && (rv == APR_SUCCESS)) {
+ rv = apr_thread_mutex_lock(comp);
+ }
+ /* The completion port is now locked. When we regain the
+ * lock, we may destroy the request.
+ */
+ if (cid->completed && (rv == APR_SUCCESS)) {
+ rv = apr_thread_mutex_lock(comp);
}
break;
}
+ else
#endif
if (cid->dconf.log_unsupported)
{
cid->r->status = cid->ecb->dwHttpStatusCode;
}
- return OK; /* NOT r->status, even if it has changed. */
+ return cid->r->status;
}
/**********************************************************