summaryrefslogtreecommitdiffstats
path: root/WWW/Library/Implementation/HTFormat.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--WWW/Library/Implementation/HTFormat.c2181
1 files changed, 2181 insertions, 0 deletions
diff --git a/WWW/Library/Implementation/HTFormat.c b/WWW/Library/Implementation/HTFormat.c
new file mode 100644
index 0000000..a830387
--- /dev/null
+++ b/WWW/Library/Implementation/HTFormat.c
@@ -0,0 +1,2181 @@
+/*
+ * $LynxId: HTFormat.c,v 1.96 2022/03/31 23:39:38 tom Exp $
+ *
+ * Manage different file formats HTFormat.c
+ * =============================
+ *
+ * Bugs:
+ * Not reentrant.
+ *
+ * Assumes the incoming stream is ASCII, rather than a local file
+ * format, and so ALWAYS converts from ASCII on non-ASCII machines.
+ * Therefore, non-ASCII machines can't read local files.
+ *
+ */
+
+#define HTSTREAM_INTERNAL 1
+
+#include <HTUtils.h>
+
+/* Implements:
+*/
+#include <HTFormat.h>
+
+static float HTMaxSecs = 1e10; /* No effective limit */
+
+#ifdef UNIX
+#ifdef NeXT
+#define PRESENT_POSTSCRIPT "open %s; /bin/rm -f %s\n"
+#else
+#define PRESENT_POSTSCRIPT "(ghostview %s ; /bin/rm -f %s)&\n"
+ /* Full pathname would be better! */
+#endif /* NeXT */
+#endif /* UNIX */
+
+#include <HTML.h>
+#include <HTMLDTD.h>
+#include <HText.h>
+#include <HTAlert.h>
+#include <HTList.h>
+#include <HTInit.h>
+#include <HTTCP.h>
+#include <HTTP.h>
+/* Streams and structured streams which we use:
+*/
+#include <HTFWriter.h>
+#include <HTPlain.h>
+#include <SGML.h>
+#include <HTMLGen.h>
+
+#include <LYexit.h>
+#include <LYUtils.h>
+#include <GridText.h>
+#include <LYGlobalDefs.h>
+#include <LYLeaks.h>
+
+#ifdef DISP_PARTIAL
+#include <LYMainLoop.h>
+#endif
+
+#ifdef USE_BROTLI
+#include <brotli/decode.h>
+#endif
+
+BOOL HTOutputSource = NO; /* Flag: shortcut parser to stdout */
+
+/* this version used by the NetToText stream */
+struct _HTStream {
+ const HTStreamClass *isa;
+ BOOL had_cr;
+ HTStream *sink;
+};
+
+/* Presentation methods
+ * --------------------
+ */
+HTList *HTPresentations = NULL;
+HTPresentation *default_presentation = NULL;
+
+/*
+ * To free off the presentation list.
+ */
+#ifdef LY_FIND_LEAKS
+static void HTFreePresentations(void);
+#endif
+
+/* Define a presentation system command for a content-type
+ * -------------------------------------------------------
+ */
+void HTSetPresentation(const char *representation,
+ const char *command,
+ const char *testcommand,
+ double quality,
+ double secs,
+ double secs_per_byte,
+ long int maxbytes,
+ AcceptMedia media)
+{
+ HTPresentation *pres = typecalloc(HTPresentation);
+
+ if (pres == NULL)
+ outofmem(__FILE__, "HTSetPresentation");
+
+ assert(representation != NULL);
+
+ CTRACE2(TRACE_CFG,
+ (tfp,
+ "HTSetPresentation rep=%s, command=%s, test=%s, qual=%f\n",
+ NonNull(representation),
+ NonNull(command),
+ NonNull(testcommand),
+ quality));
+
+ pres->rep = HTAtom_for(representation);
+ pres->rep_out = WWW_PRESENT; /* Fixed for now ... :-) */
+ pres->converter = HTSaveAndExecute; /* Fixed for now ... */
+ pres->quality = (float) quality;
+ pres->secs = (float) secs;
+ pres->secs_per_byte = (float) secs_per_byte;
+ pres->maxbytes = maxbytes;
+ pres->get_accept = 0;
+ pres->accept_opt = media;
+
+ pres->command = NULL;
+ StrAllocCopy(pres->command, command);
+
+ pres->testcommand = NULL;
+ StrAllocCopy(pres->testcommand, testcommand);
+
+ /*
+ * Memory leak fixed.
+ * 05-28-94 Lynx 2-3-1 Garrett Arch Blythe
+ */
+ if (!HTPresentations) {
+ HTPresentations = HTList_new();
+#ifdef LY_FIND_LEAKS
+ atexit(HTFreePresentations);
+#endif
+ }
+
+ if (strcmp(representation, "*") == 0) {
+ FREE(default_presentation);
+ default_presentation = pres;
+ } else {
+ HTList_addObject(HTPresentations, pres);
+ }
+}
+
+/* Define a built-in function for a content-type
+ * ---------------------------------------------
+ */
+void HTSetConversion(const char *representation_in,
+ const char *representation_out,
+ HTConverter *converter,
+ double quality,
+ double secs,
+ double secs_per_byte,
+ long int maxbytes,
+ AcceptMedia media)
+{
+ HTPresentation *pres = typecalloc(HTPresentation);
+
+ if (pres == NULL)
+ outofmem(__FILE__, "HTSetConversion");
+
+ CTRACE2(TRACE_CFG,
+ (tfp,
+ "HTSetConversion rep_in=%s, rep_out=%s, qual=%f\n",
+ NonNull(representation_in),
+ NonNull(representation_out),
+ quality));
+
+ pres->rep = HTAtom_for(representation_in);
+ pres->rep_out = HTAtom_for(representation_out);
+ pres->converter = converter;
+ pres->command = NULL;
+ pres->testcommand = NULL;
+ pres->quality = (float) quality;
+ pres->secs = (float) secs;
+ pres->secs_per_byte = (float) secs_per_byte;
+ pres->maxbytes = maxbytes;
+ pres->get_accept = TRUE;
+ pres->accept_opt = media;
+
+ /*
+ * Memory Leak fixed.
+ * 05-28-94 Lynx 2-3-1 Garrett Arch Blythe
+ */
+ if (!HTPresentations) {
+ HTPresentations = HTList_new();
+#ifdef LY_FIND_LEAKS
+ atexit(HTFreePresentations);
+#endif
+ }
+
+ HTList_addObject(HTPresentations, pres);
+}
+
+#ifdef LY_FIND_LEAKS
+/*
+ * Purpose: Free the presentation list.
+ * Arguments: void
+ * Return Value: void
+ * Remarks/Portability/Dependencies/Restrictions:
+ * Made to clean up Lynx's bad leakage.
+ * Revision History:
+ * 05-28-94 created Lynx 2-3-1 Garrett Arch Blythe
+ */
+static void HTFreePresentations(void)
+{
+ HTPresentation *pres = NULL;
+
+ /*
+ * Loop through the list.
+ */
+ while (!HTList_isEmpty(HTPresentations)) {
+ /*
+ * Free off each item. May also need to free off it's items, but not
+ * sure as of yet.
+ */
+ pres = (HTPresentation *) HTList_removeLastObject(HTPresentations);
+ FREE(pres->command);
+ FREE(pres->testcommand);
+ FREE(pres);
+ }
+ /*
+ * Free the list itself.
+ */
+ HTList_delete(HTPresentations);
+ HTPresentations = NULL;
+}
+#endif /* LY_FIND_LEAKS */
+
+/* File buffering
+ * --------------
+ *
+ * The input file is read using the macro which can read from
+ * a socket or a file.
+ * The input buffer size, if large will give greater efficiency and
+ * release the server faster, and if small will save space on PCs etc.
+ */
+#define INPUT_BUFFER_SIZE 4096 /* Tradeoff */
+static char input_buffer[INPUT_BUFFER_SIZE];
+static char *input_pointer;
+static char *input_limit;
+static int input_file_number;
+
+/* Set up the buffering
+ *
+ * These routines are public because they are in fact needed by
+ * many parsers, and on PCs and Macs we should not duplicate
+ * the static buffer area.
+ */
+void HTInitInput(int file_number)
+{
+ input_file_number = file_number;
+ input_pointer = input_limit = input_buffer;
+}
+
+int interrupted_in_htgetcharacter = 0;
+int HTGetCharacter(void)
+{
+ char ch;
+
+ interrupted_in_htgetcharacter = 0;
+ do {
+ if (input_pointer >= input_limit) {
+ int status = NETREAD(input_file_number,
+ input_buffer, INPUT_BUFFER_SIZE);
+
+ if (status <= 0) {
+ if (status == 0)
+ return EOF;
+ if (status == HT_INTERRUPTED) {
+ CTRACE((tfp, "HTFormat: Interrupted in HTGetCharacter\n"));
+ interrupted_in_htgetcharacter = 1;
+ return EOF;
+ }
+ CTRACE((tfp, "HTFormat: File read error %d\n", status));
+ return EOF; /* -1 is returned by UCX
+ at end of HTTP link */
+ }
+ input_pointer = input_buffer;
+ input_limit = input_buffer + status;
+ }
+ ch = *input_pointer++;
+ } while (ch == (char) 13); /* Ignore ASCII carriage return */
+
+ return FROMASCII(UCH(ch));
+}
+
+#ifdef USE_SSL
+int HTGetSSLCharacter(void *handle)
+{
+ char ch;
+
+ interrupted_in_htgetcharacter = 0;
+ if (!handle)
+ return (char) EOF;
+ do {
+ if (input_pointer >= input_limit) {
+ int status = SSL_read((SSL *) handle,
+ input_buffer, INPUT_BUFFER_SIZE);
+
+ if (status <= 0) {
+ if (status == 0)
+ return (char) EOF;
+ if (status == HT_INTERRUPTED) {
+ CTRACE((tfp,
+ "HTFormat: Interrupted in HTGetSSLCharacter\n"));
+ interrupted_in_htgetcharacter = 1;
+ return (char) EOF;
+ }
+ CTRACE((tfp, "HTFormat: SSL_read error %d\n", status));
+ return (char) EOF; /* -1 is returned by UCX
+ at end of HTTP link */
+ }
+ input_pointer = input_buffer;
+ input_limit = input_buffer + status;
+ }
+ ch = *input_pointer++;
+ } while (ch == (char) 13); /* Ignore ASCII carriage return */
+
+ return FROMASCII(ch);
+}
+#endif /* USE_SSL */
+
+/* Match maintype to any MIME type starting with maintype, for example:
+ * image/gif should match image
+ */
+static int half_match(char *trial_type, char *target)
+{
+ char *cp = StrChr(trial_type, '/');
+
+ /* if no '/' or no '*' */
+ if (!cp || *(cp + 1) != '*')
+ return 0;
+
+ CTRACE((tfp, "HTFormat: comparing %s and %s for half match\n",
+ trial_type, target));
+
+ /* main type matches */
+ if (!StrNCmp(trial_type, target, ((cp - trial_type) - 1)))
+ return 1;
+
+ return 0;
+}
+
+/*
+ * Evaluate a deferred mailcap test command, i.e.,. one that substitutes the
+ * document's charset or other values in %{name} format.
+ */
+static BOOL failsMailcap(HTPresentation *pres, HTParentAnchor *anchor)
+{
+ if (pres->testcommand != NULL &&
+ anchor != NULL &&
+ anchor->content_type_params != NULL) {
+ if (LYTestMailcapCommand(pres->testcommand,
+ anchor->content_type_params) != 0)
+ return TRUE;
+ }
+ return FALSE;
+}
+
+#define WWW_WILDCARD_REP_OUT HTAtom_for("*")
+
+/* Look up a presentation
+ * ----------------------
+ *
+ * If fill_in is NULL, only look for an exact match.
+ * If a wildcard match is made, *fill_in is used to store
+ * a possibly modified presentation, and a pointer to it is
+ * returned. For an exact match, a pointer to the presentation
+ * in the HTPresentations list is returned. Returns NULL if
+ * nothing found. - kw
+ *
+ */
+static HTPresentation *HTFindPresentation(HTFormat rep_in,
+ HTFormat rep_out,
+ HTPresentation *fill_in,
+ HTParentAnchor *anchor)
+{
+#undef THIS_FUNC
+#define THIS_FUNC "HTFindPresentation"
+ HTAtom *wildcard = NULL; /* = HTAtom_for("*"); lookup when needed - kw */
+ int n;
+ int i;
+ HTPresentation *pres;
+ HTPresentation *match;
+ HTPresentation *strong_wildcard_match = 0;
+ HTPresentation *weak_wildcard_match = 0;
+ HTPresentation *last_default_match = 0;
+ HTPresentation *strong_subtype_wildcard_match = 0;
+
+ CTRACE((tfp, THIS_FUNC ": Looking up presentation for %s to %s\n",
+ HTAtom_name(rep_in), HTAtom_name(rep_out)));
+
+ n = HTList_count(HTPresentations);
+ for (i = 0; i < n; i++) {
+ pres = (HTPresentation *) HTList_objectAt(HTPresentations, i);
+ if (pres->rep == rep_in) {
+ if (pres->rep_out == rep_out) {
+ if (failsMailcap(pres, anchor))
+ continue;
+ CTRACE((tfp, THIS_FUNC ": found exact match: %s -> %s\n",
+ HTAtom_name(pres->rep),
+ HTAtom_name(pres->rep_out)));
+ return pres;
+
+ } else if (!fill_in) {
+ continue;
+ } else {
+ if (!wildcard)
+ wildcard = WWW_WILDCARD_REP_OUT;
+ if (pres->rep_out == wildcard) {
+ if (failsMailcap(pres, anchor))
+ continue;
+ if (!strong_wildcard_match)
+ strong_wildcard_match = pres;
+ /* otherwise use the first one */
+ CTRACE((tfp, THIS_FUNC
+ ": found strong wildcard match: %s -> %s\n",
+ HTAtom_name(pres->rep),
+ HTAtom_name(pres->rep_out)));
+ }
+ }
+
+ } else if (!fill_in) {
+ continue;
+
+ } else if (half_match(HTAtom_name(pres->rep),
+ HTAtom_name(rep_in))) {
+ if (pres->rep_out == rep_out) {
+ if (failsMailcap(pres, anchor))
+ continue;
+ if (!strong_subtype_wildcard_match)
+ strong_subtype_wildcard_match = pres;
+ /* otherwise use the first one */
+ CTRACE((tfp, THIS_FUNC
+ ": found strong subtype wildcard match: %s -> %s\n",
+ HTAtom_name(pres->rep),
+ HTAtom_name(pres->rep_out)));
+ }
+ }
+
+ if (pres->rep == WWW_SOURCE) {
+ if (pres->rep_out == rep_out) {
+ if (failsMailcap(pres, anchor))
+ continue;
+ if (!weak_wildcard_match)
+ weak_wildcard_match = pres;
+ /* otherwise use the first one */
+ CTRACE((tfp,
+ THIS_FUNC ": found weak wildcard match: %s\n",
+ HTAtom_name(pres->rep_out)));
+
+ } else if (!last_default_match) {
+ if (!wildcard)
+ wildcard = WWW_WILDCARD_REP_OUT;
+ if (pres->rep_out == wildcard) {
+ if (failsMailcap(pres, anchor))
+ continue;
+ last_default_match = pres;
+ /* otherwise use the first one */
+ }
+ }
+ }
+ }
+
+ match = (strong_subtype_wildcard_match
+ ? strong_subtype_wildcard_match
+ : (strong_wildcard_match
+ ? strong_wildcard_match
+ : (weak_wildcard_match
+ ? weak_wildcard_match
+ : last_default_match)));
+
+ if (match) {
+ *fill_in = *match; /* Specific instance */
+ fill_in->rep = rep_in; /* yuk */
+ fill_in->rep_out = rep_out; /* yuk */
+ return fill_in;
+ }
+
+ return NULL;
+#undef THIS_FUNC
+}
+
+/* Create a filter stack
+ * ---------------------
+ *
+ * If a wildcard match is made, a temporary HTPresentation
+ * structure is made to hold the destination format while the
+ * new stack is generated. This is just to pass the out format to
+ * MIME so far. Storing the format of a stream in the stream might
+ * be a lot neater.
+ *
+ */
+HTStream *HTStreamStack(HTFormat rep_in,
+ HTFormat rep_out,
+ HTStream *sink,
+ HTParentAnchor *anchor)
+{
+#undef THIS_FUNC
+#define THIS_FUNC "HTStreamStack"
+ HTPresentation temp;
+ HTPresentation *match;
+ HTStream *result;
+
+ CTRACE((tfp, THIS_FUNC ": Constructing stream stack for %s to %s (%s)\n",
+ HTAtom_name(rep_in),
+ HTAtom_name(rep_out),
+ NONNULL(anchor->content_type_params)));
+
+ if (rep_out == rep_in) {
+ result = sink;
+
+ } else if ((match = HTFindPresentation(rep_in, rep_out, &temp, anchor))) {
+ if (match == &temp) {
+ CTRACE((tfp, THIS_FUNC ": Using %s\n", HTAtom_name(temp.rep_out)));
+ } else {
+ CTRACE((tfp, THIS_FUNC ": found exact match: %s -> %s\n",
+ HTAtom_name(match->rep),
+ HTAtom_name(match->rep_out)));
+ }
+ result = (*match->converter) (match, anchor, sink);
+ } else {
+ result = NULL;
+ }
+ if (TRACE) {
+ if (result && result->isa && result->isa->name) {
+ CTRACE((tfp, THIS_FUNC ": Returning \"%s\"\n", result->isa->name));
+ } else if (result) {
+ CTRACE((tfp, THIS_FUNC ": Returning *unknown* stream!\n"));
+ } else {
+ CTRACE((tfp, THIS_FUNC ": Returning NULL!\n"));
+ CTRACE_FLUSH(tfp); /* a crash may be imminent... - kw */
+ }
+ }
+ return result;
+#undef THIS_FUNC
+}
+
+/* Put a presentation near start of list
+ * -------------------------------------
+ *
+ * Look up a presentation (exact match only) and, if found, reorder
+ * it to the start of the HTPresentations list. - kw
+ */
+void HTReorderPresentation(HTFormat rep_in,
+ HTFormat rep_out)
+{
+ HTPresentation *match;
+
+ if ((match = HTFindPresentation(rep_in, rep_out, NULL, NULL))) {
+ HTList_removeObject(HTPresentations, match);
+ HTList_addObject(HTPresentations, match);
+ }
+}
+
+/*
+ * Setup 'get_accept' flag to denote presentations that are not redundant,
+ * and will be listed in "Accept:" header.
+ */
+void HTFilterPresentations(void)
+{
+ int i, j;
+ int n = HTList_count(HTPresentations);
+ HTPresentation *p, *q;
+ BOOL matched;
+ char *s, *t;
+
+ CTRACE((tfp, "HTFilterPresentations (AcceptMedia %#x)\n", LYAcceptMedia));
+ for (i = 0; i < n; i++) {
+ p = (HTPresentation *) HTList_objectAt(HTPresentations, i);
+ s = HTAtom_name(p->rep);
+
+ p->get_accept = FALSE;
+ if ((LYAcceptMedia & p->accept_opt) != 0
+ && p->rep_out == WWW_PRESENT
+ && p->rep != WWW_SOURCE
+ && strcasecomp(s, "www/mime")
+ && strcasecomp(s, "www/compressed")
+ && p->quality <= 1.0 && p->quality >= 0.0) {
+ matched = TRUE;
+ for (j = 0; j < i; j++) {
+ q = (HTPresentation *) HTList_objectAt(HTPresentations, j);
+ t = HTAtom_name(q->rep);
+
+ if (!strcasecomp(s, t)) {
+ matched = FALSE;
+ CTRACE((tfp, " match %s %s\n", s, t));
+ break;
+ }
+ }
+ p->get_accept = matched;
+ }
+ }
+}
+
+/* Find the cost of a filter stack
+ * -------------------------------
+ *
+ * Must return the cost of the same stack which HTStreamStack would set up.
+ *
+ * On entry,
+ * length The size of the data to be converted
+ */
+float HTStackValue(HTFormat rep_in,
+ HTFormat rep_out,
+ double initial_value,
+ long int length)
+{
+ HTAtom *wildcard = WWW_WILDCARD_REP_OUT;
+
+ CTRACE((tfp, "HTFormat: Evaluating stream stack for %s worth %.3f to %s\n",
+ HTAtom_name(rep_in), initial_value, HTAtom_name(rep_out)));
+
+ if (rep_out == WWW_SOURCE || rep_out == rep_in)
+ return 0.0;
+
+ {
+ int n = HTList_count(HTPresentations);
+ int i;
+ HTPresentation *pres;
+
+ for (i = 0; i < n; i++) {
+ pres = (HTPresentation *) HTList_objectAt(HTPresentations, i);
+ if (pres->rep == rep_in &&
+ (pres->rep_out == rep_out || pres->rep_out == wildcard)) {
+ float value = (float) (initial_value * pres->quality);
+
+ if (HTMaxSecs > 0.0)
+ value = (value
+ - ((float) length * pres->secs_per_byte
+ + pres->secs)
+ / HTMaxSecs);
+ return value;
+ }
+ }
+ }
+
+ return (float) -1e30; /* Really bad */
+
+}
+
+/* Display the page while transfer in progress
+ * -------------------------------------------
+ *
+ * Repaint the page only when necessary.
+ * This is a traverse call for HText_pageDisplay() - it works!.
+ *
+ */
+void HTDisplayPartial(void)
+{
+#ifdef DISP_PARTIAL
+ if (display_partial) {
+ /*
+ * HText_getNumOfLines() = "current" number of complete lines received
+ * NumOfLines_partial = number of lines at the moment of last repaint.
+ * (we update NumOfLines_partial only when we repaint the display.)
+ *
+ * display_partial could only be enabled in HText_new() so a new
+ * HTMainText object available - all HText_ functions use it, lines
+ * counter HText_getNumOfLines() in particular.
+ *
+ * Otherwise HTMainText holds info from the previous document and we
+ * may repaint it instead of the new one: prev doc scrolled to the
+ * first line (=Newline_partial) is not good looking :-) 23 Aug 1998
+ * Leonid Pauzner
+ *
+ * So repaint the page only when necessary:
+ */
+ int Newline_partial = LYGetNewline();
+
+ if (((Newline_partial + display_lines) - 1 > NumOfLines_partial)
+ /* current page not complete... */
+ && (partial_threshold > 0 ?
+ ((Newline_partial + partial_threshold) - 1 <=
+ HText_getNumOfLines()) :
+ ((Newline_partial + display_lines) - 1 <= HText_getNumOfLines()))
+ /*
+ * Originally we rendered by increments of 2 lines,
+ * but that got annoying on slow network connections.
+ * Then we switched to full-pages. Now it's configurable.
+ * If partial_threshold <= 0, then it's a full page
+ */
+ ) {
+ if (LYMainLoop_pageDisplay(Newline_partial))
+ NumOfLines_partial = HText_getNumOfLines();
+ }
+ }
+#else /* nothing */
+#endif /* DISP_PARTIAL */
+}
+
+/* Put this as early as possible, OK just after HTDisplayPartial() */
+void HTFinishDisplayPartial(void)
+{
+#ifdef DISP_PARTIAL
+ /*
+ * End of incremental rendering stage here.
+ */
+ display_partial = FALSE;
+#endif /* DISP_PARTIAL */
+}
+
+/* Push data from a socket down a stream
+ * -------------------------------------
+ *
+ * This routine is responsible for creating and PRESENTING any
+ * graphic (or other) objects described by the file.
+ *
+ * The file number given is assumed to be a TELNET stream, i.e., containing
+ * CRLF at the end of lines which need to be stripped to LF for unix
+ * when the format is textual.
+ *
+ * State of socket and target stream on entry:
+ * socket (file_number) assumed open,
+ * target (sink) assumed valid.
+ *
+ * Return values:
+ * HT_INTERRUPTED Interruption or error after some data received.
+ * -2 Unexpected disconnect before any data received.
+ * -1 Interruption or error before any data received, or
+ * (UNIX) other read error before any data received, or
+ * download cancelled.
+ * HT_LOADED Normal close of socket (end of file indication
+ * received), or
+ * unexpected disconnect after some data received, or
+ * other read error after some data received, or
+ * (not UNIX) other read error before any data received.
+ *
+ * State of socket and target stream on return depends on return value:
+ * HT_INTERRUPTED socket still open, target aborted.
+ * -2 socket still open, target stream still valid.
+ * -1 socket still open, target aborted.
+ * otherwise socket closed, target stream still valid.
+ */
+int HTCopy(HTParentAnchor *anchor,
+ int file_number,
+ void *handle GCC_UNUSED,
+ HTStream *sink)
+{
+ HTStreamClass targetClass;
+ BOOL suppress_readprogress = NO;
+ off_t limit = anchor ? anchor->content_length : 0;
+ off_t bytes = 0;
+ off_t header_length = 0;
+ int rv = 0;
+
+ /* Push the data down the stream
+ */
+ targetClass = *(sink->isa); /* Copy pointers to procedures */
+
+ /*
+ * Push binary from socket down sink
+ *
+ * This operation could be put into a main event loop
+ */
+ HTReadProgress(bytes, (off_t) 0);
+ for (;;) {
+ int status;
+
+ if (LYCancelDownload) {
+ LYCancelDownload = FALSE;
+ (*targetClass._abort) (sink, NULL);
+ rv = -1;
+ goto finished;
+ }
+
+ if (HTCheckForInterrupt()) {
+ _HTProgress(TRANSFER_INTERRUPTED);
+ (*targetClass._abort) (sink, NULL);
+ if (bytes)
+ rv = HT_INTERRUPTED;
+ else
+ rv = -1;
+ goto finished;
+ }
+#ifdef USE_SSL
+ if (handle)
+ status = SSL_read((SSL *) handle, input_buffer, INPUT_BUFFER_SIZE);
+ else
+ status = NETREAD(file_number, input_buffer, INPUT_BUFFER_SIZE);
+#else
+ status = NETREAD(file_number, input_buffer, INPUT_BUFFER_SIZE);
+#endif /* USE_SSL */
+ if (status <= 0) {
+ if (status == 0) {
+ break;
+ } else if (status == HT_INTERRUPTED) {
+ _HTProgress(TRANSFER_INTERRUPTED);
+ (*targetClass._abort) (sink, NULL);
+ if (bytes)
+ rv = HT_INTERRUPTED;
+ else
+ rv = -1;
+ goto finished;
+ } else if (SOCKET_ERRNO == ENOTCONN ||
+#ifdef _WINDOWS /* 1997/11/10 (Mon) 16:57:18 */
+ SOCKET_ERRNO == ETIMEDOUT ||
+#endif
+ SOCKET_ERRNO == ECONNRESET ||
+ SOCKET_ERRNO == EPIPE) {
+ /*
+ * Arrrrgh, HTTP 0/1 compatibility problem, maybe.
+ */
+ if (bytes <= 0) {
+ /*
+ * Don't have any data, so let the calling function decide
+ * what to do about it. - FM
+ */
+ rv = -2;
+ goto finished;
+ } else {
+#ifdef UNIX
+ /*
+ * Treat what we've received already as the complete
+ * transmission, but not without giving the user an alert.
+ * I don't know about all the different TCP stacks for VMS
+ * etc., so this is currently only for UNIX. - kw
+ */
+ HTInetStatus("NETREAD");
+ HTAlert("Unexpected server disconnect.");
+ CTRACE((tfp,
+ "HTCopy: Unexpected server disconnect. Treating as completed.\n"));
+#else /* !UNIX */
+ /*
+ * Treat what we've gotten already as the complete
+ * transmission. - FM
+ */
+ CTRACE((tfp,
+ "HTCopy: Unexpected server disconnect. Treating as completed.\n"));
+ status = 0;
+#endif /* UNIX */
+ }
+#ifdef UNIX
+ } else { /* status < 0 and other errno */
+ /*
+ * Treat what we've received already as the complete
+ * transmission, but not without giving the user an alert. I
+ * don't know about all the different TCP stacks for VMS etc.,
+ * so this is currently only for UNIX. - kw
+ */
+ HTInetStatus("NETREAD");
+ HTAlert("Unexpected read error.");
+ if (bytes) {
+ (void) NETCLOSE(file_number);
+ rv = HT_LOADED;
+ } else {
+ (*targetClass._abort) (sink, NULL);
+ rv = -1;
+ }
+ goto finished;
+#endif
+ }
+ break;
+ }
+
+ /*
+ * Suppress ReadProgress messages when collecting a redirection
+ * message, at least initially (unless/until anchor->content_type gets
+ * changed, probably by the MIME message parser). That way messages
+ * put up by the HTTP module or elsewhere can linger in the statusline
+ * for a while. - kw
+ */
+ suppress_readprogress = (BOOL) (anchor && anchor->content_type &&
+ !strcmp(anchor->content_type,
+ "message/x-http-redirection"));
+#ifdef NOT_ASCII
+ {
+ char *p;
+
+ for (p = input_buffer; p < input_buffer + status; p++) {
+ *p = FROMASCII(*p);
+ }
+ }
+#endif /* NOT_ASCII */
+
+ header_length = anchor != 0 ? anchor->header_length : 0;
+
+ (*targetClass.put_block) (sink, input_buffer, status);
+ if (anchor != 0 && anchor->inHEAD) {
+ if (!suppress_readprogress) {
+ statusline(gettext("Reading headers..."));
+ }
+ CTRACE((tfp, "HTCopy read %" PRI_off_t " header bytes\n",
+ CAST_off_t (anchor->header_length)));
+ } else {
+ /*
+ * If header-length is increased at this point, that is due to
+ * HTMIME, which detects the end of the server headers. There
+ * may be additional (non-header) data in that block.
+ */
+ if (anchor != 0 && (anchor->header_length > header_length)) {
+ int header = (int) (anchor->header_length - header_length);
+
+ CTRACE((tfp, "HTCopy read %" PRI_off_t " header bytes "
+ "(%d extra vs %d total)\n",
+ CAST_off_t (anchor->header_length),
+ header, status));
+ if (status > header) {
+ bytes += (status - header);
+ }
+ } else {
+ bytes += status;
+ }
+ if (!suppress_readprogress) {
+ HTReadProgress(bytes, limit);
+ }
+ HTDisplayPartial();
+ }
+
+ /* a few buggy implementations do not close the connection properly
+ * and will hang if we try to read past the declared content-length.
+ */
+ if (limit > 0 && bytes >= limit)
+ break;
+ } /* next bufferload */
+ if (anchor != 0) {
+ CTRACE((tfp, "HTCopy copied %"
+ PRI_off_t " actual, %"
+ PRI_off_t " limit\n", CAST_off_t (bytes), CAST_off_t (limit)));
+ anchor->actual_length = bytes;
+ }
+
+ _HTProgress(TRANSFER_COMPLETE);
+ (void) NETCLOSE(file_number);
+ rv = HT_LOADED;
+
+ finished:
+ HTFinishDisplayPartial();
+ return (rv);
+}
+
+/* Push data from a file pointer down a stream
+ * -------------------------------------
+ *
+ * This routine is responsible for creating and PRESENTING any
+ * graphic (or other) objects described by the file.
+ *
+ *
+ * State of file and target stream on entry:
+ * FILE* (fp) assumed open,
+ * target (sink) assumed valid.
+ *
+ * Return values:
+ * HT_INTERRUPTED Interruption after some data read.
+ * HT_PARTIAL_CONTENT Error after some data read.
+ * -1 Error before any data read.
+ * HT_LOADED Normal end of file indication on reading.
+ *
+ * State of file and target stream on return:
+ * always fp still open, target stream still valid.
+ */
+int HTFileCopy(FILE *fp, HTStream *sink)
+{
+ HTStreamClass targetClass;
+ int status;
+ off_t bytes;
+ int rv = HT_OK;
+
+ /* Push the data down the stream
+ */
+ targetClass = *(sink->isa); /* Copy pointers to procedures */
+
+ /* Push binary from socket down sink
+ */
+ HTReadProgress(bytes = 0, (off_t) 0);
+ for (;;) {
+ status = (int) fread(input_buffer,
+ (size_t) 1,
+ (size_t) INPUT_BUFFER_SIZE, fp);
+ if (status == 0) { /* EOF or error */
+ if (ferror(fp) == 0) {
+ rv = HT_LOADED;
+ break;
+ }
+ CTRACE((tfp, "HTFormat: Read error, read returns %d\n",
+ ferror(fp)));
+ if (bytes) {
+ rv = HT_PARTIAL_CONTENT;
+ } else {
+ rv = -1;
+ }
+ break;
+ }
+
+ (*targetClass.put_block) (sink, input_buffer, status);
+ bytes += status;
+ HTReadProgress(bytes, (off_t) 0);
+ /* Suppress last screen update in partial mode - a regular update under
+ * control of mainloop() should follow anyway. - kw
+ */
+#ifdef DISP_PARTIAL
+ if (display_partial && bytes != HTMainAnchor->content_length)
+ HTDisplayPartial();
+#endif
+
+ if (HTCheckForInterrupt()) {
+ _HTProgress(TRANSFER_INTERRUPTED);
+ if (bytes) {
+ rv = HT_INTERRUPTED;
+ } else {
+ rv = -1;
+ }
+ break;
+ }
+ } /* next bufferload */
+
+ HTFinishDisplayPartial();
+ return rv;
+}
+
+#ifdef USE_SOURCE_CACHE
+/* Push data from an HTChunk down a stream
+ * ---------------------------------------
+ *
+ * This routine is responsible for creating and PRESENTING any
+ * graphic (or other) objects described by the file.
+ *
+ * State of memory and target stream on entry:
+ * HTChunk* (chunk) and target (sink) assumed valid.
+ *
+ * Return values:
+ * HT_LOADED All data sent.
+ * HT_INTERRUPTED Interruption after some data read.
+ *
+ * State of memory and target stream on return:
+ * always chunk unchanged, target stream still valid.
+ */
+int HTMemCopy(HTChunk *chunk, HTStream *sink)
+{
+ HTStreamClass targetClass;
+ off_t bytes;
+ int rv = HT_OK;
+
+ targetClass = *(sink->isa);
+ HTReadProgress(bytes = 0, (off_t) 0);
+ for (; chunk != NULL; chunk = chunk->next) {
+
+ /* Push the data down the stream a piece at a time, in case we're
+ * running a large document on a slow machine.
+ */
+ (*targetClass.put_block) (sink, chunk->data, chunk->size);
+ bytes += chunk->size;
+
+ HTReadProgress(bytes, (off_t) 0);
+ HTDisplayPartial();
+
+ if (HTCheckForInterrupt()) {
+ _HTProgress(TRANSFER_INTERRUPTED);
+ if (bytes) {
+ rv = HT_INTERRUPTED;
+ } else {
+ rv = -1;
+ }
+ break;
+ }
+ }
+
+ HTFinishDisplayPartial();
+ return rv;
+}
+#endif
+
+#ifdef USE_ZLIB
+/* Push data from a gzip file pointer down a stream
+ * -------------------------------------
+ *
+ * This routine is responsible for creating and PRESENTING any
+ * graphic (or other) objects described by the file.
+ *
+ *
+ * State of file and target stream on entry:
+ * gzFile (gzfp) assumed open (should have gzipped content),
+ * target (sink) assumed valid.
+ *
+ * Return values:
+ * HT_INTERRUPTED Interruption after some data read.
+ * HT_PARTIAL_CONTENT Error after some data read.
+ * -1 Error before any data read.
+ * HT_LOADED Normal end of file indication on reading.
+ *
+ * State of file and target stream on return:
+ * always gzfp still open, target stream still valid.
+ */
+static int HTGzFileCopy(gzFile gzfp, HTStream *sink)
+{
+ HTStreamClass targetClass;
+ int status;
+ off_t bytes;
+ int gzerrnum;
+ int rv = HT_OK;
+
+ /* Push the data down the stream
+ */
+ targetClass = *(sink->isa); /* Copy pointers to procedures */
+
+ /* read and inflate gzip'd file, and push binary down sink
+ */
+ HTReadProgress(bytes = 0, (off_t) 0);
+ for (;;) {
+ status = gzread(gzfp, input_buffer, INPUT_BUFFER_SIZE);
+ if (status <= 0) { /* EOF or error */
+ if (status == 0) {
+ rv = HT_LOADED;
+ break;
+ }
+ CTRACE((tfp, "HTGzFileCopy: Read error, gzread returns %d\n",
+ status));
+ CTRACE((tfp, "gzerror : %s\n",
+ gzerror(gzfp, &gzerrnum)));
+ if (TRACE) {
+ if (gzerrnum == Z_ERRNO)
+ perror("gzerror ");
+ }
+ if (bytes) {
+ rv = HT_PARTIAL_CONTENT;
+ } else {
+ rv = -1;
+ }
+ break;
+ }
+
+ (*targetClass.put_block) (sink, input_buffer, status);
+ bytes += status;
+ HTReadProgress(bytes, (off_t) -1);
+ HTDisplayPartial();
+
+ if (HTCheckForInterrupt()) {
+ _HTProgress(TRANSFER_INTERRUPTED);
+ rv = HT_INTERRUPTED;
+ break;
+ }
+ } /* next bufferload */
+
+ HTFinishDisplayPartial();
+ return rv;
+}
+
+#ifndef HAVE_ZERROR
+#define zError(s) LynxZError(s)
+static const char *zError(int status)
+{
+ static char result[80];
+
+ sprintf(result, "zlib error %d", status);
+ return result;
+}
+#endif
+
+/* Push data from a deflate file pointer down a stream
+ * -------------------------------------
+ *
+ * This routine is responsible for creating and PRESENTING any
+ * graphic (or other) objects described by the file. The code is
+ * loosely based on the inflate.c file from w3m.
+ *
+ *
+ * State of file and target stream on entry:
+ * FILE (zzfp) assumed open (should have deflated content),
+ * target (sink) assumed valid.
+ *
+ * Return values:
+ * HT_INTERRUPTED Interruption after some data read.
+ * HT_PARTIAL_CONTENT Error after some data read.
+ * -1 Error before any data read.
+ * HT_LOADED Normal end of file indication on reading.
+ *
+ * State of file and target stream on return:
+ * always zzfp still open, target stream still valid.
+ */
+static int HTZzFileCopy(FILE *zzfp, HTStream *sink)
+{
+#undef THIS_FUNC
+#define THIS_FUNC "HTZzFileCopy"
+ static char dummy_head[1 + 1] =
+ {
+ 0x8 + 0x7 * 0x10,
+ (((0x8 + 0x7 * 0x10) * 0x100 + 30) / 31 * 31) & 0xFF,
+ };
+
+ z_stream s;
+ HTStreamClass targetClass;
+ off_t bytes;
+ int rv = HT_OK;
+ char output_buffer[INPUT_BUFFER_SIZE];
+ int status;
+ int flush;
+ int retry = 0;
+ int len = 0;
+
+ /* Push the data down the stream
+ */
+ targetClass = *(sink->isa); /* Copy pointers to procedures */
+
+ memset(&s, 0, sizeof(s));
+ status = inflateInit(&s);
+ if (status != Z_OK) {
+ CTRACE((tfp, THIS_FUNC " inflateInit() %s\n", zError(status)));
+ exit_immediately(EXIT_FAILURE);
+ }
+ s.avail_in = 0;
+ s.next_out = (Bytef *) output_buffer;
+ s.avail_out = sizeof(output_buffer);
+ flush = Z_NO_FLUSH;
+
+ /* read and inflate deflate'd file, and push binary down sink
+ */
+ HTReadProgress(bytes = 0, (off_t) 0);
+ for (;;) {
+ if (s.avail_in == 0) {
+ s.next_in = (Bytef *) input_buffer;
+ s.avail_in = (uInt) fread(input_buffer,
+ (size_t) 1,
+ (size_t) INPUT_BUFFER_SIZE, zzfp);
+ len = (int) s.avail_in;
+ }
+ status = inflate(&s, flush);
+ if (status == Z_STREAM_END || status == Z_BUF_ERROR) {
+ len = (int) sizeof(output_buffer) - (int) s.avail_out;
+ if (len > 0) {
+ (*targetClass.put_block) (sink, output_buffer, len);
+ bytes += len;
+ HTReadProgress(bytes, (off_t) -1);
+ HTDisplayPartial();
+ }
+ rv = HT_LOADED;
+ break;
+ } else if (status == Z_DATA_ERROR && !retry++) {
+ status = inflateReset(&s);
+ if (status != Z_OK) {
+ CTRACE((tfp, THIS_FUNC " inflateReset() %s\n", zError(status)));
+ rv = -1;
+ break;
+ }
+ s.next_in = (Bytef *) dummy_head;
+ s.avail_in = sizeof(dummy_head);
+ (void) inflate(&s, flush);
+ s.next_in = (Bytef *) input_buffer;
+ s.avail_in = (unsigned) len;
+ continue;
+ } else if (status != Z_OK) {
+ CTRACE((tfp, THIS_FUNC " inflate() %s\n", zError(status)));
+ rv = bytes ? HT_PARTIAL_CONTENT : -1;
+ break;
+ } else if (s.avail_out == 0) {
+ len = sizeof(output_buffer);
+ s.next_out = (Bytef *) output_buffer;
+ s.avail_out = sizeof(output_buffer);
+
+ (*targetClass.put_block) (sink, output_buffer, len);
+ bytes += len;
+ HTReadProgress(bytes, (off_t) -1);
+ HTDisplayPartial();
+
+ if (HTCheckForInterrupt()) {
+ _HTProgress(TRANSFER_INTERRUPTED);
+ rv = bytes ? HT_INTERRUPTED : -1;
+ break;
+ }
+ }
+ retry = 1;
+ } /* next bufferload */
+
+ inflateEnd(&s);
+ HTFinishDisplayPartial();
+ return rv;
+#undef THIS_FUNC
+}
+#endif /* USE_ZLIB */
+
+#ifdef USE_BZLIB
+/* Push data from a bzip file pointer down a stream
+ * -------------------------------------
+ *
+ * This routine is responsible for creating and PRESENTING any
+ * graphic (or other) objects described by the file.
+ *
+ *
+ * State of file and target stream on entry:
+ * BZFILE (bzfp) assumed open (should have bzipped content),
+ * target (sink) assumed valid.
+ *
+ * Return values:
+ * HT_INTERRUPTED Interruption after some data read.
+ * HT_PARTIAL_CONTENT Error after some data read.
+ * -1 Error before any data read.
+ * HT_LOADED Normal end of file indication on reading.
+ *
+ * State of file and target stream on return:
+ * always bzfp still open, target stream still valid.
+ */
+static int HTBzFileCopy(BZFILE * bzfp, HTStream *sink)
+{
+ HTStreamClass targetClass;
+ int status;
+ off_t bytes;
+ int bzerrnum;
+ int rv = HT_OK;
+
+ /* Push the data down the stream
+ */
+ targetClass = *(sink->isa); /* Copy pointers to procedures */
+
+ /* read and inflate bzip'd file, and push binary down sink
+ */
+ HTReadProgress(bytes = 0, (off_t) 0);
+ for (;;) {
+ status = BZ2_bzread(bzfp, input_buffer, INPUT_BUFFER_SIZE);
+ if (status <= 0) { /* EOF or error */
+ if (status == 0) {
+ rv = HT_LOADED;
+ break;
+ }
+ CTRACE((tfp, "HTBzFileCopy: Read error, bzread returns %d\n",
+ status));
+ CTRACE((tfp, "bzerror : %s\n",
+ BZ2_bzerror(bzfp, &bzerrnum)));
+ if (bytes) {
+ rv = HT_PARTIAL_CONTENT;
+ } else {
+ rv = -1;
+ }
+ break;
+ }
+
+ (*targetClass.put_block) (sink, input_buffer, status);
+ bytes += status;
+ HTReadProgress(bytes, (off_t) -1);
+ HTDisplayPartial();
+
+ if (HTCheckForInterrupt()) {
+ _HTProgress(TRANSFER_INTERRUPTED);
+ rv = HT_INTERRUPTED;
+ break;
+ }
+ } /* next bufferload */
+
+ HTFinishDisplayPartial();
+ return rv;
+}
+#endif /* USE_BZLIB */
+
+#ifdef USE_BROTLI
+/* Push data from a brotli file pointer down a stream
+ * -------------------------------------
+ *
+ * This routine is responsible for creating and PRESENTING any
+ * graphic (or other) objects described by the file.
+ *
+ *
+ * State of file and target stream on entry:
+ * BZFILE (bzfp) assumed open (should have bzipped content),
+ * target (sink) assumed valid.
+ *
+ * Return values:
+ * HT_INTERRUPTED Interruption after some data read.
+ * HT_PARTIAL_CONTENT Error after some data read.
+ * -1 Error before any data read.
+ * HT_LOADED Normal end of file indication on reading.
+ *
+ * State of file and target stream on return:
+ * always bzfp still open, target stream still valid.
+ */
+static int HTBrFileCopy(FILE *brfp, HTStream *sink)
+{
+#undef THIS_FUNC
+#define THIS_FUNC "HTBrFileCopy"
+ HTStreamClass targetClass;
+ int status;
+ off_t bytes;
+ int rv = HT_OK;
+ BrotliDecoderResult status2 = BROTLI_DECODER_RESULT_ERROR;
+
+ char *brotli_buffer = NULL;
+ char *normal_buffer = NULL;
+ size_t brotli_size;
+ size_t brotli_limit = 0;
+ size_t brotli_offset = brotli_limit;
+ size_t normal_size;
+ size_t normal_limit = 0;
+
+ /* Push the data down the stream
+ */
+ targetClass = *(sink->isa); /* Copy pointers to procedures */
+
+ /* read and inflate brotli'd file, and push binary down sink
+ */
+ HTReadProgress(bytes = 0, (off_t) 0);
+ /*
+ * first, read all of the brotli'd file into memory, to work with the
+ * library's limitations.
+ */
+ for (;;) {
+ size_t input_chunk = INPUT_BUFFER_SIZE;
+
+ brotli_offset = brotli_limit;
+ brotli_limit += input_chunk;
+ brotli_buffer = realloc(brotli_buffer, brotli_limit);
+ if (brotli_buffer == NULL)
+ outofmem(__FILE__, THIS_FUNC);
+ status = (int) fread(brotli_buffer + brotli_offset, sizeof(char),
+ input_chunk, brfp);
+
+ if (status <= 0) { /* EOF or error */
+ if (status == 0) {
+ rv = HT_LOADED;
+ break;
+ }
+ CTRACE((tfp, THIS_FUNC ": Read error, fread returns %d\n", status));
+ if (bytes) {
+ if (!feof(brfp))
+ rv = HT_PARTIAL_CONTENT;
+ } else {
+ rv = -1;
+ }
+ break;
+ }
+ bytes += status;
+ }
+
+ /*
+ * next, unless we encountered an error (and have no data), try
+ * decompressing with increasing output buffer sizes until the brotli
+ * library succeeds.
+ */
+ if (bytes > 0) {
+ do {
+ if (normal_limit == 0)
+ normal_limit = (10 * brotli_limit) + INPUT_BUFFER_SIZE;
+ else
+ normal_limit *= 2;
+ normal_buffer = realloc(normal_buffer, normal_limit);
+ if (normal_buffer == NULL)
+ outofmem(__FILE__, THIS_FUNC);
+ brotli_size = (size_t) bytes;
+ normal_size = normal_limit;
+ status2 = BrotliDecoderDecompress(brotli_size,
+ (uint8_t *) brotli_buffer,
+ &normal_size,
+ (uint8_t *) normal_buffer);
+ /*
+ * brotli library should return
+ * BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT,
+ * but actually returns
+ * BROTLI_DECODER_RESULT_ERROR
+ *
+ * Accommodate possible improvements...
+ */
+ } while (status2 != BROTLI_DECODER_RESULT_SUCCESS);
+ }
+
+ /*
+ * finally, pump that data into the output stream.
+ */
+ if (status2 == BROTLI_DECODER_RESULT_SUCCESS) {
+ CTRACE((tfp, THIS_FUNC ": decompressed %ld -> %ld (1:%.1f)\n",
+ brotli_size, normal_size,
+ (double) normal_size / (double) brotli_size));
+ (*targetClass.put_block) (sink, normal_buffer, (int) normal_size);
+ bytes += status;
+ HTReadProgress(bytes, (off_t) -1);
+ HTDisplayPartial();
+
+ if (HTCheckForInterrupt()) {
+ _HTProgress(TRANSFER_INTERRUPTED);
+ rv = HT_INTERRUPTED;
+ }
+ }
+ free(brotli_buffer);
+ free(normal_buffer);
+
+ /* next bufferload */
+ HTFinishDisplayPartial();
+ return rv;
+#undef THIS_FUNC
+}
+#endif /* USE_BZLIB */
+
+/* Push data from a socket down a stream STRIPPING CR
+ * --------------------------------------------------
+ *
+ * This routine is responsible for creating and PRESENTING any
+ * graphic (or other) objects described by the socket.
+ *
+ * The file number given is assumed to be a TELNET stream ie containing
+ * CRLF at the end of lines which need to be stripped to LF for unix
+ * when the format is textual.
+ *
+ */
+void HTCopyNoCR(HTParentAnchor *anchor GCC_UNUSED,
+ int file_number,
+ HTStream *sink)
+{
+ HTStreamClass targetClass;
+ int character;
+
+ /* Push the data, ignoring CRLF, down the stream
+ */
+ targetClass = *(sink->isa); /* Copy pointers to procedures */
+
+ /*
+ * Push text from telnet socket down sink
+ *
+ * @@@@@ To push strings could be faster? (especially is we cheat and
+ * don't ignore CR! :-}
+ */
+ HTInitInput(file_number);
+ for (;;) {
+ character = HTGetCharacter();
+ if (character == EOF)
+ break;
+ (*targetClass.put_character) (sink, (char) character);
+ }
+}
+
+/* Parse a socket given format and file number
+ *
+ * This routine is responsible for creating and PRESENTING any
+ * graphic (or other) objects described by the file.
+ *
+ * The file number given is assumed to be a TELNET stream ie containing
+ * CRLF at the end of lines which need to be stripped to LF for unix
+ * when the format is textual.
+ *
+ * State of socket and target stream on entry:
+ * socket (file_number) assumed open,
+ * target (sink) usually NULL (will call stream stack).
+ *
+ * Return values:
+ * HT_INTERRUPTED Interruption or error after some data received.
+ * -501 Stream stack failed (cannot present or convert).
+ * -2 Unexpected disconnect before any data received.
+ * -1 Stream stack failed (cannot present or convert), or
+ * Interruption or error before any data received, or
+ * (UNIX) other read error before any data received, or
+ * download cancelled.
+ * HT_LOADED Normal close of socket (end of file indication
+ * received), or
+ * unexpected disconnect after some data received, or
+ * other read error after some data received, or
+ * (not UNIX) other read error before any data received.
+ *
+ * State of socket and target stream on return depends on return value:
+ * HT_INTERRUPTED socket still open, target aborted.
+ * -501 socket still open, target stream NULL.
+ * -2 socket still open, target freed.
+ * -1 socket still open, target stream aborted or NULL.
+ * otherwise socket closed, target stream freed.
+ */
+int HTParseSocket(HTFormat rep_in,
+ HTFormat format_out,
+ HTParentAnchor *anchor,
+ int file_number,
+ HTStream *sink)
+{
+ HTStream *stream;
+ HTStreamClass targetClass;
+ int rv;
+
+ stream = HTStreamStack(rep_in, format_out, sink, anchor);
+
+ if (!stream) {
+ char *buffer = 0;
+
+ if (LYCancelDownload) {
+ LYCancelDownload = FALSE;
+ return -1;
+ }
+ HTSprintf0(&buffer, CANNOT_CONVERT_I_TO_O,
+ HTAtom_name(rep_in), HTAtom_name(format_out));
+ CTRACE((tfp, "HTFormat: %s\n", buffer));
+ rv = HTLoadError(sink, 501, buffer); /* returns -501 */
+ FREE(buffer);
+ } else {
+ /*
+ * Push the data, don't worry about CRLF we can strip them later.
+ */
+ targetClass = *(stream->isa); /* Copy pointers to procedures */
+ rv = HTCopy(anchor, file_number, NULL, stream);
+ if (rv != -1 && rv != HT_INTERRUPTED)
+ (*targetClass._free) (stream);
+ }
+ return rv;
+ /* Originally: full: HT_LOADED; partial: HT_INTERRUPTED; no bytes: -1 */
+}
+
+/* Parse a file given format and file pointer
+ *
+ * This routine is responsible for creating and PRESENTING any
+ * graphic (or other) objects described by the file.
+ *
+ * The file number given is assumed to be a TELNET stream ie containing
+ * CRLF at the end of lines which need to be stripped to \n for unix
+ * when the format is textual.
+ *
+ * State of file and target stream on entry:
+ * FILE* (fp) assumed open,
+ * target (sink) usually NULL (will call stream stack).
+ *
+ * Return values:
+ * -501 Stream stack failed (cannot present or convert).
+ * -1 Download cancelled.
+ * HT_NO_DATA Error before any data read.
+ * HT_PARTIAL_CONTENT Interruption or error after some data read.
+ * HT_LOADED Normal end of file indication on reading.
+ *
+ * State of file and target stream on return:
+ * always fp still open; target freed, aborted, or NULL.
+ */
+int HTParseFile(HTFormat rep_in,
+ HTFormat format_out,
+ HTParentAnchor *anchor,
+ FILE *fp,
+ HTStream *sink)
+{
+ HTStream *stream;
+ HTStreamClass targetClass;
+ int rv;
+ int result;
+
+ if (fp == NULL) {
+ result = HT_LOADED;
+ } else {
+ stream = HTStreamStack(rep_in, format_out, sink, anchor);
+
+ if (!stream || !stream->isa) {
+ char *buffer = 0;
+
+ if (LYCancelDownload) {
+ LYCancelDownload = FALSE;
+ result = -1;
+ } else {
+ HTSprintf0(&buffer, CANNOT_CONVERT_I_TO_O,
+ HTAtom_name(rep_in), HTAtom_name(format_out));
+ CTRACE((tfp, "HTFormat(in HTParseFile): %s\n", buffer));
+ rv = HTLoadError(sink, 501, buffer);
+ FREE(buffer);
+ result = rv;
+ }
+ } else {
+
+ /*
+ * Push the data down the stream
+ *
+ * @@ Bug: This decision ought to be made based on "encoding"
+ * rather than on content-type. @@@ When we handle encoding. The
+ * current method smells anyway.
+ */
+ targetClass = *(stream->isa); /* Copy pointers to procedures */
+ rv = HTFileCopy(fp, stream);
+ if (rv == -1 || rv == HT_INTERRUPTED) {
+ (*targetClass._abort) (stream, NULL);
+ } else {
+ (*targetClass._free) (stream);
+ }
+
+ if (rv == -1) {
+ result = HT_NO_DATA;
+ } else if (rv == HT_INTERRUPTED || (rv > 0 && rv != HT_LOADED)) {
+ result = HT_PARTIAL_CONTENT;
+ } else {
+ result = HT_LOADED;
+ }
+ }
+ }
+ return result;
+}
+
+#ifdef USE_SOURCE_CACHE
+/* Parse a document in memory given format and memory block pointer
+ *
+ * This routine is responsible for creating and PRESENTING any
+ * graphic (or other) objects described by the file.
+ *
+ * State of memory and target stream on entry:
+ * HTChunk* (chunk) assumed valid,
+ * target (sink) usually NULL (will call stream stack).
+ *
+ * Return values:
+ * -501 Stream stack failed (cannot present or convert).
+ * HT_LOADED All data sent.
+ *
+ * State of memory and target stream on return:
+ * always chunk unchanged; target freed, aborted, or NULL.
+ */
+int HTParseMem(HTFormat rep_in,
+ HTFormat format_out,
+ HTParentAnchor *anchor,
+ HTChunk *chunk,
+ HTStream *sink)
+{
+ HTStream *stream;
+ HTStreamClass targetClass;
+ int rv;
+ int result;
+
+ stream = HTStreamStack(rep_in, format_out, sink, anchor);
+ if (!stream || !stream->isa) {
+ char *buffer = 0;
+
+ HTSprintf0(&buffer, CANNOT_CONVERT_I_TO_O,
+ HTAtom_name(rep_in), HTAtom_name(format_out));
+ CTRACE((tfp, "HTFormat(in HTParseMem): %s\n", buffer));
+ rv = HTLoadError(sink, 501, buffer);
+ FREE(buffer);
+ result = rv;
+ } else {
+
+ /* Push the data down the stream
+ */
+ targetClass = *(stream->isa);
+ (void) HTMemCopy(chunk, stream);
+ (*targetClass._free) (stream);
+ result = HT_LOADED;
+ }
+ return result;
+}
+#endif
+
+#ifdef USE_ZLIB
+static int HTCloseGzFile(gzFile gzfp)
+{
+ int gzres;
+
+ if (gzfp == NULL)
+ return 0;
+ gzres = gzclose(gzfp);
+ if (TRACE) {
+ if (gzres == Z_ERRNO) {
+ perror("gzclose ");
+ } else if (gzres != Z_OK) {
+ CTRACE((tfp, "gzclose : error number %d\n", gzres));
+ }
+ }
+ return (gzres);
+}
+
+/* HTParseGzFile
+ *
+ * State of file and target stream on entry:
+ * gzFile (gzfp) assumed open,
+ * target (sink) usually NULL (will call stream stack).
+ *
+ * Return values:
+ * -501 Stream stack failed (cannot present or convert).
+ * -1 Download cancelled.
+ * HT_NO_DATA Error before any data read.
+ * HT_PARTIAL_CONTENT Interruption or error after some data read.
+ * HT_LOADED Normal end of file indication on reading.
+ *
+ * State of file and target stream on return:
+ * always gzfp closed; target freed, aborted, or NULL.
+ */
+int HTParseGzFile(HTFormat rep_in,
+ HTFormat format_out,
+ HTParentAnchor *anchor,
+ gzFile gzfp,
+ HTStream *sink)
+{
+ HTStream *stream;
+ HTStreamClass targetClass;
+ int rv;
+ int result;
+
+ stream = HTStreamStack(rep_in, format_out, sink, anchor);
+
+ if (!stream || !stream->isa) {
+ char *buffer = 0;
+
+ HTCloseGzFile(gzfp);
+ if (LYCancelDownload) {
+ LYCancelDownload = FALSE;
+ result = -1;
+ } else {
+ HTSprintf0(&buffer, CANNOT_CONVERT_I_TO_O,
+ HTAtom_name(rep_in), HTAtom_name(format_out));
+ CTRACE((tfp, "HTFormat(in HTParseGzFile): %s\n", buffer));
+ rv = HTLoadError(sink, 501, buffer);
+ FREE(buffer);
+ result = rv;
+ }
+ } else {
+
+ /*
+ * Push the data down the stream
+ *
+ * @@ Bug: This decision ought to be made based on "encoding" rather than
+ * on content-type. @@@ When we handle encoding. The current method
+ * smells anyway.
+ */
+ targetClass = *(stream->isa); /* Copy pointers to procedures */
+ rv = HTGzFileCopy(gzfp, stream);
+ if (rv == -1 || rv == HT_INTERRUPTED) {
+ (*targetClass._abort) (stream, NULL);
+ } else {
+ (*targetClass._free) (stream);
+ }
+
+ HTCloseGzFile(gzfp);
+ if (rv == -1) {
+ result = HT_NO_DATA;
+ } else if (rv == HT_INTERRUPTED || (rv > 0 && rv != HT_LOADED)) {
+ result = HT_PARTIAL_CONTENT;
+ } else {
+ result = HT_LOADED;
+ }
+ }
+ return result;
+}
+
+/* HTParseZzFile
+ *
+ * State of file and target stream on entry:
+ * FILE (zzfp) assumed open,
+ * target (sink) usually NULL (will call stream stack).
+ *
+ * Return values:
+ * -501 Stream stack failed (cannot present or convert).
+ * -1 Download cancelled.
+ * HT_NO_DATA Error before any data read.
+ * HT_PARTIAL_CONTENT Interruption or error after some data read.
+ * HT_LOADED Normal end of file indication on reading.
+ *
+ * State of file and target stream on return:
+ * always zzfp closed; target freed, aborted, or NULL.
+ */
+int HTParseZzFile(HTFormat rep_in,
+ HTFormat format_out,
+ HTParentAnchor *anchor,
+ FILE *zzfp,
+ HTStream *sink)
+{
+ HTStream *stream;
+ HTStreamClass targetClass;
+ int rv;
+ int result;
+
+ stream = HTStreamStack(rep_in, format_out, sink, anchor);
+
+ if (!stream || !stream->isa) {
+ char *buffer = 0;
+
+ fclose(zzfp);
+ if (LYCancelDownload) {
+ LYCancelDownload = FALSE;
+ result = -1;
+ } else {
+ HTSprintf0(&buffer, CANNOT_CONVERT_I_TO_O,
+ HTAtom_name(rep_in), HTAtom_name(format_out));
+ CTRACE((tfp, "HTFormat(in HTParseGzFile): %s\n", buffer));
+ rv = HTLoadError(sink, 501, buffer);
+ FREE(buffer);
+ result = rv;
+ }
+ } else {
+
+ /*
+ * Push the data down the stream
+ *
+ * @@ Bug: This decision ought to be made based on "encoding" rather than
+ * on content-type. @@@ When we handle encoding. The current method
+ * smells anyway.
+ */
+ targetClass = *(stream->isa); /* Copy pointers to procedures */
+ rv = HTZzFileCopy(zzfp, stream);
+ if (rv == -1 || rv == HT_INTERRUPTED) {
+ (*targetClass._abort) (stream, NULL);
+ } else {
+ (*targetClass._free) (stream);
+ }
+
+ fclose(zzfp);
+ if (rv == -1) {
+ result = HT_NO_DATA;
+ } else if (rv == HT_INTERRUPTED || (rv > 0 && rv != HT_LOADED)) {
+ result = HT_PARTIAL_CONTENT;
+ } else {
+ result = HT_LOADED;
+ }
+ }
+ return result;
+}
+#endif /* USE_ZLIB */
+
+#ifdef USE_BZLIB
+static void HTCloseBzFile(BZFILE * bzfp)
+{
+ if (bzfp)
+ BZ2_bzclose(bzfp);
+}
+
+/* HTParseBzFile
+ *
+ * State of file and target stream on entry:
+ * bzFile (bzfp) assumed open,
+ * target (sink) usually NULL (will call stream stack).
+ *
+ * Return values:
+ * -501 Stream stack failed (cannot present or convert).
+ * -1 Download cancelled.
+ * HT_NO_DATA Error before any data read.
+ * HT_PARTIAL_CONTENT Interruption or error after some data read.
+ * HT_LOADED Normal end of file indication on reading.
+ *
+ * State of file and target stream on return:
+ * always bzfp closed; target freed, aborted, or NULL.
+ */
+int HTParseBzFile(HTFormat rep_in,
+ HTFormat format_out,
+ HTParentAnchor *anchor,
+ BZFILE * bzfp,
+ HTStream *sink)
+{
+ HTStream *stream;
+ HTStreamClass targetClass;
+ int rv;
+ int result;
+
+ stream = HTStreamStack(rep_in, format_out, sink, anchor);
+
+ if (!stream || !stream->isa) {
+ char *buffer = 0;
+
+ HTCloseBzFile(bzfp);
+ if (LYCancelDownload) {
+ LYCancelDownload = FALSE;
+ result = -1;
+ } else {
+ HTSprintf0(&buffer, CANNOT_CONVERT_I_TO_O,
+ HTAtom_name(rep_in), HTAtom_name(format_out));
+ CTRACE((tfp, "HTFormat(in HTParseBzFile): %s\n", buffer));
+ rv = HTLoadError(sink, 501, buffer);
+ FREE(buffer);
+ result = rv;
+ }
+ } else {
+
+ /*
+ * Push the data down the stream
+ *
+ * @@ Bug: This decision ought to be made based on "encoding" rather than
+ * on content-type. @@@ When we handle encoding. The current method
+ * smells anyway.
+ */
+ targetClass = *(stream->isa); /* Copy pointers to procedures */
+ rv = HTBzFileCopy(bzfp, stream);
+ if (rv == -1 || rv == HT_INTERRUPTED) {
+ (*targetClass._abort) (stream, NULL);
+ } else {
+ (*targetClass._free) (stream);
+ }
+
+ HTCloseBzFile(bzfp);
+ if (rv == -1) {
+ result = HT_NO_DATA;
+ } else if (rv == HT_INTERRUPTED || (rv > 0 && rv != HT_LOADED)) {
+ result = HT_PARTIAL_CONTENT;
+ } else {
+ result = HT_LOADED;
+ }
+ }
+ return result;
+}
+#endif /* USE_BZLIB */
+
+#ifdef USE_BROTLI
+/* HTParseBrFile
+ *
+ * State of file and target stream on entry:
+ * FILE* (brfp) assumed open,
+ * target (sink) usually NULL (will call stream stack).
+ *
+ * Return values:
+ * -501 Stream stack failed (cannot present or convert).
+ * -1 Download cancelled.
+ * HT_NO_DATA Error before any data read.
+ * HT_PARTIAL_CONTENT Interruption or error after some data read.
+ * HT_LOADED Normal end of file indication on reading.
+ *
+ * State of file and target stream on return:
+ * always brfp closed; target freed, aborted, or NULL.
+ */
+int HTParseBrFile(HTFormat rep_in,
+ HTFormat format_out,
+ HTParentAnchor *anchor,
+ FILE *brfp,
+ HTStream *sink)
+{
+#undef THIS_FUNC
+#define THIS_FUNC "HTParseBrFile"
+ HTStream *stream;
+ HTStreamClass targetClass;
+ int rv;
+ int result;
+
+ stream = HTStreamStack(rep_in, format_out, sink, anchor);
+
+ if (!stream || !stream->isa) {
+ char *buffer = 0;
+
+ fclose(brfp);
+ if (LYCancelDownload) {
+ LYCancelDownload = FALSE;
+ result = -1;
+ } else {
+ HTSprintf0(&buffer, CANNOT_CONVERT_I_TO_O,
+ HTAtom_name(rep_in), HTAtom_name(format_out));
+ CTRACE((tfp, "HTFormat(in " THIS_FUNC "): %s\n", buffer));
+ rv = HTLoadError(sink, 501, buffer);
+ FREE(buffer);
+ result = rv;
+ }
+ } else {
+
+ /*
+ * Push the data down the stream
+ *
+ * @@ Bug: This decision ought to be made based on "encoding" rather than
+ * on content-type. @@@ When we handle encoding. The current method
+ * smells anyway.
+ */
+ targetClass = *(stream->isa); /* Copy pointers to procedures */
+ rv = HTBrFileCopy(brfp, stream);
+ if (rv == -1 || rv == HT_INTERRUPTED) {
+ (*targetClass._abort) (stream, NULL);
+ } else {
+ (*targetClass._free) (stream);
+ }
+
+ fclose(brfp);
+ if (rv == -1) {
+ result = HT_NO_DATA;
+ } else if (rv == HT_INTERRUPTED || (rv > 0 && rv != HT_LOADED)) {
+ result = HT_PARTIAL_CONTENT;
+ } else {
+ result = HT_LOADED;
+ }
+ }
+ return result;
+#undef THIS_FUNC
+}
+#endif /* USE_BROTLI */
+
+/* Converter stream: Network Telnet to internal character text
+ * -----------------------------------------------------------
+ *
+ * The input is assumed to be in ASCII, with lines delimited
+ * by (13,10) pairs, These pairs are converted into (CR,LF)
+ * pairs in the local representation. The (CR,LF) sequence
+ * when found is changed to a '\n' character, the internal
+ * C representation of a new line.
+ */
+
+static void NetToText_put_character(HTStream *me, int net_char)
+{
+ char c = (char) FROMASCII(net_char);
+
+ if (me->had_cr) {
+ if (c == LF) {
+ me->sink->isa->put_character(me->sink, '\n'); /* Newline */
+ me->had_cr = NO;
+ return;
+ } else {
+ me->sink->isa->put_character(me->sink, CR); /* leftover */
+ }
+ }
+ me->had_cr = (BOOL) (c == CR);
+ if (!me->had_cr)
+ me->sink->isa->put_character(me->sink, c); /* normal */
+}
+
+static void NetToText_put_string(HTStream *me, const char *s)
+{
+ const char *p;
+
+ for (p = s; *p; p++)
+ NetToText_put_character(me, *p);
+}
+
+static void NetToText_put_block(HTStream *me, const char *s, int l)
+{
+ const char *p;
+
+ for (p = s; p < (s + l); p++)
+ NetToText_put_character(me, *p);
+}
+
+static void NetToText_free(HTStream *me)
+{
+ (me->sink->isa->_free) (me->sink); /* Close rest of pipe */
+ FREE(me);
+}
+
+static void NetToText_abort(HTStream *me, HTError e)
+{
+ me->sink->isa->_abort(me->sink, e); /* Abort rest of pipe */
+ FREE(me);
+}
+
+/* The class structure
+*/
+static HTStreamClass NetToTextClass =
+{
+ "NetToText",
+ NetToText_free,
+ NetToText_abort,
+ NetToText_put_character,
+ NetToText_put_string,
+ NetToText_put_block
+};
+
+/* The creation method
+*/
+HTStream *HTNetToText(HTStream *sink)
+{
+ HTStream *me = typecalloc(HTStream);
+
+ if (me == NULL)
+ outofmem(__FILE__, "NetToText");
+
+ me->isa = &NetToTextClass;
+
+ me->had_cr = NO;
+ me->sink = sink;
+ return me;
+}
+
+static HTStream HTBaseStreamInstance; /* Made static */
+
+/*
+ * ERROR STREAM
+ * ------------
+ * There is only one error stream shared by anyone who wants a
+ * generic error returned from all stream methods.
+ */
+static void HTErrorStream_put_character(HTStream *me GCC_UNUSED, int c GCC_UNUSED)
+{
+ LYCancelDownload = TRUE;
+}
+
+static void HTErrorStream_put_string(HTStream *me GCC_UNUSED, const char *s)
+{
+ if (s && *s)
+ LYCancelDownload = TRUE;
+}
+
+static void HTErrorStream_write(HTStream *me GCC_UNUSED, const char *s, int l)
+{
+ if (l && s)
+ LYCancelDownload = TRUE;
+}
+
+static void HTErrorStream_free(HTStream *me GCC_UNUSED)
+{
+ return;
+}
+
+static void HTErrorStream_abort(HTStream *me GCC_UNUSED, HTError e GCC_UNUSED)
+{
+ return;
+}
+
+static const HTStreamClass HTErrorStreamClass =
+{
+ "ErrorStream",
+ HTErrorStream_free,
+ HTErrorStream_abort,
+ HTErrorStream_put_character,
+ HTErrorStream_put_string,
+ HTErrorStream_write
+};
+
+HTStream *HTErrorStream(void)
+{
+ CTRACE((tfp, "ErrorStream. Created\n"));
+ HTBaseStreamInstance.isa = &HTErrorStreamClass; /* The rest is random */
+ return &HTBaseStreamInstance;
+}