1 | /* |
---|
2 | * Copyright (c) 2004-2005 Sergey Lyubka <valenok@gmail.com> |
---|
3 | * All rights reserved |
---|
4 | * |
---|
5 | * "THE BEER-WARE LICENSE" (Revision 42): |
---|
6 | * Sergey Lyubka wrote this file. As long as you retain this notice you |
---|
7 | * can do whatever you want with this stuff. If we meet some day, and you think |
---|
8 | * this stuff is worth it, you can buy me a beer in return. |
---|
9 | */ |
---|
10 | |
---|
11 | #include "defs.h" |
---|
12 | |
---|
13 | #include <unistd.h> |
---|
14 | |
---|
15 | static int |
---|
16 | write_cgi(struct stream *stream, const void *buf, size_t len) |
---|
17 | { |
---|
18 | assert(stream->chan.sock != -1); |
---|
19 | assert(stream->flags & FLAG_W); |
---|
20 | |
---|
21 | return (send(stream->chan.sock, buf, len, 0)); |
---|
22 | } |
---|
23 | |
---|
24 | static int |
---|
25 | read_cgi(struct stream *stream, void *buf, size_t len) |
---|
26 | { |
---|
27 | struct headers parsed; |
---|
28 | char status[4]; |
---|
29 | int n; |
---|
30 | |
---|
31 | assert(stream->chan.sock != -1); |
---|
32 | assert(stream->flags & FLAG_R); |
---|
33 | |
---|
34 | stream->flags &= ~FLAG_DONT_CLOSE; |
---|
35 | |
---|
36 | n = recv(stream->chan.sock, buf, len, 0); |
---|
37 | |
---|
38 | if (stream->flags & FLAG_HEADERS_PARSED) |
---|
39 | return (n); |
---|
40 | |
---|
41 | if (n <= 0 && ERRNO != EWOULDBLOCK) { |
---|
42 | send_server_error(stream->conn, 500, "Error running CGI"); |
---|
43 | return (n); |
---|
44 | } |
---|
45 | |
---|
46 | /* |
---|
47 | * CGI script may output Status: and Location: headers, which |
---|
48 | * may alter the status code. Buffer in headers, parse |
---|
49 | * them, send correct status code and then forward all data |
---|
50 | * from CGI script back to the remote end. |
---|
51 | * Reply line was alredy appended to the IO buffer in |
---|
52 | * decide_what_to_do(), with blank status code. |
---|
53 | */ |
---|
54 | |
---|
55 | stream->flags |= FLAG_DONT_CLOSE; |
---|
56 | io_inc_head(&stream->io, n); |
---|
57 | |
---|
58 | stream->headers_len = get_headers_len(stream->io.buf, stream->io.head); |
---|
59 | if (stream->headers_len < 0) { |
---|
60 | stream->flags &= ~FLAG_DONT_CLOSE; |
---|
61 | send_server_error(stream->conn, 500, "Bad headers sent"); |
---|
62 | elog(E_LOG, stream->conn, "CGI script sent invalid headers: " |
---|
63 | "[%.*s]", stream->io.head - CGI_REPLY_LEN, |
---|
64 | stream->io.buf + CGI_REPLY_LEN); |
---|
65 | return (0); |
---|
66 | } |
---|
67 | |
---|
68 | /* |
---|
69 | * If we did not received full headers yet, we must not send any |
---|
70 | * data read from the CGI back to the client. Suspend sending by |
---|
71 | * setting tail = head, which tells that there is no data in IO buffer |
---|
72 | */ |
---|
73 | |
---|
74 | if (stream->headers_len == 0) { |
---|
75 | stream->io.tail = stream->io.head; |
---|
76 | return (0); |
---|
77 | } |
---|
78 | |
---|
79 | /* Received all headers. Set status code for the connection. */ |
---|
80 | (void) memset(&parsed, 0, sizeof(parsed)); |
---|
81 | parse_headers(stream->io.buf, stream->headers_len, &parsed); |
---|
82 | stream->content_len = parsed.cl.v_big_int; |
---|
83 | stream->conn->status = (int) parsed.status.v_big_int; |
---|
84 | |
---|
85 | /* If script outputs 'Location:' header, set status code to 302 */ |
---|
86 | if (parsed.location.v_vec.len > 0) |
---|
87 | stream->conn->status = 302; |
---|
88 | |
---|
89 | /* |
---|
90 | * If script did not output neither 'Location:' nor 'Status' headers, |
---|
91 | * set the default status code 200, which means 'success'. |
---|
92 | */ |
---|
93 | if (stream->conn->status == 0) |
---|
94 | stream->conn->status = 200; |
---|
95 | |
---|
96 | /* Append the status line to the beginning of the output */ |
---|
97 | (void) my_snprintf(status, sizeof(status), "%3d", stream->conn->status); |
---|
98 | (void) memcpy(stream->io.buf + 9, status, 3); |
---|
99 | DBG(("read_cgi: content len %lu status %s", |
---|
100 | stream->content_len, status)); |
---|
101 | |
---|
102 | /* Next time, pass output directly back to the client */ |
---|
103 | assert((big_int_t) stream->headers_len <= stream->io.total); |
---|
104 | stream->io.total -= stream->headers_len; |
---|
105 | stream->io.tail = 0; |
---|
106 | stream->flags |= FLAG_HEADERS_PARSED; |
---|
107 | |
---|
108 | /* Return 0 because we've already shifted the head */ |
---|
109 | return (0); |
---|
110 | } |
---|
111 | |
---|
112 | static void |
---|
113 | close_cgi(struct stream *stream) |
---|
114 | { |
---|
115 | assert(stream->chan.sock != -1); |
---|
116 | (void) closesocket(stream->chan.sock); |
---|
117 | } |
---|
118 | |
---|
119 | const struct io_class io_cgi = { |
---|
120 | "cgi", |
---|
121 | read_cgi, |
---|
122 | write_cgi, |
---|
123 | close_cgi |
---|
124 | }; |
---|