On 2005.07.24, Martin Saturka <[EMAIL PROTECTED]> wrote:
> I can not make PHP4 to work as cgi (with nscgi). I added an option of:
> ns_param "REDIRECT_STATUS" "1"
> so that it try to work at all. Still, however, PHP can not find script files,
> with the only output of: 'No input file specified.'

There's a change to the nscgi module in CVS (which will be included as
part of the AOLserver 4.0.11 release, whenever that happens).

You can try checking out the latest nscgi module code from CVS.
Otherwise, I'm attaching the nscgi.c to this email -- just drop it into
your nscgi/ directory and recompile.

-- Dossy

-- 
Dossy Shiobara                       mail: [EMAIL PROTECTED] 
Panoptic Computer Network             web: http://www.panoptic.com/ 
  "He realized the fastest way to change is to laugh at your own
    folly -- then you can let go and quickly move on." (p. 70)


--
AOLserver - http://www.aolserver.com/

To Remove yourself from this list, simply send an email to <[EMAIL PROTECTED]> 
with the
body of "SIGNOFF AOLSERVER" in the email message. You can leave the Subject: 
field of your email blank.
/*
 * The contents of this file are subject to the AOLserver Public License
 * Version 1.1 (the "License"); you may not use this file except in
 * compliance with the License. You may obtain a copy of the License at
 * http://aolserver.com/.
 *
 * Software distributed under the License is distributed on an "AS IS"
 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
 * the License for the specific language governing rights and limitations
 * under the License.
 *
 * The Original Code is AOLserver Code and related documentation
 * distributed by AOL.
 * 
 * The Initial Developer of the Original Code is America Online,
 * Inc. Portions created by AOL are Copyright (C) 1999 America Online,
 * Inc. All Rights Reserved.
 *
 * Alternatively, the contents of this file may be used under the terms
 * of the GNU General Public License (the "GPL"), in which case the
 * provisions of GPL are applicable instead of those above.  If you wish
 * to allow use of your version of this file only under the terms of the
 * GPL and not to allow others to use your version of this file under the
 * License, indicate your decision by deleting the provisions above and
 * replace them with the notice and other provisions required by the GPL.
 * If you do not delete the provisions above, a recipient may use your
 * version of this file under either the License or the GPL.
 */


static const char *RCSID = "@(#) $Header: /cvsroot/aolserver/aolserver/nscgi/nscgi.c,v 1.23.2.2 2005/05/10 01:19:50 dossy Exp $, compiled: " __DATE__ " " __TIME__;

#include "ns.h"
#include <sys/stat.h>
#include <ctype.h>
#include <stdlib.h>	/* environ */

#define BUFSIZE	    4096
#define NDSTRINGS   5
#define DEFAULT_MAXINPUT    1024000
#define CGI_NPH	    	1
#define CGI_GETHOST	2
#define CGI_ECONTENT	4
#define CGI_SYSENV	8

#ifdef _WIN32
#define S_ISREG(m)	((m)&_S_IFREG)
#define S_ISDIR(m)	((m)&_S_IFDIR)
#define DEVNULL	    "nul:"
#else
#define DEVNULL	    "/dev/null"
#endif

/*
 * The following structure is allocated for each instance the module is
 * loaded (normally just once).
 */
 
struct Cgi;

typedef struct Mod {
    char	   *server;
    char	   *module;
    Ns_Set         *interps;
    Ns_Set         *mergeEnv;
    struct Cgi     *firstCgiPtr;
    int		    flags;
    int             maxInput;
    int             maxCgi;
    int     	    maxWait;
    int             activeCgi;
    Ns_Mutex 	    lock;
    Ns_Cond 	    cond;
} Mod;

/*
 * The following structure, allocated on the stack of CgiRequest, is used
 * to accumulate all the resources of a CGI.  CGI is a very messy interface
 * which requires varying degrees of resources.  Packing everything into
 * this structure allows building up the state in multiple places and
 * tearing it all down in FreeCgi, thus simplifying the CgiRequest procedure.
 */
 
typedef struct Cgi {
    Mod     	   *modPtr;
    int		    flags;
    int     	    pid;
    Ns_Set	   *env;
    char    	   *name;
    char    	   *path;
    char           *pathinfo;
    char    	   *dir;
    char           *exec;
    char           *interp;
    Ns_Set         *interpEnv;
    int		    ifd;
    int		    ofd;
    int		    cnt;
    char	   *ptr;
    int		    nextds;
    Tcl_DString	    ds[NDSTRINGS];
    char	    buf[BUFSIZE];
} Cgi;

/*
 * The following structure defines the context of a single CGI config
 * mapping, supporting both directory-style and pageroot-style CGI locations.
 */
 
typedef struct Map {
    Mod	     *modPtr;
    char     *url;
    char     *path;
} Map;

/*
 * The following file descriptor is opened once on the first load and used
 * simply for duping as stdin in the child process.  This ensures the child
 * will get a proper EOF without having to allocate an empty temp file.
 */
 
static int devNull;

static Ns_OpProc CgiRequest;
static void     CgiRegister(Mod *modPtr, char *map);
static Ns_Callback CgiFreeMap;
static Ns_DString *CgiDs(Cgi *cgiPtr);
static int	CgiInit(Cgi *cgiPtr, Map *mapPtr, Ns_Conn *conn);
static void	CgiFree(Cgi *cgiPtr);
static int  	CgiExec(Cgi *cgiPtr, Ns_Conn *conn);
static int	CgiSpool(Cgi *cgiPtr, Ns_Conn *conn);
static int	CgiCopy(Cgi *cgiPtr, Ns_Conn *conn);
static int	CgiRead(Cgi *cgiPtr);
static int	CgiReadLine(Cgi *cgiPtr, Ns_DString *dsPtr);
static char    *NextWord(char *s);
static void	SetAppend(Ns_Set *set, int index, char *sep, char *value);
static void	SetUpdate(Ns_Set *set, char *key, char *value);

int Ns_ModuleVersion = 1;	


/*
 *----------------------------------------------------------------------
 *
 * Ns_ModuleInit --
 *
 *	Create a new CGI module instance.  Note: This module can
 *	be loaded multiple times.
 *
 * Results:
 *	NS_OK/NS_ERROR.
 *
 * Side effects:
 *	URL's may be registered for CGI.
 *
 *----------------------------------------------------------------------
 */
 
int
Ns_ModuleInit(char *server, char *module)
{
    char           *path, *key, *value, *section;
    int             i;
    Ns_Set         *set;
    Ns_DString      ds;
    Mod		   *modPtr;
    static int	    initialized;

    /*
     * On the first (and likely only) load, register
     * the temp file cleanup routine and open devNull
     * for requests without content data.
     */

    if (!initialized) {
	devNull = open(DEVNULL, O_RDONLY);
	if (devNull < 0) {
	    Ns_Log(Error, "nscgi: open(%s) failed: %s",
		   DEVNULL, strerror(errno));
	    return NS_ERROR;
	}
	Ns_DupHigh(&devNull);
	Ns_CloseOnExec(devNull);
	initialized = 1;
    }

    /*
     * Config basic options.
     */

    path = Ns_ConfigGetPath(server, module, NULL);
    modPtr = ns_calloc(1, sizeof(Mod));
    modPtr->module = module;
    modPtr->server = server;
    Ns_MutexInit(&modPtr->lock);
    Ns_MutexSetName2(&modPtr->lock, "nscgi", server);
    if (!Ns_ConfigGetInt(path, "maxinput", &modPtr->maxInput)) {
        modPtr->maxInput = DEFAULT_MAXINPUT;
    }
    if (!Ns_ConfigGetInt(path, "limit", &modPtr->maxCgi)) {
        modPtr->maxCgi = 0;
    }
    if (!Ns_ConfigGetInt(path, "maxwait", &modPtr->maxWait)) {
        modPtr->maxWait = 30;
    }
    if (!Ns_ConfigGetBool(path, "gethostbyaddr", &i)) {
        i = 0;
    }
    if (i) {
	modPtr->flags |= CGI_GETHOST;
    }

    /*
     * Configure the various interp and env options.
     */

    Ns_DStringInit(&ds);
    section = Ns_ConfigGetValue(path, "interps");
    if (section != NULL) {
        Ns_DStringVarAppend(&ds, "ns/interps/", section, NULL);
        modPtr->interps = Ns_ConfigGetSection(ds.string);
        if (modPtr->interps == NULL) {
            Ns_Log(Warning, "nscgi: no such interps section: %s",
		   ds.string);
        }
    	Ns_DStringTrunc(&ds, 0);
    }
    section = Ns_ConfigGetValue(path, "environment");
    if (section != NULL) {
        Ns_DStringVarAppend(&ds, "ns/environment/", section, NULL);
        modPtr->mergeEnv = Ns_ConfigGetSection(ds.string);
        if (modPtr->mergeEnv == NULL) {
            Ns_Log(Warning, "nscgi: no such environment section: %s",
		   ds.string);
        }
    	Ns_DStringTrunc(&ds, 0);
    }
    if (!Ns_ConfigGetBool(path, "systemenvironment", &i)) {
        i = 0;
    }
    if (i) {
	modPtr->flags |= CGI_SYSENV;
    }

    /*
     * Register all requested mappings.
     */

    set = Ns_ConfigGetSection(path);
    for (i = 0; set != NULL && i < Ns_SetSize(set); ++i) {
        key = Ns_SetKey(set, i);
        value = Ns_SetValue(set, i);
        if (STRIEQ(key, "map")) {
            CgiRegister(modPtr, value);
        }
    }
    Ns_DStringFree(&ds);

    return NS_OK;
}


/*
 *----------------------------------------------------------------------
 *
 * CgiRequest -
 *
 *	Process a CGI request.
 *
 * Results:
 *	Standard AOLserver request result.
 *
 * Side effects:
 *	Program may be executed.
 *
 *----------------------------------------------------------------------
 */

static int
CgiRequest(void *arg, Ns_Conn *conn)
{
    Map		   *mapPtr;
    Mod		   *modPtr;
    Cgi		    cgi;
    int             status;

    mapPtr = arg;
    modPtr = mapPtr->modPtr;

    /*
     * Check for input overflow and initialize the CGI context.
     */

    if (modPtr->maxInput > 0 && conn->contentLength > modPtr->maxInput) {
        return Ns_ReturnBadRequest(conn, "Exceeded maximum CGI input size");
    }
    if (CgiInit(&cgi, mapPtr, conn) != NS_OK) {
	return Ns_ReturnNotFound(conn);
    } else if (cgi.interp == NULL && access(cgi.exec, X_OK) != 0) {
        if (STREQ(conn->request->method, "GET") ||
	    STREQ(conn->request->method, "HEAD")) {

	    /*
	     * Evidently people are storing images and such in
	     * their cgi bin directory and they expect us to
	     * return these files directly.
	     */

            status = Ns_ConnReturnFile(conn, 200, NULL, cgi.exec);
        } else {
	    status = Ns_ReturnNotFound(conn);
	}
	goto done;
    }

    /*
     * Spool input to temp file if necessary.
     */

    if (conn->contentLength > 0 && CgiSpool(&cgi, conn) != NS_OK) {
	if (cgi.flags & CGI_ECONTENT) {
	    status = Ns_ConnReturnBadRequest(conn, "Insufficient Content");
	} else {
	    status = Ns_ConnReturnInternalError(conn);
	}
	goto done;
    }

    /*
     * Wait for CGI access if necessary.
     */

    if (modPtr->maxCgi > 0) {
	Ns_Time timeout;
	int wait = NS_OK;

	Ns_GetTime(&timeout);
	Ns_IncrTime(&timeout, modPtr->maxWait, 0);
        Ns_MutexLock(&modPtr->lock);
	while (wait == NS_OK && modPtr->activeCgi >= modPtr->maxCgi) {
	    wait = Ns_CondTimedWait(&modPtr->cond, &modPtr->lock, &timeout);
	}
	if (wait == NS_OK) {
	    ++modPtr->activeCgi;
	}
	Ns_MutexUnlock(&modPtr->lock);
	if (wait != NS_OK) {
	    status = Ns_ConnReturnStatus(conn, 503);
	    goto done;
	}
    }

    /*
     * Execute the CGI and copy output.
     */
    
    if (CgiExec(&cgi, conn) != NS_OK) {
	status = Ns_ConnReturnInternalError(conn);
    } else {
	status = CgiCopy(&cgi, conn);
    }
    
    /*
     * Release CGI access.
     */
     
    if (modPtr->maxCgi > 0) {
	Ns_MutexLock(&modPtr->lock);
	--modPtr->activeCgi;
	Ns_CondSignal(&modPtr->cond);
	Ns_MutexUnlock(&modPtr->lock);
    }

done:
    CgiFree(&cgi);
    return status;
}


/*
 *----------------------------------------------------------------------
 *
 * CgiInit -
 *
 *	Setup a CGI context structure.  This function
 *	encapsulates the majority of the CGI semantics.
 *
 * Results:
 *	NS_OK or NS_ERROR.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

static int
CgiInit(Cgi *cgiPtr, Map *mapPtr, Ns_Conn *conn)
{
    Mod		   *modPtr;
    Ns_DString     *dsPtr;
    int             ulen, plen, i;
    struct stat     st;
    char           *s, *e;
    char    	   *url = conn->request->url;
    char	   *server = Ns_ConnServer(conn);
    
    modPtr = mapPtr->modPtr;
    memset(cgiPtr, 0, ((char *) &cgiPtr->ds[0]) - (char *) cgiPtr);
    cgiPtr->buf[0] = '\0';
    cgiPtr->modPtr = modPtr;
    cgiPtr->pid = -1;
    cgiPtr->ofd = cgiPtr->ifd = -1;
    cgiPtr->ptr = cgiPtr->buf;
    for (i = 0; i < NDSTRINGS; ++i) {
	Ns_DStringInit(&cgiPtr->ds[i]);
    }

    /*
     * Determine the executable or script to run.
     */

    ulen = strlen(url);
    plen = strlen(mapPtr->url);
    if ((strncmp(mapPtr->url, url, (size_t)plen) == 0) &&
    	(ulen == plen || url[plen] == '/')) {
	
        if (mapPtr->path == NULL) {

            /*
             * No path mapping, script in pages directory:
             * 
             * 1. Path is Url2File up to the URL prefix.
	     * 2. SCRIPT_NAME is the URL prefix.
	     * 3. PATH_INFO is everything past SCRIPT_NAME in the URL.
             */
	     
            cgiPtr->name = Ns_DStringNAppend(CgiDs(cgiPtr), url, plen);
	    dsPtr = CgiDs(cgiPtr);
            Ns_UrlToFile(dsPtr, server, cgiPtr->name);
	    cgiPtr->path = dsPtr->string;
            cgiPtr->pathinfo = url + plen;
	    
        } else if (stat(mapPtr->path, &st) != 0) {
	    goto err;
	
    	} else if (S_ISDIR(st.st_mode)) {

            /*
             * Path mapping is a directory:
             * 
             * 1. The script file is the first path element in the URL past
             * the mapping prefix.
	     * 2. SCRIPT_NAME is the URL up to and including the
	     * script file.
	     * 3. PATH_INFO is everything in the URL past SCRIPT_NAME.
	     * 4. The script pathname is the script prefix plus the
	     * script file.
             */

            if (plen == ulen) {
		goto err;
            }

            s = url + plen + 1;
            e = strchr(s, '/');
	    if (e != NULL) {
		*e = '\0';
	    }
	    cgiPtr->name = Ns_DStringAppend(CgiDs(cgiPtr), url);
            cgiPtr->path = Ns_DStringVarAppend(CgiDs(cgiPtr),
	    	    	    	    	      mapPtr->path, "/", s, NULL);
    	    if (e == NULL) {
		cgiPtr->pathinfo = "";
	    } else {
		*e = '/';
		cgiPtr->pathinfo = e;
	    }

        } else if (S_ISREG(st.st_mode)) {

            /*
             * When the path mapping is (or at least could be) a file:
             * 
             * 1. The script pathname is the mapping.
	     * 2. SCRIPT_NAME is the url prefix.
	     * 3. PATH_INFO is everything in the URL past SCRIPT_NAME.
             */

	    cgiPtr->path = Ns_DStringAppend(CgiDs(cgiPtr), mapPtr->path);
	    cgiPtr->name = Ns_DStringAppend(CgiDs(cgiPtr), mapPtr->url);
            cgiPtr->pathinfo = url + plen;

    	} else {
	    goto err;
        }

    } else {

        /*
         * The prefix didn't match.  Assume the mapping was a wildcard
         * mapping like *.cgi which was fetched by UrlSpecificGet() but
         * skipped by strncmp() above. In this case:
         * 
         * 1. The script pathname is the URL file in the pages directory.
	 * 2. SCRIPT_NAME is the URL.
	 * 3. PATH_INFO is "".
         */
	 
	dsPtr = CgiDs(cgiPtr);   
	Ns_UrlToFile(dsPtr, server, url);
	cgiPtr->path = dsPtr->string;
	cgiPtr->name = url;
        cgiPtr->pathinfo = url + ulen;
    }

    /*
     * Copy the script directory and see if the script is NPH.
     */
     
    s = strrchr(cgiPtr->path, '/');
    if (s == NULL || access(cgiPtr->path, R_OK) != 0) {
	goto err;
    }
    *s = '\0';
    cgiPtr->dir = Ns_DStringAppend(CgiDs(cgiPtr), cgiPtr->path);
    *s++ = '/';
    if (strncmp(s, "nph-", 4) == 0) {
        cgiPtr->flags |= CGI_NPH;
    }

    /*
     * Look for a script interpreter.
     */

    if (modPtr->interps != NULL
    	&&(s = strrchr(cgiPtr->path, '.')) != NULL
        && (cgiPtr->interp = Ns_SetIGet(modPtr->interps, s)) != NULL) {
    	cgiPtr->interp = Ns_DStringAppend(CgiDs(cgiPtr), cgiPtr->interp);
        s = strchr(cgiPtr->interp, '(');
        if (s != NULL) {
            *s++ = '\0';
            e = strchr(s, ')');
            if (e != NULL) {
                *e = '\0';
            }
            cgiPtr->interpEnv = Ns_ConfigGetSection(s);
        }
    }
    if (cgiPtr->interp != NULL) {
        cgiPtr->exec = cgiPtr->interp;
    } else {
        cgiPtr->exec = cgiPtr->path;
    }
    return NS_OK;

err:
    CgiFree(cgiPtr);
    return NS_ERROR;
}


/*
 *----------------------------------------------------------------------
 *
 * CgiSpool --
 *
 *	Spool content to a temp file.
 *
 * Results:
 *	File descriptor of temp file or -1 on error.
 *
 * Side effects:
 *	May open a new temp file.
 *
 *----------------------------------------------------------------------
 */

static int
CgiSpool(Cgi *cgiPtr, Ns_Conn *conn)
{
    int     len, fd;
    char   *content, *err;

    err = NULL;
    len = conn->contentLength;
    content = Ns_ConnContent(conn);
    fd = Ns_GetTemp();
    if (fd < 0) {
	Ns_Log(Error, "nscgi: could not allocate temp file.");
    } else if (write(fd, content, (size_t)len) != len) {
	err = "write";
    } else if (lseek(fd, 0, SEEK_SET) != 0) {
	err = "lseek";
    }
    if (err != NULL) {
	Ns_Log(Error, "nscgi: temp file %s failed: %s", err, strerror(errno));
	close(fd);
	fd = -1;
    }
    if (fd < 0) {
	return NS_ERROR;
    }
    cgiPtr->ifd = fd;
    return NS_OK;
}


/*
 *----------------------------------------------------------------------
 *
 * CgiDs -
 *
 *	Return the next available dstring in the CGI context.
 *
 * Results:
 *	Pointer to DString.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

static Ns_DString *
CgiDs(Cgi *cgiPtr)
{
    return &cgiPtr->ds[cgiPtr->nextds++];
}


/*
 *----------------------------------------------------------------------
 *
 * CgiFree -
 *
 *	Free temp buffers used in CGI context.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

static void
CgiFree(Cgi *cgiPtr)
{
    /*
     * Close the pipe.
     */
    
    if (cgiPtr->ofd >= 0) {
    	close(cgiPtr->ofd);
    }
        
    /*
     * Release the temp file.
     */

    if (cgiPtr->ifd >= 0) {
	Ns_ReleaseTemp(cgiPtr->ifd);
    }
     
    /*
     * Free the environment.
     */
     
    if (cgiPtr->env != NULL) {
	Ns_SetFree(cgiPtr->env);
    }

    /*
     * Reap the process.
     */
     
    if (cgiPtr->pid != -1 && Ns_WaitProcess(cgiPtr->pid) != NS_OK) {
	Ns_Log(Error, "nscgi: wait for %s failed: %s",
	       cgiPtr->exec, strerror(errno));
    }

    /*
     * Free all dstrings.
     */

    while (cgiPtr->nextds-- > 0) {
	Ns_DStringFree(&cgiPtr->ds[cgiPtr->nextds]);
    }
}


/*
 *----------------------------------------------------------------------
 *
 * CgiExec -
 *
 *	Construct the command args and environment and execute
 *  	the CGI.
 *
 * Results:
 *	NS_OK or NS_ERROR.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

static int
CgiExec(Cgi *cgiPtr, Ns_Conn *conn)
{
    int i, index, opipe[2];
    char *s, *e, *p, **envp;
    Ns_DString *dsPtr;
    Mod *modPtr = cgiPtr->modPtr;

    /*
     * Get a dstring which will be used to setup env variables
     * and the arg list.
     */
     
    dsPtr = CgiDs(cgiPtr);

    /*
     * Setup and merge the environment set.
     */

    cgiPtr->env = Ns_SetCreate(NULL);
    if (cgiPtr->interpEnv != NULL) {
        cgiPtr->env = Ns_SetCopy(cgiPtr->interpEnv);
    } else {
        cgiPtr->env = Ns_SetCreate(NULL);
    }
    if (modPtr->mergeEnv != NULL) {
        Ns_SetMerge(cgiPtr->env, modPtr->mergeEnv);
    }
    if (modPtr->flags & CGI_SYSENV) {
	envp = Ns_CopyEnviron(dsPtr);
	while (*envp != NULL) {
	    s = *envp;
	    e = strchr(s, '=');
	    if (e != NULL) {
		*e = '\0';
        	i = Ns_SetFind(cgiPtr->env, s);
		if (i < 0) {
        	    Ns_SetPut(cgiPtr->env, s, e+1);
		}
		*e = '=';
	    }
	    ++envp;
	}
	Ns_DStringTrunc(dsPtr, 0);
    }

    /*
     * PATH is the only variable copied from the running environment if it
     * isn't already in the server default environment.
     */

    if (Ns_SetFind(cgiPtr->env, "PATH") < 0) {
        s = getenv("PATH");
        if (s != NULL) {
            SetUpdate(cgiPtr->env, "PATH", s);
        }
    }

    /*
     * Set all the CGI specified variables.
     */

    SetUpdate(cgiPtr->env, "SCRIPT_NAME", cgiPtr->name);
    SetUpdate(cgiPtr->env, "SCRIPT_FILENAME", cgiPtr->path);
    if (cgiPtr->pathinfo != NULL && *cgiPtr->pathinfo != '\0') {
    	Ns_DString tmp;
	
        if (Ns_DecodeUrl(dsPtr, cgiPtr->pathinfo) != NULL) {
            SetUpdate(cgiPtr->env, "PATH_INFO", dsPtr->string);
        } else {
            SetUpdate(cgiPtr->env, "PATH_INFO", cgiPtr->pathinfo);
        }
	Ns_DStringTrunc(dsPtr, 0);
	Ns_DStringInit(&tmp);
        Ns_UrlToFile(dsPtr, modPtr->server, cgiPtr->pathinfo);
        if (Ns_DecodeUrl(&tmp, dsPtr->string) != NULL) {
            SetUpdate(cgiPtr->env, "PATH_TRANSLATED", tmp.string);
        } else {
            SetUpdate(cgiPtr->env, "PATH_TRANSLATED", dsPtr->string);
        }
	Ns_DStringFree(&tmp);
	Ns_DStringTrunc(dsPtr, 0);
    } else {
        SetUpdate(cgiPtr->env, "PATH_INFO", "");
    }
    SetUpdate(cgiPtr->env, "GATEWAY_INTERFACE", "CGI/1.1");
    Ns_DStringVarAppend(dsPtr, Ns_InfoServer(), "/", Ns_InfoVersion(), NULL);
    SetUpdate(cgiPtr->env, "SERVER_SOFTWARE", dsPtr->string);
    Ns_DStringTrunc(dsPtr, 0);
    SetUpdate(cgiPtr->env, "SERVER_ROOT", Ns_InfoHomePath());
    SetUpdate(cgiPtr->env, "DOCUMENT_ROOT", Ns_PageRoot(modPtr->server));
    Ns_DStringPrintf(dsPtr, "HTTP/%2.1f", conn->request->version);
    SetUpdate(cgiPtr->env, "SERVER_PROTOCOL", dsPtr->string);
    Ns_DStringTrunc(dsPtr, 0);

    /*
     * Determine SERVER_NAME from the conn location.
     */

    s = Ns_ConnLocation(conn);
    p = NULL;
    if (s != NULL) {
        if (strstr(s, "://") == NULL) {
            Ns_Log(Warning, "nscgi: location does not contain '://'");
            s = NULL;
        } else {
            s = strchr(s, ':');         /* Get past the http */
            if (s != NULL) {
                s += 3;                 /* Get past the // */
                p = strchr(s, ':');     /* Get to the port number */ 
            }
        }
    }
    if (s == NULL) {
        s = Ns_ConnHost(conn);
        SetUpdate(cgiPtr->env, "SERVER_NAME", s);
    } else {
        if (p == NULL) {
            Ns_DStringAppend(dsPtr, s);           /* No port number */
        } else {
            Ns_DStringNAppend(dsPtr, s, (p - s)); /* Port number exists */
        }
        s = Ns_DStringExport(dsPtr);
        SetUpdate(cgiPtr->env, "SERVER_NAME", s);
        ns_free(s);
    }

    /*
     * Determine SERVER_PORT from the conn location.
     */

    s = Ns_ConnLocation(conn);
    if (s != NULL) {
        s = strchr(s, ':');             /* Skip past http. */
        if (s != NULL) {
            ++s;
            s = strchr(s, ':');         /* Skip past hostname. */
            if (s != NULL) {
                ++s;
            }
        }
    }
    if (s == NULL) {
        s = "80";
    }
    SetUpdate(cgiPtr->env, "SERVER_PORT", s);
    SetUpdate(cgiPtr->env, "AUTH_TYPE", "Basic");
    SetUpdate(cgiPtr->env, "REMOTE_USER", conn->authUser);
    s = Ns_ConnPeer(conn);
    if (s != NULL) {
        SetUpdate(cgiPtr->env, "REMOTE_ADDR", s);
        if ((modPtr->flags & CGI_GETHOST)) {
            if (Ns_GetHostByAddr(dsPtr, s)) {
                SetUpdate(cgiPtr->env, "REMOTE_HOST", dsPtr->string);
            }
            Ns_DStringTrunc(dsPtr, 0);
        } else {
            SetUpdate(cgiPtr->env, "REMOTE_HOST", s);
        }
    }
    SetUpdate(cgiPtr->env, "REQUEST_METHOD", conn->request->method);
    SetUpdate(cgiPtr->env, "QUERY_STRING", conn->request->query);

    s = Ns_SetIGet(conn->headers, "Content-Type");
    if (s == NULL) {
        if (STREQ("POST", conn->request->method)) {
            s = "application/x-www-form-urlencoded";
        } else {
            s = "";
        }
    }
    SetUpdate(cgiPtr->env, "CONTENT_TYPE", s);

    if (conn->contentLength <= 0) {
        SetUpdate(cgiPtr->env, "CONTENT_LENGTH", "");
    } else {
        Ns_DStringPrintf(dsPtr, "%u", (unsigned) conn->contentLength);
        SetUpdate(cgiPtr->env, "CONTENT_LENGTH", dsPtr->string);
        Ns_DStringTrunc(dsPtr, 0);
    }

    /*
     * Set the HTTP_ header variables.
     */

    Ns_DStringAppend(dsPtr, "HTTP_");
    for (i = 0; i < Ns_SetSize(conn->headers); ++i) {
        s = Ns_SetKey(conn->headers, i);
        e = Ns_SetValue(conn->headers, i);
        Ns_DStringAppend(dsPtr, s);
        s = dsPtr->string + 5;
        while (*s != '\0') {
            if (*s == '-') {
                *s = '_';
            } else if (islower(UCHAR(*s))) {
                *s = toupper(UCHAR(*s));
            }
            ++s;
        }
        index = Ns_SetFind(cgiPtr->env, dsPtr->string);
        if (index < 0) {
            Ns_SetPut(cgiPtr->env, dsPtr->string, e);
        } else {
	    SetAppend(cgiPtr->env, index, ", ", e);
        }
        Ns_DStringTrunc(dsPtr, 5);
    }

    /*
     * Build up the argument block.
     */

    Ns_DStringTrunc(dsPtr, 0);
    if (cgiPtr->interp != NULL) {
        Ns_DStringAppendArg(dsPtr, cgiPtr->interp);
    }
    if (cgiPtr->path != NULL) {
        Ns_DStringAppendArg(dsPtr, cgiPtr->path);
    }
    s = conn->request->query;
    if (s != NULL) {
	if (strchr(s, '=') == NULL) {
    	    do {
	    	e = strchr(s, '+');
		if (e != NULL) {
		    *e = '\0';
		}
		Ns_UrlDecode(dsPtr, s);
		Ns_DStringNAppend(dsPtr, "", 1);
		if (e != NULL) {
		    *e++ = '+';
		}
		s = e;
	    } while (s != NULL);
	}
	Ns_DStringNAppend(dsPtr, "", 1);
    }

    /*
     * Create the output pipe.
     */
     
    if (ns_pipe(opipe) != 0) {
	Ns_Log(Error, "nscgi: pipe() failed: %s", strerror(errno));
	return NS_ERROR;
    }

    /*
     * Execute the CGI.
     */
     
    cgiPtr->pid = Ns_ExecProcess(cgiPtr->exec, cgiPtr->dir,
	cgiPtr->ifd < 0 ? devNull : cgiPtr->ifd,
	opipe[1], dsPtr->string, cgiPtr->env);
    close(opipe[1]);
    if (cgiPtr->pid < 0) {
    	close(opipe[0]);
	return NS_ERROR;
    }

    cgiPtr->ofd = opipe[0];
    return NS_OK;
}


/*
 *----------------------------------------------------------------------
 *
 * CgiRead -
 *
 *	Read content from pipe into the CGI buffer.
 *
 * Results:
 *	Number of bytes read or -1 on error.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

static int
CgiRead(Cgi *cgiPtr)
{
    int n;
    
    cgiPtr->ptr = cgiPtr->buf;
    do {
    	n = read(cgiPtr->ofd, cgiPtr->buf, sizeof(cgiPtr->buf));
    } while (n < 0 && errno == EINTR);
    if (n > 0) {
	cgiPtr->cnt = n;
    } else if (n < 0) {
	Ns_Log(Error, "nscgi: pipe read() from %s failed: %s",
	       cgiPtr->exec, strerror(errno));
    }
    return n;
}


/*
 *----------------------------------------------------------------------
 *
 * CgiReadLine -
 *
 *	Read and right trim a line from the pipe.
 *
 * Results:
 *	Length of header read or -1 on error.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

static int
CgiReadLine(Cgi *cgiPtr, Ns_DString *dsPtr)
{
    char c;
    int n;

    do {
	while (cgiPtr->cnt > 0) { 
	    c = *cgiPtr->ptr;
	    ++cgiPtr->ptr;
	    --cgiPtr->cnt;
	    if (c == '\n') {
		while (dsPtr->length > 0
		    && isspace(UCHAR(dsPtr->string[dsPtr->length - 1]))) {
		    Ns_DStringTrunc(dsPtr, dsPtr->length-1);
		}
		return dsPtr->length;
	    }
	    Ns_DStringNAppend(dsPtr, &c, 1);
	}
    } while ((n = CgiRead(cgiPtr)) > 0);
    return n;
}


/*
 *----------------------------------------------------------------------
 *
 * CgiCopy
 *
 *	Read and parse headers and then copy output.
 *
 * Results:
 *	AOLserver request result.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

static int
CgiCopy(Cgi *cgiPtr, Ns_Conn *conn)
{
    Ns_DString      ds, redir;
    int             status, last, n, httpstatus;
    char           *value;
    Ns_Set         *hdrs;
    
    /*
     * Skip to copy for nph CGI's.
     */

    if (cgiPtr->flags & CGI_NPH) {
    	goto copy;
    }

    /*
     * Read and parse headers up to the blank line or end of file.
     */
     
    Ns_DStringInit(&ds);
    last = -1;
    httpstatus = 200;
    hdrs = conn->outputheaders;
    while ((n = CgiReadLine(cgiPtr, &ds)) > 0) {
        if (isspace(UCHAR(*ds.string))) {   /* NB: Continued header. */
            if (last == -1) {
		continue;	/* NB: Silently ignore bad header. */
            }
	    SetAppend(hdrs, last, "\n", ds.string);
        } else {
            value = strchr(ds.string, ':');
            if (value == NULL) {
		continue;	/* NB: Silently ignore bad header. */
            }
            *value++ = '\0';
            while (isspace(UCHAR(*value))) {
                ++value;
            }
            if (STRIEQ(ds.string, "status")) {
                httpstatus = atoi(value);
            } else if (STRIEQ(ds.string, "location")) {
                httpstatus = 302;
                if (*value == '/') {
                    Ns_DStringInit(&redir);
                    Ns_DStringVarAppend(&redir, Ns_ConnLocation(conn), value, NULL);
                    last = Ns_SetPut(hdrs, ds.string, redir.string);
                    Ns_DStringFree(&redir);
                } else {
                    last = Ns_SetPut(hdrs, ds.string, value);
                }
            } else {
                last = Ns_SetPut(hdrs, ds.string, value);
            }
        }
        Ns_DStringTrunc(&ds, 0);
    }
    Ns_DStringFree(&ds);
    if (n < 0) {
	return Ns_ConnReturnInternalError(conn);
    }
    
    /*
     * Queue the headers and copy remaining content up to end of file.
     */
     
    Ns_ConnSetRequiredHeaders(conn, NULL, -1);
    Ns_ConnQueueHeaders(conn, httpstatus);
copy:
    do {
    	status = Ns_WriteConn(conn, cgiPtr->ptr, cgiPtr->cnt);
    } while (status == NS_OK && CgiRead(cgiPtr) > 0);

    /*
     * Close connection now so it will not linger on
     * waiting for process exit.
     */
     
    if (status == NS_OK) {
    	status = Ns_ConnClose(conn);
    }
    
    return status;
}


/*
 *----------------------------------------------------------------------
 *
 * NextWord -
 *
 *	Locate next word in CGI mapping.
 *
 * Results:
 *	Pointer to next word.
 *
 * Side effects:
 *	String is modified in place.
 *
 *----------------------------------------------------------------------
 */

static char    *
NextWord(char *s)
{
    while (*s != '\0' && !isspace(UCHAR(*s))) {
        ++s;
    }
    if (*s != '\0') {
        *s++ = '\0';
        while (isspace(UCHAR(*s))) {
            ++s;
        }
    }
    return s;
}


/*
 *----------------------------------------------------------------------
 *
 * CgiRegister -
 *
 *	Register a CGI request mapping.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	May register or re-register a mapping.
 *
 *----------------------------------------------------------------------
 */

static void
CgiRegister(Mod *modPtr, char *map)
{
    char           *method;
    char           *url;
    char           *path;
    Ns_DString      ds1, ds2;
    Map     	   *mapPtr;

    Ns_DStringInit(&ds1);
    Ns_DStringInit(&ds2);
    
    Ns_DStringAppend(&ds1, map);
    method = ds1.string;
    url = NextWord(method);
    if (*method == '\0' || *url == '\0') {
        Ns_Log(Error, "nscgi: invalid mapping: %s", map);
	goto done;
    }
    
    path = NextWord(url);
    if (*path == '\0') {
        path = NULL;
    } else {
    	Ns_NormalizePath(&ds2, path);
    	path = ds2.string;
    	if (!Ns_PathIsAbsolute(path) || access(path, R_OK) != 0) {
            Ns_Log(Error, "nscgi: invalid directory: %s", path);
	    goto done;
	}
    }

    mapPtr = ns_malloc(sizeof(Map));
    mapPtr->modPtr = modPtr;
    mapPtr->url = ns_strdup(url);
    mapPtr->path = ns_strcopy(path);
    Ns_Log(Notice, "nscgi: %s %s%s%s",
	   method, url, path ? " -> " : "", path ? path : "");
    Ns_RegisterRequest(modPtr->server, method, url, 
		       CgiRequest, CgiFreeMap, mapPtr, 0);

done:
    Ns_DStringFree(&ds1);
    Ns_DStringFree(&ds2);
}


/*
 *----------------------------------------------------------------------
 *
 * CgiFreeMap -
 *
 *	Free a request mapping context.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

static void
CgiFreeMap(void *arg)
{
    Map  *mapPtr = (Map *) arg;

    ns_free(mapPtr->url);
    ns_free(mapPtr->path);
    ns_free(mapPtr);
}


/*
 *----------------------------------------------------------------------
 *
 * SetAppend -
 *
 *	Append data to an existing Ns_Set value.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

static void
SetAppend(Ns_Set *set, int index, char *sep, char *value)
{
    Ns_DString ds;

    Ns_DStringInit(&ds);
    Ns_DStringVarAppend(&ds, Ns_SetValue(set, index),
	sep, value, NULL);
    Ns_SetPutValue(set, index, ds.string);
    Ns_DStringFree(&ds);
}


/*
 *----------------------------------------------------------------------
 *
 * SetUpdate -
 *
 *	Update value in an Ns_Set, translating NULL to "".
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

static void
SetUpdate(Ns_Set *set, char *key, char *value)
{
    Ns_SetUpdate(set, key, value ? value : "");
}

Reply via email to