All pastes #548695 Raw Edit

Untitled

public text v1 · immutable
·published 2007-06-07 20:37 UTC
rendered paste body
static void do_cgi(struct http_server_instance *ser, const char *uri, struct variable *vars, struct variable *headers)
{
	int env_cnt, cgi_argc, i, res;
	char **cgi_env, **cgi_argv, *tmp, *path, *prog;
	struct variable *v;

	env_cnt = envcount(environ);
	cgi_env = (char **) alloca((CGI_ENV_LEN + env_cnt + 1) * sizeof(char *));
	i = 0;

	cgi_env[i++] = "GATEWAY_INTERFACE=CGI/1.1";
	cgi_env[i++] = "SERVER_PROTOCOL=HTTP/1.0";
	cgi_env[i++] = "SERVER_SOFTWARE=OOGA";				/* FIXME */

	asprintf(&cgi_env[i++], "SERVER_NAME=%s", "this.is.my.server.name.dom" /* host */);
	asprintf(&cgi_env[i++], "SERVER_PORT=%s", "80" /* port */);
	asprintf(&cgi_env[i++], "REQUEST_METHOD=%s", "POST" /* method_name[method] */);
	asprintf(&cgi_env[i++], "REQUEST_URI=%s", uri);
	asprintf(&cgi_env[i++], "SCRIPT_NAME=%s", "ooga" /* url */);

	if((v = find_var(headers, "Content-Length")))
		asprintf(&cgi_env[i++], "CONTENT_LENGTH=%s", v->value);

	if((v = find_var(headers, "Content-Type")))
		asprintf(&cgi_env[i++], "CONTENT_TYPE=%s", v->value);

	cgi_env[i] = 0;

/*
 * FIXME: I don't like this, not one bit.
 * is there a clean way to count the number of variables without walking the list, or
 * alternatively, is there a way to cleanly keep reallocating larger and larger space for the argv list?
 *
 * Also, I don't like the if(!strlen_zero) statement there, I was going to do %s%c%s
 * and make %c a \0 and the second %s a zero-length string if there was no value, but I think that'd
 * end up stinkier.
 */
	/* now set up argc/argv */
	i = 1;
	if(vars) {
		v = vars;
		while(v) { v = v->next; i++; }
		}

	cgi_argv = (char **) alloca((i + 1) * sizeof(char *));

	cgi_argc = 1;
	if(vars) {
		v = vars;
		while(v) {
			if(!strlen_zero(v->value))
				asprintf(&cgi_argv[cgi_argc++], "%s=%s", v->name, v->value);
			else
				asprintf(&cgi_argv[cgi_argc++], "%s", v->name);
			v = v->next;
			}
		}

	cgi_argv[cgi_argc] = 0;

	/* copy uri to tmp for splitting into path and program */
	path = tmp = alloca(strlen(uri) + 1);
	strcpy(tmp, uri);
	prog = strrchr(tmp, '/');
	if(prog) {
		tmp[prog-tmp] = 0;
		prog++;		/* prog now points to the program */
		}
	else {			/* no path?? */
		path = "/bin";
		prog = tmp;
		}

	chdir(path);

	cgi_argv[0] = prog;

	i = 3;
	while((res = access(cgi_argv[0], X_OK) != 0) || ((res == EAGAIN) && (i > 0))) {
		fprintf(stderr, "access returned %i, retries=%i\n", res, i);
		usleep(250);
		--i;
		}

	if(res == 0) {
		execve(cgi_argv[0], cgi_argv, cgi_env);		/* doesn't return */
		fprintf(stderr, "wtf, execve returned? errno=%i\n", errno);
		_exit(255);
		}
	else {			/* can't execute */
		fprintf(stderr, "can't execute %s: %i (%s)\n", cgi_argv[0], errno, strerror(errno));
		_exit(255);
		}
}


static char *cgi_parent_process(FILE *http, pid_t child, int infd, int outfd, int *status, char **title, struct variable *headers)
{
	int res, wres, first_read = 1, clen;
	struct pollfd pfd[2];
	size_t size = 0;
	char inbuf[1024], outbuf[1024], *result = NULL;

// TODO: add signal handlers
//	signal(SIGCHLD, cgi_child);
//	signal(SIGPIPE, SIG_IGN);		/* NO! no signal! */

	pfd[0].fd = infd;
	pfd[0].events = POLLIN;
	pfd[0].revents = 0;

	if(outfd > 0) {
		pfd[1].fd = outfd;
		pfd[1].events = POLLOUT;
		pfd[1].revents = 0;
		}

	/* now marshall data back and forth as needed */
	while(poll(pfd, (outfd > 0) ? 2 : 1, -1) != -1) {

		if(pfd[0].revents & POLLIN) {		/* CGI has data to read */
			res = read(infd, inbuf, sizeof(inbuf));
			if(res == 0)
				break;			/* no data */
			else if(res < 0)
				goto cgi_500;		/* error */

			if(first_read) {
				first_read = 0;
				*status = 200;
				fprintf(http, "HTTP/1.0 200 OK\r\nServer: OOGA\r\nPragma: no-cache\r\nConnection: close\r\n\r\n");
//				signal(SIGCHLD, SIG_IGN);
				}
			else {
				wres = fwrite(inbuf, res, 1, http);
				}

			size += res;
			if(pfd[0].revents & POLLHUP)	/* disconnect? */
				break;
			}

		else if(outfd > 0 && pfd[1].revents & POLLOUT) {	/* write POST data to cgi */
			res = read(fileno(http), outbuf, sizeof(outbuf));
			if(res < 1) {
				if(res == 0) {				/* EOF? */
					close(outfd);
					outfd = 0;
					}
				else					/* error */
					goto cgi_500;
				}
	
			wres = write(outfd, outbuf, res);
			if(wres != res)
				goto cgi_500;
			}

		else if(pfd[0].revents & POLLHUP)	/* CGI disconnection */
			break;
		else {					/* other (unhandled) revent */
cgi_500:
			if(first_read) {
				result = http_error(500, "Internal Server Error", NULL, "Looks like the CGI crashed.");
				break;
				}
			else {
				fprintf(http, "\n\nLooks like the CGI crashed.\n\n");
				break;
				}
			}
		}	/* while(poll) */

/*	buffer_flush(buffer_1); */

/* if we're using the TCP_CORK socket option, we've got to remove it to ensure the rest of the message is written out */
#ifdef TCP_CORK
{
	int zero = 0;
	setsockopt(1, IPPROTO_TCP, TCP_CORK, &zero, sizeof(zero));
}
#endif
	return result;
}


static char *cgi_callback(struct http_server_instance *ser, const char *uri, struct variable *vars, struct variable *headers, int *status, char **title, int *contentlength)
{
	char *result = NULL;
	pid_t pid;	/* child PID */
	int fd[2];	/* http to CGI pipe pair, http writes to [1] */
	int df[2];	/* CGI to http pipe pair, cgi writes to [1] */

	if(pipe(fd) || pipe(df)) {
		return http_error(500, "Internal Server Error", NULL, "Cannot allocate pipe fds.");
		}

	pid = vfork();
	if(pid > 0) {			/* this is the parent (server) process */
		close(fd[0]);
		close(df[1]);
		result = cgi_parent_process(ser->f, pid, df[0], fd[1], status, title, headers);
		}

	else if(pid == 0) {		/* this is the child process that runs the CGI */
		close(df[0]);
		close(fd[1]);
//		alarm(CGI_TIMEOUT);	/* set up an alarm in case the CGI hangs */
		do_cgi(ser, uri, vars, headers);	/* and actually run the CGI program (doesn't return on success) */
		}

	else {				/* fork error */
		result = http_error(500, "Internal Server Error", NULL, "Cannot vfork.");
		}

	return result;
}