about summary refs log tree commit diff stats
path: root/WWW/Library/Implementation
diff options
context:
space:
mode:
authorThomas E. Dickey <dickey@invisible-island.net>2022-03-30 00:29:50 +0000
committerThomas E. Dickey <dickey@invisible-island.net>2022-03-30 00:29:50 +0000
commit04fd5be50c369e986053e7bfcc4b9eb2fa5ac937 (patch)
tree840e3e3dca02952345abbb2614b27ddbb0025bd5 /WWW/Library/Implementation
parentbaa72f144c15896a40c794b967854f0508459a20 (diff)
downloadlynx-snapshots-04fd5be50c369e986053e7bfcc4b9eb2fa5ac937.tar.gz
snapshot of project "lynx", label v2-9-0dev_10d
Diffstat (limited to 'WWW/Library/Implementation')
-rw-r--r--WWW/Library/Implementation/HTFTP.c5
-rw-r--r--WWW/Library/Implementation/HTFile.c87
-rw-r--r--WWW/Library/Implementation/HTFile.h4
-rw-r--r--WWW/Library/Implementation/HTFormat.c251
-rw-r--r--WWW/Library/Implementation/HTFormat.h24
-rw-r--r--WWW/Library/Implementation/HTTP.c5
6 files changed, 350 insertions, 26 deletions
diff --git a/WWW/Library/Implementation/HTFTP.c b/WWW/Library/Implementation/HTFTP.c
index 092c2076..cb058cc9 100644
--- a/WWW/Library/Implementation/HTFTP.c
+++ b/WWW/Library/Implementation/HTFTP.c
@@ -1,5 +1,5 @@
 /*
- * $LynxId: HTFTP.c,v 1.143 2022/03/12 12:06:13 tom Exp $
+ * $LynxId: HTFTP.c,v 1.144 2022/03/17 20:04:17 tom Exp $
  *
  *			File Transfer Protocol (FTP) Client
  *			for a WorldWideWeb browser
@@ -4089,6 +4089,9 @@ int HTFTPLoad(const char *name,
 	    case cftBzip2:
 		StrAllocCopy(anchor->content_encoding, "x-bzip2");
 		break;
+	    case cftBrotli:
+		StrAllocCopy(anchor->content_encoding, "x-brotli");
+		break;
 	    case cftNone:
 		break;
 	    }
diff --git a/WWW/Library/Implementation/HTFile.c b/WWW/Library/Implementation/HTFile.c
index 1624e5c2..4da14c1a 100644
--- a/WWW/Library/Implementation/HTFile.c
+++ b/WWW/Library/Implementation/HTFile.c
@@ -1,5 +1,5 @@
 /*
- * $LynxId: HTFile.c,v 1.152 2019/08/16 22:53:10 tom Exp $
+ * $LynxId: HTFile.c,v 1.153 2022/03/29 23:48:38 tom Exp $
  *
  *			File Access				HTFile.c
  *			===========
@@ -1281,9 +1281,14 @@ CompressFileType HTCompressFileType(const char *filename,
 	len = strlen(filename);
 	ftype = filename + len;
 
-	if ((len > 4)
-	    && !strcasecomp((ftype - 3), "bz2")
-	    && StrChr(dots, ftype[-4]) != 0) {
+	if ((len > 3)
+	    && !strcasecomp((ftype - 2), "br")
+	    && StrChr(dots, ftype[-3]) != 0) {
+	    result = cftBrotli;
+	    ftype -= 3;
+	} else if ((len > 4)
+		   && !strcasecomp((ftype - 3), "bz2")
+		   && StrChr(dots, ftype[-4]) != 0) {
 	    result = cftBzip2;
 	    ftype -= 4;
 	} else if ((len > 3)
@@ -1335,6 +1340,9 @@ const char *HTCompressTypeToSuffix(CompressFileType method)
     case cftDeflate:
 	result = ".zz";
 	break;
+    case cftBrotli:
+	result = ".br";
+	break;
     }
     return result;
 }
@@ -1363,6 +1371,9 @@ const char *HTCompressTypeToEncoding(CompressFileType method)
     case cftDeflate:
 	result = "deflate";
 	break;
+    case cftBrotli:
+	result = "brotli";
+	break;
     }
     return result;
 }
@@ -1390,6 +1401,10 @@ CompressFileType HTEncodingToCompressType(const char *coding)
     } else if (!strcasecomp(coding, "bzip2") ||
 	       !strcasecomp(coding, "x-bzip2")) {
 	result = cftBzip2;
+    } else if (!strcasecomp(coding, "br") ||	/* actual */
+	       !strcasecomp(coding, "brotli") ||	/* expected */
+	       !strcasecomp(coding, "x-brotli")) {
+	result = cftBrotli;
     } else if (!strcasecomp(coding, "deflate") ||
 	       !strcasecomp(coding, "x-deflate")) {
 	result = cftDeflate;
@@ -1412,6 +1427,10 @@ CompressFileType HTContentTypeToCompressType(const char *ct)
     } else if (!strncasecomp(ct, "application/bzip2", 17) ||
 	       !strncasecomp(ct, "application/x-bzip2", 19)) {
 	method = cftBzip2;
+    } else if (!strncasecomp(ct, "application/br", 14) ||
+	       !strncasecomp(ct, "application/brotli", 18) ||
+	       !strncasecomp(ct, "application/x-brotli", 20)) {
+	method = cftBrotli;
     }
     return method;
 }
@@ -2420,6 +2439,15 @@ static BOOL isBzip2Stream(FILE *fp)
 #define DOT_STRING "."
 #endif
 
+#ifdef USE_BROTLI
+static FILE *
+brotli_open(const char *localname, const char *mode)
+{
+    CTRACE((tfp, "brotli_open file=%s, mode=%s\n", localname, mode));
+    return fopen(localname, mode);
+}
+#endif
+
 static int decompressAndParse(HTParentAnchor *anchor,
 			      HTFormat format_out,
 			      HTStream *sink,
@@ -2437,7 +2465,10 @@ static int decompressAndParse(HTParentAnchor *anchor,
 #endif /* USE_ZLIB */
 #ifdef USE_BZLIB
     BZFILE *bzfp = 0;
-#endif /* USE_ZLIB */
+#endif /* USE_BZLIB */
+#ifdef USE_BROTLI
+    FILE *brfp = 0;
+#endif /* USE_BROTLI */
 #if defined(USE_ZLIB) || defined(USE_BZLIB)
     CompressFileType internal_decompress = cftNone;
     BOOL failed_decompress = NO;
@@ -2536,6 +2567,17 @@ static int decompressAndParse(HTParentAnchor *anchor,
 		internal_decompress = cftBzip2;
 	    } else
 #endif /* USE_BZLIB */
+#ifdef USE_BROTLI
+	    if (isDOWNLOAD(cftBrotli)) {
+		fclose(fp);
+		fp = 0;
+		brfp = brotli_open(localname, BIN_R);
+
+		CTRACE((tfp, "HTLoadFile: brotli_open of `%s' gives %p\n",
+			localname, (void *) brfp));
+		internal_decompress = cftBrotli;
+	    } else
+#endif /* USE_BROTLI */
 	    {
 		StrAllocCopy(anchor->content_type, format->name);
 		StrAllocCopy(anchor->content_encoding, HTAtom_name(myEncoding));
@@ -2614,6 +2656,22 @@ static int decompressAndParse(HTParentAnchor *anchor,
 		format = HTAtom_for("www/compressed");
 #endif /* USE_BZLIB */
 		break;
+	    case cftBrotli:
+		StrAllocCopy(anchor->content_encoding, "x-brotli");
+#ifdef USE_BROTLI
+		if (strcmp(format_out->name, "www/download") != 0) {
+		    fclose(fp);
+		    fp = 0;
+		    brfp = brotli_open(localname, BIN_R);
+
+		    CTRACE((tfp, "HTLoadFile: brotli_open of `%s' gives %p\n",
+			    localname, (void *) brfp));
+		    internal_decompress = cftBrotli;
+		}
+#else /* USE_BROTLI */
+		format = HTAtom_for("www/compressed");
+#endif /* USE_BROTLI */
+		break;
 	    case cftNone:
 		break;
 	    }
@@ -2635,6 +2693,11 @@ static int decompressAndParse(HTParentAnchor *anchor,
 		failed_decompress = (BOOLEAN) (bzfp == NULL);
 		break;
 #endif
+#ifdef USE_BROTLI
+	    case cftBrotli:
+		failed_decompress = (BOOLEAN) (brfp == NULL);
+		break;
+#endif
 	    default:
 		failed_decompress = YES;
 		break;
@@ -2666,6 +2729,12 @@ static int decompressAndParse(HTParentAnchor *anchor,
 		if (sugfname && *sugfname)
 		    StrAllocCopy(anchor->SugFname, sugfname);
 		FREE(sugfname);
+#ifdef USE_BROTLI
+		if (brfp)
+		    *statusp = HTParseBrFile(format, format_out,
+					     anchor,
+					     brfp, sink);
+#endif
 #ifdef USE_BZLIB
 		if (bzfp)
 		    *statusp = HTParseBzFile(format, format_out,
@@ -2954,6 +3023,9 @@ int HTLoadFile(const char *addr,
 			case cftBzip2:
 			    atomname = "application/x-bzip2";
 			    break;
+			case cftBrotli:
+			    atomname = "application/x-brotli";
+			    break;
 			case cftNone:
 			    break;
 			}
@@ -3182,6 +3254,11 @@ void HTInitProgramPaths(BOOL init)
 
     for (n = (int) ppUnknown + 1; n < (int) pp_Last; ++n) {
 	switch (code = (ProgramPaths) n) {
+#ifdef BROTLI_PATH
+	case ppBROTLI:
+	    path = BROTLI_PATH;
+	    break;
+#endif
 #ifdef BZIP2_PATH
 	case ppBZIP2:
 	    path = BZIP2_PATH;
diff --git a/WWW/Library/Implementation/HTFile.h b/WWW/Library/Implementation/HTFile.h
index 204f5404..0bdfb794 100644
--- a/WWW/Library/Implementation/HTFile.h
+++ b/WWW/Library/Implementation/HTFile.h
@@ -1,5 +1,5 @@
 /*
- * $LynxId: HTFile.h,v 1.34 2020/01/21 22:08:07 tom Exp $
+ * $LynxId: HTFile.h,v 1.35 2021/07/29 22:54:21 tom Exp $
  *							File access in libwww
  *				FILE ACCESS
  *
@@ -213,6 +213,7 @@ extern "C" {
 	,cftGzip
 	,cftBzip2
 	,cftDeflate
+	,cftBrotli
     } CompressFileType;
 
 /*
@@ -307,6 +308,7 @@ extern "C" {
  */
     typedef enum {
 	ppUnknown = 0
+	,ppBROTLI
 	,ppBZIP2
 	,ppCHMOD
 	,ppCOMPRESS
diff --git a/WWW/Library/Implementation/HTFormat.c b/WWW/Library/Implementation/HTFormat.c
index a1ad71ae..4f48e5d5 100644
--- a/WWW/Library/Implementation/HTFormat.c
+++ b/WWW/Library/Implementation/HTFormat.c
@@ -1,5 +1,5 @@
 /*
- * $LynxId: HTFormat.c,v 1.92 2022/03/12 14:40:38 tom Exp $
+ * $LynxId: HTFormat.c,v 1.95 2022/03/30 00:29:50 tom Exp $
  *
  *		Manage different file formats			HTFormat.c
  *		=============================
@@ -57,6 +57,10 @@ static float HTMaxSecs = 1e10;	/* No effective limit */
 #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 */
@@ -375,6 +379,8 @@ static HTPresentation *HTFindPresentation(HTFormat rep_in,
 					  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;
@@ -385,7 +391,7 @@ static HTPresentation *HTFindPresentation(HTFormat rep_in,
     HTPresentation *last_default_match = 0;
     HTPresentation *strong_subtype_wildcard_match = 0;
 
-    CTRACE((tfp, "HTFormat: Looking up presentation for %s to %s\n",
+    CTRACE((tfp, THIS_FUNC ": Looking up presentation for %s to %s\n",
 	    HTAtom_name(rep_in), HTAtom_name(rep_out)));
 
     n = HTList_count(HTPresentations);
@@ -395,7 +401,7 @@ static HTPresentation *HTFindPresentation(HTFormat rep_in,
 	    if (pres->rep_out == rep_out) {
 		if (failsMailcap(pres, anchor))
 		    continue;
-		CTRACE((tfp, "FindPresentation: found exact match: %s -> %s\n",
+		CTRACE((tfp, THIS_FUNC ": found exact match: %s -> %s\n",
 			HTAtom_name(pres->rep),
 			HTAtom_name(pres->rep_out)));
 		return pres;
@@ -411,8 +417,8 @@ static HTPresentation *HTFindPresentation(HTFormat rep_in,
 		    if (!strong_wildcard_match)
 			strong_wildcard_match = pres;
 		    /* otherwise use the first one */
-		    CTRACE((tfp,
-			    "StreamStack: found strong wildcard match: %s -> %s\n",
+		    CTRACE((tfp, THIS_FUNC
+			    ": found strong wildcard match: %s -> %s\n",
 			    HTAtom_name(pres->rep),
 			    HTAtom_name(pres->rep_out)));
 		}
@@ -429,8 +435,8 @@ static HTPresentation *HTFindPresentation(HTFormat rep_in,
 		if (!strong_subtype_wildcard_match)
 		    strong_subtype_wildcard_match = pres;
 		/* otherwise use the first one */
-		CTRACE((tfp,
-			"StreamStack: found strong subtype wildcard match: %s -> %s\n",
+		CTRACE((tfp, THIS_FUNC
+			": found strong subtype wildcard match: %s -> %s\n",
 			HTAtom_name(pres->rep),
 			HTAtom_name(pres->rep_out)));
 	    }
@@ -444,7 +450,7 @@ static HTPresentation *HTFindPresentation(HTFormat rep_in,
 		    weak_wildcard_match = pres;
 		/* otherwise use the first one */
 		CTRACE((tfp,
-			"StreamStack: found weak wildcard match: %s\n",
+			THIS_FUNC ": found weak wildcard match: %s\n",
 			HTAtom_name(pres->rep_out)));
 
 	    } else if (!last_default_match) {
@@ -476,6 +482,7 @@ static HTPresentation *HTFindPresentation(HTFormat rep_in,
     }
 
     return NULL;
+#undef THIS_FUNC
 }
 
 /*		Create a filter stack
@@ -493,11 +500,13 @@ HTStream *HTStreamStack(HTFormat rep_in,
 			HTStream *sink,
 			HTParentAnchor *anchor)
 {
+#undef THIS_FUNC
+#define THIS_FUNC "HTStreamStack"
     HTPresentation temp;
     HTPresentation *match;
     HTStream *result;
 
-    CTRACE((tfp, "StreamStack: Constructing stream stack for %s to %s (%s)\n",
+    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)));
@@ -507,9 +516,9 @@ HTStream *HTStreamStack(HTFormat rep_in,
 
     } else if ((match = HTFindPresentation(rep_in, rep_out, &temp, anchor))) {
 	if (match == &temp) {
-	    CTRACE((tfp, "StreamStack: Using %s\n", HTAtom_name(temp.rep_out)));
+	    CTRACE((tfp, THIS_FUNC ": Using %s\n", HTAtom_name(temp.rep_out)));
 	} else {
-	    CTRACE((tfp, "StreamStack: found exact match: %s -> %s\n",
+	    CTRACE((tfp, THIS_FUNC ": found exact match: %s -> %s\n",
 		    HTAtom_name(match->rep),
 		    HTAtom_name(match->rep_out)));
 	}
@@ -519,15 +528,16 @@ HTStream *HTStreamStack(HTFormat rep_in,
     }
     if (TRACE) {
 	if (result && result->isa && result->isa->name) {
-	    CTRACE((tfp, "StreamStack: Returning \"%s\"\n", result->isa->name));
+	    CTRACE((tfp, THIS_FUNC ": Returning \"%s\"\n", result->isa->name));
 	} else if (result) {
-	    CTRACE((tfp, "StreamStack: Returning *unknown* stream!\n"));
+	    CTRACE((tfp, THIS_FUNC ": Returning *unknown* stream!\n"));
 	} else {
-	    CTRACE((tfp, "StreamStack: Returning NULL!\n"));
+	    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
@@ -590,7 +600,7 @@ void HTFilterPresentations(void)
 /*		Find the cost of a filter stack
  *		-------------------------------
  *
- *	Must return the cost of the same stack which StreamStack would set up.
+ *	Must return the cost of the same stack which HTStreamStack would set up.
  *
  * On entry,
  *	length	The size of the data to be converted
@@ -1330,6 +1340,142 @@ static int HTBzFileCopy(BZFILE * bzfp, HTStream *sink)
 }
 #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
  *	--------------------------------------------------
  *
@@ -1817,6 +1963,81 @@ int HTParseBzFile(HTFormat rep_in,
 }
 #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		bzfp closed; target freed, aborted, or NULL.
+ */
+int HTParseBrFile(HTFormat rep_in,
+		  HTFormat format_out,
+		  HTParentAnchor *anchor,
+		  FILE *brfp,
+		  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(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 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 = 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;
+}
+#endif /* USE_BROTLI */
+
 /*	Converter stream: Network Telnet to internal character text
  *	-----------------------------------------------------------
  *
diff --git a/WWW/Library/Implementation/HTFormat.h b/WWW/Library/Implementation/HTFormat.h
index 20e718a8..76a8b253 100644
--- a/WWW/Library/Implementation/HTFormat.h
+++ b/WWW/Library/Implementation/HTFormat.h
@@ -1,5 +1,5 @@
 /*
- * $LynxId: HTFormat.h,v 1.37 2020/01/21 22:02:59 tom Exp $
+ * $LynxId: HTFormat.h,v 1.38 2022/03/28 08:09:44 tom Exp $
  *
  *                                            HTFormat: The format manager in the WWW Library
  *                          MANAGE DIFFERENT DOCUMENT FORMATS
@@ -229,10 +229,12 @@ The HTPresentation and HTConverter types
 	,encodingDEFLATE = 2
 	,encodingCOMPRESS = 4
 	,encodingBZIP2 = 8
+	,encodingBROTLI = 16
 	,encodingALL = (encodingGZIP
 			+ encodingDEFLATE
 			+ encodingCOMPRESS
-			+ encodingBZIP2)
+			+ encodingBZIP2
+			+ encodingBROTLI)
     } AcceptEncoding;
 
 /*
@@ -512,7 +514,7 @@ HTParseZzFile: Parse a deflate'd File through a file pointer
 HTParseBzFile: Parse a bzip2'ed File through a file pointer
 
    This routine is called by protocols modules to load an object.  uses
-   HTStreamStack and HTGzFileCopy.  Returns HT_LOADED if successful, can also
+   HTStreamStack and HTBzFileCopy.  Returns HT_LOADED if successful, can also
    return HT_PARTIAL_CONTENT, HT_NO_DATA, or other <0 for failure.
  */
     extern int HTParseBzFile(HTFormat format_in,
@@ -523,6 +525,22 @@ HTParseBzFile: Parse a bzip2'ed File through a file pointer
 
 #endif				/* USE_BZLIB */
 
+#ifdef USE_BROTLI
+/*
+HTParseBzFile: Parse a brotli'ed File through a file pointer
+
+   This routine is called by protocols modules to load an object.  uses
+   HTStreamStack and HTBrFileCopy.  Returns HT_LOADED if successful, can also
+   return HT_PARTIAL_CONTENT, HT_NO_DATA, or other <0 for failure.
+ */
+    extern int HTParseBrFile(HTFormat format_in,
+			     HTFormat format_out,
+			     HTParentAnchor *anchor,
+			     FILE * brfp,
+			     HTStream *sink);
+
+#endif				/* USE_BROTLI */
+
 /*
 
 HTNetToText: Convert Net ASCII to local representation
diff --git a/WWW/Library/Implementation/HTTP.c b/WWW/Library/Implementation/HTTP.c
index e405e46f..46f1924e 100644
--- a/WWW/Library/Implementation/HTTP.c
+++ b/WWW/Library/Implementation/HTTP.c
@@ -1,5 +1,5 @@
 /*
- * $LynxId: HTTP.c,v 1.181 2021/11/04 22:15:26 Sylvain.Bertrand Exp $
+ * $LynxId: HTTP.c,v 1.182 2022/03/17 20:02:41 tom Exp $
  *
  * HyperText Transfer Protocol	- Client implementation		HTTP.c
  * ===========================
@@ -710,6 +710,9 @@ static BOOL acceptEncoding(int code)
 	case encodingBZIP2:
 	    program = HTGetProgramPath(ppBZIP2);
 	    break;
+	case encodingBROTLI:
+	    program = HTGetProgramPath(ppBROTLI);
+	    break;
 	default:
 	    break;
 	}