1 /*
2 * $LynxId: LYCgi.c,v 1.72 2018/03/18 18:56:05 tom Exp $
3 * Lynx CGI support LYCgi.c
4 * ================
5 *
6 * Authors
7 * GL George Lindholm <George.Lindholm@ubc.ca>
8 *
9 * History
10 * 15 Jun 95 Created as way to provide a lynx based service with
11 * dynamic pages without the need for a http daemon. GL
12 * 27 Jun 95 Added <index> (command line) support. Various cleanup
13 * and bug fixes. GL
14 * 04 Sep 97 Added support for PATH_INFO scripts. JKT
15 *
16 * Bugs
17 * If the called scripts aborts before sending the mime headers then
18 * lynx hangs.
19 *
20 * Should do something about SIGPIPE, (but then it should never happen)
21 *
22 * No support for redirection. Or mime-types.
23 *
24 * Should try and parse for a HTTP 1.1 header in case we are "calling" a
25 * nph- script.
26 */
27
28 #include <HTUtils.h>
29 #include <HTTP.h>
30 #include <HTParse.h>
31 #include <HTTCP.h>
32 #include <HTFormat.h>
33 #include <HTFile.h>
34 #include <HTAlert.h>
35 #include <HTMIME.h>
36 #include <HTAABrow.h>
37
38 #include <LYGlobalDefs.h>
39 #include <LYUtils.h>
40 #include <HTML.h>
41 #include <HTInit.h>
42 #include <LYGetFile.h>
43 #include <LYBookmark.h>
44 #include <GridText.h>
45 #include <LYCgi.h>
46 #include <LYStrings.h>
47 #include <LYLocal.h>
48
49 #include <LYLeaks.h>
50 #include <www_wait.h>
51
52 static char **env = NULL; /* Environment variables */
53 static unsigned envc_size = 0; /* Slots in environment array */
54 static unsigned envc = 0; /* Slots used so far */
55 static HTList *alloced = NULL;
56
57 #if defined(LYNXCGI_LINKS) && !defined(__MINGW32__)
58 static char *user_agent = NULL;
59 static char *server_software = NULL;
60 static char *accept_language = NULL;
61 static char *post_len = NULL;
62 #endif /* LYNXCGI_LINKS */
63
64 static void add_environment_value(const char *env_value);
65
66 #define PERROR(msg) CTRACE((tfp, "LYNXCGI: %s: %s\n", msg, LYStrerror(errno)))
67
68 #define PUTS(buf) (*target->isa->put_block)(target, buf, strlen(buf))
69
70 #ifdef LY_FIND_LEAKS
free_alloced_lynxcgi(void)71 static void free_alloced_lynxcgi(void)
72 {
73 void *ptr;
74
75 while ((ptr = HTList_removeLastObject(alloced)) != NULL) {
76 FREE(ptr);
77 }
78 FREE(alloced);
79 #ifdef LYNXCGI_LINKS
80 FREE(user_agent);
81 FREE(server_software);
82 #endif
83 }
84 #endif /* LY_FIND_LEAKS */
85
remember_alloced(void * ptr)86 static void remember_alloced(void *ptr)
87 {
88 if (!alloced) {
89 alloced = HTList_new();
90 #ifdef LY_FIND_LEAKS
91 atexit(free_alloced_lynxcgi);
92 #endif
93 }
94 HTList_addObject(alloced, ptr);
95 }
96
97 /*
98 * Simple routine for expanding the environment array and adding a value to
99 * it
100 */
add_environment_value(const char * env_value)101 static void add_environment_value(const char *env_value)
102 {
103 if (envc == envc_size) { /* Need some more slots */
104 envc_size += 10;
105 if (env) {
106 env = (char **) realloc(env,
107 sizeof(env[0]) * (envc_size + 2));
108 /* + terminator and base 0 */
109 } else {
110 env = (char **) malloc(sizeof(env[0]) * (envc_size + 2));
111 /* + terminator and base 0 */
112 remember_alloced(env);
113 }
114 if (env == NULL) {
115 outofmem(__FILE__, "LYCgi");
116 }
117 }
118
119 env[envc++] = DeConst(env_value);
120 env[envc] = NULL; /* Make sure it is always properly terminated */
121 }
122
123 /*
124 * Add the value of an existing environment variable to those passed on to the
125 * lynxcgi script.
126 */
add_lynxcgi_environment(const char * variable_name)127 void add_lynxcgi_environment(const char *variable_name)
128 {
129 char *env_value;
130
131 env_value = LYGetEnv(variable_name);
132 if (env_value != NULL) {
133 char *add_value = NULL;
134
135 HTSprintf0(&add_value, "%s=%s", variable_name, env_value);
136 add_environment_value(add_value);
137 remember_alloced(add_value);
138 }
139 }
140
141 #ifdef __MINGW32__
LYLoadCGI(const char * arg,HTParentAnchor * anAnchor,HTFormat format_out,HTStream * sink)142 static int LYLoadCGI(const char *arg,
143 HTParentAnchor *anAnchor,
144 HTFormat format_out,
145 HTStream *sink)
146 {
147 (void) arg;
148 (void) anAnchor;
149 (void) format_out;
150 (void) sink;
151 return -1;
152 }
153 #else
154 #ifdef LYNXCGI_LINKS
155 /*
156 * Wrapper for exec_ok(), confirming with user if the link text is not visible
157 * in the status line.
158 */
can_exec_cgi(const char * linktext,const char * linkargs)159 static BOOL can_exec_cgi(const char *linktext, const char *linkargs)
160 {
161 const char *format = gettext("Do you want to execute \"%s\"?");
162 char *message = NULL;
163 char *command = NULL;
164 char *p;
165 BOOL result = TRUE;
166
167 if (!exec_ok(HTLoadedDocumentURL(), linktext, CGI_PATH)) {
168 /* exec_ok gives out msg. */
169 result = FALSE;
170 } else {
171 StrAllocCopy(command, linktext);
172 if (non_empty(linkargs)) {
173 HTSprintf(&command, " %s", linkargs);
174 }
175 HTUnEscape(command);
176 for (p = command; *p; ++p)
177 if (*p == '+')
178 *p = ' ';
179 HTSprintf0(&message, format, command);
180 result = HTConfirm(message);
181 FREE(message);
182 FREE(command);
183 }
184 return result;
185 }
186 #endif /* LYNXCGI_LINKS */
187
LYLoadCGI(const char * arg,HTParentAnchor * anAnchor,HTFormat format_out,HTStream * sink)188 static int LYLoadCGI(const char *arg,
189 HTParentAnchor *anAnchor,
190 HTFormat format_out,
191 HTStream *sink)
192 {
193 int status = 0;
194
195 #ifdef LYNXCGI_LINKS
196 #ifndef VMS
197 char *cp;
198 struct stat stat_buf;
199 char *pgm = NULL; /* executable */
200 char *pgm_args = NULL; /* and its argument(s) */
201 int statrv;
202 char *orig_pgm = NULL; /* Path up to ? as given, URL-escaped */
203 char *document_root = NULL; /* Corrected value of DOCUMENT_ROOT */
204 char *path_info = NULL; /* PATH_INFO extracted from pgm */
205 char *pgm_buff = NULL; /* PATH_INFO extraction buffer */
206 char *path_translated; /* From document_root/path_info */
207
208 if (isEmpty(arg) || strlen(arg) <= 8) {
209 HTAlert(BAD_REQUEST);
210 status = -2;
211 return (status);
212
213 } else {
214 if (StrNCmp(arg, "lynxcgi://localhost", 19) == 0) {
215 StrAllocCopy(pgm, arg + 19);
216 } else {
217 StrAllocCopy(pgm, arg + 8);
218 }
219 if ((cp = StrChr(pgm, '?')) != NULL) { /* Need to terminate executable */
220 *cp++ = '\0';
221 pgm_args = cp;
222 }
223 }
224
225 StrAllocCopy(orig_pgm, pgm);
226 if (trimPoundSelector(pgm) != NULL) {
227 /*
228 * Strip a #fragment from path. In this case any pgm_args found above
229 * will also be bogus, since the '?' came after the '#' and is part of
230 * the fragment. Note that we don't handle the case where a '#'
231 * appears after a '?' properly according to URL rules. - kw
232 */
233 pgm_args = NULL;
234 }
235 HTUnEscape(pgm);
236
237 /* BEGIN WebSter Mods */
238 /* If pgm is not stat-able, see if PATH_INFO data is at the end of pgm */
239 if ((statrv = stat(pgm, &stat_buf)) < 0) {
240 StrAllocCopy(pgm_buff, pgm);
241 while (statrv < 0 || (statrv = stat(pgm_buff, &stat_buf)) < 0) {
242 if ((cp = strrchr(pgm_buff, '/')) != NULL) {
243 *cp = '\0';
244 statrv = 1; /* force new stat() - kw */
245 } else {
246 PERROR("strrchr(pgm_buff, '/') returned NULL");
247 break;
248 }
249 }
250
251 if (statrv < 0) {
252 /* Did not find PATH_INFO data */
253 PERROR("stat() of pgm_buff failed");
254 } else {
255 /* Found PATH_INFO data. Strip it off of pgm and into path_info. */
256 StrAllocCopy(path_info, pgm + strlen(pgm_buff));
257 /* The following is safe since pgm_buff was derived from pgm
258 by stripping stuff off its end and by HTUnEscaping, so we
259 know we have enough memory allocated for pgm. Note that
260 pgm_args may still point into that memory, so we cannot
261 reallocate pgm here. - kw */
262 strcpy(pgm, pgm_buff);
263 CTRACE((tfp,
264 "LYNXCGI: stat() of %s succeeded, path_info=\"%s\".\n",
265 pgm_buff, path_info));
266 }
267 FREE(pgm_buff);
268 }
269 /* END WebSter Mods */
270
271 if (statrv != 0) {
272 /*
273 * Neither the path as given nor any components examined by backing up
274 * were stat()able. - kw
275 */
276 HTAlert(gettext("Unable to access cgi script"));
277 PERROR("stat() failed");
278 status = -4;
279
280 } else
281 #ifdef _WINDOWS /* 1998/01/14 (Wed) 09:16:04 */
282 #define isExecutable(mode) (mode & (S_IXUSR))
283 #else
284 #define isExecutable(mode) (mode & (S_IXUSR|S_IXGRP|S_IXOTH))
285 #endif
286 if (!(S_ISREG(stat_buf.st_mode) && isExecutable(stat_buf.st_mode))) {
287 /*
288 * Not a runnable file, See if we can load it using "file:" code.
289 */
290 char *new_arg = NULL;
291
292 /*
293 * But try "file:" only if the file we are looking at is the path as
294 * given (no path_info was extracted), otherwise it will be to
295 * confusing to know just what file is loaded. - kw
296 */
297 if (path_info) {
298 CTRACE((tfp,
299 "%s is not a file and %s not an executable, giving up.\n",
300 orig_pgm, pgm));
301 FREE(path_info);
302 FREE(pgm);
303 FREE(orig_pgm);
304 status = -4;
305 return (status);
306 }
307
308 LYLocalFileToURL(&new_arg, orig_pgm);
309
310 CTRACE((tfp, "%s is not an executable file, passing the buck.\n", arg));
311 status = HTLoadFile(new_arg, anAnchor, format_out, sink);
312 FREE(new_arg);
313
314 } else if (path_info &&
315 anAnchor != HTMainAnchor &&
316 !(reloading && anAnchor->document) &&
317 strcmp(arg, HTLoadedDocumentURL()) &&
318 HText_AreDifferent(anAnchor, arg) &&
319 HTUnEscape(orig_pgm) &&
320 !can_exec_cgi(orig_pgm, "")) {
321 /*
322 * If we have extra path info and are not just reloading the current,
323 * check the full file path (after unescaping) now to catch forbidden
324 * segments. - kw
325 */
326 status = HT_NOT_LOADED;
327
328 } else if (no_lynxcgi) {
329 HTUserMsg(CGI_DISABLED);
330 status = HT_NOT_LOADED;
331
332 } else if (no_bookmark_exec &&
333 anAnchor != HTMainAnchor &&
334 !(reloading && anAnchor->document) &&
335 strcmp(arg, HTLoadedDocumentURL()) &&
336 HText_AreDifferent(anAnchor, arg) &&
337 HTLoadedDocumentBookmark()) {
338 /*
339 * If we are reloading a lynxcgi document that had already been loaded,
340 * the various checks above should allow it even if no_bookmark_exec is
341 * TRUE an we are not now coming from a bookmark page. - kw
342 */
343 HTUserMsg(BOOKMARK_EXEC_DISABLED);
344 status = HT_NOT_LOADED;
345
346 } else if (anAnchor != HTMainAnchor &&
347 !(reloading && anAnchor->document) &&
348 strcmp(arg, HTLoadedDocumentURL()) &&
349 HText_AreDifferent(anAnchor, arg) &&
350 !can_exec_cgi(pgm, pgm_args)) {
351 /*
352 * If we are reloading a lynxcgi document that had already been loaded,
353 * the various checks above should allow it even if exec_ok() would
354 * reject it because we are not now coming from a document with a URL
355 * allowed by TRUSTED_LYNXCGI rules. - kw
356 */
357 status = HT_NOT_LOADED;
358
359 } else {
360 HTFormat format_in;
361 HTStream *target = NULL; /* Unconverted data */
362 int fd1[2], fd2[2];
363 char buf[MAX_LINE];
364 int pid;
365
366 #ifdef HAVE_TYPE_UNIONWAIT
367 union wait wstatus;
368
369 #else
370 int wstatus;
371 #endif
372
373 fd1[0] = -1;
374 fd1[1] = -1;
375 fd2[0] = -1;
376 fd2[1] = -1;
377
378 if (anAnchor->isHEAD || keep_mime_headers) {
379
380 /* Show output as plain text */
381 format_in = WWW_PLAINTEXT;
382 } else {
383
384 /* Decode full HTTP response */
385 format_in = HTAtom_for("www/mime");
386 }
387
388 target = HTStreamStack(format_in,
389 format_out,
390 sink, anAnchor);
391
392 if (target == NULL) {
393 char *tmp = 0;
394
395 HTSprintf0(&tmp, CANNOT_CONVERT_I_TO_O,
396 HTAtom_name(format_in),
397 HTAtom_name(format_out));
398 HTAlert(tmp);
399 FREE(tmp);
400 status = HT_NOT_LOADED;
401
402 } else if (anAnchor->post_data && pipe(fd1) < 0) {
403 HTAlert(CONNECT_SET_FAILED);
404 PERROR("pipe() failed");
405 status = -3;
406
407 } else if (pipe(fd2) < 0) {
408 HTAlert(CONNECT_SET_FAILED);
409 PERROR("pipe() failed");
410 close(fd1[0]);
411 close(fd1[1]);
412 status = -3;
413
414 } else {
415 static BOOL first_time = TRUE; /* One time setup flag */
416
417 if (first_time) { /* Set up static environment variables */
418 first_time = FALSE; /* Only once */
419
420 add_environment_value("REMOTE_HOST=localhost");
421 add_environment_value("REMOTE_ADDR=127.0.0.1");
422
423 HTSprintf0(&user_agent, "HTTP_USER_AGENT=%s/%s libwww/%s",
424 LYNX_NAME, LYNX_VERSION, HTLibraryVersion);
425 add_environment_value(user_agent);
426
427 HTSprintf0(&server_software, "SERVER_SOFTWARE=%s/%s",
428 LYNX_NAME, LYNX_VERSION);
429 add_environment_value(server_software);
430 }
431 fflush(stdout);
432 fflush(stderr);
433 CTRACE_FLUSH(tfp);
434
435 if ((pid = fork()) > 0) { /* The good, */
436 ssize_t chars;
437 off_t total_chars;
438
439 close(fd2[1]);
440
441 if (anAnchor->post_data) {
442 ssize_t written;
443 int remaining, total_written = 0;
444
445 close(fd1[0]);
446
447 /* We have form data to push across the pipe */
448 if (TRACE) {
449 CTRACE((tfp,
450 "LYNXCGI: Doing post, content-type '%s'\n",
451 anAnchor->post_content_type));
452 CTRACE((tfp, "LYNXCGI: Writing:\n"));
453 trace_bstring(anAnchor->post_data);
454 CTRACE((tfp, "----------------------------------\n"));
455 }
456 remaining = BStrLen(anAnchor->post_data);
457 while ((written = write(fd1[1],
458 BStrData(anAnchor->post_data) + total_written,
459 (size_t) remaining)) != 0) {
460 if (written < 0) {
461 #ifdef EINTR
462 if (errno == EINTR)
463 continue;
464 #endif /* EINTR */
465 #ifdef ERESTARTSYS
466 if (errno == ERESTARTSYS)
467 continue;
468 #endif /* ERESTARTSYS */
469 PERROR("write() of POST data failed");
470 break;
471 }
472 CTRACE((tfp, "LYNXCGI: Wrote %d bytes of POST data.\n",
473 (int) written));
474 total_written += (int) written;
475 remaining -= (int) written;
476 if (remaining == 0)
477 break;
478 }
479 if (remaining != 0) {
480 CTRACE((tfp, "LYNXCGI: %d bytes remain unwritten!\n",
481 remaining));
482 }
483 close(fd1[1]);
484 }
485
486 HTReadProgress(total_chars = 0, (off_t) 0);
487 while ((chars = read(fd2[0], buf, sizeof(buf))) != 0) {
488 if (chars < 0) {
489 #ifdef EINTR
490 if (errno == EINTR)
491 continue;
492 #endif /* EINTR */
493 #ifdef ERESTARTSYS
494 if (errno == ERESTARTSYS)
495 continue;
496 #endif /* ERESTARTSYS */
497 PERROR("read() of CGI output failed");
498 break;
499 }
500 total_chars += (int) chars;
501 HTReadProgress(total_chars, (off_t) 0);
502 CTRACE((tfp, "LYNXCGI: Rx: %.*s\n", (int) chars, buf));
503 (*target->isa->put_block) (target, buf, (int) chars);
504 }
505
506 if (chars < 0 && total_chars == 0) {
507 status = HT_NOT_LOADED;
508 (*target->isa->_abort) (target, NULL);
509 target = NULL;
510 } else if (chars != 0) {
511 status = HT_PARTIAL_CONTENT;
512 } else {
513 status = HT_LOADED;
514 }
515
516 #ifndef HAVE_WAITPID
517 while (wait(&wstatus) != pid) ; /* do nothing */
518 #else
519 while (-1 == waitpid(pid, &wstatus, 0)) { /* wait for child */
520 #ifdef EINTR
521 if (errno == EINTR)
522 continue;
523 #endif /* EINTR */
524 #ifdef ERESTARTSYS
525 if (errno == ERESTARTSYS)
526 continue;
527 #endif /* ERESTARTSYS */
528 break;
529 }
530 #endif /* !HAVE_WAITPID */
531 close(fd2[0]);
532
533 } else if (pid == 0) { /* The Bad, */
534 char **argv = NULL;
535 int argv_cnt = 3; /* name, one arg and terminator */
536 char **cur_argv = NULL;
537 int exec_errno;
538
539 /* Set up output pipe */
540 close(fd2[0]);
541 dup2(fd2[1], fileno(stdout)); /* Should check success code */
542 dup2(fd2[1], fileno(stderr));
543 close(fd2[1]);
544
545 if (non_empty(language)) {
546 HTSprintf0(&accept_language, "HTTP_ACCEPT_LANGUAGE=%s", language);
547 add_environment_value(accept_language);
548 }
549
550 if (non_empty(pref_charset)) {
551 cp = NULL;
552 StrAllocCopy(cp, "HTTP_ACCEPT_CHARSET=");
553 StrAllocCat(cp, pref_charset);
554 add_environment_value(cp);
555 }
556
557 if (anAnchor->post_data &&
558 anAnchor->post_content_type) {
559 cp = NULL;
560 StrAllocCopy(cp, "CONTENT_TYPE=");
561 StrAllocCat(cp, anAnchor->post_content_type);
562 add_environment_value(cp);
563 }
564
565 if (anAnchor->post_data) { /* post script, read stdin */
566 close(fd1[1]);
567 dup2(fd1[0], fileno(stdin));
568 close(fd1[0]);
569
570 /* Build environment variables */
571
572 add_environment_value("REQUEST_METHOD=POST");
573
574 HTSprintf0(&post_len, "CONTENT_LENGTH=%d",
575 BStrLen(anAnchor->post_data));
576 add_environment_value(post_len);
577 } else {
578 close(fileno(stdin));
579
580 if (anAnchor->isHEAD) {
581 add_environment_value("REQUEST_METHOD=HEAD");
582 }
583 }
584
585 /*
586 * Set up argument line, mainly for <index> scripts
587 */
588 if (pgm_args != NULL) {
589 for (cp = pgm_args; *cp != '\0'; cp++) {
590 if (*cp == '+') {
591 argv_cnt++;
592 }
593 }
594 }
595
596 argv = (char **) malloc((unsigned) argv_cnt * sizeof(char *));
597
598 if (argv == NULL) {
599 outofmem(__FILE__, "LYCgi");
600 }
601
602 cur_argv = argv + 1; /* For argv[0] */
603 if (pgm_args != NULL) {
604 char *cr;
605
606 /* Data for a get/search form */
607 if (is_www_index) {
608 add_environment_value("REQUEST_METHOD=SEARCH");
609 } else if (!anAnchor->isHEAD && !anAnchor->post_data) {
610 add_environment_value("REQUEST_METHOD=GET");
611 }
612
613 cp = NULL;
614 StrAllocCopy(cp, "QUERY_STRING=");
615 StrAllocCat(cp, pgm_args);
616 add_environment_value(cp);
617
618 /*
619 * Split up arguments into argv array
620 */
621 cp = pgm_args;
622 cr = cp;
623 while (1) {
624 if (*cp == '\0') {
625 *(cur_argv++) = HTUnEscape(cr);
626 break;
627
628 } else if (*cp == '+') {
629 *cp++ = '\0';
630 *(cur_argv++) = HTUnEscape(cr);
631 cr = cp;
632 }
633 cp++;
634 }
635 } else if (!anAnchor->isHEAD && !anAnchor->post_data) {
636 add_environment_value("REQUEST_METHOD=GET");
637 }
638 *cur_argv = NULL; /* Terminate argv */
639 argv[0] = pgm;
640
641 /* Begin WebSter Mods -jkt */
642 if (non_empty(LYCgiDocumentRoot)) {
643 /* Add DOCUMENT_ROOT to env */
644 cp = NULL;
645 StrAllocCopy(cp, "DOCUMENT_ROOT=");
646 StrAllocCat(cp, LYCgiDocumentRoot);
647 add_environment_value(cp);
648 }
649 if (path_info != NULL) {
650 /* Add PATH_INFO to env */
651 cp = NULL;
652 StrAllocCopy(cp, "PATH_INFO=");
653 StrAllocCat(cp, path_info);
654 add_environment_value(cp);
655 }
656 if (non_empty(LYCgiDocumentRoot) && path_info != NULL) {
657 /* Construct and add PATH_TRANSLATED to env */
658 StrAllocCopy(document_root, LYCgiDocumentRoot);
659 LYTrimHtmlSep(document_root);
660 path_translated = document_root;
661 StrAllocCat(path_translated, path_info);
662 cp = NULL;
663 StrAllocCopy(cp, "PATH_TRANSLATED=");
664 StrAllocCat(cp, path_translated);
665 add_environment_value(cp);
666 FREE(path_translated);
667 }
668 /* End WebSter Mods -jkt */
669
670 execve(argv[0], argv, env);
671 exec_errno = errno;
672 PERROR("execve failed");
673 printf("Content-Type: " STR_PLAINTEXT "\r\n\r\n");
674 if (!anAnchor->isHEAD) {
675 printf("exec of %s failed", pgm);
676 printf(": %s.\r\n", LYStrerror(exec_errno));
677 }
678 fflush(stdout);
679 fflush(stderr);
680 _exit(1);
681
682 } else { /* and the Ugly */
683 HTAlert(CONNECT_FAILED);
684 PERROR("fork() failed");
685 close(fd1[0]);
686 close(fd1[1]);
687 close(fd2[0]);
688 close(fd2[1]);
689 status = -1;
690 }
691
692 }
693 if (target != NULL) {
694 (*target->isa->_free) (target);
695 }
696 }
697 FREE(path_info);
698 FREE(pgm);
699 FREE(orig_pgm);
700 #else /* VMS */
701 HTStream *target;
702 char *buf = 0;
703
704 target = HTStreamStack(WWW_HTML,
705 format_out,
706 sink, anAnchor);
707
708 HTSprintf0(&buf, "<html>\n<head>\n<title>%s</title>\n</head>\n<body>\n",
709 gettext("Good Advice"));
710 PUTS(buf);
711
712 HTSprintf0(&buf, "<h1>%s</h1>\n", gettext("Good Advice"));
713 PUTS(buf);
714
715 HTSprintf0(&buf, "%s <a\n",
716 gettext("An excellent http server for VMS is available via"));
717 PUTS(buf);
718
719 HTSprintf0(&buf,
720 "href=\"http://www.ecr6.ohio-state.edu/www/doc/serverinfo.html\"\n");
721 PUTS(buf);
722
723 HTSprintf0(&buf, ">%s</a>.\n", gettext("this link"));
724 PUTS(buf);
725
726 HTSprintf0(&buf, "<p>%s\n",
727 gettext("It provides state of the art CGI script support.\n"));
728 PUTS(buf);
729
730 HTSprintf0(&buf, "</body>\n</html>\n");
731 PUTS(buf);
732
733 (*target->isa->_free) (target);
734 FREE(buf);
735 status = HT_LOADED;
736 #endif /* VMS */
737 #else /* LYNXCGI_LINKS */
738 HTUserMsg(CGI_NOT_COMPILED);
739 status = HT_NOT_LOADED;
740 #endif /* LYNXCGI_LINKS */
741
742 (void) arg;
743 (void) anAnchor;
744 (void) format_out;
745 (void) sink;
746
747 return (status);
748 }
749 #endif /* __MINGW32__ */
750
751 #ifdef GLOBALDEF_IS_MACRO
752 #define _LYCGI_C_GLOBALDEF_1_INIT { "lynxcgi", LYLoadCGI, 0 }
753 GLOBALDEF(HTProtocol, LYLynxCGI, _LYCGI_C_GLOBALDEF_1_INIT);
754 #else
755 GLOBALDEF HTProtocol LYLynxCGI =
756 {"lynxcgi", LYLoadCGI, 0};
757 #endif /* GLOBALDEF_IS_MACRO */
758