You are viewing a plain text version of this content. The canonical link for it is here.
Posted to cvs@httpd.apache.org by wr...@locus.apache.org on 2000/10/02 17:39:03 UTC

cvs commit: apache-2.0/src/include util_script.h

wrowe       00/10/02 08:39:02

  Modified:    src/os/win32 mod_isapi.c
               src/main util_script.c
               src/include util_script.h
  Log:
    Forward port 1.3 overhaul of isapi to 2.0.
  
    Yes - it's blatently obvious that this should be 'bucketized', but the
    sequence I will follow is:
  
      *) compatibility of sources
      *) extension to async + transmitfile emulated support
      *) localization of ISAPI* directives to Directory, File blocks
      *) addition of ISAPI caching
      *) recode for buckets.
  
    I'm actually looking forward to the mod_cgi bucketization as a model.
  
  Revision  Changes    Path
  1.20      +467 -362  apache-2.0/src/os/win32/mod_isapi.c
  
  Index: mod_isapi.c
  ===================================================================
  RCS file: /home/cvs/apache-2.0/src/os/win32/mod_isapi.c,v
  retrieving revision 1.19
  retrieving revision 1.20
  diff -u -r1.19 -r1.20
  --- mod_isapi.c	2000/09/11 19:02:40	1.19
  +++ mod_isapi.c	2000/10/02 15:38:55	1.20
  @@ -100,6 +100,11 @@
   
   module isapi_module;
   
  +static DWORD ReadAheadBuffer = 49152;
  +static int LogNotSupported = -1;
  +static int AppendLogToErrors = 0;
  +static int AppendLogToQuery = 0;
  +
   /* Declare the ISAPI functions */
   
   BOOL WINAPI GetServerVariable (HCONN hConn, LPSTR lpszVariableName,
  @@ -152,6 +157,10 @@
       apr_table_t *e = r->subprocess_env;
       isapi_loaded *isa;
       isapi_cid *cid;
  +    DWORD read;
  +    char *fspec;
  +    char *p;
  +    int res;
   
       /* Use similar restrictions as CGIs
        *
  @@ -160,17 +169,32 @@
       if (!(ap_allow_options(r) & OPT_EXECCGI))
           return HTTP_FORBIDDEN;
   
  -    if (r->finfo.protection == 0)
  +    if (r->finfo.filetype == APR_NOFILE)
           return HTTP_NOT_FOUND;
   
  -    if (r->finfo.filetype == APR_DIR)
  +    if (r->finfo.filetype != APR_FILE)
           return HTTP_FORBIDDEN;
   
  +    /* Load the module...
  +     * per PR2555, the LoadLibraryEx function is very picky about slashes.
  +     * Debugging on NT 4 SP 6a reveals First Chance Exception within NTDLL.
  +     * LoadLibrary in the MS PSDK also reveals that it -explicitly- states
  +     * that backslashes must be used.
  +     *
  +     * Transpose '\' for '/' in the filename.
  +     */
  +    p = fspec = ap_pstrdup(r->pool, r->filename);
  +    while (*p) {
  +        if (*p == '/')
  +            *p = '\\';
  +        ++p;
  +    }
  +
       /* Load the module 
        *
        * TODO: Critical section
        *
  -     * Warning: cid should not be allocated from pool if we 
  +     * Warning: cid should not be allocated from request pool if we 
        * cache the isapi process in-memory.
        *
        * This code could use cacheing... everything that follows
  @@ -237,6 +261,9 @@
       /* Set up variables */
       ap_add_common_vars(r);
       ap_add_cgi_vars(r);
  +    apr_table_setn(r->subprocess_env, "UNMAPPED_REMOTE_USER", "REMOTE_USER");
  +    apr_table_setn(r->subprocess_env, "SERVER_PORT_SECURE", "0");
  +    apr_table_setn(r->subprocess_env, "URL", r->uri);
   
       /* Set up connection structure and ecb */
       cid = apr_pcalloc(r->pool, sizeof(isapi_cid));
  @@ -283,44 +310,45 @@
       }
   
       if (ap_should_client_block(r)) {
  -        /* Unlike IIS, which limits this to 48k, we read the whole
  -         * sucker in. I suppose this could be bad for memory if someone
  -         * uploaded the complete works of Shakespeare. Well, WebSite
  -         * does the same thing.
  -         *
  -         * But we can be smarter and read up to our 48k and then allow
  -         * the ISAPI app to read further blocks as desired.
  +        /* Time to start reading the appropriate amount of data,
  +         * and allow the administrator to tweak the number
  +         * TODO: add the httpd.conf option for ReadAheadBuffer.
            */
  -        long to_read = atol(apr_table_get(e, "CONTENT_LENGTH"));
  -        long read;
  +        if (r->remaining) {
  +            cid->ecb->cbTotalBytes = r->remaining;
  +            if (cid->ecb->cbTotalBytes > ReadAheadBuffer)
  +                cid->ecb->cbAvailable = ReadAheadBuffer;
  +            else
  +                cid->ecb->cbAvailable = cid->ecb->cbTotalBytes;
  +        }
  +        else
  +        {
  +            cid->ecb->cbTotalBytes = 0xffffffff;
  +            cid->ecb->cbAvailable = ReadAheadBuffer;
  +        }
   
  -        /* Actually, let's cap it at 48k, until we figure out what
  -         * to do with this... we don't want a Content-Length: 1000000000
  -         * taking out the machine.
  -         */
  +        cid->ecb->lpbData = apr_pcalloc(r->pool, cid->ecb->cbAvailable + 1);
   
  -        if (to_read > 49152) {
  -            if (isa->TerminateExtension) 
  -                (*isa->TerminateExtension)(HSE_TERM_MUST_UNLOAD);
  -            FreeLibrary(isa->handle);
  -            return HTTP_REQUEST_ENTITY_TOO_LARGE;
  +        p = cid->ecb->lpbData;
  +        read = 0;
  +        while (read < cid->ecb->cbAvailable &&
  +               ((res = ap_get_client_block(r, cid->ecb->lpbData + read,
  +                                      cid->ecb->cbAvailable - read)) > 0)) {
  +            read += res;
           }
  -
  -        cid->ecb->lpbData = apr_pcalloc(r->pool, 1 + to_read);
   
  -        if ((read = ap_get_client_block(r, cid->ecb->lpbData, to_read)) < 0) {
  -            if (isa->TerminateExtension) 
  -                (*isa->TerminateExtension)(HSE_TERM_MUST_UNLOAD);
  -            FreeLibrary(isa->handle);
  -            return HTTP_INTERNAL_SERVER_ERROR;
  +        if (res < 0) {
  +            cid->retval = HTTP_INTERNAL_SERVER_ERROR;
  +            goto contentfailure;
           }
   
           /* Although its not to spec, IIS seems to null-terminate
  -         * its lpdData string. So we will too. To make sure
  -         * cbAvailable matches cbTotalBytes, we'll up the latter
  -         * and equalize them.
  +         * its lpdData string. So we will too.
            */
  -        cid->ecb->cbAvailable = cid->ecb->cbTotalBytes = read + 1;
  +        if (res == 0)
  +            cid->ecb->cbAvailable = cid->ecb->cbTotalBytes = read;
  +        else
  +            cid->ecb->cbAvailable = read;
           cid->ecb->lpbData[read] = '\0';
       }
       else {
  @@ -339,7 +367,7 @@
   
       /* Check for a log message - and log it */
       if (cid->ecb->lpszLogData && *cid->ecb->lpszLogData)
  -        ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r,
  +        ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_INFO, 0, r,
                         "ISAPI %s: %s", r->filename, cid->ecb->lpszLogData);
   
       switch(cid->retval) {
  @@ -366,10 +394,13 @@
                */
               
               if (!isa->fakeasync) {
  -                ap_log_rerror(APLOG_MARK, APLOG_WARNING, APR_ENOTIMPL, r,
  -                              "ISAPI %s asynch I/O request refused", 
  -                              r->filename);
  -                cid->retval = APR_ENOTIMPL;
  +                if (LogNotSupported)
  +                {
  +                     ap_log_rerror(APLOG_MARK, APLOG_WARNING|ARP_NOERRNO, 0, r,
  +                                   "ISAPI %s asynch I/O request refused", 
  +                                   r->filename);
  +                     cid->retval = APR_ENOTIMPL;
  +                }
               }
               else {
                   cid->complete = CreateEvent(NULL, FALSE, FALSE, NULL);
  @@ -395,6 +426,7 @@
               break;
       }
   
  +contentfailure:
       /* All done with the DLL... get rid of it...
        *
        * If optionally cached, pass HSE_TERM_ADVISORY_UNLOAD,
  @@ -467,8 +499,10 @@
   
       /* We only support synchronous writing */
       if (dwReserved && dwReserved != HSE_IO_SYNC) {
  -        ap_log_rerror(APLOG_MARK, APLOG_WARNING, ERROR_INVALID_PARAMETER, r,
  -                      "ISAPI %s asynch write", r->filename);
  +        if (LogNotSupported)
  +            ap_log_rerror(APLOG_MARK, APLOG_WARNING|API_NOERRNO, 0, r,
  +                          "ISAPI %s  asynch I/O request refused",
  +                          r->filename);
           SetLastError(ERROR_INVALID_PARAMETER);
           return FALSE;
       }
  @@ -484,102 +518,83 @@
   
   BOOL WINAPI ReadClient (HCONN ConnID, LPVOID lpvBuffer, LPDWORD lpdwSize)
   {
  -    /* TODO: If the request was a huge transmit or chunked, continue piping the
  -     * request here, but if it's of a sane size, continue to ...
  -     */
  -    return TRUE;
  -}
  +    request_rec *r = ((isapi_cid *)ConnID)->r;
  +    DWORD read = 0;
  +    int res;
   
  -static char* ComposeHeaders(request_rec *r, char* data)
  -{
  -    /* We *should* break before this while loop ends */
  -    while (*data) 
  -    {
  -        char *value, *lf = strchr(data, '\n');
  -        int p;
  +    if (r->remaining < (long) *lpdwSize)
  +        *lpdwSize = r->remaining;
   
  -#ifdef RELAX_HEADER_RULE
  -        if (lf)
  -            *lf = '\0';
  -#else
  -        if (!lf) { /* Huh? Invalid data, I think */
  -            ap_log_rerror(APLOG_MARK, APLOG_ERR, r,
  -                          "ISAPI %s sent invalid headers", r->filename);
  -            SetLastError(TODO_ERROR);
  +    while (read < *lpdwSize &&
  +           ((res = ap_get_client_block(r, (char*)lpvBuffer + read,
  +                                       *lpdwSize - read)) > 0)) {
  +        if (res < 0) {
  +            *lpdwSize = 0;
  +            if (!GetLastError())
  +                SetLastError(TODO_ERROR); /* XXX: Find the right error code */
               return FALSE;
           }
  -
  -        /* Get rid of \n and \r */
  -        *lf = '\0';
  -#endif
  -        p = strlen(data);
  -        if (p > 0 && data[p-1] == '\r') data[p-1] = '\0';
  -
  -        /* End of headers */
  -        if (*data == '\0') {
  -#ifdef RELAX_HEADER_RULE
  -            if (lf)
  -#endif
  -                data = lf + 1;  /* Reset data */
  -            break;
  -        }
   
  -        if (!(value = strchr(data, ':'))) {
  -            SetLastError(TODO_ERROR);
  -            /* ### euh... we're passing the wrong type of error
  -               ### code here */
  -            ap_log_rerror(APLOG_MARK, APLOG_ERR, HTTP_INTERNAL_SERVER_ERROR, r,
  -                          "ISAPI %s sent invalid headers", r->filename);
  -            return FALSE;
  -        }
  +        read += res;
  +    }
   
  -        *value++ = '\0';
  -        while (*value && apr_isspace(*value)) ++value;
  +    *lpdwSize = read;
  +    return TRUE;
  +}
   
  -        /* Check all the special-case headers. Similar to what
  -         * ap_scan_script_header_err() does (see that function for
  -         * more detail)
  -         */
  +static BOOL SendResponseHeaderEx(isapi_cid *cid, const char *stat,
  +                                 const char *head, size_t statlen,
  +                                 size_t headlen)
  +{
  +    int termarg;
  +    char *termch;
   
  -        if (!strcasecmp(data, "Content-Type")) 
  -        {
  -            /* Nuke trailing whitespace */    
  -            char *tmp;
  -            char *endp = value + strlen(value) - 1;
  -            while (endp > value && apr_isspace(*endp)) 
  -                *endp-- = '\0';
  +    if (!stat || !*stat) {
  +        stat = "Status: 200 OK";
  +    }
  +    else {
  +        char *newstat;
  +        if (statlen == 0)
  +            statlen = strlen(stat);
  +        /* Whoops... not NULL terminated */
  +        newstat = apr_palloc(cid->r->pool, statlen + 9);
  +        strcpy(newstat, "Status: ");
  +        strncpy(newstat + 8, stat, statlen);
  +        stat = newstat;
  +    }
  +
  +    if (!head || !*head) {
  +        head = "\r\n";
  +    }
  +    else if ((headlen >= 0) && head[headlen]) {
  +        /* Whoops... not NULL terminated */
  +        head = apr_pstrndup(cid->r->pool, head, headlen);
  +    }
  +
  +    /* Parse them out, or die trying */
  +    cid->retval = ap_scan_script_header_err_strs(cid->r, NULL, &termch,
  +                                                 &termarg, stat, head, NULL);
  +    cid->ecb->dwHttpStatusCode = cid->r->status;
   
  -            tmp = apr_pstrdup (r->pool, value);
  -            ap_str_tolower(tmp);
  -            r->content_type = tmp;
  -        }
  -        else if (!strcasecmp(data, "Content-Length")) {
  -            apr_table_set(r->headers_out, data, value);
  -        }
  -        else if (!strcasecmp(data, "Transfer-Encoding")) {
  -            apr_table_set(r->headers_out, data, value);
  -        }
  -        else if (!strcasecmp(data, "Set-Cookie")) {
  -            apr_table_add(r->err_headers_out, data, value);
  -        }
  -        else {
  -            apr_table_merge(r->err_headers_out, data, value);
  -        }
  +    /* All the headers should be set now */
  +    ap_send_http_header(cid->r);
   
  -        /* Reset data */
  -#ifdef RELAX_HEADER_RULE
  -        if (!lf) {
  -            data += p;
  -            break;
  -        }
  -#endif
  -        data = lf + 1;
  +    /* Any data left should now be sent directly,
  +     * it may be raw if headlen was provided.
  +     */
  +    if (termch && (termarg == 1)) {
  +        if (headlen == -1 && *termch)
  +            ap_rputs(termch, cid->r);
  +        else if (headlen > (size_t) (termch - head))
  +            ap_rwrite(termch, headlen - (termch - head), cid->r);
       }
  -    return data;
  -}
   
  +    if (cid->retval == HTTP_INTERNAL_SERVER_ERROR)
  +        return FALSE;
  +    return TRUE;
  +}
   
  -/* XXX: There is an O(n^2) attack possible here. */
  +/* XXX: Is there is still an O(n^2) attack possible here?  Please detail. */
   BOOL WINAPI ServerSupportFunction (HCONN hConn, DWORD dwHSERequest,
                                      LPVOID lpvBuffer, LPDWORD lpdwSize,
                                      LPDWORD lpdwDataType)
  @@ -587,290 +602,380 @@
       isapi_cid *cid = (isapi_cid *)hConn;
       request_rec *r = cid->r;
       request_rec *subreq;
  -    char *data;
   
       switch (dwHSERequest) {
  -        case HSE_REQ_SEND_URL_REDIRECT_RESP:
  -            /* Set the status to be returned when the HttpExtensionProc()
  -             * is done.
  -             */
  -            apr_table_set (r->headers_out, "Location", lpvBuffer);
  -            cid->r->status = cid->ecb->dwHttpStatusCode 
  -                                                   = HTTP_MOVED_TEMPORARILY;
  -            return TRUE;
  -
  -        case HSE_REQ_SEND_URL:
  -            /* Read any additional input */
  -
  -            if (r->remaining > 0) {
  -                char argsbuffer[HUGE_STRING_LEN];
  -
  -                while (ap_get_client_block(r, argsbuffer, HUGE_STRING_LEN));
  -            }
  +    case 1: /* 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
  +         *          and HSE_REQ_SEND_URL as equivalant per the Jan 2000 SDK.
  +         *          They most definately are not, even in their own samples.
  +         */
  +        apr_table_set (r->headers_out, "Location", lpvBuffer);
  +        cid->r->status = cid->ecb->dwHttpStatusCode 
  +                                               = HTTP_MOVED_TEMPORARILY;
  +        return TRUE;
   
  -            /* Reset the method to GET */
  -            r->method = apr_pstrdup(r->pool, "GET");
  -            r->method_number = M_GET;
  +    case 2: /* HSE_REQ_SEND_URL */
  +        /* Soak up remaining input */
  +        if (r->remaining > 0) {
  +            char argsbuffer[HUGE_STRING_LEN];
  +            while (ap_get_client_block(r, argsbuffer, HUGE_STRING_LEN));
  +        }
  +
  +        /* Reset the method to GET */
  +        r->method = apr_pstrdup(r->pool, "GET");
  +        r->method_number = M_GET;
  +
  +        /* Don't let anyone think there's still data */
  +        apr_table_unset(r->headers_in, "Content-Length");
  +
  +        /* AV fault per PR3598 - redirected path is lost! */
  +        (char*)lpvBuffer = apr_pstrdup(r->pool, (char*)lpvBuffer);
  +        ap_internal_redirect((char*)lpvBuffer, r);
  +        return TRUE;
   
  -            /* Don't let anyone think there's still data */
  -            apr_table_unset(r->headers_in, "Content-Length");
  +    case 3: /* HSE_REQ_SEND_RESPONSE_HEADER */
  +        /* Parse them out, or die trying */
  +        return SendResponseHeaderEx(cid, (char*) lpvBuffer,
  +                                    (char*) lpdwDataType, -1, -1);
   
  -            ap_internal_redirect((char *)lpvBuffer, r);
  -            return TRUE;
   
  -        case HSE_REQ_SEND_RESPONSE_HEADER:
  -            r->status_line = lpvBuffer ? lpvBuffer : apr_pstrdup(r->pool, "200 OK");
  -            sscanf(r->status_line, "%d", &r->status);
  -            cid->ecb->dwHttpStatusCode = r->status;
  -
  -            /* Now fill in the HTTP headers, and the rest of it. Ick.
  -             * lpdwDataType contains a string that has headers (in MIME
  -             * format), a blank like, then (possibly) data. We need
  -             * to parse it.
  -             *
  -             * Easy case first:
  -             */
  -            if (!lpdwDataType) {
  -                ap_send_http_header(r);
  -                return TRUE;
  -            }
  -                        
  -            /* Make a copy - don't disturb the original */
  -            data = apr_pstrdup(r->pool, (char *)lpdwDataType);
  -            
  -            /* Parse them out, or die trying */
  -            data = ComposeHeaders(r, data);
  -            if (!data)
  -                return FALSE;
  -
  -            /* All the headers should be set now */
  -            ap_send_http_header(r);
  -
  -            /* Any data left should now be sent directly */
  -            if (*data)
  -                ap_rputs(data, r);
  -
  -            return TRUE;
  -
           case HSE_REQ_DONE_WITH_SESSION:
               /* Signal to resume the thread completing this request
                */
               if (cid->complete)
                   SetEvent(cid->complete);
               return TRUE;
  -
  -        case HSE_REQ_MAP_URL_TO_PATH:
  -            /* Map a URL to a filename */
  -            subreq = ap_sub_req_lookup_uri(apr_pstrndup(r->pool, (char *)lpvBuffer,
  -                                           *lpdwSize), r);
  -
  -            GetFullPathName(subreq->filename, *lpdwSize - 1, (char *)lpvBuffer, NULL);
   
  -            /* IIS puts a trailing slash on directories, Apache doesn't */
  -
  -            if (subreq->finfo.filetype == APR_DIR) {
  -                int l = strlen((char *)lpvBuffer);
  -
  -                ((char *)lpvBuffer)[l] = '\\';
  -                ((char *)lpvBuffer)[l + 1] = '\0';
  +    case 1001: /* HSE_REQ_MAP_URL_TO_PATH */
  +    {
  +        /* Map a URL to a filename */
  +        char *file = (char *)lpvBuffer;
  +        subreq = ap_sub_req_lookup_uri(apr_pstrndup(r->pool, file, *lpdwSize), r);
  +
  +        strncpy(file, subreq->filename, *lpdwSize - 1);
  +        file[*lpdwSize - 1] = '\0';
  +
  +        /* IIS puts a trailing slash on directories, Apache doesn't */
  +        if (subreq->finfo.filetype == APR_DIR) {
  +            DWORD l = strlen(file);
  +            if (l < *lpdwSize - 1) {
  +                file[l] = '\\';
  +                file[l + 1] = '\0';
               }
  -
  -            return TRUE;
  +        }
  +        return TRUE;
  +    }
   
  -        case HSE_REQ_GET_SSPI_INFO:
  -            SetLastError(ERROR_INVALID_PARAMETER);
  -            return FALSE;
  +    case 1002: /* HSE_REQ_GET_SSPI_INFO */
  +        if (LogNotSupported)
  +            ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_WARNING, 0, r,
  +                           "ISAPI ServerSupportFunction HSE_REQ_GET_SSPI_INFO "
  +                           "is not supported: %s", r->filename);
  +        SetLastError(ERROR_INVALID_PARAMETER);
  +        return FALSE;
           
  -        case HSE_APPEND_LOG_PARAMETER:
  -            /* Log lpvBuffer, of lpdwSize bytes, in the URI Query (cs-uri-query) field 
  -             * This code will do for now...
  -             */
  -            ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r,
  -                      "ISAPI %s: %s", cid->r->filename, 
  -                      (char*) lpvBuffer);
  -            return TRUE;
  +    case 1003: /* HSE_APPEND_LOG_PARAMETER */
  +        /* Log lpvBuffer, of lpdwSize bytes, in the URI Query (cs-uri-query) field
  +         */
  +        apr_table_set(r->notes, "isapi-parameter", (char*) lpvBuffer);
  +        if (AppendLogToQuery) {
  +            if (r->args)
  +                r->args = apr_pstrcat(r->pool, r->args, (char*) lpvBuffer, NULL);
  +            else
  +                r->args = apr_pstrdup(r->pool, (char*) lpvBuffer);
  +        }
  +        if (AppendLogToErrors)
  +            ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_INFO, 0, r,
  +                          "ISAPI %s: %s", cid->r->filename,
  +                          (char*) lpvBuffer);
  +        return TRUE;
           
  -        case HSE_REQ_IO_COMPLETION:
  -            /* TODO: Emulate a completion port, if we can...
  -             * Record the callback address and user defined argument...
  -             * we will call this after any async request (e.g. transmitfile)
  -             * as if the request had completed async execution.
  -             * Per MS docs... HSE_REQ_IO_COMPLETION replaces any prior call
  -             * to HSE_REQ_IO_COMPLETION, and lpvBuffer may be set to NULL.
  -             */
  -            if (!cid->isa->fakeasync)
  -                return FALSE;
  -            cid->completion = (PFN_HSE_IO_COMPLETION) lpvBuffer;
  -            cid->completion_arg = (PVOID) lpdwDataType;
  -            return TRUE;
  +    case 1005: /* HSE_REQ_IO_COMPLETION */
  +        /* TODO: Emulate a completion port, if we can...
  +         * Record the callback address and user defined argument...
  +         * we will call this after any async request (e.g. transmitfile)
  +         * as if the request had completed async execution.
  +         * Per MS docs... HSE_REQ_IO_COMPLETION replaces any prior call
  +         * to HSE_REQ_IO_COMPLETION, and lpvBuffer may be set to NULL.
  +         */
  +        if (!cid->isa->fakeasync)
  +            return FALSE;
  +        cid->completion = (PFN_HSE_IO_COMPLETION) lpvBuffer;
  +        cid->completion_arg = (PVOID) lpdwDataType;
  +        return TRUE;
   
  -        case HSE_REQ_TRANSMIT_FILE:
  -            /* Use TransmitFile... nothing wrong with that :)
  -             */
  +    case 1006: /* HSE_REQ_TRANSMIT_FILE */
  +        /* Use TransmitFile... nothing wrong with that :)
  +         * Just not quite ready yet...
  +         */
   
  -            /* ### euh... we're passing the wrong type of error code here */
  -            ap_log_rerror(APLOG_MARK, APLOG_WARNING,
  -                          HTTP_INTERNAL_SERVER_ERROR, r,
  -                          "ISAPI asynchronous I/O not supported: %s",
  +        if (LogNotSupported)
  +            ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_WARNING, 0, r,
  +                          "ISAPI asynchronous I/O not supported: %s", 
                             r->filename);
  -            return FALSE;
  +        SetLastError(ERROR_INVALID_PARAMETER);
  +        return FALSE;
               
  -        case HSE_REQ_REFRESH_ISAPI_ACL:
  -            SetLastError(ERROR_INVALID_PARAMETER);
  -            return FALSE;
  -
  -#if defined(HSE_REQ_IS_KEEP_CONN) /* this and a lot more aren't in VC++ 5.0 */
  -        case HSE_REQ_IS_KEEP_CONN:
  -            SetLastError(ERROR_INVALID_PARAMETER);
  -            return FALSE;
  -#endif
  -
  -#if defined(HSE_REQ_ASYNC_READ_CLIENT)
  -        case HSE_REQ_ASYNC_READ_CLIENT:
  -            SetLastError(ERROR_INVALID_PARAMETER);
  -            return FALSE;
  -#endif
  -
  -#if defined(HSE_REQ_GET_IMPERSONATION_TOKEN)
  -        case HSE_REQ_GET_IMPERSONATION_TOKEN:  /* Added in ISAPI 4.0 */
  -            SetLastError(ERROR_INVALID_PARAMETER);
  -            return FALSE;
  -#endif
  -
  -#if defined(HSE_REQ_MAP_URL_TO_PATH_EX)
  -        case HSE_REQ_MAP_URL_TO_PATH_EX:
  -            SetLastError(ERROR_INVALID_PARAMETER);
  -            return FALSE;
  -
  -            /* TODO: Not quite ready for prime time yet */
  -
  -            /* Map a URL to a filename */
  -            subreq = ap_sub_req_lookup_uri(apr_pstrndup(r->pool, (char *)lpvBuffer,
  -                                           *lpdwSize), r);
  +    case 1007: /* HSE_REQ_REFRESH_ISAPI_ACL */
  +        if (LogNotSupported)
  +            ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_WARNING, 0, r,
  +                          "ISAPI ServerSupportFunction "
  +                          "HSE_REQ_REFRESH_ISAPI_ACL "
  +                          "is not supported: %s", r->filename);
  +        SetLastError(ERROR_INVALID_PARAMETER);
  +        return FALSE;
   
  -            GetFullPathName(subreq->filename, *lpdwSize - 1, (char *)lpvBuffer, NULL);
  +    case 1008: /* HSE_REQ_IS_KEEP_CONN */
  +        *((LPBOOL) lpvBuffer) = (r->connection->keepalive == 1);
  +        return TRUE;
   
  -            /* IIS puts a trailing slash on directories, Apache doesn't */
  +    case 1010: /* HSE_REQ_ASYNC_READ_CLIENT */
  +        if (LogNotSupported)
  +            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 FALSE;
   
  -            if (subreq->finfo.filetype == APR_DIR) {
  -                int l = strlen((char *)lpvBuffer);
  +    case 1011: /* HSE_REQ_GET_IMPERSONATION_TOKEN  Added in ISAPI 4.0 */
  +        if (LogNotSupported)
  +            ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_WARNING, 0, r,
  +                          "ISAPI ServerSupportFunction "
  +                          "HSE_REQ_GET_IMPERSONATION_TOKEN "
  +                          "is not supported: %s", r->filename);
  +        SetLastError(ERROR_INVALID_PARAMETER);
  +        return FALSE;
   
  -                ((char *)lpvBuffer)[l] = '\\';
  -                ((char *)lpvBuffer)[l + 1] = '\0';
  +    case 1012: /* HSE_REQ_MAP_URL_TO_PATH_EX */
  +    {
  +        /* Map a URL to a filename */
  +        LPHSE_URL_MAPEX_INFO info = (LPHSE_URL_MAPEX_INFO) lpdwDataType;
  +        char* test_uri = apr_pstrndup(r->pool, (char *)lpvBuffer, *lpdwSize);
  +
  +        subreq = ap_sub_req_lookup_uri(test_uri, r);
  +        info->lpszPath[MAX_PATH - 1] = '\0';
  +        strncpy(info->lpszPath, subreq->filename, MAX_PATH - 1);
  +        info->cchMatchingURL = strlen(test_uri);        
  +        info->cchMatchingPath = strlen(info->lpszPath);
  +        /* Mapping started with assuming both strings matched.
  +         * Now roll on the path_info as a mismatch and handle
  +         * terminating slashes for directory matches.
  +         */
  +        if (subreq->path_info && *subreq->path_info) {
  +            strncpy(info->lpszPath + info->cchMatchingPath, subreq->path_info,
  +                    MAX_PATH - info->cchMatchingPath - 1);
  +            info->cchMatchingURL -= strlen(subreq->path_info);
  +            if (subreq->finfo.filetype == APR_DIR
  +                 && info->cchMatchingPath < MAX_PATH - 1) {
  +                /* roll forward over path_info's first slash */
  +                ++info->cchMatchingPath;
  +                ++info->cchMatchingURL;
               }
  -
  -            lpdwDataType = (LPDWORD) apr_palloc(r->pool, sizeof(HSE_URL_MAPEX_INFO));
  -            strncpy(((LPHSE_URL_MAPEX_INFO)lpdwDataType)->lpszPath,
  -                    (char *) lpvBuffer, MAX_PATH);
  -            ((LPHSE_URL_MAPEX_INFO)lpdwDataType)->dwFlags = 0;
  -            /* is a combination of:
  -             * HSE_URL_FLAGS_READ       Allow for read. 
  -             * HSE_URL_FLAGS_WRITE      Allow for write. 
  -             * HSE_URL_FLAGS_EXECUTE    Allow for execute. 
  -             * HSE_URL_FLAGS_SSL        Require SSL. 
  -             * HSE_URL_FLAGS_DONT_CACHE Don't cache (virtual root only). 
  -             * HSE_URL_FLAGS_NEGO_CERT  Allow client SSL certifications. 
  -             * HSE_URL_FLAGS_REQUIRE_CERT Require client SSL certifications. 
  -             * HSE_URL_FLAGS_MAP_CERT   Map SSL certification to a Windows account. 
  -             * HSE_URL_FLAGS_SSL128     Requires a 128-bit SSL. 
  -             * HSE_URL_FLAGS_SCRIPT     Allows for script execution. 
  -             */
  -            /* (LPHSE_URL_MAPEX_INFO)lpdwDataType)->cchMatchingPath
  -             * (LPHSE_URL_MAPEX_INFO)lpdwDataType)->cchMatchingURL
  -             */
  +        }
  +        else if (subreq->finfo.filetype == APR_DIR
  +                 && info->cchMatchingPath < MAX_PATH - 1) {
  +            /* Add a trailing slash for directory */
  +            info->lpszPath[info->cchMatchingPath++] = '/';
  +            info->lpszPath[info->cchMatchingPath] = '\0';
  +        }
   
  -            return TRUE;
  -#endif
  +        /* If the matched isn't a file, roll match back to the prior slash */
  +        if (subreq->finfo.filetype == APR_NOFILE) {
  +            while (info->cchMatchingPath && info->cchMatchingURL) {
  +                if (info->lpszPath[info->cchMatchingPath - 1] == '/') 
  +                    break;
  +                --info->cchMatchingPath;
  +                --info->cchMatchingURL;
  +            }
  +        }
  +        
  +        /* Paths returned with back slashes */
  +        for (test_uri = info->lpszPath; *test_uri; ++test_uri)
  +            if (*test_uri == '/')
  +                *test_uri = '\\';
  +        
  +        /* is a combination of:
  +         * HSE_URL_FLAGS_READ         0x001 Allow read
  +         * HSE_URL_FLAGS_WRITE        0x002 Allow write
  +         * HSE_URL_FLAGS_EXECUTE      0x004 Allow execute
  +         * HSE_URL_FLAGS_SSL          0x008 Require SSL
  +         * HSE_URL_FLAGS_DONT_CACHE   0x010 Don't cache (VRoot only)
  +         * HSE_URL_FLAGS_NEGO_CERT    0x020 Allow client SSL cert
  +         * HSE_URL_FLAGS_REQUIRE_CERT 0x040 Require client SSL cert
  +         * HSE_URL_FLAGS_MAP_CERT     0x080 Map client SSL cert to account
  +         * HSE_URL_FLAGS_SSL128       0x100 Require 128-bit SSL cert
  +         * HSE_URL_FLAGS_SCRIPT       0x200 Allow script execution
  +         *
  +         * XxX: As everywhere, EXEC flags could use some work...
  +         *      and this could go further with more flags, as desired.
  +         */ 
  +        info->dwFlags = (subreq->finfo.protection & APR_UREAD    ? 0x001 : 0)
  +                      | (subreq->finfo.protection & APR_UWRITE   ? 0x002 : 0)
  +                      | (subreq->finfo.protection & APR_UEXECUTE ? 0x204 : 0);
  +        return TRUE;
  +    }
   
  -#if defined(HSE_REQ_ABORTIVE_CLOSE)
  -        case HSE_REQ_ABORTIVE_CLOSE:
  -            SetLastError(ERROR_INVALID_PARAMETER);
  +    case 1014: /* HSE_REQ_ABORTIVE_CLOSE */
  +        if (LogNotSupported)
  +            ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_WARNING, 0, r,
  +                          "ISAPI ServerSupportFunction HSE_REQ_ABORTIVE_CLOSE"
  +                          " is not supported: %s", r->filename);
  +        SetLastError(ERROR_INVALID_PARAMETER);
               return FALSE;
  -#endif
   
  -#if defined(HSE_REQ_GET_CERT_INFO_EX)
  -        case HSE_REQ_GET_CERT_INFO_EX:  /* Added in ISAPI 4.0 */
  -            SetLastError(ERROR_INVALID_PARAMETER);
  +    case 1015: /* HSE_REQ_GET_CERT_INFO_EX  Added in ISAPI 4.0 */
  +        if (LogNotSupported)
  +            ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_WARNING, 0, r,
  +                          "ISAPI ServerSupportFunction "
  +                          "HSE_REQ_GET_CERT_INFO_EX "
  +                          "is not supported: %s", r->filename);
  +        SetLastError(ERROR_INVALID_PARAMETER);
               return FALSE;
  -#endif
   
  -#if defined(HSE_REQ_SEND_RESPONSE_HEADER_EX)
  -        case HSE_REQ_SEND_RESPONSE_HEADER_EX:  /* Added in ISAPI 4.0 */
  -            SetLastError(ERROR_INVALID_PARAMETER);
  -            return FALSE;
  +    case 1016: /* HSE_REQ_SEND_RESPONSE_HEADER_EX  Added in ISAPI 4.0 */
  +    {
  +        LPHSE_SEND_HEADER_EX_INFO shi
  +                                  = (LPHSE_SEND_HEADER_EX_INFO) lpvBuffer;
  +        /* XXX: ignore shi->fKeepConn?  We shouldn't need the advise */
  +        /* r->connection->keepalive = shi->fKeepConn; */
  +        return SendResponseHeaderEx(cid, shi->pszStatus, shi->pszHeader,
  +                                         shi->cchStatus, shi->cchHeader);
  +    }
   
  -            /* TODO: Not quite ready for prime time */
  +    case 1017: /* HSE_REQ_CLOSE_CONNECTION  Added after ISAPI 4.0 */
  +        if (LogNotSupported)
  +            ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_WARNING, 0, r,
  +                          "ISAPI ServerSupportFunction "
  +                          "HSE_REQ_CLOSE_CONNECTION "
  +                          "is not supported: %s", r->filename);
  +        SetLastError(ERROR_INVALID_PARAMETER);
  +        return FALSE;
   
  -            if (((LPHSE_SEND_HEADER_EX_INFO)lpvBuffer)->pszStatus
  -                && ((LPHSE_SEND_HEADER_EX_INFO)lpvBuffer)->cchStatus) {
  -                r->status_line = apr_pstrndup(r->pool, 
  -                           ((LPHSE_SEND_HEADER_EX_INFO)lpvBuffer)->pszStatus,
  -                           ((LPHSE_SEND_HEADER_EX_INFO)lpvBuffer)->cchStatus);
  -            }
  -            else {
  -                r->status_line = apr_pstrdup(r->pool, "200 OK");
  -            }
  -            sscanf(r->status_line, "%d", &r->status);
  -            cid->ecb->dwHttpStatusCode = r->status;
  +    case 1018: /* HSE_REQ_IS_CONNECTED  Added after ISAPI 4.0 */
  +        /* Returns True if client is connected c.f. MSKB Q188346
  +         * XXX: That statement is very ambigious... assuming the 
  +         * identical return mechanism as HSE_REQ_IS_KEEP_CONN.
  +         */
  +        *((LPBOOL) lpvBuffer) = (r->connection->aborted == 0);
  +        return TRUE;
   
  -            if (((LPHSE_SEND_HEADER_EX_INFO)lpvBuffer)->pszHeader
  -                && ((LPHSE_SEND_HEADER_EX_INFO)lpvBuffer)->cchHeader)
  -            {
  -                /* Make a copy - don't disturb the original */
  -                data = apr_pstrndup(r->pool, 
  -                           ((LPHSE_SEND_HEADER_EX_INFO)lpvBuffer)->pszHeader,
  -                           ((LPHSE_SEND_HEADER_EX_INFO)lpvBuffer)->cchHeader);
  -                
  -                /* Parse them out, or die trying */
  -                data = ComposeHeaders(r, data);
  -                if (!data)
  -                    return FALSE;
  +    case 1020: /* HSE_REQ_EXTENSION_TRIGGER  Added after ISAPI 4.0 */
  +        /*  Undocumented - defined by the Microsoft Jan '00 Platform SDK
  +         */
  +        if (LogNotSupported)
  +            ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_WARNING, 0, r,
  +                          "ISAPI ServerSupportFunction "
  +                          "HSE_REQ_EXTENSION_TRIGGER "
  +                          "is not supported: %s", r->filename);
  +        SetLastError(ERROR_INVALID_PARAMETER);
  +        return FALSE;
   
  -            }
  -            else {
  -                data = "\0";
  -            }
  -            
  -            /* ((LPHSE_SEND_HEADER_EX_INFO)lpvBuffer)->fKeepConn; 
  -             *
  -             * Now how are we about to start listening to an ISAPI's
  -             * idea of keeping or closing a connection?  Seriously :)
  -             */
  +    default:
  +        if (LogNotSupported)
  +            ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_WARNING, 0, r,
  +                          "ISAPI ServerSupportFunction (%d) not supported: "
  +                          "%s", dwHSERequest, r->filename);
  +        SetLastError(ERROR_INVALID_PARAMETER);
  +        return FALSE;
  +    }
  +}
   
  -            /* All the headers should be set now */
  -            ap_send_http_header(r);
  +/*
  + * Command handler for the ISAPIReadAheadBuffer directive, which is TAKE1
  + */
  +static const char *isapi_cmd_readaheadbuffer(cmd_parms *cmd, void *config, 
  +                                             char *arg)
  +{
  +    long val;
  +    const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
  +    if (err != NULL) {
  +        return err;
  +    }
   
  -            /* Any data left should now be sent directly */
  -            if (*data)
  -                ap_rputs(data, r);
  +    if (((val = strtol(arg, (char **) &err, 10)) <= 0) || *err)
  +        return "ISAPIReadAheadBuffer must be a legitimate value.";
  +    
  +    ReadAheadBuffer = val;
  +    return NULL;
  +}
   
  -            return TRUE;
  -#endif
  +/*
  + * Command handler for the ISAPIReadAheadBuffer directive, which is TAKE1
  + */
  +static const char *isapi_cmd_lognotsupported(cmd_parms *cmd, void *config, 
  +                                             char *arg)
  +{
  +    const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
  +    if (err != NULL) {
  +        return err;
  +    }
   
  -#if defined(HSE_REQ_CLOSE_CONNECTION) /* not in VC++ 5.0 or 6.0 */
  -        case HSE_REQ_CLOSE_CONNECTION:  /* Added after ISAPI 4.0 */
  -            SetLastError(ERROR_INVALID_PARAMETER);
  -            return FALSE;
  -#endif
  +    if (strcasecmp(arg, "on") == 0) {
  +        LogNotSupported = -1;
  +    }
  +    else if (strcasecmp(arg, "off") == 0) {
  +        LogNotSupported = 0;
  +    }
  +    else {
  +        return "ISAPILogNotSupported must be on or off";
  +    }
  +    return NULL;
  +}
   
  -#if defined(HSE_REQ_IS_CONNECTED) /* not in VC++ 5.0 or 6.0 */
  -        case HSE_REQ_IS_CONNECTED:  /* Added after ISAPI 4.0 */
  -            /* Returns True if client is connected c.f. Q188346*/
  -            return TRUE;
  -#endif
  +static const char *isapi_cmd_appendlogtoerrors(cmd_parms *cmd, void *config, 
  +                                               char *arg)
  +{
  +    const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
  +    if (err != NULL) {
  +        return err;
  +    }
   
  -     /* case HSE_REQ_EXTENSION_TRIGGER:  
  -      *     Added after ISAPI 4.0? 
  -      *      Undocumented - from the Microsoft Jan '00 Platform SDK
  -      */
  -        default:
  -            /* TODO: log unrecognized ServerSupportCommand for debugging 
  -             */
  -            SetLastError(ERROR_INVALID_PARAMETER);
  -            return FALSE;
  +    if (strcasecmp(arg, "on") == 0) {
  +        AppendLogToErrors = -1;
  +    }
  +    else if (strcasecmp(arg, "off") == 0) {
  +        AppendLogToErrors = 0;
  +    }
  +    else {
  +        return "ISAPIAppendLogToErrors must be on or off";
       }
  +    return NULL;
   }
   
  +static const char *isapi_cmd_appendlogtoquery(cmd_parms *cmd, void *config, 
  +                                               char *arg)
  +{
  +    const char *err = ap_check_cmd_context(cmd, GLOBAL_ONLY);
  +    if (err != NULL) {
  +        return err;
  +    }
  +
  +    if (strcasecmp(arg, "on") == 0) {
  +        AppendLogToQuery = -1;
  +    }
  +    else if (strcasecmp(arg, "off") == 0) {
  +        AppendLogToQuery = 0;
  +    }
  +    else {
  +        return "ISAPIAppendLogToQuery must be on or off";
  +    }
  +    return NULL;
  +}
  +
  +static const command_rec isapi_cmds[] = {
  +{ "ISAPIReadAheadBuffer", isapi_cmd_readaheadbuffer, NULL, RSRC_CONF, TAKE1, 
  +  "Maximum bytes to initially pass to the ISAPI handler" },
  +{ "ISAPILogNotSupported", isapi_cmd_lognotsupported, NULL, RSRC_CONF, TAKE1, 
  +  "Log requests not supported by the ISAPI server" },
  +{ "ISAPIAppendLogToErrors", isapi_cmd_appendlogtoerrors, NULL, RSRC_CONF, TAKE1, 
  +  "Send all Append Log requests to the error log" },
  +{ "ISAPIAppendLogToQuery", isapi_cmd_appendlogtoquery, NULL, RSRC_CONF, TAKE1, 
  +  "Append Log requests are concatinated to the query args" },
  +{ NULL }
  +};
  +
   handler_rec isapi_handlers[] = {
       { "isapi-isa", isapi_handler },
       { NULL}
  @@ -882,7 +987,7 @@
      NULL,                        /* merge per-dir config */
      NULL,                        /* server config */
      NULL,                        /* merge server config */
  -   NULL,                        /* command apr_table_t */
  +   isapi_cmds,                  /* command apr_table_t */
      isapi_handlers,              /* handlers */
      NULL                         /* register hooks */
   };
  
  
  
  1.41      +58 -0     apache-2.0/src/main/util_script.c
  
  Index: util_script.c
  ===================================================================
  RCS file: /home/cvs/apache-2.0/src/main/util_script.c,v
  retrieving revision 1.40
  retrieving revision 1.41
  diff -u -r1.40 -r1.41
  --- util_script.c	2000/08/07 19:26:00	1.40
  +++ util_script.c	2000/10/02 15:38:57	1.41
  @@ -607,6 +607,64 @@
   }
   
   
  +struct vastrs {
  +    va_list args;
  +    int arg;
  +    const char *curpos;
  +};
  +
  +static int getsfunc_STRING(char *w, int len, void *pvastrs)
  +{
  +    struct vastrs *strs = (struct vastrs*) pvastrs;
  +    char *p;
  +    int t;
  +    
  +    if (!strs->curpos || !*strs->curpos) 
  +        return 0;
  +    p = strchr(strs->curpos, '\n');
  +    if (p)
  +        ++p;
  +    else
  +        p = strchr(strs->curpos, '\0');
  +    t = p - strs->curpos;
  +    if (t > len)
  +        t = len;
  +    strncpy (w, strs->curpos, t);
  +    w[t] = '\0';
  +    if (!strs->curpos[t]) {
  +        ++strs->arg;
  +        strs->curpos = va_arg(strs->args, const char *);
  +    }
  +    else
  +        strs->curpos += t;
  +    return t;    
  +}
  +
  +/* ap_scan_script_header_err_strs() accepts additional const char* args...
  + * each is treated as one or more header lines, and the first non-header
  + * character is returned to **arg, **data.  (The first optional arg is
  + * counted as 0.)
  + */
  +API_EXPORT_NONSTD(int) ap_scan_script_header_err_strs(request_rec *r, 
  +                                                      char *buffer, 
  +                                                      const char **termch,
  +                                                      int *termarg, ...)
  +{
  +    struct vastrs strs;
  +    int res;
  +
  +    va_start(strs.args, termarg);
  +    strs.arg = 0;
  +    strs.curpos = va_arg(strs.args, char*);
  +    res = ap_scan_script_header_err_core(r, buffer, getsfunc_STRING, (void *) &strs);
  +    if (termch)
  +        *termch = strs.curpos;
  +    if (termarg)
  +        *termarg = strs.arg;
  +    va_end(strs.args);
  +    return res;
  +}
  +
   API_EXPORT(void) ap_send_size(apr_ssize_t size, request_rec *r)
   {
       /* XXX: this -1 thing is a gross hack */
  
  
  
  1.11      +19 -0     apache-2.0/src/include/util_script.h
  
  Index: util_script.h
  ===================================================================
  RCS file: /home/cvs/apache-2.0/src/include/util_script.h,v
  retrieving revision 1.10
  retrieving revision 1.11
  diff -u -r1.10 -r1.11
  --- util_script.h	2000/08/07 03:07:50	1.10
  +++ util_script.h	2000/10/02 15:38:59	1.11
  @@ -139,6 +139,25 @@
                                                  char *buffer);
   
   /**
  + * Read headers strings from a script, ensuring that the output is valid.  If
  + * the output is valid, then the headers are added to the headers out of the
  + * current request
  + * @param r The current request
  + * @param buffer Empty when calling the function.  On output, if there was an
  + *               error, the string that cause the error is stored here. 
  + * @param termch Pointer to the last character parsed.
  + * @param termarg Pointer to an int to capture the last argument parsed.
  + * @param args   String arguments to parse consecutively for headers, 
  + *               a NULL argument terminates the list.
  + * @return HTTP_OK on success, HTTP_INTERNAL_SERVER_ERROR otherwise
  + * @deffunc int ap_scan_script_header_err_core(request_rec *r, char *buffer, int (*getsfunc)(char *, int, void *), void *getsfunc_data)
  + */ 
  +API_EXPORT_NONSTD(int) ap_scan_script_header_err_strs(request_rec *r, 
  +                                                      char *buffer, 
  +                                                      const char **termch,
  +                                                      int *termarg, ...);
  +
  +/**
    * Read headers output from a script, ensuring that the output is valid.  If
    * the output is valid, then the headers are added to the headers out of the
    * current request