summaryrefslogtreecommitdiffstats
path: root/cpukit/shttpd/io_cgi.c
blob: f7efb6423fc249e5a4a5dbdb077167abf8f80fa8 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
/*
 * Copyright (c) 2004-2005 Sergey Lyubka <valenok@gmail.com>
 * All rights reserved
 *
 * "THE BEER-WARE LICENSE" (Revision 42):
 * Sergey Lyubka wrote this file.  As long as you retain this notice you
 * can do whatever you want with this stuff. If we meet some day, and you think
 * this stuff is worth it, you can buy me a beer in return.
 */

#include "defs.h"

static int
write_cgi(struct stream *stream, const void *buf, size_t len)
{
	assert(stream->chan.sock != -1);
	assert(stream->flags & FLAG_W);

	return (send(stream->chan.sock, buf, len, 0));
}

static int
read_cgi(struct stream *stream, void *buf, size_t len)
{
	struct headers	parsed;
	char		status[4];
	int		n;

	assert(stream->chan.sock != -1);
	assert(stream->flags & FLAG_R);

	stream->flags &= ~FLAG_DONT_CLOSE;

	n = recv(stream->chan.sock, buf, len, 0);

	if (stream->flags & FLAG_HEADERS_PARSED)
		return (n);

	if (n <= 0 && ERRNO != EWOULDBLOCK) {
		send_server_error(stream->conn, 500, "Error running CGI");
		return (n);
	}

	/*
	 * CGI script may output Status: and Location: headers, which
	 * may alter the status code. Buffer in headers, parse
	 * them, send correct status code and then forward all data
	 * from CGI script back to the remote end.
	 * Reply line was alredy appended to the IO buffer in
	 * decide_what_to_do(), with blank status code.
	 */

	stream->flags |= FLAG_DONT_CLOSE;
	io_inc_head(&stream->io, n);

	stream->headers_len = get_headers_len(stream->io.buf, stream->io.head);
	if (stream->headers_len < 0) {
		stream->flags &= ~FLAG_DONT_CLOSE;
		send_server_error(stream->conn, 500, "Bad headers sent");
		elog(E_LOG, stream->conn, "CGI script sent invalid headers: "
		    "[%.*s]", stream->io.head - CGI_REPLY_LEN,
		    stream->io.buf + CGI_REPLY_LEN);
		return (0);
	}

	/*
	 * If we did not received full headers yet, we must not send any
	 * data read from the CGI back to the client. Suspend sending by
	 * setting tail = head, which tells that there is no data in IO buffer
	 */

	if (stream->headers_len == 0) {
		stream->io.tail = stream->io.head;
		return (0);
	}

	/* Received all headers. Set status code for the connection. */
	(void) memset(&parsed, 0, sizeof(parsed));
	parse_headers(stream->io.buf, stream->headers_len, &parsed);
	stream->content_len = parsed.cl.v_big_int;
	stream->conn->status = (int) parsed.status.v_big_int;

	/* If script outputs 'Location:' header, set status code to 302 */
	if (parsed.location.v_vec.len > 0)
		stream->conn->status = 302;

	/*
	 * If script did not output neither 'Location:' nor 'Status' headers,
	 * set the default status code 200, which means 'success'.
	 */
	if (stream->conn->status == 0)
		stream->conn->status = 200;

	/* Append the status line to the beginning of the output */
	(void) my_snprintf(status, sizeof(status), "%3d", stream->conn->status);
	(void) memcpy(stream->io.buf + 9, status, 3);
	DBG(("read_cgi: content len %lu status %s",
	    stream->content_len, status));

	/* Next time, pass output directly back to the client */
	assert((big_int_t) stream->headers_len <= stream->io.total);
	stream->io.total -= stream->headers_len;
	stream->io.tail = 0;
	stream->flags |= FLAG_HEADERS_PARSED;

	/* Return 0 because we've already shifted the head */
	return (0);
}

static void
close_cgi(struct stream *stream)
{
	assert(stream->chan.sock != -1);
	(void) closesocket(stream->chan.sock);
}

const struct io_class	io_cgi =  {
	"cgi",
	read_cgi,
	write_cgi,
	close_cgi
};