<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv=Content-Type content="text/html; charset=utf8">
<title>/usr/web/sources/contrib/rsc/cgi.c - Plan 9 from Bell Labs</title>
<!-- THIS FILE IS AUTOMATICALLY GENERATED. -->
<!-- EDIT sources.tr INSTEAD. -->
</meta>
</head>
<body>
<p style="margin-top: 0; margin-bottom: 0.17in"></p>
<p style="line-height: 1.2em; margin-left: 1.00in; text-indent: 0.00in; margin-right: 1.00in; margin-top: 0; margin-bottom: 0; text-align: center;">
<span style="font-size: 10pt"><a href="/plan9/">Plan 9 from Bell Labs</a>&rsquo;s /usr/web/sources/contrib/rsc/cgi.c</span></p>
<p style="margin-top: 0; margin-bottom: 0.17in"></p>
<p style="margin-top: 0; margin-bottom: 0.17in"></p>
<center><font size=-1>
Copyright © 2009 Alcatel-Lucent.<br />
Distributed under the
<a href="/plan9/license.html">Lucent Public License version 1.02</a>.
<br />
<a href="/plan9/download.html">Download the Plan 9 distribution.</a>
</font>
</center>
<p style="margin-top: 0; margin-bottom: 0.17in"></p>
<table width="100%" cellspacing=0 border=0><tr><td align="center">
<table cellspacing=0 cellpadding=5 bgcolor="#eeeeff"><tr><td align="left">
<pre>
<!-- END HEADER -->
/*
* Invoke the named program using the conventional CGI interface.
*
* hget http://cgi-spec.golux.com/draft-coar-cgi-v11-03.txt
*/

#include &lt;u.h&gt;
#include &lt;libc.h&gt;
#include &lt;ctype.h&gt;
#include "httpd.h"
#include "httpsrv.h"

typedef struct Hline Hline;
struct Hline
{
       Hline *next;
       char *name;
       char *value;
};

typedef struct Header Header;
struct Header
{
       Hline *first;
       Hline *last;
};

typedef struct Hcgi Hcgi;
struct Hcgi
{
       HConnect *c;
       int argc;
       char **argv;
       char *execname;
       char *pathinfo;
       char *pathtranslated;
       char *scriptname;
       Header hdrin;
       int outputparsing;
};

char Eexec[] = "magic/cgi: exec failed";

void
die(HConnect *c, char *fmt)
{
       if(fmt)
               hfail(c, HInternal);
       postnote(PNGROUP, getpid(), "kill");
       _exits("die");
}

void
addheader(HConnect *c, Header *h, char *n, char *v)
{
       Hline *l;

       l = halloc(c, sizeof(Hline));
       l-&gt;name = n;
       l-&gt;value = v;
       l-&gt;next = nil;
       if(h-&gt;last)
               h-&gt;last-&gt;next = l;
       else
               h-&gt;first = l;
       h-&gt;last = l;
}

void
parseheaders(HConnect *c, Header *h, char *os, int len)
{
       char *s, *f, *v, *p, *e;

       s = halloc(c, len+1);
       memmove(s, os, len);
       s[len] = '\0';
       for(p=s; p &amp;&amp; *p; ){
               f = p;
               while((p = strchr(p, '\n')) != nil){
                       if(p &gt; s &amp;&amp; p[-1] == '\r')
                               p[-1] = ' ';
                       if(p[1] != ' ' &amp;&amp; p[1] != '\t'){
                               *p++ = '\0';
                               break;
                       }
                       *p++ = ' ';
               }
               v = strchr(f, ':');
               if(v == nil)
                       continue;
               *v++ = '\0';
               while(*v == ' ' || *v == '\t')
                       v++;
               e = v+strlen(v);
               while(e &gt; v &amp;&amp; (e[-1]==' ' || e[-1]=='\t'))
                       *--e = '\0';
               addheader(c, h, f, v);
       }
}

char*
findheader(Header *h, char *n, char *def)
{
       Hline *l;

       for(l=h-&gt;first; l; l=l-&gt;next)
               if(cistrcmp(n, l-&gt;name) == 0)
                       return l-&gt;value;
       return def;
}

void
mkargv(Hcgi *hcgi)
{
       int n, nn;
       char *s, *t, **argv, *meth;

       nn = 5;
       if(hcgi-&gt;c-&gt;req.search != nil)
               nn += strlen(hcgi-&gt;c-&gt;req.search);

       /* can't possibly have more args than chars */
       argv = halloc(hcgi-&gt;c, nn*sizeof(char*));

       n = 0;
       argv[n++] = hcgi-&gt;scriptname;

       /*
        * only pass on cmd line if GET or HEAD and no = in line
        */
       meth = hcgi-&gt;c-&gt;req.meth;
       if((strcmp(meth, "GET")==0 || strcmp(meth, "HEAD") == 0)
       &amp;&amp; hcgi-&gt;c-&gt;req.search
       &amp;&amp; strchr(hcgi-&gt;c-&gt;req.search, '=') == nil){
               s = hstrdup(hcgi-&gt;c, hcgi-&gt;c-&gt;req.search);
               for(t=s; *t; t++)
                       if(*t == '+')
                               *t = ' ';
               s = hurlunesc(hcgi-&gt;c, s);
               n += getfields(s, argv+n, nn-n, 1, " \t\r\n\v");
       }
       hcgi-&gt;argc = n;
       hcgi-&gt;argv = argv;
}

void
mkenv(Hcgi *hcgi)
{
       char *s, *t;
       Hline *l;
       Header *h;
       HSPriv *hp;

       rfork(RFCENVG);
       h = &amp;hcgi-&gt;hdrin;

       putenv("AUTH_TYPE", "");        /* BUG */
       putenv("CONTENT_LENGTH", findheader(h, "content-length", ""));
       putenv("CONTENT_TYPE", findheader(h, "content-type", ""));
       putenv("GATEWAY_INTERFACE", "CGI/1.1");

       /* http header Foo: Bar becomes HTTP_FOO=Bar */
       for(l=hcgi-&gt;hdrin.first; l; l=l-&gt;next){
               s = halloc(hcgi-&gt;c, 5+strlen(l-&gt;name)+1);
               strcpy(s, "HTTP_");
               strcat(s, l-&gt;name);
               for(t=s; *t; t++)
                       if(islower(*t))
                               *t += 'A' - 'a';
                       else if(*t == '-')
                               *t = '_';
               putenv(s, l-&gt;value);
       }

       putenv("PATH_INFO", hcgi-&gt;pathinfo);
       putenv("PATH_TRANSLATED", hcgi-&gt;pathtranslated);
       if(hcgi-&gt;c-&gt;req.search)
               putenv("QUERY_STRING", hcgi-&gt;c-&gt;req.search);
       hp = hcgi-&gt;c-&gt;private;
       putenv("REMOTE_ADDR", hp-&gt;remotesys);
       putenv("REQUEST_METHOD", hcgi-&gt;c-&gt;req.meth);
       putenv("SCRIPT_NAME", hcgi-&gt;scriptname);

       /*
        * not clear which is right:
        * use urihost, or Host: header, or domain
        */
       if(hcgi-&gt;c-&gt;req.urihost)
               putenv("SERVER_NAME", hcgi-&gt;c-&gt;req.urihost);
       else
               putenv("SERVER_NAME", findheader(h, "host", hmydomain));

       putenv("SERVER_PORT", "80");            /* BUG */
       putenv("SERVER_PROTOCOL", hversion);
       putenv("SERVER_SOFTWARE", "plan9httpd");
}

void
mkpath(Hcgi *hcgi)
{
       char *p;

       hcgi-&gt;scriptname = hstrdup(hcgi-&gt;c, hcgi-&gt;c-&gt;req.uri);      /* actually just the path */
       if(hcgi-&gt;scriptname[0] != '/')
               die(hcgi-&gt;c, "internal error");
       p = strchr(hcgi-&gt;scriptname+1, '/');
       if(p == nil)
               hcgi-&gt;pathinfo = "";
       else{
               hcgi-&gt;pathinfo = hstrdup(hcgi-&gt;c, p);
               *p = '\0';
       }

       /*
        * since the script name contains no slashes except the beginning one,
        * it's safe.  at worst it's "/.." or "/.", which will fail when we exec.
        */
       hcgi-&gt;execname = smprint("/bin/ip/httpd/cgi-bin%s", hcgi-&gt;scriptname);
       if(hcgi-&gt;execname == nil)
               die(hcgi-&gt;c, "out of memory");

       /* BUG: should deal with pathtranslated */
       hcgi-&gt;pathtranslated = "";
}

void
printheader(Hio *h, Header *hdr)
{
       Hline *l;

       for(l=hdr-&gt;first; l; l=l-&gt;next)
               hprint(h, "%s: %s\r\n", l-&gt;name, l-&gt;value);
}

void
parseoutput(Hcgi *hcgi, int fd)
{
       char *s;
       HConnect tmp;
       Header h;
       Hio *hout;
       int redirect;

       /* fake a connection using fd and read headers into buffer */
       memset(&amp;tmp, 0, sizeof tmp);
       tmp.hstop = tmp.header;
       hinit(&amp;tmp.hin, fd, Hread);
       if(hgethead(&amp;tmp, 1) &lt; 0)
               die(hcgi-&gt;c, nil);
       parseheaders(hcgi-&gt;c, &amp;h, (char*)tmp.header, tmp.hstop - tmp.header);

       hout = &amp;hcgi-&gt;c-&gt;hout;
       /* must have location or status */
       redirect = 0;
       if(findheader(&amp;h, "location", nil)){
               redirect = 1;
               hprint(hout, "%s 302 Redirect\r\n", hversion);
       }else if(s = findheader(&amp;h, "status", nil))
               hprint(hout, "%s %s\r\n", hversion, s);
       else
               hprint(hout, "%s 200 OK\r\n", hversion);

       printheader(hout, &amp;h);
       /*
        * maybe there are other fields to add if we're not redirecting?
        */
       USED(redirect);

       hprint(hout, "\r\n");

       for(; s = hreadbuf(&amp;tmp.hin, tmp.hin.pos); tmp.hin.pos = tmp.hin.stop)
               hwrite(hout, s, hbuflen(&amp;tmp.hin, s));

       hflush(hout);
}

void
main(int argc, char **argv)
{
       char *s;
       int i, infd, outfd, logfd, len, p[2], pid;
       Hcgi hcgi;
       HConnect *c;
       Hio *hin, *hout;
       Waitmsg *w;

       rfork(RFNOTEG);

       c = init(argc, argv);
       hout = &amp;c-&gt;hout;

       if(hparseheaders(c, 15*60*1000) &lt; 0)
               exits("failed");

       /* what do these do? */
       if(c-&gt;head.expectother){
               hfail(c, HExpectFail, nil);
               exits("failed");
       }
       if(c-&gt;head.expectcont){
               hprint(hout, "100 Continue\r\n");
               hprint(hout, "\r\n");
               hflush(hout);
       }

       memset(&amp;hcgi, 0, sizeof hcgi);
       hcgi.c = c;
       parseheaders(c, &amp;hcgi.hdrin, (char*)c-&gt;header, c-&gt;hstop-c-&gt;header);
       mkpath(&amp;hcgi);
       hcgi.outputparsing = 1;
       if(strncmp(hcgi.scriptname, "/nph-", 5) == 0)
               hcgi.outputparsing = 0;
       mkargv(&amp;hcgi);

       /*
        * Set up input.  Pipe posted data, or just /dev/null.
        */
       if(strcmp(c-&gt;req.meth, "POST") == 0){
               if(pipe(p) &lt; 0)
                       die(c, "pipe: %r");
               hin = hbodypush(&amp;c-&gt;hin, c-&gt;head.contlen, c-&gt;head.transenc);
               if(hin == nil)
                       die(c, "bad transfer encoding");

               switch(rfork(RFPROC|RFFDG|RFNOWAIT)){
               case -1:
                       die(c, "fork: %r");

               case 0:
                       close(p[0]);
                       for(; s = hreadbuf(hin, hin-&gt;pos); hin-&gt;pos = hin-&gt;stop){
                               len = hbuflen(hin, s);
                               if(write(p[1], s, len) != len)
                                       die(c, nil);
                       }
                       _exits(nil);

               default:
                       close(p[1]);
                       close(hin-&gt;fd);
                       infd = p[0];
                       break;
               }
       }else
               infd = open("/dev/null", OREAD);

       /*
        * Set up stderr.
        */
       logfd = open("/sys/log/httpd/cgi", OWRITE);
       if(logfd &lt; 0)
               logfd = open("/dev/null", OWRITE);
       seek(logfd, 0, 2);

       /*
        * Set up output pipe, if any.
        */
       if(hcgi.outputparsing){
               if(pipe(p) &lt; 0)
                       die(c, "pipe: %r");
               outfd = p[1];
       }else
               outfd = c-&gt;hout.fd;

       /*
        * Fork the process
        */
       switch(pid = fork()){
       case -1:
               die(c, "fork: %r");

       case 0:
               if(hcgi.outputparsing)
                       close(p[0]);
               if(infd != 0){
                       dup(infd, 0);
                       close(infd);
               }
               if(outfd != 1){
                       dup(outfd, 1);
                       close(outfd);
               }
               dup(logfd, 2);
               close(logfd);
               /*
                * httpd is a little sloppy.
                */
               for(i=3; i&lt;20; i++)
                       close(i);
               mkenv(&amp;hcgi);

               /*
                * Not in the spec, but everyone expects the chdir to the cgi-bin dir.
                */
               chdir("/bin/ip/httpd/cgi-bin");

               exec(hcgi.execname, hcgi.argv);
               _exits(Eexec);
       }

       /*
        * If we're parsing the output, do that.
        * Otherwise just wait to see if the exec fails.
        */
       if(hcgi.outputparsing){
               close(p[1]);
               parseoutput(&amp;hcgi, p[0]);
       }else{
               if((w = wait()) == nil || w-&gt;pid != pid)
                       die(c, "wait failed");
               if(strstr(w-&gt;msg, Eexec))
                       die(c, "exec failed");
       }
}
<!-- BEGIN TAIL -->
</pre>
</td></tr></table>
</td></tr></table>
<p style="margin-top: 0; margin-bottom: 0.17in"></p>
<p style="line-height: 1.2em; margin-left: 1.00in; text-indent: 0.00in; margin-right: 1.00in; margin-top: 0; margin-bottom: 0; text-align: center;">
<span style="font-size: 10pt"></span></p>
<p style="margin-top: 0; margin-bottom: 0.50in"></p>
<p style="margin-top: 0; margin-bottom: 0.33in"></p>
<center><table border="0"><tr>
<td valign="middle"><a href="http://www.alcatel-lucent.com/"><img border="0" src="/plan9/img/logo_ft.gif" alt="Bell Labs" />
</a></td>
<td valign="middle"><a href="http://www.opensource.org"><img border="0" alt="OSI certified" src="/plan9/img/osi-certified-60x50.gif" />
</a></td>
<td><img style="padding-right: 45px;" alt="Powered by Plan 9" src="/plan9/img/power36.gif" />
</td>
</tr></table></center>
<p style="margin-top: 0; margin-bottom: 0.17in"></p>
<center>
<span style="font-size: 10pt">(<a href="/plan9/">Return to Plan 9 Home Page</a>)</span>
</center>
<p style="margin-top: 0; margin-bottom: 0.17in"></p>
<center><font size=-1>
<span style="font-size: 10pt"><a href="http://www.lucent.com/copyright.html">Copyright</a></span>
<span style="font-size: 10pt">© 2009 Alcatel-Lucent.</span>
<span style="font-size: 10pt">All Rights Reserved.</span>
<br />
<span style="font-size: 10pt">Comments to</span>
<span style="font-size: 10pt"><a href="mailto:[email protected]">[email protected]</a>.</span>
</font></center>
</body>
</html>