1
/* Lynx CGI support LYCgi.c
5
** GL George Lindholm <George.Lindholm@ubc.ca>
8
** 15 Jun 95 Created as way to provide a lynx based service with
9
** dynamic pages without the need for a http daemon. GL
10
** 27 Jun 95 Added <index> (command line) support. Various cleanup
12
** 04 Sep 97 Added support for PATH_INFO scripts. JKT
15
** If the called scripts aborts before sending the mime headers then
18
** Should do something about SIGPIPE, (but then it should never happen)
20
** No support for redirection. Or mime-types.
22
** Should try and parse for a HTTP 1.1 header in case we are "calling" a
36
#include <LYGlobalDefs.h>
40
#include <LYGetFile.h>
41
#include <LYBookmark.h>
44
#include <LYStrings.h>
55
PRIVATE char **env = NULL; /* Environment variables */
56
PRIVATE int envc_size = 0; /* Slots in environment array */
57
PRIVATE int envc = 0; /* Slots used so far */
58
PRIVATE HTList *alloced = NULL;
60
PRIVATE char *user_agent = NULL;
61
PRIVATE char *server_software = NULL;
62
PRIVATE char *accept_language = NULL;
63
PRIVATE char *post_len = NULL;
64
#endif /* LYNXCGI_LINKS */
66
PRIVATE void add_environment_value PARAMS((char *env_value));
68
#define PERROR(msg) CTRACE((tfp, "LYNXCGI: %s: %s\n", msg, LYStrerror(errno)))
71
PRIVATE void free_alloced_lynxcgi NOARGS
74
while ((ptr = HTList_removeLastObject(alloced)) != NULL) {
80
FREE(server_software);
83
#endif /* LY_FIND_LEAKS */
85
PRIVATE void remember_alloced ARGS1(
89
alloced = HTList_new();
91
atexit(free_alloced_lynxcgi);
94
HTList_addObject(alloced, ptr);
98
* Simple routine for expanding the environment array and adding a value to
101
PRIVATE void add_environment_value ARGS1(
104
if (envc == envc_size) { /* Need some more slots */
107
env = (char **)realloc(env,
108
sizeof(env[0]) * (envc_size + 2));
109
/* + terminator and base 0 */
111
env = (char **)malloc(sizeof(env[0]) * (envc_size + 2));
112
/* + terminator and base 0 */
113
remember_alloced(env);
116
outofmem(__FILE__, "LYCgi");
120
env[envc++] = env_value;
121
env[envc] = NULL; /* Make sure it is always properly terminated */
125
* Add the value of an existing environment variable to those passed on to the
128
PUBLIC void add_lynxcgi_environment ARGS1(
129
CONST char *, variable_name)
133
env_value = LYGetEnv(variable_name);
134
if (env_value != NULL) {
135
char *add_value = NULL;
137
HTSprintf0(&add_value, "%s=%s", variable_name, env_value);
138
add_environment_value(add_value);
139
remember_alloced(add_value);
144
PRIVATE int LYLoadCGI ARGS4(
146
HTParentAnchor *, anAnchor,
147
HTFormat, format_out,
153
PRIVATE int LYLoadCGI ARGS4(
155
HTParentAnchor *, anAnchor,
156
HTFormat, format_out,
163
struct stat stat_buf;
164
char *pgm = NULL; /* executable */
165
char *pgm_args = NULL; /* and its argument(s) */
167
char *orig_pgm = NULL; /* Path up to ? as given, URL-escaped*/
168
char *document_root = NULL; /* Corrected value of DOCUMENT_ROOT */
169
char *path_info = NULL; /* PATH_INFO extracted from pgm */
170
char *pgm_buff = NULL; /* PATH_INFO extraction buffer */
171
char *path_translated; /* From document_root/path_info */
173
if (isEmpty(arg) || strlen(arg) <= 8) {
174
HTAlert(BAD_REQUEST);
179
if (strncmp(arg, "lynxcgi://localhost", 19) == 0) {
180
StrAllocCopy(pgm, arg+19);
182
StrAllocCopy(pgm, arg+8);
184
if ((cp=strchr(pgm, '?')) != NULL) { /* Need to terminate executable */
190
StrAllocCopy(orig_pgm, pgm);
191
if ((cp = trimPoundSelector(pgm)) != NULL) {
193
* Strip a #fragment from path. In this case any pgm_args
194
* found above will also be bogus, since the '?' came after
195
* the '#' and is part of the fragment. Note that we don't
196
* handle the case where a '#' appears after a '?' properly
197
* according to URL rules. - kw
203
/* BEGIN WebSter Mods */
204
/* If pgm is not stat-able, see if PATH_INFO data is at the end of pgm */
205
if ((statrv = stat(pgm, &stat_buf)) < 0) {
206
StrAllocCopy(pgm_buff, pgm);
207
while (statrv < 0 || (statrv = stat(pgm_buff, &stat_buf)) < 0) {
208
if ((cp=strrchr(pgm_buff, '/')) != NULL) {
210
statrv = 1; /* force new stat() - kw */
212
PERROR("strrchr(pgm_buff, '/') returned NULL");
218
/* Did not find PATH_INFO data */
219
PERROR("stat() of pgm_buff failed");
221
/* Found PATH_INFO data. Strip it off of pgm and into path_info. */
222
StrAllocCopy(path_info, pgm + strlen(pgm_buff));
223
/* The following is safe since pgm_buff was derived from pgm
224
by stripping stuff off its end and by HTUnEscaping, so we
225
know we have enough memory allocated for pgm. Note that
226
pgm_args may still point into that memory, so we cannot
227
reallocate pgm here. - kw */
228
strcpy(pgm, pgm_buff);
229
CTRACE((tfp, "LYNXCGI: stat() of %s succeeded, path_info=\"%s\".\n",
230
pgm_buff, path_info));
234
/* END WebSter Mods */
238
* Neither the path as given nor any components examined by
239
* backing up were stat()able. - kw
241
HTAlert(gettext("Unable to access cgi script"));
242
PERROR("stat() failed");
246
#ifdef _WINDOWS /* 1998/01/14 (Wed) 09:16:04 */
247
#define isExecutable(mode) (mode & (S_IXUSR))
249
#define isExecutable(mode) (mode & (S_IXUSR|S_IXGRP|S_IXOTH))
251
if (!(S_ISREG(stat_buf.st_mode) && isExecutable(stat_buf.st_mode))) {
253
* Not a runnable file, See if we can load it using "file:" code.
255
char *new_arg = NULL;
258
* But try "file:" only if the file we are looking at is the path
259
* as given (no path_info was extracted), otherwise it will be
260
* to confusing to know just what file is loaded. - kw
263
CTRACE((tfp, "%s is not a file and %s not an executable, giving up.\n",
272
LYLocalFileToURL (&new_arg, orig_pgm);
274
CTRACE((tfp, "%s is not an executable file, passing the buck.\n", arg));
275
status = HTLoadFile(new_arg, anAnchor, format_out, sink);
278
} else if (path_info &&
279
anAnchor != HTMainAnchor &&
280
!(reloading && anAnchor->document) &&
281
strcmp(arg, HTLoadedDocumentURL()) &&
282
HText_AreDifferent(anAnchor, arg) &&
283
HTUnEscape(orig_pgm) &&
284
!exec_ok(HTLoadedDocumentURL(), orig_pgm,
285
CGI_PATH)) { /* exec_ok gives out msg. */
287
* If we have extra path info and are not just reloading
288
* the current, check the full file path (after unescaping)
289
* now to catch forbidden segments. - kw
291
status = HT_NOT_LOADED;
293
} else if (no_lynxcgi) {
294
HTUserMsg(CGI_DISABLED);
295
status = HT_NOT_LOADED;
297
} else if (no_bookmark_exec &&
298
anAnchor != HTMainAnchor &&
299
!(reloading && anAnchor->document) &&
300
strcmp(arg, HTLoadedDocumentURL()) &&
301
HText_AreDifferent(anAnchor, arg) &&
302
HTLoadedDocumentBookmark()) {
304
* If we are reloading a lynxcgi document that had already been
305
* loaded, the various checks above should allow it even if
306
* no_bookmark_exec is TRUE an we are not now coming from a
307
* bookmark page. - kw
309
HTUserMsg(BOOKMARK_EXEC_DISABLED);
310
status = HT_NOT_LOADED;
312
} else if (anAnchor != HTMainAnchor &&
313
!(reloading && anAnchor->document) &&
314
strcmp(arg, HTLoadedDocumentURL()) &&
315
HText_AreDifferent(anAnchor, arg) &&
316
!exec_ok(HTLoadedDocumentURL(), pgm,
317
CGI_PATH)) { /* exec_ok gives out msg. */
319
* If we are reloading a lynxcgi document that had already been
320
* loaded, the various checks above should allow it even if
321
* exec_ok() would reject it because we are not now coming from
322
* a document with a URL allowed by TRUSTED_LYNXCGI rules. - kw
324
status = HT_NOT_LOADED;
328
HTStream *target = NULL; /* Unconverted data */
332
#ifdef HAVE_TYPE_UNIONWAIT
338
if (anAnchor->isHEAD || keep_mime_headers) {
340
/* Show output as plain text */
341
format_in = WWW_PLAINTEXT;
344
/* Decode full HTTP response */
345
format_in = HTAtom_for("www/mime");
348
target = HTStreamStack(format_in,
352
if (!target || target == NULL) {
354
HTSprintf0(&tmp, CANNOT_CONVERT_I_TO_O,
355
HTAtom_name(format_in),
356
HTAtom_name(format_out));
359
status = HT_NOT_LOADED;
361
} else if (anAnchor->post_data && pipe(fd1) < 0) {
362
HTAlert(CONNECT_SET_FAILED);
363
PERROR("pipe() failed");
366
} else if (pipe(fd2) < 0) {
367
HTAlert(CONNECT_SET_FAILED);
368
PERROR("pipe() failed");
374
static BOOL first_time = TRUE; /* One time setup flag */
376
if (first_time) { /* Set up static environment variables */
377
first_time = FALSE; /* Only once */
379
add_environment_value("REMOTE_HOST=localhost");
380
add_environment_value("REMOTE_ADDR=127.0.0.1");
382
HTSprintf0(&user_agent, "HTTP_USER_AGENT=%s/%s libwww/%s",
383
LYNX_NAME, LYNX_VERSION, HTLibraryVersion);
384
add_environment_value(user_agent);
386
HTSprintf0(&server_software, "SERVER_SOFTWARE=%s/%s",
387
LYNX_NAME, LYNX_VERSION);
388
add_environment_value(server_software);
394
if ((pid = fork()) > 0) { /* The good, */
395
int chars, total_chars;
399
if (anAnchor->post_data) {
400
int written, remaining, total_written = 0;
404
/* We have form data to push across the pipe */
406
CTRACE((tfp, "LYNXCGI: Doing post, content-type '%s'\n",
407
anAnchor->post_content_type));
408
CTRACE((tfp, "LYNXCGI: Writing:\n"));
409
trace_bstring(anAnchor->post_data);
410
CTRACE((tfp, "----------------------------------\n"));
412
remaining = BStrLen(anAnchor->post_data);
413
while ((written = write(fd1[1],
414
BStrData(anAnchor->post_data) + total_written,
422
if (errno == ERESTARTSYS)
424
#endif /* ERESTARTSYS */
425
PERROR("write() of POST data failed");
428
CTRACE((tfp, "LYNXCGI: Wrote %d bytes of POST data.\n",
430
total_written += written;
431
remaining -= written;
435
if (remaining != 0) {
436
CTRACE((tfp, "LYNXCGI: %d bytes remain unwritten!\n",
442
HTReadProgress(total_chars = 0, 0);
443
while((chars = read(fd2[0], buf, sizeof(buf))) != 0) {
450
if (errno == ERESTARTSYS)
452
#endif /* ERESTARTSYS */
453
PERROR("read() of CGI output failed");
456
HTReadProgress(total_chars += chars, 0);
457
CTRACE((tfp, "LYNXCGI: Rx: %.*s\n", chars, buf));
458
(*target->isa->put_block)(target, buf, chars);
461
if (chars < 0 && total_chars == 0) {
462
status = HT_NOT_LOADED;
463
(*target->isa->_abort)(target, NULL);
465
} else if (chars != 0) {
466
status = HT_PARTIAL_CONTENT;
472
while (wait(&wstatus) != pid)
475
while (-1 == waitpid(pid, &wstatus, 0)) { /* wait for child */
481
if (errno == ERESTARTSYS)
483
#endif /* ERESTARTSYS */
486
#endif /* !HAVE_WAITPID */
489
} else if (pid == 0) { /* The Bad, */
491
int argv_cnt = 3; /* name, one arg and terminator */
492
char **cur_argv = NULL;
495
/* Set up output pipe */
497
dup2(fd2[1], fileno(stdout)); /* Should check success code */
498
dup2(fd2[1], fileno(stderr));
501
if (language && *language) {
502
HTSprintf0(&accept_language, "HTTP_ACCEPT_LANGUAGE=%s", language);
503
add_environment_value(accept_language);
506
if (pref_charset && *pref_charset) {
508
StrAllocCopy(cp, "HTTP_ACCEPT_CHARSET=");
509
StrAllocCat(cp, pref_charset);
510
add_environment_value(cp);
513
if (anAnchor->post_data &&
514
anAnchor->post_content_type) {
516
StrAllocCopy(cp, "CONTENT_TYPE=");
517
StrAllocCat(cp, anAnchor->post_content_type);
518
add_environment_value(cp);
521
if (anAnchor->post_data) { /* post script, read stdin */
523
dup2(fd1[0], fileno(stdin));
526
/* Build environment variables */
528
add_environment_value("REQUEST_METHOD=POST");
530
HTSprintf0(&post_len, "CONTENT_LENGTH=%d",
531
BStrLen(anAnchor->post_data));
532
add_environment_value(post_len);
534
close(fileno(stdin));
536
if (anAnchor->isHEAD) {
537
add_environment_value("REQUEST_METHOD=HEAD");
542
* Set up argument line, mainly for <index> scripts
544
if (pgm_args != NULL) {
545
for (cp = pgm_args; *cp != '\0'; cp++) {
552
argv = (char**)malloc(argv_cnt * sizeof(char*));
554
outofmem(__FILE__, "LYCgi");
556
cur_argv = argv + 1; /* For argv[0] */
557
if (pgm_args != NULL) {
560
/* Data for a get/search form */
562
add_environment_value("REQUEST_METHOD=SEARCH");
563
} else if (!anAnchor->isHEAD && !anAnchor->post_data) {
564
add_environment_value("REQUEST_METHOD=GET");
568
StrAllocCopy(cp, "QUERY_STRING=");
569
StrAllocCat(cp, pgm_args);
570
add_environment_value(cp);
573
* Split up arguments into argv array
579
*(cur_argv++) = HTUnEscape(cr);
582
} else if (*cp == '+') {
584
*(cur_argv++) = HTUnEscape(cr);
589
} else if (!anAnchor->isHEAD && !anAnchor->post_data) {
590
add_environment_value("REQUEST_METHOD=GET");
592
*cur_argv = NULL; /* Terminate argv */
595
/* Begin WebSter Mods -jkt */
596
if (LYCgiDocumentRoot != NULL) {
597
/* Add DOCUMENT_ROOT to env */
599
StrAllocCopy(cp, "DOCUMENT_ROOT=");
600
StrAllocCat(cp, LYCgiDocumentRoot);
601
add_environment_value(cp);
603
if (path_info != NULL ) {
604
/* Add PATH_INFO to env */
606
StrAllocCopy(cp, "PATH_INFO=");
607
StrAllocCat(cp, path_info);
608
add_environment_value(cp);
610
if (LYCgiDocumentRoot != NULL && path_info != NULL ) {
611
/* Construct and add PATH_TRANSLATED to env */
612
StrAllocCopy(document_root, LYCgiDocumentRoot);
613
LYTrimHtmlSep(document_root);
614
path_translated = document_root;
615
StrAllocCat(path_translated, path_info);
617
StrAllocCopy(cp, "PATH_TRANSLATED=");
618
StrAllocCat(cp, path_translated);
619
add_environment_value(cp);
620
FREE(path_translated);
622
/* End WebSter Mods -jkt */
624
execve(argv[0], argv, env);
626
PERROR("execve failed");
627
printf("Content-Type: text/plain\r\n\r\n");
628
if (!anAnchor->isHEAD) {
629
printf("exec of %s failed", pgm);
630
printf(": %s.\r\n", LYStrerror(exec_errno));
636
} else { /* and the Ugly */
637
HTAlert(CONNECT_FAILED);
638
PERROR("fork() failed");
648
if (target != NULL) {
649
(*target->isa->_free)(target);
659
target = HTStreamStack(WWW_HTML,
663
HTSprintf0(&buf, "<html>\n<head>\n<title>%s</title>\n</head>\n<body>\n",
664
gettext("Good Advice"));
665
(*target->isa->put_block)(target, buf, strlen(buf));
667
HTSprintf0(&buf, "<h1>%s</h1>\n", gettext("Good Advice"));
668
(*target->isa->put_block)(target, buf, strlen(buf));
670
HTSprintf0(&buf, "%s <a\n", gettext("An excellent http server for VMS is available via"));
671
(*target->isa->put_block)(target, buf, strlen(buf));
674
"href=\"http://kcgl1.eng.ohio-state.edu/www/doc/serverinfo.html\"\n");
675
(*target->isa->put_block)(target, buf, strlen(buf));
677
HTSprintf0(&buf, ">%s</a>.\n", gettext("this link"));
678
(*target->isa->put_block)(target, buf, strlen(buf));
680
HTSprintf0(&buf, "<p>%s\n",
681
gettext("It provides state of the art CGI script support.\n"));
682
(*target->isa->put_block)(target, buf, strlen(buf));
684
HTSprintf0(&buf,"</body>\n</html>\n");
685
(*target->isa->put_block)(target, buf, strlen(buf));
687
(*target->isa->_free)(target);
691
#else /* LYNXCGI_LINKS */
692
HTUserMsg(CGI_NOT_COMPILED);
693
status = HT_NOT_LOADED;
694
#endif /* LYNXCGI_LINKS */
697
#endif /* __MINGW32__ */
699
#ifdef GLOBALDEF_IS_MACRO
700
#define _LYCGI_C_GLOBALDEF_1_INIT { "lynxcgi", LYLoadCGI, 0 }
701
GLOBALDEF (HTProtocol,LYLynxCGI,_LYCGI_C_GLOBALDEF_1_INIT);
703
GLOBALDEF PUBLIC HTProtocol LYLynxCGI = { "lynxcgi", LYLoadCGI, 0 };
704
#endif /* GLOBALDEF_IS_MACRO */