Revision history for FCGI-Buffer
 
0.23	Fri Oct 30 02:52:42 PM EDT 2026
	[ Security ]
	- Pages for clients that have logged in (Authorization or REMOTE_USER)
		and POST requests are no longer cached or saved
	- Responses with Set-Cookie, or a Cache-Control of no-store, no-cache or
		private, are no longer cached or saved.  A Set-Cookie header was
		being replayed to other clients
	- A Vary on anything other than Accept-Encoding (or Accept-Language with
		lingua) now stops a response from being cached or saved.  The
		code that was meant to honour it could never run
	- save_to: cookies now stop a page from being saved when a cache_key is
		given
	- save_to: stored cross-site scripting through REQUEST_URI
	- A page that includes the value of the client's csrf_token cookie is no
		longer cached or saved
	- A client's Cache-Control: max-age=1abc caused an empty response, and
		max-age=99999999 kept pages in the cache for years.  Invalid
		values and those over 600 seconds are now ignored
	- save_to: max_pages, default 10000, stops clients from filling the disc
	- optimise_content: an unterminated <title> took quadratic time

	[ Bug Fixes ]
	- The ETag of a page that wasn't in the cache was worked out before its
		links were changed to point to static pages, so a client with
		the old links could be given a 304 and keep them
	- If-Modified-Since was looked at before If-None-Match for a page in the
		cache, so a 304 could be sent for a page that had changed
	- Several Expires headers could be sent, and they were kept in the cache
	- With generate_304 off, If-None-Match could still give a 304 for a page in
		the cache
	- save_to: static pages are written atomically, a reader could be given a
		partial page and a full disc wasn't noticed
	- save_to: links to a page weren't changed if they gave its arguments in
		a different order
	- Range: when there is no cache, a part of a page is now cut from the page
		with its links changed, so that it matches the ETag
	- A csrf_token cookie no longer stops a page from being cached or saved.
		It is different for each client but doesn't change the output
	- An exception while sending the page lost the output, it could also
		leave all that the process then printed going to a dead buffer.
		Failures of the cache, the database or the logger now give a
		warning and the page is still sent
	- UTF-8 that a script printed as bytes was encoded twice when compressed
		or written by save_to
	- Entries in the cache were used after they had expired
	- save_to: pages that had passed their ttl were never updated or deleted,
		and were sent with an Expires header in the past
	- save_to: only HTML is saved, other types were corrupted
	- save_to: virtual hosts sharing a directory linked to each other's pages
	- HTML with no </html>, which suggests that the script died, isn't stored
	- optimise_content: changed the contents of <pre> and <textarea>
	- A Content-Length from the script was kept when the body was changed
	- Output for which the script gave a Content-Encoding was compressed again
	- Headers ending in LF gave duplicate Status and Content-Length headers
	- Range: a Status header from the script stopped the 206 being sent, so
		part of the page was sent as a 200 or a 404.  Ranges were worked
		out in characters, and applied to POST requests.  A part of a
		page is no longer compressed, and If-Range is honoured
	- A 304 no longer also has the script's Status: 200 or a Content-Encoding
	- If-None-Match matching on other than GET and HEAD gives 412, not 304, and
		If-Modified-Since is ignored for them
	- If-Modified-Since could turn a 404 into a 304, and was used when
		If-None-Match had failed if there was no cache
	- Compressed and uncompressed output had the same strong ETag
	- Vary: Accept-Encoding wasn't sent with uncompressed output
	- ETags weren't generated for HTTP/2 and HTTP/3 in some spellings
	- init() returned undef if save_to couldn't be used, so chained calls
		died.  It now gives one warning and carries on without save_to.
		Arguments to save_to are checked
	- new() now checks the cache, logger and save_to arguments in the same way
	- A logger that doesn't understand all of the methods is ignored
	- Removed import(), which died if given any arguments
	- Text::Diff was used without loading it safely, and Storable might not
		have been loaded
	- Test::Log::Abstraction, which needs Perl 5.26, is no longer required to
		install this module
	- Raised MIN_PERL_VERSION to 5.012, which the latest DBI needs,
		CGI::Info to 0.71 and added DBD::SQLite to the prerequisites
	- save_to: links to a page from its own static copy pointed to the
		file's location on disk rather than its URL
	- save_to: requests with no arguments were saved as a hidden ".html" file
	- save_to: wrong URLs were generated when DOCUMENT_ROOT ended in "/" or
		the save_to directory wasn't under the document root, such links
		are now left alone
	- save_to: characters such as "%", "&", "\\" and spaces in arguments
		weren't escaped in URLs, and arguments differing only by spaces
		shared a static page
	- save_to: links containing ";", "&amp;" or regex metacharacters such as
		"+" weren't matched, and a "(" in the URL caused a crash
	- save_to: the language was recorded differently when saving and looking
		up pages, so links weren't rewritten for languages such as
		"English (United Kingdom)", and looking up pages died without lingua
	- save_to: SQL injection through the URL
	- save_to: don't save pages that depend on the client's cookies or Vary
		headers, since the static page would be served to everyone
	- optimise_content: links to the script with no arguments became href="",
		which reloads the current page, and the greedy match could span
		attributes
	- optimise_content: links to other sites whose names start with ours
		(e.g. www.example.com.evil.org) were made local
	- PATH_INFO is now part of the generated cache key
	- Google Analytics cookies after the first weren't excluded from the
		cache key
	- optimise_content: several regexes took quadratic time on long runs
		of whitespace or unterminated links, allowing a slow down by
		hostile input
	- optimise_content: "+" at the start of a line was deleted, breaking
		JavaScript
	- optimise_content: document.write() calls in different <script>
		blocks could be merged, and the script's type was changed
	- optimise_content: text of an HTML comment after a <script> tag was
		displayed
	- optimise_content: no longer removes visible spaces between tags,
		e.g. "Hello <b>world</b>" became "Hello<b>world</b>"
	- Range: "bytes=0-n" was ignored and ranges were a byte short, multiple
		and backwards ranges are now ignored rather than half applied
	- Range: "bytes=-n" gave the first n bytes rather than the last n
		(RFC 7233)
	- Range: unsatisfiable ranges, e.g. starting after the end, now give a
		416 rather than a warning, and 206 and 416 responses include
		Content-Range
	- Accept-Encoding with a space before a comma wasn't understood
	- If the first encoding the client accepts couldn't be loaded (e.g.
		IO::Compress::Brotli isn't installed), a 406 was sent instead of
		trying the next, or sending it uncompressed
	- Accept-Encoding parameters, e.g. "gzip;q=0.8", weren't understood, and
		q=0 wasn't honoured.  x-gzip is now supported.
	- Content-Type "Text/html" wasn't compressed
	- zstd compression could die if Encode wasn't loaded
	- Cache-Control with more than one directive (e.g. "no-cache, max-age=0")
		or in upper case didn't stop the cache being used
	- If-None-Match with a list of ETags, weak ETags or "*" wasn't
		understood, and an undefined ETag gave a warning
	- An ETag in the script's own headers lost its quotes when read from the
		cache, so never matched If-None-Match
	- new(generate_etag => 0) was overridden by the server protocol
	- Removed a branch which did nothing but look in the cache, and dead
		debugging code

	[ Documentation ]
	- API and formal specifications for the public methods
	- Documented character encoding, failures and how output is sent
	- Corrected what's said about NO_CACHE, the logger, Last-Modified, Range
		and when save_to works

	[ Tests ]
	- Use Test::Log::Abstraction
	- Added t/save_to.t, t/optimise.t, t/logic.t and t/regressions.t

0.22	Sat Apr 18 20:55:55 EDT 2026
	Fix uninitialized variable
	More helpful error message
	Reduce chance of key name clash

0.21	Fri Mar 21 17:08:18 EDT 2025
	Added support for Zstd
	Handle the new CGI::Info::as_string()

0.20	Thu Dec 19 07:12:02 EST 2024
	Use Readonly
	cache_age is now known as cache_duration
	IO::Compress::Brotli doesn't work on FreeBSD or Arm Linux
	Set CGI::Info::status() if available
	Added CircleCI integration
	Improve setting CGI::Info status
	Use Test::DescribeMe to simplify tests
	Use Test::Needs
	Use gtar to create a distribution on Macs

0.19	Fri Sep  1 21:28:40 EDT 2023
	Fix uninitialised variable
	Ensure 403 statuses are printed
	IO::Compress::Brotli doesn't work on OpenBSD

0.18	Sun Feb 26 17:06:17 EST 2023
	Make debugging more readable
	Log correctly when data isn't retrieved from the cache
	Call logger instead of warn where possible

0.17	Thu Jan 20 11:00:19 EST 2022
	save_to: support '/' in an argument
	Fix where ETags were not being added with HTTP/2.0
	Fix installation as root

0.16	Wed Jun  2 09:05:38 EDT 2021
	Try harder to mkdir the save_to directory
	Fixed handling when save_to->ttl is not given
	Fix problem where Last-Modified header not set when no cache
	Generate 304 on non-cached files
	Don't warn about ETag not being added when NO_CACHE is set

0.15	Fri  3 Jan 17:05:38 EST 2020
	IO::Compress:Brotli hasn't been ported to Windows
	Added sanity checking to the save_to->directory parameter

0.14	Mon 10 Jun 14:18:31 EDT 2019
	Untaint the language part of the save_to directory

0.13	Sat Feb 23 20:17:41 EST 2019
	Fix copy and paste error

0.12	Sat Feb 23 13:24:56 EST 2019
	Get the return status from the CGI::Info object
	Optimise lists a bit more
	Optmise <center>
	Added t/links.t and t/no404s.t

0.11	Wed Oct  4 08:52:44 EDT 2017
	Work with the latest IO::Compress::Brotli
	Allow save_to to be disabled after it was enabled

0.10	Sun Aug 27 13:28:11 EDT 2017
	Remove false positive bug reports

0.09	Sat 21 Jan 09:42:42 EST 2017
	Replace dynamic links to static links on subsequent calls
	In /cgi-bin/foo.fcgi?arg1=a replace references to
		/cgi-bin/foo.fcgi?arg2=b to ?arg2=b
	Honour the order of HTTP_ACCEPT_ENCODING
	Improved handling of HTTP status
	init() returns the object itself so you can now chain messages
	Put different languages in different caches
	On HTTP sites, replace href=http://www.example.com with href=www.example.com

0.08	Tue Oct  4 10:06:22 EDT 2016
	Print stack trace when calling init too late
	Support Brotli

0.07	Sat Dec  5 15:03:56 EST 2015
	Fix RT110011
	If you give a cache to init() then later give cache => undef,
		the server side cache is no longer used.

0.06	Mon Oct 12 18:41:13 EDT 2015
	Fix on older perls

0.05	Sun Aug 16 20:19:13 EDT 2015
	generate_key - honour the vary header
	Fix problem were server side cached contents weren't compressed when
		loading the content from the cache

0.04	Fri May 22 12:19:38 EDT 2015
	Added t/nocache.t
	Fix t/cache.t when CHI is not installed

0.03	Sun May 17 08:43:42 EDT 2015
	Added X-Cache and X-Cache-Lookup headers
	Fixed overly agressive optimisation
	Don't use the cache if the client sends max-age=0
	Added cache_age option to init()
	Removed Test::TempDir RT102225
	Added t/cache.t
	Fix '(in cleanup) Can't call method "READ" on an undefined value'
		Perl >= 5.14.0 only
	Improved handling of Wide characters

0.02	Sat Jan 31 11:16:28 EST 2015
	Fix set_options
	Fix handling when content-type isn't set

0.01	Thu Jan 29 15:42:06 EST 2015
	First draft, clone from CGI::Buffer and object orient the code
