Bug 1013: Don't assume errno is between 0 and 100000
[elinks.git] / src / protocol / file / cgi.c
blobc8f3a41fee52ce2a51f56e244c0e72e2e5223f93
1 /* Internal "cgi" protocol implementation */
3 #ifdef HAVE_CONFIG_H
4 #include "config.h"
5 #endif
7 #include <errno.h>
8 #include <stdio.h>
9 #include <stdlib.h>
10 #include <sys/types.h>
11 #include <sys/stat.h> /* OS/2 needs this after sys/types.h */
12 #ifdef HAVE_FCNTL_H
13 #include <fcntl.h> /* OS/2 needs this after sys/types.h */
14 #endif
15 #ifdef HAVE_UNISTD_H
16 #include <unistd.h>
17 #endif
19 #include "elinks.h"
21 #include "config/options.h"
22 #include "cookies/cookies.h"
23 #include "intl/gettext/libintl.h"
24 #include "mime/backend/common.h"
25 #include "network/connection.h"
26 #include "network/socket.h"
27 #include "osdep/osdep.h"
28 #include "osdep/sysname.h"
29 #include "protocol/common.h"
30 #include "protocol/file/cgi.h"
31 #include "protocol/http/http.h"
32 #include "protocol/uri.h"
33 #include "terminal/terminal.h"
34 #include "util/conv.h"
35 #include "util/env.h"
36 #include "util/string.h"
38 static struct option_info cgi_options[] = {
39 INIT_OPT_TREE("protocol.file", N_("Local CGI"),
40 "cgi", 0,
41 N_("Local CGI specific options.")),
43 INIT_OPT_STRING("protocol.file.cgi", N_("Path"),
44 "path", 0, "",
45 N_("Colon separated list of directories, where CGI scripts are stored.")),
47 INIT_OPT_BOOL("protocol.file.cgi", N_("Allow local CGI"),
48 "policy", 0, 0,
49 N_("Whether to execute local CGI scripts.")),
50 NULL_OPTION_INFO,
53 struct module cgi_protocol_module = struct_module(
54 /* name: */ N_("CGI"),
55 /* options: */ cgi_options,
56 /* hooks: */ NULL,
57 /* submodules: */ NULL,
58 /* data: */ NULL,
59 /* init: */ NULL,
60 /* done: */ NULL
63 static void
64 close_pipe_and_read(struct socket *data_socket)
66 struct connection *conn = data_socket->conn;
67 struct read_buffer *rb = alloc_read_buffer(conn->socket);
69 if (!rb) return;
71 memcpy(rb->data, "HTTP/1.0 200 OK\r\n", 17);
72 rb->length = 17;
73 rb->freespace -= 17;
75 conn->unrestartable = 1;
76 close(data_socket->fd);
77 data_socket->fd = -1;
79 conn->socket->state = SOCKET_END_ONCLOSE;
80 read_from_socket(conn->socket, rb, connection_state(S_SENT),
81 http_got_header);
84 static void
85 send_post_data(struct connection *conn)
87 #define POST_BUFFER_SIZE 4096
88 unsigned char *post = conn->uri->post;
89 unsigned char *postend;
90 unsigned char buffer[POST_BUFFER_SIZE];
91 struct string data;
92 int n = 0;
94 if (!init_string(&data)) {
95 abort_connection(conn, connection_state(S_OUT_OF_MEM));
96 return;
98 postend = strchr(post, '\n');
99 if (postend) post = postend + 1;
101 /* FIXME: Code duplication with protocol/http/http.c! --witekfl */
102 while (post[0] && post[1]) {
103 int h1, h2;
105 h1 = unhx(post[0]);
106 assert(h1 >= 0 && h1 < 16);
107 if_assert_failed h1 = 0;
109 h2 = unhx(post[1]);
110 assert(h2 >= 0 && h2 < 16);
111 if_assert_failed h2 = 0;
113 buffer[n++] = (h1<<4) + h2;
114 post += 2;
115 if (n == POST_BUFFER_SIZE) {
116 add_bytes_to_string(&data, buffer, n);
117 n = 0;
120 if (n)
121 add_bytes_to_string(&data, buffer, n);
124 /* If we're submitting a form whose controls do not have
125 * names, then the POST has a Content-Type but empty data,
126 * and an assertion would fail in write_to_socket. */
127 if (data.length)
128 write_to_socket(conn->data_socket, data.source, data.length,
129 connection_state(S_SENT), close_pipe_and_read);
130 else
131 close_pipe_and_read(conn->data_socket);
133 done_string(&data);
134 #undef POST_BUFFER_SIZE
137 static void
138 send_request(struct connection *conn)
140 if (conn->uri->post) send_post_data(conn);
141 else close_pipe_and_read(conn->data_socket);
144 /* This function sets CGI environment variables. */
145 static int
146 set_vars(struct connection *conn, unsigned char *script)
148 unsigned char *post = conn->uri->post;
149 unsigned char *query = get_uri_string(conn->uri, URI_QUERY);
150 unsigned char *str;
151 int res = env_set("QUERY_STRING", empty_string_or_(query), -1);
153 mem_free_if(query);
154 if (res) return -1;
156 if (post) {
157 unsigned char *postend = strchr(post, '\n');
158 unsigned char buf[16];
160 if (postend) {
161 res = env_set("CONTENT_TYPE", post, postend - post);
162 if (res) return -1;
163 post = postend + 1;
165 snprintf(buf, 16, "%d", (int) strlen(post) / 2);
166 if (env_set("CONTENT_LENGTH", buf, -1)) return -1;
169 if (env_set("REQUEST_METHOD", post ? "POST" : "GET", -1)) return -1;
170 if (env_set("SERVER_SOFTWARE", "ELinks/" VERSION, -1)) return -1;
171 if (env_set("SERVER_PROTOCOL", "HTTP/1.0", -1)) return -1;
172 /* XXX: Maybe it is better to set this to an empty string? --pasky */
173 if (env_set("SERVER_NAME", "localhost", -1)) return -1;
174 /* XXX: Maybe it is better to set this to an empty string? --pasky */
175 if (env_set("REMOTE_ADDR", "127.0.0.1", -1)) return -1;
176 if (env_set("GATEWAY_INTERFACE", "CGI/1.1", -1)) return -1;
177 /* This is the path name extracted from the URI and decoded, per
178 * http://cgi-spec.golux.com/draft-coar-cgi-v11-03-clean.html#8.1 */
179 if (env_set("SCRIPT_NAME", script, -1)) return -1;
180 if (env_set("SCRIPT_FILENAME", script, -1)) return -1;
181 if (env_set("PATH_TRANSLATED", script, -1)) return -1;
182 if (env_set("REDIRECT_STATUS", "1", -1)) return -1;
184 /* From now on, just HTTP-like headers are being set. Missing variables
185 * due to full environment are not a problem according to the CGI/1.1
186 * standard, so we already filled our environment with we have to have
187 * there and we won't fail anymore if it won't work out. */
189 str = get_opt_str("protocol.http.user_agent");
190 if (*str && strcmp(str, " ")) {
191 unsigned char *ustr, ts[64] = "";
193 if (!list_empty(terminals)) {
194 unsigned int tslen = 0;
195 struct terminal *term = terminals.prev;
197 ulongcat(ts, &tslen, term->width, 3, 0);
198 ts[tslen++] = 'x';
199 ulongcat(ts, &tslen, term->height, 3, 0);
201 ustr = subst_user_agent(str, VERSION_STRING, system_name, ts);
203 if (ustr) {
204 env_set("HTTP_USER_AGENT", ustr, -1);
205 mem_free(ustr);
209 switch (get_opt_int("protocol.http.referer.policy")) {
210 case REFERER_NONE:
211 /* oh well */
212 break;
214 case REFERER_FAKE:
215 str = get_opt_str("protocol.http.referer.fake");
216 env_set("HTTP_REFERER", str, -1);
217 break;
219 case REFERER_TRUE:
220 /* XXX: Encode as in add_url_to_http_string() ? --pasky */
221 if (conn->referrer)
222 env_set("HTTP_REFERER", struri(conn->referrer), -1);
223 break;
225 case REFERER_SAME_URL:
226 str = get_uri_string(conn->uri, URI_HTTP_REFERRER);
227 if (str) {
228 env_set("HTTP_REFERER", str, -1);
229 mem_free(str);
231 break;
234 /* Protection against vim cindent bugs ;-). */
235 env_set("HTTP_ACCEPT", "*/" "*", -1);
237 /* We do not set HTTP_ACCEPT_ENCODING. Yeah, let's let the CGI script
238 * gzip the stuff so that the CPU doesn't at least sit idle. */
240 str = get_opt_str("protocol.http.accept_language");
241 if (*str) {
242 env_set("HTTP_ACCEPT_LANGUAGE", str, -1);
244 #ifdef CONFIG_NLS
245 else if (get_opt_bool("protocol.http.accept_ui_language")) {
246 env_set("HTTP_ACCEPT_LANGUAGE",
247 language_to_iso639(current_language), -1);
249 #endif
251 if (conn->cached && !conn->cached->incomplete && conn->cached->head
252 && conn->cached->last_modified
253 && conn->cache_mode <= CACHE_MODE_CHECK_IF_MODIFIED) {
254 env_set("HTTP_IF_MODIFIED_SINCE", conn->cached->last_modified, -1);
257 if (conn->cache_mode >= CACHE_MODE_FORCE_RELOAD) {
258 env_set("HTTP_PRAGMA", "no-cache", -1);
259 env_set("HTTP_CACHE_CONTROL", "no-cache", -1);
262 /* TODO: HTTP auth support. On the other side, it was weird over CGI
263 * IIRC. --pasky */
265 #ifdef CONFIG_COOKIES
267 struct string *cookies = send_cookies(conn->uri);
269 if (cookies) {
270 env_set("HTTP_COOKIE", cookies->source, -1);
272 done_string(cookies);
275 #endif
277 return 0;
280 static int
281 test_path(unsigned char *path)
283 unsigned char *cgi_path = get_opt_str("protocol.file.cgi.path");
284 unsigned char **path_ptr;
285 unsigned char *filename;
287 for (path_ptr = &cgi_path;
288 (filename = get_next_path_filename(path_ptr, ':'));
290 int filelen = strlen(filename);
291 int res;
293 if (filename[filelen - 1] != '/') {
294 add_to_strn(&filename, "/");
295 filelen++;
298 res = strncmp(path, filename, filelen);
299 mem_free(filename);
300 if (!res) return 0;
302 return 1;
306 execute_cgi(struct connection *conn)
308 unsigned char *last_slash;
309 unsigned char *script;
310 int scriptlen;
311 struct stat buf;
312 pid_t pid;
313 struct connection_state state = connection_state(S_OK);
314 int pipe_read[2], pipe_write[2];
316 if (!get_opt_bool("protocol.file.cgi.policy")) return 1;
318 /* Not file referrer */
319 if (conn->referrer && conn->referrer->protocol != PROTOCOL_FILE) {
320 return 1;
323 script = get_uri_string(conn->uri, URI_PATH);
324 if (!script) {
325 state = connection_state(S_OUT_OF_MEM);
326 goto end2;
328 decode_uri(script);
329 scriptlen = strlen(script);
331 if (stat(script, &buf) || !(S_ISREG(buf.st_mode))
332 || !(buf.st_mode & S_IXUSR)) {
333 mem_free(script);
334 return 1;
337 last_slash = strrchr(script, '/');
338 if (last_slash++) {
339 unsigned char storage;
340 int res;
342 /* We want to compare against path with the trailing slash. */
343 storage = *last_slash;
344 *last_slash = 0;
345 res = test_path(script);
346 *last_slash = storage;
347 if (res) {
348 mem_free(script);
349 return 1;
351 } else {
352 mem_free(script);
353 return 1;
356 if (c_pipe(pipe_read) || c_pipe(pipe_write)) {
357 state = connection_state_for_errno(errno);
358 goto end1;
361 pid = fork();
362 if (pid < 0) {
363 state = connection_state_for_errno(errno);
364 goto end0;
366 if (!pid) { /* CGI script */
367 if (set_vars(conn, script)) {
368 _exit(1);
370 if ((dup2(pipe_write[0], STDIN_FILENO) < 0)
371 || (dup2(pipe_read[1], STDOUT_FILENO) < 0)) {
372 _exit(2);
374 /* We implicitly chain stderr to ELinks' stderr. */
375 close_all_non_term_fd();
377 last_slash[-1] = 0; set_cwd(script); last_slash[-1] = '/';
378 if (execl(script, script, (char *) NULL)) {
379 _exit(3);
382 } else { /* ELinks */
383 mem_free(script);
385 if (!init_http_connection_info(conn, 1, 0, 1)) {
386 close(pipe_read[0]); close(pipe_read[1]);
387 close(pipe_write[0]); close(pipe_write[1]);
388 return 0;
391 close(pipe_read[1]); close(pipe_write[0]);
392 conn->socket->fd = pipe_read[0];
394 /* Use data socket for passing the pipe. It will be cleaned up in
395 * close_pipe_and_read(). */
396 conn->data_socket->fd = pipe_write[1];
397 conn->cgi = 1;
398 set_nonblocking_fd(conn->socket->fd);
399 set_nonblocking_fd(conn->data_socket->fd);
401 send_request(conn);
402 return 0;
405 end0:
406 close(pipe_read[0]); close(pipe_read[1]);
407 close(pipe_write[0]); close(pipe_write[1]);
408 end1:
409 mem_free(script);
410 end2:
411 abort_connection(conn, state);
412 return 0;