static inline int cgi_interpose_output()

in netutils/thttpd/thttpd_cgi.c [443:711]


static inline int cgi_interpose_output(struct cgi_conn_s *cc)
{
  ssize_t nbytes_read;
  char *br = NULL;
  int status;
  const char *title;
  char *cp;

  /* Make sure the connection is in blocking mode.  It should already be
   * blocking, but we might as well be sure.
   */

  httpd_clear_ndelay(cc->connfd);

  /* Loop while there are things we can do without waiting for more input */

  ninfo("state: %d\n", cc->outbuf.state);
  switch (cc->outbuf.state)
    {
      case CGI_OUTBUFFER_READHEADER:
        {
          /* Slurp in all headers as they become available from the client. */

          do
            {
              /* Read until we successfully read data or until an error
               * occurs. EAGAIN is not an error, but it is still cause to
               * return.
               */

              nbytes_read = read(cc->rdfd, cc->inbuf.buffer,
                                 CONFIG_THTTPD_CGIINBUFFERSIZE);
              ninfo("Read %d bytes from fd %d\n", nbytes_read, cc->rdfd);

              if (nbytes_read < 0)
                {
                  if (errno != EINTR)
                    {
                      if (errno != EAGAIN)
                        {
                          nerr("ERROR: read: %d\n", errno);
                        }

                      return 1;
                    }
                }
              else
                {
                  cgi_dumpbuffer("Received from CGI:", cc->inbuf.buffer,
                                 nbytes_read);
                }
            }
          while (nbytes_read < 0);

          /* Check for end-of-file */

          if (nbytes_read <= 0)
            {
              ninfo("End-of-file\n");
              br               = &(cc->outbuf.buffer[cc->outbuf.len]);
              cc->outbuf.state = CGI_OUTBUFFER_HEADERREAD;
            }
          else
            {
              /* Accumulate more header data */

              httpd_realloc_str(&cc->outbuf.buffer, &cc->outbuf.size,
                                cc->outbuf.len + nbytes_read);
              memcpy(&(cc->outbuf.buffer[cc->outbuf.len]), cc->inbuf.buffer,
                       nbytes_read);
              cc->outbuf.len                   += nbytes_read;
              cc->outbuf.buffer[cc->outbuf.len] = '\0';
              ninfo("Header bytes accumulated: %d\n", cc->outbuf.len);

              /* Check for end of header */

              if ((br = strstr(cc->outbuf.buffer, "\r\n\r\n")) != NULL ||
                  (br = strstr(cc->outbuf.buffer, "\012\012")) != NULL)
                {
                  ninfo("End-of-header\n");
                  cc->outbuf.state = CGI_OUTBUFFER_HEADERREAD;
                }
              else
                {
                  /* All of the headers have not yet been read ... Return.
                   * We will be called again when more data is available
                   * in the pipe connected to the CGI task.
                   */

                  return 0;
                }
            }
        }

        /* Otherwise, fall through and parse status in the HTTP headers */

      case CGI_OUTBUFFER_HEADERREAD:
        {
          /* If there were no headers, bail. */

          if (cc->outbuf.buffer[0] == '\0')
            {
              cc->outbuf.state = CGI_OUTBUFFER_DONE;
              return 1;
            }

          /* Figure out the status.  Look for a Status: or Location: header;
           * else if there's an HTTP header line, get it from there; else
           * default to 200.
           */

          status = 200;
          if (strncmp(cc->outbuf.buffer, "HTTP/", 5) == 0)
            {
              cp     = cc->outbuf.buffer;
              cp    += strcspn(cp, " \t");
              status = atoi(cp);
            }

          if ((cp = strstr(cc->outbuf.buffer, "Status:")) != NULL &&
              cp < br && (cp == cc->outbuf.buffer || *(cp - 1) == '\012'))
            {
              cp    += 7;
              cp    += strspn(cp, " \t");
              status = atoi(cp);
            }

          if ((cp = strstr(cc->outbuf.buffer, "Location:")) != NULL &&
              cp < br && (cp == cc->outbuf.buffer || *(cp - 1) == '\012'))
            {
              status = 302;
            }

          /* Write the status line. */

          ninfo("Status: %d\n", status);
          switch (status)
            {
            case 200:
              title = ok200title;
              break;

            case 302:
              title = err302title;
              break;

            case 304:
              title = err304title;
              break;

            case 400:
              BADREQUEST("status");
              title = httpd_err400title;
              break;

#ifdef CONFIG_THTTPD_AUTH_FILE
            case 401:
              title = err401title;
              break;
#endif

            case 403:
              title = err403title;
              break;

            case 404:
              title = err404title;
             break;

            case 408:
              title = httpd_err408title;
              break;

            case 500:
              INTERNALERROR("status");
              title = err500title;
              break;

            case 501:
              NOTIMPLEMENTED("status");
              title = err501title;
              break;

            case 503:
              title = httpd_err503title;
              break;

            default:
              title = "Something";
              break;
            }

          snprintf(cc->inbuf.buffer, CONFIG_THTTPD_CGIINBUFFERSIZE,
                   "HTTP/1.0 %d %s\r\n", status, title);
          httpd_write(cc->connfd, cc->inbuf.buffer,
                      strlen(cc->inbuf.buffer));

          /* Write the saved cc->outbuf.buffer to the client. */

          httpd_write(cc->connfd, cc->outbuf.buffer, cc->outbuf.len);
        }

        /* Then set up to read the data following the header from the CGI
         * program and pass it back to the client. We return now; we will
         * be called again when data is available on the pipe.
         */

        cc->outbuf.state = CGI_OUTBUFFER_READDATA;
        return 0;

      case CGI_OUTBUFFER_READDATA:
        {
          /* Read data from the pipe. */

          do
            {
              /* Read until we successfully read data or until an error
               * occurs. EAGAIN is not an error, but it is still cause
               * to return.
               */

              nbytes_read = read(cc->rdfd, cc->inbuf.buffer,
                                 CONFIG_THTTPD_CGIINBUFFERSIZE);
              ninfo("Read %d bytes from fd %d\n", nbytes_read, cc->rdfd);

              if (nbytes_read < 0)
                {
                  if (errno != EINTR)
                    {
                      if (errno != EAGAIN)
                        {
                          nerr("ERROR: read: %d\n", errno);
                        }

                      return 1;
                    }
                }
              else
                {
                  cgi_dumpbuffer("Received from CGI:", cc->inbuf.buffer,
                                 nbytes_read);
                }
            }
          while (nbytes_read < 0);

          /* Check for end of file */

          if (nbytes_read == 0)
            {
              ninfo("End-of-file\n");
              cc->outbuf.state = CGI_OUTBUFFER_DONE;
              return 1;
            }
          else
            {
               /* Forward the data from the CGI program to the client */

             httpd_write(cc->connfd, cc->inbuf.buffer, nbytes_read);
            }
        }
        break;

      case CGI_OUTBUFFER_DONE:
      default:
        return 1;
    }

  return 0;
}