1 /* Internal "cgi" protocol implementation */
10 #include <sys/types.h>
11 #include <sys/stat.h> /* OS/2 needs this after sys/types.h */
13 #include <fcntl.h> /* OS/2 needs this after sys/types.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"
36 #include "util/string.h"
38 static struct option_info cgi_options
[] = {
39 INIT_OPT_TREE("protocol.file", N_("Local CGI"),
41 N_("Local CGI specific options.")),
43 INIT_OPT_STRING("protocol.file.cgi", N_("Path"),
45 N_("Colon separated list of directories, where CGI scripts are stored.")),
47 INIT_OPT_BOOL("protocol.file.cgi", N_("Allow local CGI"),
49 N_("Whether to execute local CGI scripts.")),
53 struct module cgi_protocol_module
= struct_module(
54 /* name: */ N_("CGI"),
55 /* options: */ cgi_options
,
57 /* submodules: */ NULL
,
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
);
71 memcpy(rb
->data
, "HTTP/1.0 200 OK\r\n", 17);
75 conn
->unrestartable
= 1;
76 close(data_socket
->fd
);
79 conn
->socket
->state
= SOCKET_END_ONCLOSE
;
80 read_from_socket(conn
->socket
, rb
, connection_state(S_SENT
),
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
];
94 if (!init_string(&data
)) {
95 abort_connection(conn
, connection_state(S_OUT_OF_MEM
));
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]) {
106 assert(h1
>= 0 && h1
< 16);
107 if_assert_failed h1
= 0;
110 assert(h2
>= 0 && h2
< 16);
111 if_assert_failed h2
= 0;
113 buffer
[n
++] = (h1
<<4) + h2
;
115 if (n
== POST_BUFFER_SIZE
) {
116 add_bytes_to_string(&data
, buffer
, 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. */
128 write_to_socket(conn
->data_socket
, data
.source
, data
.length
,
129 connection_state(S_SENT
), close_pipe_and_read
);
131 close_pipe_and_read(conn
->data_socket
);
134 #undef POST_BUFFER_SIZE
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. */
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
);
151 int res
= env_set("QUERY_STRING", empty_string_or_(query
), -1);
157 unsigned char *postend
= strchr(post
, '\n');
158 unsigned char buf
[16];
161 res
= env_set("CONTENT_TYPE", post
, postend
- post
);
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);
199 ulongcat(ts
, &tslen
, term
->height
, 3, 0);
201 ustr
= subst_user_agent(str
, VERSION_STRING
, system_name
, ts
);
204 env_set("HTTP_USER_AGENT", ustr
, -1);
209 switch (get_opt_int("protocol.http.referer.policy")) {
215 str
= get_opt_str("protocol.http.referer.fake");
216 env_set("HTTP_REFERER", str
, -1);
220 /* XXX: Encode as in add_url_to_http_string() ? --pasky */
222 env_set("HTTP_REFERER", struri(conn
->referrer
), -1);
225 case REFERER_SAME_URL
:
226 str
= get_uri_string(conn
->uri
, URI_HTTP_REFERRER
);
228 env_set("HTTP_REFERER", str
, -1);
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");
242 env_set("HTTP_ACCEPT_LANGUAGE", str
, -1);
245 else if (get_opt_bool("protocol.http.accept_ui_language")) {
246 env_set("HTTP_ACCEPT_LANGUAGE",
247 language_to_iso639(current_language
), -1);
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
265 #ifdef CONFIG_COOKIES
267 struct string
*cookies
= send_cookies(conn
->uri
);
270 env_set("HTTP_COOKIE", cookies
->source
, -1);
272 done_string(cookies
);
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
);
293 if (filename
[filelen
- 1] != '/') {
294 add_to_strn(&filename
, "/");
298 res
= strncmp(path
, filename
, filelen
);
306 execute_cgi(struct connection
*conn
)
308 unsigned char *last_slash
;
309 unsigned char *script
;
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
) {
323 script
= get_uri_string(conn
->uri
, URI_PATH
);
325 state
= connection_state(S_OUT_OF_MEM
);
329 scriptlen
= strlen(script
);
331 if (stat(script
, &buf
) || !(S_ISREG(buf
.st_mode
))
332 || !(buf
.st_mode
& S_IXUSR
)) {
337 last_slash
= strrchr(script
, '/');
339 unsigned char storage
;
342 /* We want to compare against path with the trailing slash. */
343 storage
= *last_slash
;
345 res
= test_path(script
);
346 *last_slash
= storage
;
356 if (c_pipe(pipe_read
) || c_pipe(pipe_write
)) {
357 state
= connection_state_for_errno(errno
);
363 state
= connection_state_for_errno(errno
);
366 if (!pid
) { /* CGI script */
367 if (set_vars(conn
, script
)) {
370 if ((dup2(pipe_write
[0], STDIN_FILENO
) < 0)
371 || (dup2(pipe_read
[1], STDOUT_FILENO
) < 0)) {
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
)) {
382 } else { /* ELinks */
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]);
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];
398 set_nonblocking_fd(conn
->socket
->fd
);
399 set_nonblocking_fd(conn
->data_socket
->fd
);
406 close(pipe_read
[0]); close(pipe_read
[1]);
407 close(pipe_write
[0]); close(pipe_write
[1]);
411 abort_connection(conn
, state
);