summaryrefslogtreecommitdiffstats
path: root/src/LYCgi.c
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-27 17:56:50 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-27 17:56:50 +0000
commit8e9f8f2d0dcdddec35091ddbbdc79650763ec922 (patch)
tree4f88ed317d44fb4e5d9ab77e565bf1d910a8f560 /src/LYCgi.c
parentInitial commit. (diff)
downloadlynx-8e9f8f2d0dcdddec35091ddbbdc79650763ec922.tar.xz
lynx-8e9f8f2d0dcdddec35091ddbbdc79650763ec922.zip
Adding upstream version 2.9.0dev.6.upstream/2.9.0dev.6upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/LYCgi.c')
-rw-r--r--src/LYCgi.c757
1 files changed, 757 insertions, 0 deletions
diff --git a/src/LYCgi.c b/src/LYCgi.c
new file mode 100644
index 0000000..72493b2
--- /dev/null
+++ b/src/LYCgi.c
@@ -0,0 +1,757 @@
+/*
+ * $LynxId: LYCgi.c,v 1.72 2018/03/18 18:56:05 tom Exp $
+ * Lynx CGI support LYCgi.c
+ * ================
+ *
+ * Authors
+ * GL George Lindholm <George.Lindholm@ubc.ca>
+ *
+ * History
+ * 15 Jun 95 Created as way to provide a lynx based service with
+ * dynamic pages without the need for a http daemon. GL
+ * 27 Jun 95 Added <index> (command line) support. Various cleanup
+ * and bug fixes. GL
+ * 04 Sep 97 Added support for PATH_INFO scripts. JKT
+ *
+ * Bugs
+ * If the called scripts aborts before sending the mime headers then
+ * lynx hangs.
+ *
+ * Should do something about SIGPIPE, (but then it should never happen)
+ *
+ * No support for redirection. Or mime-types.
+ *
+ * Should try and parse for a HTTP 1.1 header in case we are "calling" a
+ * nph- script.
+ */
+
+#include <HTUtils.h>
+#include <HTTP.h>
+#include <HTParse.h>
+#include <HTTCP.h>
+#include <HTFormat.h>
+#include <HTFile.h>
+#include <HTAlert.h>
+#include <HTMIME.h>
+#include <HTAABrow.h>
+
+#include <LYGlobalDefs.h>
+#include <LYUtils.h>
+#include <HTML.h>
+#include <HTInit.h>
+#include <LYGetFile.h>
+#include <LYBookmark.h>
+#include <GridText.h>
+#include <LYCgi.h>
+#include <LYStrings.h>
+#include <LYLocal.h>
+
+#include <LYLeaks.h>
+#include <www_wait.h>
+
+static char **env = NULL; /* Environment variables */
+static unsigned envc_size = 0; /* Slots in environment array */
+static unsigned envc = 0; /* Slots used so far */
+static HTList *alloced = NULL;
+
+#if defined(LYNXCGI_LINKS) && !defined(__MINGW32__)
+static char *user_agent = NULL;
+static char *server_software = NULL;
+static char *accept_language = NULL;
+static char *post_len = NULL;
+#endif /* LYNXCGI_LINKS */
+
+static void add_environment_value(const char *env_value);
+
+#define PERROR(msg) CTRACE((tfp, "LYNXCGI: %s: %s\n", msg, LYStrerror(errno)))
+
+#define PUTS(buf) (*target->isa->put_block)(target, buf, strlen(buf))
+
+#ifdef LY_FIND_LEAKS
+static void free_alloced_lynxcgi(void)
+{
+ void *ptr;
+
+ while ((ptr = HTList_removeLastObject(alloced)) != NULL) {
+ FREE(ptr);
+ }
+ FREE(alloced);
+#ifdef LYNXCGI_LINKS
+ FREE(user_agent);
+ FREE(server_software);
+#endif
+}
+#endif /* LY_FIND_LEAKS */
+
+static void remember_alloced(void *ptr)
+{
+ if (!alloced) {
+ alloced = HTList_new();
+#ifdef LY_FIND_LEAKS
+ atexit(free_alloced_lynxcgi);
+#endif
+ }
+ HTList_addObject(alloced, ptr);
+}
+
+/*
+ * Simple routine for expanding the environment array and adding a value to
+ * it
+ */
+static void add_environment_value(const char *env_value)
+{
+ if (envc == envc_size) { /* Need some more slots */
+ envc_size += 10;
+ if (env) {
+ env = (char **) realloc(env,
+ sizeof(env[0]) * (envc_size + 2));
+ /* + terminator and base 0 */
+ } else {
+ env = (char **) malloc(sizeof(env[0]) * (envc_size + 2));
+ /* + terminator and base 0 */
+ remember_alloced(env);
+ }
+ if (env == NULL) {
+ outofmem(__FILE__, "LYCgi");
+ }
+ }
+
+ env[envc++] = DeConst(env_value);
+ env[envc] = NULL; /* Make sure it is always properly terminated */
+}
+
+/*
+ * Add the value of an existing environment variable to those passed on to the
+ * lynxcgi script.
+ */
+void add_lynxcgi_environment(const char *variable_name)
+{
+ char *env_value;
+
+ env_value = LYGetEnv(variable_name);
+ if (env_value != NULL) {
+ char *add_value = NULL;
+
+ HTSprintf0(&add_value, "%s=%s", variable_name, env_value);
+ add_environment_value(add_value);
+ remember_alloced(add_value);
+ }
+}
+
+#ifdef __MINGW32__
+static int LYLoadCGI(const char *arg,
+ HTParentAnchor *anAnchor,
+ HTFormat format_out,
+ HTStream *sink)
+{
+ (void) arg;
+ (void) anAnchor;
+ (void) format_out;
+ (void) sink;
+ return -1;
+}
+#else
+#ifdef LYNXCGI_LINKS
+/*
+ * Wrapper for exec_ok(), confirming with user if the link text is not visible
+ * in the status line.
+ */
+static BOOL can_exec_cgi(const char *linktext, const char *linkargs)
+{
+ const char *format = gettext("Do you want to execute \"%s\"?");
+ char *message = NULL;
+ char *command = NULL;
+ char *p;
+ BOOL result = TRUE;
+
+ if (!exec_ok(HTLoadedDocumentURL(), linktext, CGI_PATH)) {
+ /* exec_ok gives out msg. */
+ result = FALSE;
+ } else {
+ StrAllocCopy(command, linktext);
+ if (non_empty(linkargs)) {
+ HTSprintf(&command, " %s", linkargs);
+ }
+ HTUnEscape(command);
+ for (p = command; *p; ++p)
+ if (*p == '+')
+ *p = ' ';
+ HTSprintf0(&message, format, command);
+ result = HTConfirm(message);
+ FREE(message);
+ FREE(command);
+ }
+ return result;
+}
+#endif /* LYNXCGI_LINKS */
+
+static int LYLoadCGI(const char *arg,
+ HTParentAnchor *anAnchor,
+ HTFormat format_out,
+ HTStream *sink)
+{
+ int status = 0;
+
+#ifdef LYNXCGI_LINKS
+#ifndef VMS
+ char *cp;
+ struct stat stat_buf;
+ char *pgm = NULL; /* executable */
+ char *pgm_args = NULL; /* and its argument(s) */
+ int statrv;
+ char *orig_pgm = NULL; /* Path up to ? as given, URL-escaped */
+ char *document_root = NULL; /* Corrected value of DOCUMENT_ROOT */
+ char *path_info = NULL; /* PATH_INFO extracted from pgm */
+ char *pgm_buff = NULL; /* PATH_INFO extraction buffer */
+ char *path_translated; /* From document_root/path_info */
+
+ if (isEmpty(arg) || strlen(arg) <= 8) {
+ HTAlert(BAD_REQUEST);
+ status = -2;
+ return (status);
+
+ } else {
+ if (StrNCmp(arg, "lynxcgi://localhost", 19) == 0) {
+ StrAllocCopy(pgm, arg + 19);
+ } else {
+ StrAllocCopy(pgm, arg + 8);
+ }
+ if ((cp = StrChr(pgm, '?')) != NULL) { /* Need to terminate executable */
+ *cp++ = '\0';
+ pgm_args = cp;
+ }
+ }
+
+ StrAllocCopy(orig_pgm, pgm);
+ if (trimPoundSelector(pgm) != NULL) {
+ /*
+ * Strip a #fragment from path. In this case any pgm_args found above
+ * will also be bogus, since the '?' came after the '#' and is part of
+ * the fragment. Note that we don't handle the case where a '#'
+ * appears after a '?' properly according to URL rules. - kw
+ */
+ pgm_args = NULL;
+ }
+ HTUnEscape(pgm);
+
+ /* BEGIN WebSter Mods */
+ /* If pgm is not stat-able, see if PATH_INFO data is at the end of pgm */
+ if ((statrv = stat(pgm, &stat_buf)) < 0) {
+ StrAllocCopy(pgm_buff, pgm);
+ while (statrv < 0 || (statrv = stat(pgm_buff, &stat_buf)) < 0) {
+ if ((cp = strrchr(pgm_buff, '/')) != NULL) {
+ *cp = '\0';
+ statrv = 1; /* force new stat() - kw */
+ } else {
+ PERROR("strrchr(pgm_buff, '/') returned NULL");
+ break;
+ }
+ }
+
+ if (statrv < 0) {
+ /* Did not find PATH_INFO data */
+ PERROR("stat() of pgm_buff failed");
+ } else {
+ /* Found PATH_INFO data. Strip it off of pgm and into path_info. */
+ StrAllocCopy(path_info, pgm + strlen(pgm_buff));
+ /* The following is safe since pgm_buff was derived from pgm
+ by stripping stuff off its end and by HTUnEscaping, so we
+ know we have enough memory allocated for pgm. Note that
+ pgm_args may still point into that memory, so we cannot
+ reallocate pgm here. - kw */
+ strcpy(pgm, pgm_buff);
+ CTRACE((tfp,
+ "LYNXCGI: stat() of %s succeeded, path_info=\"%s\".\n",
+ pgm_buff, path_info));
+ }
+ FREE(pgm_buff);
+ }
+ /* END WebSter Mods */
+
+ if (statrv != 0) {
+ /*
+ * Neither the path as given nor any components examined by backing up
+ * were stat()able. - kw
+ */
+ HTAlert(gettext("Unable to access cgi script"));
+ PERROR("stat() failed");
+ status = -4;
+
+ } else
+#ifdef _WINDOWS /* 1998/01/14 (Wed) 09:16:04 */
+#define isExecutable(mode) (mode & (S_IXUSR))
+#else
+#define isExecutable(mode) (mode & (S_IXUSR|S_IXGRP|S_IXOTH))
+#endif
+ if (!(S_ISREG(stat_buf.st_mode) && isExecutable(stat_buf.st_mode))) {
+ /*
+ * Not a runnable file, See if we can load it using "file:" code.
+ */
+ char *new_arg = NULL;
+
+ /*
+ * But try "file:" only if the file we are looking at is the path as
+ * given (no path_info was extracted), otherwise it will be to
+ * confusing to know just what file is loaded. - kw
+ */
+ if (path_info) {
+ CTRACE((tfp,
+ "%s is not a file and %s not an executable, giving up.\n",
+ orig_pgm, pgm));
+ FREE(path_info);
+ FREE(pgm);
+ FREE(orig_pgm);
+ status = -4;
+ return (status);
+ }
+
+ LYLocalFileToURL(&new_arg, orig_pgm);
+
+ CTRACE((tfp, "%s is not an executable file, passing the buck.\n", arg));
+ status = HTLoadFile(new_arg, anAnchor, format_out, sink);
+ FREE(new_arg);
+
+ } else if (path_info &&
+ anAnchor != HTMainAnchor &&
+ !(reloading && anAnchor->document) &&
+ strcmp(arg, HTLoadedDocumentURL()) &&
+ HText_AreDifferent(anAnchor, arg) &&
+ HTUnEscape(orig_pgm) &&
+ !can_exec_cgi(orig_pgm, "")) {
+ /*
+ * If we have extra path info and are not just reloading the current,
+ * check the full file path (after unescaping) now to catch forbidden
+ * segments. - kw
+ */
+ status = HT_NOT_LOADED;
+
+ } else if (no_lynxcgi) {
+ HTUserMsg(CGI_DISABLED);
+ status = HT_NOT_LOADED;
+
+ } else if (no_bookmark_exec &&
+ anAnchor != HTMainAnchor &&
+ !(reloading && anAnchor->document) &&
+ strcmp(arg, HTLoadedDocumentURL()) &&
+ HText_AreDifferent(anAnchor, arg) &&
+ HTLoadedDocumentBookmark()) {
+ /*
+ * If we are reloading a lynxcgi document that had already been loaded,
+ * the various checks above should allow it even if no_bookmark_exec is
+ * TRUE an we are not now coming from a bookmark page. - kw
+ */
+ HTUserMsg(BOOKMARK_EXEC_DISABLED);
+ status = HT_NOT_LOADED;
+
+ } else if (anAnchor != HTMainAnchor &&
+ !(reloading && anAnchor->document) &&
+ strcmp(arg, HTLoadedDocumentURL()) &&
+ HText_AreDifferent(anAnchor, arg) &&
+ !can_exec_cgi(pgm, pgm_args)) {
+ /*
+ * If we are reloading a lynxcgi document that had already been loaded,
+ * the various checks above should allow it even if exec_ok() would
+ * reject it because we are not now coming from a document with a URL
+ * allowed by TRUSTED_LYNXCGI rules. - kw
+ */
+ status = HT_NOT_LOADED;
+
+ } else {
+ HTFormat format_in;
+ HTStream *target = NULL; /* Unconverted data */
+ int fd1[2], fd2[2];
+ char buf[MAX_LINE];
+ int pid;
+
+#ifdef HAVE_TYPE_UNIONWAIT
+ union wait wstatus;
+
+#else
+ int wstatus;
+#endif
+
+ fd1[0] = -1;
+ fd1[1] = -1;
+ fd2[0] = -1;
+ fd2[1] = -1;
+
+ if (anAnchor->isHEAD || keep_mime_headers) {
+
+ /* Show output as plain text */
+ format_in = WWW_PLAINTEXT;
+ } else {
+
+ /* Decode full HTTP response */
+ format_in = HTAtom_for("www/mime");
+ }
+
+ target = HTStreamStack(format_in,
+ format_out,
+ sink, anAnchor);
+
+ if (target == NULL) {
+ char *tmp = 0;
+
+ HTSprintf0(&tmp, CANNOT_CONVERT_I_TO_O,
+ HTAtom_name(format_in),
+ HTAtom_name(format_out));
+ HTAlert(tmp);
+ FREE(tmp);
+ status = HT_NOT_LOADED;
+
+ } else if (anAnchor->post_data && pipe(fd1) < 0) {
+ HTAlert(CONNECT_SET_FAILED);
+ PERROR("pipe() failed");
+ status = -3;
+
+ } else if (pipe(fd2) < 0) {
+ HTAlert(CONNECT_SET_FAILED);
+ PERROR("pipe() failed");
+ close(fd1[0]);
+ close(fd1[1]);
+ status = -3;
+
+ } else {
+ static BOOL first_time = TRUE; /* One time setup flag */
+
+ if (first_time) { /* Set up static environment variables */
+ first_time = FALSE; /* Only once */
+
+ add_environment_value("REMOTE_HOST=localhost");
+ add_environment_value("REMOTE_ADDR=127.0.0.1");
+
+ HTSprintf0(&user_agent, "HTTP_USER_AGENT=%s/%s libwww/%s",
+ LYNX_NAME, LYNX_VERSION, HTLibraryVersion);
+ add_environment_value(user_agent);
+
+ HTSprintf0(&server_software, "SERVER_SOFTWARE=%s/%s",
+ LYNX_NAME, LYNX_VERSION);
+ add_environment_value(server_software);
+ }
+ fflush(stdout);
+ fflush(stderr);
+ CTRACE_FLUSH(tfp);
+
+ if ((pid = fork()) > 0) { /* The good, */
+ ssize_t chars;
+ off_t total_chars;
+
+ close(fd2[1]);
+
+ if (anAnchor->post_data) {
+ ssize_t written;
+ int remaining, total_written = 0;
+
+ close(fd1[0]);
+
+ /* We have form data to push across the pipe */
+ if (TRACE) {
+ CTRACE((tfp,
+ "LYNXCGI: Doing post, content-type '%s'\n",
+ anAnchor->post_content_type));
+ CTRACE((tfp, "LYNXCGI: Writing:\n"));
+ trace_bstring(anAnchor->post_data);
+ CTRACE((tfp, "----------------------------------\n"));
+ }
+ remaining = BStrLen(anAnchor->post_data);
+ while ((written = write(fd1[1],
+ BStrData(anAnchor->post_data) + total_written,
+ (size_t) remaining)) != 0) {
+ if (written < 0) {
+#ifdef EINTR
+ if (errno == EINTR)
+ continue;
+#endif /* EINTR */
+#ifdef ERESTARTSYS
+ if (errno == ERESTARTSYS)
+ continue;
+#endif /* ERESTARTSYS */
+ PERROR("write() of POST data failed");
+ break;
+ }
+ CTRACE((tfp, "LYNXCGI: Wrote %d bytes of POST data.\n",
+ (int) written));
+ total_written += (int) written;
+ remaining -= (int) written;
+ if (remaining == 0)
+ break;
+ }
+ if (remaining != 0) {
+ CTRACE((tfp, "LYNXCGI: %d bytes remain unwritten!\n",
+ remaining));
+ }
+ close(fd1[1]);
+ }
+
+ HTReadProgress(total_chars = 0, (off_t) 0);
+ while ((chars = read(fd2[0], buf, sizeof(buf))) != 0) {
+ if (chars < 0) {
+#ifdef EINTR
+ if (errno == EINTR)
+ continue;
+#endif /* EINTR */
+#ifdef ERESTARTSYS
+ if (errno == ERESTARTSYS)
+ continue;
+#endif /* ERESTARTSYS */
+ PERROR("read() of CGI output failed");
+ break;
+ }
+ total_chars += (int) chars;
+ HTReadProgress(total_chars, (off_t) 0);
+ CTRACE((tfp, "LYNXCGI: Rx: %.*s\n", (int) chars, buf));
+ (*target->isa->put_block) (target, buf, (int) chars);
+ }
+
+ if (chars < 0 && total_chars == 0) {
+ status = HT_NOT_LOADED;
+ (*target->isa->_abort) (target, NULL);
+ target = NULL;
+ } else if (chars != 0) {
+ status = HT_PARTIAL_CONTENT;
+ } else {
+ status = HT_LOADED;
+ }
+
+#ifndef HAVE_WAITPID
+ while (wait(&wstatus) != pid) ; /* do nothing */
+#else
+ while (-1 == waitpid(pid, &wstatus, 0)) { /* wait for child */
+#ifdef EINTR
+ if (errno == EINTR)
+ continue;
+#endif /* EINTR */
+#ifdef ERESTARTSYS
+ if (errno == ERESTARTSYS)
+ continue;
+#endif /* ERESTARTSYS */
+ break;
+ }
+#endif /* !HAVE_WAITPID */
+ close(fd2[0]);
+
+ } else if (pid == 0) { /* The Bad, */
+ char **argv = NULL;
+ int argv_cnt = 3; /* name, one arg and terminator */
+ char **cur_argv = NULL;
+ int exec_errno;
+
+ /* Set up output pipe */
+ close(fd2[0]);
+ dup2(fd2[1], fileno(stdout)); /* Should check success code */
+ dup2(fd2[1], fileno(stderr));
+ close(fd2[1]);
+
+ if (non_empty(language)) {
+ HTSprintf0(&accept_language, "HTTP_ACCEPT_LANGUAGE=%s", language);
+ add_environment_value(accept_language);
+ }
+
+ if (non_empty(pref_charset)) {
+ cp = NULL;
+ StrAllocCopy(cp, "HTTP_ACCEPT_CHARSET=");
+ StrAllocCat(cp, pref_charset);
+ add_environment_value(cp);
+ }
+
+ if (anAnchor->post_data &&
+ anAnchor->post_content_type) {
+ cp = NULL;
+ StrAllocCopy(cp, "CONTENT_TYPE=");
+ StrAllocCat(cp, anAnchor->post_content_type);
+ add_environment_value(cp);
+ }
+
+ if (anAnchor->post_data) { /* post script, read stdin */
+ close(fd1[1]);
+ dup2(fd1[0], fileno(stdin));
+ close(fd1[0]);
+
+ /* Build environment variables */
+
+ add_environment_value("REQUEST_METHOD=POST");
+
+ HTSprintf0(&post_len, "CONTENT_LENGTH=%d",
+ BStrLen(anAnchor->post_data));
+ add_environment_value(post_len);
+ } else {
+ close(fileno(stdin));
+
+ if (anAnchor->isHEAD) {
+ add_environment_value("REQUEST_METHOD=HEAD");
+ }
+ }
+
+ /*
+ * Set up argument line, mainly for <index> scripts
+ */
+ if (pgm_args != NULL) {
+ for (cp = pgm_args; *cp != '\0'; cp++) {
+ if (*cp == '+') {
+ argv_cnt++;
+ }
+ }
+ }
+
+ argv = (char **) malloc((unsigned) argv_cnt * sizeof(char *));
+
+ if (argv == NULL) {
+ outofmem(__FILE__, "LYCgi");
+ }
+
+ cur_argv = argv + 1; /* For argv[0] */
+ if (pgm_args != NULL) {
+ char *cr;
+
+ /* Data for a get/search form */
+ if (is_www_index) {
+ add_environment_value("REQUEST_METHOD=SEARCH");
+ } else if (!anAnchor->isHEAD && !anAnchor->post_data) {
+ add_environment_value("REQUEST_METHOD=GET");
+ }
+
+ cp = NULL;
+ StrAllocCopy(cp, "QUERY_STRING=");
+ StrAllocCat(cp, pgm_args);
+ add_environment_value(cp);
+
+ /*
+ * Split up arguments into argv array
+ */
+ cp = pgm_args;
+ cr = cp;
+ while (1) {
+ if (*cp == '\0') {
+ *(cur_argv++) = HTUnEscape(cr);
+ break;
+
+ } else if (*cp == '+') {
+ *cp++ = '\0';
+ *(cur_argv++) = HTUnEscape(cr);
+ cr = cp;
+ }
+ cp++;
+ }
+ } else if (!anAnchor->isHEAD && !anAnchor->post_data) {
+ add_environment_value("REQUEST_METHOD=GET");
+ }
+ *cur_argv = NULL; /* Terminate argv */
+ argv[0] = pgm;
+
+ /* Begin WebSter Mods -jkt */
+ if (non_empty(LYCgiDocumentRoot)) {
+ /* Add DOCUMENT_ROOT to env */
+ cp = NULL;
+ StrAllocCopy(cp, "DOCUMENT_ROOT=");
+ StrAllocCat(cp, LYCgiDocumentRoot);
+ add_environment_value(cp);
+ }
+ if (path_info != NULL) {
+ /* Add PATH_INFO to env */
+ cp = NULL;
+ StrAllocCopy(cp, "PATH_INFO=");
+ StrAllocCat(cp, path_info);
+ add_environment_value(cp);
+ }
+ if (non_empty(LYCgiDocumentRoot) && path_info != NULL) {
+ /* Construct and add PATH_TRANSLATED to env */
+ StrAllocCopy(document_root, LYCgiDocumentRoot);
+ LYTrimHtmlSep(document_root);
+ path_translated = document_root;
+ StrAllocCat(path_translated, path_info);
+ cp = NULL;
+ StrAllocCopy(cp, "PATH_TRANSLATED=");
+ StrAllocCat(cp, path_translated);
+ add_environment_value(cp);
+ FREE(path_translated);
+ }
+ /* End WebSter Mods -jkt */
+
+ execve(argv[0], argv, env);
+ exec_errno = errno;
+ PERROR("execve failed");
+ printf("Content-Type: " STR_PLAINTEXT "\r\n\r\n");
+ if (!anAnchor->isHEAD) {
+ printf("exec of %s failed", pgm);
+ printf(": %s.\r\n", LYStrerror(exec_errno));
+ }
+ fflush(stdout);
+ fflush(stderr);
+ _exit(1);
+
+ } else { /* and the Ugly */
+ HTAlert(CONNECT_FAILED);
+ PERROR("fork() failed");
+ close(fd1[0]);
+ close(fd1[1]);
+ close(fd2[0]);
+ close(fd2[1]);
+ status = -1;
+ }
+
+ }
+ if (target != NULL) {
+ (*target->isa->_free) (target);
+ }
+ }
+ FREE(path_info);
+ FREE(pgm);
+ FREE(orig_pgm);
+#else /* VMS */
+ HTStream *target;
+ char *buf = 0;
+
+ target = HTStreamStack(WWW_HTML,
+ format_out,
+ sink, anAnchor);
+
+ HTSprintf0(&buf, "<html>\n<head>\n<title>%s</title>\n</head>\n<body>\n",
+ gettext("Good Advice"));
+ PUTS(buf);
+
+ HTSprintf0(&buf, "<h1>%s</h1>\n", gettext("Good Advice"));
+ PUTS(buf);
+
+ HTSprintf0(&buf, "%s <a\n",
+ gettext("An excellent http server for VMS is available via"));
+ PUTS(buf);
+
+ HTSprintf0(&buf,
+ "href=\"http://www.ecr6.ohio-state.edu/www/doc/serverinfo.html\"\n");
+ PUTS(buf);
+
+ HTSprintf0(&buf, ">%s</a>.\n", gettext("this link"));
+ PUTS(buf);
+
+ HTSprintf0(&buf, "<p>%s\n",
+ gettext("It provides state of the art CGI script support.\n"));
+ PUTS(buf);
+
+ HTSprintf0(&buf, "</body>\n</html>\n");
+ PUTS(buf);
+
+ (*target->isa->_free) (target);
+ FREE(buf);
+ status = HT_LOADED;
+#endif /* VMS */
+#else /* LYNXCGI_LINKS */
+ HTUserMsg(CGI_NOT_COMPILED);
+ status = HT_NOT_LOADED;
+#endif /* LYNXCGI_LINKS */
+
+ (void) arg;
+ (void) anAnchor;
+ (void) format_out;
+ (void) sink;
+
+ return (status);
+}
+#endif /* __MINGW32__ */
+
+#ifdef GLOBALDEF_IS_MACRO
+#define _LYCGI_C_GLOBALDEF_1_INIT { "lynxcgi", LYLoadCGI, 0 }
+GLOBALDEF(HTProtocol, LYLynxCGI, _LYCGI_C_GLOBALDEF_1_INIT);
+#else
+GLOBALDEF HTProtocol LYLynxCGI =
+{"lynxcgi", LYLoadCGI, 0};
+#endif /* GLOBALDEF_IS_MACRO */